mod action_converters;
mod color_editor;
mod copy_dialog;
mod editor;
mod feedback_lines;
mod intent;
mod learning;
mod list_state;
mod midi_details;
mod pickers;
mod rule_card;
mod rule_list;
mod rules;
mod side_tabs;
mod status_line;
mod test_panel;
mod token_editors;
mod top_bar;
mod value_source_converters;
mod widgets;
use crate::midi_learn::MidiPortDiagnostics;
use crate::{
midi_learn::LearnState,
ui::{
asset_tree::{AssetTree, TREE_WIDTH, TreeSelection},
window_common::{gled_window_frame, raise, tool_window_builder},
},
};
use editor::{EditorContext, EditorState, midi_controller_editor};
use egui::epaint::text::{LayoutJob, TextFormat};
use egui::{Align, Context, FontSelection, Id, Ui, Vec2, ViewportId};
use egui_phosphor_icons::Icon;
use gled_midi::monitor::label;
use gled_model::{asset::midi_controller::MidiController, collection::Collections};
use gled_protocol::midi::MidiMonitorEvent;
use gled_protocol::midi::TestCommand;
use kanal::{Receiver, Sender};
use std::collections::{HashMap, HashSet, VecDeque};
const MAX_EVENTS: usize = 1000;
fn iconized(ui: &Ui, icon: Icon, text: &str) -> LayoutJob {
let mut layout_job = LayoutJob::default();
icon.regular().append_to(
&mut layout_job,
ui.style(),
FontSelection::Default,
Align::Center,
);
layout_job.append(text, 0.0, TextFormat::default());
layout_job
}
#[derive(Debug, Default)]
pub(crate) struct MidiControllersWindow {
open: bool,
raise: bool,
dirty: bool,
tree: AssetTree<MidiController>,
editor: EditorState,
events: VecDeque<MidiMonitorEvent>,
test_state_by_controller:
HashMap<gled_model::asset_id::AssetId<MidiController>, MidiControllerTestState>,
test_output_released: bool,
}
#[derive(Debug, Clone, Default)]
struct MidiControllerTestState {
selected_output_port: Option<String>,
value_preview_binding_indices: HashSet<usize>,
value_preview_binding_indices_previous: HashSet<usize>,
}
impl MidiControllersWindow {
fn poll(&mut self, receiver: &Receiver<MidiMonitorEvent>) {
while let Ok(Some(event)) = receiver.try_recv() {
if self.events.len() >= MAX_EVENTS {
self.events.pop_front();
}
self.events.push_back(event);
}
}
pub(crate) fn diagnostics(&self) -> Vec<MidiPortDiagnostics> {
let mut by_port: HashMap<String, MidiPortDiagnostics> = HashMap::new();
for event in &self.events {
let entry =
by_port
.entry(event.port_name.clone())
.or_insert_with(|| MidiPortDiagnostics {
port_name: event.port_name.clone(),
..Default::default()
});
match event.label.as_str() {
label::INPUT_CONNECTED => entry.input_connected = true,
label::OUTPUT_CONNECTED => entry.output_connected = true,
label::INPUT_CONNECT_FAILED => entry.input_connect_failed += 1,
label::OUTPUT_CONNECT_FAILED => entry.output_connect_failed += 1,
label::MIDI => entry.message_count += 1,
_ => {}
}
}
let mut list: Vec<_> = by_port.into_values().collect();
list.sort_by(|a, b| a.port_name.cmp(&b.port_name));
list
}
#[cfg_attr(feature = "profiling", profiling::function)]
pub(crate) fn update(
&mut self,
ctx: &Context,
project: &mut Option<gled_model::asset::project::Project>,
collections: &mut Collections,
receiver: &Receiver<MidiMonitorEvent>,
test_command_sender: &Sender<TestCommand>,
learn_state: &mut LearnState,
) {
if self.open {
crate::ui::frame_requests::watch_midi();
}
learn_state.set_streaming_enabled(self.open);
self.poll(receiver);
if !self.open {
self.release_test_output(test_command_sender);
return;
}
self.test_output_released = false;
let diagnostics = self.diagnostics();
ctx.show_viewport_immediate(
ViewportId(Id::new("midi controllers window")),
tool_window_builder("MIDI Controllers")
.with_inner_size(Vec2::new(1400.0, 740.0))
.with_min_inner_size(Vec2::new(1240.0, 740.0)),
|ctx, _viewport_class| {
raise(ctx, &mut self.raise);
ctx.input(|input| {
if input.viewport().close_requested()
|| crate::ui::window_common::embedded_close_clicked("MIDI Controllers")
{
self.open = false;
learn_state.set_streaming_enabled(false);
}
});
gled_window_frame(ctx, "MIDI Controllers", |ui| {
egui::Panel::left("midi controllers tree")
.exact_size(TREE_WIDTH)
.resizable(false)
.show(ui, |ui| {
if self.tree.show(
ui,
ui.make_persistent_id("midi_controllers_tree"),
collections,
) {
self.dirty = false;
}
});
egui::CentralPanel::default().show(ui, |ui| {
self.tree.common_settings(ui, &mut self.dirty, collections);
match self.tree.selected() {
TreeSelection::Asset(controller) => {
ui.separator();
let others: Vec<OtherController> = collections
.get::<MidiController>()
.map(|collection| {
collection
.shared()
.filter(|other| other.id != controller.id)
.map(|other| OtherController {
id: other.id,
name: other.name().to_owned(),
ports: other.data.default_for_ports.clone(),
})
.collect()
})
.unwrap_or_default();
let mut take_over = None;
let test_state = self
.test_state_by_controller
.entry(controller.id)
.or_default();
midi_controller_editor(
ui,
controller,
&mut self.editor,
EditorContext {
test_state,
test_command_sender,
dirty: &mut self.dirty,
project,
diagnostics: &diagnostics,
learn_state,
events: &mut self.events,
others: &others,
take_over: &mut take_over,
},
);
if let Some((other, port)) = take_over {
stop_using_port(collections, other, &port);
}
}
_ => {
for state in self.test_state_by_controller.values_mut() {
state.value_preview_binding_indices.clear();
}
}
}
});
});
},
);
}
pub(crate) fn open(&mut self) {
self.open = true;
self.raise = true;
}
fn release_test_output(&mut self, test_command_sender: &Sender<TestCommand>) {
if !self.test_output_released {
let _ = test_command_sender.send(TestCommand::UnsetTestDevice);
self.test_output_released = true;
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct OtherController {
pub(crate) id: gled_model::asset_id::AssetId<MidiController>,
pub(crate) name: String,
pub(crate) ports: Vec<String>,
}
fn stop_using_port(
collections: &mut Collections,
id: gled_model::asset_id::AssetId<MidiController>,
port: &str,
) {
use crate::assets::AssetEdits;
let Some(other) = gled_model::asset::Asset::get(id, collections) else {
return;
};
let mut other = (*other).clone();
other
.data
.default_for_ports
.retain(|other_port| other_port != port);
other.save(collections);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_closed_window_stops_the_test_output_once() {
let (sender, receiver) = kanal::unbounded();
let mut window = MidiControllersWindow::default();
window.release_test_output(&sender);
window.release_test_output(&sender);
assert_eq!(receiver.len(), 1);
window.test_output_released = false;
window.release_test_output(&sender);
assert_eq!(receiver.len(), 2);
}
}