Enhance Slider and VerticalSlider functionality
* Add optional default behavior * Add a `default` field * Add a `default()` method to set the `default` field * A double-click, ctrl-click or command-click will set the slider to the default value * Add optional fine-grained control * Add an optional `step_fine` field * Add a `step_fine()` method to set the `step_fine` field * Use `step_fine` in place of `step` while shift is pressed * Add increment/decrement via up/down keys * Update `Slider` and `VerticalSlider` examples
This commit is contained in:
parent
66c8a804c6
commit
5e2b3d4a51
3 changed files with 324 additions and 40 deletions
|
|
@ -12,13 +12,21 @@ pub enum Message {
|
||||||
|
|
||||||
pub struct Slider {
|
pub struct Slider {
|
||||||
slider_value: u8,
|
slider_value: u8,
|
||||||
|
slider_default: u8,
|
||||||
|
slider_step: u8,
|
||||||
|
slider_step_fine: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sandbox for Slider {
|
impl Sandbox for Slider {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
fn new() -> Slider {
|
fn new() -> Slider {
|
||||||
Slider { slider_value: 50 }
|
Slider {
|
||||||
|
slider_value: 50,
|
||||||
|
slider_default: 50,
|
||||||
|
slider_step: 5,
|
||||||
|
slider_step_fine: 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
|
|
@ -35,14 +43,25 @@ impl Sandbox for Slider {
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let value = self.slider_value;
|
let value = self.slider_value;
|
||||||
|
let default = self.slider_default;
|
||||||
|
let step = self.slider_step;
|
||||||
|
let step_fine = self.slider_step_fine;
|
||||||
|
|
||||||
let h_slider =
|
let h_slider = container(
|
||||||
container(slider(0..=100, value, Message::SliderChanged))
|
slider(0..=100, value, Message::SliderChanged)
|
||||||
.width(250);
|
.default(default)
|
||||||
|
.step(step)
|
||||||
|
.step_fine(step_fine),
|
||||||
|
)
|
||||||
|
.width(250);
|
||||||
|
|
||||||
let v_slider =
|
let v_slider = container(
|
||||||
container(vertical_slider(0..=100, value, Message::SliderChanged))
|
vertical_slider(0..=100, value, Message::SliderChanged)
|
||||||
.height(200);
|
.default(default)
|
||||||
|
.step(step)
|
||||||
|
.step_fine(step_fine),
|
||||||
|
)
|
||||||
|
.height(200);
|
||||||
|
|
||||||
let text = text(format!("{value}"));
|
let text = text(format!("{value}"));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
//!
|
//!
|
||||||
//! A [`Slider`] has some local [`State`].
|
//! A [`Slider`] has some local [`State`].
|
||||||
use crate::core::event::{self, Event};
|
use crate::core::event::{self, Event};
|
||||||
|
use crate::core::keyboard;
|
||||||
|
use crate::core::keyboard::key::{self, Key};
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse::{self, click};
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
|
@ -49,7 +51,9 @@ where
|
||||||
{
|
{
|
||||||
range: RangeInclusive<T>,
|
range: RangeInclusive<T>,
|
||||||
step: T,
|
step: T,
|
||||||
|
step_fine: Option<T>,
|
||||||
value: T,
|
value: T,
|
||||||
|
default: Option<T>,
|
||||||
on_change: Box<dyn Fn(T) -> Message + 'a>,
|
on_change: Box<dyn Fn(T) -> Message + 'a>,
|
||||||
on_release: Option<Message>,
|
on_release: Option<Message>,
|
||||||
width: Length,
|
width: Length,
|
||||||
|
|
@ -92,8 +96,10 @@ where
|
||||||
|
|
||||||
Slider {
|
Slider {
|
||||||
value,
|
value,
|
||||||
|
default: None,
|
||||||
range,
|
range,
|
||||||
step: T::from(1),
|
step: T::from(1),
|
||||||
|
step_fine: None,
|
||||||
on_change: Box::new(on_change),
|
on_change: Box::new(on_change),
|
||||||
on_release: None,
|
on_release: None,
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
|
|
@ -102,6 +108,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the optional default value for the [`Slider`].
|
||||||
|
/// If set, [`Slider`] will reset to this value when doubled-clicked, ctrl-clicked, or command-clicked.
|
||||||
|
pub fn default(mut self, default: impl Into<T>) -> Self {
|
||||||
|
self.default = Some(default.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the release message of the [`Slider`].
|
/// Sets the release message of the [`Slider`].
|
||||||
/// This is called when the mouse is released from the slider.
|
/// This is called when the mouse is released from the slider.
|
||||||
///
|
///
|
||||||
|
|
@ -136,6 +149,13 @@ where
|
||||||
self.step = step.into();
|
self.step = step.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the optional fine-grained step size for the [`Slider`].
|
||||||
|
/// If set, this value is used as the step size while shift is pressed.
|
||||||
|
pub fn step_fine(mut self, step_fine: impl Into<T>) -> Self {
|
||||||
|
self.step_fine = Some(step_fine.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
|
@ -188,8 +208,10 @@ where
|
||||||
shell,
|
shell,
|
||||||
tree.state.downcast_mut::<State>(),
|
tree.state.downcast_mut::<State>(),
|
||||||
&mut self.value,
|
&mut self.value,
|
||||||
|
self.default,
|
||||||
&self.range,
|
&self.range,
|
||||||
self.step,
|
self.step,
|
||||||
|
self.step_fine,
|
||||||
self.on_change.as_ref(),
|
self.on_change.as_ref(),
|
||||||
&self.on_release,
|
&self.on_release,
|
||||||
)
|
)
|
||||||
|
|
@ -253,8 +275,10 @@ pub fn update<Message, T>(
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
value: &mut T,
|
value: &mut T,
|
||||||
|
default: Option<T>,
|
||||||
range: &RangeInclusive<T>,
|
range: &RangeInclusive<T>,
|
||||||
step: T,
|
step: T,
|
||||||
|
step_fine: Option<T>,
|
||||||
on_change: &dyn Fn(T) -> Message,
|
on_change: &dyn Fn(T) -> Message,
|
||||||
on_release: &Option<Message>,
|
on_release: &Option<Message>,
|
||||||
) -> event::Status
|
) -> event::Status
|
||||||
|
|
@ -264,14 +288,19 @@ where
|
||||||
{
|
{
|
||||||
let is_dragging = state.is_dragging;
|
let is_dragging = state.is_dragging;
|
||||||
|
|
||||||
let mut change = |cursor_position: Point| {
|
let change_cursor_position = |cursor_position: Point| -> Option<T> {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let new_value = if cursor_position.x <= bounds.x {
|
let new_value = if cursor_position.x <= bounds.x {
|
||||||
*range.start()
|
Some(*range.start())
|
||||||
} else if cursor_position.x >= bounds.x + bounds.width {
|
} else if cursor_position.x >= bounds.x + bounds.width {
|
||||||
*range.end()
|
Some(*range.end())
|
||||||
} else {
|
} else {
|
||||||
let step = step.into();
|
let step = match step_fine {
|
||||||
|
Some(step_fine) if state.keyboard_modifiers.shift() => {
|
||||||
|
step_fine.into()
|
||||||
|
}
|
||||||
|
_ => step.into(),
|
||||||
|
};
|
||||||
let start = (*range.start()).into();
|
let start = (*range.start()).into();
|
||||||
let end = (*range.end()).into();
|
let end = (*range.end()).into();
|
||||||
|
|
||||||
|
|
@ -281,17 +310,67 @@ where
|
||||||
let steps = (percent * (end - start) / step).round();
|
let steps = (percent * (end - start) / step).round();
|
||||||
let value = steps * step + start;
|
let value = steps * step + start;
|
||||||
|
|
||||||
if let Some(value) = T::from_f64(value) {
|
T::from_f64(value)
|
||||||
value
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
|
new_value
|
||||||
shell.publish((on_change)(new_value));
|
};
|
||||||
|
|
||||||
*value = new_value;
|
let increment = |value: T| -> Option<T> {
|
||||||
|
let step = match step_fine {
|
||||||
|
Some(step_fine) if state.keyboard_modifiers.shift() => {
|
||||||
|
step_fine.into()
|
||||||
|
}
|
||||||
|
_ => step.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let steps = (value.into() / step).round();
|
||||||
|
let new_value = step * (steps + f64::from(1));
|
||||||
|
|
||||||
|
if new_value > (*range.end()).into() {
|
||||||
|
return Some(*range.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
T::from_f64(new_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
let decrement = |value: T| -> Option<T> {
|
||||||
|
let step = match step_fine {
|
||||||
|
Some(step_fine) if state.keyboard_modifiers.shift() => {
|
||||||
|
step_fine.into()
|
||||||
|
}
|
||||||
|
_ => step.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let steps = (value.into() / step).round();
|
||||||
|
let new_value = step * (steps - f64::from(1));
|
||||||
|
|
||||||
|
if new_value < (*range.start()).into() {
|
||||||
|
return Some(*range.start());
|
||||||
|
}
|
||||||
|
|
||||||
|
T::from_f64(new_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Change {
|
||||||
|
Default,
|
||||||
|
CursorPosition(Point),
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut change = |change: Change| {
|
||||||
|
if let Some(new_value) = match change {
|
||||||
|
Change::Default => default,
|
||||||
|
Change::CursorPosition(point) => change_cursor_position(point),
|
||||||
|
Change::Increment => increment(*value),
|
||||||
|
Change::Decrement => decrement(*value),
|
||||||
|
} {
|
||||||
|
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
|
||||||
|
shell.publish((on_change)(new_value));
|
||||||
|
|
||||||
|
*value = new_value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -300,8 +379,31 @@ where
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
if let Some(cursor_position) = cursor.position_over(layout.bounds())
|
if let Some(cursor_position) = cursor.position_over(layout.bounds())
|
||||||
{
|
{
|
||||||
change(cursor_position);
|
let click =
|
||||||
state.is_dragging = true;
|
mouse::Click::new(cursor_position, state.last_click);
|
||||||
|
|
||||||
|
match click.kind() {
|
||||||
|
click::Kind::Single => {
|
||||||
|
if state.keyboard_modifiers.control()
|
||||||
|
|| state.keyboard_modifiers.command()
|
||||||
|
{
|
||||||
|
change(Change::Default);
|
||||||
|
state.is_dragging = false;
|
||||||
|
} else {
|
||||||
|
change(Change::CursorPosition(cursor_position));
|
||||||
|
state.is_dragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
click::Kind::Double => {
|
||||||
|
change(Change::Default);
|
||||||
|
state.is_dragging = false;
|
||||||
|
}
|
||||||
|
mouse::click::Kind::Triple => {
|
||||||
|
state.is_dragging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.last_click = Some(click);
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
|
|
@ -321,11 +423,31 @@ where
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
if is_dragging {
|
if is_dragging {
|
||||||
let _ = cursor.position().map(change);
|
let _ = cursor
|
||||||
|
.position()
|
||||||
|
.map(|point| change(Change::CursorPosition(point)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
|
||||||
|
if cursor.position_over(layout.bounds()).is_some() {
|
||||||
|
match key {
|
||||||
|
Key::Named(key::Named::ArrowUp) => {
|
||||||
|
change(Change::Increment);
|
||||||
|
}
|
||||||
|
Key::Named(key::Named::ArrowDown) => {
|
||||||
|
change(Change::Decrement);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
return event::Status::Captured;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||||
|
state.keyboard_modifiers = modifiers;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,9 +573,11 @@ pub fn mouse_interaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The local state of a [`Slider`].
|
/// The local state of a [`Slider`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
is_dragging: bool,
|
is_dragging: bool,
|
||||||
|
last_click: Option<mouse::Click>,
|
||||||
|
keyboard_modifiers: keyboard::Modifiers,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -462,3 +586,11 @@ impl State {
|
||||||
State::default()
|
State::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for State {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.is_dragging == other.is_dragging
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for State {}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,11 @@ pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet};
|
||||||
|
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::core::event::{self, Event};
|
use crate::core::event::{self, Event};
|
||||||
|
use crate::core::keyboard;
|
||||||
|
use crate::core::keyboard::key::{self, Key};
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
|
use crate::core::mouse::click;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
|
@ -46,7 +49,9 @@ where
|
||||||
{
|
{
|
||||||
range: RangeInclusive<T>,
|
range: RangeInclusive<T>,
|
||||||
step: T,
|
step: T,
|
||||||
|
step_fine: Option<T>,
|
||||||
value: T,
|
value: T,
|
||||||
|
default: Option<T>,
|
||||||
on_change: Box<dyn Fn(T) -> Message + 'a>,
|
on_change: Box<dyn Fn(T) -> Message + 'a>,
|
||||||
on_release: Option<Message>,
|
on_release: Option<Message>,
|
||||||
width: f32,
|
width: f32,
|
||||||
|
|
@ -89,8 +94,10 @@ where
|
||||||
|
|
||||||
VerticalSlider {
|
VerticalSlider {
|
||||||
value,
|
value,
|
||||||
|
default: None,
|
||||||
range,
|
range,
|
||||||
step: T::from(1),
|
step: T::from(1),
|
||||||
|
step_fine: None,
|
||||||
on_change: Box::new(on_change),
|
on_change: Box::new(on_change),
|
||||||
on_release: None,
|
on_release: None,
|
||||||
width: Self::DEFAULT_WIDTH,
|
width: Self::DEFAULT_WIDTH,
|
||||||
|
|
@ -99,6 +106,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the optional default value for the [`VerticalSlider`].
|
||||||
|
/// If set, [`VerticalSlider`] will reset to this value when doubled-clicked, ctrl-clicked, or command-clicked.
|
||||||
|
pub fn default(mut self, default: impl Into<T>) -> Self {
|
||||||
|
self.default = Some(default.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the release message of the [`VerticalSlider`].
|
/// Sets the release message of the [`VerticalSlider`].
|
||||||
/// This is called when the mouse is released from the slider.
|
/// This is called when the mouse is released from the slider.
|
||||||
///
|
///
|
||||||
|
|
@ -133,6 +147,13 @@ where
|
||||||
self.step = step;
|
self.step = step;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the optional fine-grained step size for the [`VerticalSlider`].
|
||||||
|
/// If set, this value is used as the step size while shift is pressed.
|
||||||
|
pub fn step_fine(mut self, step_fine: impl Into<T>) -> Self {
|
||||||
|
self.step_fine = Some(step_fine.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
|
@ -185,8 +206,10 @@ where
|
||||||
shell,
|
shell,
|
||||||
tree.state.downcast_mut::<State>(),
|
tree.state.downcast_mut::<State>(),
|
||||||
&mut self.value,
|
&mut self.value,
|
||||||
|
self.default,
|
||||||
&self.range,
|
&self.range,
|
||||||
self.step,
|
self.step,
|
||||||
|
self.step_fine,
|
||||||
self.on_change.as_ref(),
|
self.on_change.as_ref(),
|
||||||
&self.on_release,
|
&self.on_release,
|
||||||
)
|
)
|
||||||
|
|
@ -251,8 +274,10 @@ pub fn update<Message, T>(
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
value: &mut T,
|
value: &mut T,
|
||||||
|
default: Option<T>,
|
||||||
range: &RangeInclusive<T>,
|
range: &RangeInclusive<T>,
|
||||||
step: T,
|
step: T,
|
||||||
|
step_fine: Option<T>,
|
||||||
on_change: &dyn Fn(T) -> Message,
|
on_change: &dyn Fn(T) -> Message,
|
||||||
on_release: &Option<Message>,
|
on_release: &Option<Message>,
|
||||||
) -> event::Status
|
) -> event::Status
|
||||||
|
|
@ -262,15 +287,20 @@ where
|
||||||
{
|
{
|
||||||
let is_dragging = state.is_dragging;
|
let is_dragging = state.is_dragging;
|
||||||
|
|
||||||
let mut change = |cursor_position: Point| {
|
let change_cursor_position = |cursor_position: Point| -> Option<T> {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let new_value = if cursor_position.y >= bounds.y + bounds.height {
|
let new_value = if cursor_position.y >= bounds.y + bounds.height {
|
||||||
*range.start()
|
Some(*range.start())
|
||||||
} else if cursor_position.y <= bounds.y {
|
} else if cursor_position.y <= bounds.y {
|
||||||
*range.end()
|
Some(*range.end())
|
||||||
} else {
|
} else {
|
||||||
let step = step.into();
|
let step = match step_fine {
|
||||||
|
Some(step_fine) if state.keyboard_modifiers.shift() => {
|
||||||
|
step_fine.into()
|
||||||
|
}
|
||||||
|
_ => step.into(),
|
||||||
|
};
|
||||||
let start = (*range.start()).into();
|
let start = (*range.start()).into();
|
||||||
let end = (*range.end()).into();
|
let end = (*range.end()).into();
|
||||||
|
|
||||||
|
|
@ -281,17 +311,67 @@ where
|
||||||
let steps = (percent * (end - start) / step).round();
|
let steps = (percent * (end - start) / step).round();
|
||||||
let value = steps * step + start;
|
let value = steps * step + start;
|
||||||
|
|
||||||
if let Some(value) = T::from_f64(value) {
|
T::from_f64(value)
|
||||||
value
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
|
new_value
|
||||||
shell.publish((on_change)(new_value));
|
};
|
||||||
|
|
||||||
*value = new_value;
|
let increment = |value: T| -> Option<T> {
|
||||||
|
let step = match step_fine {
|
||||||
|
Some(step_fine) if state.keyboard_modifiers.shift() => {
|
||||||
|
step_fine.into()
|
||||||
|
}
|
||||||
|
_ => step.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let steps = (value.into() / step).round();
|
||||||
|
let new_value = step * (steps + f64::from(1));
|
||||||
|
|
||||||
|
if new_value > (*range.end()).into() {
|
||||||
|
return Some(*range.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
T::from_f64(new_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
let decrement = |value: T| -> Option<T> {
|
||||||
|
let step = match step_fine {
|
||||||
|
Some(step_fine) if state.keyboard_modifiers.shift() => {
|
||||||
|
step_fine.into()
|
||||||
|
}
|
||||||
|
_ => step.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let steps = (value.into() / step).round();
|
||||||
|
let new_value = step * (steps - f64::from(1));
|
||||||
|
|
||||||
|
if new_value < (*range.start()).into() {
|
||||||
|
return Some(*range.start());
|
||||||
|
}
|
||||||
|
|
||||||
|
T::from_f64(new_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Change {
|
||||||
|
Default,
|
||||||
|
CursorPosition(Point),
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut change = |change: Change| {
|
||||||
|
if let Some(new_value) = match change {
|
||||||
|
Change::Default => default,
|
||||||
|
Change::CursorPosition(point) => change_cursor_position(point),
|
||||||
|
Change::Increment => increment(*value),
|
||||||
|
Change::Decrement => decrement(*value),
|
||||||
|
} {
|
||||||
|
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
|
||||||
|
shell.publish((on_change)(new_value));
|
||||||
|
|
||||||
|
*value = new_value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -300,8 +380,31 @@ where
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
if let Some(cursor_position) = cursor.position_over(layout.bounds())
|
if let Some(cursor_position) = cursor.position_over(layout.bounds())
|
||||||
{
|
{
|
||||||
change(cursor_position);
|
let click =
|
||||||
state.is_dragging = true;
|
mouse::Click::new(cursor_position, state.last_click);
|
||||||
|
|
||||||
|
match click.kind() {
|
||||||
|
click::Kind::Single => {
|
||||||
|
if state.keyboard_modifiers.control()
|
||||||
|
|| state.keyboard_modifiers.command()
|
||||||
|
{
|
||||||
|
change(Change::Default);
|
||||||
|
state.is_dragging = false;
|
||||||
|
} else {
|
||||||
|
change(Change::CursorPosition(cursor_position));
|
||||||
|
state.is_dragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
click::Kind::Double => {
|
||||||
|
change(Change::Default);
|
||||||
|
state.is_dragging = false;
|
||||||
|
}
|
||||||
|
mouse::click::Kind::Triple => {
|
||||||
|
state.is_dragging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.last_click = Some(click);
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
|
|
@ -321,11 +424,31 @@ where
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
if is_dragging {
|
if is_dragging {
|
||||||
let _ = cursor.position().map(change);
|
let _ = cursor
|
||||||
|
.position()
|
||||||
|
.map(|point| change(Change::CursorPosition(point)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
|
||||||
|
if cursor.position_over(layout.bounds()).is_some() {
|
||||||
|
match key {
|
||||||
|
Key::Named(key::Named::ArrowUp) => {
|
||||||
|
change(Change::Increment);
|
||||||
|
}
|
||||||
|
Key::Named(key::Named::ArrowDown) => {
|
||||||
|
change(Change::Decrement);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
return event::Status::Captured;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||||
|
state.keyboard_modifiers = modifiers;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,9 +574,11 @@ pub fn mouse_interaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The local state of a [`VerticalSlider`].
|
/// The local state of a [`VerticalSlider`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
is_dragging: bool,
|
is_dragging: bool,
|
||||||
|
last_click: Option<mouse::Click>,
|
||||||
|
keyboard_modifiers: keyboard::Modifiers,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -462,3 +587,11 @@ impl State {
|
||||||
State::default()
|
State::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for State {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.is_dragging == other.is_dragging
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for State {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue