diff --git a/Servers/WindowServer/WSMenu.cpp b/Servers/WindowServer/WSMenu.cpp index 210a247b10..1533a58a5e 100644 --- a/Servers/WindowServer/WSMenu.cpp +++ b/Servers/WindowServer/WSMenu.cpp @@ -201,6 +201,42 @@ void WSMenu::draw() } } +void WSMenu::redraw_for_new_hovered_item() +{ + if (m_hovered_item && m_hovered_item->is_submenu()) { + ASSERT(m_hovered_item == &m_items.at(m_current_index)); + WSWindowManager::the().menu_manager().close_everyone_not_in_lineage(*m_hovered_item->submenu()); + m_hovered_item->submenu()->popup(m_hovered_item->rect().top_right().translated(menu_window()->rect().location()), true); + } else { + WSWindowManager::the().menu_manager().close_everyone_not_in_lineage(*this); + } + redraw(); +} + +void WSMenu::open_hovered_item() +{ + ASSERT(menu_window()); + ASSERT(menu_window()->is_visible()); + if (!m_hovered_item) + return; + ASSERT(m_hovered_item == &m_items.at(m_current_index)); + if (m_hovered_item->is_enabled()) + did_activate(*m_hovered_item); + clear_hovered_item(); +} + +void WSMenu::decend_into_submenu_at_hovered_item() +{ + ASSERT(m_hovered_item); + ASSERT(m_hovered_item->is_submenu()); + auto submenu = m_hovered_item->submenu(); + ASSERT(submenu->m_current_index == 0); + submenu->m_hovered_item = &submenu->m_items.at(0); + ASSERT(submenu->m_hovered_item->type() != WSMenuItem::Separator); + submenu->redraw_for_new_hovered_item(); + m_in_submenu = true; +} + void WSMenu::event(CEvent& event) { if (event.type() == WSEvent::MouseMove) { @@ -209,26 +245,99 @@ void WSMenu::event(CEvent& event) if (m_hovered_item == item) return; m_hovered_item = item; - if (m_hovered_item && m_hovered_item->is_submenu()) { - WSWindowManager::the().menu_manager().close_everyone_not_in_lineage(*m_hovered_item->submenu()); - m_hovered_item->submenu()->popup(m_hovered_item->rect().top_right().translated(menu_window()->rect().location()), true); - } else { - WSWindowManager::the().menu_manager().close_everyone_not_in_lineage(*this); + + if (m_hovered_item) { + // FIXME: Tell parent menu (if it exists) that it is currently in a submenu + m_current_index = m_items.find([&](auto& item) { return item.ptr() == m_hovered_item; }).index(); + ASSERT(m_current_index != m_items.size()); } - redraw(); + + m_in_submenu = false; + redraw_for_new_hovered_item(); return; } if (event.type() == WSEvent::MouseUp) { - ASSERT(menu_window()); - if (!m_hovered_item) - return; - if (m_hovered_item->is_enabled()) - did_activate(*m_hovered_item); - clear_hovered_item(); + open_hovered_item(); return; } + if (event.type() == WSEvent::KeyDown) { + auto key = static_cast(event).key(); + + if (!(key == Key_Up || key == Key_Down || key == Key_Left || key == Key_Right || key == Key_Return)) + return; + + ASSERT(menu_window()); + ASSERT(menu_window()->is_visible()); + + // Default to the first item on key press if one has not been selected yet + if (!m_hovered_item) { + m_hovered_item = &m_items.at(0); + redraw_for_new_hovered_item(); + return; + } + + // Pass the event for the submenu that we are currently in to handle + if (m_in_submenu && key != Key_Left) { + ASSERT(m_hovered_item->is_submenu()); + m_hovered_item->submenu()->dispatch_event(event); + return; + } + + if (key == Key_Return) { + if (m_hovered_item->is_submenu()) + decend_into_submenu_at_hovered_item(); + else + open_hovered_item(); + return; + } + + if (key == Key_Up) { + ASSERT(m_items.at(0).type() != WSMenuItem::Separator); + + do { + m_current_index--; + if (m_current_index < 0) + m_current_index = m_items.size() - 1; + m_hovered_item = &m_items.at(m_current_index); + } while (m_hovered_item->type() == WSMenuItem::Separator); + + redraw_for_new_hovered_item(); + return; + } + + if (key == Key_Down) { + ASSERT(m_items.at(0).type() != WSMenuItem::Separator); + + do { + m_current_index++; + if (m_current_index >= m_items.size()) + m_current_index = 0; + m_hovered_item = &m_items.at(m_current_index); + } while (m_hovered_item->type() == WSMenuItem::Separator); + + redraw_for_new_hovered_item(); + return; + } + + if (key == Key_Left) { + if (!m_in_submenu) + return; + + m_hovered_item = &m_items.at(m_current_index); + ASSERT(m_hovered_item->is_submenu()); + m_hovered_item->submenu()->clear_hovered_item(); + m_in_submenu = false; + return; + } + + if (key == Key_Right) { + if (m_hovered_item->is_submenu()) + decend_into_submenu_at_hovered_item(); + return; + } + } CObject::event(event); } @@ -236,7 +345,9 @@ void WSMenu::clear_hovered_item() { if (!m_hovered_item) return; + m_current_index = 0; m_hovered_item = nullptr; + m_in_submenu = false; redraw(); } diff --git a/Servers/WindowServer/WSMenu.h b/Servers/WindowServer/WSMenu.h index 0240aaca09..6201871cbd 100644 --- a/Servers/WindowServer/WSMenu.h +++ b/Servers/WindowServer/WSMenu.h @@ -92,6 +92,10 @@ private: int padding_between_text_and_shortcut() const { return 50; } void did_activate(WSMenuItem&); + void open_hovered_item(); + void redraw_for_new_hovered_item(); + void decend_into_submenu_at_hovered_item(); + WSClientConnection* m_client { nullptr }; int m_menu_id { 0 }; String m_name; @@ -101,7 +105,11 @@ private: WSMenuItem* m_hovered_item { nullptr }; NonnullOwnPtrVector m_items; RefPtr m_menu_window; + WeakPtr m_window_menu_of; bool m_is_window_menu_open = { false }; + int m_theme_index_at_last_paint { -1 }; + int m_current_index { 0 }; + bool m_in_submenu { false }; }; diff --git a/Servers/WindowServer/WSMenuManager.cpp b/Servers/WindowServer/WSMenuManager.cpp index fe474c7578..0ff230c700 100644 --- a/Servers/WindowServer/WSMenuManager.cpp +++ b/Servers/WindowServer/WSMenuManager.cpp @@ -263,6 +263,15 @@ void WSMenuManager::event(CEvent& event) applet->event(local_event); } } + + if (event.type() == WSEvent::KeyDown) { + for_each_active_menubar_menu([&](WSMenu& menu) { + if (is_open(menu)) + menu.dispatch_event(event); + return IterationDecision::Continue; + }); + } + return CObject::event(event); } @@ -270,7 +279,7 @@ void WSMenuManager::handle_menu_mouse_event(WSMenu& menu, const WSMouseEvent& ev { bool is_hover_with_any_menu_open = event.type() == WSMouseEvent::MouseMove && !m_open_menu_stack.is_empty() - && (m_open_menu_stack.first()->menubar() || m_open_menu_stack.first() == system_menu()); + && (m_open_menu_stack.first()->menubar() || m_open_menu_stack.first() == m_system_menu.ptr()); bool is_mousedown_with_left_button = event.type() == WSMouseEvent::MouseDown && event.button() == MouseButton::Left; bool should_open_menu = &menu != m_current_menu && (is_hover_with_any_menu_open || is_mousedown_with_left_button); @@ -278,17 +287,7 @@ void WSMenuManager::handle_menu_mouse_event(WSMenu& menu, const WSMouseEvent& ev m_bar_open = !m_bar_open; if (should_open_menu && m_bar_open) { - if (m_current_menu == &menu) - return; - close_everyone(); - if (!menu.is_empty()) { - menu.redraw_if_theme_changed(); - auto& menu_window = menu.ensure_menu_window(); - menu_window.move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() + 2 }); - menu_window.set_visible(true); - } - set_current_menu(&menu); - refresh(); + open_menu(menu); return; } @@ -368,6 +367,20 @@ void WSMenuManager::close_menu_and_descendants(WSMenu& menu) close_menus(menus_to_close); } +void WSMenuManager::open_menu(WSMenu& menu) +{ + if (is_open(menu)) + return; + if (!menu.is_empty()) { + menu.redraw_if_theme_changed(); + auto& menu_window = menu.ensure_menu_window(); + menu_window.move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() + 2 }); + menu_window.set_visible(true); + } + set_current_menu(&menu); + refresh(); +} + void WSMenuManager::set_current_menu(WSMenu* menu, bool is_submenu) { if (!is_submenu && m_current_menu) diff --git a/Servers/WindowServer/WSMenuManager.h b/Servers/WindowServer/WSMenuManager.h index 26cd609800..c497e740ff 100644 --- a/Servers/WindowServer/WSMenuManager.h +++ b/Servers/WindowServer/WSMenuManager.h @@ -30,6 +30,7 @@ public: WSMenu* current_menu() { return m_current_menu.ptr(); } void set_current_menu(WSMenu*, bool is_submenu = false); + void open_menu(WSMenu&); WSMenuBar* current_menubar() { return m_current_menubar.ptr(); } void set_current_menubar(WSMenuBar*); @@ -47,14 +48,14 @@ public: void invalidate_applet(const WSWindow&, const Rect&); Color menu_selection_color() const { return m_menu_selection_color; } - WSMenu* system_menu() { return m_system_menu; } + WSMenu& system_menu() { return *m_system_menu; } WSMenu* find_internal_menu_by_id(int); int theme_index() const { return m_theme_index; } template void for_each_active_menubar_menu(Callback callback) { - if (callback(*system_menu()) == IterationDecision::Break) + if (callback(system_menu()) == IterationDecision::Break) return; if (m_current_menubar) m_current_menubar->for_each_menu(callback); diff --git a/Servers/WindowServer/WSWindowManager.cpp b/Servers/WindowServer/WSWindowManager.cpp index 515abb4dcc..adcdce0371 100644 --- a/Servers/WindowServer/WSWindowManager.cpp +++ b/Servers/WindowServer/WSWindowManager.cpp @@ -975,10 +975,18 @@ void WSWindowManager::event(CEvent& event) } } } - - return m_active_window->dispatch_event(event); + m_active_window->dispatch_event(event); + return; } - return; + + // FIXME: I would prefer to be WSEvent::KeyUp, and be at the top of this function + // However, the modifier is Invalid of Mod_Logo for a keyup event. Move after a fix is made. + if (key_event.type() == WSEvent::KeyDown && key_event.modifiers() == Mod_Logo) { + m_menu_manager.open_menu(m_menu_manager.system_menu()); + return; + } + + m_menu_manager.dispatch_event(event); } CObject::event(event); diff --git a/Servers/WindowServer/WSWindowManager.h b/Servers/WindowServer/WSWindowManager.h index f8e01f1361..f02a497a6b 100644 --- a/Servers/WindowServer/WSWindowManager.h +++ b/Servers/WindowServer/WSWindowManager.h @@ -258,7 +258,6 @@ private: u8 m_keyboard_modifiers { 0 }; - WSWindowSwitcher m_switcher; WSMenuManager m_menu_manager;