use densa_core::{Color, Size};
use densa_draw::Canvas;
use densa_ui::{
AccessAction, AccessNode, AccessRequest, ActionKind, Constraints, Cursor, Event, EventCx,
Handled, Key, LayoutCx, NamedKey, PaintCx, PointerButton, PointerKind, Role, Theme, Widget,
};
use crate::paint::{centered, outline};
use crate::text::Text;
const PADDING_X: f32 = 12.0;
const PADDING_Y: f32 = 5.0;
#[derive(Debug)]
pub struct Button {
text: Text,
primary: bool,
}
impl Button {
pub fn new(label: impl Into<String>) -> Self {
Self {
text: Text::new(label),
primary: false,
}
}
#[must_use]
pub fn primary(mut self) -> Self {
self.primary = true;
self
}
pub fn label(&self) -> &str {
self.text.as_str()
}
pub fn set_label(&mut self, cx: &mut EventCx<'_>, label: impl Into<String>) {
self.text.set(label);
cx.request_layout();
}
fn text_color(&self, theme: &Theme) -> Color {
if self.primary {
theme.on_accent
} else {
theme.text
}
}
fn fill(&self, cx: &PaintCx<'_>) -> Color {
let theme = cx.theme();
match (self.primary, cx.is_pressed(), cx.is_hovered()) {
(true, true, _) => theme.accent.with_alpha(0.75),
(true, false, true) => theme.accent.with_alpha(0.88),
(true, false, false) => theme.accent,
(false, true, _) => theme.pressed,
(false, false, true) => theme.hover,
(false, false, false) => theme.raised,
}
}
}
impl Widget for Button {
fn measure(&mut self, cx: &mut LayoutCx<'_>, constraints: Constraints) -> Size {
let color = self.text_color(cx.theme());
let text = self.text.measure(cx, constraints, color);
Size::new(
text.width + 2.0 * cx.px(PADDING_X),
text.height + 2.0 * cx.px(PADDING_Y),
)
}
fn layout(&mut self, cx: &mut LayoutCx<'_>, size: Size) {
let color = self.text_color(cx.theme());
self.text.fit(cx, size.width, color);
}
fn paint(&mut self, cx: &PaintCx<'_>, canvas: &mut Canvas<'_>) {
let rect = cx.rect();
canvas.fill_rect(rect, self.fill(cx));
if !self.primary {
outline(canvas, rect, cx.px(1.0).round(), cx.theme().border);
}
if cx.is_focused() {
outline(canvas, rect, cx.px(1.5).round(), cx.theme().focus);
}
let size = self.text.size();
self.text
.paint(canvas, centered(cx.content_rect(), size.width, size.height));
}
fn event(&mut self, cx: &mut EventCx<'_>, event: &Event<'_>) -> Handled {
match event {
Event::Pointer(pointer) => match pointer.kind {
PointerKind::Enter => {
cx.set_cursor(Some(Cursor::Pointer));
cx.request_paint();
Handled::Yes
}
PointerKind::Leave | PointerKind::Down(PointerButton::Primary) => {
cx.request_paint();
Handled::Yes
}
PointerKind::Up(PointerButton::Primary) => {
if cx.is_pressed() && cx.rect().contains(pointer.position) {
cx.emit(ActionKind::Clicked);
}
cx.request_paint();
Handled::Yes
}
_ => Handled::No,
},
Event::Key(key)
if key.pressed
&& !key.repeat
&& matches!(
key.key,
Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space)
) =>
{
cx.emit(ActionKind::Clicked);
Handled::Yes
}
Event::FocusGained | Event::FocusLost => {
cx.request_paint();
Handled::Yes
}
_ => Handled::No,
}
}
fn accepts_focus(&self) -> bool {
true
}
fn accessibility(&self, _: &PaintCx<'_>, node: &mut AccessNode) {
node.set_role(Role::Button);
node.set_label(self.text.as_str());
node.add_action(AccessAction::Click);
}
fn access_action(&mut self, cx: &mut EventCx<'_>, request: &AccessRequest<'_>) -> Handled {
if request.action != AccessAction::Click {
return Handled::No;
}
cx.emit(ActionKind::Clicked);
Handled::Yes
}
}