diff --git a/src/application/commands/crop_document.rs b/src/application/commands/crop_document.rs index 327fcf0..2d2f408 100644 --- a/src/application/commands/crop_document.rs +++ b/src/application/commands/crop_document.rs @@ -8,7 +8,7 @@ use cosmic::iced::{ContentFit, Size, Vector}; use crate::application::DocumentManager; use crate::domain::document::core::content::DocumentKind; use crate::domain::document::core::document::DocResult; -use crate::ui::widgets::CropRegion; +use crate::domain::document::operations::CropRegion; /// Crop document command. /// diff --git a/src/domain/document/operations/crop.rs b/src/domain/document/operations/crop.rs new file mode 100644 index 0000000..8bc0035 --- /dev/null +++ b/src/domain/document/operations/crop.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// src/domain/document/operations/crop.rs +// +// Crop operation domain model. + +/// Crop region in pixel coordinates. +/// +/// Pure domain model - represents a rectangular region to crop. +/// No UI concerns, just data. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CropRegion { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +impl CropRegion { + pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self { + Self { x, y, width, height } + } + + pub fn as_tuple(&self) -> (u32, u32, u32, u32) { + (self.x, self.y, self.width, self.height) + } + + /// Check if region has valid dimensions. + pub fn is_valid(&self) -> bool { + self.width > 0 && self.height > 0 + } +} diff --git a/src/domain/document/operations/mod.rs b/src/domain/document/operations/mod.rs index f79eb1a..72f7c23 100644 --- a/src/domain/document/operations/mod.rs +++ b/src/domain/document/operations/mod.rs @@ -3,10 +3,14 @@ // // Document operations: transformations, rendering, and export. +pub mod crop; pub mod export; pub mod render; pub mod transform; +// Re-export CropRegion for convenience +pub use crop::CropRegion; + // Note: Low-level pixel operations (apply_rotation, apply_flip, crop_image) // are internal helpers (pub(crate)) used only by document type implementations. // Use high-level operations above for application and UI code. diff --git a/src/ui/widgets/crop_types.rs b/src/ui/widgets/crop_model.rs similarity index 79% rename from src/ui/widgets/crop_types.rs rename to src/ui/widgets/crop_model.rs index 8f287fa..465e471 100644 --- a/src/ui/widgets/crop_types.rs +++ b/src/ui/widgets/crop_model.rs @@ -1,18 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/ui/widgets/crop_types.rs +// src/ui/widgets/crop_model.rs // -// Simple crop types (based on Cupola, simplified from our complex implementation). +// Crop UI model (drag state and logic). -/// Crop region in pixel coordinates. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct CropRegion { - pub x: u32, - pub y: u32, - pub width: u32, - pub height: u32, -} +use crate::domain::document::operations::CropRegion; -/// Drag handle for crop selection (simple enum, no Direction struct). +/// Drag handle for crop selection. +/// +/// Identifies which part of the selection is being dragged. +/// Pure UI concern - not part of domain. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum DragHandle { #[default] @@ -28,14 +24,29 @@ pub enum DragHandle { Move, } -/// Crop selection state (uses simple tuples instead of complex structs). +/// Crop selection UI model. +/// +/// Manages the interactive state of crop selection: +/// - Current selection region (in screen/canvas coordinates) +/// - Drag state (what's being dragged, where it started) +/// - Drag logic (how to update region based on handle type) +/// +/// This is UI-specific logic, not domain logic! #[derive(Debug, Clone, Default)] pub struct CropSelection { - /// Region as (x, y, width, height) in image coordinates + /// Current selection region as (x, y, width, height) in screen coordinates pub region: Option<(f32, f32, f32, f32)>, + + /// Is user currently dragging? pub is_dragging: bool, + + /// Which handle/part is being dragged? pub drag_handle: DragHandle, + + /// Where did the drag start? (for delta calculation) drag_start: Option<(f32, f32)>, + + /// What was the region when drag started? (for resize calculation) drag_start_region: Option<(f32, f32, f32, f32)>, } @@ -44,6 +55,7 @@ impl CropSelection { Self::default() } + /// Start a new selection (user clicks on empty area). pub fn start_new_selection(&mut self, x: f32, y: f32) { self.region = Some((x, y, 0.0, 0.0)); self.is_dragging = true; @@ -52,6 +64,7 @@ impl CropSelection { self.drag_start_region = None; } + /// Start dragging a handle (user clicks on existing selection). pub fn start_handle_drag(&mut self, handle: DragHandle, x: f32, y: f32) { self.is_dragging = true; self.drag_handle = handle; @@ -59,6 +72,7 @@ impl CropSelection { self.drag_start_region = self.region; } + /// Update selection during drag. pub fn update_drag(&mut self, x: f32, y: f32, img_width: f32, img_height: f32) { if !self.is_dragging { return; @@ -66,7 +80,7 @@ impl CropSelection { match self.drag_handle { DragHandle::None => { - // Creating new selection + // Creating new selection - expand from start point if let Some((start_x, start_y)) = self.drag_start { let min_x = start_x.min(x).max(0.0); let min_y = start_y.min(y).max(0.0); @@ -99,6 +113,7 @@ impl CropSelection { } } + /// Resize region based on which handle is being dragged. fn resize_region( &self, rx: f32, @@ -158,12 +173,26 @@ impl CropSelection { } } + /// End drag operation. pub fn end_drag(&mut self) { self.is_dragging = false; self.drag_start = None; self.drag_start_region = None; } + /// Reset selection (cancel). + pub fn reset(&mut self) { + *self = Self::default(); + } + + /// Check if there's a valid selection. + pub fn has_selection(&self) -> bool { + self.region + .map(|(_, _, w, h)| w > 1.0 && h > 1.0) + .unwrap_or(false) + } + + /// Convert to domain CropRegion for actual crop operation. pub fn to_crop_region(&self) -> Option { self.region.and_then(|(x, y, w, h)| { if w > 1.0 && h > 1.0 { @@ -178,24 +207,4 @@ impl CropSelection { } }) } - - pub fn reset(&mut self) { - *self = Self::default(); - } - - pub fn has_selection(&self) -> bool { - self.region - .map(|(_, _, w, h)| w > 1.0 && h > 1.0) - .unwrap_or(false) - } -} - -impl CropRegion { - pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self { - Self { x, y, width, height } - } - - pub fn as_tuple(&self) -> (u32, u32, u32, u32) { - (self.x, self.y, self.width, self.height) - } } diff --git a/src/ui/widgets/crop_overlay.rs b/src/ui/widgets/crop_overlay.rs index 8233be3..90b25f1 100644 --- a/src/ui/widgets/crop_overlay.rs +++ b/src/ui/widgets/crop_overlay.rs @@ -18,7 +18,7 @@ use cosmic::{ }, }; -use crate::ui::widgets::{CropSelection, DragHandle}; +use crate::ui::widgets::crop_model::{CropSelection, DragHandle}; use crate::ui::AppMessage; // Visual constants diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index fd7021c..fc0a669 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -3,10 +3,11 @@ // // Custom widgets module. -pub mod crop_types; +pub mod crop_model; pub mod crop_overlay; pub mod image_viewer; -pub use crop_types::{CropRegion, CropSelection, DragHandle}; +// Re-exports for convenience +pub use crop_model::{CropSelection, DragHandle}; pub use crop_overlay::crop_overlay; pub use image_viewer::Viewer;