pub mod config;
pub mod effects;
pub mod menu;
pub mod no_project;
pub mod persistant_state;
pub mod preview;
pub mod project_functions;
pub mod status_bar;
pub mod storage;
pub mod svg;
pub mod timing;
use crate::{
input::Input,
midi::state::MidiState,
pipeline::{
extract_output::ExtractOutput,
output_sender::{self, GpuReadyReceiver, OutputSender},
renderer_callback::RendererCallback,
},
storage::{
asset::{
Asset,
project::{DeckPath, Project, scene_instance_path::SceneInstancePathId},
},
asset_id::AssetId,
loading,
},
ui::{action::UiAction, viewport_builder::default_viewport_builder, windows::Windows},
};
use eframe::egui_wgpu::Callback;
use egui::{CentralPanel, Rect, UiBuilder, ViewportId, ahash::HashSet};
use persistant_state::PersistantState;
use std::{
sync::mpsc::Receiver,
time::{Instant, SystemTime, UNIX_EPOCH},
};
use storage::{show_storage_error, show_storage_loading};
use timing::Timing;
pub struct App {
pub startup: bool,
pub windows: Windows,
pub output_sender: OutputSender,
pub gpu_ready_receiver: GpuReadyReceiver,
pub timing: Timing,
pub last_always_render_fps_frame: Instant,
pub project: Option<Project>,
pub project_id: Option<AssetId<Project>>,
pub other_main_windows: HashSet<ViewportId>,
pub blackout: bool,
pub blackout_hold: bool,
pub selected_scene_instance: SceneInstancePathId,
pub git_commit_message: String,
pub ui_action_receiver: Receiver<UiAction>,
pub last_title_update: u64,
pub midi_output_active: bool,
}
impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ExtractOutput::trigger_output_sender(&self.output_sender);
self.gpu_ready_receiver
.recv()
.expect("GPU ready sender lost");
if loading().is_none() && self.startup {
self.startup = false;
if let Some(project) = PersistantState::get().last_project_id {
UiAction::SetProject(project).enqueue();
}
}
self.timing.tick();
Input::tick();
self.handle_ui_actions();
if SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
!= self.last_title_update
{
ctx.send_viewport_cmd(egui::ViewportCommand::Title(format!(
"gled - {} {}",
match self.project_id.and_then(Asset::get) {
None => "no project loaded".to_owned(),
Some(project) => project.name().to_owned(),
},
match self.timing.framerate() {
Some(fps) => format!("({fps:.1} fps)"),
None => String::new(),
}
)));
self.last_title_update = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
}
if let Some(project) = &mut self.project {
project.render(
&self.timing,
self.blackout || self.blackout_hold,
if PersistantState::effects_always_render() || {
PersistantState::always_render_fps() > 0.0
&& self.last_always_render_fps_frame.elapsed().as_secs_f32()
> 1.0 / PersistantState::always_render_fps()
} {
self.last_always_render_fps_frame = Instant::now();
true
} else {
false
},
self.timing.fade_duration(),
);
}
if self.midi_output_active {
MidiState {
blackout: self.blackout || self.blackout_hold,
beat_flank: self.timing.beat_flank(),
active_scenes: self
.project
.as_mut()
.map_or_else(Default::default, |project| {
project
.all_scene_instances_index()
.filter_map(
|(path, scene)| if scene.active { Some(path) } else { None },
)
.collect()
}),
available_scenes_grid: self
.project
.as_mut()
.map_or(0, |project| project.scenes_instances_grid.len()),
available_scenes_quick: self
.project
.as_mut()
.map_or(0, |project| project.scenes_instances_quick.len()),
}
.enqueue();
}
self.draw_main_window(ctx, None);
let viewport_ids = self.other_main_windows.clone();
for viewport_id in viewport_ids {
ctx.show_viewport_immediate(
viewport_id,
default_viewport_builder()
.with_inner_size([1300.0, 1024.0])
.with_drag_and_drop(true)
.with_min_inner_size([300.0, 200.0]),
|ctx, _viewport_class| {
ctx.input(|input| {
if input.viewport().close_requested() {
UiAction::CloseWindow(viewport_id).enqueue();
}
});
self.draw_main_window(ctx, Some(viewport_id));
},
);
}
self.windows
.update(ctx, &self.timing, self.project.as_mut());
let callback = Callback::new_paint_callback(Rect::ZERO, RendererCallback);
ctx.debug_painter().add(callback);
ctx.request_repaint();
}
}
impl App {
pub fn draw_main_window(&mut self, ctx: &egui::Context, viewport_id: Option<ViewportId>) {
let panel_frame = egui::Frame::new()
.fill(ctx.style().visuals.window_fill())
.stroke(ctx.style().visuals.widgets.noninteractive.fg_stroke);
CentralPanel::default().frame(panel_frame).show(ctx, |ui| {
let mut ui = ui.new_child(UiBuilder::new().max_rect(ui.max_rect().shrink(4.0)));
self.menu(&mut ui, viewport_id);
self.status_bar(&mut ui, viewport_id);
if let Some(error) = crate::storage::error() {
show_storage_error(&mut ui, error);
return;
} else if let Some(loading) = crate::storage::loading() {
show_storage_loading(&mut ui, loading);
return;
}
if self.project.is_some() {
egui::TopBottomPanel::bottom("scenes_quick")
.resizable(false)
.exact_height(PersistantState::effects_size() + 80.0)
.show_inside(&mut ui, |ui| self.scenes(ui, DeckPath::Quick));
egui::SidePanel::left("config")
.resizable(false)
.exact_width(400.0)
.show_inside(&mut ui, |ui| self.config(ui));
egui::TopBottomPanel::top("preview")
.resizable(true)
.default_height(150.0)
.show_inside(&mut ui, |ui| self.preview(ui));
egui::CentralPanel::default()
.show_inside(&mut ui, |ui| self.scenes(ui, DeckPath::Grid));
} else {
self.no_project(&mut ui);
}
});
}
pub fn new(ui_action_receiver: Receiver<UiAction>) -> Option<Self> {
let (output_sender, gpu_ready_receiver) =
output_sender::start().expect("Could not start output sender");
let app = Self {
startup: true,
output_sender,
gpu_ready_receiver,
timing: Default::default(),
last_always_render_fps_frame: Instant::now(),
blackout: true,
blackout_hold: false,
selected_scene_instance: Default::default(),
project: Default::default(),
project_id: Default::default(),
windows: Default::default(),
other_main_windows: Default::default(),
git_commit_message: Default::default(),
ui_action_receiver,
last_title_update: 0,
midi_output_active: false,
};
Some(app)
}
}
pub struct GitUiState {
pub url: String,
pub use_passphrase: bool,
pub passphrase: String,
}
impl Default for GitUiState {
fn default() -> Self {
let persistant_state = PersistantState::get();
Self {
url: persistant_state.git_url,
use_passphrase: persistant_state.git_credentials.use_passphrase(),
passphrase: Default::default(),
}
}
}