zng_layout/
context.rs

1//! Layout context.
2
3use std::{fmt, sync::Arc};
4
5use bitflags::bitflags;
6use unicode_bidi::BidiDataSource as _;
7use zng_app_context::context_local;
8use zng_unit::{Factor, Px, PxRect, PxSize, about_eq, about_eq_hash, about_eq_ord, euclid};
9use zng_var::context_var;
10
11use atomic::{Atomic, Ordering::Relaxed};
12
13use crate::unit::{LayoutAxis, Ppi, PxConstraints, PxConstraints2d};
14
15/// Current layout context.
16///
17/// Only available in measure and layout methods.
18pub struct LAYOUT;
19impl LAYOUT {
20    /// Gets the current window layout pass.
21    ///
22    /// Widgets can be layout more then once per window layout pass, you can use this ID to identify such cases.
23    pub fn pass_id(&self) -> LayoutPassId {
24        LAYOUT_PASS_CTX.get_clone()
25    }
26
27    /// Calls `f` in a new layout pass.
28    pub fn with_root_context<R>(&self, pass_id: LayoutPassId, metrics: LayoutMetrics, f: impl FnOnce() -> R) -> R {
29        let mut pass = Some(Arc::new(pass_id));
30        LAYOUT_PASS_CTX.with_context(&mut pass, || self.with_context(metrics, f))
31    }
32
33    /// Calls `f` in a new layout context.
34    pub fn with_context<R>(&self, metrics: LayoutMetrics, f: impl FnOnce() -> R) -> R {
35        let mut ctx = Some(Arc::new(LayoutCtx { metrics }));
36        LAYOUT_CTX.with_context(&mut ctx, f)
37    }
38
39    /// Calls `f` without a layout context.
40    pub fn with_no_context<R>(&self, f: impl FnOnce() -> R) -> R {
41        LAYOUT_CTX.with_default(f)
42    }
43
44    /// Gets the context metrics.
45    pub fn metrics(&self) -> LayoutMetrics {
46        LAYOUT_CTX.get().metrics.clone()
47    }
48
49    /// Capture all layout metrics used in `f`.
50    ///
51    /// Note that the captured mask is not propagated to the current context, you can use [`register_metrics_use`] to propagate
52    /// the returned mask.
53    ///
54    /// [`register_metrics_use`]: Self::register_metrics_use
55    #[inline(always)]
56    pub fn capture_metrics_use<R>(&self, f: impl FnOnce() -> R) -> (LayoutMask, R) {
57        METRICS_USED_CTX.with_context(&mut Some(Arc::new(Atomic::new(LayoutMask::empty()))), || {
58            let r = f();
59            let uses = METRICS_USED_CTX.get().load(Relaxed);
60            (uses, r)
61        })
62    }
63
64    /// Register that the node layout depends on these contextual values.
65    ///
66    /// Note that the value methods already register by the [`LayoutMetrics`] getter methods.
67    pub fn register_metrics_use(&self, uses: LayoutMask) {
68        let ctx = METRICS_USED_CTX.get();
69        let m = ctx.load(Relaxed);
70        ctx.store(m | uses, Relaxed);
71    }
72
73    /// Current size constraints.
74    pub fn constraints(&self) -> PxConstraints2d {
75        LAYOUT_CTX.get().metrics.constraints()
76    }
77
78    /// Current perspective constraints.
79    pub fn z_constraints(&self) -> PxConstraints {
80        LAYOUT_CTX.get().metrics.z_constraints()
81    }
82
83    /// Current length constraints for the given axis.
84    pub fn constraints_for(&self, axis: LayoutAxis) -> PxConstraints {
85        match axis {
86            LayoutAxis::X => self.constraints().x,
87            LayoutAxis::Y => self.constraints().y,
88            LayoutAxis::Z => self.z_constraints(),
89        }
90    }
91
92    /// Calls `f` with the `constraints` in context.
93    pub fn with_constraints<R>(&self, constraints: PxConstraints2d, f: impl FnOnce() -> R) -> R {
94        self.with_context(self.metrics().with_constraints(constraints), f)
95    }
96
97    /// Calls `f` with the `constraints` for perspective in context.
98    pub fn with_z_constraints<R>(&self, constraints: PxConstraints, f: impl FnOnce() -> R) -> R {
99        self.with_context(self.metrics().with_z_constraints(constraints), f)
100    }
101
102    /// Calls `f` with the `constraints` in context.
103    pub fn with_constraints_for<R>(&self, axis: LayoutAxis, constraints: PxConstraints, f: impl FnOnce() -> R) -> R {
104        match axis {
105            LayoutAxis::X => {
106                let mut c = self.constraints();
107                c.x = constraints;
108                self.with_constraints(c, f)
109            }
110            LayoutAxis::Y => {
111                let mut c = self.constraints();
112                c.y = constraints;
113                self.with_constraints(c, f)
114            }
115            LayoutAxis::Z => self.with_z_constraints(constraints, f),
116        }
117    }
118
119    /// Runs a function `f` in a context that has its max size subtracted by `removed` and its final size added by `removed`.
120    pub fn with_sub_size(&self, removed: PxSize, f: impl FnOnce() -> PxSize) -> PxSize {
121        self.with_constraints(self.constraints().with_less_size(removed), f) + removed
122    }
123
124    /// Runs a function `f` in a layout context that has its max size added by `added` and its final size subtracted by `added`.
125    pub fn with_add_size(&self, added: PxSize, f: impl FnOnce() -> PxSize) -> PxSize {
126        self.with_constraints(self.constraints().with_more_size(added), f) - added
127    }
128
129    /// Current inline constraints.
130    pub fn inline_constraints(&self) -> Option<InlineConstraints> {
131        LAYOUT_CTX.get().metrics.inline_constraints()
132    }
133
134    /// Calls `f` with no inline constraints.
135    pub fn with_no_inline<R>(&self, f: impl FnOnce() -> R) -> R {
136        let metrics = self.metrics();
137        if metrics.inline_constraints().is_none() {
138            f()
139        } else {
140            self.with_context(metrics.with_inline_constraints(None), f)
141        }
142    }
143
144    /// Root font size.
145    pub fn root_font_size(&self) -> Px {
146        LAYOUT_CTX.get().metrics.root_font_size()
147    }
148
149    /// Current font size.
150    pub fn font_size(&self) -> Px {
151        LAYOUT_CTX.get().metrics.font_size()
152    }
153
154    /// Calls `f` with `font_size` in the context.
155    pub fn with_font_size<R>(&self, font_size: Px, f: impl FnOnce() -> R) -> R {
156        self.with_context(self.metrics().with_font_size(font_size), f)
157    }
158
159    /// Current viewport size.
160    pub fn viewport(&self) -> PxSize {
161        LAYOUT_CTX.get().metrics.viewport()
162    }
163
164    /// Current smallest dimension of the viewport.
165    pub fn viewport_min(&self) -> Px {
166        LAYOUT_CTX.get().metrics.viewport_min()
167    }
168
169    /// Current largest dimension of the viewport.
170    pub fn viewport_max(&self) -> Px {
171        LAYOUT_CTX.get().metrics.viewport_max()
172    }
173
174    /// Current viewport length for the given axis.
175    pub fn viewport_for(&self, axis: LayoutAxis) -> Px {
176        let vp = self.viewport();
177        match axis {
178            LayoutAxis::X => vp.width,
179            LayoutAxis::Y => vp.height,
180            LayoutAxis::Z => Px::MAX,
181        }
182    }
183
184    /// Calls `f` with `viewport` in the context.
185    pub fn with_viewport<R>(&self, viewport: PxSize, f: impl FnOnce() -> R) -> R {
186        self.with_context(self.metrics().with_viewport(viewport), f)
187    }
188
189    /// Current scale factor.
190    pub fn scale_factor(&self) -> Factor {
191        LAYOUT_CTX.get().metrics.scale_factor()
192    }
193
194    /// Calls `f` with `scale_factor` in the context.
195    pub fn with_scale_factor<R>(&self, scale_factor: Factor, f: impl FnOnce() -> R) -> R {
196        self.with_context(self.metrics().with_scale_factor(scale_factor), f)
197    }
198
199    /// Current screen PPI.
200    pub fn screen_ppi(&self) -> Ppi {
201        LAYOUT_CTX.get().metrics.screen_ppi()
202    }
203
204    /// Calls `f` with `screen_ppi` in the context.
205    pub fn with_screen_ppi<R>(&self, screen_ppi: Ppi, f: impl FnOnce() -> R) -> R {
206        self.with_context(self.metrics().with_screen_ppi(screen_ppi), f)
207    }
208
209    /// Current layout direction.
210    pub fn direction(&self) -> LayoutDirection {
211        LAYOUT_CTX.get().metrics.direction()
212    }
213
214    /// Calls `f` with `direction` in the context.
215    pub fn with_direction<R>(&self, direction: LayoutDirection, f: impl FnOnce() -> R) -> R {
216        self.with_context(self.metrics().with_direction(direction), f)
217    }
218
219    /// Context leftover length for the widget, given the [`Length::Leftover`] value it communicated to the parent.
220    ///
221    /// [`Length::Leftover`]: crate::unit::Length::Leftover
222    pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
223        LAYOUT_CTX.get().metrics.leftover()
224    }
225
226    /// Context leftover length for the given axis.
227    pub fn leftover_for(&self, axis: LayoutAxis) -> Option<Px> {
228        let l = self.leftover();
229
230        match axis {
231            LayoutAxis::X => l.width,
232            LayoutAxis::Y => l.height,
233            LayoutAxis::Z => None,
234        }
235    }
236
237    /// Calls `f` with [`leftover`] set to `with` and `height`.
238    ///
239    /// [`leftover`]: Self::leftover
240    pub fn with_leftover<R>(&self, width: Option<Px>, height: Option<Px>, f: impl FnOnce() -> R) -> R {
241        self.with_context(self.metrics().with_leftover(width, height), f)
242    }
243}
244
245context_local! {
246    static LAYOUT_CTX: LayoutCtx = LayoutCtx::no_context();
247    static LAYOUT_PASS_CTX: LayoutPassId = LayoutPassId::new();
248    static METRICS_USED_CTX: Atomic<LayoutMask> = Atomic::new(LayoutMask::empty());
249}
250
251struct LayoutCtx {
252    metrics: LayoutMetrics,
253}
254impl LayoutCtx {
255    fn no_context() -> Self {
256        panic!("no layout context")
257    }
258}
259
260/// Identifies the layout pass of a window.
261///
262/// This value is different for each window layout, but the same for children of panels that do more then one layout pass.
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
264pub struct LayoutPassId(u32);
265impl LayoutPassId {
266    /// New default.
267    pub const fn new() -> Self {
268        LayoutPassId(0)
269    }
270
271    /// Gets the next layout pass ID.
272    pub const fn next(self) -> LayoutPassId {
273        LayoutPassId(self.0.wrapping_add(1))
274    }
275}
276
277/// Constraints for inline measure.
278///
279/// See [`InlineConstraints`] for more details.
280#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
281#[non_exhaustive]
282pub struct InlineConstraintsMeasure {
283    /// Available space on the first row.
284    pub first_max: Px,
285    /// Current height of the row in the parent. If the widget wraps and defines the first
286    /// row in *this* parent's row, the `mid_clear` value will be the extra space needed to clear
287    /// this minimum or zero if the first row is taller. The widget must use this value to estimate the `mid_clear`
288    /// value and include it in the overall measured height of the widget.
289    pub mid_clear_min: Px,
290}
291impl InlineConstraintsMeasure {
292    /// New constraint.
293    pub fn new(first_max: Px, mid_clear_min: Px) -> Self {
294        Self { first_max, mid_clear_min }
295    }
296}
297
298/// Position of an inline segment set by the inlining parent.
299///
300/// See [`InlineConstraintsLayout::first_segs`] for more details.
301///
302/// [`InlineConstraintsLayout::first_segs`]: crate::context::InlineConstraintsLayout::first_segs
303#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
304#[non_exhaustive]
305pub struct InlineSegmentPos {
306    /// Seg offset to the right from the row origin, in pixels.
307    pub x: f32,
308}
309impl InlineSegmentPos {
310    /// New pos.
311    pub fn new(x: f32) -> Self {
312        Self { x }
313    }
314}
315impl PartialEq for InlineSegmentPos {
316    fn eq(&self, other: &Self) -> bool {
317        about_eq(self.x, other.x, 0.001)
318    }
319}
320impl Eq for InlineSegmentPos {}
321impl std::hash::Hash for InlineSegmentPos {
322    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
323        about_eq_hash(self.x, 0.001, state);
324    }
325}
326impl PartialOrd for InlineSegmentPos {
327    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
328        Some(self.cmp(other))
329    }
330}
331impl Ord for InlineSegmentPos {
332    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
333        about_eq_ord(self.x, other.x, 0.001)
334    }
335}
336
337/// Constraints for inline layout.
338///
339/// See [`InlineConstraints`] for more details.
340#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
341#[non_exhaustive]
342pub struct InlineConstraintsLayout {
343    /// First row rect, defined by the parent.
344    pub first: PxRect,
345    /// Extra space in-between the first row and the mid-rows that must be offset to clear the other segments in the row.
346    pub mid_clear: Px,
347    /// Last row rect, defined by the parent.
348    pub last: PxRect,
349
350    /// Position of inline segments of the first row.
351    pub first_segs: Arc<Vec<InlineSegmentPos>>,
352    /// Position of inline segments of the last row.
353    pub last_segs: Arc<Vec<InlineSegmentPos>>,
354}
355
356impl InlineConstraintsLayout {
357    /// New constraint.
358    pub fn new(
359        first: PxRect,
360        mid_clear: Px,
361        last: PxRect,
362        first_segs: Arc<Vec<InlineSegmentPos>>,
363        last_segs: Arc<Vec<InlineSegmentPos>>,
364    ) -> Self {
365        Self {
366            first,
367            mid_clear,
368            last,
369            first_segs,
370            last_segs,
371        }
372    }
373}
374
375/// Constraints for inline measure or layout.
376#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
377pub enum InlineConstraints {
378    /// Constraints for the measure pass.
379    Measure(InlineConstraintsMeasure),
380    /// Constraints the layout pass.
381    Layout(InlineConstraintsLayout),
382}
383impl InlineConstraints {
384    /// Get the `Measure` data or default.
385    pub fn measure(self) -> InlineConstraintsMeasure {
386        match self {
387            InlineConstraints::Measure(m) => m,
388            InlineConstraints::Layout(l) => InlineConstraintsMeasure {
389                first_max: l.first.width(),
390                mid_clear_min: l.mid_clear,
391            },
392        }
393    }
394
395    /// Get the `Layout` data or default.
396    pub fn layout(self) -> InlineConstraintsLayout {
397        match self {
398            InlineConstraints::Layout(m) => m,
399            InlineConstraints::Measure(_) => Default::default(),
400        }
401    }
402}
403
404/// Layout metrics snapshot.
405///
406/// A snapshot can be taken using the [`LayoutMetrics::snapshot`].
407#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
408#[non_exhaustive]
409pub struct LayoutMetricsSnapshot {
410    /// The [`constraints`].
411    ///
412    /// [`constraints`]: LayoutMetrics::constraints
413    pub constraints: PxConstraints2d,
414
415    /// The [`inline_constraints`].
416    ///
417    /// [`inline_constraints`]: LayoutMetrics::inline_constraints
418    pub inline_constraints: Option<InlineConstraints>,
419
420    /// The [`z_constraints`].
421    ///
422    /// [`z_constraints`]: LayoutMetrics::z_constraints
423    pub z_constraints: PxConstraints,
424
425    /// The [`font_size`].
426    ///
427    /// [`font_size`]: LayoutMetrics::font_size
428    pub font_size: Px,
429    /// The [`root_font_size`].
430    ///
431    /// [`root_font_size`]: LayoutMetrics::root_font_size
432    pub root_font_size: Px,
433    /// The [`scale_factor`].
434    ///
435    /// [`scale_factor`]: LayoutMetrics::scale_factor
436    pub scale_factor: Factor,
437    /// The [`viewport`].
438    ///
439    /// [`viewport`]: LayoutMetrics::viewport
440    pub viewport: PxSize,
441    /// The [`screen_ppi`].
442    ///
443    /// [`screen_ppi`]: LayoutMetrics::screen_ppi
444    pub screen_ppi: Ppi,
445
446    /// The [`direction`].
447    ///
448    /// [`direction`]: LayoutMetrics::direction
449    pub direction: LayoutDirection,
450
451    /// The [`leftover`].
452    ///
453    /// [`leftover`]: LayoutMetrics::leftover
454    pub leftover: euclid::Size2D<Option<Px>, ()>,
455}
456impl LayoutMetricsSnapshot {
457    /// Gets if all of the fields in `mask` are equal between `self` and `other`.
458    pub fn masked_eq(&self, other: &Self, mask: LayoutMask) -> bool {
459        (!mask.contains(LayoutMask::CONSTRAINTS)
460            || (self.constraints == other.constraints
461                && self.z_constraints == other.z_constraints
462                && self.inline_constraints == other.inline_constraints))
463            && (!mask.contains(LayoutMask::FONT_SIZE) || self.font_size == other.font_size)
464            && (!mask.contains(LayoutMask::ROOT_FONT_SIZE) || self.root_font_size == other.root_font_size)
465            && (!mask.contains(LayoutMask::SCALE_FACTOR) || self.scale_factor == other.scale_factor)
466            && (!mask.contains(LayoutMask::VIEWPORT) || self.viewport == other.viewport)
467            && (!mask.contains(LayoutMask::SCREEN_PPI) || self.screen_ppi == other.screen_ppi)
468            && (!mask.contains(LayoutMask::DIRECTION) || self.direction == other.direction)
469            && (!mask.contains(LayoutMask::LEFTOVER) || self.leftover == other.leftover)
470    }
471}
472impl PartialEq for LayoutMetricsSnapshot {
473    fn eq(&self, other: &Self) -> bool {
474        self.constraints == other.constraints
475            && self.z_constraints == other.z_constraints
476            && self.inline_constraints == other.inline_constraints
477            && self.font_size == other.font_size
478            && self.root_font_size == other.root_font_size
479            && self.scale_factor == other.scale_factor
480            && self.viewport == other.viewport
481            && self.screen_ppi == other.screen_ppi
482            && self.direction == other.direction
483            && self.leftover == other.leftover
484    }
485}
486impl std::hash::Hash for LayoutMetricsSnapshot {
487    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
488        self.constraints.hash(state);
489        self.inline_constraints.hash(state);
490        self.font_size.hash(state);
491        self.root_font_size.hash(state);
492        self.scale_factor.hash(state);
493        self.viewport.hash(state);
494        self.screen_ppi.hash(state);
495        self.direction.hash(state);
496        self.leftover.hash(state);
497    }
498}
499
500/// Layout metrics in a [`LAYOUT`] context.
501#[derive(Debug, Clone)]
502pub struct LayoutMetrics {
503    s: LayoutMetricsSnapshot,
504}
505impl LayoutMetrics {
506    /// New root [`LayoutMetrics`].
507    ///
508    /// The `font_size` sets both font sizes, the initial PPI is `96.0`, you can use the builder style method and
509    /// [`with_screen_ppi`] to set a different value.
510    ///
511    /// [`with_screen_ppi`]: LayoutMetrics::with_screen_ppi
512    pub fn new(scale_factor: Factor, viewport: PxSize, font_size: Px) -> Self {
513        LayoutMetrics {
514            s: LayoutMetricsSnapshot {
515                constraints: PxConstraints2d::new_fill_size(viewport),
516                z_constraints: PxConstraints::new_unbounded().with_min(Px(1)),
517                inline_constraints: None,
518                font_size,
519                root_font_size: font_size,
520                scale_factor,
521                viewport,
522                screen_ppi: Ppi::default(),
523                direction: LayoutDirection::default(),
524                leftover: euclid::size2(None, None),
525            },
526        }
527    }
528
529    /// Current size constraints.
530    pub fn constraints(&self) -> PxConstraints2d {
531        LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
532        self.s.constraints
533    }
534
535    /// Current perspective constraints.
536    pub fn z_constraints(&self) -> PxConstraints {
537        LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
538        self.s.z_constraints
539    }
540
541    /// Current inline constraints.
542    ///
543    /// Only present if the parent widget supports inline.
544    pub fn inline_constraints(&self) -> Option<InlineConstraints> {
545        LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
546        self.s.inline_constraints.clone()
547    }
548
549    /// Gets the inline or text flow direction.
550    pub fn direction(&self) -> LayoutDirection {
551        LAYOUT.register_metrics_use(LayoutMask::DIRECTION);
552        self.s.direction
553    }
554
555    /// Current computed font size.
556    pub fn font_size(&self) -> Px {
557        LAYOUT.register_metrics_use(LayoutMask::FONT_SIZE);
558        self.s.font_size
559    }
560
561    /// Computed font size at the root widget.
562    pub fn root_font_size(&self) -> Px {
563        LAYOUT.register_metrics_use(LayoutMask::ROOT_FONT_SIZE);
564        self.s.root_font_size
565    }
566
567    /// Pixel scale factor.
568    pub fn scale_factor(&self) -> Factor {
569        LAYOUT.register_metrics_use(LayoutMask::SCALE_FACTOR);
570        self.s.scale_factor
571    }
572
573    /// Computed size of the nearest viewport ancestor.
574    ///
575    /// This is usually the window content area size, but can be the scroll viewport size or any other
576    /// value depending on the implementation of the context widgets.
577    pub fn viewport(&self) -> PxSize {
578        LAYOUT.register_metrics_use(LayoutMask::VIEWPORT);
579        self.s.viewport
580    }
581
582    /// Smallest dimension of the [`viewport`].
583    ///
584    /// [`viewport`]: Self::viewport
585    pub fn viewport_min(&self) -> Px {
586        self.s.viewport.width.min(self.s.viewport.height)
587    }
588
589    /// Largest dimension of the [`viewport`].
590    ///
591    /// [`viewport`]: Self::viewport
592    pub fn viewport_max(&self) -> Px {
593        self.s.viewport.width.max(self.s.viewport.height)
594    }
595
596    /// The current screen "pixels-per-inch" resolution.
597    ///
598    /// This value is dependent in the actual physical size of the screen.
599    ///
600    /// Default is `96.0`.
601    pub fn screen_ppi(&self) -> Ppi {
602        LAYOUT.register_metrics_use(LayoutMask::SCREEN_PPI);
603        self.s.screen_ppi
604    }
605
606    /// Computed leftover length for the widget, given the [`Length::Leftover`] value it communicated to the parent.
607    ///
608    /// [`Length::Leftover`]: crate::unit::Length::Leftover
609    pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
610        LAYOUT.register_metrics_use(LayoutMask::LEFTOVER);
611        self.s.leftover
612    }
613
614    /// Sets the [`constraints`] to `constraints`.
615    ///
616    /// [`constraints`]: Self::constraints
617    pub fn with_constraints(mut self, constraints: PxConstraints2d) -> Self {
618        self.s.constraints = constraints;
619        self
620    }
621
622    /// Sets the [`z_constraints`] to `constraints`.
623    ///
624    /// [`z_constraints`]: Self::z_constraints
625    pub fn with_z_constraints(mut self, constraints: PxConstraints) -> Self {
626        self.s.z_constraints = constraints;
627        self
628    }
629
630    /// Set the [`inline_constraints`].
631    ///
632    /// [`inline_constraints`]: Self::inline_constraints
633    pub fn with_inline_constraints(mut self, inline_constraints: Option<InlineConstraints>) -> Self {
634        self.s.inline_constraints = inline_constraints;
635        self
636    }
637
638    /// Sets the [`font_size`].
639    ///
640    /// [`font_size`]: Self::font_size
641    pub fn with_font_size(mut self, font_size: Px) -> Self {
642        self.s.font_size = font_size;
643        self
644    }
645
646    /// Sets the [`viewport`].
647    ///
648    /// [`viewport`]: Self::viewport
649    pub fn with_viewport(mut self, viewport: PxSize) -> Self {
650        self.s.viewport = viewport;
651        self
652    }
653
654    /// Sets the [`scale_factor`].
655    ///
656    /// [`scale_factor`]: Self::scale_factor
657    pub fn with_scale_factor(mut self, scale_factor: Factor) -> Self {
658        self.s.scale_factor = scale_factor;
659        self
660    }
661
662    /// Sets the [`screen_ppi`].
663    ///
664    /// [`screen_ppi`]: Self::screen_ppi
665    pub fn with_screen_ppi(mut self, screen_ppi: Ppi) -> Self {
666        self.s.screen_ppi = screen_ppi;
667        self
668    }
669
670    /// Sets the [`direction`].
671    ///
672    /// [`direction`]: Self::direction
673    pub fn with_direction(mut self, direction: LayoutDirection) -> Self {
674        self.s.direction = direction;
675        self
676    }
677
678    /// Sets the [`leftover`].
679    ///
680    /// [`leftover`]: Self::leftover
681    pub fn with_leftover(mut self, width: Option<Px>, height: Option<Px>) -> Self {
682        self.s.leftover = euclid::size2(width, height);
683        self
684    }
685
686    /// Clones all current metrics into a [snapshot].
687    ///
688    /// [snapshot]: LayoutMetricsSnapshot
689    pub fn snapshot(&self) -> LayoutMetricsSnapshot {
690        self.s.clone()
691    }
692}
693
694context_var! {
695    /// Wrap direction of text in a widget context.
696    pub static DIRECTION_VAR: LayoutDirection = LayoutDirection::LTR;
697}
698
699/// Defines the layout flow direction.
700///
701/// This affects inline layout, some [`Align`] options and the base text shaping direction.
702///
703/// The contextual value can be read during layout in [`LayoutMetrics::direction`], and it can be set using [`LayoutMetrics::with_direction`].
704/// Properties that define a more specific *direction* value also set this value, for example, a *TextDirection* property will also set the
705/// layout direction.
706///
707/// Note that this does not affect the layout origin, all points are offsets from the top-left corner independent of this value.
708///
709/// [`Align`]: crate::unit::Align
710#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
711pub enum LayoutDirection {
712    /// left-to-right.
713    LTR,
714    /// Right-to-left.
715    RTL,
716}
717impl LayoutDirection {
718    /// Matches `LTR`.
719    pub fn is_ltr(self) -> bool {
720        matches!(self, Self::LTR)
721    }
722
723    /// Matches `RTL`.
724    pub fn is_rtl(self) -> bool {
725        matches!(self, Self::RTL)
726    }
727}
728impl Default for LayoutDirection {
729    /// Default is `LTR`.
730    fn default() -> Self {
731        Self::LTR
732    }
733}
734impl fmt::Debug for LayoutDirection {
735    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736        if f.alternate() {
737            write!(f, "LayoutDirection::")?;
738        }
739        match self {
740            Self::LTR => write!(f, "LTR"),
741            Self::RTL => write!(f, "RTL"),
742        }
743    }
744}
745
746/// Represents a segment in an inlined widget first or last row.
747///
748/// This info is used by inlining parent to sort the joiner row in a way that preserves bidirectional text flow.
749#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
750#[non_exhaustive]
751pub struct InlineSegment {
752    /// Width of the segment, in pixels.
753    pub width: f32,
754    /// Info for bidirectional reorder.
755    pub kind: TextSegmentKind,
756}
757
758impl InlineSegment {
759    /// New from width and text kind.
760    pub fn new(width: f32, kind: TextSegmentKind) -> Self {
761        Self { width, kind }
762    }
763}
764impl PartialEq for InlineSegment {
765    fn eq(&self, other: &Self) -> bool {
766        about_eq(self.width, other.width, 0.001) && self.kind == other.kind
767    }
768}
769impl Eq for InlineSegment {}
770impl std::hash::Hash for InlineSegment {
771    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
772        about_eq_hash(self.width, 0.001, state);
773        self.kind.hash(state);
774    }
775}
776
777/// The type of an inline/text segment.
778#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
779pub enum TextSegmentKind {
780    /// Any strong left-to-right character.
781    LeftToRight,
782    /// Any strong right-to-left (non-Arabic-type) character.
783    RightToLeft,
784    /// Any strong right-to-left (Arabic-type) character.
785    ArabicLetter,
786
787    /// Any ASCII digit or Eastern Arabic-Indic digit.
788    EuropeanNumber,
789    /// Plus and minus signs.
790    EuropeanSeparator,
791    /// A terminator in a numeric format context, includes currency signs.
792    EuropeanTerminator,
793    /// Any Arabic-Indic digit.
794    ArabicNumber,
795    /// Commas, colons, and slashes.
796    CommonSeparator,
797    /// Any non-spacing mark.
798    NonSpacingMark,
799    /// Most format characters, control codes, or non-characters.
800    BoundaryNeutral,
801
802    /// Emoji chars, components and zero-width-joiner between emoji.
803    Emoji,
804
805    /// Various newline characters.
806    LineBreak,
807    /// A sequence of `'\t', '\v'` or `'\u{1F}'`.
808    Tab,
809    /// Spaces.
810    Space,
811    /// Most other symbols and punctuation marks.
812    OtherNeutral,
813    /// Open or close bidi bracket.
814    ///
815    /// Can be any chars in <https://unicode.org/Public/UNIDATA/BidiBrackets.txt>.
816    Bracket(char),
817
818    /// Bidi control character.
819    ///
820    /// Chars can be:
821    ///
822    /// * `\u{202A}`: The LR embedding control.
823    /// * `\u{202D}`: The LR override control.
824    /// * `\u{202B}`: The RL embedding control.
825    /// * `\u{202E}`: The RL override control.
826    /// * `\u{202C}`: Terminates an embedding or override control.
827    ///
828    /// * `\u{2066}`: The LR isolate control.
829    /// * `\u{2067}`: The RL isolate control.
830    /// * `\u{2068}`: The first strong isolate control.
831    /// * `\u{2069}`: Terminates an isolate control.
832    BidiCtrl(char),
833}
834impl TextSegmentKind {
835    /// Returns `true` if the segment can be considered part of a word for the purpose of inserting letter spacing.
836    pub fn is_word(self) -> bool {
837        use TextSegmentKind::*;
838        matches!(
839            self,
840            LeftToRight
841                | RightToLeft
842                | ArabicLetter
843                | EuropeanNumber
844                | EuropeanSeparator
845                | EuropeanTerminator
846                | ArabicNumber
847                | CommonSeparator
848                | NonSpacingMark
849                | BoundaryNeutral
850                | OtherNeutral
851                | Bracket(_)
852                | Emoji
853        )
854    }
855
856    /// Returns `true` if the segment can be considered part of space between words for the purpose of inserting word spacing.
857    pub fn is_space(self) -> bool {
858        matches!(self, Self::Space | Self::Tab)
859    }
860
861    /// Returns `true` if the segment terminates the current line.
862    ///
863    /// Line break segments are the last segment of their line and explicitly start a new line.
864    pub fn is_line_break(self) -> bool {
865        matches!(self, Self::LineBreak)
866    }
867
868    /// If multiple segments of this same kind can be represented by a single segment in the Unicode bidi algorithm.
869    pub fn can_merge(self) -> bool {
870        use TextSegmentKind::*;
871        !matches!(self, Bracket(_) | BidiCtrl(_))
872    }
873
874    /// Get more info about the bracket char if `self` is `Bracket(_)` with a valid char.
875    pub fn bracket_info(self) -> Option<unicode_bidi::data_source::BidiMatchedOpeningBracket> {
876        if let TextSegmentKind::Bracket(c) = self {
877            unicode_bidi::HardcodedBidiData.bidi_matched_opening_bracket(c)
878        } else {
879            None
880        }
881    }
882
883    /// Gets the layout direction this segment will always be in, independent of the base direction.
884    ///
885    /// Returns `None` if the segment direction depends on the line context.
886    pub fn strong_direction(self) -> Option<LayoutDirection> {
887        use TextSegmentKind::*;
888
889        match self {
890            LeftToRight => Some(LayoutDirection::LTR),
891            RightToLeft | ArabicLetter => Some(LayoutDirection::RTL),
892            BidiCtrl(_) => {
893                use unicode_bidi::BidiClass::*;
894                match unicode_bidi::BidiClass::from(self) {
895                    LRE | LRO | LRI => Some(LayoutDirection::LTR),
896                    RLE | RLO | RLI => Some(LayoutDirection::RTL),
897                    _ => None,
898                }
899            }
900            _ => None,
901        }
902    }
903}
904impl From<char> for TextSegmentKind {
905    fn from(c: char) -> Self {
906        use unicode_bidi::*;
907
908        unicode_bidi::HardcodedBidiData.bidi_class(c).into()
909    }
910}
911
912impl From<unicode_bidi::BidiClass> for TextSegmentKind {
913    fn from(value: unicode_bidi::BidiClass) -> Self {
914        use TextSegmentKind::*;
915        use unicode_bidi::BidiClass::*;
916
917        match value {
918            WS => Space,
919            L => LeftToRight,
920            R => RightToLeft,
921            AL => ArabicLetter,
922            AN => ArabicNumber,
923            CS => CommonSeparator,
924            B => LineBreak,
925            EN => EuropeanNumber,
926            ES => EuropeanSeparator,
927            ET => EuropeanTerminator,
928            S => Tab,
929            ON => OtherNeutral,
930            BN => BoundaryNeutral,
931            NSM => NonSpacingMark,
932            RLE => BidiCtrl('\u{202B}'),
933            LRI => BidiCtrl('\u{2066}'),
934            RLI => BidiCtrl('\u{2067}'),
935            LRO => BidiCtrl('\u{202D}'),
936            FSI => BidiCtrl('\u{2068}'),
937            PDF => BidiCtrl('\u{202C}'),
938            LRE => BidiCtrl('\u{202A}'),
939            PDI => BidiCtrl('\u{2069}'),
940            RLO => BidiCtrl('\u{202E}'),
941        }
942    }
943}
944impl From<TextSegmentKind> for unicode_bidi::BidiClass {
945    fn from(value: TextSegmentKind) -> Self {
946        use TextSegmentKind::*;
947        use unicode_bidi::BidiClass::*;
948
949        match value {
950            Space => WS,
951            LeftToRight => L,
952            RightToLeft => R,
953            ArabicLetter => AL,
954            ArabicNumber => AN,
955            CommonSeparator => CS,
956            LineBreak => B,
957            EuropeanNumber => EN,
958            EuropeanSeparator => ES,
959            EuropeanTerminator => ET,
960            Tab => S,
961            OtherNeutral | Emoji | Bracket(_) => ON,
962            BoundaryNeutral => BN,
963            NonSpacingMark => NSM,
964            BidiCtrl(c) => match c {
965                '\u{202A}' => LRE,
966                '\u{202D}' => LRO,
967                '\u{202B}' => RLE,
968                '\u{202E}' => RLO,
969                '\u{202C}' => PDF,
970                '\u{2066}' => LRI,
971                '\u{2067}' => RLI,
972                '\u{2068}' => FSI,
973                '\u{2069}' => PDI,
974                _c => {
975                    #[cfg(debug_assertions)]
976                    {
977                        tracing::error!("invalid bidi ctrl char '{_c}'");
978                    }
979                    ON
980                }
981            },
982        }
983    }
984}
985
986bitflags! {
987    /// Mask of values that can affect the layout operation of a value.
988    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, bytemuck::NoUninit)]
989    #[repr(transparent)]
990    pub struct LayoutMask: u32 {
991        /// The `default_value`.
992        const DEFAULT_VALUE = 1 << 31;
993        /// The [`LayoutMetrics::constraints`], [`LayoutMetrics::z_constraints`] and [`LayoutMetrics::inline_constraints`].
994        const CONSTRAINTS = 1 << 30;
995
996        /// The [`LayoutMetrics::font_size`].
997        const FONT_SIZE = 1;
998        /// The [`LayoutMetrics::root_font_size`].
999        const ROOT_FONT_SIZE = 1 << 1;
1000        /// The [`LayoutMetrics::scale_factor`].
1001        const SCALE_FACTOR = 1 << 2;
1002        /// The [`LayoutMetrics::viewport`].
1003        const VIEWPORT = 1 << 3;
1004        /// The [`LayoutMetrics::screen_ppi`].
1005        const SCREEN_PPI = 1 << 4;
1006        /// The [`LayoutMetrics::direction`].
1007        const DIRECTION = 1 << 5;
1008        /// The [`LayoutMetrics::leftover`].
1009        const LEFTOVER = 1 << 6;
1010    }
1011}
1012impl Default for LayoutMask {
1013    /// Empty.
1014    fn default() -> Self {
1015        LayoutMask::empty()
1016    }
1017}