refactor: Clean Architecture - Separate domain (CropRegion) from UI (crop_model)

- CropRegion moved to domain/document/operations/crop.rs (pure model)
- crop_types.rs renamed to crop_model.rs (honest UI model name)
- DragHandle and CropSelection stay in UI layer (where they belong)
- Clean separation: Domain has no UI concerns, UI imports from Domain
This commit is contained in:
wfx 2026-02-04 16:46:41 +01:00
parent be956f701b
commit 531cfef715
6 changed files with 83 additions and 38 deletions

View file

@ -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.
///

View file

@ -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
}
}

View file

@ -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.

View file

@ -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<CropRegion> {
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)
}
}

View file

@ -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

View file

@ -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;