浏览代码

[macOS extend-to-title] Add scene/project name to the editor title, fix incorrect window button position/order when system primary language is RTL.

bruvzg 3 年之前
父节点
当前提交
0dab11afa4

+ 2 - 0
doc/classes/DisplayServer.xml

@@ -1548,6 +1548,8 @@
 		</constant>
 		</constant>
 		<constant name="WINDOW_EVENT_DPI_CHANGE" value="6" enum="WindowEvent">
 		<constant name="WINDOW_EVENT_DPI_CHANGE" value="6" enum="WindowEvent">
 		</constant>
 		</constant>
+		<constant name="WINDOW_EVENT_TITLEBAR_CHANGE" value="7" enum="WindowEvent">
+		</constant>
 		<constant name="VSYNC_DISABLED" value="0" enum="VSyncMode">
 		<constant name="VSYNC_DISABLED" value="0" enum="VSyncMode">
 			No vertical synchronization, which means the engine will display frames as fast as possible (tearing may be visible).
 			No vertical synchronization, which means the engine will display frames as fast as possible (tearing may be visible).
 		</constant>
 		</constant>

+ 5 - 0
doc/classes/Window.xml

@@ -455,6 +455,11 @@
 				Emitted when the [constant NOTIFICATION_THEME_CHANGED] notification is sent.
 				Emitted when the [constant NOTIFICATION_THEME_CHANGED] notification is sent.
 			</description>
 			</description>
 		</signal>
 		</signal>
+		<signal name="titlebar_changed">
+			<description>
+				Emitted when window title bar decorations are changed, e.g., macOS window enter/exit full screen mode, or extend-to-title flag is changed.
+			</description>
+		</signal>
 		<signal name="visibility_changed">
 		<signal name="visibility_changed">
 			<description>
 			<description>
 				Emitted when [Window] is made visible or disappears.
 				Emitted when [Window] is made visible or disappears.

+ 50 - 20
editor/editor_node.cpp

@@ -429,7 +429,7 @@ void EditorNode::_version_control_menu_option(int p_idx) {
 
 
 void EditorNode::_update_title() {
 void EditorNode::_update_title() {
 	const String appname = ProjectSettings::get_singleton()->get("application/config/name");
 	const String appname = ProjectSettings::get_singleton()->get("application/config/name");
-	String title = (appname.is_empty() ? TTR("Unnamed Project") : appname) + String(" - ") + VERSION_NAME;
+	String title = (appname.is_empty() ? TTR("Unnamed Project") : appname);
 	const String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_scene_file_path() : String();
 	const String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_scene_file_path() : String();
 	if (!edited.is_empty()) {
 	if (!edited.is_empty()) {
 		// Display the edited scene name before the program name so that it can be seen in the OS task bar.
 		// Display the edited scene name before the program name so that it can be seen in the OS task bar.
@@ -439,8 +439,10 @@ void EditorNode::_update_title() {
 		// Display the "modified" mark before anything else so that it can always be seen in the OS task bar.
 		// Display the "modified" mark before anything else so that it can always be seen in the OS task bar.
 		title = vformat("(*) %s", title);
 		title = vformat("(*) %s", title);
 	}
 	}
-
-	DisplayServer::get_singleton()->window_set_title(title);
+	DisplayServer::get_singleton()->window_set_title(title + String(" - ") + VERSION_NAME);
+	if (project_title) {
+		project_title->set_text(title);
+	}
 }
 }
 
 
 void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
 void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
@@ -659,6 +661,12 @@ void EditorNode::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 		case NOTIFICATION_ENTER_TREE: {
 			Engine::get_singleton()->set_editor_hint(true);
 			Engine::get_singleton()->set_editor_hint(true);
 
 
+			Window *window = static_cast<Window *>(get_tree()->get_root());
+			if (window) {
+				// Handle macOS fullscreen and extend-to-title changes.
+				window->connect("titlebar_changed", callable_mp(this, &EditorNode::_titlebar_resized));
+			}
+
 			OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec")));
 			OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec")));
 			get_tree()->get_root()->set_as_audio_listener_3d(false);
 			get_tree()->get_root()->set_as_audio_listener_3d(false);
 			get_tree()->get_root()->set_as_audio_listener_2d(false);
 			get_tree()->get_root()->set_as_audio_listener_2d(false);
@@ -713,6 +721,8 @@ void EditorNode::_notification(int p_what) {
 				ProjectSettings::get_singleton()->save();
 				ProjectSettings::get_singleton()->save();
 			}
 			}
 
 
+			_titlebar_resized();
+
 			/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
 			/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
 		} break;
 		} break;
 
 
@@ -1170,6 +1180,18 @@ void EditorNode::_reload_project_settings() {
 void EditorNode::_vp_resized() {
 void EditorNode::_vp_resized() {
 }
 }
 
 
+void EditorNode::_titlebar_resized() {
+	const Size2 &margin = DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID);
+	if (left_menu_spacer) {
+		int w = (gui_base->is_layout_rtl()) ? margin.y : margin.x;
+		left_menu_spacer->set_custom_minimum_size(Size2(w, 0));
+	}
+	if (right_menu_spacer) {
+		int w = (gui_base->is_layout_rtl()) ? margin.x : margin.y;
+		right_menu_spacer->set_custom_minimum_size(Size2(w, 0));
+	}
+}
+
 void EditorNode::_version_button_pressed() {
 void EditorNode::_version_button_pressed() {
 	DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY));
 	DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY));
 }
 }
@@ -3390,7 +3412,7 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed
 		tb->add_theme_font_size_override("font_size", singleton->gui_base->get_theme_font_size(SNAME("main_button_font_size"), SNAME("EditorFonts")));
 		tb->add_theme_font_size_override("font_size", singleton->gui_base->get_theme_font_size(SNAME("main_button_font_size"), SNAME("EditorFonts")));
 
 
 		singleton->main_editor_buttons.push_back(tb);
 		singleton->main_editor_buttons.push_back(tb);
-		singleton->main_editor_button_vb->add_child(tb);
+		singleton->main_editor_button_hb->add_child(tb);
 		singleton->editor_table.push_back(p_editor);
 		singleton->editor_table.push_back(p_editor);
 
 
 		singleton->distraction_free->move_to_front();
 		singleton->distraction_free->move_to_front();
@@ -6590,14 +6612,14 @@ EditorNode::EditorNode() {
 
 
 	if (can_expand) {
 	if (can_expand) {
 		// Add spacer to avoid other controls under window minimize/maximize/close buttons (left side).
 		// Add spacer to avoid other controls under window minimize/maximize/close buttons (left side).
-		Control *menu_spacer = memnew(Control);
-		menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
-		menu_spacer->set_custom_minimum_size(Size2(DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID).x, 0));
-		menu_hb->add_child(menu_spacer);
+		left_menu_spacer = memnew(Control);
+		left_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+		menu_hb->add_child(left_menu_spacer);
 	}
 	}
 
 
 	main_menu = memnew(MenuBar);
 	main_menu = memnew(MenuBar);
 	menu_hb->add_child(main_menu);
 	menu_hb->add_child(main_menu);
+
 	main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
 	main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
 	main_menu->set_flat(true);
 	main_menu->set_flat(true);
 	main_menu->set_start_index(0); // Main menu, add to the start of global menu.
 	main_menu->set_start_index(0); // Main menu, add to the start of global menu.
@@ -6766,22 +6788,30 @@ EditorNode::EditorNode() {
 	project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true);
 	project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true);
 
 
 	// Spacer to center 2D / 3D / Script buttons.
 	// Spacer to center 2D / 3D / Script buttons.
-	Control *left_spacer = memnew(Control);
+	HBoxContainer *left_spacer = memnew(HBoxContainer);
 	left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
 	left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+	left_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	menu_hb->add_child(left_spacer);
 	menu_hb->add_child(left_spacer);
 
 
-	menu_hb->add_spacer();
+	if (can_expand && global_menu) {
+		project_title = memnew(Label);
+		project_title->add_theme_font_override("font", gui_base->get_theme_font(SNAME("bold"), SNAME("EditorFonts")));
+		project_title->add_theme_font_size_override("font_size", gui_base->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")));
+		project_title->set_focus_mode(Control::FOCUS_NONE);
+		project_title->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
+		project_title->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
+		project_title->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+		left_spacer->add_child(project_title);
+	}
 
 
-	main_editor_button_vb = memnew(HBoxContainer);
-	menu_hb->add_child(main_editor_button_vb);
+	main_editor_button_hb = memnew(HBoxContainer);
+	menu_hb->add_child(main_editor_button_hb);
 
 
 	// Options are added and handled by DebuggerEditorPlugin.
 	// Options are added and handled by DebuggerEditorPlugin.
 	debug_menu = memnew(PopupMenu);
 	debug_menu = memnew(PopupMenu);
 	debug_menu->set_name(TTR("Debug"));
 	debug_menu->set_name(TTR("Debug"));
 	main_menu->add_child(debug_menu);
 	main_menu->add_child(debug_menu);
 
 
-	menu_hb->add_spacer();
-
 	settings_menu = memnew(PopupMenu);
 	settings_menu = memnew(PopupMenu);
 	settings_menu->set_name(TTR("Editor"));
 	settings_menu->set_name(TTR("Editor"));
 	main_menu->add_child(settings_menu);
 	main_menu->add_child(settings_menu);
@@ -6855,6 +6885,7 @@ EditorNode::EditorNode() {
 	// Spacer to center 2D / 3D / Script buttons.
 	// Spacer to center 2D / 3D / Script buttons.
 	Control *right_spacer = memnew(Control);
 	Control *right_spacer = memnew(Control);
 	right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
 	right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+	right_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	menu_hb->add_child(right_spacer);
 	menu_hb->add_child(right_spacer);
 
 
 	launch_pad = memnew(PanelContainer);
 	launch_pad = memnew(PanelContainer);
@@ -6965,10 +6996,9 @@ EditorNode::EditorNode() {
 
 
 	if (can_expand) {
 	if (can_expand) {
 		// Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side).
 		// Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side).
-		Control *menu_spacer = memnew(Control);
-		menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
-		menu_spacer->set_custom_minimum_size(Size2(DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID).y, 0));
-		menu_hb->add_child(menu_spacer);
+		right_menu_spacer = memnew(Control);
+		right_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+		menu_hb->add_child(right_menu_spacer);
 	}
 	}
 
 
 	String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method");
 	String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method");
@@ -7523,9 +7553,9 @@ EditorNode::EditorNode() {
 	screenshot_timer->set_owner(get_owner());
 	screenshot_timer->set_owner(get_owner());
 
 
 	// Adjust spacers to center 2D / 3D / Script buttons.
 	// Adjust spacers to center 2D / 3D / Script buttons.
-	int max_w = MAX(launch_pad_hb->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu->get_minimum_size().x);
+	int max_w = MAX(launch_pad->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu->get_minimum_size().x);
 	left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu->get_minimum_size().x), 0));
 	left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu->get_minimum_size().x), 0));
-	right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - launch_pad_hb->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0));
+	right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - launch_pad->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0));
 
 
 	// Extend menu bar to window title.
 	// Extend menu bar to window title.
 	if (can_expand) {
 	if (can_expand) {

+ 5 - 1
editor/editor_node.h

@@ -322,6 +322,9 @@ private:
 	HBoxContainer *bottom_hb = nullptr;
 	HBoxContainer *bottom_hb = nullptr;
 	Control *vp_base = nullptr;
 	Control *vp_base = nullptr;
 
 
+	Label *project_title = nullptr;
+	Control *left_menu_spacer = nullptr;
+	Control *right_menu_spacer = nullptr;
 	EditorTitleBar *menu_hb = nullptr;
 	EditorTitleBar *menu_hb = nullptr;
 	VBoxContainer *main_screen_vbox = nullptr;
 	VBoxContainer *main_screen_vbox = nullptr;
 	MenuBar *main_menu = nullptr;
 	MenuBar *main_menu = nullptr;
@@ -397,7 +400,7 @@ private:
 	String current_path;
 	String current_path;
 	MenuButton *update_spinner = nullptr;
 	MenuButton *update_spinner = nullptr;
 
 
-	HBoxContainer *main_editor_button_vb = nullptr;
+	HBoxContainer *main_editor_button_hb = nullptr;
 	Vector<Button *> main_editor_buttons;
 	Vector<Button *> main_editor_buttons;
 	Vector<EditorPlugin *> editor_table;
 	Vector<EditorPlugin *> editor_table;
 
 
@@ -560,6 +563,7 @@ private:
 	void _close_messages();
 	void _close_messages();
 	void _show_messages();
 	void _show_messages();
 	void _vp_resized();
 	void _vp_resized();
+	void _titlebar_resized();
 	void _version_button_pressed();
 	void _version_button_pressed();
 
 
 	int _save_external_resources();
 	int _save_external_resources();

+ 16 - 15
platform/macos/display_server_macos.mm

@@ -2664,21 +2664,17 @@ Vector2i DisplayServerMacOS::window_get_safe_title_margins(WindowID p_window) co
 	ERR_FAIL_COND_V(!windows.has(p_window), Vector2i());
 	ERR_FAIL_COND_V(!windows.has(p_window), Vector2i());
 	const WindowData &wd = windows[p_window];
 	const WindowData &wd = windows[p_window];
 
 
-	float max_x = 0.f;
-	NSButton *cb = [wd.window_object standardWindowButton:NSWindowCloseButton];
-	if (cb) {
-		max_x = MAX(max_x, [cb frame].origin.x + [cb frame].size.width);
-	}
-	NSButton *mb = [wd.window_object standardWindowButton:NSWindowMiniaturizeButton];
-	if (mb) {
-		max_x = MAX(max_x, [mb frame].origin.x + [mb frame].size.width);
-	}
-	NSButton *zb = [wd.window_object standardWindowButton:NSWindowZoomButton];
-	if (zb) {
-		max_x = MAX(max_x, [zb frame].origin.x + [zb frame].size.width);
+	if (!wd.window_button_view) {
+		return Vector2i();
 	}
 	}
 
 
-	return Vector2i(max_x * screen_get_max_scale(), 0);
+	float max_x = wd.wb_offset.x + [wd.window_button_view frame].size.width;
+
+	if ([wd.window_object windowTitlebarLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft) {
+		return Vector2i(0, max_x * screen_get_max_scale());
+	} else {
+		return Vector2i(max_x * screen_get_max_scale(), 0);
+	}
 }
 }
 
 
 void DisplayServerMacOS::window_set_custom_window_buttons(WindowData &p_wd, bool p_enabled) {
 void DisplayServerMacOS::window_set_custom_window_buttons(WindowData &p_wd, bool p_enabled) {
@@ -2687,7 +2683,11 @@ void DisplayServerMacOS::window_set_custom_window_buttons(WindowData &p_wd, bool
 		p_wd.window_button_view = nil;
 		p_wd.window_button_view = nil;
 	}
 	}
 	if (p_enabled) {
 	if (p_enabled) {
-		float window_buttons_spacing = NSMinX([[p_wd.window_object standardWindowButton:NSWindowMiniaturizeButton] frame]) - NSMinX([[p_wd.window_object standardWindowButton:NSWindowCloseButton] frame]);
+		float cb_frame = NSMinX([[p_wd.window_object standardWindowButton:NSWindowCloseButton] frame]);
+		float mb_frame = NSMinX([[p_wd.window_object standardWindowButton:NSWindowMiniaturizeButton] frame]);
+		bool is_rtl = ([p_wd.window_object windowTitlebarLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft);
+
+		float window_buttons_spacing = (is_rtl) ? (cb_frame - mb_frame) : (mb_frame - cb_frame);
 
 
 		[p_wd.window_object setTitleVisibility:NSWindowTitleHidden];
 		[p_wd.window_object setTitleVisibility:NSWindowTitleHidden];
 		[[p_wd.window_object standardWindowButton:NSWindowZoomButton] setHidden:YES];
 		[[p_wd.window_object standardWindowButton:NSWindowZoomButton] setHidden:YES];
@@ -2695,7 +2695,7 @@ void DisplayServerMacOS::window_set_custom_window_buttons(WindowData &p_wd, bool
 		[[p_wd.window_object standardWindowButton:NSWindowCloseButton] setHidden:YES];
 		[[p_wd.window_object standardWindowButton:NSWindowCloseButton] setHidden:YES];
 
 
 		p_wd.window_button_view = [[GodotButtonView alloc] initWithFrame:NSZeroRect];
 		p_wd.window_button_view = [[GodotButtonView alloc] initWithFrame:NSZeroRect];
-		[p_wd.window_button_view initButtons:window_buttons_spacing offset:NSMakePoint(p_wd.wb_offset.x, p_wd.wb_offset.y)];
+		[p_wd.window_button_view initButtons:window_buttons_spacing offset:NSMakePoint(p_wd.wb_offset.x, p_wd.wb_offset.y) rtl:is_rtl];
 		[p_wd.window_view addSubview:p_wd.window_button_view];
 		[p_wd.window_view addSubview:p_wd.window_button_view];
 	} else {
 	} else {
 		[p_wd.window_object setTitleVisibility:NSWindowTitleVisible];
 		[p_wd.window_object setTitleVisibility:NSWindowTitleVisible];
@@ -2741,6 +2741,7 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
 				}
 				}
 			}
 			}
 			[wd.window_object setFrame:rect display:YES];
 			[wd.window_object setFrame:rect display:YES];
+			send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_TITLEBAR_CHANGE);
 		} break;
 		} break;
 		case WINDOW_FLAG_BORDERLESS: {
 		case WINDOW_FLAG_BORDERLESS: {
 			// OrderOut prevents a lose focus bug with the window.
 			// OrderOut prevents a lose focus bug with the window.

+ 2 - 1
platform/macos/godot_button_view.h

@@ -41,9 +41,10 @@
 	NSPoint offset;
 	NSPoint offset;
 	CGFloat spacing;
 	CGFloat spacing;
 	bool mouse_in_group;
 	bool mouse_in_group;
+	bool rtl;
 }
 }
 
 
-- (void)initButtons:(CGFloat)button_spacing offset:(NSPoint)button_offset;
+- (void)initButtons:(CGFloat)button_spacing offset:(NSPoint)button_offset rtl:(bool)is_rtl;
 - (void)displayButtons;
 - (void)displayButtons;
 
 
 @end
 @end

+ 17 - 6
platform/macos/godot_button_view.mm

@@ -39,15 +39,17 @@
 	offset = NSMakePoint(8, 8);
 	offset = NSMakePoint(8, 8);
 	spacing = 20;
 	spacing = 20;
 	mouse_in_group = false;
 	mouse_in_group = false;
+	rtl = false;
 
 
 	return self;
 	return self;
 }
 }
 
 
-- (void)initButtons:(CGFloat)button_spacing offset:(NSPoint)button_offset {
+- (void)initButtons:(CGFloat)button_spacing offset:(NSPoint)button_offset rtl:(bool)is_rtl {
 	spacing = button_spacing;
 	spacing = button_spacing;
+	rtl = is_rtl;
 
 
 	NSButton *close_button = [NSWindow standardWindowButton:NSWindowCloseButton forStyleMask:NSWindowStyleMaskTitled];
 	NSButton *close_button = [NSWindow standardWindowButton:NSWindowCloseButton forStyleMask:NSWindowStyleMaskTitled];
-	[close_button setFrameOrigin:NSMakePoint(0, 0)];
+	[close_button setFrameOrigin:NSMakePoint(rtl ? spacing * 2 : 0, 0)];
 	[self addSubview:close_button];
 	[self addSubview:close_button];
 
 
 	NSButton *miniaturize_button = [NSWindow standardWindowButton:NSWindowMiniaturizeButton forStyleMask:NSWindowStyleMaskTitled];
 	NSButton *miniaturize_button = [NSWindow standardWindowButton:NSWindowMiniaturizeButton forStyleMask:NSWindowStyleMaskTitled];
@@ -55,13 +57,17 @@
 	[self addSubview:miniaturize_button];
 	[self addSubview:miniaturize_button];
 
 
 	NSButton *zoom_button = [NSWindow standardWindowButton:NSWindowZoomButton forStyleMask:NSWindowStyleMaskTitled];
 	NSButton *zoom_button = [NSWindow standardWindowButton:NSWindowZoomButton forStyleMask:NSWindowStyleMaskTitled];
-	[zoom_button setFrameOrigin:NSMakePoint(spacing * 2, 0)];
+	[zoom_button setFrameOrigin:NSMakePoint(rtl ? 0 : spacing * 2, 0)];
 	[self addSubview:zoom_button];
 	[self addSubview:zoom_button];
 
 
 	offset.y = button_offset.y - zoom_button.frame.size.height / 2;
 	offset.y = button_offset.y - zoom_button.frame.size.height / 2;
 	offset.x = button_offset.x - zoom_button.frame.size.width / 2;
 	offset.x = button_offset.x - zoom_button.frame.size.width / 2;
 
 
-	[self setFrameSize:NSMakeSize(zoom_button.frame.origin.x + zoom_button.frame.size.width, zoom_button.frame.size.height)];
+	if (rtl) {
+		[self setFrameSize:NSMakeSize(close_button.frame.origin.x + close_button.frame.size.width, close_button.frame.size.height)];
+	} else {
+		[self setFrameSize:NSMakeSize(zoom_button.frame.origin.x + zoom_button.frame.size.width, zoom_button.frame.size.height)];
+	}
 	[self displayButtons];
 	[self displayButtons];
 }
 }
 
 
@@ -70,8 +76,13 @@
 		return;
 		return;
 	}
 	}
 
 
-	[self setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
-	[self setFrameOrigin:NSMakePoint(offset.x, self.window.frame.size.height - self.frame.size.height - offset.y)];
+	if (rtl) {
+		[self setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
+		[self setFrameOrigin:NSMakePoint(self.window.frame.size.width - self.frame.size.width - offset.x, self.window.frame.size.height - self.frame.size.height - offset.y)];
+	} else {
+		[self setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
+		[self setFrameOrigin:NSMakePoint(offset.x, self.window.frame.size.height - self.frame.size.height - offset.y)];
+	}
 }
 }
 
 
 - (BOOL)_mouseInGroup:(NSButton *)button {
 - (BOOL)_mouseInGroup:(NSButton *)button {

+ 2 - 0
platform/macos/godot_window.h

@@ -38,9 +38,11 @@
 
 
 @interface GodotWindow : NSWindow {
 @interface GodotWindow : NSWindow {
 	DisplayServer::WindowID window_id;
 	DisplayServer::WindowID window_id;
+	NSTimeInterval anim_duration;
 }
 }
 
 
 - (void)setWindowID:(DisplayServer::WindowID)wid;
 - (void)setWindowID:(DisplayServer::WindowID)wid;
+- (void)setAnimDuration:(NSTimeInterval)duration;
 
 
 @end
 @end
 
 

+ 13 - 0
platform/macos/godot_window.mm

@@ -37,9 +37,22 @@
 - (id)init {
 - (id)init {
 	self = [super init];
 	self = [super init];
 	window_id = DisplayServer::INVALID_WINDOW_ID;
 	window_id = DisplayServer::INVALID_WINDOW_ID;
+	anim_duration = -1.0f;
 	return self;
 	return self;
 }
 }
 
 
+- (void)setAnimDuration:(NSTimeInterval)duration {
+	anim_duration = duration;
+}
+
+- (NSTimeInterval)animationResizeTime:(NSRect)newFrame {
+	if (anim_duration > 0) {
+		return anim_duration;
+	} else {
+		return [super animationResizeTime:newFrame];
+	}
+}
+
 - (void)setWindowID:(DisplayServerMacOS::WindowID)wid {
 - (void)setWindowID:(DisplayServerMacOS::WindowID)wid {
 	window_id = wid;
 	window_id = wid;
 }
 }

+ 2 - 0
platform/macos/godot_window_delegate.h

@@ -38,6 +38,8 @@
 
 
 @interface GodotWindowDelegate : NSObject <NSWindowDelegate> {
 @interface GodotWindowDelegate : NSObject <NSWindowDelegate> {
 	DisplayServer::WindowID window_id;
 	DisplayServer::WindowID window_id;
+	NSRect old_frame;
+	NSWindowStyleMask old_style_mask;
 }
 }
 
 
 - (void)setWindowID:(DisplayServer::WindowID)wid;
 - (void)setWindowID:(DisplayServer::WindowID)wid;

+ 51 - 5
platform/macos/godot_window_delegate.mm

@@ -32,6 +32,7 @@
 
 
 #include "display_server_macos.h"
 #include "display_server_macos.h"
 #include "godot_button_view.h"
 #include "godot_button_view.h"
+#include "godot_window.h"
 
 
 @implementation GodotWindowDelegate
 @implementation GodotWindowDelegate
 
 
@@ -69,6 +70,26 @@
 	ds->window_destroy(window_id);
 	ds->window_destroy(window_id);
 }
 }
 
 
+- (NSArray<NSWindow *> *)customWindowsToEnterFullScreenForWindow:(NSWindow *)window {
+	DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return nullptr;
+	}
+
+	old_frame = [window frame];
+	old_style_mask = [window styleMask];
+
+	NSMutableArray<NSWindow *> *windows = [[NSMutableArray alloc] init];
+	[windows addObject:window];
+
+	return windows;
+}
+
+- (void)window:(NSWindow *)window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration {
+	[(GodotWindow *)window setAnimDuration:duration];
+	[window setFrame:[[window screen] frame] display:YES animate:YES];
+}
+
 - (void)windowDidEnterFullScreen:(NSNotification *)notification {
 - (void)windowDidEnterFullScreen:(NSNotification *)notification {
 	DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
 	DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
 	if (!ds || !ds->has_window(window_id)) {
 	if (!ds || !ds->has_window(window_id)) {
@@ -81,6 +102,7 @@
 	// Reset window size limits.
 	// Reset window size limits.
 	[wd.window_object setContentMinSize:NSMakeSize(0, 0)];
 	[wd.window_object setContentMinSize:NSMakeSize(0, 0)];
 	[wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
 	[wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+	[(GodotWindow *)wd.window_object setAnimDuration:-1.0f];
 
 
 	// Reset custom window buttons.
 	// Reset custom window buttons.
 	if ([wd.window_object styleMask] & NSWindowStyleMaskFullSizeContentView) {
 	if ([wd.window_object styleMask] & NSWindowStyleMaskFullSizeContentView) {
@@ -89,6 +111,33 @@
 
 
 	// Force window resize event.
 	// Force window resize event.
 	[self windowDidResize:notification];
 	[self windowDidResize:notification];
+	ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_TITLEBAR_CHANGE);
+}
+
+- (NSArray<NSWindow *> *)customWindowsToExitFullScreenForWindow:(NSWindow *)window {
+	DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+	if (!ds || !ds->has_window(window_id)) {
+		return nullptr;
+	}
+
+	DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
+
+	// Restore custom window buttons.
+	if ([wd.window_object styleMask] & NSWindowStyleMaskFullSizeContentView) {
+		ds->window_set_custom_window_buttons(wd, true);
+	}
+
+	ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_TITLEBAR_CHANGE);
+
+	NSMutableArray<NSWindow *> *windows = [[NSMutableArray alloc] init];
+	[windows addObject:wd.window_object];
+	return windows;
+}
+
+- (void)window:(NSWindow *)window startCustomAnimationToExitFullScreenWithDuration:(NSTimeInterval)duration {
+	[(GodotWindow *)window setAnimDuration:duration];
+	[window setStyleMask:old_style_mask];
+	[window setFrame:old_frame display:YES animate:YES];
 }
 }
 
 
 - (void)windowDidExitFullScreen:(NSNotification *)notification {
 - (void)windowDidExitFullScreen:(NSNotification *)notification {
@@ -100,6 +149,8 @@
 	DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
 	DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
 	wd.fullscreen = false;
 	wd.fullscreen = false;
 
 
+	[(GodotWindow *)wd.window_object setAnimDuration:-1.0f];
+
 	// Set window size limits.
 	// Set window size limits.
 	const float scale = ds->screen_get_max_scale();
 	const float scale = ds->screen_get_max_scale();
 	if (wd.min_size != Size2i()) {
 	if (wd.min_size != Size2i()) {
@@ -111,11 +162,6 @@
 		[wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
 		[wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
 	}
 	}
 
 
-	// Restore custom window buttons.
-	if ([wd.window_object styleMask] & NSWindowStyleMaskFullSizeContentView) {
-		ds->window_set_custom_window_buttons(wd, true);
-	}
-
 	// Restore resizability state.
 	// Restore resizability state.
 	if (wd.resize_disabled) {
 	if (wd.resize_disabled) {
 		[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
 		[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];

+ 4 - 0
scene/main/window.cpp

@@ -389,6 +389,9 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
 			_propagate_window_notification(this, NOTIFICATION_WM_DPI_CHANGE);
 			_propagate_window_notification(this, NOTIFICATION_WM_DPI_CHANGE);
 			emit_signal(SNAME("dpi_changed"));
 			emit_signal(SNAME("dpi_changed"));
 		} break;
 		} break;
+		case DisplayServer::WINDOW_EVENT_TITLEBAR_CHANGE: {
+			emit_signal(SNAME("titlebar_changed"));
+		} break;
 	}
 	}
 }
 }
 
 
@@ -1782,6 +1785,7 @@ void Window::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("visibility_changed"));
 	ADD_SIGNAL(MethodInfo("visibility_changed"));
 	ADD_SIGNAL(MethodInfo("about_to_popup"));
 	ADD_SIGNAL(MethodInfo("about_to_popup"));
 	ADD_SIGNAL(MethodInfo("theme_changed"));
 	ADD_SIGNAL(MethodInfo("theme_changed"));
+	ADD_SIGNAL(MethodInfo("titlebar_changed"));
 
 
 	BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED);
 	BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED);
 	BIND_CONSTANT(NOTIFICATION_THEME_CHANGED);
 	BIND_CONSTANT(NOTIFICATION_THEME_CHANGED);

+ 1 - 0
servers/display_server.cpp

@@ -824,6 +824,7 @@ void DisplayServer::_bind_methods() {
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_CLOSE_REQUEST);
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_CLOSE_REQUEST);
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_GO_BACK_REQUEST);
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_GO_BACK_REQUEST);
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_DPI_CHANGE);
 	BIND_ENUM_CONSTANT(WINDOW_EVENT_DPI_CHANGE);
+	BIND_ENUM_CONSTANT(WINDOW_EVENT_TITLEBAR_CHANGE);
 
 
 	BIND_ENUM_CONSTANT(VSYNC_DISABLED);
 	BIND_ENUM_CONSTANT(VSYNC_DISABLED);
 	BIND_ENUM_CONSTANT(VSYNC_ENABLED);
 	BIND_ENUM_CONSTANT(VSYNC_ENABLED);

+ 1 - 0
servers/display_server.h

@@ -336,6 +336,7 @@ public:
 		WINDOW_EVENT_CLOSE_REQUEST,
 		WINDOW_EVENT_CLOSE_REQUEST,
 		WINDOW_EVENT_GO_BACK_REQUEST,
 		WINDOW_EVENT_GO_BACK_REQUEST,
 		WINDOW_EVENT_DPI_CHANGE,
 		WINDOW_EVENT_DPI_CHANGE,
+		WINDOW_EVENT_TITLEBAR_CHANGE,
 	};
 	};
 	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
 	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
 	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
 	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;