surf_n_term/view/
mod.rs

1//! Defines [View] that represents anything that can be rendered into a terminal.
2//! As well as some useful implementations such as [Text], [Flex], [Container], ...
3mod 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
50/// View is anything that can be layed out and rendered to the terminal
51pub trait View: Send + Sync {
52    /// Render view into a given surface with the provided layout
53    fn render(
54        &self,
55        ctx: &ViewContext,
56        surf: TerminalSurface<'_>,
57        layout: ViewLayout<'_>,
58    ) -> Result<(), Error>;
59
60    /// Compute layout of the view based on the constraints
61    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    /// Convert into boxed view
80    fn boxed<'a>(self) -> BoxView<'a>
81    where
82        Self: Sized + Send + Sync + 'a,
83    {
84        Box::new(self)
85    }
86
87    /// Convert into Arc-ed view
88    fn arc<'a>(self) -> ArcView<'a>
89    where
90        Self: Sized + Send + Sync + 'a,
91    {
92        Arc::new(self)
93    }
94
95    /// Wrapper around view that implements [std::fmt::Debug] which renders
96    /// view. Only supposed to be used for debugging.
97    fn debug(&self, size: Size) -> ViewDebug<&'_ Self>
98    where
99        Self: Sized,
100    {
101        ViewDebug { view: self, size }
102    }
103
104    /// Wrapper around view that calls trace function on every layout call
105    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    /// Tag the view
114    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    /// Convert into left side of [Either] view
123    fn left_view<R>(self) -> Either<Self, R>
124    where
125        Self: Sized,
126    {
127        Either::Left(self)
128    }
129
130    /// Convert into right side of [Either] view
131    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
252/// Something that can be converted to a [View]
253pub trait IntoView {
254    /// Result view type
255    type View: View;
256
257    /// Convert into a [View]
258    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    /// Dummy view context for debug purposes
287    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    /// Number of pixels in the single terminal cell
299    pub fn pixels_per_cell(&self) -> Size {
300        self.pixels_per_cell
301    }
302
303    /// Whether terminal supports glyph rendering
304    pub fn has_glyphs(&self) -> bool {
305        self.has_glyphs
306    }
307
308    /// Color depth supported by terminal
309    pub fn color_depth(&self) -> ColorDepth {
310        self.color_depth
311    }
312}
313
314/// Constraint that specify size of the view that it can take. Any view when layout
315/// should that the size between `min` and `max` sizes.
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
317pub struct BoxConstraint {
318    min: Size,
319    max: Size,
320}
321
322impl BoxConstraint {
323    /// Create new constraint
324    pub fn new(min: Size, max: Size) -> Self {
325        Self { min, max }
326    }
327
328    /// Constraint min and max equal to size
329    pub fn tight(size: Size) -> Self {
330        Self {
331            min: size,
332            max: size,
333        }
334    }
335
336    /// Constraint with zero min constrain
337    pub fn loose(size: Size) -> Self {
338        Self {
339            min: Size::empty(),
340            max: size,
341        }
342    }
343
344    /// Minimal size
345    pub fn min(&self) -> Size {
346        self.min
347    }
348
349    /// Maximum size
350    pub fn max(&self) -> Size {
351        self.max
352    }
353
354    /// Remove minimal size constraint
355    pub fn loosen(&self) -> Self {
356        Self {
357            min: Size::empty(),
358            max: self.max,
359        }
360    }
361
362    /// Clamp size with constraint
363    pub fn clamp(&self, size: Size) -> Size {
364        size.clamp(self.min, self.max)
365    }
366}
367
368/// Major axis of the [Flex] and [ScrollBar] views
369#[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
377/// Helper to get values along [Axis]
378pub trait AlongAxis {
379    /// Axis value
380    type Value;
381
382    /// Get value along major axis
383    fn major(&self, axis: Axis) -> Self::Value;
384
385    /// Get mutable reference along major axis
386    fn major_mut(&mut self, axis: Axis) -> &mut Self::Value;
387
388    /// Get value along minor axis
389    fn minor(&self, axis: Axis) -> Self::Value {
390        self.major(axis.cross())
391    }
392
393    /// Get mutable reference along minor axis
394    fn minor_mut(&mut self, axis: Axis) -> &mut Self::Value {
395        self.major_mut(axis.cross())
396    }
397
398    /// Construct new value given [Axis] and values along major and minor axes
399    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    /// Flip axis
466    pub fn cross(self) -> Self {
467        match self {
468            Self::Horizontal => Self::Vertical,
469            Self::Vertical => Self::Horizontal,
470        }
471    }
472
473    /// Get major axis value
474    pub fn major<T: AlongAxis>(&self, target: T) -> T::Value {
475        target.major(*self)
476    }
477
478    /// Get minor axis value
479    pub fn minor<T: AlongAxis>(&self, target: T) -> T::Value {
480        target.minor(*self)
481    }
482
483    /// Change constraint along axis
484    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/// View that does not effect rendering only adds tag as `Layout::data`
564/// for generated layout.
565#[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
625/// Renders nothing takes all space
626impl 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
647/// Fills with color takes all space
648impl 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
706/// Passed to `ViewDeserializer` and represents cache, when view with type `ref`
707/// is deserialized this cache is used to resolve `uid`s into corresponding view
708/// objects.
709pub 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
752/// View deserializer
753pub 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                // Generate debug message with calculated constraints and layout
813                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}