mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 19:37:35 +00:00
WindowServer: Allow scrolling of menus that don't fit on screen
Menus now have a scroll offset (index based, not pixel based) which is controlled either with the mouse wheel or with the up/down arrow keys. This finally allows us to browse all of the fonts that @xTibor has made avilable through his serenity-fontdev project: https://github.com/xTibor/serenity-fontdev I'm not completely sure about the up/down arrows. They feel like maybe they occupy a bit too much vertical space. Also FIXME: this mechanism probably won't look completely right for menus that have separators in them. Fixes #1043.
This commit is contained in:
parent
5ce1cc89a0
commit
fb7a885cae
4 changed files with 119 additions and 50 deletions
|
@ -92,7 +92,7 @@ static const int s_item_icon_width = 16;
|
|||
static const int s_checkbox_or_icon_padding = 6;
|
||||
static const int s_stripe_width = 23;
|
||||
|
||||
int WSMenu::width() const
|
||||
int WSMenu::content_width() const
|
||||
{
|
||||
int widest_text = 0;
|
||||
int widest_shortcut = 0;
|
||||
|
@ -114,13 +114,6 @@ int WSMenu::width() const
|
|||
return max(widest_item, rect_in_menubar().width()) + horizontal_padding() + frame_thickness() * 2;
|
||||
}
|
||||
|
||||
int WSMenu::height() const
|
||||
{
|
||||
if (m_items.is_empty())
|
||||
return 0;
|
||||
return (m_items.last().rect().bottom() + 1) + frame_thickness();
|
||||
}
|
||||
|
||||
void WSMenu::redraw()
|
||||
{
|
||||
if (!menu_window())
|
||||
|
@ -131,7 +124,7 @@ void WSMenu::redraw()
|
|||
|
||||
WSWindow& WSMenu::ensure_menu_window()
|
||||
{
|
||||
int width = this->width();
|
||||
int width = this->content_width();
|
||||
if (!m_menu_window) {
|
||||
Point next_item_location(frame_thickness(), frame_thickness());
|
||||
for (auto& item : m_items) {
|
||||
|
@ -144,14 +137,32 @@ WSWindow& WSMenu::ensure_menu_window()
|
|||
next_item_location.move_by(0, height);
|
||||
}
|
||||
|
||||
int window_height_available = WSScreen::the().height() - WSMenuManager::the().menubar_rect().height() - frame_thickness() * 2;
|
||||
int max_window_height = (window_height_available / item_height()) * item_height() + frame_thickness() * 2;
|
||||
int content_height = m_items.is_empty() ? 0 : (m_items.last().rect().bottom() + 1) + frame_thickness();
|
||||
int window_height = min(max_window_height, content_height);
|
||||
if (window_height < content_height) {
|
||||
m_scrollable = true;
|
||||
m_max_scroll_offset = item_count() - window_height / item_height() + 2;
|
||||
}
|
||||
|
||||
auto window = WSWindow::construct(*this, WSWindowType::Menu);
|
||||
window->set_rect(0, 0, width, height());
|
||||
window->set_rect(0, 0, width, window_height);
|
||||
m_menu_window = move(window);
|
||||
draw();
|
||||
}
|
||||
return *m_menu_window;
|
||||
}
|
||||
|
||||
int WSMenu::visible_item_count() const
|
||||
{
|
||||
if (!is_scrollable())
|
||||
return m_items.size();
|
||||
ASSERT(m_menu_window);
|
||||
// Make space for up/down arrow indicators
|
||||
return m_menu_window->height() / item_height() - 2;
|
||||
}
|
||||
|
||||
void WSMenu::draw()
|
||||
{
|
||||
auto palette = WSWindowManager::the().palette();
|
||||
|
@ -164,7 +175,7 @@ void WSMenu::draw()
|
|||
Rect rect { {}, menu_window()->size() };
|
||||
painter.fill_rect(rect.shrunken(6, 6), palette.menu_base());
|
||||
StylePainter::paint_window_frame(painter, rect, palette);
|
||||
int width = this->width();
|
||||
int width = this->content_width();
|
||||
|
||||
if (!s_checked_bitmap)
|
||||
s_checked_bitmap = &CharacterBitmap::create_from_ascii(s_checked_bitmap_data, s_checked_bitmap_width, s_checked_bitmap_height).leak_ref();
|
||||
|
@ -176,11 +187,23 @@ void WSMenu::draw()
|
|||
has_items_with_icon = has_items_with_icon | !!item.icon();
|
||||
}
|
||||
|
||||
Rect stripe_rect { frame_thickness(), frame_thickness(), s_stripe_width, height() - frame_thickness() * 2 };
|
||||
Rect stripe_rect { frame_thickness(), frame_thickness(), s_stripe_width, menu_window()->height() - frame_thickness() * 2 };
|
||||
painter.fill_rect(stripe_rect, palette.menu_stripe());
|
||||
painter.draw_line(stripe_rect.top_right(), stripe_rect.bottom_right(), palette.menu_stripe().darkened());
|
||||
|
||||
for (auto& item : m_items) {
|
||||
int visible_item_count = this->visible_item_count();
|
||||
|
||||
if (is_scrollable()) {
|
||||
bool can_go_up = m_scroll_offset > 0;
|
||||
bool can_go_down = m_scroll_offset < m_max_scroll_offset;
|
||||
Rect up_indicator_rect { frame_thickness(), frame_thickness(), content_width(), item_height() };
|
||||
painter.draw_text(up_indicator_rect, "\xc3\xb6", TextAlignment::Center, can_go_up ? palette.menu_base_text() : palette.color(ColorRole::DisabledText));
|
||||
Rect down_indicator_rect { frame_thickness(), menu_window()->height() - item_height() - frame_thickness(), content_width(), item_height() };
|
||||
painter.draw_text(down_indicator_rect, "\xc3\xb7", TextAlignment::Center, can_go_down ? palette.menu_base_text() : palette.color(ColorRole::DisabledText));
|
||||
}
|
||||
|
||||
for (int i = 0; i < visible_item_count; ++i) {
|
||||
auto& item = m_items.at(m_scroll_offset + i);
|
||||
if (item.type() == WSMenuItem::Text) {
|
||||
Color text_color = palette.menu_base_text();
|
||||
if (&item == hovered_item() && item.is_enabled()) {
|
||||
|
@ -277,34 +300,40 @@ void WSMenu::decend_into_submenu_at_hovered_item()
|
|||
m_in_submenu = true;
|
||||
}
|
||||
|
||||
void WSMenu::handle_hover_event(const WSMouseEvent& event)
|
||||
{
|
||||
ASSERT(menu_window());
|
||||
auto mouse_event = static_cast<const WSMouseEvent&>(event);
|
||||
|
||||
if (hovered_item() && hovered_item()->is_submenu()) {
|
||||
|
||||
auto item = *hovered_item();
|
||||
auto submenu_top_left = item.rect().location() + Point { item.rect().width(), 0 };
|
||||
auto submenu_bottom_left = submenu_top_left + Point { 0, item.submenu()->menu_window()->height() };
|
||||
|
||||
auto safe_hover_triangle = Triangle { m_last_position_in_hover, submenu_top_left, submenu_bottom_left };
|
||||
m_last_position_in_hover = mouse_event.position();
|
||||
|
||||
// Don't update the hovered item if mouse is moving towards a submenu
|
||||
if (safe_hover_triangle.contains(mouse_event.position()))
|
||||
return;
|
||||
}
|
||||
|
||||
int index = item_index_at(mouse_event.position());
|
||||
if (m_hovered_item_index == index)
|
||||
return;
|
||||
m_hovered_item_index = index;
|
||||
|
||||
// FIXME: Tell parent menu (if it exists) that it is currently in a submenu
|
||||
m_in_submenu = false;
|
||||
update_for_new_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
||||
void WSMenu::event(CEvent& event)
|
||||
{
|
||||
if (event.type() == WSEvent::MouseMove) {
|
||||
ASSERT(menu_window());
|
||||
auto mouse_event = static_cast<const WSMouseEvent&>(event);
|
||||
|
||||
if (hovered_item() && hovered_item()->is_submenu()) {
|
||||
|
||||
auto item = *hovered_item();
|
||||
auto submenu_top_left = item.rect().location() + Point { item.rect().width(), 0 };
|
||||
auto submenu_bottom_left = submenu_top_left + Point { 0, item.submenu()->height() };
|
||||
|
||||
auto safe_hover_triangle = Triangle { m_last_position_in_hover, submenu_top_left, submenu_bottom_left };
|
||||
m_last_position_in_hover = mouse_event.position();
|
||||
|
||||
// Don't update the hovered item if mouse is moving towards a submenu
|
||||
if (safe_hover_triangle.contains(mouse_event.position()))
|
||||
return;
|
||||
}
|
||||
|
||||
int index = item_index_at(mouse_event.position());
|
||||
if (m_hovered_item_index == index)
|
||||
return;
|
||||
m_hovered_item_index = index;
|
||||
|
||||
// FIXME: Tell parent menu (if it exists) that it is currently in a submenu
|
||||
m_in_submenu = false;
|
||||
update_for_new_hovered_item();
|
||||
handle_hover_event(static_cast<const WSMouseEvent&>(event));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -313,6 +342,18 @@ void WSMenu::event(CEvent& event)
|
|||
return;
|
||||
}
|
||||
|
||||
if (event.type() == WSEvent::MouseWheel && is_scrollable()) {
|
||||
auto& mouse_event = static_cast<const WSMouseEvent&>(event);
|
||||
m_scroll_offset += mouse_event.wheel_delta();
|
||||
if (m_scroll_offset < 0)
|
||||
m_scroll_offset = 0;
|
||||
if (m_scroll_offset >= m_max_scroll_offset)
|
||||
m_scroll_offset = m_max_scroll_offset;
|
||||
handle_hover_event(mouse_event);
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type() == WSEvent::KeyDown) {
|
||||
auto key = static_cast<WSKeyEvent&>(event).key();
|
||||
|
||||
|
@ -347,12 +388,18 @@ void WSMenu::event(CEvent& event)
|
|||
if (key == Key_Up) {
|
||||
ASSERT(m_items.at(0).type() != WSMenuItem::Separator);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index == 0)
|
||||
return;
|
||||
|
||||
do {
|
||||
m_hovered_item_index--;
|
||||
if (m_hovered_item_index < 0)
|
||||
m_hovered_item_index = m_items.size() - 1;
|
||||
} while (hovered_item()->type() == WSMenuItem::Separator);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index < m_scroll_offset)
|
||||
--m_scroll_offset;
|
||||
|
||||
update_for_new_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
@ -360,12 +407,18 @@ void WSMenu::event(CEvent& event)
|
|||
if (key == Key_Down) {
|
||||
ASSERT(m_items.at(0).type() != WSMenuItem::Separator);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index == m_items.size() - 1)
|
||||
return;
|
||||
|
||||
do {
|
||||
m_hovered_item_index++;
|
||||
if (m_hovered_item_index >= m_items.size())
|
||||
m_hovered_item_index = 0;
|
||||
} while (hovered_item()->type() == WSMenuItem::Separator);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index >= (m_scroll_offset + visible_item_count()))
|
||||
++m_scroll_offset;
|
||||
|
||||
update_for_new_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
@ -452,16 +505,16 @@ void WSMenu::popup(const Point& position, bool is_submenu)
|
|||
|
||||
const int margin = 30;
|
||||
Point adjusted_pos = position;
|
||||
if (window.height() >= WSScreen::the().height()) {
|
||||
adjusted_pos.set_y(0);
|
||||
} else {
|
||||
if (adjusted_pos.x() + window.width() >= WSScreen::the().width() - margin) {
|
||||
adjusted_pos = adjusted_pos.translated(-window.width(), 0);
|
||||
}
|
||||
if (adjusted_pos.y() + window.height() >= WSScreen::the().height() - margin) {
|
||||
adjusted_pos = adjusted_pos.translated(0, -window.height());
|
||||
}
|
||||
|
||||
if (adjusted_pos.x() + window.width() >= WSScreen::the().width() - margin) {
|
||||
adjusted_pos = adjusted_pos.translated(-window.width(), 0);
|
||||
}
|
||||
if (adjusted_pos.y() + window.height() >= WSScreen::the().height() - margin) {
|
||||
adjusted_pos = adjusted_pos.translated(0, -window.height());
|
||||
}
|
||||
|
||||
if (adjusted_pos.y() < WSMenuManager::the().menubar_rect().height())
|
||||
adjusted_pos.set_y(WSMenuManager::the().menubar_rect().height());
|
||||
|
||||
window.move_to(adjusted_pos);
|
||||
window.set_visible(true);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue