use std::cell::RefCell; use std::rc::Rc; use guiduck_scene::geom::{Point, Size}; use taffy::prelude::percent; use super::*; use crate::clipboard::NoClipboard; use crate::event::{Key, KeyInput, Modifiers, PointerButton, PointerInput}; use crate::widget::{Container, WidgetId, WidgetTree}; fn sample_font() -> guiduck_scene::paint::Blob { static FONT: &[u8] = include_bytes!("../../../../guiduck-samples/fonts/DejaVuSans.ttf"); static BLOB: std::sync::OnceLock> = std::sync::OnceLock::new(); BLOB.get_or_init(|| guiduck_scene::paint::Blob::new(std::sync::Arc::new(FONT))) .clone() } /// Root containing a single button at a known location. fn fixture() -> (WidgetTree, WidgetId, Rc>) { let mut tree = WidgetTree::with_text_context(crate::text::TextContext::hermetic([sample_font()])); let root = tree.insert( Container::new(), taffy::Style { size: taffy::Size { width: percent(1.0_f32), height: percent(1.0_f32), }, padding: taffy::Rect::length(10.0_f32), align_items: Some(taffy::AlignItems::FLEX_START), ..Default::default() }, None, ); let button = tree.insert( Button::new("Save").family("DejaVu Sans"), taffy::Style::default(), Some(root), ); let clicks = Rc::new(RefCell::new(0u32)); let sink = clicks.clone(); tree.on_event(button, crate::event::EventKind::Click, move |_ctx, _ev| { *sink.borrow_mut() += 1; }); tree.render_frame(Size::new(300.0, 120.0)); (tree, button, clicks) } fn key(tree: &mut WidgetTree, key: Key) { tree.dispatch_key( &KeyInput { key, modifiers: Modifiers::default(), pressed: true, }, &mut NoClipboard, ); } fn button_center(tree: &WidgetTree, button: WidgetId) -> Point { let origin = tree.absolute_origin(button); let size = tree.layout(button).size(); Point::new(origin.x + size.width / 2.0, origin.y + size.height / 2.0) } #[test] fn pointer_click_fires_the_wire() { let (mut tree, button, clicks) = fixture(); let center = button_center(&tree, button); tree.dispatch_pointer( PointerInput::Down { pos: center, button: PointerButton::Left, time: std::time::Instant::now(), }, &mut NoClipboard, ); tree.dispatch_pointer( PointerInput::Up { pos: center, button: PointerButton::Left, }, &mut NoClipboard, ); assert_eq!(*clicks.borrow(), 1); } #[test] fn enter_and_space_activate_a_focused_button() { let (mut tree, button, clicks) = fixture(); // The focus invariant already focused the only focusable widget. assert_eq!(tree.focus(), Some(button)); key(&mut tree, Key::Enter); tree.render_frame(Size::new(300.0, 120.0)); assert_eq!(*clicks.borrow(), 1, "Enter activates"); key(&mut tree, Key::Character(" ".to_owned())); tree.render_frame(Size::new(300.0, 120.0)); assert_eq!(*clicks.borrow(), 2, "Space activates"); } #[test] fn a_disabled_button_is_inert() { let (mut tree, button, clicks) = fixture(); tree.set_enabled(button, false); tree.render_frame(Size::new(300.0, 120.0)); // A disabled button is not focusable, so keyboard activation cannot // reach it, and pointer clicks retarget to the enabled ancestor. let center = button_center(&tree, button); tree.dispatch_pointer( PointerInput::Down { pos: center, button: PointerButton::Left, time: std::time::Instant::now(), }, &mut NoClipboard, ); tree.dispatch_pointer( PointerInput::Up { pos: center, button: PointerButton::Left, }, &mut NoClipboard, ); assert_eq!(*clicks.borrow(), 0); } #[test] fn cursor_is_a_pointer_over_the_button() { let (mut tree, button, _clicks) = fixture(); let center = button_center(&tree, button); tree.dispatch_pointer(PointerInput::Moved(center), &mut NoClipboard); assert_eq!(tree.cursor_shape(), CursorShape::Pointer); }