diff --git a/lab2/Cargo.toml b/lab2/Cargo.toml index c1a652d..11c21fd 100644 --- a/lab2/Cargo.toml +++ b/lab2/Cargo.toml @@ -8,6 +8,7 @@ workspace = true [dependencies] rand = "0.9.2" eframe = { version = "0.33.3", default-features = false, features = ["default_fonts", "glow"] } +egui_extras = { version = "0.33.3" } [profile.dev.package.eframe] opt-level = 2 diff --git a/lab2/src/main.rs b/lab2/src/main.rs index 384521b..9d01fd9 100644 --- a/lab2/src/main.rs +++ b/lab2/src/main.rs @@ -1,4 +1,221 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release mod algo; -fn main() {} + +use crate::algo::BitVector; +use eframe::egui; +use eframe::egui::{Align, Button, Ui}; +use eframe::emath::Numeric; +use egui_extras::{Column, TableBuilder}; +use rand::SeedableRng; +use std::ops::RangeInclusive; + +fn main() -> eframe::Result { + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([640.0, 400.0]), + ..Default::default() + }; + eframe::run_native( + "Annealing Simulation for Chess Queens Task", + options, + Box::new(|_cc| Ok(Box::::default())), + ) +} + +enum UpdatePending { + NoChange, + Add, + Remove(usize), +} + +struct MyApp { + _isFirstFrame: bool, + bitCount: usize, + beta: usize, + attentiveness: f64, + data: Vec, + result: Option>, + columnUpdate: UpdatePending, + rowUpdate: UpdatePending, +} + +impl Default for MyApp { + fn default() -> Self { + return Self { + _isFirstFrame: true, + bitCount: 0, + beta: 1, + attentiveness: 0.5, + data: Vec::new(), + result: None, + columnUpdate: UpdatePending::NoChange, + rowUpdate: UpdatePending::NoChange, + }; + } +} + +fn _slider( + ui: &mut Ui, + name: &str, + storage: &mut T, + range: RangeInclusive, + step: f64, +) { + let label = ui.label(name); + + ui.scope(|ui| { + ui.spacing_mut().slider_width = ui.available_width() + - ui.spacing().interact_size.x + - ui.spacing().button_padding.x * 2.0; + ui.add(egui::Slider::new(storage, range).step_by(step)) + .labelled_by(label.id); + }); +} + +impl eframe::App for MyApp { + fn update(&mut self, ui: &eframe::egui::Context, _frame: &mut eframe::Frame) { + match self.columnUpdate { + UpdatePending::NoChange => {} + UpdatePending::Add => { + self.data = self + .data + .iter() + .map(|oldVec| oldVec.resized(oldVec.len() + 1)) + .collect(); + self.bitCount += 1; + self.columnUpdate = UpdatePending::NoChange; + } + UpdatePending::Remove(i) => { + self.data = self + .data + .iter() + .map(|oldVec| { + let mut newVec = oldVec.resized(oldVec.len() - 1); + for j in i..newVec.len() { + newVec[j] = oldVec[j + 1] + } + return newVec; + }) + .collect(); + + self.bitCount -= 1; + self.columnUpdate = UpdatePending::NoChange; + } + } + match self.rowUpdate { + UpdatePending::NoChange => {} + UpdatePending::Add => { + self.data.push(BitVector::alloc(self.bitCount)); + self.rowUpdate = UpdatePending::NoChange; + } + UpdatePending::Remove(i) => { + self.data.remove(i); + self.rowUpdate = UpdatePending::NoChange; + } + } + + egui::CentralPanel::default().show(ui, |ui| { + ui.add_enabled_ui(matches!(self.result, None), |ui| { + _slider(ui, "beta", &mut self.beta, 1..=10, 1f64); + ui.label(""); + _slider( + ui, + "attentiveness", + &mut self.attentiveness, + 0f64..=1f64, + 0.001, + ); + ui.label(""); + }); + + ui.horizontal(|ui| { + ui.add_enabled_ui(matches!(self.result, None), |ui| { + if ui.button("Add row").clicked() { + self.rowUpdate = UpdatePending::Add; + } + if ui.button("Add column").clicked() { + self.columnUpdate = UpdatePending::Add; + } + }); + ui.separator(); + + ui.add_enabled_ui(matches!(self.result, None), |ui| { + if ui.button("Classify").clicked() { + self.result = Some(algo::adaptiveResonanceTheoryImpl( + self.data.as_slice(), + self.beta, + self.attentiveness, + )) + } + }); + + ui.add_enabled_ui(!matches!(self.result, None), |ui| { + if ui.button("Edit").clicked() { + self.result = None; + } + }); + }); + + ui.label(""); + + let mut tableBuilder = TableBuilder::new(ui) + .striped(true) // Alternating row colors + .resizable(true) + .column(Column::remainder()) + .column(Column::remainder()); + for _ in 0..self.bitCount { + tableBuilder = tableBuilder.column(Column::auto()); + } + tableBuilder + .header(20.0, |mut header| { + header.col(|ui| { + ui.horizontal(|ui| { + ui.label("#"); + }); + }); + header.col(|ui| { + ui.label("Group"); + }); + for i in 0..self.bitCount { + header.col(|ui| { + ui.horizontal(|ui| { + ui.label(i.to_string()); + ui.add_enabled_ui(matches!(self.result, None), |ui| { + if ui.button("-").clicked() { + self.columnUpdate = UpdatePending::Remove(i); + } + }); + }); + }); + } + }) + .body(|body| { + body.rows(20.0, self.data.len(), |mut row| { + let i = row.index(); + row.col(|ui| { + ui.horizontal(|ui| { + if ui.button("-").clicked() { + self.rowUpdate = UpdatePending::Remove(i); + } + ui.label(i.to_string()); + }); + }); + row.col(|ui| { + if let Some(result) = &self.result { + ui.label(result[i].to_string()); + } else { + ui.label("?"); + } + }); + for j in 0..self.bitCount { + row.col(|ui| { + ui.add_enabled_ui(matches!(self.result, None), |ui| { + ui.checkbox(&mut self.data[i][j], ""); + }); + }); + } + }); + }); + }); + } +}