More text improvements, performance enhancements, input handling, text selection, wl cursors

This commit is contained in:
2026-03-20 22:24:29 -04:00
parent d79a3bb728
commit 423df4ae1f
15 changed files with 2458 additions and 265 deletions

View File

@@ -14,11 +14,12 @@ use tracing::{debug, info};
use crate::interaction::PointerEvent;
use crate::scene::{SceneSnapshot, UiSize};
use crate::trace_targets;
use crate::tree::CursorIcon;
use crate::window::{WindowConfigured, WindowId, WindowLifecycle, WindowSpec, WindowUpdate};
#[derive(Clone)]
pub struct PlatformProxy {
command_tx: mpsc::UnboundedSender<PlatformCommand>,
command_tx: mpsc::UnboundedSender<PlatformRequest>,
next_window_id: Arc<AtomicU64>,
}
@@ -33,6 +34,11 @@ pub struct PlatformRuntime {
_worker: WorkerHandle,
}
pub struct PlatformEndpoint {
pub commands: mpsc::Receiver<PlatformRequest>,
pub events: mpsc::UnboundedSender<PlatformEvent>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum PlatformEvent {
Opened {
@@ -75,7 +81,7 @@ impl fmt::Display for PlatformClosed {
impl std::error::Error for PlatformClosed {}
#[derive(Clone, Debug)]
enum PlatformCommand {
pub enum PlatformRequest {
CreateWindow {
window_id: WindowId,
spec: WindowSpec,
@@ -88,9 +94,21 @@ enum PlatformCommand {
window_id: WindowId,
scene: SceneSnapshot,
},
SetPrimarySelectionText {
window_id: WindowId,
text: String,
},
SetCursorIcon {
window_id: WindowId,
cursor: CursorIcon,
},
EmitCloseRequested {
window_id: WindowId,
},
EmitPointerEvent {
window_id: WindowId,
event: PointerEvent,
},
Shutdown,
}
@@ -135,6 +153,25 @@ impl PlatformRuntime {
start_headless()
}
pub fn custom(start: impl FnOnce(PlatformEndpoint) -> WorkerHandle) -> Self {
let (command_tx, command_rx) = mpsc::unbounded_channel::<PlatformRequest>();
let (event_tx, event_rx) = mpsc::unbounded_channel::<PlatformEvent>();
let proxy = PlatformProxy {
command_tx,
next_window_id: Arc::new(AtomicU64::new(1)),
};
let worker = start(PlatformEndpoint {
commands: command_rx,
events: event_tx,
});
Self {
proxy,
events: event_rx,
_worker: worker,
}
}
pub fn proxy(&self) -> PlatformProxy {
self.proxy.clone()
}
@@ -142,12 +179,20 @@ impl PlatformRuntime {
pub async fn next_event(&mut self) -> Option<PlatformEvent> {
self.events.recv().await
}
pub fn take_pending_events(&mut self) -> Vec<PlatformEvent> {
let mut events = Vec::new();
while let Ok(event) = self.events.try_recv() {
events.push(event);
}
events
}
}
impl PlatformProxy {
pub fn create_window(&self, spec: WindowSpec) -> Result<WindowId, PlatformClosed> {
let window_id = WindowId::from_raw(self.next_window_id.fetch_add(1, Ordering::Relaxed));
self.send(PlatformCommand::CreateWindow { window_id, spec })?;
self.send(PlatformRequest::CreateWindow { window_id, spec })?;
Ok(window_id)
}
@@ -156,7 +201,7 @@ impl PlatformProxy {
window_id: WindowId,
update: WindowUpdate,
) -> Result<(), PlatformClosed> {
self.send(PlatformCommand::UpdateWindow { window_id, update })
self.send(PlatformRequest::UpdateWindow { window_id, update })
}
pub fn replace_scene(
@@ -164,18 +209,45 @@ impl PlatformProxy {
window_id: WindowId,
scene: SceneSnapshot,
) -> Result<(), PlatformClosed> {
self.send(PlatformCommand::ReplaceScene { window_id, scene })
self.send(PlatformRequest::ReplaceScene { window_id, scene })
}
pub fn set_primary_selection_text(
&self,
window_id: WindowId,
text: impl Into<String>,
) -> Result<(), PlatformClosed> {
self.send(PlatformRequest::SetPrimarySelectionText {
window_id,
text: text.into(),
})
}
pub fn set_cursor_icon(
&self,
window_id: WindowId,
cursor: CursorIcon,
) -> Result<(), PlatformClosed> {
self.send(PlatformRequest::SetCursorIcon { window_id, cursor })
}
pub fn emit_close_requested(&self, window_id: WindowId) -> Result<(), PlatformClosed> {
self.send(PlatformCommand::EmitCloseRequested { window_id })
self.send(PlatformRequest::EmitCloseRequested { window_id })
}
pub fn emit_pointer_event(
&self,
window_id: WindowId,
event: PointerEvent,
) -> Result<(), PlatformClosed> {
self.send(PlatformRequest::EmitPointerEvent { window_id, event })
}
pub fn shutdown(&self) -> Result<(), PlatformClosed> {
self.send(PlatformCommand::Shutdown)
self.send(PlatformRequest::Shutdown)
}
fn send(&self, command: PlatformCommand) -> Result<(), PlatformClosed> {
fn send(&self, command: PlatformRequest) -> Result<(), PlatformClosed> {
self.command_tx.send(command).map_err(|_| PlatformClosed)
}
}
@@ -186,69 +258,64 @@ impl PlatformProxy {
/// scene for that window even if the scene version itself did not change. Visibility restoration is
/// treated as a presentation-affecting state change.
pub fn start_headless() -> PlatformRuntime {
let (command_tx, mut command_rx) = mpsc::unbounded_channel::<PlatformCommand>();
let (event_tx, event_rx) = mpsc::unbounded_channel::<PlatformEvent>();
PlatformRuntime::custom(|mut endpoint| {
spawn_worker(
move || {
let state = Rc::new(RefCell::new(HeadlessState {
events: endpoint.events.clone(),
windows: BTreeMap::new(),
}));
let worker = spawn_worker(
move || {
let state = Rc::new(RefCell::new(HeadlessState {
events: event_tx.clone(),
windows: BTreeMap::new(),
}));
queue_future(async move {
debug!(
target: trace_targets::PLATFORM,
event = "worker_started",
backend = "headless",
"starting headless platform worker"
);
queue_future(async move {
debug!(
target: trace_targets::PLATFORM,
event = "worker_started",
backend = "headless",
"starting headless platform worker"
);
while let Some(command) = command_rx.recv().await {
match command {
PlatformCommand::CreateWindow { window_id, spec } => {
handle_create_window(&state, window_id, spec);
}
PlatformCommand::UpdateWindow { window_id, update } => {
handle_update_window(&state, window_id, update);
}
PlatformCommand::ReplaceScene { window_id, scene } => {
handle_replace_scene(&state, window_id, scene);
}
PlatformCommand::EmitCloseRequested { window_id } => {
let sender = state.borrow().events.clone();
let _ = sender.send(PlatformEvent::CloseRequested { window_id });
}
PlatformCommand::Shutdown => {
debug!(
target: trace_targets::PLATFORM,
event = "worker_shutdown_requested",
"shutting down headless platform worker"
);
break;
while let Some(command) = endpoint.commands.recv().await {
match command {
PlatformRequest::CreateWindow { window_id, spec } => {
handle_create_window(&state, window_id, spec);
}
PlatformRequest::UpdateWindow { window_id, update } => {
handle_update_window(&state, window_id, update);
}
PlatformRequest::ReplaceScene { window_id, scene } => {
handle_replace_scene(&state, window_id, scene);
}
PlatformRequest::SetPrimarySelectionText { .. } => {}
PlatformRequest::SetCursorIcon { .. } => {}
PlatformRequest::EmitCloseRequested { window_id } => {
let sender = state.borrow().events.clone();
let _ = sender.send(PlatformEvent::CloseRequested { window_id });
}
PlatformRequest::EmitPointerEvent { window_id, event } => {
handle_emit_pointer_event(&state, window_id, event);
}
PlatformRequest::Shutdown => {
debug!(
target: trace_targets::PLATFORM,
event = "worker_shutdown_requested",
"shutting down headless platform worker"
);
break;
}
}
}
}
});
},
|| {
debug!(
target: trace_targets::PLATFORM,
event = "worker_exited",
backend = "headless",
"headless platform worker exited"
);
},
);
PlatformRuntime {
proxy: PlatformProxy {
command_tx,
next_window_id: Arc::new(AtomicU64::new(1)),
},
events: event_rx,
_worker: worker,
}
});
},
|| {
debug!(
target: trace_targets::PLATFORM,
event = "worker_exited",
backend = "headless",
"headless platform worker exited"
);
},
)
})
}
fn handle_create_window(state: &Rc<RefCell<HeadlessState>>, window_id: WindowId, spec: WindowSpec) {
@@ -450,6 +517,20 @@ fn set_visibility(state: &Rc<RefCell<HeadlessState>>, window_id: WindowId, visib
}
}
fn handle_emit_pointer_event(
state: &Rc<RefCell<HeadlessState>>,
window_id: WindowId,
event: PointerEvent,
) {
let should_emit = matches!(
state.borrow().windows.get(&window_id),
Some(window) if window.lifecycle == WindowLifecycle::OpenVisible
);
if should_emit {
emit_event(state, PlatformEvent::Pointer { window_id, event });
}
}
fn present_latest_scene(state: &Rc<RefCell<HeadlessState>>, window_id: WindowId) {
let presentation = {
let mut state_ref = state.borrow_mut();