11module gui
22
3+ // view_range_slider.v implements a range slider UI component that allows users
4+ // to select a value from a continuous range by dragging a thumb along a track.
5+ // The component supports both horizontal and vertical orientations, customizable
6+ // styling, keyboard navigation, mouse wheel input, and configurable value ranges.
7+ //
38import math
49
10+ // RangeSliderCfg defines the configuration options for the range slider component.
11+ // It includes visual styling properties like colors and dimensions, behavioral
12+ // settings like value range and step size, and callbacks for handling user input.
513@[heap; minify]
614pub struct RangeSliderCfg {
715pub :
3442 invisible bool
3543}
3644
45+ // range_slider creates and returns a range slider View component based on the provided configuration.
46+ // The range slider allows users to select a numeric value within a specified range by dragging
47+ // a thumb along a track or using keyboard/mouse wheel input.
48+ //
49+ // Parameters:
50+ // cfg RangeSliderCfg - Configuration struct containing all customization options including:
51+ // - Visual styling (colors, dimensions, etc.)
52+ // - Value range (min/max)
53+ // - Step size
54+ // - Callbacks for input handling
55+ // - Layout options
56+ //
57+ // Returns:
58+ // View - A fully configured range slider View component
59+ //
3760pub fn range_slider (cfg RangeSliderCfg) View {
3861 if cfg.min > = cfg.max {
3962 panic ('range_slider.min must be less than range_slider.max' )
@@ -54,6 +77,7 @@ pub fn range_slider(cfg RangeSliderCfg) View {
5477 h_align: .center
5578 v_align: .middle
5679 axis: if cfg.vertical { .top_to_bottom } else { .left_to_right }
80+ on_click: cfg.on_mouse_down
5781 amend_layout: cfg.amend_layout_slide
5882 on_hover: cfg.on_hover_slide
5983 on_keydown: cfg.on_keydown
@@ -80,9 +104,7 @@ pub fn range_slider(cfg RangeSliderCfg) View {
80104 fill: cfg.fill
81105 color: cfg.color_border
82106 padding: cfg.padding_border
83- on_click: cfg.on_mouse_down
84107 amend_layout: cfg.amend_layout_thumb
85- on_hover: cfg.on_hover_thumb
86108 content: [
87109 circle (
88110 name: 'range_slider thumb'
@@ -100,8 +122,28 @@ pub fn range_slider(cfg RangeSliderCfg) View {
100122 )
101123}
102124
125+ // amend_layout_slide adjusts the layout of the range slider components based on the
126+ // current value and configuration.
127+ //
128+ // The slider consists of three main visual elements:
129+ // 1. A border container that wraps everything
130+ // 2. An interior container with the main track
131+ // 3. A "filled" portion showing the selected value (left bar)
132+ //
133+ // For vertical sliders:
134+ // - Adjusts the height of the left bar based on current value percentage
135+ // - Centers the track horizontally relative to the thumb
136+ // - Applies padding and sizing to maintain proper visual alignment
137+ //
138+ // For horizontal sliders:
139+ // - Adjusts the width of the left bar based on current value percentage
140+ // - Centers the track vertically relative to the thumb
141+ // - Applies padding and sizing to maintain proper visual alignment
142+ //
143+ // Parameters:
144+ // layout Layout - The root layout node for the range slider
145+ // w Window - Window context for focus state handling
103146fn (cfg &RangeSliderCfg) amend_layout_slide (mut layout Layout, mut w Window) {
104- layout.shape.on_click = cfg.on_click
105147 layout.shape.on_mouse_scroll = cfg.on_mouse_scroll
106148
107149 // set positions of left/right or top/bottom rectangles
@@ -148,36 +190,72 @@ fn (cfg &RangeSliderCfg) amend_layout_slide(mut layout Layout, mut w Window) {
148190 }
149191}
150192
151- fn (cfg &RangeSliderCfg) on_hover_slide (mut layout Layout, mut e Event, mut _ Window) {
152- layout.children[0 ].shape.color = cfg.color_hover
193+ fn (cfg &RangeSliderCfg) on_hover_slide (mut layout Layout, mut e Event, mut w Window) {
194+ w.set_mouse_cursor_pointing_hand ()
195+ layout.shape.color = cfg.color_hover
153196 if e.mouse_button == .left {
154197 layout.children[0 ].shape.color = cfg.color_click
155198 }
156199}
157200
201+ // amend_layout_thumb positions the slider's thumb element based on the current value.
202+ // This function is called as an amend layout callback after the main layout is composed
203+ // because the thumb position depends on the final dimensions of the slider track.
204+ //
205+ // The thumb position is calculated as follows:
206+ // 1. Converts the current value to a percentage within the min/max range
207+ // 2. For vertical sliders:
208+ // - Maps percentage to y-coordinate along the track height
209+ // - Centers thumb horizontally using padding
210+ // 3. For horizontal sliders:
211+ // - Maps percentage to x-coordinate along the track width
212+ // - Centers thumb vertically using padding
213+ //
214+ // Parameters:
215+ // layout Layout - The thumb element's layout node to position
216+ // _ Window - Window context (unused)
158217fn (cfg &RangeSliderCfg) amend_layout_thumb (mut layout Layout, mut _ Window) {
159218 // set the thumb position
160219 value := f32_clamp (cfg.value, cfg.min, cfg.max)
161220 percent := math.abs (value / (cfg.max - cfg.min))
221+ radius := cfg.thumb_size / 2
162222 if cfg.vertical {
163223 height := layout.parent.shape.height
164224 y := f32_min (height * percent, height)
165- layout.shape.y = layout.parent.shape.y + y - cfg.padding_border.height ()
225+ layout.shape.y = layout.parent.shape.y + y - cfg.padding_border.height () - radius
166226 layout.children[0 ].shape.y = layout.shape.y + cfg.padding_border.top
167227 } else {
168228 width := layout.parent.shape.width
169229 x := f32_min (width * percent, width)
170- layout.shape.x = layout.parent.shape.x + x - cfg.padding_border.width ()
230+ layout.shape.x = layout.parent.shape.x + x - cfg.padding_border.width () - radius
171231 layout.children[0 ].shape.x = layout.shape.x + cfg.padding_border.top
172232 }
173233}
174234
175- fn (_ &RangeSliderCfg) on_hover_thumb (mut _ Layout, mut _ Event, mut w Window) {
176- w.set_mouse_cursor_pointing_hand ()
177- }
235+ // on_mouse_down handles the initial mouse click on the range slider.
236+ // When clicked, it:
237+ // 1. Creates a copy of the event with unadjusted coordinates since mouse_move
238+ // handler expects absolute screen coordinates rather than layout-relative ones
239+ // 2. Calls mouse_move to immediately update slider position to click location
240+ // 3. Sets up mouse lock to track drag movements by registering mouse_move as the
241+ // drag handler and configuring mouse release to unlock
242+ //
243+ // The coordinate adjustment is needed because regular events get layout-relative
244+ // coordinates while MouseLock events use absolute screen coordinates. Since the
245+ // same mouse_move handler is used for both initial click and dragging, we need
246+ // to convert to absolute coordinates.
247+ fn (cfg &RangeSliderCfg) on_mouse_down (layout & Layout, mut e Event, mut w Window) {
248+ mut ev := & Event{
249+ ...e
250+ touches: e.touches // runtime mem error otherwise
251+ mouse_x: e.mouse_x + layout.shape.x
252+ mouse_y: e.mouse_y + layout.shape.y
253+ }
254+ cfg.mouse_move (layout, mut ev, mut w)
178255
179- fn (cfg &RangeSliderCfg) on_mouse_down (_ & Layout, mut e Event, mut w Window) {
256+ // Lock the mouse to the range slider until the mouse button is released
180257 w.mouse_lock (MouseLockCfg{
258+ // event mouse coordinates are not adjusted here
181259 mouse_move: cfg.mouse_move
182260 mouse_up: fn (_ & Layout, mut _ Event, mut w Window) {
183261 w.mouse_unlock ()
@@ -186,16 +264,17 @@ fn (cfg &RangeSliderCfg) on_mouse_down(_ &Layout, mut e Event, mut w Window) {
186264 e.is_handled = true
187265}
188266
189- // mouse_move pass cfg by value more reliable here
190- fn (cfg RangeSliderCfg) mouse_move (layout & Layout, mut e Event, mut w Window) {
267+ // mouse_move expects the events mouse coordinates to MOT be adjusted (see on_mouse_down)
268+ fn (cfg & RangeSliderCfg) mouse_move (layout & Layout, mut e Event, mut w Window) {
191269 id := cfg.id
192270
193271 if cfg.on_change != unsafe { nil } {
194- if node_circle := layout.find_layout (fn [id] (n Layout) bool {
272+ range_slider := layout.find_layout (fn [id] (n Layout) bool {
195273 return n.shape.id == id
196274 })
197- {
198- shape := node_circle.parent.shape
275+ if range_slider != none {
276+ w.set_mouse_cursor_pointing_hand ()
277+ shape := range_slider.shape
199278 if cfg.vertical {
200279 height := shape.height
201280 percent := f32_clamp ((e.mouse_y - shape.y) / height, 0 , 1 )
@@ -221,26 +300,6 @@ fn (cfg RangeSliderCfg) mouse_move(layout &Layout, mut e Event, mut w Window) {
221300 }
222301}
223302
224- fn (cfg &RangeSliderCfg) on_click (layout & Layout, mut e Event, mut w Window) {
225- if cfg.on_change != unsafe { nil } {
226- forgiveness := 10
227- len := if cfg.vertical { layout.shape.height } else { layout.shape.width }
228- mouse := if cfg.vertical { e.mouse_y } else { e.mouse_x }
229- pos := if cfg.vertical { layout.shape.y } else { layout.shape.x }
230- percent := match true {
231- mouse < = pos + forgiveness { 0 }
232- mouse > = pos + len - forgiveness { 1 }
233- else { f32_clamp ((mouse - pos) / len, 0 , 1 ) }
234- }
235- val := (cfg.max - cfg.min) * percent
236- mut value := f32_clamp (val, cfg.min, cfg.max)
237- if cfg.round_value {
238- value = f32 (math.round (f64 (value)))
239- }
240- cfg.on_change (value, mut e, mut w)
241- }
242- }
243-
244303fn (cfg &RangeSliderCfg) on_keydown (_ & Layout, mut e Event, mut w Window) {
245304 if cfg.on_change != unsafe { nil } && e.modifiers == .none {
246305 mut value := cfg.value
0 commit comments