1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 18: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:
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

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