mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-10-30 19:47:03 +00:00 
			
		
		
		
	 cfc48a0c9b
			
		
	
	
		cfc48a0c9b
		
	
	
	
		
			
	
		
	
	
		
			Some checks are pending
		
		
	
	autofix.ci / format_fix (push) Waiting to run
				
			autofix.ci / lint_typecheck (push) Waiting to run
				
			CI / tree-sitter (push) Blocked by required conditions
				
			CI / files-changed (push) Waiting to run
				
			CI / build_and_test (--exclude bevy-example, ubuntu-22.04, 1.85) (push) Blocked by required conditions
				
			CI / python_test (windows-2022) (push) Blocked by required conditions
				
			CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, --exclude bevy-example, windows-2022, 1.85) (push) Blocked by required conditions
				
			CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, macos-14, stable) (push) Blocked by required conditions
				
			CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, beta) (push) Blocked by required conditions
				
			CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, stable) (push) Blocked by required conditions
				
			CI / build_and_test (ubuntu-22.04, nightly) (push) Blocked by required conditions
				
			CI / node_test (macos-14) (push) Blocked by required conditions
				
			CI / node_test (ubuntu-22.04) (push) Blocked by required conditions
				
			CI / node_test (windows-2022) (push) Blocked by required conditions
				
			CI / python_test (macos-14) (push) Blocked by required conditions
				
			CI / python_test (ubuntu-22.04) (push) Blocked by required conditions
				
			CI / cpp_test_driver (macos-13) (push) Blocked by required conditions
				
			CI / cpp_test_driver (ubuntu-22.04) (push) Blocked by required conditions
				
			CI / cpp_test_driver (windows-2022) (push) Blocked by required conditions
				
			CI / cpp_cmake (macos-14, 1.85) (push) Blocked by required conditions
				
			CI / cpp_cmake (ubuntu-22.04, stable) (push) Blocked by required conditions
				
			CI / cpp_cmake (windows-2022, nightly) (push) Blocked by required conditions
				
			CI / cpp_package_test (push) Blocked by required conditions
				
			CI / vsce_build_test (push) Blocked by required conditions
				
			CI / wasm_demo (push) Blocked by required conditions
				
			CI / mcu (pico-st7789, thumbv6m-none-eabi) (push) Blocked by required conditions
				
			CI / mcu (pico2-st7789, thumbv8m.main-none-eabihf) (push) Blocked by required conditions
				
			CI / mcu (stm32h735g, thumbv7em-none-eabihf) (push) Blocked by required conditions
				
			CI / mcu-embassy (push) Blocked by required conditions
				
			CI / ffi_32bit_build (push) Blocked by required conditions
				
			CI / docs (push) Blocked by required conditions
				
			CI / wasm (push) Blocked by required conditions
				
			CI / updater_test (0.3.0) (push) Blocked by required conditions
				
			CI / fmt_test (push) Blocked by required conditions
				
			CI / esp-idf-quick (push) Blocked by required conditions
				
			CI / android (push) Blocked by required conditions
				
			CI / miri (push) Blocked by required conditions
				
			CI / test-figma-inspector (push) Blocked by required conditions
				
			The value is usually negative, so it should be negative in our test as well.
		
			
				
	
	
		
			268 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| // Copyright © SixtyFPS GmbH <info@slint.dev>
 | |
| // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
 | |
| 
 | |
| 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 i_slint_core::items::TextWrap;
 | |
| use std::cell::{Cell, RefCell};
 | |
| use std::pin::Pin;
 | |
| use std::rc::Rc;
 | |
| use std::sync::Mutex;
 | |
| 
 | |
| #[derive(Default)]
 | |
| pub struct TestingBackendOptions {
 | |
|     pub mock_time: bool,
 | |
|     pub threading: bool,
 | |
| }
 | |
| 
 | |
| pub struct TestingBackend {
 | |
|     clipboard: Mutex<Option<String>>,
 | |
|     queue: Option<Queue>,
 | |
|     mock_time: bool,
 | |
| }
 | |
| 
 | |
| impl TestingBackend {
 | |
|     pub fn new(options: TestingBackendOptions) -> Self {
 | |
|         Self {
 | |
|             clipboard: Mutex::default(),
 | |
|             queue: options.threading.then(|| Queue(Default::default(), std::thread::current())),
 | |
|             mock_time: options.mock_time,
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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 {
 | |
|         if self.mock_time {
 | |
|             // The slint::testing::mock_elapsed_time updates the animation tick directly
 | |
|             core::time::Duration::from_millis(i_slint_core::animations::current_tick().0)
 | |
|         } else {
 | |
|             static INITIAL_INSTANT: std::sync::OnceLock<std::time::Instant> =
 | |
|                 std::sync::OnceLock::new();
 | |
|             let the_beginning = *INITIAL_INSTANT.get_or_init(std::time::Instant::now);
 | |
|             std::time::Instant::now() - the_beginning
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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();
 | |
|             if !self.mock_time {
 | |
|                 i_slint_core::platform::update_timers_and_animations();
 | |
|             }
 | |
|             match e {
 | |
|                 Some(Event::Quit) => break Ok(()),
 | |
|                 Some(Event::Event(e)) => e(),
 | |
|                 None => match i_slint_core::platform::duration_until_next_timer_update() {
 | |
|                     Some(duration) if !self.mock_time => std::thread::park_timeout(duration),
 | |
|                     _ => 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,
 | |
|         _text_wrap: TextWrap,
 | |
|     ) -> LogicalSize {
 | |
|         LogicalSize::new(text.len() as f32 * 10., 10.)
 | |
|     }
 | |
| 
 | |
|     fn font_metrics(
 | |
|         &self,
 | |
|         font_request: i_slint_core::graphics::FontRequest,
 | |
|         _scale_factor: ScaleFactor,
 | |
|     ) -> i_slint_core::items::FontMetrics {
 | |
|         let pixel_size = font_request.pixel_size.unwrap_or(LogicalLength::new(10.));
 | |
|         i_slint_core::items::FontMetrics {
 | |
|             ascent: pixel_size.get() * 0.7,
 | |
|             descent: -pixel_size.get() * 0.3,
 | |
|             x_height: 3.,
 | |
|             cap_height: 7.,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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 = text_input.text();
 | |
|         if pos.y < 0. {
 | |
|             return 0;
 | |
|         }
 | |
|         let line = (pos.y / 10.) as usize;
 | |
|         let offset =
 | |
|             if line >= 1 { text.split('\n').take(line - 1).map(|l| l.len() + 1).sum() } else { 0 };
 | |
|         let Some(line) = text.split('\n').nth(line) else {
 | |
|             return text.len();
 | |
|         };
 | |
|         let column = ((pos.x / 10.).max(0.) as usize).min(line.len());
 | |
|         offset + column
 | |
|     }
 | |
| 
 | |
|     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 {
 | |
|         let text = text_input.text();
 | |
|         let line = text[..byte_offset].chars().filter(|c| *c == '\n').count();
 | |
|         let column = text[..byte_offset].split('\n').nth(line).unwrap_or("").len();
 | |
|         LogicalRect::new(Point2D::new(column as f32 * 10., line as f32 * 10.), 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 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(())
 | |
|     }
 | |
| }
 |