1use crate::{
2 border::BorderBuilder,
3 brush::{Brush, GradientPoint},
4 button::{ButtonBuilder, ButtonMessage},
5 canvas::CanvasBuilder,
6 core::{
7 algebra::Vector2,
8 color::Color,
9 math::{self},
10 pool::Handle,
11 },
12 decorator::DecoratorBuilder,
13 define_constructor,
14 grid::{Column, GridBuilder, Row},
15 message::{MessageDirection, UiMessage},
16 text::{TextBuilder, TextMessage},
17 utils::{make_arrow, ArrowDirection},
18 widget::{Widget, WidgetBuilder, WidgetMessage},
19 BuildContext, Control, HorizontalAlignment, NodeHandleMapping, Orientation, Thickness, UiNode,
20 UserInterface, VerticalAlignment, BRUSH_LIGHT, BRUSH_LIGHTER, BRUSH_LIGHTEST, COLOR_DARKEST,
21 COLOR_LIGHTEST,
22};
23use std::{
24 any::{Any, TypeId},
25 ops::{Deref, DerefMut},
26};
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum ScrollBarMessage {
30 Value(f32),
31 MinValue(f32),
32 MaxValue(f32),
33}
34
35impl ScrollBarMessage {
36 define_constructor!(ScrollBarMessage:Value => fn value(f32), layout: false);
37 define_constructor!(ScrollBarMessage:MaxValue => fn max_value(f32), layout: false);
38 define_constructor!(ScrollBarMessage:MinValue => fn min_value(f32), layout: false);
39}
40
41#[derive(Clone)]
42pub struct ScrollBar {
43 pub widget: Widget,
44 pub min: f32,
45 pub max: f32,
46 pub value: f32,
47 pub step: f32,
48 pub orientation: Orientation,
49 pub is_dragging: bool,
50 pub offset: Vector2<f32>,
51 pub increase: Handle<UiNode>,
52 pub decrease: Handle<UiNode>,
53 pub indicator: Handle<UiNode>,
54 pub field: Handle<UiNode>,
55 pub value_text: Handle<UiNode>,
56 pub value_precision: usize,
57}
58
59crate::define_widget_deref!(ScrollBar);
60
61impl Control for ScrollBar {
62 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
63 if type_id == TypeId::of::<Self>() {
64 Some(self)
65 } else {
66 None
67 }
68 }
69
70 fn resolve(&mut self, node_map: &NodeHandleMapping) {
71 node_map.resolve(&mut self.increase);
72 node_map.resolve(&mut self.decrease);
73 node_map.resolve(&mut self.indicator);
74 if self.value_text.is_some() {
75 node_map.resolve(&mut self.value_text);
76 }
77 node_map.resolve(&mut self.field);
78 }
79
80 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
81 let size = self.widget.arrange_override(ui, final_size);
82
83 let percent = (self.value - self.min) / (self.max - self.min);
85
86 let field_size = ui.node(self.field).actual_size();
87
88 let indicator = ui.node(self.indicator);
89 match self.orientation {
90 Orientation::Horizontal => {
91 ui.send_message(WidgetMessage::height(
92 self.indicator,
93 MessageDirection::ToWidget,
94 field_size.y,
95 ));
96 ui.send_message(WidgetMessage::width(
97 self.decrease,
98 MessageDirection::ToWidget,
99 field_size.y,
100 ));
101 ui.send_message(WidgetMessage::width(
102 self.increase,
103 MessageDirection::ToWidget,
104 field_size.y,
105 ));
106
107 let position = Vector2::new(
108 percent * (field_size.x - indicator.actual_size().x).max(0.0),
109 0.0,
110 );
111 ui.send_message(WidgetMessage::desired_position(
112 self.indicator,
113 MessageDirection::ToWidget,
114 position,
115 ));
116 }
117 Orientation::Vertical => {
118 ui.send_message(WidgetMessage::width(
119 self.indicator,
120 MessageDirection::ToWidget,
121 field_size.x,
122 ));
123 ui.send_message(WidgetMessage::height(
124 self.decrease,
125 MessageDirection::ToWidget,
126 field_size.x,
127 ));
128 ui.send_message(WidgetMessage::height(
129 self.increase,
130 MessageDirection::ToWidget,
131 field_size.x,
132 ));
133
134 let position = Vector2::new(
135 0.0,
136 percent * (field_size.y - indicator.actual_size().y).max(0.0),
137 );
138 ui.send_message(WidgetMessage::desired_position(
139 self.indicator,
140 MessageDirection::ToWidget,
141 position,
142 ));
143 }
144 }
145
146 size
147 }
148
149 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
150 self.widget.handle_routed_message(ui, message);
151
152 if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
153 if message.destination() == self.increase {
154 ui.send_message(ScrollBarMessage::value(
155 self.handle(),
156 MessageDirection::ToWidget,
157 self.value + self.step,
158 ));
159 } else if message.destination() == self.decrease {
160 ui.send_message(ScrollBarMessage::value(
161 self.handle(),
162 MessageDirection::ToWidget,
163 self.value - self.step,
164 ));
165 }
166 } else if let Some(msg) = message.data::<ScrollBarMessage>() {
167 if message.destination() == self.handle()
168 && message.direction() == MessageDirection::ToWidget
169 {
170 match *msg {
171 ScrollBarMessage::Value(value) => {
172 let old_value = self.value;
173 let new_value = math::clampf(value, self.min, self.max);
174 if (new_value - old_value).abs() > f32::EPSILON {
175 self.value = new_value;
176 self.invalidate_arrange();
177
178 if self.value_text.is_some() {
179 ui.send_message(TextMessage::text(
180 self.value_text,
181 MessageDirection::ToWidget,
182 format!("{:.1$}", value, self.value_precision),
183 ));
184 }
185
186 let response = ScrollBarMessage::value(
187 self.handle,
188 MessageDirection::FromWidget,
189 self.value,
190 );
191 response.set_handled(message.handled());
192 ui.send_message(response);
193 }
194 }
195 ScrollBarMessage::MinValue(min) => {
196 if self.min != min {
197 self.min = min;
198 if self.min > self.max {
199 std::mem::swap(&mut self.min, &mut self.max);
200 }
201 let old_value = self.value;
202 let new_value = math::clampf(self.value, self.min, self.max);
203 if (new_value - old_value).abs() > f32::EPSILON {
204 ui.send_message(ScrollBarMessage::value(
205 self.handle(),
206 MessageDirection::ToWidget,
207 new_value,
208 ));
209 }
210
211 let response = ScrollBarMessage::min_value(
212 self.handle,
213 MessageDirection::FromWidget,
214 self.min,
215 );
216 response.set_handled(message.handled());
217 ui.send_message(response);
218 }
219 }
220 ScrollBarMessage::MaxValue(max) => {
221 if self.max != max {
222 self.max = max;
223 if self.max < self.min {
224 std::mem::swap(&mut self.min, &mut self.max);
225 }
226 let old_value = self.value;
227 let value = math::clampf(self.value, self.min, self.max);
228 if (value - old_value).abs() > f32::EPSILON {
229 ui.send_message(ScrollBarMessage::value(
230 self.handle(),
231 MessageDirection::ToWidget,
232 value,
233 ));
234 }
235
236 let response = ScrollBarMessage::max_value(
237 self.handle,
238 MessageDirection::FromWidget,
239 self.max,
240 );
241 response.set_handled(message.handled());
242 ui.send_message(response);
243 }
244 }
245 }
246 }
247 } else if let Some(msg) = message.data::<WidgetMessage>() {
248 if message.destination() == self.indicator {
249 match msg {
250 WidgetMessage::MouseDown { pos, .. } => {
251 if self.indicator.is_some() {
252 let indicator_pos = ui.nodes.borrow(self.indicator).screen_position;
253 self.is_dragging = true;
254 self.offset = indicator_pos - *pos;
255 ui.capture_mouse(self.indicator);
256 message.set_handled(true);
257 }
258 }
259 WidgetMessage::MouseUp { .. } => {
260 self.is_dragging = false;
261 ui.release_mouse_capture();
262 message.set_handled(true);
263 }
264 WidgetMessage::MouseMove { pos: mouse_pos, .. } => {
265 if self.indicator.is_some() {
266 let canvas =
267 ui.borrow_by_name_up(self.indicator, ScrollBar::PART_CANVAS);
268 let indicator_size = ui.nodes.borrow(self.indicator).actual_size();
269 if self.is_dragging {
270 let percent = match self.orientation {
271 Orientation::Horizontal => {
272 let span = canvas.actual_size().x - indicator_size.x;
273 let offset =
274 mouse_pos.x - canvas.screen_position.x + self.offset.x;
275 if span > 0.0 {
276 math::clampf(offset / span, 0.0, 1.0)
277 } else {
278 0.0
279 }
280 }
281 Orientation::Vertical => {
282 let span = canvas.actual_size().y - indicator_size.y;
283 let offset =
284 mouse_pos.y - canvas.screen_position.y + self.offset.y;
285 if span > 0.0 {
286 math::clampf(offset / span, 0.0, 1.0)
287 } else {
288 0.0
289 }
290 }
291 };
292 ui.send_message(ScrollBarMessage::value(
293 self.handle(),
294 MessageDirection::ToWidget,
295 self.min + percent * (self.max - self.min),
296 ));
297 message.set_handled(true);
298 }
299 }
300 }
301 _ => (),
302 }
303 }
304 }
305 }
306}
307
308impl ScrollBar {
309 pub const PART_CANVAS: &'static str = "PART_Canvas";
310
311 pub fn new(
312 widget: Widget,
313 increase: Handle<UiNode>,
314 decrease: Handle<UiNode>,
315 indicator: Handle<UiNode>,
316 field: Handle<UiNode>,
317 value_text: Handle<UiNode>,
318 ) -> Self {
319 Self {
320 widget,
321 min: 0.0,
322 max: 100.0,
323 value: 0.0,
324 step: 1.0,
325 orientation: Orientation::Vertical,
326 is_dragging: false,
327 offset: Default::default(),
328 increase,
329 decrease,
330 indicator,
331 field,
332 value_text,
333 value_precision: 3,
334 }
335 }
336
337 pub fn value(&self) -> f32 {
338 self.value
339 }
340
341 pub fn max_value(&self) -> f32 {
342 self.max
343 }
344
345 pub fn min_value(&self) -> f32 {
346 self.min
347 }
348
349 pub fn set_step(&mut self, step: f32) -> &mut Self {
350 self.step = step;
351 self
352 }
353
354 pub fn step(&self) -> f32 {
355 self.step
356 }
357}
358
359pub struct ScrollBarBuilder {
360 widget_builder: WidgetBuilder,
361 min: Option<f32>,
362 max: Option<f32>,
363 value: Option<f32>,
364 step: Option<f32>,
365 orientation: Option<Orientation>,
366 increase: Option<Handle<UiNode>>,
367 decrease: Option<Handle<UiNode>>,
368 indicator: Option<Handle<UiNode>>,
369 body: Option<Handle<UiNode>>,
370 show_value: bool,
371 value_precision: usize,
372}
373
374impl ScrollBarBuilder {
375 pub fn new(widget_builder: WidgetBuilder) -> Self {
376 Self {
377 widget_builder,
378 min: None,
379 max: None,
380 value: None,
381 step: None,
382 orientation: None,
383 increase: None,
384 decrease: None,
385 indicator: None,
386 body: None,
387 show_value: false,
388 value_precision: 3,
389 }
390 }
391
392 pub fn with_min(mut self, min: f32) -> Self {
393 self.min = Some(min);
394 self
395 }
396
397 pub fn with_max(mut self, max: f32) -> Self {
398 self.max = Some(max);
399 self
400 }
401
402 pub fn with_value(mut self, value: f32) -> Self {
403 self.value = Some(value);
404 self
405 }
406
407 pub fn with_orientation(mut self, orientation: Orientation) -> Self {
408 self.orientation = Some(orientation);
409 self
410 }
411
412 pub fn with_step(mut self, step: f32) -> Self {
413 self.step = Some(step);
414 self
415 }
416
417 pub fn with_increase(mut self, increase: Handle<UiNode>) -> Self {
418 self.increase = Some(increase);
419 self
420 }
421
422 pub fn with_decrease(mut self, decrease: Handle<UiNode>) -> Self {
423 self.decrease = Some(decrease);
424 self
425 }
426
427 pub fn with_indicator(mut self, indicator: Handle<UiNode>) -> Self {
428 self.indicator = Some(indicator);
429 self
430 }
431
432 pub fn with_body(mut self, body: Handle<UiNode>) -> Self {
433 self.body = Some(body);
434 self
435 }
436
437 pub fn show_value(mut self, state: bool) -> Self {
438 self.show_value = state;
439 self
440 }
441
442 pub fn with_value_precision(mut self, precision: usize) -> Self {
443 self.value_precision = precision;
444 self
445 }
446
447 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
448 let orientation = self.orientation.unwrap_or(Orientation::Horizontal);
449
450 let increase = self.increase.unwrap_or_else(|| {
451 ButtonBuilder::new(WidgetBuilder::new())
452 .with_content(match orientation {
453 Orientation::Horizontal => make_arrow(ctx, ArrowDirection::Right, 8.0),
454 Orientation::Vertical => make_arrow(ctx, ArrowDirection::Bottom, 8.0),
455 })
456 .build(ctx)
457 });
458
459 match orientation {
460 Orientation::Vertical => {
461 ctx[increase].set_height(30.0).set_row(2).set_column(0);
462 }
463 Orientation::Horizontal => {
464 ctx[increase].set_width(30.0).set_row(0).set_column(2);
465 }
466 }
467
468 let decrease = self.decrease.unwrap_or_else(|| {
469 ButtonBuilder::new(WidgetBuilder::new())
470 .with_content(match orientation {
471 Orientation::Horizontal => make_arrow(ctx, ArrowDirection::Left, 8.0),
472 Orientation::Vertical => make_arrow(ctx, ArrowDirection::Top, 8.0),
473 })
474 .build(ctx)
475 });
476
477 ctx[decrease].set_row(0).set_column(0);
478
479 match orientation {
480 Orientation::Vertical => ctx[decrease].set_height(30.0),
481 Orientation::Horizontal => ctx[decrease].set_width(30.0),
482 };
483
484 let indicator = self.indicator.unwrap_or_else(|| {
485 DecoratorBuilder::new(
486 BorderBuilder::new(WidgetBuilder::new().with_foreground(Brush::LinearGradient {
487 from: Vector2::new(0.5, 0.0),
488 to: Vector2::new(0.5, 1.0),
489 stops: vec![
490 GradientPoint {
491 stop: 0.0,
492 color: COLOR_DARKEST,
493 },
494 GradientPoint {
495 stop: 0.25,
496 color: COLOR_LIGHTEST,
497 },
498 GradientPoint {
499 stop: 0.75,
500 color: COLOR_LIGHTEST,
501 },
502 GradientPoint {
503 stop: 1.0,
504 color: COLOR_DARKEST,
505 },
506 ],
507 }))
508 .with_stroke_thickness(Thickness::uniform(1.0)),
509 )
510 .with_normal_brush(BRUSH_LIGHT)
511 .with_hover_brush(BRUSH_LIGHTER)
512 .with_pressed_brush(BRUSH_LIGHTEST)
513 .build(ctx)
514 });
515
516 match orientation {
517 Orientation::Vertical => {
518 ctx[indicator]
519 .set_min_size(Vector2::new(0.0, 30.0))
520 .set_width(30.0);
521 }
522 Orientation::Horizontal => {
523 ctx[indicator]
524 .set_min_size(Vector2::new(30.0, 0.0))
525 .set_height(30.0);
526 }
527 }
528
529 let min = self.min.unwrap_or(0.0);
530 let max = self.max.unwrap_or(100.0);
531 let value = math::clampf(self.value.unwrap_or(0.0), min, max);
532
533 let value_text = if self.show_value {
534 let value_text = TextBuilder::new(
535 WidgetBuilder::new()
536 .with_visibility(self.show_value)
537 .with_horizontal_alignment(HorizontalAlignment::Center)
538 .with_vertical_alignment(VerticalAlignment::Center)
539 .with_hit_test_visibility(false)
540 .with_margin(Thickness::uniform(3.0))
541 .on_column(match orientation {
542 Orientation::Horizontal => 1,
543 Orientation::Vertical => 0,
544 })
545 .on_row(match orientation {
546 Orientation::Horizontal => 0,
547 Orientation::Vertical => 1,
548 }),
549 )
550 .with_text(format!("{:.1$}", value, self.value_precision))
551 .build(ctx);
552
553 ctx.link(value_text, indicator);
554
555 value_text
556 } else {
557 Handle::NONE
558 };
559
560 let field = CanvasBuilder::new(
561 WidgetBuilder::new()
562 .with_name(ScrollBar::PART_CANVAS)
563 .on_column(match orientation {
564 Orientation::Horizontal => 1,
565 Orientation::Vertical => 0,
566 })
567 .on_row(match orientation {
568 Orientation::Horizontal => 0,
569 Orientation::Vertical => 1,
570 })
571 .with_child(indicator),
572 )
573 .build(ctx);
574
575 let grid = GridBuilder::new(
576 WidgetBuilder::new()
577 .with_child(decrease)
578 .with_child(field)
579 .with_child(increase),
580 )
581 .add_rows(match orientation {
582 Orientation::Horizontal => vec![Row::stretch()],
583 Orientation::Vertical => vec![Row::auto(), Row::stretch(), Row::auto()],
584 })
585 .add_columns(match orientation {
586 Orientation::Horizontal => vec![Column::auto(), Column::stretch(), Column::auto()],
587 Orientation::Vertical => vec![Column::stretch()],
588 })
589 .build(ctx);
590
591 let body = self.body.unwrap_or_else(|| {
592 BorderBuilder::new(
593 WidgetBuilder::new().with_background(Brush::Solid(Color::opaque(60, 60, 60))),
594 )
595 .with_stroke_thickness(Thickness::uniform(1.0))
596 .build(ctx)
597 });
598 ctx.link(grid, body);
599
600 let node = UiNode::new(ScrollBar {
601 widget: self.widget_builder.with_child(body).build(),
602 min,
603 max,
604 value,
605 step: self.step.unwrap_or(1.0),
606 orientation,
607 is_dragging: false,
608 offset: Vector2::default(),
609 increase,
610 decrease,
611 indicator,
612 field,
613 value_text,
614 value_precision: self.value_precision,
615 });
616 ctx.add_node(node)
617 }
618}