use std::any::Any;
use std::sync::RwLock;
use super::{Element, ViewLimits};
use super::context::{BasicContext, Context};
use crate::support::point::Point;
use crate::support::rect::Rect;
use crate::support::color::Color;
use crate::support::canvas::CornerRadii;
use crate::support::theme::get_theme;
use crate::view::{MouseButton, CursorTracking};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ButtonState {
#[default]
Normal,
Hover,
Pressed,
Disabled,
}
pub type ClickCallback = Box<dyn Fn() + Send + Sync>;
pub struct BasicButton {
label: String,
state: RwLock<ButtonState>,
body_color: Color,
text_color: Color,
corner_radius: f32,
enabled: bool,
on_click: Option<ClickCallback>,
value: bool, }
impl BasicButton {
pub fn new(label: impl Into<String>) -> Self {
let theme = get_theme();
Self {
label: label.into(),
state: RwLock::new(ButtonState::Normal),
body_color: theme.default_button_color,
text_color: theme.label_font_color,
corner_radius: theme.button_corner_radius,
enabled: true,
on_click: None,
value: false,
}
}
pub fn on_click<F: Fn() + Send + Sync + 'static>(mut self, callback: F) -> Self {
self.on_click = Some(Box::new(callback));
self
}
pub fn with_body_color(mut self, color: Color) -> Self {
self.body_color = color;
self
}
pub fn with_text_color(mut self, color: Color) -> Self {
self.text_color = color;
self
}
pub fn with_corner_radius(mut self, radius: f32) -> Self {
self.corner_radius = radius;
self
}
pub fn label(&self) -> &str {
&self.label
}
pub fn set_label(&mut self, label: impl Into<String>) {
self.label = label.into();
}
pub fn state(&self) -> ButtonState {
*self.state.read().unwrap()
}
pub fn value(&self) -> bool {
self.value
}
pub fn set_value(&mut self, value: bool) {
self.value = value;
}
fn draw_background(&self, ctx: &Context) {
let state = *self.state.read().unwrap();
let color = match state {
ButtonState::Normal => self.body_color,
ButtonState::Hover => self.body_color.level(1.2),
ButtonState::Pressed => self.body_color.level(0.8),
ButtonState::Disabled => self.body_color.with_alpha(0.5),
};
let mut canvas = ctx.canvas.borrow_mut();
canvas.fill_style(color);
canvas.fill_round_rect(ctx.bounds, self.corner_radius);
}
fn draw_label(&self, ctx: &Context) {
let color = if self.enabled {
self.text_color
} else {
self.text_color.with_alpha(0.5)
};
let theme = get_theme();
let mut canvas = ctx.canvas.borrow_mut();
canvas.fill_style(color);
canvas.font_size(theme.label_font_size);
let text_width = self.label.len() as f32 * theme.label_font_size * 0.6;
let text_height = theme.label_font_size;
let x = ctx.bounds.left + (ctx.bounds.width() - text_width) / 2.0;
let y = ctx.bounds.top + (ctx.bounds.height() - text_height) / 2.0 + text_height * 0.8;
canvas.fill_text(&self.label, Point::new(x, y));
}
}
impl Element for BasicButton {
fn limits(&self, ctx: &BasicContext) -> ViewLimits {
let theme = get_theme();
let text_width = self.label.len() as f32 * theme.label_font_size * 0.6;
let text_height = theme.label_font_size * 1.2;
let margin = &theme.button_margin;
let width = text_width + margin.left + margin.right;
let height = text_height + margin.top + margin.bottom;
ViewLimits::fixed(width, height)
}
fn stretch(&self) -> super::ViewStretch {
super::ViewStretch::new(0.0, 0.0)
}
fn draw(&self, ctx: &Context) {
self.draw_background(ctx);
self.draw_label(ctx);
}
fn hit_test(&self, ctx: &Context, p: Point, _leaf: bool, _control: bool) -> Option<&dyn Element> {
if ctx.bounds.contains(p) && self.enabled {
Some(self)
} else {
None
}
}
fn wants_control(&self) -> bool {
self.enabled
}
fn click(&mut self, ctx: &Context, btn: MouseButton) -> bool {
self.handle_click(ctx, btn)
}
fn handle_click(&self, ctx: &Context, btn: MouseButton) -> bool {
if !self.enabled || btn.button != crate::view::MouseButtonKind::Left {
return false;
}
let mut state = self.state.write().unwrap();
if btn.down {
*state = ButtonState::Pressed;
} else {
if *state == ButtonState::Pressed {
drop(state);
if let Some(ref callback) = self.on_click {
callback();
}
let mut state = self.state.write().unwrap();
*state = if ctx.bounds.contains(btn.pos) {
ButtonState::Hover
} else {
ButtonState::Normal
};
} else {
*state = if ctx.bounds.contains(btn.pos) {
ButtonState::Hover
} else {
ButtonState::Normal
};
}
}
true
}
fn cursor(&mut self, ctx: &Context, p: Point, status: CursorTracking) -> bool {
if !self.enabled {
return false;
}
let mut state = self.state.write().unwrap();
match status {
CursorTracking::Entering | CursorTracking::Hovering => {
if *state != ButtonState::Pressed {
*state = ButtonState::Hover;
}
}
CursorTracking::Leaving => {
if *state != ButtonState::Pressed {
*state = ButtonState::Normal;
}
}
}
true
}
fn enable(&mut self, state: bool) {
self.enabled = state;
let mut btn_state = self.state.write().unwrap();
if !state {
*btn_state = ButtonState::Disabled;
} else if *btn_state == ButtonState::Disabled {
*btn_state = ButtonState::Normal;
}
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub struct ToggleButton {
inner: BasicButton,
active_color: Color,
}
impl ToggleButton {
pub fn new(label: impl Into<String>) -> Self {
let theme = get_theme();
Self {
inner: BasicButton::new(label),
active_color: theme.indicator_bright_color,
}
}
pub fn with_active_color(mut self, color: Color) -> Self {
self.active_color = color;
self
}
pub fn value(&self) -> bool {
self.inner.value
}
pub fn set_value(&mut self, value: bool) {
self.inner.value = value;
}
pub fn toggle(&mut self) {
self.inner.value = !self.inner.value;
}
}
impl Element for ToggleButton {
fn limits(&self, ctx: &BasicContext) -> ViewLimits {
self.inner.limits(ctx)
}
fn draw(&self, ctx: &Context) {
let original_color = self.inner.body_color;
if self.inner.value {
}
self.inner.draw(ctx);
}
fn wants_control(&self) -> bool {
self.inner.wants_control()
}
fn click(&mut self, ctx: &Context, btn: MouseButton) -> bool {
if !self.inner.enabled || btn.button != crate::view::MouseButtonKind::Left {
return false;
}
let mut state = self.inner.state.write().unwrap();
if btn.down {
*state = ButtonState::Pressed;
} else {
let was_pressed = *state == ButtonState::Pressed;
if was_pressed && ctx.bounds.contains(btn.pos) {
drop(state);
self.toggle();
let mut state = self.inner.state.write().unwrap();
*state = ButtonState::Hover;
} else {
*state = if ctx.bounds.contains(btn.pos) {
ButtonState::Hover
} else {
ButtonState::Normal
};
}
}
true
}
fn cursor(&mut self, ctx: &Context, p: Point, status: CursorTracking) -> bool {
self.inner.cursor(ctx, p, status)
}
fn enable(&mut self, state: bool) {
self.inner.enable(state);
}
fn is_enabled(&self) -> bool {
self.inner.is_enabled()
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub fn button(label: impl Into<String>) -> BasicButton {
BasicButton::new(label)
}
pub fn toggle_button(label: impl Into<String>) -> ToggleButton {
ToggleButton::new(label)
}
pub fn draw_button_base(
ctx: &Context,
bounds: Rect,
color: Color,
enabled: bool,
corner_radii: CornerRadii,
) {
let actual_color = if enabled {
color
} else {
color.with_alpha(color.alpha * 0.5)
};
let mut canvas = ctx.canvas.borrow_mut();
canvas.fill_style(actual_color);
canvas.begin_path();
canvas.add_round_rect_varying(bounds, corner_radii);
canvas.fill();
}