2025-12-31 22:24:26 +01:00
|
|
|
use core::{cell::RefCell, time::Duration};
|
|
|
|
|
|
2026-01-20 01:59:18 +01:00
|
|
|
use alloc::{rc::Rc, string::ToString};
|
2026-01-06 22:52:41 +01:00
|
|
|
use esp_hal::time::Instant;
|
2026-01-31 15:36:36 +01:00
|
|
|
use log::{debug, info};
|
2026-01-06 22:52:41 +01:00
|
|
|
use slint::{
|
2026-01-31 15:36:36 +01:00
|
|
|
PhysicalSize, SharedString, WindowSize,
|
2026-01-10 19:21:13 +01:00
|
|
|
platform::{
|
|
|
|
|
Key, WindowEvent,
|
|
|
|
|
software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer},
|
2026-01-06 22:52:41 +01:00
|
|
|
},
|
|
|
|
|
};
|
2026-01-20 01:59:18 +01:00
|
|
|
use xkbcommon::xkb::{self, Keysym};
|
2026-01-10 19:21:13 +01:00
|
|
|
|
|
|
|
|
use crate::keymap::{KEY_MESSAGE_CHANNEL, TryFromKeysym};
|
2025-12-31 22:24:26 +01:00
|
|
|
|
|
|
|
|
use super::window_adapter::SoftwareWindowAdapter;
|
|
|
|
|
|
|
|
|
|
pub struct FramebufferPtr(pub *mut [Rgb565Pixel]);
|
|
|
|
|
|
|
|
|
|
unsafe impl Send for FramebufferPtr {}
|
|
|
|
|
|
|
|
|
|
pub struct SlintBackend {
|
|
|
|
|
pub window_size: [u32; 2],
|
|
|
|
|
pub window: RefCell<Option<Rc<SoftwareWindowAdapter>>>,
|
|
|
|
|
pub framebuffer: FramebufferPtr,
|
|
|
|
|
// pub peripherals: RefCell<Option<Peripherals>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl slint::platform::Platform for SlintBackend {
|
2026-01-06 22:52:41 +01:00
|
|
|
fn create_window_adapter(
|
|
|
|
|
&self,
|
|
|
|
|
) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
|
|
|
|
|
let renderer = SoftwareRenderer::new_with_repaint_buffer_type(
|
|
|
|
|
RepaintBufferType::ReusedBuffer, /* TODO: Implement a swapchain */
|
|
|
|
|
);
|
2025-12-31 22:24:26 +01:00
|
|
|
renderer.set_rendering_rotation(RenderingRotation::Rotate270);
|
|
|
|
|
let window = SoftwareWindowAdapter::new(renderer);
|
|
|
|
|
// window.set_scale_factor(4.0);
|
2026-01-06 22:52:41 +01:00
|
|
|
window.set_size(WindowSize::Physical(PhysicalSize::new(
|
|
|
|
|
self.window_size[0],
|
|
|
|
|
self.window_size[1],
|
|
|
|
|
)));
|
2025-12-31 22:24:26 +01:00
|
|
|
self.window.replace(Some(window.clone()));
|
|
|
|
|
Ok(window)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn duration_since_start(&self) -> Duration {
|
|
|
|
|
Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
2026-01-01 01:24:52 +01:00
|
|
|
// Instead of `loop`ing here, we execute a single iteration and handle `loop`ing
|
|
|
|
|
// in `crate::run_renderer_task`, where we can make use of `await`.
|
|
|
|
|
|
2026-01-06 22:52:41 +01:00
|
|
|
/* loop */
|
|
|
|
|
{
|
2025-12-31 22:24:26 +01:00
|
|
|
if let Some(window) = self.window.borrow().clone() {
|
2026-01-10 19:21:13 +01:00
|
|
|
// Handle key presses
|
2026-01-20 01:59:18 +01:00
|
|
|
while let Ok(mut key_message) = KEY_MESSAGE_CHANNEL.try_receive() {
|
2026-01-11 00:06:35 +01:00
|
|
|
debug!("key_message = {key_message:?}");
|
|
|
|
|
|
2026-01-20 01:59:18 +01:00
|
|
|
if let Some(string) = key_message.string.as_ref()
|
|
|
|
|
&& (Keysym::a..=Keysym::z).contains(&key_message.keysym)
|
2026-01-31 15:36:36 +01:00
|
|
|
&& let &[code] = string.as_bytes() {
|
2026-01-20 01:59:18 +01:00
|
|
|
const UNICODE_CTRL_A: char = '\u{1}';
|
|
|
|
|
|
|
|
|
|
let letter_index_from_keysym =
|
|
|
|
|
key_message.keysym.raw().wrapping_sub(Keysym::a.raw());
|
|
|
|
|
let letter_index_from_string =
|
|
|
|
|
(code as u32).wrapping_sub(UNICODE_CTRL_A as u32);
|
|
|
|
|
|
|
|
|
|
if letter_index_from_keysym == letter_index_from_string {
|
|
|
|
|
key_message.keysym =
|
|
|
|
|
Keysym::new(Keysym::a.raw() + letter_index_from_keysym);
|
|
|
|
|
// TODO: Avoid allocation
|
|
|
|
|
key_message.string = Some(
|
|
|
|
|
((b'a' + letter_index_from_keysym as u8) as char).to_string(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
info!(
|
|
|
|
|
"Translating CTRL-{letter} to {letter}",
|
|
|
|
|
letter = (b'A' + letter_index_from_keysym as u8) as char
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// let text = key_message.string.map(SharedString::from).or_else(|| {
|
|
|
|
|
// Key::try_from_keysym(key_message.keysym).map(SharedString::from)
|
|
|
|
|
// });
|
|
|
|
|
let text = Key::try_from_keysym(key_message.keysym)
|
|
|
|
|
.map(SharedString::from)
|
|
|
|
|
.or_else(|| key_message.string.map(SharedString::from));
|
2026-01-10 19:21:13 +01:00
|
|
|
|
2026-01-11 00:06:35 +01:00
|
|
|
debug!("text = {text:?}");
|
|
|
|
|
|
2026-01-10 19:21:13 +01:00
|
|
|
if let Some(text) = text {
|
2026-01-11 00:06:35 +01:00
|
|
|
match key_message.direction {
|
|
|
|
|
xkb::KeyDirection::Down => window
|
|
|
|
|
.try_dispatch_event(WindowEvent::KeyPressed { text: text.clone() })
|
|
|
|
|
.unwrap(),
|
|
|
|
|
xkb::KeyDirection::Up => window
|
|
|
|
|
.try_dispatch_event(WindowEvent::KeyReleased { text })
|
|
|
|
|
.unwrap(),
|
|
|
|
|
}
|
2026-01-10 19:21:13 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-31 22:24:26 +01:00
|
|
|
|
|
|
|
|
window.draw_if_needed(|renderer| {
|
|
|
|
|
// TODO: Proper synchronization.
|
|
|
|
|
let framebuffer = unsafe { &mut *self.framebuffer.0 };
|
|
|
|
|
renderer.render(framebuffer, self.window_size[1] as usize);
|
|
|
|
|
info!("UI rendered.");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-01 01:24:52 +01:00
|
|
|
|
|
|
|
|
Ok(())
|
2025-12-31 22:24:26 +01:00
|
|
|
}
|
|
|
|
|
}
|