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 { .. } if style_vars.iter().any(|v| v.is_new()) || style_fn_var_styles.iter().any(|(n, _)| n.is_new()) => {
353 WIDGET.reinit();
354 WIDGET.update_info().layout().render();
355 c.delegated();
356 }
357 _ => {}
358 })
359}
360
361/// Represents a style instance.
362///
363/// Use the [`Style!`] *widget* to declare.
364///
365/// [`Style!`]: struct@Style
366#[derive(Debug, Clone)]
367pub struct StyleBuilder {
368 builder: WidgetBuilder,
369 replace: bool,
370}
371impl Default for StyleBuilder {
372 fn default() -> Self {
373 Self {
374 builder: WidgetBuilder::new(Style::widget_type()),
375 replace: false,
376 }
377 }
378}
379impl StyleBuilder {
380 /// Importance of style properties set by default in style widgets.
381 ///
382 /// Is `Importance::WIDGET - 10`.
383 pub const WIDGET_IMPORTANCE: Importance = Importance(Importance::WIDGET.0 - 10);
384
385 /// Importance of style properties set in style instances.
386 ///
387 /// Is `Importance::INSTANCE - 10`.
388 pub const INSTANCE_IMPORTANCE: Importance = Importance(Importance::INSTANCE.0 - 10);
389
390 /// Negative offset on the position index of style properties.
391 ///
392 /// Is `1`.
393 pub const POSITION_OFFSET: u16 = 1;
394
395 /// New style from a widget builder.
396 ///
397 /// The importance and position index of properties are adjusted,
398 /// any custom build or widget build action is ignored.
399 pub fn from_builder(mut wgt: WidgetBuilder) -> StyleBuilder {
400 wgt.clear_build_actions();
401 wgt.clear_custom_build();
402 let replace = wgt.capture_value_or_default(property_id!(self::replace));
403 for p in wgt.properties_mut() {
404 *p.importance = match *p.importance {
405 Importance::WIDGET => StyleBuilder::WIDGET_IMPORTANCE,
406 Importance::INSTANCE => StyleBuilder::INSTANCE_IMPORTANCE,
407 other => other,
408 };
409 p.position.index = p.position.index.saturating_sub(Self::POSITION_OFFSET);
410 }
411 StyleBuilder { builder: wgt, replace }
412 }
413
414 /// Unwrap the style dynamic widget.
415 pub fn into_builder(self) -> WidgetBuilder {
416 self.builder
417 }
418
419 /// Override or replace `self` with `other`.
420 pub fn extend(&mut self, other: StyleBuilder) {
421 if other.is_replace() {
422 *self = other;
423 } else {
424 self.builder.extend(other.builder);
425 }
426 }
427
428 /// Override `self` with items set in the instance of `other` if both are the same type. Otherwise replace `self` with `other`.
429 ///
430 /// `self` is the [`named_style_fn`] and `other` is the style instance set in `style_fn`.
431 ///
432 /// [`named_style_fn`]: fn@named_style_fn
433 pub fn extend_named(&mut self, other: StyleBuilder) {
434 if self.builder.widget_type() != other.builder.widget_type() {
435 *self = other;
436 } else {
437 self.builder.extend_important(other.builder, Self::INSTANCE_IMPORTANCE);
438 }
439 }
440
441 /// if the style removes all contextual properties.
442 pub fn is_replace(&self) -> bool {
443 self.replace
444 }
445
446 /// If the style does nothing.
447 pub fn is_empty(&self) -> bool {
448 !self.builder.has_properties() && !self.builder.has_whens() && !self.builder.has_unsets()
449 }
450}
451impl From<StyleBuilder> for WidgetBuilder {
452 fn from(t: StyleBuilder) -> Self {
453 t.into_builder()
454 }
455}
456impl From<WidgetBuilder> for StyleBuilder {
457 fn from(p: WidgetBuilder) -> Self {
458 StyleBuilder::from_builder(p)
459 }
460}
461impl_from_and_into_var! {
462 /// Singleton.
463 fn from(style: StyleBuilder) -> StyleFn {
464 StyleFn::singleton(style)
465 }
466}
467
468/// Arguments for [`StyleFn`] closure.
469///
470/// Empty struct, there are no style args in the current release, this struct is declared so that if
471/// args may be introduced in the future with minimal breaking changes.
472#[derive(Debug, Default)]
473#[non_exhaustive]
474pub struct StyleArgs {}
475
476/// Boxed shared closure that generates a style instance for a given widget context.
477///
478/// You can also use the [`style_fn!`] macro to instantiate.
479#[derive(Clone)]
480pub struct StyleFn(Option<Arc<dyn Fn(&StyleArgs) -> Option<StyleBuilder> + Send + Sync>>);
481impl Default for StyleFn {
482 fn default() -> Self {
483 Self::nil()
484 }
485}
486impl PartialEq for StyleFn {
487 fn eq(&self, other: &Self) -> bool {
488 match (&self.0, &other.0) {
489 (None, None) => true,
490 (Some(a), Some(b)) => Arc::ptr_eq(a, b),
491 _ => false,
492 }
493 }
494}
495impl StyleFn {
496 /// Default function, produces an empty style.
497 pub fn nil() -> Self {
498 Self(None)
499 }
500
501 /// If this function represents no style.
502 pub fn is_nil(&self) -> bool {
503 self.0.is_none()
504 }
505
506 /// New style function, the `func` closure is called for each styleable widget, before the widget is inited.
507 pub fn new(func: impl Fn(&StyleArgs) -> StyleBuilder + Send + Sync + 'static) -> Self {
508 Self(Some(Arc::new(move |a| {
509 let style = func(a);
510 if style.is_empty() { None } else { Some(style) }
511 })))
512 }
513
514 /// New style function that returns clones of `style`.
515 ///
516 /// Note that if the `style` contains properties with node values the nodes will be moved to
517 /// the last usage of the style, as nodes can't be cloned.
518 ///
519 /// Also note that the `style` will stay in memory for the lifetime of the `StyleFn`.
520 pub fn singleton(style: StyleBuilder) -> Self {
521 Self::new(move |_| style.clone())
522 }
523
524 /// Call the function to create a style for the styleable widget in the context.
525 ///
526 /// Returns `None` if [`is_nil`] or empty, otherwise returns the style.
527 ///
528 /// Note that you can call the style function directly:
529 ///
530 /// ```
531 /// # use zng_wgt_style::{StyleFn, StyleArgs};
532 /// fn foo(func: &StyleFn) {
533 /// let a = func.call(&StyleArgs::default());
534 /// let b = func(&StyleArgs::default());
535 /// }
536 /// ```
537 ///
538 /// In the example above `a` and `b` are both calls to the style function.
539 ///
540 /// [`is_nil`]: Self::is_nil
541 pub fn call(&self, args: &StyleArgs) -> Option<StyleBuilder> {
542 self.0.as_ref()?(args)
543 }
544
545 /// New style function that instantiates `self` and `other` and then [`extend`] `self` with `other`.
546 ///
547 /// [`extend`]: StyleBuilder::extend
548 pub fn with_extend(self, other: StyleFn) -> StyleFn {
549 if self.is_nil() {
550 other
551 } else if other.is_nil() {
552 self
553 } else {
554 StyleFn::new(move |args| match (self(args), other(args)) {
555 (Some(mut a), Some(b)) => {
556 a.extend(b);
557 a
558 }
559 (Some(r), None) | (None, Some(r)) => r,
560 _ => StyleBuilder::default(),
561 })
562 }
563 }
564}
565impl fmt::Debug for StyleFn {
566 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567 write!(f, "StyleFn(_)")
568 }
569}
570impl ops::Deref for StyleFn {
571 type Target = dyn Fn(&StyleArgs) -> Option<StyleBuilder>;
572
573 fn deref(&self) -> &Self::Target {
574 if let Some(func) = &self.0 { &**func } else { &nil_func }
575 }
576}
577fn nil_func(_: &StyleArgs) -> Option<StyleBuilder> {
578 None
579}
580
581/// <span data-del-macro-root></span> Declares a style function closure.
582///
583/// The output type is a [`StyleFn`], the input can be a function name path or a closure,
584/// with input type `&StyleArgs`. The closure syntax is clone-move ([`clmv!`]).
585///
586/// # Examples
587///
588/// The example below declares a closure that prints every time it is used, the closure captures `cloned` by clone-move
589/// and `moved` by move. The closure ignores the [`StyleArgs`] because it is empty.
590///
591/// ```
592/// # zng_wgt::enable_widget_macros!();
593/// # use zng_wgt::prelude::*;
594/// # use zng_wgt_style::*;
595/// # fn main() {
596/// let cloned = var(10u32);
597/// let moved = var(20u32);
598/// let style_fn = style_fn!(cloned, |_| {
599/// println!(
600/// "style instantiated in {:?}, with captured values, {} and {}",
601/// WIDGET.try_id(),
602/// cloned.get(),
603/// moved.get()
604/// );
605///
606/// Style! {
607/// // ..
608/// }
609/// });
610/// # }
611/// ```
612///
613/// [`clmv!`]: zng_wgt::prelude::clmv
614#[macro_export]
615macro_rules! style_fn {
616 ($fn:path) => {
617 $crate::StyleFn::new($fn)
618 };
619 ($($tt:tt)+) => {
620 $crate::StyleFn::new($crate::__clmv! {
621 $($tt)+
622 })
623 };
624 () => {
625 $crate::StyleFn::nil()
626 };
627}
628
629context_var! {
630 static MISSING_STYLE_VAR: StyleFn = StyleFn::nil();
631}
632#[property(WIDGET)]
633fn missing_style_fn(child: impl IntoUiNode, _s: impl IntoVar<StyleFn>) -> UiNode {
634 child.into_node()
635}