Skip to content

Commit 5731f1c

Browse files
committed
Increase/decrease value with stepper widget
1 parent ca6b5e6 commit 5731f1c

File tree

2 files changed

+66
-30
lines changed

2 files changed

+66
-30
lines changed

druid/examples/switch.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use druid::widget::{Column, Label, Padding, Row, Stepper, Switch};
15+
use druid::widget::{Column, DynLabel, Label, Padding, Row, Stepper, Switch};
1616
use druid::{AppLauncher, Data, Lens, LensWrap, Widget, WindowDesc};
1717

1818
#[derive(Clone, Data, Lens)]
@@ -30,13 +30,23 @@ fn build_widget() -> impl Widget<DemoState> {
3030
row.add_child(Padding::new(5.0, switch_label), 0.0);
3131
row.add_child(Padding::new(5.0, switch), 0.0);
3232

33-
let stepper = LensWrap::new(
34-
Stepper::new(0.0, 10.0, 1.0, |_ctx, _data, _env| eprintln!("---")),
33+
34+
let label_stepper = LensWrap::new(
35+
Stepper::new(0.0, 10.0, 1.0, true, |_ctx, _data, _env| {}),
3536
lenses::demo_state::stepper_value,
3637
);
37-
row.add_child(Padding::new(5.0, stepper), 0.0);
38+
39+
let mut stepper_row = Row::new();
40+
41+
let label = DynLabel::new(|data: &DemoState, _env| {
42+
format!("Stepper value: {0:.0}", data.stepper_value)
43+
});
44+
45+
stepper_row.add_child(Padding::new(5.0, label), 0.0);
46+
stepper_row.add_child(Padding::new(5.0, label_stepper), 0.0);
3847

3948
col.add_child(Padding::new(5.0, row), 1.0);
49+
col.add_child(Padding::new(5.0, stepper_row), 1.0);
4050
col
4151
}
4252

druid/src/widget/stepper.rs

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
//! A stepper widget.
1616
1717
use crate::{
18-
BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, UpdateCtx,
19-
Widget,
18+
BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, Size, TimerToken,
19+
UpdateCtx, Widget,
2020
};
21+
use std::time::{Duration, Instant};
2122

2223
use crate::kurbo::{BezPath, Rect, RoundedRect};
2324
use crate::piet::{
@@ -28,8 +29,6 @@ use crate::theme;
2829
use crate::widget::{Align, Label, LabelText, SizedBox};
2930
use crate::Point;
3031

31-
const STEPPER_WIDTH: f64 = 15.;
32-
3332
/// A stepper.
3433
pub struct Stepper {
3534
max: f64,
@@ -38,15 +37,18 @@ pub struct Stepper {
3837
wrap: bool,
3938
/// A closure that will be invoked when the value changed.
4039
value_changed: Box<dyn Fn(&mut EventCtx, &mut f64, &Env)>,
40+
/// Keeps track of which button is currently triggered.
4141
increase_active: bool,
4242
decrease_active: bool,
43+
timer_id: TimerToken,
4344
}
4445

4546
impl Stepper {
4647
pub fn new(
4748
max: f64,
4849
min: f64,
4950
step: f64,
51+
wrap: bool,
5052
value_changed: impl Fn(&mut EventCtx, &mut f64, &Env) + 'static,
5153
) -> impl Widget<f64> {
5254
Align::vertical(
@@ -55,27 +57,38 @@ impl Stepper {
5557
max,
5658
min,
5759
step,
58-
wrap: false,
60+
wrap,
5961
value_changed: Box::new(value_changed),
6062
increase_active: false,
6163
decrease_active: false,
64+
timer_id: TimerToken::INVALID,
6265
},
6366
)
6467
}
6568

66-
pub fn min(mut self, min: f64) -> Self {
67-
self.min = min;
68-
self
69-
}
69+
fn change_value(&mut self, ctx: &mut EventCtx, data: &mut f64, env: &Env) {
70+
let delta = if self.increase_active {
71+
self.step
72+
} else if self.decrease_active {
73+
-1. * self.step
74+
} else {
75+
0.0
76+
};
7077

71-
pub fn max(mut self, max: f64) -> Self {
72-
self.max = max;
73-
self
74-
}
78+
let old_data = *data;
79+
*data = (*data + delta).min(self.min).max(self.max);
7580

76-
pub fn wrap(mut self, wrap: bool) -> Self {
77-
self.wrap = wrap;
78-
self
81+
if old_data != *data {
82+
(self.value_changed)(ctx, data, env);
83+
} else {
84+
if self.wrap {
85+
if *data == self.min {
86+
*data = self.max
87+
} else {
88+
*data = self.min
89+
}
90+
}
91+
}
7992
}
8093
}
8194

@@ -85,8 +98,13 @@ impl Widget<f64> for Stepper {
8598
RoundedRect::from_origin_size(Point::ORIGIN, base_state.size().to_vec2(), 4.);
8699

87100
let height = base_state.size().height;
88-
let button_size = Size::new(STEPPER_WIDTH, height / 2.);
101+
let width = env.get(theme::BASIC_WIDGET_HEIGHT);
102+
let button_size = Size::new(width, height / 2.);
89103

104+
paint_ctx.stroke(rounded_rect, &env.get(theme::BORDER), 2.0);
105+
paint_ctx.clip(rounded_rect);
106+
107+
// draw buttons for increase/decrease
90108
let mut increase_button_origin = Point::ORIGIN;
91109
let mut decrease_button_origin = Point::ORIGIN;
92110
decrease_button_origin.y += height / 2.;
@@ -106,9 +124,7 @@ impl Widget<f64> for Stepper {
106124
(env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)),
107125
);
108126

109-
paint_ctx.stroke(rounded_rect, &env.get(theme::BORDER), 2.0);
110-
paint_ctx.clip(rounded_rect);
111-
127+
// draw buttons that are currently triggered as active
112128
if self.increase_active {
113129
paint_ctx.fill(increase_rect, &active_gradient);
114130
} else {
@@ -121,17 +137,18 @@ impl Widget<f64> for Stepper {
121137
paint_ctx.fill(decrease_rect, &inactive_gradient);
122138
};
123139

140+
// draw up and down triangles
124141
let mut increase_arrow = BezPath::new();
125142
increase_arrow.move_to(Point::new(4., height / 2. - 4.));
126-
increase_arrow.line_to(Point::new(STEPPER_WIDTH - 4., height / 2. - 4.));
127-
increase_arrow.line_to(Point::new(STEPPER_WIDTH / 2., 4.));
143+
increase_arrow.line_to(Point::new(width - 4., height / 2. - 4.));
144+
increase_arrow.line_to(Point::new(width / 2., 4.));
128145
increase_arrow.close_path();
129146
paint_ctx.fill(increase_arrow, &env.get(theme::LABEL_COLOR));
130147

131148
let mut decrease_arrow = BezPath::new();
132149
decrease_arrow.move_to(Point::new(4., height / 2. + 4.));
133-
decrease_arrow.line_to(Point::new(STEPPER_WIDTH - 4., height / 2. + 4.));
134-
decrease_arrow.line_to(Point::new(STEPPER_WIDTH / 2., height - 4.));
150+
decrease_arrow.line_to(Point::new(width - 4., height / 2. + 4.));
151+
decrease_arrow.line_to(Point::new(width / 2., height - 4.));
135152
decrease_arrow.close_path();
136153
paint_ctx.fill(decrease_arrow, &env.get(theme::LABEL_COLOR));
137154
}
@@ -144,7 +161,7 @@ impl Widget<f64> for Stepper {
144161
env: &Env,
145162
) -> Size {
146163
bc.constrain(Size::new(
147-
STEPPER_WIDTH,
164+
env.get(theme::BASIC_WIDGET_HEIGHT),
148165
env.get(theme::BORDERED_WIDGET_HEIGHT),
149166
))
150167
}
@@ -162,7 +179,10 @@ impl Widget<f64> for Stepper {
162179
self.increase_active = true;
163180
}
164181

165-
// todo: increase/decrease value
182+
self.change_value(ctx, data, env);
183+
184+
let delay = Instant::now() + Duration::from_millis(500);
185+
self.timer_id = ctx.request_timer(delay);
166186

167187
ctx.invalidate();
168188
}
@@ -171,9 +191,15 @@ impl Widget<f64> for Stepper {
171191

172192
self.decrease_active = false;
173193
self.increase_active = false;
194+
self.timer_id = TimerToken::INVALID;
174195

175196
ctx.invalidate();
176197
}
198+
Event::Timer(id) if *id == self.timer_id => {
199+
self.change_value(ctx, data, env);
200+
let delay = Instant::now() + Duration::from_millis(200);
201+
self.timer_id = ctx.request_timer(delay);
202+
}
177203
_ => (),
178204
}
179205
}

0 commit comments

Comments
 (0)