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