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