makepad_widgets/
color_picker.rs

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