use core::time::Duration;
use crate::{
environment::DefaultEnvironment,
event::{Event, EventContext, EventResult},
focus::{DefaultFocus, Role, RoleSet},
primitives::{Point, Size, transform::LinearTransform},
render::{AnimatedJoin, AnimationDomain, ContentShape, Render},
render_target::{RenderTarget, SolidBrush, Stroke},
view::{View, ViewLayout},
};
mod harness;
pub use harness::Harness;
mod tracked;
pub use tracked::Tracked;
mod trees;
pub use trees::Trees;
pub struct App<V, S, F>
where
V: ViewLayout<S>,
{
state: S,
view_fn: F,
view: V,
view_state: V::State,
trees: Trees<V::Renderables>,
focus_tree: V::FocusTree,
env: DefaultEnvironment,
focus_shape: ContentShape,
roles: RoleSet,
size: Size,
elapsed: Duration,
requires_redraw: bool,
requires_rebuild: bool,
}
impl<V, S, F> core::fmt::Debug for App<V, S, F>
where
V: ViewLayout<S> + core::fmt::Debug,
V::State: core::fmt::Debug,
V::Renderables: core::fmt::Debug,
V::FocusTree: core::fmt::Debug,
S: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("App")
.field("state", &self.state)
.field("env", &self.env)
.field("focus_shape", &self.focus_shape)
.field("roles", &self.roles)
.field("size", &self.size)
.field("elapsed", &self.elapsed)
.field("requires_redraw", &self.requires_redraw)
.field("requires_rebuild", &self.requires_rebuild)
.finish_non_exhaustive()
}
}
impl<V, S, F> App<V, S, F>
where
V: ViewLayout<S>,
V::FocusTree: DefaultFocus,
V::Renderables: AnimatedJoin,
F: Fn(&S) -> V,
{
pub fn new(state: S, size: Size, view_fn: F) -> Self
where
S: 'static,
{
let view = (view_fn)(&state);
let env = DefaultEnvironment::non_animated();
let mut state = state;
let mut view_state = view.build_state(&mut state);
let layout = view.layout(&size.into(), &env, &mut state, &mut view_state);
let source_tree = view.render_tree(
&layout.sublayouts,
Point::zero(),
&env,
&mut state,
&mut view_state,
);
let target_tree = view.render_tree(
&layout.sublayouts,
Point::zero(),
&env,
&mut state,
&mut view_state,
);
let focus_tree = DefaultFocus::default_first();
let roles = Role::Button | Role::Container;
Self {
state,
view_fn,
view,
view_state,
trees: Trees::new(source_tree, target_tree),
focus_tree,
env,
roles,
focus_shape: ContentShape::Empty,
size,
elapsed: Duration::default(),
requires_redraw: true,
requires_rebuild: false,
}
}
#[must_use]
pub fn with_roles(mut self, roles: impl Into<RoleSet>) -> Self {
self.roles = roles.into();
self
}
#[must_use]
pub fn with_focus_at_end(mut self) -> Self {
self.focus_tree = DefaultFocus::default_last();
self
}
#[must_use]
pub fn state(&self) -> &S {
&self.state
}
#[must_use]
pub fn state_mut(&mut self) -> Tracked<'_, S> {
Tracked::new(&mut self.state, &mut self.requires_rebuild)
}
#[must_use]
pub fn size(&self) -> Size {
self.size
}
#[must_use]
pub fn elapsed(&self) -> Duration {
self.elapsed
}
#[must_use]
pub fn focus_shape(&self) -> &ContentShape {
&self.focus_shape
}
#[must_use]
pub fn render_trees(&self) -> &Trees<V::Renderables> {
&self.trees
}
#[must_use]
pub fn render_trees_mut(&mut self) -> &mut Trees<V::Renderables> {
&mut self.trees
}
pub fn advance_time(&mut self, duration: Duration) {
self.elapsed += duration;
}
pub fn set_time(&mut self, time: Duration) {
self.elapsed = time;
}
pub fn force_rebuild(&mut self) {
let domain = AnimationDomain::top_level(self.elapsed);
self.trees.swap();
let (source_tree, target_tree) = self.trees.both_mut();
source_tree.join_from(target_tree, &domain);
self.view = (self.view_fn)(&self.state);
self.env = DefaultEnvironment::new(self.elapsed);
let layout = self.view.layout(
&self.size.into(),
&self.env,
&mut self.state,
&mut self.view_state,
);
*target_tree = self.view.render_tree(
&layout.sublayouts,
Point::zero(),
&self.env,
&mut self.state,
&mut self.view_state,
);
let result = self.focus_forward();
self.update_focus_shape(&result);
self.requires_rebuild = false;
self.requires_redraw = true;
}
pub fn resize(&mut self, new_size: Size) {
self.size = new_size;
self.requires_rebuild = true;
}
pub fn finalize_view(&mut self) {
if self.requires_rebuild {
self.force_rebuild();
}
}
pub fn render_animated<T, C>(&mut self, target: &mut T, color: &C)
where
V: View<C, S>,
T: RenderTarget<ColorFormat = C>,
{
self.finalize_view();
let domain = AnimationDomain::top_level(self.elapsed);
Render::render_animated(
target,
self.trees.source(),
self.trees.target(),
color,
&domain,
);
self.requires_redraw = false;
}
pub fn should_redraw(&mut self) -> bool {
self.requires_redraw || self.requires_rebuild
}
pub fn draw_focus_overlay<T, C>(&self, target: &mut T, color: C, line_width: u32)
where
T: RenderTarget<ColorFormat = C>,
C: Copy,
{
let stroke = Stroke::new(line_width);
let brush = SolidBrush::new(color);
match &self.focus_shape {
ContentShape::Rectangle(rect) => {
target.stroke(&stroke, LinearTransform::identity(), &brush, None, rect);
}
ContentShape::RoundedRectangle(rrect) => {
target.stroke(&stroke, LinearTransform::identity(), &brush, None, rrect);
}
ContentShape::Circle(circle) => {
target.stroke(&stroke, LinearTransform::identity(), &brush, None, circle);
}
ContentShape::Empty => (),
}
}
fn update_focus_shape(&mut self, result: &EventResult) {
if let EventResult::Handled { shape, .. } = result {
self.focus_shape = shape.clone();
}
}
}
impl<V, S, F> Harness for App<V, S, F>
where
V: ViewLayout<S>,
V::FocusTree: DefaultFocus,
V::Renderables: AnimatedJoin,
F: Fn(&S) -> V,
{
fn send(&mut self, event: impl Into<Event>) -> EventResult {
let context = EventContext::new(self.elapsed).with_roles(self.roles);
let event = event.into();
let target_tree = self.trees.target_mut();
let result = self.view.handle_event(
&event,
&context,
target_tree,
&mut self.state,
&mut self.view_state,
&mut self.focus_tree,
);
self.update_focus_shape(&result);
if context.view_rebuild_requested.get() {
self.requires_rebuild = true;
}
if context.redraw_requested.get() {
self.requires_redraw = true;
}
result
}
}