From 96eb67c94ee04fcaf662c3f900711834f30c84fc Mon Sep 17 00:00:00 2001 From: Anna Scholtz Date: Thu, 14 Nov 2019 14:55:31 -0800 Subject: [PATCH 1/7] Draw stepper widget --- druid/examples/switch.rs | 12 ++- druid/src/widget/stepper.rs | 184 ++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 druid/src/widget/stepper.rs diff --git a/druid/examples/switch.rs b/druid/examples/switch.rs index fa8dbe6bb8..6474e667ee 100644 --- a/druid/examples/switch.rs +++ b/druid/examples/switch.rs @@ -18,6 +18,7 @@ use druid::{AppLauncher, Data, Lens, LensWrap, Widget, WindowDesc}; #[derive(Clone, Data, Lens)] struct DemoState { value: bool, + stepper_value: f64, } fn build_widget() -> impl Widget { @@ -29,6 +30,12 @@ fn build_widget() -> impl Widget { row.add_child(Padding::new(5.0, switch_label), 0.0); row.add_child(Padding::new(5.0, switch), 0.0); + let stepper = LensWrap::new( + Stepper::new(0.0, 10.0, 1.0, |_ctx, _data, _env| eprintln!("---")), + lenses::demo_state::stepper_value, + ); + row.add_child(Padding::new(5.0, stepper), 0.0); + col.add_child(Padding::new(5.0, row), 1.0); col } @@ -37,6 +44,9 @@ fn main() { let window = WindowDesc::new(build_widget); AppLauncher::with_window(window) .use_simple_logger() - .launch(DemoState { value: true }) + .launch(DemoState { + value: true, + stepper_value: 1.0, + }) .expect("launch failed"); } diff --git a/druid/src/widget/stepper.rs b/druid/src/widget/stepper.rs new file mode 100644 index 0000000000..74c5f2a037 --- /dev/null +++ b/druid/src/widget/stepper.rs @@ -0,0 +1,184 @@ +// Copyright 2019 The xi-editor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A stepper widget. + +use crate::{ + BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, UpdateCtx, + Widget, +}; + +use crate::kurbo::{BezPath, Rect, RoundedRect}; +use crate::piet::{ + FontBuilder, LinearGradient, RenderContext, Text, TextLayout, TextLayoutBuilder, UnitPoint, +}; + +use crate::theme; +use crate::widget::{Align, Label, LabelText, SizedBox}; +use crate::Point; + +const STEPPER_WIDTH: f64 = 15.; + +/// A stepper. +pub struct Stepper { + max: f64, + min: f64, + step: f64, + wrap: bool, + /// A closure that will be invoked when the value changed. + value_changed: Box, + increase_active: bool, + decrease_active: bool, +} + +impl Stepper { + pub fn new( + max: f64, + min: f64, + step: f64, + value_changed: impl Fn(&mut EventCtx, &mut f64, &Env) + 'static, + ) -> impl Widget { + Align::vertical( + UnitPoint::CENTER, + Stepper { + max, + min, + step, + wrap: false, + value_changed: Box::new(value_changed), + increase_active: false, + decrease_active: false, + }, + ) + } + + pub fn min(mut self, min: f64) -> Self { + self.min = min; + self + } + + pub fn max(mut self, max: f64) -> Self { + self.max = max; + self + } + + pub fn wrap(mut self, wrap: bool) -> Self { + self.wrap = wrap; + self + } +} + +impl Widget for Stepper { + fn paint(&mut self, paint_ctx: &mut PaintCtx, base_state: &BaseState, data: &f64, env: &Env) { + let rounded_rect = + RoundedRect::from_origin_size(Point::ORIGIN, base_state.size().to_vec2(), 4.); + + let height = base_state.size().height; + let button_size = Size::new(STEPPER_WIDTH, height / 2.); + + let mut increase_button_origin = Point::ORIGIN; + let mut decrease_button_origin = Point::ORIGIN; + decrease_button_origin.y += height / 2.; + + let increase_rect = Rect::from_origin_size(increase_button_origin, button_size); + let decrease_rect = Rect::from_origin_size(decrease_button_origin, button_size); + + let active_gradient = LinearGradient::new( + UnitPoint::TOP, + UnitPoint::BOTTOM, + (env.get(theme::PRIMARY_LIGHT), env.get(theme::PRIMARY_DARK)), + ); + + let inactive_gradient = LinearGradient::new( + UnitPoint::TOP, + UnitPoint::BOTTOM, + (env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)), + ); + + paint_ctx.stroke(rounded_rect, &env.get(theme::BORDER), 2.0); + paint_ctx.clip(rounded_rect); + + if self.increase_active { + paint_ctx.fill(increase_rect, &active_gradient); + } else { + paint_ctx.fill(increase_rect, &inactive_gradient); + }; + + if self.decrease_active { + paint_ctx.fill(decrease_rect, &active_gradient); + } else { + paint_ctx.fill(decrease_rect, &inactive_gradient); + }; + + let mut increase_arrow = BezPath::new(); + increase_arrow.move_to(Point::new(4., height / 2. - 4.)); + increase_arrow.line_to(Point::new(STEPPER_WIDTH - 4., height / 2. - 4.)); + increase_arrow.line_to(Point::new(STEPPER_WIDTH / 2., 4.)); + increase_arrow.close_path(); + paint_ctx.fill(increase_arrow, &env.get(theme::LABEL_COLOR)); + + let mut decrease_arrow = BezPath::new(); + decrease_arrow.move_to(Point::new(4., height / 2. + 4.)); + decrease_arrow.line_to(Point::new(STEPPER_WIDTH - 4., height / 2. + 4.)); + decrease_arrow.line_to(Point::new(STEPPER_WIDTH / 2., height - 4.)); + decrease_arrow.close_path(); + paint_ctx.fill(decrease_arrow, &env.get(theme::LABEL_COLOR)); + } + + fn layout( + &mut self, + _layout_ctx: &mut LayoutCtx, + bc: &BoxConstraints, + _data: &f64, + env: &Env, + ) -> Size { + bc.constrain(Size::new( + STEPPER_WIDTH, + env.get(theme::BORDERED_WIDGET_HEIGHT), + )) + } + + fn event(&mut self, event: &Event, ctx: &mut EventCtx, data: &mut f64, env: &Env) { + let height = env.get(theme::BORDERED_WIDGET_HEIGHT); + + match event { + Event::MouseDown(mouse) => { + ctx.set_active(true); + + if mouse.pos.y > height / 2. { + self.decrease_active = true; + } else { + self.increase_active = true; + } + + // todo: increase/decrease value + + ctx.invalidate(); + } + Event::MouseUp(_) => { + ctx.set_active(false); + + self.decrease_active = false; + self.increase_active = false; + + ctx.invalidate(); + } + _ => (), + } + } + + fn update(&mut self, ctx: &mut UpdateCtx, _old_data: Option<&f64>, _data: &f64, _env: &Env) { + ctx.invalidate(); + } +} From 1185145599a11a766dba3145f4b615d0cae59a17 Mon Sep 17 00:00:00 2001 From: Anna Scholtz Date: Thu, 14 Nov 2019 16:54:19 -0800 Subject: [PATCH 2/7] Increase/decrease value with stepper widget --- druid/examples/switch.rs | 18 +++++++-- druid/src/widget/stepper.rs | 78 ++++++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/druid/examples/switch.rs b/druid/examples/switch.rs index 6474e667ee..07f4d6d373 100644 --- a/druid/examples/switch.rs +++ b/druid/examples/switch.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use druid::widget::{Flex, Label, Padding, Switch}; +use druid::widget::{Flex, Switch, DynLabel, Label, Padding, Row, Stepper, Switch}; use druid::{AppLauncher, Data, Lens, LensWrap, Widget, WindowDesc}; #[derive(Clone, Data, Lens)] @@ -30,13 +30,23 @@ fn build_widget() -> impl Widget { row.add_child(Padding::new(5.0, switch_label), 0.0); row.add_child(Padding::new(5.0, switch), 0.0); - let stepper = LensWrap::new( - Stepper::new(0.0, 10.0, 1.0, |_ctx, _data, _env| eprintln!("---")), + + let label_stepper = LensWrap::new( + Stepper::new(0.0, 10.0, 1.0, true, |_ctx, _data, _env| {}), lenses::demo_state::stepper_value, ); - row.add_child(Padding::new(5.0, stepper), 0.0); + + let mut stepper_row = Row::new(); + + let label = DynLabel::new(|data: &DemoState, _env| { + format!("Stepper value: {0:.0}", data.stepper_value) + }); + + stepper_row.add_child(Padding::new(5.0, label), 0.0); + stepper_row.add_child(Padding::new(5.0, label_stepper), 0.0); col.add_child(Padding::new(5.0, row), 1.0); + col.add_child(Padding::new(5.0, stepper_row), 1.0); col } diff --git a/druid/src/widget/stepper.rs b/druid/src/widget/stepper.rs index 74c5f2a037..753d281d0a 100644 --- a/druid/src/widget/stepper.rs +++ b/druid/src/widget/stepper.rs @@ -15,9 +15,10 @@ //! A stepper widget. use crate::{ - BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, UpdateCtx, - Widget, + BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, TimerToken, + UpdateCtx, Widget, }; +use std::time::{Duration, Instant}; use crate::kurbo::{BezPath, Rect, RoundedRect}; use crate::piet::{ @@ -28,8 +29,6 @@ use crate::theme; use crate::widget::{Align, Label, LabelText, SizedBox}; use crate::Point; -const STEPPER_WIDTH: f64 = 15.; - /// A stepper. pub struct Stepper { max: f64, @@ -38,8 +37,10 @@ pub struct Stepper { wrap: bool, /// A closure that will be invoked when the value changed. value_changed: Box, + /// Keeps track of which button is currently triggered. increase_active: bool, decrease_active: bool, + timer_id: TimerToken, } impl Stepper { @@ -47,6 +48,7 @@ impl Stepper { max: f64, min: f64, step: f64, + wrap: bool, value_changed: impl Fn(&mut EventCtx, &mut f64, &Env) + 'static, ) -> impl Widget { Align::vertical( @@ -55,27 +57,38 @@ impl Stepper { max, min, step, - wrap: false, + wrap, value_changed: Box::new(value_changed), increase_active: false, decrease_active: false, + timer_id: TimerToken::INVALID, }, ) } - pub fn min(mut self, min: f64) -> Self { - self.min = min; - self - } + fn change_value(&mut self, ctx: &mut EventCtx, data: &mut f64, env: &Env) { + let delta = if self.increase_active { + self.step + } else if self.decrease_active { + -1. * self.step + } else { + 0.0 + }; - pub fn max(mut self, max: f64) -> Self { - self.max = max; - self - } + let old_data = *data; + *data = (*data + delta).min(self.min).max(self.max); - pub fn wrap(mut self, wrap: bool) -> Self { - self.wrap = wrap; - self + if old_data != *data { + (self.value_changed)(ctx, data, env); + } else { + if self.wrap { + if *data == self.min { + *data = self.max + } else { + *data = self.min + } + } + } } } @@ -85,8 +98,13 @@ impl Widget for Stepper { RoundedRect::from_origin_size(Point::ORIGIN, base_state.size().to_vec2(), 4.); let height = base_state.size().height; - let button_size = Size::new(STEPPER_WIDTH, height / 2.); + let width = env.get(theme::BASIC_WIDGET_HEIGHT); + let button_size = Size::new(width, height / 2.); + paint_ctx.stroke(rounded_rect, &env.get(theme::BORDER), 2.0); + paint_ctx.clip(rounded_rect); + + // draw buttons for increase/decrease let mut increase_button_origin = Point::ORIGIN; let mut decrease_button_origin = Point::ORIGIN; decrease_button_origin.y += height / 2.; @@ -106,9 +124,7 @@ impl Widget for Stepper { (env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)), ); - paint_ctx.stroke(rounded_rect, &env.get(theme::BORDER), 2.0); - paint_ctx.clip(rounded_rect); - + // draw buttons that are currently triggered as active if self.increase_active { paint_ctx.fill(increase_rect, &active_gradient); } else { @@ -121,17 +137,18 @@ impl Widget for Stepper { paint_ctx.fill(decrease_rect, &inactive_gradient); }; + // draw up and down triangles let mut increase_arrow = BezPath::new(); increase_arrow.move_to(Point::new(4., height / 2. - 4.)); - increase_arrow.line_to(Point::new(STEPPER_WIDTH - 4., height / 2. - 4.)); - increase_arrow.line_to(Point::new(STEPPER_WIDTH / 2., 4.)); + increase_arrow.line_to(Point::new(width - 4., height / 2. - 4.)); + increase_arrow.line_to(Point::new(width / 2., 4.)); increase_arrow.close_path(); paint_ctx.fill(increase_arrow, &env.get(theme::LABEL_COLOR)); let mut decrease_arrow = BezPath::new(); decrease_arrow.move_to(Point::new(4., height / 2. + 4.)); - decrease_arrow.line_to(Point::new(STEPPER_WIDTH - 4., height / 2. + 4.)); - decrease_arrow.line_to(Point::new(STEPPER_WIDTH / 2., height - 4.)); + decrease_arrow.line_to(Point::new(width - 4., height / 2. + 4.)); + decrease_arrow.line_to(Point::new(width / 2., height - 4.)); decrease_arrow.close_path(); paint_ctx.fill(decrease_arrow, &env.get(theme::LABEL_COLOR)); } @@ -144,7 +161,7 @@ impl Widget for Stepper { env: &Env, ) -> Size { bc.constrain(Size::new( - STEPPER_WIDTH, + env.get(theme::BASIC_WIDGET_HEIGHT), env.get(theme::BORDERED_WIDGET_HEIGHT), )) } @@ -162,7 +179,10 @@ impl Widget for Stepper { self.increase_active = true; } - // todo: increase/decrease value + self.change_value(ctx, data, env); + + let delay = Instant::now() + Duration::from_millis(500); + self.timer_id = ctx.request_timer(delay); ctx.invalidate(); } @@ -171,9 +191,15 @@ impl Widget for Stepper { self.decrease_active = false; self.increase_active = false; + self.timer_id = TimerToken::INVALID; ctx.invalidate(); } + Event::Timer(id) if *id == self.timer_id => { + self.change_value(ctx, data, env); + let delay = Instant::now() + Duration::from_millis(200); + self.timer_id = ctx.request_timer(delay); + } _ => (), } } From 25940344e228c91443efcf0243cde0b1ddbde37c Mon Sep 17 00:00:00 2001 From: Anna Scholtz Date: Fri, 15 Nov 2019 10:17:44 -0800 Subject: [PATCH 3/7] Cleanup stepper widget --- druid/examples/switch.rs | 5 ++- druid/src/widget/stepper.rs | 66 ++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/druid/examples/switch.rs b/druid/examples/switch.rs index 07f4d6d373..23e62d4f60 100644 --- a/druid/examples/switch.rs +++ b/druid/examples/switch.rs @@ -30,16 +30,15 @@ fn build_widget() -> impl Widget { row.add_child(Padding::new(5.0, switch_label), 0.0); row.add_child(Padding::new(5.0, switch), 0.0); - let label_stepper = LensWrap::new( - Stepper::new(0.0, 10.0, 1.0, true, |_ctx, _data, _env| {}), + Stepper::new(0.0, 10.0, 0.25, true, |_ctx, _data, _env| {}), lenses::demo_state::stepper_value, ); let mut stepper_row = Row::new(); let label = DynLabel::new(|data: &DemoState, _env| { - format!("Stepper value: {0:.0}", data.stepper_value) + format!("Stepper value: {0:.2}", data.stepper_value) }); stepper_row.add_child(Padding::new(5.0, label), 0.0); diff --git a/druid/src/widget/stepper.rs b/druid/src/widget/stepper.rs index 753d281d0a..5def100cb7 100644 --- a/druid/src/widget/stepper.rs +++ b/druid/src/widget/stepper.rs @@ -15,21 +15,19 @@ //! A stepper widget. use crate::{ - BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, TimerToken, + BaseState, BoxConstraints, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, TimerToken, UpdateCtx, Widget, }; use std::time::{Duration, Instant}; use crate::kurbo::{BezPath, Rect, RoundedRect}; -use crate::piet::{ - FontBuilder, LinearGradient, RenderContext, Text, TextLayout, TextLayoutBuilder, UnitPoint, -}; +use crate::piet::{LinearGradient, RenderContext, UnitPoint}; use crate::theme; -use crate::widget::{Align, Label, LabelText, SizedBox}; +use crate::widget::Align; use crate::Point; -/// A stepper. +/// A stepper widget for step-wise increasing and decreasing a value. pub struct Stepper { max: f64, min: f64, @@ -67,6 +65,7 @@ impl Stepper { } fn change_value(&mut self, ctx: &mut EventCtx, data: &mut f64, env: &Env) { + // increase/decrease value depending on which button is currently active let delta = if self.increase_active { self.step } else if self.decrease_active { @@ -79,21 +78,20 @@ impl Stepper { *data = (*data + delta).min(self.min).max(self.max); if old_data != *data { + // callback (self.value_changed)(ctx, data, env); - } else { - if self.wrap { - if *data == self.min { - *data = self.max - } else { - *data = self.min - } + } else if self.wrap { + if *data == self.min { + *data = self.max + } else { + *data = self.min } } } } impl Widget for Stepper { - fn paint(&mut self, paint_ctx: &mut PaintCtx, base_state: &BaseState, data: &f64, env: &Env) { + fn paint(&mut self, paint_ctx: &mut PaintCtx, base_state: &BaseState, _data: &f64, env: &Env) { let rounded_rect = RoundedRect::from_origin_size(Point::ORIGIN, base_state.size().to_vec2(), 4.); @@ -105,12 +103,12 @@ impl Widget for Stepper { paint_ctx.clip(rounded_rect); // draw buttons for increase/decrease - let mut increase_button_origin = Point::ORIGIN; + let increase_button_origin = Point::ORIGIN; let mut decrease_button_origin = Point::ORIGIN; decrease_button_origin.y += height / 2.; - let increase_rect = Rect::from_origin_size(increase_button_origin, button_size); - let decrease_rect = Rect::from_origin_size(decrease_button_origin, button_size); + let increase_button_rect = Rect::from_origin_size(increase_button_origin, button_size); + let decrease_button_rect = Rect::from_origin_size(decrease_button_origin, button_size); let active_gradient = LinearGradient::new( UnitPoint::TOP, @@ -126,31 +124,31 @@ impl Widget for Stepper { // draw buttons that are currently triggered as active if self.increase_active { - paint_ctx.fill(increase_rect, &active_gradient); + paint_ctx.fill(increase_button_rect, &active_gradient); } else { - paint_ctx.fill(increase_rect, &inactive_gradient); + paint_ctx.fill(increase_button_rect, &inactive_gradient); }; if self.decrease_active { - paint_ctx.fill(decrease_rect, &active_gradient); + paint_ctx.fill(decrease_button_rect, &active_gradient); } else { - paint_ctx.fill(decrease_rect, &inactive_gradient); + paint_ctx.fill(decrease_button_rect, &inactive_gradient); }; // draw up and down triangles - let mut increase_arrow = BezPath::new(); - increase_arrow.move_to(Point::new(4., height / 2. - 4.)); - increase_arrow.line_to(Point::new(width - 4., height / 2. - 4.)); - increase_arrow.line_to(Point::new(width / 2., 4.)); - increase_arrow.close_path(); - paint_ctx.fill(increase_arrow, &env.get(theme::LABEL_COLOR)); - - let mut decrease_arrow = BezPath::new(); - decrease_arrow.move_to(Point::new(4., height / 2. + 4.)); - decrease_arrow.line_to(Point::new(width - 4., height / 2. + 4.)); - decrease_arrow.line_to(Point::new(width / 2., height - 4.)); - decrease_arrow.close_path(); - paint_ctx.fill(decrease_arrow, &env.get(theme::LABEL_COLOR)); + let mut increase_button_arrow = BezPath::new(); + increase_button_arrow.move_to(Point::new(4., height / 2. - 4.)); + increase_button_arrow.line_to(Point::new(width - 4., height / 2. - 4.)); + increase_button_arrow.line_to(Point::new(width / 2., 4.)); + increase_button_arrow.close_path(); + paint_ctx.fill(increase_button_arrow, &env.get(theme::LABEL_COLOR)); + + let mut decrease_button_arrow = BezPath::new(); + decrease_button_arrow.move_to(Point::new(4., height / 2. + 4.)); + decrease_button_arrow.line_to(Point::new(width - 4., height / 2. + 4.)); + decrease_button_arrow.line_to(Point::new(width / 2., height - 4.)); + decrease_button_arrow.close_path(); + paint_ctx.fill(decrease_button_arrow, &env.get(theme::LABEL_COLOR)); } fn layout( From 3b64ecf6d33d36da1e84a70062fc48bf2ab004d7 Mon Sep 17 00:00:00 2001 From: Anna Scholtz Date: Thu, 2 Jan 2020 19:12:47 -0800 Subject: [PATCH 4/7] Add example for connecting stepper widget with textbox --- druid/examples/switch.rs | 26 +++++++++++++++++--------- druid/src/widget/mod.rs | 2 ++ druid/src/widget/stepper.rs | 16 ++++++++-------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/druid/examples/switch.rs b/druid/examples/switch.rs index 23e62d4f60..be902ec1ac 100644 --- a/druid/examples/switch.rs +++ b/druid/examples/switch.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use druid::widget::{Flex, Switch, DynLabel, Label, Padding, Row, Stepper, Switch}; -use druid::{AppLauncher, Data, Lens, LensWrap, Widget, WindowDesc}; +use druid::widget::{Flex, Label, Padding, Parse, Stepper, Switch, TextBox}; +use druid::{AppLauncher, Data, Lens, LensExt, LensWrap, Widget, WindowDesc}; #[derive(Clone, Data, Lens)] struct DemoState { @@ -30,22 +30,30 @@ fn build_widget() -> impl Widget { row.add_child(Padding::new(5.0, switch_label), 0.0); row.add_child(Padding::new(5.0, switch), 0.0); - let label_stepper = LensWrap::new( + let stepper = LensWrap::new( Stepper::new(0.0, 10.0, 0.25, true, |_ctx, _data, _env| {}), - lenses::demo_state::stepper_value, + DemoState::stepper_value, ); - let mut stepper_row = Row::new(); + let mut textbox_row = Flex::row(); + let textbox = LensWrap::new( + Parse::new(TextBox::new()), + DemoState::stepper_value.map(|x| Some(*x), |x, y| *x = y.unwrap_or(0.0)), + ); + textbox_row.add_child(Padding::new(5.0, textbox), 0.0); + textbox_row.add_child(Padding::new(5.0, stepper), 0.0); + + let mut label_row = Flex::row(); - let label = DynLabel::new(|data: &DemoState, _env| { + let label = Label::new(|data: &DemoState, _env: &_| { format!("Stepper value: {0:.2}", data.stepper_value) }); - stepper_row.add_child(Padding::new(5.0, label), 0.0); - stepper_row.add_child(Padding::new(5.0, label_stepper), 0.0); + label_row.add_child(Padding::new(5.0, label), 0.0); col.add_child(Padding::new(5.0, row), 1.0); - col.add_child(Padding::new(5.0, stepper_row), 1.0); + col.add_child(Padding::new(5.0, textbox_row), 1.0); + col.add_child(Padding::new(5.0, label_row), 1.0); col } diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index 3eb5a831d5..e046bae951 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -31,6 +31,7 @@ mod scroll; mod sized_box; mod slider; mod split; +mod stepper; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] mod svg; @@ -55,6 +56,7 @@ pub use scroll::Scroll; pub use sized_box::SizedBox; pub use slider::Slider; pub use split::Split; +pub use stepper::Stepper; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] pub use svg::{Svg, SvgData}; diff --git a/druid/src/widget/stepper.rs b/druid/src/widget/stepper.rs index 5def100cb7..06b3f481eb 100644 --- a/druid/src/widget/stepper.rs +++ b/druid/src/widget/stepper.rs @@ -15,9 +15,9 @@ //! A stepper widget. use crate::{ - BaseState, BoxConstraints, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, TimerToken, - UpdateCtx, Widget, + BoxConstraints, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, TimerToken, UpdateCtx, Widget, }; +use std::f64::EPSILON; use std::time::{Duration, Instant}; use crate::kurbo::{BezPath, Rect, RoundedRect}; @@ -77,11 +77,11 @@ impl Stepper { let old_data = *data; *data = (*data + delta).min(self.min).max(self.max); - if old_data != *data { + if (*data - old_data).abs() > EPSILON { // callback (self.value_changed)(ctx, data, env); } else if self.wrap { - if *data == self.min { + if (*data - self.min).abs() < EPSILON { *data = self.max } else { *data = self.min @@ -91,11 +91,11 @@ impl Stepper { } impl Widget for Stepper { - fn paint(&mut self, paint_ctx: &mut PaintCtx, base_state: &BaseState, _data: &f64, env: &Env) { + fn paint(&mut self, paint_ctx: &mut PaintCtx, _data: &f64, env: &Env) { let rounded_rect = - RoundedRect::from_origin_size(Point::ORIGIN, base_state.size().to_vec2(), 4.); + RoundedRect::from_origin_size(Point::ORIGIN, paint_ctx.size().to_vec2(), 4.); - let height = base_state.size().height; + let height = paint_ctx.size().height; let width = env.get(theme::BASIC_WIDGET_HEIGHT); let button_size = Size::new(width, height / 2.); @@ -164,7 +164,7 @@ impl Widget for Stepper { )) } - fn event(&mut self, event: &Event, ctx: &mut EventCtx, data: &mut f64, env: &Env) { + fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut f64, env: &Env) { let height = env.get(theme::BORDERED_WIDGET_HEIGHT); match event { From 879736560a02e622ac7bee0e3b22edec485d50eb Mon Sep 17 00:00:00 2001 From: Anna Scholtz Date: Sat, 11 Jan 2020 18:23:45 -0800 Subject: [PATCH 5/7] Incroporate review feedback into stepper widget --- druid/examples/switch.rs | 10 ++-- druid/src/widget/stepper.rs | 97 ++++++++++++++++++++++--------------- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/druid/examples/switch.rs b/druid/examples/switch.rs index be902ec1ac..6c9c236cd4 100644 --- a/druid/examples/switch.rs +++ b/druid/examples/switch.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use druid::widget::{Flex, Label, Padding, Parse, Stepper, Switch, TextBox}; +use druid::widget::{Align, Flex, Label, Padding, Parse, Stepper, Switch, TextBox}; use druid::{AppLauncher, Data, Lens, LensExt, LensWrap, Widget, WindowDesc}; #[derive(Clone, Data, Lens)] @@ -31,7 +31,11 @@ fn build_widget() -> impl Widget { row.add_child(Padding::new(5.0, switch), 0.0); let stepper = LensWrap::new( - Stepper::new(0.0, 10.0, 0.25, true, |_ctx, _data, _env| {}), + Stepper::new(|_ctx, _data, _env| {}) + .max(10.0) + .min(0.0) + .step(0.5) + .wrap(false), DemoState::stepper_value, ); @@ -41,7 +45,7 @@ fn build_widget() -> impl Widget { DemoState::stepper_value.map(|x| Some(*x), |x, y| *x = y.unwrap_or(0.0)), ); textbox_row.add_child(Padding::new(5.0, textbox), 0.0); - textbox_row.add_child(Padding::new(5.0, stepper), 0.0); + textbox_row.add_child(Padding::new(5.0, Align::centered(stepper)), 0.0); let mut label_row = Flex::row(); diff --git a/druid/src/widget/stepper.rs b/druid/src/widget/stepper.rs index 06b3f481eb..8260ce8853 100644 --- a/druid/src/widget/stepper.rs +++ b/druid/src/widget/stepper.rs @@ -24,9 +24,13 @@ use crate::kurbo::{BezPath, Rect, RoundedRect}; use crate::piet::{LinearGradient, RenderContext, UnitPoint}; use crate::theme; -use crate::widget::Align; use crate::Point; +// Delay until stepper starts automatically changing valued when one of the button is held down. +const STEPPER_REPEAT_DELAY: Duration = Duration::from_millis(500); +// Delay between value changes when one of the button is held down. +const STEPPER_REPEAT: Duration = Duration::from_millis(200); + /// A stepper widget for step-wise increasing and decreasing a value. pub struct Stepper { max: f64, @@ -42,26 +46,37 @@ pub struct Stepper { } impl Stepper { - pub fn new( - max: f64, - min: f64, - step: f64, - wrap: bool, - value_changed: impl Fn(&mut EventCtx, &mut f64, &Env) + 'static, - ) -> impl Widget { - Align::vertical( - UnitPoint::CENTER, - Stepper { - max, - min, - step, - wrap, - value_changed: Box::new(value_changed), - increase_active: false, - decrease_active: false, - timer_id: TimerToken::INVALID, - }, - ) + pub fn new(value_changed: impl Fn(&mut EventCtx, &mut f64, &Env) + 'static) -> Self { + Stepper { + max: std::f64::MAX, + min: std::f64::MIN, + step: 1.0, + wrap: false, + value_changed: Box::new(value_changed), + increase_active: false, + decrease_active: false, + timer_id: TimerToken::INVALID, + } + } + + pub fn max(mut self, max: f64) -> Self { + self.max = max; + self + } + + pub fn min(mut self, min: f64) -> Self { + self.min = min; + self + } + + pub fn step(mut self, step: f64) -> Self { + self.step = step; + self + } + + pub fn wrap(mut self, wrap: bool) -> Self { + self.wrap = wrap; + self } fn change_value(&mut self, ctx: &mut EventCtx, data: &mut f64, env: &Env) { @@ -75,7 +90,7 @@ impl Stepper { }; let old_data = *data; - *data = (*data + delta).min(self.min).max(self.max); + *data = (*data + delta).max(self.min).min(self.max); if (*data - old_data).abs() > EPSILON { // callback @@ -136,19 +151,18 @@ impl Widget for Stepper { }; // draw up and down triangles - let mut increase_button_arrow = BezPath::new(); - increase_button_arrow.move_to(Point::new(4., height / 2. - 4.)); - increase_button_arrow.line_to(Point::new(width - 4., height / 2. - 4.)); - increase_button_arrow.line_to(Point::new(width / 2., 4.)); - increase_button_arrow.close_path(); - paint_ctx.fill(increase_button_arrow, &env.get(theme::LABEL_COLOR)); - - let mut decrease_button_arrow = BezPath::new(); - decrease_button_arrow.move_to(Point::new(4., height / 2. + 4.)); - decrease_button_arrow.line_to(Point::new(width - 4., height / 2. + 4.)); - decrease_button_arrow.line_to(Point::new(width / 2., height - 4.)); - decrease_button_arrow.close_path(); - paint_ctx.fill(decrease_button_arrow, &env.get(theme::LABEL_COLOR)); + let mut arrows = BezPath::new(); + arrows.move_to(Point::new(4., height / 2. - 4.)); + arrows.line_to(Point::new(width - 4., height / 2. - 4.)); + arrows.line_to(Point::new(width / 2., 4.)); + arrows.close_path(); + + arrows.move_to(Point::new(4., height / 2. + 4.)); + arrows.line_to(Point::new(width - 4., height / 2. + 4.)); + arrows.line_to(Point::new(width / 2., height - 4.)); + arrows.close_path(); + + paint_ctx.fill(arrows, &env.get(theme::LABEL_COLOR)); } fn layout( @@ -179,7 +193,7 @@ impl Widget for Stepper { self.change_value(ctx, data, env); - let delay = Instant::now() + Duration::from_millis(500); + let delay = Instant::now() + STEPPER_REPEAT_DELAY; self.timer_id = ctx.request_timer(delay); ctx.invalidate(); @@ -195,14 +209,19 @@ impl Widget for Stepper { } Event::Timer(id) if *id == self.timer_id => { self.change_value(ctx, data, env); - let delay = Instant::now() + Duration::from_millis(200); + let delay = Instant::now() + STEPPER_REPEAT; self.timer_id = ctx.request_timer(delay); } _ => (), } } - fn update(&mut self, ctx: &mut UpdateCtx, _old_data: Option<&f64>, _data: &f64, _env: &Env) { - ctx.invalidate(); + fn update(&mut self, ctx: &mut UpdateCtx, old_data: Option<&f64>, data: &f64, _env: &Env) { + if old_data + .map(|old_data| (*data - old_data).abs() > EPSILON) + .unwrap_or(true) + { + ctx.invalidate(); + } } } From 4f76e71eb2001a33bd82980252498c7f5b950b6f Mon Sep 17 00:00:00 2001 From: Anna Scholtz Date: Sun, 12 Jan 2020 14:12:52 -0800 Subject: [PATCH 6/7] Adress stepper review feedback --- druid/examples/switch.rs | 10 +++------- druid/src/widget/stepper.rs | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/druid/examples/switch.rs b/druid/examples/switch.rs index 6c9c236cd4..684375d356 100644 --- a/druid/examples/switch.rs +++ b/druid/examples/switch.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use druid::widget::{Align, Flex, Label, Padding, Parse, Stepper, Switch, TextBox}; +use druid::widget::{Flex, Label, Padding, Parse, Stepper, Switch, TextBox, WidgetExt}; use druid::{AppLauncher, Data, Lens, LensExt, LensWrap, Widget, WindowDesc}; #[derive(Clone, Data, Lens)] @@ -31,11 +31,7 @@ fn build_widget() -> impl Widget { row.add_child(Padding::new(5.0, switch), 0.0); let stepper = LensWrap::new( - Stepper::new(|_ctx, _data, _env| {}) - .max(10.0) - .min(0.0) - .step(0.5) - .wrap(false), + Stepper::new().max(10.0).min(0.0).step(0.5).wrap(false), DemoState::stepper_value, ); @@ -45,7 +41,7 @@ fn build_widget() -> impl Widget { DemoState::stepper_value.map(|x| Some(*x), |x, y| *x = y.unwrap_or(0.0)), ); textbox_row.add_child(Padding::new(5.0, textbox), 0.0); - textbox_row.add_child(Padding::new(5.0, Align::centered(stepper)), 0.0); + textbox_row.add_child(Padding::new(5.0, stepper.center().padding(5.0)), 0.0); let mut label_row = Flex::row(); diff --git a/druid/src/widget/stepper.rs b/druid/src/widget/stepper.rs index 8260ce8853..7f554b6ea7 100644 --- a/druid/src/widget/stepper.rs +++ b/druid/src/widget/stepper.rs @@ -37,8 +37,6 @@ pub struct Stepper { min: f64, step: f64, wrap: bool, - /// A closure that will be invoked when the value changed. - value_changed: Box, /// Keeps track of which button is currently triggered. increase_active: bool, decrease_active: bool, @@ -46,40 +44,43 @@ pub struct Stepper { } impl Stepper { - pub fn new(value_changed: impl Fn(&mut EventCtx, &mut f64, &Env) + 'static) -> Self { + pub fn new() -> Self { Stepper { max: std::f64::MAX, min: std::f64::MIN, step: 1.0, wrap: false, - value_changed: Box::new(value_changed), increase_active: false, decrease_active: false, timer_id: TimerToken::INVALID, } } + /// Set the stepper's maximum value. pub fn max(mut self, max: f64) -> Self { self.max = max; self } + /// Set the stepper's minimum value. pub fn min(mut self, min: f64) -> Self { self.min = min; self } + /// Set the steppers amount by which the value increases or decreases. pub fn step(mut self, step: f64) -> Self { self.step = step; self } + /// Set whether the stepper should wrap around the minimum/maximum values. pub fn wrap(mut self, wrap: bool) -> Self { self.wrap = wrap; self } - fn change_value(&mut self, ctx: &mut EventCtx, data: &mut f64, env: &Env) { + fn change_value(&mut self, _ctx: &mut EventCtx, data: &mut f64, _env: &Env) { // increase/decrease value depending on which button is currently active let delta = if self.increase_active { self.step @@ -89,13 +90,9 @@ impl Stepper { 0.0 }; - let old_data = *data; *data = (*data + delta).max(self.min).min(self.max); - if (*data - old_data).abs() > EPSILON { - // callback - (self.value_changed)(ctx, data, env); - } else if self.wrap { + if self.wrap { if (*data - self.min).abs() < EPSILON { *data = self.max } else { @@ -105,6 +102,12 @@ impl Stepper { } } +impl Default for Stepper { + fn default() -> Self { + Self::new() + } +} + impl Widget for Stepper { fn paint(&mut self, paint_ctx: &mut PaintCtx, _data: &f64, env: &Env) { let rounded_rect = From 48759af71fccf8c3a406e1797ca5874f95a761bc Mon Sep 17 00:00:00 2001 From: Anna Scholtz Date: Sun, 12 Jan 2020 15:19:52 -0800 Subject: [PATCH 7/7] Single padding widget in stepper example --- druid/examples/switch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druid/examples/switch.rs b/druid/examples/switch.rs index 684375d356..e2ad8e82e1 100644 --- a/druid/examples/switch.rs +++ b/druid/examples/switch.rs @@ -41,7 +41,7 @@ fn build_widget() -> impl Widget { DemoState::stepper_value.map(|x| Some(*x), |x, y| *x = y.unwrap_or(0.0)), ); textbox_row.add_child(Padding::new(5.0, textbox), 0.0); - textbox_row.add_child(Padding::new(5.0, stepper.center().padding(5.0)), 0.0); + textbox_row.add_child(Padding::new(5.0, stepper.center()), 0.0); let mut label_row = Flex::row();