Add Iris Android APK tooling
This commit is contained in:
1 parent
3246f397b5
commit
37f956707e
20 files changed
+1766
-54
No files matched your search
+60
-34
@@ -89,8 +89,8 @@ impl AndroidUiState {
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: 'static> HasRoot<StdRsc<State>> for AndroidUiState {
|
||||
fn set_root(&mut self, rsc: &mut StdRsc<State>, root: StrongWidget) {
|
||||
impl<Rsc: HasEvents> HasRoot<Rsc> for AndroidUiState {
|
||||
fn set_root(&mut self, rsc: &mut Rsc, root: StrongWidget) {
|
||||
self.root = Some(crate::overlay::default_overlay_root(rsc, root));
|
||||
}
|
||||
}
|
||||
@@ -100,16 +100,41 @@ pub trait HasAndroidUiState: Sized + 'static {
|
||||
fn android_state_mut(&mut self) -> &mut AndroidUiState;
|
||||
}
|
||||
|
||||
/// Application state retained for the lifetime of one Android `View`.
|
||||
///
|
||||
/// [`StdRsc`] is the usual [`AndroidResources`] implementation, but the host only
|
||||
/// requires the capabilities in [`AndroidResources`]. An application may add
|
||||
/// its own resources by supplying another implementation.
|
||||
pub trait AndroidAppState: HasAndroidUiState {
|
||||
fn new(ui_state: AndroidUiState, rsc: &mut StdRsc<Self>) -> Self;
|
||||
type Resources: AndroidResources<Self>;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn back_pressed(&mut self, rsc: &mut StdRsc<Self>) -> bool {
|
||||
fn back_pressed(&mut self, rsc: &mut Self::Resources) -> bool {
|
||||
false
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn platform_ready(&mut self, rsc: &mut StdRsc<Self>, vm: JavaVM, view: GlobalRef) {}
|
||||
fn platform_ready(&mut self, rsc: &mut Self::Resources, vm: JavaVM, view: GlobalRef) {}
|
||||
#[allow(unused_variables)]
|
||||
fn on_insets_changed(&mut self, rsc: &mut StdRsc<Self>, insets: WindowInsets) {}
|
||||
fn on_insets_changed(&mut self, rsc: &mut Self::Resources, insets: WindowInsets) {}
|
||||
}
|
||||
|
||||
/// Resources the Android host needs to draw and dispatch application events.
|
||||
///
|
||||
/// This deliberately names capabilities rather than storage. Custom bundles
|
||||
/// can embed or replace [`StdRsc`] as long as they implement these traits and
|
||||
/// create the task receiver paired with their [`Tasks`] value.
|
||||
pub trait AndroidResources<State>:
|
||||
HasTasks<State = State> + HasWidgetState + Sized + 'static
|
||||
where
|
||||
State: 'static,
|
||||
{
|
||||
fn new(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Self>);
|
||||
}
|
||||
|
||||
impl<State: 'static> AndroidResources<State> for StdRsc<State> {
|
||||
fn new(redraw: Arc<dyn RequestRedraw>) -> (Self, TaskMsgReceiver<Self>) {
|
||||
StdRsc::new(redraw)
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget-facing insets in physical pixels, decoupled from JNI's integer shape.
|
||||
@@ -144,9 +169,9 @@ impl WindowInsets {
|
||||
/// `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: StdRsc<State>,
|
||||
pub(super) rsc: State::Resources,
|
||||
pub(super) state: State,
|
||||
task_recv: TaskMsgReceiver<StdRsc<State>>,
|
||||
task_recv: TaskMsgReceiver<State::Resources>,
|
||||
/// Converts input and Choreographer timestamps onto one monotonic clock.
|
||||
device_clock: Option<DeviceClock>,
|
||||
}
|
||||
@@ -169,7 +194,7 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
let ui_state = self.state.android_state_mut();
|
||||
let cursor = ui_state.cursor.clone();
|
||||
let old_focus = ui_state.focus;
|
||||
let render_state = self.rsc.ui.render_state();
|
||||
let render_state = self.rsc.ui().render_state();
|
||||
render_state
|
||||
.get()
|
||||
.run_sensors(&mut self.rsc, &mut self.state, cursor, window_size);
|
||||
@@ -200,7 +225,7 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
|
||||
let ui_state = self.state.android_state_mut();
|
||||
ui_state.cursor.end_frame();
|
||||
let render_state = self.rsc.ui.render_state();
|
||||
let render_state = self.rsc.ui().render_state();
|
||||
if render_state
|
||||
.get()
|
||||
.needs_redraw(&ui_state.root, self.rsc.widgets())
|
||||
@@ -298,13 +323,13 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
"render(): root={:?} widgets={} active={} root_px={:?} out_size={:?}",
|
||||
ui_state.root.is_some(),
|
||||
self.rsc.widgets().len(),
|
||||
self.rsc.ui.render_state().get().active_widgets(),
|
||||
self.rsc.ui().render_state().get().active_widgets(),
|
||||
ui_state
|
||||
.root
|
||||
.as_ref()
|
||||
.and_then(|r| {
|
||||
self.rsc
|
||||
.ui
|
||||
.ui()
|
||||
.render_state()
|
||||
.get()
|
||||
.window_region(r, &self.rsc)
|
||||
@@ -313,7 +338,7 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
);
|
||||
}
|
||||
let frame_start = Instant::now();
|
||||
let animating = self.rsc.ui.tick_animations(now);
|
||||
let animating = self.rsc.ui_mut().tick_animations(now);
|
||||
if animating {
|
||||
ctx.view.post_frame_callback(&mut ctx.env);
|
||||
}
|
||||
@@ -323,7 +348,7 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
let Some(renderer) = &mut ui_state.renderer else {
|
||||
return;
|
||||
};
|
||||
let frame_diagnostics = renderer.update(&mut self.rsc.ui);
|
||||
let frame_diagnostics = renderer.update(self.rsc.ui_mut());
|
||||
if renderer.frame_count() <= DIAGNOSTIC_FRAMES {
|
||||
log::info!(
|
||||
"iris frame diagnostics: frame={} masks_resized={} moves_resized={} \
|
||||
@@ -344,20 +369,20 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
.android_state_mut()
|
||||
.frame_report
|
||||
.record(now, parts, animating);
|
||||
let render_state = self.rsc.ui.render_state();
|
||||
let render_state = self.rsc.ui().render_state();
|
||||
crate::diagnostics::log_frame(&render_state.get(), now, parts, animating);
|
||||
if crate::diagnostics::trace_enabled() {
|
||||
let ui_state = self.state.android_state();
|
||||
log::debug!(
|
||||
target: "iris::frame",
|
||||
"render(): after update active={} root_px={:?}",
|
||||
self.rsc.ui.render_state().get().active_widgets(),
|
||||
self.rsc.ui().render_state().get().active_widgets(),
|
||||
ui_state
|
||||
.root
|
||||
.as_ref()
|
||||
.and_then(|r| {
|
||||
self.rsc
|
||||
.ui
|
||||
.ui()
|
||||
.render_state()
|
||||
.get()
|
||||
.window_region(r, &self.rsc)
|
||||
@@ -368,7 +393,7 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
let ui_state = self.state.android_state_mut();
|
||||
if let Some(tree_update) = ui_state.access.update(
|
||||
self.rsc.widgets(),
|
||||
&self.rsc.ui.render_state().get(),
|
||||
&self.rsc.ui().render_state().get(),
|
||||
&self.rsc,
|
||||
) {
|
||||
let ui_state = self.state.android_state_mut();
|
||||
@@ -424,7 +449,7 @@ impl<State: AndroidAppState> ViewPeer for IrisViewPeer<State> {
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
if self.rsc.events.controllers.command_target_blocks_input() {
|
||||
if self.rsc.events().controllers.command_target_blocks_input() {
|
||||
return true;
|
||||
}
|
||||
let handled = super::input::on_key(
|
||||
@@ -542,7 +567,7 @@ impl<State: AndroidAppState> ViewPeer for IrisViewPeer<State> {
|
||||
let x = event.x(&mut ctx.env);
|
||||
let y = event.y(&mut ctx.env);
|
||||
let access_events = {
|
||||
let render_handle = self.rsc.ui.render_state();
|
||||
let render_handle = self.rsc.ui().render_state();
|
||||
let render_state = render_handle.get();
|
||||
let mut source = AndroidAccessSource {
|
||||
widgets: self.rsc.widgets(),
|
||||
@@ -596,15 +621,15 @@ impl<State: AndroidAppState> ViewPeer for IrisViewPeer<State> {
|
||||
) {
|
||||
self.drain_tasks();
|
||||
// The layout canvas and wgpu surface are separate and both use physical pixels.
|
||||
self.rsc.ui.resize((width as f32, height as f32));
|
||||
self.rsc.ui_mut().resize((width as f32, height as f32));
|
||||
|
||||
// Resizing preserves GPU resources; recreating a destroyed surface does not.
|
||||
let already_live = self.state.android_state().renderer.is_some();
|
||||
log::info!(
|
||||
"iris surface: surface_changed {width}x{height} already_live={already_live} \
|
||||
glyphs_cached={} atlas_pages={}",
|
||||
self.rsc.ui.text.atlas.glyph_count(),
|
||||
self.rsc.ui.text.atlas.page_count(),
|
||||
self.rsc.ui().text.atlas.glyph_count(),
|
||||
self.rsc.ui().text.atlas.page_count(),
|
||||
);
|
||||
if already_live {
|
||||
let ui_state = self.state.android_state_mut();
|
||||
@@ -648,11 +673,11 @@ impl<State: AndroidAppState> ViewPeer for IrisViewPeer<State> {
|
||||
"iris surface: new renderer built ({:?}), re-uploading textures: \
|
||||
glyphs={} pages={}",
|
||||
renderer.adapter_backend,
|
||||
self.rsc.ui.text.atlas.glyph_count(),
|
||||
self.rsc.ui.text.atlas.page_count(),
|
||||
self.rsc.ui().text.atlas.glyph_count(),
|
||||
self.rsc.ui().text.atlas.page_count(),
|
||||
);
|
||||
self.rsc.ui.textures.reupload();
|
||||
self.rsc.ui.paints.reupload();
|
||||
self.rsc.ui_mut().textures.reupload();
|
||||
self.rsc.ui_mut().paints.reupload();
|
||||
self.state.android_state_mut().renderer = Some(renderer);
|
||||
self.render(ctx, Instant::now());
|
||||
}
|
||||
@@ -673,8 +698,8 @@ impl<State: AndroidAppState> ViewPeer for IrisViewPeer<State> {
|
||||
log::info!(
|
||||
"iris surface: surface_destroyed, tearing the renderer down \
|
||||
(glyphs_cached={} atlas_pages={})",
|
||||
self.rsc.ui.text.atlas.glyph_count(),
|
||||
self.rsc.ui.text.atlas.page_count(),
|
||||
self.rsc.ui().text.atlas.glyph_count(),
|
||||
self.rsc.ui().text.atlas.page_count(),
|
||||
);
|
||||
self.state.android_state_mut().renderer = None;
|
||||
}
|
||||
@@ -724,7 +749,7 @@ impl<State: AndroidAppState> AccessibilityNodeProvider for IrisViewPeer<State> {
|
||||
ctx: &mut CallbackCtx<'local>,
|
||||
virtual_view_id: jint,
|
||||
) -> AccessibilityNodeInfo<'local> {
|
||||
let render_handle = self.rsc.ui.render_state();
|
||||
let render_handle = self.rsc.ui().render_state();
|
||||
let render_state = render_handle.get();
|
||||
let mut source = AndroidAccessSource {
|
||||
widgets: self.rsc.widgets(),
|
||||
@@ -745,7 +770,7 @@ impl<State: AndroidAppState> AccessibilityNodeProvider for IrisViewPeer<State> {
|
||||
ctx: &mut CallbackCtx<'local>,
|
||||
focus_type: jint,
|
||||
) -> AccessibilityNodeInfo<'local> {
|
||||
let render_handle = self.rsc.ui.render_state();
|
||||
let render_handle = self.rsc.ui().render_state();
|
||||
let render_state = render_handle.get();
|
||||
let mut source = AndroidAccessSource {
|
||||
widgets: self.rsc.widgets(),
|
||||
@@ -797,6 +822,7 @@ pub fn new_peer<'local, State: AndroidAppState>(
|
||||
mut env: JNIEnv<'local>,
|
||||
view: View<'local>,
|
||||
context: Context<'local>,
|
||||
init: fn(AndroidUiState, &mut State::Resources) -> State,
|
||||
) -> android_view::jni::sys::jlong {
|
||||
// `DisplayMetrics.density` -- physical pixels per dp on this device.
|
||||
// Read once here, at the one point in this file already handed a
|
||||
@@ -810,11 +836,11 @@ 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) = StdRsc::new(redraw);
|
||||
rsc.ui.set_density(content_scale);
|
||||
let (mut rsc, task_recv) = State::Resources::new(redraw);
|
||||
rsc.ui_mut().set_density(content_scale);
|
||||
let shared = Rc::new(RefCell::new(Shared::default()));
|
||||
let ui_state = AndroidUiState::new(shared.clone(), content_scale);
|
||||
let mut state = State::new(ui_state, &mut rsc);
|
||||
let mut state = init(ui_state, &mut rsc);
|
||||
let platform_vm = env.get_java_vm().unwrap();
|
||||
let platform_view = env.new_global_ref(&view.0).unwrap();
|
||||
state.platform_ready(&mut rsc, platform_vm, platform_view);
|
||||
|
||||
Reference in new issue
Block a user