1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 12:07:44 +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:
Andreas Kling 2021-03-25 21:01:19 +01:00
parent 1daaa4f38d
commit e76771bfad
21 changed files with 335 additions and 44 deletions

View file

@ -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);

View file

@ -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));

View file

@ -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());
}
}

View file

@ -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 };

View file

@ -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 {

View file

@ -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; }

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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()

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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;
};
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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)