fix(tui): login picker scrolls in a capped viewport; the moon never clips
Root cause (issues: unscrollable list + half-blocked moon): the stacked welcome layout gave the menu one Length row per item. With ~30 advertised providers the vertical layout overflowed and the constraint solver squeezed every Length — clipping the logo — while rows past the fold were silently unreachable. - WelcomeLayout::compute_inner caps the menu to the rows genuinely left over after logo/prompt/version, so the chrome never gets squeezed. - render_menu is now a minimal-scroll viewport: it scrolls only when the selection exits it (stable under hover — no re-centering feedback loop), draws the picker-style scrollbar, and returns index-aligned hit rects (zero Rect for off-screen rows) plus the offset, which AppView feeds back next frame. - Mouse wheel moves the login-picker selection (clamped, not wrapping). Test: pending_menu_scrolls_and_never_clips_the_moon renders the full method list at 40 rows and asserts the whole moon (10 braille rows), the version badge, and that selecting Quit scrolls it into view. Verified: kigi-tui 6864 tests green, clippy clean.
This commit is contained in:
1 parent
48d89c7830
commit
c2d1067f0d
5 files changed
+175
-14
No files matched your search
@@ -695,6 +695,9 @@ pub struct AppView {
|
||||
pub(crate) minimal_state: crate::minimal_api::MinimalState,
|
||||
/// Currently highlighted menu item on the welcome screen (arrow keys / hover).
|
||||
pub welcome_menu_index: Option<usize>,
|
||||
/// Login-picker menu viewport offset (minimal-scroll; fed back into the
|
||||
/// next render so the list only moves when the selection exits it).
|
||||
pub welcome_menu_scroll: usize,
|
||||
/// Hit-test rects for welcome menu items (populated during render).
|
||||
pub welcome_menu_rects: Vec<ratatui::layout::Rect>,
|
||||
/// Hit-test rect for the import-claude banner on the welcome screen.
|
||||
@@ -1006,6 +1009,7 @@ impl AppView {
|
||||
pending_pager_ansi: false,
|
||||
minimal_state: crate::minimal_api::MinimalState::default(),
|
||||
welcome_menu_index: None,
|
||||
welcome_menu_scroll: 0,
|
||||
welcome_menu_rects: Vec::new(),
|
||||
welcome_import_banner_rect: None,
|
||||
last_mouse_pos: None,
|
||||
@@ -2747,6 +2751,24 @@ fn handle_welcome_input(ev: &Event, ctx: &mut WelcomeInputCtx<'_>) -> InputOutco
|
||||
return InputOutcome::Changed;
|
||||
}
|
||||
}
|
||||
// Wheel on the login picker: move the selection (the menu
|
||||
// viewport follows it). Clamped at the ends — a wheel must not
|
||||
// wrap around like the arrow keys do.
|
||||
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp
|
||||
if matches!(ctx.auth_state, AuthState::Pending { .. }) =>
|
||||
{
|
||||
let count = pending_menu_items(ctx.auth_methods, None).len();
|
||||
let down = matches!(mouse.kind, MouseEventKind::ScrollDown);
|
||||
let next = match (*ctx.menu_index, down) {
|
||||
(Some(i), true) => (i + 1).min(count.saturating_sub(1)),
|
||||
(Some(i), false) => i.saturating_sub(1),
|
||||
(None, _) => 0,
|
||||
};
|
||||
if *ctx.menu_index != Some(next) {
|
||||
*ctx.menu_index = Some(next);
|
||||
return InputOutcome::Changed;
|
||||
}
|
||||
}
|
||||
MouseEventKind::Moved => {
|
||||
let mut new_index = None;
|
||||
for (i, rect) in ctx.menu_rects.iter().enumerate() {
|
||||
@@ -3151,6 +3173,7 @@ impl AppView {
|
||||
model_name: &model_name,
|
||||
flags: &flags_vec,
|
||||
selected: self.welcome_menu_index,
|
||||
menu_scroll: self.welcome_menu_scroll,
|
||||
has_claude_import: self.has_claude_import,
|
||||
mouse_pos: self.last_mouse_pos,
|
||||
session_picker: self.session_picker_entries.as_deref(),
|
||||
@@ -3183,6 +3206,7 @@ impl AppView {
|
||||
&mut self.session_picker_state,
|
||||
);
|
||||
self.welcome_menu_rects = result.menu_rects;
|
||||
self.welcome_menu_scroll = result.menu_scroll;
|
||||
self.welcome_prompt_rect = result.prompt_rect;
|
||||
self.welcome_import_banner_rect = result.import_banner_rect;
|
||||
self.welcome_auth_url_rect = result.auth_url_rect;
|
||||
@@ -4282,6 +4306,7 @@ pub(crate) mod tests {
|
||||
welcome_prompt_focused: false,
|
||||
welcome_tip_typing_dismissed: false,
|
||||
welcome_menu_index: None,
|
||||
welcome_menu_scroll: 0,
|
||||
welcome_menu_rects: Vec::new(),
|
||||
welcome_import_banner_rect: None,
|
||||
last_mouse_pos: None,
|
||||
|
||||
@@ -140,6 +140,7 @@ fn test_app() -> AppView {
|
||||
welcome_prompt_focused: false,
|
||||
welcome_tip_typing_dismissed: false,
|
||||
welcome_menu_index: None,
|
||||
welcome_menu_scroll: 0,
|
||||
welcome_menu_rects: Vec::new(),
|
||||
welcome_import_banner_rect: None,
|
||||
last_mouse_pos: None,
|
||||
|
||||
Reference in new issue
Block a user