1mod layout;
4pub use layout::{
5 Layout, Tree, TreeId, TreeMut, TreeMutView, TreeNode, TreeStore, TreeView, ViewLayout,
6 ViewLayoutStore, ViewMutLayout,
7};
8
9mod container;
10pub use container::{Align, Container, Margins};
11
12pub mod flex;
13pub use flex::{Flex, FlexChild, FlexRef, Justify};
14
15mod scrollbar;
16use rasterize::{RGBADeserializer, SVG_COLORS};
17pub use scrollbar::{ScrollBar, ScrollBarFn, ScrollBarPosition};
18
19mod text;
20use serde_json::Value;
21pub use text::Text;
22
23mod dynamic;
24pub use dynamic::Dynamic;
25
26mod frame;
27pub use frame::Frame;
28
29mod offscreen;
30pub use offscreen::Offscreen;
31
32pub use either::{self, Either};
33
34use crate::{
35 Cell, Error, Face, FaceAttrs, FaceDeserializer, Image, Position, RGBA, Size, Surface,
36 SurfaceMut, SurfaceOwned, SurfaceView, Terminal, TerminalSurface, TerminalSurfaceExt,
37 encoder::ColorDepth, glyph::GlyphDeserializer, image::ImageAsciiView,
38};
39use serde::{
40 Deserialize, Deserializer, Serialize,
41 de::{self, DeserializeSeed},
42};
43use std::{any::Any, collections::HashMap, fmt::Debug, sync::Arc};
44
45use self::text::TextDeserializer;
46
47pub type BoxView<'a> = Box<dyn View + 'a>;
48pub type ArcView<'a> = Arc<dyn View + 'a>;
49
50pub trait View: Send + Sync {
52 fn render(
54 &self,
55 ctx: &ViewContext,
56 surf: TerminalSurface<'_>,
57 layout: ViewLayout<'_>,
58 ) -> Result<(), Error>;
59
60 fn layout(
62 &self,
63 ctx: &ViewContext,
64 ct: BoxConstraint,
65 layout: ViewMutLayout<'_>,
66 ) -> Result<(), Error>;
67
68 fn layout_new<'a>(
69 &self,
70 ctx: &ViewContext,
71 ct: BoxConstraint,
72 store: &'a mut ViewLayoutStore,
73 ) -> Result<ViewMutLayout<'a>, Error> {
74 let mut layout = ViewMutLayout::new(store, Layout::default());
75 self.layout(ctx, ct, layout.view_mut())?;
76 Ok(layout)
77 }
78
79 fn boxed<'a>(self) -> BoxView<'a>
81 where
82 Self: Sized + Send + Sync + 'a,
83 {
84 Box::new(self)
85 }
86
87 fn arc<'a>(self) -> ArcView<'a>
89 where
90 Self: Sized + Send + Sync + 'a,
91 {
92 Arc::new(self)
93 }
94
95 fn debug(&self, size: Size) -> ViewDebug<&'_ Self>
98 where
99 Self: Sized,
100 {
101 ViewDebug { view: self, size }
102 }
103
104 fn trace_layout<T>(self, trace: T) -> TraceLayout<Self, T>
106 where
107 T: Fn(&BoxConstraint, ViewLayout<'_>),
108 Self: Sized,
109 {
110 TraceLayout { view: self, trace }
111 }
112
113 fn tag<T>(self, tag: T) -> Tag<T, Self>
115 where
116 T: Any + Clone,
117 Self: Sized,
118 {
119 Tag::new(tag, self)
120 }
121
122 fn left_view<R>(self) -> Either<Self, R>
124 where
125 Self: Sized,
126 {
127 Either::Left(self)
128 }
129
130 fn right_view<L>(self) -> Either<L, Self>
132 where
133 Self: Sized,
134 {
135 Either::Right(self)
136 }
137}
138
139impl<V: View + ?Sized> View for &V {
140 fn render(
141 &self,
142 ctx: &ViewContext,
143 surf: TerminalSurface<'_>,
144 layout: ViewLayout<'_>,
145 ) -> Result<(), Error> {
146 (**self).render(ctx, surf, layout)
147 }
148
149 fn layout(
150 &self,
151 ctx: &ViewContext,
152 ct: BoxConstraint,
153 layout: ViewMutLayout<'_>,
154 ) -> Result<(), Error> {
155 (**self).layout(ctx, ct, layout)
156 }
157}
158
159impl<T: View + ?Sized> View for Box<T> {
160 fn render(
161 &self,
162 ctx: &ViewContext,
163 surf: TerminalSurface<'_>,
164 layout: ViewLayout<'_>,
165 ) -> Result<(), Error> {
166 (**self).render(ctx, surf, layout)
167 }
168
169 fn layout(
170 &self,
171 ctx: &ViewContext,
172 ct: BoxConstraint,
173 layout: ViewMutLayout<'_>,
174 ) -> Result<(), Error> {
175 (**self).layout(ctx, ct, layout)
176 }
177}
178
179impl<T: View + ?Sized> View for Arc<T> {
180 fn render(
181 &self,
182 ctx: &ViewContext,
183 surf: TerminalSurface<'_>,
184 layout: ViewLayout<'_>,
185 ) -> Result<(), Error> {
186 (**self).render(ctx, surf, layout)
187 }
188
189 fn layout(
190 &self,
191 ctx: &ViewContext,
192 ct: BoxConstraint,
193 layout: ViewMutLayout<'_>,
194 ) -> Result<(), Error> {
195 (**self).layout(ctx, ct, layout)
196 }
197}
198
199#[derive(Clone)]
200pub struct ViewDebug<V> {
201 view: V,
202 size: Size,
203}
204
205impl<V: View> std::fmt::Debug for ViewDebug<V> {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 let ctx = ViewContext::dummy();
208 let mut surf = SurfaceOwned::new(self.size);
209 surf.draw_check_pattern(
210 "fg=#282828,bg=#3c3836"
211 .parse()
212 .expect("[ViewDebug] failed parse face"),
213 );
214 surf.draw_view(&ctx, None, &self.view)
215 .map_err(|_| std::fmt::Error)?;
216 surf.debug().fmt(f)
217 }
218}
219
220#[derive(Clone)]
221pub struct TraceLayout<V, T> {
222 view: V,
223 trace: T,
224}
225
226impl<V, S> View for TraceLayout<V, S>
227where
228 V: View,
229 S: Fn(&BoxConstraint, ViewLayout<'_>) + Send + Sync,
230{
231 fn render(
232 &self,
233 ctx: &ViewContext,
234 surf: TerminalSurface<'_>,
235 layout: ViewLayout<'_>,
236 ) -> Result<(), Error> {
237 self.view.render(ctx, surf, layout)
238 }
239
240 fn layout(
241 &self,
242 ctx: &ViewContext,
243 ct: BoxConstraint,
244 mut layout: ViewMutLayout<'_>,
245 ) -> Result<(), Error> {
246 self.view.layout(ctx, ct, layout.view_mut())?;
247 (self.trace)(&ct, layout.view());
248 Ok(())
249 }
250}
251
252pub trait IntoView {
254 type View: View;
256
257 fn into_view(self) -> Self::View;
259}
260
261impl<V: View> IntoView for V {
262 type View = V;
263
264 fn into_view(self) -> Self::View {
265 self
266 }
267}
268
269#[derive(Debug, Clone)]
270pub struct ViewContext {
271 pub(crate) pixels_per_cell: Size,
272 pub(crate) has_glyphs: bool,
273 pub(crate) color_depth: ColorDepth,
274}
275
276impl ViewContext {
277 pub fn new(term: &dyn Terminal) -> Result<Self, Error> {
278 let caps = term.capabilities();
279 Ok(Self {
280 pixels_per_cell: term.size()?.pixels_per_cell(),
281 has_glyphs: caps.glyphs,
282 color_depth: caps.depth,
283 })
284 }
285
286 pub fn dummy() -> Self {
288 Self {
289 pixels_per_cell: Size {
290 height: 37,
291 width: 15,
292 },
293 has_glyphs: true,
294 color_depth: ColorDepth::TrueColor,
295 }
296 }
297
298 pub fn pixels_per_cell(&self) -> Size {
300 self.pixels_per_cell
301 }
302
303 pub fn has_glyphs(&self) -> bool {
305 self.has_glyphs
306 }
307
308 pub fn color_depth(&self) -> ColorDepth {
310 self.color_depth
311 }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
317pub struct BoxConstraint {
318 min: Size,
319 max: Size,
320}
321
322impl BoxConstraint {
323 pub fn new(min: Size, max: Size) -> Self {
325 Self { min, max }
326 }
327
328 pub fn tight(size: Size) -> Self {
330 Self {
331 min: size,
332 max: size,
333 }
334 }
335
336 pub fn loose(size: Size) -> Self {
338 Self {
339 min: Size::empty(),
340 max: size,
341 }
342 }
343
344 pub fn min(&self) -> Size {
346 self.min
347 }
348
349 pub fn max(&self) -> Size {
351 self.max
352 }
353
354 pub fn loosen(&self) -> Self {
356 Self {
357 min: Size::empty(),
358 max: self.max,
359 }
360 }
361
362 pub fn clamp(&self, size: Size) -> Size {
364 size.clamp(self.min, self.max)
365 }
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
370pub enum Axis {
371 #[serde(rename = "horizontal")]
372 Horizontal,
373 #[serde(rename = "vertical")]
374 Vertical,
375}
376
377pub trait AlongAxis {
379 type Value;
381
382 fn major(&self, axis: Axis) -> Self::Value;
384
385 fn major_mut(&mut self, axis: Axis) -> &mut Self::Value;
387
388 fn minor(&self, axis: Axis) -> Self::Value {
390 self.major(axis.cross())
391 }
392
393 fn minor_mut(&mut self, axis: Axis) -> &mut Self::Value {
395 self.major_mut(axis.cross())
396 }
397
398 fn from_axes(axis: Axis, major: Self::Value, minor: Self::Value) -> Self;
400}
401
402impl AlongAxis for Size {
403 type Value = usize;
404
405 fn major(&self, axis: Axis) -> Self::Value {
406 match axis {
407 Axis::Horizontal => self.width,
408 Axis::Vertical => self.height,
409 }
410 }
411
412 fn major_mut(&mut self, axis: Axis) -> &mut Self::Value {
413 match axis {
414 Axis::Horizontal => &mut self.width,
415 Axis::Vertical => &mut self.height,
416 }
417 }
418
419 fn from_axes(axis: Axis, major: Self::Value, minor: Self::Value) -> Self {
420 match axis {
421 Axis::Horizontal => Size {
422 width: major,
423 height: minor,
424 },
425 Axis::Vertical => Size {
426 width: minor,
427 height: major,
428 },
429 }
430 }
431}
432
433impl AlongAxis for Position {
434 type Value = usize;
435
436 fn major(&self, axis: Axis) -> Self::Value {
437 match axis {
438 Axis::Horizontal => self.col,
439 Axis::Vertical => self.row,
440 }
441 }
442
443 fn major_mut(&mut self, axis: Axis) -> &mut Self::Value {
444 match axis {
445 Axis::Horizontal => &mut self.col,
446 Axis::Vertical => &mut self.row,
447 }
448 }
449
450 fn from_axes(axis: Axis, major: Self::Value, minor: Self::Value) -> Self {
451 match axis {
452 Axis::Horizontal => Position {
453 col: major,
454 row: minor,
455 },
456 Axis::Vertical => Position {
457 col: minor,
458 row: major,
459 },
460 }
461 }
462}
463
464impl Axis {
465 pub fn cross(self) -> Self {
467 match self {
468 Self::Horizontal => Self::Vertical,
469 Self::Vertical => Self::Horizontal,
470 }
471 }
472
473 pub fn major<T: AlongAxis>(&self, target: T) -> T::Value {
475 target.major(*self)
476 }
477
478 pub fn minor<T: AlongAxis>(&self, target: T) -> T::Value {
480 target.minor(*self)
481 }
482
483 pub fn constraint(&self, ct: BoxConstraint, min: usize, max: usize) -> BoxConstraint {
485 match self {
486 Self::Horizontal => BoxConstraint::new(
487 Size {
488 height: ct.min().height,
489 width: min,
490 },
491 Size {
492 height: ct.max().height,
493 width: max,
494 },
495 ),
496 Self::Vertical => BoxConstraint::new(
497 Size {
498 height: min,
499 width: ct.min().width,
500 },
501 Size {
502 height: max,
503 width: ct.max().width,
504 },
505 ),
506 }
507 }
508}
509
510impl<L, R> View for either::Either<L, R>
511where
512 L: View,
513 R: View,
514{
515 fn render(
516 &self,
517 ctx: &ViewContext,
518 surf: TerminalSurface<'_>,
519 layout: ViewLayout<'_>,
520 ) -> Result<(), Error> {
521 either::for_both!(self, view => view.render(ctx, surf, layout))
522 }
523
524 fn layout(
525 &self,
526 ctx: &ViewContext,
527 ct: BoxConstraint,
528 layout: ViewMutLayout<'_>,
529 ) -> Result<(), Error> {
530 either::for_both!(self, view => view.layout(ctx, ct, layout))
531 }
532}
533
534impl<V: View> View for Option<V> {
535 fn render(
536 &self,
537 ctx: &ViewContext,
538 surf: TerminalSurface<'_>,
539 layout: ViewLayout<'_>,
540 ) -> Result<(), Error> {
541 if let Some(view) = self.as_ref() {
542 view.render(ctx, surf, layout)?
543 }
544 Ok(())
545 }
546
547 fn layout(
548 &self,
549 ctx: &ViewContext,
550 ct: BoxConstraint,
551 mut layout: ViewMutLayout<'_>,
552 ) -> Result<(), Error> {
553 match self.as_ref() {
554 Some(view) => view.layout(ctx, ct, layout)?,
555 None => {
556 *layout = Layout::new();
557 }
558 }
559 Ok(())
560 }
561}
562
563#[derive(Clone)]
566pub struct Tag<T, V> {
567 view: V,
568 tag: T,
569}
570
571impl<T: Clone + Any, V: View> Tag<T, V> {
572 pub fn new(tag: T, view: impl IntoView<View = V>) -> Self {
573 Self {
574 view: view.into_view(),
575 tag,
576 }
577 }
578}
579
580fn tag_from_json_value(
581 seed: &ViewDeserializer<'_>,
582 value: &serde_json::Value,
583) -> Result<Tag<serde_json::Value, ArcView<'static>>, Error> {
584 let view = value
585 .get("view")
586 .ok_or_else(|| Error::ParseError("Tag", "must include view attribute".to_owned()))?;
587 let tag = value
588 .get("tag")
589 .ok_or_else(|| Error::ParseError("Tag", "must include tag attribute".to_owned()))?;
590 Ok(Tag::new(tag.clone(), seed.deserialize(view)?))
591}
592
593impl<T, V> View for Tag<T, V>
594where
595 T: Clone + Any + Send + Sync,
596 V: View,
597{
598 fn render(
599 &self,
600 ctx: &ViewContext,
601 surf: TerminalSurface<'_>,
602 layout: ViewLayout<'_>,
603 ) -> Result<(), Error> {
604 let surf = layout.apply_to(surf);
605 let child_layout = layout.children().next().ok_or(Error::InvalidLayout)?;
606 self.view.render(ctx, surf, child_layout)?;
607 Ok(())
608 }
609
610 fn layout(
611 &self,
612 ctx: &ViewContext,
613 ct: BoxConstraint,
614 mut layout: ViewMutLayout<'_>,
615 ) -> Result<(), Error> {
616 let mut layout_child = layout.push_default();
617 self.view.layout(ctx, ct, layout_child.view_mut())?;
618 *layout = Layout::new()
619 .with_size(layout_child.size())
620 .with_data(self.tag.clone());
621 Ok(())
622 }
623}
624
625impl View for () {
627 fn render(
628 &self,
629 _ctx: &ViewContext,
630 _surf: TerminalSurface<'_>,
631 _layout: ViewLayout<'_>,
632 ) -> Result<(), Error> {
633 Ok(())
634 }
635
636 fn layout(
637 &self,
638 _ctx: &ViewContext,
639 ct: BoxConstraint,
640 mut layout: ViewMutLayout<'_>,
641 ) -> Result<(), Error> {
642 *layout = Layout::new().with_size(ct.max());
643 Ok(())
644 }
645}
646
647impl View for RGBA {
649 fn render(
650 &self,
651 _ctx: &ViewContext,
652 surf: TerminalSurface<'_>,
653 layout: ViewLayout<'_>,
654 ) -> Result<(), Error> {
655 let cell = Cell::new_char(Face::new(None, Some(*self), FaceAttrs::default()), ' ');
656 layout.apply_to(surf).fill(cell);
657 Ok(())
658 }
659
660 fn layout(
661 &self,
662 _ctx: &ViewContext,
663 ct: BoxConstraint,
664 mut layout: ViewMutLayout<'_>,
665 ) -> Result<(), Error> {
666 *layout = Layout::new().with_size(ct.max());
667 Ok(())
668 }
669}
670
671impl View for SurfaceView<'_, Cell> {
672 fn render(
673 &self,
674 _ctx: &ViewContext,
675 surf: TerminalSurface<'_>,
676 layout: ViewLayout<'_>,
677 ) -> Result<(), Error> {
678 let src_data = self.data();
679 let src_shape = self.shape();
680
681 let mut dst_surf = layout.apply_to(surf);
682 let width = dst_surf.width().min(src_shape.width);
683 let height = dst_surf.height().min(src_shape.height);
684 dst_surf
685 .view_mut(..height, ..width)
686 .fill_with(|pos, mut dst| {
687 let src = src_data[src_shape.offset(pos)].clone();
688 dst.overlay(src);
689 dst
690 });
691
692 Ok(())
693 }
694
695 fn layout(
696 &self,
697 _ctx: &ViewContext,
698 ct: BoxConstraint,
699 mut layout: ViewMutLayout<'_>,
700 ) -> Result<(), Error> {
701 *layout = Layout::new().with_size(ct.clamp(self.shape().size()));
702 Ok(())
703 }
704}
705
706pub trait ViewCache: Send + Sync {
710 fn get(&self, uid: i64) -> Option<ArcView<'static>>;
711}
712
713impl<T: ViewCache + ?Sized> ViewCache for Arc<T> {
714 fn get(&self, uid: i64) -> Option<ArcView<'static>> {
715 (**self).get(uid)
716 }
717}
718
719#[derive(Clone)]
720struct ViewCached {
721 cache: Option<Arc<dyn ViewCache>>,
722 uid: i64,
723}
724
725impl View for ViewCached {
726 fn render(
727 &self,
728 ctx: &ViewContext,
729 surf: TerminalSurface<'_>,
730 layout: ViewLayout<'_>,
731 ) -> Result<(), Error> {
732 if let Some(view) = layout.data::<ArcView<'static>>() {
733 view.render(ctx, surf, layout.view())?;
734 }
735 Ok(())
736 }
737
738 fn layout(
739 &self,
740 ctx: &ViewContext,
741 ct: BoxConstraint,
742 mut layout: ViewMutLayout<'_>,
743 ) -> Result<(), Error> {
744 if let Some(view) = self.cache.as_ref().and_then(|c| c.get(self.uid)) {
745 view.layout(ctx, ct, layout.view_mut())?;
746 layout.set_data(view);
747 }
748 Ok(())
749 }
750}
751
752pub struct ViewDeserializer<'a> {
754 colors: &'a HashMap<String, RGBA>,
755 view_cache: Option<Arc<dyn ViewCache>>,
756 handlers: HashMap<
757 String,
758 Box<dyn for<'b> Fn(&'b ViewDeserializer<'_>, &'b serde_json::Value) -> ArcView<'static>>,
759 >,
760}
761
762impl<'a> ViewDeserializer<'a> {
763 pub fn new(
764 colors: Option<&'a HashMap<String, RGBA>>,
765 view_cache: Option<Arc<dyn ViewCache>>,
766 ) -> Self {
767 Self {
768 colors: colors.unwrap_or(&SVG_COLORS),
769 view_cache,
770 handlers: HashMap::default(),
771 }
772 }
773
774 pub fn face<'de, D>(&self, deserializer: D) -> Result<Face, D::Error>
775 where
776 D: Deserializer<'de>,
777 {
778 FaceDeserializer {
779 colors: self.colors,
780 }
781 .deserialize(deserializer)
782 }
783
784 pub fn register<H>(&mut self, name: impl Into<String>, handler: H)
785 where
786 H: for<'b> Fn(&'b ViewDeserializer<'_>, &'b serde_json::Value) -> ArcView<'static>
787 + 'static,
788 {
789 self.handlers.insert(name.into(), Box::new(handler));
790 }
791}
792
793impl<'de> de::DeserializeSeed<'de> for &ViewDeserializer<'_> {
794 type Value = ArcView<'static>;
795
796 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
797 where
798 D: Deserializer<'de>,
799 {
800 let value = Value::deserialize(deserializer)?;
801 let Some(view_type) = value.get("type").and_then(Value::as_str) else {
802 return Err(de::Error::custom("map expected with \"type\" attribute"));
803 };
804 let view = match view_type {
805 "text" => TextDeserializer {
806 colors: self.colors,
807 }
808 .deserialize(&value)
809 .map_err(de::Error::custom)?
810 .arc(),
811 "trace-layout" => {
812 let view_value = value.get("view").ok_or_else(|| {
814 let err = Error::ParseError(
815 "TracelLayout",
816 "must include `view` attribute".to_owned(),
817 );
818 de::Error::custom(format!("[TraceLayout] {err}"))
819 })?;
820 let msg = value
821 .get("msg")
822 .and_then(|v| v.as_str())
823 .unwrap_or("trace-layout")
824 .to_owned();
825 self.deserialize(view_value)
826 .map(move |view| {
827 view.trace_layout(move |ct, layout| {
828 tracing::debug!(?ct, ?layout, "{}", msg);
829 })
830 })
831 .map_err(|err| de::Error::custom(format!("[TraceLayout] {err}")))?
832 .arc()
833 }
834 "flex" => Flex::from_json_value(self, &value)
835 .map_err(|err| de::Error::custom(format!("[Flex] {err}")))?
836 .arc(),
837 "container" => container::from_json_value(self, &value)
838 .map_err(|err| de::Error::custom(format!("[Container] {err}")))?
839 .arc(),
840 "glyph" => GlyphDeserializer {
841 colors: self.colors,
842 }
843 .deserialize(value)
844 .map_err(|err| de::Error::custom(format!("[Glyph] {err}")))?
845 .arc(),
846 "image" => Image::deserialize(value)
847 .map_err(|err| de::Error::custom(format!("[Image] {err}")))?
848 .arc(),
849 "image_ascii" => ImageAsciiView::deserialize(value)
850 .map_err(|err| de::Error::custom(format!("[ImageAscii] {err}")))?
851 .arc(),
852 "color" => RGBADeserializer {
853 colors: self.colors,
854 }
855 .deserialize(value)
856 .map_err(|err| de::Error::custom(format!("[RGBA] {err}")))?
857 .arc(),
858 "tag" => tag_from_json_value(self, &value)
859 .map_err(|err| de::Error::custom(format!("[Tag] {err}")))?
860 .arc(),
861 "ref" => {
862 let uid = value.get("ref").and_then(|v| v.as_i64()).ok_or_else(|| {
863 de::Error::custom("[ref] must include `uid: u64` attribute".to_string())
864 })?;
865 ViewCached {
866 cache: self.view_cache.clone(),
867 uid,
868 }
869 .arc()
870 }
871 name => match self.handlers.get(name) {
872 None => return Err(de::Error::custom(format!("unknown view type: {name}"))),
873 Some(handler) => handler(self, &value),
874 },
875 };
876 Ok(view)
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883
884 pub(crate) fn render(
885 ctx: &ViewContext,
886 view: &dyn View,
887 size: Size,
888 ) -> Result<SurfaceOwned<Cell>, Error> {
889 let mut surf = SurfaceOwned::new(size);
890
891 let mut layout_store = ViewLayoutStore::new();
892 let layout = view.layout_new(ctx, BoxConstraint::loose(size), &mut layout_store)?;
893
894 view.render(ctx, surf.as_mut(), layout.view())?;
895 Ok(surf)
896 }
897
898 #[test]
899 fn test_references() -> Result<(), Error> {
900 fn witness<V: View>(_: V) {
901 println!("{}", std::any::type_name::<V>());
902 }
903 let color = "#ff0000".parse::<RGBA>()?;
904 let color_boxed = color.boxed();
905 let color_arc: ArcView<'static> = Arc::new(color);
906
907 witness(color);
908 witness(color);
909 witness(&color as &dyn View);
910 witness(&color_boxed);
911 witness(color_boxed);
912 witness(&color_arc);
913 witness(color_arc);
914
915 Ok(())
916 }
917
918 #[test]
919 fn test_box_constraint() -> Result<(), Error> {
920 let ct = BoxConstraint::new(Size::new(10, 0), Size::new(10, 100));
921 let size = Size::new(1, 4);
922 assert_eq!(ct.clamp(size), Size::new(10, 4));
923 Ok(())
924 }
925
926 #[test]
927 fn test_view_deserialize() -> Result<(), Error> {
928 let view_value = serde_json::json!({
929 "type": "flex",
930 "direction": "vertical",
931 "justify": "center",
932 "children": [
933 {
934 "face": "bg=#ff0000/.2",
935 "view": {
936 "type": "container",
937 "horizontal": "center",
938 "child": {
939 "type": "text",
940 "text": {
941 "face": "bg=white,fg=black,bold",
942 "text": [
943 {
944 "glyph": {
945 "size": [1, 3],
946 "view_box": [0, 0, 128, 128],
947 "path": "M20.33 68.63L20.33 68.63L107.67 68.63Q107.26 73.17 106.02 77.49L106.02 77.49L72.86 77.49L72.86 86.35L86.04 86.35L86.04 95.00L77.18 95.00L77.18 103.86L66.27 103.86L66.27 108.18L64 108.18Q52.88 108.18 43.20 102.93Q33.51 97.68 27.44 88.72Q21.36 79.76 20.33 68.63ZM107.67 59.98L107.67 59.98L20.33 59.98Q21.36 48.86 27.44 39.90Q33.51 30.94 43.20 25.69Q52.88 20.43 64 20.43L64 20.43Q74.51 20.43 83.77 25.17L83.77 25.17L83.77 33.62L92.63 33.62L92.63 42.27L99.22 42.27L99.22 51.13L106.02 51.13Q107.26 55.45 107.67 59.98ZM64 41.24L64 41.24Q64 36.71 60.81 33.51Q57.61 30.32 53.08 30.32Q48.55 30.32 45.26 33.51Q41.96 36.71 41.96 41.24Q41.96 45.77 45.26 48.96Q48.55 52.16 53.08 52.16Q57.61 52.16 60.81 48.96Q64 45.77 64 41.24Z",
948 }
949 },
950 "Space Invaders "
951 ]
952 }
953 }
954 }
955 },
956 {
957 "align": "center",
958 "face": "bg=#00ff00/.05",
959 "view": {
960 "type": "image_ascii",
961 "data": "AAAAAAAAAAAAAAAAAAAAAP8AAAAAAP8AAAAAAAAA/wAAAP8AAAAAAAAA/////////wAAAAAA//8A////AP//AAAA//////////////8AAP8A/////////wD/AAD/AP8AAAAAAP8A/wAAAAAA//8A//8AAAAAAAAAAAAAAAAAAAAAAA==",
962 "channels": 1,
963 "size": [10, 13],
964 }
965 }
966 ]
967 });
968
969 let view = ViewDeserializer::new(None, None).deserialize(view_value)?;
970 let size = Size::new(10, 20);
971 println!("[view] deserialize: {:?}", view.debug(size));
972 let mut layout_store = ViewLayoutStore::new();
973 let layout = view.layout_new(
974 &ViewContext::dummy(),
975 BoxConstraint::loose(size),
976 &mut layout_store,
977 )?;
978 let mut reference_store = ViewLayoutStore::new();
979 let mut reference = ViewMutLayout::new(
980 &mut reference_store,
981 Layout::new().with_size(Size::new(8, 20)),
982 );
983 reference
984 .push(
985 Layout::new()
986 .with_position(Position::new(2, 0))
987 .with_size(Size::new(1, 20)),
988 )
989 .push(
990 Layout::new()
991 .with_position(Position::new(0, 1))
992 .with_size(Size::new(1, 18)),
993 );
994 reference.push(
995 Layout::new()
996 .with_position(Position::new(3, 3))
997 .with_size(Size::new(5, 13)),
998 );
999 assert_eq!(reference, layout);
1000
1001 Ok(())
1002 }
1003}