use super::*;
use crate::draw_ctx::DrawCtx;
use crate::geometry::Rect;
use crate::{Event, EventResult, Scene};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
struct SpyField {
bounds: Rect,
children: Vec<Box<dyn Widget>>,
focused: Rc<Cell<bool>>,
typed: Rc<RefCell<String>>,
}
impl SpyField {
fn new(focused: Rc<Cell<bool>>, typed: Rc<RefCell<String>>) -> Self {
Self {
bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
children: Vec::new(),
focused,
typed,
}
}
}
impl Widget for SpyField {
fn type_name(&self) -> &'static str {
"SpyField"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn layout(&mut self, _available: Size) -> Size {
Size::new(100.0, 100.0)
}
fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
fn is_focusable(&self) -> bool {
true
}
fn on_event(&mut self, event: &Event) -> EventResult {
match event {
Event::FocusGained => {
self.focused.set(true);
EventResult::Consumed
}
Event::FocusLost => {
self.focused.set(false);
EventResult::Consumed
}
Event::KeyDown {
key: Key::Char(c), ..
} => {
self.typed.borrow_mut().push(*c);
EventResult::Consumed
}
Event::MouseDown { .. } => EventResult::Consumed,
_ => EventResult::Ignored,
}
}
}
fn scene_app() -> (App, Rc<Cell<bool>>, Rc<RefCell<String>>) {
let focused = Rc::new(Cell::new(false));
let typed = Rc::new(RefCell::new(String::new()));
let field = SpyField::new(Rc::clone(&focused), Rc::clone(&typed));
let scene = Scene::new(Box::new(field)).with_content_size(Size::new(100.0, 100.0));
let mut app = App::new(Box::new(scene));
app.layout(Size::new(200.0, 200.0));
(app, focused, typed)
}
#[test]
fn tab_focuses_field_inside_scene_and_types() {
let (mut app, focused, typed) = scene_app();
assert!(
app.focused_widget_type_name().is_none(),
"nothing focused before Tab"
);
app.on_key_down(Key::Tab, Modifiers::default());
assert_eq!(
app.focused_widget_type_name(),
Some("SpyField"),
"Tab must reach the field hosted inside the Scene"
);
assert!(focused.get(), "the field must have received FocusGained");
app.on_key_down(Key::Char('H'), Modifiers::default());
app.on_key_down(Key::Char('i'), Modifiers::default());
assert_eq!(
typed.borrow().as_str(),
"Hi",
"typed characters must land in the focused field inside the Scene"
);
}
#[test]
fn click_to_focus_maps_through_scene_zoom() {
let (mut app, focused, _typed) = scene_app();
let click_x = 50.0;
let click_y_up = 50.0;
app.on_mouse_down(
click_x,
200.0 - click_y_up,
MouseButton::Left,
Modifiers::default(),
);
assert_eq!(
app.focused_widget_type_name(),
Some("SpyField"),
"a click at the field's transformed screen position must focus it"
);
assert!(focused.get(), "the field must have received FocusGained");
}
struct InertContent {
bounds: Rect,
children: Vec<Box<dyn Widget>>,
}
impl Widget for InertContent {
fn type_name(&self) -> &'static str {
"InertContent"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn layout(&mut self, _available: Size) -> Size {
Size::new(100.0, 100.0)
}
fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
fn on_event(&mut self, _event: &Event) -> EventResult {
EventResult::Ignored
}
}
fn scene_rect_prop(app: &App) -> String {
app.collect_inspector_nodes()
.into_iter()
.find(|n| n.type_name == "Scene")
.and_then(|n| {
n.properties
.iter()
.find(|(k, _)| *k == "scene_rect")
.map(|(_, v)| v.clone())
})
.expect("Scene node with scene_rect property")
}
#[test]
fn drag_on_inert_content_pans_scene() {
let scene = Scene::new(Box::new(InertContent {
bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
children: Vec::new(),
}))
.with_content_size(Size::new(100.0, 100.0));
let mut app = App::new(Box::new(scene));
app.layout(Size::new(200.0, 200.0));
let before = scene_rect_prop(&app);
app.on_mouse_down(100.0, 200.0 - 100.0, MouseButton::Left, Modifiers::default());
app.on_mouse_move(140.0, 200.0 - 120.0);
app.on_mouse_up(140.0, 200.0 - 120.0, MouseButton::Left, Modifiers::default());
let after = scene_rect_prop(&app);
assert_ne!(
before, after,
"dragging the empty background must pan the Scene view"
);
}
#[test]
fn inspector_sees_scene_content_at_transformed_bounds() {
let (app, _focused, _typed) = scene_app();
let nodes = app.collect_inspector_nodes();
let field = nodes
.iter()
.find(|n| n.type_name == "SpyField")
.expect("the Scene's hosted field must appear in the inspector snapshot");
let b = field.screen_bounds;
assert!(
(b.x - 0.0).abs() < 1e-6
&& (b.y - 0.0).abs() < 1e-6
&& (b.width - 200.0).abs() < 1e-6
&& (b.height - 200.0).abs() < 1e-6,
"hosted field screen_bounds must reflect the Scene's 2× zoom; got {:?}",
b
);
}
struct ClickCatcher {
bounds: Rect,
children: Vec<Box<dyn Widget>>,
}
impl Widget for ClickCatcher {
fn type_name(&self) -> &'static str {
"ClickCatcher"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn layout(&mut self, _available: Size) -> Size {
Size::new(20.0, 20.0)
}
fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
fn on_event(&mut self, event: &Event) -> EventResult {
match event {
Event::MouseDown { .. } => EventResult::Consumed,
_ => EventResult::Ignored,
}
}
}
struct MixedContent {
bounds: Rect,
children: Vec<Box<dyn Widget>>,
}
impl Widget for MixedContent {
fn type_name(&self) -> &'static str {
"MixedContent"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn layout(&mut self, _available: Size) -> Size {
self.children[0].set_bounds(Rect::new(0.0, 0.0, 20.0, 20.0));
self.children[0].layout(Size::new(20.0, 20.0));
Size::new(100.0, 100.0)
}
fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
fn on_event(&mut self, _event: &Event) -> EventResult {
EventResult::Ignored
}
}
fn mixed_scene_app() -> App {
let content = MixedContent {
bounds: Rect::new(0.0, 0.0, 100.0, 100.0),
children: vec![Box::new(ClickCatcher {
bounds: Rect::new(0.0, 0.0, 20.0, 20.0),
children: Vec::new(),
})],
};
let scene = Scene::new(Box::new(content)).with_content_size(Size::new(100.0, 100.0));
let mut app = App::new(Box::new(scene));
app.layout(Size::new(200.0, 200.0));
app
}
fn app_click(app: &mut App, sx: f64, sy_up: f64) {
app.on_mouse_down(sx, 200.0 - sy_up, MouseButton::Left, Modifiers::default());
app.on_mouse_up(sx, 200.0 - sy_up, MouseButton::Left, Modifiers::default());
}
fn app_drag(app: &mut App, from: (f64, f64), to: (f64, f64)) {
app.on_mouse_down(from.0, 200.0 - from.1, MouseButton::Left, Modifiers::default());
app.on_mouse_move(to.0, 200.0 - to.1);
app.on_mouse_up(to.0, 200.0 - to.1, MouseButton::Left, Modifiers::default());
}
#[test]
fn bg_double_click_straddling_child_click_does_not_reset() {
let mut app = mixed_scene_app();
let fitted = scene_rect_prop(&app);
app_drag(&mut app, (100.0, 100.0), (90.0, 90.0));
let panned = scene_rect_prop(&app);
assert_ne!(panned, fitted, "the drag should have panned the view");
app_click(&mut app, 110.0, 110.0); app_click(&mut app, 5.0, 5.0); app_click(&mut app, 110.0, 110.0);
assert_eq!(
scene_rect_prop(&app),
panned,
"a background double-click interrupted by a hosted-child click must NOT reset the view"
);
}
#[test]
fn bg_genuine_double_click_resets_view_via_app() {
let mut app = mixed_scene_app();
let fitted = scene_rect_prop(&app);
app_drag(&mut app, (100.0, 100.0), (90.0, 90.0));
assert_ne!(scene_rect_prop(&app), fitted, "the drag should have panned");
app_click(&mut app, 110.0, 110.0);
app_click(&mut app, 110.0, 110.0);
assert_eq!(
scene_rect_prop(&app),
fitted,
"a genuine background double-click must reset the view to the fitted rect"
);
}