nicer palette selector

This commit is contained in:
galister 2026-07-13 15:08:14 +09:00
parent 245666ab06
commit 952b6e10b6
7 changed files with 326 additions and 83 deletions

View File

@ -0,0 +1,21 @@
<layout>
<!-- id, text -->
<template name="ColorPaletteButton">
<Button id="${id}" width="350" flex_direction="column" justify_content="space_around"
color="${background}" hover_color="${background_variant}" border_color="${outline}" hover_border_color="${highlight}" padding="16" >
<label text="${text}" size="24" weight="bold" color="${on_background}" parent_color="ignore"/>
<div flex_direction="row" width="100%" justify_content="space_evenly" padding_top="16" >
<rectangle round="100%" width="32" height="32" color="${primary}" border="2" border_color="${on_primary}" />
<rectangle round="100%" width="32" height="32" color="${secondary}" border="2" border_color="${on_secondary}" />
<rectangle round="100%" width="32" height="32" color="${tertiary}" border="2" border_color="${on_tertiary}" />
<rectangle round="100%" width="32" height="32" color="${danger}" border="2" border_color="${on_danger}" />
</div>
</Button>
</template>
<elements>
<div flex_direction="column" gap="8" width="100%" align_items="center">
<div id="list_parent" flex_grow="1" flex_wrap="wrap" flex_direction="row" gap="4" width="100%" justify_content="center"></div>
</div>
</elements>
</layout>

View File

@ -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",

View File

@ -71,7 +71,7 @@ impl TabNameEnum {
}
#[derive(Clone)]
enum Task {
pub(crate) enum Task {
ClearPipewireTokens,
ClearSavedState,
DeleteAllConfigs,

View File

@ -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<color_palettes::View>,
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
tasks: Tasks<Task>,
settings_tasks: Tasks<SettingsTask>,
}
impl SettingsTab for State {
fn context_menu_custom(
&mut self,
action: Rc<str>,
config: &mut GeneralConfig,
change_kind: &mut Option<ConfigChangeKind>,
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::<WidgetLabel>(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<State> {
let tasks = Tasks::<Task>::new();
let popup = PopupHolder::<color_palettes::View>::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::<wlx_common::locale::Language>(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<Task>,
_popup: &PopupHolder<color_palettes::View>,
) -> 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::<WidgetLabel>(&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::<ComponentButton>(&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::<Vec<_>>();
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(())
}

View File

@ -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<SettingsTask>,
}
pub struct View {
tasks: Tasks<Task>,
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
popup_dialog: PopupHolder<views::dialog_box::View>,
settings_tasks: Tasks<SettingsTask>,
}
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<Self> {
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(&params.layout.state, "list_parent")?.id;
let tasks = Tasks::new();
let popup_dialog = PopupHolder::<views::dialog_box::View>::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::<ComponentButton>(&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<View>,
settings_tasks: Tasks<SettingsTask>,
) {
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 */
)));
}

View File

@ -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;

View File

@ -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 {