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
@@ -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));@@ -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; } }@@ -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í@@ -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 Apps@@ -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 coincide@@ -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 correspond@@ -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álat@@ -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 一致するアプリがありません@@ -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 aplikacji@@ -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 encontrado@@ -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 Нет подходящих приложений@@ -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() {@@ -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 没有匹配的应用@@ -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;new file mode 100644@@ -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);+ }+ }+}@@ -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, } } }@@ -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 {