181 lines
7.0 KiB
Rust
181 lines
7.0 KiB
Rust
//! Title bar — Photoshop-style unified bar with menus, doc info, and window controls.
|
||
//!
|
||
//! Combines the menu bar and title bar into a single 28px bar:
|
||
//! [HCIE logo] [menu items...] [doc info] [min/max/close]
|
||
//! The entire bar is draggable for window movement.
|
||
|
||
use crate::app::Message;
|
||
use crate::theme::ThemeColors;
|
||
use iced::widget::{button, container, mouse_area, row, text};
|
||
use iced::{Element, Length};
|
||
|
||
/// All menu definitions for the menu bar.
|
||
const MENU_LABELS: &[&str] = &["File", "Edit", "Tools", "Image", "Layer", "Filter", "Select", "View", "Window", "Help"];
|
||
|
||
/// Build the unified title/menu bar element.
|
||
///
|
||
/// Layout: [HCIE] [File Edit Tools ... Help] [doc info] [─ □ ×]
|
||
/// The left portion (before window controls) is draggable.
|
||
pub fn view<'a>(
|
||
doc_name: &'a str,
|
||
zoom: f32,
|
||
canvas_w: u32,
|
||
canvas_h: u32,
|
||
colors: ThemeColors,
|
||
active_menu: Option<usize>,
|
||
) -> Element<'a, Message> {
|
||
// ── App name (fixed width) ──
|
||
let app_name = text("HCIE")
|
||
.size(13)
|
||
.font(iced::Font::MONOSPACE)
|
||
.style(move |_theme: &iced::Theme| iced::widget::text::Style {
|
||
color: Some(colors.accent),
|
||
});
|
||
|
||
// ── Menu items with hover states ──
|
||
let mut menu_items = row![].spacing(0);
|
||
for (idx, &label) in MENU_LABELS.iter().enumerate() {
|
||
let is_open = active_menu == Some(idx);
|
||
let c = colors;
|
||
let btn = button(text(label).size(11))
|
||
.on_press(Message::MenuOpen(idx))
|
||
.padding([4, 8])
|
||
.style(move |_theme: &iced::Theme, status: iced::widget::button::Status| {
|
||
if is_open {
|
||
iced::widget::button::Style {
|
||
background: Some(iced::Background::Color(c.bg_hover)),
|
||
text_color: c.text_primary,
|
||
border: iced::Border::default().color(c.border_high).width(1),
|
||
..Default::default()
|
||
}
|
||
} else {
|
||
match status {
|
||
iced::widget::button::Status::Hovered => iced::widget::button::Style {
|
||
background: Some(iced::Background::Color(c.bg_hover)),
|
||
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_primary,
|
||
border: iced::Border::default(),
|
||
..Default::default()
|
||
},
|
||
}
|
||
}
|
||
});
|
||
menu_items = menu_items.push(btn);
|
||
}
|
||
|
||
// ── Document info ──
|
||
let doc_info = text(format!("{} — {}×{} @ {:.0}%", doc_name, canvas_w, canvas_h, zoom * 100.0))
|
||
.size(10)
|
||
.style(move |_theme: &iced::Theme| iced::widget::text::Style {
|
||
color: Some(colors.text_secondary),
|
||
});
|
||
|
||
// ── Window controls (right side) with hover states ──
|
||
let c = colors;
|
||
|
||
let minimize_btn = button(text("─").size(10))
|
||
.on_press(Message::WindowMinimize)
|
||
.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(c.bg_hover)),
|
||
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()
|
||
},
|
||
}
|
||
});
|
||
|
||
let maximize_btn = button(text("□").size(10))
|
||
.on_press(Message::WindowMaximize)
|
||
.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(c.bg_hover)),
|
||
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()
|
||
},
|
||
}
|
||
});
|
||
|
||
let close_btn = button(text("×").size(11))
|
||
.on_press(Message::WindowClose)
|
||
.padding([4, 10])
|
||
.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.danger)),
|
||
text_color: iced::Color::WHITE,
|
||
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()
|
||
},
|
||
}
|
||
});
|
||
|
||
let window_controls = row![minimize_btn, maximize_btn, close_btn].spacing(0);
|
||
|
||
// ── Left side (draggable): app name + menus + doc info ──
|
||
let left_side = row![
|
||
app_name,
|
||
text(" ").size(11),
|
||
menu_items,
|
||
iced::widget::Space::with_width(Length::Fill),
|
||
doc_info,
|
||
]
|
||
.spacing(0)
|
||
.align_y(iced::Alignment::Center);
|
||
|
||
let draggable_left = mouse_area(
|
||
container(left_side)
|
||
.width(Length::Fill)
|
||
.height(28)
|
||
.align_y(iced::Alignment::Center)
|
||
.padding([0, 8])
|
||
)
|
||
.on_press(Message::WindowDrag)
|
||
.interaction(iced::mouse::Interaction::Grab);
|
||
|
||
// ── Full bar ──
|
||
let bar = row![
|
||
draggable_left,
|
||
window_controls,
|
||
]
|
||
.align_y(iced::Alignment::Center);
|
||
|
||
container(bar)
|
||
.width(Length::Fill)
|
||
.height(28)
|
||
.style(move |_theme| iced::widget::container::Style {
|
||
background: Some(iced::Background::Color(colors.bg_app)),
|
||
border: iced::Border::default().color(colors.border_low).width(1),
|
||
..Default::default()
|
||
})
|
||
.into()
|
||
}
|