feat(iced): add missing dialogs (Image Size, Canvas Size, About)
This commit is contained in:
@@ -70,6 +70,9 @@ pub enum ActiveDialog {
|
||||
CloseConfirm,
|
||||
SelectionOp(&'static str),
|
||||
LayerStyleDialog,
|
||||
ImageSize,
|
||||
CanvasSize,
|
||||
About,
|
||||
}
|
||||
|
||||
/// Top-level application state.
|
||||
@@ -111,6 +114,15 @@ pub struct HcieIcedApp {
|
||||
pub dialog_lightness: f32,
|
||||
/// Selection operation dialog state.
|
||||
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.
|
||||
pub selected_filter: Option<FilterType>,
|
||||
/// Filter parameters (JSON) for the currently selected filter.
|
||||
@@ -429,6 +441,18 @@ pub enum Message {
|
||||
SelectionOpValue(f32),
|
||||
SelectionOpApply,
|
||||
|
||||
// ── Image Size Dialog ──────────────────────────────
|
||||
DialogImageSizeWidth(u32),
|
||||
DialogImageSizeHeight(u32),
|
||||
DialogImageSizeConstrain(bool),
|
||||
DialogImageSizeApply,
|
||||
|
||||
// ── Canvas Size Dialog ─────────────────────────────
|
||||
DialogCanvasSizeWidth(u32),
|
||||
DialogCanvasSizeHeight(u32),
|
||||
DialogCanvasSizeAnchor(usize, usize),
|
||||
DialogCanvasSizeApply,
|
||||
|
||||
// ── Selection tools ─────────────────────────────────
|
||||
SelectionDragStart(f32, f32),
|
||||
SelectionDragMove(f32, f32),
|
||||
@@ -706,6 +730,13 @@ impl HcieIcedApp {
|
||||
dialog_saturation: 0.0,
|
||||
dialog_lightness: 0.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,
|
||||
filter_params: serde_json::json!({}),
|
||||
filter_preview_active: false,
|
||||
@@ -2187,6 +2218,54 @@ impl HcieIcedApp {
|
||||
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 ───────────────────────────
|
||||
Message::SelectionDragStart(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, 8) = Wavelet Decompose...
|
||||
// (2, 9) = separator
|
||||
// (2, 10) = Canvas Size...
|
||||
// (2, 11) = Image Size...
|
||||
(2, 10) => {
|
||||
// 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, 13) = Crop
|
||||
// (2, 14) = Trim...
|
||||
@@ -3731,6 +3825,18 @@ impl HcieIcedApp {
|
||||
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).
|
||||
|
||||
@@ -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 confirm;
|
||||
pub mod dock_profile;
|
||||
pub mod image_size;
|
||||
pub mod canvas_size;
|
||||
pub mod about;
|
||||
|
||||
Reference in New Issue
Block a user