//! 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, /// Previous fill color for undo old_fill_color: Option>, } 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() } }