blob: 4aa04961292374645fcc4ddee3dca91aa318cbad [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;
use argh::FromArgs;
use carnelian::{
color::Color,
drawing::{path_for_circle, path_for_polygon, path_for_rectangle, path_for_rounded_rectangle},
geometry::{Corners, IntPoint},
input::{self},
make_app_assistant,
render::{
BlendMode, Composition, Context as RenderContext, Fill, FillRule, Layer, PreClear, Raster,
RenderExt, Style,
},
AnimationMode, App, AppAssistant, Coord, Point, Rect, RenderOptions, Size, ViewAssistant,
ViewAssistantContext, ViewAssistantPtr, ViewKey,
};
use euclid::default::Vector2D;
use fuchsia_trace::duration;
use fuchsia_zircon::{AsHandleRef, Event, Signals};
use rand::{thread_rng, Rng};
use std::{collections::BTreeMap, mem};
fn make_bounds(context: &ViewAssistantContext) -> Rect {
Rect::new(Point::zero(), context.size)
}
/// Shapes
#[derive(Clone, Debug, FromArgs)]
#[argh(name = "shapes")]
struct Args {
/// use spinel
#[argh(switch, short = 's')]
use_spinel: bool,
}
#[derive(Default)]
struct ShapeDropAppAssistant;
impl AppAssistant for ShapeDropAppAssistant {
fn setup(&mut self) -> Result<(), Error> {
Ok(())
}
fn create_view_assistant(&mut self, _: ViewKey) -> Result<ViewAssistantPtr, Error> {
Ok(Box::new(ShapeDropViewAssistant::new()?))
}
fn get_render_options(&self) -> RenderOptions {
let args: Args = argh::from_env();
RenderOptions { use_spinel: args.use_spinel, ..RenderOptions::default() }
}
}
struct ShapeAnimator {
shape_type: ShapeType,
color: Color,
location: Point,
accel: Vector2D<f32>,
velocity: Vector2D<f32>,
running: bool,
}
impl ShapeAnimator {
pub fn new(touch_handler: TouchHandler, location: Point) -> ShapeAnimator {
ShapeAnimator {
shape_type: touch_handler.shape_type,
color: touch_handler.color,
location: location,
accel: Vector2D::new(0.0, 1.0),
velocity: Vector2D::zero(),
running: true,
}
}
pub fn animate(&mut self, bounds: &Rect) {
self.location += self.velocity;
self.velocity += self.accel;
if self.location.y > bounds.max_y() {
self.running = false;
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum ShapeType {
Rectangle,
RoundedRectangle,
Circle,
Hexagon,
Triangle,
Octagon,
LastShapeType,
}
#[derive(Debug)]
struct TouchHandler {
location: Point,
shape_type: ShapeType,
color: Color,
}
fn random_color_element() -> u8 {
let mut rng = thread_rng();
let e: u8 = rng.gen_range(0, 128);
e + 128
}
fn random_color() -> Color {
Color {
r: random_color_element(),
g: random_color_element(),
b: random_color_element(),
a: 0xff,
}
}
impl TouchHandler {
pub fn new() -> TouchHandler {
let mut rng = thread_rng();
let shape_type = match rng.gen_range(0, ShapeType::LastShapeType as usize) {
0 => ShapeType::Rectangle,
1 => ShapeType::RoundedRectangle,
2 => ShapeType::Hexagon,
3 => ShapeType::Triangle,
4 => ShapeType::Octagon,
_ => ShapeType::Circle,
};
let color = random_color();
TouchHandler { location: Point::zero(), color, shape_type }
}
fn update(&mut self, context: &mut ViewAssistantContext, location: &IntPoint) {
let touch_point = location.to_f32();
let bounds = make_bounds(context);
self.location =
Point::new(touch_point.x, touch_point.y).clamp(bounds.origin, bounds.bottom_right());
}
}
fn raster_for_rectangle(bounds: &Rect, render_context: &mut RenderContext) -> Raster {
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&path_for_rectangle(bounds, render_context), None);
raster_builder.build()
}
fn raster_for_rounded_rectangle(
bounds: &Rect,
corner_radius: Coord,
render_context: &mut RenderContext,
) -> Raster {
let path = path_for_rounded_rectangle(bounds, corner_radius, render_context);
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&path, None);
raster_builder.build()
}
fn raster_for_circle(center: Point, radius: Coord, render_context: &mut RenderContext) -> Raster {
let path = path_for_circle(center, radius, render_context);
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&path, None);
raster_builder.build()
}
fn raster_for_polygon(
center: Point,
radius: Coord,
segment_count: usize,
render_context: &mut RenderContext,
) -> Raster {
let path = path_for_polygon(center, radius, segment_count, render_context);
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&path, None);
raster_builder.build()
}
struct ShapeDropViewAssistant {
renderings: BTreeMap<u64, Rendering>,
touch_handlers: BTreeMap<input::pointer::PointerId, TouchHandler>,
animators: Vec<ShapeAnimator>,
background_color: Color,
composition: Composition,
shapes: BTreeMap<ShapeType, Raster>,
}
impl ShapeDropViewAssistant {
fn new() -> Result<ShapeDropViewAssistant, Error> {
let background_color = Color::from_hash_code("#2F4F4F")?;
let composition = Composition::new(background_color);
Ok(ShapeDropViewAssistant {
renderings: BTreeMap::new(),
composition,
background_color,
touch_handlers: BTreeMap::new(),
animators: Vec::new(),
shapes: BTreeMap::new(),
})
}
fn start_animating(
&mut self,
context: &mut ViewAssistantContext,
location: &IntPoint,
pointer_id: &input::pointer::PointerId,
) {
if let Some(handler) = self.touch_handlers.remove(&pointer_id) {
let bounds = make_bounds(context);
let touch_point = location.to_f32();
let location = Point::new(touch_point.x, touch_point.y)
.clamp(bounds.origin, bounds.bottom_right());
let animator = ShapeAnimator::new(handler, Point::new(location.x, location.y));
self.animators.push(animator);
}
}
fn setup_shapes(&mut self, render_context: &mut RenderContext) {
if self.shapes.is_empty() {
let size = Size::new(60.0, 60.0);
let origin = Point::zero() - size.to_vector() / 2.0;
let shape_bounds = Rect::new(origin, size);
let raster = raster_for_rectangle(&shape_bounds, render_context);
self.shapes.insert(ShapeType::Rectangle, raster);
let raster = raster_for_circle(Point::zero(), size.width / 2.0, render_context);
self.shapes.insert(ShapeType::Circle, raster);
let raster =
raster_for_rounded_rectangle(&shape_bounds, size.width * 0.25, render_context);
self.shapes.insert(ShapeType::RoundedRectangle, raster);
let raster = raster_for_polygon(Point::zero(), size.width / 2.0, 6, render_context);
self.shapes.insert(ShapeType::Hexagon, raster);
let raster = raster_for_polygon(Point::zero(), size.width / 2.0, 3, render_context);
self.shapes.insert(ShapeType::Triangle, raster);
let raster = raster_for_polygon(Point::zero(), size.width / 2.0, 8, render_context);
self.shapes.insert(ShapeType::Octagon, raster);
}
}
}
#[derive(Debug)]
struct ShapeAtPosition {
shape_type: ShapeType,
translation: Vector2D<i32>,
}
struct Rendering {
size: Size,
previous_shapes: Vec<ShapeAtPosition>,
}
impl Rendering {
fn new() -> Rendering {
Rendering { previous_shapes: Vec::new(), size: Size::zero() }
}
}
impl ViewAssistant for ShapeDropViewAssistant {
fn render(
&mut self,
render_context: &mut RenderContext,
ready_event: Event,
context: &ViewAssistantContext,
) -> Result<(), Error> {
duration!("gfx", "ShapeDropViewAssistant::render");
let background_color = self.background_color;
self.setup_shapes(render_context);
let shapes = self.shapes.clone();
let mut animators = Vec::new();
mem::swap(&mut animators, &mut self.animators);
let bounds = make_bounds(context);
for animator in animators.iter_mut() {
animator.animate(&bounds);
}
let image_id = context.image_id;
let rendering = self.renderings.entry(image_id).or_insert_with(|| Rendering::new());
let pre_clear = if context.size != rendering.size {
rendering.size = context.size;
rendering.previous_shapes.clear();
Some(PreClear { color: background_color })
} else {
None
};
let mut previous_shapes = Vec::new();
mem::swap(&mut rendering.previous_shapes, &mut previous_shapes);
let clear_layers = previous_shapes.into_iter().map(|previous_shape| {
let raster = shapes.get(&previous_shape.shape_type).expect("shape");
let translation = previous_shape.translation;
let translated_raster = raster.clone().translate(translation);
Layer {
raster: translated_raster,
style: Style {
fill_rule: FillRule::WholeTile,
fill: Fill::Solid(background_color),
blend_mode: BlendMode::Over,
},
}
});
let (mut animator_positioned_shapes, animation_layers): (Vec<_>, Vec<_>) = animators
.iter()
.map(|animator| {
let raster = shapes.get(&animator.shape_type).expect("shape");
let translation = animator.location.to_vector().to_i32();
let translated_raster = raster.clone().translate(translation);
(
ShapeAtPosition { shape_type: animator.shape_type, translation: translation },
Layer {
raster: translated_raster,
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(animator.color),
blend_mode: BlendMode::Over,
},
},
)
})
.unzip();
let (mut touch_positioned_shapes, touch_layers): (Vec<_>, Vec<_>) = self
.touch_handlers
.iter()
.map(|(_, touch_handler)| {
let raster = shapes.get(&touch_handler.shape_type).expect("shape");
let translation = touch_handler.location.to_vector().to_i32();
let translated_raster = raster.clone().translate(translation);
(
ShapeAtPosition {
shape_type: touch_handler.shape_type,
translation: translation,
},
Layer {
raster: translated_raster,
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(touch_handler.color),
blend_mode: BlendMode::Over,
},
},
)
})
.unzip();
let layers = touch_layers.into_iter().chain(animation_layers).chain(clear_layers);
self.composition.replace(.., layers);
animators.retain(|animator| animator.running);
mem::swap(&mut animators, &mut self.animators);
rendering.previous_shapes.append(&mut touch_positioned_shapes);
rendering.previous_shapes.append(&mut animator_positioned_shapes);
let image = render_context.get_current_image(context);
let ext = RenderExt { pre_clear, ..Default::default() };
render_context.render(&self.composition, None, image, &ext);
ready_event.as_handle_ref().signal(Signals::NONE, Signals::EVENT_SIGNALED)?;
Ok(())
}
fn initial_animation_mode(&mut self) -> AnimationMode {
return AnimationMode::EveryFrame;
}
fn handle_pointer_event(
&mut self,
context: &mut ViewAssistantContext,
_event: &input::Event,
pointer_event: &input::pointer::Event,
) -> Result<(), Error> {
match &pointer_event.phase {
input::pointer::Phase::Down(touch_location) => {
let mut t = TouchHandler::new();
t.update(context, &touch_location);
self.touch_handlers.insert(pointer_event.pointer_id.clone(), t);
}
input::pointer::Phase::Moved(touch_location) => {
if let Some(handler) = self.touch_handlers.get_mut(&pointer_event.pointer_id) {
handler.update(context, &touch_location);
}
}
input::pointer::Phase::Up => {
let end_location =
if let Some(handler) = self.touch_handlers.get_mut(&pointer_event.pointer_id) {
handler.location.to_i32()
} else {
IntPoint::zero()
};
self.start_animating(context, &end_location, &pointer_event.pointer_id);
}
_ => (),
}
Ok(())
}
}
fn main() -> Result<(), Error> {
fuchsia_trace_provider::trace_provider_create_with_fdio();
App::run(make_app_assistant::<ShapeDropAppAssistant>())
}