| // Copyright 2018 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::{Context as _, Error}; |
| use carnelian::{ |
| color::Color, |
| geometry::Corners, |
| input::{self}, |
| make_app_assistant, |
| render::{ |
| BlendMode, Composition, Context as RenderContext, Fill, FillRule, Layer, Path, PreClear, |
| Raster, RenderExt, Style, |
| }, |
| App, AppAssistant, Coord, Point, Rect, Size, ViewAssistant, ViewAssistantContext, |
| ViewAssistantPtr, ViewKey, |
| }; |
| use euclid::{Angle, Transform2D, Vector2D}; |
| use fidl::endpoints::{RequestStream, ServiceMarker}; |
| use fidl_test_placeholders::{EchoMarker, EchoRequest, EchoRequestStream}; |
| use fuchsia_async as fasync; |
| use fuchsia_zircon::{AsHandleRef, Event, Signals, Time}; |
| use futures::prelude::*; |
| use std::f32::consts::PI; |
| |
| #[derive(Default)] |
| struct SpinningSquareAppAssistant; |
| |
| impl AppAssistant for SpinningSquareAppAssistant { |
| fn setup(&mut self) -> Result<(), Error> { |
| Ok(()) |
| } |
| |
| fn create_view_assistant(&mut self, _: ViewKey) -> Result<ViewAssistantPtr, Error> { |
| SpinningSquareViewAssistant::new() |
| } |
| |
| /// Return the list of names of services this app wants to provide |
| fn outgoing_services_names(&self) -> Vec<&'static str> { |
| [EchoMarker::NAME].to_vec() |
| } |
| |
| /// Handle a request to connect to a service provided by this app |
| fn handle_service_connection_request( |
| &mut self, |
| _service_name: &str, |
| channel: fasync::Channel, |
| ) -> Result<(), Error> { |
| Self::create_echo_server(channel, false); |
| Ok(()) |
| } |
| } |
| |
| impl SpinningSquareAppAssistant { |
| fn create_echo_server(channel: fasync::Channel, quiet: bool) { |
| fasync::Task::local( |
| async move { |
| let mut stream = EchoRequestStream::from_channel(channel); |
| while let Some(EchoRequest::EchoString { value, responder }) = |
| stream.try_next().await.context("error running echo server")? |
| { |
| if !quiet { |
| println!("Spinning Square received echo request for string {:?}", value); |
| } |
| responder |
| .send(value.as_ref().map(|s| &**s)) |
| .context("error sending response")?; |
| if !quiet { |
| println!("echo response sent successfully"); |
| } |
| } |
| Ok(()) |
| } |
| .unwrap_or_else(|e: anyhow::Error| eprintln!("{:?}", e)), |
| ) |
| .detach(); |
| } |
| } |
| |
| fn path_for_rectangle(bounds: &Rect, render_context: &mut RenderContext) -> Path { |
| let mut path_builder = render_context.path_builder().expect("path_builder"); |
| path_builder.move_to(bounds.origin); |
| path_builder.line_to(bounds.top_right()); |
| path_builder.line_to(bounds.bottom_right()); |
| path_builder.line_to(bounds.bottom_left()); |
| path_builder.line_to(bounds.origin); |
| path_builder.build() |
| } |
| |
| fn path_for_rounded_rectangle( |
| bounds: &Rect, |
| corner_radius: Coord, |
| render_context: &mut RenderContext, |
| ) -> Path { |
| let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan(); |
| let control_dist = kappa * corner_radius; |
| |
| let mut path_builder = render_context.path_builder().expect("path_builder"); |
| let top_left_arc_start = bounds.origin + Vector2D::new(0.0, corner_radius); |
| let top_left_arc_end = bounds.origin + Vector2D::new(corner_radius, 0.0); |
| path_builder.move_to(top_left_arc_start); |
| let top_left_curve_center = bounds.origin + Vector2D::new(corner_radius, corner_radius); |
| let p1 = top_left_curve_center + Vector2D::new(-corner_radius, -control_dist); |
| let p2 = top_left_curve_center + Vector2D::new(-control_dist, -corner_radius); |
| path_builder.cubic_to(p1, p2, top_left_arc_end); |
| |
| let top_right = bounds.top_right(); |
| let top_right_arc_start = top_right + Vector2D::new(-corner_radius, 0.0); |
| let top_right_arc_end = top_right + Vector2D::new(0.0, corner_radius); |
| path_builder.line_to(top_right_arc_start); |
| let top_right_curve_center = top_right + Vector2D::new(-corner_radius, corner_radius); |
| let p1 = top_right_curve_center + Vector2D::new(control_dist, -corner_radius); |
| let p2 = top_right_curve_center + Vector2D::new(corner_radius, -control_dist); |
| path_builder.cubic_to(p1, p2, top_right_arc_end); |
| |
| let bottom_right = bounds.bottom_right(); |
| let bottom_right_arc_start = bottom_right + Vector2D::new(0.0, -corner_radius); |
| let bottom_right_arc_end = bottom_right + Vector2D::new(-corner_radius, 0.0); |
| path_builder.line_to(bottom_right_arc_start); |
| let bottom_right_curve_center = bottom_right + Vector2D::new(-corner_radius, -corner_radius); |
| let p1 = bottom_right_curve_center + Vector2D::new(corner_radius, control_dist); |
| let p2 = bottom_right_curve_center + Vector2D::new(control_dist, corner_radius); |
| path_builder.cubic_to(p1, p2, bottom_right_arc_end); |
| |
| let bottom_left = bounds.bottom_left(); |
| let bottom_left_arc_start = bottom_left + Vector2D::new(corner_radius, 0.0); |
| let bottom_left_arc_end = bottom_left + Vector2D::new(0.0, -corner_radius); |
| path_builder.line_to(bottom_left_arc_start); |
| let bottom_left_curve_center = bottom_left + Vector2D::new(corner_radius, -corner_radius); |
| let p1 = bottom_left_curve_center + Vector2D::new(-control_dist, corner_radius); |
| let p2 = bottom_left_curve_center + Vector2D::new(-corner_radius, control_dist); |
| path_builder.cubic_to(p1, p2, bottom_left_arc_end); |
| |
| path_builder.line_to(top_left_arc_start); |
| path_builder.build() |
| } |
| |
| struct SpinningSquareViewAssistant { |
| background_color: Color, |
| square_color: Color, |
| rounded: bool, |
| start: Time, |
| square_raster: Option<Raster>, |
| square_path: Option<Path>, |
| composition: Composition, |
| } |
| |
| impl SpinningSquareViewAssistant { |
| fn new() -> Result<ViewAssistantPtr, Error> { |
| let square_color = Color { r: 0xff, g: 0x00, b: 0xff, a: 0xff }; |
| let background_color = Color { r: 0xb7, g: 0x41, b: 0x0e, a: 0xff }; |
| let start = Time::get_monotonic(); |
| let composition = Composition::new(background_color); |
| Ok(Box::new(SpinningSquareViewAssistant { |
| background_color, |
| square_color, |
| rounded: false, |
| start, |
| square_raster: None, |
| square_path: None, |
| composition, |
| })) |
| } |
| |
| fn clone_square_raster(&self) -> Raster { |
| self.square_raster.as_ref().expect("square_raster").clone() |
| } |
| |
| fn clone_square_path(&self) -> Path { |
| self.square_path.as_ref().expect("square_path").clone() |
| } |
| |
| fn toggle_rounded(&mut self) { |
| self.rounded = !self.rounded; |
| self.square_path = None; |
| } |
| } |
| |
| impl ViewAssistant for SpinningSquareViewAssistant { |
| fn render( |
| &mut self, |
| render_context: &mut RenderContext, |
| ready_event: Event, |
| context: &ViewAssistantContext, |
| ) -> Result<(), Error> { |
| const SPEED: f32 = 0.25; |
| const SECONDS_PER_NANOSECOND: f32 = 1e-9; |
| const SQUARE_PATH_SIZE: Coord = 1.0; |
| const SQUARE_PATH_SIZE_2: Coord = SQUARE_PATH_SIZE / 2.0; |
| const CORNER_RADIUS: Coord = SQUARE_PATH_SIZE / 4.0; |
| |
| let center_x = context.size.width * 0.5; |
| let center_y = context.size.height * 0.5; |
| let square_size = context.size.width.min(context.size.height) * 0.6; |
| let t = ((context.presentation_time.into_nanos() - self.start.into_nanos()) as f32 |
| * SECONDS_PER_NANOSECOND |
| * SPEED) |
| % 1.0; |
| let angle = t * PI * 2.0; |
| |
| if self.square_path.is_none() { |
| let top_left = Point::new(-SQUARE_PATH_SIZE_2, -SQUARE_PATH_SIZE_2); |
| let square = Rect::new(top_left, Size::new(SQUARE_PATH_SIZE, SQUARE_PATH_SIZE)); |
| let square_path = if self.rounded { |
| path_for_rounded_rectangle(&square, CORNER_RADIUS, render_context) |
| } else { |
| path_for_rectangle(&square, render_context) |
| }; |
| self.square_path.replace(square_path); |
| } |
| |
| let transformation = Transform2D::create_rotation(Angle::radians(angle)) |
| .post_scale(square_size, square_size) |
| .post_translate(Vector2D::new(center_x, center_y)); |
| let mut raster_builder = render_context.raster_builder().expect("raster_builder"); |
| raster_builder.add(&self.clone_square_path(), Some(&transformation)); |
| self.square_raster = Some(raster_builder.build()); |
| |
| let layers = std::iter::once(Layer { |
| raster: self.clone_square_raster(), |
| style: Style { |
| fill_rule: FillRule::NonZero, |
| fill: Fill::Solid(self.square_color), |
| blend_mode: BlendMode::Over, |
| }, |
| }); |
| |
| self.composition.replace(.., layers); |
| |
| let image = render_context.get_current_image(context); |
| let ext = RenderExt { |
| pre_clear: Some(PreClear { color: self.background_color }), |
| ..Default::default() |
| }; |
| render_context.render(&self.composition, None, image, &ext); |
| ready_event.as_handle_ref().signal(Signals::NONE, Signals::EVENT_SIGNALED)?; |
| context.request_render(); |
| Ok(()) |
| } |
| |
| fn handle_keyboard_event( |
| &mut self, |
| _context: &mut ViewAssistantContext, |
| _event: &input::Event, |
| keyboard_event: &input::keyboard::Event, |
| ) -> Result<(), Error> { |
| if let Some(code_point) = keyboard_event.code_point { |
| if code_point == ' ' as u32 && keyboard_event.phase == input::keyboard::Phase::Pressed { |
| self.toggle_rounded(); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| fn main() -> Result<(), Error> { |
| fuchsia_trace_provider::trace_provider_create_with_fdio(); |
| App::run(make_app_assistant::<SpinningSquareAppAssistant>()) |
| } |