From 952b6e10b61698eb65f86fe6709c5c47a5b5736f Mon Sep 17 00:00:00 2001
From: galister <22305755+galister@users.noreply.github.com>
Date: Mon, 13 Jul 2026 15:08:14 +0900
Subject: [PATCH] nicer palette selector
---
.../assets/gui/view/color_palettes.xml | 21 ++
dash-frontend/assets/lang/en.json | 2 +
dash-frontend/src/tab/settings/mod.rs | 2 +-
.../src/tab/settings/tab_look_and_feel.rs | 130 +++++------
dash-frontend/src/views/color_palettes.rs | 219 ++++++++++++++++++
dash-frontend/src/views/mod.rs | 1 +
wgui/src/palette.rs | 34 +--
7 files changed, 326 insertions(+), 83 deletions(-)
create mode 100644 dash-frontend/assets/gui/view/color_palettes.xml
create mode 100644 dash-frontend/src/views/color_palettes.rs
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 {