Lock editing on tween in-between frames
Block geometry/clip edits on frames that fall strictly inside a tween span, where an edit would silently mutate the bracketing keyframe instead: - Shape tweens: gate vector editing (vertices, curves, DCEL hits) and all geometry tools in stage.rs behind VectorLayer::is_tween_inbetween. - Motion tweens: block selecting/dragging/transforming clip instances whose transform is mid-tween, via AnimationData::is_object_tweened_at. Also: inserting a keyframe mid-tween now captures the interpolated geometry shown at that frame (not the left keyframe's) and inherits the shape tween, so the new keyframe continues morphing toward the right keyframe.
This commit is contained in:
parent
a1acecf396
commit
5fd04b5dd5
|
|
@ -322,6 +322,20 @@ impl AnimationCurve {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True when `time` lies strictly between two keyframes — an in-between frame of a
|
||||||
|
/// tween (not on a keyframe, not in the pre/post-extrapolation tails).
|
||||||
|
pub fn is_tween_inbetween(&self, time: f64, tol: f64) -> bool {
|
||||||
|
if self.keyframes.len() < 2 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let first = self.keyframes.first().unwrap().time;
|
||||||
|
let last = self.keyframes.last().unwrap().time;
|
||||||
|
if time <= first + tol || time >= last - tol {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
!self.keyframes.iter().any(|kf| (kf.time - time).abs() <= tol)
|
||||||
|
}
|
||||||
|
|
||||||
/// Extrapolate before the first keyframe
|
/// Extrapolate before the first keyframe
|
||||||
fn extrapolate_pre(&self, time: f64, first_kf: &Keyframe) -> f64 {
|
fn extrapolate_pre(&self, time: f64, first_kf: &Keyframe) -> f64 {
|
||||||
match self.pre_extrapolation {
|
match self.pre_extrapolation {
|
||||||
|
|
@ -516,6 +530,17 @@ impl AnimationData {
|
||||||
self.curves.remove(target)
|
self.curves.remove(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True when the object (e.g. a clip instance) is mid motion-tween at `time` — any of
|
||||||
|
/// its curves has `time` strictly between two keyframes. Used to lock out editing on
|
||||||
|
/// in-between frames (editing there would silently insert a keyframe and disturb the tween).
|
||||||
|
pub fn is_object_tweened_at(&self, id: uuid::Uuid, time: f64) -> bool {
|
||||||
|
const TOL: f64 = 0.001;
|
||||||
|
self.curves.iter().any(|(target, curve)| {
|
||||||
|
matches!(target, AnimationTarget::Object { id: oid, .. } if *oid == id)
|
||||||
|
&& curve.is_tween_inbetween(time, TOL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate a property at a given time
|
/// Evaluate a property at a given time
|
||||||
pub fn eval(&self, target: &AnimationTarget, time: f64, default: f64) -> f64 {
|
pub fn eval(&self, target: &AnimationTarget, time: f64, default: f64) -> f64 {
|
||||||
self.curves
|
self.curves
|
||||||
|
|
@ -545,3 +570,51 @@ impl AnimationData {
|
||||||
(t, opacity)
|
(t, opacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tween_lock_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn curve_in_between_detection() {
|
||||||
|
let mut c = AnimationCurve::new(
|
||||||
|
AnimationTarget::Object { id: uuid::Uuid::nil(), property: TransformProperty::X },
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
c.set_keyframe(Keyframe::linear(0.0, 0.0));
|
||||||
|
c.set_keyframe(Keyframe::linear(10.0, 100.0));
|
||||||
|
|
||||||
|
assert!(c.is_tween_inbetween(5.0, 0.001), "strictly between keyframes");
|
||||||
|
assert!(!c.is_tween_inbetween(0.0, 0.001), "on a keyframe");
|
||||||
|
assert!(!c.is_tween_inbetween(10.0, 0.001), "on a keyframe");
|
||||||
|
assert!(!c.is_tween_inbetween(15.0, 0.001), "past the last keyframe (extrapolation tail)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_keyframe_is_never_in_between() {
|
||||||
|
let mut c = AnimationCurve::new(
|
||||||
|
AnimationTarget::Object { id: uuid::Uuid::nil(), property: TransformProperty::X },
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
c.set_keyframe(Keyframe::linear(10.0, 100.0));
|
||||||
|
assert!(!c.is_tween_inbetween(5.0, 0.001));
|
||||||
|
assert!(!c.is_tween_inbetween(20.0, 0.001));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_tweened_when_any_curve_is_in_between() {
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
let mut data = AnimationData::new();
|
||||||
|
let mut cx = AnimationCurve::new(
|
||||||
|
AnimationTarget::Object { id, property: TransformProperty::X },
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
cx.set_keyframe(Keyframe::linear(0.0, 0.0));
|
||||||
|
cx.set_keyframe(Keyframe::linear(10.0, 100.0));
|
||||||
|
data.set_curve(cx);
|
||||||
|
|
||||||
|
assert!(data.is_object_tweened_at(id, 5.0));
|
||||||
|
assert!(!data.is_object_tweened_at(id, 0.0));
|
||||||
|
assert!(!data.is_object_tweened_at(uuid::Uuid::new_v4(), 5.0), "different object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -493,30 +493,53 @@ impl VectorLayer {
|
||||||
&mut self.keyframes[insert_idx]
|
&mut self.keyframes[insert_idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new keyframe at time by cloning the DCEL from the active keyframe.
|
/// Insert a new keyframe at time, taking the geometry the layer shows there.
|
||||||
/// If a keyframe already exists at the exact time, does nothing and returns it.
|
/// If a keyframe already exists at the exact time, does nothing and returns it.
|
||||||
|
///
|
||||||
|
/// Inside a shape-tween span this captures the *interpolated* geometry at `time` (not
|
||||||
|
/// the left keyframe's), and inherits the span's `tween_after` so the new keyframe keeps
|
||||||
|
/// tweening toward the next one — i.e. splitting a tween in two leaves the motion intact.
|
||||||
pub fn insert_keyframe_from_current(&mut self, time: f64) -> &mut ShapeKeyframe {
|
pub fn insert_keyframe_from_current(&mut self, time: f64) -> &mut ShapeKeyframe {
|
||||||
let tolerance = 0.001;
|
let tolerance = 0.001;
|
||||||
if let Some(idx) = self.keyframe_index_at_exact(time, tolerance) {
|
if let Some(idx) = self.keyframe_index_at_exact(time, tolerance) {
|
||||||
return &mut self.keyframes[idx];
|
return &mut self.keyframes[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone graph and clip instance IDs from the active keyframe
|
// Geometry shown at `time` (interpolated if mid-tween, else the held keyframe).
|
||||||
let (cloned_graph, cloned_clip_ids) = self
|
let cloned_graph = self
|
||||||
|
.tweened_graph_at(time)
|
||||||
|
.map(|g| g.into_owned())
|
||||||
|
.unwrap_or_else(VectorGraph::new);
|
||||||
|
// Inherit tween + clip instances from the active (left) keyframe.
|
||||||
|
let (tween_after, cloned_clip_ids) = self
|
||||||
.keyframe_at(time)
|
.keyframe_at(time)
|
||||||
.map(|kf| {
|
.map(|kf| (kf.tween_after, kf.clip_instance_ids.clone()))
|
||||||
(kf.graph.clone(), kf.clip_instance_ids.clone())
|
.unwrap_or((TweenType::None, Vec::new()));
|
||||||
})
|
|
||||||
.unwrap_or_else(|| (VectorGraph::new(), Vec::new()));
|
|
||||||
|
|
||||||
let insert_idx = self.keyframes.partition_point(|kf| kf.time < time);
|
let insert_idx = self.keyframes.partition_point(|kf| kf.time < time);
|
||||||
let mut kf = ShapeKeyframe::new(time);
|
let mut kf = ShapeKeyframe::new(time);
|
||||||
kf.graph = cloned_graph;
|
kf.graph = cloned_graph;
|
||||||
|
kf.tween_after = tween_after;
|
||||||
kf.clip_instance_ids = cloned_clip_ids;
|
kf.clip_instance_ids = cloned_clip_ids;
|
||||||
self.keyframes.insert(insert_idx, kf);
|
self.keyframes.insert(insert_idx, kf);
|
||||||
&mut self.keyframes[insert_idx]
|
&mut self.keyframes[insert_idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True when `time` falls strictly inside a shape-tween span — i.e. an in-between frame
|
||||||
|
/// (the left keyframe has `tween_after == Shape` and `time` is not on either keyframe).
|
||||||
|
/// Editing such a frame would silently modify the left keyframe, so the editor blocks it.
|
||||||
|
pub fn is_tween_inbetween(&self, time: f64) -> bool {
|
||||||
|
let tol = 0.001;
|
||||||
|
let idx = self.keyframes.partition_point(|kf| kf.time <= time);
|
||||||
|
if idx == 0 || idx >= self.keyframes.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let (a, b) = (&self.keyframes[idx - 1], &self.keyframes[idx]);
|
||||||
|
a.tween_after == TweenType::Shape
|
||||||
|
&& (time - a.time).abs() > tol
|
||||||
|
&& (b.time - time).abs() > tol
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove a keyframe at the exact time (within tolerance).
|
/// Remove a keyframe at the exact time (within tolerance).
|
||||||
/// Returns the removed keyframe if found.
|
/// Returns the removed keyframe if found.
|
||||||
pub(crate) fn remove_keyframe_at(&mut self, time: f64, tolerance: f64) -> Option<ShapeKeyframe> {
|
pub(crate) fn remove_keyframe_at(&mut self, time: f64, tolerance: f64) -> Option<ShapeKeyframe> {
|
||||||
|
|
@ -1136,6 +1159,53 @@ mod tests {
|
||||||
assert!((g.vertices[0].position.x - 0.0).abs() < 1e-6);
|
assert!((g.vertices[0].position.x - 0.0).abs() < 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inserting_keyframe_mid_tween_captures_interpolated_geometry_and_inherits_tween() {
|
||||||
|
use crate::vector_graph::{Direction, FillRule, ShapeColor};
|
||||||
|
use kurbo::{CubicBez, Point};
|
||||||
|
|
||||||
|
let mk = |x: f64| {
|
||||||
|
let mut g = VectorGraph::new();
|
||||||
|
let v0 = g.alloc_vertex(Point::new(x, 0.0));
|
||||||
|
let v1 = g.alloc_vertex(Point::new(x + 10.0, 0.0));
|
||||||
|
let c = CubicBez::new(
|
||||||
|
Point::new(x, 0.0),
|
||||||
|
Point::new(x + 3.0, 0.0),
|
||||||
|
Point::new(x + 7.0, 0.0),
|
||||||
|
Point::new(x + 10.0, 0.0),
|
||||||
|
);
|
||||||
|
g.alloc_edge(c, v0, v1, None, Some(ShapeColor::rgb(0, 0, 0)));
|
||||||
|
g.alloc_fill(vec![(crate::vector_graph::EdgeId(0), Direction::Forward)],
|
||||||
|
ShapeColor::rgb(255, 0, 0), FillRule::NonZero);
|
||||||
|
g
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut layer = VectorLayer::new("L");
|
||||||
|
layer.keyframes.clear();
|
||||||
|
let mut kf_a = ShapeKeyframe::new(0.0);
|
||||||
|
kf_a.graph = mk(0.0);
|
||||||
|
kf_a.tween_after = TweenType::Shape;
|
||||||
|
let mut kf_b = ShapeKeyframe::new(10.0);
|
||||||
|
kf_b.graph = mk(100.0);
|
||||||
|
layer.keyframes.push(kf_a);
|
||||||
|
layer.keyframes.push(kf_b);
|
||||||
|
|
||||||
|
// Insert keyframe C at the midpoint of the A→B shape tween.
|
||||||
|
layer.insert_keyframe_from_current(5.0);
|
||||||
|
|
||||||
|
let c = layer.keyframe_at(5.0).expect("keyframe C exists");
|
||||||
|
// C took the interpolated geometry (x=50), not A's geometry (x=0).
|
||||||
|
assert!((c.graph.vertices[0].position.x - 50.0).abs() < 1e-6,
|
||||||
|
"C should hold interpolated geometry, got {}", c.graph.vertices[0].position.x);
|
||||||
|
// C inherits the shape tween so it still morphs toward B.
|
||||||
|
assert_eq!(c.tween_after, TweenType::Shape, "C should inherit the shape tween");
|
||||||
|
|
||||||
|
// The frame between C and B is still an in-between (tween continues past C).
|
||||||
|
assert!(layer.is_tween_inbetween(7.0));
|
||||||
|
// C itself is a keyframe, not an in-between.
|
||||||
|
assert!(!layer.is_tween_inbetween(5.0));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_layer_creation() {
|
fn test_layer_creation() {
|
||||||
let layer = Layer::new(LayerType::Vector, "Test Layer");
|
let layer = Layer::new(LayerType::Vector, "Test Layer");
|
||||||
|
|
|
||||||
|
|
@ -3671,6 +3671,11 @@ impl StagePane {
|
||||||
|
|
||||||
let point = Point::new(world_pos.x as f64, world_pos.y as f64);
|
let point = Point::new(world_pos.x as f64, world_pos.y as f64);
|
||||||
|
|
||||||
|
// On a shape-tween in-between frame the displayed geometry is interpolated, not a
|
||||||
|
// real keyframe — editing it would silently mutate the left keyframe. Lock out
|
||||||
|
// geometry selection/editing there; clip-instance interaction stays available.
|
||||||
|
let inbetween = vector_layer.is_tween_inbetween(*shared.playback_time);
|
||||||
|
|
||||||
// Double-click: enter/exit movie clip editing
|
// Double-click: enter/exit movie clip editing
|
||||||
if response.double_clicked() {
|
if response.double_clicked() {
|
||||||
// Hit test clip instances at the click position
|
// Hit test clip instances at the click position
|
||||||
|
|
@ -3710,16 +3715,21 @@ impl StagePane {
|
||||||
// Scope this section to drop vector_layer borrow before drag handling
|
// Scope this section to drop vector_layer borrow before drag handling
|
||||||
let mouse_pressed = self.rsp_primary_pressed(ui);
|
let mouse_pressed = self.rsp_primary_pressed(ui);
|
||||||
if mouse_pressed {
|
if mouse_pressed {
|
||||||
// VECTOR EDITING: Check for vertex/curve editing first (higher priority than selection)
|
// VECTOR EDITING: Check for vertex/curve editing first (higher priority than
|
||||||
|
// selection). Skipped on tween in-between frames — geometry isn't editable there.
|
||||||
let tolerance = EditingHitTolerance::scaled_by_zoom(self.zoom as f64);
|
let tolerance = EditingHitTolerance::scaled_by_zoom(self.zoom as f64);
|
||||||
let vector_hit = hit_test_vector_editing(
|
let vector_hit = if inbetween {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
hit_test_vector_editing(
|
||||||
vector_layer,
|
vector_layer,
|
||||||
*shared.playback_time,
|
*shared.playback_time,
|
||||||
point,
|
point,
|
||||||
&tolerance,
|
&tolerance,
|
||||||
Affine::IDENTITY,
|
Affine::IDENTITY,
|
||||||
false, // Select tool doesn't show control points
|
false, // Select tool doesn't show control points
|
||||||
);
|
)
|
||||||
|
};
|
||||||
// Priority 1: Vector editing (vertices immediately, curves deferred)
|
// Priority 1: Vector editing (vertices immediately, curves deferred)
|
||||||
if let Some(hit) = vector_hit {
|
if let Some(hit) = vector_hit {
|
||||||
match hit {
|
match hit {
|
||||||
|
|
@ -3756,6 +3766,9 @@ impl StagePane {
|
||||||
|
|
||||||
let hit_result = if let Some(clip_id) = clip_hit {
|
let hit_result = if let Some(clip_id) = clip_hit {
|
||||||
Some(hit_test::HitResult::ClipInstance(clip_id))
|
Some(hit_test::HitResult::ClipInstance(clip_id))
|
||||||
|
} else if inbetween {
|
||||||
|
// Geometry not selectable on an in-between frame.
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
// No clip hit, test DCEL edges and faces
|
// No clip hit, test DCEL edges and faces
|
||||||
hit_test::hit_test_layer(vector_layer, *shared.playback_time, point, 5.0, Affine::IDENTITY)
|
hit_test::hit_test_layer(vector_layer, *shared.playback_time, point, 5.0, Affine::IDENTITY)
|
||||||
|
|
@ -3806,8 +3819,16 @@ impl StagePane {
|
||||||
}
|
}
|
||||||
*shared.focus = lightningbeam_core::selection::FocusSelection::ClipInstances(shared.selection.clip_instances().to_vec());
|
*shared.focus = lightningbeam_core::selection::FocusSelection::ClipInstances(shared.selection.clip_instances().to_vec());
|
||||||
|
|
||||||
// If clip instance is now selected, prepare for dragging
|
// A clip mid motion-tween shows an interpolated position; moving it
|
||||||
if shared.selection.contains_clip_instance(&clip_id) {
|
// there would silently insert a keyframe and disturb the tween. Allow
|
||||||
|
// selecting it, but don't start a drag.
|
||||||
|
let clip_motion_locked = vector_layer
|
||||||
|
.layer
|
||||||
|
.animation_data
|
||||||
|
.is_object_tweened_at(clip_id, *shared.playback_time);
|
||||||
|
|
||||||
|
// If clip instance is now selected (and editable), prepare for dragging
|
||||||
|
if !clip_motion_locked && shared.selection.contains_clip_instance(&clip_id) {
|
||||||
// Store original positions of all selected clip instances
|
// Store original positions of all selected clip instances
|
||||||
let mut original_positions = std::collections::HashMap::new();
|
let mut original_positions = std::collections::HashMap::new();
|
||||||
for &clip_inst_id in shared.selection.clip_instances() {
|
for &clip_inst_id in shared.selection.clip_instances() {
|
||||||
|
|
@ -9430,6 +9451,20 @@ impl StagePane {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block transforming a clip instance that's mid motion-tween (on an in-between
|
||||||
|
// frame) — it would silently insert a keyframe and disturb the tween.
|
||||||
|
if is_vector_layer {
|
||||||
|
let locked = match shared.action_executor.document().get_layer(&active_layer_id) {
|
||||||
|
Some(AnyLayer::Vector(vl)) => shared.selection.clip_instances().iter().any(|id| {
|
||||||
|
vl.layer.animation_data.is_object_tweened_at(*id, *shared.playback_time)
|
||||||
|
}),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if locked {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let point = Point::new(world_pos.x as f64, world_pos.y as f64);
|
let point = Point::new(world_pos.x as f64, world_pos.y as f64);
|
||||||
|
|
||||||
// For video layers, transform the visible clip at playback time (no selection needed)
|
// For video layers, transform the visible clip at playback time (no selection needed)
|
||||||
|
|
@ -10688,6 +10723,17 @@ impl StagePane {
|
||||||
if !alt_held {
|
if !alt_held {
|
||||||
use lightningbeam_core::tool::Tool;
|
use lightningbeam_core::tool::Tool;
|
||||||
|
|
||||||
|
// On a shape-tween in-between frame the active vector layer's geometry is an
|
||||||
|
// interpolated preview, not a real keyframe — lock out the editing tools.
|
||||||
|
let vector_inbetween = shared.active_layer_id.and_then(|id| {
|
||||||
|
match shared.action_executor.document().get_layer(&id) {
|
||||||
|
Some(lightningbeam_core::layer::AnyLayer::Vector(vl)) => {
|
||||||
|
Some(vl.is_tween_inbetween(*shared.playback_time))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}).unwrap_or(false);
|
||||||
|
|
||||||
match *shared.selected_tool {
|
match *shared.selected_tool {
|
||||||
Tool::Select => {
|
Tool::Select => {
|
||||||
let is_raster = shared.active_layer_id.and_then(|id| {
|
let is_raster = shared.active_layer_id.and_then(|id| {
|
||||||
|
|
@ -10699,13 +10745,13 @@ impl StagePane {
|
||||||
self.handle_select_tool(ui, &response, world_pos, shift_held, shared);
|
self.handle_select_tool(ui, &response, world_pos, shift_held, shared);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tool::BezierEdit => {
|
Tool::BezierEdit if !vector_inbetween => {
|
||||||
self.handle_bezier_edit_tool(ui, &response, world_pos, shift_held, shared);
|
self.handle_bezier_edit_tool(ui, &response, world_pos, shift_held, shared);
|
||||||
}
|
}
|
||||||
Tool::Rectangle => {
|
Tool::Rectangle if !vector_inbetween => {
|
||||||
self.handle_rectangle_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
self.handle_rectangle_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
||||||
}
|
}
|
||||||
Tool::Ellipse => {
|
Tool::Ellipse if !vector_inbetween => {
|
||||||
self.handle_ellipse_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
self.handle_ellipse_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
||||||
}
|
}
|
||||||
Tool::Draw => {
|
Tool::Draw => {
|
||||||
|
|
@ -10715,7 +10761,7 @@ impl StagePane {
|
||||||
}).map_or(false, |l| matches!(l, lightningbeam_core::layer::AnyLayer::Raster(_)));
|
}).map_or(false, |l| matches!(l, lightningbeam_core::layer::AnyLayer::Raster(_)));
|
||||||
if is_raster {
|
if is_raster {
|
||||||
self.handle_unified_raster_stroke_tool(ui, &response, world_pos, &crate::tools::paint::PAINT, shared);
|
self.handle_unified_raster_stroke_tool(ui, &response, world_pos, &crate::tools::paint::PAINT, shared);
|
||||||
} else {
|
} else if !vector_inbetween {
|
||||||
self.handle_draw_tool(ui, &response, world_pos, shared);
|
self.handle_draw_tool(ui, &response, world_pos, shared);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10735,19 +10781,19 @@ impl StagePane {
|
||||||
Tool::Transform => {
|
Tool::Transform => {
|
||||||
self.handle_transform_tool(ui, &response, world_pos, shared);
|
self.handle_transform_tool(ui, &response, world_pos, shared);
|
||||||
}
|
}
|
||||||
Tool::PaintBucket => {
|
Tool::PaintBucket if !vector_inbetween => {
|
||||||
self.handle_paint_bucket_tool(&response, world_pos, shared);
|
self.handle_paint_bucket_tool(&response, world_pos, shared);
|
||||||
}
|
}
|
||||||
Tool::Line => {
|
Tool::Line if !vector_inbetween => {
|
||||||
self.handle_line_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
self.handle_line_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
||||||
}
|
}
|
||||||
Tool::Polygon => {
|
Tool::Polygon if !vector_inbetween => {
|
||||||
self.handle_polygon_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
self.handle_polygon_tool(ui, &response, world_pos, shift_held, ctrl_held, shared);
|
||||||
}
|
}
|
||||||
Tool::Eyedropper => {
|
Tool::Eyedropper => {
|
||||||
self.handle_eyedropper_tool(ui, &response, mouse_pos, shared);
|
self.handle_eyedropper_tool(ui, &response, mouse_pos, shared);
|
||||||
}
|
}
|
||||||
Tool::RegionSelect => {
|
Tool::RegionSelect if !vector_inbetween => {
|
||||||
self.handle_region_select_tool(ui, &response, world_pos, shift_held, shared);
|
self.handle_region_select_tool(ui, &response, world_pos, shift_held, shared);
|
||||||
}
|
}
|
||||||
Tool::Warp => {
|
Tool::Warp => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue