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}