gled-ui 2.33.0

gled's egui interface, shared by the desktop app and the browser
Documentation
use std::sync::Arc;

use crate::ui::{
    asset::CollectionsChangeButton,
    window_common::{gled_window_frame, raise, tool_window_builder},
};
use egui::{
    Color32, ComboBox, Context, Id, Layout, RichText, Vec2, ViewportId, WidgetText,
    scroll_area::ScrollBarVisibility::AlwaysVisible,
};
use gled_model::{
    asset::{Asset, output_device::routing::OutputRouting},
    collection::Collections,
};
use gled_protocol::state::OutputConfig;

#[derive(Debug, Default)]
pub(crate) struct OutputRoutingsWindow {
    open: bool,
    /// Set while the window still has to be brought forward.
    raise: bool,
}

impl OutputRoutingsWindow {
    #[cfg_attr(feature = "profiling", profiling::function)]
    pub(crate) fn update(
        &mut self,
        ctx: &Context,
        collections: &mut Collections,
        output_config: &mut OutputConfig,
    ) {
        if !self.open {
            return;
        }

        let mut hovered_output_routing = None;
        ctx.show_viewport_immediate(
            ViewportId(Id::new("output routings window")),
            tool_window_builder("Output Routings")
                .with_inner_size(Vec2::new(340.0, 500.0))
                .with_min_inner_size(Vec2::new(340.0, 500.0)),
            |ctx, _viewport_class| {
                raise(ctx, &mut self.raise);
                ctx.input(|input| {
                    if input.viewport().close_requested()
                        || crate::ui::window_common::embedded_close_clicked("Output Routings") {
                        self.open = false;
                    }
                });

                gled_window_frame(ctx, "Output Routings", |ui| {
                    if !output_config.routings.is_empty() && ui.button("Clear Output Routings").clicked() {
                        output_config.routings = Default::default();
                    }

                    let used_multiple_times = output_config.routings.output_universes_which_are_used_multiple_times();
                    if !used_multiple_times.is_empty() {
                        ui.colored_label(
                            Color32::RED,
                            RichText::new("Some output universes are used multiple times! This can lead to unexpected results.")
                                .heading(),
                        );
                    }

                    let mut routings = Arc::unwrap_or_clone(output_config.routings.clone());
                    let universes = Arc::unwrap_or_clone(output_config.universes.clone());

                    egui::ScrollArea::vertical()
                        .scroll_bar_visibility(AlwaysVisible)
                        .id_salt("output_scroll")
                        .show(ui, |ui| {
                            ui.with_layout(Layout::top_down_justified(egui::Align::Min), |ui| {
                                // The engine derives the universes from the patch.
                                let patch_universes = universes.clone();
                                routings.remove_old(&patch_universes);

                                if patch_universes.is_empty() {
                                    ui.label(RichText::new(
                                        "No universes available. Patch some fixtures first!",
                                    ));
                                }

                                let mut set_routing = None;
                                let mut set_device = None;

                                for universe in patch_universes.clone() {
                                    ui.label(
                                        RichText::new(format!("Universe: {universe}")).heading(),
                                    );

                                    ui.horizontal(|ui| {
                                        let output_routing =
                                            routings.universe_output_routing(universe);
                                        if output_routing.device.collections_change_button(ui, collections) {
                                            set_device = Some((universe, output_routing.device));
                                        }

                                        if let Some(device) =
                                            output_routing.device.and_then(|id|Asset::get(id, collections))
                                        {
                                            let device_universes = device.data.universes();
                                            if !device_universes.is_empty() {
                                                ComboBox::new(format!("{universe}_universe"), "")
                                                    .selected_text(match output_routing.universe {
                                                        None => WidgetText::from("No universe selected"),
                                                        Some(universe) => {
                                                            let text = WidgetText::from(universe.to_string());
                                                            if used_multiple_times.contains(&(device.id, universe)) {
                                                                text.color(Color32::RED)
                                                            } else {
                                                                text
                                                            }
                                                        }
                                                    })
                                                    .width(150.0)
                                                    .show_ui(ui, |ui| {
                                                        for device_universe in device_universes {
                                                            let res = ui
                                                                .selectable_value(
                                                                    &mut 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) });
                                                            }
                                                            if res
                                                                .changed()
                                                            {
                                                                set_routing = Some((
                                                                    universe,
                                                                    *device_universe,
                                                                ));
                                                            }
                                                        }
                                                    });
                                            }
                                        }
                                    });

                                    ui.separator();
                                }

                                if let Some((mut universe, device)) = set_device {
                                    let mut first = true;
                                    loop {
                                        let output_routing =
                                            routings.universe_output_routing(universe);
                                        if first || output_routing.device.is_none() {
                                            output_routing.device = device;

                                            if let Some(device) = device.and_then(|id|Asset::get(id, collections)) {
                                                let device_universes = device.data.universes();
                                                if device_universes.len() == 1 {
                                                    output_routing.universe =
                                                        Some(device_universes[0]);
                                                    break;
                                                }
                                            };
                                        } else {
                                            break;
                                        }

                                        first = false;
                                        universe += 1;
                                        if !universes.contains(&universe) {
                                            break;
                                        }
                                    }
                                }
                                if let Some((mut universe, mut device_universe)) = set_routing {
                                    let mut first = true;
                                    loop {
                                        let output_routing =
                                            routings.universe_output_routing(universe);
                                        let Some(device) =
                                            output_routing.device.and_then(|id|Asset::get(id, collections))
                                        else {
                                            break;
                                        };
                                        if !device.data.universes().contains(&device_universe) {
                                            break;
                                        }
                                        if first || output_routing.universe.is_none() {
                                            output_routing.universe = Some(device_universe);
                                        } else {
                                            break;
                                        }

                                        first = false;
                                        universe += 1;
                                        device_universe += 1;
                                        if !universes.contains(&universe) {
                                            break;
                                        }
                                    }
                                }

                            });
                        });

                    output_config.routings = Arc::new(routings);
                    output_config.universes = Arc::new(universes);
                });
            },
        );

        crate::ui::frame_requests::hover_output_routing(hovered_output_routing);
    }

    pub(crate) fn open(&mut self) {
        self.open = true;
        self.raise = true;
    }

    pub(crate) fn close(&mut self) {
        self.open = false;
    }
}