dash-frontend: Chroma key settings

This commit is contained in:
Aleksander 2026-04-11 13:48:49 +02:00
parent 0c1b0906c1
commit 26f5be36b6
5 changed files with 225 additions and 11 deletions

View File

@ -1,12 +1,56 @@
<layout>
<include src="../t_group_box.xml" />
<elements>
<rectangle macro="group_box">
<GroupBoxTitle src_builtin="dashboard/settings.svg" translation="GENERAL_SETTINGS" />
<macro name="slider" width="100%" height="24"/>
<label translation="DISPLAY_BRIGHTNESS" />
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
</rectangle>
<theme>
<var key="s_width" value="500"/>
</theme>
<elements>
<div flex_direction="column" gap="8">
<rectangle macro="group_box">
<GroupBoxTitle src_builtin="dashboard/settings.svg" translation="GENERAL_SETTINGS" />
<label translation="DISPLAY_BRIGHTNESS" />
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
</rectangle>
<rectangle macro="group_box">
<GroupBoxTitle src_builtin="dashboard/settings.svg" translation="APP_SETTINGS.COLOR_KEYING" />
<div gap="8" width="~s_width">
<div gap="8" flex_direction="column" width="100%">
<label text="Color"/>
<ColorSelector id="cs_keying" height="24" width="100%"/>
</div>
<div gap="8" flex_direction="column" width="100%">
<label text="Despill"/>
<Slider id="slider_keying_despill" min_value="0" max_value="100" macro="slider"/>
</div>
<div gap="8" flex_direction="column" width="100%">
<label text="Curve"/>
<Slider id="slider_keying_curve" min_value="0" max_value="100" macro="slider"/>
</div>
</div>
<div gap="8" width="~s_width">
<div gap="8" flex_direction="column" width="100%">
<label text="Hue range"/>
<Slider id="slider_keying_hue_range" min_value="0" max_value="100" macro="slider"/>
</div>
<div gap="8" flex_direction="column" width="100%">
<label text="Saturation range"/>
<Slider id="slider_keying_saturation_range" min_value="0" max_value="100" macro="slider"/>
</div>
<div gap="8" flex_direction="column" width="100%">
<label text="Value range"/>
<Slider id="slider_keying_value_range" min_value="0" max_value="100" macro="slider"/>
</div>
</div>
</rectangle>
</div>
</elements>
</layout>
</layout>

View File

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

View File

@ -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<ComponentSlider>,
slider_keying_despill: Rc<ComponentSlider>,
slider_keying_hue_range: Rc<ComponentSlider>,
slider_keying_saturation_range: Rc<ComponentSlider>,
slider_keying_value_range: Rc<ComponentSlider>,
cs_keying: Rc<ComponentColorSelector>,
}
struct DebugGraph {
@ -158,6 +172,12 @@ impl<T> Tab<T> for TabMonado<T> {
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<T>(
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::<ComponentColorSelector>("cs_keying")?;
let slider_keying_despill = state.fetch_component_as::<ComponentSlider>("slider_keying_despill")?;
let slider_keying_curve = state.fetch_component_as::<ComponentSlider>("slider_keying_curve")?;
let slider_keying_hue_range = state.fetch_component_as::<ComponentSlider>("slider_keying_hue_range")?;
let slider_keying_saturation_range =
state.fetch_component_as::<ComponentSlider>("slider_keying_saturation_range")?;
let slider_keying_value_range = state.fetch_component_as::<ComponentSlider>("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<Task>) -> SliderValueChangedCallback {
Box::new({
let tasks = tasks.clone();
move |_, _| {
tasks.push(Task::GeneralSettingsChromaUpdate);
}
})
}
fn get_color_selector_callback(tasks: &Tasks<Task>) -> 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,
);
}
}

View File

@ -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<Color> for HsvColor {

View File

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