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