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 {
24 layout_style: MaybeSignal<LayoutStyle>,
25 value: MaybeSignal<f32>,
26 on_change: MaybeSignal<Update>,
27 dragging: bool,
28}
29
30impl Slider {
31 #[inline(always)]
33 pub fn new(value: impl Into<MaybeSignal<f32>>) -> Self {
34 Self {
35 layout_style: LayoutStyle {
36 size: Vector2::<Dimension>::new(Dimension::length(100.0), Dimension::length(10.0)),
37 margin: layout::Rect::<LengthPercentageAuto> {
38 left: LengthPercentageAuto::length(10.0),
39 right: LengthPercentageAuto::length(0.0),
40 top: LengthPercentageAuto::length(10.0),
41 bottom: LengthPercentageAuto::length(10.0),
42 },
43 ..Default::default()
44 }
45 .into(),
46 value: value.into(),
47 on_change: MaybeSignal::value(Update::empty()),
48 dragging: false,
49 }
50 }
51
52 #[inline(always)]
54 pub fn with_value(mut self, value: impl Into<MaybeSignal<f32>>) -> Self {
55 self.value = value.into();
56 self
57 }
58
59 #[inline(always)]
61 pub fn with_on_change(mut self, on_change: impl Into<MaybeSignal<Update>>) -> Self {
62 self.on_change = on_change.into();
63 self
64 }
65}
66
67impl WidgetLayoutExt for Slider {
68 #[inline(always)]
69 fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
70 self.layout_style = layout_style.into();
71 }
72}
73
74impl Widget for Slider {
75 fn render(
76 &mut self,
77 scene: &mut dyn Scene,
78 theme: &mut dyn Theme,
79 layout_node: &LayoutNode,
80 _: &AppInfo,
81 _: AppContext,
82 ) {
83 let value = *self.value.get();
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.draw_rounded_rect(
100 &brush,
101 None,
102 None,
103 &RoundedRect::from_rect(
104 Rect::new(
105 layout_node.layout.location.x as f64,
106 layout_node.layout.location.y as f64,
107 (layout_node.layout.location.x + layout_node.layout.size.width) as f64,
108 (layout_node.layout.location.y + layout_node.layout.size.height) as f64,
109 ),
110 RoundedRectRadii::from_single_radius(20.0),
111 ),
112 );
113
114 scene.draw_circle(
115 &ball_brush,
116 None,
117 None,
118 &Circle::new(
119 Point::new(
120 (layout_node.layout.location.x + layout_node.layout.size.width * value) as f64,
121 (layout_node.layout.location.y + layout_node.layout.size.height / 2.0) as f64,
122 ),
123 circle_radius,
124 ),
125 );
126 }
127
128 #[inline(always)]
129 fn layout_style(&self) -> StyleNode {
130 StyleNode {
131 style: self.layout_style.get().clone(),
132 children: Vec::new(),
133 }
134 }
135
136 fn update(&mut self, layout: &LayoutNode, _: AppContext, info: &AppInfo) -> Update {
137 let mut update = Update::empty();
138
139 if let Some(cursor) = info.cursor_pos
140 && layout::intersects(cursor, &layout.layout)
141 {
142 for (_, btn, el_state) in &info.buttons {
143 if btn == &MouseButton::Left && el_state.is_pressed() {
144 self.dragging = el_state.is_pressed();
145 }
146 }
147
148 if self.dragging {
149 let new_value = (cursor.x - layout.layout.location.x) / layout.layout.size.width;
150
151 if let Some(sig) = self.value.as_signal() {
152 sig.set(new_value);
153 }
154
155 update.insert(*self.on_change.get());
156 update.insert(Update::DRAW);
157 }
158 } else {
159 self.dragging = false;
160 }
161
162 update
163 }
164
165 #[inline(always)]
166 fn widget_id(&self) -> WidgetId {
167 WidgetId::new("maycoon-widgets", "Slider")
168 }
169}