Skip to main content

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, PxConstraints, PxConstraints2d, PxDensity};
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 pixel density.
200    pub fn screen_density(&self) -> PxDensity {
201        LAYOUT_CTX.get().metrics.screen_density()
202    }
203
204    /// Calls `f` with `screen_density` in the context.
205    pub fn with_screen_density<R>(&self, screen_density: PxDensity, f: impl FnOnce() -> R) -> R {
206        self.with_context(self.metrics().with_screen_density(screen_density), 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_density`].
442    ///
443    /// [`screen_density`]: LayoutMetrics::screen_density
444    pub screen_density: PxDensity,
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) || self.constraints == other.constraints)
460            && (!mask.contains(LayoutMask::Z_CONSTRAINTS) || self.z_constraints == other.z_constraints)
461            && (!mask.contains(LayoutMask::INLINE_CONSTRAINTS) || self.inline_constraints == other.inline_constraints)
462            && (!mask.contains(LayoutMask::FONT_SIZE) || self.font_size == other.font_size)
463            && (!mask.contains(LayoutMask::ROOT_FONT_SIZE) || self.root_font_size == other.root_font_size)
464            && (!mask.contains(LayoutMask::SCALE_FACTOR) || self.scale_factor == other.scale_factor)
465            && (!mask.contains(LayoutMask::VIEWPORT) || self.viewport == other.viewport)
466            && (!mask.contains(LayoutMask::SCREEN_DENSITY) || self.screen_density == other.screen_density)
467            && (!mask.contains(LayoutMask::DIRECTION) || self.direction == other.direction)
468            && (!mask.contains(LayoutMask::LEFTOVER) || self.leftover == other.leftover)
469    }
470}
471impl PartialEq for LayoutMetricsSnapshot {
472    fn eq(&self, other: &Self) -> bool {
473        self.constraints == other.constraints
474            && self.z_constraints == other.z_constraints
475            && self.inline_constraints == other.inline_constraints
476            && self.font_size == other.font_size
477            && self.root_font_size == other.root_font_size
478            && self.scale_factor == other.scale_factor
479            && self.viewport == other.viewport
480            && self.screen_density == other.screen_density
481            && self.direction == other.direction
482            && self.leftover == other.leftover
483    }
484}
485impl std::hash::Hash for LayoutMetricsSnapshot {
486    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
487        self.constraints.hash(state);
488        self.inline_constraints.hash(state);
489        self.font_size.hash(state);
490        self.root_font_size.hash(state);
491        self.scale_factor.hash(state);
492        self.viewport.hash(state);
493        self.screen_density.hash(state);
494        self.direction.hash(state);
495        self.leftover.hash(state);
496    }
497}
498
499/// Layout metrics in a [`LAYOUT`] context.
500#[derive(Debug, Clone)]
501pub struct LayoutMetrics {
502    s: LayoutMetricsSnapshot,
503}
504impl LayoutMetrics {
505    /// New root [`LayoutMetrics`].
506    ///
507    /// The `font_size` sets both font sizes, the initial screen density is `96.ppi()`, you can use the builder style method and
508    /// [`with_screen_density`] to set a different value.
509    ///
510    /// [`with_screen_density`]: LayoutMetrics::with_screen_density
511    pub fn new(scale_factor: Factor, viewport: PxSize, font_size: Px) -> Self {
512        LayoutMetrics {
513            s: LayoutMetricsSnapshot {
514                constraints: PxConstraints2d::new_fill_size(viewport),
515                z_constraints: PxConstraints::new_unbounded().with_min(Px(1)),
516                inline_constraints: None,
517                font_size,
518                root_font_size: font_size,
519                scale_factor,
520                viewport,
521                screen_density: PxDensity::default(),
522                direction: LayoutDirection::default(),
523                leftover: euclid::size2(None, None),
524            },
525        }
526    }
527
528    /// Current size constraints.
529    pub fn constraints(&self) -> PxConstraints2d {
530        LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
531        self.s.constraints
532    }
533
534    /// Current perspective constraints.
535    pub fn z_constraints(&self) -> PxConstraints {
536        LAYOUT.register_metrics_use(LayoutMask::Z_CONSTRAINTS);
537        self.s.z_constraints
538    }
539
540    /// Current inline constraints.
541    ///
542    /// Only present if the parent widget supports inline.
543    pub fn inline_constraints(&self) -> Option<InlineConstraints> {
544        LAYOUT.register_metrics_use(LayoutMask::INLINE_CONSTRAINTS);
545        self.s.inline_constraints.clone()
546    }
547
548    /// Gets the inline or text flow direction.
549    pub fn direction(&self) -> LayoutDirection {
550        LAYOUT.register_metrics_use(LayoutMask::DIRECTION);
551        self.s.direction
552    }
553
554    /// Current computed font size.
555    pub fn font_size(&self) -> Px {
556        LAYOUT.register_metrics_use(LayoutMask::FONT_SIZE);
557        self.s.font_size
558    }
559
560    /// Computed font size at the root widget.
561    pub fn root_font_size(&self) -> Px {
562        LAYOUT.register_metrics_use(LayoutMask::ROOT_FONT_SIZE);
563        self.s.root_font_size
564    }
565
566    /// Pixel scale factor.
567    pub fn scale_factor(&self) -> Factor {
568        LAYOUT.register_metrics_use(LayoutMask::SCALE_FACTOR);
569        self.s.scale_factor
570    }
571
572    /// Computed size of the nearest viewport ancestor.
573    ///
574    /// This is usually the window content area size, but can be the scroll viewport size or any other
575    /// value depending on the implementation of the context widgets.
576    pub fn viewport(&self) -> PxSize {
577        LAYOUT.register_metrics_use(LayoutMask::VIEWPORT);
578        self.s.viewport
579    }
580
581    /// Smallest dimension of the [`viewport`].
582    ///
583    /// [`viewport`]: Self::viewport
584    pub fn viewport_min(&self) -> Px {
585        self.s.viewport.width.min(self.s.viewport.height)
586    }
587
588    /// Largest dimension of the [`viewport`].
589    ///
590    /// [`viewport`]: Self::viewport
591    pub fn viewport_max(&self) -> Px {
592        self.s.viewport.width.max(self.s.viewport.height)
593    }
594
595    /// The current screen pixel density.
596    ///
597    /// Default is `96.ppi()`.
598    pub fn screen_density(&self) -> PxDensity {
599        LAYOUT.register_metrics_use(LayoutMask::SCREEN_DENSITY);
600        self.s.screen_density
601    }
602
603    /// Computed leftover length for the widget, given the [`Length::Leftover`] value it communicated to the parent.
604    ///
605    /// [`Length::Leftover`]: crate::unit::Length::Leftover
606    pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
607        LAYOUT.register_metrics_use(LayoutMask::LEFTOVER);
608        self.s.leftover
609    }
610
611    /// Sets the [`constraints`] to `constraints`.
612    ///
613    /// [`constraints`]: Self::constraints
614    pub fn with_constraints(mut self, constraints: PxConstraints2d) -> Self {
615        self.s.constraints = constraints;
616        self
617    }
618
619    /// Sets the [`z_constraints`] to `constraints`.
620    ///
621    /// [`z_constraints`]: Self::z_constraints
622    pub fn with_z_constraints(mut self, constraints: PxConstraints) -> Self {
623        self.s.z_constraints = constraints;
624        self
625    }
626
627    /// Set the [`inline_constraints`].
628    ///
629    /// [`inline_constraints`]: Self::inline_constraints
630    pub fn with_inline_constraints(mut self, inline_constraints: Option<InlineConstraints>) -> Self {
631        self.s.inline_constraints = inline_constraints;
632        self
633    }
634
635    /// Sets the [`font_size`].
636    ///
637    /// [`font_size`]: Self::font_size
638    pub fn with_font_size(mut self, font_size: Px) -> Self {
639        self.s.font_size = font_size;
640        self
641    }
642
643    /// Sets the [`viewport`].
644    ///
645    /// [`viewport`]: Self::viewport
646    pub fn with_viewport(mut self, viewport: PxSize) -> Self {
647        self.s.viewport = viewport;
648        self
649    }
650
651    /// Sets the [`scale_factor`].
652    ///
653    /// [`scale_factor`]: Self::scale_factor
654    pub fn with_scale_factor(mut self, scale_factor: Factor) -> Self {
655        self.s.scale_factor = scale_factor;
656        self
657    }
658
659    /// Sets the [`screen_density`].
660    ///
661    /// [`screen_density`]: Self::screen_density
662    pub fn with_screen_density(mut self, screen_density: PxDensity) -> Self {
663        self.s.screen_density = screen_density;
664        self
665    }
666
667    /// Sets the [`direction`].
668    ///
669    /// [`direction`]: Self::direction
670    pub fn with_direction(mut self, direction: LayoutDirection) -> Self {
671        self.s.direction = direction;
672        self
673    }
674
675    /// Sets the [`leftover`].
676    ///
677    /// [`leftover`]: Self::leftover
678    pub fn with_leftover(mut self, width: Option<Px>, height: Option<Px>) -> Self {
679        self.s.leftover = euclid::size2(width, height);
680        self
681    }
682
683    /// Clones all current metrics into a [snapshot].
684    ///
685    /// [snapshot]: LayoutMetricsSnapshot
686    pub fn snapshot(&self) -> LayoutMetricsSnapshot {
687        self.s.clone()
688    }
689}
690
691context_var! {
692    /// Wrap direction of text in a widget context.
693    pub static DIRECTION_VAR: LayoutDirection = LayoutDirection::LTR;
694}
695
696/// Defines the layout flow direction.
697///
698/// This affects inline layout, some [`Align`] options and the base text shaping direction.
699///
700/// The contextual value can be read during layout in [`LayoutMetrics::direction`], and it can be set using [`LayoutMetrics::with_direction`].
701/// Properties that define a more specific *direction* value also set this value, for example, a *TextDirection* property will also set the
702/// layout direction.
703///
704/// Note that this does not affect the layout origin, all points are offsets from the top-left corner independent of this value.
705///
706/// [`Align`]: crate::unit::Align
707#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
708pub enum LayoutDirection {
709    /// left-to-right.
710    LTR,
711    /// Right-to-left.
712    RTL,
713}
714impl LayoutDirection {
715    /// Matches `LTR`.
716    pub fn is_ltr(self) -> bool {
717        matches!(self, Self::LTR)
718    }
719
720    /// Matches `RTL`.
721    pub fn is_rtl(self) -> bool {
722        matches!(self, Self::RTL)
723    }
724}
725impl Default for LayoutDirection {
726    /// Default is `LTR`.
727    fn default() -> Self {
728        Self::LTR
729    }
730}
731impl fmt::Debug for LayoutDirection {
732    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733        if f.alternate() {
734            write!(f, "LayoutDirection::")?;
735        }
736        match self {
737            Self::LTR => write!(f, "LTR"),
738            Self::RTL => write!(f, "RTL"),
739        }
740    }
741}
742
743/// Represents a segment in an inlined widget first or last row.
744///
745/// This info is used by inlining parent to sort the joiner row in a way that preserves bidirectional text flow.
746#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
747#[non_exhaustive]
748pub struct InlineSegment {
749    /// Width of the segment, in pixels.
750    pub width: f32,
751    /// Info for bidirectional reorder.
752    pub kind: TextSegmentKind,
753}
754
755impl InlineSegment {
756    /// New from width and text kind.
757    pub fn new(width: f32, kind: TextSegmentKind) -> Self {
758        Self { width, kind }
759    }
760}
761impl PartialEq for InlineSegment {
762    fn eq(&self, other: &Self) -> bool {
763        about_eq(self.width, other.width, 0.001) && self.kind == other.kind
764    }
765}
766impl Eq for InlineSegment {}
767impl std::hash::Hash for InlineSegment {
768    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
769        about_eq_hash(self.width, 0.001, state);
770        self.kind.hash(state);
771    }
772}
773
774/// The type of an inline/text segment.
775#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
776pub enum TextSegmentKind {
777    /// Any strong left-to-right character.
778    LeftToRight,
779    /// Any strong right-to-left (non-Arabic-type) character.
780    RightToLeft,
781    /// Any strong right-to-left (Arabic-type) character.
782    ArabicLetter,
783
784    /// Any ASCII digit or Eastern Arabic-Indic digit.
785    EuropeanNumber,
786    /// Plus and minus signs.
787    EuropeanSeparator,
788    /// A terminator in a numeric format context, includes currency signs.
789    EuropeanTerminator,
790    /// Any Arabic-Indic digit.
791    ArabicNumber,
792    /// Commas, colons, and slashes.
793    CommonSeparator,
794    /// Any non-spacing mark.
795    ///
796    /// Note that segmentation already merges non-spacing marks with the previous segment assuming
797    /// it is the modification target, this will only appear at the text start in segmented text.
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_bidi(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`].
994        const CONSTRAINTS = 1 << 30;
995        /// The [`LayoutMetrics::z_constraints`].
996        const Z_CONSTRAINTS = 1 << 29;
997        /// The [`LayoutMetrics::inline_constraints`].
998        const INLINE_CONSTRAINTS = 1 << 28;
999
1000        /// The [`LayoutMetrics::font_size`].
1001        const FONT_SIZE = 1;
1002        /// The [`LayoutMetrics::root_font_size`].
1003        const ROOT_FONT_SIZE = 1 << 1;
1004        /// The [`LayoutMetrics::scale_factor`].
1005        const SCALE_FACTOR = 1 << 2;
1006        /// The [`LayoutMetrics::viewport`].
1007        const VIEWPORT = 1 << 3;
1008        /// The [`LayoutMetrics::screen_density`].
1009        const SCREEN_DENSITY = 1 << 4;
1010        /// The [`LayoutMetrics::direction`].
1011        const DIRECTION = 1 << 5;
1012        /// The [`LayoutMetrics::leftover`].
1013        const LEFTOVER = 1 << 6;
1014    }
1015}
1016impl Default for LayoutMask {
1017    /// Empty.
1018    fn default() -> Self {
1019        LayoutMask::empty()
1020    }
1021}