From f295dc7a1275fed1ca75bec0fdfd596c28d6d0e7 Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Fri, 20 Feb 2026 16:42:17 +0300 Subject: [PATCH] Attempt 1 to make mutability-independent UI --- lab3/src/algo/mod.rs | 6 +- lab3/src/gui/data.rs | 26 +- lab3/src/gui/graph.rs | 6 +- lab3/src/gui/input.rs | 108 ++- lab3/src/gui/mod.rs | 2 +- lab3/src/main.rs | 6 +- utility/src/graph/complete_graph.rs | 5 +- utility/src/gui/const_mut_switch/const.rs | 97 +++ utility/src/gui/const_mut_switch/mod.rs | 14 + utility/src/gui/const_mut_switch/mut.rs | 74 ++ utility/src/gui/const_mut_switch/ref.rs | 106 +++ utility/src/gui/const_mut_switch/table.rs | 110 +++ utility/src/gui/const_mut_switch/trait.rs | 68 ++ .../gui/const_mut_switch/эволюция_костылей.md | 686 ++++++++++++++++++ utility/src/gui/lengths_table.rs | 235 ++++-- utility/src/gui/mod.rs | 3 +- 16 files changed, 1423 insertions(+), 129 deletions(-) create mode 100644 utility/src/gui/const_mut_switch/const.rs create mode 100644 utility/src/gui/const_mut_switch/mod.rs create mode 100644 utility/src/gui/const_mut_switch/mut.rs create mode 100644 utility/src/gui/const_mut_switch/ref.rs create mode 100644 utility/src/gui/const_mut_switch/table.rs create mode 100644 utility/src/gui/const_mut_switch/trait.rs create mode 100644 utility/src/gui/const_mut_switch/эволюция_костылей.md diff --git a/lab3/src/algo/mod.rs b/lab3/src/algo/mod.rs index 08325d9..6fe002a 100644 --- a/lab3/src/algo/mod.rs +++ b/lab3/src/algo/mod.rs @@ -3,8 +3,6 @@ mod ant; mod config; mod update_state; -pub use bgtu_ai_utility::graph::{VerticesVec}; -use bgtu_ai_utility::graph::{Edge as _BaseEdge, EdgesVec as _BaseEdgesVec}; - pub(crate) use ant::Ant; -pub(crate) use config::AntsSimulationConfig; \ No newline at end of file +pub(crate) use config::AntsSimulationConfig; +pub(crate) use state::AntsSimulationState; \ No newline at end of file diff --git a/lab3/src/gui/data.rs b/lab3/src/gui/data.rs index 48369a3..a84b65e 100644 --- a/lab3/src/gui/data.rs +++ b/lab3/src/gui/data.rs @@ -2,17 +2,21 @@ use bgtu_ai_utility::graph::CompleteGraph; use bgtu_ai_utility::UpdatePending; use crate::algo::{AntsSimulationConfig, AntsSimulationState}; -pub(crate)enum GlobalState { - Edit, - Running, +pub(crate) enum AntsVisualisationData<'cfg> { + Edit { + config: &'cfg mut AntsSimulationConfig, + graph: &'cfg mut CompleteGraph<()>, + ants_per_vertex: usize, + vertex_update: UpdatePending, + }, + Running { + simulation: AntsSimulationState<'cfg>, + ants_per_vertex: usize, + vertex_locations: Vec<(f32, f32)>, + }, } -pub(crate) struct MyApp { - pub graph: CompleteGraph<()>, - pub simulation: AntsSimulationState, - pub config: AntsSimulationConfig, - pub vertex_update: UpdatePending, - pub state: GlobalState, - pub vertex_locations: Vec<(f32, f32)>, +pub(crate) struct MyApp<'cfg> { + pub state: AntsVisualisationData<'cfg>, pub ants_per_vertex: usize, } @@ -30,7 +34,7 @@ impl MyApp { q: 1.0, r: 0.5, }, - state: GlobalState::Edit {}, + state: AntsVisualisationData::Edit {}, vertex_locations: Vec::new(), ants_per_vertex: 1, }; diff --git a/lab3/src/gui/graph.rs b/lab3/src/gui/graph.rs index bbc346a..38cfff4 100644 --- a/lab3/src/gui/graph.rs +++ b/lab3/src/gui/graph.rs @@ -1,4 +1,4 @@ -use super::{GlobalState, MyApp}; +use super::{AntsVisualisationData, MyApp}; use crate::algo::updateState; use bgtu_ai_utility::gui::render_graph; use eframe::egui::Ui; @@ -7,7 +7,7 @@ pub(crate) fn graph_with_controls(ui: &mut Ui, data: &mut MyApp) { ui.vertical(|ui| { ui.horizontal(|ui| { if ui.button("Exit").clicked() { - data.state = GlobalState::Edit + data.state = AntsVisualisationData::Edit } if ui.button("Step").clicked() { @@ -17,7 +17,7 @@ pub(crate) fn graph_with_controls(ui: &mut Ui, data: &mut MyApp) { }); draw_ants(ui, data); if ui.input(|i| i.viewport().close_requested()) { - data.state = GlobalState::Edit + data.state = AntsVisualisationData::Edit } }); } diff --git a/lab3/src/gui/input.rs b/lab3/src/gui/input.rs index 175766a..5ff4bd6 100644 --- a/lab3/src/gui/input.rs +++ b/lab3/src/gui/input.rs @@ -1,49 +1,88 @@ -use std::collections::HashSet; -use eframe::egui::Ui; -use bgtu_ai_utility::gui::labeled_slider; -use bgtu_ai_utility::gui::lengths_table::draw_lengths_table; +use crate::algo::AntsSimulationConfig; use bgtu_ai_utility::UpdatePending; -use crate::algo::Ant; -use super::{GlobalState, MyApp}; +use bgtu_ai_utility::graph::CompleteGraph; +use bgtu_ai_utility::gui::const_mut_switch::{_ConstMutSwitchUiCallback, ConstMutSwitchUi, RefType, RefGetter}; -pub(crate) fn input(ui: &mut Ui, data: &mut MyApp) { - let mut run: bool = false; +pub(crate) fn input( + ctx: &mut Ctx, + mut config: Ctx::Ref<'_, AntsSimulationConfig>, + mut graph: Ctx::RefType::Ref<'_, CompleteGraph<()>>, + mut ants_per_vertex: Ctx::RefType::Ref<'_, usize>, + mut vertex_update: Ctx::RefType::Ref<'_, UpdatePending>, + mut launch: Ctx::RefType::Ref<'_, bool>, +) { + type Ref<'a, T> = <::RefType as RefType>::Ref<'a, T>; - labeled_slider( - ui, + ctx.labeled_slider( "Ferment weight", - &mut data.config.ferment_weight, 0.0..=1.0, 0.001, + &mut Ctx::_get( + &mut config, + |config| &config.ferment_weight, + |config| &mut config.ferment_weight, + ), ); - ui.label(""); - labeled_slider( - ui, + + ctx.space(); + + ctx.labeled_slider( "Heuristic coefficient", - &mut data.config.heuristic_coefficient, 0.0..=1.0, 0.001, + &mut Ctx::_get( + &mut config, + |config| &config.heuristic_coefficient, + |config| &mut config.heuristic_coefficient, + ), ); - ui.label(""); - labeled_slider(ui, "Q", &mut data.config.q, 0.0..=1.0, 0.001); - ui.label(""); - labeled_slider(ui, "r", &mut data.config.r, 0.0..=1.0, 0.001); - ui.label(""); - labeled_slider( - ui, + + ctx.space(); + + ctx.labeled_slider( + "Q", + 0.0..=1.0, + 0.001, + &mut Ctx::_get(&mut config, |config| &config.q, |config| &mut config.q), + ); + + ctx.space(); + + ctx.labeled_slider( + "R", + 0.0..=1.0, + 0.001, + &mut Ctx::_get(&mut config, |config| &config.r, |config| &mut config.r), + ); + + ctx.space(); + + ctx.labeled_slider( "Ants per vertex", - &mut data.ants_per_vertex, 1..=100, 1.0, + &mut Ctx::_get(&mut ants_per_vertex, |apv| apv, |apv| apv), ); - ui.label(""); - ui.horizontal(|ui| { - if ui.button("Add vertex").clicked() { - data.vertex_update = UpdatePending::Add; + + ctx.space(); + + struct ControlsRow {} + impl _ConstMutSwitchUiCallback for ControlsRow { + type Clojure = (Ref<'_, UpdatePending>, Ref<'_, bool>); + + fn render>( + ctx: &mut SubCtx, + mut clojure: (Ref<'_, UpdatePending>, Ref<'_, bool>), + ) { + ctx.button("Add vertex", &mut clojure.0, |d| *d = UpdatePending::Add); + ctx.separator(); + ctx.button("Run", &mut clojure.1, |d| *d = true); } - ui.separator(); - run = ui.button("Run").clicked(); - }); + } + + ctx.horizontal::(&mut (vertex_update, launch)) + + /* draw_lengths_table( ui, @@ -62,12 +101,13 @@ pub(crate) fn input(ui: &mut Ui, data: &mut MyApp) { ) } - data.state = GlobalState::Running; + data.state = AntsVisualisationData::Running; data.vertex_locations = coords; let allowed_locations = data .simulation - .graph.vertices + .graph + .vertices .iter_indexed() .map(|(i, _)| i) .collect::>(); @@ -79,5 +119,5 @@ pub(crate) fn input(ui: &mut Ui, data: &mut MyApp) { } } data.simulation.ants = ants; - } -} \ No newline at end of file + }*/ +} diff --git a/lab3/src/gui/mod.rs b/lab3/src/gui/mod.rs index 8b49ee2..3ef4294 100644 --- a/lab3/src/gui/mod.rs +++ b/lab3/src/gui/mod.rs @@ -2,6 +2,6 @@ mod data; mod input; mod graph; -pub(crate) use data::{MyApp, GlobalState}; +pub(crate) use data::{MyApp, AntsVisualisationData}; pub(crate) use input::input; pub(crate) use graph::graph_with_controls; \ No newline at end of file diff --git a/lab3/src/main.rs b/lab3/src/main.rs index 4a46683..9280e26 100644 --- a/lab3/src/main.rs +++ b/lab3/src/main.rs @@ -16,7 +16,7 @@ fn main() -> eframe::Result { impl eframe::App for gui::MyApp { fn update(&mut self, ui: &eframe::egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ui, |ui| match self.state { - gui::GlobalState::Edit {} => { + gui::AntsVisualisationData::Edit {} => { match self.vertex_update { UpdatePending::NoChange => {} UpdatePending::Add => { @@ -32,13 +32,13 @@ impl eframe::App for gui::MyApp { } gui::input(ui, self) } - gui::GlobalState::Running { .. } => { + gui::AntsVisualisationData::Running { .. } => { ui.add_enabled_ui(false, |ui| gui::input(ui, self)); subwindow( ui, "visualisation", "Visualisation", |vb| vb.with_inner_size([640.0, 480.0]), |ui| gui::graph_with_controls(ui, self), - ).on_close(|| self.state = gui::GlobalState::Edit); + ).on_close(|| self.state = gui::AntsVisualisationData::Edit); } }); } diff --git a/utility/src/graph/complete_graph.rs b/utility/src/graph/complete_graph.rs index 478b033..79a7762 100644 --- a/utility/src/graph/complete_graph.rs +++ b/utility/src/graph/complete_graph.rs @@ -14,9 +14,7 @@ impl CompleteGraph { edges: EdgesVec::new(), }; } -} -impl CompleteGraph { pub fn add_vertex(&mut self, edge_constructor: impl Fn() -> E) { self.vertices.add(|vertices, new_vi| { let mut new_edges_set = HashSet::new(); @@ -36,4 +34,7 @@ impl CompleteGraph { } self.vertices.remove(vi); } + pub fn vertex_count(&self) -> usize { + return self.vertices.len(); + } } diff --git a/utility/src/gui/const_mut_switch/const.rs b/utility/src/gui/const_mut_switch/const.rs new file mode 100644 index 0000000..eb2ee99 --- /dev/null +++ b/utility/src/gui/const_mut_switch/const.rs @@ -0,0 +1,97 @@ +use super::{ + _ConstMutSwitchUiCallback, _ConstMutSwitchUiTableBaseCallback, + _ConstMutSwitchUiTableRowCallback, ConstMutSwitchUi, ConstRef, RefType, +}; +use eframe::egui::{ScrollArea, Ui}; +use eframe::emath::Numeric; +use std::ops::RangeInclusive; + +pub struct ConstUI<'ui> { + ui: &'ui mut Ui, +} + +impl<'ui> ConstUI<'ui> { + pub fn wrap(ui: &'ui mut Ui) -> Self { + return Self { ui }; + } +} + +impl ConstMutSwitchUi for ConstUI<'_> { + type RefType = ConstRef; + + fn label(&mut self, s: &str) { + self.ui.label(s); + } + + fn slider( + &mut self, + range: RangeInclusive, + step: f64, + storage: &mut ::Ref<'_, T>, + ) { + let mut mirror = **storage; + self.ui.add_enabled_ui(false, |ui| { + crate::gui::slider(ui, &mut mirror, range, step); + }); + } + + fn labeled_slider( + &mut self, + name: &str, + range: RangeInclusive, + step: f64, + storage: &mut &T, + ) { + let mut mirror = **storage; + self.ui.add_enabled_ui(false, |ui| { + crate::gui::labeled_slider(ui, name, &mut mirror, range, step); + }); + } + + fn space(&mut self) { + self.ui.label(""); + } + + fn horizontal>(&mut self, clojure: &mut F::Clojure<'_>) { + self.ui + .horizontal(|ui| F::render(&mut ConstUI { ui }, clojure)); + } + + fn button(&mut self, label: &str, _storage: &mut &T, _on_click: impl FnOnce(&mut T)) { + self.ui.add_enabled_ui(false, |ui| ui.button(label)); + } + + fn separator(&mut self) { + self.ui.separator(); + } + + fn scroll_area_2>( + &mut self, + clojure: &mut F::Clojure<'_>, + ) { + ScrollArea::both() + .auto_shrink([false, false]) + .show_viewport(self.ui, |ui, _| F::render(&mut ConstUI { ui }, clojure)); + } + + fn table< + 'a, + F: _ConstMutSwitchUiTableBaseCallback, + _B: _ConstMutSwitchUiTableRowCallback = _BC>, + _BC, + >( + &mut self, + vscroll: bool, + columns: usize, + header_clojure: >::Clojure<'_>, + body_data: impl Iterator, + ) { + super::render_table::( + self.ui, + vscroll, + columns, + header_clojure, + body_data, + ); + } +} diff --git a/utility/src/gui/const_mut_switch/mod.rs b/utility/src/gui/const_mut_switch/mod.rs new file mode 100644 index 0000000..6915233 --- /dev/null +++ b/utility/src/gui/const_mut_switch/mod.rs @@ -0,0 +1,14 @@ +mod r#const; +mod r#mut; +mod r#ref; +mod table; +mod r#trait; + +pub use r#const::ConstUI; +pub use r#mut::MutUI; +pub use r#ref::{ConstRef, MutRef, RefGetter, RefType, _IteratorGetter}; +use table::render_table; +pub use table::{ + _ConstMutSwitchUiTableBaseCallback, _ConstMutSwitchUiTableRowCallback, ConstMutSwitchUiTableRow, +}; +pub use r#trait::{_ConstMutSwitchUiCallback, ConstMutSwitchUi}; diff --git a/utility/src/gui/const_mut_switch/mut.rs b/utility/src/gui/const_mut_switch/mut.rs new file mode 100644 index 0000000..f433608 --- /dev/null +++ b/utility/src/gui/const_mut_switch/mut.rs @@ -0,0 +1,74 @@ +use super::{ConstMutSwitchUi, MutRef, RefType, _ConstMutSwitchUiTableBaseCallback, _ConstMutSwitchUiTableRowCallback}; +use crate::gui::const_mut_switch::r#trait::_ConstMutSwitchUiCallback; +use eframe::egui::{ScrollArea, Ui}; +use eframe::emath::Numeric; +use std::ops::RangeInclusive; + +pub struct MutUI<'ui> { + ui: &'ui mut Ui, +} + +impl ConstMutSwitchUi for MutUI<'_> { + type RefType = MutRef; + + fn label(&mut self, s: &str) { + self.ui.label(s); + } + + fn slider(&mut self, range: RangeInclusive, step: f64, storage: &mut ::Ref<'_, T>) { + crate::gui::slider(self.ui, *storage, range, step); + } + + fn labeled_slider( + &mut self, + name: &str, + range: RangeInclusive, + step: f64, + storage: &mut &mut T, + ) { + crate::gui::labeled_slider(self.ui, name, *storage, range, step); + } + + fn space(&mut self) { + self.ui.label(""); + } + + fn horizontal>(&mut self, clojure: &mut I::Clojure<'_>) { + self.ui + .horizontal(|ui| I::render(&mut MutUI { ui }, clojure)); + } + + fn button(&mut self, label: &str, storage: &mut &mut T, on_click: impl FnOnce(&mut T)) { + if self.ui.button(label).clicked() { + on_click(*storage) + } + } + + fn separator(&mut self) { + self.ui.separator(); + } + + fn scroll_area_2>( + &mut self, + clojure: &mut F::Clojure<'_>, + ) { + ScrollArea::both() + .auto_shrink([false, false]) + .show_viewport(self.ui, |ui, _| F::render(&mut MutUI { ui }, clojure)); + } + + fn table< + 'a, + F: _ConstMutSwitchUiTableBaseCallback, + _B: _ConstMutSwitchUiTableRowCallback = _BC>, + _BC, + >( + &mut self, + vscroll: bool, + columns: usize, + header_clojure: >::Clojure<'_>, + body_data: impl Iterator, + ) { + super::render_table::(self.ui, vscroll, columns, header_clojure, body_data); + } +} diff --git a/utility/src/gui/const_mut_switch/ref.rs b/utility/src/gui/const_mut_switch/ref.rs new file mode 100644 index 0000000..2ab1427 --- /dev/null +++ b/utility/src/gui/const_mut_switch/ref.rs @@ -0,0 +1,106 @@ +use crate::gui::const_mut_switch::ConstMutSwitchUi; +use std::ops::Deref; +use std::ptr::NonNull; + +pub trait RefType { + type Ref<'a, T: 'a>: Deref; + + fn _get<'r, 'f: 'r, 's: 'f, S, M>( + storge: &'f mut Self::Ref<'s, S>, + const_: impl FnOnce(&S) -> &M, + mut_: impl FnOnce(&mut S) -> &mut M, + ) -> Self::Ref<'r, M>; + + unsafe fn _to_ptr(r: Self::Ref<'_, T>) -> NonNull; + unsafe fn _from_ptr<'a, T>(r: NonNull) -> Self::Ref<'a, T>; + + fn _iter<'r, 'f: 'r, 's: 'f, F: _IteratorGetter>( + storge: &'f mut Self::Ref<'s, F::C>, + ) -> impl Iterator>>; + + fn _enum_iter<'r, 'f: 'r, 's: 'f, F: _IteratorGetter>( + storge: &'f mut Self::Ref<'s, F::C>, + ) -> impl Iterator>>; +} + +pub trait _IteratorGetter { + type C; + type T<'a>: 'a; + fn get_const_iterator<'a>(cont: &'a Self::C) -> impl Iterator>; + + fn get_mut_iterator<'a>(cont: &'a mut Self::C) -> impl Iterator>; +} + +pub trait _EnumeratedIteratorGetter { + type C; + type T<'a>: 'a; + fn get_const_iterator<'a>(cont: &'a Self::C) -> impl Iterator)>; + + fn get_mut_iterator<'a>( + cont: &'a mut Self::C, + ) -> impl Iterator)>; +} + +pub struct ConstRef {} + +impl RefType for ConstRef { + type Ref<'a, T: 'a> = &'a T; + + fn _get<'r, 'f: 'r, 's: 'f, S, M>( + storge: &'f mut &'s S, + const_: impl FnOnce(&S) -> &M, + _mut: impl FnOnce(&mut S) -> &mut M, + ) -> &'r M { + return const_(*storge); + } + + unsafe fn _to_ptr(r: &T) -> NonNull { + return NonNull::from_ref(r); + } + + unsafe fn _from_ptr<'a, T>(r: NonNull) -> Self::Ref<'a, T> { + return r.as_ref(); + } + + fn _iter<'r, 'f: 'r, 's: 'f, F: _IteratorGetter>( + storge: &'f mut Self::Ref<'s, F::C>, + ) -> impl Iterator> { + return F::get_const_iterator(storge); + } +} + +pub struct MutRef {} + +impl RefType for MutRef { + type Ref<'a, T: 'a> = &'a mut T; + + fn _get<'r, 'f: 'r, 's: 'f, S, M>( + storge: &'f mut &'s mut S, + _const: impl FnOnce(&S) -> &M, + mut_: impl FnOnce(&mut S) -> &mut M, + ) -> &'r mut M { + return mut_(*storge); + } + + unsafe fn _to_ptr(r: &mut T) -> NonNull { + return NonNull::from_mut(r); + } + + unsafe fn _from_ptr<'a, T>(mut r: NonNull) -> Self::Ref<'a, T> { + return r.as_mut(); + } + + fn _iter<'r, 'f: 'r, 's: 'f, F: _IteratorGetter>( + storge: &'f mut Self::Ref<'s, F::C>, + ) -> impl Iterator> { + return F::get_mut_iterator(storge); + } +} + +pub trait RefGetter { + type Ref<'a, T: 'a>; +} + +impl RefGetter for C { + type Ref<'a, T: 'a> = ::Ref<'a, T>; +} diff --git a/utility/src/gui/const_mut_switch/table.rs b/utility/src/gui/const_mut_switch/table.rs new file mode 100644 index 0000000..a57f2f6 --- /dev/null +++ b/utility/src/gui/const_mut_switch/table.rs @@ -0,0 +1,110 @@ +use super::{_ConstMutSwitchUiCallback, ConstMutSwitchUi, ConstRef, ConstUI, MutRef, MutUI, RefType}; +use eframe::egui::Ui; +use egui_extras::{Column, TableBuilder, TableRow}; +use std::marker::PhantomData; + +pub trait _ConstMutSwitchUiTableBaseCallback { + type Header: _ConstMutSwitchUiTableRowCallback; + type Body: _ConstMutSwitchUiTableRowCallback; + + fn header_to_body<'a>( + header: &mut >::Clojure<'a>, + ) -> impl Iterator>::Clojure<'a>>; +} + +pub trait _ConstMutSwitchUiTableRowCallback { + type Clojure<'a>; + + fn render_row>( + row: &mut Row, + clojure: &mut Self::Clojure<'_>, + ); +} + +pub trait ConstMutSwitchUiTableRow { + type RefType: RefType; + + fn cell>(&mut self, clojure: &mut F::Clojure<'_>); +} + +pub(super) struct ConstMutSwitchUiTableRowImpl< + 'a, + 'b, + RefType: super::RefType, + Constructor: __SwitchUiConstructor1, +> { + row: TableRow<'a, 'b>, + __phantom: PhantomData<(Constructor, RefType)>, +} + +impl> ConstMutSwitchUiTableRow + for ConstMutSwitchUiTableRowImpl<'_, '_, RefType, Constructor> +{ + type RefType = RefType; + + fn cell>(&mut self, clojure: &mut F::Clojure<'_>) { + self.row + .col(|ui| F::render(&mut Constructor::__wrap(ui), clojure)); + } +} + +trait __SwitchUiConstructor1 { + type Ctx<'a>: ConstMutSwitchUi; + + fn __wrap<'a>(ui: &'a mut Ui) -> Self::Ctx<'a>; +} + +impl __SwitchUiConstructor1 for ConstUI<'_> { + type Ctx<'a> = ConstUI<'a>; + + fn __wrap<'a>(ui: &'a mut Ui) -> Self::Ctx<'a> { + todo!() + } +} +impl __SwitchUiConstructor1 for MutUI<'_> { + type Ctx<'a> = MutUI<'a>; + + fn __wrap<'a>(ui: &'a mut Ui) -> Self::Ctx<'a> { + todo!() + } +} + +pub(super) fn render_table< + 'a, + Ctx: ConstMutSwitchUi, + F: _ConstMutSwitchUiTableBaseCallback, + Constructor: __SwitchUiConstructor1, +>( + ui: &mut Ui, + vscroll: bool, + columnsCount: usize, + headerClojure: &mut >::Clojure<'_>, +) { + TableBuilder::new(ui) + .striped(true) // Alternating row colors + .resizable(true) + .vscroll(vscroll) + .columns(Column::remainder(), columnsCount) + .header(20.0, |row| { + F::Header::render_row( + &mut ConstMutSwitchUiTableRowImpl::<_, Constructor> { + row, + __phantom: PhantomData::default(), + }, + headerClojure, + ) + }) + .body(|mut body| { + for mut rowData in F::header_to_body(headerClojure) { + body.row(20.0, |row| { + F::Body::render_row( + &mut ConstMutSwitchUiTableRowImpl::<_, Constructor> { + row, + __phantom: PhantomData::default(), + }, + &mut rowData, + ) + }); + } + }); +} diff --git a/utility/src/gui/const_mut_switch/trait.rs b/utility/src/gui/const_mut_switch/trait.rs new file mode 100644 index 0000000..41788c8 --- /dev/null +++ b/utility/src/gui/const_mut_switch/trait.rs @@ -0,0 +1,68 @@ +use super::{ + _ConstMutSwitchUiTableBaseCallback, _ConstMutSwitchUiTableRowCallback, ConstRef, RefType, +}; +use eframe::emath::Numeric; +use std::ops::RangeInclusive; + +pub trait ConstMutSwitchUi { + type RefType: RefType; + + fn label(&mut self, s: &str); + + fn slider( + &mut self, + range: RangeInclusive, + step: f64, + storage: &mut ::Ref<'_, T>, + ); + + fn labeled_slider( + &mut self, + name: &str, + range: RangeInclusive, + step: f64, + storage: &mut ::Ref<'_, T>, + ); + + fn space(&mut self); + + fn horizontal>( + &mut self, + clojure: &mut F::Clojure<'_>, + ); + + fn button( + &mut self, + label: &str, + storage: &mut ::Ref<'_, T>, + on_click: impl FnOnce(&mut T), + ); + + fn separator(&mut self); + + fn scroll_area_2>( + &mut self, + clojure: &mut F::Clojure<'_>, + ); + + fn table< + 'a, + F: _ConstMutSwitchUiTableBaseCallback + >( + &mut self, + vscroll: bool, + columns: usize, + header_clojure: >::Clojure< + '_, + >, + ); +} + +pub trait _ConstMutSwitchUiCallback { + type Clojure<'a>; + + fn render>( + ctx: &mut SubCtx, + clojure: &mut Self::Clojure<'_>, + ); +} diff --git a/utility/src/gui/const_mut_switch/эволюция_костылей.md b/utility/src/gui/const_mut_switch/эволюция_костылей.md new file mode 100644 index 0000000..9b12f7f --- /dev/null +++ b/utility/src/gui/const_mut_switch/эволюция_костылей.md @@ -0,0 +1,686 @@ +## Введение +Цель этого набора трейтов - описание графического интерфейса единым образом +(***ровно 1 раз***) независимо от возможности изменять данные, которые он визуализирует: +если свойство неизменяемое, соответствующий элемент отключается (`disabled="true"`); в +противном случае отображение происходит без дополнительных изменений. + +Так как `&T` более ограниченный тип, чем `&mut T`, то весь функционал должен строиться +вокруг него. Но тогда возникают проблемы с тем, что нельзя преобразовать его в `&mut T` +без `unsafe` блоков. + +Альтернативный вариант - параметризовать тип ссылок... + +## 1 + +Начальный интерфейс содержал ассоциированный тип с самой ссылкой и методом +преобразования ссылки на тип в ссылку на её члена: +```rust +trait ConstMutSwitchUi { + type Ref<'a, T: 'a>; + + fn _get<'r, 'f: 'r, 's: 'f, S, M>( + storge: &'f mut Self::Ref<'s, S>, + const_: impl FnOnce(&S) -> &M, + mut_: impl FnOnce(&mut S) -> &mut M, + ) -> Self::Ref<'r, M>; + + // ... +} + +impl ConstMutSwitchUi for ConstUI<'_> { + type Ref<'a, T> = &'a T; + + fn _get<'r, 'f: 'r, 's: 'f, S, M>( + storge: &'f mut &'s S, + const_: impl FnOnce(&S) -> &M, + _mut: impl FnOnce(&mut S) -> &mut M, + ) -> &'r M { + return const_(*storge); + } +} + +impl ConstMutSwitchUi for MutUI<'_> { + type RefType = &'a mut T; + + fn _get<'r, 'f: 'r, 's: 'f, S, M>( + storge: &'f mut &'s mut S, + _const: impl FnOnce(&S) -> &M, + mut_: impl FnOnce(&mut S) -> &mut M, + ) -> &'r mut M { + return mut_(*storge); + } +} +``` + +Ассоциированный тип `Ref<'a, T>` разворачивается в `&'a T` или `&'a mut T` в зависимости +от использованной реализации. Почему параметр `T` ограничен временем жизни `'a` - нет +идей, но компилятор заставил ограничить. + +Расставить времена жизни в методе `_get` было довольно нетривиально (с учётом +малоинформативных сообщений об ошибках от компилятора): +* `'r` (*return*) - время жизни ссылки на члена типа; +* `'f` (*function*) - время жизни ссылки на ссылку; +* `'s` (*storage*) - время ссылки на значение, член которого нужно получить; + +Лямбды без проблем работают и без явного указания времён жизни. + +Параметр `storage` принимает ссылку на ссылку так как компилятор не знает о том, что `Ref` +это тоже ссылка, поэтому передача по значению приведёт к передаче владения внутрь метода +и недоступности ссылки после. Изменяемая ссылка нужна чтобы нельзя было получить +несколько изменяемых ссылок на членов одного значения. На практике обратное не тестировалось. + +По аналогичной логике ссылки передавались в простые виджеты: +```rust +trait ConstMutSwitchUi { + // ... + + fn labeled_slider( + &mut self, + name: &str, + range: RangeInclusive, + step: f64, + storage: &mut Self::Ref<'_, T>, + ); + + // ... +} +``` + + +## 2 + +### 2.1 + +Более сложные виджеты предоставляют локальные области и при создании обёртки нужно как-то +передавать тип ссылки в эти дочерние области. Самое очевидное решение использовать лямбду: +которая принимает `impl ConstMutSwitchUi` в качестве параметра, но так как тип функции сам +по себе трейт, а компилятор такое не поддерживает, такой вариант не подходит. + +Для обхода этого ограничения создан дополнительный функциональный трейт: +```rust +trait _ConstMutSwitchUiCallback { + type Clojure; + + fn render( + &self, + ctx: &mut SubCtx, + clojure: &mut Self::Clojure, + ); +} +``` +Параметр `clojure` является изменяемой ссылкой по той же причине, что и раньше. + +Этот трейт имплементируется пользователем на каком-нибудь анонимном типе и передаётся в +метод рантайма: +```rust +trait ConstMutSwitchUi { + // ... + + fn horizontal( + &mut self, + clojure: &mut F::Clojure, + scope: F + ); + + // ... +} +``` + +### 2.2 + +Следующая проблема - перевести ссылки из родительского `Ref` в дочерний `Ref`. Попытки +сделать трейт для перевода ссылок оказались безуспешными, так как в переменное число +шаблонных параметров, из-за чего нельзя переводить значения, содержащие более одной +ссылки: `(&T1, &T2)`. +```rust +pub trait RefsMapper { + type NewCtx<'n>: ConstMutSwitchUi; + + fn _map_ctx<'n>(&self) -> impl Refs>; +} +``` +Возможно у этого способа существовали и другие проблемы, но он не дожил до их выявления. + +Другой подход - сказать тип ссылки у дочерней области такой же как и у родительской, но +тогда возникает проблема невозможности использовать локальные шаблонные параметры +при подстановке ассоциированного типа: +```rust +trait ConstMutSwitchUi { + // ... + + fn horizontal=Self::Ref<'a, T>>>( + &mut self, + clojure: &mut F::Clojure, + scope: F + ); + + // ... +} +``` +Но зато можно создать ещё один трейт: +```rust +pub trait RefType { + type Ref<'a, T: 'a>; +} + +impl RefType for ConstRef { + type Ref<'a, T: 'a> = &'a T; +} + +impl RefType for MutRef { + type Ref<'a, T: 'a> = &'a mut T; +} + + +trait ConstMutSwitchUi { + type RefType: RefType; + + // ... + + fn horizontal>( + &mut self, + clojure: &mut F::Clojure, + scope: F + ); + + // ... +} + + +trait _ConstMutSwitchUiCallback { + type Clojure; + + fn render>( + &self, + ctx: &mut SubCtx, + clojure: &mut Self::Clojure, + ); +} + +``` +Это решает проблему идентичности типов ссылок, но вынуждает писать чуть более громоздкий +код (`Ctx::RefType::RefType` или `::Ref` вместо `Ctx::Ref`). + + +```rust + +trait ConstMutSwitchUi { + type RefType: RefType; + + // ... + + fn horizontal>( + &mut self, + clojure: &mut F::Clojure, + scope: F + ); + + // ... +} + +trait _ConstMutSwitchUiCallback { + type Clojure; + + fn render>( + &self, + ctx: &mut SubCtx, + clojure: &mut Self::Clojure, + ); +} +``` + +### 2.3 +Так как все необходимые данные передаются через параметры методов, необходимость в `&self` +отпала. Это избавляет от необходимости прописывать всех типы и имена в самой структуре и +потенциально упрощает код: +```rust +trait ConstMutSwitchUi { + // ... + + fn horizontal=Self::Ref<'a, T>>>( + &mut self, + clojure: &mut F::Clojure, + ); + + // ... +} + +trait _ConstMutSwitchUiCallback { + type Clojure; + + fn render>( + ctx: &mut SubCtx, + clojure: &mut Self::Clojure, + ); +} +``` + + +### 2.4 +Предпринимались попытки сделать обращение к типу ссылки более лаконичным через +дополнительный трейт: +```rust + +pub trait RefGetter { + type Ref<'a, T: 'a>; +} + +impl RefGetter for C { + type Ref<'a, T: 'a> = ::Ref<'a, T>; +} +``` +Но в таком случае компилятор не понимает, что `ConstMutSwitchUi::RefType::RefType` и +`RefGetter::Ref`. + +Ручная реализация трейта `Fn` для `_ConstMutSwitchUiCallback` является экспериментальной, +поэтому реализована не была. + + +## 3 +### 3.1 + +Вишенка на торте - создание таблицы. Аналогично набор из большого количества трейтов, +половина из которых могли бы быть заменены лямбдами. Первая версия: + +```rust +trait _ConstMutSwitchUiTableBaseCallback { + type Clojure; + + fn render_table>( + clojure: &mut Self::Clojure, + ); +} + +trait ConstMutSwitchUiTableBase { + type RefType: RefType; + + fn header>( + &mut self, + clojure: &mut F::Clojure, + ); + fn body>( + &mut self, + clojure: &mut F::Clojure, + ); +} + +trait _ConstMutSwitchUiTableRowCallback { + type Clojure; + + fn render_row>( + row: &mut Row, + index: I, + clojure: &mut Self::Clojure, + ); +} + +trait ConstMutSwitchUiTableRow { + type RefType: RefType; + + fn cell>(&mut self, clojure: &mut F::Clojure); +} +``` +```rust + +struct ConstMutSwitchUiTableRowImpl< + 'a, + 'b, + 'c, + Ctx: ConstMutSwitchUi, + Constructor: Fn(&mut Ui) -> Ctx, +> { + row: TableRow<'a, 'b>, + constructor: &'c Constructor, +} + +impl Ctx> ConstMutSwitchUiTableRow + for ConstMutSwitchUiTableRowImpl<'_, '_, '_, Ctx, Constructor> +{ + type RefType = Ctx::RefType; + + fn cell>(&mut self, clojure: &mut F::Clojure) { + self.row + .col(|ui| F::render(&mut (self.constructor)(ui), clojure)); + } +} + +enum ConstMutSwitchUiTableBaseImplState<'a> { + Header(TableBuilder<'a>), + Body(Table<'a>), + Done, +} + +impl<'a> ConstMutSwitchUiTableBaseImplState<'a> { + #[allow(deprecated)] + fn header_map(&mut self, f: impl FnOnce(TableBuilder) -> Table) { + match self { + ConstMutSwitchUiTableBaseImplState::Header(_) => { + let mut local = unsafe { Self::Header(uninitialized()) }; + swap(self, &mut local); + if let Self::Header(local) = local { + *self = Self::Body(f(local)) + } + } + _ => panic!("Not in header state"), + } + } + + #[allow(deprecated)] + fn body_map(&mut self, f: impl FnOnce(Table) ) { + match self { + ConstMutSwitchUiTableBaseImplState::Header(_) => { + let mut local = unsafe { Self::Body(uninitialized()) }; + swap(self, &mut local); + if let Self::Body(local) = local { + f(local); + *self = Self::Done + } + } + _ => panic!("Not in body state"), + } + } +} + +pub(super) struct ConstMutSwitchUiTableBaseImpl< + 'a, + Ctx: ConstMutSwitchUi, + Constructor: Fn(&mut Ui) -> Ctx, +> { + table: ConstMutSwitchUiTableBaseImplState<'a>, + constructor: Constructor, + size: usize +} + +impl Ctx> ConstMutSwitchUiTableBase + for ConstMutSwitchUiTableBaseImpl<'_, Ctx, Constructor> +{ + type RefType = Ctx::RefType; + + fn header>( + &mut self, + clojure: &mut F::Clojure, + ) { + match &mut self.table { + ConstMutSwitchUiTableBaseImplState::Body(_) | ConstMutSwitchUiTableBaseImplState::Done => { + panic!("Header duplication") + } + ConstMutSwitchUiTableBaseImplState::Header(h) => { + self.table.header_map(|h| { + return h.header(20.0, |row| { + F::render_row( + &mut ConstMutSwitchUiTableRowImpl { + row, + constructor: &self.constructor, + }, + (), + clojure, + ) + }); + }); + } + } + } +} + +trait ConstMutSwitchUi { + // ... + + fn table>( + &mut self, + columns: usize, + rows: usize, + clojure: &mut F::Clojure, + ); + + // ... +} + +``` + +### 3.2 +Область `ConstMutSwitchUiTableBase` оказалась избыточной, так как каждый из методов будет +вызываться ровно один раз в фиксированном порядке. К тому же, из-за отсутствия возможности +писать конечные автоматы без "moving out of borrowed reference" приходится прибегать к +небезопасным трюкам. Упрощённая версия: + +```rust +trait _ConstMutSwitchUiTableBaseCallback { + type Header: _ConstMutSwitchUiTableRowCallback; + type Body: _ConstMutSwitchUiTableRowCallback; +} + + +trait ConstMutSwitchUi { + // ... + + fn table>( + &mut self, + vscroll: bool, + columns: usize, + header_clojure: &mut >::Clojure, + body_data: impl Iterator>::Clojure>, + ); + + // ... +} +``` + +### 3.3 +Данные для строк передаются через итератор, поэтому нет необходимости в передаче номера строки +отдельно (если они нужны можно делать `it.enumerate()`): + + +```rust +trait _ConstMutSwitchUiTableBaseCallback { + type Header: _ConstMutSwitchUiTableRowCallback; + type Body: _ConstMutSwitchUiTableRowCallback; +} + +trait _ConstMutSwitchUiTableRowCallback { + type Clojure; + + fn render_row>( + row: &mut Row, + clojure: &mut Self::Clojure, + ); +} + +trait ConstMutSwitchUi { + // ... + + fn table>( + &mut self, + vscroll: bool, + columns: usize, + header_clojure: &mut >::Clojure, + body_data: impl Iterator>::Clojure>, + ); + + // ... +} +``` + +### 3.4 +При имплементации этого метода в структуре (просто от сигнатуры, с пустым телом) компилятор +сходит с ума и выдаёт ошибку о том, что *`F`doesn't implements +_ConstMutSwitchUiTableBaseCallback*. Решается эта проблема явной распаковкой всех ассоциированных +типов: +```rust +trait ConstMutSwitchUi { + // ... + + fn table< + 'a, + F: _ConstMutSwitchUiTableBaseCallback, + _B: _ConstMutSwitchUiTableRowCallback=_BC>, + _BC, + >( + &mut self, + vscroll: bool, + columns: usize, + header_clojure: >::Clojure, + body_data: impl Iterator, + ); + + // ... +} +``` +Что тут происходит с `'a` нет идей, но оно компилируется. + +### 3.5 +Что бы не писать повторять реализации контекста для таблиц (который довольно объёмный), код +был вынесен в функцию `table::render_table`: +```rust +pub(super) fn render_table< + Ctx: ConstMutSwitchUi, + F: _ConstMutSwitchUiTableBaseCallback, +>( + ui: &mut Ui, + vscroll: bool, + columnsCount: usize, + headerClojure: >::Clojure, + rowsData: impl Iterator< + Item = >::Clojure, + >, + constructor: impl Fn(&mut Ui) -> Ctx, +) { + // ... +} +``` + +Но при попытке создать конструктор всплывала проблема: компилятор не видит в сигнатуре лямбды, +что возвращаемое значение использует время жизни передаваемой ссылки, поэтому выдовал ошибку +об их несвязанности. Решение - просто ещё один трейт, который просто используется вместо +стандартного `Fn`: + +```rust + +trait __SwitchUiConstructor1 { + type Ctx<'a>: ConstMutSwitchUi; + + fn __wrap<'a>(ui: &'a mut Ui) -> Self::Ctx<'a>; +} + +``` + +### 3.6 +Текущая реализация требует передачи двух изменяемых ссылок, что с точки зрения языка недопустимо. +Решение - передавать данные только для заголовка, а после его отрисовки превращать данные +в итератор для тела таблицы: +```rust + +trait _ConstMutSwitchUiTableBaseCallback { + type Header: _ConstMutSwitchUiTableRowCallback; + type Body: _ConstMutSwitchUiTableRowCallback; + + fn header_to_body<'a>( + header: &mut >::Clojure<'a>, + ) -> impl Iterator>::Clojure<'a>>; +} + + +trait _ConstMutSwitchUiTableRowCallback { + type Clojure<'a>; + + fn render_row>( + row: &mut Row, + clojure: &mut Self::Clojure<'_>, + ); +} + + +trait ConstMutSwitchUi { + // ... + + fn table< + F: _ConstMutSwitchUiTableBaseCallback, + >( + &mut self, + vscroll: bool, + columns: usize, + header_clojure: &mut >::Clojure + ); + + // ... +} + +``` + +Метод отрисовки строки принимает ссылку, что бы была возможность потом передать + + +## 4 +### 4.1 +Для более прозрачной работы с типом ссылок им добавлено ограничение трейтом `Deref`, который +имплементируют оба типа ссылок и это не нарушает безопасность программы в обоих режимах. +```rust +trait RefType { + type Ref<'a, T: 'a>: Deref; +} +``` + +### 4.2 +Метод `ConstMutSwitchUi::_get` нужен не только в функциях с `Ctx: ConstMutSwitchUi`, но и в +функциях `Ctx: ConstMutSwitchUiTableRow`, поэтому сначала была идея вынести её в общий +супертрейт, но это усложнило бы использование её в пользовательском коде, который обычно +указывает только `RefType`, поэтому метод и был перемещён именно туда: +```rust +trait RefType { + // ... + + fn _get<'r, 'f: 'r, 's: 'f, S, M>( + storge: &'f mut Self::Ref<'s, S>, + const_: impl FnOnce(&S) -> &M, + mut_: impl FnOnce(&mut S) -> &mut M, + ) -> Self::Ref<'r, M>; + + // ... +} +``` +Не в `Ref`, потому что это требует ещё одного трейта и будет вызывать коллизии с методом `_get` +у объекта по ссылке. + +### 4.3 +В некоторых случаях, чтобы победить borrow checker, нужно создавать весьма нетривиальные структуры +данных (которые всё равно будут внутри использовать `unsafe`). Структуры создавать сложно, скастовать +ссылку в сырой указатель - просто. + +```rust +pub trait RefType { + // ... + + unsafe fn _to_ptr(r: Self::Ref<'_, T>) -> NonNull; + + unsafe fn _from_ptr<'a, T>(r: NonNull) -> Self::Ref<'a, T>; + + // ... +} +``` + +### 4.4 +Итераторы, которые возвращают ссылки не могут быть корректно получены методом `RefType::_get`, +поэтому ещё один трейт: +```rust +trait RefType { + // ... + + fn _iter<'r, 'f: 'r, 's: 'f, F: _IteratorGetter>( + storge: &'f mut Self::Ref<'s, F::C>, + ) -> impl Iterator>; + + // ... +} + +trait _IteratorGetter { + type T<'a>: 'a; + type C; + fn get_const_iterator<'a>(cont: &'a Self::C) + -> impl Iterator>; + + fn get_mut_iterator<'a>( + cont: &'a mut Self::C, + ) -> impl Iterator>; +} + +``` +Но способ работает только для очень простых случаев, так что малопригоден. \ No newline at end of file diff --git a/utility/src/gui/lengths_table.rs b/utility/src/gui/lengths_table.rs index e1273d1..7bc1578 100644 --- a/utility/src/gui/lengths_table.rs +++ b/utility/src/gui/lengths_table.rs @@ -1,78 +1,173 @@ -use crate::graph::{EdgesVec, VerticesVec}; -use crate::gui::slider; +use crate::UpdatePending; +use crate::graph::{CompleteGraph, Edge, EdgesVec, VerticesVec}; +use crate::gui::const_mut_switch::{_ConstMutSwitchUiCallback, _ConstMutSwitchUiTableBaseCallback, _ConstMutSwitchUiTableRowCallback, ConstMutSwitchUi, ConstMutSwitchUiTableRow, RefGetter, RefType, _IteratorGetter}; use eframe::egui::{ScrollArea, Ui}; use egui_extras::{Column, TableBuilder}; +use std::collections::HashSet; +use std::ops::{Index, IndexMut}; use std::ptr::NonNull; -use crate::UpdatePending; -pub fn draw_lengths_table( - ui: &mut Ui, - vertices: &mut VerticesVec, - edges: &mut EdgesVec, - update: &mut UpdatePending, +pub fn draw_lengths_table<'a, Ctx: ConstMutSwitchUi>( + ctx: &mut Ctx, + graph: ::Ref<'a, CompleteGraph<()>>, + update: ::Ref<'a, UpdatePending>, ) { - ScrollArea::both() - .auto_shrink([false, false]) - .show_viewport(ui, |ui, _area| { - TableBuilder::new(ui) - .striped(true) // Alternating row colors - .resizable(true) - .vscroll(false) - .column(Column::remainder()) - .columns(Column::remainder(), vertices.len()) - .header(20.0, |mut header| { - header.col(|ui| { - ui.label("#"); - }); - for (i, _) in vertices.iter().enumerate() { - header.col(|ui| { - ui.horizontal(|ui| { - ui.label(i.to_string()); - if ui.button("-").clicked() { - *update = UpdatePending::Remove(i); - } - }); - }); - } - }) - .body(|mut body| { - for (ri, (vi, v)) in vertices.iter_indexed().enumerate() { - body.row(20.0, |mut row| { - row.col(|ui| { - ui.horizontal(|ui| { - ui.label(ri.to_string()); - if ui.button("-").clicked() { - *update = UpdatePending::Remove(vi); - } - }); - }); + ctx.scroll_area_2::(&mut (graph, update)); - let mut local_edges = - Vec::<(usize, NonNull)>::with_capacity(v.len()); - for ei in v.iter() { - let e = &mut edges[*ei]; - let p = (e.another(vi), &mut e.length); - local_edges.push((p.0, NonNull::from_mut(p.1))); - } - local_edges.sort_by_key(|(k, _)| *k); + struct ScrollArea {} + impl _ConstMutSwitchUiCallback for ScrollArea { + type Clojure<'a> = (RT::Ref<'a, CompleteGraph<()>>, RT::Ref<'a, UpdatePending>); - let mut ci = 0usize; - for &(_, mut l) in local_edges.iter() { - if (ci == ri) { - row.col(|ui| {}); - ci += 1 - } - row.col(|ui| { - slider(ui, unsafe { l.as_mut() }, 0.0..=10.0, 0.1); - }); - ci += 1; - } - - if ri == vertices.len() - 1 { - row.col(|ui| {}); - } - }); - } - }); - }); + fn render>( + ctx: &mut SubCtx, + mut clojure: &mut (RT::Ref<'_, CompleteGraph<()>>, RT::Ref<'_, UpdatePending>), + ) { + ctx.table::(false, clojure.0.vertex_count(), HeaderData { + update_state: clojure.1, + edges: CompleteGraph::<()>, + vertices: (), + }) + } + } +} + +struct Table{} +impl _ConstMutSwitchUiTableBaseCallback for Table { + type Header = Header; + type Body = Body; + + fn header_to_body<'a>(header: &mut >::Clojure<'a>) -> impl Iterator>::Clojure<'a>> { + struct ItGet{}; + impl _IteratorGetter for ItGet { + type C = VerticesVec; + type T<'a> = HashSet; + + fn get_const_iterator<'a>(cont: &'a Self::C) -> impl Iterator> { + return cont.iter_indexed() + } + + fn get_mut_iterator<'a>(cont: &'a mut Self::C) -> impl Iterator> { + return cont.iter_indexed_mut() + } + } + + return header.vertices.iter_indexed().enumerate().map(|(ri, (vi, eis))| { + return RowData{ + update_state: header.update_state, + edges: header.edges, + row_id: ri, + vertex_id: vi, + vertex_edges_ids: eis, + } + }) + } +} + + +struct HeaderData<'us, 'es, 'eis, RT: RefType> { + update_state: RT::Ref<'us, UpdatePending>, + edges: RT::Ref<'es, EdgesVec<()>>, + vertices: RT::Ref<'eis, VerticesVec>, +} + +struct Header {} +impl _ConstMutSwitchUiTableRowCallback for Header { + type Clojure<'a> = HeaderData<'a, 'a, 'a, RT>; + + fn render_row>( + row: &mut Row, + clojure: &mut Self::Clojure<'_>, + ) { + row.cell::(&mut ()); + for i in 0..clojure.vertices.len() { + row.cell::(&mut (i, NonNull::from_ref(&clojure.update_state))); + } + + struct CornerCell {} + impl<RT: RefType> _ConstMutSwitchUiCallback<RT> for CornerCell { + type Clojure<'a> = (); + + fn render<SubCtx: ConstMutSwitchUi<RefType = RT>>(ctx: &mut SubCtx, _: &mut ()) { + ctx.label("#"); + } + } + } +} + +struct RowData<'us, 'es, 'eis, RT: RefType> { + update_state: RT::Ref<'us, UpdatePending>, + edges: RT::Ref<'es, EdgesVec<()>>, + row_id: usize, + vertex_id: usize, + vertex_edges_ids: RT::Ref<'eis, HashSet<usize>>, +} + +struct Body {} +impl<RT: RefType> _ConstMutSwitchUiTableRowCallback<RT> for Body { + type Clojure<'a> = RowData<'a, 'a, 'a, RT>; + + fn render_row<Row: ConstMutSwitchUiTableRow<RefType = RT>>( + row: &mut Row, + clojure: &mut Self::Clojure<'_>, + ) { + row.cell::<Title>(&mut (clojure.row_id, NonNull::from_ref(&clojure.update_state))); + + // not map because a borrow checker doesn't allow returning from lambda. + // raw pointer because a borrow checker. + let mut local_edges = + Vec::<(usize, NonNull<f64>)>::with_capacity(clojure.vertex_edges_ids.len()); + for ei in clojure.vertex_edges_ids.iter() { + let mut e = RT::_get( + &mut clojure.edges, + |ee| ee.index(*ei), + |ee| ee.index_mut(*ei), + ); + let another_vertex = e.another(clojure.vertex_id); + let length_storage = RT::_get(&mut e, |e| &e.length, |e| &mut e.length); + let length_storage = unsafe { RT::_to_ptr(length_storage) }; + local_edges.push((another_vertex, length_storage)); + } + local_edges.sort_by_key(|(k, _)| *k); + + for (column_index, cell_data) in local_edges.iter_mut().enumerate() { + if (column_index == clojure.row_id) { + row.cell::<EmptyCell>(&mut ()); + } + row.cell::<Cell>(&mut unsafe { RT::_from_ptr(cell_data.1) }); + } + + struct Cell {} + impl<RT: RefType> _ConstMutSwitchUiCallback<RT> for Cell { + type Clojure<'a> = RT::Ref<'a, f64>; + + fn render<SubCtx: ConstMutSwitchUi<RefType = RT>>( + ctx: &mut SubCtx, + clojure: &mut Self::Clojure<'_>, + ) { + ctx.slider(0.0..=10.0, 0.1, clojure); + } + } + + struct EmptyCell {} + impl<RT: RefType> _ConstMutSwitchUiCallback<RT> for EmptyCell { + type Clojure<'a> = (); + + fn render<SubCtx: ConstMutSwitchUi<RefType = RT>>(_: &mut SubCtx, _: &mut ()) {} + } + } +} + +struct Title {} +impl<RT: RefType> _ConstMutSwitchUiCallback<RT> for Title { + type Clojure<'a> = (usize, NonNull<RT::Ref<'a, UpdatePending>>); + + fn render<SubCtx: ConstMutSwitchUi<RefType = RT>>( + ctx: &mut SubCtx, + clojure: &mut Self::Clojure<'_>, + ) { + ctx.label(clojure.0.to_string().as_str()); + ctx.button("-", unsafe { clojure.1.as_mut() }, |s| { + *s = UpdatePending::Remove(clojure.0) + }) + } } diff --git a/utility/src/gui/mod.rs b/utility/src/gui/mod.rs index 7afcb77..9378fdd 100644 --- a/utility/src/gui/mod.rs +++ b/utility/src/gui/mod.rs @@ -1,10 +1,11 @@ mod boot; +pub mod const_mut_switch; pub mod lengths_table; mod render_graph; mod slider; mod subwindow; pub use boot::boot_eframe; +pub use render_graph::render_graph; pub use slider::{labeled_slider, slider}; pub use subwindow::subwindow; -pub use render_graph::render_graph; \ No newline at end of file