use super::super::*;
use crate::Stack;
use frust_core::{FrameTime, PointerButton, PointerEvent, PointerPhase, RenderRoot, any};
use std::any::Any;
use std::cell::Cell;
pub(super) fn down(x: f64, y: f64) -> InputEvent {
InputEvent::Pointer(PointerEvent {
phase: PointerPhase::Down,
position: Point::new(x, y),
button: PointerButton::Primary,
})
}
pub(super) fn move_to(x: f64, y: f64) -> InputEvent {
InputEvent::Pointer(PointerEvent {
phase: PointerPhase::Move,
position: Point::new(x, y),
button: PointerButton::Primary,
})
}
pub(super) fn up(x: f64, y: f64) -> InputEvent {
InputEvent::Pointer(PointerEvent {
phase: PointerPhase::Up,
position: Point::new(x, y),
button: PointerButton::Primary,
})
}
pub(super) fn cancel_ev(x: f64, y: f64) -> InputEvent {
InputEvent::Pointer(PointerEvent {
phase: PointerPhase::Cancel,
position: Point::new(x, y),
button: PointerButton::Primary,
})
}
struct CounterView {
observed: Rc<Cell<u32>>,
}
struct CounterWidget {
count: u32,
observed: Rc<Cell<u32>>,
}
impl View<()> for CounterView {
type Element = CounterWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> CounterWidget {
CounterWidget {
count: 0,
observed: self.observed.clone(),
}
}
fn rebuild(
&self,
_prev: &Self,
element: &mut CounterWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
element.observed = self.observed.clone();
ChangeFlags::NONE
}
}
impl Widget for CounterWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.max()
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {
self.observed.set(self.count);
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
if let InputEvent::Pointer(p) = event
&& p.phase == PointerPhase::Down
{
self.count += 1;
ctx.request_redraw();
return EventResult::Handled;
}
EventResult::Ignored
}
}
pub(super) fn counter_page(observed: &Rc<Cell<u32>>) -> AnyView<()> {
any(CounterView {
observed: observed.clone(),
})
}
pub(super) struct SizedLeaf {
pub(super) size: Size,
}
impl<S: 'static> View<S> for SizedLeaf {
type Element = SizedLeafWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> SizedLeafWidget {
SizedLeafWidget { size: self.size }
}
fn rebuild(
&self,
_prev: &Self,
_element: &mut SizedLeafWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
ChangeFlags::NONE
}
}
pub(super) struct SizedLeafWidget {
size: Size,
}
impl Widget for SizedLeafWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(self.size)
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
scene.fill_rect(ctx.origin(), ctx.size(), peniko::Color::BLACK);
}
}
pub(super) fn sized_page<S: 'static>(w: f64, h: f64) -> AnyView<S> {
any(SizedLeaf {
size: Size::new(w, h),
})
}
#[derive(Default)]
pub(super) struct ResultState {
pub(super) received: Option<i32>,
}
pub(super) type AppLogic = Box<dyn FnMut(&mut ()) -> NavigatorView<()>>;
#[derive(Default)]
pub(super) struct TransitionScene {
pub(super) fills: Vec<(Point, Size, f32)>,
layer_alpha: Vec<f32>,
}
impl PaintScene for TransitionScene {
fn fill_rect(&mut self, origin: Point, size: Size, _color: peniko::Color) {
let alpha = self.layer_alpha.last().copied().unwrap_or(1.0);
self.fills.push((origin, size, alpha));
}
fn draw_text(&mut self, _origin: Point, _text: &str) {}
fn push_layer(&mut self, _origin: Point, _size: Size, alpha: f32) {
self.layer_alpha.push(alpha);
}
fn pop_layer(&mut self) {
self.layer_alpha.pop();
}
}
pub(super) fn ft(ms: u64) -> FrameTime {
FrameTime::from_nanos(ms * 1_000_000)
}
pub(super) fn full_frame<State: 'static>(
root: &mut RenderRoot<State, NavigatorView<State>>,
app: &mut impl FnMut(&mut State) -> NavigatorView<State>,
state: &mut State,
time: FrameTime,
) -> (Vec<(Point, Size, f32)>, bool) {
root.rebuild(app, state);
root.layout(Size::new(100.0, 100.0));
let mut scene = TransitionScene::default();
let out = root.paint(&mut scene, time);
(scene.fills, out.needs_frame)
}
pub(super) fn fill_h(fills: &[(Point, Size, f32)], h: f64) -> (Point, Size, f32) {
*fills
.iter()
.find(|(_, s, _)| (s.height - h).abs() < 1e-9)
.unwrap_or_else(|| panic!("no fill with height {h} in {fills:?}"))
}
pub(super) fn nav_widget<S: 'static>(
root: &RenderRoot<S, NavigatorView<S>>,
) -> &NavigatorWidget<S> {
let id = root.root_id().expect("root built");
(root.tree().pod(id).expect("root pod").widget() as &dyn Any)
.downcast_ref::<NavigatorWidget<S>>()
.expect("root is a NavigatorWidget")
}
pub(super) fn run_until_settled(
root: &mut RenderRoot<(), NavigatorView<()>>,
app: &mut impl FnMut(&mut ()) -> NavigatorView<()>,
state: &mut (),
mut time_ms: u64,
) -> Vec<(Point, Size, f32)> {
for _ in 0..10_000 {
let (fills, needs_frame) = full_frame(root, app, state, ft(time_ms));
if !needs_frame {
return fills;
}
time_ms += 16;
}
panic!("transition failed to settle");
}
#[derive(Default)]
pub(super) struct SwipeResultState {
pub(super) popped: bool,
}
struct HostProbe {
label: &'static str,
hits: Rc<Cell<u32>>,
}
struct HostProbeWidget {
label: &'static str,
hits: Rc<Cell<u32>>,
}
impl View<()> for HostProbe {
type Element = HostProbeWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> HostProbeWidget {
HostProbeWidget {
label: self.label,
hits: self.hits.clone(),
}
}
fn rebuild(
&self,
_prev: &Self,
element: &mut HostProbeWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
element.label = self.label;
element.hits = self.hits.clone();
ChangeFlags::NONE
}
}
impl Widget for HostProbeWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.max()
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
scene.fill_rect(ctx.origin(), ctx.size(), peniko::Color::BLACK);
}
fn event(&mut self, _ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
if let InputEvent::Pointer(p) = event
&& p.phase == PointerPhase::Down
{
self.hits.set(self.hits.get() + 1);
return EventResult::Handled;
}
EventResult::Ignored
}
fn semantics(&self, ctx: &mut SemanticsCtx) {
ctx.push_node(frust_core::accesskit::Role::Button, |node| {
node.set_label(self.label)
});
}
}
pub(super) fn host_probe(label: &'static str, hits: &Rc<Cell<u32>>) -> AnyView<()> {
any(HostProbe {
label,
hits: hits.clone(),
})
}
pub(super) fn semantic_labels(root: &RenderRoot<(), NavigatorView<()>>) -> Vec<String> {
root.semantics()
.nodes
.iter()
.filter_map(|(_, node)| node.label().map(str::to_string))
.collect()
}
type HostAppLogic = Box<dyn FnMut(&mut ()) -> NavigatorView<()>>;
type HostFixture = (
NavigatorController<()>,
RenderRoot<(), NavigatorView<()>>,
HostAppLogic,
Rc<Cell<u32>>,
Rc<Cell<u32>>,
);
pub(super) fn overlay_host_fixture() -> HostFixture {
let controller: NavigatorController<()> = NavigatorController::new();
let content_hits = Rc::new(Cell::new(0u32));
let chrome_hits = Rc::new(Cell::new(0u32));
let app: HostAppLogic = {
let ctrl = controller.clone();
let content = content_hits.clone();
let chrome = chrome_hits.clone();
Box::new(move |_: &mut ()| {
let content = content.clone();
let chrome = chrome.clone();
overlay_host(&ctrl, move || {
any(Stack(vec![
host_probe("app-content", &content),
host_probe("app-chrome", &chrome),
]))
})
})
};
(
controller,
RenderRoot::new(),
app,
content_hits,
chrome_hits,
)
}
pub(super) fn route(path: &str) -> Location {
Location::parse(path)
}