The Gradle side of RUST.md's I2: MainActivity, IrisView (extending
android-view's RustView with the two native methods it has no hook
for -- window insets, and unregistering this view's entry in
iris::android::insets's side table), and RustView.java/
RustInputConnection.java vendored from android-view (no published AAR
to depend on) with one deliberate diff noted in a comment: mViewPeer
is protected rather than package-private, so a subclass in a different
package can reach it.
Measured on the emulator (x86_64, API 26, SwiftShader Vulkan):
dumpsys input_method shows the served InputConnection is ours, and
Gboard's suggestion strip reads real buffer content back through
text_before_cursor ("hi | Hi | HI" after typing "hi") -- the same bar
E1 set, met. Not met: nothing draws. The clear colour reaches the
screen (confirmed by swapping it to magenta) and the layout engine
reports the correct widget count and pixel regions (log::debug! calls
left in view.rs's render() for exactly this), but no primitive shows
up, on both Vulkan/SwiftShader and GLES/virgl. Root cause not found;
one unconfirmed lead (a GLES-only D2/D2Array warning that could point
at the glyph atlas) is written up in RUST.md's I2 rather than chased
into core/src/render/, which is mid-flight in a separate benchmark
branch this session.
I2 is therefore built and wired but not tickable -- RUST.md has the
full writeup, what was ruled out, and where to pick this up.
Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
6.1 KiB
iris: notable public API changes
For Iris to read on her own time. Each entry is a change to iris's public surface that a widget author or app author would notice: a trait method added, removed or re-shaped; a type that callers construct differently; a capability that moved. Small and trivial changes do not go here.
An entry gives the date, what changed, why, and a short before/after where it helps judge the change without the session that made it. Newest first.
2026-09-05: a second backend (android-view), and what moved to make room for it
RUST.md's I2. Three changes a widget or app author would notice, all in
service of the same thing: default (winit) and the new android
(android-view) backends sharing what does not depend on windowing.
Selector/Selectable's bound changed fromRsc::State: HasDefaultUiStatetoRsc::State: FocusHost(new trait,attr.rs).HasDefaultUiStatestill exists and still works —default/attr.rsnow implementsFocusHostfor anything that has it — so a winit app's existing code is unaffected. An Android app implementsFocusHostviaHasAndroidUiStateinstead. Affects only an app that referencedHasDefaultUiStatedirectly at aSelectable/Selectorcall site rather than through.attr::<Selectable>(()), which nothing in-tree does.Tasks::inittakesArc<dyn RequestRedraw>instead ofArc<winit::window::Window>.RequestRedraw(task.rs) is one method,fn request_redraw(&self);winit::window::Windowimplements it (default/render.rs), soTasks::init(window)at a call site is unchanged by inference. Only matters if something constructed aTasksdirectly rather than throughDefaultRsc/AndroidRsc.TextEdit::apply_event/TextInputResultare#[cfg(not(target_os = "android"))]— they take awinit::event::KeyEvent, which does not exist on Android;android/input.rsdrives the same primitives (backspace/delete/motion/insert, all still unconditional) fromndk::event::Keycodedirectly instead. New unconditional getters on the way:TextEdit::text()/selection_range()/caret(), andTextEditCtx::delete_byte_range/set_cursor_byte— the primitivesandroid/ime.rs'sInputConnectionbridge needed and that were not previously exposed publicly.
2026-09-04: Widget::draw reports the size it used; desired_width/desired_height are gone
A widget used to implement three methods (draw, desired_width,
desired_height); it now implements one, fn draw(&mut self, painter: &mut Painter) -> Size, which draws into painter.region() and returns how much
of it was used. Why: the two extra methods routinely re-simulated what
draw was about to do anyway (Span::desired_ortho copied its own draw
loop to get cross-axis sizing right) — one visit per widget per frame
instead of up to three. A container that needs a child's size before
placing it (alignment, centering) draws the child once at a provisional
region, reads the returned Size, and calls the new Painter::reposition
to move it into its final spot — an O(1) offset write, not a second draw. A
widget whose drawn output never depends on the size it's given (a
fixed-size Rect, a decoded Image) overrides the new fn is_size_independent(&self) -> bool { false } to true, which skips
redrawing it when only its offered region changes shape.
// before
fn draw(&mut self, painter: &mut Painter) { /* ... */ }
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { /* ... */ }
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len { /* ... */ }
// after
fn draw(&mut self, painter: &mut Painter) -> Size { /* ... */ }
SizeCtx and Cache are gone with it — see LAYOUT.md for the full
design, the move-offset mechanism this shipped alongside, and the file
list.
2026-09-04: texture pipeline rebuilt off the binding array
Textures/TextureHandle, GlyphPrimitive, and UiRenderNode::new all
changed shape. Why: the old pipeline bound every texture ever drawn in one
binding_array<texture_2d<f32>> and asked every device, unconditionally,
for VK_EXT_descriptor_indexing — a real share of Android GPUs lack it,
and it failed outright on the Android emulator's software Vulkan. See
TEXTURES.md's "Recommended shape" and "Implemented, 2026-09-04".
UiRenderNode::newdrops itslimits: UiLimitsparameter, andUiLimitsis gone. Before:UiRenderNode::new(&device, &queue, &config, UiLimits::default()). After:UiRenderNode::new(&device, &queue, &config). Nothing replaces it — there are no more binding-array limits to size.src/default/render.rs's device request asks for no features and no binding-array limits. Before:required_features: Features::TEXTURE_BINDING_ARRAY | Features::PARTIALLY_BOUND_BINDING_ARRAY | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXINGplus twomax_binding_array_*limits. After:Features::empty()(theDeviceDescriptordefault) and onlymax_buffer_sizeset, which was never about the binding array.TextureHandlehas noprimitive()method any more; a caller outsideirisshouldn't have been calling it (it fed the old renderer's internals), but if something did: useimage_index()for a standalone image's bind-group index. There is no equivalent for a page — a page has no bind group of its own now, see below.GlyphPrimitivehas no public constructor from a struct literal. Before:GlyphPrimitive { uv_min, uv_max, view_idx, sampler_idx, color, flags }. After:GlyphPrimitive::new(uv_min, uv_max, layer, color, flags)— onelayer(the shared atlas array's layer) instead of aview_idx/sampler_idxpair, since a page is now a layer of one array texture rather than its own bound texture.- A widget author drawing images is unaffected:
Painter::texture/texture_at/texture_withinandTextures::addkeep their signatures. What changed underneath is that each standalone image now gets its ownwgpu::BindGroupand draw call instead of a slot in the shared array — invisible from the widget API, visible only inUiRenderNode's internals and iniris's device requirements.