rive_rs/shapes/paint/
trim_path.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::mem;
6use std::rc::Rc;
7
8use crate::component::Component;
9use crate::component_dirt::ComponentDirt;
10use crate::core::{Core, CoreContext, ObjectRef, OnAdded, Property};
11use crate::option_cell::OptionCell;
12use crate::shapes::paint::stroke_effect::StrokeEffect;
13use crate::shapes::paint::Stroke;
14use crate::shapes::{CommandPath, CommandPathBuilder, MetricsPath};
15use crate::status_code::StatusCode;
16
17#[derive(Debug, Default)]
18pub struct TrimPath {
19    component: Component,
20    start: Property<f32>,
21    end: Property<f32>,
22    offset: Property<f32>,
23    mode_value: Property<u64>,
24    command_path: OptionCell<Rc<CommandPath>>,
25}
26
27impl ObjectRef<'_, TrimPath> {
28    pub fn start(&self) -> f32 {
29        self.start.get()
30    }
31
32    pub fn set_start(&self, start: f32) {
33        self.start.set(start);
34        self.invalidate_effect();
35    }
36
37    pub fn end(&self) -> f32 {
38        self.end.get()
39    }
40
41    pub fn set_end(&self, end: f32) {
42        self.end.set(end);
43        self.invalidate_effect();
44    }
45
46    pub fn offset(&self) -> f32 {
47        self.offset.get()
48    }
49
50    pub fn set_offset(&self, offset: f32) {
51        self.offset.set(offset);
52        self.invalidate_effect();
53    }
54
55    pub fn mode_value(&self) -> u64 {
56        self.mode_value.get()
57    }
58
59    pub fn set_mode_value(&self, mode_value: u64) {
60        self.mode_value.set(mode_value);
61        self.invalidate_effect();
62    }
63}
64
65impl StrokeEffect for ObjectRef<'_, TrimPath> {
66    fn effect_path(&self, source: &mut MetricsPath) -> Rc<CommandPath> {
67        if let Some(command_path) = self.command_path.get() {
68            return command_path;
69        }
70
71        let mut render_offset = self.offset().fract();
72        if render_offset.is_sign_negative() {
73            render_offset += 1.0;
74        }
75
76        // todo!("implement mode 2");
77
78        let total_len = source.compute_length();
79        let mut start_len = total_len * (self.start() + render_offset);
80        let mut end_len = total_len * (self.end() + render_offset);
81
82        if end_len < start_len {
83            mem::swap(&mut start_len, &mut end_len);
84        }
85
86        if start_len > total_len {
87            start_len -= total_len;
88            end_len -= total_len;
89        }
90
91        let mut builder = CommandPathBuilder::new();
92
93        while end_len > 0.0 {
94            if start_len < total_len {
95                source.trimmed(&mut builder, start_len, end_len, true);
96                end_len -= total_len;
97                start_len = 0.0;
98            } else {
99                start_len -= total_len;
100                end_len -= total_len;
101            }
102        }
103
104        let command_path = Rc::new(builder.build());
105        self.command_path.set(Some(command_path.clone()));
106
107        command_path
108    }
109
110    fn invalidate_effect(&self) {
111        self.command_path.set(None);
112        let stroke = self.cast::<Component>().parent().unwrap().cast::<Stroke>();
113
114        stroke.as_ref().outlined_stroke.set(None);
115        stroke
116            .cast::<Component>()
117            .as_ref()
118            .parent()
119            .unwrap()
120            .cast::<Component>()
121            .as_ref()
122            .add_dirt(ComponentDirt::PAINT, false);
123    }
124}
125
126impl Core for TrimPath {
127    parent_types![(component, Component)];
128
129    properties![
130        (114, start, set_start),
131        (115, end, set_end),
132        (116, offset, set_offset),
133        (117, mode_value, set_mode_value),
134        component,
135    ];
136}
137
138impl OnAdded for ObjectRef<'_, TrimPath> {
139    on_added!([on_added_dirty, import], Component);
140
141    fn on_added_clean(&self, _context: &dyn CoreContext) -> StatusCode {
142        if let Some(stroke) =
143            self.cast::<Component>().parent().and_then(|parent| parent.try_cast::<Stroke>())
144        {
145            stroke.as_ref().set_stroke_effect(Some(self.as_object().into()));
146            StatusCode::Ok
147        } else {
148            StatusCode::InvalidObject
149        }
150    }
151}