iced_aw/widget/
color_picker.rs

1//! Use a color picker as an input element for picking colors.
2//!
3//! *This API requires the following crate features to be activated: `color_picker`*
4
5use self::style::{Status, StyleFn};
6
7use super::overlay::color_picker::{
8    self, ColorBarDragged, ColorPickerOverlay, ColorPickerOverlayButtons,
9};
10
11use iced_core::{
12    Clipboard, Color, Element, Event, Layout, Length, Point, Rectangle, Shell, Vector, Widget,
13    layout::{Limits, Node},
14    mouse::{self, Cursor},
15    overlay, renderer,
16    widget::tree::{self, Tag, Tree},
17};
18use iced_widget::Renderer;
19
20pub use crate::style::{self, color_picker::Style};
21
22//TODO: Remove ignore when Null is updated. Temp fix for Test runs
23/// An input element for picking colors.
24///
25/// # Example
26/// ```ignore
27/// # use iced_aw::ColorPicker;
28/// # use iced::{Color, widget::{button, Button, Text}};
29/// #
30/// #[derive(Clone, Debug)]
31/// enum Message {
32///     Open,
33///     Cancel,
34///     Submit(Color),
35/// }
36///
37/// let color_picker = ColorPicker::new(
38///     true,
39///     Color::default(),
40///     Button::new(Text::new("Pick color"))
41///         .on_press(Message::Open),
42///     Message::Cancel,
43///     Message::Submit,
44/// );
45/// ```
46#[allow(missing_debug_implementations)]
47pub struct ColorPicker<'a, Message, Theme = iced_widget::Theme>
48where
49    Message: Clone,
50    Theme: style::color_picker::Catalog + iced_widget::button::Catalog,
51{
52    /// Show the picker.
53    show_picker: bool,
54    /// The color to show.
55    color: Color,
56    /// The underlying element.
57    underlay: Element<'a, Message, Theme, Renderer>,
58    /// The message that is sent if the cancel button of the [`ColorPickerOverlay`] is pressed.
59    on_cancel: Message,
60    /// The function that produces a message when the submit button of the [`ColorPickerOverlay`] is pressed.
61    on_submit: Box<dyn Fn(Color) -> Message>,
62    /// The style of the [`ColorPickerOverlay`].
63    class: <Theme as style::color_picker::Catalog>::Class<'a>,
64    /// The buttons of the overlay.
65    overlay_state: Element<'a, Message, Theme, Renderer>,
66}
67
68impl<'a, Message, Theme> ColorPicker<'a, Message, Theme>
69where
70    Message: 'a + Clone,
71    Theme: 'a
72        + style::color_picker::Catalog
73        + iced_widget::button::Catalog
74        + iced_widget::text::Catalog,
75{
76    /// Creates a new [`ColorPicker`] wrapping around the given underlay.
77    ///
78    /// It expects:
79    ///     * if the overlay of the color picker is visible.
80    ///     * the initial color to show.
81    ///     * the underlay [`Element`] on which this [`ColorPicker`]
82    ///         will be wrapped around.
83    ///     * a message that will be send when the cancel button of the [`ColorPicker`]
84    ///         is pressed.
85    ///     * a function that will be called when the submit button of the [`ColorPicker`]
86    ///         is pressed, which takes the picked [`Color`] value.
87    pub fn new<U, F>(
88        show_picker: bool,
89        color: Color,
90        underlay: U,
91        on_cancel: Message,
92        on_submit: F,
93    ) -> Self
94    where
95        U: Into<Element<'a, Message, Theme, Renderer>>,
96        F: 'static + Fn(Color) -> Message,
97    {
98        Self {
99            show_picker,
100            color,
101            underlay: underlay.into(),
102            on_cancel,
103            on_submit: Box::new(on_submit),
104            class: <Theme as style::color_picker::Catalog>::default(),
105            overlay_state: ColorPickerOverlayButtons::default().into(),
106        }
107    }
108
109    /// Sets the style of the [`ColorPicker`].
110    #[must_use]
111    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
112    where
113        <Theme as style::color_picker::Catalog>::Class<'a>: From<StyleFn<'a, Theme, Style>>,
114    {
115        self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into();
116        self
117    }
118
119    /// Sets the class of the input of the [`ColorPicker`].
120    #[must_use]
121    pub fn class(
122        mut self,
123        class: impl Into<<Theme as style::color_picker::Catalog>::Class<'a>>,
124    ) -> Self {
125        self.class = class.into();
126        self
127    }
128}
129
130/// The state of the [`ColorPicker`].
131#[derive(Debug, Default)]
132pub struct State {
133    /// The state of the overlay.
134    pub(crate) overlay_state: color_picker::State,
135    /// Was overlay shown during the previous render?
136    pub(crate) old_show_picker: bool,
137}
138
139impl State {
140    /// Creates a new [`State`].
141    #[must_use]
142    pub fn new(color: Color) -> Self {
143        Self {
144            overlay_state: color_picker::State::new(color),
145            old_show_picker: false,
146        }
147    }
148
149    /// Resets the color of the state.
150    pub fn reset(&mut self) {
151        self.overlay_state.color = Color::from_rgb(0.5, 0.25, 0.25);
152        self.overlay_state.color_bar_dragged = ColorBarDragged::None;
153    }
154
155    /// Synchronize with the provided color if it was changed or picker was reopened
156    ///
157    /// Keep the overlay state in sync. While overlay is open, it "owns" the value
158    /// (there is no other way the user can update its value). When it is reopened,
159    /// reset the color to the initial one.
160    fn synchronize(&mut self, show_picker: bool, color: Color) {
161        if show_picker && (!self.old_show_picker || self.overlay_state.initial_color != color) {
162            self.overlay_state.force_synchronize(color);
163        }
164        self.old_show_picker = show_picker;
165    }
166}
167
168impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for ColorPicker<'a, Message, Theme>
169where
170    Message: 'static + Clone,
171    Theme: 'a
172        + style::color_picker::Catalog
173        + iced_widget::button::Catalog
174        + iced_widget::text::Catalog,
175{
176    fn tag(&self) -> Tag {
177        Tag::of::<State>()
178    }
179
180    fn state(&self) -> tree::State {
181        tree::State::new(State::new(self.color))
182    }
183
184    fn children(&self) -> Vec<Tree> {
185        vec![Tree::new(&self.underlay), Tree::new(&self.overlay_state)]
186    }
187
188    fn diff(&self, tree: &mut Tree) {
189        let color_picker_state = tree.state.downcast_mut::<State>();
190
191        color_picker_state.synchronize(self.show_picker, self.color);
192
193        tree.diff_children(&[&self.underlay, &self.overlay_state]);
194    }
195
196    fn size(&self) -> iced_core::Size<Length> {
197        self.underlay.as_widget().size()
198    }
199
200    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
201        self.underlay
202            .as_widget_mut()
203            .layout(&mut tree.children[0], renderer, limits)
204    }
205
206    fn update(
207        &mut self,
208        state: &mut Tree,
209        event: &Event,
210        layout: Layout<'_>,
211        cursor: Cursor,
212        renderer: &Renderer,
213        clipboard: &mut dyn Clipboard,
214        shell: &mut Shell<'_, Message>,
215        viewport: &Rectangle,
216    ) {
217        self.underlay.as_widget_mut().update(
218            &mut state.children[0],
219            event,
220            layout,
221            cursor,
222            renderer,
223            clipboard,
224            shell,
225            viewport,
226        );
227    }
228
229    fn mouse_interaction(
230        &self,
231        state: &Tree,
232        layout: Layout<'_>,
233        cursor: Cursor,
234        viewport: &Rectangle,
235        renderer: &Renderer,
236    ) -> mouse::Interaction {
237        self.underlay.as_widget().mouse_interaction(
238            &state.children[0],
239            layout,
240            cursor,
241            viewport,
242            renderer,
243        )
244    }
245
246    fn draw(
247        &self,
248        state: &Tree,
249        renderer: &mut Renderer,
250        theme: &Theme,
251        style: &renderer::Style,
252        layout: Layout<'_>,
253        cursor: Cursor,
254        viewport: &Rectangle,
255    ) {
256        self.underlay.as_widget().draw(
257            &state.children[0],
258            renderer,
259            theme,
260            style,
261            layout,
262            cursor,
263            viewport,
264        );
265    }
266
267    fn overlay<'b>(
268        &'b mut self,
269        tree: &'b mut Tree,
270        layout: Layout<'b>,
271        renderer: &Renderer,
272        viewport: &Rectangle,
273        translation: Vector,
274    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
275        let picker_state: &mut State = tree.state.downcast_mut();
276
277        if !self.show_picker {
278            return self.underlay.as_widget_mut().overlay(
279                &mut tree.children[0],
280                layout,
281                renderer,
282                viewport,
283                translation,
284            );
285        }
286
287        let bounds = layout.bounds();
288        let position = Point::new(bounds.center_x(), bounds.center_y());
289
290        Some(
291            ColorPickerOverlay::new(
292                picker_state,
293                self.on_cancel.clone(),
294                &self.on_submit,
295                position,
296                &self.class,
297                &mut tree.children[1],
298                *viewport,
299            )
300            .overlay(),
301        )
302    }
303}
304
305impl<'a, Message, Theme> From<ColorPicker<'a, Message, Theme>>
306    for Element<'a, Message, Theme, Renderer>
307where
308    Message: 'static + Clone,
309    Theme: 'a
310        + style::color_picker::Catalog
311        + iced_widget::button::Catalog
312        + iced_widget::text::Catalog,
313{
314    fn from(color_picker: ColorPicker<'a, Message, Theme>) -> Self {
315        Element::new(color_picker)
316    }
317}