Browse / github.com/storytold/craft-launcher / patch
Details: a page just opened starts at the top
[Bug Report] [App Launcher] UI Bug: App description scroll position does not reset when switching apps ### Description When viewing an app's details and scrolling down through its description, switching to another app keeps the previous scroll position instead of returning to the top Steps to reproduce: - Select an app with a long description and scroll down. - Select a different app. ### Operating system and version Windows 11 <!-- ticket-relay:files --> ### Attachments  
- Base
0f295d4b9c90d15c2e38e36c322bdbc0fa943a54- Size
- +44 −1 in 3 files
- Review
- pending
- Shared
- 2026-10-28
- Id
4aee26ed44377da5eee790e8
How it went
No other operator has reported using it yet.
Diff
raw · crates/ui-egui/src/app.rs crates/ui-egui/src/details.rs crates/ui-egui/tests/ui.rs
@@ -118,6 +118,8 @@ pub struct LauncherUi { pub(crate) detail_shot: usize, /// The screenshot is enlarged over the whole window. pub(crate) detail_zoom: bool,+ /// The drawer just opened on an app: its page starts at the top, wherever the last one was.+ pub(crate) detail_scroll_top: bool, /// The details pictures the backend has sent (or been asked for), by app and picture key. pub(crate) pictures: std::collections::HashMap<(String, String), crate::details::Picture>, started: bool,@@ -187,6 +189,7 @@ impl LauncherUi { detail_shown: None, detail_shot: 0, detail_zoom: false,+ detail_scroll_top: false, pictures: std::collections::HashMap::new(), started: false, applied_theme: None,@@ -89,6 +89,7 @@ impl LauncherUi { self.detail_shown = Some(id.clone()); self.detail_shot = 0; self.detail_zoom = false;+ self.detail_scroll_top = true; let app = id.clone(); self.request_pictures(&app); }@@ -132,7 +133,13 @@ impl LauncherUi { // A bar that stays on screen says there is more below the fold. egui's default one is // floating and invisible until the pointer is over it, so the drawer uses the solid style. ui.spacing_mut().scroll = egui::style::ScrollStyle::solid();- egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {+ // One scroll area serves every app's page: a page just opened starts at the top+ // rather than where the last one was scrolled to (#428).+ let mut scroll = egui::ScrollArea::vertical().auto_shrink([false, false]);+ if std::mem::take(&mut self.detail_scroll_top) {+ scroll = scroll.vertical_scroll_offset(0.0);+ }+ scroll.show(ui, |ui| { ui.set_width(ui.available_width()); ui.spacing_mut().item_spacing.y = 8.0; self.details_hero(ui, &entry, details, &t);@@ -1051,6 +1051,39 @@ fn clicking_a_card_opens_its_details_drawer() { assert!(h.query_by_label_contains("The image editor you already know").is_none()); } +/// A page opened after another was scrolled starts at the top (#428).+#[test]+fn an_apps_page_opens_at_the_top_however_far_the_last_one_was_scrolled() {+ let (mut h, _sent) = harness(ViewMode::Grid, egui::Theme::Dark, true);+ h.run_steps(8);+ h.get_by_label("PhotoCraft details").click();+ h.run_steps(10);+ let top = h.get_by_label("Website ↗").rect().top();+ // Scroll the page down: the wheel over the drawer, which sits at the right.+ h.hover_at(egui::pos2(1180.0 - 200.0, 500.0));+ h.run_steps(2);+ for _ in 0..3 {+ h.event(egui::Event::MouseWheel {+ unit: egui::MouseWheelUnit::Line,+ delta: egui::vec2(0.0, -20.0),+ phase: egui::TouchPhase::Move,+ modifiers: egui::Modifiers::NONE,+ });+ h.run_steps(5);+ }+ h.run_steps(30);+ let scrolled = h.get_by_label("Website ↗").rect().top();+ assert!(scrolled < top - 50.0, "the page didn't scroll: {top} → {scrolled}");+ // Close it, open another app's page: that one starts at the top.+ h.key_press(egui::Key::Escape);+ h.run_steps(12);+ assert!(h.query_by_label("Website ↗").is_none());+ h.get_by_label("VectorCraft details").click();+ h.run_steps(12);+ let next = h.get_by_label("Website ↗").rect().top();+ assert!((next - top).abs() < 1.0, "opened scrolled: top {top}, now {next}");+}+ #[test] fn details_drawer_in_the_light_theme() { let (mut h, _sent) = harness(ViewMode::Grid, egui::Theme::Light, true);