tudelft-xray-sim 1.0.0

simulation library for the modeling assignment in the course 'Software Systems' at the TU Delft.
Documentation
use crate::*;
use eframe::egui;
use log::info;

/// Run a single-plane simulation with three pedals.
///
/// You should provide a type which implements both [`PedalMapper`] and [`ActionLogic`].
/// If you need to initialize the type somehow, do it before you pass it into this function.
///
/// A GUI will show up which allows you to simulate the press and release of each pedal.
/// The state of the frontal plane is visible.
pub fn run_single_plane_sim<T>(logic: T)
where
    T: PedalMapper<Pedals = ThreePedals> + ActionLogic<false> + 'static,
{
    run_sim(logic);
}

/// Run a double-plane simulation with six pedals.
///
/// You should provide a type which implements both [`PedalMapper`] and [`ActionLogic`].
/// If you need to initialize the type somehow, do it before you pass it into this function.
///
/// A GUI will show up which allows you to simulate the press and release of each pedal.
/// The state of both planes is visible.
pub fn run_double_plane_sim<T>(logic: T)
where
    T: PedalMapper<Pedals = SixPedals> + ActionLogic<true> + 'static,
{
    run_sim(logic);
}

fn run_sim<const IS_TWO_PLANE: bool, PEDALS, T>(logic: T)
where
    T: PedalMapper<Pedals = PEDALS> + ActionLogic<IS_TWO_PLANE> + 'static,
    PEDALS: Pedals + 'static,
{
    let native_options = eframe::NativeOptions::default();
    eframe::run_native(
        "X-Ray Simulator",
        native_options,
        Box::new(|cc| Box::new(Simulator::<IS_TWO_PLANE, PEDALS, T>::new(cc, logic))),
    );
}

struct Simulator<const IS_TWO_PLANE: bool, PEDALS: Pedals, T>
where
    T: PedalMapper<Pedals = PEDALS> + ActionLogic<IS_TWO_PLANE>,
{
    logic: T,
    controller: Controller<IS_TWO_PLANE>,
    pedal_states: PEDALS::STATES,
}

impl<const IS_TWO_PLANE: bool, PEDALS: Pedals, T> Simulator<IS_TWO_PLANE, PEDALS, T>
where
    T: PedalMapper<Pedals = PEDALS> + ActionLogic<IS_TWO_PLANE>,
{
    fn new(_cc: &eframe::CreationContext, logic: T) -> Self {
        Self {
            logic,
            controller: Controller {
                frontal: Plane::frontal(),
                lateral: Plane::lateral(),
            },
            pedal_states: PEDALS::STATES::default(),
        }
    }
}

impl<const IS_TWO_PLANE: bool, PEDALS: Pedals, T> eframe::App for Simulator<IS_TWO_PLANE, PEDALS, T>
where
    T: PedalMapper<Pedals = PEDALS> + ActionLogic<IS_TWO_PLANE>,
{
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        // Panel showing status of each plane.
        // Buttons can also be used to modify the plane directly (for debugging).
        egui::SidePanel::right("right_panel").show(ctx, |ui| {
            indicators(ui, &self.controller.frontal);
            if IS_TWO_PLANE {
                ui.separator();
                indicators(ui, &self.controller.lateral);
            }
        });

        // Checkboxes for the pedals.
        egui::CentralPanel::default().show(ctx, |ui| {
            let mut queue = Vec::new();

            // Checkbox for every pedal.
            // Collect requests in queue.
            for (index, &pedal_enum) in PEDALS::variants().iter().enumerate() {
                let pedal = &mut self.pedal_states[index];
                let label = format!("{pedal_enum:?}");
                if ui.checkbox(pedal, &label).changed() {
                    if *pedal {
                        info!("{label} was pressed");
                        if let Some(request) = self.logic.on_press(pedal_enum) {
                            queue.push(request);
                        }
                    } else {
                        info!("{label} was released");
                        if let Some(request) = self.logic.on_release(pedal_enum) {
                            queue.push(request);
                        }
                    }
                }
            }

            // Handle requests in order.
            for request in queue {
                self.logic.handle_request(request, &mut self.controller);
            }
        });
    }
}

fn indicators(ui: &mut egui::Ui, plane: &Plane) {
    ui.heading(format!("{:?} Plane", plane.projection()));

    ui.horizontal(|ui| {
        if plane.active() {
            ui.strong("Active");
            ui.spinner();
        } else {
            ui.weak("Inactive");
        }
    });

    ui.horizontal(|ui| {
        ui.label("Dose");
        let (low, high) = ("Low", "High");
        match plane.dose() {
            Dose::Low => {
                ui.strong(low);
                ui.weak(high)
            }
            Dose::High => {
                ui.weak(low);
                ui.strong(high)
            }
        };
    });

    ui.horizontal(|ui| {
        ui.label("Mode");
        let (video, image) = ("Video", "Image");
        match plane.mode() {
            Mode::Video => {
                ui.strong(video);
                ui.weak(image)
            }
            Mode::Image => {
                ui.weak(video);
                ui.strong(image)
            }
        };
    });
}