snappy sliders

This commit is contained in:
galister
2025-11-13 00:32:44 +09:00
parent d26ddd66bd
commit adb093b725
4 changed files with 54 additions and 10 deletions

View File

@@ -28,6 +28,7 @@ pub struct ValuesMinMax {
pub value: f32,
pub min_value: f32,
pub max_value: f32,
pub step: f32,
}
impl ValuesMinMax {
@@ -38,6 +39,27 @@ impl ValuesMinMax {
fn get_from_normalized(&self, normalized: f32) -> f32 {
normalized * (self.max_value - self.min_value) + self.min_value
}
fn set_value(&mut self, new_value: f32) -> &mut Self {
let span = self.max_value - self.min_value;
let clamped = new_value.max(self.min_value).min(self.max_value);
// get the step index from min
let mut k = ((clamped - self.min_value) / self.step).round();
let k_max = (span / self.step).floor();
if k < 0.0 {
k = 0.0;
}
if k > k_max {
k = k_max;
}
let snapped = self.min_value + k * self.step;
self.value = snapped.max(self.min_value).min(self.max_value);
self
}
}
#[derive(Default)]
@@ -153,16 +175,23 @@ impl State {
}
fn update_text(common: &mut CallbackDataCommon, text: &mut WidgetLabel, value: f32) {
// round displayed value, should be sufficient for now
text.set_text(common, Translation::from_raw_text(&format!("{}", value.round())));
let pretty = if value < 0.005 && value >= -0.005 {
"0".to_string() // avoid cursed "-0"
} else {
let s = format!("{:.2}", value);
s.trim_end_matches('0').trim_end_matches('.').to_string()
};
text.set_text(common, Translation::from_raw_text(&pretty));
}
fn set_value(&mut self, common: &mut CallbackDataCommon, data: &Data, value: f32) {
//common.call_on_widget(data.slider_handle_id, |_div: &mut Div| {});
let changed = (self.values.value - value).abs() > f32::EPSILON;
let before = self.values.value;
self.values.set_value(value);
self.values.value = value;
let changed = self.values.value != before;
let mut style = common.state.tree.style(data.slider_handle_node).unwrap().clone();
conf_handle_style(&self.values, data.slider_body_node, &mut style, &common.state.tree);
common.alterables.mark_dirty(data.slider_handle_node);
@@ -170,11 +199,16 @@ impl State {
common.alterables.set_style(data.slider_handle_node, style);
if let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(data.slider_text_id) {
Self::update_text(common, &mut label, value);
Self::update_text(common, &mut label, self.values.value);
}
if changed && let Some(on_value_changed) = &self.on_value_changed {
if let Err(e) = on_value_changed(common, SliderValueChangedEvent { value }) {
if let Err(e) = on_value_changed(
common,
SliderValueChangedEvent {
value: self.values.value,
},
) {
log::error!("{e:?}"); // FIXME: proper error handling
}
}

View File

@@ -1,7 +1,7 @@
use crate::{
components::{Component, slider},
components::{slider, Component},
layout::WidgetID,
parser::{AttribPair, ParserContext, parse_check_f32, process_component, style::parse_style},
parser::{parse_check_f32, process_component, style::parse_style, AttribPair, ParserContext},
widget::ConstructEssentials,
};
@@ -13,6 +13,7 @@ pub fn parse_component_slider(
let mut min_value = 0.0;
let mut max_value = 1.0;
let mut initial_value = 0.5;
let mut step = 1.0;
let style = parse_style(attribs);
@@ -28,6 +29,9 @@ pub fn parse_component_slider(
"value" => {
parse_check_f32(value, &mut initial_value);
}
"step" => {
parse_check_f32(value, &mut step);
}
_ => {}
}
}
@@ -43,6 +47,7 @@ pub fn parse_component_slider(
min_value,
max_value,
value: initial_value,
step,
},
},
)?;
@@ -50,4 +55,4 @@ pub fn parse_component_slider(
process_component(ctx, Component(component), widget.id, attribs);
Ok(widget.id)
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M2.025 20.5V3.475q1.875.875 4.5 1.45t5.5.575t5.5-.575t4.5-1.45V20.5q-1.875-.875-4.5-1.437t-5.5-.563t-5.5.563t-4.5 1.437"/></svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@@ -37,9 +37,9 @@
<TopButton id="lock" src="bar/lock_open.svg" tooltip="Lock interaction" kind="btn_primary" />
<TopButton id="anchor" src="bar/anchor.svg" tooltip="Positioning" kind="btn_primary" />
<TopButton id="fade" src="bar/fade.svg" tooltip="Opacity" kind="btn_primary" />
<TopButton id="curve" src="bar/curve.svg" tooltip="Adjust curvature" kind="btn_primary" />
<TopButton id="move" src="bar/move-all.svg" tooltip="Move (click &amp; drag)" kind="btn_primary" />
<TopButton id="resize" src="bar/resize.svg" tooltip="Resize (click &amp; drag)" kind="btn_primary" />
<TopButton id="inout" src="bar/inout.svg" tooltip="Adjust distance (click &amp; drag)" kind="btn_primary" />
<TopButtonDanger id="delete" src="bar/delete.svg" tooltip="Delete" kind="btn_danger" />
<div width="8" height="100%" />
<TopButtonFaded src="watch/edit.svg" tooltip="Leave edit mode" press="::EditToggle" kind="btn_faded" />
@@ -49,6 +49,10 @@
<Slider width="200" height="16" min_value="5" max_value="100" value="100" />
<CheckBox id="additive" translation="Additive" tooltip="Alpha blend mode" tooltip_side="bottom" />
</div>
<div padding="8" gap="8" justify_content="center" align_items="center">
<label translation="Curvature" />
<Slider width="250" height="16" min_value="0" max_value="0.5" value="0.15" step="0.01" />
</div>
</div>
</rectangle>
</div>