renamite_behavior_common/
lib.rs1pub mod align;
4pub mod assets;
5pub mod color;
6pub mod context_menu;
7pub mod edit;
8pub mod fill;
9pub mod inspect;
10pub mod layers;
11pub mod machine;
12pub mod modifiers;
13pub mod path;
14pub mod pathfinder;
15pub mod snap;
16pub mod stroke;
17
18use glam::DVec2;
19use renamite_animation::Frame;
20use renamite_model::{CompId, Document, NodeId};
21use serde::{Deserialize, Serialize};
22
23#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
24pub struct Selection {
25 pub nodes: Vec<NodeId>,
26 pub comp: Option<CompId>,
28}
29
30impl Selection {
31 pub fn is_empty(&self) -> bool {
32 self.nodes.is_empty()
33 }
34
35 pub fn contains(&self, id: NodeId) -> bool {
36 self.nodes.contains(&id)
37 }
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
42pub struct ViewTransform {
43 pub scale: f64,
44 pub offset: DVec2,
45}
46
47impl ViewTransform {
48 pub fn identity() -> Self {
49 Self {
50 scale: 1.0,
51 offset: DVec2::ZERO,
52 }
53 }
54
55 fn usable_scale(&self) -> f64 {
56 if self.scale.is_finite() && self.scale > 1e-12 {
57 self.scale
58 } else {
59 1.0
60 }
61 }
62
63 pub fn screen_to_world(&self, p: DVec2) -> DVec2 {
64 if !p.is_finite() {
65 return DVec2::ZERO;
66 }
67 let offset = if self.offset.is_finite() {
68 self.offset
69 } else {
70 DVec2::ZERO
71 };
72 (p - offset) / self.usable_scale()
73 }
74
75 pub fn world_to_screen(&self, p: DVec2) -> DVec2 {
76 if !p.is_finite() {
77 return DVec2::ZERO;
78 }
79 let offset = if self.offset.is_finite() {
80 self.offset
81 } else {
82 DVec2::ZERO
83 };
84 p * self.usable_scale() + offset
85 }
86
87 pub fn world_tolerance(&self, px: f64) -> f64 {
89 if !px.is_finite() || px < 0.0 {
90 return 0.0;
91 }
92 px / self.usable_scale()
93 }
94
95 pub fn zoom_at(&mut self, screen_pos: DVec2, factor: f64, min: f64, max: f64) {
98 if !screen_pos.is_finite()
99 || !factor.is_finite()
100 || factor <= 0.0
101 || !min.is_finite()
102 || !max.is_finite()
103 || min <= 0.0
104 || max < min
105 {
106 return;
107 }
108 let current = self.usable_scale();
109 let next = (current * factor).clamp(min, max);
110 if !next.is_finite() || next <= 0.0 {
111 return;
112 }
113 let offset = if self.offset.is_finite() {
114 self.offset
115 } else {
116 DVec2::ZERO
117 };
118 let world = (screen_pos - offset) / current;
119 self.scale = next;
120 self.offset = screen_pos - world * next;
121 }
122
123 pub fn pan_by(&mut self, delta: DVec2) {
124 if delta.is_finite() && self.offset.is_finite() {
125 self.offset += delta;
126 }
127 }
128
129 pub fn fit(&mut self, surface: DVec2, artboard: DVec2) {
133 fit_view(self, surface, artboard);
134 }
135}
136
137pub fn fit_view(view: &mut ViewTransform, surface: DVec2, artboard: DVec2) {
141 if !surface.is_finite()
142 || !artboard.is_finite()
143 || surface.x <= 1.0
144 || surface.y <= 1.0
145 || artboard.x <= 0.0
146 || artboard.y <= 0.0
147 {
148 return;
149 }
150 let margin = 56.0;
151 let available = (surface - DVec2::splat(margin * 2.0)).max(DVec2::splat(1.0));
152 let scale = (available.x / artboard.x)
153 .min(available.y / artboard.y)
154 .clamp(0.05, 32.0);
155 view.scale = scale;
156 view.offset = (surface - artboard * scale) * 0.5;
157}
158
159pub fn fit_exact_view(view: &mut ViewTransform, surface: DVec2, artboard: DVec2) {
163 if !surface.is_finite()
164 || !artboard.is_finite()
165 || surface.x <= 1.0
166 || surface.y <= 1.0
167 || artboard.x <= 0.0
168 || artboard.y <= 0.0
169 {
170 return;
171 }
172 let scale = (surface.x / artboard.x)
173 .min(surface.y / artboard.y)
174 .clamp(0.05, 64.0);
175 view.scale = scale;
176 view.offset = (surface - artboard * scale) * 0.5;
177}
178
179#[derive(Clone, Copy, Debug, Default, PartialEq)]
183pub struct FitState {
184 surface: DVec2,
185 artboard: DVec2,
186 pub pending: bool,
187}
188
189impl FitState {
190 pub fn new() -> Self {
191 Self {
192 surface: DVec2::ZERO,
193 artboard: DVec2::ZERO,
194 pending: true,
195 }
196 }
197
198 pub fn with_surface(surface: DVec2) -> Self {
201 let surface = if surface.is_finite() {
202 surface
203 } else {
204 DVec2::ZERO
205 };
206 Self {
207 surface,
208 artboard: DVec2::ZERO,
209 pending: false,
210 }
211 }
212
213 pub fn ensure(
222 &mut self,
223 view: &mut ViewTransform,
224 surface: DVec2,
225 artboard: DVec2,
226 refit_on_resize: bool,
227 ) -> bool {
228 if !surface.is_finite() || !artboard.is_finite() {
229 return false;
230 }
231 let resized = (surface - self.surface).abs().max_element() > 0.5;
232 let art_changed = (artboard - self.artboard).abs().max_element() > 0.5;
233 let first_layout = self.surface == DVec2::ZERO && surface != DVec2::ZERO;
234 self.surface = surface;
235 if self.pending || first_layout || art_changed || (refit_on_resize && resized) {
236 view.fit(surface, artboard);
237 self.artboard = artboard;
238 self.pending = false;
239 true
240 } else {
241 false
242 }
243 }
244
245 pub fn request(&mut self) {
248 self.pending = true;
249 }
250
251 pub fn surface_size(&self) -> DVec2 {
252 self.surface
253 }
254
255 pub fn zoom_centered(&self, view: &mut ViewTransform, factor: f64) {
257 if self.surface == DVec2::ZERO {
258 return;
259 }
260 self.zoom_at(view, self.surface * 0.5, factor);
261 }
262
263 pub fn zoom_at(&self, view: &mut ViewTransform, screen_pos: DVec2, factor: f64) {
265 if self.surface == DVec2::ZERO {
266 return;
267 }
268 view.zoom_at(screen_pos, factor, 0.05, 64.0);
269 }
270}
271
272#[derive(Clone, Copy, Debug, Default, PartialEq)]
289pub struct GestureAnchor {
290 anchored: Option<DVec2>,
291}
292
293impl GestureAnchor {
294 pub fn new() -> Self {
295 Self::default()
296 }
297
298 pub fn begin(&mut self, local: DVec2) {
302 if self.anchored.is_none() {
303 self.anchored = Some(local);
304 }
305 }
306
307 pub fn resolve(&self, fallback: DVec2) -> DVec2 {
310 self.anchored.unwrap_or(fallback)
311 }
312
313 pub fn is_anchored(&self) -> bool {
315 self.anchored.is_some()
316 }
317
318 pub fn end(&mut self) {
320 self.anchored = None;
321 }
322}
323
324#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
325pub struct Modifiers {
326 pub shift: bool,
327 pub alt: bool,
328 pub ctrl: bool,
329}
330
331impl Modifiers {
332 pub fn none() -> Self {
333 Self {
334 shift: false,
335 alt: false,
336 ctrl: false,
337 }
338 }
339}
340
341#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
342pub struct SnapConfig {
343 pub grid: Option<f64>,
344 pub anchor: bool,
345 pub guide: bool,
346}
347
348pub struct ToolContext<'a> {
349 pub doc: &'a Document,
350 pub scene: &'a renamite_model::Scene,
352 pub comp: CompId,
353 pub selection: &'a Selection,
354 pub playhead: Frame,
355 pub record: bool,
356 pub view: ViewTransform,
357 pub snap: SnapConfig,
358 pub guides: &'a [(bool, f64)],
361 pub modifiers: Modifiers,
362 pub current_paint: &'a renamite_model::StylePaint,
364}