feat(iced): add missing dialogs (Image Size, Canvas Size, About)
This commit is contained in:
@@ -70,6 +70,9 @@ pub enum ActiveDialog {
|
|||||||
CloseConfirm,
|
CloseConfirm,
|
||||||
SelectionOp(&'static str),
|
SelectionOp(&'static str),
|
||||||
LayerStyleDialog,
|
LayerStyleDialog,
|
||||||
|
ImageSize,
|
||||||
|
CanvasSize,
|
||||||
|
About,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Top-level application state.
|
/// Top-level application state.
|
||||||
@@ -111,6 +114,15 @@ pub struct HcieIcedApp {
|
|||||||
pub dialog_lightness: f32,
|
pub dialog_lightness: f32,
|
||||||
/// Selection operation dialog state.
|
/// Selection operation dialog state.
|
||||||
pub dialog_selection_value: f32,
|
pub dialog_selection_value: f32,
|
||||||
|
/// Image Size dialog state.
|
||||||
|
pub dialog_image_size_width: u32,
|
||||||
|
pub dialog_image_size_height: u32,
|
||||||
|
pub dialog_image_size_constrain: bool,
|
||||||
|
pub dialog_image_size_original_ratio: f32,
|
||||||
|
/// Canvas Size dialog state.
|
||||||
|
pub dialog_canvas_size_width: u32,
|
||||||
|
pub dialog_canvas_size_height: u32,
|
||||||
|
pub dialog_canvas_size_anchor: (usize, usize),
|
||||||
/// Selected filter.
|
/// Selected filter.
|
||||||
pub selected_filter: Option<FilterType>,
|
pub selected_filter: Option<FilterType>,
|
||||||
/// Filter parameters (JSON) for the currently selected filter.
|
/// Filter parameters (JSON) for the currently selected filter.
|
||||||
@@ -429,6 +441,18 @@ pub enum Message {
|
|||||||
SelectionOpValue(f32),
|
SelectionOpValue(f32),
|
||||||
SelectionOpApply,
|
SelectionOpApply,
|
||||||
|
|
||||||
|
// ── Image Size Dialog ──────────────────────────────
|
||||||
|
DialogImageSizeWidth(u32),
|
||||||
|
DialogImageSizeHeight(u32),
|
||||||
|
DialogImageSizeConstrain(bool),
|
||||||
|
DialogImageSizeApply,
|
||||||
|
|
||||||
|
// ── Canvas Size Dialog ─────────────────────────────
|
||||||
|
DialogCanvasSizeWidth(u32),
|
||||||
|
DialogCanvasSizeHeight(u32),
|
||||||
|
DialogCanvasSizeAnchor(usize, usize),
|
||||||
|
DialogCanvasSizeApply,
|
||||||
|
|
||||||
// ── Selection tools ─────────────────────────────────
|
// ── Selection tools ─────────────────────────────────
|
||||||
SelectionDragStart(f32, f32),
|
SelectionDragStart(f32, f32),
|
||||||
SelectionDragMove(f32, f32),
|
SelectionDragMove(f32, f32),
|
||||||
@@ -706,6 +730,13 @@ impl HcieIcedApp {
|
|||||||
dialog_saturation: 0.0,
|
dialog_saturation: 0.0,
|
||||||
dialog_lightness: 0.0,
|
dialog_lightness: 0.0,
|
||||||
dialog_selection_value: 5.0,
|
dialog_selection_value: 5.0,
|
||||||
|
dialog_image_size_width: 800,
|
||||||
|
dialog_image_size_height: 600,
|
||||||
|
dialog_image_size_constrain: true,
|
||||||
|
dialog_image_size_original_ratio: 800.0 / 600.0,
|
||||||
|
dialog_canvas_size_width: 800,
|
||||||
|
dialog_canvas_size_height: 600,
|
||||||
|
dialog_canvas_size_anchor: (1, 1),
|
||||||
selected_filter: None,
|
selected_filter: None,
|
||||||
filter_params: serde_json::json!({}),
|
filter_params: serde_json::json!({}),
|
||||||
filter_preview_active: false,
|
filter_preview_active: false,
|
||||||
@@ -2187,6 +2218,54 @@ impl HcieIcedApp {
|
|||||||
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Image Size Dialog ────────────────────────
|
||||||
|
Message::DialogImageSizeWidth(w) => {
|
||||||
|
self.dialog_image_size_width = w;
|
||||||
|
if self.dialog_image_size_constrain && self.dialog_image_size_original_ratio > 0.0 {
|
||||||
|
self.dialog_image_size_height = (w as f32 / self.dialog_image_size_original_ratio) as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::DialogImageSizeHeight(h) => {
|
||||||
|
self.dialog_image_size_height = h;
|
||||||
|
if self.dialog_image_size_constrain && self.dialog_image_size_original_ratio > 0.0 {
|
||||||
|
self.dialog_image_size_width = (h as f32 * self.dialog_image_size_original_ratio) as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::DialogImageSizeConstrain(c) => {
|
||||||
|
self.dialog_image_size_constrain = c;
|
||||||
|
}
|
||||||
|
Message::DialogImageSizeApply => {
|
||||||
|
let w = self.dialog_image_size_width.max(1);
|
||||||
|
let h = self.dialog_image_size_height.max(1);
|
||||||
|
self.documents[self.active_doc].engine.resize_canvas(w, h);
|
||||||
|
self.documents[self.active_doc].engine.pre_tile_all_layers();
|
||||||
|
self.refresh_composite_if_needed();
|
||||||
|
self.documents[self.active_doc].full_upload.replace(true);
|
||||||
|
self.active_dialog = ActiveDialog::None;
|
||||||
|
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Canvas Size Dialog ───────────────────────
|
||||||
|
Message::DialogCanvasSizeWidth(w) => {
|
||||||
|
self.dialog_canvas_size_width = w;
|
||||||
|
}
|
||||||
|
Message::DialogCanvasSizeHeight(h) => {
|
||||||
|
self.dialog_canvas_size_height = h;
|
||||||
|
}
|
||||||
|
Message::DialogCanvasSizeAnchor(r, c) => {
|
||||||
|
self.dialog_canvas_size_anchor = (r, c);
|
||||||
|
}
|
||||||
|
Message::DialogCanvasSizeApply => {
|
||||||
|
let new_w = self.dialog_canvas_size_width.max(1);
|
||||||
|
let new_h = self.dialog_canvas_size_height.max(1);
|
||||||
|
self.documents[self.active_doc].engine.resize_canvas(new_w, new_h);
|
||||||
|
self.documents[self.active_doc].engine.pre_tile_all_layers();
|
||||||
|
self.refresh_composite_if_needed();
|
||||||
|
self.documents[self.active_doc].full_upload.replace(true);
|
||||||
|
self.active_dialog = ActiveDialog::None;
|
||||||
|
return Task::perform(async {}, |_| Message::CompositeRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Selection tools ───────────────────────────
|
// ── Selection tools ───────────────────────────
|
||||||
Message::SelectionDragStart(x, y) => {
|
Message::SelectionDragStart(x, y) => {
|
||||||
self.documents[self.active_doc].selection_rect = Some((x, y, x, y));
|
self.documents[self.active_doc].selection_rect = Some((x, y, x, y));
|
||||||
@@ -3288,8 +3367,23 @@ impl HcieIcedApp {
|
|||||||
// (2, 7) = Vectorize Bitmap...
|
// (2, 7) = Vectorize Bitmap...
|
||||||
// (2, 8) = Wavelet Decompose...
|
// (2, 8) = Wavelet Decompose...
|
||||||
// (2, 9) = separator
|
// (2, 9) = separator
|
||||||
// (2, 10) = Canvas Size...
|
(2, 10) => {
|
||||||
// (2, 11) = Image Size...
|
// Canvas Size...
|
||||||
|
let (w, h) = self.documents[self.active_doc].engine.get_canvas_size();
|
||||||
|
self.dialog_canvas_size_width = w;
|
||||||
|
self.dialog_canvas_size_height = h;
|
||||||
|
self.dialog_canvas_size_anchor = (1, 1);
|
||||||
|
self.active_dialog = ActiveDialog::CanvasSize;
|
||||||
|
}
|
||||||
|
(2, 11) => {
|
||||||
|
// Image Size...
|
||||||
|
let (w, h) = self.documents[self.active_doc].engine.get_canvas_size();
|
||||||
|
self.dialog_image_size_width = w;
|
||||||
|
self.dialog_image_size_height = h;
|
||||||
|
self.dialog_image_size_original_ratio = w as f32 / h as f32;
|
||||||
|
self.dialog_image_size_constrain = true;
|
||||||
|
self.active_dialog = ActiveDialog::ImageSize;
|
||||||
|
}
|
||||||
// (2, 12) = Transform (submenu)
|
// (2, 12) = Transform (submenu)
|
||||||
// (2, 13) = Crop
|
// (2, 13) = Crop
|
||||||
// (2, 14) = Trim...
|
// (2, 14) = Trim...
|
||||||
@@ -3731,6 +3825,18 @@ impl HcieIcedApp {
|
|||||||
self.layer_style_offset,
|
self.layer_style_offset,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ActiveDialog::ImageSize => dialogs::image_size::view(
|
||||||
|
self.dialog_image_size_width,
|
||||||
|
self.dialog_image_size_height,
|
||||||
|
self.dialog_image_size_constrain,
|
||||||
|
),
|
||||||
|
ActiveDialog::CanvasSize => dialogs::canvas_size::view(
|
||||||
|
self.dialog_canvas_size_width,
|
||||||
|
self.dialog_canvas_size_height,
|
||||||
|
self.dialog_canvas_size_anchor.0,
|
||||||
|
self.dialog_canvas_size_anchor.1,
|
||||||
|
),
|
||||||
|
ActiveDialog::About => dialogs::about::view(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stack main content with overlays (dialog + menu dropdown + dock profile dialog).
|
// Stack main content with overlays (dialog + menu dropdown + dock profile dialog).
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//! About dialog — displays application name, version, and credits.
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
use iced::widget::{button, column, container, horizontal_rule, text};
|
||||||
|
use iced::{Element, Length};
|
||||||
|
|
||||||
|
/// Build the About dialog.
|
||||||
|
pub fn view() -> Element<'static, Message> {
|
||||||
|
let ok_btn = button(text("OK").size(12))
|
||||||
|
.on_press(Message::DialogClose)
|
||||||
|
.padding([8, 16]);
|
||||||
|
|
||||||
|
let dialog = column![
|
||||||
|
text("HCIE-Rust").size(20).font(iced::Font::MONOSPACE),
|
||||||
|
text("Version 3.05").size(12),
|
||||||
|
horizontal_rule(1),
|
||||||
|
text("A pixel-grade image editor built with Rust.").size(11),
|
||||||
|
text("Powered by ICED and the HCIE engine.").size(11),
|
||||||
|
horizontal_rule(1),
|
||||||
|
text("© 2025 HCIE-Rust Contributors").size(10),
|
||||||
|
ok_btn,
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.padding(20)
|
||||||
|
.width(320)
|
||||||
|
.align_x(iced::Alignment::Center);
|
||||||
|
|
||||||
|
container(dialog)
|
||||||
|
.center_x(Length::Fill)
|
||||||
|
.center_y(Length::Fill)
|
||||||
|
.style(|_theme| iced::widget::container::Style {
|
||||||
|
background: Some(iced::Background::Color(iced::Color::from_rgba(0.0, 0.0, 0.0, 0.5))),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//! Canvas Size dialog — extend or crop the canvas around an anchor point.
|
||||||
|
//!
|
||||||
|
//! Shows width/height inputs and a 9-point anchor grid. The anchor
|
||||||
|
//! determines which corner/edge of the existing content stays fixed
|
||||||
|
//! when the canvas is resized.
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
use iced::widget::{button, column, container, horizontal_rule, row, text, text_input};
|
||||||
|
use iced::{Element, Length};
|
||||||
|
|
||||||
|
/// Anchor position labels for the 9-point grid (row-major).
|
||||||
|
const ANCHOR_LABELS: [[&str; 3]; 3] = [
|
||||||
|
["TL", "TC", "TR"],
|
||||||
|
["ML", "MC", "MR"],
|
||||||
|
["BL", "BC", "BR"],
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Build the Canvas Size dialog.
|
||||||
|
pub fn view(
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
anchor_row: usize,
|
||||||
|
anchor_col: usize,
|
||||||
|
) -> Element<'static, Message> {
|
||||||
|
let width_str = width.to_string();
|
||||||
|
let height_str = height.to_string();
|
||||||
|
|
||||||
|
let width_input = text_input("Width", &width_str)
|
||||||
|
.on_input(|s| {
|
||||||
|
let v: u32 = s.parse().unwrap_or(0);
|
||||||
|
Message::DialogCanvasSizeWidth(v)
|
||||||
|
})
|
||||||
|
.width(120);
|
||||||
|
|
||||||
|
let height_input = text_input("Height", &height_str)
|
||||||
|
.on_input(|s| {
|
||||||
|
let v: u32 = s.parse().unwrap_or(0);
|
||||||
|
Message::DialogCanvasSizeHeight(v)
|
||||||
|
})
|
||||||
|
.width(120);
|
||||||
|
|
||||||
|
// 3×3 anchor grid
|
||||||
|
let mut anchor_grid = column![].spacing(2);
|
||||||
|
for r in 0..3 {
|
||||||
|
let mut row_widgets = row![].spacing(2);
|
||||||
|
for c in 0..3 {
|
||||||
|
let is_selected = r == anchor_row && c == anchor_col;
|
||||||
|
let label = ANCHOR_LABELS[r][c];
|
||||||
|
let btn = button(text(label).size(10))
|
||||||
|
.on_press(Message::DialogCanvasSizeAnchor(r, c))
|
||||||
|
.padding([4, 6]);
|
||||||
|
let btn = if is_selected {
|
||||||
|
btn.style(iced::widget::button::primary)
|
||||||
|
} else {
|
||||||
|
btn.style(iced::widget::button::secondary)
|
||||||
|
};
|
||||||
|
row_widgets = row_widgets.push(btn);
|
||||||
|
}
|
||||||
|
anchor_grid = anchor_grid.push(row_widgets);
|
||||||
|
}
|
||||||
|
|
||||||
|
let apply_btn = button(text("Apply").size(12))
|
||||||
|
.on_press(Message::DialogCanvasSizeApply)
|
||||||
|
.padding([8, 16]);
|
||||||
|
|
||||||
|
let cancel_btn = button(text("Cancel").size(12))
|
||||||
|
.on_press(Message::DialogClose)
|
||||||
|
.padding([8, 16]);
|
||||||
|
|
||||||
|
let dialog = column![
|
||||||
|
text("Canvas Size").size(16).font(iced::Font::MONOSPACE),
|
||||||
|
horizontal_rule(1),
|
||||||
|
row![
|
||||||
|
column![text("Width:").size(11), width_input].spacing(4),
|
||||||
|
text("×").size(12),
|
||||||
|
column![text("Height:").size(11), height_input].spacing(4),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Alignment::Center),
|
||||||
|
text("Anchor:").size(11),
|
||||||
|
anchor_grid,
|
||||||
|
horizontal_rule(1),
|
||||||
|
row![cancel_btn, apply_btn].spacing(12),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.padding(16)
|
||||||
|
.width(360);
|
||||||
|
|
||||||
|
container(dialog)
|
||||||
|
.center_x(Length::Fill)
|
||||||
|
.center_y(Length::Fill)
|
||||||
|
.style(|_theme| iced::widget::container::Style {
|
||||||
|
background: Some(iced::Background::Color(iced::Color::from_rgba(0.0, 0.0, 0.0, 0.5))),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
//! Image Size dialog — resize the image (all layers).
|
||||||
|
//!
|
||||||
|
//! Provides width/height inputs with a constrain-proportions checkbox.
|
||||||
|
//! When the checkbox is checked, changing one dimension automatically
|
||||||
|
//! updates the other to maintain the current aspect ratio.
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
use iced::widget::{button, checkbox, column, container, horizontal_rule, row, text, text_input};
|
||||||
|
use iced::{Element, Length};
|
||||||
|
|
||||||
|
/// Build the Image Size dialog.
|
||||||
|
pub fn view(
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
constrain: bool,
|
||||||
|
) -> Element<'static, Message> {
|
||||||
|
let width_str = width.to_string();
|
||||||
|
let height_str = height.to_string();
|
||||||
|
|
||||||
|
let width_input = text_input("Width", &width_str)
|
||||||
|
.on_input(|s| {
|
||||||
|
let v: u32 = s.parse().unwrap_or(0);
|
||||||
|
Message::DialogImageSizeWidth(v)
|
||||||
|
})
|
||||||
|
.width(120);
|
||||||
|
|
||||||
|
let height_input = text_input("Height", &height_str)
|
||||||
|
.on_input(|s| {
|
||||||
|
let v: u32 = s.parse().unwrap_or(0);
|
||||||
|
Message::DialogImageSizeHeight(v)
|
||||||
|
})
|
||||||
|
.width(120);
|
||||||
|
|
||||||
|
let constrain_check = checkbox("Constrain proportions", constrain)
|
||||||
|
.on_toggle(|v| Message::DialogImageSizeConstrain(v));
|
||||||
|
|
||||||
|
let apply_btn = button(text("Apply").size(12))
|
||||||
|
.on_press(Message::DialogImageSizeApply)
|
||||||
|
.padding([8, 16]);
|
||||||
|
|
||||||
|
let cancel_btn = button(text("Cancel").size(12))
|
||||||
|
.on_press(Message::DialogClose)
|
||||||
|
.padding([8, 16]);
|
||||||
|
|
||||||
|
let dialog = column![
|
||||||
|
text("Image Size").size(16).font(iced::Font::MONOSPACE),
|
||||||
|
horizontal_rule(1),
|
||||||
|
row![
|
||||||
|
column![text("Width:").size(11), width_input].spacing(4),
|
||||||
|
text("×").size(12),
|
||||||
|
column![text("Height:").size(11), height_input].spacing(4),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Alignment::Center),
|
||||||
|
constrain_check,
|
||||||
|
horizontal_rule(1),
|
||||||
|
row![cancel_btn, apply_btn].spacing(12),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.padding(16)
|
||||||
|
.width(360);
|
||||||
|
|
||||||
|
container(dialog)
|
||||||
|
.center_x(Length::Fill)
|
||||||
|
.center_y(Length::Fill)
|
||||||
|
.style(|_theme| iced::widget::container::Style {
|
||||||
|
background: Some(iced::Background::Color(iced::Color::from_rgba(0.0, 0.0, 0.0, 0.5))),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
@@ -4,3 +4,6 @@ pub mod new_image;
|
|||||||
pub mod adjustments;
|
pub mod adjustments;
|
||||||
pub mod confirm;
|
pub mod confirm;
|
||||||
pub mod dock_profile;
|
pub mod dock_profile;
|
||||||
|
pub mod image_size;
|
||||||
|
pub mod canvas_size;
|
||||||
|
pub mod about;
|
||||||
|
|||||||
Reference in New Issue
Block a user