canary-rs/scripts/sao-ui/src/widgets/button.rs

269 lines
7.2 KiB
Rust

// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: AGPL-3.0-or-later
use super::prelude::*;
use text::{HorizontalAlignment, Icon, Label, LabelText};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ButtonState {
Idle,
Clicking,
Clicked,
Releasing,
}
#[derive(Clone, Debug)]
pub struct RoundButtonStyle {
pub radius: f32,
pub spacing: f32,
pub thickness: f32,
pub body_color: Color,
pub ring_color: Color,
pub icon_color: Color,
}
pub struct RoundButton {
style: RoundButtonStyle,
shrink_anim: Animation<EaseOutQuint>,
was_clicked: bool,
state: ButtonState,
icon: Option<Icon>,
}
impl RoundButton {
pub fn new(style: RoundButtonStyle, icon: Option<LabelText>) -> Self {
let icon = icon.map(|text| {
let scale = style.radius * 1.5;
let center = Vec2::ZERO;
let color = style.icon_color;
Icon::new(text, scale, color, center)
});
Self {
style,
shrink_anim: Animation::new(EaseOutQuint, 0.1, 1.0, 0.0),
was_clicked: false,
state: ButtonState::Idle,
icon,
}
}
pub fn set_text(&mut self, text: &str) {
if let Some(icon) = self.icon.as_mut() {
icon.set_text(text);
}
}
}
impl Button for RoundButton {
fn was_clicked(&self) -> bool {
self.was_clicked
}
}
impl Widget for RoundButton {
fn update(&mut self, dt: f32) {
self.shrink_anim.update(dt);
self.was_clicked = false;
}
fn draw(&mut self, ctx: &DrawContext) {
let RoundButtonStyle {
body_color,
ring_color,
thickness,
radius,
spacing,
..
} = self.style;
let center = Vec2::ZERO;
let spacing = self.shrink_anim.get() * spacing;
ctx.draw_circle(center, radius, body_color);
ctx.draw_ring(center, radius + spacing, thickness, ring_color);
self.icon.draw(ctx);
}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
match kind {
CursorEventKind::Select => {
if at.length() < self.style.radius {
self.shrink_anim.ease_in();
self.state = ButtonState::Clicked;
}
}
CursorEventKind::Deselect => {
if self.state == ButtonState::Clicked {
self.shrink_anim.ease_out();
self.was_clicked = true;
self.state = ButtonState::Idle;
}
}
_ => {}
}
}
}
#[derive(Clone, Debug)]
pub struct RectButtonStyle {
pub rounded_corners: CornerFlags,
pub radius: f32,
pub label_scale_factor: f32,
pub label_baseline: f32,
pub icon_scale_factor: f32,
pub icon_margin_factor: f32,
pub inactive_color: Color,
pub hover_color: Color,
pub selected_color: Color,
pub icon_color: Color,
pub label_color: Color,
}
impl Default for RectButtonStyle {
fn default() -> Self {
Self {
rounded_corners: CornerFlags::empty(),
radius: 0.0,
label_scale_factor: 0.65,
label_baseline: 0.25,
icon_scale_factor: 0.8,
icon_margin_factor: 1.1,
inactive_color: THEME.palette.base,
hover_color: THEME.palette.base_hover,
selected_color: THEME.palette.base_active,
icon_color: THEME.palette.black,
label_color: THEME.palette.text,
}
}
}
pub struct RectButton {
pub style: RectButtonStyle,
pub rect: Rect,
pub was_clicked: bool,
pub is_selected: bool,
pub is_hovering: bool,
pub color_anim: Animation<EaseInQuad, Color>,
pub label: Option<Label>,
pub icon: Option<Icon>,
}
impl Button for RectButton {
fn was_clicked(&self) -> bool {
self.was_clicked
}
}
impl RectButton {
pub fn new(
style: RectButtonStyle,
rect: Rect,
label: Option<LabelText>,
icon: Option<LabelText>,
) -> Self {
let mut label_left = rect.tl.x;
let mut alignment = HorizontalAlignment::Center;
let icon = icon.map(|text| {
let margin = rect.height() * style.icon_margin_factor;
label_left += margin;
alignment = HorizontalAlignment::Left;
let scale = rect.height() * style.icon_scale_factor;
let color = style.icon_color;
let cx = rect.tl.x + margin / 2.0;
let cy = rect.tl.y + rect.height() / 2.0;
let center = Vec2::new(cx, cy);
Icon::new(text, scale, color, center)
});
let label = label.map(|text| {
let scale = rect.height() * style.label_scale_factor;
let left = label_left;
let right = rect.br.x;
let baseline = rect.tl.y;
let baseline = (rect.height() * (1.0 - style.label_baseline)) + baseline;
let color = style.label_color;
Label::new(text, alignment, scale, color, left, right, baseline)
});
let color_anim =
Animation::new(EaseInQuad, 0.05, style.inactive_color, style.inactive_color);
Self {
style,
rect,
was_clicked: false,
is_selected: false,
is_hovering: false,
color_anim,
label,
icon,
}
}
}
impl Widget for RectButton {
fn update(&mut self, dt: f32) {
self.was_clicked = false;
self.color_anim.update(dt);
}
fn draw(&mut self, ctx: &DrawContext) {
ctx.draw_partially_rounded_rect(
self.style.rounded_corners,
self.rect,
self.style.radius,
self.color_anim.get(),
);
self.label.draw(ctx);
self.icon.draw(ctx);
}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
let is_on = self.rect.contains_point(at);
let style = &self.style;
match kind {
CursorEventKind::Hover | CursorEventKind::Drag => {
if is_on {
if !self.is_hovering && !self.is_selected {
self.color_anim.ease_to(style.hover_color);
}
self.is_hovering = true;
} else {
if self.is_hovering && !self.is_selected {
self.color_anim.ease_to(style.inactive_color);
}
self.is_hovering = false;
}
}
CursorEventKind::Select => {
if is_on {
self.is_selected = true;
self.color_anim.ease_to(style.selected_color);
}
}
CursorEventKind::Deselect => {
if self.is_selected {
self.was_clicked = true;
self.is_selected = false;
if self.is_hovering {
self.color_anim.ease_to(style.hover_color);
} else {
self.color_anim.ease_to(style.inactive_color);
}
}
}
}
}
}