From 23643cf21b5b102486e1501adccae708d9a1d4e6 Mon Sep 17 00:00:00 2001 From: ry-sev Date: Mon, 7 Mar 2022 19:30:21 -0500 Subject: [PATCH] HackStudio: Move editors inside tab widgets This will move the editors inside a tab widget and the user will be able to add new editors as tabs as well as add new tab widgets. The user will be able to easily switch between editors as well as the tab widgets. --- Userland/DevTools/HackStudio/Editor.cpp | 6 +- .../DevTools/HackStudio/EditorWrapper.cpp | 18 +- Userland/DevTools/HackStudio/EditorWrapper.h | 8 +- .../DevTools/HackStudio/HackStudioWidget.cpp | 212 +++++++++++++----- .../DevTools/HackStudio/HackStudioWidget.h | 16 +- 5 files changed, 182 insertions(+), 78 deletions(-) diff --git a/Userland/DevTools/HackStudio/Editor.cpp b/Userland/DevTools/HackStudio/Editor.cpp index a742c58731..f452428e5b 100644 --- a/Userland/DevTools/HackStudio/Editor.cpp +++ b/Userland/DevTools/HackStudio/Editor.cpp @@ -108,7 +108,6 @@ const EditorWrapper& Editor::wrapper() const void Editor::focusin_event(GUI::FocusEvent& event) { - wrapper().set_editor_has_focus({}, true); if (on_focus) on_focus(); GUI::TextEditor::focusin_event(event); @@ -116,7 +115,6 @@ void Editor::focusin_event(GUI::FocusEvent& event) void Editor::focusout_event(GUI::FocusEvent& event) { - wrapper().set_editor_has_focus({}, false); GUI::TextEditor::focusout_event(event); } @@ -323,10 +321,10 @@ void Editor::mousedown_event(GUI::MouseEvent& event) if (event.button() == GUI::MouseButton::Primary && event.position().x() < ruler_line_rect.width()) { if (!breakpoint_lines().contains_slow(text_position.line())) { breakpoint_lines().append(text_position.line()); - Debugger::the().on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Added); + Debugger::the().on_breakpoint_change(wrapper().filename_title(), text_position.line(), BreakpointChange::Added); } else { breakpoint_lines().remove_first_matching([&](size_t line) { return line == text_position.line(); }); - Debugger::the().on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Removed); + Debugger::the().on_breakpoint_change(wrapper().filename_title(), text_position.line(), BreakpointChange::Removed); } } diff --git a/Userland/DevTools/HackStudio/EditorWrapper.cpp b/Userland/DevTools/HackStudio/EditorWrapper.cpp index b245f7e380..0ca9ff0fc7 100644 --- a/Userland/DevTools/HackStudio/EditorWrapper.cpp +++ b/Userland/DevTools/HackStudio/EditorWrapper.cpp @@ -21,15 +21,7 @@ EditorWrapper::EditorWrapper() { set_layout(); - auto& label_wrapper = add(); - label_wrapper.set_fixed_height(14); - label_wrapper.set_fill_with_background_color(true); - label_wrapper.set_layout(); - label_wrapper.layout()->set_margins({ 0, 2 }); - - m_filename_label = label_wrapper.add(untitled_label); - m_filename_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); - m_filename_label->set_fixed_height(14); + m_filename_title = untitled_label; // FIXME: Propagate errors instead of giving up m_editor = MUST(try_add()); @@ -49,12 +41,6 @@ EditorWrapper::EditorWrapper() }; } -void EditorWrapper::set_editor_has_focus(Badge, bool focus) -{ - auto& font = Gfx::FontDatabase::default_font(); - m_filename_label->set_font(focus ? font.bold_variant() : font); -} - LanguageClient& EditorWrapper::language_client() { return m_editor->language_client(); } void EditorWrapper::set_mode_displayable() @@ -121,7 +107,7 @@ void EditorWrapper::update_title() if (editor().document().is_modified()) title.append(" (*)"); - m_filename_label->set_text(title.to_string()); + m_filename_title = title.to_string(); } void EditorWrapper::set_debug_mode(bool enabled) diff --git a/Userland/DevTools/HackStudio/EditorWrapper.h b/Userland/DevTools/HackStudio/EditorWrapper.h index 4713e8828a..3c7a677f3b 100644 --- a/Userland/DevTools/HackStudio/EditorWrapper.h +++ b/Userland/DevTools/HackStudio/EditorWrapper.h @@ -32,10 +32,6 @@ public: void save(); - GUI::Label& filename_label() { return *m_filename_label; } - const GUI::Label& filename_label() const { return *m_filename_label; } - - void set_editor_has_focus(Badge, bool); LanguageClient& language_client(); void set_mode_displayable(); @@ -43,6 +39,7 @@ public: void set_debug_mode(bool); void set_filename(const String&); const String& filename() const { return m_filename; } + String const& filename_title() const { return m_filename_title; } Optional const& project_root() const { return m_project_root; } void set_project_root(String const& project_root); @@ -53,6 +50,7 @@ public: Vector const& hunks() const { return m_hunks; } Function on_change; + Function on_tab_close_request; private: static constexpr auto untitled_label = "(Untitled)"; @@ -62,7 +60,7 @@ private: void update_title(); String m_filename; - RefPtr m_filename_label; + String m_filename_title; RefPtr m_editor; Optional m_project_root; diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.cpp b/Userland/DevTools/HackStudio/HackStudioWidget.cpp index 10ca01ac54..0ba73ea25b 100644 --- a/Userland/DevTools/HackStudio/HackStudioWidget.cpp +++ b/Userland/DevTools/HackStudio/HackStudioWidget.cpp @@ -111,11 +111,13 @@ HackStudioWidget::HackStudioWidget(String path_to_project) m_editors_splitter = m_right_hand_stack->add(); m_editors_splitter->layout()->set_margins({ 3, 0, 0 }); - add_new_editor(*m_editors_splitter); + add_new_editor_tab_widget(*m_editors_splitter); + m_switch_to_next_editor_tab_widget = create_switch_to_next_editor_tab_widget_action(); m_switch_to_next_editor = create_switch_to_next_editor_action(); m_switch_to_previous_editor = create_switch_to_previous_editor_action(); + m_remove_current_editor_tab_widget_action = create_remove_current_editor_tab_widget_action(); m_remove_current_editor_action = create_remove_current_editor_action(); m_open_action = create_open_action(); m_save_action = create_save_action(); @@ -124,6 +126,7 @@ HackStudioWidget::HackStudioWidget(String path_to_project) create_action_tab(*m_right_hand_splitter); + m_add_editor_tab_widget_action = create_add_editor_tab_widget_action(); m_add_editor_action = create_add_editor_action(); m_add_terminal_action = create_add_terminal_action(); m_remove_current_terminal_action = create_remove_current_terminal_action(); @@ -358,7 +361,7 @@ bool HackStudioWidget::open_file(const String& full_filename, size_t line, size_ m_project_tree_view->update(); current_editor_wrapper().set_filename(filename); - + update_current_editor_title(); current_editor().set_focus(true); current_editor().on_cursor_change = [this] { on_cursor_change(); }; @@ -396,6 +399,18 @@ void HackStudioWidget::close_file_in_all_editors(String const& filename) m_open_files_view->model()->invalidate(); } +GUI::TabWidget& HackStudioWidget::current_editor_tab_widget() +{ + VERIFY(m_current_editor_tab_widget); + return *m_current_editor_tab_widget; +} + +GUI::TabWidget const& HackStudioWidget::current_editor_tab_widget() const +{ + VERIFY(m_current_editor_tab_widget); + return *m_current_editor_tab_widget; +} + EditorWrapper& HackStudioWidget::current_editor_wrapper() { VERIFY(m_current_editor_wrapper); @@ -643,70 +658,137 @@ NonnullRefPtr HackStudioWidget::create_new_project_action() }); } -void HackStudioWidget::add_new_editor(GUI::Widget& parent) +NonnullRefPtr HackStudioWidget::create_remove_current_editor_tab_widget_action() { - auto wrapper = EditorWrapper::construct(); + return GUI::Action::create("Switch to Next Editor Group", { Mod_Alt | Mod_Shift, Key_Backslash }, [this](auto&) { + if (m_all_editor_tab_widgets.size() <= 1) + return; + auto tab_widget = m_current_editor_tab_widget; + while (tab_widget->children().size() > 1) { + auto active_wrapper = tab_widget->active_widget(); + tab_widget->remove_tab(*active_wrapper); + m_all_editor_wrappers.remove_first_matching([&active_wrapper](auto& entry) { return entry == active_wrapper; }); + } + tab_widget->on_tab_close_click(*tab_widget->active_widget()); + }); +} + +void HackStudioWidget::add_new_editor_tab_widget(GUI::Widget& parent) +{ + auto tab_widget = GUI::TabWidget::construct(); if (m_action_tab_widget) { - parent.insert_child_before(wrapper, *m_action_tab_widget); + parent.insert_child_before(tab_widget, *m_action_tab_widget); } else { - parent.add_child(wrapper); + parent.add_child(tab_widget); } + + m_current_editor_tab_widget = tab_widget; + m_all_editor_tab_widgets.append(tab_widget); + + tab_widget->set_reorder_allowed(true); + + if (m_all_editor_tab_widgets.size() > 1) { + for (auto& widget : m_all_editor_tab_widgets) + widget.set_close_button_enabled(true); + } + + tab_widget->on_change = [&](auto& widget) { + auto& wrapper = static_cast(widget); + set_current_editor_wrapper(wrapper); + current_editor().set_focus(true); + }; + + tab_widget->on_middle_click = [](auto& widget) { + auto& wrapper = static_cast(widget); + wrapper.on_tab_close_request(wrapper); + }; + + tab_widget->on_tab_close_click = [](auto& widget) { + auto& wrapper = static_cast(widget); + wrapper.on_tab_close_request(wrapper); + }; + + add_new_editor(*m_current_editor_tab_widget); +} + +void HackStudioWidget::add_new_editor(GUI::TabWidget& parent) +{ + auto& wrapper = parent.add_tab("(Untitled)"); + parent.set_active_widget(&wrapper); + if (parent.children().size() > 1 || m_all_editor_tab_widgets.size() > 1) + parent.set_close_button_enabled(true); + auto previous_editor_wrapper = m_current_editor_wrapper; m_current_editor_wrapper = wrapper; m_all_editor_wrappers.append(wrapper); - wrapper->editor().set_focus(true); - wrapper->editor().set_font(*m_editor_font); - wrapper->set_project_root(m_project->root_path()); - wrapper->editor().on_cursor_change = [this] { on_cursor_change(); }; - wrapper->on_change = [this] { update_gml_preview(); }; + wrapper.editor().set_focus(true); + wrapper.editor().set_font(*m_editor_font); + wrapper.set_project_root(m_project->root_path()); + wrapper.editor().on_cursor_change = [this] { on_cursor_change(); }; + wrapper.on_change = [this] { update_gml_preview(); }; set_edit_mode(EditMode::Text); if (previous_editor_wrapper && previous_editor_wrapper->editor().editing_engine()->is_regular()) - wrapper->editor().set_editing_engine(make()); + wrapper.editor().set_editing_engine(make()); else if (previous_editor_wrapper && previous_editor_wrapper->editor().editing_engine()->is_vim()) - wrapper->editor().set_editing_engine(make()); + wrapper.editor().set_editing_engine(make()); else - wrapper->editor().set_editing_engine(make()); + wrapper.editor().set_editing_engine(make()); + + wrapper.on_tab_close_request = [this, &parent](auto& tab) { + parent.deferred_invoke([this, &parent, &tab] { + set_current_editor_wrapper(tab); + parent.remove_tab(tab); + m_all_editor_wrappers.remove_first_matching([&tab](auto& entry) { return entry == &tab; }); + if (parent.children().is_empty()) { + m_switch_to_next_editor_tab_widget->activate(); + m_editors_splitter->remove_child(parent); + m_all_editor_tab_widgets.remove_first_matching([&parent](auto& entry) { return entry == &parent; }); + if (m_current_editor_tab_widget->children().size() == 1) + m_current_editor_tab_widget->set_close_button_enabled(false); + } + if (parent.children().size() == 1 && m_all_editor_tab_widgets.size() <= 1) + parent.set_close_button_enabled(false); + update_actions(); + }); + }; +} + +NonnullRefPtr HackStudioWidget::create_switch_to_next_editor_tab_widget_action() +{ + return GUI::Action::create("Switch to Next Editor Group", { Mod_Ctrl | Mod_Shift, Key_T }, [this](auto&) { + if (m_all_editor_tab_widgets.size() <= 1) + return; + Vector tab_widgets; + m_editors_splitter->for_each_child_of_type([&tab_widgets](auto& child) { + tab_widgets.append(child); + return IterationDecision::Continue; + }); + for (size_t i = 0; i < tab_widgets.size(); ++i) { + if (m_current_editor_tab_widget.ptr() == &tab_widgets[i]) { + if (i == tab_widgets.size() - 1) + m_current_editor_tab_widget = tab_widgets[0]; + else + m_current_editor_tab_widget = tab_widgets[i + 1]; + auto wrapper = static_cast(m_current_editor_tab_widget->active_widget()); + set_current_editor_wrapper(wrapper); + current_editor().set_focus(true); + break; + } + } + }); } NonnullRefPtr HackStudioWidget::create_switch_to_next_editor_action() { return GUI::Action::create("Switch to &Next Editor", { Mod_Ctrl, Key_E }, [this](auto&) { - if (m_all_editor_wrappers.size() <= 1) - return; - Vector wrappers; - m_editors_splitter->for_each_child_of_type([&wrappers](auto& child) { - wrappers.append(child); - return IterationDecision::Continue; - }); - for (size_t i = 0; i < wrappers.size(); ++i) { - if (m_current_editor_wrapper.ptr() == &wrappers[i]) { - if (i == wrappers.size() - 1) - wrappers[0].editor().set_focus(true); - else - wrappers[i + 1].editor().set_focus(true); - } - } + m_current_editor_tab_widget->activate_next_tab(); }); } NonnullRefPtr HackStudioWidget::create_switch_to_previous_editor_action() { return GUI::Action::create("Switch to &Previous Editor", { Mod_Ctrl | Mod_Shift, Key_E }, [this](auto&) { - if (m_all_editor_wrappers.size() <= 1) - return; - Vector wrappers; - m_editors_splitter->for_each_child_of_type([&wrappers](auto& child) { - wrappers.append(child); - return IterationDecision::Continue; - }); - for (int i = wrappers.size() - 1; i >= 0; --i) { - if (m_current_editor_wrapper.ptr() == &wrappers[i]) { - if (i == 0) - wrappers.last().editor().set_focus(true); - else - wrappers[i - 1].editor().set_focus(true); - } - } + m_current_editor_tab_widget->activate_previous_tab(); }); } @@ -715,10 +797,10 @@ NonnullRefPtr HackStudioWidget::create_remove_current_editor_action return GUI::Action::create("&Remove Current Editor", { Mod_Alt | Mod_Shift, Key_E }, Gfx::Bitmap::try_load_from_file("/res/icons/hackstudio/remove-editor.png").release_value_but_fixme_should_propagate_errors(), [this](auto&) { if (m_all_editor_wrappers.size() <= 1) return; - auto wrapper = m_current_editor_wrapper; - m_switch_to_next_editor->activate(); - m_editors_splitter->remove_child(*wrapper); - m_all_editor_wrappers.remove_first_matching([&wrapper](auto& entry) { return entry == wrapper.ptr(); }); + auto tab_widget = m_current_editor_tab_widget; + auto* active_wrapper = tab_widget->active_widget(); + VERIFY(active_wrapper); + tab_widget->on_tab_close_click(*active_wrapper); update_actions(); }); } @@ -782,6 +864,7 @@ NonnullRefPtr HackStudioWidget::create_save_as_action() m_open_files_vector.remove_all_matching([&old_filename](auto const& element) { return element == old_filename; }); update_window_title(); + update_current_editor_title(); m_project->model().invalidate(); update_tree_view(); @@ -804,12 +887,21 @@ NonnullRefPtr HackStudioWidget::create_remove_current_terminal_acti }); } +NonnullRefPtr HackStudioWidget::create_add_editor_tab_widget_action() +{ + return GUI::Action::create("Add New Editor Group", { Mod_Ctrl | Mod_Alt, Key_Backslash }, + [this](auto&) { + add_new_editor_tab_widget(*m_editors_splitter); + update_actions(); + }); +} + NonnullRefPtr HackStudioWidget::create_add_editor_action() { return GUI::Action::create("Add New &Editor", { Mod_Ctrl | Mod_Alt, Key_E }, Gfx::Bitmap::try_load_from_file("/res/icons/hackstudio/add-editor.png").release_value_but_fixme_should_propagate_errors(), [this](auto&) { - add_new_editor(*m_editors_splitter); + add_new_editor(*m_current_editor_tab_widget); update_actions(); }); } @@ -1003,11 +1095,19 @@ Project& HackStudioWidget::project() return *m_project; } +void HackStudioWidget::set_current_editor_tab_widget(RefPtr tab_widget) +{ + m_current_editor_tab_widget = tab_widget; +} + void HackStudioWidget::set_current_editor_wrapper(RefPtr editor_wrapper) { m_current_editor_wrapper = editor_wrapper; update_window_title(); + update_current_editor_title(); update_tree_view(); + set_current_editor_tab_widget(static_cast(m_current_editor_wrapper->parent())); + update_statusbar(); } void HackStudioWidget::file_renamed(String const& old_name, String const& new_name) @@ -1034,6 +1134,9 @@ void HackStudioWidget::file_renamed(String const& old_name, String const& new_na VERIFY(!m_file_watcher->remove_watch(old_name).is_error()); VERIFY(!m_file_watcher->add_watch(new_name, Core::FileWatcherEvent::Type::Deleted).is_error()); } + + update_window_title(); + update_current_editor_title(); } void HackStudioWidget::configure_project_tree_view() @@ -1331,6 +1434,7 @@ void HackStudioWidget::create_view_menu(GUI::Window& window) view_menu.add_action(*m_editor_font_action); view_menu.add_separator(); + view_menu.add_action(*m_add_editor_tab_widget_action); view_menu.add_action(*m_add_editor_action); view_menu.add_action(*m_remove_current_editor_action); view_menu.add_action(*m_add_terminal_action); @@ -1412,7 +1516,7 @@ void HackStudioWidget::close_current_project() m_all_editor_wrappers.clear(); m_open_files.clear(); m_open_files_vector.clear(); - add_new_editor(*m_editors_splitter); + add_new_editor(*m_current_editor_tab_widget); m_find_in_files_widget->reset(); m_todo_entries_widget->clear(); m_terminal_wrapper->clear_including_history(); @@ -1470,7 +1574,12 @@ void HackStudioWidget::update_tree_view() void HackStudioWidget::update_window_title() { - window()->set_title(String::formatted("{} - {} - Hack Studio", m_current_editor_wrapper->filename_label().text(), m_project->name())); + window()->set_title(String::formatted("{} - {} - Hack Studio", m_current_editor_wrapper->filename_title(), m_project->name())); +} + +void HackStudioWidget::update_current_editor_title() +{ + current_editor_tab_widget().set_tab_title(current_editor_wrapper(), current_editor_wrapper().filename_title()); } void HackStudioWidget::on_cursor_change() @@ -1614,5 +1723,4 @@ bool HackStudioWidget::semantic_syntax_highlighting_is_enabled() const { return m_toggle_semantic_highlighting_action->is_checked(); } - } diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.h b/Userland/DevTools/HackStudio/HackStudioWidget.h index aaeaa0b462..45e8d8c555 100644 --- a/Userland/DevTools/HackStudio/HackStudioWidget.h +++ b/Userland/DevTools/HackStudio/HackStudioWidget.h @@ -48,6 +48,10 @@ public: EditorWrapper& current_editor_wrapper(); EditorWrapper const& current_editor_wrapper() const; void set_current_editor_wrapper(RefPtr); + void set_current_editor_tab_widget(RefPtr); + + GUI::TabWidget& current_editor_tab_widget(); + GUI::TabWidget const& current_editor_tab_widget() const; const String& active_file() const { return m_current_editor_wrapper->filename(); } void initialize_menubar(GUI::Window&); @@ -98,13 +102,16 @@ private: NonnullRefPtr create_open_selected_action(); NonnullRefPtr create_delete_action(); NonnullRefPtr create_new_project_action(); + NonnullRefPtr create_switch_to_next_editor_tab_widget_action(); NonnullRefPtr create_switch_to_next_editor_action(); NonnullRefPtr create_switch_to_previous_editor_action(); + NonnullRefPtr create_remove_current_editor_tab_widget_action(); NonnullRefPtr create_remove_current_editor_action(); NonnullRefPtr create_open_action(); NonnullRefPtr create_save_action(); NonnullRefPtr create_save_as_action(); NonnullRefPtr create_show_in_file_manager_action(); + NonnullRefPtr create_add_editor_tab_widget_action(); NonnullRefPtr create_add_editor_action(); NonnullRefPtr create_add_terminal_action(); NonnullRefPtr create_remove_current_terminal_action(); @@ -115,7 +122,8 @@ private: NonnullRefPtr create_toggle_syntax_highlighting_mode_action(); void create_location_history_actions(); - void add_new_editor(GUI::Widget& parent); + void add_new_editor_tab_widget(GUI::Widget& parent); + void add_new_editor(GUI::TabWidget& parent); RefPtr get_editor_of_file(const String& filename); String get_project_executable_path() const; @@ -149,6 +157,7 @@ private: void update_gml_preview(); void update_tree_view(); void update_window_title(); + void update_current_editor_title(); void on_cursor_change(); void file_renamed(String const& old_name, String const& new_name); @@ -163,6 +172,8 @@ private: NonnullRefPtrVector m_all_editor_wrappers; RefPtr m_current_editor_wrapper; + NonnullRefPtrVector m_all_editor_tab_widgets; + RefPtr m_current_editor_tab_widget; HashMap> m_open_files; RefPtr m_file_watcher; @@ -208,13 +219,16 @@ private: RefPtr m_delete_action; RefPtr m_tree_view_rename_action; RefPtr m_new_project_action; + RefPtr m_switch_to_next_editor_tab_widget; RefPtr m_switch_to_next_editor; RefPtr m_switch_to_previous_editor; + RefPtr m_remove_current_editor_tab_widget_action; RefPtr m_remove_current_editor_action; RefPtr m_open_action; RefPtr m_save_action; RefPtr m_save_as_action; RefPtr m_add_editor_action; + RefPtr m_add_editor_tab_widget_action; RefPtr m_add_terminal_action; RefPtr m_remove_current_terminal_action; RefPtr m_stop_action;