1use crate::message::{ButtonState, KeyCode, OsEvent};
22use crate::{
23 core::{algebra::Vector2, info, pool::Handle, reflect::Reflect},
24 message::{MessageData, UiMessage},
25 text::Text,
26 widget::WidgetMessage,
27 BuildContext, Control, UiNode, UserInterface,
28};
29use fyrox_core::err;
30use fyrox_core::pool::ObjectOrVariant;
31use fyrox_graph::{SceneGraph, SceneGraphNode};
32use uuid::Uuid;
33
34pub trait UserInterfaceTestingExtension {
35 fn click(&mut self, position: Vector2<f32>);
37
38 fn click_at(&mut self, uuid: Uuid) -> Handle<UiNode>;
40
41 fn click_at_text(&mut self, uuid: Uuid, text: &str);
42
43 fn find_by_uuid(&self, uuid: Uuid) -> Option<&UiNode>;
44
45 fn find_by_uuid_of<T: Control>(&self, uuid: Uuid) -> Option<&T>;
46
47 fn is_visible(&self, uuid: Uuid) -> bool {
48 if let Some(node) = self.find_by_uuid(uuid) {
49 node.is_globally_visible()
50 } else {
51 panic!("Widget {uuid} does not exist!")
52 }
53 }
54
55 fn poll_all_messages(&mut self);
56
57 fn poll_and_count(&mut self, pred: impl FnMut(&UiMessage) -> bool) -> usize;
58
59 fn click_at_count_response<M: MessageData + PartialEq>(
60 &mut self,
61 name: Uuid,
62 response: M,
63 ) -> usize;
64
65 fn type_text(&mut self, text: &str);
66}
67
68fn is_enabled(mut handle: Handle<UiNode>, ui: &UserInterface) -> bool {
69 while let Ok(node) = ui.try_get(handle) {
70 if !node.enabled() {
71 return false;
72 }
73 handle = node.parent();
74 }
75 true
76}
77
78impl UserInterfaceTestingExtension for UserInterface {
79 fn click(&mut self, position: Vector2<f32>) {
80 self.process_os_event(&crate::message::OsEvent::CursorMoved { position });
81 self.process_os_event(&crate::message::OsEvent::MouseInput {
82 button: crate::message::MouseButton::Left,
83 state: crate::message::ButtonState::Pressed,
84 });
85 self.process_os_event(&crate::message::OsEvent::MouseInput {
86 button: crate::message::MouseButton::Left,
87 state: crate::message::ButtonState::Released,
88 });
89 }
90
91 fn click_at(&mut self, uuid: Uuid) -> Handle<UiNode> {
92 assert_ne!(uuid, Uuid::default());
93 if let Some((handle, n)) = self.find_from_root(&mut |n| n.id == uuid) {
94 if self.visual_debug {
95 info!("{} - bounds {:?}", uuid, n.screen_bounds());
96 }
97 assert!(is_enabled(handle, self));
98 assert!(n.is_globally_visible());
99 let center = n.screen_bounds().center();
100 self.click(center);
101 if self.visual_debug {
102 info!(
103 "==== Clicked at {uuid}({}:{}) at [{};{}] coords. ====",
104 handle.index(),
105 handle.generation(),
106 center.x,
107 center.y
108 );
109 }
110 handle
111 } else {
112 panic!("There's no widget {uuid}!")
113 }
114 }
115
116 fn click_at_text(&mut self, uuid: Uuid, text: &str) {
117 assert_ne!(uuid, Uuid::default());
118 if let Some((start_handle, start_node)) = self.find_from_root(&mut |n| n.id == uuid) {
119 if self.visual_debug {
120 info!("{} - bounds {:?}", uuid, start_node.screen_bounds());
121 }
122 assert!(is_enabled(start_handle, self));
123 assert!(start_node.is_globally_visible());
124 if let Some((text_handle, text_node)) = self.find(start_handle, &mut |n| {
125 if let Some(text_widget) = n.component_ref::<Text>() {
126 text_widget.text() == text
127 } else {
128 false
129 }
130 }) {
131 assert!(is_enabled(text_handle, self));
132 assert!(text_node.is_globally_visible());
133 let center = text_node.screen_bounds().center();
134 self.click(center);
135 if self.visual_debug {
136 info!(
137 "==== Clicked at {text}({}:{}) at [{};{}] coords. Found from {uuid} starting location. ====",
138 text_handle.index(),
139 text_handle.generation(),
140 center.x,
141 center.y
142 );
143 }
144 }
145 } else {
146 panic!("There's no widget {uuid}!")
147 }
148 }
149
150 fn click_at_count_response<M: MessageData + PartialEq>(
151 &mut self,
152 uuid: Uuid,
153 response: M,
154 ) -> usize {
155 let handle = self.click_at(uuid);
156 self.poll_and_count(move |msg| msg.data_from::<M>(handle) == Some(&response))
157 }
158
159 fn find_by_uuid(&self, uuid: Uuid) -> Option<&UiNode> {
160 self.find_from_root(&mut |n| n.id == uuid).map(|(_, n)| n)
161 }
162
163 fn find_by_uuid_of<T: Control>(&self, uuid: Uuid) -> Option<&T> {
164 self.find_from_root(&mut |n| n.id == uuid)
165 .and_then(|(_, n)| n.cast())
166 }
167
168 fn poll_all_messages(&mut self) {
169 while let Some(msg) = self.poll_message() {
170 if self.visual_debug {
171 if let Ok(widget) = self.try_get(msg.destination()) {
172 let ty = Reflect::type_name(widget);
173 info!("[{ty}]{msg:?}");
174 }
175 }
176 }
177 let screen_size = self.screen_size();
178 self.update(screen_size, 1.0 / 60.0, &Default::default());
179 }
180
181 fn poll_and_count(&mut self, mut pred: impl FnMut(&UiMessage) -> bool) -> usize {
182 let mut num = 0;
183 while let Some(msg) = self.poll_message() {
184 if self.visual_debug {
185 if let Ok(widget) = self.try_get(msg.destination()) {
186 let ty = Reflect::type_name(widget);
187 info!("[{ty}]{msg:?}");
188 }
189 }
190
191 if pred(&msg) {
192 num += 1;
193 }
194 }
195 let screen_size = self.screen_size();
196 self.update(screen_size, 1.0 / 60.0, &Default::default());
197 num
198 }
199
200 fn type_text(&mut self, text: &str) {
201 for char in text.chars() {
202 if char.is_ascii() {
203 match KeyCode::try_from(char) {
204 Ok(button) => {
205 self.process_os_event(&OsEvent::KeyboardInput {
206 button,
207 state: ButtonState::Pressed,
208 text: char.to_string(),
209 });
210 self.process_os_event(&OsEvent::KeyboardInput {
211 button,
212 state: ButtonState::Released,
213 text: Default::default(),
214 });
215 }
216 Err(err) => {
217 err!(
218 "Unable to emulate {} character press. Reason: {}",
219 char,
220 err
221 )
222 }
223 }
224 }
225 }
226 }
227}
228
229pub fn test_widget_deletion<F, U>(constructor: F)
230where
231 F: FnOnce(&mut BuildContext) -> Handle<U>,
232 U: ObjectOrVariant<UiNode>,
233{
234 let screen_size = Vector2::new(100.0, 100.0);
235 let mut ui = UserInterface::new(screen_size);
236 let widget = constructor(&mut ui.build_ctx());
237 ui.send(widget, WidgetMessage::Remove);
238 ui.update(screen_size, 1.0 / 60.0, &Default::default());
239 while ui.poll_message().is_some() {}
240 assert_eq!(ui.nodes().alive_count(), 1);
242}