Procházet zdrojové kódy

app: more light theme additions

darkfi před 2 týdny
rodič
revize
c7c8b3f57e

+ 18 - 23
bin/app/src/app/schema/chat.rs

@@ -124,12 +124,12 @@ mod android_ui_consts {
 }
 
 #[cfg(target_os = "android")]
-mod ui_consts {
+pub mod ui_consts {
     pub use super::android_ui_consts::*;
 }
 
 #[cfg(feature = "emulate-android")]
-mod ui_consts {
+pub mod ui_consts {
     pub use super::android_ui_consts::*;
 }
 
@@ -137,7 +137,7 @@ mod ui_consts {
     any(target_os = "linux", target_os = "macos", target_os = "windows"),
     not(feature = "emulate-android")
 ))]
-mod ui_consts {
+pub mod ui_consts {
     // Chat UI
     pub const CHANNEL_LABEL_X: f32 = 100.;
     pub const CHANNEL_LABEL_Y: f32 = 14.;
@@ -289,8 +289,9 @@ pub async fn make(
     prop.set_default_f32(3, BACKARROW_SCALE).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
 
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_back_arrow([0., 1., 1., 1.]).scaled(BACKARROW_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let back_btn_bg_node =
         node.setup(|me| VectorArt::new(me, renderer.clone(), redraw.clone())).await;
     layer_node.link(back_btn_bg_node.clone());
@@ -827,8 +828,7 @@ pub async fn make(
 
     let editbox_bg_rect_prop = prop.clone();
 
-    let (bg_color, lhs_bg_color, line_color) =
-        ([0., 0.13, 0.08, 1.], [0., 0.11, 0.11, 1.], [0.41, 0.6, 0.65, 1.]);
+    let (bg_color, line_color) = ([0., 0.13, 0.08, 1.], [0.41, 0.6, 0.65, 1.]);
 
     let mut shape = VectorShape::new();
     // Main green background
@@ -862,15 +862,8 @@ pub async fn make(
         // Color
         [0., 0.28, 0.2, 1.],
     );
-    // Bottom line
-    //shape.add_filled_box(
-    //    expr::const_f32(0.),
-    //    cc.compile("h - 1").unwrap(),
-    //    expr::load_var("w"),
-    //    expr::load_var("h"),
-    //    [0.41, 0.6, 0.65, 1.],
-    //);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node = node.setup(|me| VectorArt::new(me, renderer.clone(), redraw.clone())).await;
     layer_node.link(node);
 
@@ -884,8 +877,9 @@ pub async fn make(
     prop.set_default_f32(2, 500.).unwrap();
     prop.set_default_f32(3, 500.).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 5).unwrap();
-    let shape = shape::create_send_arrow().scaled(EMOJI_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_send_arrow([0., 1., 1., 1.]).scaled(EMOJI_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node = node.setup(|me| VectorArt::new(me, renderer.clone(), redraw.clone())).await;
     layer_node.link(node);
 
@@ -899,9 +893,9 @@ pub async fn make(
     prop.set_default_f32(2, 500.).unwrap();
     prop.set_default_f32(3, 500.).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 5).unwrap();
-    let color = [0., 1., 1., 1.];
-    let shape = shape::create_emoji_selector(color).scaled(EMOJI_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_emoji_selector([0., 1., 1., 1.]).scaled(EMOJI_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node = node.setup(|me| VectorArt::new(me, renderer.clone(), redraw.clone())).await;
     layer_node.link(node);
 
@@ -916,8 +910,9 @@ pub async fn make(
     prop.set_default_f32(2, 500.).unwrap();
     prop.set_default_f32(3, 500.).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 5).unwrap();
-    let shape = shape::create_close_icon().scaled(EMOJI_CLOSE_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_close_icon([0., 1., 1., 1.]).scaled(EMOJI_CLOSE_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node = node.setup(|me| VectorArt::new(me, renderer.clone(), redraw.clone())).await;
     layer_node.link(node);
 

+ 3 - 2
bin/app/src/app/schema/menu/channel.rs

@@ -199,8 +199,9 @@ pub async fn make(
     prop.set_default_f32(3, BACKARROW_SCALE).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
 
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_back_arrow([0., 1., 1., 1.]).scaled(BACKARROW_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node =
         node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
     content.link(node);

+ 3 - 2
bin/app/src/app/schema/menu/contact.rs

@@ -200,8 +200,9 @@ pub async fn make(
     prop.set_default_f32(3, BACKARROW_SCALE).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
 
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_back_arrow([0., 1., 1., 1.]).scaled(BACKARROW_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node =
         node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
     content.link(node);

+ 1 - 1
bin/app/src/app/schema/mod.rs

@@ -38,7 +38,7 @@ use crate::{
     util::i18n::I18nBabelFish,
 };
 
-mod chat;
+pub mod chat;
 pub mod menu;
 use menu::channel::Channel;
 mod settings;

+ 3 - 2
bin/app/src/app/schema/settings.rs

@@ -223,8 +223,9 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     prop.set_default_f32(3, 0.).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
 
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_back_arrow([0., 1., 1., 1.]).scaled(BACKARROW_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node =
         node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
     layer_node.link(node);

+ 4 - 3
bin/app/src/app/schema/wallet/main.rs

@@ -81,8 +81,9 @@ pub async fn make(
     prop.set_default_f32(3, BACKARROW_SCALE).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
     node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    node.set_property_shape(atom, Role::App, "shape", shape).unwrap();
+    let shape = shape::create_back_arrow([0., 1., 1., 1.]).scaled(BACKARROW_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    prop.set_default_shape(0, shape).unwrap();
     let node =
         node.setup(|me| VectorArt::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
     wallet_layer.link(node.clone());
@@ -516,7 +517,7 @@ pub async fn make(
         &window_scale,
         i18n_fish,
         "tokens_label",
-        "tokens",
+        "TOKENS",
         &mut y,
     )
     .await;

+ 15 - 12
bin/app/src/shape/back_arrow.rs

@@ -16,20 +16,23 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use crate::ui::{ShapeVertex, VectorShape};
-pub fn create_back_arrow() -> VectorShape {
+use crate::{
+    mesh::Color,
+    ui::{ShapeVertex, VectorShape},
+};
+pub fn create_back_arrow(color: Color) -> VectorShape {
     VectorShape {
         verts: vec![
-            ShapeVertex::from_xy(-0.877643, -0.03111, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(0.992314, -0.03111, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.993081, 0.000168, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.154072, -0.752301, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.198105, -0.794808, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.877643, 0.03111, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(0.992314, 0.03111, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.993081, -0.000168, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.154072, 0.752301, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.198105, 0.794808, [0., 1., 1., 1.]),
+            ShapeVertex::from_xy(-0.877643, -0.03111, color),
+            ShapeVertex::from_xy(0.992314, -0.03111, color),
+            ShapeVertex::from_xy(-0.993081, 0.000168, color),
+            ShapeVertex::from_xy(-0.154072, -0.752301, color),
+            ShapeVertex::from_xy(-0.198105, -0.794808, color),
+            ShapeVertex::from_xy(-0.877643, 0.03111, color),
+            ShapeVertex::from_xy(0.992314, 0.03111, color),
+            ShapeVertex::from_xy(-0.993081, -0.000168, color),
+            ShapeVertex::from_xy(-0.154072, 0.752301, color),
+            ShapeVertex::from_xy(-0.198105, 0.794808, color),
         ],
         indices: vec![0, 4, 2, 1, 5, 0, 0, 5, 7, 5, 9, 7, 0, 3, 4, 1, 6, 5, 5, 8, 9],
     }

+ 5 - 2
bin/app/src/shape/close.rs

@@ -16,8 +16,11 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use crate::ui::{ShapeVertex, VectorShape};
-pub fn create_close_icon() -> VectorShape {
+use crate::{
+    mesh::Color,
+    ui::{ShapeVertex, VectorShape},
+};
+pub fn create_close_icon(color: Color) -> VectorShape {
     VectorShape {
         verts: vec![
             ShapeVertex::from_xy(0.0, 0.0, [0., 1., 1., 1.]),

+ 12 - 8
bin/app/src/shape/send_arrow.rs

@@ -16,16 +16,20 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use crate::ui::{ShapeVertex, VectorShape};
-pub fn create_send_arrow() -> VectorShape {
+use crate::{
+    mesh::Color,
+    ui::{ShapeVertex, VectorShape},
+};
+
+pub fn create_send_arrow(color: Color) -> VectorShape {
     VectorShape {
         verts: vec![
-            ShapeVertex::from_xy(-0.763722, -0.082607, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.137017, 0.190169, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(0.992481, -0.087373, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.137017, -0.368093, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.934894, -0.730526, [0., 1., 1., 1.]),
-            ShapeVertex::from_xy(-0.89359, 0.560546, [0., 1., 1., 1.]),
+            ShapeVertex::from_xy(-0.763722, -0.082607, color),
+            ShapeVertex::from_xy(-0.137017, 0.190169, color),
+            ShapeVertex::from_xy(0.992481, -0.087373, color),
+            ShapeVertex::from_xy(-0.137017, -0.368093, color),
+            ShapeVertex::from_xy(-0.934894, -0.730526, color),
+            ShapeVertex::from_xy(-0.89359, 0.560546, color),
         ],
         indices: vec![0, 1, 3, 3, 1, 2, 0, 3, 4, 1, 0, 5],
     }

+ 112 - 1
bin/app/src/text/mod.rs

@@ -189,6 +189,35 @@ pub fn make_layout2(
     foreground_colors: &[(Range<usize>, Color)],
     text_align: parley::Alignment,
     overflow_wrap: parley::OverflowWrap,
+) -> TextLayout {
+    make_layout_with_indent(
+        text,
+        text_color,
+        font_size,
+        lineheight,
+        window_scale,
+        width,
+        underlines,
+        foreground_colors,
+        text_align,
+        overflow_wrap,
+        0.,
+    )
+}
+
+/// Reserve space before the first line without changing text or style ranges.
+pub fn make_layout_with_indent(
+    text: &str,
+    text_color: Color,
+    font_size: f32,
+    lineheight: f32,
+    window_scale: f32,
+    width: Option<f32>,
+    underlines: &[Range<usize>],
+    foreground_colors: &[(Range<usize>, Color)],
+    text_align: parley::Alignment,
+    overflow_wrap: parley::OverflowWrap,
+    first_line_indent: f32,
 ) -> TextLayout {
     THREAD_LAYOUT_CTX.with(|layout_ctx| {
         let mut layout_ctx = layout_ctx.borrow_mut();
@@ -212,8 +241,90 @@ pub fn make_layout2(
         let mut layout: parley::Layout<Color> = builder.build(text);
         // The wrap width is given in virtual units while the layout
         // coordinates are physical, so scale it up before breaking.
-        layout.break_all_lines(width.map(|w| w * window_scale));
+        let width = width.map(|w| w * window_scale);
+        if first_line_indent > 0. {
+            let width = width.unwrap_or(f32::MAX);
+            let indent = first_line_indent * window_scale;
+            let mut breaker = layout.break_lines();
+            breaker.state_mut().set_layout_max_advance(width);
+            breaker.state_mut().set_line_max_advance((width - indent).max(0.));
+            breaker.state_mut().set_line_x(indent);
+            breaker.break_next();
+            breaker.state_mut().set_line_x(0.);
+            breaker.break_remaining(width);
+        } else {
+            layout.break_all_lines(width);
+        }
         layout.align(text_align, parley::AlignmentOptions::default());
         TextLayout { layout, scale: window_scale }
     })
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn first_line_indent_uses_full_width_on_continuations() {
+        for scale in [1., 1.5, 2.] {
+            let text = "word ".repeat(30);
+            let layout = make_layout_with_indent(
+                &text,
+                [1.; 4],
+                14.,
+                20. / 14.,
+                scale,
+                Some(200.),
+                &[],
+                &[],
+                parley::Alignment::Start,
+                parley::OverflowWrap::Normal,
+                50.,
+            );
+            assert!(layout.lines().count() > 2);
+            for (i, line) in layout.lines().enumerate() {
+                let run = line
+                    .items()
+                    .find_map(|item| match item {
+                        parley::PositionedLayoutItem::GlyphRun(run) => Some(run),
+                        _ => None,
+                    })
+                    .unwrap();
+                let expected = if i == 0 { 50. } else { 0. };
+                assert!((run.offset() / scale - expected).abs() < 0.01);
+            }
+            let old_layout = make_layout(&text, [1.; 4], 14., 20. / 14., scale, Some(150.), &[]);
+            assert!(layout.height() < old_layout.height());
+            for line in old_layout.lines() {
+                assert_eq!(line.metrics().offset, 0.);
+            }
+        }
+    }
+
+    #[test]
+    fn first_line_indent_preserves_empty_and_overflow_metrics() {
+        for text in ["", "\n", "first\nsecond", "averylongunbrokenword"] {
+            for width in [0., 30., 80., 200.] {
+                let layout = make_layout_with_indent(
+                    text,
+                    [1.; 4],
+                    14.,
+                    20. / 14.,
+                    1.,
+                    Some(width),
+                    &[],
+                    &[],
+                    parley::Alignment::Start,
+                    parley::OverflowWrap::Normal,
+                    50.,
+                );
+                let expected_lines = if text.contains('\n') { 2 } else { 1 };
+                assert_eq!(layout.lines().count(), expected_lines, "{text:?}, width={width}");
+                assert!((layout.height() - 20. * expected_lines as f32).abs() < 0.01);
+                for (i, line) in layout.lines().enumerate() {
+                    assert_eq!(line.metrics().offset, if i == 0 { 50. } else { 0. });
+                }
+            }
+        }
+    }
+}

+ 100 - 3
bin/app/src/theme/light.rs

@@ -29,11 +29,14 @@ use std::sync::Arc;
 use crate::{
     app::{
         node::create_vector_art,
-        schema::menu::ui_consts::{MENU_ICON_SCALE, SETTINGS_ICON_SCALE},
+        schema::{
+            chat::ui_consts::{BACKARROW_SCALE, EMOJI_CLOSE_SCALE, EMOJI_SCALE},
+            menu::ui_consts::{MENU_ICON_SCALE, SETTINGS_ICON_SCALE},
+        },
     },
     error::{Error, Result},
     expr,
-    mesh::{rgba, COLOR_BLACK},
+    mesh::{rgba, COLOR_BLACK, COLOR_WHITE},
     prop::{PropertyAtomicGuard, PropertyValue, Role},
     shape,
     theme::{Theme, ThemeCtx},
@@ -62,7 +65,7 @@ async fn light_apply(ctx: &ThemeCtx) -> Result<()> {
         ("edit.text_color", [0.08, 0.08, 0.08, 1.]),
         ("edit.hi_bg_color", [0.65, 0.65, 0.65, 1.]),
         ("edit.text_hi_color", [1., 1., 1., 1.]),
-        ("edit.cursor_color", [0.10, 0.10, 0.10, 1.]),
+        ("edit.cursor_color", rgba!(0x3300ffff)),
         ("edit.action_fg_color", [0.10, 0.10, 0.10, 1.]),
         ("edit.action_bg_color", [0.85, 0.85, 0.85, 1.]),
         ("menu.bg_color", [0.95, 0.95, 0.95, 0.5]),
@@ -149,6 +152,13 @@ async fn light_apply(ctx: &ThemeCtx) -> Result<()> {
     let prop = node.get_property("text_color").unwrap();
     ctx.set_touched_color(atom, &prop, COLOR_BLACK)?;
 
+    let node = sg_root.lookup_node("/window/content/chat/main_chat_layer/channel_label").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    ctx.set_touched_color(atom, &prop, COLOR_BLACK)?;
+    let node = sg_root.lookup_node("/window/content/chat/menu_layer/channels_label").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    ctx.set_touched_color(atom, &prop, COLOR_BLACK)?;
+
     let node = sg_root.lookup_node("/window/content/header_bg").unwrap();
     let mut shape = VectorShape::new();
     shape.add_filled_box(
@@ -173,6 +183,93 @@ async fn light_apply(ctx: &ThemeCtx) -> Result<()> {
     let prop = node.get_property("shape").unwrap();
     ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(Arc::new(shape)))?;
 
+    let node =
+        sg_root.lookup_node("/window/content/chat/main_chat_layer/content/editbox_bg").unwrap();
+    let (bg_color, line_color) = (COLOR_WHITE, rgba!(0x263B3Cff));
+    let mut shape = VectorShape::new();
+    // Main green background
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::load_var("h"),
+        bg_color,
+    );
+    // Top line
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::const_f32(1.),
+        line_color,
+    );
+    shape.add_radial_glow(
+        // Center
+        cc.compile("w / 2").unwrap(),
+        expr::load_var("h"),
+        // Size
+        expr::load_var("w"),
+        cc.compile("h / 4").unwrap(),
+        // Segments
+        8,
+        // Angles
+        std::f32::consts::PI,
+        2. * std::f32::consts::PI,
+        // Color
+        [0., 0.28, 0.2, 1.],
+    );
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(Arc::new(shape)))?;
+
+    // Chat editor icons
+
+    let color = rgba!(0x3300ffff);
+
+    let node =
+        sg_root.lookup_node("/window/content/chat/main_chat_layer/content/send_btn_bg").unwrap();
+    let shape = shape::create_send_arrow(color).scaled(EMOJI_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(Arc::new(shape)))?;
+
+    let node =
+        sg_root.lookup_node("/window/content/chat/main_chat_layer/content/emoji_btn_bg").unwrap();
+    let shape = shape::create_emoji_selector(color).scaled(EMOJI_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(Arc::new(shape)))?;
+
+    let node = sg_root
+        .lookup_node("/window/content/chat/main_chat_layer/content/emoji_close_btn_bg")
+        .unwrap();
+    let shape = shape::create_close_icon(color).scaled(EMOJI_CLOSE_SCALE);
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(Arc::new(shape)))?;
+
+    // Back buttons
+
+    let shape = Arc::new(shape::create_back_arrow(color).scaled(BACKARROW_SCALE));
+
+    let node = sg_root.lookup_node("/window/content/chat/main_chat_layer/back_btn_bg").unwrap();
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(shape.clone()))?;
+
+    let node =
+        sg_root.lookup_node("/window/content/chat/contact_screen_layer/back_btn_bg").unwrap();
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(shape.clone()))?;
+
+    let node =
+        sg_root.lookup_node("/window/content/chat/channel_screen_layer/back_btn_bg").unwrap();
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(shape.clone()))?;
+
+    let node = sg_root.lookup_node("/window/content/wallet/wallet_back_btn_bg").unwrap();
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(shape.clone()))?;
+
+    let node = sg_root.lookup_node("/window/content/settings_layer/back_btn_bg").unwrap();
+    let prop = node.get_property("shape").unwrap();
+    ctx.set_touched(atom, &prop, 0, PropertyValue::VectorShape(shape.clone()))?;
+
     Ok(())
 }