blob: 17f5f38d631bdf4bef9ac82f336161f942edfd08 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::Error,
argh::FromArgs,
carnelian::{
app::{Config, ViewCreationParameters},
color::Color,
drawing::DisplayRotation,
input::{self},
make_app_assistant,
render::{rive::load_rive, Context as RenderContext},
scene::{
facets::{FacetId, RiveFacet, SetSizeMessage},
scene::{Scene, SceneBuilder},
},
App, AppAssistant, AppSender, Size, ViewAssistant, ViewAssistantContext, ViewAssistantPtr,
ViewKey,
},
fuchsia_trace::{duration, duration_begin, duration_end},
fuchsia_zircon::{Event, Time},
rive_rs::{self as rive},
std::path::Path,
};
/// Rive.
#[derive(Debug, FromArgs)]
#[argh(name = "rive-rs")]
struct Args {
/// display rotatation
#[argh(option)]
rotation: Option<DisplayRotation>,
/// rive file to load (default is juice.riv)
#[argh(option, default = "String::from(\"juice.riv\")")]
file: String,
/// playback speed (default is 1.0)
#[argh(option, default = "1.0")]
playback_speed: f32,
/// background color (default is white)
#[argh(option, from_str_fn(color_from_str))]
background: Option<Color>,
/// artboard name (default is first artboard found)
#[argh(option)]
artboard: Option<String>,
}
fn color_from_str(value: &str) -> Result<Color, String> {
Color::from_hash_code(value).map_err(|err| err.to_string())
}
struct RiveAppAssistant {
display_rotation: DisplayRotation,
filename: String,
playback_speed: f32,
background: Color,
artboard: Option<String>,
}
impl Default for RiveAppAssistant {
fn default() -> Self {
let args: Args = argh::from_env();
let display_rotation = args.rotation.unwrap_or(DisplayRotation::Deg0);
let filename = args.file;
let playback_speed = args.playback_speed;
let background = args.background.unwrap_or(Color::white());
let artboard = args.artboard;
Self { display_rotation, filename, playback_speed, background, artboard }
}
}
impl AppAssistant for RiveAppAssistant {
fn setup(&mut self) -> Result<(), Error> {
Ok(())
}
fn create_view_assistant_with_parameters(
&mut self,
params: ViewCreationParameters,
) -> Result<ViewAssistantPtr, Error> {
let file = load_rive(&Path::new("/pkg/data/static").join(self.filename.clone()))?;
let playback_speed = self.playback_speed;
let background = self.background;
let artboard = self.artboard.clone();
Ok(Box::new(RiveViewAssistant::new(
params.app_sender,
params.view_key,
file,
playback_speed,
background,
artboard,
)))
}
fn filter_config(&mut self, config: &mut Config) {
config.display_rotation = self.display_rotation;
}
}
struct SceneDetails {
scene: Scene,
rive: FacetId,
artboard: rive::Object<rive::Artboard>,
animations: Vec<(rive::animation::LinearAnimationInstance, bool)>,
}
struct RiveViewAssistant {
app_sender: AppSender,
view_key: ViewKey,
file: rive::File,
playback_speed: f32,
background: Color,
artboard: Option<String>,
last_presentation_time: Option<Time>,
scene_details: Option<SceneDetails>,
}
impl RiveViewAssistant {
fn new(
app_sender: AppSender,
view_key: ViewKey,
file: rive::File,
playback_speed: f32,
background: Color,
artboard: Option<String>,
) -> RiveViewAssistant {
let background = Color { r: background.r, g: background.g, b: background.b, a: 255 };
RiveViewAssistant {
app_sender: app_sender.clone(),
view_key,
file,
playback_speed,
background,
artboard,
last_presentation_time: None,
scene_details: None,
}
}
fn set_size(&mut self, size: &Size) {
if let Some(scene_details) = self.scene_details.as_mut() {
scene_details
.scene
.send_message(&scene_details.rive, Box::new(SetSizeMessage { size: *size }));
}
}
fn toggle_animation(&mut self, index: usize) {
if let Some(scene_details) = self.scene_details.as_mut() {
if index < scene_details.animations.len() {
scene_details.animations[index].1 = !scene_details.animations[index].1;
self.app_sender.request_render(self.view_key);
}
}
}
fn adjust_playback_speed(&mut self, factor: f32) {
self.playback_speed *= factor;
self.app_sender.request_render(self.view_key);
}
}
impl ViewAssistant for RiveViewAssistant {
fn resize(&mut self, new_size: &Size) -> Result<(), Error> {
self.set_size(new_size);
Ok(())
}
fn render(
&mut self,
render_context: &mut RenderContext,
ready_event: Event,
context: &ViewAssistantContext,
) -> Result<(), Error> {
let mut scene_details = self.scene_details.take().unwrap_or_else(|| {
let mut builder = SceneBuilder::new().background_color(self.background).mutable(false);
let artboards: Vec<_> = self
.file
.artboards()
.map(|artboard| artboard.cast::<rive::Component>().as_ref().name())
.collect();
println!("artboards: {:#?}", artboards);
let artboard = self
.artboard
.as_ref()
.map_or_else(|| self.file.artboard(), |name| self.file.get_artboard(name))
.expect("failed to get artboard");
let artboard_ref = artboard.as_ref();
let animations: Vec<_> = artboard_ref
.animations()
.enumerate()
.map(|(i, animation)| {
let name = animation.cast::<rive::animation::Animation>().as_ref().name();
format!("{}:{}", i, name)
})
.collect();
println!("animations: {:#?}", animations);
let rive_facet = RiveFacet::new(context.size, artboard.clone());
let rive = builder.facet(Box::new(rive_facet));
let scene = builder.build();
// Create animation instances and enable animation at index 0.
let animations: Vec<(rive::animation::LinearAnimationInstance, bool)> = artboard_ref
.animations()
.enumerate()
.map(|(i, animation)| {
(rive::animation::LinearAnimationInstance::new(animation), i == 0)
})
.collect();
SceneDetails { scene, rive, artboard, animations }
});
let presentation_time = context.presentation_time;
let elapsed = if let Some(last_presentation_time) = self.last_presentation_time {
const NANOS_PER_SECOND: f32 = 1_000_000_000.0;
(presentation_time - last_presentation_time).into_nanos() as f32 / NANOS_PER_SECOND
} else {
0.0
};
self.last_presentation_time = Some(presentation_time);
// Apply playback speed to elapsed time.
let elapsed = elapsed * self.playback_speed;
let mut request_render = false;
for (animation_instance, is_animating) in scene_details.animations.iter_mut() {
if *is_animating {
duration!(c"gfx", c"rive::animation::advance");
animation_instance.advance(elapsed);
animation_instance.apply(scene_details.artboard.clone(), 1.0);
}
if animation_instance.is_done() {
animation_instance.reset();
*is_animating = false;
} else {
request_render = true;
}
}
duration_begin!(c"gfx", c"rive::artboard::advance");
let artboard_ref = scene_details.artboard.as_ref();
artboard_ref.advance(elapsed);
duration_end!(c"gfx", c"rive::artboard::advance");
scene_details.scene.render(render_context, ready_event, context)?;
self.scene_details = Some(scene_details);
if request_render {
context.request_render();
}
Ok(())
}
fn handle_keyboard_event(
&mut self,
_context: &mut ViewAssistantContext,
_event: &input::Event,
keyboard_event: &input::keyboard::Event,
) -> Result<(), Error> {
const ZERO: u32 = '0' as u32;
const NINE: u32 = '9' as u32;
const MINUS: u32 = '-' as u32;
const EQUAL: u32 = '=' as u32;
if let Some(code_point) = keyboard_event.code_point {
if keyboard_event.phase == input::keyboard::Phase::Pressed {
match code_point {
// "0-9" can be used to toggle animations on/off.
ZERO..=NINE => self.toggle_animation((code_point - ZERO) as usize),
// "-" can be used to decrease playback speed.
MINUS => self.adjust_playback_speed(0.5),
// "=" can be used to increase playback speed.
EQUAL => self.adjust_playback_speed(2.0),
_ => {}
}
}
}
Ok(())
}
}
fn main() -> Result<(), Error> {
fuchsia_trace_provider::trace_provider_create_with_fdio();
App::run(make_app_assistant::<RiveAppAssistant>())
}