makepad_widgets/
color_picker.rs

1use crate::{makepad_derive_widget::*, makepad_draw::*, widget::*};
2
3live_design!{
4    use makepad_draw::shader::std::*;
5    
6    DrawColorWheel= {{DrawColorWheel}} {
7        instance hover: float
8        instance down: float
9        
10        fn circ_to_rect(u: float, v: float) -> vec2 {
11            let u2 = u * u;
12            let v2 = v * v;
13            return vec2(
14                0.5 * sqrt(2. + 2. * sqrt(2.) * u + u2 - v2) -
15                0.5 * sqrt(2. - 2. * sqrt(2.) * u + u2 - v2),
16                0.5 * sqrt(2. + 2. * sqrt(2.) * v - u2 + v2) -
17                0.5 * sqrt(2. - 2. * sqrt(2.) * v - u2 + v2)
18            );
19        }
20        
21        fn pixel(self) -> vec4 {
22            
23            let rgbv = Pal::hsv2rgb(vec4(self.hue, self.sat, self.val, 1.));
24            let w = self.rect_size.x;
25            let h = self.rect_size.y;
26            let sdf = Sdf2d::viewport(self.pos * vec2(w, h));
27            let cx = w * 0.5;
28            let cy = h * 0.5;
29            
30            let radius = w * 0.37;
31            let inner = w * 0.28;
32            
33            sdf.hexagon(cx, cy, w * 0.45);
34            sdf.hexagon(cx, cy, w * 0.4);
35            sdf.subtract();
36            let ang = atan(self.pos.x * w - cx, 0.0001 + self.pos.y * h - cy) / PI * 0.5 - 0.33333;
37            sdf.fill(Pal::hsv2rgb(vec4(ang, 1.0, 1.0, 1.0)));
38            
39            let rsize = inner / sqrt(2.0);
40            sdf.rect(cx - rsize, cy - rsize, rsize * 2.0, rsize * 2.0);
41            
42            let norm_rect = vec2(self.pos.x * w - (cx - inner), self.pos.y * h - (cy - inner)) / (2. * inner);
43            let circ = clamp(circ_to_rect(norm_rect.x * 2. - 1., norm_rect.y * 2. - 1.), vec2(-1.), vec2(1.));
44            
45            sdf.fill(Pal::hsv2rgb(vec4(self.hue, (circ.x * .5 + .5), 1. - (circ.y * .5 + .5), 1.)));
46            
47            let col_angle = (self.hue + .333333) * 2. * PI;
48            let circle_puk = vec2(sin(col_angle) * radius + cx, cos(col_angle) * radius + cy);
49            
50            let rect_puk = vec2(cx + self.sat * 2. * rsize - rsize, cy + (1. - self.val) * 2. * rsize - rsize);
51            
52            let color = mix(mix(#3, #E, self.hover), #F, self.down);
53            let puck_size = 0.1 * w;
54            sdf.circle(rect_puk.x, rect_puk.y, puck_size);
55            sdf.rect(cx - rsize, cy - rsize, rsize * 2.0, rsize * 2.0);
56            sdf.intersect();
57            sdf.fill(color);
58            sdf.circle(rect_puk.x, rect_puk.y, puck_size - 1. - 2. * self.hover + self.down);
59            sdf.rect(cx - rsize, cy - rsize, rsize * 2.0, rsize * 2.0);
60            sdf.intersect();
61            sdf.fill(rgbv);
62            
63            sdf.circle(circle_puk.x, circle_puk.y, puck_size);
64            sdf.fill(color);
65            sdf.circle(circle_puk.x, circle_puk.y, puck_size - 1. - 2. * self.hover + self.down);
66            sdf.fill(rgbv);
67            
68            return sdf.result;
69        }
70    }
71    
72    ColorPicker= {{ColorPicker}} {
73        
74        animator: {
75            hover = {
76                default: off
77                off = {
78                    from: {all: Forward {duration: 0.1}}
79                    apply: {
80                        draw_wheel: { down: 0.0, hover: 0.0}
81                    }
82                }
83                
84                on = {
85                    cursor: Arrow,
86                    from: {
87                        all: Forward {duration: 0.1}
88                        down: Forward {duration: 0.01}
89                    }
90                    apply: {
91                        draw_wheel: {
92                            down: 0.0,
93                            hover: [{time: 0.0, value: 1.0}],
94                        }
95                    }
96                }
97                
98                down = {
99                    cursor: Arrow,
100                    from: {all: Forward {duration: 0.2}}
101                    apply: {
102                        draw_wheel: {
103                            down: [{time: 0.0, value: 1.0}],
104                            hover: 1.0,
105                        }
106                    }
107                }
108            }
109        }
110    }
111}
112
113
114#[derive(Live, LiveHook, LiveRegister)]
115#[repr(C)]
116pub struct DrawColorWheel {
117    #[deref] draw_super: DrawQuad,
118    #[live] hue: f32,
119    #[live] sat: f32,
120    #[live] val: f32,
121}
122
123#[derive(Live, LiveHook, Widget)]
124pub struct ColorPicker {
125    #[redraw] #[live] draw_wheel: DrawColorWheel,
126    
127    #[animator] animator: Animator,
128    
129    #[walk] walk: Walk,
130        
131    #[rust] pub size: f64,
132    #[rust] hue: f32,
133    #[rust] sat: f32,
134    #[rust] val: f32,
135    #[rust(ColorPickerDragMode::None)] drag_mode: ColorPickerDragMode
136}
137
138#[derive(Clone, Debug, DefaultNone)]
139pub enum ColorPickerAction {
140    Change {rgba: Vec4},
141    DoneChanging,
142    None
143}
144
145#[derive(Clone, Debug, PartialEq)]
146pub enum ColorPickerDragMode {
147    Wheel,
148    Rect,
149    None
150}
151
152impl ColorPicker {
153    
154    pub fn handle_finger(&mut self, cx: &mut Cx, rel: DVec2, scope:&mut Scope) {
155        
156        fn clamp(x: f64, mi: f64, ma: f64) -> f64 {if x < mi {mi} else if x > ma {ma} else {x}}
157        
158        let vx = rel.x - 0.5 * self.size;
159        let vy = rel.y - 0.5 * self.size;
160        let rsize = (self.size * 0.28) / 2.0f64.sqrt();
161        let last_hue = self.hue;
162        let last_sat = self.sat;
163        let last_val = self.val;
164        
165        match self.drag_mode {
166            ColorPickerDragMode::Rect => {
167                self.sat = clamp((vx + rsize) / (2.0 * rsize), 0.0, 1.0) as f32;
168                self.val = 1.0 - clamp((vy + rsize) / (2.0 * rsize), 0.0, 1.0) as f32;
169            },
170            ColorPickerDragMode::Wheel => {
171                self.hue = ((vx.atan2(vy) / std::f64::consts::PI * 0.5) - 0.33333 + 1.0) as f32;
172            },
173            _ => ()
174        }
175        // lets update hue sat val directly
176        let mut changed = false;
177        
178        if last_hue != self.hue {
179            self.draw_wheel.apply_over(cx, live!{hue: (self.hue)});
180            changed = true;
181        }
182        if last_sat != self.sat {
183            self.draw_wheel.apply_over(cx, live!{sat: (self.sat)});
184            changed = true;
185        }
186        if last_val != self.val {
187            self.draw_wheel.apply_over(cx, live!{val: (self.val)});
188            changed = true;
189        }
190        if changed {
191            let uid = self.widget_uid();
192            cx.widget_action(uid, &scope.path, ColorPickerAction::Change {rgba: self.to_rgba()});
193        }
194    }
195    
196    pub fn to_rgba(&self) -> Vec4 {
197        Vec4::from_hsva(Vec4 {x: self.hue, y: self.sat, z: self.val, w: 1.0})
198    }
199    
200    
201    pub fn draw_color_picker(&mut self, cx: &mut Cx2d, rgba: Vec4, walk:Walk) {
202        if self.drag_mode == ColorPickerDragMode::None {
203            // lets convert to rgba
204            let old_rgba = self.to_rgba();
205            if !rgba.is_equal_enough(&old_rgba, 0.0001) {
206                let hsva = rgba.to_hsva();
207                self.hue = hsva.x;
208                self.sat = hsva.y;
209                self.val = hsva.z;
210            }
211        }
212        //self.draw_wheel.shader = live_shader!(cx, self::shader_wheel);
213        // i wanna draw a draw_wheel with 'width' set but height a fixed height.
214        self.size = cx.turtle().rect().size.y;
215        self.draw_wheel.hue = self.hue;
216        self.draw_wheel.sat = self.sat;
217        self.draw_wheel.val = self.val;
218        self.draw_wheel.draw_walk(cx, walk);
219    }
220}
221
222
223impl Widget for ColorPicker {
224    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
225        self.animator_handle_event(cx, event);
226                
227        match event.hits(cx, self.draw_wheel.area()) {
228            Hit::FingerHoverIn(_) => {
229                self.animator_play(cx, id!(hover.on));
230            }
231            Hit::FingerHoverOut(_) => {
232                self.animator_play(cx, id!(hover.off));
233            },
234            Hit::FingerDown(fe) => {
235                self.animator_play(cx, id!(hover.down));
236                let rsize = (self.size * 0.28) / 2.0f64.sqrt();
237                let rel = fe.abs - fe.rect.pos;
238                let vx = rel.x - 0.5 * self.size;
239                let vy = rel.y - 0.5 * self.size;
240                if vx >= -rsize && vx <= rsize && vy >= -rsize && vy <= rsize {
241                    self.drag_mode = ColorPickerDragMode::Rect;
242                }
243                else if vx >= -0.5 * self.size && vx <= 0.5 * self.size && vy >= -0.5 * self.size && vy <= 0.5 * self.size {
244                    self.drag_mode = ColorPickerDragMode::Wheel;
245                }
246                else {
247                    self.drag_mode = ColorPickerDragMode::None;
248                }
249                return self.handle_finger(cx, rel, scope);
250                // lets check where we clicked!
251            },
252            Hit::FingerUp(fe) => {
253                if fe.is_over && fe.device.has_hovers() {
254                    self.animator_play(cx, id!(hover.on));
255                }
256                else {
257                    self.animator_play(cx, id!(hover.off));
258                }
259                self.drag_mode = ColorPickerDragMode::None;
260                let uid = self.widget_uid();
261                cx.widget_action(uid, &scope.path, ColorPickerAction::DoneChanging);
262            }
263            Hit::FingerMove(fe) => {
264                let rel = fe.abs - fe.rect.pos;
265                return self.handle_finger(cx, rel, scope)
266                                
267            },
268            _ => ()
269        }
270    }
271        
272    fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep {
273        self.draw_color_picker(cx, vec4(1.0,0.0,0.0,1.0), walk);
274        DrawStep::done()
275    }
276}