From d97bbfc79615699d584874f75e0d284c18f846f4 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Tue, 27 Jan 2026 19:49:11 +0100 Subject: [PATCH] dash-frontend: games: pagination --- dash-frontend/assets/dashboard/arrow_left.svg | 3 + .../assets/dashboard/arrow_right.svg | 3 + dash-frontend/assets/gui/dashboard.xml | 7 +- dash-frontend/assets/gui/tab/games.xml | 6 +- dash-frontend/assets/gui/view/game_list.xml | 7 +- dash-frontend/assets/lang/de.json | 11 +- dash-frontend/assets/lang/en.json | 3 +- dash-frontend/assets/lang/es.json | 11 +- dash-frontend/assets/lang/it.json | 11 +- dash-frontend/assets/lang/ja.json | 11 +- dash-frontend/assets/lang/pl.json | 11 +- dash-frontend/assets/lang/zh_CN.json | 11 +- dash-frontend/src/frontend.rs | 1 - dash-frontend/src/views/game_list.rs | 146 ++++++++++++------ dash-frontend/src/views/running_games_list.rs | 30 ++-- scripts/translator/bun.lock | 1 + wayvr/src/gui/panel/label.rs | 3 +- wgui/src/components/tooltip.rs | 2 +- wgui/src/layout.rs | 74 +++++---- wgui/src/widget/rectangle.rs | 2 +- 20 files changed, 224 insertions(+), 130 deletions(-) create mode 100644 dash-frontend/assets/dashboard/arrow_left.svg create mode 100644 dash-frontend/assets/dashboard/arrow_right.svg diff --git a/dash-frontend/assets/dashboard/arrow_left.svg b/dash-frontend/assets/dashboard/arrow_left.svg new file mode 100644 index 0000000..76bd320 --- /dev/null +++ b/dash-frontend/assets/dashboard/arrow_left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/dash-frontend/assets/dashboard/arrow_right.svg b/dash-frontend/assets/dashboard/arrow_right.svg new file mode 100644 index 0000000..583a946 --- /dev/null +++ b/dash-frontend/assets/dashboard/arrow_right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/dash-frontend/assets/gui/dashboard.xml b/dash-frontend/assets/gui/dashboard.xml index 8852664..1d0eb4e 100644 --- a/dash-frontend/assets/gui/dashboard.xml +++ b/dash-frontend/assets/gui/dashboard.xml @@ -66,7 +66,7 @@
- +
@@ -98,10 +98,7 @@ flex_direction="column" overflow_x="scroll" overflow_y="scroll" - padding_top="8" - padding_bottom="8" - padding_left="16" - padding_right="16" + padding="16" gap="8" width="100%" min_height="100%" diff --git a/dash-frontend/assets/gui/tab/games.xml b/dash-frontend/assets/gui/tab/games.xml index c1ade2f..032fbbe 100644 --- a/dash-frontend/assets/gui/tab/games.xml +++ b/dash-frontend/assets/gui/tab/games.xml @@ -1,8 +1,6 @@ - - - -
+
+
\ No newline at end of file diff --git a/dash-frontend/assets/gui/view/game_list.xml b/dash-frontend/assets/gui/view/game_list.xml index d7b334c..307217e 100644 --- a/dash-frontend/assets/gui/view/game_list.xml +++ b/dash-frontend/assets/gui/view/game_list.xml @@ -1,7 +1,12 @@ -
+
+
+
\ No newline at end of file diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index e342707..c7124fb 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -78,12 +78,16 @@ "SCREENCOPY_HELP": "Langsam, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway", "NONE": "Keine", "HMD_PINCH": "HMD + Kneifen", - "EYE_PINCH": "Auge + Kneifen" + "EYE_PINCH": "Auge + Kneifen", + "EYE_ONLY": "Nur Auge", + "HMD_ONLY": "Nur HMD" }, "AUTOSTART_APPS": "Anwendungen, die beim Start ausgeführt werden sollen", "HANDSFREE_POINTER": "Freihändige Modus", "HANDSFREE_POINTER_HELP": "Eingabe, die bei Bewegung\nder Controller verwendet wird, wenn diese nicht verfügbar sind.\nLinkes Kneifen greift, rechtes klickt.", - "UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität" + "UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität", + "RESET_PLAYSPACE": "Spielbereich zurücksetzen", + "RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs zurücksetzen." }, "HELLO": "Hallo!", "AUDIO": { @@ -116,8 +120,7 @@ "CLOSE_WINDOW": "Fenster schließen", "GAME_LIST": { "NO_GAMES_FOUND": "Keine Spiele gefunden", - "RUNNING_GAMES_LIST": "Liste der laufenden Spiele", - "NO_RUNNING_GAME_FOUND": "Kein laufendes Spiel gefunden" + "RUNNING_GAMES_LIST": "Liste der laufenden Spiele" }, "TERMINATE_PROCESS": "Prozess beenden", "GAME_LAUNCHED": "Spiel gestartet", diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index ae16a05..c3e594c 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -132,8 +132,7 @@ "GAME_LAUNCHED": "Game launched", "GAME_LIST": { "NO_GAMES_FOUND": "No games found", - "RUNNING_GAMES_LIST": "List of running games", - "NO_RUNNING_GAME_FOUND": "No running game found" + "RUNNING_GAMES_LIST": "List of running games" }, "GAMES": "Games", "GENERAL_SETTINGS": "General settings", diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index 7f1f747..6ee4e5b 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -78,12 +78,16 @@ "SCREENCOPY_HELP": "Lento, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway", "NONE": "Ninguno", "HMD_PINCH": "HMD + pellizco", - "EYE_PINCH": "Ojo + pellizco" + "EYE_PINCH": "Ojo + pellizco", + "EYE_ONLY": "Solo ojo", + "HMD_ONLY": "Solo HMD" }, "AUTOSTART_APPS": "Aplicaciones a ejecutar al inicio", "HANDSFREE_POINTER": "Modo manos libres", "HANDSFREE_POINTER_HELP": "Entrada a utilizar cuando no\nestén disponibles los mandos de movimiento.\nPellizco con la izquierda para agarrar, con la derecha para hacer clic.", - "UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU" + "UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU", + "RESET_PLAYSPACE": "Restablecer espacio de juego", + "RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del espacio de juego." }, "HELLO": "¡Hola!", "AUDIO": { @@ -116,8 +120,7 @@ "CLOSE_WINDOW": "Cerrar ventana", "GAME_LIST": { "NO_GAMES_FOUND": "No se encontraron juegos", - "RUNNING_GAMES_LIST": "Lista de juegos en ejecución", - "NO_RUNNING_GAME_FOUND": "No se encontró ningún juego en ejecución" + "RUNNING_GAMES_LIST": "Lista de juegos en ejecución" }, "TERMINATE_PROCESS": "Finalizar proceso", "GAME_LAUNCHED": "Juego lanzado", diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json index 7bd173c..787bc1f 100644 --- a/dash-frontend/assets/lang/it.json +++ b/dash-frontend/assets/lang/it.json @@ -75,7 +75,9 @@ "SCREENCOPY_HELP": "Lento, nessuna finestra pop-up per la condivisione dello schermo.\nFunziona su: Hyprland, Niri, River, Sway", "NONE": "Nessuno", "HMD_PINCH": "HMD + pizzico", - "EYE_PINCH": "Occhio + pizzico" + "EYE_PINCH": "Occhio + pizzico", + "EYE_ONLY": "Solo occhio", + "HMD_ONLY": "Solo HMD" }, "POINTER_LERP_FACTOR": "Smussamento puntatore", "RESTART_SOFTWARE": "Riavvia il software", @@ -103,7 +105,9 @@ "AUTOSTART_APPS": "App da avviare all'avvio", "HANDSFREE_POINTER": "Modalità a mani libere", "HANDSFREE_POINTER_HELP": "Input da usare quando i\ncontroller di movimento non sono disponibili.\nPizzico sinistro per afferrare, destro per cliccare.", - "UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente" + "UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente", + "RESET_PLAYSPACE": "Ripristina playspace", + "RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco." }, "APPLICATION_LAUNCHER": "Lanciatore applicazioni", "APPLICATION_STARTED": "Applicazione avviata", @@ -128,8 +132,7 @@ "GAME_LAUNCHED": "Gioco lanciato", "GAME_LIST": { "NO_GAMES_FOUND": "Nessun gioco trovato", - "RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione", - "NO_RUNNING_GAME_FOUND": "Nessun gioco in esecuzione trovato" + "RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione" }, "GAMES": "Giochi", "GENERAL_SETTINGS": "Impostazioni generali", diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index c647069..f8aec23 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -78,12 +78,16 @@ "SCREENCOPY_HELP": "遅延あり、画面共有ポップアップなし。\n動作する環境: Hyprland, Niri, River, Sway", "NONE": "なし", "HMD_PINCH": "HMD + ピンチ", - "EYE_PINCH": "つまんで目を合わせる" + "EYE_PINCH": "つまんで目を合わせる", + "EYE_ONLY": "視野のみ", + "HMD_ONLY": "HMDのみ" }, "AUTOSTART_APPS": "起動時に実行するアプリ", "HANDSFREE_POINTER": "ハンズフリーモード", "HANDSFREE_POINTER_HELP": "モーションコントローラーが利用できない場合の入力方法。\n左手のピンチは掴み、右手のピンチはクリックです。", - "UI_GRADIENT_INTENSITY": "UIグラデーションの強さ" + "UI_GRADIENT_INTENSITY": "UIグラデーションの強さ", + "RESET_PLAYSPACE": "プレイエリアをリセット", + "RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。" }, "HELLO": "こんにちは!", "AUDIO": { @@ -116,8 +120,7 @@ "CLOSE_WINDOW": "ウィンドウを閉じる", "GAME_LIST": { "NO_GAMES_FOUND": "ゲームが見つかりませんでした", - "RUNNING_GAMES_LIST": "実行中のゲーム一覧", - "NO_RUNNING_GAME_FOUND": "実行中のゲームが見つかりません" + "RUNNING_GAMES_LIST": "実行中のゲーム一覧" }, "TERMINATE_PROCESS": "プロセスを終了する", "GAME_LAUNCHED": "ゲームが起動しました", diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index e9fb87f..30f0baa 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -73,12 +73,16 @@ "SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway", "NONE": "Brak", "HMD_PINCH": "HMD + szczyknięcie", - "EYE_PINCH": "Ściśnięcie palcami + oko" + "EYE_PINCH": "Ściśnięcie palcami + oko", + "EYE_ONLY": "Tylko oko", + "HMD_ONLY": "Tylko HMD" }, "AUTOSTART_APPS": "Aplikacje do uruchomienia przy starcie", "HANDSFREE_POINTER": "Tryb bez użycia rąk", "HANDSFREE_POINTER_HELP": "Wejście do użycia, gdy kontrolery ruchu\nsą niedostępne. Lewy szczyptak to chwyt,\nprawy to kliknięcie.", - "UI_GRADIENT_INTENSITY": "Intensywność gradientu UI" + "UI_GRADIENT_INTENSITY": "Intensywność gradientu UI", + "RESET_PLAYSPACE": "Zresetuj przestrzeń gry", + "RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni gry." }, "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", "APPLICATIONS": "Aplikacje", @@ -116,8 +120,7 @@ "CLOSE_WINDOW": "Zamknij okno", "GAME_LIST": { "NO_GAMES_FOUND": "Nie znaleziono gier", - "RUNNING_GAMES_LIST": "Lista uruchomionych gier", - "NO_RUNNING_GAME_FOUND": "Nie znaleziono uruchomionej gry" + "RUNNING_GAMES_LIST": "Lista uruchomionych gier" }, "TERMINATE_PROCESS": "Zakończ proces", "GAME_LAUNCHED": "Gra uruchomiona", diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json index faf4b74..64fc5a5 100644 --- a/dash-frontend/assets/lang/zh_CN.json +++ b/dash-frontend/assets/lang/zh_CN.json @@ -75,7 +75,9 @@ "SCREENCOPY_HELP": "慢速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway", "NONE": "无", "HMD_PINCH": "HMD + 捏合", - "EYE_PINCH": "眼睛 + 捏合" + "EYE_PINCH": "眼睛 + 捏合", + "EYE_ONLY": "仅眼球", + "HMD_ONLY": "仅限头显" }, "POINTER_LERP_FACTOR": "指针平滑", "RESTART_SOFTWARE": "重启软件", @@ -103,7 +105,9 @@ "AUTOSTART_APPS": "开机启动应用", "HANDSFREE_POINTER": "免提模式", "HANDSFREE_POINTER_HELP": "当运动控制器不可用时使用的输入。\n左手捏合为抓取,右手为点击。", - "UI_GRADIENT_INTENSITY": "UI 渐变强度" + "UI_GRADIENT_INTENSITY": "UI 渐变强度", + "RESET_PLAYSPACE": "重置游戏空间", + "RESET_PLAYSPACE_HELP": "清除舞台空间偏移。" }, "APPLICATION_LAUNCHER": "应用启动器", "APPLICATION_STARTED": "应用已启动", @@ -128,8 +132,7 @@ "GAME_LAUNCHED": "游戏已启动", "GAME_LIST": { "NO_GAMES_FOUND": "未找到游戏", - "RUNNING_GAMES_LIST": "正在运行的游戏列表", - "NO_RUNNING_GAME_FOUND": "未找到正在运行的游戏" + "RUNNING_GAMES_LIST": "正在运行的游戏列表" }, "GAMES": "游戏", "GENERAL_SETTINGS": "通用设置", diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 639e311..070d1ce 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -1,6 +1,5 @@ use std::{path::PathBuf, rc::Rc}; -use anyhow::Context; use chrono::Timelike; use glam::Vec2; use wgui::{ diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs index 72e50a5..56dcac3 100644 --- a/dash-frontend/src/views/game_list.rs +++ b/dash-frontend/src/views/game_list.rs @@ -2,6 +2,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use wgui::{ assets::AssetPath, + components::button::ComponentButton, globals::WguiGlobals, i18n::Translation, layout::{Layout, WidgetID}, @@ -29,7 +30,10 @@ enum Task { AppManifestClicked(steam_utils::AppManifest), SetCoverArt(AppID, Rc), CloseLauncher, - Refresh, + LoadManifests, + FillPage(u32), + PrevPage, + NextPage, } pub struct Params<'a> { @@ -40,7 +44,9 @@ pub struct Params<'a> { pub parent_id: WidgetID, } -pub struct Cell { +const MAX_GAMES_PER_PAGE: u32 = 30; + +pub struct GameCoverCell { view_cover: game_cover::View, } @@ -55,10 +61,14 @@ pub struct View { frontend_tasks: FrontendTasks, globals: WguiGlobals, id_list_parent: WidgetID, - cells: HashMap, game_cover_view_common: game_cover::ViewCommon, executor: AsyncExecutor, state: Rc>, + mounted_game_covers: HashMap, + all_manifests: Vec, + cur_page: u32, + page_count: u32, + id_label_page: WidgetID, } impl View { @@ -71,10 +81,21 @@ impl View { let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?; let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?; + let id_label_page = parser_state.get_widget_id("label_page")?; let tasks = Tasks::new(); - tasks.push(Task::Refresh); + tasks.handle_button( + &parser_state.fetch_component_as::("btn_prev")?, + Task::PrevPage, + ); + + tasks.handle_button( + &parser_state.fetch_component_as::("btn_next")?, + Task::NextPage, + ); + + tasks.push(Task::LoadManifests); Ok(Self { parser_state, @@ -82,10 +103,14 @@ impl View { frontend_tasks: params.frontend_tasks, globals: params.globals.clone(), id_list_parent: list_parent.id, - cells: HashMap::new(), + mounted_game_covers: HashMap::new(), game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()), state: Rc::new(RefCell::new(State { view_launcher: None })), executor: params.executor, + all_manifests: Vec::new(), + cur_page: 0, + page_count: 0, + id_label_page, }) } @@ -102,10 +127,13 @@ impl View { } for task in tasks { match task { - Task::Refresh => self.refresh(layout, steam_utils, executor)?, + Task::LoadManifests => self.load_manifests(steam_utils), + Task::FillPage(page_idx) => self.fill_page(layout, executor, page_idx)?, Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?, Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(layout, app_id, cover_art), Task::CloseLauncher => self.state.borrow_mut().view_launcher = None, + Task::PrevPage => self.page_prev(), + Task::NextPage => self.page_next(), } } } @@ -119,18 +147,14 @@ impl View { } } -pub struct Games { - manifests: Vec, -} - fn fill_game_list( ess: &mut ConstructEssentials, executor: &AsyncExecutor, - cells: &mut HashMap, - games: &Games, + mounted_game_covers: &mut HashMap, + manifests: &[steam_utils::AppManifest], tasks: &Tasks, ) -> anyhow::Result<()> { - for manifest in &games.manifests { + for manifest in manifests { let on_loaded = { let app_id = manifest.app_id.clone(); let tasks = tasks.clone(); @@ -156,49 +180,81 @@ fn fill_game_list( }) }); - cells.insert(manifest.app_id.clone(), Cell { view_cover }); + mounted_game_covers.insert(manifest.app_id.clone(), GameCoverCell { view_cover }); } Ok(()) } impl View { - fn game_list(&self, steam_utils: &mut SteamUtils) -> anyhow::Result { - let manifests = steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)?; - - Ok(Games { manifests }) + fn load_manifests(&mut self, steam_utils: &mut SteamUtils) { + match steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc) { + Ok(manifests) => { + self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE; + self.all_manifests = manifests; + self.tasks.push(Task::FillPage(0)); + } + Err(e) => { + log::error!("Failed to list installed games: {e:?}"); + } + } } - fn refresh( - &mut self, - layout: &mut Layout, - steam_utils: &mut SteamUtils, - executor: &AsyncExecutor, - ) -> anyhow::Result<()> { + fn page_prev(&mut self) { + if self.cur_page == 0 { + return; + } + + self.cur_page -= 1; + self.tasks.push(Task::FillPage(self.cur_page)); + } + + fn page_next(&mut self) { + if self.cur_page >= self.page_count - 1 { + return; + } + self.cur_page += 1; + self.tasks.push(Task::FillPage(self.cur_page)); + } + + fn fill_page(&mut self, layout: &mut Layout, executor: &AsyncExecutor, page_idx: u32) -> anyhow::Result<()> { layout.remove_children(self.id_list_parent); - self.cells.clear(); + self.mounted_game_covers.clear(); + + let idx_from = (page_idx * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32); + let idx_to = ((page_idx + 1) * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32); + + let page_manifests = &self.all_manifests[idx_from as usize..idx_to as usize]; let mut text: Option = None; - match self.game_list(steam_utils) { - Ok(list) => { - if list.manifests.is_empty() { - text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND")) - } else { - fill_game_list( - &mut ConstructEssentials { - layout, - parent: self.id_list_parent, - }, - executor, - &mut self.cells, - &list, - &self.tasks, - )? - } - } - Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))), + + if page_manifests.is_empty() { + text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND")) } + // set page text + let mut c = layout.start_common(); + { + let mut common = c.common(); + let mut widget = common.state.widgets.cast_as::(self.id_label_page)?; + widget.set_text( + &mut common, + Translation::from_raw_text_string(format!("{}/{}", self.cur_page + 1, self.page_count)), + ); + } + c.finish()?; + + fill_game_list( + &mut ConstructEssentials { + layout, + parent: self.id_list_parent, + }, + executor, + &mut self.mounted_game_covers, + page_manifests, + &self.tasks, + )?; + if let Some(text) = text.take() { layout.add_child( self.id_list_parent, @@ -217,11 +273,11 @@ impl View { } fn set_cover_art(&mut self, layout: &mut Layout, app_id: AppID, cover_art: Rc) { - let Some(cell) = &mut self.cells.get_mut(&app_id) else { + let Some(cover) = &mut self.mounted_game_covers.get_mut(&app_id) else { return; }; - if let Err(e) = cell + if let Err(e) = cover .view_cover .set_cover_art(&mut self.game_cover_view_common, layout, &cover_art) { diff --git a/dash-frontend/src/views/running_games_list.rs b/dash-frontend/src/views/running_games_list.rs index 45b3241..19f9691 100644 --- a/dash-frontend/src/views/running_games_list.rs +++ b/dash-frontend/src/views/running_games_list.rs @@ -1,20 +1,19 @@ use wgui::{ assets::AssetPath, components::button::ComponentButton, + event::StyleSetRequest, globals::WguiGlobals, i18n::Translation, - layout::{Layout, WidgetID}, + layout::{Layout, LayoutTask, WidgetID}, parser::{Fetchable, ParseDocumentParams, ParserState}, + taffy::Display, task::Tasks, widget::label::WidgetLabel, }; use crate::{ frontend::{FrontendTask, FrontendTasks}, - util::{ - steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils}, - wgui_simple, - }, + util::steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils}, }; #[derive(Clone)] @@ -39,6 +38,7 @@ pub struct View { id_list_parent: WidgetID, installed_games: Vec, frontend_tasks: FrontendTasks, + parent_id: WidgetID, } fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> { @@ -58,7 +58,7 @@ impl View { let installed_games = params .steam_utils .list_installed_games(GameSortMethod::None) - .unwrap_or(Vec::new()); + .unwrap_or_default(); let tasks = Tasks::::new(); @@ -72,6 +72,7 @@ impl View { id_list_parent, installed_games, frontend_tasks: params.frontend_tasks, + parent_id: params.parent_id, }) } @@ -98,7 +99,7 @@ impl View { Ok(()) } - fn extract_name_from_appid<'a>(app_id: &AppID, manifests: &[AppManifest]) -> String { + fn extract_name_from_appid(app_id: &AppID, manifests: &[AppManifest]) -> String { for manifest in manifests { if manifest.app_id == *app_id { return manifest.name.clone(); @@ -110,14 +111,19 @@ impl View { fn fill_list(&mut self, layout: &mut Layout, games: Vec) -> anyhow::Result<()> { if games.is_empty() { - wgui_simple::create_label( - layout, - self.id_list_parent, - Translation::from_translation_key("GAME_LIST.NO_RUNNING_GAME_FOUND"), - )?; + // hide self + layout.tasks.push(LayoutTask::SetWidgetStyle( + self.parent_id, + StyleSetRequest::Display(Display::None), + )); return Ok(()); } + layout.tasks.push(LayoutTask::SetWidgetStyle( + self.parent_id, + StyleSetRequest::Display(Display::DEFAULT), + )); + for game in games { let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games); diff --git a/scripts/translator/bun.lock b/scripts/translator/bun.lock index 8df74b6..cbd723a 100644 --- a/scripts/translator/bun.lock +++ b/scripts/translator/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "llm_translator", diff --git a/wayvr/src/gui/panel/label.rs b/wayvr/src/gui/panel/label.rs index e8a32b5..c281619 100644 --- a/wayvr/src/gui/panel/label.rs +++ b/wayvr/src/gui/panel/label.rs @@ -103,7 +103,8 @@ pub(super) fn setup_custom_label( layout .state .widgets - .cast_as::(attribs.widget_id)? + .cast_as::(attribs.widget_id) + .unwrap() .set_text_simple(&mut globals, Translation::from_raw_text(pretty_tz)); // does not need to be dynamic diff --git a/wgui/src/components/tooltip.rs b/wgui/src/components/tooltip.rs index 6eef47c..4f9b40f 100644 --- a/wgui/src/components/tooltip.rs +++ b/wgui/src/components/tooltip.rs @@ -195,7 +195,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul color: TOOLTIP_COLOR, border_color: TOOLTIP_BORDER_COLOR, border: 2.0, - round: WLength::Percent(1.0), + round: WLength::Units(24.0), ..Default::default() }), taffy::Style { diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 45d2e37..18d656d 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -138,6 +138,7 @@ pub type ModifyLayoutStateFunc = Box anyhow pub enum LayoutTask { RemoveWidget(WidgetID), + SetWidgetStyle(WidgetID, event::StyleSetRequest), ModifyLayoutState(ModifyLayoutStateFunc), PlaySound(WguiSoundType), Dispatch(Box anyhow::Result<()>>), @@ -706,12 +707,51 @@ impl Layout { func(&mut c.common())?; c.finish()?; } + LayoutTask::SetWidgetStyle(widget_id, style_request) => { + self.set_style_request(widget_id, style_request); + } } } Ok(()) } + fn set_style_request(&mut self, widget_id: WidgetID, style_request: event::StyleSetRequest) { + let Some(node_id) = self.state.nodes.get(widget_id) else { + return; + }; + + // taffy requires us to copy this whole 536-byte style struct. + // we can't get `&mut Style` directly from taffy unfortunately + let mut cur_style = self.state.tree.style(*node_id).unwrap().clone() /* always safe */; + + match style_request { + event::StyleSetRequest::Display(display) => { + // refresh the component in case if visibility/display mode has changed + if cur_style.display != display + && let Some(component) = self.registered_components_to_refresh.get(node_id) + { + self.components_to_refresh_once.insert(component.clone()); + } + + cur_style.display = display; + } + event::StyleSetRequest::Margin(margin) => { + cur_style.margin = margin; + } + event::StyleSetRequest::Width(val) => { + cur_style.size.width = val; + } + event::StyleSetRequest::Height(val) => { + cur_style.size.height = val; + } + } + + if let Err(e) = self.state.tree.set_style(*node_id, cur_style) { + log::error!("failed to set style for taffy widget ID {node_id:?}: {e:?}"); + } + } + pub fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> { for task in alterables.tasks { self.tasks.push(task); @@ -747,39 +787,7 @@ impl Layout { } for (widget_id, style_request) in alterables.style_set_requests { - let Some(node_id) = self.state.nodes.get(widget_id) else { - continue; - }; - - // taffy requires us to copy this whole 536-byte style struct. - // we can't get `&mut Style` directly from taffy unfortunately - let mut cur_style = self.state.tree.style(*node_id).unwrap().clone() /* always safe */; - - match style_request { - event::StyleSetRequest::Display(display) => { - // refresh the component in case if visibility/display mode has changed - if cur_style.display != display - && let Some(component) = self.registered_components_to_refresh.get(node_id) - { - self.components_to_refresh_once.insert(component.clone()); - } - - cur_style.display = display; - } - event::StyleSetRequest::Margin(margin) => { - cur_style.margin = margin; - } - event::StyleSetRequest::Width(val) => { - cur_style.size.width = val; - } - event::StyleSetRequest::Height(val) => { - cur_style.size.height = val; - } - } - - if let Err(e) = self.state.tree.set_style(*node_id, cur_style) { - log::error!("failed to set style for taffy widget ID {node_id:?}: {e:?}"); - } + self.set_style_request(widget_id, style_request); } Ok(()) diff --git a/wgui/src/widget/rectangle.rs b/wgui/src/widget/rectangle.rs index fdab121..b5a14a2 100644 --- a/wgui/src/widget/rectangle.rs +++ b/wgui/src/widget/rectangle.rs @@ -47,7 +47,7 @@ impl WidgetObj for WidgetRectangle { let boundary = drawing::Boundary::construct_relative(state.transform_stack); let round_units = match self.params.round { - WLength::Units(units) => units as u8, + WLength::Units(units) => (f32::min(boundary.size.x, boundary.size.y) as u8 / 2).min(units as u8), WLength::Percent(percent) => (f32::min(boundary.size.x, boundary.size.y) * percent / 2.0) as u8, };