mod bpm_bar;
use super::App;
use crate::assets::AssetEdits;
use crate::ui::{logo::logo_image, window_common::window_buttons};
use bpm_bar::BpmBar;
use egui::{
Button, Color32, Frame, Id, Image, Key, KeyboardShortcut, Modifiers, PointerButton, Sense,
Slider, Stroke, TextFormat, Ui, UiKind, Vec2, ViewportCommand, ViewportId, text::LayoutJob,
};
use gled_model::asset::Asset;
use gled_protocol::action::UiAction;
use gled_protocol::state::FPS_LIMIT_MAX;
use std::sync::Arc;
use web_time::{SystemTime, UNIX_EPOCH};
const BPM_BAR_WIDTH: f32 = 500.0;
const BUTTON_HEIGHT: f32 = 25.0;
impl App {
pub(crate) fn menu(&mut self, ui: &mut Ui, viewport_id: Option<ViewportId>) {
egui::Panel::top(format!("{viewport_id:?} menu")).show(ui, |ui| {
if self.loading().is_some() {
ui.disable();
}
let title_bar_response = ui.interact(
ui.available_rect_before_wrap(),
Id::new("title_bar"),
Sense::click_and_drag(),
);
if title_bar_response.double_clicked() {
let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
ui.ctx()
.send_viewport_cmd(ViewportCommand::Maximized(!is_maximized));
}
if title_bar_response.drag_started_by(PointerButton::Primary) {
ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
}
egui::MenuBar::new().ui(ui, |ui| {
Frame::new().show(ui, |ui| {
ui.set_height(32.0);
let mut bpm_rect = ui.available_rect_before_wrap();
bpm_rect.set_left((bpm_rect.width() - BPM_BAR_WIDTH) * 0.55);
bpm_rect.set_width(BPM_BAR_WIDTH);
self.menus(ui);
ui.put(bpm_rect, BpmBar { app: self });
self.window_buttons(ui);
});
});
});
}
fn menus(&mut self, ui: &mut Ui) {
ui.horizontal_centered(|ui| {
ui.take_available_height();
ui.scope(|ui| {
ui.spacing_mut().button_padding = egui::vec2(4.0, 4.0);
if ui.add(Button::image(Image::new(logo_image()))).clicked() {
self.windows.about.toggle();
};
ui.separator();
let mut open_project = ui
.ctx()
.input_mut(|i| i.consume_key(Modifiers::COMMAND, Key::O));
let mut save_project = ui
.ctx()
.input_mut(|i| i.consume_key(Modifiers::COMMAND, Key::S));
ui.menu_button("Project", |ui| {
ui.set_min_width(300.0);
if let Some(project) = self
.project_id
.and_then(|id| Asset::get(id, &self.model.replica.assets))
{
ui.label(format!("Project: {}", project.name()));
} else {
ui.label("No project loaded");
}
ui.separator();
if ui
.add(Button::new("Load project").shortcut_text(
ui.ctx().format_shortcut(&KeyboardShortcut::new(
Modifiers::COMMAND,
Key::O,
)),
))
.clicked()
{
open_project = true;
ui.close_kind(UiKind::Menu);
}
if ui
.add_enabled(
self.project.is_some(),
Button::new("Save project").shortcut_text(ui.ctx().format_shortcut(
&KeyboardShortcut::new(Modifiers::COMMAND, Key::S),
)),
)
.clicked()
{
save_project = true;
ui.close_kind(UiKind::Menu);
}
if ui
.add_enabled(self.project.is_some(), Button::new("Close project"))
.clicked()
{
self.project.take();
ui.close_kind(UiKind::Menu);
}
ui.separator();
if ui
.add_enabled(self.project.is_some(), Button::new("Patch Fixtures"))
.clicked()
{
self.windows.fixture_window.open();
ui.close_kind(UiKind::Menu);
}
if ui
.add_enabled(self.project.is_some(), Button::new("External Devices"))
.clicked()
{
self.windows.external_device_settings.open();
ui.close_kind(UiKind::Menu);
}
if ui
.add_enabled(self.project.is_some(), Button::new("Channel Overwrites"))
.clicked()
{
self.windows.channel_overwrites.open();
ui.close_kind(UiKind::Menu);
}
if ui
.add_enabled(self.project.is_some(), Button::new("Output Routings"))
.clicked()
{
self.windows.output_routings.open();
ui.close_kind(UiKind::Menu);
}
if ui
.add_enabled(self.project.is_some(), Button::new("Shortcuts"))
.clicked()
{
self.windows.shortcuts.open();
ui.close_kind(UiKind::Menu);
}
});
if open_project {
self.windows.projects.open();
}
if save_project
&& let (Some(project), Some(mut asset)) = (
self.project.as_ref(),
self.project_id
.and_then(|id| Asset::get(id, &self.model.replica.assets))
.map(Arc::unwrap_or_clone),
)
{
asset.data = project.to_owned();
asset.data.artnet_config = self.model.replica.state.status.artnet.clone();
asset.data.output_routings = self.output_config.routings.clone();
asset.save(&mut self.model.replica.assets);
}
ui.menu_button("Assets", |ui| {
ui.set_min_width(300.0);
if ui.button("Animations").clicked() {
self.windows.animations.open();
ui.close_kind(UiKind::Menu);
}
if ui.button("Curves").clicked() {
self.windows.curves.open();
ui.close_kind(UiKind::Menu);
}
if ui.button("Fixture Definitions").clicked() {
self.windows.fixture_definitions.open();
ui.close_kind(UiKind::Menu);
}
if ui.button("Output Devices").clicked() {
self.windows.output_devices.open();
ui.close_kind(UiKind::Menu);
}
if ui.button("MIDI Controllers").clicked() {
self.windows.midi_controllers.open();
ui.close_kind(UiKind::Menu);
}
if ui.button("Palettes").clicked() {
self.windows.palettes.open();
ui.close_kind(UiKind::Menu);
}
if ui.button("Projects").clicked() {
self.windows.projects.open();
ui.close_kind(UiKind::Menu);
}
if ui.button("Scenes").clicked() {
self.windows.scenes.open();
ui.close_kind(UiKind::Menu);
}
});
let config_menu = egui::containers::menu::MenuConfig::new()
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside);
egui::containers::menu::MenuButton::new("Config")
.config(config_menu)
.ui(ui, |ui| {
ui.set_min_width(300.0);
if ui.button("Git Configuration").clicked() {
self.windows.git_config.open();
ui.close_kind(UiKind::Menu);
}
if let Some(extension) = &mut self.extension {
extension.config_menu(ui);
}
ui.separator();
ui.spacing_mut().slider_width = 290.0;
ui.label("Output framerate limit");
let mut fps_limit = self.server_settings().fps_limit.min(FPS_LIMIT_MAX);
if ui
.add(framerate_limit_slider(&mut fps_limit, 30.0, FPS_LIMIT_MAX))
.on_hover_text(
"Rate of the render and Art-Net/DMX output thread. \
∞ renders as fast as the GPU allows. \
The window itself repaints at the monitor refresh rate.",
)
.changed()
{
self.change_server_settings(|settings| settings.fps_limit = fps_limit);
}
ui.separator();
egui::gui_zoom::zoom_menu_buttons(ui);
});
let mut open_new_window = ui.ctx().input_mut(|i| {
i.consume_key(Modifiers::COMMAND.plus(Modifiers::SHIFT), Key::N)
});
ui.menu_button("Window", |ui| {
ui.set_min_width(300.0);
if ui
.add(Button::new("Open new window").shortcut_text(
ui.ctx().format_shortcut(&KeyboardShortcut::new(
Modifiers::COMMAND.plus(Modifiers::SHIFT),
Key::N,
)),
))
.clicked()
{
open_new_window = true;
ui.close_kind(UiKind::Menu);
}
});
#[cfg(target_arch = "wasm32")]
if open_new_window {
crate::platform::open_in_new_tab();
}
#[cfg(not(target_arch = "wasm32"))]
if open_new_window {
let viewport_id =
ViewportId(Id::new(format!("Second Window {}", uuid::Uuid::new_v4())));
self.other_main_windows.insert(viewport_id);
}
ui.separator();
});
});
}
fn window_buttons(&mut self, ui: &mut Ui) {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
window_buttons(ui);
let underlined = TextFormat {
underline: Stroke::new(1.0, Color32::GRAY),
..Default::default()
};
let mut blackout_text = LayoutJob::default();
blackout_text.append("B", 0.0, underlined);
blackout_text.append("lackout", 0.0, TextFormat::default());
let mut blackout = Button::new(blackout_text);
if self.blackout
&& SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() / 200 % 2 == 0)
.unwrap_or_default()
{
blackout = blackout.fill(Color32::DARK_RED);
}
if ui
.add_sized(Vec2::new(100.0, BUTTON_HEIGHT), blackout)
.clicked()
{
self.blackout = !self.blackout;
UiAction::SetBlackout(self.blackout).enqueue();
}
ui.separator();
});
}
}
fn framerate_limit_slider(value: &mut f32, min: f32, max: f32) -> Slider<'_> {
Slider::new(value, min..=max)
.integer()
.custom_formatter(move |n, _| {
if n >= f64::from(max) {
"∞".to_owned()
} else {
format!("{n:.0} fps")
}
})
}