Skip to main content

egui_macview/layout/
mod.rs

1//! Layout engines: `MacFixedView` (absolute pt), `MacFlexView` (springs/struts), `MacSplitView` (NSSplitView),
2//! `MacScrollView` (NSScrollView), and `MacStackView` (NSStackView).
3
4pub mod scroll_view;
5pub mod scrollbar;
6pub mod split_view;
7pub mod stack_view;
8
9pub use scroll_view::{MacScrollView, MacScrollViewOutput};
10pub use scrollbar::render_overlay_thumb;
11pub use split_view::{MacSplitOrientation, MacSplitView};
12pub use stack_view::{
13    MacStackAlignment, MacStackOrientation, MacStackUi, MacStackView, MacStackViewOutput,
14};
15
16use egui::{Pos2, Rect, Ui, Vec2};
17
18/// Snaps a logical coordinate to the nearest physical hardware pixel boundary
19/// according to the Subpixel Snap Criterion (SPEC-001 ยง2.1 A):
20///
21/// $$x_{\text{pixel}} = \frac{\lfloor x_{\text{logical}} \cdot s + 0.5 \rfloor}{s}$$
22///
23/// # Example
24///
25/// ```rust
26/// use egui_macview::layout::subpixel_snap;
27///
28/// let snapped = subpixel_snap(10.333, 2.0); // 200% scaling
29/// assert_eq!(snapped, 10.5);
30/// ```
31#[inline]
32pub fn subpixel_snap(logical: f32, scale_factor: f32) -> f32 {
33    let s = scale_factor.max(f32::EPSILON);
34    (logical * s + 0.5).floor() / s
35}
36
37/// Snaps a 2D logical point `Pos2` to hardware pixel boundaries.
38///
39/// # Example
40///
41/// ```rust
42/// use egui_macview::layout::subpixel_snap_pos;
43/// use egui::Pos2;
44///
45/// let snapped = subpixel_snap_pos(Pos2::new(10.25, 20.75), 1.0);
46/// assert_eq!(snapped, Pos2::new(10.0, 21.0));
47/// ```
48#[inline]
49pub fn subpixel_snap_pos(pos: Pos2, scale_factor: f32) -> Pos2 {
50    Pos2::new(
51        subpixel_snap(pos.x, scale_factor),
52        subpixel_snap(pos.y, scale_factor),
53    )
54}
55
56/// Snaps a logical `Rect` to hardware pixel boundaries.
57///
58/// # Example
59///
60/// ```rust
61/// use egui_macview::layout::subpixel_snap_rect;
62/// use egui::{Pos2, Rect};
63///
64/// let rect = Rect::from_min_max(Pos2::new(0.4, 0.4), Pos2::new(10.6, 20.6));
65/// let snapped = subpixel_snap_rect(rect, 1.0);
66/// assert_eq!(snapped.min, Pos2::new(0.0, 0.0));
67/// assert_eq!(snapped.max, Pos2::new(11.0, 21.0));
68/// ```
69#[inline]
70pub fn subpixel_snap_rect(rect: Rect, scale_factor: f32) -> Rect {
71    let min = subpixel_snap_pos(rect.min, scale_factor);
72    let max = subpixel_snap_pos(rect.max, scale_factor);
73    Rect::from_min_max(min, max)
74}
75
76/// A logical geometry frame in AppKit points.
77#[derive(Debug, Clone, Copy, PartialEq)]
78pub struct MacFrame {
79    /// Top-left origin point in logical points.
80    pub origin: Pos2,
81    /// Width and height dimensions in logical points.
82    pub size: Vec2,
83}
84
85impl MacFrame {
86    /// Constructs a `MacFrame` with the given origin `(x, y)` and size `(w, h)`.
87    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
88        Self {
89            origin: Pos2::new(x, y),
90            size: Vec2::new(w, h),
91        }
92    }
93
94    /// Converts frame into an `egui::Rect`.
95    pub fn to_rect(&self) -> Rect {
96        Rect::from_min_size(self.origin, self.size)
97    }
98}
99
100/// Absolute pt coordinate container based on macOS NIB geometries.
101pub struct MacFixedView<'a> {
102    /// Underlying egui `Ui` context.
103    pub ui: &'a mut Ui,
104    /// Absolute bounding rectangle of the fixed container.
105    pub container_rect: Rect,
106}
107
108impl<'a> MacFixedView<'a> {
109    /// Creates a new `MacFixedView` wrapping `ui` and bounded by `container_rect`.
110    pub fn new(ui: &'a mut Ui, container_rect: Rect) -> Self {
111        Self { ui, container_rect }
112    }
113
114    /// Allocates an exact subview at relative NIB coordinates (dx, dy, w, h).
115    pub fn subview_rect(&self, rel_origin: Pos2, size: Vec2) -> Rect {
116        Rect::from_min_size(
117            Pos2::new(
118                self.container_rect.left() + rel_origin.x,
119                self.container_rect.top() + rel_origin.y,
120            ),
121            size,
122        )
123    }
124}
125
126/// Flexible container supporting auto-resizing masks and fill splits.
127pub struct MacFlexView<'a> {
128    /// Underlying egui `Ui` context.
129    pub ui: &'a mut Ui,
130    /// Available bounding rectangle for flexible layout.
131    pub available_rect: Rect,
132}
133
134impl<'a> MacFlexView<'a> {
135    /// Creates a new `MacFlexView` wrapping `ui` within `available_rect`.
136    pub fn new(ui: &'a mut Ui, available_rect: Rect) -> Self {
137        Self { ui, available_rect }
138    }
139}
140
141/// Deterministic horizontal row container with fixed height and spacing budget.
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct MacRow {
144    /// Vertical offset of the row in container coordinates.
145    pub y_offset: f32,
146    /// Fixed row height in points.
147    pub height: f32,
148    /// Horizontal cursor advancing as cells are allocated.
149    pub cursor_x: f32,
150    /// Spacing between adjacent cells in points.
151    pub item_spacing: f32,
152    /// Total available container width in points.
153    pub container_width: f32,
154}
155
156impl MacRow {
157    /// Creates a new `MacRow` container.
158    pub fn new(y_offset: f32, height: f32, container_width: f32, item_spacing: f32) -> Self {
159        Self {
160            y_offset,
161            height,
162            cursor_x: 0.0,
163            item_spacing,
164            container_width,
165        }
166    }
167
168    /// Allocates next cell of width `width`, advancing cursor with spacing.
169    /// Returns relative bounds `(x, y, width, height)`.
170    pub fn allocate_cell(&mut self, width: f32) -> (f32, f32, f32, f32) {
171        let x = self.cursor_x;
172        let y = self.y_offset;
173        let h = self.height;
174        self.cursor_x += width + self.item_spacing;
175        (x, y, width, h)
176    }
177
178    /// Returns remaining horizontal budget in points.
179    pub fn remaining_width(&self) -> f32 {
180        (self.container_width - self.cursor_x).max(0.0)
181    }
182}