zng_wgt_style/lib.rs
1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/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`](#replace) on a style to `true` to fully remove all contextual properties and only use the
34/// new style properties.
35///
36/// # Inherit Style
37///
38/// Note that you can declare a custom style *widget* using the same inheritance mechanism of normal widgets, as long
39/// as they build to [`StyleBuilder`]. This is different from the *extend/replace* mechanism as it operates on the style
40/// type, not the instances.
41#[widget($crate::Style)]
42pub struct Style(zng_app::widget::base::NonWidgetBase);
43impl Style {
44 /// Build the style.
45 pub fn widget_build(&mut self) -> StyleBuilder {
46 StyleBuilder::from_builder(self.widget_take())
47 }
48}
49
50/// Fully replace the contextual style.
51///
52/// This is not enabled by default, if set to `true` the contextual style properties are removed.
53#[property(WIDGET, capture, default(false), widget_impl(Style))]
54pub fn replace(replace: impl IntoValue<bool>) {}
55
56/// Styleable widget mixin.
57///
58/// Widgets that inherit this mix-in have a `style_fn` property that can be set to a [`style_fn!`]
59/// that generates properties that are dynamically injected into the widget to alter its appearance.
60///
61/// The style mixin drastically affects the widget build process, only the `style_base_fn`, `style_fn` and `when` condition
62/// properties that affects these are instantiated with the widget, all the other properties and intrinsic nodes are instantiated
63/// on init, after the style is generated.
64///
65/// Widgets that inherit this mix-in must call [`style_intrinsic`] in their own `widget_intrinsic`, the call is missing
66/// the widget will log an error on instantiation and only the `style_base_fn` will be used. You can use the [`impl_style_fn!`]
67/// macro to generate the style var and property.
68///
69/// [`style_intrinsic`]: StyleMix::style_intrinsic
70#[widget_mixin]
71pub struct StyleMix<P>(P);
72impl<P: WidgetImpl> StyleMix<P> {
73 fn widget_intrinsic(&mut self) {
74 self.base()
75 .widget_builder()
76 .set_custom_build(|b| StyleMix::<()>::custom_build(b, None));
77 }
78
79 /// Setup the style build.
80 pub fn style_intrinsic(&mut self, style_var: ContextVar<StyleFn>, style_fn: PropertyId) {
81 self.base()
82 .widget_builder()
83 .set_custom_build(move |b| StyleMix::<()>::custom_build(b, Some((style_var, style_fn))));
84 }
85}
86impl<P> StyleMix<P> {
87 /// The custom build that is set on intrinsic by the mixin.
88 pub fn custom_build(mut wgt: WidgetBuilder, cfg: Option<(ContextVar<StyleFn>, PropertyId)>) -> BoxedUiNode {
89 let (style_var, style_id) = cfg.unwrap_or_else(|| {
90 tracing::error!("missing `style_intrinsic` in `{}`", wgt.widget_type().path);
91 (MISSING_STYLE_VAR, property_id!(self::missing_style_fn))
92 });
93
94 // 1 - "split_off" the properties `style_base_fn` and `style_fn`
95 // this moves the properties and any `when` that affects them to a new widget builder.
96 let style_base_id = property_id!(style_base_fn);
97 let mut style_builder = WidgetBuilder::new(wgt.widget_type());
98 wgt.split_off([style_base_id, style_id], &mut style_builder);
99
100 if style_builder.has_properties() {
101 // 2.a - There was a `style_fn` property, build a "mini widget" that is only the style property
102 // and when condition properties that affect it.
103
104 #[cfg(feature = "trace_widget")]
105 wgt.push_build_action(|wgt| {
106 // avoid double trace as the style builder already inserts a widget tracer.
107 wgt.disable_trace_widget();
108 });
109
110 let mut wgt = Some(wgt);
111 style_builder.push_build_action(move |b| {
112 // 3 - The actual style_node and builder is a child of the "mini widget".
113
114 let style_base = b
115 .capture_var::<StyleFn>(style_base_id)
116 .unwrap_or_else(|| LocalVar(StyleFn::nil()).boxed());
117 let style = b
118 .capture_var::<StyleFn>(style_id)
119 .unwrap_or_else(|| LocalVar(StyleFn::nil()).boxed());
120
121 b.set_child(style_node(None, wgt.take().unwrap(), style_base, style_var, style));
122 });
123 // 4 - Build the "mini widget",
124 // if the `style` property was not affected by any `when` this just returns the `StyleNode`.
125 style_builder.build()
126 } else {
127 // 2.b - There was no `style_fn` property, this widget is not styleable, just build the default.
128 wgt.build()
129 }
130 }
131}
132
133#[doc(hidden)]
134pub mod __impl_style_context_util {
135 pub use zng_wgt::prelude::{IntoVar, UiNode, context_var, property};
136}
137
138/// Implements the contextual `STYLE_FN_VAR` and `style_fn`.
139///
140/// This is a helper for [`StyleMix<P>`](struct@StyleMix) implementers, see the `zng::style` module level
141/// documentation for more details.
142#[macro_export]
143macro_rules! impl_style_fn {
144 ($Widget:ty) => {
145 $crate::__impl_style_context_util::context_var! {
146 /// Contextual style variable.
147 ///
148 /// Use [`style_fn`](fn@style_fn) to set.
149 pub static STYLE_FN_VAR: $crate::StyleFn = $crate::StyleFn::nil();
150 }
151
152 /// Extends or replaces the widget style.
153 ///
154 /// Properties and `when` conditions in the style are applied to the widget. Style instances extend the contextual style
155 /// by default, you can set `replace` on a style to `true` to fully replace.
156 #[$crate::__impl_style_context_util::property(WIDGET, default($crate::StyleFn::nil()), widget_impl($Widget))]
157 pub fn style_fn(
158 child: impl $crate::__impl_style_context_util::UiNode,
159 style_fn: impl $crate::__impl_style_context_util::IntoVar<$crate::StyleFn>,
160 ) -> impl $crate::__impl_style_context_util::UiNode {
161 $crate::with_style_fn(child, STYLE_FN_VAR, style_fn)
162 }
163 };
164}
165
166/// Widget's base style. All other styles set using `style_fn` are applied over this style.
167///
168/// Is `nil` by default.
169#[property(WIDGET, capture, default(StyleFn::nil()), widget_impl(StyleMix<P>))]
170pub fn style_base_fn(style: impl IntoVar<StyleFn>) {}
171
172/// Helper for declaring the `style_fn` property.
173///
174/// The [`impl_style_fn!`] macro uses this function as the implementation of `style_fn`.
175pub fn with_style_fn(child: impl UiNode, style_context: ContextVar<StyleFn>, style: impl IntoVar<StyleFn>) -> impl UiNode {
176 with_context_var(
177 child,
178 style_context,
179 merge_var!(style_context, style.into_var(), |base, over| {
180 base.clone().with_extend(over.clone())
181 }),
182 )
183}
184
185fn style_node(
186 child: Option<BoxedUiNode>,
187 builder: WidgetBuilder,
188 captured_style_base: BoxedVar<StyleFn>,
189 style_var: ContextVar<StyleFn>,
190 captured_style: BoxedVar<StyleFn>,
191) -> impl UiNode {
192 let style_vars = [captured_style_base, style_var.boxed(), captured_style];
193 match_node_typed(child, move |c, op| match op {
194 UiNodeOp::Init => {
195 let mut style_builder = StyleBuilder::default();
196 for var in &style_vars {
197 WIDGET.sub_var(var);
198
199 if let Some(style) = var.get().call(&StyleArgs {}) {
200 style_builder.extend(style);
201 }
202 }
203
204 if !style_builder.is_empty() {
205 let mut builder = builder.clone();
206 builder.extend(style_builder.into_builder());
207 *c.child() = Some(builder.default_build());
208 } else {
209 *c.child() = Some(builder.clone().default_build());
210 }
211 }
212 UiNodeOp::Deinit => {
213 c.deinit();
214 *c.child() = None;
215 }
216 UiNodeOp::Update { .. } => {
217 if style_vars.iter().any(|v| v.is_new()) {
218 WIDGET.reinit();
219 WIDGET.update_info().layout().render();
220 c.delegated();
221 }
222 }
223 _ => {}
224 })
225}
226
227/// Represents a style instance.
228///
229/// Use the [`Style!`] *widget* to declare.
230///
231/// [`Style!`]: struct@Style
232#[derive(Debug, Clone)]
233pub struct StyleBuilder {
234 builder: WidgetBuilder,
235 replace: bool,
236}
237impl Default for StyleBuilder {
238 fn default() -> Self {
239 Self {
240 builder: WidgetBuilder::new(Style::widget_type()),
241 replace: false,
242 }
243 }
244}
245impl StyleBuilder {
246 /// Importance of style properties set by default in style widgets.
247 ///
248 /// Is `Importance::WIDGET - 10`.
249 pub const WIDGET_IMPORTANCE: Importance = Importance(Importance::WIDGET.0 - 10);
250
251 /// Importance of style properties set in style instances.
252 ///
253 /// Is `Importance::INSTANCE - 10`.
254 pub const INSTANCE_IMPORTANCE: Importance = Importance(Importance::INSTANCE.0 - 10);
255
256 /// Negative offset on the position index of style properties.
257 ///
258 /// Is `1`.
259 pub const POSITION_OFFSET: u16 = 1;
260
261 /// New style from a widget builder.
262 ///
263 /// The importance and position index of properties are adjusted,
264 /// any custom build or widget build action is ignored.
265 pub fn from_builder(mut wgt: WidgetBuilder) -> StyleBuilder {
266 wgt.clear_build_actions();
267 wgt.clear_custom_build();
268 let replace = wgt.capture_value_or_default(property_id!(self::replace));
269 for p in wgt.properties_mut() {
270 *p.importance = match *p.importance {
271 Importance::WIDGET => StyleBuilder::WIDGET_IMPORTANCE,
272 Importance::INSTANCE => StyleBuilder::INSTANCE_IMPORTANCE,
273 other => other,
274 };
275 p.position.index = p.position.index.saturating_sub(Self::POSITION_OFFSET);
276 }
277 StyleBuilder { builder: wgt, replace }
278 }
279
280 /// Unwrap the style dynamic widget.
281 pub fn into_builder(self) -> WidgetBuilder {
282 self.builder
283 }
284
285 /// Override or replace `self` with `other`.
286 pub fn extend(&mut self, other: StyleBuilder) {
287 if other.is_replace() {
288 *self = other;
289 } else {
290 self.builder.extend(other.builder);
291 }
292 }
293
294 /// if the style removes all contextual properties.
295 pub fn is_replace(&self) -> bool {
296 self.replace
297 }
298
299 /// If the style does nothing.
300 pub fn is_empty(&self) -> bool {
301 !self.builder.has_properties() && !self.builder.has_whens() && !self.builder.has_unsets()
302 }
303}
304impl From<StyleBuilder> for WidgetBuilder {
305 fn from(t: StyleBuilder) -> Self {
306 t.into_builder()
307 }
308}
309impl From<WidgetBuilder> for StyleBuilder {
310 fn from(p: WidgetBuilder) -> Self {
311 StyleBuilder::from_builder(p)
312 }
313}
314impl_from_and_into_var! {
315 /// Singleton.
316 fn from(style: StyleBuilder) -> StyleFn {
317 StyleFn::singleton(style)
318 }
319}
320
321/// Arguments for [`StyleFn`] closure.
322///
323/// Empty struct, there are no style args in the current release, this struct is declared so that if
324/// args may be introduced in the future with minimal breaking changes.
325#[derive(Debug, Default)]
326pub struct StyleArgs {}
327
328/// Boxed shared closure that generates a style instance for a given widget context.
329///
330/// You can also use the [`style_fn!`] macro to instantiate.
331#[derive(Clone)]
332pub struct StyleFn(Option<Arc<dyn Fn(&StyleArgs) -> Option<StyleBuilder> + Send + Sync>>);
333impl Default for StyleFn {
334 fn default() -> Self {
335 Self::nil()
336 }
337}
338impl PartialEq for StyleFn {
339 fn eq(&self, other: &Self) -> bool {
340 match (&self.0, &other.0) {
341 (None, None) => true,
342 (Some(a), Some(b)) => Arc::ptr_eq(a, b),
343 _ => false,
344 }
345 }
346}
347impl StyleFn {
348 /// Default function, produces an empty style.
349 pub fn nil() -> Self {
350 Self(None)
351 }
352
353 /// If this function represents no style.
354 pub fn is_nil(&self) -> bool {
355 self.0.is_none()
356 }
357
358 /// New style function, the `func` closure is called for each styleable widget, before the widget is inited.
359 pub fn new(func: impl Fn(&StyleArgs) -> StyleBuilder + Send + Sync + 'static) -> Self {
360 Self(Some(Arc::new(move |a| {
361 let style = func(a);
362 if style.is_empty() { None } else { Some(style) }
363 })))
364 }
365
366 /// New style function that returns clones of `style`.
367 ///
368 /// Note that if the `style` contains properties with node values the nodes will be moved to
369 /// the last usage of the style, as nodes can't be cloned.
370 ///
371 /// Also note that the `style` will stay in memory for the lifetime of the `StyleFn`.
372 pub fn singleton(style: StyleBuilder) -> Self {
373 Self::new(move |_| style.clone())
374 }
375
376 /// Call the function to create a style for the styleable widget in the context.
377 ///
378 /// Returns `None` if [`is_nil`] or empty, otherwise returns the style.
379 ///
380 /// Note that you can call the style function directly:
381 ///
382 /// ```
383 /// # use zng_wgt_style::{StyleFn, StyleArgs};
384 /// fn foo(func: &StyleFn) {
385 /// let a = func.call(&StyleArgs {});
386 /// let b = func(&StyleArgs {});
387 /// }
388 /// ```
389 ///
390 /// In the example above `a` and `b` are both calls to the style function.
391 ///
392 /// [`is_nil`]: Self::is_nil
393 pub fn call(&self, args: &StyleArgs) -> Option<StyleBuilder> {
394 self.0.as_ref()?(args)
395 }
396
397 /// New style function that instantiates `self` and `other` and then [`extend`] `self` with `other`.
398 ///
399 /// [`extend`]: StyleBuilder::extend
400 pub fn with_extend(self, other: StyleFn) -> StyleFn {
401 if self.is_nil() {
402 other
403 } else if other.is_nil() {
404 self
405 } else {
406 StyleFn::new(move |args| match (self(args), other(args)) {
407 (Some(mut a), Some(b)) => {
408 a.extend(b);
409 a
410 }
411 (Some(r), None) | (None, Some(r)) => r,
412 _ => StyleBuilder::default(),
413 })
414 }
415 }
416}
417impl fmt::Debug for StyleFn {
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 write!(f, "StyleFn(_)")
420 }
421}
422impl ops::Deref for StyleFn {
423 type Target = dyn Fn(&StyleArgs) -> Option<StyleBuilder>;
424
425 fn deref(&self) -> &Self::Target {
426 if let Some(func) = &self.0 { &**func } else { &nil_func }
427 }
428}
429fn nil_func(_: &StyleArgs) -> Option<StyleBuilder> {
430 None
431}
432
433/// <span data-del-macro-root></span> Declares a style function closure.
434///
435/// The output type is a [`StyleFn`], the input can be a function name path or a closure,
436/// with input type `&StyleArgs`. The closure syntax is clone-move ([`clmv!`]).
437///
438/// # Examples
439///
440/// The example below declares a closure that prints every time it is used, the closure captures `cloned` by clone-move
441/// and `moved` by move. The closure ignores the [`StyleArgs`] because it is empty.
442///
443/// ```
444/// # zng_wgt::enable_widget_macros!();
445/// # use zng_wgt::prelude::*;
446/// # use zng_wgt_style::*;
447/// # fn main() {
448/// let cloned = var(10u32);
449/// let moved = var(20u32);
450/// let style_fn = style_fn!(cloned, |_| {
451/// println!(
452/// "style instantiated in {:?}, with captured values, {} and {}",
453/// WIDGET.try_id(),
454/// cloned.get(),
455/// moved.get()
456/// );
457///
458/// Style! {
459/// // ..
460/// }
461/// });
462/// # }
463/// ```
464///
465/// [`clmv!`]: zng_wgt::prelude::clmv
466#[macro_export]
467macro_rules! style_fn {
468 ($fn:path) => {
469 $crate::StyleFn::new($fn)
470 };
471 ($($tt:tt)+) => {
472 $crate::StyleFn::new($crate::__clmv! {
473 $($tt)+
474 })
475 };
476 () => {
477 $crate::StyleFn::nil()
478 };
479}
480
481context_var! {
482 static MISSING_STYLE_VAR: StyleFn = StyleFn::nil();
483}
484#[property(WIDGET)]
485fn missing_style_fn(child: impl UiNode, _s: impl IntoVar<StyleFn>) -> impl UiNode {
486 child
487}