use std::{ collections::{HashMap, HashSet}, default::Default, str::FromStr, sync::Arc, }; use crate::{ database::DBOperator, material_tab::MaterialTabViewer, misc::{self, ELEMENTS_PER_PAGE}, models::{ Equipment, EquipmentFilter, EquipmentModalState, LoginModalState, LoginStatus, Material, MaterialCategory, MaterialRow, ModalStatus, ModalWinState, Position, Salary, SalaryModalState, Worker, }, order_tab::OrderTabViewer, recipe_tab::RecipeTabViewer, reports::Reporter, tab::*, worker_tab::{self, *}, }; use bigdecimal::ToPrimitive; use chrono::{Datelike, NaiveDate}; use egui::{TextBuffer, Vec2, mutex::RwLock}; use egui_dock::{DockArea, DockState, Style, TabViewer}; use egui_extras::{Column, TableBuilder}; use sha2::{Digest, Sha256}; use sqlx::types::BigDecimal; pub struct App<'a> { pub login_modal_state: LoginModalState<'a>, pub tree: egui_dock::DockState, pub main_viewer: MainTabViewer, pub db_oper: DBOperator, pub rt: tokio::runtime::Runtime, pub is_admin: bool, } impl Default for App<'_> { fn default() -> Self { let mut tree = egui_dock::DockState::new(vec![ Tab { tab_type: TabTypes::Equipment, title: "Оборудование".to_owned(), }, Tab { tab_type: TabTypes::Material, title: "Сырьё".to_owned(), }, Tab { tab_type: TabTypes::Worker, title: "Сотрудники".to_owned(), }, Tab { tab_type: TabTypes::Salary, title: "Выплаты".to_owned(), }, Tab { tab_type: TabTypes::Recipe, title: "Рецепты".to_owned(), }, Tab { tab_type: TabTypes::Order, title: "Заказы".to_owned(), }, Tab { tab_type: TabTypes::Client, title: "Клиенты".to_owned(), }, Tab { tab_type: TabTypes::Settings, title: "Настройки".to_owned(), }, ]); let main_viewer = MainTabViewer::default(); let rt = tokio::runtime::Runtime::new().unwrap(); Self { tree, main_viewer, login_modal_state: LoginModalState { is_open: true, data: HashMap::from([("login", String::new()), ("password", String::new())]), status: crate::models::LoginStatus::Login, ..Default::default() }, db_oper: rt.block_on(DBOperator::new()), rt, is_admin: false, } } } impl App<'_> { pub fn new(_cc: &eframe::CreationContext<'_>) -> Self { Default::default() } } impl eframe::App for App<'_> { fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { if self.login_modal_state.status == LoginStatus::Finished { ui.horizontal(|ui| { ui.label(self.login_modal_state.data.get("login").unwrap()); if ui.button("Выйти").clicked() { self.login_modal_state.is_open = true; self.login_modal_state.status = LoginStatus::Login; self.login_modal_state.data.insert("login", String::new()); self.login_modal_state .data .insert("password", String::new()); } }); } if self.login_modal_state.is_open { egui::Modal::new(egui::Id::new("login_modal")).show(ui.ctx(), |ui| { ui.label("Пройдите авторизацию:"); ui.add( egui::TextEdit::singleline( self.login_modal_state.data.get_mut("login").unwrap(), ) .hint_text("Введите логин"), ); ui.add( egui::TextEdit::singleline( self.login_modal_state.data.get_mut("password").unwrap(), ) .password(true) .hint_text("Пароль"), ); if self.login_modal_state.fail { ui.label(&self.login_modal_state.fail_message); } ui.vertical_centered(|ui| { if ui.button("Авторизоваться").clicked() || ui.input(|i| i.key_pressed(egui::Key::Enter)) { let uname = self.login_modal_state.data.get("login").unwrap().clone(); self.login_modal_state.fail = false; let mut hashed_pass = Sha256::new(); hashed_pass.update(self.login_modal_state.data.get("password").unwrap()); let pass_hash = hex::encode(hashed_pass.finalize()); if self.rt.block_on(async { self.db_oper .check_password(uname.clone(), pass_hash) .await .unwrap() }) { self.login_modal_state.fail = false; self.login_modal_state.status = crate::models::LoginStatus::Finished; self.login_modal_state.is_open = false; if self.rt.block_on(async { self.db_oper.check_admin(uname.clone()).await.unwrap() }) { self.main_viewer.is_admin = true; } else { self.main_viewer.is_admin = false; } self.main_viewer = MainTabViewer::new(self.main_viewer.is_admin); } else { self.login_modal_state.fail = true; self.login_modal_state.fail_message = "Неверные логин или пароль".to_string(); } } }); }); } DockArea::new(&mut self.tree) .style(Style::from_egui(ui.style().as_ref())) .show_inside(ui, &mut self.main_viewer); } fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { if self.main_viewer.is_dark_theme { ctx.set_visuals(egui::Visuals::dark()); } else { ctx.set_visuals(egui::Visuals::light()); } ctx.set_zoom_factor(self.main_viewer.interface_scale_ratio); } } struct MainTabViewer { workers: Arc>>, equipment: Arc>>, salaries: Arc>>, db_oper: DBOperator, rt: tokio::runtime::Runtime, worker_tabs: WorkerTabViewer, worker_tree: egui_dock::DockState, material_tabs: MaterialTabViewer, material_tree: egui_dock::DockState, recipe_tab: RecipeTabViewer, order_tabs: OrderTabViewer, order_tree: egui_dock::DockState, equipment_modal_state: EquipmentModalState, salary_modal_state: SalaryModalState, is_dark_theme: bool, interface_scale_ratio: f32, equipment_page: i32, salaries_page: i32, workers_page: i32, is_admin: bool, reporter: Reporter, only_hired_workers: bool, } impl Default for MainTabViewer { fn default() -> Self { let rt = tokio::runtime::Runtime::new().unwrap(); let db_oper = rt.block_on(async { DBOperator::new().await }); let elements_per_page = 5; Self { workers: Arc::new(RwLock::new(rt.block_on(async { db_oper .get_workers(0, elements_per_page, false) .await .unwrap() }))), equipment: Arc::new(RwLock::new(rt.block_on(async { db_oper .get_equipment(0, elements_per_page, false) .await .unwrap() }))), salaries: Arc::new(RwLock::new(rt.block_on(async { db_oper.get_salaries(0, elements_per_page).await.unwrap() }))), db_oper, worker_tabs: WorkerTabViewer::default(), worker_tree: egui_dock::DockState::new(vec![ Tab { title: "Работники".to_owned(), tab_type: TabTypes::WorkerList, }, Tab { title: "Должности".to_owned(), tab_type: TabTypes::WorkerPosition, }, Tab { title: "Повышения".to_owned(), tab_type: TabTypes::WorkerPromotion, }, ]), material_tabs: MaterialTabViewer::default(), material_tree: egui_dock::DockState::new(vec![ Tab { title: "Сырьё".to_owned(), tab_type: TabTypes::MaterialList, }, Tab { title: "Тип".to_owned(), tab_type: TabTypes::MaterialTypeList, }, ]), recipe_tab: RecipeTabViewer::default(), order_tabs: OrderTabViewer::default(), order_tree: DockState::new(vec![ Tab { title: "Входящие".to_owned(), tab_type: TabTypes::IncomingOrderList, }, Tab { title: "Исходящие".to_owned(), tab_type: TabTypes::OutcomingOrderList, }, ]), rt, is_dark_theme: true, interface_scale_ratio: 1.2, equipment_modal_state: EquipmentModalState { is_open: false, can_finish: false, equipment: Equipment::default(), status: ModalStatus::Add, filters: EquipmentFilter::default(), }, salary_modal_state: SalaryModalState { is_open: false, can_finish: false, data: HashMap::from([ ("id".to_owned(), String::new()), ("worker_id".to_owned(), String::new()), ("salary_size".to_owned(), String::new()), ("comment".to_owned(), String::new()), ]), status: ModalStatus::Add, workers: HashSet::new(), }, is_admin: false, reporter: Reporter {}, equipment_page: 0, salaries_page: 0, workers_page: 0, only_hired_workers: false, } } } impl MainTabViewer { fn update_salary(&mut self) { self.salaries = Arc::new(RwLock::new(self.rt.block_on(async { self.db_oper .get_salaries( self.salaries_page * misc::ELEMENTS_PER_PAGE, misc::ELEMENTS_PER_PAGE, ) .await .unwrap() }))); } fn update_equipment(&mut self) { self.equipment = Arc::new(RwLock::new(self.rt.block_on(async { self.db_oper .get_equipment( self.equipment_page * misc::ELEMENTS_PER_PAGE, misc::ELEMENTS_PER_PAGE, self.equipment_modal_state.filters.without_written_offs, ) .await .unwrap() }))); } fn update_workers(&mut self) { self.workers = Arc::new(RwLock::new(self.rt.block_on(async { self.db_oper .get_workers( self.workers_page * misc::ELEMENTS_PER_PAGE, ELEMENTS_PER_PAGE, self.only_hired_workers, ) .await .unwrap() }))); } fn get_salary(&self) -> Salary { Salary { id: self .salary_modal_state .data .get("id") .unwrap() .parse::() .unwrap(), worker: self .workers .read() .iter() .find(|w| { &w.id.to_string() == self.salary_modal_state.data.get("worker_id").unwrap() }) .unwrap() .clone(), salary_size: BigDecimal::from_str( self.salary_modal_state.data.get("salary_size").unwrap(), ) .unwrap(), salary_date: chrono::NaiveDate::from_str( self.salary_modal_state.data.get("salary_date").unwrap(), ) .unwrap() .and_hms_opt(0, 0, 0) .unwrap() .and_local_timezone(chrono::Local) .unwrap(), comment: self.salary_modal_state.data.get("comment").unwrap().clone(), } } fn new(is_admin: bool) -> Self { Self { is_admin, worker_tabs: WorkerTabViewer::new(is_admin), material_tabs: MaterialTabViewer::new(is_admin), recipe_tab: RecipeTabViewer::new(is_admin), ..Default::default() } } fn show_equipment(&mut self, ui: &mut egui::Ui) { if self.is_admin { ui.horizontal(|ui| { if ui.button("Добавить").clicked() { self.equipment_modal_state.is_open = true; self.equipment_modal_state.status = ModalStatus::Add; self.equipment_modal_state.equipment = Equipment::default(); } }); } ui.horizontal(|ui| { if ui .add(egui::Checkbox::new( &mut self.equipment_modal_state.filters.without_written_offs, "Без списанных", )) .changed() { self.update_equipment(); }; if ui.button("<-").clicked() { if self.equipment_page > 0 { self.equipment_page -= 1; } self.update_equipment(); } ui.monospace(self.equipment_page.to_string()); if ui.button("->").clicked() { if self.equipment.read().len() == 5 { self.equipment_page += 1; self.update_equipment(); } } }); TableBuilder::new(ui) .striped(true) .resizable(false) .vscroll(false) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto()) .column(Column::auto()) .column(Column::auto()) .column(Column::auto()) .column(Column::auto()) .column(Column::auto()) .sense(egui::Sense::click()) .header(30.0, |mut header| { header.col(|ui| { ui.heading("Название"); }); header.col(|ui| { ui.heading("Инв. номер"); }); header.col(|ui| { ui.heading("Ответственный"); }); header.col(|ui| { ui.heading("Дата последнего\nтехобслуживания"); }); header.col(|ui| { ui.heading("Списано"); }); }) .body(|mut body| { body.ui_mut().style_mut().interaction.selectable_labels = false; for eq in self.equipment.read().clone().iter() { body.row(20.0, |mut row| { row.col(|ui| { ui.label(&eq.name); }); row.col(|ui| { ui.label(&eq.inv_number); }); row.col(|ui| { ui.label(&eq.worker.full_name); }); row.col(|ui| { ui.label(eq.maintenance_date.date_naive().to_string()); }); row.col(|ui| { ui.label(if eq.is_written_off { "Да" } else { "Нет" }); }); row.col(|ui| { if self.is_admin { if ui.button("Списать").clicked() { self.equipment_modal_state.is_open = true; self.equipment_modal_state.status = ModalStatus::Remove; self.equipment_modal_state.equipment = eq.clone(); } } }); if row.response().double_clicked() { self.equipment_modal_state.is_open = true; self.equipment_modal_state.status = ModalStatus::Edit; self.equipment_modal_state.equipment = eq.clone(); } }); } }); if self.equipment_modal_state.is_open { egui::Modal::new(egui::Id::new("process_equipment_modal")).show(ui.ctx(), |ui| { if ui.button("Закрыть").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) { self.equipment_modal_state.is_open = false; } if self.equipment_modal_state.status != ModalStatus::Remove { egui::Grid::new("process_equipment_grid").show(ui, |ui| { ui.label("Название"); ui.add_sized( Vec2::new(200.0, 20.0), egui::TextEdit::singleline( &mut self.equipment_modal_state.equipment.name, ) .hint_text("Название оборудования"), ); ui.end_row(); ui.label("Инв. номер"); ui.add( egui::TextEdit::singleline( &mut self.equipment_modal_state.equipment.inv_number, ) .hint_text("НН-00"), ); ui.end_row(); ui.label("Ответственный"); egui::ComboBox::new("process_equipment_combobox", "") .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) .height(100.0) .selected_text( self.equipment_modal_state .equipment .worker .full_name .clone(), ) .show_ui(ui, |ui| { for wkrk in self.workers.read().clone().iter() { ui.selectable_value( &mut self.equipment_modal_state.equipment.worker, wkrk.clone(), &wkrk.full_name, ); } ui.horizontal(|ui| { if ui.button("<-").clicked() { if self.workers_page > 0 { self.workers_page -= 1; self.update_workers(); }; } ui.monospace(self.workers_page.to_string()); if ui.button("->").clicked() { if self.workers.read().len().to_i32().unwrap() == misc::ELEMENTS_PER_PAGE { self.workers_page += 1; self.update_workers(); } } }) }); ui.end_row(); ui.label("Дата последнего\nтехобслуживания"); let ndate = self .equipment_modal_state .equipment .maintenance_date .date_naive(); let mut date = misc::naivedate_to_jiff(ndate); ui.add(egui_extras::DatePickerButton::new(&mut date)); if misc::naivedate_to_jiff( self.equipment_modal_state .equipment .maintenance_date .date_naive(), ) != date { self.equipment_modal_state.equipment.maintenance_date = misc::jiff_to_naivedate(date) .and_hms_opt(0, 0, 0) .unwrap() .and_local_timezone(chrono::Local) .unwrap(); } }); } else { ui.label("Вы уверены, что хотите удалить:"); ui.strong(format!( "{} | {}?", self.equipment_modal_state.equipment.name, self.equipment_modal_state.equipment.inv_number )); } let btext = match &self.equipment_modal_state.status { ModalStatus::Add => "Добавить", ModalStatus::Edit => "Сохранить", ModalStatus::Remove => "Списать", }; match &self.equipment_modal_state.status { ModalStatus::Remove => { self.equipment_modal_state.can_finish = true; } _ => { self.equipment_modal_state.can_finish = self.equipment_modal_state.equipment.name.len() > 0 && self.equipment_modal_state.equipment.inv_number.len() > 0 && self.equipment_modal_state.equipment.worker.id != 0; } } let resp = ui.add_enabled( self.equipment_modal_state.can_finish, egui::Button::new(btext), ); if resp.clicked() { match &self.equipment_modal_state.status { ModalStatus::Add => self.rt.block_on(async { self.db_oper .add_equipment(self.equipment_modal_state.equipment.clone()) .await .unwrap(); }), ModalStatus::Edit => self.rt.block_on(async { self.db_oper .update_equipment(self.equipment_modal_state.equipment.clone()) .await .unwrap(); }), ModalStatus::Remove => { self.rt.block_on(async { self.db_oper .write_off_equipment(self.equipment_modal_state.equipment.id) .await .unwrap(); }); } } self.equipment_modal_state.is_open = false; self.update_equipment(); } }); } } fn show_worker(&mut self, ui: &mut egui::Ui) { let id = ui.make_persistent_id("WorkerMenu"); DockArea::new(&mut self.worker_tree) .style(Style::from_egui(ui.style().as_ref())) .id(id) .show_inside(ui, &mut self.worker_tabs); } fn show_material(&mut self, ui: &mut egui::Ui) { let id = ui.make_persistent_id("MaterialMenu"); DockArea::new(&mut self.material_tree) .id(id) .show_inside(ui, &mut self.material_tabs); } fn show_order(&mut self, ui: &mut egui::Ui) { let id = ui.make_persistent_id("OrderMenu"); DockArea::new(&mut self.order_tree) .id(id) .show_inside(ui, &mut self.order_tabs); } fn show_salary(&mut self, ui: &mut egui::Ui) { /* * Чё надо: * 1. Список платежей. */ if self.is_admin && ui.button("Добавить").clicked() { self.salary_modal_state .data .insert("id".to_owned(), String::new()); self.salary_modal_state .data .insert("worker_id".to_owned(), String::new()); self.salary_modal_state .data .insert("salary_size".to_owned(), String::new()); self.salary_modal_state .data .insert("salary_date".to_owned(), String::new()); self.salary_modal_state .data .insert("comment".to_owned(), String::new()); self.salary_modal_state.workers.clear(); self.salary_modal_state.is_open = true; self.salary_modal_state.status = ModalStatus::Add; } TableBuilder::new(ui) .striped(true) .columns(Column::auto(), 5) .sense(egui::Sense::click()) .vscroll(false) .header(30.0, |mut header| { header.col(|ui| { ui.heading("Имя сотрудника"); }); header.col(|ui| { ui.heading("Размер"); }); header.col(|ui| { ui.heading("Дата"); }); header.col(|ui| { ui.heading("Примечание"); }); }) .body(|mut body| { body.ui_mut().style_mut().interaction.selectable_labels = false; for sal in (self.salaries.read().iter().clone()) { body.row(25.0, |mut row| { row.col(|ui| { ui.label(&sal.worker.full_name); }); row.col(|ui| { ui.label(&sal.salary_size.to_string()); }); row.col(|ui| { ui.label(&sal.salary_date.date_naive().to_string()); }); row.col(|ui| { let text: String = sal.comment.chars().take(15).collect(); ui.label(text); }); row.col(|ui| { if ui.button("Удалить").clicked() { self.salary_modal_state .data .insert("id".to_owned(), sal.id.to_string()); self.salary_modal_state .data .insert("worker_id".to_owned(), sal.worker.id.to_string()); self.salary_modal_state .data .insert("salary_size".to_owned(), sal.salary_size.to_string()); self.salary_modal_state.data.insert( "salary_date".to_owned(), sal.salary_date.date_naive().to_string(), ); self.salary_modal_state .data .insert("comment".to_owned(), sal.comment.clone()); self.salary_modal_state.is_open = true; self.salary_modal_state.status = ModalStatus::Remove; } }); if row.response().double_clicked() { self.salary_modal_state .data .insert("id".to_owned(), sal.id.to_string()); self.salary_modal_state .data .insert("worker_id".to_owned(), sal.worker.id.to_string()); self.salary_modal_state .data .insert("salary_size".to_owned(), sal.salary_size.to_string()); self.salary_modal_state.data.insert( "salary_date".to_owned(), sal.salary_date.date_naive().to_string(), ); self.salary_modal_state .data .insert("comment".to_owned(), sal.comment.clone()); self.salary_modal_state.is_open = true; self.salary_modal_state.status = ModalStatus::Edit; } }); } }); if self.salary_modal_state.is_open { egui::Modal::new(egui::Id::new("salary_modal")).show(ui.ctx(), |ui| { ui.horizontal(|ui| { if ui.button("Закрыть").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) { self.salary_modal_state.is_open = false; } if ui.button("Отчёт").clicked() { self.reporter.payment_report(self.get_salary().clone()); } }); if self.salary_modal_state.status != ModalStatus::Remove { egui::Grid::new("process_salary_grid").show(ui, |ui| { let size = ui.available_size(); if self.salary_modal_state.status == ModalStatus::Add { ui.label("Сотрудник(-и):"); egui::ComboBox::new("process_salary_grid_combobox", "") .selected_text(format!( "{} выбран(-о)", self.salary_modal_state.workers.len() )) .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) .show_ui(ui, |ui| { for wrkr in self.workers.read().clone().iter() { let mut checked = self.salary_modal_state.workers.contains(wrkr); let res = ui.add(egui::Checkbox::new( &mut checked, &wrkr.full_name, )); if res.clicked() { if checked { self.salary_modal_state .workers .insert(wrkr.clone()); } else { self.salary_modal_state.workers.remove(wrkr); } } } }); ui.end_row(); } ui.label("Размер платежа:"); ui.add_sized( size, egui::TextEdit::singleline( self.salary_modal_state.data.get_mut("salary_size").unwrap(), ), ); ui.end_row(); ui.label("Дата платежа:"); ui.add_sized( size, egui::TextEdit::singleline( self.salary_modal_state.data.get_mut("salary_date").unwrap(), ) .hint_text("1970-01-01"), ); ui.end_row(); ui.label("Примечание:"); ui.add_sized( size, egui::TextEdit::multiline( self.salary_modal_state.data.get_mut("comment").unwrap(), ), ); ui.end_row(); }); } else { let wname = self .workers .read() .clone() .iter() .find(|w| { &w.id.to_string() == self.salary_modal_state.data.get("worker_id").unwrap() }) .unwrap() .full_name .clone(); ui.label("Вы уверены, что хотите удалить запись о выплате:"); ui.label(format!( "{}: {} | ${}", self.salary_modal_state.data.get("salary_date").unwrap(), wname, self.salary_modal_state.data.get("salary_size").unwrap() )); } let btext = match self.salary_modal_state.status { ModalStatus::Add => "Добавить", ModalStatus::Edit => "Сохранить", ModalStatus::Remove => "Удалить", }; if ui.button(btext).clicked() { match self.salary_modal_state.status { ModalStatus::Add => { self.rt.block_on(async { for w in self.salary_modal_state.workers.iter() { self.db_oper .add_salary( w.id, chrono::NaiveDate::from_str( self.salary_modal_state .data .get("salary_date") .unwrap(), ) .unwrap() .and_hms_opt(0, 0, 0) .unwrap() .and_local_timezone(chrono::Local) .unwrap(), BigDecimal::from_str( self.salary_modal_state .data .get("salary_size") .unwrap(), ) .unwrap(), Some( self.salary_modal_state .data .get("comment") .unwrap() .clone(), ), ) .await .ok(); } }); } ModalStatus::Edit => { let sal = Salary { id: self .salary_modal_state .data .get("id") .unwrap() .parse::() .unwrap(), worker: self .workers .read() .iter() .find(|w| { &w.id.to_string() == self .salary_modal_state .data .get("worker_id") .unwrap() }) .unwrap() .clone(), salary_size: BigDecimal::from_str( self.salary_modal_state.data.get("salary_size").unwrap(), ) .unwrap(), salary_date: chrono::NaiveDate::parse_from_str( self.salary_modal_state.data.get("salary_date").unwrap(), "%Y-%m-%d", ) .unwrap() .and_hms_opt(0, 0, 0) .unwrap() .and_local_timezone(chrono::Local) .unwrap(), comment: self .salary_modal_state .data .get("comment") .unwrap() .clone(), }; self.rt.block_on(async { self.db_oper.update_salary(sal).await; }); } ModalStatus::Remove => self.rt.block_on(async { self.db_oper .remove_salary( self.salary_modal_state .data .get("id") .unwrap() .parse::() .unwrap(), ) .await; }), } self.salary_modal_state.is_open = false; self.update_salary(); } }); } /* * 2. Возможность добавить платежи. * 3. Возможность назначить платежи сразу нескольким сотрудникам. * 4. Возможность отменить платежи. */ } fn show_settings(&mut self, ui: &mut egui::Ui) { ui.checkbox(&mut self.is_dark_theme, "Тёмная тема"); ui.add( egui::Slider::new(&mut self.interface_scale_ratio, 1.0..=2.0) .text("Масштаб интерфейса"), ); if ui.button("Сбросить Масштаб интерфейса").clicked() { self.interface_scale_ratio = 1.2; } } fn show_recipe(&mut self, ui: &mut egui::Ui) { self.recipe_tab.show_recipe_list(ui); } } impl egui_dock::TabViewer for MainTabViewer { type Tab = Tab; fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { (&*tab.title).into() } fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { match &tab.tab_type { TabTypes::Equipment => { &self.show_equipment(ui); } TabTypes::Worker => { &self.show_worker(ui); } TabTypes::Salary => { &self.show_salary(ui); } TabTypes::Material => { &self.show_material(ui); } TabTypes::Settings => { &self.show_settings(ui); } TabTypes::Recipe => { &self.show_recipe(ui); } TabTypes::Order => { &self.show_order(ui); } _ => { ui.label("Welcome to Avalonia!"); } } } fn allowed_in_windows(&self, _tab: &mut Self::Tab) -> bool { TABS_CAN_BE_WINDOWS } fn is_closeable(&self, _tab: &Self::Tab) -> bool { false } fn id(&mut self, tab: &mut Self::Tab) -> egui::Id { egui::Id::new(&tab.title) } }