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 [!!!] -->
<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 -->
<template name="ActionRow">
<rectangle macro="group_box" id="${id}">

View File

@ -81,7 +81,8 @@
"LEFT": "Left",
"RIGHT": "Right",
"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_SKYMAPS": "Browse skymaps",

View File

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

View File

@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashMap, rc::Rc};
use glam::Vec2;
use wgui::{
assets::AssetPath,
components::button::{ButtonClickEvent, ComponentButton},
components::{button::{ButtonClickEvent, ComponentButton}, slider::ComponentSlider},
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, WidgetID},
@ -34,6 +34,7 @@ enum Task {
Save,
Cancel,
OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>),
UpdateThreshold(Rc<str>, f32, f32)
}
pub struct Params<'a> {
@ -83,14 +84,20 @@ impl ViewTrait for View {
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
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 mut s = name.splitn(5, ';');
(s.next(), s.next(), s.next(), s.next(), s.next())
&& let (Some(action), Some(action_name), Some(side), Some(value)) = {
let mut s = name.splitn(4, ';');
(s.next(), s.next(), s.next(), s.next())
} {
let side = side.to_lowercase();
let value = value.to_lowercase();
@ -336,6 +343,7 @@ fn input_controls_for_action(
action.clone(),
click_type,
profile,
current.threshold,
)?;
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
});
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(
@ -354,6 +362,7 @@ fn input_controls_for_hand(
action: Rc<str>,
click_type: ClickType,
profile: &Profile,
threshold: Option<[f32; 2]>,
) -> anyhow::Result<()> {
let subaction_path = match side {
Side::Left => "/user/hand/left",
@ -405,7 +414,11 @@ fn input_controls_for_hand(
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(())
}
@ -478,6 +491,39 @@ fn clicks_dropdown(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, curr
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<()> {
let id = mp.idx.to_string();
mp.idx += 1;