| // 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}, |
| facet::{FacetId, RasterFacet, Scene, SceneBuilder, SetLocationMessage}, |
| geometry::{Corners, IntPoint}, |
| input::{self}, |
| make_app_assistant, |
| render::{BlendMode, Context as RenderContext, Fill, FillRule, Raster, Style}, |
| App, AppAssistant, Coord, Point, Rect, RenderOptions, ViewAssistant, ViewAssistantContext, |
| ViewAssistantPtr, ViewKey, |
| }; |
| use euclid::{default::Vector2D, point2, size2, vec2}; |
| use fuchsia_trace::duration; |
| use fuchsia_zircon::Event; |
| use rand::{thread_rng, Rng}; |
| use std::{collections::HashMap, mem}; |
| |
| fn make_bounds(context: &ViewAssistantContext) -> Rect { |
| Rect::from_size(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 { |
| location: Point, |
| accel: Vector2D<f32>, |
| velocity: Vector2D<f32>, |
| running: bool, |
| facet_id: FacetId, |
| } |
| |
| impl ShapeAnimator { |
| pub fn new(touch_handler: TouchHandler, location: Point) -> Self { |
| Self { |
| location: location, |
| accel: vec2(0.0, 1.0), |
| velocity: Vector2D::zero(), |
| running: true, |
| facet_id: touch_handler.facet_id, |
| } |
| } |
| |
| 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, |
| facet_id: FacetId, |
| } |
| |
| 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(shapes: &HashMap<ShapeType, Raster>, scene: &mut Scene) -> Self { |
| 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(); |
| let raster = shapes.get(&shape_type).expect("shape"); |
| let raster_facet = RasterFacet::new( |
| raster.clone(), |
| Style { |
| fill_rule: FillRule::NonZero, |
| fill: Fill::Solid(color), |
| blend_mode: BlendMode::Over, |
| }, |
| Point::zero(), |
| ); |
| let facet_id = scene.add_facet(Box::new(raster_facet)); |
| Self { location: Point::zero(), color, shape_type, facet_id } |
| } |
| |
| fn update( |
| &mut self, |
| context: &mut ViewAssistantContext, |
| location: &IntPoint, |
| scene: &mut Scene, |
| ) { |
| let touch_point = location.to_f32(); |
| let bounds = make_bounds(context); |
| self.location = |
| point2(touch_point.x, touch_point.y).clamp(bounds.origin, bounds.bottom_right()); |
| scene |
| .send_message(&self.facet_id, Box::new(SetLocationMessage { location: self.location })); |
| } |
| } |
| |
| 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 { |
| touch_handlers: HashMap<input::pointer::PointerId, TouchHandler>, |
| animators: Vec<ShapeAnimator>, |
| background_color: Color, |
| shapes: HashMap<ShapeType, Raster>, |
| scene_details: Option<SceneDetails>, |
| } |
| |
| impl ShapeDropViewAssistant { |
| fn new() -> Result<Self, Error> { |
| let background_color = Color::from_hash_code("#2F4F4F")?; |
| Ok(Self { |
| background_color, |
| touch_handlers: HashMap::new(), |
| animators: Vec::new(), |
| shapes: HashMap::new(), |
| scene_details: None, |
| }) |
| } |
| |
| 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 = |
| point2(touch_point.x, touch_point.y).clamp(bounds.origin, bounds.bottom_right()); |
| let animator = ShapeAnimator::new(handler, point2(location.x, location.y)); |
| self.animators.push(animator); |
| } |
| } |
| |
| fn setup_shapes(&mut self, render_context: &mut RenderContext) { |
| if self.shapes.is_empty() { |
| let size = size2(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); |
| } |
| } |
| } |
| |
| struct SceneDetails { |
| scene: Scene, |
| } |
| |
| 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; |
| |
| let mut scene_details = self.scene_details.take().unwrap_or_else(|| { |
| let builder = SceneBuilder::new(background_color); |
| let scene = builder.build(); |
| SceneDetails { scene } |
| }); |
| |
| self.setup_shapes(render_context); |
| |
| 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); |
| scene_details.scene.send_message( |
| &animator.facet_id, |
| Box::new(SetLocationMessage { location: animator.location }), |
| ); |
| } |
| |
| let (mut running, not_running): (Vec<_>, Vec<_>) = |
| animators.into_iter().partition(|animator| animator.running); |
| |
| for animator in not_running { |
| scene_details.scene.remove_facet(animator.facet_id)?; |
| } |
| |
| mem::swap(&mut running, &mut self.animators); |
| |
| scene_details.scene.render(render_context, ready_event, context)?; |
| self.scene_details = Some(scene_details); |
| |
| if !self.animators.is_empty() { |
| context.request_render(); |
| } |
| |
| Ok(()) |
| } |
| |
| 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 scene_details = self.scene_details.as_mut().expect("scene_details"); |
| let mut t = TouchHandler::new(&mut self.shapes, &mut scene_details.scene); |
| t.update(context, &touch_location, &mut scene_details.scene); |
| self.touch_handlers.insert(pointer_event.pointer_id.clone(), t); |
| context.request_render(); |
| } |
| input::pointer::Phase::Moved(touch_location) => { |
| if let Some(handler) = self.touch_handlers.get_mut(&pointer_event.pointer_id) { |
| let scene_details = self.scene_details.as_mut().expect("scene_details"); |
| handler.update(context, &touch_location, &mut scene_details.scene); |
| } |
| context.request_render(); |
| } |
| 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); |
| context.request_render(); |
| } |
| _ => (), |
| } |
| Ok(()) |
| } |
| } |
| |
| fn main() -> Result<(), Error> { |
| fuchsia_trace_provider::trace_provider_create_with_fdio(); |
| App::run(make_app_assistant::<ShapeDropAppAssistant>()) |
| } |