use iced::widget::{Space, button, column, container, mouse_area, row, svg};
use iced::{Element, Length, alignment};
use super::icons;
use super::menu_dropdowns::{
FILE_DROPDOWN_WIDTH, HELP_DROPDOWN_WIDTH, MENU_ICON_SIZE, MP_DROPDOWN_WIDTH,
VIEW_DROPDOWN_WIDTH, file_dropdown, help_dropdown, mp_dropdown, view_dropdown,
};
use super::menu_labels::{inactive_category_keys, settings_category_key};
use super::styles::{
caption_button_style, close_caption_button_style, menu_bar_divider_style, menu_bar_style,
};
use super::theme::{TOKYO_MAGENTA, TOKYO_TEXT, ui_text};
use crate::app::{DesktopApp, MenuId, Message};
use crate::i18n::Key;
const CAPTION_ICON_SIZE: f32 = 14.0;
const CAPTION_CLOSE_ICON_SIZE: f32 = 16.0;
const CAPTION_BUTTON_WIDTH: f32 = 32.0;
const CAPTION_BUTTON_HEIGHT: f32 = 24.0;
impl DesktopApp {
pub(super) fn menu_bar(&self) -> Element<'_, Message> {
let drag_handle: Element<'_, Message> = mouse_area(
container(Space::new())
.width(Length::Fill)
.height(Length::Fill),
)
.on_press(Message::WindowDragStart)
.into();
let caption_buttons = row![
caption_button(
icons::window_minimize(),
Message::WindowMinimize,
CaptionKind::Neutral,
),
caption_button(
if self.window_maximized {
icons::window_restore()
} else {
icons::window_maximize()
},
Message::WindowToggleMaximize,
CaptionKind::Neutral,
),
caption_button(
icons::window_close(),
self.main_window_id
.map(Message::WindowCloseRequested)
.unwrap_or(Message::WindowClose),
CaptionKind::Close
),
]
.spacing(2)
.align_y(alignment::Vertical::Center);
let cpu_icon = svg(icons::cpu())
.width(Length::Fixed(MENU_ICON_SIZE))
.height(Length::Fixed(MENU_ICON_SIZE))
.style(|_theme, _status| svg::Style {
color: Some(TOKYO_TEXT),
});
let cpu_toggle: Element<'_, Message> = mouse_area(cpu_icon)
.on_press(Message::MenuCategoriesToggled)
.interaction(iced::mouse::Interaction::Pointer)
.into();
let mut bar_children: Vec<Element<'_, Message>> = Vec::with_capacity(8);
bar_children.push(cpu_toggle);
if self.menu_categories_visible {
bar_children.push(menu_trigger(
self.lang.t(Key::MenuFile),
MenuId::File,
self.open_menu == Some(MenuId::File),
));
bar_children.push(menu_trigger(
self.lang.t(Key::MenuMp),
MenuId::Mp,
self.open_menu == Some(MenuId::Mp),
));
bar_children.push(menu_trigger(
self.lang.t(Key::MenuView),
MenuId::View,
self.open_menu == Some(MenuId::View),
));
for key in inactive_category_keys() {
bar_children.push(menu_label(self.lang.t(key)));
}
bar_children.push(settings_trigger(self.lang.t(settings_category_key())));
bar_children.push(menu_trigger(
self.lang.t(Key::MenuHelp),
MenuId::Help,
self.open_menu == Some(MenuId::Help),
));
}
bar_children.push(drag_handle);
bar_children.push(caption_buttons.into());
let bar = container(
iced::widget::Row::with_children(bar_children)
.spacing(18)
.align_y(alignment::Vertical::Center),
)
.padding(iced::Padding::ZERO.left(11).right(2))
.width(Length::Fill)
.height(Length::Fixed(34.0))
.style(menu_bar_style);
const DIVIDER_GAP_BLEED: f32 = -6.0;
const ROOT_PADDING_LEFT: f32 = 8.0;
let divider: Element<'_, Message> = match self.open_menu {
None => container(Space::new())
.width(Length::Fill)
.height(Length::Fixed(1.0))
.style(menu_bar_divider_style)
.into(),
Some(menu) => {
let (dropdown_left, gap_width) = match menu {
MenuId::File => (super::FILE_MENU_DROPDOWN_LEFT, FILE_DROPDOWN_WIDTH),
MenuId::Mp => (super::MP_MENU_DROPDOWN_LEFT, MP_DROPDOWN_WIDTH),
MenuId::View => (super::VIEW_MENU_DROPDOWN_LEFT, VIEW_DROPDOWN_WIDTH),
MenuId::Help => (super::HELP_MENU_DROPDOWN_LEFT, HELP_DROPDOWN_WIDTH),
};
let gap_local_left = (dropdown_left - ROOT_PADDING_LEFT).max(0.0);
let left_segment_width = (gap_local_left - DIVIDER_GAP_BLEED).max(0.0);
let gap_total_width = gap_width + DIVIDER_GAP_BLEED * 2.0;
let line_segment = |w: Length| {
container(Space::new())
.width(w)
.height(Length::Fixed(1.0))
.style(menu_bar_divider_style)
};
row![
line_segment(Length::Fixed(left_segment_width)),
Space::new().width(Length::Fixed(gap_total_width)),
line_segment(Length::Fill),
]
.height(Length::Fixed(1.0))
.into()
}
};
column![bar, divider].into()
}
pub(super) fn menu_dropdown(&self) -> Option<Element<'_, Message>> {
match self.open_menu? {
MenuId::File => Some(file_dropdown(self.lang)),
MenuId::Mp => Some(mp_dropdown(self.snapshot.cpu.halted, self.lang)),
MenuId::View => Some(view_dropdown(self.stack_view, self.lang)),
MenuId::Help => Some(help_dropdown(self.lang)),
}
}
}
fn menu_label(label: &str) -> Element<'_, Message> {
ui_text(label.to_owned(), 13, TOKYO_TEXT).into()
}
fn menu_trigger(label: &str, menu: MenuId, active: bool) -> Element<'_, Message> {
let color = if active { TOKYO_MAGENTA } else { TOKYO_TEXT };
mouse_area(ui_text(label.to_owned(), 13, color))
.on_press(Message::MenuToggled(menu))
.interaction(iced::mouse::Interaction::Pointer)
.into()
}
fn settings_trigger(label: &str) -> Element<'_, Message> {
mouse_area(ui_text(label.to_owned(), 13, TOKYO_TEXT))
.on_press(Message::OpenSettings)
.interaction(iced::mouse::Interaction::Pointer)
.into()
}
#[derive(Clone, Copy)]
enum CaptionKind {
Neutral,
Close,
}
fn caption_button(
icon: svg::Handle,
action: Message,
kind: CaptionKind,
) -> Element<'static, Message> {
let glyph_size = match kind {
CaptionKind::Neutral => CAPTION_ICON_SIZE,
CaptionKind::Close => CAPTION_CLOSE_ICON_SIZE,
};
let glyph = svg(icon)
.width(Length::Fixed(glyph_size))
.height(Length::Fixed(glyph_size))
.style(|_theme, _status| svg::Style {
color: Some(TOKYO_TEXT),
});
let body = container(glyph)
.width(Length::Fill)
.height(Length::Fill)
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center);
button(body)
.on_press(action)
.padding(0)
.width(Length::Fixed(CAPTION_BUTTON_WIDTH))
.height(Length::Fixed(CAPTION_BUTTON_HEIGHT))
.style(move |_theme, status| match kind {
CaptionKind::Neutral => caption_button_style(status),
CaptionKind::Close => close_caption_button_style(status),
})
.into()
}