diff --git a/dash-frontend/assets/gui/view/color_palettes.xml b/dash-frontend/assets/gui/view/color_palettes.xml new file mode 100644 index 00000000..b439f5fb --- /dev/null +++ b/dash-frontend/assets/gui/view/color_palettes.xml @@ -0,0 +1,21 @@ + + + + + +
+
+
+
+
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index a476afe9..d590de3c 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -97,6 +97,8 @@ "CAPTURE_METHOD_HELP": "Try changing this if you are\nexperiencing black or glitchy screens", "COLOR_KEYING": "Color keying", "COLOR_PALETTE": "Color palette", + "COLOR_PALETTES": "Color palettes", + "APPLY_CHANGES_RESTART": "Restart WayVR to apply changes?", "CLEAR_PIPEWIRE_TOKENS": "Clear PipeWire tokens", "CLEAR_PIPEWIRE_TOKENS_HELP": "Prompt for screen selection on next start", "CLEAR_SAVED_STATE": "Clear saved state", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index cc13efc3..1d29b338 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -71,7 +71,7 @@ impl TabNameEnum { } #[derive(Clone)] -enum Task { +pub(crate) enum Task { ClearPipewireTokens, ClearSavedState, DeleteAllConfigs, diff --git a/dash-frontend/src/tab/settings/tab_look_and_feel.rs b/dash-frontend/src/tab/settings/tab_look_and_feel.rs index 12db4c71..d332293c 100644 --- a/dash-frontend/src/tab/settings/tab_look_and_feel.rs +++ b/dash-frontend/src/tab/settings/tab_look_and_feel.rs @@ -1,58 +1,69 @@ use std::rc::Rc; use wgui::{ - components::button::{ButtonClickEvent, ComponentButton}, - i18n::Translation, + components::button::ComponentButton, + globals::WguiGlobals, layout::WidgetID, - palette::PALETTES, parser::{Fetchable, TemplateParams}, - widget::label::WidgetLabel, - windowing::context_menu, -}; -use wlx_common::{config::GeneralConfig, dash_interface::ConfigChangeKind}; - -use crate::tab::settings::{ - SettingType, SettingsMountParams, SettingsTab, Task, horiz_cell, - macros::{MacroParams, options_category, options_checkbox, options_dropdown, options_slider_f32}, - mount_requires_restart, + task::Tasks, }; -pub struct State {} +use crate::{ + frontend::FrontendTasks, + tab::settings::{ + SettingType, SettingsMountParams, SettingsTab, Task as SettingsTask, horiz_cell, + macros::{options_category, options_checkbox, options_dropdown, options_slider_f32}, + mount_requires_restart, + }, + util::popup_manager::PopupHolder, + views::{ViewUpdateParams, color_palettes}, +}; + +#[derive(Clone)] +enum Task { + OpenColorPalettes, +} + +pub struct State { + popup_color_palettes: PopupHolder, + frontend_tasks: FrontendTasks, + globals: WguiGlobals, + tasks: Tasks, + settings_tasks: Tasks, +} impl SettingsTab for State { - fn context_menu_custom( - &mut self, - action: Rc, - config: &mut GeneralConfig, - change_kind: &mut Option, - layout: &mut wgui::layout::Layout, - state: &mut wgui::parser::ParserState, - ) -> anyhow::Result<()> { - if let (Some(_), Some(id), Some(palette_name)) = { - let mut s = action.splitn(3, ';'); - (s.next(), s.next(), s.next()) - } { - let mut common = layout.common(); - let mut label = state.fetch_widget_as::(common.state, &format!("{id}_value"))?; - label.set_text(&mut common, Translation::from_raw_text(palette_name)); + fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> { + self.popup_color_palettes.update(par)?; - config.color_palette = palette_name.into(); - change_kind.replace(ConfigChangeKind::WguiThemeChange); + for task in self.tasks.drain() { + match task { + Task::OpenColorPalettes => { + color_palettes::mount_popup( + self.frontend_tasks.clone(), + self.globals.clone(), + self.popup_color_palettes.clone(), + self.settings_tasks.clone(), + ); + } + } } - Ok(()) } } impl State { pub fn mount(par: SettingsMountParams) -> anyhow::Result { + let tasks = Tasks::::new(); + let popup = PopupHolder::::default(); + let c = options_category( par.mp, par.id_parent, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg", )?; - palettes_dropdown(par.mp, c)?; + create_color_palettes_button(par.mp, c, tasks.clone(), &popup)?; options_dropdown::(par.mp, c, &SettingType::Language)?; options_checkbox(par.mp, c, SettingType::OpaqueBackground)?; options_checkbox(par.mp, c, SettingType::HideUsername)?; @@ -64,56 +75,45 @@ impl State { options_checkbox(par.mp, c, SettingType::EnableWatch)?; options_checkbox(par.mp, c, SettingType::SetsOnWatch)?; options_checkbox(par.mp, c, SettingType::Clock12h)?; - Ok(State {}) + Ok(State { + popup_color_palettes: popup, + frontend_tasks: par.frontend_tasks.clone(), + globals: par.mp.doc_params.globals.clone(), + tasks, + settings_tasks: par.mp.tasks.clone(), + }) } } -fn palettes_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { +fn create_color_palettes_button( + mp: &mut crate::tab::settings::macros::MacroParams, + parent: WidgetID, + tasks: Tasks, + _popup: &PopupHolder, +) -> anyhow::Result<()> { let id = mp.idx.to_string(); mp.idx += 1; - let parent = horiz_cell(mp.layout, parent)?; - - let current_palette = mp.config.color_palette.as_ref(); + let id_cell = horiz_cell(mp.layout, parent)?; let mut params = TemplateParams::new(); params.insert("id", &id); - params.insert("translation", "APP_SETTINGS.COLOR_PALETTE".into()); + params.insert("translation", "APP_SETTINGS.COLOR_PALETTE"); + params.insert("icon", "dashboard/palette.svg"); mp.parser_state - .instantiate_template(mp.doc_params, "DropdownButton", mp.layout, parent, params)?; - - { - let mut label = mp - .parser_state - .fetch_widget_as::(&mp.layout.state, &format!("{id}_value"))?; - label.set_text_simple( - &mut mp.layout.state.globals.get(), - Translation::from_raw_text(current_palette), - ); - } - - mount_requires_restart(mp.layout, parent)?; + .instantiate_template(mp.doc_params, "ButtonText", mp.layout, id_cell, params)?; let btn = mp.parser_state.fetch_component_as::(&id)?; btn.on_click(Rc::new({ - let parent_tasks = mp.tasks.clone(); - move |_common, e: ButtonClickEvent| { - let cells = PALETTES //TODO: enumerate custom palettes - .iter() - .enumerate() - .map(|(_, item)| context_menu::Cell { - action_name: Some(format!(";{id};{}", item.0).into()), - title: Translation::from_raw_text(item.0), - tooltip: None, - attribs: vec![], - }) - .collect::>(); - - parent_tasks.push(Task::OpenContextMenu(e.mouse_pos_absolute.unwrap_or_default(), cells)); + let tasks = tasks.clone(); + move |_common, _e| { + tasks.push(Task::OpenColorPalettes); Ok(()) } })); + mount_requires_restart(mp.layout, id_cell)?; + Ok(()) } diff --git a/dash-frontend/src/views/color_palettes.rs b/dash-frontend/src/views/color_palettes.rs new file mode 100644 index 00000000..f4ff2953 --- /dev/null +++ b/dash-frontend/src/views/color_palettes.rs @@ -0,0 +1,219 @@ +use std::rc::Rc; +use wgui::{ + assets::AssetPath, + color::WguiColorName, + components::button::ComponentButton, + globals::WguiGlobals, + i18n::Translation, + layout::{Layout, WidgetID}, + palette::PALETTES, + parser::{Fetchable, ParseDocumentParams, TemplateParams}, + task::Tasks, +}; +use wlx_common::dash_interface::ConfigChangeKind; + +use crate::{ + frontend::{FrontendTask, FrontendTasks}, + tab::settings::Task as SettingsTask, + util::popup_manager::{MountPopupOnceParams, PopupHolder}, + views::{self, ViewTrait, ViewUpdateParams}, +}; + +#[derive(Clone)] +enum Task { + SelectPalette(String), + Restart, + Cancel, +} + +pub struct Params<'a> { + pub globals: WguiGlobals, + pub layout: &'a mut Layout, + pub parent_id: WidgetID, + pub frontend_tasks: &'a FrontendTasks, + pub settings_tasks: Tasks, +} + +pub struct View { + tasks: Tasks, + frontend_tasks: FrontendTasks, + globals: WguiGlobals, + popup_dialog: PopupHolder, + settings_tasks: Tasks, +} + +impl ViewTrait for View { + fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> { + self.popup_dialog.update(par)?; + + for task in self.tasks.drain() { + match task { + Task::SelectPalette(profile) => { + par.general_config.color_palette = profile.into(); + par.config_change_kind.replace(ConfigChangeKind::WguiThemeChange); + + self.show_restart_dialog_box()?; + } + Task::Cancel => { + let close_dialog = self.popup_dialog.get_close_callback(par.layout); + close_dialog(); + } + Task::Restart => { + self.settings_tasks.push(SettingsTask::RestartSoftware); + } + } + } + Ok(()) + } +} + +macro_rules! insert_colors { + ( + $params:expr, + $palette:expr, + $( $key:literal => $color:ident ),* $(,)? + ) => { + $( + $params.insert( + $key, + WguiColorName::$color + .to_wgui_color() + .resolve($palette) + .to_hex() + .as_str() + ); + )* + }; +} + +impl View { + pub fn new(params: Params) -> anyhow::Result { + let doc_params = &ParseDocumentParams { + globals: params.globals.clone(), + path: AssetPath::BuiltIn("gui/view/color_palettes.xml"), + extra: Default::default(), + }; + + let mut 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")?.id; + + let tasks = Tasks::new(); + let popup_dialog = PopupHolder::::default(); + + for (idx, (name, palette)) in PALETTES.iter().enumerate() { + let id = format!("profile_btn_{idx}"); + + let mut cell_params = TemplateParams::new(); + cell_params.insert("id", &id); + cell_params.insert("text", name); + insert_colors!( + cell_params, + palette, + "primary" => Primary, + "on_primary" => OnPrimary, + "secondary" => Secondary, + "on_secondary" => OnSecondary, + "tertiary" => Tertiary, + "on_tertiary" => OnTertiary, + "danger" => Danger, + "on_danger" => OnDanger, + "background" => Background, + "on_background" => OnBackground, + "background_variant" => BackgroundVariant, + "outline" => Outline, + "highlight" => Highlight, + ); + + parser_state.instantiate_template( + doc_params, + "ColorPaletteButton", + params.layout, + list_parent, + cell_params, + )?; + + let btn = parser_state.fetch_component_as::(&id)?; + let tasks_clone = tasks.clone(); + btn.on_click(Rc::new({ + move |_common, _e| { + tasks_clone.push(Task::SelectPalette(name.to_string())); + Ok(()) + } + })); + } + + Ok(Self { + tasks, + frontend_tasks: params.frontend_tasks.clone(), + globals: params.globals.clone(), + popup_dialog, + settings_tasks: params.settings_tasks, + }) + } + + fn show_restart_dialog_box(&mut self) -> anyhow::Result<()> { + const ACTION_RESTART: &str = "restart"; + const ACTION_CANCEL: &str = "cancel"; + + let tasks = self.tasks.clone(); + + views::dialog_box::mount_popup( + self.popup_dialog.clone(), + self.frontend_tasks.clone(), + views::dialog_box::Params { + globals: self.globals.clone(), + message: Translation::from_translation_key("APP_SETTINGS.APPLY_CHANGES_RESTART"), + entries: vec![ + views::dialog_box::ButtonEntry { + content: Translation::from_translation_key("APP_SETTINGS.CANCEL"), + icon: "dashboard/close.svg", + action: ACTION_CANCEL, + }, + views::dialog_box::ButtonEntry { + content: Translation::from_translation_key("APP_SETTINGS.RESTART_SOFTWARE"), + icon: "dashboard/refresh.svg", + action: ACTION_RESTART, + }, + ], + on_action_click: Box::new(move |action| match action { + ACTION_RESTART => { + tasks.push(Task::Restart); + } + ACTION_CANCEL => { + tasks.push(Task::Cancel); + } + _ => unreachable!(), + }), + }, + ); + + Ok(()) + } +} + +pub fn mount_popup( + frontend_tasks: FrontendTasks, + globals: WguiGlobals, + popup: PopupHolder, + settings_tasks: Tasks, +) { + frontend_tasks + .clone() + .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new( + Translation::from_translation_key("APP_SETTINGS.COLOR_PALETTES"), + Box::new(move |data| { + let view = View::new(Params { + globals: globals.clone(), + layout: data.layout, + parent_id: data.id_content, + frontend_tasks: &frontend_tasks, + settings_tasks, + })?; + + popup.set_view(data.handle, view, None); + Ok(popup.get_close_callback(data.layout)) + }), + Default::default(), /* extra */ + ))); +} diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs index 5b8a6917..1c6b7f06 100644 --- a/dash-frontend/src/views/mod.rs +++ b/dash-frontend/src/views/mod.rs @@ -3,6 +3,7 @@ use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_inte pub mod app_launcher; pub mod audio_settings; pub mod bindings; +pub mod color_palettes; pub mod dialog_box; pub mod download_file; pub mod game_cover; diff --git a/wgui/src/palette.rs b/wgui/src/palette.rs index 7c9a13a0..868f642b 100644 --- a/wgui/src/palette.rs +++ b/wgui/src/palette.rs @@ -83,33 +83,33 @@ const fn hex(hex: &str) -> drawing::Color { pub static PALETTES: &[(&'static str, &WguiColorPalette)] = &[ ("Default", DEFAULT), ("Ayu", AYU), - // ("Ayu Light", AYU_LIGHT), ("Catppuccin", CATTPUCCIN), - // ("Catppuccin Light", CATTPUCCIN_LIGHT), - // ("Cyberpunk", CYBERPUNK), - // ("Cyberpunk Light", CYBERPUNK_LIGHT), + ("Cyberpunk", CYBERPUNK), ("Dracula", DRACULA), - // ("Dracula Light", DRACULA_LIGHT), ("Eldritch", ELDRITCH), - // ("Eldritch Light", ELDRITCH_LIGHT), ("Everforest", EVERFOREST), - // ("Everforest Light", EVERFOREST_LIGHT), ("Gruvbox", GRUVBOX), - // ("Gruvbox Light", GRUVBOX_LIGHT), ("Kanagawa", KANAGAWA), - // ("Kanagawa Light", KANAGAWA_LIGHT), - // ("Monochrome", MONOCHROME), - // ("Monochrome Light", MONOCHROME_LIGHT), + ("Monochrome", MONOCHROME), ("Nord", NORD), - // ("Nord Light", NORD_LIGHT), - // ("Osaka Jade", OSAKA_JADE), - // ("Osaka Jade Light", OSAKA_JADE_LIGHT), + ("Osaka Jade", OSAKA_JADE), ("Rosepine", ROSEPINE), - // ("Rosepine Light", ROSEPINE_LIGHT), ("Solarized Night", SOLARIZED_NIGHT), - // ("Solarized Day", SOLARIZED_DAY), ("Tokyo Night", TOKYO_NIGHT), - // ("Tokyo Day", TOKYO_DAY), + ("Ayu Light", AYU_LIGHT), + ("Catppuccin Light", CATTPUCCIN_LIGHT), + ("Cyberpunk Light", CYBERPUNK_LIGHT), + ("Dracula Light", DRACULA_LIGHT), + ("Eldritch Light", ELDRITCH_LIGHT), + ("Everforest Light", EVERFOREST_LIGHT), + ("Gruvbox Light", GRUVBOX_LIGHT), + ("Kanagawa Light", KANAGAWA_LIGHT), + ("Monochrome Light", MONOCHROME_LIGHT), + ("Nord Light", NORD_LIGHT), + ("Osaka Jade Light", OSAKA_JADE_LIGHT), + ("Rosepine Light", ROSEPINE_LIGHT), + ("Solarized Day", SOLARIZED_DAY), + ("Tokyo Day", TOKYO_DAY), ]; static DEFAULT: &WguiColorPalette = &WguiColorPalette {