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}