blob: fdf1c2ca0a3399f9674dbf645f7ba85e160078b1 [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.
//! This is the button sample.
use anyhow::Error;
use argh::FromArgs;
use carnelian::{
app::{Config, ViewCreationParameters},
color::Color,
derive_handle_message,
drawing::{load_font, measure_text_width, DisplayRotation, FontFace},
input::{self},
make_app_assistant, make_message,
scene::{
facets::{
FacetId, SetColorMessage, SetSizeMessage, TextFacetOptions, TextHorizontalAlignment,
TextVerticalAlignment,
},
layout::{
Alignment, CrossAxisAlignment, Flex, FlexOptions, MainAxisAlignment, MainAxisSize,
Stack, StackOptions,
},
scene::{Scene, SceneBuilder},
},
App, AppAssistant, AppSender, MessageTarget, Point, Size, ViewAssistant, ViewAssistantContext,
ViewAssistantPtr, ViewKey,
};
use euclid::size2;
use fuchsia_zircon::Time;
use std::{
f32::consts::PI,
path::PathBuf,
thread,
time::{Duration, Instant},
};
/// Button Sample
#[derive(Debug, FromArgs)]
#[argh(name = "recovery")]
struct Args {
/// rotate
#[argh(option)]
rotation: Option<DisplayRotation>,
}
/// enum that defines all messages sent with `App::queue_message` that
/// the button view assistant will understand and process.
pub enum ButtonMessages {
Pressed(Time),
Resize,
}
struct ButtonAppAssistant {
display_rotation: DisplayRotation,
}
impl Default for ButtonAppAssistant {
fn default() -> Self {
let args: Args = argh::from_env();
Self { display_rotation: args.rotation.unwrap_or(DisplayRotation::Deg0) }
}
}
impl AppAssistant for ButtonAppAssistant {
fn setup(&mut self) -> Result<(), Error> {
Ok(())
}
fn create_view_assistant_with_parameters(
&mut self,
params: ViewCreationParameters,
) -> Result<ViewAssistantPtr, Error> {
Ok(Box::new(ButtonViewAssistant::new(params.app_sender, params.view_key)?))
}
fn filter_config(&mut self, config: &mut Config) {
config.display_rotation = self.display_rotation;
}
}
#[allow(unused)]
struct Button {
pub font_size: f32,
pub padding: f32,
bg_color: Color,
bg_color_active: Color,
bg_color_disabled: Color,
fg_color: Color,
fg_color_disabled: Color,
tracking_pointer: Option<input::pointer::PointerId>,
active: bool,
focused: bool,
label_text: String,
face: FontFace,
background: FacetId,
label: FacetId,
}
impl Button {
pub fn new(
text: &str,
font_size: f32,
padding: f32,
builder: &mut SceneBuilder,
) -> Result<Button, Error> {
let options = StackOptions { alignment: Alignment::center(), ..StackOptions::default() };
builder.start_group("button", Stack::with_options_ptr(options));
let face = load_font(PathBuf::from("/pkg/data/fonts/RobotoSlab-Regular.ttf"))?;
let label_width = measure_text_width(&face, font_size, text);
let label = builder.text(
face.clone(),
text,
font_size,
Point::zero(),
TextFacetOptions {
color: Color::white(),
horizontal_alignment: TextHorizontalAlignment::Left,
vertical_alignment: TextVerticalAlignment::Top,
..TextFacetOptions::default()
},
);
let bg_color = Color::from_hash_code("#B7410E")?;
let bg_size = size2(label_width + padding * 2.0, font_size + padding * 2.0);
let background = builder.rounded_rectangle(bg_size, padding / 2.0, bg_color);
builder.end_group();
let button = Button {
font_size: font_size,
padding: padding,
fg_color: Color::white(),
bg_color,
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_pointer: None,
active: false,
focused: false,
label_text: text.to_string(),
face,
background,
label,
};
Ok(button)
}
fn update_button_bg_color(&mut self, scene: &mut Scene) {
let (label_color, color) = if self.focused {
if self.active {
(self.fg_color, self.bg_color_active)
} else {
(self.fg_color, self.bg_color)
}
} else {
(self.fg_color_disabled, self.bg_color_disabled)
};
scene.send_message(&self.background, Box::new(SetColorMessage { color }));
scene.send_message(&self.label, Box::new(SetColorMessage { color: label_color }));
}
fn set_active(&mut self, scene: &mut Scene, active: bool) {
if self.active != active {
self.active = active;
self.update_button_bg_color(scene);
}
}
pub fn set_focused(&mut self, scene: &mut Scene, focused: bool) {
if focused != self.focused {
self.focused = focused;
self.active = false;
self.update_button_bg_color(scene);
if !focused {
self.tracking_pointer = None;
}
}
}
pub fn handle_pointer_event(
&mut self,
scene: &mut Scene,
context: &mut ViewAssistantContext,
pointer_event: &input::pointer::Event,
) {
if !self.focused {
return;
}
let bounds = scene.get_facet_bounds(&self.background);
if self.tracking_pointer.is_none() {
match pointer_event.phase {
input::pointer::Phase::Down(location) => {
self.set_active(scene, bounds.contains(location.to_f32()));
if self.active {
self.tracking_pointer = Some(pointer_event.pointer_id.clone());
}
}
_ => (),
}
} else {
let tracking_pointer = self.tracking_pointer.as_ref().expect("tracking_pointer");
if tracking_pointer == &pointer_event.pointer_id {
match pointer_event.phase {
input::pointer::Phase::Moved(location) => {
self.set_active(scene, bounds.contains(location.to_f32()));
}
input::pointer::Phase::Up => {
if self.active {
context.queue_message(make_message(ButtonMessages::Pressed(
Time::get_monotonic(),
)));
}
self.tracking_pointer = None;
self.set_active(scene, false);
}
input::pointer::Phase::Remove => {
self.set_active(scene, false);
self.tracking_pointer = None;
}
input::pointer::Phase::Cancel => {
self.set_active(scene, false);
self.tracking_pointer = None;
}
_ => (),
}
}
}
}
}
#[allow(unused)]
struct SceneDetails {
button: Button,
indicator: FacetId,
scene: Scene,
}
struct ButtonViewAssistant {
app_sender: AppSender,
view_key: ViewKey,
focused: bool,
bg_color: Color,
red_light: bool,
original_indicator_size: Size,
indicator_size: Size,
animation_start: Instant,
main_alignment_index: usize,
cross_alignment_index: usize,
column_main_alignment_index: usize,
scene_details: Option<SceneDetails>,
}
const BUTTON_LABEL: &'static str = "Depress Me";
const CROSS_AXIS_ALIGNMENTS: &'static [CrossAxisAlignment] =
&[CrossAxisAlignment::Start, CrossAxisAlignment::Center, CrossAxisAlignment::End];
const MAIN_AXIS_ALIGNMENTS: &'static [MainAxisAlignment] = &[
MainAxisAlignment::Start,
MainAxisAlignment::Center,
MainAxisAlignment::End,
MainAxisAlignment::SpaceBetween,
MainAxisAlignment::SpaceAround,
MainAxisAlignment::SpaceEvenly,
];
impl ButtonViewAssistant {
fn new(app_sender: AppSender, view_key: ViewKey) -> Result<ButtonViewAssistant, Error> {
let bg_color = Color::from_hash_code("#EBD5B3")?;
let mut b = ButtonViewAssistant {
app_sender,
view_key,
focused: false,
bg_color,
red_light: false,
original_indicator_size: Size::default(),
indicator_size: Size::default(),
animation_start: Instant::now(),
main_alignment_index: 5,
cross_alignment_index: 1,
column_main_alignment_index: 5,
scene_details: None,
};
b.schedule_resize_timer();
Ok(b)
}
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 padding = (min_dimension / 20.0).ceil().max(8.0);
let mut builder = SceneBuilder::new().background_color(self.bg_color);
let mut button = None;
let mut indicator_id = None;
let mut indicator_size_opt = None;
builder
.group()
.column()
.max_size()
.main_align(MAIN_AXIS_ALIGNMENTS[self.column_main_alignment_index])
.contents(|builder| {
let indicator_len = size.height.min(size.width) / 8.0;
let indicator_size = size2(indicator_len * 1.25, indicator_len);
indicator_size_opt = Some(indicator_size);
let indicator_side_size = size2(indicator_len / 2.0, indicator_len * 1.5);
builder.start_group(
"indicator_row",
Flex::with_options_ptr(FlexOptions::row(
MainAxisSize::Max,
MAIN_AXIS_ALIGNMENTS[self.main_alignment_index],
CROSS_AXIS_ALIGNMENTS[self.cross_alignment_index],
)),
);
builder.rectangle(indicator_side_size, Color::blue());
indicator_id = Some(builder.rectangle(
indicator_size,
if self.red_light { Color::red() } else { Color::green() },
));
builder.rectangle(indicator_side_size, Color::blue());
builder.end_group();
button = Some(
Button::new(BUTTON_LABEL, font_size, padding, builder).expect("button"),
);
});
let mut button = button.expect("button");
let mut scene = builder.build();
self.indicator_size = indicator_size_opt.expect("indicator_size_opt");
self.original_indicator_size = self.indicator_size;
button.set_focused(&mut scene, self.focused);
self.animation_start = Instant::now();
self.scene_details = Some(SceneDetails {
scene,
indicator: indicator_id.expect("indicator_id"),
button,
});
}
}
fn schedule_resize_timer(&mut self) {
// use a thread to drive the button resize timer as a way to demonstrate the cross thread
// sender.
let sender = self
.app_sender
.create_cross_thread_sender::<ButtonMessages>(MessageTarget::View(self.view_key));
thread::spawn(move || loop {
sender.unbounded_send(ButtonMessages::Resize).expect("unbounded_send");
thread::sleep(Duration::from_millis(16));
});
}
fn set_red_light(&mut self, red_light: bool) {
if red_light != self.red_light {
if let Some(scene_details) = self.scene_details.as_mut() {
let color = if red_light { Color::red() } else { Color::green() };
scene_details
.scene
.send_message(&scene_details.indicator, Box::new(SetColorMessage { color }));
}
self.red_light = red_light;
}
}
fn resize_indicator(&mut self, indicator_size: Size) {
if indicator_size != self.indicator_size {
if let Some(scene_details) = self.scene_details.as_mut() {
self.indicator_size = indicator_size;
scene_details.scene.send_message(
&scene_details.indicator,
Box::new(SetSizeMessage { size: self.indicator_size }),
);
}
}
}
fn cycle_main_alignment(&mut self) {
self.main_alignment_index = (self.main_alignment_index + 1) % MAIN_AXIS_ALIGNMENTS.len();
self.scene_details = None;
}
fn cycle_cross_alignment(&mut self) {
self.cross_alignment_index = (self.cross_alignment_index + 1) % CROSS_AXIS_ALIGNMENTS.len();
self.scene_details = None;
}
fn cycle_column_main_alignment(&mut self) {
self.column_main_alignment_index =
(self.column_main_alignment_index + 1) % MAIN_AXIS_ALIGNMENTS.len();
self.scene_details = None;
}
fn handle_button_message(&mut self, button_message: &ButtonMessages) {
match button_message {
ButtonMessages::Pressed(value) => {
println!("value = {:#?}", value);
self.set_red_light(!self.red_light);
}
ButtonMessages::Resize => {
let t = Instant::now().duration_since(self.animation_start).as_millis() % 3000;
let f = t as f32 / 3000.0;
let angle = f * PI * 2.0;
let ratio = angle.sin();
self.resize_indicator(size2(
self.original_indicator_size.width + ratio * 80.0,
self.indicator_size.height,
));
self.app_sender.request_render(self.view_key);
}
}
}
}
impl ViewAssistant for ButtonViewAssistant {
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, target_size: Size) -> Option<&mut Scene> {
self.ensure_scene_built(target_size);
Some(&mut self.scene_details.as_mut().unwrap().scene)
}
derive_handle_message!(ButtonMessages => handle_button_message);
fn handle_pointer_event(
&mut self,
context: &mut ViewAssistantContext,
_event: &input::Event,
pointer_event: &input::pointer::Event,
) -> Result<(), Error> {
if let Some(scene_details) = self.scene_details.as_mut() {
scene_details.button.handle_pointer_event(
&mut scene_details.scene,
context,
&pointer_event,
);
context.request_render();
}
Ok(())
}
fn handle_focus_event(
&mut self,
context: &mut ViewAssistantContext,
focused: bool,
) -> Result<(), Error> {
self.focused = focused;
if let Some(scene_details) = self.scene_details.as_mut() {
scene_details.button.set_focused(&mut scene_details.scene, focused);
}
context.request_render();
Ok(())
}
fn handle_keyboard_event(
&mut self,
_context: &mut ViewAssistantContext,
_event: &input::Event,
keyboard_event: &input::keyboard::Event,
) -> Result<(), Error> {
const C: u32 = 99;
const M: u32 = 109;
const R: u32 = 114;
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 {
C => self.cycle_cross_alignment(),
M => self.cycle_main_alignment(),
R => self.cycle_column_main_alignment(),
_ => println!("code_point = {}", code_point),
}
}
}
Ok(())
}
fn ownership_changed(&mut self, owned: bool) -> Result<(), Error> {
println!("ownership_changed {}", owned);
Ok(())
}
}
fn main() -> Result<(), Error> {
fuchsia_trace_provider::trace_provider_create_with_fdio();
App::run(make_app_assistant::<ButtonAppAssistant>())
}