mod midi_mappings;
mod settings_menu;
use crate::midi_learn::MidiPortDiagnostics;
use crate::ui::asset::CollectionsChangeButton;
use crate::ui::window_common::{gled_window_frame, raise, tool_window_builder};
use chrono::Local;
use egui::{
Button, ComboBox, Context, DragValue, Id, Layout, RichText, Slider, Ui, Vec2, ViewportId,
WidgetText,
};
use egui_phosphor_icons::icons;
use gled_model::asset::project::Project;
use gled_model::asset::{Asset, output_device::routing::OutputRouting};
use gled_model::collection::Collections;
use gled_model::patching::fixture::MAX_UNIVERSE;
use gled_protocol::action::UiAction;
use gled_protocol::state::ServerStatus;
use midi_mappings::{
collect_midi_controller_rows, collect_midi_mapping_options, midi_mapping_settings,
};
use settings_menu::SettingsMenu;
#[derive(Debug, Default)]
pub(crate) struct ExternalDeviceSettings {
open: bool,
raise: bool,
selected_submenu: String,
}
impl ExternalDeviceSettings {
#[cfg_attr(feature = "profiling", profiling::function)]
pub(crate) fn update(
&mut self,
ctx: &Context,
project: &mut Option<Project>,
collections: &mut Collections,
midi_diagnostics: &[MidiPortDiagnostics],
status: &ServerStatus,
) {
if !self.open {
return;
}
let mut project = if let Some(project) = project {
project
} else {
return;
};
ctx.show_viewport_immediate(
ViewportId(Id::new("external devices settings window")),
tool_window_builder("External Devices")
.with_inner_size(Vec2::new(800.0, 500.0))
.with_min_inner_size(Vec2::new(800.0, 500.0))
.with_resizable(false)
.with_minimize_button(false)
.with_maximize_button(true),
|ctx, _viewport_class| {
raise(ctx, &mut self.raise);
ctx.input(|input| {
if input.viewport().close_requested()
|| crate::ui::window_common::embedded_close_clicked("External Devices")
{
self.open = false;
}
});
gled_window_frame(ctx, "External Devices", |ui| {
let midi_mapping_rows = collect_midi_controller_rows(project, midi_diagnostics);
let midi_mapping_options = collect_midi_mapping_options(collections);
let settings_menu = SettingsMenu::new(&mut project, &mut self.selected_submenu)
.add_submenu("Audio Input".into(), |ui, project| {
audio_input_settings(ui, project)
})
.add_submenu("MIDI Mappings".into(), |ui, project| {
midi_mapping_settings(
ui,
project,
&midi_mapping_rows,
&midi_mapping_options,
)
})
.add_submenu("Artnet Bridge".into(), |ui, project| {
artnet_bridge_settings(ui, project, collections, status)
})
.add_submenu("Artnet Control".into(), |ui, project| {
artnet_control_input_settings(ui, project)
})
.add_submenu("Artnet Trigger".into(), |ui, project| {
artnet_trigger_settings(ui, project, status)
})
.add_submenu("OSC Input".into(), |ui, project| osc_settings(ui, project));
ui.add(settings_menu);
});
},
);
}
pub(crate) fn open(&mut self) {
self.open = true;
self.raise = true;
}
pub(crate) fn close(&mut self) {
self.open = false;
}
}
fn audio_input_settings(ui: &mut Ui, state: &mut Project) {
let project = state;
ui.heading("Audio Input Device");
let mut selected = project.audio_input_device.clone();
let old_selected = selected.clone();
ui.selectable_value(&mut selected, None, "None");
for (id, label) in crate::ui::frame_requests::devices().audio_inputs {
ui.selectable_value(&mut selected, Some(id), label);
}
if selected != old_selected {
tracing::info!("Audio input device changed to {:?}", selected);
{
UiAction::SetAudioDevice(selected).enqueue();
}
}
}
fn artnet_control_input_settings(ui: &mut Ui, project: &mut Project) {
let config = &mut project.artnet_control_config;
ui.heading("Artnet Control");
ui.checkbox(&mut config.active, "Active");
ui.add(DragValue::new(&mut config.universe).range(0..=MAX_UNIVERSE));
}
fn artnet_bridge_settings(
ui: &mut Ui,
project: &mut Project,
collections: &mut Collections,
status: &ServerStatus,
) {
let mut config = status.artnet.clone();
ui.heading("Artnet Bridge");
ui.add_space(3.0);
if config.bridge.is_empty() {
ui.label("No artnet data received..");
}
egui::ScrollArea::vertical()
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible)
.id_salt("artnet_bridge_output_scroll")
.show(ui, |ui| {
ui.with_layout(Layout::top_down_justified(egui::Align::Min), |ui| {
let mut hovered_output_routing = None;
for (universe, bridge) in config.bridge.iter_mut() {
ui.label(RichText::new(format!("Universe: {universe}")).heading());
ui.horizontal(|ui| {
ui.label(format!("Updates: {}", bridge.updates));
ui.label(
bridge
.last_data_at
.with_timezone(&Local)
.format("(Last: %H:%M:%S)")
.to_string(),
);
bridge
.output_routing
.device
.collections_change_button(ui, collections);
if let Some(device) = bridge
.output_routing
.device
.and_then(|asset_id| Asset::get(asset_id, collections))
{
let device_universes = device.data.universes();
if !device_universes.is_empty() {
ComboBox::new(format!("{universe}_universe"), "")
.selected_text(match bridge.output_routing.universe {
None => WidgetText::from("No universe selected"),
Some(universe) => WidgetText::from(universe.to_string()),
})
.width(150.0)
.show_ui(ui, |ui| {
for device_universe in device_universes {
let res = ui.selectable_value(
&mut bridge.output_routing.universe,
Some(*device_universe),
device_universe.to_string(),
);
if res.hovered() {
hovered_output_routing = Some(OutputRouting {
device: Some(device.id),
universe: Some(*device_universe),
});
}
}
});
}
ComboBox::new(format!("{universe}_merge"), "")
.selected_text(match bridge.htp {
true => "HTP",
false => "LTP",
})
.width(50.0)
.show_ui(ui, |ui| {
ui.selectable_value(&mut bridge.htp, true, "HTP");
ui.selectable_value(&mut bridge.htp, false, "LTP");
});
if ui.add(Button::new(icons::X)).clicked() {
bridge.output_routing = OutputRouting::default();
}
}
});
}
crate::ui::frame_requests::hover_output_routing(hovered_output_routing);
});
});
if config != status.artnet {
project.artnet_config = config;
}
}
fn artnet_trigger_settings(ui: &mut Ui, project: &mut Project, status: &ServerStatus) {
let mut config = status.artnet.clone();
ui.heading("Artnet Trigger");
ui.add_space(3.0);
ui.separator();
ui.label("Universe");
ui.add(Slider::new(&mut config.universe, 0..=MAX_UNIVERSE));
if config != status.artnet {
project.artnet_config = config;
}
}
fn osc_settings(ui: &mut Ui, project: &mut Project) {
let osc_config = project.osc_config;
ui.heading("OSC");
let mut active = osc_config.active;
if ui.checkbox(&mut active, "Active").changed() {
UiAction::SetProjectOscActive(active).enqueue();
}
ui.horizontal(|ui| {
ui.label("Port");
let mut port = osc_config.port;
let response = ui.add(DragValue::new(&mut port).range(1..=65535));
if (response.lost_focus() || response.drag_stopped()) && port != osc_config.port {
UiAction::SetProjectOscPort(port).enqueue();
}
});
}