@@ -92,7 +92,7 @@ static const int s_item_icon_width = 16;
92
92
static const int s_checkbox_or_icon_padding = 6 ;
93
93
static const int s_stripe_width = 23 ;
94
94
95
- int WSMenu::width () const
95
+ int WSMenu::content_width () const
96
96
{
97
97
int widest_text = 0 ;
98
98
int widest_shortcut = 0 ;
@@ -114,13 +114,6 @@ int WSMenu::width() const
114
114
return max (widest_item, rect_in_menubar ().width ()) + horizontal_padding () + frame_thickness () * 2 ;
115
115
}
116
116
117
- int WSMenu::height () const
118
- {
119
- if (m_items.is_empty ())
120
- return 0 ;
121
- return (m_items.last ().rect ().bottom () + 1 ) + frame_thickness ();
122
- }
123
-
124
117
void WSMenu::redraw ()
125
118
{
126
119
if (!menu_window ())
@@ -131,7 +124,7 @@ void WSMenu::redraw()
131
124
132
125
WSWindow& WSMenu::ensure_menu_window ()
133
126
{
134
- int width = this ->width ();
127
+ int width = this ->content_width ();
135
128
if (!m_menu_window) {
136
129
Point next_item_location (frame_thickness (), frame_thickness ());
137
130
for (auto & item : m_items) {
@@ -144,14 +137,32 @@ WSWindow& WSMenu::ensure_menu_window()
144
137
next_item_location.move_by (0 , height);
145
138
}
146
139
140
+ int window_height_available = WSScreen::the ().height () - WSMenuManager::the ().menubar_rect ().height () - frame_thickness () * 2 ;
141
+ int max_window_height = (window_height_available / item_height ()) * item_height () + frame_thickness () * 2 ;
142
+ int content_height = m_items.is_empty () ? 0 : (m_items.last ().rect ().bottom () + 1 ) + frame_thickness ();
143
+ int window_height = min (max_window_height, content_height);
144
+ if (window_height < content_height) {
145
+ m_scrollable = true ;
146
+ m_max_scroll_offset = item_count () - window_height / item_height () + 2 ;
147
+ }
148
+
147
149
auto window = WSWindow::construct (*this , WSWindowType::Menu);
148
- window->set_rect (0 , 0 , width, height () );
150
+ window->set_rect (0 , 0 , width, window_height );
149
151
m_menu_window = move (window);
150
152
draw ();
151
153
}
152
154
return *m_menu_window;
153
155
}
154
156
157
+ int WSMenu::visible_item_count () const
158
+ {
159
+ if (!is_scrollable ())
160
+ return m_items.size ();
161
+ ASSERT (m_menu_window);
162
+ // Make space for up/down arrow indicators
163
+ return m_menu_window->height () / item_height () - 2 ;
164
+ }
165
+
155
166
void WSMenu::draw ()
156
167
{
157
168
auto palette = WSWindowManager::the ().palette ();
@@ -164,7 +175,7 @@ void WSMenu::draw()
164
175
Rect rect { {}, menu_window ()->size () };
165
176
painter.fill_rect (rect.shrunken (6 , 6 ), palette.menu_base ());
166
177
StylePainter::paint_window_frame (painter, rect, palette);
167
- int width = this ->width ();
178
+ int width = this ->content_width ();
168
179
169
180
if (!s_checked_bitmap)
170
181
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()
176
187
has_items_with_icon = has_items_with_icon | !!item.icon ();
177
188
}
178
189
179
- Rect stripe_rect { frame_thickness (), frame_thickness (), s_stripe_width, height () - frame_thickness () * 2 };
190
+ Rect stripe_rect { frame_thickness (), frame_thickness (), s_stripe_width, menu_window ()-> height () - frame_thickness () * 2 };
180
191
painter.fill_rect (stripe_rect, palette.menu_stripe ());
181
192
painter.draw_line (stripe_rect.top_right (), stripe_rect.bottom_right (), palette.menu_stripe ().darkened ());
182
193
183
- for (auto & item : m_items) {
194
+ int visible_item_count = this ->visible_item_count ();
195
+
196
+ if (is_scrollable ()) {
197
+ bool can_go_up = m_scroll_offset > 0 ;
198
+ bool can_go_down = m_scroll_offset < m_max_scroll_offset;
199
+ Rect up_indicator_rect { frame_thickness (), frame_thickness (), content_width (), item_height () };
200
+ painter.draw_text (up_indicator_rect, " \xc3\xb6 " , TextAlignment::Center, can_go_up ? palette.menu_base_text () : palette.color (ColorRole::DisabledText));
201
+ Rect down_indicator_rect { frame_thickness (), menu_window ()->height () - item_height () - frame_thickness (), content_width (), item_height () };
202
+ painter.draw_text (down_indicator_rect, " \xc3\xb7 " , TextAlignment::Center, can_go_down ? palette.menu_base_text () : palette.color (ColorRole::DisabledText));
203
+ }
204
+
205
+ for (int i = 0 ; i < visible_item_count; ++i) {
206
+ auto & item = m_items.at (m_scroll_offset + i);
184
207
if (item.type () == WSMenuItem::Text) {
185
208
Color text_color = palette.menu_base_text ();
186
209
if (&item == hovered_item () && item.is_enabled ()) {
@@ -277,34 +300,40 @@ void WSMenu::decend_into_submenu_at_hovered_item()
277
300
m_in_submenu = true ;
278
301
}
279
302
280
- void WSMenu::event (CEvent & event)
303
+ void WSMenu::handle_hover_event ( const WSMouseEvent & event)
281
304
{
282
- if (event.type () == WSEvent::MouseMove) {
283
- ASSERT (menu_window ());
284
- auto mouse_event = static_cast <const WSMouseEvent&>(event);
305
+ ASSERT (menu_window ());
306
+ auto mouse_event = static_cast <const WSMouseEvent&>(event);
285
307
286
- if (hovered_item () && hovered_item ()->is_submenu ()) {
308
+ if (hovered_item () && hovered_item ()->is_submenu ()) {
287
309
288
- auto item = *hovered_item ();
289
- auto submenu_top_left = item.rect ().location () + Point { item.rect ().width (), 0 };
290
- auto submenu_bottom_left = submenu_top_left + Point { 0 , item.submenu ()->height () };
310
+ auto item = *hovered_item ();
311
+ auto submenu_top_left = item.rect ().location () + Point { item.rect ().width (), 0 };
312
+ auto submenu_bottom_left = submenu_top_left + Point { 0 , item.submenu ()-> menu_window ()->height () };
291
313
292
- auto safe_hover_triangle = Triangle { m_last_position_in_hover, submenu_top_left, submenu_bottom_left };
293
- m_last_position_in_hover = mouse_event.position ();
314
+ auto safe_hover_triangle = Triangle { m_last_position_in_hover, submenu_top_left, submenu_bottom_left };
315
+ m_last_position_in_hover = mouse_event.position ();
294
316
295
- // Don't update the hovered item if mouse is moving towards a submenu
296
- if (safe_hover_triangle.contains (mouse_event.position ()))
297
- return ;
298
- }
299
-
300
- int index = item_index_at (mouse_event.position ());
301
- if (m_hovered_item_index == index)
317
+ // Don't update the hovered item if mouse is moving towards a submenu
318
+ if (safe_hover_triangle.contains (mouse_event.position ()))
302
319
return ;
303
- m_hovered_item_index = index;
320
+ }
304
321
305
- // FIXME: Tell parent menu (if it exists) that it is currently in a submenu
306
- m_in_submenu = false ;
307
- update_for_new_hovered_item ();
322
+ int index = item_index_at (mouse_event.position ());
323
+ if (m_hovered_item_index == index)
324
+ return ;
325
+ m_hovered_item_index = index;
326
+
327
+ // FIXME: Tell parent menu (if it exists) that it is currently in a submenu
328
+ m_in_submenu = false ;
329
+ update_for_new_hovered_item ();
330
+ return ;
331
+ }
332
+
333
+ void WSMenu::event (CEvent& event)
334
+ {
335
+ if (event.type () == WSEvent::MouseMove) {
336
+ handle_hover_event (static_cast <const WSMouseEvent&>(event));
308
337
return ;
309
338
}
310
339
@@ -313,6 +342,18 @@ void WSMenu::event(CEvent& event)
313
342
return ;
314
343
}
315
344
345
+ if (event.type () == WSEvent::MouseWheel && is_scrollable ()) {
346
+ auto & mouse_event = static_cast <const WSMouseEvent&>(event);
347
+ m_scroll_offset += mouse_event.wheel_delta ();
348
+ if (m_scroll_offset < 0 )
349
+ m_scroll_offset = 0 ;
350
+ if (m_scroll_offset >= m_max_scroll_offset)
351
+ m_scroll_offset = m_max_scroll_offset;
352
+ handle_hover_event (mouse_event);
353
+ redraw ();
354
+ return ;
355
+ }
356
+
316
357
if (event.type () == WSEvent::KeyDown) {
317
358
auto key = static_cast <WSKeyEvent&>(event).key ();
318
359
@@ -347,25 +388,37 @@ void WSMenu::event(CEvent& event)
347
388
if (key == Key_Up) {
348
389
ASSERT (m_items.at (0 ).type () != WSMenuItem::Separator);
349
390
391
+ if (is_scrollable () && m_hovered_item_index == 0 )
392
+ return ;
393
+
350
394
do {
351
395
m_hovered_item_index--;
352
396
if (m_hovered_item_index < 0 )
353
397
m_hovered_item_index = m_items.size () - 1 ;
354
398
} while (hovered_item ()->type () == WSMenuItem::Separator);
355
399
400
+ if (is_scrollable () && m_hovered_item_index < m_scroll_offset)
401
+ --m_scroll_offset;
402
+
356
403
update_for_new_hovered_item ();
357
404
return ;
358
405
}
359
406
360
407
if (key == Key_Down) {
361
408
ASSERT (m_items.at (0 ).type () != WSMenuItem::Separator);
362
409
410
+ if (is_scrollable () && m_hovered_item_index == m_items.size () - 1 )
411
+ return ;
412
+
363
413
do {
364
414
m_hovered_item_index++;
365
415
if (m_hovered_item_index >= m_items.size ())
366
416
m_hovered_item_index = 0 ;
367
417
} while (hovered_item ()->type () == WSMenuItem::Separator);
368
418
419
+ if (is_scrollable () && m_hovered_item_index >= (m_scroll_offset + visible_item_count ()))
420
+ ++m_scroll_offset;
421
+
369
422
update_for_new_hovered_item ();
370
423
return ;
371
424
}
@@ -452,16 +505,16 @@ void WSMenu::popup(const Point& position, bool is_submenu)
452
505
453
506
const int margin = 30 ;
454
507
Point adjusted_pos = position;
455
- if (window.height () >= WSScreen::the ().height ()) {
456
- adjusted_pos.set_y (0 );
457
- } else {
458
- if (adjusted_pos.x () + window.width () >= WSScreen::the ().width () - margin) {
459
- adjusted_pos = adjusted_pos.translated (-window.width (), 0 );
460
- }
461
- if (adjusted_pos.y () + window.height () >= WSScreen::the ().height () - margin) {
462
- adjusted_pos = adjusted_pos.translated (0 , -window.height ());
463
- }
508
+
509
+ if (adjusted_pos.x () + window.width () >= WSScreen::the ().width () - margin) {
510
+ adjusted_pos = adjusted_pos.translated (-window.width (), 0 );
464
511
}
512
+ if (adjusted_pos.y () + window.height () >= WSScreen::the ().height () - margin) {
513
+ adjusted_pos = adjusted_pos.translated (0 , -window.height ());
514
+ }
515
+
516
+ if (adjusted_pos.y () < WSMenuManager::the ().menubar_rect ().height ())
517
+ adjusted_pos.set_y (WSMenuManager::the ().menubar_rect ().height ());
465
518
466
519
window.move_to (adjusted_pos);
467
520
window.set_visible (true );
0 commit comments