zng_wgt_style/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Style building blocks.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11// suppress nag about very simple boxed closure signatures.
12#![expect(clippy::type_complexity)]
13
14use zng_app::widget::builder::{Importance, PropertyId};
15use zng_wgt::prelude::*;
16
17#[doc(hidden)]
18pub use zng_wgt::prelude::clmv as __clmv;
19
20use std::sync::Arc;
21use std::{fmt, ops};
22
23/// Represents a set of properties that can be applied to any styleable widget.
24///
25/// Style can be instantiated using the same syntax as any widget, but it produces a [`StyleBuilder`]
26/// instance instead of a widget. Widgets that have [`StyleMix<P>`] can be modified using properties
27/// defined in a style, the properties are dynamically spliced into each widget instance.
28///
29/// # Extend/Replace
30///
31/// Style instances extend the contextual style by default, meaning all properties set on the style are inserted over
32/// the parent style, so properties set on the contextual style that are not reset in the new style are retained. You
33/// can set [`replace`](fn@replace) on a style to `true` to fully remove all contextual properties and only use the
34/// new style properties.
35///
36/// ## Named Styles
37///
38/// Styleable widgets have one contextual style by default, usually defined by [`impl_style_fn!`] the `style_fn` property
39/// implements the extend/replace mixing of the style, tracked by a `STYLE_FN_VAR`.
40///
41/// This same pattern can be used to define alternate named styles, these styles set [`named_style_fn`](fn@named_style_fn) to another
42/// context variable that defines the style context, on widget instantiation this other context will be used instead of the default one.
43/// You can use [`impl_named_style_fn!`] to declare most of the boilerplate.
44///
45/// # Inherit Style
46///
47/// Note that you can declare a custom style *widget* using the same inheritance mechanism of normal widgets, as long
48/// as they build to [`StyleBuilder`]. This is different from the *extend/replace* mechanism as it operates on the style
49/// type, not the instances. A style that inherits a `named_style_fn` will not inherit that named context either, each named
50/// context property is strongly associated with a single style type only.
51#[widget($crate::Style)]
52pub struct Style(zng_app::widget::base::NonWidgetBase);
53impl Style {
54    /// Build the style.
55    pub fn widget_build(&mut self) -> StyleBuilder {
56        StyleBuilder::from_builder(self.widget_take())
57    }
58}
59
60/// Fully replace the contextual style.
61///
62/// This is not enabled by default, if set to `true` the contextual style properties are removed.
63#[property(WIDGET, default(false), widget_impl(Style))]
64pub fn replace(wgt: &mut WidgetBuilding, replace: impl IntoValue<bool>) {
65    let _ = replace;
66    wgt.expect_property_capture();
67}
68
69/// Set in the default properties of a named style to define the contextual variable for that style.
70///
71/// During widget instantiation, if this is set by default in a style the contextual style is used as the *defaults* and only the
72/// properties set on the style instance *replace* them.
73///
74/// This property is part of the *named styles* pattern, see [`impl_named_style_fn!`] for more details.
75///
76/// Note that this property expects a `ContextVar<StyleFn>` as a value, not a variable directly, it will also only work if
77/// set in the default properties of a style type.
78#[property(WIDGET, widget_impl(Style))]
79pub fn named_style_fn(wgt: &mut WidgetBuilding, name: impl IntoValue<NamedStyleVar>) {
80    let _ = name;
81    wgt.expect_property_capture();
82}
83
84/// Represents a `ContextVar<StyleFn>` that defines a named style.
85///
86/// See [`named_style_fn`](fn@named_style_fn) for more details.
87#[derive(Clone, Copy)]
88pub struct NamedStyleVar(ContextVar<StyleFn>);
89impl fmt::Debug for NamedStyleVar {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_tuple("NamedStyleVar").finish_non_exhaustive()
92    }
93}
94impl PartialEq for NamedStyleVar {
95    fn eq(&self, other: &Self) -> bool {
96        self.0.var_eq(&other.0)
97    }
98}
99impl_from_and_into_var! {
100    fn from(var: ContextVar<StyleFn>) -> NamedStyleVar {
101        NamedStyleVar(var)
102    }
103}
104impl IntoVar<StyleFn> for NamedStyleVar {
105    fn into_var(self) -> Var<StyleFn> {
106        self.0.into_var()
107    }
108}
109impl ops::Deref for NamedStyleVar {
110    type Target = ContextVar<StyleFn>;
111
112    fn deref(&self) -> &Self::Target {
113        &self.0
114    }
115}
116
117/// Styleable widget mixin.
118///
119/// Widgets that inherit this mixin have a `style_fn` property that can be set to a [`style_fn!`]
120/// that generates properties that are dynamically injected into the widget to alter its appearance.
121///
122/// The style mixin drastically affects the widget build process, only the `style_fn` and `when` condition
123/// properties that affects it are instantiated with the widget, all the other properties and intrinsic nodes are instantiated
124/// on init, after the style is generated.
125///
126/// Widgets that inherit this mixin must call [`style_intrinsic`] in their own `widget_intrinsic`, if the call is missing
127/// the widget will log an error on instantiation. You can use the [`impl_style_fn!`] macro to generate the style var and property.
128///
129/// [`style_intrinsic`]: StyleMix::style_intrinsic
130#[widget_mixin]
131pub struct StyleMix<P>(P);
132impl<P: WidgetImpl> StyleMix<P> {
133    fn widget_intrinsic(&mut self) {
134        self.base()
135            .widget_builder()
136            .set_custom_build(|b| StyleMix::<()>::custom_build(b, None));
137    }
138
139    /// Setup the style build.
140    pub fn style_intrinsic(&mut self, style_var: ContextVar<StyleFn>, style_fn: PropertyId) {
141        self.base()
142            .widget_builder()
143            .set_custom_build(move |b| StyleMix::<()>::custom_build(b, Some((style_var, style_fn))));
144    }
145}
146impl<P> StyleMix<P> {
147    /// The custom build that is set on intrinsic by the mixin.
148    pub fn custom_build(mut wgt: WidgetBuilder, cfg: Option<(ContextVar<StyleFn>, PropertyId)>) -> UiNode {
149        let (style_var, style_id) = cfg.unwrap_or_else(|| {
150            tracing::error!("missing `style_intrinsic` in `{}`", wgt.widget_type().path);
151            (MISSING_STYLE_VAR, property_id!(self::missing_style_fn))
152        });
153
154        // 1 - "split_off" the property `style_fn`
155        //     this moves the properties and any `when` that affects them to a new widget builder.
156        let mut style_builder = WidgetBuilder::new(wgt.widget_type());
157        wgt.split_off([style_id], &mut style_builder);
158
159        // 2 - build a "mini widget" that is only the intrinsic default style var,
160        //     `style_fn` property and when condition properties that affect `style_fn`.
161
162        #[cfg(feature = "trace_widget")]
163        wgt.push_build_action(|wgt| {
164            // avoid double trace as the style builder already inserts a widget tracer.
165            wgt.disable_trace_widget();
166        });
167
168        let mut wgt = Some(wgt);
169        style_builder.push_build_action(move |b| {
170            // 3 - The actual style_node and builder is a child of the "mini widget".
171
172            let style = b.capture_var::<StyleFn>(style_id).unwrap_or_else(|| const_var(StyleFn::nil()));
173
174            b.set_child(style_node(UiNode::nil(), wgt.take().unwrap(), style_var, style));
175        });
176        // 4 - Build the "mini widget"
177        style_builder.build()
178    }
179}
180
181#[doc(hidden)]
182pub mod __impl_style_context_util {
183    pub use pastey::paste;
184    pub use zng_wgt::prelude::{IntoUiNode, IntoVar, UiNode, context_var, property};
185}
186
187/// Implements the contextual `STYLE_FN_VAR` and `style_fn`.
188///
189/// This is a helper for [`StyleMix<P>`](struct@StyleMix) implementers, see the `zng::style` module level
190/// documentation for more details.
191#[macro_export]
192macro_rules! impl_style_fn {
193    ($Widget:path, $DefaultStyle:path) => {
194        $crate::__impl_style_context_util::paste! {
195            $crate::__impl_style_context_util::context_var! {
196                /// Contextual style variable.
197                ///
198                /// Use [`style_fn`](fn@style_fn) to set.
199                ///
200                #[doc = "Is `" $DefaultStyle "!` by default."]
201                pub static STYLE_FN_VAR: $crate::StyleFn = $crate::style_fn!(|_| $DefaultStyle!());
202            }
203        }
204
205        /// Extends or replaces the widget style.
206        ///
207        /// Properties and `when` conditions in the style are applied to the widget. Style instances extend the contextual style
208        /// by default, you can set `replace` on a style to `true` to fully replace.
209        #[$crate::__impl_style_context_util::property(WIDGET, default($crate::StyleFn::nil()), widget_impl($Widget))]
210        pub fn style_fn(
211            child: impl $crate::__impl_style_context_util::IntoUiNode,
212            style_fn: impl $crate::__impl_style_context_util::IntoVar<$crate::StyleFn>,
213        ) -> $crate::__impl_style_context_util::UiNode {
214            $crate::with_style_fn(child, STYLE_FN_VAR, style_fn)
215        }
216    };
217}
218
219/// Implements a `NAMED_STYLE_FN_VAR`, `named_style_fn` and `NamedStyle!` items.
220///
221/// This is a helper for declaring *named styles* that can be modified contextually, just like the default style.
222///
223/// # Examples
224///
225/// The example bellow declares a `FooStyle` manually, this is a normal definition for a named style. This macro generates
226/// a `foo_style_fn` property and a `FOO_STYLE_FN_VAR` context var. Note that the manual style implementation must set the
227/// [`named_style_fn`](fn@named_style_fn), otherwise the style will not inherit from the correct *name*.
228///
229/// ```
230/// # macro_rules! example { () => {
231/// /// Foo style.
232/// #[widget($crate::FooStyle)]
233/// pub struct FooStyle(Style);
234/// impl FooStyle {
235///     fn widget_intrinsic(&mut self) {
236///         widget_set! {
237///             self;
238///             style_fn_var = FOO_STYLE_FN_VAR;
239///
240///             // .. style properties here
241///         }
242///     }
243/// }
244/// impl_named_style_fn!(foo, FooStyle);
245/// # };}
246/// ```
247#[macro_export]
248macro_rules! impl_named_style_fn {
249    ($name:ident, $NamedStyle:ty) => {
250        $crate::__impl_style_context_util::paste! {
251            $crate::__impl_style_context_util::context_var! {
252                /// Contextual style variable.
253                ///
254                #[doc = "Use [`" $name "_style_fn`](fn@" $name "_style_fn) to set."]
255                pub static [<$name:upper _STYLE_FN_VAR>]: $crate::StyleFn = $crate::style_fn!(|_| $NamedStyle!());
256            }
257
258            #[doc = "Extends or replaces the [`" $NamedStyle "!`](struct@" $NamedStyle ")."]
259            ///
260            /// Properties and `when` conditions set here are applied to widgets using the style.
261            ///
262            /// Note that style instances extend the contextual style by default,
263            /// you can set `replace` on a style to `true` to fully replace.
264            #[$crate::__impl_style_context_util::property(WIDGET, default($crate::StyleFn::nil()))]
265            pub fn [<$name _style_fn>](
266                child: impl $crate::__impl_style_context_util::IntoUiNode,
267                style_fn: impl $crate::__impl_style_context_util::IntoVar<$crate::StyleFn>,
268            ) -> $crate::__impl_style_context_util::UiNode {
269                $crate::with_style_fn(child, [<$name:upper _STYLE_FN_VAR>], style_fn)
270            }
271        }
272    };
273}
274
275/// Helper for declaring the `style_fn` property.
276///
277/// The [`impl_style_fn!`] and [`impl_named_style_fn!`] macros uses this function as the implementation of `style_fn`.
278pub fn with_style_fn(child: impl IntoUiNode, style_context: ContextVar<StyleFn>, style: impl IntoVar<StyleFn>) -> UiNode {
279    with_context_var(
280        child,
281        style_context,
282        merge_var!(style_context, style.into_var(), |base, over| {
283            base.clone().with_extend(over.clone())
284        }),
285    )
286}
287
288fn style_node(child: UiNode, widget_builder: WidgetBuilder, style_var: ContextVar<StyleFn>, captured_style: Var<StyleFn>) -> UiNode {
289    let style_vars = [style_var.into_var(), captured_style];
290    let mut style_fn_var_styles = vec![];
291    match_node(child, move |c, op| match op {
292        UiNodeOp::Init => {
293            // the final style builder
294            let mut style_builder = StyleBuilder::default();
295            for var in &style_vars {
296                // each style var is subscribed and extend/replaces the previous
297                WIDGET.sub_var(var);
298
299                if let Some(mut style) = var.get().call(&StyleArgs {}) {
300                    // style var was set
301
302                    let named_style_fn = property_id!(named_style_fn);
303                    if let Some(p) = style.builder.property(named_style_fn) {
304                        if p.importance != StyleBuilder::WIDGET_IMPORTANCE {
305                            tracing::warn!("ignoring `named_style_fn` not set as default")
306                        } else {
307                            // style is *named*, the contextual named style is used, only the items explicitly set
308                            // on the style override the contextual named style.
309
310                            let named_style = style
311                                .builder
312                                .capture_value::<NamedStyleVar>(named_style_fn)
313                                .unwrap()
314                                .current_context();
315
316                            if let Some(mut from) = named_style.get().call(&StyleArgs {}) {
317                                // contextual named style is set
318                                let _ = from.builder.capture_value::<NamedStyleVar>(named_style_fn); // cleanup capture-only property
319
320                                // only override instance set properties/whens
321
322                                from.extend_named(style);
323                                style = from;
324                            }
325
326                            // subscribe to the contextual named style
327                            let handle = named_style.subscribe(UpdateOp::Update, WIDGET.id());
328                            style_fn_var_styles.push((named_style, handle));
329                        }
330                    }
331
332                    // extend/replace
333                    style_builder.extend(style);
334                }
335            }
336
337            if !style_builder.is_empty() {
338                // apply style items and build actual widget
339                let mut builder = widget_builder.clone();
340                builder.extend(style_builder.into_builder());
341                *c.node() = builder.default_build();
342            } else {
343                // no styles set, just build widget directly
344                *c.node() = widget_builder.clone().default_build();
345            }
346        }
347        UiNodeOp::Deinit => {
348            c.deinit();
349            *c.node() = UiNode::nil();
350            style_fn_var_styles.clear();
351        }
352        UiNodeOp::Update { .. } => {
353            if style_vars.iter().any(|v| v.is_new()) || style_fn_var_styles.iter().any(|(n, _)| n.is_new()) {
354                WIDGET.reinit();
355                WIDGET.update_info().layout().render();
356                c.delegated();
357            }
358        }
359        _ => {}
360    })
361}
362
363/// Represents a style instance.
364///
365/// Use the [`Style!`] *widget* to declare.
366///
367/// [`Style!`]: struct@Style
368#[derive(Debug, Clone)]
369pub struct StyleBuilder {
370    builder: WidgetBuilder,
371    replace: bool,
372}
373impl Default for StyleBuilder {
374    fn default() -> Self {
375        Self {
376            builder: WidgetBuilder::new(Style::widget_type()),
377            replace: false,
378        }
379    }
380}
381impl StyleBuilder {
382    /// Importance of style properties set by default in style widgets.
383    ///
384    /// Is `Importance::WIDGET - 10`.
385    pub const WIDGET_IMPORTANCE: Importance = Importance(Importance::WIDGET.0 - 10);
386
387    /// Importance of style properties set in style instances.
388    ///
389    /// Is `Importance::INSTANCE - 10`.
390    pub const INSTANCE_IMPORTANCE: Importance = Importance(Importance::INSTANCE.0 - 10);
391
392    /// Negative offset on the position index of style properties.
393    ///
394    /// Is `1`.
395    pub const POSITION_OFFSET: u16 = 1;
396
397    /// New style from a widget builder.
398    ///
399    /// The importance and position index of properties are adjusted,
400    /// any custom build or widget build action is ignored.
401    pub fn from_builder(mut wgt: WidgetBuilder) -> StyleBuilder {
402        wgt.clear_build_actions();
403        wgt.clear_custom_build();
404        let replace = wgt.capture_value_or_default(property_id!(self::replace));
405        for p in wgt.properties_mut() {
406            *p.importance = match *p.importance {
407                Importance::WIDGET => StyleBuilder::WIDGET_IMPORTANCE,
408                Importance::INSTANCE => StyleBuilder::INSTANCE_IMPORTANCE,
409                other => other,
410            };
411            p.position.index = p.position.index.saturating_sub(Self::POSITION_OFFSET);
412        }
413        StyleBuilder { builder: wgt, replace }
414    }
415
416    /// Unwrap the style dynamic widget.
417    pub fn into_builder(self) -> WidgetBuilder {
418        self.builder
419    }
420
421    /// Override or replace `self` with `other`.
422    pub fn extend(&mut self, other: StyleBuilder) {
423        if other.is_replace() {
424            *self = other;
425        } else {
426            self.builder.extend(other.builder);
427        }
428    }
429
430    /// Override `self` with items set in the instance of `other` if both are the same type. Otherwise replace `self` with `other`.
431    ///
432    /// `self` is the [`named_style_fn`] and `other` is the style instance set in `style_fn`.
433    ///
434    /// [`named_style_fn`]: fn@named_style_fn
435    pub fn extend_named(&mut self, other: StyleBuilder) {
436        if self.builder.widget_type() != other.builder.widget_type() {
437            *self = other;
438        } else {
439            self.builder.extend_important(other.builder, Self::INSTANCE_IMPORTANCE);
440        }
441    }
442
443    /// if the style removes all contextual properties.
444    pub fn is_replace(&self) -> bool {
445        self.replace
446    }
447
448    /// If the style does nothing.
449    pub fn is_empty(&self) -> bool {
450        !self.builder.has_properties() && !self.builder.has_whens() && !self.builder.has_unsets()
451    }
452}
453impl From<StyleBuilder> for WidgetBuilder {
454    fn from(t: StyleBuilder) -> Self {
455        t.into_builder()
456    }
457}
458impl From<WidgetBuilder> for StyleBuilder {
459    fn from(p: WidgetBuilder) -> Self {
460        StyleBuilder::from_builder(p)
461    }
462}
463impl_from_and_into_var! {
464    /// Singleton.
465    fn from(style: StyleBuilder) -> StyleFn {
466        StyleFn::singleton(style)
467    }
468}
469
470/// Arguments for [`StyleFn`] closure.
471///
472/// Empty struct, there are no style args in the current release, this struct is declared so that if
473/// args may be introduced in the future with minimal breaking changes.
474#[derive(Debug, Default)]
475#[non_exhaustive]
476pub struct StyleArgs {}
477
478/// Boxed shared closure that generates a style instance for a given widget context.
479///
480/// You can also use the [`style_fn!`] macro to instantiate.
481#[derive(Clone)]
482pub struct StyleFn(Option<Arc<dyn Fn(&StyleArgs) -> Option<StyleBuilder> + Send + Sync>>);
483impl Default for StyleFn {
484    fn default() -> Self {
485        Self::nil()
486    }
487}
488impl PartialEq for StyleFn {
489    fn eq(&self, other: &Self) -> bool {
490        match (&self.0, &other.0) {
491            (None, None) => true,
492            (Some(a), Some(b)) => Arc::ptr_eq(a, b),
493            _ => false,
494        }
495    }
496}
497impl StyleFn {
498    /// Default function, produces an empty style.
499    pub fn nil() -> Self {
500        Self(None)
501    }
502
503    /// If this function represents no style.
504    pub fn is_nil(&self) -> bool {
505        self.0.is_none()
506    }
507
508    /// New style function, the `func` closure is called for each styleable widget, before the widget is inited.
509    pub fn new(func: impl Fn(&StyleArgs) -> StyleBuilder + Send + Sync + 'static) -> Self {
510        Self(Some(Arc::new(move |a| {
511            let style = func(a);
512            if style.is_empty() { None } else { Some(style) }
513        })))
514    }
515
516    /// New style function that returns clones of `style`.
517    ///
518    /// Note that if the `style` contains properties with node values the nodes will be moved to
519    /// the last usage of the style, as nodes can't be cloned.
520    ///
521    /// Also note that the `style` will stay in memory for the lifetime of the `StyleFn`.
522    pub fn singleton(style: StyleBuilder) -> Self {
523        Self::new(move |_| style.clone())
524    }
525
526    /// Call the function to create a style for the styleable widget in the context.
527    ///
528    /// Returns `None` if [`is_nil`] or empty, otherwise returns the style.
529    ///
530    /// Note that you can call the style function directly:
531    ///
532    /// ```
533    /// # use zng_wgt_style::{StyleFn, StyleArgs};
534    /// fn foo(func: &StyleFn) {
535    ///     let a = func.call(&StyleArgs::default());
536    ///     let b = func(&StyleArgs::default());
537    /// }
538    /// ```
539    ///
540    /// In the example above `a` and `b` are both calls to the style function.
541    ///
542    /// [`is_nil`]: Self::is_nil
543    pub fn call(&self, args: &StyleArgs) -> Option<StyleBuilder> {
544        self.0.as_ref()?(args)
545    }
546
547    /// New style function that instantiates `self` and `other` and then [`extend`] `self` with `other`.
548    ///
549    /// [`extend`]: StyleBuilder::extend
550    pub fn with_extend(self, other: StyleFn) -> StyleFn {
551        if self.is_nil() {
552            other
553        } else if other.is_nil() {
554            self
555        } else {
556            StyleFn::new(move |args| match (self(args), other(args)) {
557                (Some(mut a), Some(b)) => {
558                    a.extend(b);
559                    a
560                }
561                (Some(r), None) | (None, Some(r)) => r,
562                _ => StyleBuilder::default(),
563            })
564        }
565    }
566}
567impl fmt::Debug for StyleFn {
568    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569        write!(f, "StyleFn(_)")
570    }
571}
572impl ops::Deref for StyleFn {
573    type Target = dyn Fn(&StyleArgs) -> Option<StyleBuilder>;
574
575    fn deref(&self) -> &Self::Target {
576        if let Some(func) = &self.0 { &**func } else { &nil_func }
577    }
578}
579fn nil_func(_: &StyleArgs) -> Option<StyleBuilder> {
580    None
581}
582
583/// <span data-del-macro-root></span> Declares a style function closure.
584///
585/// The output type is a [`StyleFn`], the input can be a function name path or a closure,
586/// with input type `&StyleArgs`. The closure syntax is clone-move ([`clmv!`]).
587///
588/// # Examples
589///
590/// The example below declares a closure that prints every time it is used, the closure captures `cloned` by clone-move
591/// and `moved` by move. The closure ignores the [`StyleArgs`] because it is empty.
592///
593/// ```
594/// # zng_wgt::enable_widget_macros!();
595/// # use zng_wgt::prelude::*;
596/// # use zng_wgt_style::*;
597/// # fn main() {
598/// let cloned = var(10u32);
599/// let moved = var(20u32);
600/// let style_fn = style_fn!(cloned, |_| {
601///     println!(
602///         "style instantiated in {:?}, with captured values, {} and {}",
603///         WIDGET.try_id(),
604///         cloned.get(),
605///         moved.get()
606///     );
607///
608///     Style! {
609///         // ..
610///     }
611/// });
612/// # }
613/// ```
614///
615/// [`clmv!`]: zng_wgt::prelude::clmv
616#[macro_export]
617macro_rules! style_fn {
618    ($fn:path) => {
619        $crate::StyleFn::new($fn)
620    };
621    ($($tt:tt)+) => {
622        $crate::StyleFn::new($crate::__clmv! {
623            $($tt)+
624        })
625    };
626    () => {
627        $crate::StyleFn::nil()
628    };
629}
630
631context_var! {
632    static MISSING_STYLE_VAR: StyleFn = StyleFn::nil();
633}
634#[property(WIDGET)]
635fn missing_style_fn(child: impl IntoUiNode, _s: impl IntoVar<StyleFn>) -> UiNode {
636    child.into_node()
637}