Restore animation support - add `spinner` view (#497)

This is the smallest change possible to make animations work in Masonry.

Essentially, we treat every redraw as a potential animation frame, so
that animations do work.

I've also added the `spinner` view in Xilem to test this out. The
`state_machine` examples uses this.

This is work done for the variable fonts demo.
This commit is contained in:
Daniel McNab 2024-08-09 16:41:36 +01:00 committed by GitHub
parent a52c3c7b3c
commit cfb0ef231e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 109 additions and 2 deletions

View File

@ -384,6 +384,8 @@ impl MasonryState<'_> {
height, height,
antialiasing_method: vello::AaConfig::Area, antialiasing_method: vello::AaConfig::Area,
}; };
// TODO: Run this in-between `submit` and `present`.
window.pre_present_notify();
self.renderer self.renderer
.get_or_insert_with(|| Renderer::new(device, renderer_options).unwrap()) .get_or_insert_with(|| Renderer::new(device, renderer_options).unwrap())
.render_to_surface(device, queue, scene_ref, &surface_texture, &render_params) .render_to_surface(device, queue, scene_ref, &surface_texture, &render_params)
@ -420,6 +422,7 @@ impl MasonryState<'_> {
.handle_window_event(WindowEvent::Rescale(scale_factor)); .handle_window_event(WindowEvent::Rescale(scale_factor));
} }
WinitWindowEvent::RedrawRequested => { WinitWindowEvent::RedrawRequested => {
self.render_root.handle_window_event(WindowEvent::AnimFrame);
let (scene, tree_update) = self.render_root.redraw(); let (scene, tree_update) = self.render_root.redraw();
self.render(scene); self.render(scene);
let WindowState::Rendering { let WindowState::Rendering {

View File

@ -49,11 +49,13 @@ impl Spinner {
} }
} }
const DEFAULT_SPINNER_COLOR: Color = theme::TEXT_COLOR;
impl Default for Spinner { impl Default for Spinner {
fn default() -> Self { fn default() -> Self {
Spinner { Spinner {
t: 0.0, t: 0.0,
color: theme::TEXT_COLOR, color: DEFAULT_SPINNER_COLOR,
} }
} }
} }
@ -69,6 +71,11 @@ impl WidgetMut<'_, Spinner> {
self.widget.color = color.into(); self.widget.color = color.into();
self.ctx.request_paint(); self.ctx.request_paint();
} }
/// Reset the spinner's color to its default value.
pub fn reset_color(&mut self) {
self.set_color(DEFAULT_SPINNER_COLOR);
}
} }
// --- MARK: IMPL WIDGET --- // --- MARK: IMPL WIDGET ---

View File

@ -6,7 +6,7 @@
use winit::error::EventLoopError; use winit::error::EventLoopError;
use xilem::{ use xilem::{
core::one_of::{OneOf, OneOf3}, core::one_of::{OneOf, OneOf3},
view::{button, flex, label, prose}, view::{button, flex, label, prose, sized_box, spinner},
EventLoop, WidgetView, Xilem, EventLoop, WidgetView, Xilem,
}; };
@ -67,6 +67,10 @@ fn app_logic(app_data: &mut StateMachine) -> impl WidgetView<StateMachine> {
}), }),
prose(&*app_data.history), prose(&*app_data.history),
label(format!("Current state: {:?}", app_data.state)), label(format!("Current state: {:?}", app_data.state)),
// TODO: Make `spinner` not need a `sized_box` to appear.
sized_box::<StateMachine, (), _>(spinner())
.height(40.)
.width(40.),
state_machine(app_data), state_machine(app_data),
// TODO: When we have a canvas widget, visualise the entire state machine here. // TODO: When we have a canvas widget, visualise the entire state machine here.
)) ))

View File

@ -16,6 +16,9 @@ pub use flex::*;
mod sized_box; mod sized_box;
pub use sized_box::*; pub use sized_box::*;
mod spinner;
pub use spinner::*;
mod label; mod label;
pub use label::*; pub use label::*;

90
xilem/src/view/spinner.rs Normal file
View File

@ -0,0 +1,90 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
use masonry::{widget, Color};
use xilem_core::{Mut, ViewMarker};
use crate::{MessageResult, Pod, View, ViewCtx, ViewId};
/// An indefinite spinner.
///
/// This can be used to display that progress is happening on some process,
/// but that the exact status is not known.
///
/// The underlying widget is the Masonry [Spinner](widget::Spinner).
///
/// # Examples
///
/// ```rust,no_run
/// # use xilem::{view::{spinner, flex}, WidgetView, core::one_of::Either};
/// # struct ApiClient;
/// # struct RequestState { pending: bool }
/// # impl RequestState {
/// # fn request_result(&mut self) -> impl WidgetView<ApiClient> { flex(()) }
/// # }
/// #
/// fn show_request_outcome(data: &mut RequestState) -> impl WidgetView<ApiClient> {
/// if data.pending {
/// Either::A(spinner())
/// } else {
/// Either::B(data.request_result())
/// }
/// }
/// ```
pub fn spinner() -> Spinner {
Spinner { color: None }
}
/// The [`View`] created by [`spinner`].
///
/// See `spinner`'s docs for more details.
pub struct Spinner {
color: Option<Color>,
}
impl Spinner {
/// Set the color for this spinner.
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.color = Some(color.into());
self
}
}
impl ViewMarker for Spinner {}
impl<State, Action> View<State, Action, ViewCtx> for Spinner {
type Element = Pod<widget::Spinner>;
type ViewState = ();
fn build(&self, _: &mut ViewCtx) -> (Self::Element, Self::ViewState) {
(Pod::new(widget::Spinner::new()), ())
}
fn rebuild<'el>(
&self,
prev: &Self,
(): &mut Self::ViewState,
_: &mut ViewCtx,
mut element: Mut<'el, Self::Element>,
) -> Mut<'el, Self::Element> {
if prev.color != self.color {
match self.color {
Some(color) => element.set_color(color),
None => element.reset_color(),
};
}
element
}
fn teardown(&self, (): &mut Self::ViewState, _: &mut ViewCtx, _: Mut<'_, Self::Element>) {}
fn message(
&self,
(): &mut Self::ViewState,
_: &[ViewId],
message: xilem_core::DynMessage,
_: &mut State,
) -> MessageResult<Action> {
tracing::error!("Message arrived in Label::message, but Label doesn't consume any messages, this is a bug");
MessageResult::Stale(message)
}
}