use crate::message::{ButtonState, KeyCode, OsEvent};
use crate::{
core::{algebra::Vector2, info, pool::Handle, reflect::Reflect},
message::{MessageData, UiMessage},
text::Text,
widget::WidgetMessage,
BuildContext, Control, UiNode, UserInterface,
};
use fyrox_core::err;
use fyrox_core::pool::ObjectOrVariant;
use fyrox_graph::{SceneGraph, SceneGraphNode};
use uuid::Uuid;
pub trait UserInterfaceTestingExtension {
fn click(&mut self, position: Vector2<f32>);
fn click_at(&mut self, uuid: Uuid) -> Handle<UiNode>;
fn click_at_text(&mut self, uuid: Uuid, text: &str);
fn find_by_uuid(&self, uuid: Uuid) -> Option<&UiNode>;
fn find_by_uuid_of<T: Control>(&self, uuid: Uuid) -> Option<&T>;
fn is_visible(&self, uuid: Uuid) -> bool {
if let Some(node) = self.find_by_uuid(uuid) {
node.is_globally_visible()
} else {
panic!("Widget {uuid} does not exist!")
}
}
fn poll_all_messages(&mut self);
fn poll_and_count(&mut self, pred: impl FnMut(&UiMessage) -> bool) -> usize;
fn click_at_count_response<M: MessageData + PartialEq>(
&mut self,
name: Uuid,
response: M,
) -> usize;
fn type_text(&mut self, text: &str);
}
fn is_enabled(mut handle: Handle<UiNode>, ui: &UserInterface) -> bool {
while let Ok(node) = ui.try_get(handle) {
if !node.enabled() {
return false;
}
handle = node.parent();
}
true
}
impl UserInterfaceTestingExtension for UserInterface {
fn click(&mut self, position: Vector2<f32>) {
self.process_os_event(&crate::message::OsEvent::CursorMoved { position });
self.process_os_event(&crate::message::OsEvent::MouseInput {
button: crate::message::MouseButton::Left,
state: crate::message::ButtonState::Pressed,
});
self.process_os_event(&crate::message::OsEvent::MouseInput {
button: crate::message::MouseButton::Left,
state: crate::message::ButtonState::Released,
});
}
fn click_at(&mut self, uuid: Uuid) -> Handle<UiNode> {
assert_ne!(uuid, Uuid::default());
if let Some((handle, n)) = self.find_from_root(&mut |n| n.id == uuid) {
if self.visual_debug {
info!("{} - bounds {:?}", uuid, n.screen_bounds());
}
assert!(is_enabled(handle, self));
assert!(n.is_globally_visible());
let center = n.screen_bounds().center();
self.click(center);
if self.visual_debug {
info!(
"==== Clicked at {uuid}({}:{}) at [{};{}] coords. ====",
handle.index(),
handle.generation(),
center.x,
center.y
);
}
handle
} else {
panic!("There's no widget {uuid}!")
}
}
fn click_at_text(&mut self, uuid: Uuid, text: &str) {
assert_ne!(uuid, Uuid::default());
if let Some((start_handle, start_node)) = self.find_from_root(&mut |n| n.id == uuid) {
if self.visual_debug {
info!("{} - bounds {:?}", uuid, start_node.screen_bounds());
}
assert!(is_enabled(start_handle, self));
assert!(start_node.is_globally_visible());
if let Some((text_handle, text_node)) = self.find(start_handle, &mut |n| {
if let Some(text_widget) = n.component_ref::<Text>() {
text_widget.text() == text
} else {
false
}
}) {
assert!(is_enabled(text_handle, self));
assert!(text_node.is_globally_visible());
let center = text_node.screen_bounds().center();
self.click(center);
if self.visual_debug {
info!(
"==== Clicked at {text}({}:{}) at [{};{}] coords. Found from {uuid} starting location. ====",
text_handle.index(),
text_handle.generation(),
center.x,
center.y
);
}
}
} else {
panic!("There's no widget {uuid}!")
}
}
fn click_at_count_response<M: MessageData + PartialEq>(
&mut self,
uuid: Uuid,
response: M,
) -> usize {
let handle = self.click_at(uuid);
self.poll_and_count(move |msg| msg.data_from::<M>(handle) == Some(&response))
}
fn find_by_uuid(&self, uuid: Uuid) -> Option<&UiNode> {
self.find_from_root(&mut |n| n.id == uuid).map(|(_, n)| n)
}
fn find_by_uuid_of<T: Control>(&self, uuid: Uuid) -> Option<&T> {
self.find_from_root(&mut |n| n.id == uuid)
.and_then(|(_, n)| n.cast())
}
fn poll_all_messages(&mut self) {
while let Some(msg) = self.poll_message() {
if self.visual_debug {
if let Ok(widget) = self.try_get(msg.destination()) {
let ty = Reflect::type_name(widget);
info!("[{ty}]{msg:?}");
}
}
}
let screen_size = self.screen_size();
self.update(screen_size, 1.0 / 60.0, &Default::default());
}
fn poll_and_count(&mut self, mut pred: impl FnMut(&UiMessage) -> bool) -> usize {
let mut num = 0;
while let Some(msg) = self.poll_message() {
if self.visual_debug {
if let Ok(widget) = self.try_get(msg.destination()) {
let ty = Reflect::type_name(widget);
info!("[{ty}]{msg:?}");
}
}
if pred(&msg) {
num += 1;
}
}
let screen_size = self.screen_size();
self.update(screen_size, 1.0 / 60.0, &Default::default());
num
}
fn type_text(&mut self, text: &str) {
for char in text.chars() {
if char.is_ascii() {
match KeyCode::try_from(char) {
Ok(button) => {
self.process_os_event(&OsEvent::KeyboardInput {
button,
state: ButtonState::Pressed,
text: char.to_string(),
});
self.process_os_event(&OsEvent::KeyboardInput {
button,
state: ButtonState::Released,
text: Default::default(),
});
}
Err(err) => {
err!(
"Unable to emulate {} character press. Reason: {}",
char,
err
)
}
}
}
}
}
}
pub fn test_widget_deletion<F, U>(constructor: F)
where
F: FnOnce(&mut BuildContext) -> Handle<U>,
U: ObjectOrVariant<UiNode>,
{
let screen_size = Vector2::new(100.0, 100.0);
let mut ui = UserInterface::new(screen_size);
let widget = constructor(&mut ui.build_ctx());
ui.send(widget, WidgetMessage::Remove);
ui.update(screen_size, 1.0 / 60.0, &Default::default());
while ui.poll_message().is_some() {}
assert_eq!(ui.nodes().alive_count(), 1);
}