State
This commit is contained in:
parent
312197b276
commit
c47e4cb496
@ -2,7 +2,7 @@ use std::ptr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{AlignNode, ColumnsNode};
|
||||
use crate::meta::{Counter, CounterAction, CounterNode, Numbering};
|
||||
use crate::meta::{Counter, CounterAction, CounterKey, CounterNode, Numbering};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Layouts its child onto one or multiple pages.
|
||||
@ -311,9 +311,12 @@ impl PageNode {
|
||||
let header_ascent = self.header_ascent(styles);
|
||||
let footer = self.footer(styles).or_else(|| {
|
||||
self.numbering(styles).map(|numbering| {
|
||||
CounterNode::new(Counter::Page, CounterAction::Both(numbering))
|
||||
.pack()
|
||||
.aligned(self.number_align(styles))
|
||||
CounterNode::new(
|
||||
Counter::new(CounterKey::Page),
|
||||
CounterAction::Both(numbering),
|
||||
)
|
||||
.pack()
|
||||
.aligned(self.number_align(styles))
|
||||
})
|
||||
});
|
||||
let footer_descent = self.footer_descent(styles);
|
||||
|
@ -10,6 +10,7 @@ pub mod symbols;
|
||||
pub mod text;
|
||||
pub mod visualize;
|
||||
|
||||
use typst::diag::At;
|
||||
use typst::eval::{LangItems, Library, Module, Scope};
|
||||
use typst::geom::{Align, Color, Dir, GenAlign, Smart};
|
||||
use typst::model::{Node, NodeId, StyleMap};
|
||||
@ -93,6 +94,7 @@ fn global(math: Module, calc: Module) -> Module {
|
||||
global.define("bibliography", meta::BibliographyNode::id());
|
||||
global.define("counter", meta::counter);
|
||||
global.define("numbering", meta::numbering);
|
||||
global.define("state", meta::state);
|
||||
|
||||
// Symbols.
|
||||
global.define("sym", symbols::sym());
|
||||
@ -225,6 +227,15 @@ fn items() -> LangItems {
|
||||
math::AccentNode::new(base, math::Accent::new(accent)).pack()
|
||||
},
|
||||
math_frac: |num, denom| math::FracNode::new(num, denom).pack(),
|
||||
counter_method: meta::counter_method,
|
||||
library_method: |dynamic, method, args, span| {
|
||||
if let Some(counter) = dynamic.downcast().cloned() {
|
||||
meta::counter_method(counter, method, args, span)
|
||||
} else if let Some(state) = dynamic.downcast().cloned() {
|
||||
meta::state_method(state, method, args, span)
|
||||
} else {
|
||||
Err(format!("type {} has no method `{method}`", dynamic.type_name()))
|
||||
.at(span)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ use std::str::FromStr;
|
||||
|
||||
use ecow::{eco_vec, EcoVec};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use typst::eval::Dynamic;
|
||||
|
||||
use super::{Numbering, NumberingPattern};
|
||||
use crate::layout::PageNode;
|
||||
@ -13,20 +12,65 @@ use crate::prelude::*;
|
||||
///
|
||||
/// Display: Counter
|
||||
/// Category: meta
|
||||
/// Returns: content
|
||||
/// Returns: counter
|
||||
#[func]
|
||||
pub fn counter(key: Counter) -> Value {
|
||||
Value::dynamic(key)
|
||||
pub fn counter(
|
||||
/// The key that identifies this counter.
|
||||
key: CounterKey,
|
||||
) -> Value {
|
||||
Value::dynamic(Counter::new(key))
|
||||
}
|
||||
|
||||
/// Identifies a counter.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum CounterKey {
|
||||
/// The page counter.
|
||||
Page,
|
||||
/// Counts elements matching the given selectors. Only works for locatable
|
||||
/// elements or labels.
|
||||
Selector(Selector),
|
||||
/// Counts through manual counters with the same key.
|
||||
Str(Str),
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
CounterKey,
|
||||
v: Str => Self::Str(v),
|
||||
label: Label => Self::Selector(Selector::Label(label)),
|
||||
func: Func => {
|
||||
let Some(id) = func.id() else {
|
||||
return Err("this function is not selectable".into());
|
||||
};
|
||||
|
||||
if id == NodeId::of::<PageNode>() {
|
||||
return Ok(Self::Page);
|
||||
}
|
||||
|
||||
if !Content::new(id).can::<dyn Locatable>() {
|
||||
Err(eco_format!("cannot count through {}s", id.name))?;
|
||||
}
|
||||
|
||||
Self::Selector(Selector::Node(id, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CounterKey {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Page => f.pad("page"),
|
||||
Self::Selector(selector) => selector.fmt(f),
|
||||
Self::Str(str) => str.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a method on counter.
|
||||
pub fn counter_method(
|
||||
dynamic: &Dynamic,
|
||||
counter: Counter,
|
||||
method: &str,
|
||||
mut args: Args,
|
||||
span: Span,
|
||||
) -> SourceResult<Value> {
|
||||
let counter = dynamic.downcast::<Counter>().unwrap();
|
||||
let pattern = |s| NumberingPattern::from_str(s).unwrap().into();
|
||||
let action = match method {
|
||||
"get" => CounterAction::Get(args.eat()?.unwrap_or_else(|| pattern("1.1"))),
|
||||
@ -41,7 +85,7 @@ pub fn counter_method(
|
||||
|
||||
args.finish()?;
|
||||
|
||||
let content = CounterNode::new(counter.clone(), action).pack();
|
||||
let content = CounterNode::new(counter, action).pack();
|
||||
Ok(Value::Content(content))
|
||||
}
|
||||
|
||||
@ -53,7 +97,7 @@ pub fn counter_method(
|
||||
pub struct CounterNode {
|
||||
/// The counter key.
|
||||
#[required]
|
||||
pub key: Counter,
|
||||
pub counter: Counter,
|
||||
|
||||
/// The action.
|
||||
#[required]
|
||||
@ -64,24 +108,30 @@ impl Show for CounterNode {
|
||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
match self.action() {
|
||||
CounterAction::Get(numbering) => {
|
||||
self.key().resolve(vt, self.0.stable_id(), &numbering)
|
||||
self.counter().resolve(vt, self.0.stable_id(), &numbering)
|
||||
}
|
||||
CounterAction::Final(numbering) => {
|
||||
self.counter().resolve(vt, None, &numbering)
|
||||
}
|
||||
CounterAction::Final(numbering) => self.key().resolve(vt, None, &numbering),
|
||||
CounterAction::Both(numbering) => {
|
||||
let both = match &numbering {
|
||||
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let key = self.key();
|
||||
let counter = self.counter();
|
||||
let id = self.0.stable_id();
|
||||
if !both {
|
||||
return key.resolve(vt, id, &numbering);
|
||||
return counter.resolve(vt, id, &numbering);
|
||||
}
|
||||
|
||||
let sequence = key.sequence(vt.world, vt.introspector)?;
|
||||
let numbers = [sequence.single(id), sequence.single(None)];
|
||||
Ok(numbering.apply(vt.world, &numbers)?.display())
|
||||
let sequence = counter.sequence(vt.world, vt.introspector)?;
|
||||
Ok(match (sequence.single(id), sequence.single(None)) {
|
||||
(Some(current), Some(total)) => {
|
||||
numbering.apply(vt.world, &[current, total])?.display()
|
||||
}
|
||||
_ => Content::empty(),
|
||||
})
|
||||
}
|
||||
CounterAction::Update(_) => Ok(Content::empty()),
|
||||
}
|
||||
@ -109,7 +159,12 @@ cast_from_value! {
|
||||
|
||||
impl Debug for CounterAction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("..")
|
||||
match self {
|
||||
Self::Get(_) => f.pad("get(..)"),
|
||||
Self::Final(_) => f.pad("final(..)"),
|
||||
Self::Both(_) => f.pad("both(..)"),
|
||||
Self::Update(_) => f.pad("update(..)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,17 +193,22 @@ pub trait Count {
|
||||
|
||||
/// Counts through pages, elements, and more.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum Counter {
|
||||
/// The page counter.
|
||||
Page,
|
||||
/// Counts elements matching the given selectors. Only works for locatable
|
||||
/// elements or labels.
|
||||
Selector(Selector),
|
||||
/// Counts through manual counters with the same key.
|
||||
Str(Str),
|
||||
pub struct Counter {
|
||||
/// The key that identifies the counter.
|
||||
pub key: CounterKey,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
/// Create a new counter from a key.
|
||||
pub fn new(key: CounterKey) -> Self {
|
||||
Self { key }
|
||||
}
|
||||
|
||||
/// The counter for the given node.
|
||||
pub fn of(id: NodeId) -> Self {
|
||||
Self::new(CounterKey::Selector(Selector::Node(id, None)))
|
||||
}
|
||||
|
||||
/// Display the value of the counter at the postition of the given stable
|
||||
/// id.
|
||||
pub fn resolve(
|
||||
@ -157,9 +217,15 @@ impl Counter {
|
||||
stop: Option<StableId>,
|
||||
numbering: &Numbering,
|
||||
) -> SourceResult<Content> {
|
||||
if !vt.introspector.init() {
|
||||
return Ok(Content::empty());
|
||||
}
|
||||
|
||||
let sequence = self.sequence(vt.world, vt.introspector)?;
|
||||
let numbers = sequence.at(stop).0;
|
||||
Ok(numbering.apply(vt.world, &numbers)?.display())
|
||||
Ok(match sequence.at(stop) {
|
||||
Some(state) => numbering.apply(vt.world, &state.0)?.display(),
|
||||
None => Content::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Produce the whole sequence of counter states.
|
||||
@ -174,21 +240,21 @@ impl Counter {
|
||||
) -> SourceResult<CounterSequence> {
|
||||
let mut search = Selector::Node(
|
||||
NodeId::of::<CounterNode>(),
|
||||
Some(dict! { "key" => self.clone() }),
|
||||
Some(dict! { "counter" => self.clone() }),
|
||||
);
|
||||
|
||||
if let Counter::Selector(selector) = self {
|
||||
if let CounterKey::Selector(selector) = &self.key {
|
||||
search = Selector::Any(eco_vec![search, selector.clone()]);
|
||||
}
|
||||
|
||||
let mut state = CounterState::new();
|
||||
let mut stops = EcoVec::new();
|
||||
let mut state = CounterState(match &self.key {
|
||||
CounterKey::Selector(_) => smallvec![],
|
||||
_ => smallvec![NonZeroUsize::ONE],
|
||||
});
|
||||
|
||||
let is_page = self.key == CounterKey::Page;
|
||||
let mut prev_page = NonZeroUsize::ONE;
|
||||
let is_page = *self == Self::Page;
|
||||
if is_page {
|
||||
state.0.push(prev_page);
|
||||
}
|
||||
|
||||
for node in introspector.query(search) {
|
||||
let id = node.stable_id().unwrap();
|
||||
@ -221,40 +287,18 @@ impl Counter {
|
||||
}
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
Counter: "counter",
|
||||
v: Str => Self::Str(v),
|
||||
v: Selector => {
|
||||
match v {
|
||||
Selector::Node(id, _) => {
|
||||
if id == NodeId::of::<PageNode>() {
|
||||
return Ok(Self::Page);
|
||||
}
|
||||
|
||||
if !Content::new_of(id).can::<dyn Locatable>() {
|
||||
Err(eco_format!("cannot count through {}s", id.name))?;
|
||||
}
|
||||
}
|
||||
Selector::Label(_) => {}
|
||||
Selector::Regex(_) => Err("cannot count through text")?,
|
||||
Selector::Any(_) => {}
|
||||
}
|
||||
Self::Selector(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Counter {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("counter(")?;
|
||||
match self {
|
||||
Self::Page => f.pad("page")?,
|
||||
Self::Selector(selector) => selector.fmt(f)?,
|
||||
Self::Str(str) => str.fmt(f)?,
|
||||
}
|
||||
self.key.fmt(f)?;
|
||||
f.write_char(')')
|
||||
}
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
Counter: "counter",
|
||||
}
|
||||
|
||||
/// A sequence of counter values.
|
||||
#[derive(Debug, Clone)]
|
||||
struct CounterSequence {
|
||||
@ -263,38 +307,33 @@ struct CounterSequence {
|
||||
}
|
||||
|
||||
impl CounterSequence {
|
||||
fn at(&self, stop: Option<StableId>) -> CounterState {
|
||||
fn at(&self, stop: Option<StableId>) -> Option<CounterState> {
|
||||
let entry = match stop {
|
||||
Some(stop) => self.stops.iter().find(|&&(id, _)| id == stop),
|
||||
None => self.stops.last(),
|
||||
};
|
||||
|
||||
if let Some((_, state)) = entry {
|
||||
return state.clone();
|
||||
return Some(state.clone());
|
||||
}
|
||||
|
||||
if self.is_page {
|
||||
return CounterState(smallvec![NonZeroUsize::ONE]);
|
||||
return Some(CounterState(smallvec![NonZeroUsize::ONE]));
|
||||
}
|
||||
|
||||
CounterState::default()
|
||||
None
|
||||
}
|
||||
|
||||
fn single(&self, stop: Option<StableId>) -> NonZeroUsize {
|
||||
self.at(stop).0.first().copied().unwrap_or(NonZeroUsize::ONE)
|
||||
fn single(&self, stop: Option<StableId>) -> Option<NonZeroUsize> {
|
||||
Some(*self.at(stop)?.0.first()?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Counts through elements with different levels.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct CounterState(pub SmallVec<[NonZeroUsize; 3]>);
|
||||
|
||||
impl CounterState {
|
||||
/// Create a new levelled counter.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Advance the counter and return the numbers for the given heading.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
|
@ -60,7 +60,7 @@ impl Show for FigureNode {
|
||||
let name = self.local_name(TextNode::lang_in(styles));
|
||||
caption = TextNode::packed(eco_format!("{name}\u{a0}"))
|
||||
+ CounterNode::new(
|
||||
Counter::Selector(Selector::node::<Self>()),
|
||||
Counter::of(Self::id()),
|
||||
CounterAction::Get(numbering),
|
||||
)
|
||||
.pack()
|
||||
|
@ -91,14 +91,12 @@ impl Show for HeadingNode {
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let mut realized = self.body();
|
||||
if let Some(numbering) = self.numbering(styles) {
|
||||
realized = CounterNode::new(
|
||||
Counter::Selector(Selector::node::<Self>()),
|
||||
CounterAction::Get(numbering),
|
||||
)
|
||||
.pack()
|
||||
.spanned(self.span())
|
||||
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
||||
+ realized;
|
||||
realized =
|
||||
CounterNode::new(Counter::of(Self::id()), CounterAction::Get(numbering))
|
||||
.pack()
|
||||
.spanned(self.span())
|
||||
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
||||
+ realized;
|
||||
}
|
||||
Ok(BlockNode::new().with_body(Some(realized)).pack())
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ mod link;
|
||||
mod numbering;
|
||||
mod outline;
|
||||
mod reference;
|
||||
mod state;
|
||||
|
||||
pub use self::bibliography::*;
|
||||
pub use self::counter::*;
|
||||
@ -19,6 +20,7 @@ pub use self::link::*;
|
||||
pub use self::numbering::*;
|
||||
pub use self::outline::*;
|
||||
pub use self::reference::*;
|
||||
pub use self::state::*;
|
||||
|
||||
use typst::doc::Lang;
|
||||
|
||||
|
@ -118,8 +118,11 @@ impl Show for OutlineNode {
|
||||
let mut hidden = Content::empty();
|
||||
for ancestor in &ancestors {
|
||||
if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
|
||||
let numbers = Counter::Selector(Selector::node::<HeadingNode>())
|
||||
.resolve(vt, ancestor.0.stable_id(), &numbering)?;
|
||||
let numbers = Counter::of(HeadingNode::id()).resolve(
|
||||
vt,
|
||||
ancestor.0.stable_id(),
|
||||
&numbering,
|
||||
)?;
|
||||
hidden += numbers + SpaceNode::new().pack();
|
||||
};
|
||||
}
|
||||
@ -133,8 +136,11 @@ impl Show for OutlineNode {
|
||||
// Format the numbering.
|
||||
let mut start = heading.body();
|
||||
if let Some(numbering) = heading.numbering(StyleChain::default()) {
|
||||
let numbers = Counter::Selector(Selector::node::<HeadingNode>())
|
||||
.resolve(vt, Some(stable_id), &numbering)?;
|
||||
let numbers = Counter::of(HeadingNode::id()).resolve(
|
||||
vt,
|
||||
Some(stable_id),
|
||||
&numbering,
|
||||
)?;
|
||||
start = numbers + SpaceNode::new().pack() + start;
|
||||
};
|
||||
|
||||
|
@ -115,11 +115,8 @@ impl Show for RefNode {
|
||||
bail!(self.span(), "only numbered elements can be referenced");
|
||||
};
|
||||
|
||||
let numbers = Counter::Selector(Selector::Node(node.id(), None)).resolve(
|
||||
vt,
|
||||
node.stable_id(),
|
||||
&numbering.trimmed(),
|
||||
)?;
|
||||
let numbers =
|
||||
Counter::of(node.id()).resolve(vt, node.stable_id(), &numbering.trimmed())?;
|
||||
|
||||
Ok((supplement + numbers).linked(Link::Node(node.stable_id().unwrap())))
|
||||
}
|
||||
|
209
library/src/meta/state.rs
Normal file
209
library/src/meta/state.rs
Normal file
@ -0,0 +1,209 @@
|
||||
use std::fmt::{self, Debug, Formatter, Write};
|
||||
|
||||
use ecow::EcoVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Handle stateful tasks.
|
||||
///
|
||||
/// Display: State
|
||||
/// Category: meta
|
||||
/// Returns: state
|
||||
#[func]
|
||||
pub fn state(
|
||||
/// The key that identifies this state.
|
||||
key: Str,
|
||||
/// The initial value of the state.
|
||||
#[default]
|
||||
init: Value,
|
||||
) -> Value {
|
||||
Value::dynamic(State { key, init })
|
||||
}
|
||||
|
||||
/// Call a method on a state.
|
||||
pub fn state_method(
|
||||
state: State,
|
||||
method: &str,
|
||||
mut args: Args,
|
||||
span: Span,
|
||||
) -> SourceResult<Value> {
|
||||
let action = match method {
|
||||
"get" => StateAction::Get(args.eat()?),
|
||||
"final" => StateAction::Final(args.eat()?),
|
||||
"update" => StateAction::Update(args.expect("value or function")?),
|
||||
_ => bail!(span, "type state has no method `{}`", method),
|
||||
};
|
||||
|
||||
args.finish()?;
|
||||
|
||||
let content = StateNode::new(state, action).pack();
|
||||
Ok(Value::Content(content))
|
||||
}
|
||||
|
||||
/// Executes an action on a state.
|
||||
///
|
||||
/// Display: State
|
||||
/// Category: special
|
||||
#[node(Locatable, Show)]
|
||||
pub struct StateNode {
|
||||
/// The state.
|
||||
#[required]
|
||||
pub state: State,
|
||||
|
||||
/// The action.
|
||||
#[required]
|
||||
pub action: StateAction,
|
||||
}
|
||||
|
||||
impl Show for StateNode {
|
||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
match self.action() {
|
||||
StateAction::Get(func) => self.state().resolve(vt, self.0.stable_id(), func),
|
||||
StateAction::Final(func) => self.state().resolve(vt, None, func),
|
||||
StateAction::Update(_) => Ok(Content::empty()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The action to perform on the state.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum StateAction {
|
||||
/// Displays the current state.
|
||||
Get(Option<Func>),
|
||||
/// Displays the final state.
|
||||
Final(Option<Func>),
|
||||
/// Updates the state, possibly based on the previous one.
|
||||
Update(StateUpdate),
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
StateAction: "state action",
|
||||
}
|
||||
|
||||
impl Debug for StateAction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Get(_) => f.pad("get(..)"),
|
||||
Self::Final(_) => f.pad("final(..)"),
|
||||
Self::Update(_) => f.pad("update(..)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An update to perform on a state.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum StateUpdate {
|
||||
/// Set the state to the specified value.
|
||||
Set(Value),
|
||||
/// Apply the given function to the state.
|
||||
Func(Func),
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
StateUpdate,
|
||||
v: Func => Self::Func(v),
|
||||
v: Value => Self::Set(v),
|
||||
}
|
||||
|
||||
/// A state.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub struct State {
|
||||
/// The key that identifies the state.
|
||||
key: Str,
|
||||
/// The initial value of the state.
|
||||
init: Value,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Display the state at the postition of the given stable id.
|
||||
fn resolve(
|
||||
&self,
|
||||
vt: &Vt,
|
||||
stop: Option<StableId>,
|
||||
func: Option<Func>,
|
||||
) -> SourceResult<Content> {
|
||||
if !vt.introspector.init() {
|
||||
return Ok(Content::empty());
|
||||
}
|
||||
|
||||
let sequence = self.sequence(vt.world, vt.introspector)?;
|
||||
Ok(match sequence.at(stop) {
|
||||
Some(value) => {
|
||||
if let Some(func) = func {
|
||||
let args = Args::new(func.span(), [value]);
|
||||
func.call_detached(vt.world, args)?.display()
|
||||
} else {
|
||||
value.display()
|
||||
}
|
||||
}
|
||||
None => Content::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Produce the whole sequence of states.
|
||||
///
|
||||
/// This has to happen just once for all states, cutting down the number
|
||||
/// of state updates from quadratic to linear.
|
||||
#[comemo::memoize]
|
||||
fn sequence(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
introspector: Tracked<Introspector>,
|
||||
) -> SourceResult<StateSequence> {
|
||||
let search = Selector::Node(
|
||||
NodeId::of::<StateNode>(),
|
||||
Some(dict! { "state" => self.clone() }),
|
||||
);
|
||||
|
||||
let mut stops = EcoVec::new();
|
||||
let mut state = self.init.clone();
|
||||
|
||||
for node in introspector.query(search) {
|
||||
let id = node.stable_id().unwrap();
|
||||
let node = node.to::<StateNode>().unwrap();
|
||||
|
||||
if let StateAction::Update(update) = node.action() {
|
||||
match update {
|
||||
StateUpdate::Set(value) => state = value,
|
||||
StateUpdate::Func(func) => {
|
||||
let args = Args::new(func.span(), [state]);
|
||||
state = func.call_detached(world, args)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stops.push((id, state.clone()));
|
||||
}
|
||||
|
||||
Ok(StateSequence(stops))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for State {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("state(")?;
|
||||
self.key.fmt(f)?;
|
||||
f.write_str(", ")?;
|
||||
self.init.fmt(f)?;
|
||||
f.write_char(')')
|
||||
}
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
State: "state",
|
||||
}
|
||||
|
||||
/// A sequence of state values.
|
||||
#[derive(Debug, Clone)]
|
||||
struct StateSequence(EcoVec<(StableId, Value)>);
|
||||
|
||||
impl StateSequence {
|
||||
fn at(&self, stop: Option<StableId>) -> Option<Value> {
|
||||
let entry = match stop {
|
||||
Some(stop) => self.0.iter().find(|&&(id, _)| id == stop),
|
||||
None => self.0.last(),
|
||||
};
|
||||
|
||||
entry.map(|(_, value)| value.clone())
|
||||
}
|
||||
}
|
@ -234,7 +234,7 @@ fn create_new_func(node: &Node) -> TokenStream {
|
||||
quote! {
|
||||
/// Create a new node.
|
||||
pub fn new(#(#params),*) -> Self {
|
||||
Self(::typst::model::Content::new::<Self>())
|
||||
Self(::typst::model::Content::new(<Self as ::typst::model::Node>::id()))
|
||||
#(#builder_calls)*
|
||||
}
|
||||
}
|
||||
@ -388,7 +388,7 @@ fn create_vtable_func(node: &Node) -> TokenStream {
|
||||
|
||||
quote! {
|
||||
|id| {
|
||||
let null = Self(::typst::model::Content::new::<#ident>());
|
||||
let null = Self(::typst::model::Content::new(<#ident as ::typst::model::Node>::id()));
|
||||
#(#checks)*
|
||||
None
|
||||
}
|
||||
@ -456,7 +456,7 @@ fn create_construct_impl(node: &Node) -> TokenStream {
|
||||
vm: &::typst::eval::Vm,
|
||||
args: &mut ::typst::eval::Args,
|
||||
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
||||
let mut node = Self(::typst::model::Content::new::<Self>());
|
||||
let mut node = Self(::typst::model::Content::new(<Self as ::typst::model::Node>::id()));
|
||||
#(#handlers)*
|
||||
Ok(node.0)
|
||||
}
|
||||
|
@ -90,9 +90,8 @@ pub struct LangItems {
|
||||
pub math_accent: fn(base: Content, accent: char) -> Content,
|
||||
/// A fraction in a formula: `x/2`.
|
||||
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
||||
/// Dispatch a method on a counter. This is hacky and should be superseded
|
||||
/// by more dynamic method dispatch.
|
||||
pub counter_method: fn(
|
||||
/// Dispatch a method on a library value.
|
||||
pub library_method: fn(
|
||||
dynamic: &Dynamic,
|
||||
method: &str,
|
||||
args: Args,
|
||||
|
@ -135,11 +135,7 @@ pub fn call(
|
||||
},
|
||||
|
||||
Value::Dyn(dynamic) => {
|
||||
if dynamic.type_name() == "counter" {
|
||||
return (vm.items.counter_method)(&dynamic, method, args, span);
|
||||
}
|
||||
|
||||
return missing();
|
||||
return (vm.items.library_method)(&dynamic, method, args, span);
|
||||
}
|
||||
|
||||
_ => return missing(),
|
||||
@ -296,6 +292,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
||||
("step", true),
|
||||
("update", true),
|
||||
],
|
||||
"state" => &[("get", true), ("final", true), ("update", true)],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
@ -40,12 +40,7 @@ enum Modifier {
|
||||
|
||||
impl Content {
|
||||
/// Create a content of the given node kind.
|
||||
pub fn new<T: Node>() -> Self {
|
||||
Self::new_of(T::id())
|
||||
}
|
||||
|
||||
/// Create a content of the given node kind.
|
||||
pub fn new_of(id: NodeId) -> Self {
|
||||
pub fn new(id: NodeId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
span: Span::detached(),
|
||||
|
BIN
tests/ref/meta/state.png
Normal file
BIN
tests/ref/meta/state.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
@ -6,7 +6,6 @@
|
||||
|
||||
Final: #mine.final() \
|
||||
#mine.step()
|
||||
#mine.step()
|
||||
First: #mine.get() \
|
||||
#mine.update(7)
|
||||
#mine.both("1 of 1") \
|
||||
|
24
tests/typ/meta/state.typ
Normal file
24
tests/typ/meta/state.typ
Normal file
@ -0,0 +1,24 @@
|
||||
// Test state.
|
||||
|
||||
---
|
||||
#set page(width: 200pt)
|
||||
#set text(8pt)
|
||||
|
||||
#let ls = state("lorem", lorem(1000).split("."))
|
||||
#let loremum(count) = {
|
||||
ls.get(list => list.slice(0, count).join(".").trim() + ".")
|
||||
ls.update(list => list.slice(count))
|
||||
}
|
||||
|
||||
#let fs = state("fader", red)
|
||||
#let trait(title) = block[
|
||||
#fs.get(color => text(fill: color)[
|
||||
*#title:* #loremum(1)
|
||||
])
|
||||
#fs.update(color => color.lighten(30%))
|
||||
]
|
||||
|
||||
#trait[Boldness]
|
||||
#trait[Adventure]
|
||||
#trait[Fear]
|
||||
#trait[Anger]
|
Loading…
x
Reference in New Issue
Block a user