396 lines
12 KiB
Rust
396 lines
12 KiB
Rust
use crate::behaviors;
|
|
use crate::editor::Message as EditorMessage;
|
|
use iced::widget::{button, container, row, text, Column, Space};
|
|
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Theme};
|
|
|
|
pub const BAR_HEIGHT: f32 = 24.0;
|
|
const TITLE_WIDTH: f32 = 75.0;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum MenuId {
|
|
File,
|
|
Edit,
|
|
Transport,
|
|
View,
|
|
}
|
|
|
|
impl MenuId {
|
|
const ALL: [MenuId; 4] = [MenuId::File, MenuId::Edit, MenuId::Transport, MenuId::View];
|
|
|
|
fn label(&self) -> &'static str {
|
|
match self {
|
|
MenuId::File => "File",
|
|
MenuId::Edit => "Edit",
|
|
MenuId::Transport => "Transport",
|
|
MenuId::View => "View",
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct State {
|
|
pub open: Option<MenuId>,
|
|
}
|
|
|
|
impl State {
|
|
pub fn new() -> Self {
|
|
Self { open: None }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Message {
|
|
Open(MenuId),
|
|
Close,
|
|
Action(behaviors::Action),
|
|
ShowNewTrackWizard,
|
|
}
|
|
|
|
struct MenuItem {
|
|
label: &'static str,
|
|
shortcut: &'static str,
|
|
message: Message,
|
|
}
|
|
|
|
enum MenuEntry {
|
|
Item(MenuItem),
|
|
Separator,
|
|
}
|
|
|
|
fn file_entries() -> Vec<MenuEntry> {
|
|
use behaviors::Action::*;
|
|
vec![
|
|
MenuEntry::Item(MenuItem {
|
|
label: "New Project",
|
|
shortcut: "\u{2318}N",
|
|
message: Message::Action(NewProject),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Open\u{2026}",
|
|
shortcut: "\u{2318}O",
|
|
message: Message::Action(OpenProject),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Save",
|
|
shortcut: "\u{2318}S",
|
|
message: Message::Action(SaveProject),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Save As\u{2026}",
|
|
shortcut: "\u{21E7}\u{2318}S",
|
|
message: Message::Action(SaveProjectAs),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Close",
|
|
shortcut: "\u{2318}W",
|
|
message: Message::Action(CloseProject),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Settings",
|
|
shortcut: "\u{2318},",
|
|
message: Message::Action(OpenSettings),
|
|
}),
|
|
]
|
|
}
|
|
|
|
fn edit_entries() -> Vec<MenuEntry> {
|
|
use behaviors::Action::*;
|
|
vec![
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Undo",
|
|
shortcut: "\u{2318}Z",
|
|
message: Message::Action(Undo),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Redo",
|
|
shortcut: "\u{21E7}\u{2318}Z",
|
|
message: Message::Action(Redo),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Cut",
|
|
shortcut: "\u{2318}X",
|
|
message: Message::Action(Cut),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Copy",
|
|
shortcut: "\u{2318}C",
|
|
message: Message::Action(Copy),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Paste",
|
|
shortcut: "\u{2318}V",
|
|
message: Message::Action(Paste),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Duplicate",
|
|
shortcut: "\u{2318}D",
|
|
message: Message::Action(Duplicate),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Select All",
|
|
shortcut: "\u{2318}A",
|
|
message: Message::Action(SelectAll),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Delete",
|
|
shortcut: "\u{232B}",
|
|
message: Message::Action(Delete),
|
|
}),
|
|
]
|
|
}
|
|
|
|
fn transport_entries() -> Vec<MenuEntry> {
|
|
use behaviors::Action::*;
|
|
vec![
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Play/Pause",
|
|
shortcut: "Space",
|
|
message: Message::Action(EditorTogglePlayback),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Stop",
|
|
shortcut: "",
|
|
message: Message::Action(EditorStop),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Record",
|
|
shortcut: "R",
|
|
message: Message::Action(EditorToggleRecord),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "From Start",
|
|
shortcut: "\u{21B5}",
|
|
message: Message::Action(EditorPlayFromBeginning),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Rewind",
|
|
shortcut: ",",
|
|
message: Message::Action(EditorRewind),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "New Track\u{2026}",
|
|
shortcut: "",
|
|
message: Message::ShowNewTrackWizard,
|
|
}),
|
|
]
|
|
}
|
|
|
|
fn view_entries() -> Vec<MenuEntry> {
|
|
use behaviors::Action::*;
|
|
vec![
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Inspector",
|
|
shortcut: "I",
|
|
message: Message::Action(EditorToggleInspector),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Bottom Panel",
|
|
shortcut: "E",
|
|
message: Message::Action(EditorToggleBottomPanel),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Mixer",
|
|
shortcut: "X",
|
|
message: Message::Action(EditorToggleMixer),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Cycle",
|
|
shortcut: "C",
|
|
message: Message::Action(EditorToggleCycle),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Metronome",
|
|
shortcut: "K",
|
|
message: Message::Action(EditorToggleMetronome),
|
|
}),
|
|
MenuEntry::Separator,
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Zoom In H",
|
|
shortcut: "\u{2318}\u{2192}",
|
|
message: Message::Action(ZoomInH),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Zoom Out H",
|
|
shortcut: "\u{2318}\u{2190}",
|
|
message: Message::Action(ZoomOutH),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Zoom In V",
|
|
shortcut: "\u{2318}\u{2191}",
|
|
message: Message::Action(ZoomInV),
|
|
}),
|
|
MenuEntry::Item(MenuItem {
|
|
label: "Zoom Out V",
|
|
shortcut: "\u{2318}\u{2193}",
|
|
message: Message::Action(ZoomOutV),
|
|
}),
|
|
]
|
|
}
|
|
|
|
fn entries_for(id: MenuId) -> Vec<MenuEntry> {
|
|
match id {
|
|
MenuId::File => file_entries(),
|
|
MenuId::Edit => edit_entries(),
|
|
MenuId::Transport => transport_entries(),
|
|
MenuId::View => view_entries(),
|
|
}
|
|
}
|
|
|
|
fn dropdown_x_offset(id: MenuId) -> f32 {
|
|
match id {
|
|
MenuId::File => 0.0,
|
|
MenuId::Edit => TITLE_WIDTH,
|
|
MenuId::Transport => TITLE_WIDTH * 2.0,
|
|
MenuId::View => TITLE_WIDTH * 3.0,
|
|
}
|
|
}
|
|
|
|
pub fn view(state: &State) -> Element<'_, EditorMessage> {
|
|
let titles: Vec<Element<'_, EditorMessage>> = MenuId::ALL
|
|
.iter()
|
|
.map(|&id| {
|
|
let is_open = state.open == Some(id);
|
|
let label_color = if is_open {
|
|
Color::WHITE
|
|
} else {
|
|
Color::from_rgb8(0xAA, 0xAA, 0xAA)
|
|
};
|
|
|
|
button(text(id.label()).size(12).color(label_color))
|
|
.on_press(EditorMessage::MenuBar(Message::Open(id)))
|
|
.padding([4, 12])
|
|
.width(TITLE_WIDTH)
|
|
.style(move |_theme: &Theme, status| {
|
|
let bg = match status {
|
|
button::Status::Hovered | button::Status::Pressed => {
|
|
Color::from_rgb8(0x2A, 0x2C, 0x2E)
|
|
}
|
|
_ if is_open => Color::from_rgb8(0x2A, 0x2C, 0x2E),
|
|
_ => Color::TRANSPARENT,
|
|
};
|
|
button::Style {
|
|
background: Some(Background::Color(bg)),
|
|
text_color: label_color,
|
|
border: Border::default(),
|
|
..button::Style::default()
|
|
}
|
|
})
|
|
.into()
|
|
})
|
|
.collect();
|
|
|
|
let bar = row(titles).spacing(0).align_y(Alignment::Center);
|
|
|
|
container(bar)
|
|
.width(Length::Fill)
|
|
.height(BAR_HEIGHT)
|
|
.style(|_theme: &Theme| container::Style {
|
|
background: Some(Background::Color(Color::from_rgb8(0x1A, 0x1C, 0x1E))),
|
|
..container::Style::default()
|
|
})
|
|
.into()
|
|
}
|
|
|
|
pub fn dropdown_view(state: &State) -> Option<Element<'_, EditorMessage>> {
|
|
let menu_id = state.open?;
|
|
let entries = entries_for(menu_id);
|
|
|
|
let mut items: Column<'_, EditorMessage> = Column::new().spacing(0).width(200);
|
|
|
|
for entry in entries {
|
|
match entry {
|
|
MenuEntry::Item(item) => {
|
|
let msg = EditorMessage::MenuBar(item.message);
|
|
let shortcut_el: Element<'_, EditorMessage> = if item.shortcut.is_empty() {
|
|
Space::new(0, 0).into()
|
|
} else {
|
|
text(item.shortcut)
|
|
.size(11)
|
|
.color(Color::from_rgb8(0x66, 0x66, 0x66))
|
|
.into()
|
|
};
|
|
|
|
let item_row = row![
|
|
text(item.label).size(12),
|
|
Space::with_width(Length::Fill),
|
|
shortcut_el,
|
|
]
|
|
.align_y(Alignment::Center)
|
|
.spacing(8);
|
|
|
|
let item_btn = button(item_row)
|
|
.on_press(msg)
|
|
.width(Length::Fill)
|
|
.padding([5, 12])
|
|
.style(|_theme: &Theme, status| {
|
|
let bg = match status {
|
|
button::Status::Hovered => Color::from_rgb8(0x00, 0x7A, 0xFF),
|
|
button::Status::Pressed => Color::from_rgb8(0x00, 0x6A, 0xDD),
|
|
_ => Color::TRANSPARENT,
|
|
};
|
|
let text_color = match status {
|
|
button::Status::Hovered | button::Status::Pressed => Color::WHITE,
|
|
_ => Color::from_rgb8(0xCC, 0xCC, 0xCC),
|
|
};
|
|
button::Style {
|
|
background: Some(Background::Color(bg)),
|
|
text_color,
|
|
border: Border::default(),
|
|
..button::Style::default()
|
|
}
|
|
});
|
|
|
|
items = items.push(item_btn);
|
|
}
|
|
MenuEntry::Separator => {
|
|
let line = container(Space::new(0, 0))
|
|
.width(Length::Fill)
|
|
.height(1)
|
|
.style(|_theme: &Theme| container::Style {
|
|
background: Some(Background::Color(Color::from_rgb8(0x30, 0x32, 0x34))),
|
|
..container::Style::default()
|
|
});
|
|
items = items.push(container(line).padding([4, 8]));
|
|
}
|
|
}
|
|
}
|
|
|
|
let dropdown = container(items)
|
|
.style(|_theme: &Theme| container::Style {
|
|
background: Some(Background::Color(Color::from_rgb8(0x22, 0x24, 0x26))),
|
|
border: Border {
|
|
color: Color::from_rgb8(0x35, 0x37, 0x39),
|
|
width: 1.0,
|
|
radius: 4.0.into(),
|
|
},
|
|
..container::Style::default()
|
|
})
|
|
.padding(4);
|
|
|
|
let offset = dropdown_x_offset(menu_id);
|
|
|
|
let positioned: Element<'_, EditorMessage> = container(row![
|
|
Space::new(offset, 0),
|
|
dropdown,
|
|
Space::with_width(Length::Fill),
|
|
])
|
|
.width(Length::Fill)
|
|
.height(Length::Fill)
|
|
.padding(Padding {
|
|
top: BAR_HEIGHT,
|
|
right: 0.0,
|
|
bottom: 0.0,
|
|
left: 0.0,
|
|
})
|
|
.into();
|
|
|
|
Some(positioned)
|
|
}
|