maycoon_widgets/
slider.rs1use maycoon_core::app::context::AppContext;
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout;
5use maycoon_core::layout::{Dimension, LayoutNode, LayoutStyle, LengthPercentageAuto, StyleNode};
6use maycoon_core::signal::MaybeSignal;
7use maycoon_core::vgi::kurbo::{Circle, Point, Rect, RoundedRect, RoundedRectRadii};
8use maycoon_core::vgi::{Brush, Scene};
9use maycoon_core::widget::{Widget, WidgetLayoutExt};
10use maycoon_core::window::MouseButton;
11use maycoon_theme::id::WidgetId;
12use maycoon_theme::theme::Theme;
13use nalgebra::Vector2;
14
15pub struct Slider {
22 layout_style: MaybeSignal<LayoutStyle>,
23 value: MaybeSignal<f32>,
24 on_change: MaybeSignal<Update>,
25 dragging: bool,
26}
27
28impl Slider {
29 pub fn new(value: impl Into<MaybeSignal<f32>>) -> Self {
31 Self {
32 layout_style: LayoutStyle {
33 size: Vector2::<Dimension>::new(Dimension::length(100.0), Dimension::length(10.0)),
34 margin: layout::Rect::<LengthPercentageAuto> {
35 left: LengthPercentageAuto::length(10.0),
36 right: LengthPercentageAuto::length(0.0),
37 top: LengthPercentageAuto::length(10.0),
38 bottom: LengthPercentageAuto::length(10.0),
39 },
40 ..Default::default()
41 }
42 .into(),
43 value: value.into(),
44 on_change: MaybeSignal::value(Update::empty()),
45 dragging: false,
46 }
47 }
48
49 pub fn with_value(mut self, value: impl Into<MaybeSignal<f32>>) -> Self {
51 self.value = value.into();
52 self
53 }
54
55 pub fn with_on_change(mut self, on_change: impl Into<MaybeSignal<Update>>) -> Self {
57 self.on_change = on_change.into();
58 self
59 }
60}
61
62impl WidgetLayoutExt for Slider {
63 fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
64 self.layout_style = layout_style.into();
65 }
66}
67
68impl Widget for Slider {
69 fn render(
70 &mut self,
71 scene: &mut dyn Scene,
72 theme: &mut dyn Theme,
73 layout_node: &LayoutNode,
74 _: &AppInfo,
75 _: AppContext,
76 ) {
77 let value = *self.value.get();
78
79 let brush = if let Some(style) = theme.of(self.widget_id()) {
80 Brush::Solid(style.get_color("color").unwrap())
81 } else {
82 Brush::Solid(theme.defaults().interactive().inactive())
83 };
84
85 let ball_brush = if let Some(style) = theme.of(self.widget_id()) {
86 Brush::Solid(style.get_color("color_ball").unwrap())
87 } else {
88 Brush::Solid(theme.defaults().interactive().active())
89 };
90
91 let circle_radius = layout_node.layout.size.height as f64 / 1.15;
92
93 scene.draw_rounded_rect(
94 &brush,
95 None,
96 None,
97 &RoundedRect::from_rect(
98 Rect::new(
99 layout_node.layout.location.x as f64,
100 layout_node.layout.location.y as f64,
101 (layout_node.layout.location.x + layout_node.layout.size.width) as f64,
102 (layout_node.layout.location.y + layout_node.layout.size.height) as f64,
103 ),
104 RoundedRectRadii::from_single_radius(20.0),
105 ),
106 );
107
108 scene.draw_circle(
109 &ball_brush,
110 None,
111 None,
112 &Circle::new(
113 Point::new(
114 (layout_node.layout.location.x + layout_node.layout.size.width * value) as f64,
115 (layout_node.layout.location.y + layout_node.layout.size.height / 2.0) as f64,
116 ),
117 circle_radius,
118 ),
119 );
120 }
121
122 fn layout_style(&self) -> StyleNode {
123 StyleNode {
124 style: self.layout_style.get().clone(),
125 children: Vec::new(),
126 }
127 }
128
129 fn update(&mut self, layout: &LayoutNode, _: AppContext, info: &AppInfo) -> Update {
130 let mut update = Update::empty();
131
132 if let Some(cursor) = info.cursor_pos {
133 if cursor.x as f32 >= layout.layout.location.x
134 && cursor.x as f32 <= layout.layout.location.x + layout.layout.size.width
135 && cursor.y as f32 >= layout.layout.location.y
136 && cursor.y as f32 <= layout.layout.location.y + layout.layout.size.height
137 {
138 for (_, btn, el_state) in &info.buttons {
139 if btn == &MouseButton::Left && el_state.is_pressed() {
140 self.dragging = el_state.is_pressed();
141 }
142 }
143
144 if self.dragging {
145 let new_value =
146 (cursor.x as f32 - layout.layout.location.x) / layout.layout.size.width;
147
148 if let Some(sig) = self.value.as_signal() {
149 sig.set(new_value);
150 }
151
152 update.insert(*self.on_change.get());
153 update.insert(Update::DRAW);
154 }
155 }
156 } else {
157 self.dragging = false;
158 }
159
160 update
161 }
162
163 fn widget_id(&self) -> WidgetId {
164 WidgetId::new("maycoon-widgets", "Slider")
165 }
166}