feat(iced): make panels movable with Properties on right side

This commit is contained in:
2026-07-16 13:15:00 +03:00
parent edae18fca0
commit 026e268a10
3 changed files with 85 additions and 29 deletions
+12 -12
View File
@@ -4972,17 +4972,17 @@ impl HcieIcedApp {
// ══════════════════════════════════════════ // ══════════════════════════════════════════
// Window menu (7) — Photopea-style // Window menu (7) — Photopea-style
// ══════════════════════════════════════════ // ══════════════════════════════════════════
(7, 0) => self.dock.reopen_pane(PaneType::Layers), (7, 0) => self.dock.toggle_pane(PaneType::Layers),
(7, 1) => self.dock.reopen_pane(PaneType::History), (7, 1) => self.dock.toggle_pane(PaneType::History),
(7, 2) => self.dock.reopen_pane(PaneType::Brushes), (7, 2) => self.dock.toggle_pane(PaneType::Brushes),
(7, 3) => self.dock.reopen_pane(PaneType::Filters), (7, 3) => self.dock.toggle_pane(PaneType::Filters),
(7, 4) => self.dock.reopen_pane(PaneType::ColorPicker), (7, 4) => self.dock.toggle_pane(PaneType::ColorPicker),
(7, 5) => self.dock.reopen_pane(PaneType::Properties), (7, 5) => self.dock.toggle_pane(PaneType::Properties),
(7, 6) => self.dock.reopen_pane(PaneType::AiChat), (7, 6) => self.dock.toggle_pane(PaneType::AiChat),
(7, 7) => self.dock.reopen_pane(PaneType::Geometry), (7, 7) => self.dock.toggle_pane(PaneType::Geometry),
(7, 8) => self.dock.reopen_pane(PaneType::Script), (7, 8) => self.dock.toggle_pane(PaneType::Script),
(7, 9) => self.dock.reopen_pane(PaneType::LayerDetails), (7, 9) => self.dock.toggle_pane(PaneType::LayerDetails),
(7, 10) => self.dock.reopen_pane(PaneType::AiScript), (7, 10) => self.dock.toggle_pane(PaneType::AiScript),
// (7, 11) = separator // (7, 11) = separator
(7, 12) => return Task::perform(async {}, |_| Message::ThemeChanged(crate::theme::ThemePreset::Photopea)), (7, 12) => return Task::perform(async {}, |_| Message::ThemeChanged(crate::theme::ThemePreset::Photopea)),
(7, 13) => return Task::perform(async {}, |_| Message::ThemeChanged(crate::theme::ThemePreset::Photoshop)), (7, 13) => return Task::perform(async {}, |_| Message::ThemeChanged(crate::theme::ThemePreset::Photoshop)),
@@ -5391,7 +5391,7 @@ impl HcieIcedApp {
&self.dock_profile_new_name, &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); stack = stack.push(menu_overlay);
} }
if let Some((cx, cy)) = self.canvas_context_menu { if let Some((cx, cy)) = self.canvas_context_menu {
@@ -6,11 +6,11 @@
//! //!
//! ```text //! ```text
//! +--------+------------+-----------+---------+ //! +--------+------------+-----------+---------+
//! | | Brushes | | Layers | //! | | | | Layers |
//! | Tools | & Tips | |---------| //! | Brushes| | |---------|
//! | (36px) |------------| Canvas | Properties //! |--------| Canvas | ColorBox |Properties|
//! | | Filters | |---------| //! | Filters| |-----------|---------|
//! | | | ColorBox | History | //! | | | History | |
//! +--------+------------+-----------+---------+ //! +--------+------------+-----------+---------+
//! ``` //! ```
@@ -89,11 +89,12 @@ impl DockState {
/// Dock layout (without toolbox), left → right: /// Dock layout (without toolbox), left → right:
/// ```text /// ```text
/// +------------+-----------+-----------+-----------+ /// +------------+-----------+-----------+-----------+
/// | Brushes | | ColorBox | Layers | /// | Brushes | | Layers | |
/// |------------| Canvas |-----------|-----------| /// |------------| Canvas |-----------|Properties |
/// | Filters | | History | Properties| /// | Filters | | ColorBox |-----------|
/// | | |-----------| History |
/// +------------+-----------+-----------+-----------+ /// +------------+-----------+-----------+-----------+
/// ~17% ~66% ~8.5% ~8.5% /// ~14% ~66% ~10% ~10%
/// ``` /// ```
/// ///
/// Axis semantics in iced PaneGrid: /// Axis semantics in iced PaneGrid:
@@ -108,16 +109,21 @@ impl DockState {
b: Box::new(Configuration::Pane(PaneType::Filters)), 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 { let right_col1 = Configuration::Split {
axis: iced::widget::pane_grid::Axis::Horizontal, axis: iced::widget::pane_grid::Axis::Horizontal,
ratio: 0.4, ratio: 0.5,
a: Box::new(Configuration::Pane(PaneType::ColorPicker)), a: Box::new(Configuration::Pane(PaneType::Layers)),
b: Box::new(Configuration::Pane(PaneType::History)), b: Box::new(Configuration::Pane(PaneType::ColorPicker)),
}; };
// Right column 2: Layers (full column) // Right column 2: Properties (top) / History (bottom) — horizontal split
let right_col2 = Configuration::Pane(PaneType::Layers); 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 // Right side: right_col1 (left) / right_col2 (right) — vertical split
let right_side = Configuration::Split { let right_side = Configuration::Split {
@@ -128,10 +134,10 @@ impl DockState {
}; };
// Center + right: Canvas (left) / right_side (right) — vertical split // 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 { let center_right = Configuration::Split {
axis: iced::widget::pane_grid::Axis::Vertical, axis: iced::widget::pane_grid::Axis::Vertical,
ratio: 0.65, ratio: 0.66,
a: Box::new(Configuration::Pane(PaneType::Canvas)), a: Box::new(Configuration::Pane(PaneType::Canvas)),
b: Box::new(right_side), 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);
}
}
} }
@@ -13,6 +13,7 @@
//! - Sub-menus: indicated by ">" arrow on the right //! - Sub-menus: indicated by ">" arrow on the right
use crate::app::Message; use crate::app::Message;
use crate::dock::state::{DockState, PaneType};
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use hcie_engine_api::Tool; use hcie_engine_api::Tool;
use iced::widget::{column, container, horizontal_rule, mouse_area, row, text}; use iced::widget::{column, container, horizontal_rule, mouse_area, row, text};
@@ -333,6 +334,7 @@ pub fn dropdown_overlay(
active_menu: Option<usize>, active_menu: Option<usize>,
recent_files: &[crate::app::RecentFileEntry], recent_files: &[crate::app::RecentFileEntry],
colors: ThemeColors, colors: ThemeColors,
dock: Option<&DockState>,
) -> Option<Element<'static, Message>> { ) -> Option<Element<'static, Message>> {
let menu_idx = active_menu?; let menu_idx = active_menu?;
let menus = all_menus(recent_files); let menus = all_menus(recent_files);
@@ -418,9 +420,35 @@ pub fn dropdown_overlay(
let sht = item.shortcut.clone(); let sht = item.shortcut.clone();
let has_sub = item.has_submenu; 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 // 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 label_row: Element<'static, Message> = {
let mut row_items = vec![]; 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()); row_items.push(text(lbl).size(13).width(Length::Fill).into());
if !sht.is_empty() { 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()); row_items.push(text(sht).size(12).color(iced::Color::from_rgba(0.4, 0.4, 0.4, 1.0)).into());