From 49f57677893cd61b3555e5fa663b66ae53e42046 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 7 Dec 2022 21:18:02 +0100 Subject: [PATCH] LibGUI+WindowServer: Add "visible" state to GUI actions This patch adds a visibility state to GUI::Action. All actions default to being visible. When invisible, they do not show up in toolbars on menus (and importantly, they don't occupy any space). This can be used to hide/show context-sensitive actions dynamically without rebuilding menus and toolbars. Thanks to Tim Slater for assuming that action visibility was a thing, which gave me a reason to implement it! :^) --- Userland/Libraries/LibGUI/Action.cpp | 13 ++++++++ Userland/Libraries/LibGUI/Action.h | 4 +++ Userland/Libraries/LibGUI/Button.cpp | 1 + Userland/Libraries/LibGUI/Menu.cpp | 4 +-- Userland/Libraries/LibGUI/MenuItem.cpp | 10 +++++- Userland/Libraries/LibGUI/MenuItem.h | 4 +++ .../WindowServer/ConnectionFromClient.cpp | 6 ++-- .../WindowServer/ConnectionFromClient.h | 4 +-- Userland/Services/WindowServer/Menu.cpp | 33 ++++++++++++++++--- Userland/Services/WindowServer/Menu.h | 3 ++ Userland/Services/WindowServer/MenuItem.cpp | 12 ++++++- Userland/Services/WindowServer/MenuItem.h | 6 +++- .../Services/WindowServer/WindowServer.ipc | 2 ++ 13 files changed, 88 insertions(+), 14 deletions(-) diff --git a/Userland/Libraries/LibGUI/Action.cpp b/Userland/Libraries/LibGUI/Action.cpp index 678ee44915d..8b502110179 100644 --- a/Userland/Libraries/LibGUI/Action.cpp +++ b/Userland/Libraries/LibGUI/Action.cpp @@ -228,6 +228,19 @@ void Action::set_enabled(bool enabled) }); } +void Action::set_visible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + for_each_toolbar_button([visible](auto& button) { + button.set_visible(visible); + }); + for_each_menu_item([visible](auto& item) { + item.set_visible(visible); + }); +} + void Action::set_checked(bool checked) { if (m_checked == checked) diff --git a/Userland/Libraries/LibGUI/Action.h b/Userland/Libraries/LibGUI/Action.h index d8367a5dd4f..f5e615048c3 100644 --- a/Userland/Libraries/LibGUI/Action.h +++ b/Userland/Libraries/LibGUI/Action.h @@ -105,6 +105,9 @@ public: bool is_enabled() const { return m_enabled; } void set_enabled(bool); + bool is_visible() const { return m_visible; } + void set_visible(bool); + bool is_checkable() const { return m_checkable; } void set_checkable(bool checkable) { m_checkable = checkable; } @@ -146,6 +149,7 @@ private: Shortcut m_shortcut; Shortcut m_alternate_shortcut; bool m_enabled { true }; + bool m_visible { true }; bool m_checkable { false }; bool m_checked { false }; bool m_swallow_key_event_when_disabled { false }; diff --git a/Userland/Libraries/LibGUI/Button.cpp b/Userland/Libraries/LibGUI/Button.cpp index 226bd9b27f4..5a19df7772b 100644 --- a/Userland/Libraries/LibGUI/Button.cpp +++ b/Userland/Libraries/LibGUI/Button.cpp @@ -165,6 +165,7 @@ void Button::set_action(Action& action) { m_action = action; action.register_button({}, *this); + set_visible(action.is_visible()); set_enabled(action.is_enabled()); set_checkable(action.is_checkable()); if (action.is_checkable()) diff --git a/Userland/Libraries/LibGUI/Menu.cpp b/Userland/Libraries/LibGUI/Menu.cpp index 74dcf7a73ec..d57141a68cc 100644 --- a/Userland/Libraries/LibGUI/Menu.cpp +++ b/Userland/Libraries/LibGUI/Menu.cpp @@ -201,14 +201,14 @@ void Menu::realize_menu_item(MenuItem& item, int item_id) bool exclusive = action.group() && action.group()->is_exclusive() && action.is_checkable(); bool is_default = (m_current_default_action.ptr() == &action); auto icon = action.icon() ? action.icon()->to_shareable_bitmap() : Gfx::ShareableBitmap(); - ConnectionToWindowServer::the().async_add_menu_item(m_menu_id, item_id, -1, action.text(), action.is_enabled(), action.is_checkable(), action.is_checkable() ? action.is_checked() : false, is_default, shortcut_text, icon, exclusive); + ConnectionToWindowServer::the().async_add_menu_item(m_menu_id, item_id, -1, action.text(), action.is_enabled(), action.is_visible(), action.is_checkable(), action.is_checkable() ? action.is_checked() : false, is_default, shortcut_text, icon, exclusive); break; } case MenuItem::Type::Submenu: { auto& submenu = *item.submenu(); submenu.realize_if_needed(m_current_default_action.strong_ref()); auto icon = submenu.icon() ? submenu.icon()->to_shareable_bitmap() : Gfx::ShareableBitmap(); - ConnectionToWindowServer::the().async_add_menu_item(m_menu_id, item_id, submenu.menu_id(), submenu.name(), true, false, false, false, "", icon, false); + ConnectionToWindowServer::the().async_add_menu_item(m_menu_id, item_id, submenu.menu_id(), submenu.name(), true, true, false, false, false, "", icon, false); break; } case MenuItem::Type::Invalid: diff --git a/Userland/Libraries/LibGUI/MenuItem.cpp b/Userland/Libraries/LibGUI/MenuItem.cpp index 426c5492eba..79d20db10a3 100644 --- a/Userland/Libraries/LibGUI/MenuItem.cpp +++ b/Userland/Libraries/LibGUI/MenuItem.cpp @@ -50,6 +50,14 @@ void MenuItem::set_enabled(bool enabled) update_window_server(); } +void MenuItem::set_visible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + update_window_server(); +} + void MenuItem::set_checked(bool checked) { VERIFY(is_checkable()); @@ -75,7 +83,7 @@ void MenuItem::update_window_server() auto& action = *m_action; auto shortcut_text = action.shortcut().is_valid() ? action.shortcut().to_deprecated_string() : DeprecatedString(); auto icon = action.icon() ? action.icon()->to_shareable_bitmap() : Gfx::ShareableBitmap(); - ConnectionToWindowServer::the().async_update_menu_item(m_menu_id, m_identifier, -1, action.text(), action.is_enabled(), action.is_checkable(), action.is_checkable() ? action.is_checked() : false, m_default, shortcut_text, icon); + ConnectionToWindowServer::the().async_update_menu_item(m_menu_id, m_identifier, -1, action.text(), action.is_enabled(), action.is_visible(), action.is_checkable(), action.is_checkable() ? action.is_checked() : false, m_default, shortcut_text, icon); } void MenuItem::set_menu_id(Badge, unsigned int menu_id) diff --git a/Userland/Libraries/LibGUI/MenuItem.h b/Userland/Libraries/LibGUI/MenuItem.h index c98cb94abbb..574f8a3b2f9 100644 --- a/Userland/Libraries/LibGUI/MenuItem.h +++ b/Userland/Libraries/LibGUI/MenuItem.h @@ -45,6 +45,9 @@ public: bool is_enabled() const { return m_enabled; } void set_enabled(bool); + bool is_visible() const { return m_visible; } + void set_visible(bool); + bool is_default() const { return m_default; } void set_default(bool); @@ -61,6 +64,7 @@ private: int m_menu_id { -1 }; unsigned m_identifier { 0 }; bool m_enabled { true }; + bool m_visible { true }; bool m_checkable { false }; bool m_checked { false }; bool m_default { false }; diff --git a/Userland/Services/WindowServer/ConnectionFromClient.cpp b/Userland/Services/WindowServer/ConnectionFromClient.cpp index c7326e2789a..e34495f2f77 100644 --- a/Userland/Services/WindowServer/ConnectionFromClient.cpp +++ b/Userland/Services/WindowServer/ConnectionFromClient.cpp @@ -127,7 +127,7 @@ void ConnectionFromClient::add_menu(i32 window_id, i32 menu_id) } void ConnectionFromClient::add_menu_item(i32 menu_id, i32 identifier, i32 submenu_id, - DeprecatedString const& text, bool enabled, bool checkable, bool checked, bool is_default, + DeprecatedString const& text, bool enabled, bool visible, bool checkable, bool checked, bool is_default, DeprecatedString const& shortcut, Gfx::ShareableBitmap const& icon, bool exclusive) { auto it = m_menus.find(menu_id); @@ -142,6 +142,7 @@ void ConnectionFromClient::add_menu_item(i32 menu_id, i32 identifier, i32 submen menu_item->set_icon(icon.bitmap()); menu_item->set_submenu_id(submenu_id); menu_item->set_exclusive(exclusive); + menu_item->set_visible(visible); menu.add_item(move(menu_item)); } @@ -172,7 +173,7 @@ void ConnectionFromClient::dismiss_menu(i32 menu_id) } void ConnectionFromClient::update_menu_item(i32 menu_id, i32 identifier, [[maybe_unused]] i32 submenu_id, - DeprecatedString const& text, bool enabled, bool checkable, bool checked, bool is_default, + DeprecatedString const& text, bool enabled, bool visible, bool checkable, bool checked, bool is_default, DeprecatedString const& shortcut, Gfx::ShareableBitmap const& icon) { auto it = m_menus.find(menu_id); @@ -190,6 +191,7 @@ void ConnectionFromClient::update_menu_item(i32 menu_id, i32 identifier, [[maybe menu_item->set_text(text); menu_item->set_shortcut_text(shortcut); menu_item->set_enabled(enabled); + menu_item->set_visible(visible); menu_item->set_checkable(checkable); menu_item->set_default(is_default); if (checkable) diff --git a/Userland/Services/WindowServer/ConnectionFromClient.h b/Userland/Services/WindowServer/ConnectionFromClient.h index 8551d4d6474..a0f71dd2446 100644 --- a/Userland/Services/WindowServer/ConnectionFromClient.h +++ b/Userland/Services/WindowServer/ConnectionFromClient.h @@ -96,9 +96,9 @@ private: virtual void create_menu(i32, DeprecatedString const&) override; virtual void destroy_menu(i32) override; virtual void add_menu(i32, i32) override; - virtual void add_menu_item(i32, i32, i32, DeprecatedString const&, bool, bool, bool, bool, DeprecatedString const&, Gfx::ShareableBitmap const&, bool) override; + virtual void add_menu_item(i32, i32, i32, DeprecatedString const&, bool, bool, bool, bool, bool, DeprecatedString const&, Gfx::ShareableBitmap const&, bool) override; virtual void add_menu_separator(i32) override; - virtual void update_menu_item(i32, i32, i32, DeprecatedString const&, bool, bool, bool, bool, DeprecatedString const&, Gfx::ShareableBitmap const&) override; + virtual void update_menu_item(i32, i32, i32, DeprecatedString const&, bool, bool, bool, bool, bool, DeprecatedString const&, Gfx::ShareableBitmap const&) override; virtual void remove_menu_item(i32 menu_id, i32 identifier) override; virtual void flash_menubar_menu(i32, i32) override; virtual void create_window(i32, Gfx::IntRect const&, bool, bool, bool, diff --git a/Userland/Services/WindowServer/Menu.cpp b/Userland/Services/WindowServer/Menu.cpp index 1f0183f5a60..fa481efad70 100644 --- a/Userland/Services/WindowServer/Menu.cpp +++ b/Userland/Services/WindowServer/Menu.cpp @@ -73,6 +73,8 @@ int Menu::content_width() const int widest_text = 0; int widest_shortcut = 0; for (auto& item : m_items) { + if (!item.is_visible()) + continue; if (item.type() != MenuItem::Text) continue; auto& use_font = item.is_default() ? font().bold_variant() : font(); @@ -108,10 +110,17 @@ void Menu::redraw(MenuItem const& menu_item) { if (!menu_window()) return; + if (!menu_item.is_visible()) + return; draw(menu_item); menu_window()->invalidate(menu_item.rect()); } +void Menu::invalidate_menu_window() +{ + m_menu_window = nullptr; +} + Window& Menu::ensure_menu_window(Gfx::IntPoint position) { auto& screen = Screen::closest_to_location(position); @@ -131,6 +140,8 @@ Window& Menu::ensure_menu_window(Gfx::IntPoint position) Gfx::IntPoint next_item_location(frame_thickness(), frame_thickness()); for (auto& item : m_items) { + if (!item.is_visible()) + continue; int height = 0; if (item.type() == MenuItem::Text) height = item_height(); @@ -194,6 +205,8 @@ void Menu::draw() bool has_checkable_items = false; bool has_items_with_icon = false; for (auto& item : m_items) { + if (!item.is_visible()) + continue; has_checkable_items = has_checkable_items | item.is_checkable(); has_items_with_icon = has_items_with_icon | !!item.icon(); } @@ -218,6 +231,9 @@ void Menu::draw() void Menu::draw(MenuItem const& item, bool is_drawing_all) { + if (!item.is_visible()) + return; + auto palette = WindowManager::the().palette(); int width = this->content_width(); Gfx::Painter painter(*menu_window()->backing_store()); @@ -400,6 +416,8 @@ void Menu::event(Core::Event& event) // Default to the last enabled, non-separator item on key press if one has not been selected yet for (auto i = static_cast(m_items.size()) - 1; i >= 0; i--) { auto& item = m_items.at(i); + if (!item.is_visible()) + continue; if (item.type() != MenuItem::Separator && item.is_enabled()) { set_hovered_index(i, key == Key_Right); break; @@ -409,6 +427,8 @@ void Menu::event(Core::Event& event) // Default to the first enabled, non-separator item on key press if one has not been selected yet int counter = 0; for (auto const& item : m_items) { + if (!item.is_visible()) + continue; if (item.type() != MenuItem::Separator && item.is_enabled()) { set_hovered_index(counter, key == Key_Right); break; @@ -434,7 +454,7 @@ void Menu::event(Core::Event& event) --new_index; if (new_index == original_index) return; - } while (item(new_index).type() == MenuItem::Separator || !item(new_index).is_enabled()); + } while (item(new_index).type() == MenuItem::Separator || !item(new_index).is_enabled() || !item(new_index).is_visible()); VERIFY(new_index >= 0); VERIFY(new_index <= static_cast(m_items.size()) - 1); @@ -461,7 +481,7 @@ void Menu::event(Core::Event& event) ++new_index; if (new_index == original_index) return; - } while (item(new_index).type() == MenuItem::Separator || !item(new_index).is_enabled()); + } while (item(new_index).type() == MenuItem::Separator || !item(new_index).is_enabled() || !item(new_index).is_visible()); VERIFY(new_index >= 0); VERIFY(new_index <= static_cast(m_items.size()) - 1); @@ -551,6 +571,8 @@ void Menu::did_activate(MenuItem& item, bool leave_menu_open) bool Menu::activate_default() { for (auto& item : m_items) { + if (!item.is_visible()) + continue; if (item.type() == MenuItem::Type::Separator) continue; if (item.is_enabled() && item.is_default()) { @@ -577,11 +599,12 @@ bool Menu::remove_item_with_identifier(unsigned identifier) int Menu::item_index_at(Gfx::IntPoint position) { - int i = 0; - for (auto& item : m_items) { + for (int i = 0; i < static_cast(m_items.size()); ++i) { + auto const& item = m_items[i]; + if (!item.is_visible()) + continue; if (item.rect().contains(position)) return i; - ++i; } return -1; } diff --git a/Userland/Services/WindowServer/Menu.h b/Userland/Services/WindowServer/Menu.h index 3bec0dceadb..5a2a32c2c5e 100644 --- a/Userland/Services/WindowServer/Menu.h +++ b/Userland/Services/WindowServer/Menu.h @@ -81,6 +81,9 @@ public: Window* menu_window() { return m_menu_window.ptr(); } Window& ensure_menu_window(Gfx::IntPoint); + // Invalidates the menu window so that it gets rebuilt the next time it's showed. + void invalidate_menu_window(); + Window* window_menu_of() { return m_window_menu_of; } void set_window_menu_of(Window& window) { m_window_menu_of = window; } bool is_window_menu_open() const { return m_is_window_menu_open; } diff --git a/Userland/Services/WindowServer/MenuItem.cpp b/Userland/Services/WindowServer/MenuItem.cpp index 255a5ae0855..e6a1c213adf 100644 --- a/Userland/Services/WindowServer/MenuItem.cpp +++ b/Userland/Services/WindowServer/MenuItem.cpp @@ -12,10 +12,11 @@ namespace WindowServer { -MenuItem::MenuItem(Menu& menu, unsigned identifier, DeprecatedString const& text, DeprecatedString const& shortcut_text, bool enabled, bool checkable, bool checked, Gfx::Bitmap const* icon) +MenuItem::MenuItem(Menu& menu, unsigned identifier, DeprecatedString const& text, DeprecatedString const& shortcut_text, bool enabled, bool visible, bool checkable, bool checked, Gfx::Bitmap const* icon) : m_menu(menu) , m_type(Text) , m_enabled(enabled) + , m_visible(visible) , m_checkable(checkable) , m_checked(checked) , m_identifier(identifier) @@ -23,6 +24,7 @@ MenuItem::MenuItem(Menu& menu, unsigned identifier, DeprecatedString const& text , m_shortcut_text(shortcut_text) , m_icon(icon) { + menu.invalidate_menu_window(); } MenuItem::MenuItem(Menu& menu, Type type) @@ -39,6 +41,14 @@ void MenuItem::set_enabled(bool enabled) m_menu.redraw(); } +void MenuItem::set_visible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + m_menu.invalidate_menu_window(); +} + void MenuItem::set_checked(bool checked) { if (m_checked == checked) diff --git a/Userland/Services/WindowServer/MenuItem.h b/Userland/Services/WindowServer/MenuItem.h index 54d966a2e36..f173f6914df 100644 --- a/Userland/Services/WindowServer/MenuItem.h +++ b/Userland/Services/WindowServer/MenuItem.h @@ -23,7 +23,7 @@ public: Separator, }; - MenuItem(Menu&, unsigned identifier, DeprecatedString const& text, DeprecatedString const& shortcut_text = {}, bool enabled = true, bool checkable = false, bool checked = false, Gfx::Bitmap const* icon = nullptr); + MenuItem(Menu&, unsigned identifier, DeprecatedString const& text, DeprecatedString const& shortcut_text = {}, bool enabled = true, bool visible = true, bool checkable = false, bool checked = false, Gfx::Bitmap const* icon = nullptr); MenuItem(Menu&, Type); ~MenuItem() = default; @@ -32,6 +32,9 @@ public: bool is_enabled() const { return m_enabled; } void set_enabled(bool); + bool is_visible() const { return m_visible; } + void set_visible(bool); + bool is_checkable() const { return m_checkable; } void set_checkable(bool checkable) { m_checkable = checkable; } @@ -69,6 +72,7 @@ private: Menu& m_menu; Type m_type { None }; bool m_enabled { true }; + bool m_visible { true }; bool m_checkable { false }; bool m_checked { false }; bool m_default { false }; diff --git a/Userland/Services/WindowServer/WindowServer.ipc b/Userland/Services/WindowServer/WindowServer.ipc index bbbf27e05ad..5411a1b8420 100644 --- a/Userland/Services/WindowServer/WindowServer.ipc +++ b/Userland/Services/WindowServer/WindowServer.ipc @@ -15,6 +15,7 @@ endpoint WindowServer i32 submenu_id, [UTF8] DeprecatedString text, bool enabled, + bool visible, bool checkable, bool checked, bool is_default, @@ -30,6 +31,7 @@ endpoint WindowServer i32 submenu_id, [UTF8] DeprecatedString text, bool enabled, + bool visible, bool checkable, bool checked, bool is_default,