From 26f5be36b61b9a32eeed527d525c72b22732dd38 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 11 Apr 2026 13:48:49 +0200 Subject: [PATCH] dash-frontend: Chroma key settings --- .../gui/tab/monado_tab_general_settings.xml | 58 ++++++++-- dash-frontend/assets/lang/en.json | 1 + dash-frontend/src/tab/monado.rs | 105 +++++++++++++++++- wgui/src/drawing.rs | 26 +++++ wlx-common/src/config.rs | 46 +++++++- 5 files changed, 225 insertions(+), 11 deletions(-) diff --git a/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml b/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml index d8329b63..3d918b82 100644 --- a/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml +++ b/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml @@ -1,12 +1,56 @@ - - - + - + + + + + +
+ + + + + + +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
-
\ No newline at end of file + diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 96b56062..7d5e2e05 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -40,6 +40,7 @@ "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blocks the game from receiving poses when the keyboard is hovered and 'Block game input' is enabled", "CAPTURE_METHOD": "Wayland screen capture", "CAPTURE_METHOD_HELP": "Try changing this if you are\nexperiencing black or glitchy screens", + "COLOR_KEYING": "Color keying", "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/monado.rs b/dash-frontend/src/tab/monado.rs index abc65c7f..8efe58de 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -6,7 +6,8 @@ use wgui::{ bar_graph::{ComponentBarGraph, ValueCell}, button::ComponentButton, checkbox::ComponentCheckbox, - slider::ComponentSlider, + color_selector::{ColorSelectorChangedCallback, ComponentColorSelector}, + slider::{ComponentSlider, SliderValueChangedCallback}, tabs::ComponentTabs, }, drawing::Color, @@ -15,7 +16,10 @@ use wgui::{ parser::{self, Fetchable, ParseDocumentParams, ParserState}, task::Tasks, }; -use wlx_common::dash_interface::{self, MonadoDumpSessionFrame}; +use wlx_common::{ + config::GeneralConfig, + dash_interface::{self, MonadoDumpSessionFrame}, +}; use crate::{ frontend::Frontend, @@ -40,6 +44,7 @@ impl TabNameEnum { } } +#[derive(Clone)] enum Task { SetBrightness(f32), SetTab(TabNameEnum), @@ -51,6 +56,8 @@ enum Task { // `DebugTimings` tab DebugTimingsRefreshSessionList, DebugTimingsSetSessionId(i64), + + GeneralSettingsChromaUpdate, } struct SubtabProcessList { @@ -62,6 +69,13 @@ struct SubtabProcessList { struct SubtabGeneralSettings { #[allow(dead_code)] state: ParserState, + + slider_keying_curve: Rc, + slider_keying_despill: Rc, + slider_keying_hue_range: Rc, + slider_keying_saturation_range: Rc, + slider_keying_value_range: Rc, + cs_keying: Rc, } struct DebugGraph { @@ -158,6 +172,12 @@ impl Tab for TabMonado { tab.set_session_id(&mut frontend.layout, session_id)?; } } + Task::GeneralSettingsChromaUpdate => { + if let Subtab::GeneralSettings(tab) = &mut self.subtab { + tab.chroma_update(frontend.interface.general_config(data)); + frontend.interface.config_changed(data); + } + } Task::SetBrightness(brightness) => self.set_brightness(frontend, data, brightness), Task::SetTab(tab) => { frontend.layout.remove_children(self.id_content); @@ -243,6 +263,8 @@ fn yesno(n: bool) -> &'static str { } } +const SLIDER_MULTIPLIER: f32 = 100.0; + impl SubtabGeneralSettings { fn new( parent_id: WidgetID, @@ -271,7 +293,84 @@ impl SubtabGeneralSettings { }); } - Ok(Self { state }) + let config = frontend.interface.general_config(data); + + let cs_keying = state.fetch_component_as::("cs_keying")?; + let slider_keying_despill = state.fetch_component_as::("slider_keying_despill")?; + let slider_keying_curve = state.fetch_component_as::("slider_keying_curve")?; + let slider_keying_hue_range = state.fetch_component_as::("slider_keying_hue_range")?; + let slider_keying_saturation_range = + state.fetch_component_as::("slider_keying_saturation_range")?; + let slider_keying_value_range = state.fetch_component_as::("slider_keying_value_range")?; + + { + let mut lc = frontend.layout.start_common(); + let mut common = lc.common(); + + // set initial values + let (rgb, range_h, range_s, range_v) = config.chroma_key_params.get_rgb_and_hsv_ranges(); + slider_keying_curve.set_value(&mut common, config.chroma_key_params.curve * SLIDER_MULTIPLIER); + slider_keying_despill.set_value(&mut common, config.chroma_key_params.despill * SLIDER_MULTIPLIER); + slider_keying_hue_range.set_value(&mut common, range_h * SLIDER_MULTIPLIER); + slider_keying_saturation_range.set_value(&mut common, range_s * SLIDER_MULTIPLIER); + slider_keying_value_range.set_value(&mut common, range_v * SLIDER_MULTIPLIER); + cs_keying.set_color(&mut common, rgb); + + // prepare callbacks + fn get_slider_callback(tasks: &Tasks) -> SliderValueChangedCallback { + Box::new({ + let tasks = tasks.clone(); + move |_, _| { + tasks.push(Task::GeneralSettingsChromaUpdate); + } + }) + } + + fn get_color_selector_callback(tasks: &Tasks) -> ColorSelectorChangedCallback { + Box::new({ + let tasks = tasks.clone(); + move |_, _| { + tasks.push(Task::GeneralSettingsChromaUpdate); + } + }) + } + slider_keying_curve.on_value_changed(get_slider_callback(&tasks)); + slider_keying_despill.on_value_changed(get_slider_callback(&tasks)); + slider_keying_hue_range.on_value_changed(get_slider_callback(&tasks)); + slider_keying_saturation_range.on_value_changed(get_slider_callback(&tasks)); + slider_keying_value_range.on_value_changed(get_slider_callback(&tasks)); + cs_keying.on_changed(get_color_selector_callback(&tasks)); + + lc.finish()?; + } + + Ok(Self { + state, + slider_keying_curve, + slider_keying_despill, + slider_keying_hue_range, + slider_keying_saturation_range, + slider_keying_value_range, + cs_keying, + }) + } + + fn chroma_update(&mut self, config: &mut GeneralConfig) { + let val_curve = self.slider_keying_curve.get_value(); + let val_despill = self.slider_keying_despill.get_value(); + let val_range_h = self.slider_keying_hue_range.get_value(); + let val_range_s = self.slider_keying_saturation_range.get_value(); + let val_range_v = self.slider_keying_value_range.get_value(); + let val_rgb = self.cs_keying.get_color(); + + config.chroma_key_params.despill = val_despill / SLIDER_MULTIPLIER; + config.chroma_key_params.curve = val_curve / SLIDER_MULTIPLIER; + config.chroma_key_params.update_hsv_range_from_rgb( + val_rgb, + val_range_h / SLIDER_MULTIPLIER, + val_range_s / SLIDER_MULTIPLIER, + val_range_v / SLIDER_MULTIPLIER, + ); } } diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 210c0a62..ef8fa550 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -211,6 +211,32 @@ impl HsvColor { pub const fn new(h: f32, s: f32, v: f32, a: f32) -> Self { Self { h, s, v, a } } + + pub fn to_rgb(&self) -> Color { + if self.s == 0.0 { + // gray + return Color::new(self.v, self.v, self.v, 1.0); + } + + let h6 = self.h * 6.0; + let i = h6.floor(); + let f = h6 - i; + + let p = self.v * (1.0 - self.s); + let q = self.v * (1.0 - self.s * f); + let t = self.v * (1.0 - self.s * (1.0 - f)); + + let (r, g, b) = match i as i32 % 6 { + 0 => (self.v, t, p), + 1 => (q, self.v, p), + 2 => (p, self.v, t), + 3 => (p, q, self.v), + 4 => (t, p, self.v), + _ => (self.v, p, q), + }; + + Color::new(r, g, b, 1.0) + } } impl From for HsvColor { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 8d5a0e20..9ce45c5f 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -5,6 +5,7 @@ use idmap::IdMap; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, EnumProperty, EnumString, VariantArray}; use wayvr_ipc::packet_client::WvrProcessLaunchParams; +use wgui::drawing::{self, HsvColor}; use crate::{ astr_containers::{AStrMap, AStrSet}, @@ -66,7 +67,7 @@ pub enum HandsfreePointer { EyeTrackingOnly, } -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize)] pub struct ChromaKeyParams { pub hsv_min: [f32; 3], pub hsv_max: [f32; 3], @@ -74,6 +75,49 @@ pub struct ChromaKeyParams { pub despill: f32, } +fn hsv_green() -> [f32; 3] { + [2.0 / 6.0, 1.0, 1.0] +} + +impl Default for ChromaKeyParams { + fn default() -> Self { + Self { + hsv_min: hsv_green(), + hsv_max: hsv_green(), + curve: 0.0, // Monado will ignore chroma keying completely if this value is zero + despill: 0.0, + } + } +} + +impl ChromaKeyParams { + pub fn update_hsv_range_from_rgb(&mut self, rgb_color: drawing::Color, range_h: f32, range_s: f32, range_v: f32) { + let hsv = HsvColor::from(rgb_color); + self.hsv_min[0] = hsv.h - range_h / 2.0; + self.hsv_min[1] = hsv.s - range_s / 2.0; + self.hsv_min[2] = hsv.v - range_v / 2.0; + self.hsv_max[0] = hsv.h + range_h / 2.0; + self.hsv_max[1] = hsv.s + range_s / 2.0; + self.hsv_max[2] = hsv.v + range_v / 2.0; + } + + // Inverse of `update_hsv_range_from_rgb` function + pub fn get_rgb_and_hsv_ranges(&self) -> (drawing::Color, f32, f32, f32) { + ( + HsvColor::new( + (self.hsv_min[0] + self.hsv_max[0]) / 2.0, + (self.hsv_min[1] + self.hsv_max[1]) / 2.0, + (self.hsv_min[2] + self.hsv_max[2]) / 2.0, + 1.0, + ) + .to_rgb(), + self.hsv_max[0] - self.hsv_min[0], + self.hsv_max[1] - self.hsv_min[1], + self.hsv_max[2] - self.hsv_min[2], + ) + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct SerializedWindowSet { pub name: Arc,