blob: df57b3a4046ce3c1f486c64cbb640fa4c718601b [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 carnelian::{
make_app_assistant, make_message, set_node_color, App, AppAssistant, Color, Label, Message,
Paint, Point, Rect, Size, ViewAssistant, ViewAssistantContext, ViewAssistantPtr, ViewKey,
ViewMessages,
};
use fidl_fuchsia_ui_input::{FocusEvent, PointerEvent, PointerEventPhase};
use fuchsia_scenic::{EntityNode, Rectangle, SessionPtr, ShapeNode};
use fuchsia_zircon::{ClockId, Time};
const BACKGROUND_Z: f32 = 0.0;
const INDICATOR_Z: f32 = BACKGROUND_Z - 0.01;
const BUTTON_Z: f32 = BACKGROUND_Z - 0.01;
const BUTTON_BACKGROUND_Z: f32 = BUTTON_Z - 0.01;
const BUTTON_LABEL_Z: f32 = BUTTON_BACKGROUND_Z - 0.01;
/// enum that defines all messages sent with `App::queue_message` that
/// the button view assistant will understand and process.
pub enum ButtonMessages {
Pressed(Time),
}
#[derive(Default)]
struct ButtonAppAssistant;
impl AppAssistant for ButtonAppAssistant {
fn setup(&mut self) -> Result<(), Error> {
Ok(())
}
fn create_view_assistant(
&mut self,
_: ViewKey,
session: &SessionPtr,
) -> Result<ViewAssistantPtr, Error> {
Ok(Box::new(ButtonViewAssistant::new(session)?))
}
}
struct Button {
label: Label,
background_node: ShapeNode,
container: EntityNode,
bounds: Rect,
bg_color: Color,
bg_color_active: Color,
bg_color_disabled: Color,
fg_color: Color,
fg_color_disabled: Color,
tracking: bool,
active: bool,
focused: bool,
}
impl Button {
pub fn new(session: &SessionPtr, text: &str) -> Result<Button, Error> {
let mut button = Button {
label: Label::new(session, text)?,
background_node: ShapeNode::new(session.clone()),
container: EntityNode::new(session.clone()),
bounds: Rect::zero(),
fg_color: Color::white(),
bg_color: Color::from_hash_code("#B7410E")?,
bg_color_active: Color::from_hash_code("#f0703c")?,
fg_color_disabled: Color::from_hash_code("#A0A0A0")?,
bg_color_disabled: Color::from_hash_code("#C0C0C0")?,
tracking: false,
active: false,
focused: false,
};
// set up the button background
button.container.add_child(&button.background_node);
set_node_color(session, &button.background_node, &button.bg_color);
// Add the label
button.container.add_child(button.label.node());
Ok(button)
}
pub fn set_focused(&mut self, focused: bool) {
self.focused = focused;
if !focused {
self.active = false;
self.tracking = false;
}
}
pub fn update(&mut self, context: &ViewAssistantContext<'_>) -> Result<(), Error> {
// set up paint with different backgrounds depending on whether the button
// is active. The active state is true when a pointer has gone down in the
// button's bounds and the pointer has not moved outside the bounds since.
let paint = if self.focused {
Paint {
fg: self.fg_color,
bg: if self.active { self.bg_color_active } else { self.bg_color },
}
} else {
Paint { fg: self.fg_color_disabled, bg: self.bg_color_disabled }
};
// center the button in the Scenic view by translating the
// container node. All child nodes will be positioned relative
// to this container
let center_x = context.size.width * 0.5;
let center_y = context.size.height * 0.5;
// pick font size and padding based on the available space
let min_dimension = context.size.width.min(context.size.height);
let font_size = (min_dimension / 5.0).ceil().min(64.0) as u32;
let padding = (min_dimension / 20.0).ceil().max(8.0);
self.container.set_translation(center_x, center_y, BUTTON_Z);
set_node_color(context.session(), &self.background_node, &paint.bg);
// calculate button size based on label's text size
// plus padding.
let button_size = self.label.dimensions(font_size);
let button_w = button_size.width + 2.0 * padding;
let button_h = button_size.height + 2.0 * padding;
// record bounds for hit testing
self.bounds = Rect::new(
Point::new(center_x - button_w / 2.0, center_y - button_h / 2.0),
Size::new(button_w, button_h),
)
.round_out();
self.background_node.set_shape(&Rectangle::new(
context.session().clone(),
self.bounds.size.width,
self.bounds.size.height,
));
self.background_node.set_translation(0.0, 0.0, BUTTON_BACKGROUND_Z);
self.label.update(font_size, &paint)?;
self.label.node().set_translation(0.0, 0.0, BUTTON_LABEL_Z);
Ok(())
}
pub fn node(&mut self) -> &EntityNode {
&self.container
}
pub fn handle_pointer_event(
&mut self,
context: &mut ViewAssistantContext<'_>,
pointer_event: &PointerEvent,
) {
if !self.focused {
return;
}
// TODO: extend this to support multiple pointers
match pointer_event.phase {
PointerEventPhase::Down => {
self.active = self.bounds.contains(&Point::new(pointer_event.x, pointer_event.y));
self.tracking = self.active;
}
PointerEventPhase::Add => {}
PointerEventPhase::Hover => {}
PointerEventPhase::Move => {
if self.tracking {
self.active =
self.bounds.contains(&Point::new(pointer_event.x, pointer_event.y));
}
}
PointerEventPhase::Up => {
if self.active {
context.queue_message(make_message(ButtonMessages::Pressed(Time::get(
ClockId::Monotonic,
))));
}
self.tracking = false;
self.active = false;
}
PointerEventPhase::Remove => {
self.active = false;
self.tracking = false;
}
PointerEventPhase::Cancel => {
self.active = false;
self.tracking = false;
}
}
}
}
struct ButtonViewAssistant {
background_node: ShapeNode,
indicator: ShapeNode,
button: Button,
red_light: bool,
}
impl ButtonViewAssistant {
fn new(session: &SessionPtr) -> Result<ButtonViewAssistant, Error> {
Ok(ButtonViewAssistant {
background_node: ShapeNode::new(session.clone()),
indicator: ShapeNode::new(session.clone()),
button: Button::new(&session, "Touch Me")?,
red_light: false,
})
}
}
impl ViewAssistant for ButtonViewAssistant {
// Called once by Carnelian when the view is first created. Good for setup
// that isn't concerned with the size of the view.
fn setup(&mut self, context: &ViewAssistantContext<'_>) -> Result<(), Error> {
set_node_color(
context.session(),
&self.background_node,
&Color::from_hash_code("#EBD5B3")?,
);
context.root_node().add_child(&self.background_node);
context.root_node().add_child(&self.indicator);
context.root_node().add_child(self.button.node());
Ok(())
}
// Called by Carnelian when the view is resized, after input events are processed
// or if sent an explicit Update message.
fn update(&mut self, context: &ViewAssistantContext<'_>) -> Result<(), Error> {
// Position and size the background
let center_x = context.size.width * 0.5;
let center_y = context.size.height * 0.5;
self.background_node.set_shape(&Rectangle::new(
context.session().clone(),
context.size.width,
context.size.height,
));
self.background_node.set_translation(center_x, center_y, BACKGROUND_Z);
// Position and size the indicator
let indicator_y = context.size.height / 5.0;
let indicator_size = context.size.height.min(context.size.width) / 8.0;
self.indicator.set_shape(&Rectangle::new(
context.session().clone(),
indicator_size,
indicator_size,
));
self.indicator.set_translation(center_x, indicator_y, INDICATOR_Z);
let indicator_color = if self.red_light {
Color::from_hash_code("#ff0000")?
} else {
Color::from_hash_code("#00ff00")?
};
set_node_color(context.session(), &self.indicator, &indicator_color);
// Update and position the button
self.button.update(context)?;
self.button.node().set_translation(center_x, center_y, BUTTON_Z);
Ok(())
}
fn handle_message(&mut self, message: Message) {
if let Some(button_message) = message.downcast_ref::<ButtonMessages>() {
match button_message {
ButtonMessages::Pressed(value) => {
println!("value = {:#?}", value);
self.red_light = !self.red_light
}
}
}
}
fn handle_pointer_event(
&mut self,
context: &mut ViewAssistantContext<'_>,
pointer_event: &PointerEvent,
) -> Result<(), Error> {
self.button.handle_pointer_event(context, pointer_event);
context.queue_message(make_message(ViewMessages::Update));
Ok(())
}
fn handle_focus_event(
&mut self,
context: &mut ViewAssistantContext<'_>,
focus_event: &FocusEvent,
) -> Result<(), Error> {
self.button.set_focused(focus_event.focused);
context.queue_message(make_message(ViewMessages::Update));
Ok(())
}
}
fn main() -> Result<(), Error> {
App::run(make_app_assistant::<ButtonAppAssistant>())
}