mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 01:07:36 +00:00
WindowServer+LibGUI: Add support for nested menus
It's now possible to add a GMenu as a submenu of another GMenu. Simply use the GMenu::add_submenu(NonnullOwnPtr<GMenu>) API :^) The WindowServer now keeps track of a stack of open menus rather than just one "current menu". This code needs a bit more work, but the basic functionality is now here!
This commit is contained in:
parent
d3ebd8897f
commit
63e6b09816
16 changed files with 177 additions and 12 deletions
|
@ -263,6 +263,7 @@ struct WSAPI_ClientMessage {
|
|||
struct {
|
||||
int menubar_id;
|
||||
int menu_id;
|
||||
int submenu_id;
|
||||
int icon_buffer_id;
|
||||
unsigned identifier;
|
||||
char shortcut_text[32];
|
||||
|
|
|
@ -159,7 +159,7 @@ bool WSClientConnection::handle_message(const WSAPI_ClientMessage& message, cons
|
|||
did_misbehave();
|
||||
return false;
|
||||
}
|
||||
CEventLoop::current().post_event(*this, make<WSAPIAddMenuItemRequest>(client_id(), message.menu.menu_id, message.menu.identifier, String(message.text, message.text_length), String(message.menu.shortcut_text, message.menu.shortcut_text_length), message.menu.enabled, message.menu.checkable, message.menu.checked, message.menu.icon_buffer_id));
|
||||
CEventLoop::current().post_event(*this, make<WSAPIAddMenuItemRequest>(client_id(), message.menu.menu_id, message.menu.identifier, String(message.text, message.text_length), String(message.menu.shortcut_text, message.menu.shortcut_text_length), message.menu.enabled, message.menu.checkable, message.menu.checked, message.menu.icon_buffer_id, message.menu.submenu_id));
|
||||
break;
|
||||
case WSAPI_ClientMessage::Type::UpdateMenuItem:
|
||||
if (message.text_length > (int)sizeof(message.text)) {
|
||||
|
@ -434,6 +434,7 @@ void WSClientConnection::handle_request(const WSAPIAddMenuItemRequest& request)
|
|||
auto shared_icon = GraphicsBitmap::create_with_shared_buffer(GraphicsBitmap::Format::RGBA32, icon_buffer.release_nonnull(), { 16, 16 });
|
||||
menu_item->set_icon(shared_icon);
|
||||
}
|
||||
menu_item->set_submenu_id(request.submenu_id());
|
||||
menu.add_item(move(menu_item));
|
||||
WSAPI_ServerMessage response;
|
||||
response.type = WSAPI_ServerMessage::Type::DidAddMenuItem;
|
||||
|
|
|
@ -37,6 +37,12 @@ public:
|
|||
void notify_about_new_screen_rect(const Rect&);
|
||||
void post_paint_message(WSWindow&);
|
||||
|
||||
WSMenu* find_menu_by_id(int menu_id)
|
||||
{
|
||||
// FIXME: Remove this const_cast when Optional knows how to vend a non-const fallback value somehow.
|
||||
return const_cast<WSMenu*>(m_menus.get(menu_id).value_or(nullptr));
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void event(CEvent&) override;
|
||||
|
||||
|
|
|
@ -303,7 +303,7 @@ private:
|
|||
|
||||
class WSAPIAddMenuItemRequest : public WSAPIClientRequest {
|
||||
public:
|
||||
WSAPIAddMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text, const String& shortcut_text, bool enabled, bool checkable, bool checked, int icon_buffer_id)
|
||||
WSAPIAddMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text, const String& shortcut_text, bool enabled, bool checkable, bool checked, int icon_buffer_id, int submenu_id)
|
||||
: WSAPIClientRequest(WSEvent::APIAddMenuItemRequest, client_id)
|
||||
, m_menu_id(menu_id)
|
||||
, m_identifier(identifier)
|
||||
|
@ -313,6 +313,7 @@ public:
|
|||
, m_checkable(checkable)
|
||||
, m_checked(checked)
|
||||
, m_icon_buffer_id(icon_buffer_id)
|
||||
, m_submenu_id(submenu_id)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -324,6 +325,7 @@ public:
|
|||
bool is_checkable() const { return m_checkable; }
|
||||
bool is_checked() const { return m_checked; }
|
||||
int icon_buffer_id() const { return m_icon_buffer_id; }
|
||||
int submenu_id() const { return m_submenu_id; }
|
||||
|
||||
private:
|
||||
int m_menu_id { 0 };
|
||||
|
@ -334,6 +336,7 @@ private:
|
|||
bool m_checkable;
|
||||
bool m_checked;
|
||||
int m_icon_buffer_id { 0 };
|
||||
int m_submenu_id { 0 };
|
||||
};
|
||||
|
||||
class WSAPIUpdateMenuItemRequest : public WSAPIClientRequest {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "WSEvent.h"
|
||||
#include "WSEventLoop.h"
|
||||
#include "WSMenuItem.h"
|
||||
#include "WSMenuManager.h"
|
||||
#include "WSScreen.h"
|
||||
#include "WSWindow.h"
|
||||
#include "WSWindowManager.h"
|
||||
|
@ -42,9 +43,23 @@ static const char* s_checked_bitmap_data = {
|
|||
" "
|
||||
};
|
||||
|
||||
static const char* s_submenu_arrow_bitmap_data = {
|
||||
" "
|
||||
" # "
|
||||
" ## "
|
||||
" ### "
|
||||
" #### "
|
||||
" ### "
|
||||
" ## "
|
||||
" # "
|
||||
" "
|
||||
};
|
||||
|
||||
static CharacterBitmap* s_checked_bitmap;
|
||||
static const int s_checked_bitmap_width = 9;
|
||||
static const int s_checked_bitmap_height = 9;
|
||||
static const int s_submenu_arrow_bitmap_width = 9;
|
||||
static const int s_submenu_arrow_bitmap_height = 9;
|
||||
static const int s_item_icon_width = 16;
|
||||
static const int s_checkbox_or_icon_padding = 6;
|
||||
static const int s_stripe_width = 23;
|
||||
|
@ -163,6 +178,17 @@ void WSMenu::draw()
|
|||
if (!item.shortcut_text().is_empty()) {
|
||||
painter.draw_text(item.rect().translated(-right_padding(), 0), item.shortcut_text(), TextAlignment::CenterRight, text_color);
|
||||
}
|
||||
if (item.is_submenu()) {
|
||||
static auto& submenu_arrow_bitmap = CharacterBitmap::create_from_ascii(s_submenu_arrow_bitmap_data, s_submenu_arrow_bitmap_width, s_submenu_arrow_bitmap_height).leak_ref();
|
||||
Rect submenu_arrow_rect {
|
||||
item.rect().right() - s_submenu_arrow_bitmap_width - 2,
|
||||
0,
|
||||
s_submenu_arrow_bitmap_width,
|
||||
s_submenu_arrow_bitmap_height
|
||||
};
|
||||
submenu_arrow_rect.center_vertically_within(item.rect());
|
||||
painter.draw_bitmap(submenu_arrow_rect.location(), submenu_arrow_bitmap, Color::Black);
|
||||
}
|
||||
} else if (item.type() == WSMenuItem::Separator) {
|
||||
Point p1(item.rect().translated(stripe_rect.width() + 4, 0).x(), item.rect().center().y() - 1);
|
||||
Point p2(width - 7, item.rect().center().y() - 1);
|
||||
|
@ -180,6 +206,23 @@ void WSMenu::event(CEvent& event)
|
|||
if (!item || m_hovered_item == item)
|
||||
return;
|
||||
m_hovered_item = item;
|
||||
if (m_hovered_item->is_submenu()) {
|
||||
m_hovered_item->submenu()->popup(m_hovered_item->rect().top_right().translated(menu_window()->rect().location()), true);
|
||||
} else {
|
||||
bool close_remaining_menus = false;
|
||||
for (auto& open_menu : WSWindowManager::the().menu_manager().open_menu_stack()) {
|
||||
if (!open_menu)
|
||||
continue;
|
||||
if (close_remaining_menus) {
|
||||
open_menu->menu_window()->set_visible(false);
|
||||
continue;
|
||||
}
|
||||
if (open_menu == this) {
|
||||
close_remaining_menus = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
|
@ -246,7 +289,7 @@ void WSMenu::close()
|
|||
menu_window()->set_visible(false);
|
||||
}
|
||||
|
||||
void WSMenu::popup(const Point& position)
|
||||
void WSMenu::popup(const Point& position, bool is_submenu)
|
||||
{
|
||||
ASSERT(!is_empty());
|
||||
|
||||
|
@ -262,5 +305,5 @@ void WSMenu::popup(const Point& position)
|
|||
|
||||
window.move_to(adjusted_pos);
|
||||
window.set_visible(true);
|
||||
WSWindowManager::the().set_current_menu(this);
|
||||
WSWindowManager::the().set_current_menu(this, is_submenu);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ public:
|
|||
|
||||
void close();
|
||||
|
||||
void popup(const Point&);
|
||||
void popup(const Point&, bool is_submenu = false);
|
||||
|
||||
private:
|
||||
virtual void event(CEvent&) override;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "WSMenuItem.h"
|
||||
#include "WSClientConnection.h"
|
||||
#include "WSMenu.h"
|
||||
#include <LibDraw/GraphicsBitmap.h>
|
||||
|
||||
|
@ -40,3 +41,10 @@ void WSMenuItem::set_checked(bool checked)
|
|||
m_checked = checked;
|
||||
m_menu.redraw();
|
||||
}
|
||||
|
||||
WSMenu* WSMenuItem::submenu()
|
||||
{
|
||||
ASSERT(is_submenu());
|
||||
ASSERT(m_menu.client());
|
||||
return m_menu.client()->find_menu_by_id(m_submenu_id);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,12 @@ public:
|
|||
const GraphicsBitmap* icon() const { return m_icon; }
|
||||
void set_icon(const GraphicsBitmap* icon) { m_icon = icon; }
|
||||
|
||||
bool is_submenu() const { return m_submenu_id != -1; }
|
||||
int submenu_id() const { return m_submenu_id; }
|
||||
void set_submenu_id(int submenu_id) { m_submenu_id = submenu_id; }
|
||||
|
||||
WSMenu* submenu();
|
||||
|
||||
private:
|
||||
WSMenu& m_menu;
|
||||
Type m_type { None };
|
||||
|
@ -55,4 +61,5 @@ private:
|
|||
String m_shortcut_text;
|
||||
Rect m_rect;
|
||||
RefPtr<GraphicsBitmap> m_icon;
|
||||
int m_submenu_id { -1 };
|
||||
};
|
||||
|
|
|
@ -31,6 +31,15 @@ void WSMenuManager::setup()
|
|||
m_window->set_rect(WSWindowManager::the().menubar_rect());
|
||||
}
|
||||
|
||||
bool WSMenuManager::is_open(const WSMenu& menu) const
|
||||
{
|
||||
for (int i = 0; i < m_open_menu_stack.size(); ++i) {
|
||||
if (&menu == m_open_menu_stack[i].ptr())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WSMenuManager::draw()
|
||||
{
|
||||
auto& wm = WSWindowManager::the();
|
||||
|
@ -43,7 +52,7 @@ void WSMenuManager::draw()
|
|||
int index = 0;
|
||||
wm.for_each_active_menubar_menu([&](WSMenu& menu) {
|
||||
Color text_color = Color::Black;
|
||||
if (&menu == wm.current_menu()) {
|
||||
if (is_open(menu)) {
|
||||
painter.fill_rect(menu.rect_in_menubar(), Color::from_rgb(0xad714f));
|
||||
painter.draw_rect(menu.rect_in_menubar(), Color::from_rgb(0x793016));
|
||||
text_color = Color::White;
|
||||
|
@ -124,7 +133,9 @@ void WSMenuManager::event(CEvent& event)
|
|||
void WSMenuManager::handle_menu_mouse_event(WSMenu& menu, const WSMouseEvent& event)
|
||||
{
|
||||
auto& wm = WSWindowManager::the();
|
||||
bool is_hover_with_any_menu_open = event.type() == WSMouseEvent::MouseMove && wm.current_menu() && (wm.current_menu()->menubar() || wm.current_menu() == wm.system_menu());
|
||||
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() == wm.system_menu());
|
||||
bool is_mousedown_with_left_button = event.type() == WSMouseEvent::MouseDown && event.button() == MouseButton::Left;
|
||||
bool should_open_menu = &menu != wm.current_menu() && (is_hover_with_any_menu_open || is_mousedown_with_left_button);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "WSMenu.h"
|
||||
#include <LibCore/CObject.h>
|
||||
#include <WindowServer/WSCPUMonitor.h>
|
||||
#include <WindowServer/WSWindow.h>
|
||||
|
@ -15,6 +16,10 @@ public:
|
|||
|
||||
virtual void event(CEvent&) override;
|
||||
|
||||
bool is_open(const WSMenu&) const;
|
||||
|
||||
Vector<WeakPtr<WSMenu>>& open_menu_stack() { return m_open_menu_stack; }
|
||||
|
||||
private:
|
||||
WSWindow& window() { return *m_window; }
|
||||
const WSWindow& window() const { return *m_window; }
|
||||
|
@ -27,4 +32,6 @@ private:
|
|||
OwnPtr<WSWindow> m_window;
|
||||
WSCPUMonitor m_cpu_monitor;
|
||||
String m_username;
|
||||
|
||||
Vector<WeakPtr<WSMenu>> m_open_menu_stack;
|
||||
};
|
||||
|
|
|
@ -211,14 +211,22 @@ int WSWindowManager::menubar_menu_margin() const
|
|||
return 16;
|
||||
}
|
||||
|
||||
void WSWindowManager::set_current_menu(WSMenu* menu)
|
||||
void WSWindowManager::set_current_menu(WSMenu* menu, bool is_submenu)
|
||||
{
|
||||
if (m_current_menu == menu)
|
||||
return;
|
||||
if (m_current_menu)
|
||||
if (!is_submenu && m_current_menu)
|
||||
m_current_menu->close();
|
||||
if (menu)
|
||||
m_current_menu = menu->make_weak_ptr();
|
||||
|
||||
if (!is_submenu) {
|
||||
m_menu_manager.open_menu_stack().clear();
|
||||
if (menu)
|
||||
m_menu_manager.open_menu_stack().append(menu->make_weak_ptr());
|
||||
} else {
|
||||
m_menu_manager.open_menu_stack().append(menu->make_weak_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
void WSWindowManager::set_current_menubar(WSMenuBar* menubar)
|
||||
|
@ -393,6 +401,11 @@ void WSWindowManager::close_current_menu()
|
|||
if (m_current_menu && m_current_menu->menu_window())
|
||||
m_current_menu->menu_window()->set_visible(false);
|
||||
m_current_menu = nullptr;
|
||||
for (auto& menu : m_menu_manager.open_menu_stack()) {
|
||||
if (menu)
|
||||
menu->menu_window()->set_visible(false);
|
||||
}
|
||||
m_menu_manager.open_menu_stack().clear();
|
||||
m_menu_manager.refresh();
|
||||
}
|
||||
|
||||
|
@ -696,6 +709,18 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& hovere
|
|||
m_current_menu->clear_hovered_item();
|
||||
if (event.type() == WSEvent::MouseDown || event.type() == WSEvent::MouseUp)
|
||||
close_current_menu();
|
||||
if (event.type() == WSEvent::MouseMove) {
|
||||
for (auto& menu : m_menu_manager.open_menu_stack()) {
|
||||
if (!menu)
|
||||
continue;
|
||||
if (!menu->menu_window()->rect().contains(event.position()))
|
||||
continue;
|
||||
hovered_window = menu->menu_window();
|
||||
auto translated_event = event.translated(-menu->menu_window()->position());
|
||||
deliver_mouse_event(*menu->menu_window(), translated_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hovered_window = &window;
|
||||
auto translated_event = event.translated(-window.position());
|
||||
|
|
|
@ -78,11 +78,14 @@ public:
|
|||
|
||||
void draw_window_switcher();
|
||||
|
||||
WSMenuManager& menu_manager() { return m_menu_manager; }
|
||||
const WSMenuManager& menu_manager() const { return m_menu_manager; }
|
||||
|
||||
Rect menubar_rect() const;
|
||||
WSMenuBar* current_menubar() { return m_current_menubar.ptr(); }
|
||||
void set_current_menubar(WSMenuBar*);
|
||||
WSMenu* current_menu() { return m_current_menu.ptr(); }
|
||||
void set_current_menu(WSMenu*);
|
||||
void set_current_menu(WSMenu*, bool is_submenu = false);
|
||||
WSMenu* system_menu() { return m_system_menu.ptr(); }
|
||||
|
||||
const WSCursor& active_cursor() const;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue