2026-07-11 18:13:03 +03:00
|
|
|
|
//! Dock view — renders the PaneGrid with panel content.
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! Each pane type maps to its corresponding panel view function.
|
|
|
|
|
|
//! The canvas pane renders the main document viewport with a document tab bar.
|
|
|
|
|
|
//! Utility panes render their respective panel content.
|
|
|
|
|
|
//! All panels receive ThemeColors for consistent styling.
|
|
|
|
|
|
|
|
|
|
|
|
use crate::app::Message;
|
|
|
|
|
|
use crate::dock::state::{DockState, PaneType};
|
|
|
|
|
|
use crate::panels::styles;
|
2026-07-12 02:05:31 +03:00
|
|
|
|
use iced::widget::svg;
|
2026-07-11 18:13:03 +03:00
|
|
|
|
use crate::theme::ThemeColors;
|
|
|
|
|
|
use iced::widget::pane_grid::{Content, TitleBar};
|
|
|
|
|
|
use iced::widget::{button, column, container, pane_grid, row, text};
|
|
|
|
|
|
use iced::{Element, Length};
|
|
|
|
|
|
|
|
|
|
|
|
/// Build the dock view element.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Renders a PaneGrid where each pane displays the appropriate panel content
|
|
|
|
|
|
/// based on its PaneType. Title bars show the pane name with close/maximize buttons.
|
|
|
|
|
|
/// The canvas pane includes a document tab bar for multi-document support.
|
|
|
|
|
|
pub fn dock_view<'a>(
|
|
|
|
|
|
dock: &'a DockState,
|
|
|
|
|
|
app: &'a crate::app::HcieIcedApp,
|
|
|
|
|
|
) -> Element<'a, Message> {
|
|
|
|
|
|
let colors = app.theme_state.colors();
|
|
|
|
|
|
|
|
|
|
|
|
pane_grid::PaneGrid::new(&dock.pane_grid, |pane, pane_type, _is_maximized| {
|
2026-07-12 02:05:31 +03:00
|
|
|
|
// Canvas pane has no title bar (the document tab bar serves as its header).
|
|
|
|
|
|
// All other panes get a Photoshop-style title bar with close/maximize buttons.
|
|
|
|
|
|
let title: Option<TitleBar<'a, Message>> = if *pane_type == PaneType::Canvas {
|
|
|
|
|
|
None
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Some(make_title_bar(pane, *pane_type, colors))
|
|
|
|
|
|
};
|
2026-07-11 18:13:03 +03:00
|
|
|
|
|
|
|
|
|
|
let body: Element<'a, Message> = match pane_type {
|
|
|
|
|
|
PaneType::Canvas => {
|
2026-07-12 02:05:31 +03:00
|
|
|
|
// Canvas pane includes document tab bar as its header
|
2026-07-11 18:13:03 +03:00
|
|
|
|
let doc_tab_bar = document_tab_bar(app, colors);
|
|
|
|
|
|
let canvas = {
|
|
|
|
|
|
let doc = &app.documents[app.active_doc];
|
2026-07-15 19:16:26 +03:00
|
|
|
|
let pressure = app.tablet_state.lock().map(|s| s.current_pressure()).unwrap_or(1.0);
|
2026-07-16 12:16:12 +03:00
|
|
|
|
let ts = &app.settings.tool_settings;
|
|
|
|
|
|
crate::canvas::view(doc, &app.tool_state, app.marching_ants_offset, pressure, ts.vector_points, ts.vector_sides, ts.vector_radius)
|
2026-07-11 18:13:03 +03:00
|
|
|
|
};
|
|
|
|
|
|
column![doc_tab_bar, canvas]
|
|
|
|
|
|
.width(Length::Fill)
|
|
|
|
|
|
.height(Length::Fill)
|
|
|
|
|
|
.into()
|
|
|
|
|
|
}
|
2026-07-12 02:05:31 +03:00
|
|
|
|
// Tools is no longer in the dock — toolbox is a fixed strip on the left
|
2026-07-11 18:13:03 +03:00
|
|
|
|
PaneType::Layers => {
|
|
|
|
|
|
let doc = &app.documents[app.active_doc];
|
2026-07-13 06:40:14 +03:00
|
|
|
|
crate::panels::layers::view(&doc.cached_layers, doc.engine.active_layer_id(), &doc.engine, colors)
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
PaneType::History => {
|
|
|
|
|
|
let doc = &app.documents[app.active_doc];
|
|
|
|
|
|
crate::panels::history::view(&doc.cached_history, doc.history_current, colors)
|
|
|
|
|
|
}
|
|
|
|
|
|
PaneType::Brushes => {
|
|
|
|
|
|
crate::panels::brushes::view(
|
2026-07-12 02:05:31 +03:00
|
|
|
|
app.tool_state.brush_style,
|
2026-07-11 18:13:03 +03:00
|
|
|
|
app.tool_state.brush_size,
|
|
|
|
|
|
app.tool_state.brush_opacity,
|
|
|
|
|
|
app.tool_state.brush_hardness,
|
|
|
|
|
|
colors,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
PaneType::Filters => {
|
2026-07-15 16:17:43 +03:00
|
|
|
|
crate::panels::filters::view(app.selected_filter, &app.filter_params, app.filter_preview_active, colors)
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
PaneType::ColorPicker => {
|
2026-07-13 06:40:14 +03:00
|
|
|
|
crate::color_picker::view(&app.fg_color, &app.bg_color, &app.recent_colors, app.color_tab)
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
PaneType::Properties => {
|
2026-07-16 11:14:01 +03:00
|
|
|
|
let doc = &app.documents[app.active_doc];
|
|
|
|
|
|
let active_id = doc.engine.active_layer_id();
|
|
|
|
|
|
let layer_info = doc.cached_layers.iter().find(|l| l.id == active_id);
|
|
|
|
|
|
let layer_name = layer_info.map(|l| l.name.as_str()).unwrap_or("—");
|
|
|
|
|
|
let layer_opacity = layer_info.map(|l| l.opacity).unwrap_or(1.0);
|
|
|
|
|
|
let layer_visible = layer_info.map(|l| l.visible).unwrap_or(true);
|
|
|
|
|
|
let layer_blend_mode = layer_info.map(|l| l.blend_mode).unwrap_or(hcie_engine_api::BlendMode::Normal);
|
|
|
|
|
|
let layer_type = layer_info.map(|l| l.layer_type).unwrap_or(hcie_engine_api::LayerType::Raster);
|
|
|
|
|
|
let layer_width = layer_info.map(|l| l.width).unwrap_or(0);
|
|
|
|
|
|
let layer_height = layer_info.map(|l| l.height).unwrap_or(0);
|
|
|
|
|
|
let vector_shapes = doc.engine.active_vector_shapes().unwrap_or_default();
|
|
|
|
|
|
let ts2 = app.settings.tool_settings.clone();
|
2026-07-11 18:13:03 +03:00
|
|
|
|
crate::panels::properties::view(
|
|
|
|
|
|
&app.tool_state.active_tool,
|
|
|
|
|
|
app.tool_state.brush_size,
|
|
|
|
|
|
app.tool_state.brush_opacity,
|
|
|
|
|
|
app.tool_state.brush_hardness,
|
2026-07-16 11:14:01 +03:00
|
|
|
|
doc.selection_rect,
|
|
|
|
|
|
doc.vector_draw,
|
2026-07-11 18:13:03 +03:00
|
|
|
|
colors,
|
2026-07-16 11:14:01 +03:00
|
|
|
|
layer_name,
|
|
|
|
|
|
layer_opacity,
|
|
|
|
|
|
layer_visible,
|
|
|
|
|
|
layer_blend_mode,
|
|
|
|
|
|
layer_type,
|
|
|
|
|
|
layer_width,
|
|
|
|
|
|
layer_height,
|
|
|
|
|
|
doc.selected_vector_shape,
|
|
|
|
|
|
&vector_shapes,
|
|
|
|
|
|
ts2.vector_stroke_width,
|
|
|
|
|
|
ts2.vector_opacity,
|
|
|
|
|
|
ts2.vector_fill,
|
|
|
|
|
|
ts2.vector_radius,
|
|
|
|
|
|
ts2.vector_points,
|
|
|
|
|
|
ts2.vector_sides,
|
|
|
|
|
|
active_id,
|
2026-07-11 18:13:03 +03:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
PaneType::Script => {
|
2026-07-15 16:54:42 +03:00
|
|
|
|
crate::panels::script_panel::view(
|
|
|
|
|
|
&app.script_content,
|
|
|
|
|
|
app.script_error.as_deref(),
|
|
|
|
|
|
app.script_error_line,
|
|
|
|
|
|
app.script_is_running,
|
|
|
|
|
|
colors,
|
|
|
|
|
|
)
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
PaneType::AiChat => {
|
2026-07-15 16:31:25 +03:00
|
|
|
|
crate::panels::ai_chat_panel::view(&app.ai_chat_state, colors)
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|
2026-07-15 17:02:06 +03:00
|
|
|
|
PaneType::AiScript => {
|
|
|
|
|
|
crate::panels::ai_script_panel::view(app)
|
|
|
|
|
|
}
|
2026-07-11 18:13:03 +03:00
|
|
|
|
PaneType::LayerDetails => {
|
|
|
|
|
|
let doc = &app.documents[app.active_doc];
|
|
|
|
|
|
crate::panels::layer_details::view(&doc.cached_layers, doc.engine.active_layer_id(), colors)
|
|
|
|
|
|
}
|
|
|
|
|
|
PaneType::Geometry => {
|
2026-07-16 04:51:30 +03:00
|
|
|
|
let shapes = app.documents[app.active_doc].engine.active_vector_shapes().unwrap_or_default();
|
2026-07-16 05:26:54 +03:00
|
|
|
|
crate::panels::geometry::view(
|
|
|
|
|
|
shapes,
|
|
|
|
|
|
colors,
|
|
|
|
|
|
app.documents[app.active_doc].selected_vector_shape,
|
|
|
|
|
|
app.bool_shape_a,
|
|
|
|
|
|
app.bool_shape_b,
|
|
|
|
|
|
)
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|
2026-07-14 14:14:10 +03:00
|
|
|
|
PaneType::ToolSettings => {
|
2026-07-15 19:16:26 +03:00
|
|
|
|
let fg = app.fg_color;
|
|
|
|
|
|
let draft = app.documents[app.active_doc].text_draft.as_ref();
|
|
|
|
|
|
crate::panels::tool_settings::view(&app.tool_state, &app.settings, fg, draft, colors)
|
2026-07-14 14:14:10 +03:00
|
|
|
|
}
|
2026-07-11 18:13:03 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
let mut content = Content::new(body);
|
|
|
|
|
|
|
|
|
|
|
|
// Apply per-pane background style. Canvas pane gets a dark workspace
|
|
|
|
|
|
// background (no border); all other panes get panel background + border
|
|
|
|
|
|
// so they are visually separated from neighbors.
|
|
|
|
|
|
if *pane_type == PaneType::Canvas {
|
|
|
|
|
|
content = content.style(move |_theme| styles::canvas_pane_background(colors));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
content = content.style(move |_theme| styles::pane_background(colors));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(title_bar) = title {
|
|
|
|
|
|
content = content.title_bar(title_bar);
|
|
|
|
|
|
}
|
|
|
|
|
|
content
|
2026-07-11 18:13:03 +03:00
|
|
|
|
})
|
|
|
|
|
|
.width(Length::Fill)
|
|
|
|
|
|
.height(Length::Fill)
|
|
|
|
|
|
.spacing(4)
|
|
|
|
|
|
.on_click(Message::PaneClicked)
|
|
|
|
|
|
.on_drag(Message::PaneDragged)
|
|
|
|
|
|
.on_resize(4, Message::PaneResized)
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.style(move |_theme| pane_grid::Style {
|
|
|
|
|
|
hovered_region: pane_grid::Highlight {
|
|
|
|
|
|
background: iced::Background::Color(iced::Color::from_rgba(
|
|
|
|
|
|
colors.accent.r, colors.accent.g, colors.accent.b, 0.15,
|
|
|
|
|
|
)),
|
|
|
|
|
|
border: iced::Border::default().color(colors.accent).width(1),
|
|
|
|
|
|
},
|
|
|
|
|
|
picked_split: pane_grid::Line {
|
|
|
|
|
|
color: colors.accent,
|
|
|
|
|
|
width: 2.0,
|
|
|
|
|
|
},
|
|
|
|
|
|
hovered_split: pane_grid::Line {
|
|
|
|
|
|
color: colors.border_high,
|
|
|
|
|
|
width: 1.0,
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
2026-07-11 18:13:03 +03:00
|
|
|
|
.into()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Build the document tab bar.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Shows a horizontal tab for each open document. The active document tab
|
|
|
|
|
|
/// is highlighted. Each tab shows the document name and a close button.
|
2026-07-12 02:05:31 +03:00
|
|
|
|
/// Modified documents show an asterisk (*). Tabs have hover feedback.
|
2026-07-11 18:13:03 +03:00
|
|
|
|
fn document_tab_bar<'a>(app: &'a crate::app::HcieIcedApp, colors: ThemeColors) -> Element<'a, Message> {
|
|
|
|
|
|
let mut tabs = row![].spacing(0);
|
|
|
|
|
|
|
|
|
|
|
|
for (idx, doc) in app.documents.iter().enumerate() {
|
|
|
|
|
|
let is_active = idx == app.active_doc;
|
|
|
|
|
|
let modified_indicator = if doc.modified { " *" } else { "" };
|
|
|
|
|
|
let label = format!("{}{}", doc.name, modified_indicator);
|
|
|
|
|
|
|
|
|
|
|
|
let tab_text = text(label).size(11);
|
|
|
|
|
|
let tab_text = if is_active {
|
|
|
|
|
|
tab_text.style(move |_theme: &iced::Theme| iced::widget::text::Style {
|
|
|
|
|
|
color: Some(colors.accent),
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
2026-07-12 02:05:31 +03:00
|
|
|
|
tab_text.style(move |_theme: &iced::Theme| iced::widget::text::Style {
|
|
|
|
|
|
color: Some(colors.text_primary),
|
|
|
|
|
|
})
|
2026-07-11 18:13:03 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
let active = is_active;
|
|
|
|
|
|
let c = colors;
|
2026-07-11 18:13:03 +03:00
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
let tab = button(
|
2026-07-11 18:13:03 +03:00
|
|
|
|
row![
|
|
|
|
|
|
tab_text,
|
|
|
|
|
|
button(text("×").size(10))
|
|
|
|
|
|
.on_press(Message::NoOp)
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.padding([0, 4])
|
|
|
|
|
|
.style(move |_theme: &iced::Theme, status: iced::widget::button::Status| {
|
|
|
|
|
|
match status {
|
|
|
|
|
|
iced::widget::button::Status::Hovered => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(c.bg_active)),
|
|
|
|
|
|
text_color: c.text_primary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
_ => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(iced::Color::from_rgba(0.0, 0.0, 0.0, 0.0))),
|
|
|
|
|
|
text_color: c.text_secondary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
2026-07-11 18:13:03 +03:00
|
|
|
|
]
|
|
|
|
|
|
.spacing(4)
|
|
|
|
|
|
.align_y(iced::Alignment::Center)
|
|
|
|
|
|
)
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.on_press(Message::DocSwitch(idx))
|
2026-07-11 18:13:03 +03:00
|
|
|
|
.padding([4, 8])
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.style(move |_theme: &iced::Theme, status: iced::widget::button::Status| {
|
|
|
|
|
|
match status {
|
|
|
|
|
|
iced::widget::button::Status::Active => {
|
|
|
|
|
|
if active {
|
|
|
|
|
|
iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(c.bg_active)),
|
|
|
|
|
|
border: iced::Border::default().color(c.accent).width(1),
|
|
|
|
|
|
text_color: c.accent,
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(c.bg_panel)),
|
|
|
|
|
|
border: iced::Border::default().color(c.border_low).width(1),
|
|
|
|
|
|
text_color: c.text_primary,
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
iced::widget::button::Status::Hovered => {
|
|
|
|
|
|
if active {
|
|
|
|
|
|
iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(c.bg_active)),
|
|
|
|
|
|
border: iced::Border::default().color(c.accent).width(1),
|
|
|
|
|
|
text_color: c.accent,
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(c.bg_hover)),
|
|
|
|
|
|
border: iced::Border::default().color(c.border_high).width(1),
|
|
|
|
|
|
text_color: c.text_primary,
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(c.bg_active)),
|
|
|
|
|
|
border: iced::Border::default().color(c.accent).width(1),
|
|
|
|
|
|
text_color: c.accent,
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-07-11 18:13:03 +03:00
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
tabs = tabs.push(tab);
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
// Add new document button with hover
|
2026-07-11 18:13:03 +03:00
|
|
|
|
let new_btn = button(text("+").size(12))
|
|
|
|
|
|
.on_press(Message::DialogOpen(crate::app::ActiveDialog::NewImage))
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.padding([4, 8])
|
|
|
|
|
|
.style(move |_theme: &iced::Theme, status: iced::widget::button::Status| {
|
|
|
|
|
|
match status {
|
|
|
|
|
|
iced::widget::button::Status::Hovered => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(colors.bg_hover)),
|
|
|
|
|
|
text_color: colors.text_primary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
_ => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(iced::Color::from_rgba(0.0, 0.0, 0.0, 0.0))),
|
|
|
|
|
|
text_color: colors.text_secondary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-07-11 18:13:03 +03:00
|
|
|
|
|
|
|
|
|
|
tabs = tabs.push(new_btn);
|
|
|
|
|
|
|
|
|
|
|
|
container(tabs)
|
|
|
|
|
|
.width(Length::Fill)
|
|
|
|
|
|
.style(move |_theme| styles::tabbar_background(colors))
|
|
|
|
|
|
.into()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
/// Map pane type to its SVG icon path.
|
|
|
|
|
|
fn pane_type_icon(pane_type: PaneType) -> Option<&'static str> {
|
|
|
|
|
|
match pane_type {
|
|
|
|
|
|
PaneType::Canvas => None,
|
|
|
|
|
|
PaneType::Layers => Some("icons/panel-layers.svg"),
|
|
|
|
|
|
PaneType::History => Some("icons/panel-history.svg"),
|
|
|
|
|
|
PaneType::Brushes => Some("icons/panel-brush.svg"),
|
|
|
|
|
|
PaneType::Filters => Some("icons/panel-filters.svg"),
|
|
|
|
|
|
PaneType::ColorPicker => Some("icons/panel-colorbox.svg"),
|
|
|
|
|
|
PaneType::Properties => Some("icons/panel-settings.svg"),
|
|
|
|
|
|
PaneType::Script => Some("icons/panel-pen.svg"),
|
|
|
|
|
|
PaneType::AiChat => Some("icons/panel-ai.svg"),
|
|
|
|
|
|
PaneType::LayerDetails => Some("icons/panel-layers.svg"),
|
|
|
|
|
|
PaneType::Geometry => Some("icons/vector_rect.svg"),
|
2026-07-14 14:14:10 +03:00
|
|
|
|
PaneType::ToolSettings => Some("icons/panel-settings.svg"),
|
2026-07-15 17:02:06 +03:00
|
|
|
|
PaneType::AiScript => Some("icons/panel-ai.svg"),
|
2026-07-12 02:05:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 18:13:03 +03:00
|
|
|
|
/// Create a Photoshop-style title bar for a pane.
|
|
|
|
|
|
///
|
2026-07-12 02:05:31 +03:00
|
|
|
|
/// Shows a panel type icon + label with close and maximize buttons.
|
|
|
|
|
|
/// Uses `panel_header` style from the theme for consistent header appearance.
|
|
|
|
|
|
/// Buttons have hover feedback: transparent by default, bg_hover on hover.
|
2026-07-11 18:13:03 +03:00
|
|
|
|
fn make_title_bar<'a>(pane: pane_grid::Pane, pane_type: PaneType, colors: ThemeColors) -> TitleBar<'a, Message> {
|
2026-07-12 02:05:31 +03:00
|
|
|
|
// Panel type icon
|
|
|
|
|
|
let icon_paths = [
|
|
|
|
|
|
std::path::PathBuf::from("hcie-iced-app/assets"),
|
|
|
|
|
|
std::path::PathBuf::from("/mnt/extra/00_PROJECTS/hcie-rust-v3.05/hcie-iced-app/assets"),
|
|
|
|
|
|
];
|
2026-07-11 18:13:03 +03:00
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
let title_content: Element<'_, Message> = if let Some(icon_file) = pane_type_icon(pane_type) {
|
|
|
|
|
|
if let Some(base) = icon_paths.iter().find(|p| p.join(icon_file).exists()) {
|
|
|
|
|
|
let handle = svg::Handle::from_path(base.join(icon_file));
|
|
|
|
|
|
let icon = svg(handle)
|
|
|
|
|
|
.width(14)
|
|
|
|
|
|
.height(14)
|
|
|
|
|
|
.style(move |_theme: &iced::Theme, _status: iced::widget::svg::Status| svg::Style {
|
|
|
|
|
|
color: Some(colors.text_secondary),
|
|
|
|
|
|
});
|
|
|
|
|
|
row![icon, text(pane_type.label()).size(11).color(colors.text_secondary)]
|
|
|
|
|
|
.spacing(4)
|
|
|
|
|
|
.align_y(iced::Alignment::Center)
|
|
|
|
|
|
.into()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
text(pane_type.label()).size(11).color(colors.text_secondary).into()
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
text(pane_type.label()).size(11).color(colors.text_secondary).into()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Close button with hover state
|
|
|
|
|
|
let close_btn = button(text("×").size(11))
|
2026-07-11 18:13:03 +03:00
|
|
|
|
.on_press(Message::PaneClose(pane))
|
|
|
|
|
|
.padding([2, 6])
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.style(move |_theme: &iced::Theme, status: iced::widget::button::Status| {
|
|
|
|
|
|
match status {
|
|
|
|
|
|
iced::widget::button::Status::Hovered => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(colors.bg_hover)),
|
|
|
|
|
|
text_color: colors.text_primary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
iced::widget::button::Status::Pressed => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(colors.bg_active)),
|
|
|
|
|
|
text_color: colors.text_primary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
_ => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(iced::Color::from_rgba(0.0, 0.0, 0.0, 0.0))),
|
|
|
|
|
|
text_color: colors.text_secondary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2026-07-11 18:13:03 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-12 02:05:31 +03:00
|
|
|
|
// Maximize button with hover state
|
|
|
|
|
|
let maximize_btn = button(text("□").size(11))
|
2026-07-11 18:13:03 +03:00
|
|
|
|
.on_press(Message::PaneMaximize(pane))
|
|
|
|
|
|
.padding([2, 6])
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.style(move |_theme: &iced::Theme, status: iced::widget::button::Status| {
|
|
|
|
|
|
match status {
|
|
|
|
|
|
iced::widget::button::Status::Hovered => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(colors.bg_hover)),
|
|
|
|
|
|
text_color: colors.text_primary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
iced::widget::button::Status::Pressed => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(colors.bg_active)),
|
|
|
|
|
|
text_color: colors.text_primary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
_ => iced::widget::button::Style {
|
|
|
|
|
|
background: Some(iced::Background::Color(iced::Color::from_rgba(0.0, 0.0, 0.0, 0.0))),
|
|
|
|
|
|
text_color: colors.text_secondary,
|
|
|
|
|
|
border: iced::Border::default(),
|
|
|
|
|
|
..Default::default()
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2026-07-11 18:13:03 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
TitleBar::new(
|
2026-07-12 02:05:31 +03:00
|
|
|
|
row![title_content, iced::widget::Space::with_width(Length::Fill), maximize_btn, close_btn]
|
2026-07-11 18:13:03 +03:00
|
|
|
|
.spacing(2)
|
|
|
|
|
|
.align_y(iced::Alignment::Center)
|
|
|
|
|
|
)
|
2026-07-12 02:05:31 +03:00
|
|
|
|
.style(move |_theme| styles::panel_header(colors))
|
2026-07-11 18:13:03 +03:00
|
|
|
|
}
|