Skip to main content

azul_layout/widgets/
color_input.rs

1//! Rectangular input that displays a color and invokes a callback when clicked
2
3use azul_core::{
4    callbacks::Update,
5    dom::Dom,
6    refany::RefAny,
7};
8use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
9use azul_css::{
10    props::{
11        basic::*,
12        layout::*,
13        property::{CssProperty, *},
14        style::*,
15    },
16    *,
17};
18
19use crate::callbacks::{Callback, CallbackInfo};
20
21/// Rectangular input that displays a color and triggers a callback when clicked.
22#[derive(Debug, Default, Clone, PartialEq)]
23#[repr(C)]
24pub struct ColorInput {
25    pub color_input_state: ColorInputStateWrapper,
26    pub style: CssPropertyWithConditionsVec,
27}
28
29/// Callback function type invoked when the color input value changes.
30pub type ColorInputOnValueChangeCallbackType =
31    extern "C" fn(RefAny, CallbackInfo, ColorInputState) -> Update;
32impl_widget_callback!(
33    ColorInputOnValueChange,
34    OptionColorInputOnValueChange,
35    ColorInputOnValueChangeCallback,
36    ColorInputOnValueChangeCallbackType
37);
38
39azul_core::impl_managed_callback! {
40    wrapper:        ColorInputOnValueChangeCallback,
41    info_ty:        CallbackInfo,
42    return_ty:      Update,
43    default_ret:    Update::DoNothing,
44    invoker_static: COLOR_INPUT_ON_VALUE_CHANGE_INVOKER,
45    invoker_ty:     AzColorInputOnValueChangeCallbackInvoker,
46    thunk_fn:       az_color_input_on_value_change_callback_thunk,
47    setter_fn:      AzApp_setColorInputOnValueChangeCallbackInvoker,
48    from_handle_fn: AzColorInputOnValueChangeCallback_createFromHostHandle,
49    extra_args:     [ state: ColorInputState ],
50}
51
52/// Wrapper around [`ColorInputState`] that includes a title and an optional value-change callback.
53#[derive(Debug, Clone, PartialEq, PartialOrd)]
54#[repr(C)]
55pub struct ColorInputStateWrapper {
56    pub inner: ColorInputState,
57    pub title: AzString,
58    pub on_value_change: OptionColorInputOnValueChange,
59}
60
61impl Default for ColorInputStateWrapper {
62    fn default() -> Self {
63        Self {
64            inner: ColorInputState::default(),
65            title: AzString::from_const_str("Pick color"),
66            on_value_change: None.into(),
67        }
68    }
69}
70
71/// Holds the current color value of a [`ColorInput`] widget.
72#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
73#[repr(C)]
74pub struct ColorInputState {
75    pub color: ColorU,
76}
77
78impl Default for ColorInputState {
79    fn default() -> Self {
80        Self {
81            color: ColorU {
82                r: 255,
83                g: 255,
84                b: 255,
85                a: 255,
86            },
87        }
88    }
89}
90
91static DEFAULT_COLOR_INPUT_STYLE: &[CssPropertyWithConditions] = &[
92    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
93    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
94    CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(14))),
95    CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(14))),
96    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
97];
98
99impl ColorInput {
100    /// Creates a new `ColorInput` displaying the given color.
101    #[inline]
102    #[must_use]
103    pub fn create(color: ColorU) -> Self {
104        Self {
105            color_input_state: ColorInputStateWrapper {
106                inner: ColorInputState { color },
107                ..Default::default()
108            },
109            style: CssPropertyWithConditionsVec::from_const_slice(DEFAULT_COLOR_INPUT_STYLE),
110        }
111    }
112
113    /// Sets the callback invoked when the color value changes.
114    #[inline]
115    pub fn set_on_value_change<I: Into<ColorInputOnValueChangeCallback>>(
116        &mut self,
117        data: RefAny,
118        callback: I,
119    ) {
120        self.color_input_state.on_value_change = Some(ColorInputOnValueChange {
121            callback: callback.into(),
122            refany: data,
123        })
124        .into();
125    }
126
127    /// Builder-style method to set the value-change callback.
128    #[inline]
129    #[must_use]
130    pub fn with_on_value_change<C: Into<ColorInputOnValueChangeCallback>>(
131        mut self,
132        data: RefAny,
133        callback: C,
134    ) -> Self {
135        self.set_on_value_change(data, callback);
136        self
137    }
138
139    /// Replaces `self` with a default `ColorInput` and returns the previous value.
140    #[inline]
141    #[must_use]
142    pub fn swap_with_default(&mut self) -> Self {
143        let mut s = Self::default();
144        core::mem::swap(&mut s, self);
145        s
146    }
147
148    /// Converts this `ColorInput` into a styled [`Dom`] node with a click callback.
149    #[inline]
150    #[must_use]
151    pub fn dom(self) -> Dom {
152        use azul_core::{
153            callbacks::{CoreCallback, CoreCallbackData},
154            dom::{EventFilter, HoverEventFilter, IdOrClass::Class},
155        };
156
157        let mut style = self.style.into_library_owned_vec();
158        style.push(CssPropertyWithConditions::simple(
159            CssProperty::const_background_content(
160                vec![StyleBackgroundContent::Color(
161                    self.color_input_state.inner.color,
162                )]
163                .into(),
164            ),
165        ));
166
167        Dom::create_div()
168            .with_ids_and_classes(vec![Class("__azul_native_color_input".into())].into())
169            .with_css_props(style.into())
170            .with_callbacks(
171                vec![CoreCallbackData {
172                    event: EventFilter::Hover(HoverEventFilter::MouseUp),
173                    refany: RefAny::new(self.color_input_state),
174                    callback: CoreCallback {
175                        cb: on_color_input_clicked as usize,
176                        ctx: azul_core::refany::OptionRefAny::None,
177                    },
178                }]
179                .into(),
180            )
181    }
182}
183
184extern "C" fn on_color_input_clicked(mut data: RefAny, mut info: CallbackInfo) -> Update {
185    let mut color_input = match data.downcast_mut::<ColorInputStateWrapper>() {
186        Some(s) => s,
187        None => return Update::DoNothing,
188    };
189
190    // No built-in color picker dialog — the on_value_change callback
191    // receives the current color so the caller can open their own picker.
192    let color_input = &mut *color_input;
193    let onvaluechange = &mut color_input.on_value_change;
194    let inner = color_input.inner.clone();
195
196    match onvaluechange.as_mut() {
197        Some(ColorInputOnValueChange {
198            callback,
199            refany: data,
200        }) => (callback.cb)(data.clone(), info.clone(), inner),
201        None => Update::DoNothing,
202    }
203}