Skip to content

Add Android Port#3

Merged
colemancda merged 5 commits into
masterfrom
feature/android
Jul 14, 2026
Merged

Add Android Port#3
colemancda merged 5 commits into
masterfrom
feature/android

Conversation

@colemancda

Copy link
Copy Markdown
Member

Summary

  • Extends the 320x240 iPod baseline to match the device's real aspect ratio instead of fixed-size letterboxing (taller/wider screens now just show more content, filling edge-to-edge)
  • Sizes the SDL window request to the display's real bounds instead of the 320x240 baseline, avoiding an internal SDL orientation/coordinate mismatch that showed artifacts during resize/orientation transitions

Test plan

  • Built and deployed to an Android emulator (1080x2340)
  • Verified content fills the full screen with no letterboxing (28 visible rows vs. the old 9)
  • Verified scrolling, selection, and navigation push/pop still work correctly
  • Verified via pixel inspection that no stray/duplicate content is rendered outside the active menu area

Ports ClassicUI to Android as a shared library loaded by SDL's Java
SDLActivity -- no JNI bootstrap, no bundled assets, no Java UI code
beyond the SDLActivity subclass.

PureSwift/Cairo and PureSwift/FontConfig (which the desktop SwiftUI-
subset resolver ClassicUICore renders through via Silica) have no
Android build: there is no prebuilt libcairo/libfontconfig for the NDK,
and cross-building that whole stack from source is out of scope. So,
like ports/3DS and ports/NDS (which hit the same wall under Embedded
Swift, for different reasons), this port carries its own portable
mini-core and software rasterizer instead of ClassicUICore:

- Sources/ClassicUIAndroid/ClassicCore.swift is byte-for-byte identical
  to ports/3DS's -- the same menu/settings/Notes/Now Playing/navigation
  model, selection restore, and scroll windowing.
- Renderer.swift is the same rasterizer adapted to 32bpp ARGB8888 (the
  console ports use RGB565/ARGB1555 for their LCD framebuffers; SDL's
  Android streaming texture takes ARGB8888 directly).
- tools/gen_font.py is the same build-time Helvetica rasterizer.

Unlike the console ports, this one shares PureSwift/SDL's SDL3Swift
with the desktop SDL3Renderer: SDL3 has an official Android build
(prebuilt .aar, vendored by scripts/fetch-sdl.sh) and cross-compiles
cleanly with the Swift Android SDK. Input: gamepad (same A/B/D-pad/
X/Y/shoulder mapping as the other presenters), keyboard, and a
three-band touch mapping (top third scrolls up, bottom third down,
middle selects) for phones without a gamepad; the system Back gesture
maps to Menu.

Verified end to end: `swift build --swift-sdk aarch64-unknown-linux-
android28` links a real arm64-v8a libClassicUIAndroid.so against the
vendored SDL3 prebuilt, its Swift-runtime DT_NEED closure resolves
cleanly against the Swift Android SDK (scripts/stage-jnilibs.sh), and
`gradle assembleDebug` packages it with SDL's Java glue into a signed
debug APK. Not yet run on a device or emulator.
Extend the 320x240 iPod baseline to fill the device's real aspect
ratio (same adaptive-layout principle as the desktop renderer)
instead of pinning the canvas to a fixed size and letterboxing the
rest. The taller/wider dimension now just shows more content.

Also size the SDL window request itself to the display's real
bounds rather than the 320x240 baseline: on Android, SDL's Java
glue uses the requested width/height for its own internal
orientation/coordinate bookkeeping even though the activity is
always fullscreen, and the earlier mismatch was producing visible
artifacts during resize/orientation transitions.
ClassicRenderer.swift imports Cairo directly, not just through Silica,
but ClassicUICore's target only declared Silica as a dependency --
a stricter toolchain now enforces that direct imports need their own
explicit dependency declaration, breaking macOS/Linux CI.

The Android workflow's font-generation step needs Pillow, which
wasn't installed on the runner.
@colemancda colemancda changed the title Android: full-bleed rendering (no more 320x240 letterboxing) Add Android Port Jul 14, 2026
Xcode's bundled Swift 6.3.2 diverges from the swift.org release of the
same version on a typed-throws/existential edge case in Silica's
CGContext initializer ('any CGContext' cannot be constructed), which
doesn't reproduce with the swift.org toolchain locally or in a
matching Linux container. Installing it via swiftly, matching what the
Android workflow already does, keeps the macOS job on a toolchain that
actually builds this dependency graph.
Silica's master branch did a breaking multi-backend split upstream
(CGContext became a protocol, with the concrete Cairo-backed type
moved to a separate SilicaCairo library), which SwiftPM silently
picked up as soon as Cairo was added as an explicit top-level
dependency here, breaking ClassicRenderer.swift's direct
Silica.CGContext(surface:size:) construction on both macOS and Linux.

Pinning to the last commit before that split keeps the build working
without a larger migration to the new multi-backend API.
@colemancda
colemancda merged commit bebd414 into master Jul 14, 2026
3 checks passed
@colemancda
colemancda deleted the feature/android branch July 14, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant