use {
super::{image::Image, Widget, WidgetBaseOf, WidgetExt},
crate::{
callback::{Callback, CallbackVec},
event::{
AccessibilityActionEvent, FocusReason, KeyboardInputEvent, MouseInputEvent,
MouseMoveEvent, StyleChangeEvent,
},
impl_widget_base,
shared_window::SetFocusRequest,
style::{
common::ComputedElementStyle,
css::{convert_content_url, convert_zoom, Element, PseudoClass},
get_style, Style,
},
system::{add_interval, add_timer, send_window_request, with_system},
text_editor::Text,
timer::TimerId,
},
accesskit::{Action, Role},
anyhow::Result,
cosmic_text::Attrs,
log::warn,
std::{fmt::Display, rc::Rc},
tiny_skia::Pixmap,
widgem_macros::impl_with,
winit::{
event::MouseButton,
keyboard::{Key, KeyCode, NamedKey, PhysicalKey},
},
};
pub struct Button {
auto_repeat: bool,
is_mouse_leave_sensitive: bool,
trigger_on_press: bool,
on_triggered: CallbackVec<()>,
is_pressed: bool,
was_pressed_but_moved_out: bool,
auto_repeat_delay_timer: Option<TimerId>,
auto_repeat_interval: Option<TimerId>,
base: WidgetBaseOf<Self>,
style: Rc<ComputedButtonStyle>,
}
#[impl_with]
impl Button {
#[allow(dead_code)]
fn image_widget(&self) -> &Image {
self.base.get_child::<Image>(0).unwrap()
}
fn image_widget_mut(&mut self) -> &mut Image {
self.base.get_child_mut::<Image>(0).unwrap()
}
fn text_widget(&self) -> &Text {
self.base.get_child::<Text>(1).unwrap()
}
fn text_widget_mut(&mut self) -> &mut Text {
self.base.get_child_mut::<Text>(1).unwrap()
}
pub fn set_text(&mut self, text: impl Display) -> &mut Self {
self.text_widget_mut().set_text(text, Attrs::new());
self.base.size_hint_changed();
self.base.update();
self
}
pub fn set_text_visible(&mut self, value: bool) -> &mut Self {
self.text_widget_mut().set_visible(value);
self.base.size_hint_changed();
self.base.update();
self
}
pub fn set_auto_repeat(&mut self, value: bool) -> &mut Self {
self.auto_repeat = value;
self
}
pub fn set_mouse_leave_sensitive(&mut self, value: bool) -> &mut Self {
self.is_mouse_leave_sensitive = value;
self
}
pub fn set_trigger_on_press(&mut self, value: bool) -> &mut Self {
self.trigger_on_press = value;
self
}
pub fn on_triggered(&mut self, callback: Callback<()>) -> &mut Self {
self.on_triggered.push(callback);
self
}
pub fn trigger(&mut self) {
self.on_triggered.invoke(());
}
fn set_pressed(&mut self, value: bool, suppress_trigger: bool) {
if self.is_pressed == value {
return;
}
self.is_pressed = value;
self.set_pseudo_class(PseudoClass::Active, self.is_pressed);
if value {
if self.trigger_on_press && !suppress_trigger {
self.trigger();
}
let delay = with_system(|s| s.config.auto_repeat_delay);
if self.auto_repeat {
let id = add_timer(
delay,
self.callback(|this, _| {
this.start_auto_repeat();
Ok(())
}),
);
self.auto_repeat_delay_timer = Some(id);
}
} else {
if let Some(id) = self.auto_repeat_delay_timer.take() {
id.cancel();
}
if let Some(id) = self.auto_repeat_interval.take() {
id.cancel();
}
if !self.trigger_on_press && !suppress_trigger {
self.trigger();
}
}
}
fn start_auto_repeat(&mut self) {
if !self.base.is_enabled() {
return;
}
self.trigger();
let interval = with_system(|s| s.config.auto_repeat_interval);
let id = add_interval(
interval,
self.callback(|this, _| {
if this.base.is_enabled() {
this.trigger();
}
Ok(())
}),
);
self.auto_repeat_interval = Some(id);
}
fn refresh_style(&mut self) {
self.style = get_style(self.base.style_element(), self.base.scale());
let icon = self.style.icon.clone();
self.image_widget_mut().set_visible(icon.is_some());
self.image_widget_mut().set_prescaled(true);
self.image_widget_mut().set_pixmap(icon);
}
}
impl Widget for Button {
impl_widget_base!();
fn new(mut base: WidgetBaseOf<Self>) -> Self {
base.set_supports_focus(true);
base.add_child::<Image>()
.set_column(0)
.set_row(0)
.set_visible(false);
let id = base.id().raw();
let element = base.style_element().clone();
base.add_child::<Text>()
.set_column(1)
.set_row(0)
.set_host_id(id)
.set_host_style_element(element);
let mut b = Self {
style: get_style(base.style_element(), base.scale()),
auto_repeat: false,
is_mouse_leave_sensitive: true,
trigger_on_press: false,
on_triggered: CallbackVec::new(),
is_pressed: false,
was_pressed_but_moved_out: false,
base,
auto_repeat_delay_timer: None,
auto_repeat_interval: None,
};
b.refresh_style();
b
}
fn handle_mouse_move(&mut self, event: MouseMoveEvent) -> Result<bool> {
let rect = self.base.rect_in_self_or_err()?;
if rect.contains(event.pos) {
if self.was_pressed_but_moved_out {
self.was_pressed_but_moved_out = true;
self.set_pressed(true, true);
self.base.update();
}
} else {
if self.is_pressed && self.is_mouse_leave_sensitive {
self.was_pressed_but_moved_out = true;
self.set_pressed(false, true);
self.base.update();
}
}
Ok(true)
}
fn handle_mouse_input(&mut self, event: MouseInputEvent) -> Result<bool> {
if !self.base.is_enabled() {
return Ok(true);
}
if event.button == MouseButton::Left {
if event.state.is_pressed() {
self.set_pressed(true, false);
if !self.base.is_focused() {
let window = self.base.window_or_err()?;
if self.base.is_focusable() {
send_window_request(
window.id(),
SetFocusRequest {
widget_id: self.base.id().into(),
reason: FocusReason::Mouse,
},
);
}
}
} else {
self.was_pressed_but_moved_out = false;
self.set_pressed(false, false);
}
self.base.update();
}
Ok(true)
}
fn handle_keyboard_input(&mut self, event: KeyboardInputEvent) -> Result<bool> {
if event.info.physical_key == PhysicalKey::Code(KeyCode::Space)
|| event.info.logical_key == Key::Named(NamedKey::Space)
{
self.set_pressed(event.info.state.is_pressed(), false);
return Ok(true);
}
if event.info.physical_key == PhysicalKey::Code(KeyCode::Enter)
|| event.info.physical_key == PhysicalKey::Code(KeyCode::NumpadEnter)
|| event.info.logical_key == Key::Named(NamedKey::Enter)
{
self.trigger();
return Ok(true);
}
Ok(false)
}
fn handle_accessibility_action(&mut self, event: AccessibilityActionEvent) -> Result<()> {
match event.action {
Action::Click => self.trigger(),
Action::Focus => {
send_window_request(
self.base.window_or_err()?.id(),
SetFocusRequest {
widget_id: self.base.id().into(),
reason: FocusReason::Mouse,
},
);
}
_ => {}
}
Ok(())
}
fn handle_accessibility_node_request(&mut self) -> Result<Option<accesskit::Node>> {
let mut node = accesskit::Node::new(Role::Button);
node.set_label(self.text_widget().text().as_str());
node.add_action(Action::Click);
node.add_action(Action::Focus);
Ok(Some(node))
}
fn handle_style_change(&mut self, _event: StyleChangeEvent) -> Result<()> {
let element = self.base.style_element().clone();
self.text_widget_mut().set_host_style_element(element);
self.refresh_style();
self.base.size_hint_changed();
self.base.update();
Ok(())
}
}
#[derive(Debug, Clone, Default)]
struct ComputedButtonStyle {
pub icon: Option<Rc<Pixmap>>,
}
impl ComputedElementStyle for ComputedButtonStyle {
fn new(style: &Style, element: &Element, scale: f32) -> ComputedButtonStyle {
let properties = style.find_rules(|s| element.matches(s));
let scale = scale * convert_zoom(&properties);
let mut icon = None;
if let Some(url) = convert_content_url(&properties) {
match style.load_pixmap(&url, scale) {
Ok(pixmap) => icon = Some(pixmap),
Err(err) => warn!("failed to load icon: {err:?}"),
}
}
Self { icon }
}
}