From cfb0ef231e1cf5588a574719af487c97c9c15d25 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:41:36 +0100 Subject: [PATCH] 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. --- masonry/src/event_loop_runner.rs | 3 ++ masonry/src/widget/spinner.rs | 9 +++- xilem/examples/state_machine.rs | 6 ++- xilem/src/view/mod.rs | 3 ++ xilem/src/view/spinner.rs | 90 ++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 xilem/src/view/spinner.rs diff --git a/masonry/src/event_loop_runner.rs b/masonry/src/event_loop_runner.rs index e9bd9271..5ee4a888 100644 --- a/masonry/src/event_loop_runner.rs +++ b/masonry/src/event_loop_runner.rs @@ -384,6 +384,8 @@ impl MasonryState<'_> { height, antialiasing_method: vello::AaConfig::Area, }; + // TODO: Run this in-between `submit` and `present`. + window.pre_present_notify(); self.renderer .get_or_insert_with(|| Renderer::new(device, renderer_options).unwrap()) .render_to_surface(device, queue, scene_ref, &surface_texture, &render_params) @@ -420,6 +422,7 @@ impl MasonryState<'_> { .handle_window_event(WindowEvent::Rescale(scale_factor)); } WinitWindowEvent::RedrawRequested => { + self.render_root.handle_window_event(WindowEvent::AnimFrame); let (scene, tree_update) = self.render_root.redraw(); self.render(scene); let WindowState::Rendering { diff --git a/masonry/src/widget/spinner.rs b/masonry/src/widget/spinner.rs index e9e69159..209d77ed 100644 --- a/masonry/src/widget/spinner.rs +++ b/masonry/src/widget/spinner.rs @@ -49,11 +49,13 @@ impl Spinner { } } +const DEFAULT_SPINNER_COLOR: Color = theme::TEXT_COLOR; + impl Default for Spinner { fn default() -> Self { Spinner { t: 0.0, - color: theme::TEXT_COLOR, + color: DEFAULT_SPINNER_COLOR, } } } @@ -69,6 +71,11 @@ impl WidgetMut<'_, Spinner> { self.widget.color = color.into(); 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 --- diff --git a/xilem/examples/state_machine.rs b/xilem/examples/state_machine.rs index 95b8beb3..3466a52b 100644 --- a/xilem/examples/state_machine.rs +++ b/xilem/examples/state_machine.rs @@ -6,7 +6,7 @@ use winit::error::EventLoopError; use xilem::{ core::one_of::{OneOf, OneOf3}, - view::{button, flex, label, prose}, + view::{button, flex, label, prose, sized_box, spinner}, EventLoop, WidgetView, Xilem, }; @@ -67,6 +67,10 @@ fn app_logic(app_data: &mut StateMachine) -> impl WidgetView { }), prose(&*app_data.history), label(format!("Current state: {:?}", app_data.state)), + // TODO: Make `spinner` not need a `sized_box` to appear. + sized_box::(spinner()) + .height(40.) + .width(40.), state_machine(app_data), // TODO: When we have a canvas widget, visualise the entire state machine here. )) diff --git a/xilem/src/view/mod.rs b/xilem/src/view/mod.rs index 0f73f167..a6503c5d 100644 --- a/xilem/src/view/mod.rs +++ b/xilem/src/view/mod.rs @@ -16,6 +16,9 @@ pub use flex::*; mod sized_box; pub use sized_box::*; +mod spinner; +pub use spinner::*; + mod label; pub use label::*; diff --git a/xilem/src/view/spinner.rs b/xilem/src/view/spinner.rs new file mode 100644 index 00000000..e6def95f --- /dev/null +++ b/xilem/src/view/spinner.rs @@ -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 { flex(()) } +/// # } +/// # +/// fn show_request_outcome(data: &mut RequestState) -> impl WidgetView { +/// 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, +} + +impl Spinner { + /// Set the color for this spinner. + pub fn color(mut self, color: impl Into) -> Self { + self.color = Some(color.into()); + self + } +} + +impl ViewMarker for Spinner {} +impl View for Spinner { + type Element = Pod; + 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 { + tracing::error!("Message arrived in Label::message, but Label doesn't consume any messages, this is a bug"); + MessageResult::Stale(message) + } +}