blob: d944b8456ac64a5fb59cc26fe5c17b047276c49d [file] [edit]
// 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::constants::constants::*;
use crate::font;
use carnelian::color::Color;
use carnelian::drawing::measure_text_width;
use carnelian::input::{self};
use carnelian::scene::facets::{
Facet, FacetId, RiveFacet, SetColorMessage, TextFacetOptions, TextHorizontalAlignment,
TextVerticalAlignment,
};
use carnelian::scene::layout::{
Alignment, CrossAxisAlignment, Flex, FlexOptions, MainAxisAlignment, MainAxisSize, Stack,
StackOptions,
};
use carnelian::scene::scene::{Scene, SceneBuilder};
use carnelian::{make_message, Coord, Point, Size, ViewAssistantContext};
use derivative::Derivative;
use euclid::{size2, Size2D, UnknownUnit};
use std::ops::Add;
use zx::MonotonicInstant;
/// enum that defines all messages sent with `App::queue_message` that
/// the button view assistant will understand and process.
pub enum ButtonMessages {
Pressed(MonotonicInstant, String),
}
// These values and the text and padding sizes are used to calculate the corners.
#[derive(Debug, Clone, Copy)]
#[allow(unused)]
pub enum ButtonShape {
Square = 0,
Rounded = 15,
Oval = 50,
}
impl Default for ButtonShape {
fn default() -> Self {
ButtonShape::Square
}
}
#[derive(Debug, Derivative)]
#[derivative(Default)]
pub struct ButtonOptions {
#[derivative(Default(value = "SMALL_BUTTON_FONT_SIZE"))]
pub font_size: f32,
#[derivative(Default(value = "BUTTON_BORDER"))]
pub padding: f32,
// Whether the background and foreground colours should be swapped
#[derivative(Default(value = "false"))]
pub bg_fg_swapped: bool,
#[derivative(Default(value = "BUTTON_COLOR"))]
pub bg_color: Color,
#[derivative(Default(value = "ACTIVE_BUTTON_COLOR"))]
pub bg_color_active: Color,
#[derivative(Default(value = "ACTIVE_BUTTON_COLOR_SWAPPED"))]
pub bg_color_active_if_bg_fg_swapped: Color,
#[derivative(Default(value = "BUTTON_DISABLED_COLOR"))]
pub bg_color_disabled: Color,
#[derivative(Default(value = "BUTTON_TEXT_COLOR"))]
pub fg_color: Color,
#[derivative(Default(value = "BUTTON_TEXT_COLOR"))]
pub fg_color_disabled: Color,
#[derivative(Default(value = "BUTTON_BORDER_COLOR"))]
pub border_color: Color,
#[derivative(Default(value = "false"))]
pub draw_border: bool,
#[derivative(Default(value = "ButtonShape::Square"))]
pub shape: ButtonShape,
#[derivative(Default(value = "None"))]
pub bg_size: Option<Size2D<f32, UnknownUnit>>,
#[derivative(Default(value = "None"))]
pub text_alignment: Option<Alignment>,
#[derivative(Default(value = "false"))]
pub hide_text: bool,
}
pub struct Button {
tracking_pointer: Option<input::pointer::PointerId>,
active: bool,
focused: bool,
label_text: String,
background: FacetId,
label: FacetId,
button_options: ButtonOptions,
}
impl Button {
pub fn new(
text: &str,
icon: Option<RiveFacet>,
mut button_options: ButtonOptions,
builder: &mut SceneBuilder,
) -> Button {
if button_options.bg_fg_swapped {
std::mem::swap(&mut button_options.fg_color, &mut button_options.bg_color);
button_options.bg_color_active = button_options.bg_color_active_if_bg_fg_swapped;
}
let mut alignment = match button_options.bg_size {
Some(_) => Alignment::center_left(),
_ => Alignment::center(),
};
alignment = button_options.text_alignment.unwrap_or(alignment);
let options = StackOptions { alignment, ..StackOptions::default() };
builder.start_group("button", Stack::with_options_ptr(options));
let font_size = button_options.font_size;
let (label_text, mut label_width) = if button_options.hide_text || font_size == 0.0 {
("", 0.0)
} else {
(text, measure_text_width(font::get_default_font_face(), font_size, text))
};
builder.start_group(
&("Button icon text row"),
Flex::with_options_ptr(FlexOptions::row(
MainAxisSize::Min,
MainAxisAlignment::Start,
CrossAxisAlignment::Start,
)),
);
if let Some(icon) = icon {
label_width += icon.calculate_size(size2(0.0, 0.0)).width;
builder.facet(Box::new(icon));
}
builder.space(size2(5.0, 1.0));
let label = builder.text(
font::get_default_font_face().clone(),
label_text,
font_size,
Point::zero(),
TextFacetOptions {
color: Color::blue(),
horizontal_alignment: TextHorizontalAlignment::Right,
vertical_alignment: TextVerticalAlignment::Center,
..TextFacetOptions::default()
},
);
builder.end_group(); // Button icon text row
let padding = button_options.padding;
let bg_size = button_options
.bg_size
.unwrap_or_else(|| size2(label_width + padding * 2.0, font_size + padding * 2.0));
let corner: Coord =
Coord::from(bg_size.height * (button_options.shape.clone() as i32 as f32) / 100.0);
let background =
builder.rounded_rectangle(bg_size.clone(), corner, button_options.bg_color.clone());
if button_options.draw_border {
let bg_size = bg_size.add(size2(2.0, 2.0));
builder.rounded_rectangle(bg_size, corner, button_options.border_color);
}
builder.end_group(); // button
Button {
button_options,
tracking_pointer: None,
active: false,
focused: false,
label_text: text.to_string(),
background,
label,
}
}
#[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 self.focused {
if self.active {
(self.button_options.fg_color, self.button_options.bg_color_active)
} else {
(self.button_options.fg_color, self.button_options.bg_color)
}
} else {
(self.button_options.fg_color_disabled, self.button_options.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 {
#[cfg(feature = "debug_logging")]
println!("====== Button {} is now active", self.label_text);
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 {
#[cfg(feature = "debug_logging")]
println!("====== Button {} pressed", self.label_text);
context.queue_message(make_message(ButtonMessages::Pressed(
MonotonicInstant::get(),
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;
}
_ => (),
}
}
}
}
}
}
pub trait SceneBuilderButtonExt {
fn button(
&mut self,
text: &str,
icon: Option<RiveFacet>,
button_options: ButtonOptions,
) -> Button;
}
impl SceneBuilderButtonExt for SceneBuilder {
fn button(
&mut self,
text: &str,
icon: Option<RiveFacet>,
button_options: ButtonOptions,
) -> Button {
Button::new(text, icon, button_options, self)
}
}