|
@@ -0,0 +1,213 @@
|
|
|
|
|
+use eframe::egui;
|
|
|
|
|
+use crate::api::ApiClient;
|
|
|
|
|
+use crate::core::workflows::borrow_flow::BorrowFlow;
|
|
|
|
|
+use crate::core::workflows::return_flow::ReturnFlow;
|
|
|
|
|
+use crate::models::UserInfo;
|
|
|
|
|
+use crate::config::KioskUiSettings;
|
|
|
|
|
+
|
|
|
|
|
+pub struct KioskDashboard {
|
|
|
|
|
+ pub borrow_flow: BorrowFlow,
|
|
|
|
|
+ pub return_flow: ReturnFlow,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl KioskDashboard {
|
|
|
|
|
+ pub fn new() -> Self {
|
|
|
|
|
+ Self {
|
|
|
|
|
+ borrow_flow: BorrowFlow::default(),
|
|
|
|
|
+ return_flow: ReturnFlow::default(),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pub fn show(
|
|
|
|
|
+ &mut self,
|
|
|
|
|
+ ui: &mut egui::Ui,
|
|
|
|
|
+ api_client: &ApiClient,
|
|
|
|
|
+ user: &UserInfo,
|
|
|
|
|
+ config: &KioskUiSettings,
|
|
|
|
|
+ show_osk: &mut bool,
|
|
|
|
|
+ logout_requested: &mut bool,
|
|
|
|
|
+ show_full_ui_requested: &mut bool,
|
|
|
|
|
+ ) {
|
|
|
|
|
+ let avail_rect = ui.available_rect_before_wrap();
|
|
|
|
|
+ // Calculate row heights
|
|
|
|
|
+ // Structure: Row 1 (0.5h), Row 2 (1h), Row 3 (1h), Row 4 (1h)
|
|
|
|
|
+ // Total units = 3.5
|
|
|
|
|
+ let total_units = 3.5;
|
|
|
|
|
+ let unit_height = avail_rect.height() / total_units;
|
|
|
|
|
+ let header_height = unit_height * 0.5;
|
|
|
|
|
+ let row_height = unit_height;
|
|
|
|
|
+
|
|
|
|
|
+ // Kiosk-only header/button text should respect kiosk font_size
|
|
|
|
|
+ let btn_font_size = config.font_size.max(12.0);
|
|
|
|
|
+ let label_font_size = (config.font_size * 0.8).max(12.0);
|
|
|
|
|
+
|
|
|
|
|
+ // --- Row 1: Header (Logout, OSK) ---
|
|
|
|
|
+ ui.allocate_ui_at_rect(
|
|
|
|
|
+ egui::Rect::from_min_size(avail_rect.min, egui::vec2(avail_rect.width(), header_height)),
|
|
|
|
|
+ |ui| {
|
|
|
|
|
+ ui.horizontal(|ui| {
|
|
|
|
|
+ ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
|
|
|
|
+ if ui.add_sized(egui::vec2(120.0, header_height - 20.0), egui::Button::new(egui::RichText::new("Logout").size(btn_font_size))).clicked() {
|
|
|
|
|
+ *logout_requested = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ui.label(egui::RichText::new(format!("Logged in as: {}", user.username)).size(label_font_size));
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
|
|
|
+ if config.enable_osk {
|
|
|
|
|
+ let btn_text = if *show_osk { "Hide Keyboard" } else { "Show Keyboard" };
|
|
|
|
|
+ if ui.add_sized(egui::vec2(150.0, header_height - 20.0), egui::Button::new(egui::RichText::new(btn_text).size(btn_font_size))).clicked() {
|
|
|
|
|
+ *show_osk = !*show_osk;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Content Area (Rows 2, 3, 4)
|
|
|
|
|
+ // If a workflow is open, it takes over the rest of the screen.
|
|
|
|
|
+ // Otherwise, show the Dashboard Buttons.
|
|
|
|
|
+
|
|
|
|
|
+ let content_rect = egui::Rect::from_min_size(
|
|
|
|
|
+ avail_rect.min + egui::vec2(0.0, header_height),
|
|
|
|
|
+ egui::vec2(avail_rect.width(), avail_rect.height() - header_height)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ ui.allocate_ui_at_rect(content_rect, |ui| {
|
|
|
|
|
+ if self.borrow_flow.is_open {
|
|
|
|
|
+ // Apply optional view-level multiplier for reused desktop views
|
|
|
|
|
+ let vscale = config.view_scale_multiplier;
|
|
|
|
|
+ if (vscale - 1.0).abs() > f32::EPSILON {
|
|
|
|
|
+ ui.scope(|ui| {
|
|
|
|
|
+ let mut style: egui::Style = ui.style().as_ref().clone();
|
|
|
|
|
+ for (_ts, font) in style.text_styles.iter_mut() {
|
|
|
|
|
+ font.size *= vscale;
|
|
|
|
|
+ }
|
|
|
|
|
+ style.spacing.item_spacing *= vscale;
|
|
|
|
|
+ style.spacing.button_padding *= vscale;
|
|
|
|
|
+ style.spacing.indent *= vscale;
|
|
|
|
|
+ style.spacing.window_margin = style.spacing.window_margin * vscale;
|
|
|
|
|
+ ui.set_style(style);
|
|
|
|
|
+ self.borrow_flow.show_content(ui, api_client);
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.borrow_flow.show_content(ui, api_client);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if self.return_flow.is_open {
|
|
|
|
|
+ let vscale = config.view_scale_multiplier;
|
|
|
|
|
+ if (vscale - 1.0).abs() > f32::EPSILON {
|
|
|
|
|
+ ui.scope(|ui| {
|
|
|
|
|
+ let mut style: egui::Style = ui.style().as_ref().clone();
|
|
|
|
|
+ for (_ts, font) in style.text_styles.iter_mut() {
|
|
|
|
|
+ font.size *= vscale;
|
|
|
|
|
+ }
|
|
|
|
|
+ style.spacing.item_spacing *= vscale;
|
|
|
|
|
+ style.spacing.button_padding *= vscale;
|
|
|
|
|
+ style.spacing.indent *= vscale;
|
|
|
|
|
+ style.spacing.window_margin = style.spacing.window_margin * vscale;
|
|
|
|
|
+ ui.set_style(style);
|
|
|
|
|
+ self.return_flow.show_content(ui, api_client);
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.return_flow.show_content(ui, api_client);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.show_dashboard_grid(ui, row_height, show_full_ui_requested, user, config, api_client);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fn show_dashboard_grid(
|
|
|
|
|
+ &mut self,
|
|
|
|
|
+ ui: &mut egui::Ui,
|
|
|
|
|
+ row_height: f32,
|
|
|
|
|
+ show_full_ui_requested: &mut bool,
|
|
|
|
|
+ user: &UserInfo,
|
|
|
|
|
+ config: &KioskUiSettings,
|
|
|
|
|
+ api_client: &ApiClient,
|
|
|
|
|
+ ) {
|
|
|
|
|
+ let width = ui.available_width();
|
|
|
|
|
+
|
|
|
|
|
+ // Dashboard grid headings/buttons use base sizes scaled by global DPI
|
|
|
|
|
+ let text_scale = ui.ctx().pixels_per_point();
|
|
|
|
|
+ let base_font_size = 14.0 * text_scale;
|
|
|
|
|
+ let heading_size = 24.0 * text_scale;
|
|
|
|
|
+
|
|
|
|
|
+ // --- Row 2: Borrowing ---
|
|
|
|
|
+ ui.allocate_ui(egui::vec2(width, row_height), |ui| {
|
|
|
|
|
+ ui.vertical(|ui| {
|
|
|
|
|
+ ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
|
|
|
|
|
+ ui.heading(egui::RichText::new("Borrowing").size(heading_size));
|
|
|
|
|
+ ui.add_space(10.0);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ui.horizontal(|ui| {
|
|
|
|
|
+ let num_buttons = 3.0;
|
|
|
|
|
+ let spacing = ui.spacing().item_spacing.x;
|
|
|
|
|
+ let total_spacing = spacing * (num_buttons - 1.0);
|
|
|
|
|
+ let btn_width = (width - total_spacing - 20.0) / num_buttons;
|
|
|
|
|
+ let btn_size = egui::vec2(btn_width, row_height - 80.0);
|
|
|
|
|
+
|
|
|
|
|
+ // Center buttons horizontally
|
|
|
|
|
+ let total_width = btn_width * num_buttons + total_spacing;
|
|
|
|
|
+ let margin = (width - total_width) / 2.0;
|
|
|
|
|
+ if margin > 0.0 { ui.add_space(margin); }
|
|
|
|
|
+
|
|
|
|
|
+ if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Check Out").size(base_font_size))).clicked() {
|
|
|
|
|
+ self.borrow_flow.open(api_client);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Check In").size(base_font_size))).clicked() {
|
|
|
|
|
+ self.return_flow.open(api_client);
|
|
|
|
|
+ }
|
|
|
|
|
+ ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Manage").size(base_font_size)));
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ui.separator();
|
|
|
|
|
+
|
|
|
|
|
+ // --- Row 3: Inventory ---
|
|
|
|
|
+ ui.allocate_ui(egui::vec2(width, row_height), |ui| {
|
|
|
|
|
+ ui.vertical(|ui| {
|
|
|
|
|
+ ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
|
|
|
|
|
+ ui.heading(egui::RichText::new("Inventory").size(heading_size));
|
|
|
|
|
+ ui.add_space(10.0);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ui.horizontal(|ui| {
|
|
|
|
|
+ let num_buttons = if user.power >= config.minimum_power_level_for_full_ui { 5.0 } else { 4.0 };
|
|
|
|
|
+ let spacing = ui.spacing().item_spacing.x;
|
|
|
|
|
+ let total_spacing = spacing * (num_buttons - 1.0);
|
|
|
|
|
+ let btn_width = (width - total_spacing - 20.0) / num_buttons;
|
|
|
|
|
+ let btn_size = egui::vec2(btn_width, row_height - 80.0);
|
|
|
|
|
+
|
|
|
|
|
+ // Center buttons horizontally
|
|
|
|
|
+ let total_width = btn_width * num_buttons + total_spacing;
|
|
|
|
|
+ let margin = (width - total_width) / 2.0;
|
|
|
|
|
+ if margin > 0.0 { ui.add_space(margin); }
|
|
|
|
|
+
|
|
|
|
|
+ ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Add").size(base_font_size)));
|
|
|
|
|
+ ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Move").size(base_font_size)));
|
|
|
|
|
+ ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("See").size(base_font_size)));
|
|
|
|
|
+ ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Print Label").size(base_font_size)));
|
|
|
|
|
+
|
|
|
|
|
+ if user.power >= config.minimum_power_level_for_full_ui {
|
|
|
|
|
+ if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Show Full UI").size(base_font_size))).clicked() {
|
|
|
|
|
+ *show_full_ui_requested = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ui.separator();
|
|
|
|
|
+
|
|
|
|
|
+ // --- Row 4: Blank ---
|
|
|
|
|
+ ui.allocate_ui(egui::vec2(width, row_height), |_ui| {
|
|
|
|
|
+ // Blank
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|