audio-oxide-old/src/gui/editor/menu_bar.rs

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)
}