use std::any::Any;
use crate::{
assets::{AssetOrPath, LoadedAsset},
canvas::Canvas,
font::Font,
sprite::Sprite,
};
use super::{Widget, WidgetRef};
use taffy::prelude::Node;
use vek::{Extent2, Rect, Vec2};
use winit_input_helper::WinitInputHelper;
#[derive(Debug)]
pub struct Button {
pub offset: Vec2<f64>,
pub size: Extent2<f64>,
pub click_region: Option<Rect<f64, f64>>,
pub label: Option<String>,
pub state: State,
pub node: Node,
pub assets: ButtonAssetPaths,
}
impl Button {
pub fn update(&mut self, input: &WinitInputHelper, mouse_pos: Option<Vec2<usize>>) -> bool {
let mut rect = Rect::new(self.offset.x, self.offset.y, self.size.w, self.size.h);
if let Some(mut click_region) = self.click_region {
click_region.x += self.offset.x;
click_region.y += self.offset.y;
rect = rect.union(click_region);
}
match self.state {
State::Normal => {
if let Some(mouse_pos) = mouse_pos {
if !input.mouse_held(0) && rect.contains_point(mouse_pos.as_()) {
self.state = State::Hover;
}
}
false
}
State::Hover => {
if let Some(mouse_pos) = mouse_pos {
if !rect.contains_point(mouse_pos.as_()) {
self.state = State::Normal;
} else if input.mouse_pressed(0) {
self.state = State::Down;
}
}
false
}
State::Down => {
if input.mouse_released(0) {
self.state = State::Normal;
true
} else {
false
}
}
}
}
pub fn render(&self, canvas: &mut Canvas) {
let button: LoadedAsset<Sprite> = match self.state {
State::Normal => &self.assets.normal,
State::Hover => &self.assets.hover,
State::Down => &self.assets.down,
}
.into();
button.render_area(self.offset, self.size.as_(), canvas);
if let Some(label) = &self.label {
let font: LoadedAsset<Font> = (&self.assets.font).into();
font.render_centered(
label,
self.offset + (self.size.w / 2.0, self.size.h / 2.0),
canvas,
);
}
}
}
impl Widget for Button {
fn update_layout(&mut self, location: Vec2<f64>, size: Extent2<f64>) {
self.offset = location;
self.size = size;
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Default for Button {
fn default() -> Self {
Self {
offset: Vec2::zero(),
size: Extent2::zero(),
label: None,
state: State::default(),
click_region: None,
node: Node::default(),
assets: ButtonAssetPaths::default(),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum State {
#[default]
Normal,
Hover,
Down,
}
#[derive(Debug)]
pub struct ButtonAssetPaths {
pub normal: AssetOrPath<Sprite>,
pub hover: AssetOrPath<Sprite>,
pub down: AssetOrPath<Sprite>,
pub font: AssetOrPath<Font>,
}
impl Default for ButtonAssetPaths {
fn default() -> Self {
Self {
normal: "button-normal".into(),
hover: "button-hover".into(),
down: "button-down".into(),
#[cfg(feature = "default-font")]
font: AssetOrPath::Owned(Font::default()),
#[cfg(not(feature = "default-font"))]
font: "font".into(),
}
}
}
#[derive(Copy, Clone)]
pub struct ButtonRef(Node);
impl WidgetRef for ButtonRef {
type Widget = Button;
}
impl From<ButtonRef> for Node {
fn from(val: ButtonRef) -> Self {
val.0
}
}
impl From<Node> for ButtonRef {
fn from(value: Node) -> Self {
Self(value)
}
}