1use super::*;
54
55#[derive(Copy, Clone, Debug, PartialEq, Eq)]
63pub enum SizePolicy {
65 Auto,
67 Fixed(i32),
69 Remainder(i32),
71}
72
73impl SizePolicy {
74 fn resolve(self, default_size: i32, available_space: i32) -> i32 {
75 let resolved = match self {
76 SizePolicy::Auto => default_size,
77 SizePolicy::Fixed(value) => value,
78 SizePolicy::Remainder(margin) => available_space.saturating_sub(margin),
79 };
80 resolved.max(0)
81 }
82}
83
84impl Default for SizePolicy {
85 fn default() -> Self {
86 SizePolicy::Auto
87 }
88}
89
90#[derive(Copy, Clone, Debug, PartialEq, Eq)]
92pub enum StackDirection {
93 TopToBottom,
95 BottomToTop,
97}
98
99impl Default for StackDirection {
100 fn default() -> Self {
101 Self::TopToBottom
102 }
103}
104
105#[derive(Clone, Default)]
106struct ScopeState {
107 body: Recti,
109 cursor: Vec2i,
111 max: Option<Vec2i>,
113 next_row: i32,
115 indent: i32,
117}
118
119impl ScopeState {
120 fn reset_cursor_for_next_row(&mut self) {
122 self.cursor = vec2(self.indent, self.next_row);
123 }
124}
125
126#[derive(Copy, Clone)]
127struct ResolveCtx {
128 spacing: i32,
130 default_width: i32,
132 default_height: i32,
134}
135
136trait LayoutFlow {
137 fn next_local(&mut self, scope: &mut ScopeState, ctx: ResolveCtx) -> Recti;
139}
140
141#[derive(Clone, Default)]
142struct RowFlow {
143 widths: Vec<SizePolicy>,
145 height: SizePolicy,
147 item_index: usize,
149}
150
151impl RowFlow {
152 fn new(widths: &[SizePolicy], height: SizePolicy) -> Self {
153 Self {
154 widths: widths.to_vec(),
155 height,
156 item_index: 0,
157 }
158 }
159
160 fn apply_template(&mut self, widths: Vec<SizePolicy>, height: SizePolicy) {
161 self.widths = widths;
162 self.height = height;
163 }
164}
165
166impl LayoutFlow for RowFlow {
167 fn next_local(&mut self, scope: &mut ScopeState, ctx: ResolveCtx) -> Recti {
168 let row_len = self.widths.len();
170 if self.item_index == row_len {
171 self.item_index = 0;
172 scope.reset_cursor_for_next_row();
173 }
174
175 let width_policy = if self.widths.is_empty() {
177 SizePolicy::Auto
178 } else {
179 self.widths.get(self.item_index).copied().unwrap_or(SizePolicy::Auto)
180 };
181
182 let x = scope.cursor.x;
183 let y = scope.cursor.y;
184
185 let available_width = scope.body.width.saturating_sub(x);
187 let available_height = scope.body.height.saturating_sub(y);
188 let width = width_policy.resolve(ctx.default_width, available_width);
189 let height = self.height.resolve(ctx.default_height, available_height);
190
191 if self.item_index < self.widths.len() {
192 self.item_index += 1;
193 }
194
195 scope.cursor.x = scope.cursor.x.saturating_add(width).saturating_add(ctx.spacing);
197 let line_end = y.saturating_add(height).saturating_add(ctx.spacing);
198 scope.next_row = max(scope.next_row, line_end);
199
200 rect(x, y, width, height)
201 }
202}
203
204#[derive(Clone)]
205struct StackFlow {
206 width: SizePolicy,
208 height: SizePolicy,
210 direction: StackDirection,
212 offset: i32,
214}
215
216impl Default for StackFlow {
217 fn default() -> Self {
218 Self {
219 width: SizePolicy::Remainder(0),
220 height: SizePolicy::Auto,
221 direction: StackDirection::TopToBottom,
222 offset: 0,
223 }
224 }
225}
226
227impl StackFlow {
228 fn new(width: SizePolicy, height: SizePolicy, direction: StackDirection) -> Self {
229 Self { width, height, direction, offset: 0 }
230 }
231
232 fn apply_template(&mut self, width: SizePolicy, height: SizePolicy, direction: StackDirection) {
233 self.width = width;
234 self.height = height;
235 self.direction = direction;
236 self.offset = 0;
237 }
238}
239
240impl LayoutFlow for StackFlow {
241 fn next_local(&mut self, scope: &mut ScopeState, ctx: ResolveCtx) -> Recti {
242 let x = scope.indent;
243 let available_width = scope.body.width.saturating_sub(x);
244 let width = self.width.resolve(ctx.default_width, available_width);
245
246 match self.direction {
247 StackDirection::TopToBottom => {
248 let y = scope.next_row;
250 let available_height = scope.body.height.saturating_sub(y);
251 let height = self.height.resolve(ctx.default_height, available_height);
252
253 let next = y.saturating_add(height).saturating_add(ctx.spacing);
255 scope.next_row = next;
256 scope.cursor = vec2(scope.indent, next);
257
258 rect(x, y, width, height)
259 }
260 StackDirection::BottomToTop => {
261 let available_height = scope.body.height.saturating_sub(self.offset);
263 let height = self.height.resolve(ctx.default_height, available_height);
264 let y = scope.body.height.saturating_sub(self.offset).saturating_sub(height);
265 self.offset = self.offset.saturating_add(height).saturating_add(ctx.spacing);
266 rect(x, y, width, height)
267 }
268 }
269 }
270}
271
272#[derive(Clone)]
273enum FlowState {
274 Row(RowFlow),
276 Stack(StackFlow),
278}
279
280impl Default for FlowState {
281 fn default() -> Self {
282 FlowState::Row(RowFlow::new(&[SizePolicy::Auto], SizePolicy::Auto))
283 }
284}
285
286impl FlowState {
287 fn as_template(&self) -> FlowTemplate {
289 match self {
290 FlowState::Row(row) => FlowTemplate::Row {
291 widths: row.widths.clone(),
292 height: row.height,
293 },
294 FlowState::Stack(stack) => FlowTemplate::Stack {
295 width: stack.width,
296 height: stack.height,
297 direction: stack.direction,
298 },
299 }
300 }
301
302 fn apply_template(&mut self, template: FlowTemplate) {
303 match template {
304 FlowTemplate::Row { widths, height } => match self {
305 FlowState::Row(row) => row.apply_template(widths, height),
306 _ => {
307 *self = FlowState::Row(RowFlow::new(widths.as_slice(), height));
308 }
309 },
310 FlowTemplate::Stack { width, height, direction } => match self {
311 FlowState::Stack(stack) => stack.apply_template(width, height, direction),
312 _ => {
313 *self = FlowState::Stack(StackFlow::new(width, height, direction));
314 }
315 },
316 }
317 }
318
319 fn next_local(&mut self, scope: &mut ScopeState, ctx: ResolveCtx) -> Recti {
321 match self {
322 FlowState::Row(flow) => flow.next_local(scope, ctx),
323 FlowState::Stack(flow) => flow.next_local(scope, ctx),
324 }
325 }
326}
327
328#[derive(Clone)]
329struct LayoutFrame {
330 scope: ScopeState,
332 flow: FlowState,
334}
335
336impl LayoutFrame {
337 fn new(body: Recti, scroll: Vec2i) -> Self {
338 Self {
339 scope: ScopeState {
340 body: rect(body.x - scroll.x, body.y - scroll.y, body.width, body.height),
342 cursor: vec2(0, 0),
343 max: None,
344 next_row: 0,
345 indent: 0,
346 },
347 flow: FlowState::default(),
348 }
349 }
350}
351
352#[derive(Clone, Default)]
353pub(crate) struct LayoutEngine {
354 pub style: Style,
356 pub last_rect: Recti,
358 default_cell_height: i32,
360 stack: Vec<LayoutFrame>,
362}
363
364impl LayoutEngine {
365 fn push_scope_with_flow(&mut self, body: Recti, scroll: Vec2i, flow: FlowState) {
367 let mut frame = LayoutFrame::new(body, scroll);
368 frame.flow = flow;
369 self.stack.push(frame);
370 }
371
372 fn top(&self) -> &LayoutFrame {
373 self.stack.last().expect("Layout stack should never be empty when accessed")
374 }
375
376 fn top_mut(&mut self) -> &mut LayoutFrame {
377 self.stack.last_mut().expect("Layout stack should never be empty when accessed")
378 }
379
380 fn fallback_dimensions(&self, preferred: Dimensioni) -> (i32, i32) {
381 let padding = self.style.padding;
382 let fallback_width = self.style.default_cell_width + padding * 2;
384 let base_height = if self.default_cell_height > 0 { self.default_cell_height } else { 0 };
386 let fallback_height = if base_height > 0 { base_height } else { padding * 2 };
387
388 let default_width = if preferred.width > 0 { preferred.width } else { fallback_width };
389 let default_height = if preferred.height > 0 { preferred.height } else { fallback_height };
390 (default_width, default_height)
391 }
392
393 pub fn reset(&mut self, body: Recti, scroll: Vec2i) {
394 self.stack.clear();
395 self.last_rect = Recti::default();
396 self.push_scope_with_flow(body, scroll, FlowState::default());
398 }
399
400 pub fn set_default_cell_height(&mut self, height: i32) {
401 self.default_cell_height = height.max(0);
402 }
403
404 pub fn current_body(&self) -> Recti {
405 self.top().scope.body
406 }
407
408 pub fn current_max(&self) -> Option<Vec2i> {
409 self.top().scope.max
410 }
411
412 pub fn pop_scope(&mut self) {
413 self.stack.pop();
414 }
415
416 pub fn adjust_indent(&mut self, delta: i32) {
417 self.top_mut().scope.indent += delta;
418 }
419
420 pub fn begin_column(&mut self) {
421 let layout_rect = self.next();
423 self.push_scope_with_flow(layout_rect, vec2(0, 0), FlowState::Row(RowFlow::new(&[SizePolicy::Auto], SizePolicy::Auto)));
424 }
425
426 pub fn end_column(&mut self) {
427 let finished = self.stack.pop().expect("cannot end column without an active child layout");
428 let parent = self.top_mut();
429
430 let child_position_x = finished.scope.cursor.x + finished.scope.body.x - parent.scope.body.x;
432 let child_next_row = finished.scope.next_row + finished.scope.body.y - parent.scope.body.y;
433
434 parent.scope.cursor.x = max(parent.scope.cursor.x, child_position_x);
435 parent.scope.next_row = max(parent.scope.next_row, child_next_row);
436
437 match (&mut parent.scope.max, finished.scope.max) {
439 (None, None) => (),
440 (Some(_), None) => (),
441 (None, Some(m)) => parent.scope.max = Some(m),
442 (Some(am), Some(bm)) => {
443 parent.scope.max = Some(Vec2i::new(max(am.x, bm.x), max(am.y, bm.y)));
444 }
445 }
446 }
447
448 pub fn row(&mut self, widths: &[SizePolicy], height: SizePolicy) {
449 let frame = self.top_mut();
450 frame.flow = FlowState::Row(RowFlow::new(widths, height));
451 frame.scope.reset_cursor_for_next_row();
453 }
454
455 pub fn stack(&mut self, width: SizePolicy, height: SizePolicy) {
456 self.stack_with_direction(width, height, StackDirection::TopToBottom);
457 }
458
459 pub fn stack_with_direction(&mut self, width: SizePolicy, height: SizePolicy, direction: StackDirection) {
460 let frame = self.top_mut();
461 frame.flow = FlowState::Stack(StackFlow::new(width, height, direction));
462 frame.scope.reset_cursor_for_next_row();
464 }
465
466 pub(crate) fn snapshot_flow_state(&self) -> FlowSnapshot {
467 FlowSnapshot::from_layout(self.top())
468 }
469
470 pub(crate) fn restore_flow_state(&mut self, snapshot: FlowSnapshot) {
471 snapshot.apply(self.top_mut());
472 }
473
474 pub fn next(&mut self) -> Recti {
475 self.next_with_preferred(Dimensioni::new(0, 0))
476 }
477
478 pub fn next_with_preferred(&mut self, preferred: Dimensioni) -> Recti {
479 let spacing = self.style.spacing;
480 let (default_width, default_height) = self.fallback_dimensions(preferred);
481 let mut local = {
482 let frame = self.top_mut();
483 let ctx = ResolveCtx { spacing, default_width, default_height };
484 frame.flow.next_local(&mut frame.scope, ctx)
485 };
486
487 let origin = {
489 let frame = self.top();
490 vec2(frame.scope.body.x, frame.scope.body.y)
491 };
492
493 local.x += origin.x;
494 local.y += origin.y;
495
496 {
497 let frame = self.top_mut();
498 match frame.scope.max {
500 None => frame.scope.max = Some(Vec2i::new(local.x + local.width, local.y + local.height)),
501 Some(am) => {
502 frame.scope.max = Some(Vec2i::new(max(am.x, local.x + local.width), max(am.y, local.y + local.height)));
503 }
504 }
505 }
506
507 self.last_rect = local;
508 self.last_rect
509 }
510}
511
512#[derive(Clone)]
513enum FlowTemplate {
514 Row {
516 widths: Vec<SizePolicy>,
517 height: SizePolicy,
518 },
519 Stack {
521 width: SizePolicy,
522 height: SizePolicy,
523 direction: StackDirection,
524 },
525}
526
527pub(crate) struct FlowSnapshot {
528 flow: FlowTemplate,
530}
531
532impl FlowSnapshot {
533 fn from_layout(layout: &LayoutFrame) -> Self {
534 Self { flow: layout.flow.as_template() }
535 }
536
537 fn apply(self, layout: &mut LayoutFrame) {
538 layout.flow.apply_template(self.flow);
539 }
540}
541
542pub(crate) type LayoutManager = LayoutEngine;
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 #[test]
549 fn layout_next_advances_row() {
550 let mut layout = LayoutManager::default();
551 layout.style = Style::default();
552 let body = rect(0, 0, 100, 100);
553 layout.reset(body, vec2(0, 0));
554 layout.set_default_cell_height(10);
555 layout.row(&[SizePolicy::Auto], SizePolicy::Auto);
556
557 let first = layout.next();
558 let second = layout.next();
559
560 let expected_width = layout.style.default_cell_width + layout.style.padding * 2;
561 assert_eq!(first.x, body.x);
562 assert_eq!(first.y, body.y);
563 assert_eq!(first.width, expected_width);
564 assert_eq!(first.height, 10);
565 assert_eq!(second.x, body.x);
566 assert_eq!(second.y, body.y + first.height + layout.style.spacing);
567 }
568
569 #[test]
570 fn layout_remainder_consumes_available_width() {
571 let mut layout = LayoutManager::default();
572 layout.style = Style::default();
573 let body = rect(0, 0, 120, 40);
574 layout.reset(body, vec2(0, 0));
575 layout.set_default_cell_height(10);
576 layout.row(&[SizePolicy::Remainder(0)], SizePolicy::Fixed(10));
577
578 let cell = layout.next();
579 assert_eq!(cell.width, body.width);
580 assert_eq!(cell.height, 10);
581 }
582
583 #[test]
584 fn stack_flow_uses_full_width_by_default() {
585 let mut layout = LayoutManager::default();
586 layout.style = Style::default();
587 let body = rect(0, 0, 120, 60);
588 layout.reset(body, vec2(0, 0));
589 layout.set_default_cell_height(10);
590 layout.stack(SizePolicy::Remainder(0), SizePolicy::Auto);
591
592 let first = layout.next();
593 let second = layout.next();
594
595 assert_eq!(first.width, body.width);
596 assert_eq!(second.y, first.y + first.height + layout.style.spacing);
597 }
598
599 #[test]
600 fn stack_flow_bottom_to_top_anchors_to_scope_bottom() {
601 let mut layout = LayoutManager::default();
602 layout.style = Style::default();
603 let body = rect(0, 0, 120, 60);
604 layout.reset(body, vec2(0, 0));
605 layout.set_default_cell_height(10);
606 layout.stack_with_direction(SizePolicy::Remainder(0), SizePolicy::Fixed(10), StackDirection::BottomToTop);
607
608 let first = layout.next();
609 let second = layout.next();
610
611 assert_eq!(first.width, body.width);
612 assert_eq!(first.y, body.y + body.height - 10);
613 assert_eq!(second.y, first.y - (10 + layout.style.spacing));
614 }
615}