From 9aea733452f635cea667837d1bef2a952c0f6e34 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Mon, 15 Dec 2025 21:08:59 +0100 Subject: [PATCH] `display_list`, `add_display` views (wip) [skip ci] --- Cargo.lock | 1 + dash-frontend/Cargo.toml | 1 + dash-frontend/assets/gui/tab/processes.xml | 4 +- dash-frontend/assets/gui/view/add_display.xml | 21 ++ .../assets/gui/view/app_launcher.xml | 2 +- .../assets/gui/view/display_list.xml | 10 + .../assets/gui/view/popup_window.xml | 2 +- dash-frontend/assets/lang/de.json | 7 + dash-frontend/assets/lang/en.json | 97 ++++---- dash-frontend/assets/lang/es.json | 7 + dash-frontend/assets/lang/ja.json | 7 + dash-frontend/assets/lang/pl.json | 103 ++++---- dash-frontend/src/frontend.rs | 65 +++-- dash-frontend/src/tab/home.rs | 16 +- dash-frontend/src/tab/mod.rs | 15 +- dash-frontend/src/tab/processes.rs | 24 +- dash-frontend/src/task.rs | 36 ++- dash-frontend/src/views/add_display.rs | 79 ++++++ dash-frontend/src/views/display_list.rs | 224 ++++++++++++++++++ dash-frontend/src/views/mod.rs | 2 + wgui/src/components/button.rs | 4 +- 21 files changed, 574 insertions(+), 153 deletions(-) create mode 100644 dash-frontend/assets/gui/view/add_display.xml create mode 100644 dash-frontend/assets/gui/view/display_list.xml create mode 100644 dash-frontend/src/views/add_display.rs create mode 100644 dash-frontend/src/views/display_list.rs diff --git a/Cargo.lock b/Cargo.lock index aa7f727e..908d0ab2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1479,6 +1479,7 @@ dependencies = [ "rust-embed", "serde", "serde_json", + "wayvr_ipc", "wgui", "wlx-common", ] diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index 5bd23d7b..6211da2e 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -15,3 +15,4 @@ gtk = "0.18.2" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" wlx-common = { path = "../wlx-common" } +wayvr_ipc = { workspace = true } diff --git a/dash-frontend/assets/gui/tab/processes.xml b/dash-frontend/assets/gui/tab/processes.xml index 0330d48c..d8234299 100644 --- a/dash-frontend/assets/gui/tab/processes.xml +++ b/dash-frontend/assets/gui/tab/processes.xml @@ -2,6 +2,8 @@ - + +
+ \ No newline at end of file diff --git a/dash-frontend/assets/gui/view/add_display.xml b/dash-frontend/assets/gui/view/add_display.xml new file mode 100644 index 00000000..34f73be2 --- /dev/null +++ b/dash-frontend/assets/gui/view/add_display.xml @@ -0,0 +1,21 @@ + + + + +
+ + + +
+
+
\ No newline at end of file diff --git a/dash-frontend/assets/gui/view/app_launcher.xml b/dash-frontend/assets/gui/view/app_launcher.xml index 8b0b4bdc..951d5bd8 100644 --- a/dash-frontend/assets/gui/view/app_launcher.xml +++ b/dash-frontend/assets/gui/view/app_launcher.xml @@ -27,7 +27,7 @@ diff --git a/dash-frontend/assets/gui/view/display_list.xml b/dash-frontend/assets/gui/view/display_list.xml new file mode 100644 index 00000000..bd72cf4e --- /dev/null +++ b/dash-frontend/assets/gui/view/display_list.xml @@ -0,0 +1,10 @@ + + + + + +
+ diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index f353d6a0..30e504ed 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -45,5 +45,12 @@ }, "ACTIONS": { "RECENTER_PLAYSPACE": "Playspace neu zentrieren" + }, + "LIST_OF_DISPLAYS": "Anzeigeliste", + "LIST_OF_PROCESSES": "Prozessliste", + "NO_DISPLAYS_FOUND": "Keine Displays gefunden", + "ADD_DISPLAY": "Bildschirm hinzufügen", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Auflösung" } } diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index b92964e6..3fe371a3 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -1,49 +1,56 @@ { - "HOME_SCREEN": "Home", - "MONADO_RUNTIME": "„Monado” runtime", - "APPLICATIONS": "Applications", - "GAMES": "Games", - "SETTINGS": "Settings", - "PROCESSES": "Processes", - "HELLO_USER": "Hello, {USER}!", - "HELLO": "Hello!", - "GENERAL_SETTINGS": "General settings", - "APPLICATION_LAUNCHER": "Application launcher", - "APP_SETTINGS": { - "RESTART_SOFTWARE": "Restart software", - "HIDE_USERNAME": "Hide username", - "OPAQUE_BACKGROUND": "Opaque background", - "RUN_IN_XWAYLAND_MODE_BY_DEFAULT": "Run in XWayland mode by default", - "WLX_OVERLAY_S_SETTINGS": "WlxOverlay-S settings", - "HEADSET_SETTINGS": "Headset settings", - "BRIGHTNESS": "Brightness", - "WLX": { - "NOTIFICATIONS_ENABLED": "Notifications enabled", - "NOTIFICATIONS_SOUND_ENABLED": "Notifications sound enabled", - "KEYBOARD_SOUND_ENABLED": "Keyboard sound enabled", - "BLOCK_GAME_INPUT": "Block game input", - "SPACE_DRAG_MULTIPLIER": "Space-drag multiplier", - "SPACE_DRAG_ROTATION_ENABLED": "Enable rotation in space-drag", - "SHOW_SKYBOX": "Show skybox", - "ENABLE_PASSTHROUGH": "Enable passthrough" - } - }, - "AUDIO": { - "SELECT_AUDIO_CARD_PROFILE": "Select audio card profile", - "SETTINGS": "Audio settings", - "VOLUME": "Volume", - "AUTO_SWITCH_TO_VR_AUDIO": "Auto-switch to VR audio", - "SPEAKERS": "Speakers", - "MICROPHONES": "Microphones", - "CARDS": "Cards", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "No VR speakers found. Switch them manually.", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "No VR microphone found. Switch it manually.", - "FAILED_TO_SWITCH_MICROPHONE": "Failed to switch microphone", - "MICROPHONE_SET_SUCCESSFULLY": "Microphone set successfully", - "SPEAKERS_SET_SUCCESSFULLY": "Speakers set successfully", - "DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Device found and initialized, but not switched" - }, "ACTIONS": { "RECENTER_PLAYSPACE": "Re-center playspace" - } + }, + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Resolution" + }, + "ADD_DISPLAY": "Add display", + "APP_SETTINGS": { + "BRIGHTNESS": "Brightness", + "HEADSET_SETTINGS": "Headset settings", + "HIDE_USERNAME": "Hide username", + "OPAQUE_BACKGROUND": "Opaque background", + "RESTART_SOFTWARE": "Restart software", + "RUN_IN_XWAYLAND_MODE_BY_DEFAULT": "Run in XWayland mode by default", + "WLX": { + "BLOCK_GAME_INPUT": "Block game input", + "ENABLE_PASSTHROUGH": "Enable passthrough", + "KEYBOARD_SOUND_ENABLED": "Keyboard sound enabled", + "NOTIFICATIONS_ENABLED": "Notifications enabled", + "NOTIFICATIONS_SOUND_ENABLED": "Notifications sound enabled", + "SHOW_SKYBOX": "Show skybox", + "SPACE_DRAG_MULTIPLIER": "Space-drag multiplier", + "SPACE_DRAG_ROTATION_ENABLED": "Enable rotation in space-drag" + }, + "WLX_OVERLAY_S_SETTINGS": "WlxOverlay-S settings" + }, + "APPLICATION_LAUNCHER": "Application launcher", + "APPLICATIONS": "Applications", + "AUDIO": { + "AUTO_SWITCH_TO_VR_AUDIO": "Auto-switch to VR audio", + "CARDS": "Cards", + "DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Device found and initialized, but not switched", + "FAILED_TO_SWITCH_MICROPHONE": "Failed to switch microphone", + "MICROPHONE_SET_SUCCESSFULLY": "Microphone set successfully", + "MICROPHONES": "Microphones", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "No VR microphone found. Switch it manually.", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "No VR speakers found. Switch them manually.", + "SELECT_AUDIO_CARD_PROFILE": "Select audio card profile", + "SETTINGS": "Audio settings", + "SPEAKERS": "Speakers", + "SPEAKERS_SET_SUCCESSFULLY": "Speakers set successfully", + "VOLUME": "Volume" + }, + "GAMES": "Games", + "GENERAL_SETTINGS": "General settings", + "HELLO": "Hello!", + "HELLO_USER": "Hello, {USER}!", + "HOME_SCREEN": "Home", + "LIST_OF_DISPLAYS": "Display list", + "LIST_OF_PROCESSES": "Process list", + "MONADO_RUNTIME": "„Monado” runtime", + "NO_DISPLAYS_FOUND": "No displays found", + "PROCESSES": "Processes", + "SETTINGS": "Settings" } diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index cb7869dc..3c6b476f 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -45,5 +45,12 @@ }, "ACTIONS": { "RECENTER_PLAYSPACE": "Re-centrar espacio de juego" + }, + "LIST_OF_DISPLAYS": "Lista de pantallas", + "LIST_OF_PROCESSES": "Lista de procesos", + "NO_DISPLAYS_FOUND": "No se encontraron pantallas", + "ADD_DISPLAY": "Agregar pantalla", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Resolución" } } \ No newline at end of file diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index e8c59fb8..ce60767d 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -45,5 +45,12 @@ }, "ACTIONS": { "RECENTER_PLAYSPACE": "プレイスペースを再中央" + }, + "LIST_OF_DISPLAYS": "ディスプレイリスト", + "LIST_OF_PROCESSES": "プロセスのリスト", + "NO_DISPLAYS_FOUND": "ディスプレイが見つかりません", + "ADD_DISPLAY": "ディスプレイを追加", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "解像度" } } \ No newline at end of file diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index e24c7364..2af95591 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -1,49 +1,56 @@ { - "HOME_SCREEN": "Ekran główny", - "MONADO_RUNTIME": "Środowisko Monado", - "APPLICATIONS": "Aplikacje", - "GAMES": "Gry", - "SETTINGS": "Ustawienia", - "PROCESSES": "Procesy", - "HELLO_USER": "Witaj, {USER}!", - "GENERAL_SETTINGS": "Ustawienia ogólne", - "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", - "APP_SETTINGS": { - "HIDE_USERNAME": "Ukryj nazwę użytkownika", - "OPAQUE_BACKGROUND": "Nieprzezroczyste tło", - "RUN_IN_XWAYLAND_MODE_BY_DEFAULT": "Uruchom domyślnie w trybie XWayland", - "WLX_OVERLAY_S_SETTINGS": "Ustawienia wlx-overlay-s", - "HEADSET_SETTINGS": "Ustawienia HMD", - "BRIGHTNESS": "Jasność", - "WLX": { - "NOTIFICATIONS_ENABLED": "Powiadomienia", - "NOTIFICATIONS_SOUND_ENABLED": "Dźwięk powiadomień", - "KEYBOARD_SOUND_ENABLED": "Dźwięki klawiatury", - "BLOCK_GAME_INPUT": "Zablokuj sterowanie grą podczas używania Wlx", - "SPACE_DRAG_MULTIPLIER": "Mnożnik space-drag", - "SPACE_DRAG_ROTATION_ENABLED": "Włącz rotację w space-drag", - "SHOW_SKYBOX": "Pokaż skybox", - "ENABLE_PASSTHROUGH": "Włącz passthrough" - }, - "RESTART_SOFTWARE": "Uruchom ponownie oprogramowanie" - }, - "HELLO": "Witaj!", - "AUDIO": { - "VOLUME": "Głośność", - "SETTINGS": "Ustawienia dźwięku", - "AUTO_SWITCH_TO_VR_AUDIO": "Automatyczne przełączanie na dźwięk VR", - "SPEAKERS": "Głośniki", - "MICROPHONES": "Mikrofony", - "CARDS": "Karty", - "SELECT_AUDIO_CARD_PROFILE": "Wybierz profil karty dźwiękowej", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Brak głośników VR. Włącz je ręcznie.", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Brak mikrofonu VR. Włącz go ręcznie.", - "FAILED_TO_SWITCH_MICROPHONE": "Nie udało się przełączyć mikrofon", - "MICROPHONE_SET_SUCCESSFULLY": "Mikrofon ustawiono pomyślnie", - "SPEAKERS_SET_SUCCESSFULLY": "Głośniki ustawiono pomyślnie", - "DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Urządzenie znalezione i zainicjalizowane, ale nie przełączone" - }, - "ACTIONS": { - "RECENTER_PLAYSPACE": "Wycentruj przestrzeń" - } -} + "HOME_SCREEN": "Ekran główny", + "MONADO_RUNTIME": "Środowisko Monado", + "APPLICATIONS": "Aplikacje", + "GAMES": "Gry", + "SETTINGS": "Ustawienia", + "PROCESSES": "Procesy", + "HELLO_USER": "Witaj, {USER}!", + "GENERAL_SETTINGS": "Ustawienia ogólne", + "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", + "APP_SETTINGS": { + "HIDE_USERNAME": "Ukryj nazwę użytkownika", + "OPAQUE_BACKGROUND": "Nieprzezroczyste tło", + "RUN_IN_XWAYLAND_MODE_BY_DEFAULT": "Uruchom domyślnie w trybie XWayland", + "WLX_OVERLAY_S_SETTINGS": "Ustawienia wlx-overlay-s", + "HEADSET_SETTINGS": "Ustawienia HMD", + "BRIGHTNESS": "Jasność", + "WLX": { + "NOTIFICATIONS_ENABLED": "Powiadomienia", + "NOTIFICATIONS_SOUND_ENABLED": "Dźwięk powiadomień", + "KEYBOARD_SOUND_ENABLED": "Dźwięki klawiatury", + "BLOCK_GAME_INPUT": "Zablokuj sterowanie grą podczas używania Wlx", + "SPACE_DRAG_MULTIPLIER": "Mnożnik space-drag", + "SPACE_DRAG_ROTATION_ENABLED": "Włącz rotację w space-drag", + "SHOW_SKYBOX": "Pokaż skybox", + "ENABLE_PASSTHROUGH": "Włącz passthrough" + }, + "RESTART_SOFTWARE": "Uruchom ponownie oprogramowanie" + }, + "HELLO": "Witaj!", + "AUDIO": { + "VOLUME": "Głośność", + "SETTINGS": "Ustawienia dźwięku", + "AUTO_SWITCH_TO_VR_AUDIO": "Automatyczne przełączanie na dźwięk VR", + "SPEAKERS": "Głośniki", + "MICROPHONES": "Mikrofony", + "CARDS": "Karty", + "SELECT_AUDIO_CARD_PROFILE": "Wybierz profil karty dźwiękowej", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Brak głośników VR. Włącz je ręcznie.", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Brak mikrofonu VR. Włącz go ręcznie.", + "FAILED_TO_SWITCH_MICROPHONE": "Nie udało się przełączyć mikrofon", + "MICROPHONE_SET_SUCCESSFULLY": "Mikrofon ustawiono pomyślnie", + "SPEAKERS_SET_SUCCESSFULLY": "Głośniki ustawiono pomyślnie", + "DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Urządzenie znalezione i zainicjalizowane, ale nie przełączone" + }, + "ACTIONS": { + "RECENTER_PLAYSPACE": "Wycentruj przestrzeń" + }, + "LIST_OF_DISPLAYS": "Lista wyświetlaczy", + "LIST_OF_PROCESSES": "Lista procesów", + "NO_DISPLAYS_FOUND": "Brak monitorów", + "ADD_DISPLAY": "Dodaj monitor", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Rozdzielczość" + } +} \ No newline at end of file diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index f2a1355b..6f2d19cb 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -18,8 +18,8 @@ use wlx_common::{dash_interface, timestep::Timestep}; use crate::{ assets, settings, tab::{ - Tab, TabParams, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, - settings::TabSettings, + Tab, TabParams, TabType, TabUpdateParams, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, + processes::TabProcesses, settings::TabSettings, }, task::Tasks, util::{ @@ -166,6 +166,17 @@ impl Frontend { self.process_task(rc_this, task)?; } + if let Some(tab) = &mut self.current_tab { + let mut layout = self.layout.borrow_mut(); + + tab.update(TabUpdateParams { + globals: &self.globals, + frontend_tasks: &self.tasks, + layout: &mut layout, + interface: &mut self.interface, + })?; + } + self.tick(width, height, timestep_alpha)?; self.ticks += 1; @@ -292,6 +303,7 @@ impl Frontend { frontend: rc_this, //frontend_widgets: &self.widgets, settings: self.settings.get_mut(), + frontend_tasks: &self.tasks, }; let tab: Box = match tab_type { @@ -308,15 +320,6 @@ impl Frontend { Ok(()) } - pub fn register_button_task(this_rc: RcFrontend, btn: &Rc, task: FrontendTask) { - btn.on_click({ - Box::new(move |_common, _evt| { - this_rc.borrow_mut().tasks.push(task.clone()); - Ok(()) - }) - }); - } - fn register_widgets(rc_this: &RcFrontend) -> anyhow::Result<()> { let this = rc_this.borrow_mut(); @@ -325,44 +328,38 @@ impl Frontend { // ################################ // "Home" side button - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_side_home")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_side_home")?, FrontendTask::SetTab(TabType::Home), ); // "Apps" side button - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_side_apps")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_side_apps")?, FrontendTask::SetTab(TabType::Apps), ); // "Games" side button - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_side_games")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_side_games")?, FrontendTask::SetTab(TabType::Games), ); // "Monado side button" - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_side_monado")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_side_monado")?, FrontendTask::SetTab(TabType::Monado), ); // "Processes" side button - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_side_processes")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_side_processes")?, FrontendTask::SetTab(TabType::Processes), ); // "Settings" side button - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_side_settings")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_side_settings")?, FrontendTask::SetTab(TabType::Settings), ); @@ -371,16 +368,14 @@ impl Frontend { // ################################ // "Audio" bottom bar button - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_audio")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_audio")?, FrontendTask::ShowAudioSettings, ); // "Recenter playspace" bottom bar button - Frontend::register_button_task( - rc_this.clone(), - &this.state.fetch_component_as::("btn_recenter")?, + this.tasks.handle_button( + this.state.fetch_component_as::("btn_recenter")?, FrontendTask::RecenterPlayspace, ); diff --git a/dash-frontend/src/tab/home.rs b/dash-frontend/src/tab/home.rs index ffb40845..ab6ed12c 100644 --- a/dash-frontend/src/tab/home.rs +++ b/dash-frontend/src/tab/home.rs @@ -66,16 +66,12 @@ impl TabHome { let btn_processes = state.fetch_component_as::("btn_processes")?; let btn_settings = state.fetch_component_as::("btn_settings")?; - let frontend = params.frontend; - Frontend::register_button_task(frontend.clone(), &btn_apps, FrontendTask::SetTab(TabType::Apps)); - Frontend::register_button_task(frontend.clone(), &btn_games, FrontendTask::SetTab(TabType::Games)); - Frontend::register_button_task(frontend.clone(), &btn_monado, FrontendTask::SetTab(TabType::Monado)); - Frontend::register_button_task( - frontend.clone(), - &btn_processes, - FrontendTask::SetTab(TabType::Processes), - ); - Frontend::register_button_task(frontend.clone(), &btn_settings, FrontendTask::SetTab(TabType::Settings)); + let tasks = params.frontend_tasks; + tasks.handle_button(btn_apps, FrontendTask::SetTab(TabType::Apps)); + tasks.handle_button(btn_games, FrontendTask::SetTab(TabType::Games)); + tasks.handle_button(btn_monado, FrontendTask::SetTab(TabType::Monado)); + tasks.handle_button(btn_processes, FrontendTask::SetTab(TabType::Processes)); + tasks.handle_button(btn_settings, FrontendTask::SetTab(TabType::Settings)); Ok(Self { state }) } diff --git a/dash-frontend/src/tab/mod.rs b/dash-frontend/src/tab/mod.rs index 15a2f759..f0f3a60c 100644 --- a/dash-frontend/src/tab/mod.rs +++ b/dash-frontend/src/tab/mod.rs @@ -2,8 +2,9 @@ use wgui::{ globals::WguiGlobals, layout::{Layout, WidgetID}, }; +use wlx_common::dash_interface; -use crate::frontend::RcFrontend; +use crate::frontend::{FrontendTasks, RcFrontend}; pub mod apps; pub mod games; @@ -28,9 +29,21 @@ pub struct TabParams<'a> { pub parent_id: WidgetID, pub frontend: &'a RcFrontend, pub settings: &'a mut crate::settings::Settings, + pub frontend_tasks: &'a FrontendTasks, +} + +pub struct TabUpdateParams<'a> { + pub globals: &'a WguiGlobals, + pub frontend_tasks: &'a FrontendTasks, + pub layout: &'a mut Layout, + pub interface: &'a mut Box, } pub trait Tab { #[allow(dead_code)] fn get_type(&self) -> TabType; + + fn update(&mut self, _params: TabUpdateParams) -> anyhow::Result<()> { + Ok(()) + } } diff --git a/dash-frontend/src/tab/processes.rs b/dash-frontend/src/tab/processes.rs index a19bf280..fdda10d2 100644 --- a/dash-frontend/src/tab/processes.rs +++ b/dash-frontend/src/tab/processes.rs @@ -1,19 +1,29 @@ use wgui::{ assets::AssetPath, - parser::{ParseDocumentParams, ParserState}, + parser::{Fetchable, ParseDocumentParams, ParserState}, }; -use crate::tab::{Tab, TabParams, TabType}; +use crate::{ + tab::{Tab, TabParams, TabType, TabUpdateParams}, + views::display_list, +}; pub struct TabProcesses { #[allow(dead_code)] pub state: ParserState, + + view_display_list: display_list::View, } impl Tab for TabProcesses { fn get_type(&self) -> TabType { TabType::Games } + + fn update(&mut self, params: TabUpdateParams) -> anyhow::Result<()> { + self.view_display_list.update(params.layout, params.interface)?; + Ok(()) + } } impl TabProcesses { @@ -28,6 +38,14 @@ impl TabProcesses { params.parent_id, )?; - Ok(Self { state }) + Ok(Self { + view_display_list: display_list::View::new(display_list::Params { + layout: params.layout, + parent_id: state.get_widget_id("display_list_parent")?, + globals: params.globals.clone(), + frontend_tasks: params.frontend_tasks.clone(), + })?, + state, + }) } } diff --git a/dash-frontend/src/task.rs b/dash-frontend/src/task.rs index f6af4569..26bc5cdd 100644 --- a/dash-frontend/src/task.rs +++ b/dash-frontend/src/task.rs @@ -1,11 +1,16 @@ use std::{cell::RefCell, collections::VecDeque, rc::Rc}; -#[derive(Clone)] -pub struct Tasks(Rc>>) -where - TaskType: Clone; +use wgui::components::button::ComponentButton; -impl Tasks { +pub struct Tasks(Rc>>); + +impl Clone for Tasks { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Tasks { pub fn new() -> Self { Self(Rc::new(RefCell::new(VecDeque::new()))) } @@ -20,8 +25,27 @@ impl Tasks { } } -impl Default for Tasks { +impl Default for Tasks { fn default() -> Self { Self::new() } } + +impl Tasks { + pub fn handle_button(&self, button: Rc, task: TaskType) { + button.on_click({ + let this = self.clone(); + Box::new(move |_, _| { + this.push(task.clone()); + Ok(()) + }) + }); + } + + pub fn make_callback(&self, task: TaskType) -> Rc { + let this = self.clone(); + Rc::new(move || { + this.push(task.clone()); + }) + } +} diff --git a/dash-frontend/src/views/add_display.rs b/dash-frontend/src/views/add_display.rs new file mode 100644 index 00000000..26722466 --- /dev/null +++ b/dash-frontend/src/views/add_display.rs @@ -0,0 +1,79 @@ +use std::{collections::HashMap, rc::Rc}; + +use wgui::{ + assets::AssetPath, + components::{button::ComponentButton, slider::ComponentSlider}, + globals::WguiGlobals, + layout::{Layout, WidgetID}, + parser::{Fetchable, ParseDocumentParams, ParserState}, + widget::{label::WidgetLabel, rectangle::WidgetRectangle}, +}; + +use crate::{frontend::FrontendTasks, tab::TabUpdateParams, task::Tasks}; + +#[derive(Clone)] +enum Task { + Confirm, +} + +pub struct View { + #[allow(dead_code)] + pub state: ParserState, + tasks: Tasks, + frontend_tasks: FrontendTasks, + on_submit: Rc, +} + +pub struct Params<'a> { + pub globals: WguiGlobals, + pub frontend_tasks: FrontendTasks, + pub layout: &'a mut Layout, + pub parent_id: WidgetID, + pub on_submit: Rc, +} + +impl View { + pub fn new(params: Params) -> anyhow::Result { + let doc_params = &ParseDocumentParams { + globals: params.globals.clone(), + path: AssetPath::BuiltIn("gui/view/add_display.xml"), + extra: Default::default(), + }; + + let state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?; + + let tasks = Tasks::new(); + + let slider_width = state.fetch_component_as::("slider_width")?; + let slider_height = state.fetch_component_as::("slider_height")?; + let label_display_name = state.fetch_widget_as::(¶ms.layout.state, "label_display_name")?; + let rect_display = state.fetch_widget_as::(¶ms.layout.state, "rect_display"); + let label_display = state.fetch_widget_as::(¶ms.layout.state, "label_display"); + let btn_confirm = state.fetch_component_as::("btn_confirm")?; + + tasks.handle_button(btn_confirm, Task::Confirm); + + Ok(Self { + state, + tasks, + frontend_tasks: params.frontend_tasks, + on_submit: params.on_submit, + }) + } + + pub fn update(&mut self) -> anyhow::Result<()> { + for task in self.tasks.drain() { + match task { + Task::Confirm => self.confirm(), + } + } + Ok(()) + } +} + +impl View { + fn confirm(&mut self) { + log::info!("confirm"); + (*self.on_submit)(); + } +} diff --git a/dash-frontend/src/views/display_list.rs b/dash-frontend/src/views/display_list.rs new file mode 100644 index 00000000..06748c9d --- /dev/null +++ b/dash-frontend/src/views/display_list.rs @@ -0,0 +1,224 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use wayvr_ipc::packet_server::WvrDisplay; +use wgui::{ + assets::AssetPath, + components::button::ComponentButton, + globals::WguiGlobals, + i18n::Translation, + layout::{Layout, WidgetID}, + parser::{Fetchable, ParseDocumentParams, ParserState}, + renderer_vk::text::{FontWeight, TextStyle}, + taffy, + widget::{ + label::{WidgetLabel, WidgetLabelParams}, + rectangle::{WidgetRectangle, WidgetRectangleParams}, + }, +}; +use wlx_common::dash_interface; + +use crate::{ + frontend::{FrontendTask, FrontendTasks}, + tab::TabUpdateParams, + task::Tasks, + util::popup_manager::{MountPopupParams, PopupHandle}, + views::add_display, +}; + +#[derive(Clone)] +enum Task { + AddDisplay, + AddDisplayFinish, + Refresh, +} + +pub struct Params<'a> { + pub globals: WguiGlobals, + pub frontend_tasks: FrontendTasks, + pub layout: &'a mut Layout, + pub parent_id: WidgetID, +} + +struct State { + view_add_display: Option<(PopupHandle, add_display::View)>, +} + +pub struct View { + #[allow(dead_code)] + pub parser_state: ParserState, + tasks: Tasks, + frontend_tasks: FrontendTasks, + globals: WguiGlobals, + state: Rc>, + id_list_parent: WidgetID, +} + +impl View { + pub fn new(params: Params) -> anyhow::Result { + let doc_params = &ParseDocumentParams { + globals: params.globals.clone(), + path: AssetPath::BuiltIn("gui/view/display_list.xml"), + extra: Default::default(), + }; + + 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 tasks = Tasks::new(); + + let btn_add = parser_state.fetch_component_as::("btn_add")?; + tasks.handle_button(btn_add, Task::AddDisplay); + + tasks.push(Task::Refresh); + + let state = Rc::new(RefCell::new(State { view_add_display: None })); + + Ok(Self { + parser_state, + tasks, + frontend_tasks: params.frontend_tasks, + globals: params.globals, + state, + id_list_parent: list_parent.id, + }) + } + + pub fn update( + &mut self, + layout: &mut Layout, + interface: &mut Box, + ) -> anyhow::Result<()> { + loop { + let tasks = self.tasks.drain(); + if tasks.is_empty() { + break; + } + for task in tasks { + match task { + Task::AddDisplay => self.add_display(), + Task::AddDisplayFinish => self.add_display_finish()?, + Task::Refresh => self.refresh(layout, interface)?, + } + } + } + + let mut state = self.state.borrow_mut(); + if let Some((_, view)) = &mut state.view_add_display { + view.update()?; + } + + Ok(()) + } +} + +fn fill_display_list( + globals: &WguiGlobals, + parent: WidgetID, + layout: &mut Layout, + list: Vec, +) -> anyhow::Result<()> { + for entry in list { + let (rect, _) = layout.add_child( + parent, + WidgetRectangle::create(WidgetRectangleParams { ..Default::default() }), + taffy::Style { + align_items: Some(taffy::AlignItems::Center), + justify_content: Some(taffy::JustifyContent::Center), + ..Default::default() + }, + )?; + + let label_name = WidgetLabel::create( + &mut globals.get(), + WidgetLabelParams { + content: Translation::from_raw_text(&entry.name), + style: TextStyle { + weight: Some(FontWeight::Bold), + ..Default::default() + }, + }, + ); + + let label_resolution = WidgetLabel::create( + &mut globals.get(), + WidgetLabelParams { + content: Translation::from_raw_text(""), + ..Default::default() + }, + ); + + layout.add_child(rect.id, label_name, Default::default())?; + layout.add_child(rect.id, label_resolution, Default::default())?; + } + + Ok(()) +} + +impl View { + fn add_display(&mut self) { + self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams { + title: Translation::from_translation_key("ADD_DISPLAY"), + on_content: { + let frontend_tasks = self.frontend_tasks.clone(); + let globals = self.globals.clone(); + let state = self.state.clone(); + let tasks = self.tasks.clone(); + + Rc::new(move |data| { + state.borrow_mut().view_add_display = Some(( + data.handle, + add_display::View::new(add_display::Params { + frontend_tasks: frontend_tasks.clone(), + globals: globals.clone(), + layout: data.layout, + parent_id: data.id_content, + on_submit: tasks.make_callback(Task::AddDisplayFinish), + })?, + )); + Ok(()) + }) + }, + })); + } + + fn add_display_finish(&mut self) -> anyhow::Result<()> { + self.state.borrow_mut().view_add_display = None; + Ok(()) + } + + fn refresh( + &mut self, + layout: &mut Layout, + interface: &mut Box, + ) -> anyhow::Result<()> { + layout.remove_children(self.id_list_parent); + + let mut text: Option = None; + match interface.display_list() { + Ok(list) => { + if list.is_empty() { + text = Some(Translation::from_translation_key("NO_DISPLAYS_FOUND")) + } else { + fill_display_list(&self.globals, self.id_list_parent, layout, list)? + } + } + Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))), + } + + if let Some(text) = text.take() { + layout.add_child( + self.id_list_parent, + WidgetLabel::create( + &mut self.globals.get(), + WidgetLabelParams { + content: text, + ..Default::default() + }, + ), + Default::default(), + )?; + } + + Ok(()) + } +} diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs index 8fe26e0b..ec19001e 100644 --- a/dash-frontend/src/views/mod.rs +++ b/dash-frontend/src/views/mod.rs @@ -1,2 +1,4 @@ +pub mod add_display; pub mod app_launcher; pub mod audio_settings; +pub mod display_list; diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index 2f66cdc6..f84b79d0 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -63,7 +63,7 @@ impl Default for Params<'_> { } pub struct ButtonClickEvent {} -pub type ButtonClickCallback = Box anyhow::Result<()>>; +pub type ButtonClickCallback = Box anyhow::Result<()>>; pub struct Colors { pub color: drawing::Color, @@ -351,7 +351,7 @@ fn register_event_mouse_release( state.down = false; if state.hovered - && let Some(on_click) = &state.on_click + && let Some(on_click) = &mut state.on_click { anim_hover( rect,