Lightningbeam/lightningbeam-ui/lightningbeam-core/src/actions/paint_bucket.rs

108 lines
3.2 KiB
Rust

//! Paint bucket fill action — sets fill_color on a DCEL face.
use crate::action::Action;
use crate::dcel::FaceId;
use crate::document::Document;
use crate::layer::AnyLayer;
use crate::shape::ShapeColor;
use uuid::Uuid;
use vello::kurbo::Point;
/// Action that performs a paint bucket fill on a DCEL face.
pub struct PaintBucketAction {
layer_id: Uuid,
time: f64,
click_point: Point,
fill_color: ShapeColor,
/// The face that was hit (resolved during execute)
hit_face: Option<FaceId>,
/// Previous fill color for undo
old_fill_color: Option<Option<ShapeColor>>,
}
impl PaintBucketAction {
pub fn new(
layer_id: Uuid,
time: f64,
click_point: Point,
fill_color: ShapeColor,
) -> Self {
Self {
layer_id,
time,
click_point,
fill_color,
hit_face: None,
old_fill_color: None,
}
}
}
impl Action for PaintBucketAction {
fn execute(&mut self, document: &mut Document) -> Result<(), String> {
let layer = document
.get_layer_mut(&self.layer_id)
.ok_or_else(|| format!("Layer {} not found", self.layer_id))?;
let vl = match layer {
AnyLayer::Vector(vl) => vl,
_ => return Err("Not a vector layer".to_string()),
};
let keyframe = vl.ensure_keyframe_at(self.time);
let dcel = &mut keyframe.dcel;
// Record for debug test generation (if recording is active)
dcel.record_paint_point(self.click_point);
// Hit-test to find which face was clicked
let face_id = dcel.find_face_containing_point(self.click_point);
// Dump cumulative test to stderr after every paint click (if recording)
// Do this before the early return so failed clicks are captured too.
if dcel.is_recording() {
eprintln!("\n--- DCEL debug test (cumulative, face={:?}) ---", face_id);
dcel.debug_recorder.as_ref().unwrap().dump_test("test_recorded");
eprintln!("--- end test ---\n");
}
if face_id.0 == 0 {
// FaceId(0) is the unbounded exterior face — nothing to fill
return Err("No face at click point".to_string());
}
// Store for undo
self.hit_face = Some(face_id);
self.old_fill_color = Some(dcel.face(face_id).fill_color.clone());
// Apply fill
dcel.face_mut(face_id).fill_color = Some(self.fill_color.clone());
Ok(())
}
fn rollback(&mut self, document: &mut Document) -> Result<(), String> {
let face_id = self.hit_face.ok_or("No face to undo")?;
let layer = document
.get_layer_mut(&self.layer_id)
.ok_or_else(|| format!("Layer {} not found", self.layer_id))?;
let vl = match layer {
AnyLayer::Vector(vl) => vl,
_ => return Err("Not a vector layer".to_string()),
};
let keyframe = vl.ensure_keyframe_at(self.time);
let dcel = &mut keyframe.dcel;
dcel.face_mut(face_id).fill_color = self.old_fill_color.take().unwrap_or(None);
Ok(())
}
fn description(&self) -> String {
"Paint bucket fill".to_string()
}
}