Use one standard Iris resource bundle

This commit is contained in:
iris committed 2026-09-11 02:09:17 -04:00
1 parent 6dbc800739
commit 7e5a4c0071
16 files changed
+129 -100

No files matched your search

+4 -8
View File
@@ -13,15 +13,11 @@ struct State {
}
impl DesktopAppState for State {
fn new(
mut ui_state: DesktopUiState,
rsc: &mut DesktopRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
let mut span = Span::empty(Dir::DOWN);
for _ in 0..ROWS {
let img = image::DynamicImage::new_rgba8(32, 32);
let widget = image::<DesktopRsc<Self>>(img)(rsc);
let widget = image::<StdRsc<Self>>(img)(rsc);
let widget = rsc.ui.widgets.add_strong(widget);
span.push(widget.any());
}
@@ -40,7 +36,7 @@ impl DesktopAppState for State {
}
}
fn window_event(&mut self, event: winit::event::WindowEvent, rsc: &mut DesktopRsc<Self>) {
fn window_event(&mut self, event: winit::event::WindowEvent, rsc: &mut StdRsc<Self>) {
if !matches!(event, winit::event::WindowEvent::RedrawRequested) {
return;
}
@@ -53,7 +49,7 @@ impl DesktopAppState for State {
if self.frame == SETTLE_FRAMES && !self.appended {
self.appended = true;
let img = image::DynamicImage::new_rgba8(32, 32);
let widget = image::<DesktopRsc<Self>>(img)(rsc);
let widget = image::<StdRsc<Self>>(img)(rsc);
let widget = rsc.ui.widgets.add_strong(widget);
rsc.ui
.widgets
+1 -5
View File
@@ -63,11 +63,7 @@ impl DesktopAppState for State {
WindowAttributes::default().with_inner_size(LogicalSize::new(420.0, 900.0))
}
fn new(
mut ui_state: DesktopUiState,
rsc: &mut DesktopRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
let mut list = LazySpan::new(Dir::DOWN, Pin::End);
for i in 0..ROWS {
let row = build_row(rsc, i);
+1 -5
View File
@@ -10,11 +10,7 @@ struct State {
}
impl DesktopAppState for State {
fn new(
mut ui_state: DesktopUiState,
rsc: &mut DesktopRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
rect(PaintId::RED).set_root(rsc, &mut ui_state);
Self { ui_state }
}
+2 -6
View File
@@ -12,11 +12,7 @@ pub struct Client {
}
impl DesktopAppState for Client {
fn new(
mut ui_state: DesktopUiState,
rsc: &mut DesktopRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
let widgets = tabs_ui::build(rsc, &mut ui_state);
Self {
ui_state,
@@ -24,7 +20,7 @@ impl DesktopAppState for Client {
}
}
fn window_event(&mut self, _: WindowEvent, rsc: &mut DesktopRsc<Self>) {
fn window_event(&mut self, _: WindowEvent, rsc: &mut StdRsc<Self>) {
let render_state = rsc.ui.render_state();
let new = format!(
"widgets: {}\nactive: {}\nviews: {}",
+1 -5
View File
@@ -11,11 +11,7 @@ struct State {
}
impl DesktopAppState for State {
fn new(
mut ui_state: DesktopUiState,
rsc: &mut DesktopRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
let rect = rect(PaintId::RED).add(rsc);
rect.task_on(CursorSense::click(), async move |mut ctx| {
tokio::time::sleep(Duration::from_secs(1)).await;
+2 -6
View File
@@ -9,7 +9,7 @@ struct State {
ui_state: DesktopUiState,
}
type Rsc = DesktopRsc<State>;
type Rsc = StdRsc<State>;
#[derive(Clone, Copy, WidgetView)]
struct Test {
@@ -36,11 +36,7 @@ impl Test {
}
impl DesktopAppState for State {
fn new(
mut ui_state: DesktopUiState,
rsc: &mut DesktopRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
let test = Test::new(rsc);
test.on(CursorSense::click(), move |_, rsc| {
+69 -14
View File
@@ -103,31 +103,72 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
#[proc_macro_derive(DesktopUiState, attributes(desktop_ui_state))]
pub fn derive_desktop_ui_state(input: TokenStream) -> TokenStream {
let mut output = proc_macro2::TokenStream::new();
let state: ItemStruct = parse_macro_input!(input);
derive_ui_state(
state,
UiStateDerive {
module: "desktop",
state_type: "DesktopUiState",
state_trait: "HasDesktopUiState",
field_attr: "desktop_ui_state",
get: "desktop_state",
get_mut: "desktop_state_mut",
},
)
}
#[proc_macro_derive(AndroidUiState, attributes(android_ui_state))]
pub fn derive_android_ui_state(input: TokenStream) -> TokenStream {
let state: ItemStruct = parse_macro_input!(input);
derive_ui_state(
state,
UiStateDerive {
module: "android",
state_type: "AndroidUiState",
state_trait: "HasAndroidUiState",
field_attr: "android_ui_state",
get: "android_state",
get_mut: "android_state_mut",
},
)
}
struct UiStateDerive {
module: &'static str,
state_type: &'static str,
state_trait: &'static str,
field_attr: &'static str,
get: &'static str,
get_mut: &'static str,
}
fn derive_ui_state(state: ItemStruct, names: UiStateDerive) -> TokenStream {
let UiStateDerive {
module,
state_type,
state_trait,
field_attr,
get,
get_mut,
} = names;
let mut output = proc_macro2::TokenStream::new();
let mut found_attr = false;
let mut state_field = None;
for field in &state.fields {
if !found_attr
&& let Type::Path(path) = &field.ty
&& path.path.is_ident("DesktopUiState")
&& path.path.is_ident(state_type)
{
state_field = Some(field);
}
let Some(attr) = field
.attrs
.iter()
.find(|a| a.path().is_ident("desktop_ui_state"))
else {
let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident(field_attr)) else {
continue;
};
if found_attr {
output.extend(
Error::new(
attr.span(),
"cannot have more than one desktop_ui_state attribute",
format!("cannot have more than one {field_attr} attribute"),
)
.into_compile_error(),
);
@@ -138,18 +179,32 @@ pub fn derive_desktop_ui_state(input: TokenStream) -> TokenStream {
}
let Some(field) = state_field else {
output.extend(
Error::new(state.ident.span(), "no DesktopUiState field found").into_compile_error(),
Error::new(state.ident.span(), format!("no {state_type} field found"))
.into_compile_error(),
);
return output.into();
};
let sname = &state.ident;
let fname = field.ident.as_ref().unwrap();
let Some(fname) = field.ident.as_ref() else {
return Error::new(
field.span(),
format!("the {state_type} field must be named"),
)
.into_compile_error()
.into();
};
let module = Ident::new(module, sname.span());
let state_type = Ident::new(state_type, sname.span());
let state_trait = Ident::new(state_trait, sname.span());
let get = Ident::new(get, sname.span());
let get_mut = Ident::new(get_mut, sname.span());
let (impl_generics, type_generics, where_clause) = state.generics.split_for_impl();
output.extend(quote! {
impl iris::desktop::HasDesktopUiState for #sname {
fn desktop_state(&self) -> &iris::desktop::DesktopUiState {
impl #impl_generics iris::#module::#state_trait for #sname #type_generics #where_clause {
fn #get(&self) -> &iris::#module::#state_type {
&self.#fname
}
fn desktop_state_mut(&mut self) -> &mut iris::desktop::DesktopUiState {
fn #get_mut(&mut self) -> &mut iris::#module::#state_type {
&mut self.#fname
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
use crate::prelude::*;
use android_view::{jni::JNIEnv, ndk::event::Keycode};
use super::view::{AndroidAppState, AndroidRsc};
use super::view::AndroidAppState;
/// Hardware/synthesized key handling for the field that currently has
/// focus. Most typing on Android goes through the IME's `InputConnection`
@@ -10,7 +10,7 @@ use super::view::{AndroidAppState, AndroidRsc};
/// the arrow keys on a physical keyboard) plus whatever `unicode_char`
/// reports for a plain key press. Returns whether anything used the event.
pub(super) fn on_key<'local, State: AndroidAppState>(
rsc: &mut AndroidRsc<State>,
rsc: &mut StdRsc<State>,
state: &mut State,
env: &mut JNIEnv<'local>,
key_code: Keycode,
+1 -2
View File
@@ -10,8 +10,7 @@ mod view;
pub use insets::Insets;
pub use render::AndroidRenderer;
pub use view::{
AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState, IrisViewPeer, WindowInsets,
new_peer,
AndroidAppState, AndroidUiState, HasAndroidUiState, IrisViewPeer, WindowInsets, new_peer,
};
/// Registers the extra native methods this backend needs beyond what
+9 -11
View File
@@ -89,8 +89,8 @@ impl AndroidUiState {
}
}
impl<State: 'static> HasRoot<AndroidRsc<State>> for AndroidUiState {
fn set_root(&mut self, rsc: &mut AndroidRsc<State>, root: StrongWidget) {
impl<State: 'static> HasRoot<StdRsc<State>> for AndroidUiState {
fn set_root(&mut self, rsc: &mut StdRsc<State>, root: StrongWidget) {
self.root = Some(crate::overlay::default_overlay_root(rsc, root));
}
}
@@ -101,15 +101,15 @@ pub trait HasAndroidUiState: Sized + 'static {
}
pub trait AndroidAppState: HasAndroidUiState {
fn new(ui_state: AndroidUiState, rsc: &mut AndroidRsc<Self>) -> Self;
fn new(ui_state: AndroidUiState, rsc: &mut StdRsc<Self>) -> Self;
#[allow(unused_variables)]
fn back_pressed(&mut self, rsc: &mut AndroidRsc<Self>) -> bool {
fn back_pressed(&mut self, rsc: &mut StdRsc<Self>) -> bool {
false
}
#[allow(unused_variables)]
fn platform_ready(&mut self, rsc: &mut AndroidRsc<Self>, vm: JavaVM, view: GlobalRef) {}
fn platform_ready(&mut self, rsc: &mut StdRsc<Self>, vm: JavaVM, view: GlobalRef) {}
#[allow(unused_variables)]
fn on_insets_changed(&mut self, rsc: &mut AndroidRsc<Self>, insets: WindowInsets) {}
fn on_insets_changed(&mut self, rsc: &mut StdRsc<Self>, insets: WindowInsets) {}
}
/// Widget-facing insets in physical pixels, decoupled from JNI's integer shape.
@@ -140,15 +140,13 @@ impl WindowInsets {
}
}
pub type AndroidRsc<State> = AppRsc<State>;
/// The `ViewPeer` android-view dispatches every callback to. One per
/// `RustView` instance; `new_peer` (below) builds it and hands the id to
/// Java the same way android-view's own demo does.
pub struct IrisViewPeer<State: AndroidAppState> {
pub(super) rsc: AndroidRsc<State>,
pub(super) rsc: StdRsc<State>,
pub(super) state: State,
task_recv: TaskMsgReceiver<AndroidRsc<State>>,
task_recv: TaskMsgReceiver<StdRsc<State>>,
/// Converts input and Choreographer timestamps onto one monotonic clock.
device_clock: Option<DeviceClock>,
}
@@ -812,7 +810,7 @@ pub fn new_peer<'local, State: AndroidAppState>(
let vm = env.get_java_vm().unwrap();
let global_view = env.new_global_ref(&view.0).unwrap();
let redraw: Arc<dyn RequestRedraw> = Arc::new(AndroidRedrawHandle::new(vm, global_view));
let (mut rsc, task_recv) = AppRsc::new(redraw);
let (mut rsc, task_recv) = StdRsc::new(redraw);
rsc.ui.set_density(content_scale);
let shared = Rc::new(RefCell::new(Shared::default()));
let ui_state = AndroidUiState::new(shared.clone(), content_scale);
+9 -12
View File
@@ -50,8 +50,8 @@ pub struct DesktopUiState {
pub access: AccessTree,
}
impl<State: 'static> HasRoot<DesktopRsc<State>> for DesktopUiState {
fn set_root(&mut self, rsc: &mut DesktopRsc<State>, root: StrongWidget) {
impl<State: 'static> HasRoot<StdRsc<State>> for DesktopUiState {
fn set_root(&mut self, rsc: &mut StdRsc<State>, root: StrongWidget) {
self.root = Some(crate::overlay::default_overlay_root(rsc, root));
}
}
@@ -81,25 +81,22 @@ pub trait HasDesktopUiState: Sized + 'static {
pub trait DesktopAppState: HasDesktopUiState {
type Event = ();
fn new(ui_state: DesktopUiState, rsc: &mut DesktopRsc<Self>, proxy: Proxy<Self::Event>)
-> Self;
fn new(ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, proxy: Proxy<Self::Event>) -> Self;
#[allow(unused_variables)]
fn event(&mut self, event: Self::Event, rsc: &mut DesktopRsc<Self>) {}
fn event(&mut self, event: Self::Event, rsc: &mut StdRsc<Self>) {}
#[allow(unused_variables)]
fn exit(&mut self, rsc: &mut DesktopRsc<Self>) {}
fn exit(&mut self, rsc: &mut StdRsc<Self>) {}
#[allow(unused_variables)]
fn window_event(&mut self, event: WindowEvent, rsc: &mut DesktopRsc<Self>) {}
fn window_event(&mut self, event: WindowEvent, rsc: &mut StdRsc<Self>) {}
fn window_attributes() -> WindowAttributes {
Default::default()
}
}
pub type DesktopRsc<State> = AppRsc<State>;
pub struct DesktopApp<State: DesktopAppState> {
rsc: DesktopRsc<State>,
rsc: StdRsc<State>,
state: State,
task_recv: TaskMsgReceiver<DesktopRsc<State>>,
task_recv: TaskMsgReceiver<StdRsc<State>>,
}
impl<State: DesktopAppState> AppState for DesktopApp<State> {
@@ -118,7 +115,7 @@ impl<State: DesktopAppState> AppState for DesktopApp<State> {
);
window.set_visible(true);
let desktop_state = DesktopUiState::new(window, access_adapter);
let (mut rsc, task_recv) = AppRsc::new(desktop_state.window.clone());
let (mut rsc, task_recv) = StdRsc::new(desktop_state.window.clone());
// Set before building widgets so the first text shape uses the right density.
let scale = content_scale(desktop_state.window.as_ref());
rsc.ui.set_density(scale);
+5 -7
View File
@@ -134,8 +134,8 @@ impl HarnessState {
}
}
impl HasRoot<HarnessRsc> for HarnessState {
fn set_root(&mut self, rsc: &mut HarnessRsc, root: StrongWidget) {
impl HasRoot<StdRsc<HarnessState>> for HarnessState {
fn set_root(&mut self, rsc: &mut StdRsc<HarnessState>, root: StrongWidget) {
self.root = Some(crate::overlay::default_overlay_root(rsc, root));
}
}
@@ -163,14 +163,12 @@ impl OpenUrl for HarnessState {
}
}
pub type HarnessRsc = AppRsc<HarnessState>;
/// A screen running with no window: the widget tree, the frame loop and
/// the pointer, all advanced by the caller. See the module doc.
pub struct Harness {
pub rsc: HarnessRsc,
pub rsc: StdRsc<HarnessState>,
pub state: HarnessState,
task_recv: TaskMsgReceiver<HarnessRsc>,
task_recv: TaskMsgReceiver<StdRsc<HarnessState>>,
redraws: Arc<RedrawCounter>,
cursor: CursorState,
base: Instant,
@@ -185,7 +183,7 @@ impl Harness {
/// `PHONE_SCALE`.
pub fn new(size: Vec2, density: f32) -> Self {
let redraws = Arc::new(RedrawCounter::default());
let (mut rsc, task_recv) = AppRsc::new(redraws.clone());
let (mut rsc, task_recv) = StdRsc::new(redraws.clone());
rsc.ui.set_density(density);
rsc.ui.resize(size);
Self {
+14 -11
View File
@@ -1,4 +1,4 @@
//! Host-independent resources carried by [`AppRsc`].
//! Host-independent resources carried by [`StdRsc`].
pub mod attr;
pub mod event;
@@ -20,8 +20,11 @@ pub use task::*;
use crate::prelude::*;
use std::sync::Arc;
/// Resources shared by every Iris host.
pub struct AppRsc<State: 'static> {
/// Iris's standard resource bundle.
///
/// Built-in hosts use this bundle, but widgets should depend on the narrow
/// resource traits they need so applications can supply a different bundle.
pub struct StdRsc<State: 'static> {
pub ui: Ui,
pub events: EventManager<Self>,
pub tasks: Tasks<Self>,
@@ -29,7 +32,7 @@ pub struct AppRsc<State: 'static> {
_state: std::marker::PhantomData<State>,
}
impl<State> AppRsc<State> {
impl<State> StdRsc<State> {
pub(crate) fn new(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Self>) {
let (tasks, receiver) = Tasks::init(redraw);
(
@@ -49,7 +52,7 @@ impl<State> AppRsc<State> {
}
}
impl<State> UiRsc for AppRsc<State> {
impl<State> UiRsc for StdRsc<State> {
fn ui(&self) -> &Ui {
&self.ui
}
@@ -72,11 +75,11 @@ impl<State> UiRsc for AppRsc<State> {
}
}
impl<State> HasState for AppRsc<State> {
impl<State> HasState for StdRsc<State> {
type State = State;
}
impl<State> HasEvents for AppRsc<State> {
impl<State> HasEvents for StdRsc<State> {
fn events(&self) -> &EventManager<Self> {
&self.events
}
@@ -86,13 +89,13 @@ impl<State> HasEvents for AppRsc<State> {
}
}
impl<State> HasTasks for AppRsc<State> {
impl<State> HasTasks for StdRsc<State> {
fn tasks_mut(&mut self) -> &mut Tasks<Self> {
&mut self.tasks
}
}
impl<State> HasWidgetState for AppRsc<State> {
impl<State> HasWidgetState for StdRsc<State> {
fn widget_state(&self) -> &WidgetState {
&self.state
}
@@ -102,7 +105,7 @@ impl<State> HasWidgetState for AppRsc<State> {
}
}
impl<State, I: RscIdx<AppRsc<State>>> std::ops::Index<I> for AppRsc<State> {
impl<State, I: RscIdx<StdRsc<State>>> std::ops::Index<I> for StdRsc<State> {
type Output = I::Output;
fn index(&self, index: I) -> &Self::Output {
@@ -110,7 +113,7 @@ impl<State, I: RscIdx<AppRsc<State>>> std::ops::Index<I> for AppRsc<State> {
}
}
impl<State, I: RscIdx<AppRsc<State>>> std::ops::IndexMut<I> for AppRsc<State> {
impl<State, I: RscIdx<StdRsc<State>>> std::ops::IndexMut<I> for StdRsc<State> {
fn index_mut(&mut self, index: I) -> &mut Self::Output {
index.get_mut(self)
}
+3 -3
View File
@@ -525,13 +525,13 @@ fn open_stackable_at<Rsc: HasEvents>(
#[cfg(test)]
mod tests {
use super::*;
use crate::harness::{Harness, HarnessRsc, TouchAction};
use crate::harness::{Harness, HarnessState, TouchAction};
use std::{cell::Cell, rc::Rc};
struct EscapeCounter(Rc<Cell<usize>>);
impl Controller<HarnessRsc> for EscapeCounter {
fn command(&mut self, command: Command, _rsc: &mut HarnessRsc) -> CommandResult {
impl Controller<StdRsc<HarnessState>> for EscapeCounter {
fn command(&mut self, command: Command, _rsc: &mut StdRsc<HarnessState>) -> CommandResult {
if command == Command::Escape {
self.0.set(self.0.get() + 1);
CommandResult::Used
+1 -1
View File
@@ -78,7 +78,7 @@ impl<'a, T: 'static> FnOnce<(&'a mut WidgetState,)> for WeakState<T> {
/// What `Rsc[weak_handle]` indexes through -- one impl per kind of handle
/// (a widget, a piece of per-widget state), shared by both backends' `Rsc`
/// types since indexing a widget tree has nothing to do with windowing.
/// `AppRsc` supplies the shared `Index`/`IndexMut` implementation; a blanket
/// `StdRsc` supplies the shared `Index`/`IndexMut` implementation; a blanket
/// implementation over every possible resource type would conflict.
pub trait RscIdx<Rsc> {
type Output;
+5 -2
View File
@@ -1,6 +1,9 @@
#![recursion_limit = "256"]
use iris::{harness::Harness, prelude::*};
use iris::{
harness::{Harness, HarnessState},
prelude::*,
};
use pollster::FutureExt;
use std::sync::OnceLock;
use wgpu::TextureFormat;
@@ -25,7 +28,7 @@ fn solid_paints_and_images_round_trip_through_an_srgb_target() {
1,
image::Rgba([IMAGE.r, IMAGE.g, IMAGE.b, IMAGE.a]),
));
let bitmap = image::<iris::harness::HarnessRsc>(bitmap)(&mut harness.rsc);
let bitmap = image::<StdRsc<HarnessState>>(bitmap)(&mut harness.rsc);
let root = (rect(solid.clone()).sized((1, 1)), bitmap)
.span(Dir::RIGHT)
.add_strong(&mut harness.rsc)