use frust_core::{
BoxConstraints, BuildCtx, ChangeFlags, ChildPod, EventCtx, EventResult, InputEvent, LayoutCtx,
PaintCtx, PaintScene, RetainedKey, SemanticsCtx, View, Widget,
};
use kurbo::{Affine, Point, Rect, Size};
const LINEAR_EPSILON: f64 = 1e-6;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BoundaryPaint {
Retained,
Recorded,
Direct,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Recorded {
linear: [f64; 4],
origin: Point,
size: Size,
serial: u64,
last: u64,
confirmed: bool,
}
impl Recorded {
fn usable_at(&self, serial: u64) -> bool {
self.confirmed || serial == 0 || self.last.wrapping_add(1) == serial
}
}
#[derive(Debug)]
pub(crate) struct RetainedBoundary {
key: RetainedKey,
recorded: Option<Recorded>,
publishes: bool,
}
impl RetainedBoundary {
pub(crate) fn new() -> Self {
Self::with_key(RetainedKey::mint())
}
pub(crate) fn with_key(key: RetainedKey) -> Self {
Self {
key,
recorded: None,
publishes: false,
}
}
pub(crate) fn key(&self) -> RetainedKey {
self.key
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn paint(
&mut self,
pod: &mut ChildPod,
ctx: &mut PaintCtx,
scene: &mut dyn PaintScene,
origin: Point,
size: Size,
alpha: f32,
may_record: bool,
) -> BoundaryPaint {
let alpha = alpha.clamp(0.0, 1.0);
let transform = scene.current_transform().unwrap_or(Affine::IDENTITY);
let finite = transform.as_coeffs().iter().all(|c| c.is_finite());
if !ctx.retained_enabled() || !finite {
self.paint_direct(pod, ctx, scene, origin, size, alpha);
return BoundaryPaint::Direct;
}
let [a, b, c, d, _, _] = transform.as_coeffs();
let linear = [a, b, c, d];
let serial = ctx.paint_serial();
if let Some(recorded) = self.recorded
&& self.can_draw(pod, ctx, size, linear, recorded)
{
self.recorded = Some(Recorded {
confirmed: true,
..recorded
});
let shift = origin - recorded.origin;
let moved = shift.hypot2() > 0.0;
if moved {
scene.push_transform(Affine::translate(shift));
}
scene.draw_retained(self.key.get(), recorded.origin, size, alpha, 1.0);
if moved {
scene.pop_transform();
}
return BoundaryPaint::Retained;
}
let visible = ctx
.visible_rect()
.is_none_or(|v| rect_contains(v, Rect::from_origin_size(origin, size)));
if may_record && visible {
let run_start = self
.recorded
.filter(|r| {
!r.confirmed
&& serial != 0
&& r.last.wrapping_add(1) == serial
&& r.origin == origin
&& r.size == size
&& same_linear(r.linear, linear)
&& ctx.subtree_clean(pod)
})
.map_or(serial, |r| r.serial);
scene.push_retained(self.key.get(), origin, size, alpha, 1.0);
self.paint_body(pod, ctx, scene);
scene.pop_retained();
self.recorded = Some(Recorded {
linear,
origin,
size,
serial: run_start,
last: serial,
confirmed: false,
});
return BoundaryPaint::Recorded;
}
self.paint_direct(pod, ctx, scene, origin, size, alpha);
BoundaryPaint::Direct
}
pub(crate) fn paint_plain(
&mut self,
pod: &mut ChildPod,
ctx: &mut PaintCtx,
scene: &mut dyn PaintScene,
) {
if !ctx.subtree_clean(pod) {
self.recorded = None;
}
self.paint_body(pod, ctx, scene);
}
fn paint_direct(
&mut self,
pod: &mut ChildPod,
ctx: &mut PaintCtx,
scene: &mut dyn PaintScene,
origin: Point,
size: Size,
alpha: f32,
) {
let layered = alpha < 1.0;
if layered {
scene.push_layer(origin, size, alpha);
}
self.paint_plain(pod, ctx, scene);
if layered {
scene.pop_layer();
}
}
fn paint_body(&mut self, pod: &mut ChildPod, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
let before = ctx.paint_publications();
pod.paint_child(ctx, scene);
self.publishes = ctx.paint_publications() != before;
}
fn can_draw(
&self,
pod: &ChildPod,
ctx: &PaintCtx,
size: Size,
linear: [f64; 4],
recorded: Recorded,
) -> bool {
if self.publishes
|| !ctx.subtree_clean(pod)
|| !recorded.usable_at(ctx.paint_serial())
|| !same_linear(recorded.linear, linear)
{
return false;
}
let [a, b, c, d] = linear;
let extent = Size::new(size.width * a.hypot(b), size.height * c.hypot(d));
let scale = (a * d - b * c).abs().sqrt() as f32;
ctx.retained_available(self.key, extent, scale, recorded.serial)
}
}
fn same_linear(a: [f64; 4], b: [f64; 4]) -> bool {
a.iter()
.zip(b.iter())
.all(|(x, y)| (x - y).abs() <= LINEAR_EPSILON)
}
fn rect_contains(outer: Rect, inner: Rect) -> bool {
const SLACK: f64 = 1e-6;
inner.x0 >= outer.x0 - SLACK
&& inner.y0 >= outer.y0 - SLACK
&& inner.x1 <= outer.x1 + SLACK
&& inner.y1 <= outer.y1 + SLACK
}
pub(crate) fn within(origin: Point, size: Size, clip_origin: Point, clip_size: Size) -> bool {
rect_contains(
Rect::from_origin_size(clip_origin, clip_size),
Rect::from_origin_size(origin, size),
)
}
pub struct RepaintBoundaryView<V> {
child: V,
alpha: f32,
}
pub fn repaint_boundary<V>(child: V) -> RepaintBoundaryView<V> {
RepaintBoundaryView { child, alpha: 1.0 }
}
impl<V> RepaintBoundaryView<V> {
pub fn alpha(mut self, alpha: f32) -> Self {
self.alpha = alpha.clamp(0.0, 1.0);
self
}
}
pub struct RepaintBoundaryWidget {
child: ChildPod,
alpha: f32,
boundary: RetainedBoundary,
}
impl RepaintBoundaryWidget {
pub fn retained_key(&self) -> RetainedKey {
self.boundary.key()
}
}
impl<State: 'static, V: View<State>> View<State> for RepaintBoundaryView<V> {
type Element = RepaintBoundaryWidget;
fn build(&self, ctx: &mut BuildCtx<'_>) -> RepaintBoundaryWidget {
RepaintBoundaryWidget {
child: crate::authoring::build_child(&self.child, ctx),
alpha: self.alpha,
boundary: RetainedBoundary::new(),
}
}
fn rebuild(
&self,
prev: &Self,
element: &mut RepaintBoundaryWidget,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
let mut flags = ChangeFlags::NONE;
if prev.alpha != self.alpha {
element.alpha = self.alpha;
flags |= ChangeFlags::PAINT;
}
flags |= crate::authoring::rebuild_child(&prev.child, &self.child, &mut element.child, ctx);
flags
}
fn teardown(&self, element: &mut RepaintBoundaryWidget, ctx: &mut BuildCtx<'_>) {
crate::authoring::teardown_child(&self.child, &mut element.child, ctx);
}
}
impl Widget for RepaintBoundaryWidget {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let size = self.child.layout_child(ctx, bc);
self.child.set_origin(Point::ZERO);
size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
let origin = ctx.origin();
let size = ctx.size();
self.boundary
.paint(&mut self.child, ctx, scene, origin, size, self.alpha, true);
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
crate::authoring::route_event_single(&mut self.child, ctx, event)
}
fn semantics(&self, ctx: &mut SemanticsCtx) {
self.child.semantics_child(ctx);
}
crate::authoring::visit_children!(child);
}