ferroui_browser/
browser_input_pane.rs1use ferroui_base::reactive::IDisposable;
2use ferroui_base::Rect;
3use ferroui_controls::platform::{IInputPane, InputPaneBase, InputPaneState, InputPaneStateEventArgs};
4use std::rc::Rc;
5
6#[derive(Default)]
9pub struct BrowserInputPane {
10 base: InputPaneBase,
11}
12
13impl BrowserInputPane {
14 pub fn new() -> Rc<Self> {
16 Rc::new(Self::default())
17 }
18
19 pub fn on_geometry_change(&self, x: f64, y: f64, width: f64, height: f64) -> bool {
22 let old_state = (self.base.occluded_rect(), self.base.state());
23
24 let occluded_rect = Rect::new(x, y, width, height);
25 self.base.set_occluded_rect(occluded_rect);
26 let state = if occluded_rect.width != 0.0 { InputPaneState::Open } else { InputPaneState::Closed };
27 self.base.set_state(state);
28
29 if old_state != (occluded_rect, state) {
30 self.base.on_state_changed(InputPaneStateEventArgs::new(state, None, occluded_rect));
31 }
32
33 true
34 }
35}
36
37impl IInputPane for BrowserInputPane {
38 fn state(&self) -> InputPaneState {
39 self.base.state()
40 }
41
42 fn occluded_rect(&self) -> Rect {
43 self.base.occluded_rect()
44 }
45
46 fn state_changed(&self, handler: Rc<dyn Fn(&InputPaneStateEventArgs)>) -> Rc<dyn IDisposable> {
47 self.base.state_changed(handler)
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use ferroui_base::threading::Dispatcher;
55 use std::cell::RefCell;
56
57 type Seen = Rc<RefCell<Vec<(InputPaneState, Option<Rect>, Rect)>>>;
58
59 fn pane() -> (Rc<BrowserInputPane>, Seen, Rc<dyn IDisposable>) {
60 let pane = BrowserInputPane::new();
61 let seen: Seen = Rc::new(RefCell::new(Vec::new()));
62 let log = seen.clone();
63 let subscription = pane.state_changed(Rc::new(move |e: &InputPaneStateEventArgs| {
64 log.borrow_mut().push((e.new_state(), e.start_rect(), e.end_rect()))
65 }));
66 (pane, seen, subscription)
67 }
68
69 #[test]
70 fn the_pane_starts_closed() {
71 let pane = BrowserInputPane::new();
72
73 assert_eq!(InputPaneState::Closed, pane.state());
74 assert_eq!(Rect::default(), pane.occluded_rect());
75 }
76
77 #[test]
78 fn a_keyboard_with_a_width_opens_the_pane() {
79 let _scope = Dispatcher::unit_test_scope();
80 let (pane, seen, _subscription) = pane();
81
82 assert!(pane.on_geometry_change(0.0, 300.0, 400.0, 200.0));
83
84 let rect = Rect::new(0.0, 300.0, 400.0, 200.0);
85 assert_eq!(InputPaneState::Open, pane.state());
86 assert_eq!(rect, pane.occluded_rect());
87 assert_eq!(vec![(InputPaneState::Open, None, rect)], *seen.borrow());
88 }
89
90 #[test]
91 fn a_keyboard_without_a_width_closes_the_pane() {
92 let _scope = Dispatcher::unit_test_scope();
93 let (pane, seen, _subscription) = pane();
94 pane.on_geometry_change(0.0, 300.0, 400.0, 200.0);
95
96 assert!(pane.on_geometry_change(0.0, 0.0, 0.0, 0.0));
97
98 assert_eq!(InputPaneState::Closed, pane.state());
99 assert_eq!(Rect::default(), pane.occluded_rect());
100 assert_eq!(2, seen.borrow().len());
101 assert_eq!((InputPaneState::Closed, None, Rect::default()), seen.borrow()[1]);
102 }
103
104 #[test]
105 fn the_change_is_raised_only_when_the_rectangle_or_the_state_changes() {
106 let _scope = Dispatcher::unit_test_scope();
107 let (pane, seen, _subscription) = pane();
108
109 pane.on_geometry_change(0.0, 0.0, 0.0, 0.0);
111 assert!(seen.borrow().is_empty());
112
113 pane.on_geometry_change(0.0, 300.0, 400.0, 200.0);
114 pane.on_geometry_change(0.0, 300.0, 400.0, 200.0);
115 assert_eq!(1, seen.borrow().len());
116
117 pane.on_geometry_change(0.0, 250.0, 400.0, 250.0);
119 assert_eq!(2, seen.borrow().len());
120 assert_eq!(InputPaneState::Open, seen.borrow()[1].0);
121
122 pane.on_geometry_change(0.0, 250.0, 0.0, 250.0);
124 assert_eq!(3, seen.borrow().len());
125 assert_eq!(InputPaneState::Closed, seen.borrow()[2].0);
126 }
127
128 #[test]
129 fn a_disposed_subscription_is_not_called() {
130 let _scope = Dispatcher::unit_test_scope();
131 let (pane, seen, subscription) = pane();
132
133 subscription.dispose();
134 pane.on_geometry_change(0.0, 300.0, 400.0, 200.0);
135
136 assert!(seen.borrow().is_empty());
137 assert_eq!(InputPaneState::Open, pane.state());
138 }
139}