1use crate::text::Grapheme;
21use crate::{Key, TextStyle, View};
22
23use super::assemble::{AssemblyCache, assemble, assemble_retained};
24use super::geometry::{Available, Constraint, Size};
25use super::height::{fit, heights};
26use super::width::widths;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Axis {
31 Width,
32 Height,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum LayoutErrorKind {
38 CanvasExtent,
39 ViewAllocation,
40 ViewportExtent,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct LayoutError {
46 kind: LayoutErrorKind,
47 axis: Axis,
48}
49
50impl LayoutError {
51 pub const fn axis(&self) -> Axis {
52 self.axis
53 }
54 pub const fn kind(&self) -> LayoutErrorKind {
55 self.kind
56 }
57 pub(crate) const fn missing_extent(axis: Axis) -> Self {
58 Self {
59 kind: LayoutErrorKind::CanvasExtent,
60 axis,
61 }
62 }
63 pub(crate) const fn missing_allocation(axis: Axis) -> Self {
64 Self {
65 kind: LayoutErrorKind::ViewAllocation,
66 axis,
67 }
68 }
69 pub(crate) const fn missing_viewport_extent(axis: Axis) -> Self {
70 Self {
71 kind: LayoutErrorKind::ViewportExtent,
72 axis,
73 }
74 }
75}
76
77impl std::fmt::Display for LayoutError {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self.kind {
80 LayoutErrorKind::CanvasExtent => write!(
81 f,
82 "Canvas requires an explicit {:?} on an unbounded axis",
83 self.axis
84 ),
85 LayoutErrorKind::ViewAllocation => write!(
86 f,
87 "a Canvas View command with Fill requires a finite {:?} allocation",
88 self.axis
89 ),
90 LayoutErrorKind::ViewportExtent => write!(
91 f,
92 "Viewport projection requires a finite {:?} allocation",
93 self.axis
94 ),
95 }
96 }
97}
98
99impl std::error::Error for LayoutError {}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct StyledGrapheme {
108 symbol: String,
109 width: usize,
110 style: TextStyle,
111}
112
113impl StyledGrapheme {
114 pub(crate) fn new(symbol: &Grapheme, style: TextStyle) -> Self {
127 Self {
128 symbol: symbol.as_str().to_owned(),
129 width: symbol.width(),
130 style,
131 }
132 }
133
134 pub fn symbol(&self) -> &str {
135 &self.symbol
136 }
137
138 pub const fn width(&self) -> usize {
139 self.width
140 }
141
142 pub const fn style(&self) -> &TextStyle {
143 &self.style
144 }
145
146 pub fn space(style: TextStyle) -> Self {
151 Self::new(Grapheme::space(), style)
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub struct VisibleRect {
161 x: i64,
162 y: i64,
163 width: usize,
164 height: usize,
165}
166
167impl VisibleRect {
168 pub const fn x(&self) -> i64 {
169 self.x
170 }
171
172 pub const fn y(&self) -> i64 {
173 self.y
174 }
175
176 pub const fn width(&self) -> usize {
177 self.width
178 }
179
180 pub const fn height(&self) -> usize {
181 self.height
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub struct AnchoredRect {
193 key: Key,
194 x: i64,
195 y: i64,
196 width: usize,
197 height: usize,
198 visible: Option<VisibleRect>,
199}
200
201impl AnchoredRect {
202 pub(super) const fn new(key: Key, x: usize, y: usize, width: usize, height: usize) -> Self {
203 Self {
204 key,
205 x: x as i64,
206 y: y as i64,
207 width,
208 height,
209 visible: Some(VisibleRect {
210 x: x as i64,
211 y: y as i64,
212 width,
213 height,
214 }),
215 }
216 }
217
218 pub const fn key(&self) -> Key {
220 self.key
221 }
222
223 pub const fn x(&self) -> i64 {
225 self.x
226 }
227
228 pub const fn y(&self) -> i64 {
230 self.y
231 }
232
233 pub const fn width(&self) -> usize {
234 self.width
235 }
236
237 pub const fn height(&self) -> usize {
238 self.height
239 }
240
241 pub const fn is_empty(&self) -> bool {
243 self.width == 0 || self.height == 0
244 }
245
246 pub const fn is_within_resolved_view(&self) -> bool {
253 match self.visible {
254 Some(_) if self.width == 0 && self.height == 0 => true,
255 Some(visible) => {
256 visible.x == self.x
257 && visible.y == self.y
258 && visible.width == self.width
259 && visible.height == self.height
260 }
261 None => false,
262 }
263 }
264
265 pub const fn visible(&self) -> Option<VisibleRect> {
267 self.visible
268 }
269
270 pub(super) fn locate(mut self, resolved: Size) -> Self {
274 self.clip(0, 0, resolved.width(), resolved.height());
275 self
276 }
277
278 pub(super) const fn offset(mut self, x: i64, y: i64) -> Self {
284 self.x = self.x.saturating_add(x);
285 self.y = self.y.saturating_add(y);
286 if let Some(visible) = &mut self.visible {
287 visible.x = visible.x.saturating_add(x);
288 visible.y = visible.y.saturating_add(y);
289 }
290 self
291 }
292
293 pub(super) fn clip(&mut self, x: i64, y: i64, width: usize, height: usize) {
294 let Some(visible) = self.visible else {
295 return;
296 };
297 let right = x.saturating_add(i64::try_from(width).unwrap_or(i64::MAX));
298 let bottom = y.saturating_add(i64::try_from(height).unwrap_or(i64::MAX));
299
300 if visible.width == 0 && visible.height == 0 {
301 if visible.x < x || visible.x >= right || visible.y < y || visible.y >= bottom {
302 self.visible = None;
303 }
304 return;
305 }
306
307 let visible_right = visible
308 .x
309 .saturating_add(i64::try_from(visible.width).unwrap_or(i64::MAX));
310 let visible_bottom = visible
311 .y
312 .saturating_add(i64::try_from(visible.height).unwrap_or(i64::MAX));
313 let left = visible.x.max(x);
314 let top = visible.y.max(y);
315 let clipped_right = visible_right.min(right);
316 let clipped_bottom = visible_bottom.min(bottom);
317 if clipped_right <= left || clipped_bottom <= top {
318 self.visible = None;
319 return;
320 }
321 self.visible = Some(VisibleRect {
322 x: left,
323 y: top,
324 width: usize::try_from(clipped_right - left).unwrap_or(usize::MAX),
325 height: usize::try_from(clipped_bottom - top).unwrap_or(usize::MAX),
326 });
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct ResolvedView {
339 size: Size,
340 rows: Vec<Vec<StyledGrapheme>>,
341 anchors: Vec<AnchoredRect>,
342}
343
344pub struct Resolver {
355 previous_available: Option<Available>,
356 previous_result: Option<ResolvedView>,
357 assembly: AssemblyCache,
358}
359
360impl Resolver {
361 pub const fn new() -> Self {
363 Self {
364 previous_available: None,
365 previous_result: None,
366 assembly: AssemblyCache::new(),
367 }
368 }
369
370 pub fn resolve(
376 &mut self,
377 view: &View,
378 available: Available,
379 ) -> Result<ResolvedView, LayoutError> {
380 if self.assembly.matches_previous_view(view)
381 && self.previous_available == Some(available)
382 && let Some(result) = &self.previous_result
383 {
384 return Ok(result.clone());
385 }
386
387 let fitted = fit(widths(view, available.width()));
388 let sized = heights(&fitted, Constraint::available(available.height()));
389 validate_canvas_extents(&sized)?;
390
391 self.assembly.begin_frame(view);
392 let mut rect = match assemble_retained(&sized, &mut self.assembly) {
393 Ok(rect) => rect,
394 Err(error) => {
395 self.assembly.abort_frame();
396 return Err(error);
397 }
398 };
399 crop_to_available(&mut rect, available);
400 let result = resolved(rect);
401 self.assembly.finish_frame(view);
402 self.previous_available = Some(available);
403 self.previous_result = Some(result.clone());
404 Ok(result)
405 }
406
407 pub fn clear(&mut self) {
409 self.previous_available = None;
410 self.previous_result = None;
411 self.assembly.clear();
412 }
413}
414
415impl Default for Resolver {
416 fn default() -> Self {
417 Self::new()
418 }
419}
420
421impl ResolvedView {
422 pub(crate) fn new(
423 size: Size,
424 rows: Vec<Vec<StyledGrapheme>>,
425 anchors: Vec<AnchoredRect>,
426 ) -> Self {
427 Self {
428 size,
429 rows,
430 anchors,
431 }
432 }
433
434 pub const fn size(&self) -> Size {
435 self.size
436 }
437
438 pub fn rows(&self) -> &[Vec<StyledGrapheme>] {
439 &self.rows
440 }
441
442 pub fn anchors(&self) -> &[AnchoredRect] {
448 &self.anchors
449 }
450
451 pub fn anchor(&self, key: impl Into<Key>) -> Option<&AnchoredRect> {
469 let key = key.into();
470 self.anchors.iter().find(|anchor| anchor.key == key)
471 }
472}
473
474pub fn measure(view: &View) -> Size {
481 try_measure(view).expect("intrinsic measurement requires every finite extent to be stated")
482}
483
484pub fn try_measure(view: &View) -> Result<Size, LayoutError> {
487 let fitted = fit(widths(view, None));
488 let sized = heights(&fitted, Constraint::unbounded());
489 validate_canvas_extents(&sized)?;
490 Ok(Size::new(sized.width, sized.height))
491}
492
493pub fn resolve(view: &View, available: Available) -> Result<ResolvedView, LayoutError> {
500 let fitted = fit(widths(view, available.width()));
501 let sized = heights(&fitted, Constraint::available(available.height()));
502 validate_canvas_extents(&sized)?;
503 let mut rect = assemble(&sized)?;
504 crop_to_available(&mut rect, available);
505 Ok(resolved(rect))
506}
507
508fn crop_to_available(rect: &mut super::assemble::Rect, available: Available) {
509 if let Some(width) = available.width() {
510 rect.crop_width(width, &TextStyle::new());
511 }
512 if let Some(height) = available.height() {
513 rect.crop_height(height);
514 }
515}
516
517fn resolved(rect: super::assemble::Rect) -> ResolvedView {
518 debug_assert_unique(&rect.anchors);
519 let size = rect.size();
520 let anchors = rect
521 .anchors
522 .into_iter()
523 .map(|anchor| anchor.locate(size))
524 .collect();
525 ResolvedView::new(size, rect.rows, anchors)
526}
527
528fn validate_canvas_extents(sized: &super::height::Sized<'_>) -> Result<(), LayoutError> {
529 use super::height::SizedNode;
530 match &sized.node {
531 SizedNode::Canvas {
532 canvas,
533 width_bounded,
534 height_bounded,
535 ..
536 } => {
537 if canvas.uses_viewport_sizing() && !width_bounded && canvas.explicit_width().is_none()
538 {
539 return Err(LayoutError::missing_extent(Axis::Width));
540 }
541 if canvas.uses_viewport_sizing()
542 && !height_bounded
543 && canvas.explicit_height().is_none()
544 {
545 return Err(LayoutError::missing_extent(Axis::Height));
546 }
547 Ok(())
548 }
549 SizedNode::Block { child, .. } => validate_canvas_extents(child),
550 SizedNode::Viewport {
551 viewport,
552 width_bounded,
553 height_bounded,
554 child,
555 } => {
556 if viewport.horizontal_projection().is_some() && !width_bounded {
557 return Err(LayoutError::missing_viewport_extent(Axis::Width));
558 }
559 if viewport.vertical_projection().is_some() && !height_bounded {
560 return Err(LayoutError::missing_viewport_extent(Axis::Height));
561 }
562 validate_canvas_extents(child)
563 }
564 SizedNode::Row(_, children) | SizedNode::Column(_, children) => {
565 children.iter().try_for_each(validate_canvas_extents)
566 }
567 SizedNode::Grid { rows, .. } => rows
568 .iter()
569 .flatten()
570 .try_for_each(|cell| validate_canvas_extents(&cell.child)),
571 SizedNode::Text { .. } => Ok(()),
572 }
573}
574
575fn debug_assert_unique(anchors: &[AnchoredRect]) {
584 debug_assert!(
585 {
586 let mut seen = std::collections::HashSet::with_capacity(anchors.len());
587 anchors.iter().all(|anchor| seen.insert(anchor.key()))
588 },
589 "two anchors carry one key; a key names one region"
590 );
591}