blob: 9a2658f674beb91b035fbf6cfc237a2551371605 [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::{
app::{Config, ViewCreationParameters},
color::Color,
derive_handle_message_with_default,
drawing::{load_font, path_for_rectangle, path_for_rounded_rectangle, FontFace},
input::{self},
render::{BlendMode, Context as RenderContext, Fill, FillRule, Layer, Path, Style},
scene::{
facets::{Facet, FacetId, TextFacetOptions},
scene::{Scene, SceneBuilder, SceneOrder},
LayerGroup,
},
App, AppAssistant, AppAssistantPtr, AppSender, AssistantCreatorFunc, Coord, LocalBoxFuture,
MessageTarget, Point, Rect, Size, ViewAssistant, ViewAssistantContext, ViewAssistantPtr,
ViewKey,
};
use euclid::{point2, size2, vec2, Angle, Transform2D};
use fidl::prelude::*;
use fidl_test_placeholders::{EchoMarker, EchoRequest, EchoRequestStream};
use fuchsia_async as fasync;
use fuchsia_zircon::Time;
use futures::prelude::*;
use std::{f32::consts::PI, path::PathBuf};
struct SpinningSquareAppAssistant {
app_sender: AppSender,
}
impl SpinningSquareAppAssistant {
fn new(app_sender: AppSender) -> Self {
Self { app_sender }
}
}
impl AppAssistant for SpinningSquareAppAssistant {
fn setup(&mut self) -> Result<(), Error> {
Ok(())
}
fn create_view_assistant_with_parameters(
&mut self,
params: ViewCreationParameters,
) -> Result<ViewAssistantPtr, Error> {
let additional = params.options.is_some();
let direction = params
.options
.and_then(|options| options.downcast_ref::<Direction>().map(|direction| *direction))
.unwrap_or(Direction::CounterClockwise);
SpinningSquareViewAssistant::new(
params.view_key,
direction,
self.app_sender.clone(),
additional,
)
}
/// Return the list of names of services this app wants to provide
fn outgoing_services_names(&self) -> Vec<&'static str> {
[EchoMarker::PROTOCOL_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(())
}
fn filter_config(&mut self, config: &mut Config) {
config.display_resource_release_delay = std::time::Duration::new(0, 0);
}
}
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();
}
}
struct SceneDetails {
scene: Scene,
square: FacetId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
Clockwise,
CounterClockwise,
}
impl Direction {
pub fn toggle(self) -> Self {
match self {
Self::Clockwise => Self::CounterClockwise,
Self::CounterClockwise => Self::Clockwise,
}
}
}
#[derive(Debug)]
pub struct ToggleRoundedMessage {}
#[derive(Debug)]
pub struct ToggleDirectionMessage {}
struct SpinningSquareFacet {
direction: Direction,
square_color: Color,
rounded: bool,
start: Time,
square_path: Option<Path>,
size: Size,
}
impl SpinningSquareFacet {
fn new(square_color: Color, start: Time, size: Size, direction: Direction) -> Self {
Self { direction, square_color, rounded: false, start, square_path: None, size }
}
fn clone_square_path(&self) -> Path {
self.square_path.as_ref().expect("square_path").clone()
}
fn handle_toggle_rounded_message(&mut self, _msg: &ToggleRoundedMessage) {
self.rounded = !self.rounded;
self.square_path = None;
}
fn handle_toggle_direction_message(&mut self, _msg: &ToggleDirectionMessage) {
self.direction = self.direction.toggle();
}
fn handle_other_message(&mut self, _msg: &carnelian::Message) {
println!("handle_other_message");
}
}
impl Facet for SpinningSquareFacet {
fn update_layers(
&mut self,
size: Size,
layer_group: &mut dyn LayerGroup,
render_context: &mut RenderContext,
view_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 = size.width * 0.5;
let center_y = size.height * 0.5;
self.size = size;
let square_size = size.width.min(size.height) * 0.6;
let presentation_time = view_context.presentation_time;
let t = ((presentation_time.into_nanos() - self.start.into_nanos()) as f32
* SECONDS_PER_NANOSECOND
* SPEED)
% 1.0;
let angle =
t * PI * 2.0 * if self.direction == Direction::CounterClockwise { -1.0 } else { 1.0 };
if self.square_path.is_none() {
let top_left = point2(-SQUARE_PATH_SIZE_2, -SQUARE_PATH_SIZE_2);
let square = Rect::new(top_left, size2(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::rotation(Angle::radians(angle))
.then_scale(square_size, square_size)
.then_translate(vec2(center_x, center_y));
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&self.clone_square_path(), Some(&transformation));
let square_raster = raster_builder.build();
layer_group.insert(
SceneOrder::default(),
Layer {
raster: square_raster,
clip: None,
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(self.square_color),
blend_mode: BlendMode::Over,
},
},
);
Ok(())
}
derive_handle_message_with_default!(handle_other_message,
ToggleRoundedMessage => handle_toggle_rounded_message,
ToggleDirectionMessage => handle_toggle_direction_message
);
fn calculate_size(&self, _available: Size) -> Size {
self.size
}
}
struct SpinningSquareViewAssistant {
direction: Direction,
view_key: ViewKey,
background_color: Color,
square_color: Color,
start: Time,
app_sender: AppSender,
scene_details: Option<SceneDetails>,
face: FontFace,
additional: bool,
}
impl SpinningSquareViewAssistant {
fn new(
view_key: ViewKey,
direction: Direction,
app_sender: AppSender,
additional: bool,
) -> Result<ViewAssistantPtr, Error> {
let square_color = Color { r: 0xbb, g: 0x00, b: 0xff, a: 0xbb };
let background_color = Color { r: 0x3f, g: 0x8a, b: 0x99, a: 0xff };
let start = Time::get_monotonic();
let face = load_font(PathBuf::from("/pkg/data/fonts/RobotoSlab-Regular.ttf"))?;
Ok(Box::new(SpinningSquareViewAssistant {
direction,
view_key,
background_color,
square_color,
start,
scene_details: None,
app_sender,
face,
additional,
}))
}
fn ensure_scene_built(&mut self, size: Size) {
if self.scene_details.is_none() {
let min_dimension = size.width.min(size.height);
let font_size = (min_dimension / 5.0).ceil().min(64.0);
let mut builder =
SceneBuilder::new().background_color(self.background_color).animated(true);
let mut square = None;
builder.group().stack().center().contents(|builder| {
if self.additional {
let key_text = format!("{}", self.view_key);
let _ = builder.text(
self.face.clone(),
&key_text,
font_size,
Point::zero(),
TextFacetOptions::default(),
);
}
let square_facet =
SpinningSquareFacet::new(self.square_color, self.start, size, self.direction);
square = Some(builder.facet(Box::new(square_facet)));
const STRIPE_COUNT: usize = 5;
let stripe_height = size.height / (STRIPE_COUNT * 2 + 1) as f32;
const STRIPE_WIDTH_RATIO: f32 = 0.8;
let stripe_size = size2(size.width * STRIPE_WIDTH_RATIO, stripe_height);
builder.group().column().max_size().space_evenly().contents(|builder| {
for _ in 0..STRIPE_COUNT {
builder.rectangle(stripe_size, Color::white());
}
});
});
let square = square.expect("square");
let scene = builder.build();
self.scene_details = Some(SceneDetails { scene, square });
}
}
fn toggle_rounded(&mut self) {
if let Some(scene_details) = self.scene_details.as_mut() {
// since we have the scene, we could call send_message directly,
// but this lets us demonstrate facet-targeted messages.
self.app_sender.queue_message(
MessageTarget::Facet(self.view_key, scene_details.square),
Box::new(ToggleRoundedMessage {}),
);
self.app_sender.request_render(self.view_key);
}
}
fn move_backward(&mut self) {
if let Some(scene_details) = self.scene_details.as_mut() {
scene_details
.scene
.move_facet_backward(scene_details.square)
.unwrap_or_else(|e| println!("error in move_facet_backward: {}", e));
self.app_sender.request_render(self.view_key);
}
}
fn move_forward(&mut self) {
if let Some(scene_details) = self.scene_details.as_mut() {
scene_details
.scene
.move_facet_forward(scene_details.square)
.unwrap_or_else(|e| println!("error in move_facet_forward: {}", e));
self.app_sender.request_render(self.view_key);
}
}
fn toggle_direction(&mut self) {
if let Some(scene_details) = self.scene_details.as_mut() {
self.app_sender.queue_message(
MessageTarget::Facet(self.view_key, scene_details.square),
Box::new(ToggleDirectionMessage {}),
);
self.app_sender.request_render(self.view_key);
}
}
fn make_new_view(&mut self) {
let direction = self.direction.toggle();
self.app_sender.create_additional_view(Some(Box::new(direction)));
}
fn close_additional_view(&mut self) {
if self.additional {
self.app_sender.close_additional_view(self.view_key);
} else {
println!("Cannot close initial window");
}
}
}
impl ViewAssistant for SpinningSquareViewAssistant {
fn resize(&mut self, new_size: &Size) -> Result<(), Error> {
self.scene_details = None;
self.ensure_scene_built(*new_size);
Ok(())
}
fn get_scene(&mut self, size: Size) -> Option<&mut Scene> {
self.ensure_scene_built(size);
Some(&mut self.scene_details.as_mut().unwrap().scene)
}
fn handle_keyboard_event(
&mut self,
_context: &mut ViewAssistantContext,
_event: &input::Event,
keyboard_event: &input::keyboard::Event,
) -> Result<(), Error> {
const SPACE: u32 = ' ' as u32;
const B: u32 = 'b' as u32;
const F: u32 = 'f' as u32;
const D: u32 = 'd' as u32;
const V: u32 = 'v' as u32;
const C: u32 = 'c' as u32;
if let Some(code_point) = keyboard_event.code_point {
if keyboard_event.phase == input::keyboard::Phase::Pressed
|| keyboard_event.phase == input::keyboard::Phase::Repeat
{
match code_point {
SPACE => self.toggle_rounded(),
B => self.move_backward(),
F => self.move_forward(),
D => self.toggle_direction(),
V => self.make_new_view(),
C => self.close_additional_view(),
_ => println!("code_point = {}", code_point),
}
}
}
Ok(())
}
}
fn make_app_assistant_fut(
app_sender: &AppSender,
) -> LocalBoxFuture<'_, Result<AppAssistantPtr, Error>> {
let f = async move {
let assistant = Box::new(SpinningSquareAppAssistant::new(app_sender.clone()));
Ok::<AppAssistantPtr, Error>(assistant)
};
Box::pin(f)
}
fn make_app_assistant() -> AssistantCreatorFunc {
Box::new(make_app_assistant_fut)
}
fn main() -> Result<(), Error> {
fuchsia_trace_provider::trace_provider_create_with_fdio();
App::run(make_app_assistant())
}