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