Step 1: Add simplified crop types in ui/widgets, update imports
This commit is contained in:
parent
6a4629bb47
commit
34d0045e0d
8 changed files with 227 additions and 21 deletions
|
|
@ -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::components::crop::CropRegion;
|
||||
use crate::ui::widgets::CropRegion;
|
||||
|
||||
/// Crop document command.
|
||||
///
|
||||
|
|
@ -52,7 +52,8 @@ impl CropDocumentCommand {
|
|||
scale: f32,
|
||||
pan_offset: Vector,
|
||||
) -> Result<Self, String> {
|
||||
let canvas_rect = crop_region.as_tuple();
|
||||
let (x, y, w, h) = crop_region.as_tuple();
|
||||
let canvas_rect = (x as f32, y as f32, w as f32, h as f32);
|
||||
|
||||
// Convert canvas coordinates to image pixel coordinates
|
||||
let image_rect = Self::canvas_rect_to_image_rect(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::ui::components::crop::DragHandle;
|
||||
use crate::ui::widgets::DragHandle;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AppMessage {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// src/ui/mod.rs
|
||||
//
|
||||
// UI layer: COSMIC application, views, and components.
|
||||
// UI layer: COSMIC application, views, and widgets.
|
||||
|
||||
pub mod app;
|
||||
pub mod message;
|
||||
|
|
@ -9,6 +9,7 @@ pub mod model;
|
|||
pub mod update;
|
||||
pub mod components;
|
||||
pub mod views;
|
||||
pub mod widgets;
|
||||
|
||||
// Internal module for syncing model from DocumentManager
|
||||
pub(crate) mod sync;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use cosmic::iced::Size;
|
||||
|
||||
use crate::ui::components::crop::CropSelection;
|
||||
use crate::ui::widgets::CropSelection;
|
||||
use crate::config::AppConfig;
|
||||
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use super::model::{AppModel, ToolMode, ViewMode};
|
|||
use crate::application::commands::transform_document::{TransformDocumentCommand, TransformOperation};
|
||||
use crate::application::commands::crop_document::CropDocumentCommand;
|
||||
|
||||
use crate::ui::components::crop::DragHandle;
|
||||
use crate::ui::widgets::DragHandle;
|
||||
|
||||
// =============================================================================
|
||||
// Update Result
|
||||
|
|
@ -200,12 +200,12 @@ pub fn update(app: &mut NoctuaApp, msg: &AppMessage) -> UpdateResult {
|
|||
AppMessage::ApplyCrop => {
|
||||
if app.model.tool_mode == ToolMode::Crop {
|
||||
// Get crop selection region
|
||||
if let Some(region) = &app.model.crop_selection.region {
|
||||
if let Some(crop_region) = app.model.crop_selection.to_crop_region() {
|
||||
// Create crop command from canvas selection
|
||||
let pan_offset = cosmic::iced::Vector::new(app.model.pan_x, app.model.pan_y);
|
||||
|
||||
match CropDocumentCommand::from_canvas_selection(
|
||||
region,
|
||||
&crop_region,
|
||||
app.model.canvas_size,
|
||||
app.model.image_size,
|
||||
app.model.scale,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// src/app/view/canvas.rs
|
||||
// src/ui/views/canvas.rs
|
||||
//
|
||||
// Render the center canvas area with the current document.
|
||||
|
||||
use cosmic::iced::widget::image::FilterMethod;
|
||||
use cosmic::iced::{ContentFit, Length};
|
||||
use cosmic::iced_widget::stack;
|
||||
use cosmic::widget::{container, text};
|
||||
use cosmic::Element;
|
||||
|
||||
use crate::ui::components::crop::crop_overlay;
|
||||
use super::image_viewer::Viewer;
|
||||
use crate::ui::model::{ToolMode, ViewMode};
|
||||
use crate::ui::{AppMessage, AppModel};
|
||||
|
|
@ -49,16 +47,12 @@ pub fn view<'a>(
|
|||
.scale_step(config.scale_step - 1.0)
|
||||
.disable_pan(model.tool_mode == ToolMode::Crop);
|
||||
|
||||
if model.tool_mode == ToolMode::Crop {
|
||||
let overlay = crop_overlay(&model.crop_selection, config.crop_show_grid);
|
||||
|
||||
stack![img_viewer, overlay].into()
|
||||
} else {
|
||||
container(img_viewer)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
// TODO: Add crop dialog when ToolMode::Crop
|
||||
// For now, just show the viewer
|
||||
container(img_viewer)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
} else {
|
||||
container(text(fl!("no-document")))
|
||||
.width(Length::Fill)
|
||||
|
|
|
|||
201
src/ui/widgets/crop_types.rs
Normal file
201
src/ui/widgets/crop_types.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// src/ui/widgets/crop_types.rs
|
||||
//
|
||||
// Simple crop types (based on Cupola, simplified from our complex implementation).
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Drag handle for crop selection (simple enum, no Direction struct).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum DragHandle {
|
||||
#[default]
|
||||
None,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
Move,
|
||||
}
|
||||
|
||||
/// Crop selection state (uses simple tuples instead of complex structs).
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CropSelection {
|
||||
/// Region as (x, y, width, height) in image coordinates
|
||||
pub region: Option<(f32, f32, f32, f32)>,
|
||||
pub is_dragging: bool,
|
||||
pub drag_handle: DragHandle,
|
||||
drag_start: Option<(f32, f32)>,
|
||||
drag_start_region: Option<(f32, f32, f32, f32)>,
|
||||
}
|
||||
|
||||
impl CropSelection {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn start_new_selection(&mut self, x: f32, y: f32) {
|
||||
self.region = Some((x, y, 0.0, 0.0));
|
||||
self.is_dragging = true;
|
||||
self.drag_handle = DragHandle::None;
|
||||
self.drag_start = Some((x, y));
|
||||
self.drag_start_region = None;
|
||||
}
|
||||
|
||||
pub fn start_handle_drag(&mut self, handle: DragHandle, x: f32, y: f32) {
|
||||
self.is_dragging = true;
|
||||
self.drag_handle = handle;
|
||||
self.drag_start = Some((x, y));
|
||||
self.drag_start_region = self.region;
|
||||
}
|
||||
|
||||
pub fn update_drag(&mut self, x: f32, y: f32, img_width: f32, img_height: f32) {
|
||||
if !self.is_dragging {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.drag_handle {
|
||||
DragHandle::None => {
|
||||
// Creating new selection
|
||||
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);
|
||||
let max_x = start_x.max(x).min(img_width);
|
||||
let max_y = start_y.max(y).min(img_height);
|
||||
|
||||
self.region = Some((min_x, min_y, max_x - min_x, max_y - min_y));
|
||||
}
|
||||
}
|
||||
DragHandle::Move => {
|
||||
// Moving entire selection
|
||||
if let (Some((start_x, start_y)), Some((rx, ry, rw, rh))) =
|
||||
(self.drag_start, self.drag_start_region)
|
||||
{
|
||||
let dx = x - start_x;
|
||||
let dy = y - start_y;
|
||||
let new_x = (rx + dx).max(0.0).min(img_width - rw);
|
||||
let new_y = (ry + dy).max(0.0).min(img_height - rh);
|
||||
self.region = Some((new_x, new_y, rw, rh));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Resizing from edge/corner
|
||||
if let Some((rx, ry, rw, rh)) = self.drag_start_region {
|
||||
let (new_x, new_y, new_w, new_h) =
|
||||
self.resize_region(rx, ry, rw, rh, x, y, img_width, img_height);
|
||||
self.region = Some((new_x, new_y, new_w, new_h));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_region(
|
||||
&self,
|
||||
rx: f32,
|
||||
ry: f32,
|
||||
rw: f32,
|
||||
rh: f32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
img_width: f32,
|
||||
img_height: f32,
|
||||
) -> (f32, f32, f32, f32) {
|
||||
const MIN_SIZE: f32 = 10.0;
|
||||
|
||||
let right = rx + rw;
|
||||
let bottom = ry + rh;
|
||||
let x = x.max(0.0).min(img_width);
|
||||
let y = y.max(0.0).min(img_height);
|
||||
|
||||
match self.drag_handle {
|
||||
DragHandle::TopLeft => {
|
||||
let new_x = x.min(right - MIN_SIZE);
|
||||
let new_y = y.min(bottom - MIN_SIZE);
|
||||
(new_x, new_y, right - new_x, bottom - new_y)
|
||||
}
|
||||
DragHandle::TopRight => {
|
||||
let new_right = x.max(rx + MIN_SIZE);
|
||||
let new_y = y.min(bottom - MIN_SIZE);
|
||||
(rx, new_y, new_right - rx, bottom - new_y)
|
||||
}
|
||||
DragHandle::BottomLeft => {
|
||||
let new_x = x.min(right - MIN_SIZE);
|
||||
let new_bottom = y.max(ry + MIN_SIZE);
|
||||
(new_x, ry, right - new_x, new_bottom - ry)
|
||||
}
|
||||
DragHandle::BottomRight => {
|
||||
let new_right = x.max(rx + MIN_SIZE);
|
||||
let new_bottom = y.max(ry + MIN_SIZE);
|
||||
(rx, ry, new_right - rx, new_bottom - ry)
|
||||
}
|
||||
DragHandle::Top => {
|
||||
let new_y = y.min(bottom - MIN_SIZE);
|
||||
(rx, new_y, rw, bottom - new_y)
|
||||
}
|
||||
DragHandle::Bottom => {
|
||||
let new_bottom = y.max(ry + MIN_SIZE);
|
||||
(rx, ry, rw, new_bottom - ry)
|
||||
}
|
||||
DragHandle::Left => {
|
||||
let new_x = x.min(right - MIN_SIZE);
|
||||
(new_x, ry, right - new_x, rh)
|
||||
}
|
||||
DragHandle::Right => {
|
||||
let new_right = x.max(rx + MIN_SIZE);
|
||||
(rx, ry, new_right - rx, rh)
|
||||
}
|
||||
_ => (rx, ry, rw, rh),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_drag(&mut self) {
|
||||
self.is_dragging = false;
|
||||
self.drag_start = None;
|
||||
self.drag_start_region = None;
|
||||
}
|
||||
|
||||
pub fn to_crop_region(&self) -> Option<CropRegion> {
|
||||
self.region.and_then(|(x, y, w, h)| {
|
||||
if w > 1.0 && h > 1.0 {
|
||||
Some(CropRegion {
|
||||
x: x as u32,
|
||||
y: y as u32,
|
||||
width: w as u32,
|
||||
height: h as u32,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
9
src/ui/widgets/mod.rs
Normal file
9
src/ui/widgets/mod.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// src/ui/widgets/mod.rs
|
||||
//
|
||||
// Custom widgets module.
|
||||
|
||||
pub mod crop_types;
|
||||
// pub mod crop_widget; // TODO: Implement next
|
||||
|
||||
pub use crop_types::{CropRegion, CropSelection, DragHandle};
|
||||
Loading…
Add table
Add a link
Reference in a new issue