Skip to main content

dc_bundle/definition/
view.rs

1/*
2 * Copyright 2024 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16use std::collections::HashMap;
17use std::sync::atomic::AtomicU16;
18
19use crate::background::{background, Background};
20use crate::blend::BlendMode;
21use crate::font::{FontStretch, FontStyle, FontWeight, TextDecoration};
22use crate::frame_extras::FrameExtras;
23use crate::geometry::{Rectangle, Size};
24use crate::layout_style::LayoutStyle;
25use crate::node_style::{Display, NodeStyle};
26use crate::path::line_height::Line_height_type;
27use crate::path::{LineHeight, Stroke};
28use crate::pointer::PointerEvents;
29use crate::positioning::{FlexWrap, LayoutSizing, Overflow, OverflowDirection, ScrollInfo};
30use crate::reaction::Reaction;
31use crate::text::{TextAlign, TextAlignVertical, TextOverflow};
32use crate::text_style::StyledTextRun;
33use crate::variable::NumOrVar;
34use crate::view::view::RenderMethod;
35use crate::view::view_data::{Container, StyledTextRuns, Text, View_data_type};
36use crate::view::{ComponentInfo, View, ViewData};
37use crate::view_shape::ViewShape;
38use crate::view_style::ViewStyle;
39
40impl NodeStyle {
41    pub(crate) fn new_default() -> NodeStyle {
42        NodeStyle {
43            font_color: Some(Background::new_with_background(background::Background_type::None(
44                ().into(),
45            )))
46            .into(),
47            font_size: Some(NumOrVar::from_num(18.0)).into(),
48            font_family: None,
49            font_weight: Some(FontWeight::normal()).into(),
50            font_style: FontStyle::FONT_STYLE_NORMAL.into(),
51            text_decoration: TextDecoration::TEXT_DECORATION_NONE.into(),
52            letter_spacing: None,
53            font_stretch: Some(FontStretch::normal()).into(),
54            backgrounds: Vec::new(),
55            box_shadows: Vec::new(),
56            stroke: Some(Stroke::default()).into(),
57            opacity: None,
58            transform: None.into(),
59            relative_transform: None.into(),
60            text_align: TextAlign::TEXT_ALIGN_LEFT.into(),
61            text_align_vertical: TextAlignVertical::TEXT_ALIGN_VERTICAL_TOP.into(),
62            text_overflow: TextOverflow::TEXT_OVERFLOW_CLIP.into(),
63            text_shadow: None.into(),
64            node_size: Some(Size { width: 0.0, height: 0.0, ..Default::default() }).into(),
65            line_height: Some(LineHeight {
66                line_height_type: Some(Line_height_type::Percent(1.0)),
67                ..Default::default()
68            })
69            .into(),
70            line_count: None,
71            font_features: Vec::new(),
72            filters: Vec::new(),
73            backdrop_filters: Vec::new(),
74            blend_mode: BlendMode::BLEND_MODE_PASS_THROUGH.into(),
75            display_type: Display::DISPLAY_FLEX.into(),
76            flex_wrap: FlexWrap::FLEX_WRAP_NO_WRAP.into(),
77            grid_layout_type: None,
78            grid_columns_rows: 0,
79            grid_adaptive_min_size: 1,
80            grid_span_contents: vec![],
81            overflow: Overflow::OVERFLOW_VISIBLE.into(),
82            max_children: None,
83            overflow_node_id: None,
84            overflow_node_name: None,
85            cross_axis_item_spacing: 0.0,
86            horizontal_sizing: LayoutSizing::LAYOUT_SIZING_FIXED.into(),
87            vertical_sizing: LayoutSizing::LAYOUT_SIZING_FIXED.into(),
88            aspect_ratio: None,
89            pointer_events: PointerEvents::POINTER_EVENTS_INHERIT.into(),
90            meter_data: None.into(),
91            hyperlink: None.into(),
92            shader_data: None.into(),
93            scalable_data: None.into(),
94            animation_override: None.into(),
95            ..Default::default()
96        }
97    }
98}
99
100impl ViewStyle {
101    pub fn new_default() -> Self {
102        Self {
103            layout_style: Some(LayoutStyle::new_default()).into(),
104            node_style: Some(NodeStyle::new_default()).into(),
105            ..Default::default()
106        }
107    }
108    pub fn node_style(&self) -> &NodeStyle {
109        self.node_style.as_ref().expect("NodeStyle is required.")
110    }
111    pub fn node_style_mut(&mut self) -> &mut NodeStyle {
112        self.node_style.as_mut().expect("NodeStyle is required.")
113    }
114    pub fn layout_style(&self) -> &LayoutStyle {
115        self.layout_style.as_ref().expect("LayoutStyle is required.")
116    }
117    pub fn layout_style_mut(&mut self) -> &mut LayoutStyle {
118        self.layout_style.as_mut().expect("LayoutStyle is required.")
119    }
120}
121
122impl ViewStyle {
123    /// Compute the difference between this style and the given style, returning a style
124    /// that can be applied to this style to make it equal the given style using apply_non_default.
125    pub fn difference(&self, other: &ViewStyle) -> ViewStyle {
126        let mut delta = ViewStyle::new_default();
127        if self.node_style().font_color != other.node_style().font_color {
128            delta.node_style_mut().font_color = other.node_style().font_color.clone();
129        }
130        if self.node_style().font_size != other.node_style().font_size {
131            delta.node_style_mut().font_size = other.node_style().font_size.clone();
132        }
133        if self.node_style().font_family != other.node_style().font_family {
134            delta.node_style_mut().font_family = other.node_style().font_family.clone();
135        }
136        if self.node_style().font_weight != other.node_style().font_weight {
137            delta.node_style_mut().font_weight = other.node_style().font_weight.clone();
138        }
139        if self.node_style().font_style != other.node_style().font_style {
140            delta.node_style_mut().font_style = other.node_style().font_style;
141        }
142        if self.node_style().text_decoration != other.node_style().text_decoration {
143            delta.node_style_mut().text_decoration = other.node_style().text_decoration;
144        }
145        if self.node_style().letter_spacing != other.node_style().letter_spacing {
146            delta.node_style_mut().letter_spacing = other.node_style().letter_spacing;
147        }
148        if self.node_style().font_stretch != other.node_style().font_stretch {
149            delta.node_style_mut().font_stretch = other.node_style().font_stretch.clone();
150        }
151        if self.node_style().backgrounds != other.node_style().backgrounds {
152            delta.node_style_mut().backgrounds = other.node_style().backgrounds.clone();
153        }
154        if self.node_style().box_shadows != other.node_style().box_shadows {
155            delta.node_style_mut().box_shadows = other.node_style().box_shadows.clone();
156        }
157        if self.node_style().stroke != other.node_style().stroke {
158            delta.node_style_mut().stroke = other.node_style().stroke.clone();
159        }
160        if self.node_style().opacity != other.node_style().opacity {
161            delta.node_style_mut().opacity = other.node_style().opacity;
162        }
163        if self.node_style().transform != other.node_style().transform {
164            delta.node_style_mut().transform = other.node_style().transform.clone();
165        }
166        if self.node_style().relative_transform != other.node_style().relative_transform {
167            delta.node_style_mut().relative_transform =
168                other.node_style().relative_transform.clone();
169        }
170        if self.node_style().text_align != other.node_style().text_align {
171            delta.node_style_mut().text_align = other.node_style().text_align;
172        }
173        if self.node_style().text_align_vertical != other.node_style().text_align_vertical {
174            delta.node_style_mut().text_align_vertical = other.node_style().text_align_vertical;
175        }
176        if self.node_style().text_overflow != other.node_style().text_overflow {
177            delta.node_style_mut().text_overflow = other.node_style().text_overflow;
178        }
179        if self.node_style().text_shadow != other.node_style().text_shadow {
180            delta.node_style_mut().text_shadow = other.node_style().text_shadow.clone();
181        }
182        if self.node_style().node_size != other.node_style().node_size {
183            delta.node_style_mut().node_size = other.node_style().node_size.clone();
184        }
185        if self.node_style().line_height != other.node_style().line_height {
186            delta.node_style_mut().line_height = other.node_style().line_height.clone();
187        }
188        if self.node_style().line_count != other.node_style().line_count {
189            delta.node_style_mut().line_count = other.node_style().line_count;
190        }
191        if self.node_style().font_features != other.node_style().font_features {
192            delta.node_style_mut().font_features = other.node_style().font_features.clone();
193        }
194        if self.node_style().filters != other.node_style().filters {
195            delta.node_style_mut().filters = other.node_style().filters.clone();
196        }
197        if self.node_style().backdrop_filters != other.node_style().backdrop_filters {
198            delta.node_style_mut().backdrop_filters = other.node_style().backdrop_filters.clone();
199        }
200        if self.node_style().blend_mode != other.node_style().blend_mode {
201            delta.node_style_mut().blend_mode = other.node_style().blend_mode;
202        }
203        if self.node_style().hyperlink != other.node_style().hyperlink {
204            delta.node_style_mut().hyperlink = other.node_style().hyperlink.clone();
205        }
206        if self.node_style().display_type != other.node_style().display_type {
207            delta.node_style_mut().display_type = other.node_style().display_type;
208        }
209        if self.layout_style().position_type != other.layout_style().position_type {
210            delta.layout_style_mut().position_type = other.layout_style().position_type;
211        }
212        if self.layout_style().flex_direction != other.layout_style().flex_direction {
213            delta.layout_style_mut().flex_direction = other.layout_style().flex_direction;
214        }
215        if self.node_style().flex_wrap != other.node_style().flex_wrap {
216            delta.node_style_mut().flex_wrap = other.node_style().flex_wrap;
217        }
218        if self.node_style().grid_layout_type != other.node_style().grid_layout_type {
219            delta.node_style_mut().grid_layout_type = other.node_style().grid_layout_type;
220        }
221        if self.node_style().grid_columns_rows != other.node_style().grid_columns_rows {
222            delta.node_style_mut().grid_columns_rows = other.node_style().grid_columns_rows;
223        }
224        if self.node_style().grid_adaptive_min_size != other.node_style().grid_adaptive_min_size {
225            delta.node_style_mut().grid_adaptive_min_size =
226                other.node_style().grid_adaptive_min_size;
227        }
228        if self.node_style().grid_span_contents != other.node_style().grid_span_contents {
229            delta.node_style_mut().grid_span_contents =
230                other.node_style().grid_span_contents.clone();
231        }
232        if self.node_style().overflow != other.node_style().overflow {
233            delta.node_style_mut().overflow = other.node_style().overflow;
234        }
235        if self.node_style().max_children != other.node_style().max_children {
236            delta.node_style_mut().max_children = other.node_style().max_children;
237        }
238        if self.node_style().overflow_node_id != other.node_style().overflow_node_id {
239            delta.node_style_mut().overflow_node_id = other.node_style().overflow_node_id.clone();
240        }
241        if self.node_style().overflow_node_name != other.node_style().overflow_node_name {
242            delta.node_style_mut().overflow_node_name =
243                other.node_style().overflow_node_name.clone();
244        }
245        if self.node_style().shader_data != other.node_style().shader_data {
246            delta.node_style_mut().shader_data = other.node_style().shader_data.clone();
247        }
248        if self.layout_style().align_items != other.layout_style().align_items {
249            delta.layout_style_mut().align_items = other.layout_style().align_items;
250        }
251        if self.layout_style().align_content != other.layout_style().align_content {
252            delta.layout_style_mut().align_content = other.layout_style().align_content;
253        }
254        if self.layout_style().justify_content != other.layout_style().justify_content {
255            delta.layout_style_mut().justify_content = other.layout_style().justify_content;
256        }
257        if self.layout_style().top != other.layout_style().top {
258            delta.layout_style_mut().top = other.layout_style().top.clone();
259        }
260        if self.layout_style().left != other.layout_style().left {
261            delta.layout_style_mut().left = other.layout_style().left.clone();
262        }
263        if self.layout_style().bottom != other.layout_style().bottom {
264            delta.layout_style_mut().bottom = other.layout_style().bottom.clone();
265        }
266        if self.layout_style().right != other.layout_style().right {
267            delta.layout_style_mut().right = other.layout_style().right.clone();
268        }
269        if self.layout_style().margin != other.layout_style().margin {
270            delta.layout_style_mut().margin = other.layout_style().margin.clone();
271        }
272        if self.layout_style().padding != other.layout_style().padding {
273            delta.layout_style_mut().padding = other.layout_style().padding.clone();
274        }
275        if self.layout_style().item_spacing != other.layout_style().item_spacing {
276            delta.layout_style_mut().item_spacing = other.layout_style().item_spacing.clone();
277        }
278        if self.node_style().cross_axis_item_spacing != other.node_style().cross_axis_item_spacing {
279            delta.node_style_mut().cross_axis_item_spacing =
280                other.node_style().cross_axis_item_spacing;
281        }
282        if self.layout_style().flex_grow != other.layout_style().flex_grow {
283            delta.layout_style_mut().flex_grow = other.layout_style().flex_grow;
284        }
285        if self.layout_style().flex_shrink != other.layout_style().flex_shrink {
286            delta.layout_style_mut().flex_shrink = other.layout_style().flex_shrink;
287        }
288        if self.layout_style().flex_basis != other.layout_style().flex_basis {
289            delta.layout_style_mut().flex_basis = other.layout_style().flex_basis.clone();
290        }
291        if self.layout_style().width != other.layout_style().width {
292            delta.layout_style_mut().width = other.layout_style().width.clone();
293        }
294        if self.layout_style().height != other.layout_style().height {
295            delta.layout_style_mut().height = other.layout_style().height.clone();
296        }
297        if self.layout_style().max_width != other.layout_style().max_width {
298            delta.layout_style_mut().max_width = other.layout_style().max_width.clone();
299        }
300        if self.layout_style().max_height != other.layout_style().max_height {
301            delta.layout_style_mut().max_height = other.layout_style().max_height.clone();
302        }
303        if self.layout_style().min_width != other.layout_style().min_width {
304            delta.layout_style_mut().min_width = other.layout_style().min_width.clone();
305        }
306        if self.layout_style().min_height != other.layout_style().min_height {
307            delta.layout_style_mut().min_height = other.layout_style().min_height.clone();
308        }
309        if self.layout_style().bounding_box != other.layout_style().bounding_box {
310            delta.layout_style_mut().bounding_box = other.layout_style().bounding_box.clone();
311        }
312        if self.node_style().aspect_ratio != other.node_style().aspect_ratio {
313            delta.node_style_mut().aspect_ratio = other.node_style().aspect_ratio;
314        }
315        if self.node_style().pointer_events != other.node_style().pointer_events {
316            delta.node_style_mut().pointer_events = other.node_style().pointer_events;
317        }
318        if self.node_style().meter_data != other.node_style().meter_data {
319            delta.node_style_mut().meter_data = other.node_style().meter_data.clone();
320        }
321        if self.node_style().horizontal_sizing != other.node_style().horizontal_sizing {
322            delta.node_style_mut().horizontal_sizing = other.node_style().horizontal_sizing;
323        }
324        if self.node_style().vertical_sizing != other.node_style().vertical_sizing {
325            delta.node_style_mut().vertical_sizing = other.node_style().vertical_sizing;
326        }
327        if self.node_style().scalable_data != other.node_style().scalable_data {
328            delta.node_style_mut().scalable_data = other.node_style().scalable_data.clone();
329        }
330        if self.node_style().animation_override != other.node_style().animation_override {
331            delta.node_style_mut().animation_override =
332                other.node_style().animation_override.clone();
333        }
334        delta
335    }
336}
337
338impl ViewData {
339    /// Compute the difference between this view data and the given view data.
340    /// Right now only computes the text overrides.
341    pub fn difference(&self, other: &ViewData) -> Option<ViewData> {
342        if let Some(View_data_type::Text { .. }) = self.view_data_type {
343            if self != other {
344                return Some(other.clone());
345            }
346        }
347        if let Some(View_data_type::StyledText { .. }) = self.view_data_type {
348            if self != other {
349                return Some(other.clone());
350            }
351        }
352        None
353    }
354}
355
356impl ScrollInfo {
357    pub fn new_default() -> Self {
358        ScrollInfo {
359            overflow: OverflowDirection::OVERFLOW_DIRECTION_NONE.into(),
360            paged_scrolling: false,
361            ..Default::default()
362        }
363    }
364}
365
366impl View {
367    fn next_unique_id() -> u16 {
368        static COUNTER: AtomicU16 = AtomicU16::new(0);
369        COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
370    }
371    #[allow(clippy::too_many_arguments)]
372    pub fn new_rect(
373        id: &str,
374        name: &str,
375        shape: ViewShape,
376        style: ViewStyle,
377        component_info: Option<ComponentInfo>,
378        reactions: Option<Vec<Reaction>>,
379        scroll_info: ScrollInfo,
380        frame_extras: Option<FrameExtras>,
381        design_absolute_bounding_box: Option<Rectangle>,
382        render_method: RenderMethod,
383        explicit_variable_modes: HashMap<String, String>,
384    ) -> View {
385        View {
386            unique_id: View::next_unique_id() as u32,
387            id: id.to_owned(),
388            name: name.to_owned(),
389            component_info: component_info.into(),
390            reactions: reactions.unwrap_or_default(),
391            style: Some(style).into(),
392            frame_extras: frame_extras.into(),
393            scroll_info: Some(scroll_info).into(),
394            data: Some(ViewData {
395                view_data_type: Some(View_data_type::Container(Container {
396                    shape: Some(shape).into(),
397                    children: vec![],
398                    ..Default::default()
399                })),
400                ..Default::default()
401            })
402            .into(),
403            design_absolute_bounding_box: design_absolute_bounding_box.into(),
404            render_method: render_method.into(),
405            explicit_variable_modes,
406            ..Default::default()
407        }
408    }
409    #[allow(clippy::too_many_arguments)]
410    pub fn new_text(
411        id: &str,
412        name: &str,
413        style: ViewStyle,
414        component_info: Option<ComponentInfo>,
415        reactions: Option<Vec<Reaction>>,
416        text: &str,
417        text_res_name: Option<String>,
418        design_absolute_bounding_box: Option<Rectangle>,
419        render_method: RenderMethod,
420        explicit_variable_modes: HashMap<String, String>,
421    ) -> View {
422        View {
423            unique_id: View::next_unique_id() as u32,
424            id: id.to_owned(),
425            name: name.to_owned(),
426            component_info: component_info.into(),
427            reactions: reactions.unwrap_or_default(),
428            style: Some(style).into(),
429            frame_extras: None.into(),
430            scroll_info: Some(ScrollInfo::new_default()).into(),
431            data: Some(ViewData {
432                view_data_type: Some(View_data_type::Text(Text {
433                    content: text.into(),
434                    res_name: text_res_name,
435                    ..Default::default()
436                })),
437                ..Default::default()
438            })
439            .into(),
440            design_absolute_bounding_box: design_absolute_bounding_box.into(),
441            render_method: render_method.into(),
442            explicit_variable_modes,
443            ..Default::default()
444        }
445    }
446    #[allow(clippy::too_many_arguments)]
447    pub fn new_styled_text(
448        id: &str,
449        name: &str,
450        style: ViewStyle,
451        component_info: Option<ComponentInfo>,
452        reactions: Option<Vec<Reaction>>,
453        text: Vec<StyledTextRun>,
454        text_res_name: Option<String>,
455        design_absolute_bounding_box: Option<Rectangle>,
456        render_method: RenderMethod,
457    ) -> View {
458        View {
459            unique_id: View::next_unique_id() as u32,
460            id: id.to_owned(),
461            name: name.to_owned(),
462            style: Some(style).into(),
463            component_info: component_info.into(),
464            reactions: reactions.unwrap_or_default(),
465            frame_extras: None.into(),
466            scroll_info: Some(ScrollInfo::new_default()).into(),
467            data: Some(ViewData {
468                view_data_type: Some(View_data_type::StyledText(StyledTextRuns {
469                    styled_texts: text,
470                    res_name: text_res_name,
471                    ..Default::default()
472                })),
473                ..Default::default()
474            })
475            .into(),
476            design_absolute_bounding_box: design_absolute_bounding_box.into(),
477            render_method: render_method.into(),
478            explicit_variable_modes: HashMap::new(),
479            ..Default::default()
480        }
481    }
482    pub fn add_child(&mut self, child: View) {
483        if let Some(data) = self.data.as_mut() {
484            if let Some(View_data_type::Container { 0: Container { children, .. } }) =
485                data.view_data_type.as_mut()
486            {
487                children.push(child);
488            }
489        }
490    }
491
492    pub fn style(&self) -> &ViewStyle {
493        self.style.as_ref().expect("ViewStyle is required.")
494    }
495    pub fn style_mut(&mut self) -> &mut ViewStyle {
496        self.style.as_mut().expect("ViewStyle is required.")
497    }
498
499    /** This function is now only called by a view that is a COMPONENT. */
500    pub fn find_view_by_id(&self, view_id: &str) -> Option<&View> {
501        if view_id == self.id {
502            return Some(self);
503        } else if let Some(id) = view_id.split(';').next_back() {
504            // If this is a descendent node of an instance, the last section is the node id
505            // of the view in the component. Example: I70:17;29:15
506            if self.id == id {
507                return Some(self);
508            }
509        }
510        if let Some(data) = &self.data.as_ref() {
511            if let Some(View_data_type::Container { 0: Container { children, .. } }) =
512                &data.view_data_type
513            {
514                for child in children {
515                    let result = child.find_view_by_id(view_id);
516                    if result.is_some() {
517                        return result;
518                    }
519                }
520            }
521        }
522        None
523    }
524}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529    use crate::path::stroke_weight;
530    use crate::path::StrokeWeight;
531    use crate::variable::num_or_var::NumOrVarType;
532
533    #[test]
534    fn test_node_style_new_default() {
535        let style = NodeStyle::new_default();
536        assert!(style.font_color.is_some());
537        assert_eq!(style.font_size.unwrap().NumOrVarType, Some(NumOrVarType::Num(18.0)));
538        assert_eq!(style.font_weight.unwrap(), FontWeight::normal());
539        assert_eq!(style.font_style.enum_value().unwrap(), FontStyle::FONT_STYLE_NORMAL);
540    }
541
542    #[test]
543    fn test_view_style_new_default() {
544        let style = ViewStyle::new_default();
545        assert!(style.layout_style.is_some());
546        assert!(style.node_style.is_some());
547    }
548
549    #[test]
550    fn test_view_style_difference() {
551        let mut style1 = ViewStyle::new_default();
552        let mut style2 = ViewStyle::new_default();
553
554        // Test a few properties
555        style2.node_style_mut().opacity = Some(0.5);
556        style2.node_style_mut().letter_spacing = Some(1.2);
557        style2.layout_style_mut().flex_grow = 1.0;
558        style2.node_style_mut().meter_data = Some(Default::default()).into();
559
560        let diff = style1.difference(&style2);
561        assert_eq!(diff.node_style().opacity, Some(0.5));
562        assert_eq!(diff.node_style().letter_spacing, Some(1.2));
563        assert_eq!(diff.layout_style().flex_grow, 1.0);
564        assert_eq!(diff.node_style().meter_data, Some(Default::default()).into());
565
566        // Test no difference
567        style1.node_style_mut().opacity = Some(0.5);
568        style1.node_style_mut().letter_spacing = Some(1.2);
569        style1.layout_style_mut().flex_grow = 1.0;
570        let diff2 = style1.difference(&style2);
571        assert_eq!(diff2.node_style().opacity, None);
572        assert_eq!(diff2.node_style().letter_spacing, None);
573        assert_eq!(diff2.layout_style().flex_grow, 0.0);
574
575        // Test all properties
576        let mut style3 = ViewStyle::new_default();
577        let mut style4 = ViewStyle::new_default();
578        style4.node_style_mut().font_color =
579            Some(Background::new_with_background(background::Background_type::Solid(
580                crate::variable::ColorOrVar::new_color(crate::color::Color::red()),
581            )))
582            .into();
583        style4.node_style_mut().font_size = Some(NumOrVar::from_num(24.0)).into();
584        style4.node_style_mut().font_family = Some("Roboto".to_string());
585        style4.node_style_mut().font_weight = Some(FontWeight::bold()).into();
586        style4.node_style_mut().font_style = FontStyle::FONT_STYLE_ITALIC.into();
587        style4.node_style_mut().text_decoration = TextDecoration::TEXT_DECORATION_UNDERLINE.into();
588        style4.node_style_mut().letter_spacing = Some(2.0);
589        style4.node_style_mut().font_stretch = Some(FontStretch::expanded()).into();
590        style4.node_style_mut().backgrounds.push(Background::new_with_background(
591            background::Background_type::Solid(crate::variable::ColorOrVar::new_color(
592                crate::color::Color::blue(),
593            )),
594        ));
595        style4.node_style_mut().stroke = Some(Stroke {
596            stroke_weight: Some(StrokeWeight {
597                stroke_weight_type: Some(stroke_weight::Stroke_weight_type::Uniform(1.0)),
598                ..Default::default()
599            })
600            .into(),
601            ..Default::default()
602        })
603        .into();
604        style4.layout_style_mut().flex_direction =
605            crate::positioning::FlexDirection::FLEX_DIRECTION_COLUMN.into();
606        style4.layout_style_mut().align_items =
607            crate::positioning::AlignItems::ALIGN_ITEMS_CENTER.into();
608        style4.layout_style_mut().margin = Some(crate::geometry::DimensionRect {
609            start: crate::geometry::DimensionProto::new_points(10.0),
610            ..Default::default()
611        })
612        .into();
613
614        let diff3 = style3.difference(&style4);
615        assert_eq!(diff3.node_style().font_color, style4.node_style().font_color.clone());
616        assert_eq!(diff3.node_style().font_size, style4.node_style().font_size.clone());
617        assert_eq!(diff3.node_style().font_family, style4.node_style().font_family.clone());
618        assert_eq!(diff3.node_style().font_weight, style4.node_style().font_weight.clone());
619        assert_eq!(diff3.node_style().font_style, style4.node_style().font_style);
620        assert_eq!(diff3.node_style().text_decoration, style4.node_style().text_decoration);
621        assert_eq!(diff3.node_style().letter_spacing, style4.node_style().letter_spacing);
622        assert_eq!(diff3.node_style().font_stretch, style4.node_style().font_stretch.clone());
623        assert_eq!(diff3.node_style().backgrounds, style4.node_style().backgrounds.clone());
624        assert_eq!(diff3.node_style().stroke, style4.node_style().stroke.clone());
625        assert_eq!(diff3.layout_style().flex_direction, style4.layout_style().flex_direction);
626        assert_eq!(diff3.layout_style().align_items, style4.layout_style().align_items);
627        assert_eq!(diff3.layout_style().margin, style4.layout_style().margin.clone());
628        assert_eq!(
629            diff3.node_style().animation_override,
630            style4.node_style().animation_override.clone()
631        );
632        assert_eq!(diff3.node_style().horizontal_sizing, style4.node_style().horizontal_sizing);
633
634        // Test no difference with all properties set
635        style3 = style4.clone();
636        let diff4 = style3.difference(&style4);
637        assert_eq!(diff4.node_style().font_color, ViewStyle::new_default().node_style().font_color);
638        assert_eq!(diff4.node_style().font_size, ViewStyle::new_default().node_style().font_size);
639        assert!(diff4.node_style().font_family.is_none());
640        assert_eq!(
641            diff4.node_style().font_weight,
642            ViewStyle::new_default().node_style().font_weight
643        );
644        assert_eq!(
645            diff4.node_style().font_style.enum_value().unwrap(),
646            FontStyle::FONT_STYLE_NORMAL
647        );
648        assert_eq!(
649            diff4.node_style().text_decoration.enum_value().unwrap(),
650            TextDecoration::TEXT_DECORATION_NONE
651        );
652        assert!(diff4.node_style().letter_spacing.is_none());
653        assert_eq!(
654            diff4.node_style().font_stretch,
655            ViewStyle::new_default().node_style().font_stretch
656        );
657        assert!(diff4.node_style().backgrounds.is_empty());
658        assert_eq!(diff4.node_style().stroke, ViewStyle::new_default().node_style().stroke);
659        assert_eq!(
660            diff4.layout_style().flex_direction.enum_value().unwrap(),
661            crate::positioning::FlexDirection::FLEX_DIRECTION_ROW
662        );
663        assert_eq!(
664            diff4.layout_style().align_items.enum_value().unwrap(),
665            crate::positioning::AlignItems::ALIGN_ITEMS_STRETCH
666        );
667        assert_eq!(diff4.layout_style().margin, ViewStyle::new_default().layout_style().margin);
668        assert_eq!(
669            diff4.node_style().animation_override,
670            ViewStyle::new_default().node_style().animation_override
671        );
672    }
673
674    #[test]
675    fn test_view_new_rect() {
676        let view = View::new_rect(
677            &"rect1".to_string(),
678            &"Rect View".to_string(),
679            ViewShape::default(),
680            ViewStyle::new_default(),
681            None,
682            None,
683            ScrollInfo::new_default(),
684            None,
685            None,
686            RenderMethod::RENDER_METHOD_NONE,
687            HashMap::new(),
688        );
689        assert_eq!(view.id, "rect1");
690        assert_eq!(view.name, "Rect View");
691        assert!(matches!(
692            view.data.unwrap().view_data_type,
693            Some(View_data_type::Container { .. })
694        ));
695    }
696
697    #[test]
698    fn test_view_new_text() {
699        let view = View::new_text(
700            &"text1".to_string(),
701            &"Text View".to_string(),
702            ViewStyle::new_default(),
703            None,
704            None,
705            "Hello",
706            None,
707            None,
708            RenderMethod::RENDER_METHOD_NONE,
709            HashMap::new(),
710        );
711        assert_eq!(view.id, "text1");
712        assert_eq!(view.name, "Text View");
713        assert!(matches!(view.data.unwrap().view_data_type, Some(View_data_type::Text { .. })));
714    }
715
716    #[test]
717    fn test_view_add_child() {
718        let mut parent = View::new_rect(
719            &"parent".to_string(),
720            &"Parent".to_string(),
721            ViewShape::default(),
722            ViewStyle::new_default(),
723            None,
724            None,
725            ScrollInfo::new_default(),
726            None,
727            None,
728            RenderMethod::RENDER_METHOD_NONE,
729            HashMap::new(),
730        );
731        let child = View::new_rect(
732            &"child".to_string(),
733            &"Child".to_string(),
734            ViewShape::default(),
735            ViewStyle::new_default(),
736            None,
737            None,
738            ScrollInfo::new_default(),
739            None,
740            None,
741            RenderMethod::RENDER_METHOD_NONE,
742            HashMap::new(),
743        );
744        parent.add_child(child);
745        if let Some(View_data_type::Container { 0: Container { children, .. } }) =
746            parent.data.unwrap().view_data_type
747        {
748            assert_eq!(children.len(), 1);
749            assert_eq!(children[0].id, "child");
750        } else {
751            panic!("Wrong data type");
752        }
753    }
754
755    #[test]
756    fn test_find_view_by_id() {
757        let child = View::new_rect(
758            &"child".to_string(),
759            &"Child".to_string(),
760            ViewShape::default(),
761            ViewStyle::new_default(),
762            None,
763            None,
764            ScrollInfo::new_default(),
765            None,
766            None,
767            RenderMethod::RENDER_METHOD_NONE,
768            HashMap::new(),
769        );
770        let mut parent = View::new_rect(
771            &"parent".to_string(),
772            &"Parent".to_string(),
773            ViewShape::default(),
774            ViewStyle::new_default(),
775            None,
776            None,
777            ScrollInfo::new_default(),
778            None,
779            None,
780            RenderMethod::RENDER_METHOD_NONE,
781            HashMap::new(),
782        );
783        parent.add_child(child);
784        assert!(parent.find_view_by_id(&"child".to_string()).is_some());
785        assert!(parent.find_view_by_id(&"parent".to_string()).is_some());
786        assert!(parent.find_view_by_id(&"I1;child".to_string()).is_some());
787        assert!(parent.find_view_by_id(&"nonexistent".to_string()).is_none());
788    }
789
790    #[test]
791    fn test_view_data_difference() {
792        // Test different text
793        let view_data1 = ViewData {
794            view_data_type: Some(View_data_type::Text(Text {
795                content: "hello".to_string(),
796                ..Default::default()
797            })),
798            ..Default::default()
799        };
800        let view_data2 = ViewData {
801            view_data_type: Some(View_data_type::Text(Text {
802                content: "world".to_string(),
803                ..Default::default()
804            })),
805            ..Default::default()
806        };
807        assert_eq!(view_data1.difference(&view_data2), Some(view_data2.clone()));
808
809        // Test same text
810        let view_data3 = ViewData {
811            view_data_type: Some(View_data_type::Text(Text {
812                content: "hello".to_string(),
813                ..Default::default()
814            })),
815            ..Default::default()
816        };
817        assert_eq!(view_data1.difference(&view_data3), None);
818
819        // Test different styled text
820        let view_data4 = ViewData {
821            view_data_type: Some(View_data_type::StyledText(StyledTextRuns {
822                styled_texts: vec![],
823                ..Default::default()
824            })),
825            ..Default::default()
826        };
827        let view_data5 = ViewData {
828            view_data_type: Some(View_data_type::StyledText(StyledTextRuns {
829                styled_texts: vec![StyledTextRun::default()],
830                ..Default::default()
831            })),
832            ..Default::default()
833        };
834        assert_eq!(view_data4.difference(&view_data5), Some(view_data5.clone()));
835
836        // Test same styled text
837        let view_data6 = ViewData {
838            view_data_type: Some(View_data_type::StyledText(StyledTextRuns {
839                styled_texts: vec![],
840                ..Default::default()
841            })),
842            ..Default::default()
843        };
844        assert_eq!(view_data4.difference(&view_data6), None);
845
846        // Test container type
847        let view_data7 = ViewData {
848            view_data_type: Some(View_data_type::Container(Container::default())),
849            ..Default::default()
850        };
851        let view_data8 = ViewData {
852            view_data_type: Some(View_data_type::Container(Container {
853                children: vec![View::default()],
854                ..Default::default()
855            })),
856            ..Default::default()
857        };
858        assert_eq!(view_data7.difference(&view_data8), None);
859    }
860}