Scroll long menus and lists with the mouse wheel or a swipe

Touch clicks in menus happen on release without a swipe. The first-start
language picker is a flag grid that scrolls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 16:04:20 +01:00
1 parent d8e9184a9c
commit 6629472811
4 files changed
+157 -25

No files matched your search

+47 -10
View File
@@ -370,6 +370,7 @@ pub struct Ui {
pub show_help: bool,
pub show_overview: bool, // list of all creatures
ov_scroll: usize,
ov_rect: Option<Rect>, // where the creature overview is (mouse wheel, swipe)
pub show_langsel: bool,
pub log: Vec<(Msg, f64)>,
pub chat_log: Vec<(Option<u8>, String, String, f64)>,
@@ -387,6 +388,7 @@ pub struct Ui {
end_buttons: Vec<(Rect, u8)>,
goals_rect: Rect, // mission goals panel (click: briefing) // end dialog: 0 new game, 1 next/again, 2 campaign
lang_rects: Vec<(Rect, String)>,
langsel_scroll: usize, // first line of the first-start language grid
pub hand_anim: Option<(u8, f64)>, // 1 = backhand, 2 = punch, start time
// touch interface
open_cat: Option<usize>,
@@ -430,6 +432,7 @@ impl Ui {
show_help: true,
show_overview: false,
ov_scroll: 0,
ov_rect: None,
show_langsel: false,
log: Vec::new(),
chat_log: Vec::new(),
@@ -447,6 +450,7 @@ impl Ui {
end_buttons: Vec::new(),
goals_rect: Rect::new(0.0, 0.0, 0.0, 0.0),
lang_rects: Vec::new(),
langsel_scroll: 0,
hand_anim: None,
open_cat: None,
tip: None,
@@ -1026,6 +1030,7 @@ impl Ui {
let y = (bottom - rows as f32 * CH()).max(top);
gfx.window_titled(x, y, cols, rows, S(), false, FRAME_HI, BG, &tr("ov.title"), FRAME_HI);
self.rects.push(Rect::new(x, y, cols as f32 * CW(), rows as f32 * CH()));
self.ov_rect = Some(Rect::new(x, y, cols as f32 * CW(), rows as f32 * CH()));
let mine: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect();
let wage: f32 = mine.iter().map(|e| st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)).sum();
let mood = if mine.is_empty() { 0.0 } else { mine.iter().map(|e| e.happy.clamp(0.0, 1.0)).sum::<f32>() / mine.len() as f32 };
@@ -1152,39 +1157,71 @@ impl Ui {
}
// First start: choose the language (names shown in their own language)
// first start: every language as a button with its flag, in a grid
fn draw_langsel(&mut self, gfx: &Gfx) {
use crate::menu::flags::{draw_flag, FLAG_H, FLAG_W};
let sw = sw();
let sh = sh();
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.6));
let langs = languages();
let cols = 50;
let rows = 6 + langs.len() as i32 * 3;
let bw_c = 22; // button width in characters
let grid = (((sw / CW()) as i32 - 6) / (bw_c + 2)).clamp(1, 3);
let lines = (langs.len() as i32 + grid - 1) / grid;
let cols = (grid * (bw_c + 2) + 4).min((sw / CW()) as i32);
let rows = (lines * 3 + 6).min((sh / CH()) as i32);
let x = (grid_center(sw, cols as f32 * CW())).max(0.0);
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
// the title shows all language names, since no language is chosen yet
let title = langs.iter().map(|(c, _)| crate::i18n::tr_in(c, "langsel.title")).collect::<Vec<_>>().join(" / ");
// no language is chosen yet: the title in English, the hint in English and German
let title = crate::i18n::tr_in("en", "langsel.title");
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI);
self.lang_rects.clear();
let (mx, my) = mouse();
// more lines than fit: the grid scrolls (mouse wheel, swipe)
let fit = ((rows - 6) / 3).max(1);
self.langsel_scroll = self.langsel_scroll.min((lines - fit).max(0) as usize);
let bw = bw_c as f32 * CW();
let gx = x + (cols as f32 * CW() - grid as f32 * (bw + 2.0 * CW()) + 2.0 * CW()) * 0.5;
for (k, (code, name)) in langs.iter().enumerate() {
let bw = 24.0 * CW();
let bx = grid_center(sw, bw);
let by = y + CH() * (2.0 + k as f32 * 3.0);
let line = k as i32 / grid - self.langsel_scroll as i32;
if line < 0 || line >= fit {
continue;
}
let bx = gx + (k as i32 % grid) as f32 * (bw + 2.0 * CW());
let by = y + CH() * (2.0 + line as f32 * 3.0);
let r = Rect::new(bx, by, bw, CH() * 2.0);
let hov = r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, if hov { FRAME_HI } else { FRAME });
gfx.text(name, bx + (bw - gfx.measure(name, S())) * 0.5, by + CH() * 0.5, S(), TXT);
let px = S();
draw_flag(code, (bx + CW() * 0.75).floor(), (by + (r.h - FLAG_H as f32 * px) * 0.5).floor(), px);
let tx = bx + CW() * 0.75 + FLAG_W as f32 * px + CW();
let name: String = name.chars().take(((bx + bw - tx) / CW()).max(1.0) as usize - 1).collect();
gfx.text(&name, tx, by + CH() * 0.5, S(), TXT);
self.lang_rects.push((r, code.clone()));
}
let hint_y = y + CH() * (2.0 + langs.len() as f32 * 3.0 + 0.5);
for (k, (c, _)) in langs.iter().enumerate() {
let hint_y = y + CH() * (2.0 + fit as f32 * 3.0 + 0.5);
for (k, c) in ["en", "de"].iter().enumerate() {
let h = crate::i18n::tr_in(c, "langsel.hint");
let h: String = h.chars().take((cols as usize).saturating_sub(4) * 2).collect();
gfx.text(&h, x + CW() * 2.0, hint_y + CH() * k as f32, SMALL(), DIM);
}
self.rects.push(Rect::new(0.0, 0.0, sw, sh));
}
// mouse wheel / swipe over the creature overview: scrolls it; true if it was there
pub fn overview_wheel(&mut self, x: f32, y: f32, d: i32) -> bool {
if !self.show_overview || !self.ov_rect.map_or(false, |r| r.contains(vec2(x, y))) {
return false;
}
self.ov_scroll = (self.ov_scroll as i32 + d).max(0) as usize;
true
}
// mouse wheel / swipe on the first-start language grid
pub fn langsel_wheel(&mut self, d: i32) {
self.langsel_scroll = (self.langsel_scroll as i32 + d).max(0) as usize;
}
fn draw_end(&mut self, g: &Game, gfx: &Gfx) {
let sw = sw();
let sh = sh();