use std::collections::VecDeque;
use super::Camera;
use super::input::{Device, Modifiers};
use crate::{Error, Name, Pending, Row, Rows};
#[derive(Clone, PartialEq, Debug, serde::Serialize, schemars::JsonSchema)]
#[serde(tag = "type", content = "detail", rename_all = "lowercase")]
#[schemars(rename = "ViewEvent")]
#[non_exhaustive]
pub enum Event {
Click {
hit: Hit,
trigger: Trigger,
},
Open {
hit: Hit,
trigger: Trigger,
},
Menu {
hits: Vec<Hit>,
trigger: Trigger,
},
Drag {
phase: Phase,
hit: Hit,
position: [f64; 3],
trigger: Trigger,
},
Connect {
from: End,
to: Option<End>,
edge: Option<Row>,
trigger: Trigger,
},
Delete {
rows: Vec<Rows>,
trigger: Trigger,
},
Hover {
hit: Option<Hit>,
trigger: Trigger,
},
Select {
rows: Vec<Rows>,
trigger: Option<Trigger>,
},
Camera(Camera),
Waiting(Waiting),
Error(Error),
}
#[derive(Clone, Copy, PartialEq, Debug, Default, serde::Serialize, schemars::JsonSchema)]
pub struct Trigger {
pub point: [f32; 2],
pub modifiers: Modifiers,
pub device: Device,
}
impl Event {
fn intent(&self) -> bool {
matches!(
self,
Event::Click { .. }
| Event::Open { .. }
| Event::Menu { .. }
| Event::Drag { .. }
| Event::Connect { .. }
| Event::Delete { .. }
)
}
fn replaces(&self, older: &Event) -> bool {
match (self, older) {
(Event::Drag { phase: Phase::Move, .. }, Event::Drag { phase: Phase::Move, .. }) => true,
(Event::Error(error), Event::Error(old)) => error == old,
(Event::Hover { .. } | Event::Select { .. } | Event::Camera(_) | Event::Waiting(_), _) => {
std::mem::discriminant(self) == std::mem::discriminant(older)
}
_ => false,
}
}
}
#[derive(Default)]
pub(crate) struct Events(VecDeque<Slot>);
enum Slot {
Ready(Event),
Picking(Pending<Vec<Hit>>, Awaited),
Gone,
}
#[derive(Clone)]
pub(crate) enum Awaited {
Menu(Trigger),
Connect {
from: End,
edge: Option<Row>,
trigger: Trigger,
},
Cycle {
after: Option<Row>,
trigger: Trigger,
},
}
impl Events {
pub(crate) fn tell(&mut self, event: Event) {
self.0.retain(|slot| !matches!(slot, Slot::Ready(older) if event.replaces(older)));
self.0.push_back(Slot::Ready(event));
}
pub(crate) fn tell_after(&mut self, hits: Pending<Vec<Hit>>, awaited: Awaited) {
self.0.push_back(Slot::Picking(hits, awaited));
}
pub(crate) fn settle(&mut self, mut make: impl FnMut(Vec<Hit>, Awaited) -> Option<Event>) {
let mut made = false;
for slot in &mut self.0 {
if let Slot::Picking(hits, awaited) = slot
&& let Some(hits) = hits.take()
{
*slot = match make(hits.unwrap_or_default(), awaited.clone()) {
Some(event) => Slot::Ready(event),
None => Slot::Gone,
};
made = true;
}
}
if made {
self.0.retain(|slot| !matches!(slot, Slot::Gone));
}
}
pub(crate) fn error(&self) -> Option<&Error> {
self.0.iter().find_map(|slot| match slot {
Slot::Ready(Event::Error(error)) => Some(error),
_ => None,
})
}
pub(crate) fn drain(&mut self) -> std::vec::IntoIter<Event> {
let (mut told, mut held) = (Vec::new(), VecDeque::new());
for slot in self.0.drain(..) {
match slot {
Slot::Ready(event) if held.is_empty() || !event.intent() => told.push(event),
slot => held.push_back(slot),
}
}
self.0 = held;
told.into_iter()
}
}
impl Extend<Event> for Events {
fn extend<I: IntoIterator<Item = Event>>(&mut self, events: I) {
events.into_iter().for_each(|event| self.tell(event));
}
}
#[derive(Clone, PartialEq, Debug, serde::Serialize, schemars::JsonSchema)]
pub struct End {
pub row: Row,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<Name>,
}
#[derive(Clone, PartialEq, Debug, serde::Serialize, schemars::JsonSchema)]
pub struct Hit {
pub row: Row,
#[serde(skip_serializing_if = "Option::is_none")]
pub mark: Option<Name>,
#[serde(skip)]
pub(crate) index: usize,
pub point: [f32; 2],
pub position: [f64; 3],
pub distance: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<Name>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample: Option<Sample>,
}
#[derive(Clone, Copy, PartialEq, Debug, serde::Serialize, schemars::JsonSchema)]
pub struct Sample {
#[serde(serialize_with = "sample_index")]
#[schemars(with = "crate::Id")]
pub frame: u64,
pub at: f64,
pub value: f64,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, schemars::JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum Phase {
Start,
Move,
End,
}
#[derive(Clone, PartialEq, Debug, serde::Deserialize, schemars::JsonSchema)]
#[schemars(description = "What a view's fit frames.")]
#[serde(rename_all = "camelCase")]
pub enum Fit {
All,
Rows(Vec<Rows>),
Reveal(Row),
Bounds {
min: [f64; 3],
max: [f64; 3],
},
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum Cursor {
#[default]
Default,
Pointer,
Grab,
Grabbing,
Crosshair,
}
#[derive(Clone, Copy, PartialEq, Debug, Default, serde::Serialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Stats {
pub frames: u64,
pub rows: u64,
pub drawn: u64,
pub draws: u64,
pub instances: u64,
pub tiles: u64,
pub tiles_loading: u64,
pub uploading: u64,
pub resident: u64,
pub budget: u64,
pub waiting: Waiting,
pub cpu_ms: f32,
pub gpu_ms: f32,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Waiting {
pub compiling: bool,
pub data: bool,
pub derived: bool,
pub labels: bool,
pub fit: bool,
pub marks: bool,
}
impl Waiting {
pub fn is_empty(&self) -> bool {
*self == Waiting::default()
}
pub fn names(&self) -> impl Iterator<Item = &'static str> {
let Waiting { compiling, data, derived, labels, fit, marks } = *self;
[
("compiling", compiling),
("data", data),
("derived", derived),
("labels", labels),
("fit", fit),
("marks", marks),
]
.into_iter()
.filter_map(|(name, waiting)| waiting.then_some(name))
}
}
impl std::fmt::Display for Waiting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.names().collect::<Vec<_>>().join(", "))
}
}
impl serde::Serialize for Waiting {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_seq(self.names())
}
}
impl schemars::JsonSchema for Waiting {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Waiting".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "Work under way a frame stands in for: pipelines the scene draws with compiling, until which the view draws nothing new (`compiling`); pages uploading or tables not yet built from them (`data`); layouts, routes, lanes, shapes, tiles, or pyramids (`derived`); a label placement or texts not yet set (`labels`); a fit due or being measured (`fit`); row regions laid out again, segment counts, or a transition beginning (`marks`). Empty once a frame is complete.",
"type": "array",
"items": { "enum": ["compiling", "data", "derived", "labels", "fit", "marks"] },
})
}
}
#[allow(clippy::trivially_copy_pass_by_ref, reason = "serde serialize_with takes a reference")]
fn sample_index<S: serde::Serializer>(index: &u64, serializer: S) -> Result<S::Ok, S::Error> {
serde::Serialize::serialize(&crate::Id::Number(*index), serializer)
}
#[cfg(test)]
mod tests {
use super::*;
fn click(id: u64) -> Event {
let hit = Hit {
row: Row::new("d", "t", id),
mark: None,
index: 0,
point: [0.0; 2],
position: [0.0; 3],
distance: 0.0,
port: None,
sample: None,
};
Event::Click { hit, trigger: Trigger::default() }
}
fn told(events: &mut Events) -> Vec<Event> {
events.drain().collect()
}
#[test]
fn intents_wait_behind_a_pick_while_changes_pass() {
let mut events = Events::default();
let (hits, reply) = Pending::new();
events.tell_after(hits, Awaited::Menu(Trigger::default()));
events.tell(click(1));
events.tell(Event::Waiting(Waiting::default()));
assert_eq!(told(&mut events), vec![Event::Waiting(Waiting::default())], "the click waits for the menu");
reply.resolve(Ok(Vec::new()));
events.settle(|hits, awaited| match awaited {
Awaited::Menu(trigger) => Some(Event::Menu { hits, trigger }),
Awaited::Connect { .. } | Awaited::Cycle { .. } => unreachable!("only a menu waits"),
});
let after = told(&mut events);
assert!(matches!(after.as_slice(), [Event::Menu { .. }, Event::Click { .. }]), "in the order asked: {after:?}");
}
#[test]
fn a_change_replaces_the_one_still_untold() {
let mut events = Events::default();
let mut waiting = Waiting { data: true, ..Waiting::default() };
events.tell(Event::Waiting(waiting));
events.tell(click(1));
waiting.data = false;
events.tell(Event::Waiting(waiting));
events.tell(Event::Error(Error::Timeout));
events.tell(Event::Error(Error::Timeout));
assert_eq!(told(&mut events), vec![click(1), Event::Waiting(waiting), Event::Error(Error::Timeout)]);
}
}