// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use crate::app::App; use crate::event_loop::{ AppData, SurfaceFocus }; use crate::widget::scroll::offset_into_view; impl AppData { /// Step the topmost scroll's `hovered_idx` one item up or down with /// the keyboard. Returns `true` when the move was applied (a scroll /// with at least one navigable item was on screen and the new item /// is different from the current hover) so the caller can fall /// through to the application's own `on_key` only on a no-op. /// /// The "topmost scroll" is the last entry in `scroll_rects`, which /// matches Stack-overlay layout order: a popup pushed after the /// main content sits above it. Auto-scrolls the new item into view /// by adjusting `scroll_offsets[scroll_idx]`. pub( crate ) fn move_keyboard_hover( &mut self, focus: SurfaceFocus, reverse: bool ) -> bool { // Find the topmost scroll that has a navigable item list. let scroll_meta = { let ss = self.surface( focus ); ss.frame.scroll_rects.iter().rev() .find_map( |( rect, idx, _ax )| { ss.frame.scroll_navigable_items.get( idx ) .filter( |list| !list.is_empty() ) .map( |list| ( *rect, *idx, list.clone() ) ) } ) }; let Some( ( scroll_rect, scroll_idx, items ) ) = scroll_meta else { return false; }; let current = self.surface( focus ).hovered_idx; let pos = current.and_then( |h| items.iter().position( |( i, _, _ )| *i == h ) ); let next_pos = match ( reverse, pos ) { ( false, None ) => 0, ( false, Some( p ) ) => ( p + 1 ).min( items.len() - 1 ), ( true, None ) => items.len() - 1, ( true, Some( p ) ) => p.saturating_sub( 1 ), }; let ( new_idx, content_y, content_h ) = items[ next_pos ]; // Keyboard navigation only steps on the navigable-item axis (vertical // list-style); the X offset is preserved as-is. let ( current_x, current_y ) = self.surface( focus ) .frame.scroll_offsets.get( &scroll_idx ) .copied().unwrap_or( ( 0.0, 0.0 ) ); let new_y = offset_into_view( current_y, content_y, content_h, scroll_rect.height ); let ss = self.surface_mut( focus ); ss.hovered_idx = Some( new_idx ); ss.frame.scroll_offsets.insert( scroll_idx, ( current_x, new_y ) ); ss.request_redraw(); true } }