blob: 1ee7d77af107120a4a33775b247800e98e3c368f [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 crate::font;
use anyhow::Error;
use carnelian::{
color::Color,
drawing::{measure_text_width, FontFace},
input::{self},
make_message,
scene::{
facets::{
FacetId, SetColorMessage, TextFacetOptions, TextHorizontalAlignment,
TextVerticalAlignment,
},
layout::{Alignment, Stack, StackOptions},
scene::{Scene, SceneBuilder},
},
Coord, Point, Size, ViewAssistantContext,
};
use euclid::{size2, Size2D, UnknownUnit};
use fuchsia_zircon::Time;
/// enum that defines all messages sent with `App::queue_message` that
/// the button view assistant will understand and process.
pub enum ButtonMessages {
Pressed(Time, String),
}
#[allow(unused)]
pub 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: Option<bool>,
label_text: String,
face: FontFace,
background: FacetId,
label: FacetId,
bg_size: Size2D<f32, UnknownUnit>,
}
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 = font::load_default_font_face()?;
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::Center,
vertical_alignment: TextVerticalAlignment::Center,
..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 corner: Coord = Coord::from(5.0);
let background = builder.rounded_rectangle(bg_size, corner, 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: None,
label_text: text.to_string(),
face,
background,
label,
bg_size,
};
Ok(button)
}
#[allow(unused)]
pub fn get_size(&self, scene: &mut Scene) -> Size {
scene.get_facet_size(&self.background)
}
fn update_button_bg_color(&mut self, scene: &mut Scene) {
let (label_color, color) = if let Some(true) = 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 self.focused.is_none() || focused != self.focused.unwrap() {
self.focused = Some(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.is_none() || !self.focused.unwrap() {
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 {
if let Some(tracking_pointer) = self.tracking_pointer.as_ref() {
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.label_text.clone(),
)));
}
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;
}
_ => (),
}
}
}
}
}
}