Simplify Iris app initialization and task updates
This commit is contained in:
1 parent
b5666cdef9
commit
166bac2a93
25 files changed
+330
-282
No files matched your search
@@ -28,8 +28,8 @@ pub struct Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AndroidResources<State> for Resources {
|
impl AndroidResources<State> for Resources {
|
||||||
fn new(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Self>) {
|
fn new(wake: Arc<dyn WakeTaskQueue>) -> (Self, TaskMsgReceiver<Self>) {
|
||||||
let (tasks, receiver) = Tasks::init(redraw);
|
let (tasks, receiver) = Tasks::init(wake);
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
ui: Ui::default(),
|
ui: Ui::default(),
|
||||||
|
|||||||
@@ -2,19 +2,11 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidAppState for State {
|
|
||||||
type Resources = StdRsc<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[iris::app_init]
|
#[iris::app_init]
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
fn create(
|
||||||
let _ = build(rsc, &mut ui_state);
|
ui_state: &mut AndroidUiState,
|
||||||
State { ui_state }
|
rsc: &mut StdRsc<AndroidUiState>,
|
||||||
|
) {
|
||||||
|
let _ = app::build(rsc, ui_state);
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ use winit::event::WindowEvent;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
const SETTLE_FRAMES: usize = 4;
|
const SETTLE_FRAMES: usize = 4;
|
||||||
const FRAMES: usize = 6;
|
const FRAMES: usize = 6;
|
||||||
@@ -17,8 +16,8 @@ struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
impl DesktopAppState for State {
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>) -> Self {
|
||||||
let span = build(rsc, &mut ui_state);
|
let span = app::build(rsc, &mut ui_state);
|
||||||
Self {
|
Self {
|
||||||
ui_state,
|
ui_state,
|
||||||
span,
|
span,
|
||||||
|
|||||||
@@ -2,19 +2,11 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidAppState for State {
|
|
||||||
type Resources = StdRsc<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[iris::app_init]
|
#[iris::app_init]
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
fn create(
|
||||||
build(rsc, &mut ui_state);
|
ui_state: &mut AndroidUiState,
|
||||||
State { ui_state }
|
rsc: &mut StdRsc<AndroidUiState>,
|
||||||
|
) {
|
||||||
|
app::build(rsc, ui_state);
|
||||||
}
|
}
|
||||||
@@ -3,24 +3,10 @@ use winit::{dpi::LogicalSize, window::WindowAttributes};
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
|
||||||
fn window_attributes() -> WindowAttributes {
|
|
||||||
WindowAttributes::default().with_inner_size(LogicalSize::new(420.0, 900.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
build(rsc, &mut ui_state);
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
DesktopApp::<State>::run();
|
let attributes = WindowAttributes::default().with_inner_size(LogicalSize::new(420.0, 900.0));
|
||||||
|
DesktopApp::<DesktopUiState>::run_with_attributes(attributes, |ui_state, rsc| {
|
||||||
|
app::build(rsc, ui_state)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,11 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidAppState for State {
|
|
||||||
type Resources = StdRsc<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[iris::app_init]
|
#[iris::app_init]
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
fn create(
|
||||||
build(rsc, &mut ui_state);
|
ui_state: &mut AndroidUiState,
|
||||||
State { ui_state }
|
rsc: &mut StdRsc<AndroidUiState>,
|
||||||
|
) {
|
||||||
|
app::build(rsc, ui_state);
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,7 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
build(rsc, &mut ui_state);
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
DesktopApp::<State>::run();
|
DesktopApp::<DesktopUiState>::run_with(|ui_state, rsc| app::build(rsc, ui_state));
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
#[derive(AndroidUiState)]
|
||||||
struct Client {
|
struct Client {
|
||||||
@@ -19,14 +18,14 @@ impl AndroidAppState for Client {
|
|||||||
.renderer
|
.renderer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |renderer| renderer.ui.view_count());
|
.map_or(0, |renderer| renderer.ui.view_count());
|
||||||
update_info(rsc, self.info, views);
|
app::update_info(rsc, self.info, views);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[iris::app_init]
|
#[iris::app_init]
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<Client>) -> Client {
|
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<Client>) -> Client {
|
||||||
let widgets = build(rsc, &mut ui_state);
|
let widgets = app::build(rsc, &mut ui_state);
|
||||||
update_info(rsc, widgets.info, 0);
|
app::update_info(rsc, widgets.info, 0);
|
||||||
Client {
|
Client {
|
||||||
ui_state,
|
ui_state,
|
||||||
info: widgets.info,
|
info: widgets.info,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use winit::event::WindowEvent;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
#[derive(DesktopUiState)]
|
||||||
struct Client {
|
struct Client {
|
||||||
@@ -12,9 +11,9 @@ struct Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopAppState for Client {
|
impl DesktopAppState for Client {
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>) -> Self {
|
||||||
let widgets = build(rsc, &mut ui_state);
|
let widgets = app::build(rsc, &mut ui_state);
|
||||||
update_info(rsc, widgets.info, 0);
|
app::update_info(rsc, widgets.info, 0);
|
||||||
Self {
|
Self {
|
||||||
ui_state,
|
ui_state,
|
||||||
info: widgets.info,
|
info: widgets.info,
|
||||||
@@ -22,7 +21,7 @@ impl DesktopAppState for Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(&mut self, _: WindowEvent, rsc: &mut StdRsc<Self>) {
|
fn window_event(&mut self, _: WindowEvent, rsc: &mut StdRsc<Self>) {
|
||||||
update_info(rsc, self.info, self.ui_state.renderer.ui.view_count());
|
app::update_info(rsc, self.info, self.ui_state.renderer.ui.view_count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,11 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidAppState for State {
|
|
||||||
type Resources = StdRsc<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[iris::app_init]
|
#[iris::app_init]
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
fn create(
|
||||||
build(rsc, &mut ui_state);
|
ui_state: &mut AndroidUiState,
|
||||||
State { ui_state }
|
rsc: &mut StdRsc<AndroidUiState>,
|
||||||
|
) {
|
||||||
|
app::build(rsc, ui_state);
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,7 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
build(rsc, &mut ui_state);
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
DesktopApp::<State>::run();
|
DesktopApp::<DesktopUiState>::run_with(|ui_state, rsc| app::build(rsc, ui_state));
|
||||||
}
|
}
|
||||||
+13
-12
@@ -6,16 +6,17 @@ where
|
|||||||
Rsc::State: FocusHost,
|
Rsc::State: FocusHost,
|
||||||
{
|
{
|
||||||
let rect = rect(PaintId::RED).add(rsc);
|
let rect = rect(PaintId::RED).add(rsc);
|
||||||
rect.task_on(CursorSense::click(), async move |mut ctx| {
|
rect.label("Toggle color")
|
||||||
iris::task::sleep(Duration::from_secs(1)).await;
|
.task_on(CursorSense::click(), async move |mut ctx| {
|
||||||
ctx.update(move |_, rsc| {
|
iris::task::sleep(Duration::from_secs(1)).await;
|
||||||
let rect = rect(rsc);
|
ctx.update(move |_, rsc| {
|
||||||
if rect.is_paint(&PaintId::RED) {
|
let rect = rect(rsc);
|
||||||
rect.set_paint(PaintId::BLUE);
|
if rect.is_paint(&PaintId::RED) {
|
||||||
} else {
|
rect.set_paint(PaintId::BLUE);
|
||||||
rect.set_paint(PaintId::RED);
|
} else {
|
||||||
}
|
rect.set_paint(PaintId::RED);
|
||||||
});
|
}
|
||||||
})
|
});
|
||||||
.set_root(rsc, ui_state);
|
})
|
||||||
|
.set_root(rsc, ui_state);
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,11 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidAppState for State {
|
|
||||||
type Resources = StdRsc<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[iris::app_init]
|
#[iris::app_init]
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
fn create(
|
||||||
build(rsc, &mut ui_state);
|
ui_state: &mut AndroidUiState,
|
||||||
State { ui_state }
|
rsc: &mut StdRsc<AndroidUiState>,
|
||||||
|
) {
|
||||||
|
app::build(rsc, ui_state);
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,7 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
#[path = "lib.rs"]
|
#[path = "lib.rs"]
|
||||||
mod app;
|
mod app;
|
||||||
use app::*;
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
build(rsc, &mut ui_state);
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
DesktopApp::<State>::run();
|
DesktopApp::<DesktopUiState>::run_with(|ui_state, rsc| app::build(rsc, ui_state));
|
||||||
}
|
}
|
||||||
+32
-13
@@ -9,13 +9,15 @@ use syn::{
|
|||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Marks the factory called when Android creates an Iris view.
|
/// Marks the initializer called when Android creates an Iris view.
|
||||||
///
|
///
|
||||||
/// An attribute is necessary here because the Android loader requires one
|
/// An attribute is necessary here because the Android loader requires one
|
||||||
/// exported `JNI_OnLoad` symbol and `android-view` requires a plain function
|
/// exported `JNI_OnLoad` symbol and `android-view` requires a plain function
|
||||||
/// pointer monomorphized for the returned application state. The generated
|
/// pointer monomorphized for the application state. A function returning a
|
||||||
/// linker and JNI glue is Android-gated; the annotated function therefore
|
/// custom state remains its factory; a function with no return value receives
|
||||||
/// does not need its own `cfg` attribute.
|
/// `&mut AndroidUiState` and uses that state directly. The generated linker and
|
||||||
|
/// JNI glue is Android-gated; the annotated function therefore does not need
|
||||||
|
/// its own `cfg` attribute.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn app_init(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn app_init(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
@@ -29,13 +31,9 @@ pub fn app_init(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
|
|
||||||
let function = parse_macro_input!(item as ItemFn);
|
let function = parse_macro_input!(item as ItemFn);
|
||||||
let name = &function.sig.ident;
|
let name = &function.sig.ident;
|
||||||
let ReturnType::Type(_, state) = &function.sig.output else {
|
let (state, direct_initializer): (Type, bool) = match &function.sig.output {
|
||||||
return Error::new(
|
ReturnType::Default => (parse_quote!(::iris::android::AndroidUiState), true),
|
||||||
function.sig.output.span(),
|
ReturnType::Type(_, state) => ((**state).clone(), false),
|
||||||
"an app_init function must return its application state",
|
|
||||||
)
|
|
||||||
.into_compile_error()
|
|
||||||
.into();
|
|
||||||
};
|
};
|
||||||
if function.sig.inputs.len() != 2
|
if function.sig.inputs.len() != 2
|
||||||
|| function
|
|| function
|
||||||
@@ -46,7 +44,7 @@ pub fn app_init(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
{
|
{
|
||||||
return Error::new(
|
return Error::new(
|
||||||
function.sig.inputs.span(),
|
function.sig.inputs.span(),
|
||||||
"an app_init function takes AndroidUiState and &mut State::Resources",
|
"an app_init function takes UI state and resources",
|
||||||
)
|
)
|
||||||
.into_compile_error()
|
.into_compile_error()
|
||||||
.into();
|
.into();
|
||||||
@@ -64,6 +62,25 @@ pub fn app_init(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let factory = if direct_initializer {
|
||||||
|
quote! {
|
||||||
|
fn init(
|
||||||
|
mut ui_state: ::iris::android::AndroidUiState,
|
||||||
|
rsc: &mut <#state as ::iris::android::AndroidAppState>::Resources,
|
||||||
|
) -> #state {
|
||||||
|
super::#name(&mut ui_state, rsc);
|
||||||
|
ui_state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
let create = if direct_initializer {
|
||||||
|
quote! { init }
|
||||||
|
} else {
|
||||||
|
quote! { super::#name }
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
#function
|
#function
|
||||||
@@ -72,12 +89,14 @@ pub fn app_init(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
mod __iris_android_app {
|
mod __iris_android_app {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#factory
|
||||||
|
|
||||||
extern "system" fn new_view_peer<'local>(
|
extern "system" fn new_view_peer<'local>(
|
||||||
env: ::iris::android::__private::JNIEnv<'local>,
|
env: ::iris::android::__private::JNIEnv<'local>,
|
||||||
view: ::iris::android::__private::View<'local>,
|
view: ::iris::android::__private::View<'local>,
|
||||||
context: ::iris::android::__private::Context<'local>,
|
context: ::iris::android::__private::Context<'local>,
|
||||||
) -> ::iris::android::__private::JLong {
|
) -> ::iris::android::__private::JLong {
|
||||||
::iris::android::new_peer::<#state>(env, view, context, super::#name)
|
::iris::android::new_peer::<#state>(env, view, context, #create)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
|
|||||||
@@ -25,31 +25,48 @@ application-id = "com.example.myapp"
|
|||||||
label = "My app"
|
label = "My app"
|
||||||
```
|
```
|
||||||
|
|
||||||
`#[iris::app_init]` marks the factory called when Android creates the Iris
|
`#[iris::app_init]` marks the initializer called when Android creates the Iris
|
||||||
view. The attribute supplies its own Android target gate and generates the JNI
|
view. The attribute supplies its own Android target gate and generates the JNI
|
||||||
loader glue. The returned state chooses its resources through
|
loader glue. An application with no state beyond the UI state receives
|
||||||
`AndroidAppState::Resources`; `StdRsc` is the standard bundle, not a
|
`AndroidUiState` directly by mutable reference:
|
||||||
requirement.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidAppState for State {
|
|
||||||
type Resources = StdRsc<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[iris::app_init]
|
#[iris::app_init]
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
fn create(
|
||||||
rect(PaintId::RED).set_root(rsc, &mut ui_state);
|
ui_state: &mut AndroidUiState,
|
||||||
State { ui_state }
|
rsc: &mut StdRsc<AndroidUiState>,
|
||||||
|
) {
|
||||||
|
rect(PaintId::RED).set_root(rsc, ui_state);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Desktop has the corresponding initializer form:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
DesktopApp::<DesktopUiState>::run_with(|ui_state, rsc| {
|
||||||
|
rect(PaintId::RED).set_root(rsc, ui_state);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Background work updates either host through the same task context. An update
|
||||||
|
wakes the UI thread; Iris schedules a frame automatically if the closure made
|
||||||
|
the retained widget tree dirty:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsc.spawn_task(async move |mut ctx| {
|
||||||
|
let text = load_text().await;
|
||||||
|
ctx.update(move |_, rsc| label.edit(rsc).set(&text));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Applications do not need a winit event proxy or an explicit redraw request.
|
||||||
|
|
||||||
|
Applications with additional fields use their own state type. Its
|
||||||
|
`AndroidAppState::Resources` associated type can also replace `StdRsc` with a
|
||||||
|
custom resource bundle.
|
||||||
|
|
||||||
Install the Cargo subcommand from a checkout, then invoke it from the
|
Install the Cargo subcommand from a checkout, then invoke it from the
|
||||||
application's directory:
|
application's directory:
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::task::RequestRedraw;
|
use crate::task::WakeTaskQueue;
|
||||||
use android_view::{
|
use android_view::{
|
||||||
View,
|
View,
|
||||||
jni::{JavaVM, objects::GlobalRef},
|
jni::{JavaVM, objects::GlobalRef},
|
||||||
@@ -429,23 +429,23 @@ impl AndroidRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Tasks`' redraw handle on Android: a background task finishes on the
|
/// `Tasks`' UI-thread wake on Android. Updates can be submitted from the
|
||||||
/// tokio thread `Tasks::init` spawned, which is not attached to the JVM, so
|
/// tokio runtime or an application-owned thread that is not attached to the
|
||||||
/// asking for a frame means attaching first. The global ref is what
|
/// JVM, so posting the callback means attaching first. The global ref is what
|
||||||
/// survives past the JNI call that handed the `View` to us.
|
/// survives past the JNI call that handed the `View` to us.
|
||||||
pub struct AndroidRedrawHandle {
|
pub struct AndroidTaskWake {
|
||||||
vm: JavaVM,
|
vm: JavaVM,
|
||||||
view: GlobalRef,
|
view: GlobalRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AndroidRedrawHandle {
|
impl AndroidTaskWake {
|
||||||
pub fn new(vm: JavaVM, view: GlobalRef) -> Self {
|
pub fn new(vm: JavaVM, view: GlobalRef) -> Self {
|
||||||
Self { vm, view }
|
Self { vm, view }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestRedraw for AndroidRedrawHandle {
|
impl WakeTaskQueue for AndroidTaskWake {
|
||||||
fn request_redraw(&self) {
|
fn wake(&self) {
|
||||||
let Ok(mut env) = self.vm.attach_current_thread() else {
|
let Ok(mut env) = self.vm.attach_current_thread() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
+31
-19
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::task::RequestRedraw;
|
use crate::task::WakeTaskQueue;
|
||||||
use accesskit_android::Adapter as AccessAdapter;
|
use accesskit_android::Adapter as AccessAdapter;
|
||||||
use android_view::{
|
use android_view::{
|
||||||
AccessibilityNodeInfo, AccessibilityNodeProvider, Bundle, CallbackCtx, Context,
|
AccessibilityNodeInfo, AccessibilityNodeProvider, Bundle, CallbackCtx, Context,
|
||||||
@@ -16,7 +16,7 @@ use std::{cell::RefCell, marker::Sized, rc::Rc, sync::Arc, time::Instant};
|
|||||||
use super::{
|
use super::{
|
||||||
access::{AndroidAccessSource, NullActionHandler, raise_if_enabled},
|
access::{AndroidAccessSource, NullActionHandler, raise_if_enabled},
|
||||||
insets::{Insets, Shared},
|
insets::{Insets, Shared},
|
||||||
render::{AndroidRedrawHandle, AndroidRenderer},
|
render::{AndroidRenderer, AndroidTaskWake},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Android host state. The renderer follows the `SurfaceView` lifecycle.
|
/// Android host state. The renderer follows the `SurfaceView` lifecycle.
|
||||||
@@ -100,6 +100,16 @@ pub trait HasAndroidUiState: Sized + 'static {
|
|||||||
fn android_state_mut(&mut self) -> &mut AndroidUiState;
|
fn android_state_mut(&mut self) -> &mut AndroidUiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasAndroidUiState for AndroidUiState {
|
||||||
|
fn android_state(&self) -> &AndroidUiState {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn android_state_mut(&mut self) -> &mut AndroidUiState {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Application state retained for the lifetime of one Android `View`.
|
/// Application state retained for the lifetime of one Android `View`.
|
||||||
///
|
///
|
||||||
/// [`StdRsc`] is the usual [`AndroidResources`] implementation, but the host only
|
/// [`StdRsc`] is the usual [`AndroidResources`] implementation, but the host only
|
||||||
@@ -118,6 +128,10 @@ pub trait AndroidAppState: HasAndroidUiState {
|
|||||||
fn on_insets_changed(&mut self, rsc: &mut Self::Resources, insets: WindowInsets) {}
|
fn on_insets_changed(&mut self, rsc: &mut Self::Resources, insets: WindowInsets) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for AndroidUiState {
|
||||||
|
type Resources = StdRsc<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Resources the Android host needs to draw and dispatch application events.
|
/// Resources the Android host needs to draw and dispatch application events.
|
||||||
///
|
///
|
||||||
/// This deliberately names capabilities rather than storage. Custom bundles
|
/// This deliberately names capabilities rather than storage. Custom bundles
|
||||||
@@ -128,12 +142,12 @@ pub trait AndroidResources<State>:
|
|||||||
where
|
where
|
||||||
State: 'static,
|
State: 'static,
|
||||||
{
|
{
|
||||||
fn new(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Self>);
|
fn new(wake: Arc<dyn WakeTaskQueue>) -> (Self, TaskMsgReceiver<Self>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<State: 'static> AndroidResources<State> for StdRsc<State> {
|
impl<State: 'static> AndroidResources<State> for StdRsc<State> {
|
||||||
fn new(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Self>) {
|
fn new(wake: Arc<dyn WakeTaskQueue>) -> (Self, TaskMsgReceiver<Self>) {
|
||||||
StdRsc::new(redraw)
|
StdRsc::new(wake)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,8 +237,12 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
|||||||
|
|
||||||
self.update_ime_selection(ctx);
|
self.update_ime_selection(ctx);
|
||||||
|
|
||||||
let ui_state = self.state.android_state_mut();
|
self.state.android_state_mut().cursor.end_frame();
|
||||||
ui_state.cursor.end_frame();
|
self.request_frame_if_needed(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_frame_if_needed(&self, ctx: &mut CallbackCtx) {
|
||||||
|
let ui_state = self.state.android_state();
|
||||||
let render_state = self.rsc.ui().render_state();
|
let render_state = self.rsc.ui().render_state();
|
||||||
if render_state
|
if render_state
|
||||||
.get()
|
.get()
|
||||||
@@ -720,18 +738,12 @@ impl<State: AndroidAppState> ViewPeer for IrisViewPeer<State> {
|
|||||||
self.render(ctx, now);
|
self.render(ctx, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where `AndroidRedrawHandle::request_redraw` (`android/render.rs`)
|
/// Where `AndroidTaskWake::wake` lands on the UI thread. Applying an
|
||||||
/// actually lands: `View.postDelayed`'s Runnable resolves to this, on
|
/// update and drawing it are deliberately separate: retained widget
|
||||||
/// the UI thread, which is what makes it safe to call from a background
|
/// invalidation decides whether this wake needs a frame.
|
||||||
/// task's own thread when `post_frame_callback`'s `Choreographer`
|
|
||||||
/// requirement (a `Looper` on the *calling* thread) is not. Same body
|
|
||||||
/// as `do_frame` -- draining tasks and rendering immediately is a
|
|
||||||
/// perfectly good answer to "a background fetch has new state," and
|
|
||||||
/// avoids a second frame-scheduling path to keep in sync with the real
|
|
||||||
/// one.
|
|
||||||
fn delayed_callback(&mut self, ctx: &mut CallbackCtx) {
|
fn delayed_callback(&mut self, ctx: &mut CallbackCtx) {
|
||||||
self.drain_tasks();
|
self.drain_tasks();
|
||||||
self.render(ctx, Instant::now());
|
self.request_frame_if_needed(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_input_connection(&mut self) -> Option<&mut dyn InputConnection> {
|
fn as_input_connection(&mut self) -> Option<&mut dyn InputConnection> {
|
||||||
@@ -835,8 +847,8 @@ pub fn new_peer<'local, State: AndroidAppState>(
|
|||||||
log::info!("iris: new_peer content_scale={content_scale}");
|
log::info!("iris: new_peer content_scale={content_scale}");
|
||||||
let vm = env.get_java_vm().unwrap();
|
let vm = env.get_java_vm().unwrap();
|
||||||
let global_view = env.new_global_ref(&view.0).unwrap();
|
let global_view = env.new_global_ref(&view.0).unwrap();
|
||||||
let redraw: Arc<dyn RequestRedraw> = Arc::new(AndroidRedrawHandle::new(vm, global_view));
|
let wake: Arc<dyn WakeTaskQueue> = Arc::new(AndroidTaskWake::new(vm, global_view));
|
||||||
let (mut rsc, task_recv) = State::Resources::new(redraw);
|
let (mut rsc, task_recv) = State::Resources::new(wake);
|
||||||
rsc.ui_mut().set_density(content_scale);
|
rsc.ui_mut().set_density(content_scale);
|
||||||
let shared = Rc::new(RefCell::new(Shared::default()));
|
let shared = Rc::new(RefCell::new(Shared::default()));
|
||||||
let ui_state = AndroidUiState::new(shared.clone(), content_scale);
|
let ui_state = AndroidUiState::new(shared.clone(), content_scale);
|
||||||
|
|||||||
+18
-19
@@ -5,41 +5,40 @@ use winit::{
|
|||||||
window::WindowId,
|
window::WindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait AppState {
|
pub trait AppState: 'static {
|
||||||
type Event: 'static;
|
|
||||||
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self;
|
|
||||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
|
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
|
||||||
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
|
fn tasks_ready(&mut self);
|
||||||
fn exit(&mut self);
|
fn exit(&mut self);
|
||||||
|
|
||||||
fn run()
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
App::<Self>::run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App<State: AppState> {
|
pub struct App<State: AppState> {
|
||||||
state: Option<State>,
|
state: Option<State>,
|
||||||
proxy: EventLoopProxy<State::Event>,
|
proxy: EventLoopProxy<()>,
|
||||||
|
init: Option<Box<Init<State>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<State: AppState> App<State> {
|
type Init<State> = dyn FnOnce(&ActiveEventLoop, EventLoopProxy<()>) -> State;
|
||||||
pub fn run() {
|
|
||||||
|
impl<State: AppState + 'static> App<State> {
|
||||||
|
pub fn run_with(init: impl FnOnce(&ActiveEventLoop, EventLoopProxy<()>) -> State + 'static) {
|
||||||
super::logging::install(log::LevelFilter::Info);
|
super::logging::install(log::LevelFilter::Info);
|
||||||
let event_loop = EventLoop::with_user_event().build().unwrap();
|
let event_loop = EventLoop::with_user_event().build().unwrap();
|
||||||
let proxy = event_loop.create_proxy();
|
let proxy = event_loop.create_proxy();
|
||||||
event_loop
|
event_loop
|
||||||
.run_app(&mut App::<State> { state: None, proxy })
|
.run_app(&mut App::<State> {
|
||||||
|
state: None,
|
||||||
|
proxy,
|
||||||
|
init: Some(Box::new(init)),
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<State: AppState> ApplicationHandler<State::Event> for App<State> {
|
impl<State: AppState> ApplicationHandler<()> for App<State> {
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
if self.state.is_none() {
|
if self.state.is_none() {
|
||||||
let state = State::new(event_loop, self.proxy.clone());
|
let init = self.init.take().unwrap();
|
||||||
|
let state = init(event_loop, self.proxy.clone());
|
||||||
self.state = Some(state);
|
self.state = Some(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,9 +48,9 @@ impl<State: AppState> ApplicationHandler<State::Event> for App<State> {
|
|||||||
state.window_event(event, event_loop);
|
state.window_event(event, event_loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: State::Event) {
|
fn user_event(&mut self, _: &ActiveEventLoop, (): ()) {
|
||||||
let state = self.state.as_mut().unwrap();
|
let state = self.state.as_mut().unwrap();
|
||||||
state.event(event, event_loop);
|
state.tasks_ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exiting(&mut self, _: &ActiveEventLoop) {
|
fn exiting(&mut self, _: &ActiveEventLoop) {
|
||||||
|
|||||||
+76
-14
@@ -16,11 +16,15 @@ mod platform;
|
|||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
pub use access::*;
|
pub use access::*;
|
||||||
pub use app::*;
|
use app::{App, AppState};
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use render::*;
|
pub use render::*;
|
||||||
|
|
||||||
pub type Proxy<Event> = EventLoopProxy<Event>;
|
impl WakeTaskQueue for EventLoopProxy<()> {
|
||||||
|
fn wake(&self) {
|
||||||
|
let _ = self.send_event(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Physical pixels per dp. Layout and input stay in physical pixels; only
|
/// Physical pixels per dp. Layout and input stay in physical pixels; only
|
||||||
/// `dp(...)` resolves through this scale.
|
/// `dp(...)` resolves through this scale.
|
||||||
@@ -79,11 +83,18 @@ pub trait HasDesktopUiState: Sized + 'static {
|
|||||||
fn desktop_state_mut(&mut self) -> &mut DesktopUiState;
|
fn desktop_state_mut(&mut self) -> &mut DesktopUiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasDesktopUiState for DesktopUiState {
|
||||||
|
fn desktop_state(&self) -> &DesktopUiState {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desktop_state_mut(&mut self) -> &mut DesktopUiState {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait DesktopAppState: HasDesktopUiState {
|
pub trait DesktopAppState: HasDesktopUiState {
|
||||||
type Event = ();
|
fn new(ui_state: DesktopUiState, rsc: &mut StdRsc<Self>) -> 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 StdRsc<Self>) {}
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn exit(&mut self, rsc: &mut StdRsc<Self>) {}
|
fn exit(&mut self, rsc: &mut StdRsc<Self>) {}
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
@@ -93,18 +104,33 @@ pub trait DesktopAppState: HasDesktopUiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DesktopAppState for DesktopUiState {
|
||||||
|
fn new(ui_state: DesktopUiState, _: &mut StdRsc<Self>) -> Self {
|
||||||
|
ui_state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DesktopApp<State: DesktopAppState> {
|
pub struct DesktopApp<State: DesktopAppState> {
|
||||||
rsc: StdRsc<State>,
|
rsc: StdRsc<State>,
|
||||||
state: State,
|
state: State,
|
||||||
task_recv: TaskMsgReceiver<StdRsc<State>>,
|
task_recv: TaskMsgReceiver<StdRsc<State>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<State: DesktopAppState> AppState for DesktopApp<State> {
|
impl<State: DesktopAppState> DesktopApp<State> {
|
||||||
type Event = State::Event;
|
pub fn run() {
|
||||||
|
App::<Self>::run_with(|event_loop, proxy| {
|
||||||
|
Self::new_with(event_loop, proxy, State::window_attributes(), State::new)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
|
fn new_with(
|
||||||
|
event_loop: &ActiveEventLoop,
|
||||||
|
proxy: EventLoopProxy<()>,
|
||||||
|
attributes: WindowAttributes,
|
||||||
|
init: impl FnOnce(DesktopUiState, &mut StdRsc<State>) -> State,
|
||||||
|
) -> Self {
|
||||||
let window = event_loop
|
let window = event_loop
|
||||||
.create_window(State::window_attributes().with_visible(false))
|
.create_window(attributes.with_visible(false))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let access_adapter = accesskit_winit::Adapter::with_direct_handlers(
|
let access_adapter = accesskit_winit::Adapter::with_direct_handlers(
|
||||||
event_loop,
|
event_loop,
|
||||||
@@ -115,20 +141,56 @@ impl<State: DesktopAppState> AppState for DesktopApp<State> {
|
|||||||
);
|
);
|
||||||
window.set_visible(true);
|
window.set_visible(true);
|
||||||
let desktop_state = DesktopUiState::new(window, access_adapter);
|
let desktop_state = DesktopUiState::new(window, access_adapter);
|
||||||
let (mut rsc, task_recv) = StdRsc::new(desktop_state.window.clone());
|
let (mut rsc, task_recv) = StdRsc::new(Arc::new(proxy));
|
||||||
// Set before building widgets so the first text shape uses the right density.
|
// Set before building widgets so the first text shape uses the right density.
|
||||||
let scale = content_scale(desktop_state.window.as_ref());
|
let scale = content_scale(desktop_state.window.as_ref());
|
||||||
rsc.ui.set_density(scale);
|
rsc.ui.set_density(scale);
|
||||||
let state = State::new(desktop_state, &mut rsc, proxy);
|
let state = init(desktop_state, &mut rsc);
|
||||||
Self {
|
Self {
|
||||||
rsc,
|
rsc,
|
||||||
state,
|
state,
|
||||||
task_recv,
|
task_recv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
|
impl DesktopApp<DesktopUiState> {
|
||||||
self.state.event(event, &mut self.rsc);
|
/// Runs an application whose only state is Iris's desktop UI state.
|
||||||
|
pub fn run_with(init: impl FnOnce(&mut DesktopUiState, &mut StdRsc<DesktopUiState>) + 'static) {
|
||||||
|
Self::run_with_attributes(DesktopUiState::window_attributes(), init);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_with_attributes(
|
||||||
|
attributes: WindowAttributes,
|
||||||
|
init: impl FnOnce(&mut DesktopUiState, &mut StdRsc<DesktopUiState>) + 'static,
|
||||||
|
) {
|
||||||
|
App::<Self>::run_with(move |event_loop, proxy| {
|
||||||
|
Self::new_with(event_loop, proxy, attributes, move |mut ui_state, rsc| {
|
||||||
|
init(&mut ui_state, rsc);
|
||||||
|
ui_state
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State: DesktopAppState> AppState for DesktopApp<State> {
|
||||||
|
fn tasks_ready(&mut self) {
|
||||||
|
let Self {
|
||||||
|
rsc,
|
||||||
|
state,
|
||||||
|
task_recv,
|
||||||
|
} = self;
|
||||||
|
for update in task_recv.try_iter() {
|
||||||
|
update(state, rsc);
|
||||||
|
}
|
||||||
|
let ui_state = state.desktop_state();
|
||||||
|
let render_state = rsc.ui.render_state();
|
||||||
|
if render_state
|
||||||
|
.get()
|
||||||
|
.needs_redraw(&ui_state.root, rsc.widgets())
|
||||||
|
{
|
||||||
|
ui_state.renderer.window().request_redraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use crate::task::RequestRedraw;
|
|
||||||
use iris_core::{FrameParts, LinearRgba, Ui, UiRenderNode, util::Vec2};
|
use iris_core::{FrameParts, LinearRgba, Ui, UiRenderNode, util::Vec2};
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -8,12 +7,6 @@ use winit::{dpi::PhysicalSize, window::Window};
|
|||||||
|
|
||||||
pub const CLEAR_COLOR: LinearRgba = LinearRgba::BLACK;
|
pub const CLEAR_COLOR: LinearRgba = LinearRgba::BLACK;
|
||||||
|
|
||||||
impl RequestRedraw for Window {
|
|
||||||
fn request_redraw(&self) {
|
|
||||||
Window::request_redraw(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UiRenderer {
|
pub struct UiRenderer {
|
||||||
window: Arc<Window>,
|
window: Arc<Window>,
|
||||||
surface: Surface<'static>,
|
surface: Surface<'static>,
|
||||||
|
|||||||
+2
-2
@@ -103,8 +103,8 @@ impl RedrawCounter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestRedraw for RedrawCounter {
|
impl WakeTaskQueue for RedrawCounter {
|
||||||
fn request_redraw(&self) {
|
fn wake(&self) {
|
||||||
self.0.fetch_add(1, Ordering::Relaxed);
|
self.0.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#![feature(unboxed_closures)]
|
#![feature(unboxed_closures)]
|
||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
// Only `desktop::DesktopAppState::Event`'s default uses this; unused (and
|
|
||||||
// warned about) on the android target, which has no such default.
|
|
||||||
#![cfg_attr(not(target_os = "android"), feature(associated_type_defaults))]
|
|
||||||
#![feature(unsize)]
|
#![feature(unsize)]
|
||||||
#![feature(option_into_flat_iter)]
|
#![feature(option_into_flat_iter)]
|
||||||
#![feature(async_fn_traits)]
|
#![feature(async_fn_traits)]
|
||||||
|
|||||||
+2
-2
@@ -33,8 +33,8 @@ pub struct StdRsc<State: 'static> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<State> StdRsc<State> {
|
impl<State> StdRsc<State> {
|
||||||
pub(crate) fn new(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Self>) {
|
pub(crate) fn new(wake: Arc<dyn WakeTaskQueue>) -> (Self, TaskMsgReceiver<Self>) {
|
||||||
let (tasks, receiver) = Tasks::init(redraw);
|
let (tasks, receiver) = Tasks::init(wake);
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
ui: Ui::default(),
|
ui: Ui::default(),
|
||||||
|
|||||||
+72
-28
@@ -19,15 +19,13 @@ pub async fn sleep(duration: std::time::Duration) {
|
|||||||
tokio::time::sleep(duration).await;
|
tokio::time::sleep(duration).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a completed task nudges when it wants its result drawn. Shared
|
/// Wakes the platform UI thread so it can apply queued task updates.
|
||||||
/// between backends rather than typed as `winit::window::Window` directly:
|
///
|
||||||
/// android-view has no `Window` at all, and the redraw request there is a
|
/// Waking does not itself mean drawing. Once the updates have run, the host
|
||||||
/// JNI call (`View::post_frame_callback`) rather than a method call on a
|
/// asks the retained UI tree whether anything visible became dirty and only
|
||||||
/// value this crate owns. Each backend supplies its own implementation --
|
/// then schedules a frame.
|
||||||
/// `desktop/render.rs` for winit, `android/render.rs` for android-view --
|
pub trait WakeTaskQueue: Send + Sync + 'static {
|
||||||
/// and this module never needs to know which one it is holding.
|
fn wake(&self);
|
||||||
pub trait RequestRedraw: Send + Sync + 'static {
|
|
||||||
fn request_redraw(&self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TaskMsgSender<Rsc> = SyncSender<Box<dyn TaskUpdate<Rsc>>>;
|
pub type TaskMsgSender<Rsc> = SyncSender<Box<dyn TaskUpdate<Rsc>>>;
|
||||||
@@ -38,29 +36,41 @@ impl<F: FnOnce(&mut Rsc::State, &mut Rsc) + Send, Rsc: HasState> TaskUpdate<Rsc>
|
|||||||
|
|
||||||
pub struct Tasks<Rsc: HasState> {
|
pub struct Tasks<Rsc: HasState> {
|
||||||
start: AsyncSender<BoxTask>,
|
start: AsyncSender<BoxTask>,
|
||||||
redraw: Arc<dyn RequestRedraw>,
|
wake: Arc<dyn WakeTaskQueue>,
|
||||||
msg_send: SyncSender<Box<dyn TaskUpdate<Rsc>>>,
|
msg_send: SyncSender<Box<dyn TaskUpdate<Rsc>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TaskCtx<Rsc: HasState> {
|
pub struct TaskCtx<Rsc: HasState> {
|
||||||
send: TaskMsgSender<Rsc>,
|
send: TaskMsgSender<Rsc>,
|
||||||
|
wake: Arc<dyn WakeTaskQueue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Rsc: HasState> Clone for TaskCtx<Rsc> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
send: self.send.clone(),
|
||||||
|
wake: self.wake.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Rsc: HasState> TaskCtx<Rsc> {
|
impl<Rsc: HasState> TaskCtx<Rsc> {
|
||||||
pub fn update(&mut self, f: impl TaskUpdate<Rsc> + 'static) {
|
pub fn update(&mut self, f: impl TaskUpdate<Rsc> + 'static) {
|
||||||
let _ = self.send.send(Box::new(f));
|
if self.send.send(Box::new(f)).is_ok() {
|
||||||
|
self.wake.wake();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<Rsc: HasState + 'static> TaskCtx<Rsc> {
|
impl<Rsc: HasState + 'static> TaskCtx<Rsc> {
|
||||||
fn new(send: TaskMsgSender<Rsc>) -> Self {
|
fn new(send: TaskMsgSender<Rsc>, wake: Arc<dyn WakeTaskQueue>) -> Self {
|
||||||
Self { send }
|
Self { send, wake }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoxTask = Pin<Box<dyn Future<Output = ()> + Send>>;
|
type BoxTask = Pin<Box<dyn Future<Output = ()> + Send>>;
|
||||||
|
|
||||||
impl<Rsc: HasState> Tasks<Rsc> {
|
impl<Rsc: HasState> Tasks<Rsc> {
|
||||||
pub fn init(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Rsc>) {
|
pub fn init(wake: Arc<dyn WakeTaskQueue>) -> (Self, TaskMsgReceiver<Rsc>) {
|
||||||
let (start, start_recv) = async_channel();
|
let (start, start_recv) = async_channel();
|
||||||
let (msgs, msgs_recv) = sync_channel();
|
let (msgs, msgs_recv) = sync_channel();
|
||||||
std::thread::spawn(|| {
|
std::thread::spawn(|| {
|
||||||
@@ -71,22 +81,16 @@ impl<Rsc: HasState> Tasks<Rsc> {
|
|||||||
Self {
|
Self {
|
||||||
start,
|
start,
|
||||||
msg_send: msgs,
|
msg_send: msgs,
|
||||||
redraw,
|
wake,
|
||||||
},
|
},
|
||||||
msgs_recv,
|
msgs_recv,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The same redraw handle `spawn`'s wrapper calls once, after a whole
|
/// A cloneable, platform-neutral way for an application-owned thread to
|
||||||
/// task's future completes -- exposed so a caller running its own
|
/// submit work to the UI thread.
|
||||||
/// longer-lived loop *inside* a spawned task (a live SSE follow, here)
|
pub fn context(&self) -> TaskCtx<Rsc> {
|
||||||
/// can ask for a frame after each `TaskCtx::update`, not just at the
|
TaskCtx::new(self.msg_send.clone(), self.wake.clone())
|
||||||
/// end. Without this a caller has no way to get a redraw mid-stream,
|
|
||||||
/// which is exactly the gap `iris/desktop-app`'s `app.rs` module doc
|
|
||||||
/// names for why it uses winit's `Proxy` instead of `Tasks` -- Android
|
|
||||||
/// has no `Proxy`, so this is what closes the same gap there.
|
|
||||||
pub fn redraw_handle(&self) -> Arc<dyn RequestRedraw> {
|
|
||||||
self.redraw.clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn<F: AsyncFnOnce(TaskCtx<Rsc>) + 'static + std::marker::Send>(&mut self, task: F)
|
pub fn spawn<F: AsyncFnOnce(TaskCtx<Rsc>) + 'static + std::marker::Send>(&mut self, task: F)
|
||||||
@@ -94,10 +98,9 @@ impl<Rsc: HasState> Tasks<Rsc> {
|
|||||||
F::CallOnceFuture: Send,
|
F::CallOnceFuture: Send,
|
||||||
{
|
{
|
||||||
let send = self.msg_send.clone();
|
let send = self.msg_send.clone();
|
||||||
let redraw = self.redraw.clone();
|
let wake = self.wake.clone();
|
||||||
let _ = self.start.send(Box::pin(async move {
|
let _ = self.start.send(Box::pin(async move {
|
||||||
task(TaskCtx::new(send)).await;
|
task(TaskCtx::new(send, wake)).await;
|
||||||
redraw.request_redraw();
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,3 +110,44 @@ async fn listen(mut recv: AsyncReceiver<BoxTask>) {
|
|||||||
tokio::spawn(task);
|
tokio::spawn(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestRsc;
|
||||||
|
|
||||||
|
impl HasState for TestRsc {
|
||||||
|
type State = usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct WakeCounter(AtomicUsize);
|
||||||
|
|
||||||
|
impl WakeTaskQueue for WakeCounter {
|
||||||
|
fn wake(&self) {
|
||||||
|
self.0.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_update_wakes_the_ui_queue() {
|
||||||
|
let wakes = Arc::new(WakeCounter::default());
|
||||||
|
let (tasks, updates) = Tasks::<TestRsc>::init(wakes.clone());
|
||||||
|
let mut ctx = tasks.context();
|
||||||
|
|
||||||
|
ctx.update(|state: &mut usize, _| *state += 1);
|
||||||
|
ctx.update(|state: &mut usize, _| *state += 2);
|
||||||
|
|
||||||
|
assert_eq!(wakes.0.load(Ordering::Relaxed), 2);
|
||||||
|
let mut state = 0;
|
||||||
|
let mut rsc = TestRsc;
|
||||||
|
updates.recv_timeout(Duration::from_secs(1)).unwrap()(&mut state, &mut rsc);
|
||||||
|
updates.recv_timeout(Duration::from_secs(1)).unwrap()(&mut state, &mut rsc);
|
||||||
|
assert_eq!(state, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user