mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-01 20:31:27 +00:00
Refactor the testing backend
Move code around and gate the internal functions used in our own tests so we can make the testing backend public
This commit is contained in:
parent
583eadbdbc
commit
8030732f46
12 changed files with 335 additions and 324 deletions
|
|
@ -26,7 +26,8 @@ name = "slint_cpp"
|
|||
# the C++ crate's CMakeLists.txt
|
||||
[features]
|
||||
interpreter = ["slint-interpreter", "std"]
|
||||
testing = ["i-slint-backend-testing"] # Enable some function used by the integration tests
|
||||
# Enable some function used by the integration tests
|
||||
internal-testing = ["i-slint-backend-testing"]
|
||||
|
||||
backend-qt = ["i-slint-backend-selector/backend-qt", "std"]
|
||||
backend-winit = ["i-slint-backend-selector/backend-winit", "std"]
|
||||
|
|
@ -50,7 +51,7 @@ default = ["std", "backend-winit", "renderer-femtovg", "backend-qt"]
|
|||
|
||||
[dependencies]
|
||||
i-slint-backend-selector = { workspace = true, optional = true }
|
||||
i-slint-backend-testing = { workspace = true, features = ["default"], optional = true }
|
||||
i-slint-backend-testing = { workspace = true, optional = true }
|
||||
i-slint-renderer-skia = { workspace = true, features = ["default", "x11", "wayland"], optional = true }
|
||||
i-slint-core = { workspace = true, features = ["ffi"] }
|
||||
slint-interpreter = { workspace = true, features = ["ffi", "compat-1-2"], optional = true }
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ pub unsafe extern "C" fn slint_register_bitmap_font(
|
|||
window_adapter.renderer().register_bitmap_font(font_data);
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
#[cfg(feature = "internal-testing")]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_testing_init_backend() {
|
||||
i_slint_backend_testing::init();
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ i-slint-backend-android-activity = { workspace = true, optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
slint-build = { path = "../build" }
|
||||
i-slint-backend-testing = { path = "../../../internal/backends/testing" }
|
||||
i-slint-backend-testing = { path = "../../../internal/backends/testing", features = ["internal"] }
|
||||
serde_json = "1.0.96"
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ publish = false
|
|||
path = "lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# Internal feature that is only enabled for Slint's own tests
|
||||
internal = []
|
||||
|
||||
[dependencies]
|
||||
i-slint-core = { workspace = true, features = ["default"] }
|
||||
i-slint-core = { workspace = true }
|
||||
vtable = { workspace = true }
|
||||
|
|
|
|||
82
internal/backends/testing/internal_tests.rs
Normal file
82
internal/backends/testing/internal_tests.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
|
||||
|
||||
//! This module contains helper functions that are used for our internal tests within Slint
|
||||
|
||||
use crate::TestingWindow;
|
||||
use i_slint_core::api::ComponentHandle;
|
||||
use i_slint_core::platform::WindowEvent;
|
||||
pub use i_slint_core::tests::slint_get_mocked_time as get_mocked_time;
|
||||
pub use i_slint_core::tests::slint_mock_elapsed_time as mock_elapsed_time;
|
||||
use i_slint_core::window::WindowInner;
|
||||
use i_slint_core::SharedString;
|
||||
|
||||
/// Simulate a mouse click
|
||||
pub fn send_mouse_click<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable> + 'static,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
x: f32,
|
||||
y: f32,
|
||||
) {
|
||||
i_slint_core::tests::slint_send_mouse_click(
|
||||
x,
|
||||
y,
|
||||
&WindowInner::from_pub(component.window()).window_adapter(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Simulate entering a sequence of ascii characters key by (pressed or released).
|
||||
pub fn send_keyboard_char<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable>,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
string: char,
|
||||
pressed: bool,
|
||||
) {
|
||||
i_slint_core::tests::slint_send_keyboard_char(
|
||||
&SharedString::from(string),
|
||||
pressed,
|
||||
&WindowInner::from_pub(component.window()).window_adapter(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Simulate entering a sequence of ascii characters key by key.
|
||||
pub fn send_keyboard_string_sequence<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable>,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
sequence: &str,
|
||||
) {
|
||||
i_slint_core::tests::send_keyboard_string_sequence(
|
||||
&SharedString::from(sequence),
|
||||
&WindowInner::from_pub(component.window()).window_adapter(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Applies the specified scale factor to the window that's associated with the given component.
|
||||
/// This overrides the value provided by the windowing system.
|
||||
pub fn set_window_scale_factor<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable>,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
factor: f32,
|
||||
) {
|
||||
component.window().dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor: factor });
|
||||
}
|
||||
|
||||
pub fn access_testing_window<R>(
|
||||
window: &i_slint_core::api::Window,
|
||||
callback: impl FnOnce(&TestingWindow) -> R,
|
||||
) -> R {
|
||||
i_slint_core::window::WindowInner::from_pub(window)
|
||||
.window_adapter()
|
||||
.internal(i_slint_core::InternalToken)
|
||||
.and_then(|wa| wa.as_any().downcast_ref::<TestingWindow>())
|
||||
.map(callback)
|
||||
.expect("access_testing_window called without testing backend/adapter")
|
||||
}
|
||||
|
|
@ -6,239 +6,21 @@
|
|||
|
||||
mod search_api;
|
||||
pub use search_api::*;
|
||||
|
||||
use i_slint_core::api::PhysicalSize;
|
||||
use i_slint_core::graphics::euclid::{Point2D, Size2D};
|
||||
use i_slint_core::graphics::FontRequest;
|
||||
use i_slint_core::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, ScaleFactor};
|
||||
use i_slint_core::platform::PlatformError;
|
||||
use i_slint_core::renderer::{Renderer, RendererSealed};
|
||||
use i_slint_core::window::{InputMethodRequest, WindowAdapter, WindowAdapterInternal};
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub struct TestingBackend {
|
||||
clipboard: Mutex<Option<String>>,
|
||||
queue: Option<Queue>,
|
||||
}
|
||||
|
||||
impl TestingBackend {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue: Some(Queue(Default::default(), std::thread::current())),
|
||||
..Self::new_no_thread()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_no_thread() -> Self {
|
||||
Self { clipboard: Mutex::default(), queue: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl i_slint_core::platform::Platform for TestingBackend {
|
||||
fn create_window_adapter(
|
||||
&self,
|
||||
) -> Result<Rc<dyn WindowAdapter>, i_slint_core::platform::PlatformError> {
|
||||
Ok(Rc::new_cyclic(|self_weak| TestingWindow {
|
||||
window: i_slint_core::api::Window::new(self_weak.clone() as _),
|
||||
size: Default::default(),
|
||||
ime_requests: Default::default(),
|
||||
mouse_cursor: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn duration_since_start(&self) -> core::time::Duration {
|
||||
// The slint::testing::mock_elapsed_time updates the animation tick directly
|
||||
core::time::Duration::from_millis(i_slint_core::animations::current_tick().0)
|
||||
}
|
||||
|
||||
fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
|
||||
if clipboard == i_slint_core::platform::Clipboard::DefaultClipboard {
|
||||
*self.clipboard.lock().unwrap() = Some(text.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
|
||||
if clipboard == i_slint_core::platform::Clipboard::DefaultClipboard {
|
||||
self.clipboard.lock().unwrap().clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn run_event_loop(&self) -> Result<(), PlatformError> {
|
||||
let queue = match self.queue.as_ref() {
|
||||
Some(queue) => queue.clone(),
|
||||
None => return Err(PlatformError::NoEventLoopProvider),
|
||||
};
|
||||
|
||||
loop {
|
||||
let e = queue.0.lock().unwrap().pop_front();
|
||||
match e {
|
||||
Some(Event::Quit) => break Ok(()),
|
||||
Some(Event::Event(e)) => e(),
|
||||
None => std::thread::park(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_event_loop_proxy(&self) -> Option<Box<dyn i_slint_core::platform::EventLoopProxy>> {
|
||||
self.queue
|
||||
.as_ref()
|
||||
.map(|q| Box::new(q.clone()) as Box<dyn i_slint_core::platform::EventLoopProxy>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestingWindow {
|
||||
window: i_slint_core::api::Window,
|
||||
size: Cell<PhysicalSize>,
|
||||
pub ime_requests: RefCell<Vec<InputMethodRequest>>,
|
||||
pub mouse_cursor: Cell<i_slint_core::items::MouseCursor>,
|
||||
}
|
||||
|
||||
impl WindowAdapterInternal for TestingWindow {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn input_method_request(&self, request: i_slint_core::window::InputMethodRequest) {
|
||||
self.ime_requests.borrow_mut().push(request)
|
||||
}
|
||||
|
||||
fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) {
|
||||
self.mouse_cursor.set(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowAdapter for TestingWindow {
|
||||
fn window(&self) -> &i_slint_core::api::Window {
|
||||
&self.window
|
||||
}
|
||||
|
||||
fn size(&self) -> PhysicalSize {
|
||||
if self.size.get().width == 0 {
|
||||
PhysicalSize::new(800, 600)
|
||||
} else {
|
||||
self.size.get()
|
||||
}
|
||||
}
|
||||
|
||||
fn set_size(&self, size: i_slint_core::api::WindowSize) {
|
||||
self.window.dispatch_event(i_slint_core::platform::WindowEvent::Resized {
|
||||
size: size.to_logical(1.),
|
||||
});
|
||||
self.size.set(size.to_physical(1.))
|
||||
}
|
||||
|
||||
fn renderer(&self) -> &dyn Renderer {
|
||||
self
|
||||
}
|
||||
|
||||
fn update_window_properties(&self, properties: i_slint_core::window::WindowProperties<'_>) {
|
||||
if self.size.get().width == 0 {
|
||||
let c = properties.layout_constraints();
|
||||
self.size.set(c.preferred.to_physical(self.window.scale_factor()));
|
||||
}
|
||||
}
|
||||
|
||||
fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RendererSealed for TestingWindow {
|
||||
fn text_size(
|
||||
&self,
|
||||
_font_request: i_slint_core::graphics::FontRequest,
|
||||
text: &str,
|
||||
_max_width: Option<LogicalLength>,
|
||||
_scale_factor: ScaleFactor,
|
||||
) -> LogicalSize {
|
||||
LogicalSize::new(text.len() as f32 * 10., 10.)
|
||||
}
|
||||
|
||||
// this works only for single line text
|
||||
fn text_input_byte_offset_for_position(
|
||||
&self,
|
||||
text_input: Pin<&i_slint_core::items::TextInput>,
|
||||
pos: LogicalPoint,
|
||||
_font_request: FontRequest,
|
||||
_scale_factor: ScaleFactor,
|
||||
) -> usize {
|
||||
let text_len = text_input.text().len();
|
||||
let result = pos.x / 10.;
|
||||
result.min(text_len as f32).max(0.) as usize
|
||||
}
|
||||
|
||||
// this works only for single line text
|
||||
fn text_input_cursor_rect_for_byte_offset(
|
||||
&self,
|
||||
_text_input: Pin<&i_slint_core::items::TextInput>,
|
||||
byte_offset: usize,
|
||||
_font_request: FontRequest,
|
||||
_scale_factor: ScaleFactor,
|
||||
) -> LogicalRect {
|
||||
LogicalRect::new(Point2D::new(byte_offset as f32 * 10., 0.), Size2D::new(1., 10.))
|
||||
}
|
||||
|
||||
fn register_font_from_memory(
|
||||
&self,
|
||||
_data: &'static [u8],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_font_from_path(
|
||||
&self,
|
||||
_path: &std::path::Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_font_size(&self) -> LogicalLength {
|
||||
LogicalLength::new(10.)
|
||||
}
|
||||
|
||||
fn set_window_adapter(&self, _window_adapter: &Rc<dyn WindowAdapter>) {
|
||||
// No-op since TestingWindow is also the WindowAdapter
|
||||
}
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Quit,
|
||||
Event(Box<dyn FnOnce() + Send>),
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct Queue(
|
||||
std::sync::Arc<std::sync::Mutex<std::collections::VecDeque<Event>>>,
|
||||
std::thread::Thread,
|
||||
);
|
||||
|
||||
impl i_slint_core::platform::EventLoopProxy for Queue {
|
||||
fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> {
|
||||
self.0.lock().unwrap().push_back(Event::Quit);
|
||||
self.1.unpark();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke_from_event_loop(
|
||||
&self,
|
||||
event: Box<dyn FnOnce() + Send>,
|
||||
) -> Result<(), i_slint_core::api::EventLoopError> {
|
||||
self.0.lock().unwrap().push_back(Event::Event(event));
|
||||
self.1.unpark();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "internal")]
|
||||
mod internal_tests;
|
||||
#[cfg(feature = "internal")]
|
||||
pub use internal_tests::*;
|
||||
mod testing_backend;
|
||||
#[cfg(feature = "internal")]
|
||||
pub use testing_backend::*;
|
||||
|
||||
/// Initialize the testing backend.
|
||||
/// Must be called before any call that would otherwise initialize the rendering backend.
|
||||
/// Calling it when the rendering backend is already initialized will have no effects
|
||||
pub fn init() {
|
||||
i_slint_core::platform::set_platform(Box::new(TestingBackend::new_no_thread()))
|
||||
i_slint_core::platform::set_platform(
|
||||
Box::new(testing_backend::TestingBackend::new_no_thread()),
|
||||
)
|
||||
.expect("platform already initialized");
|
||||
}
|
||||
|
||||
|
|
@ -246,88 +28,6 @@ pub fn init() {
|
|||
/// This function can only be called once per process, so make sure to use integration
|
||||
/// tests with one `#[test]` function.
|
||||
pub fn init_with_event_loop() {
|
||||
i_slint_core::platform::set_platform(Box::new(TestingBackend::new()))
|
||||
i_slint_core::platform::set_platform(Box::new(testing_backend::TestingBackend::new()))
|
||||
.expect("platform already initialized");
|
||||
}
|
||||
|
||||
/// This module contains functions useful for unit tests
|
||||
mod for_unit_test {
|
||||
use i_slint_core::api::ComponentHandle;
|
||||
use i_slint_core::platform::WindowEvent;
|
||||
pub use i_slint_core::tests::slint_get_mocked_time as get_mocked_time;
|
||||
pub use i_slint_core::tests::slint_mock_elapsed_time as mock_elapsed_time;
|
||||
use i_slint_core::window::WindowInner;
|
||||
use i_slint_core::SharedString;
|
||||
|
||||
/// Simulate a mouse click
|
||||
pub fn send_mouse_click<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable> + 'static,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
x: f32,
|
||||
y: f32,
|
||||
) {
|
||||
i_slint_core::tests::slint_send_mouse_click(
|
||||
x,
|
||||
y,
|
||||
&WindowInner::from_pub(component.window()).window_adapter(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Simulate entering a sequence of ascii characters key by (pressed or released).
|
||||
pub fn send_keyboard_char<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable>,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
string: char,
|
||||
pressed: bool,
|
||||
) {
|
||||
i_slint_core::tests::slint_send_keyboard_char(
|
||||
&SharedString::from(string),
|
||||
pressed,
|
||||
&WindowInner::from_pub(component.window()).window_adapter(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Simulate entering a sequence of ascii characters key by key.
|
||||
pub fn send_keyboard_string_sequence<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable>,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
sequence: &str,
|
||||
) {
|
||||
i_slint_core::tests::send_keyboard_string_sequence(
|
||||
&SharedString::from(sequence),
|
||||
&WindowInner::from_pub(component.window()).window_adapter(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Applies the specified scale factor to the window that's associated with the given component.
|
||||
/// This overrides the value provided by the windowing system.
|
||||
pub fn set_window_scale_factor<
|
||||
X: vtable::HasStaticVTable<i_slint_core::item_tree::ItemTreeVTable>,
|
||||
Component: Into<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, X>> + ComponentHandle,
|
||||
>(
|
||||
component: &Component,
|
||||
factor: f32,
|
||||
) {
|
||||
component.window().dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor: factor });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn access_testing_window<R>(
|
||||
window: &i_slint_core::api::Window,
|
||||
callback: impl FnOnce(&TestingWindow) -> R,
|
||||
) -> R {
|
||||
i_slint_core::window::WindowInner::from_pub(window)
|
||||
.window_adapter()
|
||||
.internal(i_slint_core::InternalToken)
|
||||
.and_then(|wa| wa.as_any().downcast_ref::<TestingWindow>())
|
||||
.map(callback)
|
||||
.expect("access_testing_window called without testing backend/adapter")
|
||||
}
|
||||
|
||||
pub use for_unit_test::*;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ impl ElementHandle {
|
|||
result.into_iter().map(|x| ElementHandle(x))
|
||||
}
|
||||
|
||||
|
||||
pub fn invoke_default_action(&self) {
|
||||
self.0.accessible_action(&AccessibilityAction::Default)
|
||||
}
|
||||
|
|
@ -61,7 +60,6 @@ impl ElementHandle {
|
|||
i_slint_core::lengths::logical_size_to_api(g.size)
|
||||
}
|
||||
|
||||
|
||||
pub fn absolute_position(&self) -> i_slint_core::api::LogicalPosition {
|
||||
let g = self.0.geometry();
|
||||
let p = self.0.map_to_window(g.origin);
|
||||
|
|
|
|||
229
internal/backends/testing/testing_backend.rs
Normal file
229
internal/backends/testing/testing_backend.rs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
|
||||
|
||||
use i_slint_core::api::PhysicalSize;
|
||||
use i_slint_core::graphics::euclid::{Point2D, Size2D};
|
||||
use i_slint_core::graphics::FontRequest;
|
||||
use i_slint_core::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, ScaleFactor};
|
||||
use i_slint_core::platform::PlatformError;
|
||||
use i_slint_core::renderer::{Renderer, RendererSealed};
|
||||
use i_slint_core::window::{InputMethodRequest, WindowAdapter, WindowAdapterInternal};
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub struct TestingBackend {
|
||||
clipboard: Mutex<Option<String>>,
|
||||
queue: Option<Queue>,
|
||||
}
|
||||
|
||||
impl TestingBackend {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue: Some(Queue(Default::default(), std::thread::current())),
|
||||
..Self::new_no_thread()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_no_thread() -> Self {
|
||||
Self { clipboard: Mutex::default(), queue: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl i_slint_core::platform::Platform for TestingBackend {
|
||||
fn create_window_adapter(
|
||||
&self,
|
||||
) -> Result<Rc<dyn WindowAdapter>, i_slint_core::platform::PlatformError> {
|
||||
Ok(Rc::new_cyclic(|self_weak| TestingWindow {
|
||||
window: i_slint_core::api::Window::new(self_weak.clone() as _),
|
||||
size: Default::default(),
|
||||
ime_requests: Default::default(),
|
||||
mouse_cursor: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn duration_since_start(&self) -> core::time::Duration {
|
||||
// The slint::testing::mock_elapsed_time updates the animation tick directly
|
||||
core::time::Duration::from_millis(i_slint_core::animations::current_tick().0)
|
||||
}
|
||||
|
||||
fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
|
||||
if clipboard == i_slint_core::platform::Clipboard::DefaultClipboard {
|
||||
*self.clipboard.lock().unwrap() = Some(text.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
|
||||
if clipboard == i_slint_core::platform::Clipboard::DefaultClipboard {
|
||||
self.clipboard.lock().unwrap().clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn run_event_loop(&self) -> Result<(), PlatformError> {
|
||||
let queue = match self.queue.as_ref() {
|
||||
Some(queue) => queue.clone(),
|
||||
None => return Err(PlatformError::NoEventLoopProvider),
|
||||
};
|
||||
|
||||
loop {
|
||||
let e = queue.0.lock().unwrap().pop_front();
|
||||
match e {
|
||||
Some(Event::Quit) => break Ok(()),
|
||||
Some(Event::Event(e)) => e(),
|
||||
None => std::thread::park(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_event_loop_proxy(&self) -> Option<Box<dyn i_slint_core::platform::EventLoopProxy>> {
|
||||
self.queue
|
||||
.as_ref()
|
||||
.map(|q| Box::new(q.clone()) as Box<dyn i_slint_core::platform::EventLoopProxy>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestingWindow {
|
||||
window: i_slint_core::api::Window,
|
||||
size: Cell<PhysicalSize>,
|
||||
pub ime_requests: RefCell<Vec<InputMethodRequest>>,
|
||||
pub mouse_cursor: Cell<i_slint_core::items::MouseCursor>,
|
||||
}
|
||||
|
||||
impl WindowAdapterInternal for TestingWindow {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn input_method_request(&self, request: i_slint_core::window::InputMethodRequest) {
|
||||
self.ime_requests.borrow_mut().push(request)
|
||||
}
|
||||
|
||||
fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) {
|
||||
self.mouse_cursor.set(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowAdapter for TestingWindow {
|
||||
fn window(&self) -> &i_slint_core::api::Window {
|
||||
&self.window
|
||||
}
|
||||
|
||||
fn size(&self) -> PhysicalSize {
|
||||
if self.size.get().width == 0 {
|
||||
PhysicalSize::new(800, 600)
|
||||
} else {
|
||||
self.size.get()
|
||||
}
|
||||
}
|
||||
|
||||
fn set_size(&self, size: i_slint_core::api::WindowSize) {
|
||||
self.window.dispatch_event(i_slint_core::platform::WindowEvent::Resized {
|
||||
size: size.to_logical(1.),
|
||||
});
|
||||
self.size.set(size.to_physical(1.))
|
||||
}
|
||||
|
||||
fn renderer(&self) -> &dyn Renderer {
|
||||
self
|
||||
}
|
||||
|
||||
fn update_window_properties(&self, properties: i_slint_core::window::WindowProperties<'_>) {
|
||||
if self.size.get().width == 0 {
|
||||
let c = properties.layout_constraints();
|
||||
self.size.set(c.preferred.to_physical(self.window.scale_factor()));
|
||||
}
|
||||
}
|
||||
|
||||
fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RendererSealed for TestingWindow {
|
||||
fn text_size(
|
||||
&self,
|
||||
_font_request: i_slint_core::graphics::FontRequest,
|
||||
text: &str,
|
||||
_max_width: Option<LogicalLength>,
|
||||
_scale_factor: ScaleFactor,
|
||||
) -> LogicalSize {
|
||||
LogicalSize::new(text.len() as f32 * 10., 10.)
|
||||
}
|
||||
|
||||
// this works only for single line text
|
||||
fn text_input_byte_offset_for_position(
|
||||
&self,
|
||||
text_input: Pin<&i_slint_core::items::TextInput>,
|
||||
pos: LogicalPoint,
|
||||
_font_request: FontRequest,
|
||||
_scale_factor: ScaleFactor,
|
||||
) -> usize {
|
||||
let text_len = text_input.text().len();
|
||||
let result = pos.x / 10.;
|
||||
result.min(text_len as f32).max(0.) as usize
|
||||
}
|
||||
|
||||
// this works only for single line text
|
||||
fn text_input_cursor_rect_for_byte_offset(
|
||||
&self,
|
||||
_text_input: Pin<&i_slint_core::items::TextInput>,
|
||||
byte_offset: usize,
|
||||
_font_request: FontRequest,
|
||||
_scale_factor: ScaleFactor,
|
||||
) -> LogicalRect {
|
||||
LogicalRect::new(Point2D::new(byte_offset as f32 * 10., 0.), Size2D::new(1., 10.))
|
||||
}
|
||||
|
||||
fn register_font_from_memory(
|
||||
&self,
|
||||
_data: &'static [u8],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_font_from_path(
|
||||
&self,
|
||||
_path: &std::path::Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_font_size(&self) -> LogicalLength {
|
||||
LogicalLength::new(10.)
|
||||
}
|
||||
|
||||
fn set_window_adapter(&self, _window_adapter: &Rc<dyn WindowAdapter>) {
|
||||
// No-op since TestingWindow is also the WindowAdapter
|
||||
}
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Quit,
|
||||
Event(Box<dyn FnOnce() + Send>),
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct Queue(
|
||||
std::sync::Arc<std::sync::Mutex<std::collections::VecDeque<Event>>>,
|
||||
std::thread::Thread,
|
||||
);
|
||||
|
||||
impl i_slint_core::platform::EventLoopProxy for Queue {
|
||||
fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> {
|
||||
self.0.lock().unwrap().push_back(Event::Quit);
|
||||
self.1.unpark();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke_from_event_loop(
|
||||
&self,
|
||||
event: Box<dyn FnOnce() + Send>,
|
||||
) -> Result<(), i_slint_core::api::EventLoopError> {
|
||||
self.0.lock().unwrap().push_back(Event::Event(event));
|
||||
self.1.unpark();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ path = "main.rs"
|
|||
name = "test-driver-cpp"
|
||||
|
||||
[dependencies]
|
||||
slint-cpp = { workspace = true, features = ["testing", "std"] }
|
||||
slint-cpp = { workspace = true, features = ["internal-testing", "std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
i-slint-compiler = { workspace = true, features = ["default", "cpp", "display-diagnostics"] }
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ name = "test-driver-interpreter"
|
|||
|
||||
[dev-dependencies]
|
||||
slint-interpreter = { workspace = true, features = ["display-diagnostics", "compat-1-2"] }
|
||||
i-slint-backend-testing = { workspace = true, features = ["default"] }
|
||||
i-slint-backend-testing = { workspace = true }
|
||||
|
||||
itertools = { workspace = true }
|
||||
lazy_static = "1.4.0"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ build-time = ["i-slint-compiler", "spin_on"]
|
|||
|
||||
[dependencies]
|
||||
slint = { workspace = true, features = ["std", "compat-1-2"] }
|
||||
i-slint-backend-testing = { workspace = true, features = ["default"] }
|
||||
i-slint-backend-testing = { workspace = true, features = ["internal"] }
|
||||
slint-interpreter = { workspace = true, features = ["std", "compat-1-2", "internal"] }
|
||||
spin_on = "0.1"
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ name = "test-driver-screenshot"
|
|||
[dependencies]
|
||||
slint = { workspace = true, features = ["std", "compat-1-2"] }
|
||||
i-slint-core = { workspace = true, features = ["default", "software-renderer", "software-renderer-rotation"] }
|
||||
i-slint-backend-testing = { workspace = true, features = ["default"] }
|
||||
i-slint-backend-testing = { workspace = true }
|
||||
image = { workspace = true }
|
||||
crossterm = "0.27"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue