//! What the user has done to a screen since it arrived. //! //! Two of the five things `quasi-tui`'s `View` holds, and the other three are //! egui's. That is the whole difference between the two crates' state, and it is //! worth saying which is which so the next reader does not go looking for the //! missing ones: //! //! | Fact | terminal | here | //! |---|---|---| //! | what is typed | `View` | **`View`** | //! | what is ticked | `View` | **`View`** | //! | what has focus | `View` | egui's id stack | //! | how far a pane is scrolled | `View` | `egui::ScrollArea` | //! | where back goes | `Runtime` | [`Runtime`](crate::Runtime) | //! //! **Why typing is not egui's, when focus is.** egui holds widget state against //! an id, and a described field is rebuilt from the description every frame; its //! `TextEdit` needs a `&mut String` that outlives the frame, and the description //! deliberately does not carry the value. So the buffer is the app's, held here. //! That is the same conclusion `makeover-immediate`'s `Filling` reached one layer //! down and for the same reason. use std::collections::{BTreeMap, BTreeSet}; use quasi_router::{Node, Params, Screen}; /// What the user has done to a screen since it arrived. /// /// A host makes one beside the screen it is holding and keeps the two together. /// Empty is the honest starting state and it draws exactly what the description /// says. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct View { /// What has been typed, by [`Field::name`](quasi_router::Field::name). /// /// Absent means untouched, which is different from present and empty: one /// draws the description's value and the other draws a box the user has /// cleared. edits: BTreeMap, /// What has been ticked, by [`Row::value`](quasi_router::Row::value). ticked: BTreeSet, } impl View { /// Nothing typed and nothing ticked. #[must_use] pub fn new() -> Self { Self::default() } /// What a field is showing: what was typed, or what the description offers. #[must_use] pub fn showing<'a>(&'a self, name: &str, described: Option<&'a str>) -> &'a str { self.edits .get(name) .map_or(described.unwrap_or_default(), String::as_str) } /// The buffer a text control writes through, seeded from the description. /// /// `&mut` because that is what an immediate-mode text control takes: there /// is no DOM to read the value back out of afterwards. Seeding on first /// touch rather than up front is what keeps "untouched" distinguishable /// from "cleared". pub fn buffer(&mut self, name: &str, described: Option<&str>) -> &mut String { self.edits .entry(name.to_owned()) .or_insert_with(|| described.unwrap_or_default().to_owned()) } /// What has been typed into a field, if anything has. #[must_use] pub fn edit(&self, name: &str) -> Option<&str> { self.edits.get(name).map(String::as_str) } /// Put a value in, as a host restoring one would. pub fn set(&mut self, name: impl Into, value: impl Into) { self.edits.insert(name.into(), value.into()); } /// Whether a row's value is in the screen's selection. #[must_use] pub fn is_ticked(&self, value: &str) -> bool { self.ticked.contains(value) } /// Add or remove a row's value from the selection. pub fn tick(&mut self, value: &str) { if !self.ticked.remove(value) { self.ticked.insert(value.to_owned()); } } /// Everything ticked, in a stable order. pub fn ticks(&self) -> impl Iterator { self.ticked.iter().map(String::as_str) } /// The rows a new screen says are already ticked. /// /// Applied once on arrival rather than read on every draw: after this the /// user's ticks are the truth, and a description that kept overriding them /// would undo a tick the moment anything redrew. pub fn seed(&mut self, screen: &Screen) { for slot in &screen.slots { for node in &slot.body { if let Node::List { rows, .. } = node { for row in rows { if let (Some(true), Some(value)) = (row.selected, row.value.as_ref()) { self.ticked.insert(value.clone()); } } } } } } /// Forget everything, for a screen that has been replaced. pub fn reset(&mut self) { self.edits.clear(); self.ticked.clear(); } /// The values a form submits, by the names it declared. /// /// Every declared name is sent, including the ones nothing was typed into, /// because a form that omits an untouched field is a form that cannot clear /// one. What is sent for those is whatever the description offered. #[must_use] pub fn submission(&self, names: &[String], described: &BTreeMap) -> Params { let mut params = Params::new(); for name in names { let value = self .edits .get(name) .or_else(|| described.get(name)) .map_or("", String::as_str); params = params.with(name.clone(), value.to_owned()); } params } /// What the screen's selection sends with an action taken over it. #[must_use] pub fn gathering(&self, under: &str) -> Params { let mut params = Params::new(); for value in self.ticks() { params = params.with(under.to_owned(), value.to_owned()); } params } }