diff --git a/doc/classes/AcceptDialog.xml b/doc/classes/AcceptDialog.xml index 202a84fb580..59cbb380363 100644 --- a/doc/classes/AcceptDialog.xml +++ b/doc/classes/AcceptDialog.xml @@ -72,6 +72,7 @@ The text displayed by the dialog. + The text displayed by the OK button (see [method get_ok_button]). diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 6cb049e0e49..7c46b75d411 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1291,6 +1291,15 @@ Returns the size of the window specified by [param window_id] (in pixels), including the borders drawn by the operating system. See also [method window_get_size]. + + + + + + Returns the estimated window title bar size (including text and window buttons) for the window specified by [param window_id] (in pixels). This method does not change the window title. + [b]Note:[/b] This method is implemented on macOS and Windows. + + diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index b03ef3ab4e8..8f697ead8f2 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -593,6 +593,9 @@ Specifies the initial type of position for the [Window]. See [enum WindowInitialPosition] constants. + + If [code]true[/code], the [Window] width is expanded to keep the title bar text fully visible. + If non-zero, the [Window] can't be resized to be bigger than this size. [b]Note:[/b] This property will be ignored if the value is lower than [member min_size]. diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index c03b4765f88..d89511927e7 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -368,6 +368,7 @@ public: virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_title_size(const String &p_title, WindowID p_window) const override; virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID) override; virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 56cb4938ec5..4e1ceac8bf5 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2626,6 +2626,47 @@ void DisplayServerMacOS::window_set_title(const String &p_title, WindowID p_wind [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; } +Size2i DisplayServerMacOS::window_get_title_size(const String &p_title, WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + Size2i size; + ERR_FAIL_COND_V(!windows.has(p_window), size); + + const WindowData &wd = windows[p_window]; + if (wd.fullscreen || wd.borderless) { + return size; + } + if ([wd.window_object respondsToSelector:@selector(isMiniaturized)]) { + if ([wd.window_object isMiniaturized]) { + return size; + } + } + + float scale = screen_get_max_scale(); + + if (wd.window_button_view) { + size.x = ([wd.window_button_view getOffset].x + [wd.window_button_view frame].size.width); + size.y = ([wd.window_button_view getOffset].y + [wd.window_button_view frame].size.height); + } else { + NSButton *cb = [wd.window_object standardWindowButton:NSWindowCloseButton]; + NSButton *mb = [wd.window_object standardWindowButton:NSWindowMiniaturizeButton]; + float cb_frame = NSMinX([cb frame]); + float mb_frame = NSMinX([mb frame]); + bool is_rtl = ([wd.window_object windowTitlebarLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft); + + float window_buttons_spacing = (is_rtl) ? (cb_frame - mb_frame) : (mb_frame - cb_frame); + size.x = window_buttons_spacing * 4; + size.y = [cb frame].origin.y + [cb frame].size.height; + } + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont titleBarFontOfSize:0], NSFontAttributeName, nil]; + NSSize text_size = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:p_title.utf8().get_data()] attributes:attributes] size]; + size.x += text_size.width; + size.y = MAX(size.y, text_size.height); + + return size * scale; +} + void DisplayServerMacOS::window_set_mouse_passthrough(const Vector &p_region, WindowID p_window) { _THREAD_SAFE_METHOD_ diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 1a1bba833d7..70c6fe6549e 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1191,6 +1191,51 @@ void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_wi SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data())); } +Size2i DisplayServerWindows::window_get_title_size(const String &p_title, WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + Size2i size; + ERR_FAIL_COND_V(!windows.has(p_window), size); + + const WindowData &wd = windows[p_window]; + if (wd.fullscreen || wd.minimized || wd.borderless) { + return size; + } + + HDC hdc = GetDCEx(wd.hWnd, NULL, DCX_WINDOW); + if (hdc) { + Char16String s = p_title.utf16(); + SIZE text_size; + if (GetTextExtentPoint32W(hdc, (LPCWSTR)(s.get_data()), s.length(), &text_size)) { + size.x = text_size.cx; + size.y = text_size.cy; + } + + ReleaseDC(wd.hWnd, hdc); + } + RECT rect; + if (DwmGetWindowAttribute(wd.hWnd, DWMWA_CAPTION_BUTTON_BOUNDS, &rect, sizeof(RECT)) == S_OK) { + if (rect.right - rect.left > 0) { + ClientToScreen(wd.hWnd, (POINT *)&rect.left); + ClientToScreen(wd.hWnd, (POINT *)&rect.right); + + if (win81p_PhysicalToLogicalPointForPerMonitorDPI) { + win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.left); + win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.right); + } + + size.x += (rect.right - rect.left); + size.y = MAX(size.y, rect.bottom - rect.top); + } + } + if (icon.is_valid()) { + size.x += 32; + } else { + size.x += 16; + } + return size; +} + void DisplayServerWindows::window_set_mouse_passthrough(const Vector &p_region, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -4385,6 +4430,7 @@ bool DisplayServerWindows::winink_available = false; GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr; GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr; +PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr; typedef enum _SHC_PROCESS_DPI_AWARENESS { SHC_PROCESS_DPI_UNAWARE = 0, @@ -4522,6 +4568,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI"); + win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI"); winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo; } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 28f2f7e6ffa..48c8c20280f 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -262,6 +262,7 @@ typedef struct tagPOINTER_PEN_INFO { typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type); typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info); typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint); +typedef BOOL(WINAPI *PhysicalToLogicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint); typedef struct { BYTE bWidth; // Width, in pixels, of the image @@ -309,6 +310,7 @@ class DisplayServerWindows : public DisplayServer { // DPI conversion API static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI; + static PhysicalToLogicalPointForPerMonitorDPIPtr win81p_PhysicalToLogicalPointForPerMonitorDPI; void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver); String tablet_driver; @@ -569,6 +571,7 @@ public: virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID) override; virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index aff0ed6f066..957a8f276ee 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -405,6 +405,7 @@ AcceptDialog::AcceptDialog() { set_transient(true); set_exclusive(true); set_clamp_to_embedder(true); + set_keep_title_visible(true); bg_panel = memnew(Panel); add_child(bg_panel, false, INTERNAL_MODE_FRONT); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index f5468b29c5c..71ec9b29c5d 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -270,20 +270,21 @@ void Window::set_title(const String &p_title) { ERR_MAIN_THREAD_GUARD; title = p_title; + tr_title = atr(p_title); +#ifdef DEBUG_ENABLED + if (window_id == DisplayServer::MAIN_WINDOW_ID) { + // Append a suffix to the window title to denote that the project is running + // from a debug build (including the editor). Since this results in lower performance, + // this should be clearly presented to the user. + tr_title = vformat("%s (DEBUG)", tr_title); + } +#endif if (embedder) { embedder->_sub_window_update(this); } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { - String tr_title = atr(p_title); -#ifdef DEBUG_ENABLED - if (window_id == DisplayServer::MAIN_WINDOW_ID) { - // Append a suffix to the window title to denote that the project is running - // from a debug build (including the editor). Since this results in lower performance, - // this should be clearly presented to the user. - tr_title = vformat("%s (DEBUG)", tr_title); - } -#endif DisplayServer::get_singleton()->window_set_title(tr_title, window_id); + _update_window_size(); } } @@ -586,15 +587,6 @@ void Window::_make_window() { DisplayServer::get_singleton()->window_set_max_size(Size2i(), window_id); DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id); DisplayServer::get_singleton()->window_set_mouse_passthrough(mpath, window_id); - String tr_title = atr(title); -#ifdef DEBUG_ENABLED - if (window_id == DisplayServer::MAIN_WINDOW_ID) { - // Append a suffix to the window title to denote that the project is running - // from a debug build (including the editor). Since this results in lower performance, - // this should be clearly presented to the user. - tr_title = vformat("%s (DEBUG)", tr_title); - } -#endif DisplayServer::get_singleton()->window_set_title(tr_title, window_id); DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id); @@ -994,6 +986,12 @@ void Window::_update_window_size() { } DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id); + + if (keep_title_visible) { + Size2i title_size = DisplayServer::get_singleton()->window_get_title_size(tr_title, window_id); + size_limit = size_limit.max(title_size); + } + DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id); DisplayServer::get_singleton()->window_set_size(size, window_id); } @@ -1281,17 +1279,19 @@ void Window::_notification(int p_what) { _invalidate_theme_cache(); _update_theme_item_cache(); - if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) { - String tr_title = atr(title); + tr_title = atr(title); #ifdef DEBUG_ENABLED - if (window_id == DisplayServer::MAIN_WINDOW_ID) { - // Append a suffix to the window title to denote that the project is running - // from a debug build (including the editor). Since this results in lower performance, - // this should be clearly presented to the user. - tr_title = vformat("%s (DEBUG)", tr_title); - } + if (window_id == DisplayServer::MAIN_WINDOW_ID) { + // Append a suffix to the window title to denote that the project is running + // from a debug build (including the editor). Since this results in lower performance, + // this should be clearly presented to the user. + tr_title = vformat("%s (DEBUG)", tr_title); + } #endif + + if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) { DisplayServer::get_singleton()->window_set_title(tr_title, window_id); + _update_window_size(); } } break; @@ -1384,6 +1384,20 @@ Window::ContentScaleStretch Window::get_content_scale_stretch() const { return content_scale_stretch; } +void Window::set_keep_title_visible(bool p_title_visible) { + if (keep_title_visible == p_title_visible) { + return; + } + keep_title_visible = p_title_visible; + if (window_id != DisplayServer::INVALID_WINDOW_ID) { + _update_window_size(); + } +} + +bool Window::get_keep_title_visible() const { + return keep_title_visible; +} + void Window::set_content_scale_factor(real_t p_factor) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_COND(p_factor <= 0); @@ -2713,6 +2727,9 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_content_scale_stretch", "stretch"), &Window::set_content_scale_stretch); ClassDB::bind_method(D_METHOD("get_content_scale_stretch"), &Window::get_content_scale_stretch); + ClassDB::bind_method(D_METHOD("set_keep_title_visible", "title_visible"), &Window::set_keep_title_visible); + ClassDB::bind_method(D_METHOD("get_keep_title_visible"), &Window::get_keep_title_visible); + ClassDB::bind_method(D_METHOD("set_content_scale_factor", "factor"), &Window::set_content_scale_factor); ClassDB::bind_method(D_METHOD("get_content_scale_factor"), &Window::get_content_scale_factor); @@ -2826,6 +2843,7 @@ void Window::_bind_methods() { ADD_GROUP("Limits", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "min_size", PROPERTY_HINT_NONE, "suffix:px"), "set_min_size", "get_min_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "max_size", PROPERTY_HINT_NONE, "suffix:px"), "set_max_size", "get_max_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_title_visible"), "set_keep_title_visible", "get_keep_title_visible"); ADD_GROUP("Content Scale", "content_scale_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size"); diff --git a/scene/main/window.h b/scene/main/window.h index db739cc3047..8a54b6c7d3e 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -111,6 +111,7 @@ private: bool initialized = false; String title; + String tr_title; mutable int current_screen = 0; mutable Vector2i position; mutable Size2i size = Size2i(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE); @@ -131,6 +132,7 @@ private: bool updating_embedded_window = false; bool clamp_to_embedder = false; bool unparent_when_invisible = false; + bool keep_title_visible = false; LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; @@ -336,6 +338,9 @@ public: void set_content_scale_stretch(ContentScaleStretch p_stretch); ContentScaleStretch get_content_scale_stretch() const; + void set_keep_title_visible(bool p_title_visible); + bool get_keep_title_visible() const; + void set_content_scale_factor(real_t p_factor); real_t get_content_scale_factor() const; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index f41238b0754..c8516a0966c 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -696,6 +696,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("window_get_popup_safe_rect", "window"), &DisplayServer::window_get_popup_safe_rect); ClassDB::bind_method(D_METHOD("window_set_title", "title", "window_id"), &DisplayServer::window_set_title, DEFVAL(MAIN_WINDOW_ID)); + ClassDB::bind_method(D_METHOD("window_get_title_size", "title", "window_id"), &DisplayServer::window_get_title_size, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_set_mouse_passthrough", "region", "window_id"), &DisplayServer::window_set_mouse_passthrough, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_get_current_screen", "window_id"), &DisplayServer::window_get_current_screen, DEFVAL(MAIN_WINDOW_ID)); diff --git a/servers/display_server.h b/servers/display_server.h index 85f92706965..71bfd7b6075 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -390,6 +390,7 @@ public: virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) = 0; + virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const { return Size2i(); } virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID);