blob: e92e2e352eb6d311686ca16deba0a760b982062c [file] [log] [blame]
// Copyright 2019 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::{
color::Color, make_app_assistant, render::*, App, AppAssistant, Point, RenderOptions, Size,
ViewAssistant, ViewAssistantContext, ViewAssistantPtr, ViewKey,
},
chrono::{Local, Timelike},
euclid::{Angle, Point2D, Rect, Size2D, Transform2D, Vector2D},
fuchsia_trace::{self, duration},
fuchsia_trace_provider,
fuchsia_zircon::{AsHandleRef, Event, Signals},
std::{collections::BTreeMap, f32},
};
const BACKGROUND_COLOR: Color = Color { r: 235, g: 213, b: 179, a: 255 };
/// Clockface.
#[derive(Debug, FromArgs)]
#[argh(name = "clockface_rs")]
struct Args {
/// use spinel (GPU rendering back-end)
#[argh(switch, short = 's')]
use_spinel: bool,
}
#[derive(Default)]
struct ClockfaceAppAssistant {
use_spinel: bool,
}
impl AppAssistant for ClockfaceAppAssistant {
fn setup(&mut self) -> Result<(), Error> {
let args: Args = argh::from_env();
self.use_spinel = args.use_spinel;
Ok(())
}
fn create_view_assistant(&mut self, _: ViewKey) -> Result<ViewAssistantPtr, Error> {
Ok(Box::new(ClockfaceViewAssistant::new()))
}
fn get_render_options(&self) -> RenderOptions {
RenderOptions { use_spinel: self.use_spinel, ..RenderOptions::default() }
}
}
struct RoundedLine {
path: Path,
}
impl RoundedLine {
fn new(mut path_builder: PathBuilder, pos: Point, length: f32, thickness: f32) -> Self {
let radius = thickness / 2.0;
let tl = pos.to_vector();
let tr = pos.to_vector() + Vector2D::new(length, 0.0);
let br = pos.to_vector() + Vector2D::new(length, thickness);
let bl = pos.to_vector() + Vector2D::new(0.0, thickness);
let radiush = Vector2D::new(radius, 0.0);
let radiusv = Vector2D::new(0.0, radius);
let path = {
macro_rules! c {
( $v:expr ) => {
Point::new($v.x, $v.y)
};
}
path_builder.move_to(c!(tl + radiush));
path_builder.line_to(c!(tr - radiush));
path_builder.rat_quad_to(c!(tr), c!(tr + radiusv), 0.7071);
path_builder.rat_quad_to(c!(br), c!(br - radiush), 0.7071);
path_builder.line_to(c!(bl + radiush));
path_builder.rat_quad_to(c!(bl), c!(bl - radiusv), 0.7071);
path_builder.rat_quad_to(c!(tl), c!(tl + radiush), 0.7071);
path_builder.build()
};
Self { path }
}
}
struct Hand {
line: RoundedLine,
raster: Option<Raster>,
color: Color,
}
impl Hand {
fn new(
path_builder: PathBuilder,
thickness: f32,
length: f32,
offset: f32,
color: Color,
) -> Self {
let line = RoundedLine::new(
path_builder,
Point::new(-(thickness / 2.0 + offset), -thickness / 2.0),
length,
thickness,
);
Self { line, raster: None, color }
}
fn update(&mut self, context: &mut Context, scale: f32, angle: f32) {
let rotation = Transform2D::create_rotation(Angle::radians(angle)).post_scale(scale, scale);
let mut raster_builder = context.raster_builder().unwrap();
raster_builder.add(&self.line.path, Some(&rotation));
self.raster.replace(raster_builder.build());
}
}
struct Scene {
size: Size,
hour_hand: Hand,
minute_hand: Hand,
second_hand: Hand,
hour_index: usize,
minute_index: usize,
second_index: usize,
}
impl Scene {
fn new(context: &mut Context) -> Self {
const HOUR_HAND_COLOR: Color = Color { r: 254, g: 72, b: 100, a: 255 };
const MINUTE_HAND_COLOR: Color = Color { r: 255, g: 114, b: 132, a: 127 };
const SECOND_HAND_COLOR: Color = Color::white();
const RADIUS: f32 = 0.4;
let thickness = RADIUS / 20.0;
let offset = RADIUS / 5.0;
let hour_hand = Hand::new(
context.path_builder().unwrap(),
thickness * 2.0,
RADIUS,
offset,
HOUR_HAND_COLOR,
);
let minute_hand =
Hand::new(context.path_builder().unwrap(), thickness, RADIUS, 0.0, MINUTE_HAND_COLOR);
let second_hand = Hand::new(
context.path_builder().unwrap(),
thickness / 2.0,
RADIUS + offset,
offset,
SECOND_HAND_COLOR,
);
Self {
size: Size::new(1.0, 1.0),
hour_hand,
minute_hand,
second_hand,
hour_index: std::usize::MAX,
minute_index: std::usize::MAX,
second_index: std::usize::MAX,
}
}
fn update(&mut self, context: &mut Context, size: &Size, scale: f32) {
if self.size != *size {
self.size = *size;
self.hour_index = std::usize::MAX;
self.minute_index = std::usize::MAX;
self.second_index = std::usize::MAX;
}
const MICROSECONDS_PER_SECOND: f32 = 1e+6;
let now = Local::now();
let (_is_pm, hour12) = now.hour12();
let us = now.nanosecond() as f32 / 1000.0;
let second = now.second() as f32 + us / MICROSECONDS_PER_SECOND;
let minute = now.minute() as f32 + second / 60.0;
let hour = hour12 as f32 + minute / 60.0;
const R0: f32 = -0.25; // Rotate from 3 to 12.
const STEPS: usize = 60 * 60; // Enough steps to ensure smooth movement
// of second hand each frame on a 60hz display.
let index = ((R0 + hour / 12.0).rem_euclid(1.0) * STEPS as f32) as usize;
if index != self.hour_index {
let angle = index as f32 * 2.0 * f32::consts::PI / STEPS as f32;
self.hour_hand.update(context, scale, -angle);
self.hour_index = index;
}
let index = ((R0 + minute / 60.0).rem_euclid(1.0) * STEPS as f32) as usize;
if index != self.minute_index {
let angle = index as f32 * 2.0 * f32::consts::PI / STEPS as f32;
self.minute_hand.update(context, scale, -angle);
self.minute_index = index;
}
let index = ((R0 + second / 60.0).rem_euclid(1.0) * STEPS as f32) as usize;
if index != self.second_index {
let angle = index as f32 * 2.0 * f32::consts::PI / STEPS as f32;
self.second_hand.update(context, scale, -angle);
self.second_index = index;
}
}
}
struct Contents {
image: Image,
composition: Composition,
size: Size,
previous_rasters: Vec<Raster>,
}
impl Contents {
fn new(image: Image) -> Self {
let composition = Composition::new(BACKGROUND_COLOR);
Self { image, composition, size: Size::zero(), previous_rasters: Vec::new() }
}
fn update(&mut self, context: &mut Context, scene: &Scene, size: &Size, scale: f32) {
const SHADOW_COLOR: Color = Color { r: 0, g: 0, b: 0, a: 13 };
const ELEVATION: f32 = 0.01;
let center = Vector2D::new(size.width as i32 / 2, size.height as i32 / 2);
let elevation = (ELEVATION * scale) as i32;
let shadow_offset = center + Vector2D::new(elevation, elevation * 2);
let clip = Rect::new(
Point2D::new(0, 0),
Size2D::new(size.width.floor() as u32, size.height.floor() as u32),
);
let ext = if self.size != *size {
self.size = *size;
RenderExt {
pre_clear: Some(PreClear { color: BACKGROUND_COLOR }),
..Default::default()
}
} else {
RenderExt::default()
};
let hands = [&scene.hour_hand, &scene.minute_hand, &scene.second_hand];
let layers = hands
.iter()
.map(|hand| Layer {
raster: hand.raster.clone().unwrap().translate(center),
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(hand.color),
blend_mode: BlendMode::Over,
},
})
.chain(std::iter::once(Layer {
raster: hands
.iter()
.fold(None, |raster_union: Option<Raster>, hand| {
let raster = hand.raster.clone().unwrap().translate(shadow_offset);
if let Some(raster_union) = raster_union {
Some(raster_union + raster)
} else {
Some(raster)
}
})
.unwrap(),
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(SHADOW_COLOR),
blend_mode: BlendMode::Over,
},
}))
.chain(std::iter::once(Layer {
raster: hands
.iter()
.enumerate()
.fold(None, |raster_union: Option<Raster>, (i, hand)| {
if i != 1 {
let raster = hand.raster.clone().unwrap().translate(shadow_offset);
if let Some(raster_union) = raster_union {
Some(raster_union + raster)
} else {
Some(raster)
}
} else {
raster_union
}
})
.unwrap(),
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(SHADOW_COLOR),
blend_mode: BlendMode::Over,
},
}))
.chain(self.previous_rasters.drain(..).map(|raster| Layer {
raster,
style: Style {
fill_rule: FillRule::WholeTile,
fill: Fill::Solid(BACKGROUND_COLOR),
blend_mode: BlendMode::Over,
},
}));
self.composition.replace(.., layers);
context.render(&self.composition, Some(clip), self.image, &ext);
// Keep reference to rasters for clearing.
self.previous_rasters.extend(
hands.iter().map(|hand| hand.raster.clone().unwrap().translate(center)).chain(
hands.iter().map(|hand| hand.raster.clone().unwrap().translate(shadow_offset)),
),
);
}
}
struct Clockface {
scene: Scene,
contents: BTreeMap<u64, Contents>,
}
impl Clockface {
pub fn new(context: &mut Context) -> Self {
let scene = Scene::new(context);
Self { scene, contents: BTreeMap::new() }
}
fn update(
&mut self,
render_context: &mut Context,
context: &ViewAssistantContext,
) -> Result<(), Error> {
duration!("gfx", "update");
let size = &context.size;
let image_id = context.image_id;
let scale = size.width.min(size.height);
self.scene.update(render_context, size, scale);
let image = render_context.get_current_image(context);
let content = self.contents.entry(image_id).or_insert_with(|| Contents::new(image));
content.update(render_context, &self.scene, size, scale);
Ok(())
}
}
struct ClockfaceViewAssistant {
size: Size,
clockface: Option<Clockface>,
}
impl ClockfaceViewAssistant {
pub fn new() -> Self {
Self { size: Size::zero(), clockface: None }
}
}
impl ViewAssistant for ClockfaceViewAssistant {
fn render(
&mut self,
render_context: &mut Context,
ready_event: Event,
context: &ViewAssistantContext,
) -> Result<(), Error> {
if context.size != self.size || self.clockface.is_none() {
self.size = context.size;
self.clockface = Some(Clockface::new(render_context));
}
if let Some(clockface) = self.clockface.as_mut() {
clockface.update(render_context, context).expect("clockface.update");
}
ready_event.as_handle_ref().signal(Signals::NONE, Signals::EVENT_SIGNALED)?;
context.request_render();
Ok(())
}
}
fn main() -> Result<(), Error> {
fuchsia_trace_provider::trace_provider_create_with_fdio();
println!("Clockface Example");
App::run(make_app_assistant::<ClockfaceAppAssistant>())
}