[refactor] move DragValue to own file drag_value.rs

This commit is contained in:
Emil Ernerfeldt 2020-09-18 23:59:35 +02:00
parent dd8c298cb5
commit 05316b7045
2 changed files with 162 additions and 162 deletions

View file

@ -0,0 +1,160 @@
use std::ops::RangeInclusive;
use crate::{paint::*, *};
/// Combined into one function (rather than two) to make it easier
/// for the borrow checker.
type GetSetValue<'a> = Box<dyn 'a + FnMut(Option<f64>) -> f64>;
fn get(value_function: &mut GetSetValue<'_>) -> f64 {
(value_function)(None)
}
fn set(value_function: &mut GetSetValue<'_>, value: f64) {
(value_function)(Some(value));
}
/// A floating point value that you can change by dragging the number. More compact than a slider.
pub struct DragValue<'a> {
value_function: GetSetValue<'a>,
speed: f32,
prefix: String,
suffix: String,
range: RangeInclusive<f64>,
}
impl<'a> DragValue<'a> {
fn from_get_set(value_function: impl 'a + FnMut(Option<f64>) -> f64) -> Self {
Self {
value_function: Box::new(value_function),
speed: 1.0,
prefix: Default::default(),
suffix: Default::default(),
range: f64::NEG_INFINITY..=f64::INFINITY,
}
}
pub fn f32(value: &'a mut f32) -> Self {
Self {
..Self::from_get_set(move |v: Option<f64>| {
if let Some(v) = v {
*value = v as f32
}
*value as f64
})
}
}
pub fn u8(value: &'a mut u8) -> Self {
Self {
..Self::from_get_set(move |v: Option<f64>| {
if let Some(v) = v {
*value = v.round() as u8;
}
*value as f64
})
}
}
pub fn i32(value: &'a mut i32) -> Self {
Self {
..Self::from_get_set(move |v: Option<f64>| {
if let Some(v) = v {
*value = v.round() as i32;
}
*value as f64
})
}
}
/// How much the value changes when dragged one point (logical pixel).
pub fn speed(mut self, speed: f32) -> Self {
self.speed = speed;
self
}
/// Clamp the value to this range
pub fn range(mut self, range: RangeInclusive<f64>) -> Self {
self.range = range;
self
}
/// Show a prefix before the number, e.g. "x: "
pub fn prefix(mut self, prefix: impl ToString) -> Self {
self.prefix = prefix.to_string();
self
}
/// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
pub fn suffix(mut self, suffix: impl ToString) -> Self {
self.suffix = suffix.to_string();
self
}
}
impl<'a> Widget for DragValue<'a> {
fn ui(self, ui: &mut Ui) -> Response {
let Self {
mut value_function,
speed,
range,
prefix,
suffix,
} = self;
let value = get(&mut value_function);
let aim_rad = ui.input().physical_pixel_size(); // ui.input().aim_radius(); // TODO
let precision = (aim_rad / speed.abs()).log10().ceil().at_least(0.0) as usize;
let value_text = format_with_minimum_precision(value as f32, precision); // TODO: full precision
let kb_edit_id = ui.make_position_id().with("edit");
let is_kb_editing = ui.memory().has_kb_focus(kb_edit_id);
if is_kb_editing {
let mut value_text = ui
.memory()
.temp_edit_string
.take()
.unwrap_or_else(|| value_text);
let response = ui.add(
TextEdit::new(&mut value_text)
.id(kb_edit_id)
.multiline(false)
.desired_width(0.0)
.text_style(TextStyle::Monospace),
);
if let Ok(parsed_value) = value_text.parse() {
let parsed_value = clamp(parsed_value, range);
set(&mut value_function, parsed_value)
}
if ui.input().key_pressed(Key::Enter) {
ui.memory().surrender_kb_focus(kb_edit_id);
} else {
ui.memory().temp_edit_string = Some(value_text);
}
response
} else {
let button = Button::new(format!("{}{}{}", prefix, value_text, suffix))
.sense(Sense::click_and_drag())
.text_style(TextStyle::Monospace);
let response = ui.add(button);
// response.tooltip_text("Drag to edit, click to enter a value"); // TODO: may clash with users own tooltips
if response.clicked {
ui.memory().request_kb_focus(kb_edit_id);
ui.memory().temp_edit_string = None; // Filled in next frame
} else if response.active {
let mdelta = ui.input().mouse.delta;
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up
let delta_value = speed * delta_points;
if delta_value != 0.0 {
let new_value = value + delta_value as f64;
let new_value = round_to_precision(new_value, precision);
let new_value = clamp(new_value, range);
set(&mut value_function, new_value);
// TODO: To make use or `smart_aim` for `DragValue` we need to store some state somewhere,
// otherwise we will just keep rounding to the same value while moving the mouse.
}
}
response
}
}
}

View file

@ -9,16 +9,15 @@
use crate::{layout::Direction, *};
pub mod color_picker;
mod drag_value;
mod image;
mod slider;
pub(crate) mod text_edit;
pub use {image::Image, slider::*, text_edit::*};
pub use {drag_value::DragValue, image::Image, slider::*, text_edit::*};
use paint::*;
use std::ops::RangeInclusive;
// ----------------------------------------------------------------------------
/// Anything implementing Widget can be added to a Ui with `Ui::add`
@ -529,162 +528,3 @@ impl Widget for Separator {
ui.interact_hover(rect)
}
}
// ----------------------------------------------------------------------------
/// Combined into one function (rather than two) to make it easier
/// for the borrow checker.
type GetSetValue<'a> = Box<dyn 'a + FnMut(Option<f64>) -> f64>;
fn get(value_function: &mut GetSetValue<'_>) -> f64 {
(value_function)(None)
}
fn set(value_function: &mut GetSetValue<'_>, value: f64) {
(value_function)(Some(value));
}
/// A floating point value that you can change by dragging the number. More compact than a slider.
pub struct DragValue<'a> {
value_function: GetSetValue<'a>,
speed: f32,
prefix: String,
suffix: String,
range: RangeInclusive<f64>,
}
impl<'a> DragValue<'a> {
fn from_get_set(value_function: impl 'a + FnMut(Option<f64>) -> f64) -> Self {
Self {
value_function: Box::new(value_function),
speed: 1.0,
prefix: Default::default(),
suffix: Default::default(),
range: f64::NEG_INFINITY..=f64::INFINITY,
}
}
pub fn f32(value: &'a mut f32) -> Self {
Self {
..Self::from_get_set(move |v: Option<f64>| {
if let Some(v) = v {
*value = v as f32
}
*value as f64
})
}
}
pub fn u8(value: &'a mut u8) -> Self {
Self {
..Self::from_get_set(move |v: Option<f64>| {
if let Some(v) = v {
*value = v.round() as u8;
}
*value as f64
})
}
}
pub fn i32(value: &'a mut i32) -> Self {
Self {
..Self::from_get_set(move |v: Option<f64>| {
if let Some(v) = v {
*value = v.round() as i32;
}
*value as f64
})
}
}
/// How much the value changes when dragged one point (logical pixel).
pub fn speed(mut self, speed: f32) -> Self {
self.speed = speed;
self
}
/// Clamp the value to this range
pub fn range(mut self, range: RangeInclusive<f64>) -> Self {
self.range = range;
self
}
/// Show a prefix before the number, e.g. "x: "
pub fn prefix(mut self, prefix: impl ToString) -> Self {
self.prefix = prefix.to_string();
self
}
/// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
pub fn suffix(mut self, suffix: impl ToString) -> Self {
self.suffix = suffix.to_string();
self
}
}
impl<'a> Widget for DragValue<'a> {
fn ui(self, ui: &mut Ui) -> Response {
let Self {
mut value_function,
speed,
range,
prefix,
suffix,
} = self;
let value = get(&mut value_function);
let aim_rad = ui.input().physical_pixel_size(); // ui.input().aim_radius(); // TODO
let precision = (aim_rad / speed.abs()).log10().ceil().at_least(0.0) as usize;
let value_text = format_with_minimum_precision(value as f32, precision); // TODO: full precision
let kb_edit_id = ui.make_position_id().with("edit");
let is_kb_editing = ui.memory().has_kb_focus(kb_edit_id);
if is_kb_editing {
let mut value_text = ui
.memory()
.temp_edit_string
.take()
.unwrap_or_else(|| value_text);
let response = ui.add(
TextEdit::new(&mut value_text)
.id(kb_edit_id)
.multiline(false)
.desired_width(0.0)
.text_style(TextStyle::Monospace),
);
if let Ok(parsed_value) = value_text.parse() {
let parsed_value = clamp(parsed_value, range);
set(&mut value_function, parsed_value)
}
if ui.input().key_pressed(Key::Enter) {
ui.memory().surrender_kb_focus(kb_edit_id);
} else {
ui.memory().temp_edit_string = Some(value_text);
}
response
} else {
let button = Button::new(format!("{}{}{}", prefix, value_text, suffix))
.sense(Sense::click_and_drag())
.text_style(TextStyle::Monospace);
let response = ui.add(button);
// response.tooltip_text("Drag to edit, click to enter a value"); // TODO: may clash with users own tooltips
if response.clicked {
ui.memory().request_kb_focus(kb_edit_id);
ui.memory().temp_edit_string = None; // Filled in next frame
} else if response.active {
let mdelta = ui.input().mouse.delta;
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up
let delta_value = speed * delta_points;
if delta_value != 0.0 {
let new_value = value + delta_value as f64;
let new_value = round_to_precision(new_value, precision);
let new_value = clamp(new_value, range);
set(&mut value_function, new_value);
// TODO: To make use or `smart_aim` for `DragValue` we need to store some state somewhere,
// otherwise we will just keep rounding to the same value while moving the mouse.
}
}
response
}
}
}