Functional parity with linux

This commit is contained in:
Will Temple
2026-05-15 13:31:59 -07:00
parent 861bf63621
commit 67400f1499
17 changed files with 2221 additions and 458 deletions

View File

@@ -15,8 +15,8 @@ use std::marker::PhantomData;
use std::rc::Rc;
use std::time::Instant;
use ruin_reactivity::effect;
use ruin_runtime::queue_future;
use ruin_reactivity::{current as current_reactor, effect};
use ruin_runtime::{queue_future, spawn_worker};
use ruin_ui::{
Border, Color, CursorIcon, DisplayItem, Edges, Element, ElementId, HitTarget, InteractionTree,
KeyboardEvent, KeyboardEventKind, KeyboardKey, LayoutCache, LayoutSnapshot, PlatformEvent,
@@ -26,7 +26,7 @@ use ruin_ui::{
layout_snapshot_with_cache,
};
#[cfg(target_os = "macos")]
use ruin_ui_platform_macos::start_macos_ui;
use ruin_ui_platform_macos::{run_macos_app_event_loop, start_macos_ui};
#[cfg(target_os = "linux")]
use ruin_ui_platform_wayland::start_wayland_ui;
@@ -98,8 +98,7 @@ impl App {
pub fn mount<M: Mountable>(self, root: M) -> MountedApp<M> {
MountedApp {
window: self.window,
root: Rc::new(root),
render_state: Rc::new(RenderState::default()),
root,
}
}
}
@@ -150,19 +149,18 @@ impl Mountable for Element {
pub struct MountedApp<M: Mountable> {
window: Window,
root: Rc<M>,
render_state: Rc<RenderState>,
root: M,
}
impl<M: Mountable> MountedApp<M> {
pub async fn run(self) -> Result<()> {
let MountedApp {
window: app_window,
root,
render_state,
} = self;
let mut ui = start_platform_ui();
async fn run_with_ui_runtime(
window: Window,
root: M,
mut ui: ruin_ui::UiRuntime,
) -> Result<()> {
let root = Rc::new(root);
let render_state = Rc::new(RenderState::default());
let app_window = window;
let window = ui.create_window(app_window.spec.clone())?;
let initial_viewport = app_window
.spec
@@ -170,6 +168,7 @@ impl<M: Mountable> MountedApp<M> {
.unwrap_or(UiSize::new(960.0, 640.0));
let viewport = ruin_reactivity::cell(initial_viewport);
let configure_serial = ruin_reactivity::cell(0_u64);
let scene_version = StdCell::new(0_u64);
let text_system = Rc::new(RefCell::new(TextSystem::new()));
let layout_cache = Rc::new(RefCell::new(LayoutCache::new()));
@@ -183,6 +182,7 @@ impl<M: Mountable> MountedApp<M> {
let _scene_effect = effect({
let window = window.clone();
let viewport = viewport.clone();
let configure_serial = configure_serial.clone();
let text_system = Rc::clone(&text_system);
let layout_cache = Rc::clone(&layout_cache);
let interaction_tree = Rc::clone(&interaction_tree);
@@ -194,6 +194,7 @@ impl<M: Mountable> MountedApp<M> {
let text_selection = Rc::clone(&input_state.text_selection);
move || {
let viewport = viewport.get();
let _ = configure_serial.get();
let version = scene_version.get().wrapping_add(1);
scene_version.set(version);
let _ = text_selection.version.get();
@@ -266,6 +267,8 @@ impl<M: Mountable> MountedApp<M> {
"app received Configured, queuing layout effect"
);
let _ = viewport.set(configuration.actual_inner_size);
configure_serial.update(|value| *value = value.wrapping_add(1));
current_reactor().flush_now();
}
PlatformEvent::Pointer { window_id, event } if window_id == window.id() => {
Self::handle_pointer_event(
@@ -276,6 +279,7 @@ impl<M: Mountable> MountedApp<M> {
&mut input_state,
event,
)?;
current_reactor().flush_now();
}
PlatformEvent::Keyboard { window_id, event } if window_id == window.id() => {
Self::handle_keyboard_event(
@@ -285,11 +289,22 @@ impl<M: Mountable> MountedApp<M> {
&input_state,
event,
)?;
current_reactor().flush_now();
}
PlatformEvent::CloseRequested { window_id } if window_id == window.id() => {
tracing::debug!(
target: "ruin_app::lifecycle",
?window_id,
"app received CloseRequested, issuing open(false)"
);
let _ = window.update(WindowUpdate::new().open(false));
}
PlatformEvent::Closed { window_id } if window_id == window.id() => {
tracing::debug!(
target: "ruin_app::lifecycle",
?window_id,
"app received Closed, shutting down ui runtime"
);
ui.shutdown()?;
return Ok(());
}
@@ -301,6 +316,14 @@ impl<M: Mountable> MountedApp<M> {
ui.shutdown()?;
Ok(())
}
}
impl<M: Mountable> MountedApp<M> {
#[cfg(not(target_os = "macos"))]
pub async fn run(self) -> Result<()> {
let ui = start_platform_ui();
Self::run_with_ui_runtime(self.window, self.root, ui).await
}
fn handle_pointer_event(
window: &WindowController,
@@ -453,13 +476,46 @@ impl<M: Mountable> MountedApp<M> {
if selection_changed {
text_selection.version.update(|value| *value += 1);
#[cfg(target_os = "linux")]
sync_primary_selection(_window, interaction_tree, *text_selection.selection.borrow())?;
sync_primary_selection(
_window,
interaction_tree,
*text_selection.selection.borrow(),
)?;
}
Ok(())
}
}
#[cfg(target_os = "macos")]
impl<M: Mountable + Send + 'static> MountedApp<M> {
pub async fn run(self) -> Result<()> {
let ui = start_platform_ui();
let (done_tx, done_rx) = std::sync::mpsc::channel::<std::result::Result<(), String>>();
let window = self.window;
let root = self.root;
let _worker = spawn_worker(
move || {
queue_future(async move {
let result = Self::run_with_ui_runtime(window, root, ui)
.await
.map_err(|err| err.to_string());
let _ = done_tx.send(result);
});
},
|| {},
);
run_macos_app_event_loop();
match done_rx.recv() {
Ok(Ok(())) => Ok(()),
Ok(Err(err)) => Err(err.into()),
Err(_) => Err("ui worker terminated before completion".into()),
}
}
}
#[derive(Clone, Default)]
pub struct View {
element: Element,
@@ -1307,6 +1363,10 @@ pub fn use_effect(effect: impl Fn() + 'static) {
with_hook_slot(|| ruin_reactivity::effect(effect), |_| ());
}
pub fn flush_reactive_updates() {
current_reactor().flush_now();
}
pub fn use_window_title(compute: impl FnOnce() -> String) {
with_render_context_state(|context| {
context.side_effects.borrow_mut().window_title = Some(compute());
@@ -1520,6 +1580,7 @@ where
let result = future.await;
if generation.get() == next_generation {
let _ = resource.state.replace(ResourceState::Ready(result));
current_reactor().flush_now();
}
}));
}
@@ -1997,10 +2058,10 @@ pub mod prelude {
ContextKey, FocusScope, FontWeight, IntoBorder, IntoEdges, IntoView, Key, Memo, Mountable,
Pending, Ready, Resource, ResourceState, Result, ScrollBoxBuilder, ScrollBoxWidget,
Shortcut, ShortcutScope, Signal, TextBuilder, TextChildren, TextRole, TextValue, View,
WidgetRef, Window, block, button, colors, column, component, context_provider, provide,
row, scroll_box, surfaces, text, use_context, use_effect, use_memo, use_resource,
use_shortcut, use_shortcut_with_context, use_signal, use_widget_ref, use_window_title,
view,
WidgetRef, Window, block, button, colors, column, component, context_provider,
flush_reactive_updates, provide, row, scroll_box, surfaces, text, use_context, use_effect,
use_memo, use_resource, use_shortcut, use_shortcut_with_context, use_signal,
use_widget_ref, use_window_title, view,
};
pub use ruin_ui::{
Border, Color, CursorIcon, Edges, Element, ElementId, InteractionTree, PointerButton,