diff --git a/iris/android-app/src/bench_client.rs b/iris/android-app/src/bench_client.rs index ed4c878..2e98beb 100644 --- a/iris/android-app/src/bench_client.rs +++ b/iris/android-app/src/bench_client.rs @@ -58,6 +58,12 @@ pub struct BenchClient { ui_state: AndroidUiState, content: WeakWidget, report_display: WeakWidget, + /// The top button row, in a `WidgetPtr` slot rather than added + /// directly (like `content`) so `on_insets_changed` can swap in a + /// version padded for the status bar once insets are known -- RUST.md's + /// P0 box, "the status-bar inset is not applied," found the row sitting + /// directly under it because nothing here read `insets().top` at all. + top_bar: WeakWidget, screen: Option, items: Vec, /// The events not yet streamed -- consumed by `start_benchmark`'s own @@ -172,9 +178,11 @@ impl AndroidAppState for BenchClient { .label("Benchmark report") .add(rsc); - let controls = bench_controls(rsc); + let top_bar = WidgetPtr::new().add(rsc); + let controls = bench_controls(rsc, 0.0); + top_bar(rsc).set(controls); let tree = ( - controls, + top_bar, content.height(rest(2)), report_display.height(rest(1)).pad(8), ) @@ -183,10 +191,28 @@ impl AndroidAppState for BenchClient { .any(); ui_state.set_root(tree); + // Startup log line (RUST.md's P0 box, "log once at startup ... the + // number of font families found, the default family resolved"): + // what font discovery actually found on this device, before + // anything is drawn. + let font = rsc.ui.text.font_diagnostics(); + log::info!( + "iris fonts: {} families found, default={:?} mono={:?}, resolved regular={:?} \ + bold={:?} italic={:?} mono={:?}", + font.families_found, + font.default_family, + font.default_mono_family, + font.regular_resolved, + font.bold_resolved, + font.italic_resolved, + font.mono_resolved, + ); + let mut client = Self { ui_state, content, report_display, + top_bar, screen: None, items: Vec::new(), stream_tail: Vec::new(), @@ -216,11 +242,24 @@ impl AndroidAppState for BenchClient { fn back_pressed(&mut self, _rsc: &mut AndroidRsc, _render: &mut UiRenderState) -> bool { false } + + /// Pads the top button row by the status-bar inset -- see `top_bar`'s + /// field comment. Rebuilds the row rather than mutating a stored + /// `Padding` in place, since nothing here holds a handle to one. + fn on_insets_changed(&mut self, rsc: &mut AndroidRsc, insets: iris::android::LogicalInsets) { + let controls = bench_controls(rsc, insets.top); + (self.top_bar)(rsc).set(controls); + } } type Rsc = AndroidRsc; -fn bench_controls(rsc: &mut Rsc) -> WeakWidget { +/// `top_pad` is the status-bar inset in logical units (0.0 until +/// `on_insets_changed` has run once) -- folded in here, rather than +/// exposing the unadded builder for a caller to `.pad()` itself, because +/// naming that builder's type at each call site is more machinery than a +/// top-of-screen padding number is worth. +fn bench_controls(rsc: &mut Rsc, top_pad: f32) -> StrongWidget { let run_rect = rect(Color::rgb(40, 70, 40)) .on( CursorSense::click(), @@ -253,7 +292,28 @@ fn bench_controls(rsc: &mut Rsc) -> WeakWidget { .pad(8) .add(rsc); - (run, copy).span(Dir::RIGHT).height(56).add(rsc) + let diag_rect = rect(Color::rgb(60, 45, 70)) + .on( + CursorSense::click(), + |ctx: EventIdCtx<'_, Rsc, _, _>, rsc: &mut Rsc| { + ctx.state.show_diagnostics(rsc); + }, + ) + .label("Diagnostics"); + let diagnostics = ( + diag_rect, + wtext("Diagnostics").size(18).text_align(Align::CENTER), + ) + .stack() + .pad(8) + .add(rsc); + + (run, copy, diagnostics) + .span(Dir::RIGHT) + .height(56) + .pad(Padding::top(top_pad)) + .add_strong(rsc) + .any() } impl BenchClient { @@ -270,6 +330,26 @@ impl BenchClient { self.screen = Some(screen); } + /// RUST.md's P0 box: "a named `Diagnostics` control ... with 'copy this + /// and send it to Iris'." Fills `report_display` (the same TextEdit the + /// benchmark report uses) rather than a separate widget, so the + /// existing "Copy report" button and clipboard path work on whichever + /// text is currently shown -- `last_report` is what `copy_report` reads, + /// so it's set here too rather than adding a second copy path. + fn show_diagnostics(&mut self, rsc: &mut Rsc) { + let font = rsc.ui.text.font_diagnostics(); + let frame_report = match self.android_state().frame_report.report() { + Some(stats) => format!("{stats}"), + None => "no frames recorded yet".to_string(), + }; + let report = match &self.android_state().renderer { + Some(renderer) => renderer.diagnostics_report(&font, &frame_report), + None => "iris diagnostics: no renderer yet (no surface)".to_string(), + }; + self.report_display.edit(rsc).set(&report); + self.last_report = Some(report); + } + fn copy_report(&mut self) { let Some(report) = &self.last_report else { log::info!("iris bench report: nothing to copy -- run the benchmark first"); diff --git a/iris/core/src/primitive/text.rs b/iris/core/src/primitive/text.rs index db50a02..1480d43 100644 --- a/iris/core/src/primitive/text.rs +++ b/iris/core/src/primitive/text.rs @@ -26,10 +26,8 @@ use swash::{ const NOTO_SANS_REGULAR: &[u8] = include_bytes!("../../assets/fonts/NotoSans-Regular.ttf"); const NOTO_SANS_BOLD: &[u8] = include_bytes!("../../assets/fonts/NotoSans-Bold.ttf"); const NOTO_SANS_ITALIC: &[u8] = include_bytes!("../../assets/fonts/NotoSans-Italic.ttf"); -const NOTO_SANS_BOLD_ITALIC: &[u8] = - include_bytes!("../../assets/fonts/NotoSans-BoldItalic.ttf"); -const NOTO_SANS_MONO_REGULAR: &[u8] = - include_bytes!("../../assets/fonts/NotoSansMono-Regular.ttf"); +const NOTO_SANS_BOLD_ITALIC: &[u8] = include_bytes!("../../assets/fonts/NotoSans-BoldItalic.ttf"); +const NOTO_SANS_MONO_REGULAR: &[u8] = include_bytes!("../../assets/fonts/NotoSansMono-Regular.ttf"); const NOTO_SANS_MONO_BOLD: &[u8] = include_bytes!("../../assets/fonts/NotoSansMono-Bold.ttf"); /// What starting up found about text rendering, for the on-screen @@ -203,14 +201,26 @@ impl TextData { family_id.and_then(|id| self.font_cx.collection.family_name(id).map(str::to_string)) }; - let regular_resolved = - resolve_family(GenericFamily::SansSerif, FontWeight::NORMAL, FontStyle::Normal); - let bold_resolved = - resolve_family(GenericFamily::SansSerif, FontWeight::BOLD, FontStyle::Normal); - let italic_resolved = - resolve_family(GenericFamily::SansSerif, FontWeight::NORMAL, FontStyle::Italic); - let mono_resolved = - resolve_family(GenericFamily::Monospace, FontWeight::NORMAL, FontStyle::Normal); + let regular_resolved = resolve_family( + GenericFamily::SansSerif, + FontWeight::NORMAL, + FontStyle::Normal, + ); + let bold_resolved = resolve_family( + GenericFamily::SansSerif, + FontWeight::BOLD, + FontStyle::Normal, + ); + let italic_resolved = resolve_family( + GenericFamily::SansSerif, + FontWeight::NORMAL, + FontStyle::Italic, + ); + let mono_resolved = resolve_family( + GenericFamily::Monospace, + FontWeight::NORMAL, + FontStyle::Normal, + ); FontDiagnostics { families_found, diff --git a/iris/src/android/mod.rs b/iris/src/android/mod.rs index e34159c..f92f80d 100644 --- a/iris/src/android/mod.rs +++ b/iris/src/android/mod.rs @@ -23,7 +23,8 @@ mod view; pub use insets::Insets; pub use render::AndroidRenderer; pub use view::{ - AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState, IrisViewPeer, new_peer, + AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState, IrisViewPeer, LogicalInsets, + new_peer, }; /// Registers the extra native methods this backend needs beyond what diff --git a/iris/src/android/render.rs b/iris/src/android/render.rs index 4ac6a60..5cdd85e 100644 --- a/iris/src/android/render.rs +++ b/iris/src/android/render.rs @@ -213,10 +213,8 @@ impl AndroidRenderer { // for why this crate now divides at all (RUST.md's P0 box, "text // is far too small"). The swapchain above stays at the real // physical `width`/`height` for a sharp framebuffer. - let logical_size = iris_core::util::Vec2::new( - width as f32 / content_scale, - height as f32 / content_scale, - ); + let logical_size = + iris_core::util::Vec2::new(width as f32 / content_scale, height as f32 / content_scale); let ui = match UiRenderNode::new(&device, &queue, &config, logical_size) { Ok(ui) => ui, Err(wgpu_error) => return Err(Self::diagnostic(&adapter, &wgpu_error)), diff --git a/iris/src/default/input.rs b/iris/src/default/input.rs index 7a0c475..edf044a 100644 --- a/iris/src/default/input.rs +++ b/iris/src/default/input.rs @@ -19,8 +19,7 @@ impl Input { pub fn event(&mut self, event: &WindowEvent, scale_factor: f32) -> bool { match event { WindowEvent::CursorMoved { position, .. } => { - self.cursor.pos = - Vec2::new(position.x as f32, position.y as f32) / scale_factor; + self.cursor.pos = Vec2::new(position.x as f32, position.y as f32) / scale_factor; self.cursor.exists = true; } WindowEvent::MouseInput { state, button, .. } => { @@ -79,7 +78,10 @@ impl DefaultUiState { let window = self.renderer.window(); let size = window.inner_size(); let scale_factor = window.scale_factor() as f32; - Vec2::new(size.width as f32 / scale_factor, size.height as f32 / scale_factor) + Vec2::new( + size.width as f32 / scale_factor, + size.height as f32 / scale_factor, + ) } pub fn cursor_state(&self) -> &CursorState { diff --git a/iris/src/default/render.rs b/iris/src/default/render.rs index 4436852..2306da1 100644 --- a/iris/src/default/render.rs +++ b/iris/src/default/render.rs @@ -163,7 +163,10 @@ impl UiRenderer { // found on Iris's phone, just never noticed here because this // crate's own dev monitors happen to run at 1.0. let scale_factor = window.scale_factor() as f32; - let logical_size = Vec2::new(size.width as f32 / scale_factor, size.height as f32 / scale_factor); + let logical_size = Vec2::new( + size.width as f32 / scale_factor, + size.height as f32 / scale_factor, + ); let ui = UiRenderNode::new(&device, &queue, &config, logical_size) .expect("Could not create iris render node!");