diff --git a/examples/bench_images.rs b/examples/bench_images.rs index db4b0b5..4754fe2 100644 --- a/examples/bench_images.rs +++ b/examples/bench_images.rs @@ -13,15 +13,11 @@ struct State { } impl DesktopAppState for State { - fn new( - mut ui_state: DesktopUiState, - rsc: &mut DesktopRsc, - _: Proxy, - ) -> Self { + fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc, _: Proxy) -> Self { let mut span = Span::empty(Dir::DOWN); for _ in 0..ROWS { let img = image::DynamicImage::new_rgba8(32, 32); - let widget = image::>(img)(rsc); + let widget = image::>(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) { + fn window_event(&mut self, event: winit::event::WindowEvent, rsc: &mut StdRsc) { 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::>(img)(rsc); + let widget = image::>(img)(rsc); let widget = rsc.ui.widgets.add_strong(widget); rsc.ui .widgets diff --git a/examples/message_list.rs b/examples/message_list.rs index 73712c1..0a6470b 100644 --- a/examples/message_list.rs +++ b/examples/message_list.rs @@ -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, - _: Proxy, - ) -> Self { + fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc, _: Proxy) -> Self { let mut list = LazySpan::new(Dir::DOWN, Pin::End); for i in 0..ROWS { let row = build_row(rsc, i); diff --git a/examples/minimal.rs b/examples/minimal.rs index 50b7807..579ae5a 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -10,11 +10,7 @@ struct State { } impl DesktopAppState for State { - fn new( - mut ui_state: DesktopUiState, - rsc: &mut DesktopRsc, - _: Proxy, - ) -> Self { + fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc, _: Proxy) -> Self { rect(PaintId::RED).set_root(rsc, &mut ui_state); Self { ui_state } } diff --git a/examples/tabs/main.rs b/examples/tabs/main.rs index 0732297..151168c 100644 --- a/examples/tabs/main.rs +++ b/examples/tabs/main.rs @@ -12,11 +12,7 @@ pub struct Client { } impl DesktopAppState for Client { - fn new( - mut ui_state: DesktopUiState, - rsc: &mut DesktopRsc, - _: Proxy, - ) -> Self { + fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc, _: Proxy) -> 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) { + fn window_event(&mut self, _: WindowEvent, rsc: &mut StdRsc) { let render_state = rsc.ui.render_state(); let new = format!( "widgets: {}\nactive: {}\nviews: {}", diff --git a/examples/task.rs b/examples/task.rs index 7947832..f0d0a3c 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -11,11 +11,7 @@ struct State { } impl DesktopAppState for State { - fn new( - mut ui_state: DesktopUiState, - rsc: &mut DesktopRsc, - _: Proxy, - ) -> Self { + fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc, _: Proxy) -> 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; diff --git a/examples/view.rs b/examples/view.rs index 7a4fbc5..396344c 100644 --- a/examples/view.rs +++ b/examples/view.rs @@ -9,7 +9,7 @@ struct State { ui_state: DesktopUiState, } -type Rsc = DesktopRsc; +type Rsc = StdRsc; #[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, - _: Proxy, - ) -> Self { + fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc, _: Proxy) -> Self { let test = Test::new(rsc); test.on(CursorSense::click(), move |_, rsc| { diff --git a/macro/src/lib.rs b/macro/src/lib.rs index bdc335e..07f8d17 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -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 } } diff --git a/src/android/input.rs b/src/android/input.rs index 963675e..fd507bf 100644 --- a/src/android/input.rs +++ b/src/android/input.rs @@ -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, + rsc: &mut StdRsc, state: &mut State, env: &mut JNIEnv<'local>, key_code: Keycode, diff --git a/src/android/mod.rs b/src/android/mod.rs index 10965fa..d37c7e7 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -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 diff --git a/src/android/view.rs b/src/android/view.rs index dc65117..7235a68 100644 --- a/src/android/view.rs +++ b/src/android/view.rs @@ -89,8 +89,8 @@ impl AndroidUiState { } } -impl HasRoot> for AndroidUiState { - fn set_root(&mut self, rsc: &mut AndroidRsc, root: StrongWidget) { +impl HasRoot> for AndroidUiState { + fn set_root(&mut self, rsc: &mut StdRsc, 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; + fn new(ui_state: AndroidUiState, rsc: &mut StdRsc) -> Self; #[allow(unused_variables)] - fn back_pressed(&mut self, rsc: &mut AndroidRsc) -> bool { + fn back_pressed(&mut self, rsc: &mut StdRsc) -> bool { false } #[allow(unused_variables)] - fn platform_ready(&mut self, rsc: &mut AndroidRsc, vm: JavaVM, view: GlobalRef) {} + fn platform_ready(&mut self, rsc: &mut StdRsc, vm: JavaVM, view: GlobalRef) {} #[allow(unused_variables)] - fn on_insets_changed(&mut self, rsc: &mut AndroidRsc, insets: WindowInsets) {} + fn on_insets_changed(&mut self, rsc: &mut StdRsc, insets: WindowInsets) {} } /// Widget-facing insets in physical pixels, decoupled from JNI's integer shape. @@ -140,15 +140,13 @@ impl WindowInsets { } } -pub type AndroidRsc = AppRsc; - /// 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 { - pub(super) rsc: AndroidRsc, + pub(super) rsc: StdRsc, pub(super) state: State, - task_recv: TaskMsgReceiver>, + task_recv: TaskMsgReceiver>, /// Converts input and Choreographer timestamps onto one monotonic clock. device_clock: Option, } @@ -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 = 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); diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index 62b2808..de6b239 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -50,8 +50,8 @@ pub struct DesktopUiState { pub access: AccessTree, } -impl HasRoot> for DesktopUiState { - fn set_root(&mut self, rsc: &mut DesktopRsc, root: StrongWidget) { +impl HasRoot> for DesktopUiState { + fn set_root(&mut self, rsc: &mut StdRsc, 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, proxy: Proxy) - -> Self; + fn new(ui_state: DesktopUiState, rsc: &mut StdRsc, proxy: Proxy) -> Self; #[allow(unused_variables)] - fn event(&mut self, event: Self::Event, rsc: &mut DesktopRsc) {} + fn event(&mut self, event: Self::Event, rsc: &mut StdRsc) {} #[allow(unused_variables)] - fn exit(&mut self, rsc: &mut DesktopRsc) {} + fn exit(&mut self, rsc: &mut StdRsc) {} #[allow(unused_variables)] - fn window_event(&mut self, event: WindowEvent, rsc: &mut DesktopRsc) {} + fn window_event(&mut self, event: WindowEvent, rsc: &mut StdRsc) {} fn window_attributes() -> WindowAttributes { Default::default() } } -pub type DesktopRsc = AppRsc; - pub struct DesktopApp { - rsc: DesktopRsc, + rsc: StdRsc, state: State, - task_recv: TaskMsgReceiver>, + task_recv: TaskMsgReceiver>, } impl AppState for DesktopApp { @@ -118,7 +115,7 @@ impl AppState for DesktopApp { ); 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); diff --git a/src/harness.rs b/src/harness.rs index a044c80..a4f2937 100644 --- a/src/harness.rs +++ b/src/harness.rs @@ -134,8 +134,8 @@ impl HarnessState { } } -impl HasRoot for HarnessState { - fn set_root(&mut self, rsc: &mut HarnessRsc, root: StrongWidget) { +impl HasRoot> for HarnessState { + fn set_root(&mut self, rsc: &mut StdRsc, root: StrongWidget) { self.root = Some(crate::overlay::default_overlay_root(rsc, root)); } } @@ -163,14 +163,12 @@ impl OpenUrl for HarnessState { } } -pub type HarnessRsc = AppRsc; - /// 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, pub state: HarnessState, - task_recv: TaskMsgReceiver, + task_recv: TaskMsgReceiver>, redraws: Arc, 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 { diff --git a/src/rsc/mod.rs b/src/rsc/mod.rs index 72c83b8..f6442ce 100644 --- a/src/rsc/mod.rs +++ b/src/rsc/mod.rs @@ -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 { +/// 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 { pub ui: Ui, pub events: EventManager, pub tasks: Tasks, @@ -29,7 +32,7 @@ pub struct AppRsc { _state: std::marker::PhantomData, } -impl AppRsc { +impl StdRsc { pub(crate) fn new(redraw: Arc) -> (Self, TaskMsgReceiver) { let (tasks, receiver) = Tasks::init(redraw); ( @@ -49,7 +52,7 @@ impl AppRsc { } } -impl UiRsc for AppRsc { +impl UiRsc for StdRsc { fn ui(&self) -> &Ui { &self.ui } @@ -72,11 +75,11 @@ impl UiRsc for AppRsc { } } -impl HasState for AppRsc { +impl HasState for StdRsc { type State = State; } -impl HasEvents for AppRsc { +impl HasEvents for StdRsc { fn events(&self) -> &EventManager { &self.events } @@ -86,13 +89,13 @@ impl HasEvents for AppRsc { } } -impl HasTasks for AppRsc { +impl HasTasks for StdRsc { fn tasks_mut(&mut self) -> &mut Tasks { &mut self.tasks } } -impl HasWidgetState for AppRsc { +impl HasWidgetState for StdRsc { fn widget_state(&self) -> &WidgetState { &self.state } @@ -102,7 +105,7 @@ impl HasWidgetState for AppRsc { } } -impl>> std::ops::Index for AppRsc { +impl>> std::ops::Index for StdRsc { type Output = I::Output; fn index(&self, index: I) -> &Self::Output { @@ -110,7 +113,7 @@ impl>> std::ops::Index for AppRsc { } } -impl>> std::ops::IndexMut for AppRsc { +impl>> std::ops::IndexMut for StdRsc { fn index_mut(&mut self, index: I) -> &mut Self::Output { index.get_mut(self) } diff --git a/src/rsc/overlay.rs b/src/rsc/overlay.rs index 8bc5b77..13bae42 100644 --- a/src/rsc/overlay.rs +++ b/src/rsc/overlay.rs @@ -525,13 +525,13 @@ fn open_stackable_at( #[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>); - impl Controller for EscapeCounter { - fn command(&mut self, command: Command, _rsc: &mut HarnessRsc) -> CommandResult { + impl Controller> for EscapeCounter { + fn command(&mut self, command: Command, _rsc: &mut StdRsc) -> CommandResult { if command == Command::Escape { self.0.set(self.0.get() + 1); CommandResult::Used diff --git a/src/rsc/state.rs b/src/rsc/state.rs index d166663..95139a4 100644 --- a/src/rsc/state.rs +++ b/src/rsc/state.rs @@ -78,7 +78,7 @@ impl<'a, T: 'static> FnOnce<(&'a mut WidgetState,)> for WeakState { /// 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 { type Output; diff --git a/tests/color_space.rs b/tests/color_space.rs index ff2b8a7..5869ee5 100644 --- a/tests/color_space.rs +++ b/tests/color_space.rs @@ -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::(bitmap)(&mut harness.rsc); + let bitmap = image::>(bitmap)(&mut harness.rsc); let root = (rect(solid.clone()).sized((1, 1)), bitmap) .span(Dir::RIGHT) .add_strong(&mut harness.rsc)