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,