ui for thresholds

This commit is contained in:
galister 2026-07-02 10:57:55 +09:00
parent 8a8c41bd9e
commit 025c6a0998
4 changed files with 67 additions and 9 deletions

View File

@ -4,6 +4,11 @@
<!-- used at runtime [!!!] --> <!-- used at runtime [!!!] -->
<include src="../t_dropdown_button.xml" /> <include src="../t_dropdown_button.xml" />
<!-- id, min, max, step, value, value2, tooltip -->
<template name="ThresholdSlider">
<Slider id="${id}" width="200" height="24" min_value="${min}" max_value="${max}" step="${step}" value="${value}" value2="${value2}" tooltip="${tooltip}" />
</template>
<!-- id, translation --> <!-- id, translation -->
<template name="ActionRow"> <template name="ActionRow">
<rectangle macro="group_box" id="${id}"> <rectangle macro="group_box" id="${id}">

View File

@ -81,7 +81,8 @@
"LEFT": "Left", "LEFT": "Left",
"RIGHT": "Right", "RIGHT": "Right",
"COMPONENT": "Actuation type", "COMPONENT": "Actuation type",
"SUBPATH": "Actuating control" "SUBPATH": "Actuating control",
"THRESHOLD": "Activate when above upper value\nDeactivate when below lower value"
}, },
"BROWSE_ONLINE_CATALOG": "Browse online catalog...", "BROWSE_ONLINE_CATALOG": "Browse online catalog...",
"BROWSE_SKYMAPS": "Browse skymaps", "BROWSE_SKYMAPS": "Browse skymaps",

View File

@ -159,6 +159,8 @@ pub enum Component {
Touch, Touch,
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.VALUE"))] #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.VALUE"))]
Value, Value,
/// Not an actual component but monado uses this instead of X/Y
Position, Position,
Pose, Pose,
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.PROXIMITY"))] #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.PROXIMITY"))]
@ -166,16 +168,20 @@ pub enum Component {
Haptic, Haptic,
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.X_AXIS"))] #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.X_AXIS"))]
/// Not an actual component, used to turn a 2D Position into a 1D Value
X, X,
#[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.Y_AXIS"))] #[strum(props(Translation = "APP_SETTINGS.BINDINGS.COMP.Y_AXIS"))]
/// Not an actual component, used to turn a 2D Position into a 1D Value
Y, Y,
#[serde(other)] #[serde(other)]
Other, Other,
} }
impl Component {
pub fn is_analog(&self) -> bool {
matches!(self, Component::Force | Component::Value | Component::X | Component::Y)
}
}
impl BindingsDropdown for Component { impl BindingsDropdown for Component {
fn translation(&self) -> Translation { fn translation(&self) -> Translation {
self self

View File

@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashMap, rc::Rc};
use glam::Vec2; use glam::Vec2;
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
components::button::{ButtonClickEvent, ComponentButton}, components::{button::{ButtonClickEvent, ComponentButton}, slider::ComponentSlider},
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
@ -34,6 +34,7 @@ enum Task {
Save, Save,
Cancel, Cancel,
OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>), OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>),
UpdateThreshold(Rc<str>, f32, f32)
} }
pub struct Params<'a> { pub struct Params<'a> {
@ -83,14 +84,20 @@ impl ViewTrait for View {
blueprint: context_menu::Blueprint::Cells(cells), blueprint: context_menu::Blueprint::Cells(cells),
}); });
} }
Task::UpdateThreshold(action_name, lo, hi) => {
let cur_profile = &mut self.profiles[self.cur_profile_idx];
let action_mut = get_action_mut(cur_profile, &*action_name);
action_mut.threshold = Some([lo, hi]);
}
} }
} }
// Dropdown handling // Dropdown handling
if let TickResult::Action(name) = self.context_menu.tick(par.layout, &mut self.parser_state)? if let TickResult::Action(name) = self.context_menu.tick(par.layout, &mut self.parser_state)?
&& let (Some(action), Some(_), Some(action_name), Some(side), Some(value)) = { && let (Some(action), Some(action_name), Some(side), Some(value)) = {
let mut s = name.splitn(5, ';'); let mut s = name.splitn(4, ';');
(s.next(), s.next(), s.next(), s.next(), s.next()) (s.next(), s.next(), s.next(), s.next())
} { } {
let side = side.to_lowercase(); let side = side.to_lowercase();
let value = value.to_lowercase(); let value = value.to_lowercase();
@ -336,6 +343,7 @@ fn input_controls_for_action(
action.clone(), action.clone(),
click_type, click_type,
profile, profile,
current.threshold,
)?; )?;
let current_right = current.right.as_ref().map(|x| match x { let current_right = current.right.as_ref().map(|x| match x {
@ -343,7 +351,7 @@ fn input_controls_for_action(
OneOrMany::Many(s) => s.first().unwrap().as_str(), // safe OneOrMany::Many(s) => s.first().unwrap().as_str(), // safe
}); });
input_controls_for_hand(mp, parent, current_right, Side::Right, action, click_type, profile) input_controls_for_hand(mp, parent, current_right, Side::Right, action, click_type, profile, current.threshold)
} }
fn input_controls_for_hand( fn input_controls_for_hand(
@ -354,6 +362,7 @@ fn input_controls_for_hand(
action: Rc<str>, action: Rc<str>,
click_type: ClickType, click_type: ClickType,
profile: &Profile, profile: &Profile,
threshold: Option<[f32; 2]>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let subaction_path = match side { let subaction_path = match side {
Side::Left => "/user/hand/left", Side::Left => "/user/hand/left",
@ -405,7 +414,11 @@ fn input_controls_for_hand(
return Ok(()); return Ok(());
} }
clicks_dropdown(mp, parent, action, click_type)?; clicks_dropdown(mp, parent, action.clone(), click_type)?;
if let Some(component) = current.as_ref().map(|x| x.component) && component.is_analog() {
threshold_slider(mp, parent, action, threshold)?;
}
Ok(()) Ok(())
} }
@ -478,6 +491,39 @@ fn clicks_dropdown(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, curr
Ok(()) Ok(())
} }
fn threshold_slider(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, current: Option<[f32; 2]>) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
let current = current.unwrap_or([0.4, 0.6]);
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
params.insert(Rc::from("tooltip"), Rc::from("APP_SETTINGS.BINDINGS.THRESHOLD"));
params.insert(Rc::from("value"), Rc::from(format!("{:.2}", current[0])));
params.insert(Rc::from("value2"), Rc::from(format!("{:.2}", current[1])));
params.insert(Rc::from("min"), Rc::from("0.0"));
params.insert(Rc::from("max"), Rc::from("1.0"));
params.insert(Rc::from("step"), Rc::from("0.1"));
mp.parser_state
.instantiate_template(mp.doc_params, "ThresholdSlider", mp.layout, parent, params)?;
let slider = mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
slider.on_value_changed(Box::new({
let tasks = mp.tasks.clone();
move |_common, e| {
if matches!(e.index, wgui::components::slider::ValueIndex::Primary) {
tasks.push(Task::UpdateThreshold(action.clone(), e.value, current[1]));
} else {
tasks.push(Task::UpdateThreshold(action.clone(), current[0], e.value));
}
}
}));
Ok(())
}
fn create_dropdown<B: 'static + BindingsDropdown>(mp: &mut MacroParams, parent: WidgetID, mut params: HashMap<Rc<str>, Rc<str>>, action: Rc<str>, side: Side, current_text: Translation, available: Rc<[B]>) -> anyhow::Result<()> { fn create_dropdown<B: 'static + BindingsDropdown>(mp: &mut MacroParams, parent: WidgetID, mut params: HashMap<Rc<str>, Rc<str>>, action: Rc<str>, side: Side, current_text: Translation, available: Rc<[B]>) -> anyhow::Result<()> {
let id = mp.idx.to_string(); let id = mp.idx.to_string();
mp.idx += 1; mp.idx += 1;