feat(iced): add missing dialogs (Image Size, Canvas Size, About)

This commit is contained in:
2026-07-15 17:54:36 +03:00
parent 45f222b4a0
commit 13f6a62085
5 changed files with 321 additions and 2 deletions
+108 -2
View File
@@ -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;