From 026e268a10d97abb59c2f00358ed0fb7746cb939 Mon Sep 17 00:00:00 2001 From: Halit Can Date: Thu, 16 Jul 2026 13:15:00 +0300 Subject: [PATCH] feat(iced): make panels movable with Properties on right side --- hcie-iced-app/crates/hcie-iced-gui/src/app.rs | 24 +++---- .../crates/hcie-iced-gui/src/dock/state.rs | 62 ++++++++++++++----- .../crates/hcie-iced-gui/src/panels/menus.rs | 28 +++++++++ 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/app.rs b/hcie-iced-app/crates/hcie-iced-gui/src/app.rs index e6f8c6a..b1172f8 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/app.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/app.rs @@ -4972,17 +4972,17 @@ impl HcieIcedApp { // ══════════════════════════════════════════ // Window menu (7) — Photopea-style // ══════════════════════════════════════════ - (7, 0) => self.dock.reopen_pane(PaneType::Layers), - (7, 1) => self.dock.reopen_pane(PaneType::History), - (7, 2) => self.dock.reopen_pane(PaneType::Brushes), - (7, 3) => self.dock.reopen_pane(PaneType::Filters), - (7, 4) => self.dock.reopen_pane(PaneType::ColorPicker), - (7, 5) => self.dock.reopen_pane(PaneType::Properties), - (7, 6) => self.dock.reopen_pane(PaneType::AiChat), - (7, 7) => self.dock.reopen_pane(PaneType::Geometry), - (7, 8) => self.dock.reopen_pane(PaneType::Script), - (7, 9) => self.dock.reopen_pane(PaneType::LayerDetails), - (7, 10) => self.dock.reopen_pane(PaneType::AiScript), + (7, 0) => self.dock.toggle_pane(PaneType::Layers), + (7, 1) => self.dock.toggle_pane(PaneType::History), + (7, 2) => self.dock.toggle_pane(PaneType::Brushes), + (7, 3) => self.dock.toggle_pane(PaneType::Filters), + (7, 4) => self.dock.toggle_pane(PaneType::ColorPicker), + (7, 5) => self.dock.toggle_pane(PaneType::Properties), + (7, 6) => self.dock.toggle_pane(PaneType::AiChat), + (7, 7) => self.dock.toggle_pane(PaneType::Geometry), + (7, 8) => self.dock.toggle_pane(PaneType::Script), + (7, 9) => self.dock.toggle_pane(PaneType::LayerDetails), + (7, 10) => self.dock.toggle_pane(PaneType::AiScript), // (7, 11) = separator (7, 12) => return Task::perform(async {}, |_| Message::ThemeChanged(crate::theme::ThemePreset::Photopea)), (7, 13) => return Task::perform(async {}, |_| Message::ThemeChanged(crate::theme::ThemePreset::Photoshop)), @@ -5391,7 +5391,7 @@ impl HcieIcedApp { &self.dock_profile_new_name, )); } - if let Some(menu_overlay) = panels::menus::dropdown_overlay(self.active_menu, &self.recent_files, self.theme_state.colors()) { + if let Some(menu_overlay) = panels::menus::dropdown_overlay(self.active_menu, &self.recent_files, self.theme_state.colors(), Some(&self.dock)) { stack = stack.push(menu_overlay); } if let Some((cx, cy)) = self.canvas_context_menu { diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs index 007959a..f676e70 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs @@ -6,11 +6,11 @@ //! //! ```text //! +--------+------------+-----------+---------+ -//! | | Brushes | | Layers | -//! | Tools | & Tips | |---------| -//! | (36px) |------------| Canvas | Properties -//! | | Filters | |---------| -//! | | | ColorBox | History | +//! | | | | Layers | +//! | Brushes| | |---------| +//! |--------| Canvas | ColorBox |Properties| +//! | Filters| |-----------|---------| +//! | | | History | | //! +--------+------------+-----------+---------+ //! ``` @@ -89,11 +89,12 @@ impl DockState { /// Dock layout (without toolbox), left → right: /// ```text /// +------------+-----------+-----------+-----------+ - /// | Brushes | | ColorBox | Layers | - /// |------------| Canvas |-----------|-----------| - /// | Filters | | History | Properties| + /// | Brushes | | Layers | | + /// |------------| Canvas |-----------|Properties | + /// | Filters | | ColorBox |-----------| + /// | | |-----------| History | /// +------------+-----------+-----------+-----------+ - /// ~17% ~66% ~8.5% ~8.5% + /// ~14% ~66% ~10% ~10% /// ``` /// /// Axis semantics in iced PaneGrid: @@ -108,16 +109,21 @@ impl DockState { b: Box::new(Configuration::Pane(PaneType::Filters)), }; - // Right column 1: ColorBox (top) / History (bottom) + // Right column 1: Layers (top) / ColorBox (bottom) — horizontal split let right_col1 = Configuration::Split { axis: iced::widget::pane_grid::Axis::Horizontal, - ratio: 0.4, - a: Box::new(Configuration::Pane(PaneType::ColorPicker)), - b: Box::new(Configuration::Pane(PaneType::History)), + ratio: 0.5, + a: Box::new(Configuration::Pane(PaneType::Layers)), + b: Box::new(Configuration::Pane(PaneType::ColorPicker)), }; - // Right column 2: Layers (full column) - let right_col2 = Configuration::Pane(PaneType::Layers); + // Right column 2: Properties (top) / History (bottom) — horizontal split + let right_col2 = Configuration::Split { + axis: iced::widget::pane_grid::Axis::Horizontal, + ratio: 0.5, + a: Box::new(Configuration::Pane(PaneType::Properties)), + b: Box::new(Configuration::Pane(PaneType::History)), + }; // Right side: right_col1 (left) / right_col2 (right) — vertical split let right_side = Configuration::Split { @@ -128,10 +134,10 @@ impl DockState { }; // Center + right: Canvas (left) / right_side (right) — vertical split - // Canvas gets ~65% — right panels need room for layers + styles + // Canvas gets ~66% — right panels need room for layers + properties let center_right = Configuration::Split { axis: iced::widget::pane_grid::Axis::Vertical, - ratio: 0.65, + ratio: 0.66, a: Box::new(Configuration::Pane(PaneType::Canvas)), b: Box::new(right_side), }; @@ -238,4 +244,26 @@ impl DockState { } } } + + /// Check if a pane type is currently open in the dock. + pub fn is_pane_open(&self, pane_type: PaneType) -> bool { + self.pane_grid.iter().any(|(_, t)| *t == pane_type) + } + + /// Toggle a pane: close it if open, reopen it if closed. + pub fn toggle_pane(&mut self, pane_type: PaneType) { + let is_open = self.pane_grid.iter().any(|(_, t)| *t == pane_type); + let pane = self.pane_grid.iter().find(|(_, t)| **t == pane_type).map(|(p, _)| *p); + + if is_open { + // Don't close the canvas pane + if pane_type != PaneType::Canvas { + if let Some(pane) = pane { + self.close_pane(pane); + } + } + } else { + self.reopen_pane(pane_type); + } + } } diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs index 3065345..56878dc 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs @@ -13,6 +13,7 @@ //! - Sub-menus: indicated by ">" arrow on the right use crate::app::Message; +use crate::dock::state::{DockState, PaneType}; use crate::theme::ThemeColors; use hcie_engine_api::Tool; use iced::widget::{column, container, horizontal_rule, mouse_area, row, text}; @@ -333,6 +334,7 @@ pub fn dropdown_overlay( active_menu: Option, recent_files: &[crate::app::RecentFileEntry], colors: ThemeColors, + dock: Option<&DockState>, ) -> Option> { let menu_idx = active_menu?; let menus = all_menus(recent_files); @@ -418,9 +420,35 @@ pub fn dropdown_overlay( let sht = item.shortcut.clone(); let has_sub = item.has_submenu; + // Check if this is a Window menu item that should show a checkmark + let is_window_panel = menu_idx == 7 && item_idx <= 10; + let is_checked = if is_window_panel { + let pane_type = match item_idx { + 0 => Some(PaneType::Layers), + 1 => Some(PaneType::History), + 2 => Some(PaneType::Brushes), + 3 => Some(PaneType::Filters), + 4 => Some(PaneType::ColorPicker), + 5 => Some(PaneType::Properties), + 6 => Some(PaneType::AiChat), + 7 => Some(PaneType::Geometry), + 8 => Some(PaneType::Script), + 9 => Some(PaneType::LayerDetails), + 10 => Some(PaneType::AiScript), + _ => None, + }; + pane_type.and_then(|pt| dock.map(|d| d.is_pane_open(pt))).unwrap_or(false) + } else { + false + }; + // Photopea-style: label left, shortcut right in gray, submenu arrow ">" if needed + // Add checkmark prefix for open panels in Window menu let label_row: Element<'static, Message> = { let mut row_items = vec![]; + if is_checked { + row_items.push(text("✓ ").size(13).color(iced::Color::from_rgba(0.2, 0.6, 0.2, 1.0)).into()); + } row_items.push(text(lbl).size(13).width(Length::Fill).into()); if !sht.is_empty() { row_items.push(text(sht).size(12).color(iced::Color::from_rgba(0.4, 0.4, 0.4, 1.0)).into());