use std::cell::RefCell;
use super::*;
use crate::edit::EditOptions;
use crate::fragment::FragmentRef;
use crate::line::Stroke;
use crate::path::{FillRule, PathOp, Turn};
use crate::resources::{ImageId, ImageOpts};
use crate::schema::EnvFacts;
use crate::scroll::ScrollGeometry;
use crate::slot::Slot;
use crate::text::Span;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SlotFill {
Replayed,
NotKept,
Params,
Reads,
NotReplayable,
Moved,
}
impl SlotFill {
pub fn replayed(self) -> bool {
self == Self::Replayed
}
pub fn name(self) -> &'static str {
match self {
Self::Replayed => "replayed",
Self::NotKept => "not-kept",
Self::Params => "params",
Self::Reads => "reads",
Self::NotReplayable => "not-replayable",
Self::Moved => "moved",
}
}
pub fn code(self) -> i32 {
self as i32
}
pub fn from_code(code: i32) -> Option<Self> {
Some(match code {
0 => Self::Replayed,
1 => Self::NotKept,
2 => Self::Params,
3 => Self::Reads,
4 => Self::NotReplayable,
5 => Self::Moved,
_ => return None,
})
}
}
pub(crate) enum Op {
Open {
key: Key,
spec: Box<NodeSpec>,
},
Close,
Text {
key: Key,
content: Box<str>,
style: TextStyle,
},
Rich {
key: Key,
spans: Vec<(Box<str>, Span<'static>)>,
base: TextStyle,
},
Image {
key: Key,
id: ImageId,
opts: ImageOpts,
spec: Box<NodeSpec>,
},
Fragment {
key: Key,
frag: FragmentRef,
params: Vec<f32>,
spec: Box<NodeSpec>,
},
Line {
key: Key,
points: Vec<Vec2>,
stroke: Stroke,
spec: Box<NodeSpec>,
},
Polygon {
key: Key,
points: Vec<Vec2>,
spec: Box<NodeSpec>,
},
Path {
key: Key,
ops: Vec<PathOp>,
rule: FillRule,
stroke: Option<Stroke>,
turn: Option<Turn>,
spec: Box<NodeSpec>,
},
Edit {
label: Box<str>,
initial: Box<str>,
opts: Box<EditOptions>,
spec: Box<NodeSpec>,
},
Label {
key: Key,
label: Box<str>,
},
Indexed(u64),
RowCount(u64),
RowCountOpen(u64),
Hint {
key: Key,
text: Box<str>,
},
KeyFocus(Key),
Slot {
name: Box<str>,
params: Value,
},
}
#[derive(Debug)]
pub(crate) enum Read {
Hover(Key, bool),
Pressed(Key, bool),
Drop(Key, bool),
GroupHover(u64, bool),
GroupPressed(u64, bool),
Focus(Option<Key>),
Focused(Key, bool),
FocusVisible(bool),
Caret(bool),
Clock,
Mods(crate::input::KeyMods),
Cursor(Option<Vec2>),
Scroll(Key, Vec2),
ScrollGeom(Key, Option<ScrollGeometry>),
Layout(Key, Option<Rect>),
TextHit(Key, Vec2, Option<crate::text::TextHit>),
EditText(Key, Option<String>),
Env(EnvFacts),
Measure(u64),
Theme(Box<crate::theme::Theme>),
Metrics(Box<crate::metrics::Metrics>),
Accent(bool),
Opaque,
}
pub(crate) const ONCE_THEME: u8 = 1;
pub(crate) const ONCE_METRICS: u8 = 2;
pub(crate) const ONCE_ACCENT: u8 = 4;
pub(crate) struct Recording {
name: String,
slot_key: Key,
origin: OriginId,
params: Value,
ops: Vec<Op>,
reads: RefCell<Vec<Read>>,
once: std::cell::Cell<u8>,
taint: Option<&'static str>,
first: u32,
nodes: u32,
foreign: u32,
paused: u32,
pause_from: u32,
}
pub(crate) struct Kept {
slot_key: Key,
origin: OriginId,
params: Value,
ops: Vec<Op>,
reads: Vec<Read>,
taint: Option<&'static str>,
frame: u64,
}
impl Kept {
pub(crate) fn nodes(&self) -> usize {
self.ops
.iter()
.filter(|op| {
matches!(
op,
Op::Open { .. }
| Op::Text { .. }
| Op::Rich { .. }
| Op::Image { .. }
| Op::Fragment { .. }
| Op::Line { .. }
| Op::Polygon { .. }
| Op::Path { .. }
| Op::Edit { .. }
)
})
.count()
}
}
fn env_same(a: &EnvFacts, b: &EnvFacts) -> bool {
a.env == b.env
&& a.viewport == b.viewport
&& a.scale == b.scale
&& a.focus == b.focus
&& a.focus_visible == b.focus_visible
&& a.region == b.region
}
impl Core {
#[inline]
pub(crate) fn keeping(&self) -> bool {
self.recording.as_ref().is_some_and(|r| r.paused == 0)
}
#[cold]
#[inline(never)]
pub(crate) fn keep_op(&mut self, op: Op) {
let Some(r) = self.recording.as_mut() else {
return;
};
if r.paused > 0 {
return;
}
if matches!(
op,
Op::Open { .. }
| Op::Text { .. }
| Op::Rich { .. }
| Op::Image { .. }
| Op::Fragment { .. }
| Op::Line { .. }
| Op::Polygon { .. }
| Op::Path { .. }
| Op::Edit { .. }
) {
r.nodes += 1;
}
r.ops.push(op);
}
#[inline]
pub(crate) fn note_read(&self, read: impl FnOnce() -> Read) {
if let Some(r) = self.recording.as_ref()
&& r.paused == 0
{
r.reads.borrow_mut().push(read());
}
}
#[inline]
pub(crate) fn note_read_once(&self, bit: u8, read: impl FnOnce() -> Read) {
if let Some(r) = self.recording.as_ref()
&& r.paused == 0
&& r.once.get() & bit == 0
{
r.once.set(r.once.get() | bit);
r.reads.borrow_mut().push(read());
}
}
pub(crate) fn taint_kept(&mut self, why: &'static str) {
if let Some(r) = self.recording.as_mut()
&& r.taint.is_none()
{
r.taint = Some(why);
}
}
pub(crate) fn pause_keeping(&mut self) {
let len = self.tree.len() as u32;
if let Some(r) = self.recording.as_mut() {
if r.paused == 0 {
r.pause_from = len;
}
r.paused += 1;
}
}
pub(crate) fn resume_keeping(&mut self) {
let len = self.tree.len() as u32;
if let Some(r) = self.recording.as_mut()
&& r.paused > 0
{
r.paused -= 1;
if r.paused == 0 {
r.foreign += len.saturating_sub(r.pause_from);
}
}
}
pub(crate) fn keep_next_fill(&mut self, name: &str, key: Key, params: &Value) {
if self.recording.is_some() {
return;
}
self.keep_next = Some((name.to_owned(), key, params.clone()));
}
pub(crate) fn forget_next_fill(&mut self) {
self.keep_next = None;
}
pub(crate) fn begin_keeping(&mut self, slot: &Slot<'_>, origin: OriginId) {
let Some((name, key, params)) = self.keep_next.take_if(|(_, k, _)| *k == slot.key) else {
return;
};
self.recording = Some(Recording {
name,
slot_key: key,
origin,
params,
ops: Vec::new(),
reads: RefCell::new(Vec::new()),
once: std::cell::Cell::new(0),
taint: None,
first: self.tree.len() as u32,
nodes: 0,
foreign: 0,
paused: 0,
pause_from: 0,
});
}
pub(crate) fn end_keeping(&mut self, slot: &Slot<'_>) {
let Some(r) = self.recording.as_ref() else {
return;
};
if r.slot_key != slot.key {
return;
}
let mut r = self.recording.take().expect("checked");
let pushed = (self.tree.len() as u32).saturating_sub(r.first);
if r.paused > 0 {
r.taint = Some("a pause left open");
} else if pushed.saturating_sub(r.foreign) != r.nodes {
r.taint = Some("a node pushed through a door the journal does not know");
}
let kept = Kept {
slot_key: r.slot_key,
origin: r.origin,
params: r.params,
ops: r.ops,
reads: r.reads.into_inner(),
taint: r.taint,
frame: self.frame_no,
};
self.kept.insert(r.name, kept);
}
fn first_moved(&self, reads: &[Read]) -> Option<String> {
reads.iter().find(|r| !self.read_holds(r)).map(|r| match r {
Read::Theme(_) => "Theme".to_owned(),
Read::Metrics(_) => "Metrics".to_owned(),
r => format!("{r:?}"),
})
}
fn read_holds(&self, r: &Read) -> bool {
match r {
Read::Hover(k, v) => self.interaction.is_hovered(*k) == *v,
Read::Pressed(k, v) => self.interaction.is_pressed(*k) == *v,
Read::Drop(k, v) => self.interaction.is_drop_target(*k) == *v,
Read::GroupHover(g, v) => self.interaction.is_group_hovered(*g) == *v,
Read::GroupPressed(g, v) => self.interaction.is_group_pressed(*g) == *v,
Read::Focus(k) => self.focus == *k,
Read::Focused(k, v) => (self.focus == Some(*k)) == *v,
Read::FocusVisible(v) => self.focus_visible == *v,
Read::Caret(v) => self.edit.blink_visible() == *v,
Read::Clock => false,
Read::Mods(m) => self.interaction.modifiers() == *m,
Read::Cursor(p) => self.interaction.cursor().map(|c| c.minus(self.dt_shift())) == *p,
Read::Scroll(k, v) => self.scroll.offset(*k) == *v,
Read::ScrollGeom(k, g) => self.scroll_geometry(*k) == *g,
Read::Layout(k, r) => self.layout_of_raw(*k) == *r,
Read::TextHit(k, p, h) => self.text_hit_raw(*k, *p) == *h,
Read::EditText(k, s) => self.edit.text(*k) == *s,
Read::Env(f) => env_same(f, &self.env_facts_raw()),
Read::Measure(rev) => self.text_rev() == *rev,
Read::Theme(t) => **t == self.theme,
Read::Metrics(m) => **m == self.metrics,
Read::Accent(a) => self.has_accent_raw() == *a,
Read::Opaque => false,
}
}
pub(crate) fn text_rev(&self) -> u64 {
self.fonts_rev
.wrapping_mul(0x9E37_79B9)
.wrapping_add(self.weights_rev)
}
pub(crate) fn take_replayable(
&mut self,
name: &str,
key: Key,
params: &Value,
) -> Result<Kept, (SlotFill, Option<String>)> {
let Some(kept) = self.kept.remove(name) else {
return Err((SlotFill::NotKept, None));
};
if kept.slot_key != key {
return Err((SlotFill::Moved, None));
}
if let Some(why) = kept.taint {
return Err((SlotFill::NotReplayable, Some(why.to_owned())));
}
if kept.params != *params {
return Err((SlotFill::Params, None));
}
if let Some(moved) = self.first_moved(&kept.reads) {
return Err((SlotFill::Reads, Some(moved)));
}
Ok(kept)
}
pub(crate) fn replay(
&mut self,
name: &str,
slot: &Slot<'_>,
mut kept: Kept,
filler: Option<&mut dyn crate::slot::Fill>,
) {
let origin = kept.origin;
let ops = std::mem::take(&mut kept.ops);
self.fill_within(slot, origin, filler, |ui| {
for op in &ops {
replay_op(ui, op);
}
});
kept.ops = ops;
kept.frame = self.frame_no;
self.kept.insert(name.to_owned(), kept);
}
pub fn slot_fill(&self, name: &str) -> Option<SlotFill> {
self.find_slot_fill(name).map(|(f, _)| f)
}
pub fn slot_fill_why(&self, name: &str) -> Option<&str> {
self.find_slot_fill(name).and_then(|(_, why)| why)
}
fn find_slot_fill(&self, name: &str) -> Option<(SlotFill, Option<&str>)> {
let last: &[_] = if self.building {
&self.slot_fills_last
} else {
&[]
};
self.slot_fills
.iter()
.rev()
.chain(last.iter().rev())
.find(|(n, ..)| n == name)
.map(|(_, f, w)| (*f, w.as_deref()))
}
pub(crate) fn note_slot_fill(&mut self, name: &str, fill: SlotFill, why: Option<String>) {
self.slot_fills.push((name.to_owned(), fill, why));
}
pub fn slot_kept_nodes(&self, name: &str) -> Option<usize> {
self.kept.get(name).map(Kept::nodes)
}
pub(crate) fn replay_begin_frame(&mut self) {
self.slot_fills_last = std::mem::take(&mut self.slot_fills);
self.recording = None;
self.keep_next = None;
}
pub(crate) fn replay_finish_frame(&mut self) {
let now = self.frame_no;
self.kept.retain(|_, k| k.frame == now);
}
}
fn replay_op(ui: &mut Ui<'_>, op: &Op) {
let core = ui.core();
match op {
Op::Open { key, spec } => core.open_with_key(*key, (**spec).clone()),
Op::Close => core.close(),
Op::Text {
key,
content,
style,
} => core.text_with_key(*key, content, *style),
Op::Rich { key, spans, base } => {
let spans: Vec<Span<'_>> = spans.iter().map(|(text, s)| Span { text, ..*s }).collect();
core.rich_text_with_key(*key, &spans, *base);
}
Op::Image {
key,
id,
opts,
spec,
} => core.image_with_key(*key, *id, *opts, (**spec).clone()),
Op::Fragment {
key,
frag,
params,
spec,
} => core.fragment_with_key(*key, *frag, params, (**spec).clone()),
Op::Line {
key,
points,
stroke,
spec,
} => core.line_with_key(*key, points, stroke, (**spec).clone()),
Op::Polygon { key, points, spec } => {
core.polygon_with_key(*key, points, (**spec).clone());
}
Op::Path {
key,
ops,
rule,
stroke,
turn,
spec,
} => core.path_with_key(*key, ops, *rule, *stroke, *turn, (**spec).clone()),
Op::Edit {
label,
initial,
opts,
spec,
} => {
core.text_edit(label, initial, opts, (**spec).clone());
}
Op::Label { key, label } => core.note_label(*key, label),
Op::Indexed(i) => {
if let Some(at) = core.tree.len().checked_sub(1) {
core.tree.indexed.push((at as u32, *i));
}
}
Op::RowCount(n) => {
if let Some(at) = core.tree.len().checked_sub(1) {
core.tree.row_counts.push((at as u32, *n));
}
}
Op::RowCountOpen(n) => {
let at = core.current();
core.tree.row_counts.push((at, *n));
}
Op::Hint { key, text } => core.hint(*key, &**text),
Op::KeyFocus(key) => core.set_key_focus(Some(*key)),
Op::Slot { name, params } => {
ui.slot_with(name, params);
}
}
}