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::application::DocumentManager;
|
||||||
use crate::domain::document::core::content::DocumentKind;
|
use crate::domain::document::core::content::DocumentKind;
|
||||||
use crate::domain::document::core::document::DocResult;
|
use crate::domain::document::core::document::DocResult;
|
||||||
use crate::ui::components::crop::CropRegion;
|
use crate::ui::widgets::CropRegion;
|
||||||
|
|
||||||
/// Crop document command.
|
/// Crop document command.
|
||||||
///
|
///
|
||||||
|
|
@ -52,7 +52,8 @@ impl CropDocumentCommand {
|
||||||
scale: f32,
|
scale: f32,
|
||||||
pan_offset: Vector,
|
pan_offset: Vector,
|
||||||
) -> Result<Self, String> {
|
) -> 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
|
// Convert canvas coordinates to image pixel coordinates
|
||||||
let image_rect = Self::canvas_rect_to_image_rect(
|
let image_rect = Self::canvas_rect_to_image_rect(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::ui::components::crop::DragHandle;
|
use crate::ui::widgets::DragHandle;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum AppMessage {
|
pub enum AppMessage {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
// src/ui/mod.rs
|
// src/ui/mod.rs
|
||||||
//
|
//
|
||||||
// UI layer: COSMIC application, views, and components.
|
// UI layer: COSMIC application, views, and widgets.
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
|
@ -9,6 +9,7 @@ pub mod model;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod views;
|
pub mod views;
|
||||||
|
pub mod widgets;
|
||||||
|
|
||||||
// Internal module for syncing model from DocumentManager
|
// Internal module for syncing model from DocumentManager
|
||||||
pub(crate) mod sync;
|
pub(crate) mod sync;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use cosmic::iced::Size;
|
use cosmic::iced::Size;
|
||||||
|
|
||||||
use crate::ui::components::crop::CropSelection;
|
use crate::ui::widgets::CropSelection;
|
||||||
use crate::config::AppConfig;
|
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::transform_document::{TransformDocumentCommand, TransformOperation};
|
||||||
use crate::application::commands::crop_document::CropDocumentCommand;
|
use crate::application::commands::crop_document::CropDocumentCommand;
|
||||||
|
|
||||||
use crate::ui::components::crop::DragHandle;
|
use crate::ui::widgets::DragHandle;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Update Result
|
// Update Result
|
||||||
|
|
@ -200,12 +200,12 @@ pub fn update(app: &mut NoctuaApp, msg: &AppMessage) -> UpdateResult {
|
||||||
AppMessage::ApplyCrop => {
|
AppMessage::ApplyCrop => {
|
||||||
if app.model.tool_mode == ToolMode::Crop {
|
if app.model.tool_mode == ToolMode::Crop {
|
||||||
// Get crop selection region
|
// 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
|
// Create crop command from canvas selection
|
||||||
let pan_offset = cosmic::iced::Vector::new(app.model.pan_x, app.model.pan_y);
|
let pan_offset = cosmic::iced::Vector::new(app.model.pan_x, app.model.pan_y);
|
||||||
|
|
||||||
match CropDocumentCommand::from_canvas_selection(
|
match CropDocumentCommand::from_canvas_selection(
|
||||||
region,
|
&crop_region,
|
||||||
app.model.canvas_size,
|
app.model.canvas_size,
|
||||||
app.model.image_size,
|
app.model.image_size,
|
||||||
app.model.scale,
|
app.model.scale,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// 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.
|
// Render the center canvas area with the current document.
|
||||||
|
|
||||||
use cosmic::iced::widget::image::FilterMethod;
|
use cosmic::iced::widget::image::FilterMethod;
|
||||||
use cosmic::iced::{ContentFit, Length};
|
use cosmic::iced::{ContentFit, Length};
|
||||||
use cosmic::iced_widget::stack;
|
|
||||||
use cosmic::widget::{container, text};
|
use cosmic::widget::{container, text};
|
||||||
use cosmic::Element;
|
use cosmic::Element;
|
||||||
|
|
||||||
use crate::ui::components::crop::crop_overlay;
|
|
||||||
use super::image_viewer::Viewer;
|
use super::image_viewer::Viewer;
|
||||||
use crate::ui::model::{ToolMode, ViewMode};
|
use crate::ui::model::{ToolMode, ViewMode};
|
||||||
use crate::ui::{AppMessage, AppModel};
|
use crate::ui::{AppMessage, AppModel};
|
||||||
|
|
@ -49,16 +47,12 @@ pub fn view<'a>(
|
||||||
.scale_step(config.scale_step - 1.0)
|
.scale_step(config.scale_step - 1.0)
|
||||||
.disable_pan(model.tool_mode == ToolMode::Crop);
|
.disable_pan(model.tool_mode == ToolMode::Crop);
|
||||||
|
|
||||||
if model.tool_mode == ToolMode::Crop {
|
// TODO: Add crop dialog when ToolMode::Crop
|
||||||
let overlay = crop_overlay(&model.crop_selection, config.crop_show_grid);
|
// For now, just show the viewer
|
||||||
|
container(img_viewer)
|
||||||
stack![img_viewer, overlay].into()
|
.width(Length::Fill)
|
||||||
} else {
|
.height(Length::Fill)
|
||||||
container(img_viewer)
|
.into()
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
container(text(fl!("no-document")))
|
container(text(fl!("no-document")))
|
||||||
.width(Length::Fill)
|
.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