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    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}
325impl PartialOrd for InlineSegmentPos {
326    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
327        Some(self.cmp(other))
328    }
329}
330impl Ord for InlineSegmentPos {
331    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
332        about_eq_ord(self.x, other.x, 0.001)
333    }
334}
335
336/// Constraints for inline layout.
337///
338/// See [`InlineConstraints`] for more details.
339#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
340#[non_exhaustive]
341pub struct InlineConstraintsLayout {
342    /// First row rect, defined by the parent.
343    pub first: PxRect,
344    /// Extra space in-between the first row and the mid-rows that must be offset to clear the other segments in the row.
345    pub mid_clear: Px,
346    /// Last row rect, defined by the parent.
347    pub last: PxRect,
348
349    /// Position of inline segments of the first row.
350    pub first_segs: Arc<Vec<InlineSegmentPos>>,
351    /// Position of inline segments of the last row.
352    pub last_segs: Arc<Vec<InlineSegmentPos>>,
353}
354
355impl InlineConstraintsLayout {
356    /// New constraint.
357    pub fn new(
358        first: PxRect,
359        mid_clear: Px,
360        last: PxRect,
361        first_segs: Arc<Vec<InlineSegmentPos>>,
362        last_segs: Arc<Vec<InlineSegmentPos>>,
363    ) -> Self {
364        Self {
365            first,
366            mid_clear,
367            last,
368            first_segs,
369            last_segs,
370        }
371    }
372}
373
374/// Constraints for inline measure or layout.
375#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
376pub enum InlineConstraints {
377    /// Constraints for the measure pass.
378    Measure(InlineConstraintsMeasure),
379    /// Constraints the layout pass.
380    Layout(InlineConstraintsLayout),
381}
382impl InlineConstraints {
383    /// Get the `Measure` data or default.
384    pub fn measure(self) -> InlineConstraintsMeasure {
385        match self {
386            InlineConstraints::Measure(m) => m,
387            InlineConstraints::Layout(l) => InlineConstraintsMeasure {
388                first_max: l.first.width(),
389                mid_clear_min: l.mid_clear,
390            },
391        }
392    }
393
394    /// Get the `Layout` data or default.
395    pub fn layout(self) -> InlineConstraintsLayout {
396        match self {
397            InlineConstraints::Layout(m) => m,
398            InlineConstraints::Measure(_) => Default::default(),
399        }
400    }
401}
402
403/// Layout metrics snapshot.
404///
405/// A snapshot can be taken using the [`LayoutMetrics::snapshot`].
406#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
407#[non_exhaustive]
408pub struct LayoutMetricsSnapshot {
409    /// The [`constraints`].
410    ///
411    /// [`constraints`]: LayoutMetrics::constraints
412    pub constraints: PxConstraints2d,
413
414    /// The [`inline_constraints`].
415    ///
416    /// [`inline_constraints`]: LayoutMetrics::inline_constraints
417    pub inline_constraints: Option<InlineConstraints>,
418
419    /// The [`z_constraints`].
420    ///
421    /// [`z_constraints`]: LayoutMetrics::z_constraints
422    pub z_constraints: PxConstraints,
423
424    /// The [`font_size`].
425    ///
426    /// [`font_size`]: LayoutMetrics::font_size
427    pub font_size: Px,
428    /// The [`root_font_size`].
429    ///
430    /// [`root_font_size`]: LayoutMetrics::root_font_size
431    pub root_font_size: Px,
432    /// The [`scale_factor`].
433    ///
434    /// [`scale_factor`]: LayoutMetrics::scale_factor
435    pub scale_factor: Factor,
436    /// The [`viewport`].
437    ///
438    /// [`viewport`]: LayoutMetrics::viewport
439    pub viewport: PxSize,
440    /// The [`screen_ppi`].
441    ///
442    /// [`screen_ppi`]: LayoutMetrics::screen_ppi
443    pub screen_ppi: Ppi,
444
445    /// The [`direction`].
446    ///
447    /// [`direction`]: LayoutMetrics::direction
448    pub direction: LayoutDirection,
449
450    /// The [`leftover`].
451    ///
452    /// [`leftover`]: LayoutMetrics::leftover
453    pub leftover: euclid::Size2D<Option<Px>, ()>,
454}
455impl LayoutMetricsSnapshot {
456    /// Gets if all of the fields in `mask` are equal between `self` and `other`.
457    pub fn masked_eq(&self, other: &Self, mask: LayoutMask) -> bool {
458        (!mask.contains(LayoutMask::CONSTRAINTS)
459            || (self.constraints == other.constraints
460                && self.z_constraints == other.z_constraints
461                && 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_PPI) || self.screen_ppi == other.screen_ppi)
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_ppi == other.screen_ppi
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_ppi.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 PPI is `96.0`, you can use the builder style method and
508    /// [`with_screen_ppi`] to set a different value.
509    ///
510    /// [`with_screen_ppi`]: LayoutMetrics::with_screen_ppi
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_ppi: Ppi::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::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::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 "pixels-per-inch" resolution.
596    ///
597    /// This value is dependent in the actual physical size of the screen.
598    ///
599    /// Default is `96.0`.
600    pub fn screen_ppi(&self) -> Ppi {
601        self.s.screen_ppi
602    }
603
604    /// Computed leftover length for the widget, given the [`Length::Leftover`] value it communicated to the parent.
605    ///
606    /// [`Length::Leftover`]: crate::unit::Length::Leftover
607    pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
608        LAYOUT.register_metrics_use(LayoutMask::LEFTOVER);
609        self.s.leftover
610    }
611
612    /// Sets the [`constraints`] to `constraints`.
613    ///
614    /// [`constraints`]: Self::constraints
615    pub fn with_constraints(mut self, constraints: PxConstraints2d) -> Self {
616        self.s.constraints = constraints;
617        self
618    }
619
620    /// Sets the [`z_constraints`] to `constraints`.
621    ///
622    /// [`z_constraints`]: Self::z_constraints
623    pub fn with_z_constraints(mut self, constraints: PxConstraints) -> Self {
624        self.s.z_constraints = constraints;
625        self
626    }
627
628    /// Set the [`inline_constraints`].
629    ///
630    /// [`inline_constraints`]: Self::inline_constraints
631    pub fn with_inline_constraints(mut self, inline_constraints: Option<InlineConstraints>) -> Self {
632        self.s.inline_constraints = inline_constraints;
633        self
634    }
635
636    /// Sets the [`font_size`].
637    ///
638    /// [`font_size`]: Self::font_size
639    pub fn with_font_size(mut self, font_size: Px) -> Self {
640        self.s.font_size = font_size;
641        self
642    }
643
644    /// Sets the [`viewport`].
645    ///
646    /// [`viewport`]: Self::viewport
647    pub fn with_viewport(mut self, viewport: PxSize) -> Self {
648        self.s.viewport = viewport;
649        self
650    }
651
652    /// Sets the [`scale_factor`].
653    ///
654    /// [`scale_factor`]: Self::scale_factor
655    pub fn with_scale_factor(mut self, scale_factor: Factor) -> Self {
656        self.s.scale_factor = scale_factor;
657        self
658    }
659
660    /// Sets the [`screen_ppi`].
661    ///
662    /// [`screen_ppi`]: Self::screen_ppi
663    pub fn with_screen_ppi(mut self, screen_ppi: Ppi) -> Self {
664        self.s.screen_ppi = screen_ppi;
665        self
666    }
667
668    /// Sets the [`direction`].
669    ///
670    /// [`direction`]: Self::direction
671    pub fn with_direction(mut self, direction: LayoutDirection) -> Self {
672        self.s.direction = direction;
673        self
674    }
675
676    /// Sets the [`leftover`].
677    ///
678    /// [`leftover`]: Self::leftover
679    pub fn with_leftover(mut self, width: Option<Px>, height: Option<Px>) -> Self {
680        self.s.leftover = euclid::size2(width, height);
681        self
682    }
683
684    /// Clones all current metrics into a [snapshot].
685    ///
686    /// [snapshot]: LayoutMetricsSnapshot
687    pub fn snapshot(&self) -> LayoutMetricsSnapshot {
688        self.s.clone()
689    }
690}
691
692context_var! {
693    /// Wrap direction of text in a widget context.
694    pub static DIRECTION_VAR: LayoutDirection = LayoutDirection::LTR;
695}
696
697/// Defines the layout flow direction.
698///
699/// This affects inline layout, some [`Align`] options and the base text shaping direction.
700///
701/// The contextual value can be read during layout in [`LayoutMetrics::direction`], and it can be set using [`LayoutMetrics::with_direction`].
702/// Properties that define a more specific *direction* value also set this value, for example, a *TextDirection* property will also set the
703/// layout direction.
704///
705/// Note that this does not affect the layout origin, all points are offsets from the top-left corner independent of this value.
706///
707/// [`Align`]: crate::unit::Align
708#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
709pub enum LayoutDirection {
710    /// left-to-right.
711    LTR,
712    /// Right-to-left.
713    RTL,
714}
715impl LayoutDirection {
716    /// Matches `LTR`.
717    pub fn is_ltr(self) -> bool {
718        matches!(self, Self::LTR)
719    }
720
721    /// Matches `RTL`.
722    pub fn is_rtl(self) -> bool {
723        matches!(self, Self::RTL)
724    }
725}
726impl Default for LayoutDirection {
727    /// Default is `LTR`.
728    fn default() -> Self {
729        Self::LTR
730    }
731}
732impl fmt::Debug for LayoutDirection {
733    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734        if f.alternate() {
735            write!(f, "LayoutDirection::")?;
736        }
737        match self {
738            Self::LTR => write!(f, "LTR"),
739            Self::RTL => write!(f, "RTL"),
740        }
741    }
742}
743
744/// Represents a segment in an inlined widget first or last row.
745///
746/// This info is used by inlining parent to sort the joiner row in a way that preserves bidirectional text flow.
747#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
748#[non_exhaustive]
749pub struct InlineSegment {
750    /// Width of the segment, in pixels.
751    pub width: f32,
752    /// Info for bidirectional reorder.
753    pub kind: TextSegmentKind,
754}
755
756impl InlineSegment {
757    /// New from width and text kind.
758    pub fn new(width: f32, kind: TextSegmentKind) -> Self {
759        Self { width, kind }
760    }
761}
762impl PartialEq for InlineSegment {
763    fn eq(&self, other: &Self) -> bool {
764        about_eq(self.width, other.width, 0.001) && self.kind == other.kind
765    }
766}
767impl Eq for InlineSegment {}
768impl std::hash::Hash for InlineSegment {
769    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
770        about_eq_hash(self.width, 0.001, state);
771        self.kind.hash(state);
772    }
773}
774
775/// The type of an inline/text segment.
776#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
777pub enum TextSegmentKind {
778    /// Any strong left-to-right character.
779    LeftToRight,
780    /// Any strong right-to-left (non-Arabic-type) character.
781    RightToLeft,
782    /// Any strong right-to-left (Arabic-type) character.
783    ArabicLetter,
784
785    /// Any ASCII digit or Eastern Arabic-Indic digit.
786    EuropeanNumber,
787    /// Plus and minus signs.
788    EuropeanSeparator,
789    /// A terminator in a numeric format context, includes currency signs.
790    EuropeanTerminator,
791    /// Any Arabic-Indic digit.
792    ArabicNumber,
793    /// Commas, colons, and slashes.
794    CommonSeparator,
795    /// Any non-spacing mark.
796    NonSpacingMark,
797    /// Most format characters, control codes, or non-characters.
798    BoundaryNeutral,
799
800    /// Emoji chars, components and zero-width-joiner between emoji.
801    Emoji,
802
803    /// Various newline characters.
804    LineBreak,
805    /// A sequence of `'\t', '\v'` or `'\u{1F}'`.
806    Tab,
807    /// Spaces.
808    Space,
809    /// Most other symbols and punctuation marks.
810    OtherNeutral,
811    /// Open or close bidi bracket.
812    ///
813    /// Can be any chars in <https://unicode.org/Public/UNIDATA/BidiBrackets.txt>.
814    Bracket(char),
815
816    /// Bidi control character.
817    ///
818    /// Chars can be:
819    ///
820    /// * `\u{202A}`: The LR embedding control.
821    /// * `\u{202D}`: The LR override control.
822    /// * `\u{202B}`: The RL embedding control.
823    /// * `\u{202E}`: The RL override control.
824    /// * `\u{202C}`: Terminates an embedding or override control.
825    ///
826    /// * `\u{2066}`: The LR isolate control.
827    /// * `\u{2067}`: The RL isolate control.
828    /// * `\u{2068}`: The first strong isolate control.
829    /// * `\u{2069}`: Terminates an isolate control.
830    BidiCtrl(char),
831}
832impl TextSegmentKind {
833    /// Returns `true` if the segment can be considered part of a word for the purpose of inserting letter spacing.
834    pub fn is_word(self) -> bool {
835        use TextSegmentKind::*;
836        matches!(
837            self,
838            LeftToRight
839                | RightToLeft
840                | ArabicLetter
841                | EuropeanNumber
842                | EuropeanSeparator
843                | EuropeanTerminator
844                | ArabicNumber
845                | CommonSeparator
846                | NonSpacingMark
847                | BoundaryNeutral
848                | OtherNeutral
849                | Bracket(_)
850                | Emoji
851        )
852    }
853
854    /// Returns `true` if the segment can be considered part of space between words for the purpose of inserting word spacing.
855    pub fn is_space(self) -> bool {
856        matches!(self, Self::Space | Self::Tab)
857    }
858
859    /// Returns `true` if the segment terminates the current line.
860    ///
861    /// Line break segments are the last segment of their line and explicitly start a new line.
862    pub fn is_line_break(self) -> bool {
863        matches!(self, Self::LineBreak)
864    }
865
866    /// If multiple segments of this same kind can be represented by a single segment in the Unicode bidi algorithm.
867    pub fn can_merge(self) -> bool {
868        use TextSegmentKind::*;
869        !matches!(self, Bracket(_) | BidiCtrl(_))
870    }
871
872    /// Get more info about the bracket char if `self` is `Bracket(_)` with a valid char.
873    pub fn bracket_info(self) -> Option<unicode_bidi::data_source::BidiMatchedOpeningBracket> {
874        if let TextSegmentKind::Bracket(c) = self {
875            unicode_bidi::HardcodedBidiData.bidi_matched_opening_bracket(c)
876        } else {
877            None
878        }
879    }
880
881    /// Gets the layout direction this segment will always be in, independent of the base direction.
882    ///
883    /// Returns `None` if the segment direction depends on the line context.
884    pub fn strong_direction(self) -> Option<LayoutDirection> {
885        use TextSegmentKind::*;
886
887        match self {
888            LeftToRight => Some(LayoutDirection::LTR),
889            RightToLeft | ArabicLetter => Some(LayoutDirection::RTL),
890            BidiCtrl(_) => {
891                use unicode_bidi::BidiClass::*;
892                match unicode_bidi::BidiClass::from(self) {
893                    LRE | LRO | LRI => Some(LayoutDirection::LTR),
894                    RLE | RLO | RLI => Some(LayoutDirection::RTL),
895                    _ => None,
896                }
897            }
898            _ => None,
899        }
900    }
901}
902impl From<char> for TextSegmentKind {
903    fn from(c: char) -> Self {
904        use unicode_bidi::*;
905
906        unicode_bidi::HardcodedBidiData.bidi_class(c).into()
907    }
908}
909
910impl From<unicode_bidi::BidiClass> for TextSegmentKind {
911    fn from(value: unicode_bidi::BidiClass) -> Self {
912        use TextSegmentKind::*;
913        use unicode_bidi::BidiClass::*;
914
915        match value {
916            WS => Space,
917            L => LeftToRight,
918            R => RightToLeft,
919            AL => ArabicLetter,
920            AN => ArabicNumber,
921            CS => CommonSeparator,
922            B => LineBreak,
923            EN => EuropeanNumber,
924            ES => EuropeanSeparator,
925            ET => EuropeanTerminator,
926            S => Tab,
927            ON => OtherNeutral,
928            BN => BoundaryNeutral,
929            NSM => NonSpacingMark,
930            RLE => BidiCtrl('\u{202B}'),
931            LRI => BidiCtrl('\u{2066}'),
932            RLI => BidiCtrl('\u{2067}'),
933            LRO => BidiCtrl('\u{202D}'),
934            FSI => BidiCtrl('\u{2068}'),
935            PDF => BidiCtrl('\u{202C}'),
936            LRE => BidiCtrl('\u{202A}'),
937            PDI => BidiCtrl('\u{2069}'),
938            RLO => BidiCtrl('\u{202E}'),
939        }
940    }
941}
942impl From<TextSegmentKind> for unicode_bidi::BidiClass {
943    fn from(value: TextSegmentKind) -> Self {
944        use TextSegmentKind::*;
945        use unicode_bidi::BidiClass::*;
946
947        match value {
948            Space => WS,
949            LeftToRight => L,
950            RightToLeft => R,
951            ArabicLetter => AL,
952            ArabicNumber => AN,
953            CommonSeparator => CS,
954            LineBreak => B,
955            EuropeanNumber => EN,
956            EuropeanSeparator => ES,
957            EuropeanTerminator => ET,
958            Tab => S,
959            OtherNeutral | Emoji | Bracket(_) => ON,
960            BoundaryNeutral => BN,
961            NonSpacingMark => NSM,
962            BidiCtrl(c) => match c {
963                '\u{202A}' => LRE,
964                '\u{202D}' => LRO,
965                '\u{202B}' => RLE,
966                '\u{202E}' => RLO,
967                '\u{202C}' => PDF,
968                '\u{2066}' => LRI,
969                '\u{2067}' => RLI,
970                '\u{2068}' => FSI,
971                '\u{2069}' => PDI,
972                _c => {
973                    #[cfg(debug_assertions)]
974                    {
975                        tracing::error!("invalid bidi ctrl char '{_c}'");
976                    }
977                    ON
978                }
979            },
980        }
981    }
982}
983
984bitflags! {
985    /// Mask of values that can affect the layout operation of a value.
986    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, bytemuck::NoUninit)]
987    #[repr(transparent)]
988    pub struct LayoutMask: u32 {
989        /// The `default_value`.
990        const DEFAULT_VALUE = 1 << 31;
991        /// The [`LayoutMetrics::constraints`], [`LayoutMetrics::z_constraints`] and [`LayoutMetrics::inline_constraints`].
992        const CONSTRAINTS = 1 << 30;
993
994        /// The [`LayoutMetrics::font_size`].
995        const FONT_SIZE = 1;
996        /// The [`LayoutMetrics::root_font_size`].
997        const ROOT_FONT_SIZE = 1 << 1;
998        /// The [`LayoutMetrics::scale_factor`].
999        const SCALE_FACTOR = 1 << 2;
1000        /// The [`LayoutMetrics::viewport`].
1001        const VIEWPORT = 1 << 3;
1002        /// The [`LayoutMetrics::screen_ppi`].
1003        const SCREEN_PPI = 1 << 4;
1004        /// The [`LayoutMetrics::direction`].
1005        const DIRECTION = 1 << 5;
1006        /// The [`LayoutMetrics::leftover`].
1007        const LEFTOVER = 1 << 6;
1008    }
1009}
1010impl Default for LayoutMask {
1011    /// Empty.
1012    fn default() -> Self {
1013        LayoutMask::empty()
1014    }
1015}