diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index e268872beb83..020d44b0a31c 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -106,6 +106,7 @@ #include "editor/gui/editor_bottom_panel.h" #include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_quick_open_dialog.h" +#include "editor/gui/editor_scroll_box.h" #include "editor/gui/editor_title_bar.h" #include "editor/gui/editor_toaster.h" #include "editor/gui/progress_dialog.h" @@ -365,6 +366,7 @@ void EditorNode::_update_title() { DisplayServer::get_singleton()->window_set_title(title + String(" - ") + GODOT_VERSION_NAME); if (project_title) { project_title->set_text(title); + _adjust_project_title(); } } @@ -1480,14 +1482,13 @@ void EditorNode::_reload_project_settings() { ProjectSettings::get_singleton()->setup(ProjectSettings::get_singleton()->get_resource_path(), String(), true, true); } -void EditorNode::_vp_resized() { -} - void EditorNode::_viewport_resized() { Window *w = get_window(); if (w) { was_window_windowed_last = w->get_mode() == Window::MODE_WINDOWED; } + + callable_mp(this, &EditorNode::_adjust_project_title).call_deferred(); } void EditorNode::_titlebar_resized() { @@ -1504,6 +1505,30 @@ void EditorNode::_titlebar_resized() { if (title_bar) { title_bar->set_custom_minimum_size(Size2(0, margin.z - title_bar->get_global_position().y)); } + + callable_mp(this, &EditorNode::_adjust_project_title).call_deferred(); +} + +void EditorNode::_adjust_project_title() { + if (!project_title) { + return; + } + + project_title->set_anchors_and_offsets_preset(Control::PRESET_CENTER_LEFT); + const int offset = 28 * EDSCALE; + + if (gui_base->is_layout_rtl()) { + Control *cc = title_bar->get_center_control(); + int init_pos = project_title->get_global_position().x; + HScrollBar *hsb = main_scroll_box->get_scroll_container()->get_h_scroll_bar(); + int scroll_offset = cc->get_size().width - hsb->get_page() - hsb->get_value(); + project_title->set_position(Point2(MIN(0, (cc->get_global_position().x + cc->get_size().width + offset) - init_pos - scroll_offset), project_title->get_position().y)); + project_title->set_size(Size2(MAX(0, Math::abs((cc->get_global_position().x + cc->get_size().width) - scroll_offset - init_pos) - offset), 0)); + } else { + int main_screen_position = title_bar->get_center_control()->get_global_position().x; + int label_size = main_screen_position - project_title->get_global_position().x; + project_title->set_size(Size2(MAX(0, label_size - offset), 0)); + } } void EditorNode::_update_undo_redo_allowed() { @@ -7419,28 +7444,31 @@ void EditorNode::_update_main_menu_type() { main_menu_button->get_popup()->add_theme_constant_override("v_separation", 16 * EDSCALE); menu_btn_spacer = memnew(Control); menu_btn_spacer->set_custom_minimum_size(Vector2(8, 0) * EDSCALE); - title_bar->add_child(menu_btn_spacer); - title_bar->move_child(menu_btn_spacer, left_menu_spacer ? left_menu_spacer->get_index() + 1 : 0); + title_left_hbox->add_child(menu_btn_spacer); + title_left_hbox->move_child(menu_btn_spacer, left_menu_spacer ? left_menu_spacer->get_index() + 1 : 0); #endif - title_bar->add_child(main_menu_button); + title_left_hbox->add_child(main_menu_button); if (menu_btn_spacer == nullptr) { - title_bar->move_child(main_menu_button, left_menu_spacer ? left_menu_spacer->get_index() + 1 : 0); + title_left_hbox->move_child(main_menu_button, left_menu_spacer ? left_menu_spacer->get_index() + 1 : 0); } else { - title_bar->move_child(main_menu_button, menu_btn_spacer->get_index() + 1); + title_left_hbox->move_child(main_menu_button, menu_btn_spacer->get_index() + 1); } memdelete_notnull(main_menu_bar); main_menu_bar = nullptr; - - if (project_run_bar != nullptr) { - // Adjust spacers to center 2D / 3D / Script buttons. - int max_w = MAX(project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu_button->get_minimum_size().x); - left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu_button->get_minimum_size().x), 0)); - right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - project_run_bar->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0)); - } +#ifndef MACOS_ENABLED + memdelete_notnull(menu_scroll_box); + menu_scroll_box = nullptr; +#endif } else { +#ifndef MACOS_ENABLED + menu_scroll_box = memnew(EditorScrollBox); + menu_scroll_box->set_custom_minimum_size(Size2(128 * EDSCALE, 0)); + title_left_hbox->add_child(menu_scroll_box); +#endif main_menu_bar = memnew(MenuBar); main_menu_bar->set_mouse_filter(Control::MOUSE_FILTER_STOP); - main_menu_bar->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + main_menu_bar->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN); + main_menu_bar->set_v_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_CENTER); main_menu_bar->set_theme_type_variation("MainMenuBar"); main_menu_bar->set_start_index(0); // Main menu, add to the start of global menu. main_menu_bar->set_prefer_global_menu(global_menu); @@ -7459,21 +7487,16 @@ void EditorNode::_update_main_menu_type() { main_menu_bar->add_child(menu); } } - - title_bar->add_child(main_menu_bar); - title_bar->move_child(main_menu_bar, left_menu_spacer ? left_menu_spacer->get_index() + 1 : 0); - +#ifndef MACOS_ENABLED + menu_scroll_box->set_control(main_menu_bar); +#else + title_left_hbox->add_child(main_menu_bar); + title_left_hbox->move_child(main_menu_bar, left_menu_spacer ? left_menu_spacer->get_index() + 1 : 0); +#endif memdelete_notnull(menu_btn_spacer); memdelete_notnull(main_menu_button); menu_btn_spacer = nullptr; main_menu_button = nullptr; - - if (project_run_bar != nullptr) { - // Adjust spacers to center 2D / 3D / Script buttons. - int max_w = MAX(project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu_bar->get_minimum_size().x); - left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu_bar->get_minimum_size().x), 0)); - right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - project_run_bar->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0)); - } } } @@ -7654,7 +7677,7 @@ EditorNode::EditorNode() { // Define a minimum window size to prevent UI elements from overlapping or being cut off. Window *w = Object::cast_to(SceneTree::get_singleton()->get_root()); if (w) { - const Size2 minimum_size = Size2(1024, 600) * EDSCALE; + const Size2 minimum_size = Size2(640, 480) * EDSCALE; w->set_min_size(minimum_size); // Calling it this early doesn't sync the property with DS. DisplayServer::get_singleton()->window_set_min_size(minimum_size); } @@ -8012,11 +8035,16 @@ EditorNode::EditorNode() { bool dark_mode = DisplayServer::get_singleton()->is_dark_mode_supported() && DisplayServer::get_singleton()->is_dark_mode(); bool can_expand = bool(EDITOR_GET("interface/editor/expand_to_title")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EXTEND_TO_TITLE); + title_left_hbox = memnew(HBoxContainer); + title_left_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title_left_hbox->set_mouse_filter(Control::MOUSE_FILTER_PASS); + title_bar->add_child(title_left_hbox); + if (can_expand) { // Add spacer to avoid other controls under window minimize/maximize/close buttons (left side). left_menu_spacer = memnew(Control); left_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS); - title_bar->add_child(left_menu_spacer); + title_left_hbox->add_child(left_menu_spacer); } _update_main_menu_type(); @@ -8181,28 +8209,31 @@ EditorNode::EditorNode() { ED_SHORTCUT_OVERRIDE("editor/quit_to_project_list", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + KeyModifierMask::ALT + Key::Q); project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), PROJECT_QUIT_TO_PROJECT_MANAGER, true); - // Spacer to center 2D / 3D / Script buttons. - left_spacer = memnew(HBoxContainer); - left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS); - left_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); - title_bar->add_child(left_spacer); - if (can_expand && global_menu) { + Control *project_title_control = memnew(Control); + project_title_control->set_h_size_flags(Control::SIZE_SHRINK_BEGIN); + project_title_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); project_title = memnew(Label); project_title->add_theme_font_override(SceneStringName(font), theme->get_font(SNAME("bold"), EditorStringName(EditorFonts))); project_title->add_theme_font_size_override(SceneStringName(font_size), theme->get_font_size(SNAME("bold_size"), EditorStringName(EditorFonts))); 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); project_title->set_mouse_filter(Control::MOUSE_FILTER_PASS); - left_spacer->add_child(project_title); + project_title_control->add_child(project_title); + title_left_hbox->add_child(project_title_control, false, INTERNAL_MODE_BACK); } + main_scroll_box = memnew(EditorScrollBox); + main_scroll_box->set_custom_minimum_size(Size2(128 * EDSCALE, 0)); + main_scroll_box->set_stretch_ratio(1.5); + title_bar->add_child(main_scroll_box); + HBoxContainer *main_editor_button_hb = memnew(HBoxContainer); + main_editor_button_hb->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_CENTER); main_editor_button_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP); main_editor_button_hb->set_name("EditorMainScreenButtons"); + main_editor_button_hb->connect(SceneStringName(resized), callable_mp(this, &EditorNode::_adjust_project_title)); editor_main_screen->set_button_container(main_editor_button_hb); - title_bar->add_child(main_editor_button_hb); + main_scroll_box->set_control(main_editor_button_hb); title_bar->set_center_control(main_editor_button_hb); // Options are added and handled by DebuggerEditorPlugin. @@ -8286,22 +8317,18 @@ EditorNode::EditorNode() { } help_menu->add_icon_shortcut(_get_editor_theme_native_menu_icon(SNAME("Heart"), global_menu, dark_mode), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTRC("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT); - // Spacer to center 2D / 3D / Script buttons. - right_spacer = memnew(Control); - right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS); - right_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); - title_bar->add_child(right_spacer); + title_right_hbox = memnew(HBoxContainer); + title_right_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title_right_hbox->set_mouse_filter(Control::MOUSE_FILTER_PASS); + title_bar->add_child(title_right_hbox); project_run_bar = memnew(EditorRunBar); + project_run_bar->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_END); project_run_bar->set_mouse_filter(Control::MOUSE_FILTER_STOP); - title_bar->add_child(project_run_bar); + title_right_hbox->add_child(project_run_bar); project_run_bar->connect("play_pressed", callable_mp(this, &EditorNode::_project_run_started)); project_run_bar->connect("stop_pressed", callable_mp(this, &EditorNode::_project_run_stopped)); - right_menu_hb = memnew(HBoxContainer); - right_menu_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP); - title_bar->add_child(right_menu_hb); - renderer = memnew(OptionButton); renderer->set_visible(true); renderer->set_flat(true); @@ -8313,13 +8340,13 @@ EditorNode::EditorNode() { renderer->set_tooltip_text(TTRC("Choose a rendering method.\n\nNotes:\n- On mobile platforms, the Mobile rendering method is used if Forward+ is selected here.\n- On the web platform, the Compatibility rendering method is always used.")); renderer->set_accessibility_name(TTRC("Rendering Method")); - right_menu_hb->add_child(renderer); + title_right_hbox->add_child(renderer); if (can_expand) { // Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side). right_menu_spacer = memnew(Control); right_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS); - title_bar->add_child(right_menu_spacer); + title_right_hbox->add_child(right_menu_spacer, false, INTERNAL_MODE_BACK); } String current_renderer_ps = GLOBAL_GET("rendering/renderer/rendering_method"); @@ -8369,7 +8396,7 @@ EditorNode::EditorNode() { layout_dialog->connect("name_confirmed", callable_mp(this, &EditorNode::_dialog_action)); update_spinner = memnew(MenuButton); - right_menu_hb->add_child(update_spinner); + title_right_hbox->add_child(update_spinner); update_spinner->set_button_icon(theme->get_icon(SNAME("Progress1"), EditorStringName(EditorIcons))); update_spinner->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_menu_option)); update_spinner->set_accessibility_name(TTRC("Update Mode")); @@ -8448,8 +8475,6 @@ EditorNode::EditorNode() { Button *output_button = bottom_panel->add_item(TTRC("Output"), log, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_output_bottom_panel", TTRC("Toggle Output Bottom Panel"), KeyModifierMask::ALT | Key::O)); log->set_tool_button(output_button); - center_split->connect(SceneStringName(resized), callable_mp(this, &EditorNode::_vp_resized)); - native_shader_source_visualizer = memnew(EditorNativeShaderSourceVisualizer); gui_base->add_child(native_shader_source_visualizer); @@ -8837,16 +8862,6 @@ EditorNode::EditorNode() { add_child(screenshot_timer); screenshot_timer->set_owner(get_owner()); - // Adjust spacers to center 2D / 3D / Script buttons. - if (main_menu_button != nullptr) { - int max_w = MAX(project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu_button->get_minimum_size().x); - left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu_button->get_minimum_size().x), 0)); - right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - project_run_bar->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0)); - } else { - int max_w = MAX(project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu_bar->get_minimum_size().x); - left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu_bar->get_minimum_size().x), 0)); - right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - project_run_bar->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0)); - } // Extend menu bar to window title. if (can_expand) { DisplayServer::get_singleton()->process_events(); diff --git a/editor/editor_node.h b/editor/editor_node.h index a44975d417d3..e7e807408198 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -86,6 +86,7 @@ class EditorResourcePreview; class EditorResourceConversionPlugin; class EditorRunBar; class EditorSceneTabs; +class EditorScrollBox; class EditorSelectionHistory; class EditorSettingsDialog; class EditorTitleBar; @@ -327,12 +328,11 @@ class EditorNode : public Node { Control *left_menu_spacer = nullptr; Control *right_menu_spacer = nullptr; EditorTitleBar *title_bar = nullptr; + HBoxContainer *title_left_hbox = nullptr; + HBoxContainer *title_right_hbox = nullptr; + EditorScrollBox *menu_scroll_box = nullptr; + EditorScrollBox *main_scroll_box = nullptr; EditorRunBar *project_run_bar = nullptr; - HBoxContainer *right_menu_hb = nullptr; - - // Spacers to center 2D / 3D / Script buttons. - HBoxContainer *left_spacer = nullptr; - Control *right_spacer = nullptr; Control *menu_btn_spacer = nullptr; MenuButton *main_menu_button = nullptr; @@ -575,9 +575,9 @@ class EditorNode : public Node { void _version_control_menu_option(int p_idx); void _close_messages(); void _show_messages(); - void _vp_resized(); void _titlebar_resized(); void _viewport_resized(); + void _adjust_project_title(); void _update_undo_redo_allowed(); @@ -735,6 +735,7 @@ class EditorNode : public Node { static EditorFolding &get_editor_folding() { return singleton->editor_folding; } static EditorTitleBar *get_title_bar() { return singleton->title_bar; } + static HBoxContainer *get_title_bar_right_hbox() { return singleton->title_right_hbox; } static VSplitContainer *get_top_split() { return singleton->top_split; } static EditorBottomPanel *get_bottom_panel() { return singleton->bottom_panel; } static EditorMainScreen *get_editor_main_screen() { return singleton->editor_main_screen; } diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp index 1a51ea2285fc..a8dda30886e3 100644 --- a/editor/gui/editor_bottom_panel.cpp +++ b/editor/gui/editor_bottom_panel.cpp @@ -33,13 +33,13 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_string_names.h" +#include "editor/gui/editor_scroll_box.h" #include "editor/gui/editor_toaster.h" #include "editor/gui/editor_version_button.h" #include "editor/settings/editor_command_palette.h" #include "editor/themes/editor_scale.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" -#include "scene/gui/scroll_container.h" #include "scene/gui/split_container.h" void EditorBottomPanel::_notification(int p_what) { @@ -47,19 +47,6 @@ void EditorBottomPanel::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin"))); expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock"))); - left_button->set_button_icon(get_editor_theme_icon(SNAME("Back"))); - right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward"))); - } break; - - case NOTIFICATION_TRANSLATION_CHANGED: - case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - if (is_layout_rtl()) { - bottom_hbox->move_child(left_button, button_scroll->get_index() + 1); - bottom_hbox->move_child(right_button, 0); - } else { - bottom_hbox->move_child(right_button, button_scroll->get_index() + 1); - bottom_hbox->move_child(left_button, 0); - } } break; } } @@ -73,42 +60,6 @@ void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control, b } } -void EditorBottomPanel::_scroll(bool p_right) { - HScrollBar *h_scroll = button_scroll->get_h_scroll_bar(); - if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { - h_scroll->set_value(p_right ? h_scroll->get_max() : 0); - } else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * (p_right ? 1 : -1)); - } else { - h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * 0.5) * (p_right ? 1 : -1)); - } -} - -void EditorBottomPanel::_update_scroll_buttons() { - bool show_arrows = button_hbox->get_size().width > button_scroll->get_size().width; - left_button->set_visible(show_arrows); - right_button->set_visible(show_arrows); - - if (show_arrows) { - _update_disabled_buttons(); - } -} - -void EditorBottomPanel::_update_disabled_buttons() { - HScrollBar *h_scroll = button_scroll->get_h_scroll_bar(); - left_button->set_disabled(h_scroll->get_value() == 0); - right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max()); -} - -void EditorBottomPanel::_ensure_control_visible(ObjectID p_id) { - Control *c = ObjectDB::get_instance(p_id); - if (!c) { - return; - } - - button_scroll->ensure_control_visible(c); -} - void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) { ERR_FAIL_INDEX(p_idx, items.size()); @@ -143,7 +94,8 @@ void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore if (expand_button->is_pressed()) { EditorNode::get_top_split()->hide(); } - callable_mp(this, &EditorBottomPanel::_ensure_control_visible).call_deferred(items[p_idx].button->get_instance_id()); + callable_mp((EditorScrollBox *)scroll_box, &EditorScrollBox::ensure_control_visible).call_deferred(items[p_idx].button->get_instance_id()); + } else { add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles))); items[p_idx].button->set_pressed_no_signal(false); @@ -314,37 +266,13 @@ EditorBottomPanel::EditorBottomPanel() { bottom_hbox->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon. item_vbox->add_child(bottom_hbox); - left_button = memnew(Button); - left_button->set_tooltip_text(TTRC("Scroll Left\nHold Ctrl to scroll to the begin.\nHold Shift to scroll one page.")); - left_button->set_accessibility_name(TTRC("Scroll Left")); - left_button->set_theme_type_variation("BottomPanelButton"); - left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - left_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(false)); - bottom_hbox->add_child(left_button); - left_button->hide(); - - button_scroll = memnew(ScrollContainer); - button_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL); - button_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER); - button_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - button_scroll->get_h_scroll_bar()->connect(CoreStringName(changed), callable_mp(this, &EditorBottomPanel::_update_scroll_buttons), CONNECT_DEFERRED); - button_scroll->get_h_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &EditorBottomPanel::_update_disabled_buttons).unbind(1), CONNECT_DEFERRED); - bottom_hbox->add_child(button_scroll); - - right_button = memnew(Button); - right_button->set_tooltip_text(TTRC("Scroll Right\nHold Ctrl to scroll to the end.\nHold Shift to scroll one page.")); - right_button->set_accessibility_name(TTRC("Scroll Right")); - right_button->set_theme_type_variation("BottomPanelButton"); - right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - right_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(true)); - bottom_hbox->add_child(right_button); - right_button->hide(); - - callable_mp(this, &EditorBottomPanel::_update_scroll_buttons).call_deferred(); + scroll_box = memnew(EditorScrollBox); + scroll_box->set_custom_minimum_size(Size2(128 * EDSCALE, 0)); + bottom_hbox->add_child(scroll_box); button_hbox = memnew(HBoxContainer); button_hbox->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN); - button_scroll->add_child(button_hbox); + scroll_box->set_control(button_hbox); editor_toaster = memnew(EditorToaster); bottom_hbox->add_child(editor_toaster); diff --git a/editor/gui/editor_bottom_panel.h b/editor/gui/editor_bottom_panel.h index 72e540341e45..216f67416f5b 100644 --- a/editor/gui/editor_bottom_panel.h +++ b/editor/gui/editor_bottom_panel.h @@ -38,6 +38,7 @@ class EditorToaster; class HBoxContainer; class VBoxContainer; class ScrollContainer; +class EditorScrollBox; class EditorBottomPanel : public PanelContainer { GDCLASS(EditorBottomPanel, PanelContainer); @@ -53,9 +54,7 @@ class EditorBottomPanel : public PanelContainer { VBoxContainer *item_vbox = nullptr; HBoxContainer *bottom_hbox = nullptr; - Button *left_button = nullptr; - Button *right_button = nullptr; - ScrollContainer *button_scroll = nullptr; + EditorScrollBox *scroll_box = nullptr; HBoxContainer *button_hbox = nullptr; EditorToaster *editor_toaster = nullptr; Button *pin_button = nullptr; @@ -66,10 +65,6 @@ class EditorBottomPanel : public PanelContainer { void _switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock = false); void _pin_button_toggled(bool p_pressed); void _expand_button_toggled(bool p_pressed); - void _scroll(bool p_right); - void _update_scroll_buttons(); - void _update_disabled_buttons(); - void _ensure_control_visible(ObjectID p_id); bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control); diff --git a/editor/gui/editor_scroll_box.cpp b/editor/gui/editor_scroll_box.cpp new file mode 100644 index 000000000000..0daafe299c56 --- /dev/null +++ b/editor/gui/editor_scroll_box.cpp @@ -0,0 +1,198 @@ +/**************************************************************************/ +/* editor_scroll_box.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "editor_scroll_box.h" + +#include "scene/gui/button.h" +#include "scene/gui/scroll_bar.h" +#include "scene/gui/scroll_container.h" +#include "scene/theme/theme_db.h" + +void EditorScrollBox::ensure_control_visible(ObjectID p_id) { + Control *c = ObjectDB::get_instance(p_id); + if (!c) { + return; + } + + scroll_container->ensure_control_visible(c); +} + +// Use set_control(nullptr) to remove the control used by the ScrollContainer. +void EditorScrollBox::set_control(Control *p_control) { + if (p_control) { + ERR_FAIL_COND_MSG(has_control(), "Container already has a control, use set_control(nullptr) to remove it."); + } + if (control) { + control->disconnect(SceneStringName(resized), callable_mp(this, &EditorScrollBox::_update_buttons)); + scroll_container->remove_child(p_control); + control = nullptr; + } + + if (!p_control) { + _update_buttons(); + return; + } + + if (p_control->get_parent()) { + p_control->get_parent()->remove_child(p_control); + } + + control = p_control; + control->connect(SceneStringName(resized), callable_mp(this, &EditorScrollBox::_update_buttons)); + scroll_container->add_child(p_control); + + _update_buttons(); +} + +bool EditorScrollBox::has_control() const { + return control != nullptr; +} + +Button *EditorScrollBox::get_right_button() const { + return is_layout_rtl() ? left_button : right_button; +} + +Button *EditorScrollBox::get_left_button() const { + return is_layout_rtl() ? right_button : left_button; +} + +void EditorScrollBox::_scroll(bool p_right) { + if (is_layout_rtl()) { + p_right = !p_right; + } + + HScrollBar *hscroll_bar = scroll_container->get_h_scroll_bar(); + if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { + hscroll_bar->set_value(p_right ? hscroll_bar->get_max() : 0); + } else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + hscroll_bar->set_value(hscroll_bar->get_value() + hscroll_bar->get_page() * (p_right ? 1 : -1)); + } else { + hscroll_bar->set_value(hscroll_bar->get_value() + (hscroll_bar->get_page() * 0.5) * (p_right ? 1 : -1)); + } +} + +// Update the buttons visibility and disabled state. +void EditorScrollBox::_update_buttons() { + bool show_arrows = control && control->get_size().width > scroll_container->get_size().width; + left_button->set_visible(show_arrows); + right_button->set_visible(show_arrows); + + if (show_arrows) { + _update_disabled_buttons(); + } +} + +void EditorScrollBox::_update_buttons_icon_and_tooltip() { + Button *button = get_left_button(); + button->set_button_icon(theme_cache.arrow_left); + button->set_accessibility_name(TTRC("Scroll Left")); + button->set_tooltip_text(TTRC("Scroll Left\nHold Ctrl to scroll to the begin.\nHold Shift to scroll one page.")); + + button = get_right_button(); + button->set_button_icon(theme_cache.arrow_right); + button->set_accessibility_name(TTRC("Scroll Right")); + button->set_tooltip_text(TTRC("Scroll Right\nHold Ctrl to scroll to the end.\nHold Shift to scroll one page.")); +} + +void EditorScrollBox::_update_disabled_buttons() { + HScrollBar *scroll_bar = scroll_container->get_h_scroll_bar(); + + get_left_button()->set_disabled(scroll_bar->get_value() == 0); + get_right_button()->set_disabled(scroll_bar->get_value() + scroll_bar->get_page() == scroll_bar->get_max()); +} + +void EditorScrollBox::_accessibility_action_scroll_left(const Variant &p_data) { + _scroll(false); +} + +void EditorScrollBox::_accessibility_action_scroll_right(const Variant &p_data) { + _scroll(true); +} + +void EditorScrollBox::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ACCESSIBILITY_UPDATE: { + RID ae = get_accessibility_element(); + ERR_FAIL_COND(ae.is_null()); + DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SCROLL_VIEW); + + DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_LEFT, callable_mp(this, &EditorScrollBox::_accessibility_action_scroll_left)); + DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SCROLL_RIGHT, callable_mp(this, &EditorScrollBox::_accessibility_action_scroll_right)); + } break; + + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + _update_buttons_icon_and_tooltip(); + _update_buttons(); + } break; + + case NOTIFICATION_RESIZED: { + _update_buttons(); + } break; + } +} + +void EditorScrollBox::_bind_methods() { + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, EditorScrollBox, arrow_left); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, EditorScrollBox, arrow_right); +} + +EditorScrollBox::EditorScrollBox() { + set_h_size_flags(SIZE_EXPAND_FILL); + + left_button = memnew(Button); + left_button->set_theme_type_variation("BottomPanelButton"); + left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + left_button->connect(SceneStringName(pressed), callable_mp(this, &EditorScrollBox::_scroll).bind(false)); + add_child(left_button); + left_button->hide(); + + scroll_container = memnew(ScrollContainer); + scroll_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER); + scroll_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + scroll_container->set_force_pass_scroll_events(false); + scroll_container->set_follow_focus(true); + add_child(scroll_container); + + HScrollBar *hscroll = scroll_container->get_h_scroll_bar(); + hscroll->connect(CoreStringName(changed), callable_mp(this, &EditorScrollBox::_update_buttons), CONNECT_DEFERRED); + hscroll->connect(SceneStringName(value_changed), callable_mp(this, &EditorScrollBox::_update_disabled_buttons).unbind(1), CONNECT_DEFERRED); + + right_button = memnew(Button); + right_button->set_theme_type_variation("BottomPanelButton"); + right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + right_button->connect(SceneStringName(pressed), callable_mp(this, &EditorScrollBox::_scroll).bind(true)); + add_child(right_button); + right_button->hide(); + + _update_buttons_icon_and_tooltip(); +} diff --git a/editor/gui/editor_scroll_box.h b/editor/gui/editor_scroll_box.h new file mode 100644 index 000000000000..1d44ac797117 --- /dev/null +++ b/editor/gui/editor_scroll_box.h @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* editor_scroll_box.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "scene/gui/box_container.h" + +class Button; +class ScrollContainer; + +class EditorScrollBox : public HBoxContainer { + GDCLASS(EditorScrollBox, HBoxContainer) + + struct ThemeCache { + Ref arrow_left; + Ref arrow_right; + } theme_cache; + + Button *left_button = nullptr; + Button *right_button = nullptr; + ScrollContainer *scroll_container = nullptr; + Control *control = nullptr; + + void _scroll(bool p_right); + void _accessibility_action_scroll_right(const Variant &p_data); + void _accessibility_action_scroll_left(const Variant &p_data); + void _update_buttons(); + void _update_disabled_buttons(); + void _update_buttons_icon_and_tooltip(); + void _update_scroll_container(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_control(Control *p_control); + Control *get_control() const { return control; } + bool has_control() const; + + void ensure_control_visible(ObjectID p_id); + + Button *get_left_button() const; + Button *get_right_button() const; + + ScrollContainer *get_scroll_container() const { return scroll_container; } + + EditorScrollBox(); +}; diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp index e2567221363b..3e8bc333ec40 100644 --- a/editor/plugins/editor_plugin.cpp +++ b/editor/plugins/editor_plugin.cpp @@ -109,7 +109,7 @@ void EditorPlugin::add_control_to_container(CustomControlContainer p_location, C switch (p_location) { case CONTAINER_TOOLBAR: { - EditorNode::get_title_bar()->add_child(p_control); + EditorNode::get_title_bar_right_hbox()->add_child(p_control); } break; case CONTAINER_SPATIAL_EDITOR_MENU: { @@ -161,7 +161,7 @@ void EditorPlugin::remove_control_from_container(CustomControlContainer p_locati switch (p_location) { case CONTAINER_TOOLBAR: { - EditorNode::get_title_bar()->remove_child(p_control); + EditorNode::get_title_bar_right_hbox()->remove_child(p_control); } break; case CONTAINER_SPATIAL_EDITOR_MENU: { diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index f3e1aa869809..cbc7cca37867 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -50,6 +50,7 @@ #include "editor/file_system/editor_file_system.h" #include "editor/file_system/editor_paths.h" #include "editor/gui/editor_file_dialog.h" +#include "editor/gui/editor_scroll_box.h" #include "editor/gui/editor_spin_slider.h" #include "editor/gui/editor_toaster.h" #include "editor/import/3d/resource_importer_obj.h" diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 5a86c3e41105..5f23baf520ee 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1331,6 +1331,10 @@ void EditorThemeManager::_populate_standard_styles(const Ref &p_the p_theme->set_constant("outline_size", "FoldableContainer", 0); p_theme->set_constant("h_separation", "FoldableContainer", p_config.separation_margin); + + // EditorScrollBox + p_theme->set_icon("arrow_left", "EditorScrollBox", p_theme->get_icon(SNAME("GuiTreeArrowLeft"), EditorStringName(EditorIcons))); + p_theme->set_icon("arrow_right", "EditorScrollBox", p_theme->get_icon(SNAME("GuiTreeArrowRight"), EditorStringName(EditorIcons))); } // Window and dialogs.