collective

Browse / github.com/storytold/craft-launcher / patch

Settings: a Mouse wheel scroll speed row, 100 points a notch on Windows and Linux

Bug: Scrolling on the launcher page feels slow in Windows 11 Hello @echelon @dylanmtaylor @vbasky @valenfys Similar to the issue I’m having [here](https://github.com/storytold/pdfcraft/issues/993) When I try to scroll through the ArtCraft Launcher home page on Windows 11, the scrolling speed feels slow—and to me, it’s really slow. This also happens on the app details page, where scrolling is very slow and unresponsive. This is quite annoying for me when using this launcher because, with a scrolling speed like that, it takes a lot of time to scroll through the app pages and specific app details. Here’s a video recording of the scrolling, which I thin

Status
open
Score
0.00
Adopters
0 other operators
Author
XianZheYang (imported from pull request #454, open)
Base
0f295d4b9c90d15c2e38e36c322bdbc0fa943a54
Size
+256 −4 in 17 files
Review
pending
Shared
2026-10-28
Id
fb8725f70597cdc11b3dffcb

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/app.rs crates/ui-egui/src/dialogs.rs crates/ui-egui/src/i18n/cs.tsv crates/ui-egui/src/i18n/de.tsv crates/ui-egui/src/i18n/es.tsv crates/ui-egui/src/i18n/fr.tsv crates/ui-egui/src/i18n/hu.tsv crates/ui-egui/src/i18n/ja.tsv crates/ui-egui/src/i18n/pl.tsv crates/ui-egui/src/i18n/pt-BR.tsv crates/ui-egui/src/i18n/ru.tsv crates/ui-egui/src/i18n/tests.rs crates/ui-egui/src/i18n/zh-CN.tsv crates/ui-egui/src/lib.rs crates/ui-egui/src/scroll_speed.rs crates/ui-egui/src/settings.rs crates/ui-egui/tests/ui.rs

diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs--- a/crates/ui-egui/src/app.rs+++ b/crates/ui-egui/src/app.rs@@ -215,9 +215,11 @@ impl LauncherUi {         self.toasts.clear();     } -    /// Input on its way to egui (the shell's `raw_input_hook`): with fewer animations asked for,-    /// a mouse wheel scrolls at once instead of gliding.+    /// Input on its way to egui (the shell's `raw_input_hook`): a mouse wheel notch scrolls as far+    /// as the Mouse wheel scroll speed setting says, and with fewer animations asked for, it+    /// scrolls at once instead of gliding.     pub fn raw_input(&self, ctx: &egui::Context, raw: &mut egui::RawInput) {+        self.settings.scroll_speed.apply(ctx, raw);         if self.reduce_motion {             motion::unsmooth_wheel(ctx, raw);         }@@ -515,6 +517,9 @@ impl LauncherUi {             motion::apply(&ctx, self.reduce_motion);             self.applied_motion = Some(self.reduce_motion);         }+        // For a host without a `raw_input_hook` (the tests' harness); the hook sets it in time+        // for the frame's own wheel events.+        self.settings.scroll_speed.set_line_speed(&ctx);         let tokens = Tokens::get(&ctx);         if self.entries.iter().any(|e| e.job.is_some()) || self.checking {             ctx.request_repaint_after(Duration::from_millis(50));diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs--- a/crates/ui-egui/src/dialogs.rs+++ b/crates/ui-egui/src/dialogs.rs@@ -6,7 +6,7 @@ use artcraft_launcher_core::{Os, PackageKind}; use egui::{RichText, vec2};  use crate::app::{Dialog, Host, LauncherUi};-use crate::settings::{CheckEvery, Settings};+use crate::settings::{CheckEvery, ScrollSpeed, Settings}; use crate::theme::{self, ThemeChoice, Tokens}; use crate::widgets::{self, Cell, Kind}; @@ -365,5 +365,10 @@ impl LauncherUi {             widgets::cells(ui, &mut theme_choice, &ThemeChoice::ALL.map(|c| (c, Cell { label: c.label(), tip: c.label(), count: None })));         });         self.settings.theme = theme_choice;+        let mut speed = self.settings.scroll_speed;+        row(ui, tl!("Mouse wheel scroll speed"), 240.0, &mut |ui| {+            widgets::cells(ui, &mut speed, &ScrollSpeed::ALL.map(|s| (s, Cell { label: s.label(), tip: s.label(), count: None })));+        });+        self.settings.scroll_speed = speed;     } }diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv--- a/crates/ui-egui/src/i18n/cs.tsv+++ b/crates/ui-egui/src/i18n/cs.tsv@@ -224,6 +224,10 @@ month	Dec	12. 	Match system	Podle systému 	Dark	Tmavý 	Light	Světlý+	Mouse wheel scroll speed	Rychlost posouvání kolečkem myši+scroll speed	Slow	Pomalá+scroll speed	Normal	Normální+scroll speed	Fast	Rychlá 	Full changelog	Úplný přehled změn 	link	odkaz 	No apps match	Žádné aplikace neodpovídají hledánídiff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv--- a/crates/ui-egui/src/i18n/de.tsv+++ b/crates/ui-egui/src/i18n/de.tsv@@ -243,6 +243,10 @@ month	Dec	Dez. 	Match system	Wie das System 	Dark	Dunkel 	Light	Hell+	Mouse wheel scroll speed	Scrollgeschwindigkeit des Mausrads+scroll speed	Slow	Langsam+scroll speed	Normal	Normal+scroll speed	Fast	Schnell 	Full changelog	Vollständiges Änderungsprotokoll 	link	Link 	No apps match	Keine passenden Appsdiff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv--- a/crates/ui-egui/src/i18n/es.tsv+++ b/crates/ui-egui/src/i18n/es.tsv@@ -237,6 +237,10 @@ month	Dec	dic. 	Match system	Igual que el sistema 	Dark	Oscuro 	Light	Claro+	Mouse wheel scroll speed	Velocidad de desplazamiento de la rueda del ratón+scroll speed	Slow	Lenta+scroll speed	Normal	Normal+scroll speed	Fast	Rápida 	Full changelog	Registro de cambios completo 	link	enlace 	No apps match	Ninguna aplicación coincidediff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv--- a/crates/ui-egui/src/i18n/fr.tsv+++ b/crates/ui-egui/src/i18n/fr.tsv@@ -241,6 +241,10 @@ month	Dec	déc. 	Match system	Comme le système 	Dark	Sombre 	Light	Clair+	Mouse wheel scroll speed	Vitesse de défilement de la molette+scroll speed	Slow	Lente+scroll speed	Normal	Normale+scroll speed	Fast	Rapide 	Full changelog	Journal des modifications complet 	link	lien 	No apps match	Aucune application ne corresponddiff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv--- a/crates/ui-egui/src/i18n/hu.tsv+++ b/crates/ui-egui/src/i18n/hu.tsv@@ -244,6 +244,10 @@ month	Dec	dec. 	Match system	Rendszerbeállítás szerint 	Dark	Sötét 	Light	Világos+	Mouse wheel scroll speed	Egérgörgő görgetési sebessége+scroll speed	Slow	Lassú+scroll speed	Normal	Normál+scroll speed	Fast	Gyors 	Full changelog	Teljes változásnapló 	link	hivatkozás 	No apps match	Nincs találatdiff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv--- a/crates/ui-egui/src/i18n/ja.tsv+++ b/crates/ui-egui/src/i18n/ja.tsv@@ -246,6 +246,10 @@ month	Dec	12月 	Match system	システムに合わせる 	Dark	ダーク 	Light	ライト+	Mouse wheel scroll speed	マウスホイールのスクロール速度+scroll speed	Slow	遅い+scroll speed	Normal	標準+scroll speed	Fast	速い 	Full changelog	完全な変更履歴 	link	リンク 	No apps match	一致するアプリがありませんdiff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv--- a/crates/ui-egui/src/i18n/pl.tsv+++ b/crates/ui-egui/src/i18n/pl.tsv@@ -245,6 +245,10 @@ month	Dec	gru 	Match system	Zgodnie z systemem 	Dark	Ciemny 	Light	Jasny+	Mouse wheel scroll speed	Szybkość przewijania kółkiem myszy+scroll speed	Slow	Wolna+scroll speed	Normal	Normalna+scroll speed	Fast	Szybka 	Full changelog	Pełna lista zmian 	link	link 	No apps match	Brak pasujących aplikacjidiff --git a/crates/ui-egui/src/i18n/pt-BR.tsv b/crates/ui-egui/src/i18n/pt-BR.tsv--- a/crates/ui-egui/src/i18n/pt-BR.tsv+++ b/crates/ui-egui/src/i18n/pt-BR.tsv@@ -231,6 +231,10 @@ month	Dec	dez. 	Match system	Igual ao sistema 	Dark	Escuro 	Light	Claro+	Mouse wheel scroll speed	Velocidade de rolagem da roda do mouse+scroll speed	Slow	Lenta+scroll speed	Normal	Normal+scroll speed	Fast	Rápida 	Full changelog	Histórico completo de alterações 	link	link 	No apps match	Nenhum aplicativo encontradodiff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv--- a/crates/ui-egui/src/i18n/ru.tsv+++ b/crates/ui-egui/src/i18n/ru.tsv@@ -240,6 +240,10 @@ month	Dec	дек. 	Match system	Как в системе 	Dark	Тёмная 	Light	Светлая+	Mouse wheel scroll speed	Скорость прокрутки колесом мыши+scroll speed	Slow	Низкая+scroll speed	Normal	Обычная+scroll speed	Fast	Высокая 	Full changelog	Полный список изменений 	link	ссылка 	No apps match	Нет подходящих приложенийdiff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs--- a/crates/ui-egui/src/i18n/tests.rs+++ b/crates/ui-egui/src/i18n/tests.rs@@ -185,6 +185,19 @@ fn complete_catalogs_translate_every_tagline() {     } } +/// The Mouse wheel scroll speed row and the speed names, which qualify "speed" and so have their+/// own context, in every complete catalog.+#[test]+fn complete_catalogs_name_the_scroll_speeds() {+    for lang in Lang::all().filter(|l| l.complete_menus()) {+        assert!(super::has(lang, "Mouse wheel scroll speed"), "{}", lang.code());+        for speed in crate::scroll_speed::ScrollSpeed::ALL {+            assert!(lang.catalog().contextual("scroll speed", speed.key()).is_some(), "{}: {}", lang.code(), speed.key());+        }+    }+    assert_eq!(super::tr_ctx(Lang::RU, "scroll speed", "Slow"), "Низкая");+}+ #[test] fn product_names_stay_in_english() {     for lang in Lang::all() {diff --git a/crates/ui-egui/src/i18n/zh-CN.tsv b/crates/ui-egui/src/i18n/zh-CN.tsv--- a/crates/ui-egui/src/i18n/zh-CN.tsv+++ b/crates/ui-egui/src/i18n/zh-CN.tsv@@ -160,6 +160,10 @@ month	Dec	12月 	Match system	跟随系统 	Dark	深色 	Light	浅色+	Mouse wheel scroll speed	鼠标滚轮滚动速度+scroll speed	Slow	慢+scroll speed	Normal	正常+scroll speed	Fast	快 	Full changelog	完整更新日志 	link	链接 	No apps match	没有匹配的应用diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs--- a/crates/ui-egui/src/lib.rs+++ b/crates/ui-egui/src/lib.rs@@ -26,6 +26,7 @@ pub mod icons; pub mod model; mod motion; mod notes;+mod scroll_speed; pub mod settings; pub mod theme; mod views;diff --git a/crates/ui-egui/src/scroll_speed.rs b/crates/ui-egui/src/scroll_speed.rsnew file mode 100644--- /dev/null+++ b/crates/ui-egui/src/scroll_speed.rs@@ -0,0 +1,130 @@+//! How far a mouse wheel notch scrolls (#386).+//!+//! egui scrolls `line_scroll_speed` points per wheel line: 40 on the desktop, 8 on the web. On+//! macOS the system already accelerates line deltas, and in a browser a line is scaled for the+//! web. But on Windows and Linux winit reports one line per notch, where browsers and most+//! other programs move about 100 pixels, so 40 felt slow. Wheel input reported in points (most+//! trackpads) keeps its own speed, and ⌘/Ctrl-wheel zoom keeps egui's rate.++use egui::{Event, MouseWheelUnit, RawInput};+use serde::{Deserialize, Serialize};++/// The user's choice, relative to the platform's normal speed.+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]+pub enum ScrollSpeed {+    Slow,+    #[default]+    Normal,+    Fast,+}++/// egui's own `line_scroll_speed`, which its wheel zoom rate is tuned against.+const EGUI_LINE_POINTS: f32 = if cfg!(target_arch = "wasm32") { 8.0 } else { 40.0 };++/// Points one wheel line scrolls at Normal speed.+const NORMAL_LINE_POINTS: f32 = if cfg!(target_arch = "wasm32") {+    // The browser's line deltas, which egui already scales for the web.+    8.0+} else if cfg!(target_os = "macos") {+    40.0+} else {+    100.0+};++/// The catalog context of the speed names: they qualify "speed".+const CONTEXT: &str = "scroll speed";++impl ScrollSpeed {+    pub const ALL: [Self; 3] = [Self::Slow, Self::Normal, Self::Fast];++    /// The English name, the catalog key.+    pub fn key(self) -> &'static str {+        match self {+            Self::Slow => "Slow",+            Self::Normal => "Normal",+            Self::Fast => "Fast",+        }+    }++    /// The name in the language the UI is drawn in.+    pub fn label(self) -> &'static str {+        crate::i18n::tr_ctx(crate::i18n::current(), CONTEXT, self.key())+    }++    /// Points one wheel line scrolls.+    pub fn line_points(self) -> f32 {+        NORMAL_LINE_POINTS+            * match self {+                Self::Slow => 0.5,+                Self::Normal => 1.0,+                Self::Fast => 2.0,+            }+    }++    /// Makes egui scroll this far per wheel line, from this frame's input on (call it before egui+    /// reads `raw`). egui scales line deltas before turning ⌘/Ctrl-wheel motion into zoom, so+    /// those events are scaled back to keep zooming at egui's own rate.+    pub(crate) fn apply(self, ctx: &egui::Context, raw: &mut RawInput) {+        self.set_line_speed(ctx);+        let zoom_modifier = ctx.options(|o| o.input_options.zoom_modifier);+        let zoom_scale = EGUI_LINE_POINTS / self.line_points();+        for event in &mut raw.events {+            if let Event::MouseWheel { unit: MouseWheelUnit::Line, delta, modifiers, .. } = event+                && modifiers.matches_any(zoom_modifier)+            {+                *delta *= zoom_scale;+            }+        }+    }++    /// Makes egui scroll this far per wheel line (from the next frame when called during one).+    pub(crate) fn set_line_speed(self, ctx: &egui::Context) {+        let points = self.line_points();+        if ctx.options(|o| o.input_options.line_scroll_speed) != points {+            ctx.options_mut(|o| o.input_options.line_scroll_speed = points);+        }+    }+}++#[cfg(test)]+mod tests {+    use super::*;++    fn wheel(unit: MouseWheelUnit, dy: f32, command: bool) -> Event {+        let modifiers = egui::Modifiers { command, ..egui::Modifiers::NONE };+        Event::MouseWheel { unit, delta: egui::vec2(0.0, dy), phase: egui::TouchPhase::Move, modifiers }+    }++    #[test]+    fn scrolling_follows_the_choice_and_zoom_keeps_egui_rate() {+        let ctx = egui::Context::default();+        for speed in ScrollSpeed::ALL {+            let mut raw = RawInput {+                events: vec![+                    wheel(MouseWheelUnit::Line, 1.0, true),+                    wheel(MouseWheelUnit::Line, 1.0, false),+                    wheel(MouseWheelUnit::Point, 30.0, true),+                ],+                ..Default::default()+            };+            speed.apply(&ctx, &mut raw);+            assert_eq!(ctx.options(|o| o.input_options.line_scroll_speed), speed.line_points());+            let dy: Vec<f32> = raw.events.iter().filter_map(|e| if let Event::MouseWheel { delta, .. } = e { Some(delta.y) } else { None }).collect();+            // A ⌘/Ctrl line is scaled back so egui's zoom sees its own points per line; a plain+            // line and a point delta are untouched.+            assert!((dy[0] * speed.line_points() - EGUI_LINE_POINTS).abs() < 1e-3, "{speed:?}: {dy:?}");+            assert_eq!((dy[1], dy[2]), (1.0, 30.0));+        }+        assert_eq!(ScrollSpeed::Slow.line_points() * 4.0, ScrollSpeed::Fast.line_points());+    }++    #[test]+    fn normal_is_faster_than_egui_only_where_a_notch_is_one_line() {+        let normal = ScrollSpeed::Normal.line_points();+        if cfg!(any(target_os = "windows", target_os = "linux")) {+            assert!(normal > EGUI_LINE_POINTS, "{normal}");+        } else {+            assert_eq!(normal, EGUI_LINE_POINTS);+        }+    }+}diff --git a/crates/ui-egui/src/settings.rs b/crates/ui-egui/src/settings.rs--- a/crates/ui-egui/src/settings.rs+++ b/crates/ui-egui/src/settings.rs@@ -6,6 +6,7 @@ use artcraft_launcher_core::PackageKind; use serde::{Deserialize, Serialize};  use crate::i18n::t;+pub use crate::scroll_speed::ScrollSpeed; use crate::theme::ThemeChoice;  #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]@@ -114,6 +115,8 @@ pub struct Settings {     pub last_check: Option<i64>,     /// Catalog ids of apps hidden from the main lists (`photocraft`, …).     pub hidden: std::collections::BTreeSet<String>,+    /// How far a mouse wheel notch scrolls.+    pub scroll_speed: ScrollSpeed, }  impl Settings {@@ -186,6 +189,7 @@ impl Default for Settings {             notified: BTreeMap::new(),             last_check: None,             hidden: std::collections::BTreeSet::new(),+            scroll_speed: ScrollSpeed::Normal,         }     } }diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs--- a/crates/ui-egui/tests/ui.rs+++ b/crates/ui-egui/tests/ui.rs@@ -14,7 +14,7 @@ use artcraft_launcher_core::protocol::{ use artcraft_launcher_core::release::{Asset, Release}; use artcraft_launcher_core::status::InstallSource; use artcraft_launcher_core::{Arch, Installed, Os, Platform, SUITE, version};-use artcraft_launcher_ui_egui::settings::ViewMode;+use artcraft_launcher_ui_egui::settings::{ScrollSpeed, ViewMode}; use artcraft_launcher_ui_egui::{Host, LauncherUi, Settings}; use egui_kittest::Harness; use egui_kittest::kittest::{NodeT, Queryable};@@ -1471,6 +1471,60 @@ fn with_animations_on_the_wheel_glides() {     assert!((moved - notch).abs() < 0.5, "moved {moved} of {notch}"); } +// Issue #386: a wheel notch moved egui's 40 points, which felt slow on Windows, where a notch+// is reported as one line and other programs move about 100 pixels. The Mouse wheel scroll+// speed setting decides how far a notch goes.+#[test]+fn a_wheel_notch_scrolls_as_far_as_the_scroll_speed_setting_says() {+    let mut h = short_window(Rc::new(Cell::new(true)));+    for speed in [ScrollSpeed::Normal, ScrollSpeed::Fast, ScrollSpeed::Slow] {+        if let Some(l) = h.state_mut() {+            l.settings.scroll_speed = speed;+        }+        h.run_steps(2);+        let start = grid_top(&h);+        // A notch, or the part of one that moves 100 points: the short window has room for that.+        let lines = (100.0 / speed.line_points()).min(1.0);+        let notch = lines * speed.line_points();+        turn_wheel(&mut h, lines);+        h.run_steps(2);+        let moved = start - grid_top(&h);+        assert!((moved - notch).abs() < 0.5, "{speed:?}: moved {moved} of {notch}");+        // Back to the top for the next speed.+        h.hover_at(egui::pos2(590.0, 300.0));+        for _ in 0..8 {+            turn_wheel(&mut h, -4.0);+        }+        h.run_steps(2);+    }+    // At Normal, the platform's own speed: egui's on macOS, faster where a notch is one line.+    let normal = ScrollSpeed::Normal.line_points();+    if cfg!(any(target_os = "windows", target_os = "linux")) {+        assert!(normal > 40.0, "{normal}");+    } else {+        assert_eq!(normal, 40.0);+    }+    assert_eq!(ScrollSpeed::Fast.line_points(), 2.0 * normal);+    assert_eq!(ScrollSpeed::Slow.line_points(), 0.5 * normal);+}++#[test]+fn the_scroll_speed_is_picked_in_settings() {+    let (mut h, _) = harness(ViewMode::Grid, egui::Theme::Dark, true);+    h.run_steps(6);+    h.get_by_label("Settings").click();+    h.run_steps(4);+    h.get_by_label("Mouse wheel scroll speed");+    h.get_by_label("Fast").click();+    h.run_steps(2);+    assert_eq!(h.state().as_ref().unwrap().settings.scroll_speed, ScrollSpeed::Fast);+    assert_eq!(h.ctx.options(|o| o.input_options.line_scroll_speed), ScrollSpeed::Fast.line_points());+    h.get_by_label("Slow").click();+    h.run_steps(2);+    assert_eq!(h.state().as_ref().unwrap().settings.scroll_speed, ScrollSpeed::Slow);+    assert_eq!(h.ctx.options(|o| o.input_options.line_scroll_speed), ScrollSpeed::Slow.line_points());+}+ /// Moves the pointer from one card to the next. Returns how many frames are drawn before the /// window is still again. fn frames_to_hover_the_next_card(h: &mut Harness<'_, Option<LauncherUi>>) -> u64 {