mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 12:47:34 +00:00
WindowServer+LibGfx: Show menus in windows! :^)
This patch begins the transition away from the global menu towards per-window menus instead. The global menu looks neat, but has always felt clunky, and there are a number of usability problems with it, especially in programs with multiple windows. You can now call GUI::Window::set_menubar() to add a menubar to your window. It will be specific to that one window only.
This commit is contained in:
parent
1daaa4f38d
commit
e76771bfad
21 changed files with 335 additions and 44 deletions
|
@ -145,10 +145,10 @@ void PreviewWidget::paint_event(GUI::PaintEvent& event)
|
|||
button.rect = rect;
|
||||
}
|
||||
|
||||
auto frame_rect = Gfx::WindowTheme::current().frame_rect_for_window(Gfx::WindowTheme::WindowType::Normal, rect, m_preview_palette);
|
||||
auto frame_rect = Gfx::WindowTheme::current().frame_rect_for_window(Gfx::WindowTheme::WindowType::Normal, rect, m_preview_palette, 0);
|
||||
Gfx::PainterStateSaver saver(painter);
|
||||
painter.translate(frame_rect.location());
|
||||
Gfx::WindowTheme::current().paint_normal_frame(painter, state, rect, title, icon, m_preview_palette, buttons.last().rect);
|
||||
Gfx::WindowTheme::current().paint_normal_frame(painter, state, rect, title, icon, m_preview_palette, buttons.last().rect, 0);
|
||||
|
||||
for (auto& button : buttons) {
|
||||
Gfx::StylePainter::paint_button(painter, button.rect, m_preview_palette, Gfx::ButtonStyle::Normal, false);
|
||||
|
|
|
@ -93,7 +93,7 @@ void Canvas::paint_event(GUI::PaintEvent& event)
|
|||
void Canvas::draw(Gfx::Painter& painter)
|
||||
{
|
||||
auto active_window_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png");
|
||||
Gfx::WindowTheme::current().paint_normal_frame(painter, Gfx::WindowTheme::WindowState::Active, { 4, 18, WIDTH - 8, HEIGHT - 29 }, "Well hello friends 🐞", *active_window_icon, palette(), { WIDTH - 20, 6, 16, 16 });
|
||||
Gfx::WindowTheme::current().paint_normal_frame(painter, Gfx::WindowTheme::WindowState::Active, { 4, 18, WIDTH - 8, HEIGHT - 29 }, "Well hello friends 🐞", *active_window_icon, palette(), { WIDTH - 20, 6, 16, 16 }, 0);
|
||||
|
||||
painter.draw_rect({ 20, 34, WIDTH - 40, HEIGHT - 45 }, palette().color(Gfx::ColorRole::Selection), true);
|
||||
painter.draw_rect({ 24, 38, WIDTH - 48, HEIGHT - 53 }, palette().color(Gfx::ColorRole::Selection));
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/Desktop.h>
|
||||
#include <LibGUI/Event.h>
|
||||
#include <LibGUI/MenuBar.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <LibGUI/Window.h>
|
||||
|
@ -1052,4 +1053,13 @@ Gfx::Bitmap* Window::back_bitmap()
|
|||
return m_back_store ? &m_back_store->bitmap() : nullptr;
|
||||
}
|
||||
|
||||
void Window::set_menubar(RefPtr<MenuBar> menubar)
|
||||
{
|
||||
if (m_menubar == menubar)
|
||||
return;
|
||||
m_menubar = move(menubar);
|
||||
if (m_window_id && m_menubar)
|
||||
WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowMenubar>(m_window_id, m_menubar->menubar_id());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -211,6 +211,8 @@ public:
|
|||
|
||||
void did_disable_focused_widget(Badge<Widget>);
|
||||
|
||||
void set_menubar(RefPtr<MenuBar>);
|
||||
|
||||
protected:
|
||||
Window(Core::Object* parent = nullptr);
|
||||
virtual void wm_event(WMEvent&);
|
||||
|
@ -241,6 +243,8 @@ private:
|
|||
OwnPtr<WindowBackingStore> m_front_store;
|
||||
OwnPtr<WindowBackingStore> m_back_store;
|
||||
|
||||
RefPtr<MenuBar> m_menubar;
|
||||
|
||||
RefPtr<Gfx::Bitmap> m_icon;
|
||||
RefPtr<Gfx::Bitmap> m_custom_cursor;
|
||||
int m_window_id { 0 };
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
|
||||
namespace Gfx {
|
||||
|
||||
static constexpr int menu_bar_height = 19;
|
||||
|
||||
ClassicWindowTheme::ClassicWindowTheme()
|
||||
{
|
||||
}
|
||||
|
@ -70,9 +72,9 @@ Gfx::IntRect ClassicWindowTheme::title_bar_text_rect(WindowType window_type, con
|
|||
};
|
||||
}
|
||||
|
||||
void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, const StringView& title_text, const Bitmap& icon, const Palette& palette, const IntRect& leftmost_button_rect) const
|
||||
void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, const StringView& title_text, const Bitmap& icon, const Palette& palette, const IntRect& leftmost_button_rect, int menu_row_count) const
|
||||
{
|
||||
auto frame_rect = frame_rect_for_window(WindowType::Normal, window_rect, palette);
|
||||
auto frame_rect = frame_rect_for_window(WindowType::Normal, window_rect, palette, menu_row_count);
|
||||
frame_rect.set_location({ 0, 0 });
|
||||
Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
|
||||
|
||||
|
@ -114,7 +116,7 @@ void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window
|
|||
|
||||
void ClassicWindowTheme::paint_tool_window_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, const StringView& title_text, const Palette& palette, const IntRect& leftmost_button_rect) const
|
||||
{
|
||||
auto frame_rect = frame_rect_for_window(WindowType::ToolWindow, window_rect, palette);
|
||||
auto frame_rect = frame_rect_for_window(WindowType::ToolWindow, window_rect, palette, 0);
|
||||
frame_rect.set_location({ 0, 0 });
|
||||
Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
|
||||
|
||||
|
@ -143,6 +145,13 @@ void ClassicWindowTheme::paint_tool_window_frame(Painter& painter, WindowState w
|
|||
}
|
||||
}
|
||||
|
||||
IntRect ClassicWindowTheme::menu_bar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette, int menu_row_count) const
|
||||
{
|
||||
if (window_type != WindowType::Normal)
|
||||
return {};
|
||||
return { 4, 4 + title_bar_height(window_type, palette) + 2, window_rect.width(), menu_bar_height * menu_row_count };
|
||||
}
|
||||
|
||||
IntRect ClassicWindowTheme::title_bar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
|
||||
{
|
||||
auto& title_font = FontDatabase::default_bold_font();
|
||||
|
@ -173,7 +182,7 @@ ClassicWindowTheme::FrameColors ClassicWindowTheme::compute_frame_colors(WindowS
|
|||
|
||||
void ClassicWindowTheme::paint_notification_frame(Painter& painter, const IntRect& window_rect, const Palette& palette, const IntRect& close_button_rect) const
|
||||
{
|
||||
auto frame_rect = frame_rect_for_window(WindowType::Notification, window_rect, palette);
|
||||
auto frame_rect = frame_rect_for_window(WindowType::Notification, window_rect, palette, 0);
|
||||
frame_rect.set_location({ 0, 0 });
|
||||
Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
|
||||
|
||||
|
@ -191,7 +200,7 @@ void ClassicWindowTheme::paint_notification_frame(Painter& painter, const IntRec
|
|||
}
|
||||
}
|
||||
|
||||
IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, const IntRect& window_rect, const Gfx::Palette& palette) const
|
||||
IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, const IntRect& window_rect, const Gfx::Palette& palette, int menu_row_count) const
|
||||
{
|
||||
auto window_titlebar_height = title_bar_height(window_type, palette);
|
||||
|
||||
|
@ -200,9 +209,9 @@ IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, const
|
|||
case WindowType::ToolWindow:
|
||||
return {
|
||||
window_rect.x() - 4,
|
||||
window_rect.y() - window_titlebar_height - 6,
|
||||
window_rect.y() - window_titlebar_height - 6 - menu_row_count * menu_bar_height,
|
||||
window_rect.width() + 8,
|
||||
window_rect.height() + 10 + window_titlebar_height
|
||||
window_rect.height() + 10 + window_titlebar_height + menu_row_count * menu_bar_height
|
||||
};
|
||||
case WindowType::Notification:
|
||||
return {
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
ClassicWindowTheme();
|
||||
virtual ~ClassicWindowTheme() override;
|
||||
|
||||
virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect) const override;
|
||||
virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect, int menu_row_count) const override;
|
||||
virtual void paint_tool_window_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Palette&, const IntRect& leftmost_button_rect) const override;
|
||||
virtual void paint_notification_frame(Painter&, const IntRect& window_rect, const Palette&, const IntRect& close_button_rect) const override;
|
||||
|
||||
|
@ -45,7 +45,9 @@ public:
|
|||
virtual IntRect title_bar_icon_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
|
||||
virtual IntRect title_bar_text_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
|
||||
|
||||
virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const override;
|
||||
virtual IntRect menu_bar_rect(WindowType, const IntRect& window_rect, const Palette&, int menu_row_count) const override;
|
||||
|
||||
virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&, int menu_row_count) const override;
|
||||
|
||||
virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const override;
|
||||
virtual bool is_simple_rect_frame() const override { return true; }
|
||||
|
|
|
@ -51,7 +51,7 @@ public:
|
|||
|
||||
static WindowTheme& current();
|
||||
|
||||
virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect) const = 0;
|
||||
virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect, int menu_row_count) const = 0;
|
||||
virtual void paint_tool_window_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Palette&, const IntRect& leftmost_button_rect) const = 0;
|
||||
virtual void paint_notification_frame(Painter&, const IntRect& window_rect, const Palette&, const IntRect& close_button_rect) const = 0;
|
||||
|
||||
|
@ -60,7 +60,9 @@ public:
|
|||
virtual IntRect title_bar_icon_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
|
||||
virtual IntRect title_bar_text_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
|
||||
|
||||
virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const = 0;
|
||||
virtual IntRect menu_bar_rect(WindowType, const IntRect& window_rect, const Palette&, int menu_row_count) const = 0;
|
||||
|
||||
virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&, int menu_row_count) const = 0;
|
||||
|
||||
virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const = 0;
|
||||
virtual bool is_simple_rect_frame() const = 0;
|
||||
|
|
|
@ -104,7 +104,7 @@ void ClientConnection::notify_about_new_screen_rect(const Gfx::IntRect& rect)
|
|||
OwnPtr<Messages::WindowServer::CreateMenubarResponse> ClientConnection::handle(const Messages::WindowServer::CreateMenubar&)
|
||||
{
|
||||
int menubar_id = m_next_menubar_id++;
|
||||
auto menubar = make<MenuBar>(*this, menubar_id);
|
||||
auto menubar = MenuBar::create(*this, menubar_id);
|
||||
m_menubars.set(menubar_id, move(menubar));
|
||||
return make<Messages::WindowServer::CreateMenubarResponse>(menubar_id);
|
||||
}
|
||||
|
@ -160,6 +160,30 @@ OwnPtr<Messages::WindowServer::SetApplicationMenubarResponse> ClientConnection::
|
|||
return make<Messages::WindowServer::SetApplicationMenubarResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowMenubarResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowMenubar& message)
|
||||
{
|
||||
RefPtr<Window> window;
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowMenubar: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
window = it->value;
|
||||
}
|
||||
RefPtr<MenuBar> menubar;
|
||||
if (message.menubar_id() != -1) {
|
||||
auto it = m_menubars.find(message.menubar_id());
|
||||
if (it == m_menubars.end()) {
|
||||
did_misbehave("SetWindowMenubar: Bad menubar ID");
|
||||
return {};
|
||||
}
|
||||
menubar = *(*it).value;
|
||||
}
|
||||
window->set_menubar(menubar);
|
||||
return make<Messages::WindowServer::SetWindowMenubarResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::AddMenuToMenubarResponse> ClientConnection::handle(const Messages::WindowServer::AddMenuToMenubar& message)
|
||||
{
|
||||
int menubar_id = message.menubar_id();
|
||||
|
|
|
@ -78,6 +78,15 @@ public:
|
|||
return menu.value().ptr();
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_window(Callback callback)
|
||||
{
|
||||
for (auto& it : m_windows) {
|
||||
if (callback(*it.value) == IterationDecision::Break)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void notify_display_link(Badge<Compositor>);
|
||||
|
||||
private:
|
||||
|
@ -98,6 +107,7 @@ private:
|
|||
virtual OwnPtr<Messages::WindowServer::DestroyMenuResponse> handle(const Messages::WindowServer::DestroyMenu&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::AddMenuToMenubarResponse> handle(const Messages::WindowServer::AddMenuToMenubar&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetApplicationMenubarResponse> handle(const Messages::WindowServer::SetApplicationMenubar&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowMenubarResponse> handle(const Messages::WindowServer::SetWindowMenubar&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::AddMenuItemResponse> handle(const Messages::WindowServer::AddMenuItem&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::AddMenuSeparatorResponse> handle(const Messages::WindowServer::AddMenuSeparator&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::UpdateMenuItemResponse> handle(const Messages::WindowServer::UpdateMenuItem&) override;
|
||||
|
@ -157,7 +167,7 @@ private:
|
|||
Window* window_from_id(i32 window_id);
|
||||
|
||||
HashMap<int, NonnullRefPtr<Window>> m_windows;
|
||||
HashMap<int, NonnullOwnPtr<MenuBar>> m_menubars;
|
||||
HashMap<int, NonnullRefPtr<MenuBar>> m_menubars;
|
||||
HashMap<int, NonnullRefPtr<Menu>> m_menus;
|
||||
WeakPtr<MenuBar> m_app_menubar;
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ int Menu::content_width() const
|
|||
if (widest_shortcut)
|
||||
widest_item += padding_between_text_and_shortcut() + widest_shortcut;
|
||||
|
||||
return max(widest_item, rect_in_menubar().width()) + horizontal_padding() + frame_thickness() * 2;
|
||||
return max(widest_item, rect_in_global_menubar().width()) + horizontal_padding() + frame_thickness() * 2;
|
||||
}
|
||||
|
||||
void Menu::redraw()
|
||||
|
|
|
@ -74,11 +74,15 @@ public:
|
|||
callback(item);
|
||||
}
|
||||
|
||||
Gfx::IntRect text_rect_in_menubar() const { return m_text_rect_in_menubar; }
|
||||
void set_text_rect_in_menubar(const Gfx::IntRect& rect) { m_text_rect_in_menubar = rect; }
|
||||
Gfx::IntRect text_rect_in_global_menubar() const { return m_text_rect_in_global_menubar; }
|
||||
void set_text_rect_in_global_menubar(const Gfx::IntRect& rect) { m_text_rect_in_global_menubar = rect; }
|
||||
Gfx::IntRect rect_in_global_menubar() const { return m_rect_in_global_menubar; }
|
||||
void set_rect_in_global_menubar(const Gfx::IntRect& rect) { m_rect_in_global_menubar = rect; }
|
||||
|
||||
Gfx::IntRect rect_in_menubar() const { return m_rect_in_menubar; }
|
||||
void set_rect_in_menubar(const Gfx::IntRect& rect) { m_rect_in_menubar = rect; }
|
||||
Gfx::IntRect text_rect_in_window_menubar() const { return m_text_rect_in_window_menubar; }
|
||||
void set_text_rect_in_window_menubar(const Gfx::IntRect& rect) { m_text_rect_in_window_menubar = rect; }
|
||||
Gfx::IntRect rect_in_window_menubar() const { return m_rect_in_window_menubar; }
|
||||
void set_rect_in_window_menubar(const Gfx::IntRect& rect) { m_rect_in_window_menubar = rect; }
|
||||
|
||||
Window* menu_window() { return m_menu_window.ptr(); }
|
||||
Window& ensure_menu_window();
|
||||
|
@ -149,8 +153,10 @@ private:
|
|||
ClientConnection* m_client { nullptr };
|
||||
int m_menu_id { 0 };
|
||||
String m_name;
|
||||
Gfx::IntRect m_rect_in_menubar;
|
||||
Gfx::IntRect m_text_rect_in_menubar;
|
||||
Gfx::IntRect m_rect_in_global_menubar;
|
||||
Gfx::IntRect m_text_rect_in_global_menubar;
|
||||
Gfx::IntRect m_rect_in_window_menubar;
|
||||
Gfx::IntRect m_text_rect_in_window_menubar;
|
||||
MenuBar* m_menubar { nullptr };
|
||||
NonnullOwnPtrVector<MenuItem> m_items;
|
||||
RefPtr<Window> m_menu_window;
|
||||
|
|
|
@ -33,9 +33,11 @@
|
|||
|
||||
namespace WindowServer {
|
||||
|
||||
class MenuBar : public Weakable<MenuBar> {
|
||||
class MenuBar
|
||||
: public RefCounted<MenuBar>
|
||||
, public Weakable<MenuBar> {
|
||||
public:
|
||||
MenuBar(ClientConnection& client, int menubar_id);
|
||||
static NonnullRefPtr<MenuBar> create(ClientConnection& client, int menubar_id) { return adopt(*new MenuBar(client, menubar_id)); }
|
||||
~MenuBar();
|
||||
|
||||
ClientConnection& client() { return m_client; }
|
||||
|
@ -53,6 +55,8 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
MenuBar(ClientConnection&, int menubar_id);
|
||||
|
||||
ClientConnection& m_client;
|
||||
int m_menubar_id { 0 };
|
||||
Vector<Menu*> m_menus;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <WindowServer/AppletManager.h>
|
||||
#include <WindowServer/ClientConnection.h>
|
||||
#include <WindowServer/MenuManager.h>
|
||||
#include <WindowServer/Screen.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
|
@ -96,19 +97,20 @@ void MenuManager::draw()
|
|||
painter.draw_line({ 0, menubar_rect.bottom() }, { menubar_rect.right(), menubar_rect.bottom() }, palette.threed_shadow2());
|
||||
|
||||
for_each_active_menubar_menu([&](Menu& menu) {
|
||||
auto text_rect = menu.text_rect_in_global_menubar();
|
||||
Color text_color = palette.window_text();
|
||||
if (is_open(menu) && &menu == m_current_menu_bar_menu) {
|
||||
painter.fill_rect(menu.rect_in_menubar(), palette.menu_selection());
|
||||
painter.draw_rect(menu.rect_in_menubar(), palette.menu_selection().darkened());
|
||||
painter.fill_rect(menu.rect_in_global_menubar(), palette.menu_selection());
|
||||
painter.draw_rect(menu.rect_in_global_menubar(), palette.menu_selection().darkened());
|
||||
text_color = palette.menu_selection_text();
|
||||
text_rect.move_by(1, 1);
|
||||
}
|
||||
painter.draw_text(
|
||||
menu.text_rect_in_menubar(),
|
||||
text_rect,
|
||||
menu.name(),
|
||||
menu.title_font(),
|
||||
Gfx::TextAlignment::CenterLeft,
|
||||
text_color);
|
||||
//painter.draw_rect(menu.text_rect_in_menubar(), Color::Magenta);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
|
@ -216,7 +218,7 @@ void MenuManager::handle_mouse_event(MouseEvent& mouse_event)
|
|||
auto* active_window = WindowManager::the().active_window();
|
||||
bool handled_menubar_event = false;
|
||||
for_each_active_menubar_menu([&](Menu& menu) {
|
||||
if (menu.rect_in_menubar().contains(mouse_event.position())) {
|
||||
if (menu.rect_in_global_menubar().contains(mouse_event.position())) {
|
||||
handled_menubar_event = &menu == m_system_menu || !active_window || !active_window->is_modal();
|
||||
if (handled_menubar_event)
|
||||
handle_menu_mouse_event(menu, mouse_event);
|
||||
|
@ -383,6 +385,25 @@ void MenuManager::close_menu_and_descendants(Menu& menu)
|
|||
close_menus(menus_to_close);
|
||||
}
|
||||
|
||||
void MenuManager::set_hovered_menu(Menu* menu)
|
||||
{
|
||||
if (m_hovered_menu == menu)
|
||||
return;
|
||||
if (menu) {
|
||||
m_hovered_menu = menu->make_weak_ptr<Menu>();
|
||||
} else {
|
||||
// FIXME: This is quite aggressive. If we knew which window the previously hovered menu was in,
|
||||
// we could just invalidate that one instead of iterating all windows in the client.
|
||||
if (auto* client = m_hovered_menu->client()) {
|
||||
client->for_each_window([&](Window& window) {
|
||||
window.invalidate_menubar();
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
m_hovered_menu = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::open_menu(Menu& menu, bool from_menu_bar, bool as_current_menu)
|
||||
{
|
||||
if (from_menu_bar)
|
||||
|
@ -405,7 +426,7 @@ void MenuManager::open_menu(Menu& menu, bool from_menu_bar, bool as_current_menu
|
|||
menu.ensure_menu_window();
|
||||
}
|
||||
if (should_update_position)
|
||||
menu.menu_window()->move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() + 2 });
|
||||
menu.menu_window()->move_to({ menu.rect_in_global_menubar().x(), menu.rect_in_global_menubar().bottom() + 2 });
|
||||
menu.menu_window()->set_visible(true);
|
||||
}
|
||||
|
||||
|
@ -430,6 +451,10 @@ void MenuManager::clear_current_menu()
|
|||
// When closing the last menu, restore the previous active input window
|
||||
auto& wm = WindowManager::the();
|
||||
wm.restore_active_input_window(m_previous_input_window);
|
||||
if (auto* window = wm.window_with_active_menu()) {
|
||||
window->invalidate_menubar();
|
||||
}
|
||||
wm.set_window_with_active_menu(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -485,12 +510,12 @@ void MenuManager::set_current_menubar(MenuBar* menubar)
|
|||
Gfx::IntPoint next_menu_location { MenuManager::menubar_menu_margin() / 2, 0 };
|
||||
for_each_active_menubar_menu([&](Menu& menu) {
|
||||
int text_width = menu.title_font().width(menu.name());
|
||||
menu.set_rect_in_menubar({ next_menu_location.x() - MenuManager::menubar_menu_margin() / 2, 0, text_width + MenuManager::menubar_menu_margin(), menubar_rect().height() - 1 });
|
||||
menu.set_rect_in_global_menubar({ next_menu_location.x() - MenuManager::menubar_menu_margin() / 2, 0, text_width + MenuManager::menubar_menu_margin(), menubar_rect().height() - 1 });
|
||||
|
||||
Gfx::IntRect text_rect { next_menu_location.translated(0, 1), { text_width, menubar_rect().height() - 3 } };
|
||||
|
||||
menu.set_text_rect_in_menubar(text_rect);
|
||||
next_menu_location.move_by(menu.rect_in_menubar().width(), 0);
|
||||
menu.set_text_rect_in_global_menubar(text_rect);
|
||||
next_menu_location.move_by(menu.rect_in_global_menubar().width(), 0);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
refresh();
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
bool has_open_menu() const { return !m_open_menu_stack.is_empty(); }
|
||||
|
||||
Gfx::IntRect menubar_rect() const;
|
||||
static int menubar_menu_margin() { return 16; }
|
||||
static int menubar_menu_margin() { return 13; }
|
||||
|
||||
void set_needs_window_resize();
|
||||
|
||||
|
@ -93,6 +93,9 @@ public:
|
|||
|
||||
void did_change_theme();
|
||||
|
||||
void set_hovered_menu(Menu*);
|
||||
Menu* hovered_menu() { return m_hovered_menu; }
|
||||
|
||||
private:
|
||||
void close_menus(const Vector<Menu*>&);
|
||||
|
||||
|
@ -121,6 +124,7 @@ private:
|
|||
int m_theme_index { 0 };
|
||||
|
||||
WeakPtr<MenuBar> m_current_menubar;
|
||||
WeakPtr<Menu> m_hovered_menu;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -942,4 +942,36 @@ bool Window::hit_test(const Gfx::IntPoint& point, bool include_frame) const
|
|||
return color.alpha() >= threshold;
|
||||
}
|
||||
|
||||
void Window::set_menubar(MenuBar* menubar)
|
||||
{
|
||||
if (m_menubar == menubar)
|
||||
return;
|
||||
m_menubar = menubar;
|
||||
if (m_menubar) {
|
||||
auto& wm = WindowManager::the();
|
||||
Gfx::IntPoint next_menu_location { MenuManager::menubar_menu_margin() / 2, 0 };
|
||||
auto menubar_rect = Gfx::WindowTheme::current().menu_bar_rect(Gfx::WindowTheme::WindowType::Normal, rect(), wm.palette(), 1);
|
||||
m_menubar->for_each_menu([&](Menu& menu) {
|
||||
int text_width = wm.font().width(menu.name());
|
||||
menu.set_rect_in_window_menubar({ next_menu_location.x() - MenuManager::menubar_menu_margin() / 2, 0, text_width + MenuManager::menubar_menu_margin(), menubar_rect.height() });
|
||||
|
||||
Gfx::IntRect text_rect { next_menu_location.translated(0, 1), { text_width, menubar_rect.height() - 3 } };
|
||||
|
||||
menu.set_text_rect_in_window_menubar(text_rect);
|
||||
next_menu_location.move_by(menu.rect_in_window_menubar().width(), 0);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
Compositor::the().invalidate_occlusions();
|
||||
frame().invalidate();
|
||||
}
|
||||
|
||||
void Window::invalidate_menubar()
|
||||
{
|
||||
if (!menubar())
|
||||
return;
|
||||
// FIXME: This invalidates way more than the menubar!
|
||||
frame().invalidate();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ namespace WindowServer {
|
|||
class ClientConnection;
|
||||
class Cursor;
|
||||
class Menu;
|
||||
class MenuBar;
|
||||
class MenuItem;
|
||||
class MouseEvent;
|
||||
|
||||
|
@ -192,6 +193,7 @@ public:
|
|||
|
||||
void invalidate(bool with_frame = true, bool re_render_frame = false);
|
||||
void invalidate(const Gfx::IntRect&, bool with_frame = false);
|
||||
void invalidate_menubar();
|
||||
bool invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame = false);
|
||||
|
||||
void refresh_client_size();
|
||||
|
@ -326,6 +328,10 @@ public:
|
|||
Gfx::DisjointRectSet& transparency_rects() { return m_transparency_rects; }
|
||||
Gfx::DisjointRectSet& transparency_wallpaper_rects() { return m_transparency_wallpaper_rects; }
|
||||
|
||||
MenuBar* menubar() { return m_menubar; }
|
||||
const MenuBar* menubar() const { return m_menubar; }
|
||||
void set_menubar(MenuBar*);
|
||||
|
||||
private:
|
||||
void handle_mouse_event(const MouseEvent&);
|
||||
void update_menu_item_text(PopupMenuItem item);
|
||||
|
@ -341,6 +347,8 @@ private:
|
|||
Vector<WeakPtr<Window>> m_child_windows;
|
||||
Vector<WeakPtr<Window>> m_accessory_windows;
|
||||
|
||||
RefPtr<MenuBar> m_menubar;
|
||||
|
||||
String m_title;
|
||||
Gfx::IntRect m_rect;
|
||||
Gfx::IntRect m_saved_nonfullscreen_rect;
|
||||
|
|
|
@ -79,7 +79,8 @@ static Gfx::IntRect frame_rect_for_window(Window& window, const Gfx::IntRect& re
|
|||
{
|
||||
if (window.is_frameless())
|
||||
return rect;
|
||||
return Gfx::WindowTheme::current().frame_rect_for_window(to_theme_window_type(window.type()), rect, WindowManager::the().palette());
|
||||
int menu_row_count = window.menubar() ? 1 : 0;
|
||||
return Gfx::WindowTheme::current().frame_rect_for_window(to_theme_window_type(window.type()), rect, WindowManager::the().palette(), menu_row_count);
|
||||
}
|
||||
|
||||
WindowFrame::WindowFrame(Window& window)
|
||||
|
@ -238,6 +239,13 @@ void WindowFrame::did_set_maximized(Badge<Window>, bool maximized)
|
|||
m_maximize_button->set_icon(maximized ? *s_restore_icon : *s_maximize_icon);
|
||||
}
|
||||
|
||||
Gfx::IntRect WindowFrame::menubar_rect() const
|
||||
{
|
||||
if (!m_window.menubar())
|
||||
return {};
|
||||
return Gfx::WindowTheme::current().menu_bar_rect(to_theme_window_type(m_window.type()), m_window.rect(), WindowManager::the().palette(), menu_row_count());
|
||||
}
|
||||
|
||||
Gfx::IntRect WindowFrame::title_bar_rect() const
|
||||
{
|
||||
return Gfx::WindowTheme::current().title_bar_rect(to_theme_window_type(m_window.type()), m_window.rect(), WindowManager::the().palette());
|
||||
|
@ -289,11 +297,46 @@ void WindowFrame::paint_tool_window_frame(Gfx::Painter& painter)
|
|||
Gfx::WindowTheme::current().paint_tool_window_frame(painter, window_state_for_theme(), m_window.rect(), compute_title_text(), palette, leftmost_button_rect);
|
||||
}
|
||||
|
||||
void WindowFrame::paint_menubar(Gfx::Painter& painter)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto& font = wm.font();
|
||||
auto palette = wm.palette();
|
||||
auto menubar_rect = this->menubar_rect();
|
||||
|
||||
painter.fill_rect(menubar_rect, palette.window());
|
||||
|
||||
Gfx::PainterStateSaver saver(painter);
|
||||
painter.translate(menubar_rect.location());
|
||||
|
||||
m_window.menubar()->for_each_menu([&](Menu& menu) {
|
||||
auto text_rect = menu.text_rect_in_window_menubar();
|
||||
Color text_color = palette.window_text();
|
||||
if (MenuManager::the().is_open(menu)) {
|
||||
painter.fill_rect(menu.rect_in_window_menubar(), palette.menu_selection());
|
||||
text_color = palette.menu_selection_text();
|
||||
text_rect.move_by(1, 1);
|
||||
}
|
||||
if (&menu == MenuManager::the().hovered_menu() || MenuManager::the().is_open(menu))
|
||||
Gfx::StylePainter::paint_button(painter, menu.rect_in_window_menubar(), palette, Gfx::ButtonStyle::CoolBar, MenuManager::the().is_open(menu), true);
|
||||
painter.draw_text(
|
||||
text_rect,
|
||||
menu.name(),
|
||||
font,
|
||||
Gfx::TextAlignment::CenterLeft,
|
||||
text_color);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void WindowFrame::paint_normal_frame(Gfx::Painter& painter)
|
||||
{
|
||||
auto palette = WindowManager::the().palette();
|
||||
auto leftmost_button_rect = m_buttons.is_empty() ? Gfx::IntRect() : m_buttons.last().relative_rect();
|
||||
Gfx::WindowTheme::current().paint_normal_frame(painter, window_state_for_theme(), m_window.rect(), compute_title_text(), m_window.icon(), palette, leftmost_button_rect);
|
||||
Gfx::WindowTheme::current().paint_normal_frame(painter, window_state_for_theme(), m_window.rect(), compute_title_text(), m_window.icon(), palette, leftmost_button_rect, menu_row_count());
|
||||
|
||||
if (m_window.menubar())
|
||||
paint_menubar(painter);
|
||||
}
|
||||
|
||||
void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect)
|
||||
|
@ -509,6 +552,13 @@ void WindowFrame::invalidate_title_bar()
|
|||
invalidate(title_bar_rect());
|
||||
}
|
||||
|
||||
void WindowFrame::invalidate()
|
||||
{
|
||||
auto frame_rect = render_rect();
|
||||
invalidate(Gfx::IntRect { frame_rect.location() - m_window.position(), frame_rect.size() });
|
||||
m_window.invalidate(true, true);
|
||||
}
|
||||
|
||||
void WindowFrame::invalidate(Gfx::IntRect relative_rect)
|
||||
{
|
||||
auto frame_rect = rect();
|
||||
|
@ -652,6 +702,13 @@ void WindowFrame::on_mouse_event(const MouseEvent& event)
|
|||
return;
|
||||
}
|
||||
|
||||
auto menubar_rect = this->menubar_rect();
|
||||
if (menubar_rect.contains(event.position())) {
|
||||
wm.clear_resize_candidate();
|
||||
handle_menubar_mouse_event(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_window.is_resizable() && event.type() == Event::MouseMove && event.buttons() == 0) {
|
||||
constexpr ResizeDirection direction_for_hot_area[3][3] = {
|
||||
{ ResizeDirection::UpLeft, ResizeDirection::Up, ResizeDirection::UpRight },
|
||||
|
@ -673,6 +730,50 @@ void WindowFrame::on_mouse_event(const MouseEvent& event)
|
|||
wm.start_window_resize(m_window, event.translated(rect().location()));
|
||||
}
|
||||
|
||||
void WindowFrame::handle_menubar_mouse_event(const MouseEvent& event)
|
||||
{
|
||||
Menu* hovered_menu = nullptr;
|
||||
auto menubar_rect = this->menubar_rect();
|
||||
auto adjusted_position = event.position().translated(-menubar_rect.location());
|
||||
m_window.menubar()->for_each_menu([&](Menu& menu) {
|
||||
if (menu.rect_in_window_menubar().contains(adjusted_position)) {
|
||||
hovered_menu = &menu;
|
||||
handle_menu_mouse_event(menu, event);
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (!hovered_menu && event.type() == Event::Type::MouseDown)
|
||||
MenuManager::the().close_everyone();
|
||||
if (hovered_menu != MenuManager::the().hovered_menu()) {
|
||||
MenuManager::the().set_hovered_menu(hovered_menu);
|
||||
invalidate(menubar_rect);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowFrame::handle_menu_mouse_event(Menu& menu, const MouseEvent& event)
|
||||
{
|
||||
auto menubar_rect = this->menubar_rect();
|
||||
bool is_hover_with_any_menu_open = event.type() == MouseEvent::MouseMove && &m_window == WindowManager::the().window_with_active_menu();
|
||||
bool is_mousedown_with_left_button = event.type() == MouseEvent::MouseDown && event.button() == MouseButton::Left;
|
||||
bool should_open_menu = &menu != MenuManager::the().current_menu() && (is_hover_with_any_menu_open || is_mousedown_with_left_button);
|
||||
bool should_close_menu = &menu == MenuManager::the().current_menu() && is_mousedown_with_left_button;
|
||||
|
||||
if (should_open_menu) {
|
||||
MenuManager::the().close_everyone();
|
||||
menu.ensure_menu_window().move_to(menu.rect_in_window_menubar().bottom_left().translated(rect().location()).translated(menubar_rect.location()));
|
||||
MenuManager::the().open_menu(menu, false);
|
||||
WindowManager::the().set_window_with_active_menu(&m_window);
|
||||
invalidate(menubar_rect);
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_close_menu) {
|
||||
invalidate(menubar_rect);
|
||||
MenuManager::the().close_everyone();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowFrame::start_flash_animation()
|
||||
{
|
||||
if (!m_flash_timer) {
|
||||
|
@ -774,4 +875,9 @@ void WindowFrame::paint_simple_rect_shadow(Gfx::Painter& painter, const Gfx::Int
|
|||
paint_vertical(containing_rect.right() - base_size + 1, 1, 0, horizontal_shift);
|
||||
}
|
||||
|
||||
int WindowFrame::menu_row_count() const
|
||||
{
|
||||
return m_window.menubar() ? 1 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
namespace WindowServer {
|
||||
|
||||
class Button;
|
||||
class Menu;
|
||||
class MouseEvent;
|
||||
class Window;
|
||||
|
||||
|
@ -43,7 +44,7 @@ class WindowFrame {
|
|||
public:
|
||||
static void reload_config();
|
||||
|
||||
WindowFrame(Window&);
|
||||
explicit WindowFrame(Window&);
|
||||
~WindowFrame();
|
||||
|
||||
Gfx::IntRect rect() const;
|
||||
|
@ -55,11 +56,15 @@ public:
|
|||
void notify_window_rect_changed(const Gfx::IntRect& old_rect, const Gfx::IntRect& new_rect);
|
||||
void invalidate_title_bar();
|
||||
void invalidate(Gfx::IntRect relative_rect);
|
||||
void invalidate();
|
||||
|
||||
Gfx::IntRect title_bar_rect() const;
|
||||
Gfx::IntRect title_bar_icon_rect() const;
|
||||
Gfx::IntRect title_bar_text_rect() const;
|
||||
|
||||
Gfx::IntRect menubar_rect() const;
|
||||
int menu_row_count() const;
|
||||
|
||||
void did_set_maximized(Badge<Window>, bool);
|
||||
|
||||
void layout_buttons();
|
||||
|
@ -97,11 +102,15 @@ private:
|
|||
void paint_notification_frame(Gfx::Painter&);
|
||||
void paint_normal_frame(Gfx::Painter&);
|
||||
void paint_tool_window_frame(Gfx::Painter&);
|
||||
void paint_menubar(Gfx::Painter&);
|
||||
Gfx::Bitmap* window_shadow() const;
|
||||
bool frame_has_alpha() const;
|
||||
Gfx::IntRect inflated_for_shadow(const Gfx::IntRect&) const;
|
||||
Gfx::Bitmap* inflate_for_shadow(Gfx::IntRect&, Gfx::IntPoint&) const;
|
||||
|
||||
void handle_menubar_mouse_event(const MouseEvent&);
|
||||
void handle_menu_mouse_event(Menu&, const MouseEvent&);
|
||||
|
||||
Gfx::WindowTheme::WindowState window_state_for_theme() const;
|
||||
String compute_title_text() const;
|
||||
|
||||
|
|
|
@ -206,6 +206,8 @@ void WindowManager::add_window(Window& window)
|
|||
});
|
||||
}
|
||||
|
||||
window.invalidate(true, true);
|
||||
|
||||
tell_wm_listeners_window_state_changed(window);
|
||||
}
|
||||
|
||||
|
@ -914,8 +916,21 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind
|
|||
if (m_hovered_button && event.type() == Event::MouseMove)
|
||||
m_hovered_button->on_mouse_event(event.translated(-m_hovered_button->screen_rect().location()));
|
||||
|
||||
// FIXME: Now that the menubar has a dedicated window, is this special-casing really necessary?
|
||||
if (MenuManager::the().has_open_menu() || menubar_rect().contains(event.position())) {
|
||||
bool hitting_menu_in_window_with_active_menu = [&] {
|
||||
if (!m_window_with_active_menu)
|
||||
return false;
|
||||
auto& frame = m_window_with_active_menu->frame();
|
||||
return frame.menubar_rect().contains(event.position().translated(-frame.rect().location()));
|
||||
}();
|
||||
|
||||
// FIXME: This is quite hackish, we clear the hovered menu before potentially setting the same menu
|
||||
// as hovered again. This makes sure that the hovered state doesn't linger after moving the
|
||||
// cursor away from a hovered menu.
|
||||
MenuManager::the().set_hovered_menu(nullptr);
|
||||
|
||||
if (MenuManager::the().has_open_menu()
|
||||
|| hitting_menu_in_window_with_active_menu
|
||||
|| menubar_rect().contains(event.position())) {
|
||||
for_each_visible_window_of_type_from_front_to_back(WindowType::MenuApplet, [&](auto& window) {
|
||||
if (!window.rect_in_menubar().contains(event.position()))
|
||||
return IterationDecision::Continue;
|
||||
|
@ -923,9 +938,12 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind
|
|||
return IterationDecision::Break;
|
||||
});
|
||||
clear_resize_candidate();
|
||||
|
||||
if (!hitting_menu_in_window_with_active_menu) {
|
||||
MenuManager::the().dispatch_event(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Window* event_window_with_frame = nullptr;
|
||||
|
||||
|
@ -1456,8 +1474,8 @@ Gfx::IntRect WindowManager::maximized_window_rect(const Window& window) const
|
|||
Gfx::IntRect rect = Screen::the().rect();
|
||||
|
||||
// Subtract window title bar (leaving the border)
|
||||
rect.set_y(rect.y() + window.frame().title_bar_rect().height());
|
||||
rect.set_height(rect.height() - window.frame().title_bar_rect().height());
|
||||
rect.set_y(rect.y() + window.frame().title_bar_rect().height() + window.frame().menubar_rect().height());
|
||||
rect.set_height(rect.height() - window.frame().title_bar_rect().height() - window.frame().menubar_rect().height());
|
||||
|
||||
// Subtract menu bar
|
||||
rect.set_y(rect.y() + menubar_rect().height());
|
||||
|
@ -1611,4 +1629,15 @@ void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icon
|
|||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void WindowManager::set_window_with_active_menu(Window* window)
|
||||
{
|
||||
if (m_window_with_active_menu == window)
|
||||
return;
|
||||
if (window)
|
||||
m_window_with_active_menu = window->make_weak_ptr<Window>();
|
||||
else
|
||||
m_window_with_active_menu = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -113,6 +113,10 @@ public:
|
|||
const Window* active_input_window() const { return m_active_input_window.ptr(); }
|
||||
const ClientConnection* active_client() const;
|
||||
|
||||
Window* window_with_active_menu() { return m_window_with_active_menu; }
|
||||
const Window* window_with_active_menu() const { return m_window_with_active_menu; }
|
||||
void set_window_with_active_menu(Window*);
|
||||
|
||||
const Window* highlight_window() const { return m_highlight_window.ptr(); }
|
||||
void set_highlight_window(Window*);
|
||||
|
||||
|
@ -329,6 +333,7 @@ private:
|
|||
WeakPtr<Window> m_highlight_window;
|
||||
WeakPtr<Window> m_active_input_window;
|
||||
WeakPtr<Window> m_active_input_tracking_window;
|
||||
WeakPtr<Window> m_window_with_active_menu;
|
||||
|
||||
WeakPtr<Window> m_move_window;
|
||||
Gfx::IntPoint m_move_origin;
|
||||
|
|
|
@ -52,6 +52,8 @@ endpoint WindowServer = 2
|
|||
|
||||
DestroyWindow(i32 window_id) => (Vector<i32> destroyed_window_ids)
|
||||
|
||||
SetWindowMenubar(i32 window_id, i32 menubar_id) => ()
|
||||
|
||||
SetWindowTitle(i32 window_id, [UTF8] String title) => ()
|
||||
GetWindowTitle(i32 window_id) => ([UTF8] String title)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue