summaryrefslogtreecommitdiff
path: root/library/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-12 23:25:54 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-12 23:46:26 +0100
commitbf59c08a0a601eeac4354c505cab15e65601c8e8 (patch)
tree2ba3230c77960acf5d55ee34065a5028d1c45d5d /library/src/layout
parentd9ce194fe71076314955dd25896f64d48bccd6e5 (diff)
New interaction model
Diffstat (limited to 'library/src/layout')
-rw-r--r--library/src/layout/columns.rs12
-rw-r--r--library/src/layout/container.rs16
-rw-r--r--library/src/layout/flow.rs95
-rw-r--r--library/src/layout/mod.rs228
-rw-r--r--library/src/layout/place.rs8
-rw-r--r--library/src/layout/spacing.rs95
6 files changed, 165 insertions, 289 deletions
diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs
index 2faa6329..ec9510d8 100644
--- a/library/src/layout/columns.rs
+++ b/library/src/layout/columns.rs
@@ -104,10 +104,20 @@ pub struct ColbreakNode {
pub weak: bool,
}
-#[node]
+#[node(Behave)]
impl ColbreakNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let weak = args.named("weak")?.unwrap_or(false);
Ok(Self { weak }.pack())
}
}
+
+impl Behave for ColbreakNode {
+ fn behaviour(&self) -> Behaviour {
+ if self.weak {
+ Behaviour::Weak(1)
+ } else {
+ Behaviour::Destructive
+ }
+ }
+}
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs
index 22a9e02e..20d80cba 100644
--- a/library/src/layout/container.rs
+++ b/library/src/layout/container.rs
@@ -66,19 +66,25 @@ pub struct BlockNode(pub Content);
impl BlockNode {
/// The spacing between the previous and this block.
#[property(skip)]
- pub const ABOVE: VNode = VNode::weak(Em::new(1.2).into());
+ pub const ABOVE: VNode = VNode::block_spacing(Em::new(1.2).into());
/// The spacing between this and the following block.
#[property(skip)]
- pub const BELOW: VNode = VNode::weak(Em::new(1.2).into());
+ pub const BELOW: VNode = VNode::block_spacing(Em::new(1.2).into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.eat()?.unwrap_or_default()).pack())
}
fn set(...) {
- let spacing = args.named("spacing")?.map(VNode::weak);
- styles.set_opt(Self::ABOVE, args.named("above")?.map(VNode::strong).or(spacing));
- styles.set_opt(Self::BELOW, args.named("below")?.map(VNode::strong).or(spacing));
+ let spacing = args.named("spacing")?.map(VNode::block_spacing);
+ styles.set_opt(
+ Self::ABOVE,
+ args.named("above")?.map(VNode::block_around).or(spacing),
+ );
+ styles.set_opt(
+ Self::BELOW,
+ args.named("below")?.map(VNode::block_around).or(spacing),
+ );
}
}
diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs
index 822d2c38..b05146c9 100644
--- a/library/src/layout/flow.rs
+++ b/library/src/layout/flow.rs
@@ -1,7 +1,4 @@
-use std::cmp::Ordering;
-
-use super::{AlignNode, PlaceNode, Spacing, VNode};
-use crate::layout::BlockNode;
+use super::{AlignNode, ColbreakNode, PlaceNode, Spacing, VNode};
use crate::prelude::*;
use crate::text::ParNode;
@@ -10,18 +7,7 @@ use crate::text::ParNode;
/// This node is reponsible for layouting both the top-level content flow and
/// the contents of boxes.
#[derive(Hash)]
-pub struct FlowNode(pub StyleVec<FlowChild>);
-
-/// A child of a flow node.
-#[derive(Hash, PartialEq)]
-pub enum FlowChild {
- /// Vertical spacing between other children.
- Spacing(VNode),
- /// Arbitrary block-level content.
- Block(Content),
- /// A column / region break.
- Colbreak,
-}
+pub struct FlowNode(pub StyleVec<Content>);
#[node(LayoutBlock)]
impl FlowNode {}
@@ -33,20 +19,18 @@ impl LayoutBlock for FlowNode {
regions: &Regions,
styles: StyleChain,
) -> SourceResult<Vec<Frame>> {
- let mut layouter = FlowLayouter::new(regions, styles);
+ let mut layouter = FlowLayouter::new(regions);
for (child, map) in self.0.iter() {
let styles = map.chain(&styles);
- match child {
- FlowChild::Spacing(node) => {
- layouter.layout_spacing(node, styles);
- }
- FlowChild::Block(block) => {
- layouter.layout_block(world, block, styles)?;
- }
- FlowChild::Colbreak => {
- layouter.finish_region();
- }
+ if let Some(&node) = child.downcast::<VNode>() {
+ layouter.layout_spacing(node.amount, styles);
+ } else if child.has::<dyn LayoutBlock>() {
+ layouter.layout_block(world, child, styles)?;
+ } else if child.is::<ColbreakNode>() {
+ layouter.finish_region();
+ } else {
+ panic!("unexpected flow child: {child:?}");
}
}
@@ -61,31 +45,10 @@ impl Debug for FlowNode {
}
}
-impl Debug for FlowChild {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Spacing(kind) => write!(f, "{:?}", kind),
- Self::Block(block) => block.fmt(f),
- Self::Colbreak => f.pad("Colbreak"),
- }
- }
-}
-
-impl PartialOrd for FlowChild {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- match (self, other) {
- (Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b),
- _ => None,
- }
- }
-}
-
/// Performs flow layout.
-struct FlowLayouter<'a> {
+struct FlowLayouter {
/// The regions to layout children into.
regions: Regions,
- /// The shared styles.
- shared: StyleChain<'a>,
/// Whether the flow should expand to fill the region.
expand: Axes<bool>,
/// The full size of `regions.size` that was available before we started
@@ -95,8 +58,6 @@ struct FlowLayouter<'a> {
used: Size,
/// The sum of fractions in the current region.
fr: Fr,
- /// The spacing below the last block.
- below: Option<VNode>,
/// Spacing and layouted blocks.
items: Vec<FlowItem>,
/// Finished frames for previous regions.
@@ -115,9 +76,9 @@ enum FlowItem {
Placed(Frame),
}
-impl<'a> FlowLayouter<'a> {
+impl FlowLayouter {
/// Create a new flow layouter.
- fn new(regions: &Regions, shared: StyleChain<'a>) -> Self {
+ fn new(regions: &Regions) -> Self {
let expand = regions.expand;
let full = regions.first;
@@ -127,20 +88,18 @@ impl<'a> FlowLayouter<'a> {
Self {
regions,
- shared,
expand,
full,
used: Size::zero(),
fr: Fr::zero(),
- below: None,
items: vec![],
finished: vec![],
}
}
- /// Layout spacing.
- fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) {
- match node.amount {
+ /// Actually layout the spacing.
+ fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) {
+ match spacing {
Spacing::Relative(v) => {
// Resolve the spacing and limit it to the remaining space.
let resolved = v.resolve(styles).relative_to(self.full.y);
@@ -154,10 +113,6 @@ impl<'a> FlowLayouter<'a> {
self.fr += v;
}
}
-
- if node.weak || node.amount.is_fractional() {
- self.below = None;
- }
}
/// Layout a block.
@@ -172,19 +127,9 @@ impl<'a> FlowLayouter<'a> {
self.finish_region();
}
- // Add spacing between the last block and this one.
- if let Some(below) = self.below.take() {
- let above = styles.get(BlockNode::ABOVE);
- let pick_below = (above.weak && !below.weak) || (below.amount > above.amount);
- let spacing = if pick_below { below } else { above };
- self.layout_spacing(&spacing, self.shared);
- }
-
// Placed nodes that are out of flow produce placed items which aren't
// aligned later.
- let mut is_placed = false;
if let Some(placed) = block.downcast::<PlaceNode>() {
- is_placed = true;
if placed.out_of_flow() {
let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
self.items.push(FlowItem::Placed(frame));
@@ -205,6 +150,7 @@ impl<'a> FlowLayouter<'a> {
.unwrap_or(Align::Top),
);
+ // Layout the block itself.
let frames = block.layout_block(world, &self.regions, styles)?;
let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() {
@@ -220,10 +166,6 @@ impl<'a> FlowLayouter<'a> {
}
}
- if !is_placed {
- self.below = Some(styles.get(BlockNode::BELOW));
- }
-
Ok(())
}
@@ -272,7 +214,6 @@ impl<'a> FlowLayouter<'a> {
self.full = self.regions.first;
self.used = Size::zero();
self.fr = Fr::zero();
- self.below = None;
self.finished.push(output);
}
diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs
index e0eb431c..d6bc9175 100644
--- a/library/src/layout/mod.rs
+++ b/library/src/layout/mod.rs
@@ -32,16 +32,18 @@ use typst::diag::SourceResult;
use typst::frame::Frame;
use typst::geom::*;
use typst::model::{
- capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVec,
+ capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry,
StyleVecBuilder, StyledNode, Target,
};
use typst::World;
+use crate::core::BehavedBuilder;
+use crate::prelude::*;
use crate::structure::{
DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
};
use crate::text::{
- LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
+ LinebreakNode, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
};
/// Root-level layout.
@@ -468,41 +470,17 @@ impl Default for DocBuilder<'_> {
/// Accepts flow content.
#[derive(Default)]
-struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>, bool);
+struct FlowBuilder<'a>(BehavedBuilder<'a>, bool);
impl<'a> FlowBuilder<'a> {
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
- // Weak flow elements:
- // Weakness | Element
- // 0 | weak colbreak
- // 1 | weak fractional spacing
- // 2 | weak spacing
- // 3 | generated weak spacing
- // 4 | generated weak fractional spacing
- // 5 | par spacing
-
let last_was_parbreak = self.1;
self.1 = false;
if content.is::<ParbreakNode>() {
self.1 = true;
- } else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
- if colbreak.weak {
- self.0.weak(FlowChild::Colbreak, styles, 0);
- } else {
- self.0.destructive(FlowChild::Colbreak, styles);
- }
- } else if let Some(vertical) = content.downcast::<VNode>() {
- let child = FlowChild::Spacing(*vertical);
- let frac = vertical.amount.is_fractional();
- if vertical.weak {
- let weakness = 1 + u8::from(frac);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
+ } else if content.is::<VNode>() || content.is::<ColbreakNode>() {
+ self.0.push(content.clone(), styles);
} else if content.has::<dyn LayoutBlock>() {
if !last_was_parbreak {
let tight = if let Some(node) = content.downcast::<ListNode>() {
@@ -517,17 +495,16 @@ impl<'a> FlowBuilder<'a> {
if tight {
let leading = styles.get(ParNode::LEADING);
- let spacing = VNode::weak(leading.into());
- self.0.weak(FlowChild::Spacing(spacing), styles, 1);
+ let spacing = VNode::list_attach(leading.into());
+ self.0.push(spacing.pack(), styles);
}
}
- let child = FlowChild::Block(content.clone());
- if content.is::<PlaceNode>() {
- self.0.ignorant(child, styles);
- } else {
- self.0.supportive(child, styles);
- }
+ let above = styles.get(BlockNode::ABOVE);
+ let below = styles.get(BlockNode::BELOW);
+ self.0.push(above.pack(), styles);
+ self.0.push(content.clone(), styles);
+ self.0.push(below.pack(), styles);
} else {
return false;
}
@@ -549,43 +526,22 @@ impl<'a> FlowBuilder<'a> {
/// Accepts paragraph content.
#[derive(Default)]
-struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
+struct ParBuilder<'a>(BehavedBuilder<'a>);
impl<'a> ParBuilder<'a> {
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
- // Weak par elements:
- // Weakness | Element
- // 0 | weak fractional spacing
- // 1 | weak spacing
- // 2 | space
-
- if content.is::<SpaceNode>() {
- self.0.weak(ParChild::Text(' '.into()), styles, 2);
- } else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
- let c = if linebreak.justify { '\u{2028}' } else { '\n' };
- self.0.destructive(ParChild::Text(c.into()), styles);
- } else if let Some(horizontal) = content.downcast::<HNode>() {
- let child = ParChild::Spacing(horizontal.amount);
- let frac = horizontal.amount.is_fractional();
- if horizontal.weak {
- let weakness = u8::from(!frac);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
- } else if let Some(quote) = content.downcast::<SmartQuoteNode>() {
- self.0.supportive(ParChild::Quote { double: quote.double }, styles);
- } else if let Some(text) = content.downcast::<TextNode>() {
- self.0.supportive(ParChild::Text(text.0.clone()), styles);
- } else if content.has::<dyn LayoutInline>() {
- self.0.supportive(ParChild::Inline(content.clone()), styles);
- } else {
- return false;
+ if content.is::<SpaceNode>()
+ || content.is::<LinebreakNode>()
+ || content.is::<HNode>()
+ || content.is::<SmartQuoteNode>()
+ || content.is::<TextNode>()
+ || content.has::<dyn LayoutInline>()
+ {
+ self.0.push(content.clone(), styles);
+ return true;
}
- true
+ false
}
fn finish(self, parent: &mut Builder<'a>) {
@@ -600,10 +556,14 @@ impl<'a> ParBuilder<'a> {
if !indent.is_zero()
&& children
.items()
- .find_map(|child| match child {
- ParChild::Spacing(_) => None,
- ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
- ParChild::Inline(_) => Some(false),
+ .find_map(|child| {
+ if child.is::<TextNode>() || child.is::<SmartQuoteNode>() {
+ Some(true)
+ } else if child.has::<dyn LayoutInline>() {
+ Some(false)
+ } else {
+ None
+ }
})
.unwrap_or_default()
&& parent
@@ -611,14 +571,10 @@ impl<'a> ParBuilder<'a> {
.0
.items()
.rev()
- .find_map(|child| match child {
- FlowChild::Spacing(_) => None,
- FlowChild::Block(content) => Some(content.is::<ParNode>()),
- FlowChild::Colbreak => Some(false),
- })
- .unwrap_or_default()
+ .find(|child| child.has::<dyn LayoutBlock>())
+ .map_or(false, |child| child.is::<ParNode>())
{
- children.push_front(ParChild::Spacing(indent.into()));
+ children.push_front(HNode::strong(indent.into()).pack());
}
parent.flow.accept(&ParNode(children).pack(), shared);
@@ -701,115 +657,3 @@ impl Default for ListBuilder<'_> {
}
}
}
-
-/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
-struct CollapsingBuilder<'a, T> {
- /// The internal builder.
- builder: StyleVecBuilder<'a, T>,
- /// Staged weak and ignorant items that we can't yet commit to the builder.
- /// The option is `Some(_)` for weak items and `None` for ignorant items.
- staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
- /// What the last non-ignorant item was.
- last: Last,
-}
-
-/// What the last non-ignorant item was.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum Last {
- Weak,
- Destructive,
- Supportive,
-}
-
-impl<'a, T> CollapsingBuilder<'a, T> {
- /// Create a new style-vec builder.
- fn new() -> Self {
- Self {
- builder: StyleVecBuilder::new(),
- staged: vec![],
- last: Last::Destructive,
- }
- }
-
- /// Whether the builder is empty.
- fn is_empty(&self) -> bool {
- self.builder.is_empty() && self.staged.is_empty()
- }
-
- /// Can only exist when there is at least one supportive item to its left
- /// and to its right, with no destructive items in between. There may be
- /// ignorant items in between in both directions.
- ///
- /// Between weak items, there may be at least one per layer and among the
- /// candidates the strongest one (smallest `weakness`) wins. When tied,
- /// the one that compares larger through `PartialOrd` wins.
- fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8)
- where
- T: PartialOrd,
- {
- if self.last == Last::Destructive {
- return;
- }
-
- if self.last == Last::Weak {
- let weak = self.staged.iter().position(|(prev_item, _, prev_weakness)| {
- prev_weakness.map_or(false, |prev_weakness| {
- weakness < prev_weakness
- || (weakness == prev_weakness && item > *prev_item)
- })
- });
-
- let Some(weak) = weak else { return };
- self.staged.remove(weak);
- }
-
- self.staged.push((item, styles, Some(weakness)));
- self.last = Last::Weak;
- }
-
- /// Forces nearby weak items to collapse.
- fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
- self.flush(false);
- self.builder.push(item, styles);
- self.last = Last::Destructive;
- }
-
- /// Allows nearby weak items to exist.
- fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
- self.flush(true);
- self.builder.push(item, styles);
- self.last = Last::Supportive;
- }
-
- /// Has no influence on other items.
- fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
- self.staged.push((item, styles, None));
- }
-
- /// Iterate over the contained items.
- fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
- self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
- }
-
- /// Return the finish style vec and the common prefix chain.
- fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
- self.flush(false);
- self.builder.finish()
- }
-
- /// Push the staged items, filtering out weak items if `supportive` is
- /// false.
- fn flush(&mut self, supportive: bool) {
- for (item, styles, meta) in self.staged.drain(..) {
- if supportive || meta.is_none() {
- self.builder.push(item, styles);
- }
- }
- }
-}
-
-impl<'a, T> Default for CollapsingBuilder<'a, T> {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs
index 7d760ab6..42f7ff7d 100644
--- a/library/src/layout/place.rs
+++ b/library/src/layout/place.rs
@@ -5,7 +5,7 @@ use crate::prelude::*;
#[derive(Debug, Hash)]
pub struct PlaceNode(pub Content);
-#[node(LayoutBlock)]
+#[node(LayoutBlock, Behave)]
impl PlaceNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start)));
@@ -54,3 +54,9 @@ impl PlaceNode {
.map_or(false, |node| node.aligns.y.is_some())
}
}
+
+impl Behave for PlaceNode {
+ fn behaviour(&self) -> Behaviour {
+ Behaviour::Ignorant
+ }
+}
diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs
index 66d71ed1..74bd2f0f 100644
--- a/library/src/layout/spacing.rs
+++ b/library/src/layout/spacing.rs
@@ -5,11 +5,13 @@ use crate::prelude::*;
/// Horizontal spacing.
#[derive(Debug, Copy, Clone, Hash)]
pub struct HNode {
+ /// The amount of horizontal spacing.
pub amount: Spacing,
+ /// Whether the node is weak, see also [`Behaviour`].
pub weak: bool,
}
-#[node]
+#[node(Behave)]
impl HNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let amount = args.expect("spacing")?;
@@ -18,31 +20,98 @@ impl HNode {
}
}
+impl HNode {
+ /// Normal strong spacing.
+ pub fn strong(amount: Spacing) -> Self {
+ Self { amount, weak: false }
+ }
+
+ /// User-created weak spacing.
+ pub fn weak(amount: Spacing) -> Self {
+ Self { amount, weak: true }
+ }
+}
+
+impl Behave for HNode {
+ fn behaviour(&self) -> Behaviour {
+ if self.amount.is_fractional() {
+ Behaviour::Destructive
+ } else if self.weak {
+ Behaviour::Weak(1)
+ } else {
+ Behaviour::Ignorant
+ }
+ }
+
+ fn larger(&self, prev: &Content) -> bool {
+ let Some(prev) = prev.downcast::<Self>() else { return false };
+ self.amount > prev.amount
+ }
+}
+
/// Vertical spacing.
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
pub struct VNode {
+ /// The amount of vertical spacing.
pub amount: Spacing,
- pub weak: bool,
+ /// The node's weakness level, see also [`Behaviour`].
+ pub weakness: u8,
}
+#[node(Behave)]
impl VNode {
- /// Create weak vertical spacing.
- pub fn weak(amount: Spacing) -> Self {
- Self { amount, weak: true }
+ fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
+ let amount = args.expect("spacing")?;
+ let node = if args.named("weak")?.unwrap_or(false) {
+ Self::weak(amount)
+ } else {
+ Self::strong(amount)
+ };
+ Ok(node.pack())
}
+}
- /// Create strong vertical spacing.
+impl VNode {
+ /// Normal strong spacing.
pub fn strong(amount: Spacing) -> Self {
- Self { amount, weak: false }
+ Self { amount, weakness: 0 }
+ }
+
+ /// User-created weak spacing.
+ pub fn weak(amount: Spacing) -> Self {
+ Self { amount, weakness: 1 }
+ }
+
+ /// Weak spacing with list attach weakness.
+ pub fn list_attach(amount: Spacing) -> Self {
+ Self { amount, weakness: 2 }
+ }
+
+ /// Weak spacing with BlockNode::ABOVE/BELOW weakness.
+ pub fn block_around(amount: Spacing) -> Self {
+ Self { amount, weakness: 3 }
+ }
+
+ /// Weak spacing with BlockNode::SPACING weakness.
+ pub fn block_spacing(amount: Spacing) -> Self {
+ Self { amount, weakness: 4 }
}
}
-#[node]
-impl VNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let amount = args.expect("spacing")?;
- let weak = args.named("weak")?.unwrap_or(false);
- Ok(Self { amount, weak }.pack())
+impl Behave for VNode {
+ fn behaviour(&self) -> Behaviour {
+ if self.amount.is_fractional() {
+ Behaviour::Destructive
+ } else if self.weakness > 0 {
+ Behaviour::Weak(self.weakness)
+ } else {
+ Behaviour::Ignorant
+ }
+ }
+
+ fn larger(&self, prev: &Content) -> bool {
+ let Some(prev) = prev.downcast::<Self>() else { return false };
+ self.amount > prev.amount
}
}