| author | |
| committer | |
| log | 29bec7f52888c6acb305478e67c31be37a5d3edd |
| tree | 2525b1181c05397ec12505d5d5195a82a4d401fe |
| parent | b27d10a6a06d758d5d362f437f3080857dc9918c |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
A menu capped its list at twelve rows, so the notebook menu scrolled with half
the window free. Menus now run to the window less its popup margin, after
flipping or shifting to fit; the palette and dialog lists keep the cap.
Assisted-by: claude-opus-5.52 files changed, 45 insertions(+), 5 deletions(-)
crates/ui/src/popup.rs+15-5| ... | ... | @@ -20,7 +20,7 @@ pub(crate) const PAD: f32 = 4.0; |
| 20 | 20 | /// A palette's rows and filter field, and Snowbound's more compact menu rows. |
| 21 | 21 | const ROW: f32 = 26.0; |
| 22 | 22 | pub(crate) const MENU_ROW: f32 = 22.0; |
| 23 | /// Rows a list shows before it scrolls. | |
| 23 | /// Rows a palette or a dialog's list shows before it scrolls; a menu runs to the window's edge. | |
| 24 | 24 | const ROWS: f32 = 12.0; |
| 25 | 25 | /// How long the pointer rests on a row before its submenu opens, as Windows waits by default. |
| 26 | 26 | const SUBMENU_DELAY: Duration = Duration::from_millis(200); |
| ... | ... | @@ -115,6 +115,12 @@ pub fn menu( |
| 115 | 115 | .map_or(0.0, |badge| crate::badge_width(ui, badge) + ICON_GAP) |
| 116 | 116 | }) |
| 117 | 117 | .collect(); |
| 118 | let window = ui.rect(Id::ROOT).map_or(0.0, |window| window[3]); | |
| 119 | let field = if filter.is_some() { | |
| 120 | ROW + style.pad | |
| 121 | } else { | |
| 122 | 0.0 | |
| 123 | }; | |
| 118 | 124 | let mut measure = |text| ui.texts.label(text, style.font_size, ui.frame).size[0]; |
| 119 | 125 | let [text, shortcut] = |
| 120 | 126 | items |
| ... | ... | @@ -140,7 +146,7 @@ pub fn menu( |
| 140 | 146 | } else { |
| 141 | 147 | 0.0 |
| 142 | 148 | } + if icons(items) { ICON + ICON_GAP } else { 0.0 } |
| 143 | + if items.len() as f32 > ROWS { | |
| 149 | + if items.len() as f32 * style.row > window - 4.0 * PAD - field { | |
| 144 | 150 | GUTTER |
| 145 | 151 | } else { |
| 146 | 152 | 0.0 |
| ... | ... | @@ -510,9 +516,13 @@ fn choose( |
| 510 | 516 | 0.0 |
| 511 | 517 | }; |
| 512 | 518 | let content = matches.count() as f32 * row + matches.space_before(matches.count()); |
| 513 | let view = content | |
| 514 | .max(row) | |
| 515 | .min((ROWS * row).min(window - 4.0 * PAD - field).max(row)); | |
| 519 | let room = window - 4.0 * PAD - field; | |
| 520 | let most = if role == Role::Menu { | |
| 521 | room | |
| 522 | } else { | |
| 523 | (ROWS * row).min(room) | |
| 524 | }; | |
| 525 | let view = content.max(row).min(most.max(row)); | |
| 516 | 526 | let chosen = if matches.count() == 0 { |
| 517 | 527 | ui.leaf( |
| 518 | 528 | "empty", |
crates/ui/src/tests.rs+30| ... | ... | @@ -2024,6 +2024,36 @@ fn keys_move_the_selection_and_the_view_eases_after_it() { |
| 2024 | 2024 | assert_eq!((selected, row_top(&ui, 0)), (Some(0), Some(0.0))); |
| 2025 | 2025 | } |
| 2026 | 2026 | |
| 2027 | #[test] | |
| 2028 | fn a_menu_runs_to_the_window_edge_before_it_scrolls() { | |
| 2029 | let names: Vec<String> = (0..20).map(|index| format!("Item {index}")).collect(); | |
| 2030 | let items: Vec<_> = names | |
| 2031 | .iter() | |
| 2032 | .map(|text| popup::Item { | |
| 2033 | text, | |
| 2034 | ..popup::Item::default() | |
| 2035 | }) | |
| 2036 | .collect(); | |
| 2037 | let build = |ui: &mut Ui, height: f32| { | |
| 2038 | sized_frame(ui, [400.0, height], |ui| { | |
| 2039 | popup::menu(ui, menu_id(), BELOW, &items, None); | |
| 2040 | }) | |
| 2041 | }; | |
| 2042 | let mut ui = Ui::new(Theme::dark(), DOUBLE_CLICK); | |
| 2043 | build(&mut ui, 800.0); | |
| 2044 | ui.open_popup(menu_id()); | |
| 2045 | for _ in 0..3 { | |
| 2046 | build(&mut ui, 800.0); | |
| 2047 | } | |
| 2048 | let bar = |ui: &Ui| ui.rect(menu_id().child("rows").child("bar")); | |
| 2049 | assert!(bar(&ui).is_none(), "twenty rows fit a tall window whole"); | |
| 2050 | assert!(ui.rect(menu_id().child("rows").child(19u64)).is_some()); | |
| 2051 | for _ in 0..3 { | |
| 2052 | build(&mut ui, 300.0); | |
| 2053 | } | |
| 2054 | assert!(bar(&ui).is_some(), "a short window scrolls them"); | |
| 2055 | } | |
| 2056 | ||
| 2027 | 2057 | #[test] |
| 2028 | 2058 | fn a_long_menu_scrolls_by_dragging_its_thumb() { |
| 2029 | 2059 | let names: Vec<String> = (0..40).map(|index| format!("Item {index}")).collect(); |