blob: 0b969c9cc49d2f833e9574062a0a150d6a4ac828 [file] [log] [blame]
// 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>())
}