//! Where this renderer drew each described row, for a host that has to read a //! gesture the description does not carry. //! //! //! //! # Why this is here and not in the vocabulary //! //! asked what, if anything, a description should hand a host about where it //! drew what. The answer is nothing. A description says what is on the screen //! and never where, which is the same rule that keeps `Act::shows` a picture //! rather than a box and keeps an anchor a described id rather than a point. //! Geometry is the *renderer's* answer and a different answer per host: a //! terminal has cells, a webview has the DOM, and this host has rects. //! //! So the fact travels from the renderer to its own host, beside the drawing, //! rather than through the description. A webview host asks the DOM the same //! question and needs nothing from here. //! //! # What it is for //! //! audiofiles drags samples into a DAW. That gesture is not an outcome -- an OS //! drag is this host's, and `quasi::panel::dragging_out` performs it -- and to //! start it correctly the host has to know two things this renderer knew and //! did not say: whether the press landed on a row at all, and which row. Without //! them a press on the toolbar could start a drag, and a press on an unchosen //! row dragged the chosen set instead of the row under the cursor. //! //! # Why egui's own memory rather than a return value //! //! `Immediate::screen` and `chromed` answer `Option`, which is what the //! user set off. Widening that to carry geometry would change every call site //! in every host to thread a fact almost none of them want. The rects are //! per-frame data belonging to a `Context`, which is what `Context` data is for, //! and a host that never asks pays a `Vec` push per row and nothing else. use egui::{Context, Id, Pos2, Rect, Ui}; use quasi_router::Anchor; /// A described row, and where this renderer put it. #[derive(Debug, Clone, PartialEq, Eq)] pub struct RowAt { /// The row's own value, when the description gave it one. /// /// `Row::value`, which is the identifier the description /// already uses for a row: it is what a tick travels under and what /// survives a reorder. `None` on a row that carries none, where the index /// is all there is. pub value: Option, /// Where the row sat in the list or table this frame. /// /// Positional, so it does not survive a reorder. Read it only against the /// same frame's description. pub index: usize, } /// The key the rects live under, which is this crate's and not a host's. fn slot() -> Id { Id::new("quasi-immediate::rows") } /// What is remembered between the drawing and the asking. #[derive(Clone, Default)] struct Drawn { /// The pass these rects were measured in. /// /// egui redraws on its own schedule, so a host asking about a pointer needs /// the rects from the frame it is asking about rather than whatever was /// left behind. Rows are cleared when the number moves rather than by a /// begin-frame hook, because this crate has none. pass: u64, /// Value, position, rect, and whether the row is part of a live selection. /// /// The last is `Anchor::Selection`'s other half. A staged tick lives in /// the runtime's view and is passed in; a live selection is the /// description's, so it arrives here with the row that carried it. rows: Vec<(Option, usize, Rect, bool)>, /// Where each described region was drawn, by `Slot::id`. /// /// The rows above answer "is the pointer over one"; these answer "where is /// the thing this menu was anchored to", which is the same class of fact /// and belongs in the same place rather than in a second store that can /// disagree about which pass it is describing. regions: Vec<(String, Rect)>, /// Where each *named* control was drawn, by `Act::id`. /// /// Only the named ones. `Act::id` is `None` on nearly every control, so a /// screen that anchors nothing pushes nothing here. acts: Vec<(String, Rect)>, } /// Say a row was drawn here. /// /// Called for every described row whether or not it answers a gesture: a row /// that is neither pressable nor menued still occupies the space, and "is the /// pointer over a row" is the question that has to be answerable for the /// dangerous case -- a press on the toolbar that would otherwise start a drag. pub(crate) fn note_row(ui: &Ui, value: Option<&str>, index: usize, rect: Rect, chosen: bool) { noting(ui, |drawn| { drawn .rows .push((value.map(ToOwned::to_owned), index, rect, chosen)); }); } /// Say a region was drawn here. /// /// Every described region, whether or not anything is ever anchored to it, for /// [`note_row`]'s reason: nothing in a description says which regions an app /// anchors to, and the answer has to exist before the question is asked. pub(crate) fn note_region(ui: &Ui, id: &str, rect: Rect) { noting(ui, |drawn| drawn.regions.push((id.to_owned(), rect))); } /// Say a named control was drawn here. /// /// Called only for an [`Act`](quasi_router::Act) carrying an /// [`id`](quasi_router::Act::id), so a screen with no anchored menus pays /// nothing. pub(crate) fn note_act(ui: &Ui, id: &str, rect: Rect) { noting(ui, |drawn| drawn.acts.push((id.to_owned(), rect))); } /// Clear a stale pass and hand the store to whoever is adding to it. /// /// One place, so the pass check cannot be forgotten by a note function added /// later -- which is exactly how a menu would come to be anchored at where its /// control sat two frames ago. fn noting(ui: &Ui, add: impl FnOnce(&mut Drawn)) { let pass = ui.ctx().cumulative_pass_nr(); ui.ctx().data_mut(|data| { let drawn: &mut Drawn = data.get_temp_mut_or_default(slot()); if drawn.pass != pass { drawn.pass = pass; drawn.rows.clear(); drawn.regions.clear(); drawn.acts.clear(); } add(drawn); }); } /// Where the thing an anchor names was drawn, if it was drawn this pass. /// /// This is the ruling in `600c9e42` doing its work: the description named a /// thing, and the renderer that drew it is the one that answers where. /// /// `ticked` is the values the view holds ticked, which is **half** of what /// [`Anchor::Selection`] resolves against -- the runtime owns that set and this /// module does not, so it is passed in rather than reached for. The other half /// is `Row::chosen`, a selection already in force, which the description /// carries and which therefore arrived with the row. /// /// `None` means the anchor named nothing on this pass: a region or control that /// is not on the screen, or a selection with nothing in it. The caller falls /// back to drawing the menu unanchored, which is what every renderer does with /// an anchor it cannot resolve. pub(crate) fn anchor_rect(ctx: &Context, anchor: &Anchor, ticked: &[String]) -> Option { ctx.data(|data| { let drawn: Drawn = data.get_temp(slot())?; match anchor { Anchor::Region(id) => find(&drawn.regions, id), Anchor::Control(id) => find(&drawn.acts, id), // The box around every ticked row, so a menu over a set opens // against the set rather than against whichever member happens to // be first. Rows scrolled out of view contribute nothing, which is // right: they were not drawn, so this renderer has no rect for them // and inventing one would put the menu off screen. Anchor::Selection => drawn .rows .iter() .filter(|(value, _, _, chosen)| { *chosen || value .as_deref() .is_some_and(|value| ticked.iter().any(|tick| tick == value)) }) .map(|(_, _, rect, _)| *rect) .reduce(Rect::union), } }) } /// The last rect noted under a name, or `None` if nothing was. /// /// Last wins, matching [`row_at`]: where one name is drawn twice -- a region /// inside an overlay over the region it names -- the later one is on top. fn find(noted: &[(String, Rect)], id: &str) -> Option { noted .iter() .rev() .find(|(name, _)| name == id) .map(|(_, rect)| *rect) } /// The described row under this point, if the point is over one. /// /// `None` means the point is not over a row: over the chrome, over a control /// beside the table, over nothing. That is the answer a host guarding a drag /// wants, and it is the one this renderer could not give before. /// /// Last drawn wins. Rows do not overlap within a list or a table, and where two /// do -- an overlay's row over the row underneath -- the later one is on top, /// which is the order they were drawn in. /// /// Reads the frame that has most recently drawn. A host asking before anything /// has been drawn gets `None` rather than a stale answer. #[must_use] pub fn row_at(ctx: &Context, pos: Pos2) -> Option { ctx.data(|data| { let drawn: Drawn = data.get_temp(slot())?; drawn .rows .iter() .rev() .find(|(_, _, rect, _)| rect.contains(pos)) .map(|(value, index, _, _)| RowAt { value: value.clone(), index: *index, }) }) }