Compare commits

...
8 Commits
Author SHA1 Message Date
DWA e9527ee8c2 refactor(publish): update caching and build logic for native dependencies based on platform 2026-07-13 17:47:41 +02:00
DWA 6040816cf7 refactor(scrollbar): improve drag handling and scrolling logic for smoother interactions 2026-07-13 17:31:07 +02:00
Bitsy 6f3bc915b2 docs: update build instructions and system dependencies for SDL3 integration 2026-07-12 23:46:54 +02:00
Bitsy 1ba84a3a2c feat(material-symbols): common API with per-stack renderers (native IconFont / JVM Skiko)
MaterialSymbols<Style> moves to commonMain so shared app code can use it on
both stacks; an internal expect/actual picks the renderer:
- native: unchanged — :foundation IconFontIcon (IconFont handles SDL3/Skia)
- jvm (NEW): Skiko direct — Typeface.makeClone per axes combination +
  TextLine on the native canvas. Not the upstream Font(variationSettings)
  route: published desktop artifacts never apply the settings at typeface
  load, and even at the pinned dev ref FontCache drops them from its cache
  key, collapsing every axes combination onto the first one loaded. Fill is
  quantised to 1/100 steps so animated FILL keeps the clone cache bounded.

Wiring:
- commonMain declares the official Maven compose coords; a repo-wide
  FULL-COMMONIZATION BRIDGE (root build) substitutes ui/foundation/material3
  for the project modules on native configurations (runtime never swapped)
- jvm stack pinned to 1.12.0-beta01+dev4324 = the vendored COMPOSE_CORE_REF
  (material3 rides its own train: 1.12.0-alpha03+dev4324); forced on jvm
  configs since gradle orders +dev below the plain version
- demo stages used icon fonts onto the jvm classpath (jvmProcessResources);
  publish workflow adds the jvm publication (macOS host)

Demo:
- DemoIcon is now common — same Material Symbols glyphs on both stacks,
  material-icons-extended jvm shim deleted
- IconsScreen (material3) keeps Icon(painter) + IconButton; the symbols
  engine gets its own Others > MaterialSymbols screen: axis sweeps, style
  families, and an animated-FILL selected-state transition
2026-07-12 23:24:53 +02:00
Bitsy e807ecf973 feat(demo): implement clipboard functionality for demo jvm 2026-07-12 22:08:13 +02:00
Bitsy dc35f5bd37 build: update build scripts to use Python for SDL builds 2026-07-12 21:16:36 +02:00
Bitsy 4888fdcda0 build: update build scripts to use Python for SDL builds 2026-07-12 21:10:44 +02:00
Bitsy 85e0179590 ci(publish): use python zipfile so macOS extracts keep the exec bit
`jar --create` strips file modes to 0644 in the archive, so any .kexe
inside extracted by macOS Archive Utility (or `ditto` / `unzip -oa`)
comes out non-executable and fails to launch. Switch the packaging
step to python's `zipfile` module — it's on every runner and preserves
the source file's exec bit + marks entries as Unix-origin, so
extractors read the right permissions.

Verified locally with ditto (Archive Utility's engine): jar-produced
zips extract source `hello.txt` at 0755 down to 0644; python-produced
zips preserve 0755 through the round-trip.

Only touches the CI workflow — next release picks up the fix, no
retag for v0.1.11 (which is publishing now with jar).
2026-07-12 18:38:05 +02:00
42 changed files with 7485 additions and 1436 deletions
+25 -15
View File
@@ -31,9 +31,13 @@ jobs:
host: macos
target: MacosArm64
exe_ext: kexe
# jvm publication: only :material-symbols declares a jvm() target
# (its common API renders via upstream Compose on JVM) — publish
# its host-independent jar from the same host as the metadata.
tasks: >-
publishKotlinMultiplatformPublicationToGitHubPackagesRepository
publishMacosArm64PublicationToGitHubPackagesRepository
publishJvmPublicationToGitHubPackagesRepository
- runner: ubuntu-22.04
host: linux
target: LinuxX64
@@ -95,23 +99,33 @@ jobs:
# intrinsic K/N's LLD can't resolve is aliased to msvcrt's _setjmpex at
# link time via --defsym in sdl3.def.
# Build static SDL3 / SDL3_ttf / SDL3_image / FreeType from
# scripts/build-sdl/build-sdl.properties. Cached against the manifest +
# scripts so version bumps invalidate cleanly.
# `cache_version` is a manual bump for when the toolchain (runner OS,
# compiler flavour, K/N sysroot expectations) changes without any
# scripts/build-sdl/ file changing. Increment to force a rebuild.
# Build the static native deps from scripts/build-sdl/build-sdl.properties.
# Only Windows ships the SDL3 renderer (no Skiko on mingw), so only it needs
# SDL3_ttf / SDL3_image / FreeType (SDL3_ttf text, SDL3_image decode, FreeType
# icon-axis rasterisation). macOS / Linux publish the Skia renderer — Skiko
# handles text / images / icons — so they only need base SDL3 for the main
# loop (windowing / input / audio / filesystem). Matches the cinterop guard
# in compose/ui/ui/build.gradle.kts, which skips those three cinterops on the
# Skia targets. Cached against the manifest + scripts so version bumps
# invalidate cleanly; bump the `v3` key when the toolchain (runner OS,
# compiler flavour, K/N sysroot) changes without a scripts/build-sdl/ edit.
- name: Cache libs/
id: cache-libs
uses: actions/cache@v4
with:
path: libs
key: sdl-libs-v2-${{ matrix.host }}-${{ hashFiles('scripts/build-sdl/**') }}
key: sdl-libs-v3-${{ matrix.host }}-${{ hashFiles('scripts/build-sdl/**') }}
- name: Build static SDL / TTF / image / FreeType
# Windows: full SDL3 renderer stack. macOS / Linux (Skia): base SDL3 only.
- name: Build static native deps
if: steps.cache-libs.outputs.cache-hit != 'true'
shell: bash
run: bash scripts/build-sdl/build-all.sh
run: |
if [ "${{ matrix.host }}" = "windows" ]; then
python3 scripts/build-sdl/build-all.py
else
python3 scripts/build-sdl/build-all.py sdl3
fi
# src/vendor/ trees are gitignored — a fresh checkout has zero vendored
# androidx.compose.* files. Sync copies them in from the upstream refs
@@ -167,15 +181,11 @@ jobs:
target_dir=$(echo "${{ matrix.target }}" | awk '{print tolower(substr($0,1,1)) substr($0,2)}')
version="${GITHUB_REF_NAME#v}"
mkdir -p dist
# Windows Git Bash has no `zip`; every runner has JDK 21 → use `jar cf`
# which writes a valid ZIP file. `-C dir file` = enter dir then add
# `file` at the archive root, so the archive stays flat like `zip -j`.
for app in demo apidemo; do
src="$app/build/bin/$target_dir/releaseExecutable"
zip_path="$(pwd)/dist/$app-$version-$target_dir.zip"
jar --create --no-manifest --file "$zip_path" \
-C "$src" "$app.${{ matrix.exe_ext }}" \
-C "$src" "data.kres"
python3 -c 'import sys,os,zipfile; z=zipfile.ZipFile(sys.argv[1],"w",zipfile.ZIP_DEFLATED,compresslevel=9); [z.write(f,os.path.basename(f)) for f in sys.argv[2:]]; z.close()' \
"$zip_path" "$src/$app.${{ matrix.exe_ext }}" "$src/data.kres"
done
ls -la dist/
+31 -19
View File
@@ -60,8 +60,18 @@ compose/
utils/
└── material-symbols/ → :material-symbols — codepoints + all three style objects
(Outlined / Rounded / Sharp). Apps get one dep;
the consumer Zip task bundles only the fonts used.
(Outlined / Rounded / Sharp). COMMON API (usable from
shared app code) + per-stack actuals: native renders
via :foundation IconFontIcon (IconFont handles the
SDL3/Skia split), jvm() via Skiko directly
(Typeface.makeClone per axes — upstream's FontCache
drops variationSettings from its key). Its commonMain
declares official Maven compose coords — the root
build's FULL-COMMONIZATION BRIDGE substitutes ui /
foundation / material3 to project modules on native
configs. Apps get one dep; the consumer Zip task
bundles only the fonts used (native) and
jvmProcessResources stages the same fonts (jvm).
components/
└── resources/library/ → :components-resources — the OFFICIAL Compose resources
@@ -94,11 +104,12 @@ demo/ → :demo — flagship showcase app (30+ screens) + th
screens on stock JVM Compose Desktop (`./gradlew :demo:run`,
MainJvmKt) — the parity reference; differences vs native = port bugs
apidemo/ → :apidemo — Postman-style REST API manager
scripts/ → vendor-sync + python helper scripts (compose-fidelity/coverage,
material-symbols generate/subset) + compose-fork/;
scripts/build-sdl/ = Windows static-lib build scripts (bash)
scripts/ → vendor-sync + python helper scripts (compose-coverage = API
coverage/fidelity vs upstream, material-symbols generate/subset)
+ compose-fork/;
scripts/build-sdl/ = static-lib build script (python)
libs/ → gitignored per-host static SDL3 / SDL3_ttf / SDL3_image / FreeType
output of scripts/build-sdl/build-*.sh on Windows
output of scripts/build-sdl/build-all.py on Windows
```
Module PATHS stay short (`:ui`, `:foundation`, `:window`, …) —
@@ -313,20 +324,21 @@ gradlew.bat :apidemo:runDebugExecutableMingwX64
### System dependencies
**macOS (default Skia build):** `brew install sdl3`. Only need `sdl3_ttf`
if you set `-Prenderer=sdl3`. Skiko klibs come from Maven.
SDL3 + SDL3_ttf + SDL3_image + FreeType (+ image codecs) are **built from
source as static libraries on every OS** and linked straight into the
executable — no brew/apt SDL packages, no runtime .dll/.so/.dylib;
a distributable is `<app>` + `data.kres`. One script does it all:
`python scripts/build-sdl/build-all.py` (plain Python 3, no Git Bash needed)
builds into the gitignored, in-repo `libs/` folder. Versions/URLs pinned in
`scripts/build-sdl/build-sdl.properties`. Steps run in order freetype → sdl3
→ sdl3-image → sdl3-ttf; pass step names to rebuild a subset
(`python scripts/build-sdl/build-all.py sdl3-ttf`).
**Linux (default Skia build):** `sudo apt install libsdl3-dev`. Same caveat
for SDL3_ttf.
**Windows (mingwX64 — always uses SDL3 + SDL3_ttf + SDL3_image + FreeType):**
these four libraries + image codecs are **linked statically into the
executable** — no runtime DLLs, distributable is just `<app>.exe` +
`data.kres`. They're not downloaded — `scripts/build-sdl/build-all.sh` (from Git
Bash) builds them from source as static libs into a gitignored, in-repo `libs/`
folder. Needs: git, cmake, mingw-w64 gcc/g++ on PATH, plus curl + python for
ninja fetch. `scripts/build-sdl/build-all.sh` = `build-freetype.sh` → `build-sdl3.sh` →
`build-sdl3-image.sh` → `build-sdl3-ttf.sh`.
Needs everywhere: git, cmake, python 3 (ninja fetched automatically). Per
host: macOS = Xcode CLT (Skia default; Skiko klibs from Maven); Linux =
gcc/g++ + the X11/Wayland/audio dev headers SDL3's configure detects (apt
list in .github/workflows/publish.yml); Windows = mingw-w64 g++ on PATH
(C compiles with K/N's bundled mingw).
## Runtime bundling — data.kres
+39 -8
View File
@@ -47,7 +47,11 @@ compose/
per-window Lifecycle/ViewModelStore/SavedState owners)
utils/
└── material-symbols/ → :material-symbols (codepoints + Outlined/Rounded/Sharp icon fonts)
└── material-symbols/ → :material-symbols (Material Symbols: 4200+ codepoints + Outlined/Rounded/
Sharp composables with the variable-font axes as
parameters (FILL/wght/GRAD/opsz). Common API — native
draws via the port's icon-font engine, the jvm target
via Skiko, so shared app code uses it on both stacks)
components/
└── resources/library/ → :components-resources (vendored official compose resources runtime —
@@ -133,13 +137,31 @@ implementation(project(":material-symbols")) // icon-font composables (optional
## Building
- **macOS:** `brew install sdl3` (Skia is the default; Skiko klibs come from
Maven).
- **Linux:** `sudo apt install libsdl3-dev`.
- **Windows:** SDL3, SDL3_ttf, SDL3_image and FreeType are built from source
as **static** libraries by `scripts/build-sdl/build-all.sh` into the gitignored
in-repo `libs/`, then linked into the executable — the Windows
distributable is just `<app>.exe` + `data.kres`, no runtime DLLs.
All four native dependencies — **SDL3**, **SDL3_ttf** (the in-house
variable-font-axes fork), **SDL3_image** (+ vendored PNG/WEBP codecs) and
**FreeType** — are built from source as **static** libraries by one script,
on every OS, and linked straight into the executable. No `brew install`, no
`apt install libsdl3-dev`, no runtime `.dll` / `.so` / `.dylib` next to the
binary — a distributable is just `<app>` + `data.kres`.
```bash
python3 scripts/build-sdl/build-all.py # freetype → sdl3 → sdl3-image → sdl3-ttf
python3 scripts/build-sdl/build-all.py sdl3-ttf # rebuild a subset
```
Library versions / URLs are pinned in `scripts/build-sdl/build-sdl.properties`;
output lands in the gitignored in-repo `libs/`. Required on every host: `git`,
`cmake`, Python 3 (`ninja` is fetched automatically when absent). Per host:
- **macOS** — Xcode command line tools. Skia is the default renderer; Skiko
klibs come from Maven.
- **Linux** — gcc/g++ plus the X11 / Wayland / audio dev headers SDL3's
configure detects (the exact apt list is in
`.github/workflows/publish.yml`).
- **Windows** — a mingw-w64 g++ on PATH (the C side compiles with
Kotlin/Native's bundled mingw).
Then build any app target — see the demo / apidemo commands above.
## Vendoring
@@ -158,6 +180,15 @@ Upstream ref pinned in `scripts/compose-fork/compose.properties`; manifests can
pin additional upstream repos inline (`SET_REPO=<url>@<ref>` — the resources
runtime vendors from the compose-multiplatform umbrella repo this way).
How much of upstream's public API the port actually covers is measured, not
guessed — `scripts/compose-coverage.py` diffs this repo's klib ABI dumps
against upstream's, per vendored module:
```bash
./gradlew apiDump && python3 scripts/compose-coverage.py # per-module coverage tables
python3 scripts/compose-coverage.py --missing ui-text # list the exact uncovered decls
```
See [CLAUDE.md](CLAUDE.md) for the full architecture, source-set hierarchy,
vendoring rules, density flow (physical-pixel Option B), and per-area file
map.
+23 -3
View File
@@ -8,8 +8,8 @@ plugins {
alias(libs.plugins.kotlin.plugin.compose) apply false
alias(libs.plugins.kotlin.plugin.serialization) apply false
alias(libs.plugins.compose.multiplatform) apply false
// Public-ABI dumps (api/<module>.klib.api). Used by scripts/compose-fidelity-check.py
// to diff our androidx.compose.* surface against the official Compose klib ABI.
// Public-ABI dumps (api/<module>.klib.api). Used by scripts/compose-coverage.py
// to diff our mirrored API surface against the official upstream klib ABI dumps.
alias(libs.plugins.binary.compatibility.validator)
}
@@ -108,10 +108,30 @@ subprojects {
}
}
// FULL-COMMONIZATION BRIDGE (repo-wide): a module may declare the OFFICIAL
// Maven Compose artifacts in its commonMain so metadata + jvm resolve them
// (e.g. :material-symbols' common API); every NATIVE target configuration
// swaps those modules for the port's project equivalents — the Maven
// artifacts ship no mingwX64/linux klibs. org.jetbrains.compose.runtime is
// deliberately NOT here: the port uses the official runtime klibs everywhere.
// (:demo carries the same pattern locally for components-resources.)
val vNativeTargetTokens = listOf("mingwX64", "linuxX64", "linuxArm64", "macosArm64")
allprojects {
configurations.configureEach {
if (vNativeTargetTokens.any { name.contains(it, ignoreCase = true) }) {
resolutionStrategy.dependencySubstitution {
substitute(module("org.jetbrains.compose.ui:ui")).using(project(":ui"))
substitute(module("org.jetbrains.compose.foundation:foundation")).using(project(":foundation"))
substitute(module("org.jetbrains.compose.material3:material3")).using(project(":material3"))
}
}
}
}
// Whether the current host can build the mingwX64 target. Kotlin/Native can
// only cross-compile mingwX64 cinterops from a Windows host — the sdl3_ttf /
// sdl3_image cinterops need Windows SDL3 headers under libs/ (produced by
// scripts/build-sdl/build-*.sh, which only runs on Windows). Declaring `mingwX64()` on
// scripts/build-sdl/build-all.py run on a Windows host). Declaring `mingwX64()` on
// a non-Windows host is safe for pure-Kotlin modules but blows up the moment
// a `depends = sdl3` cinterop tries to include SDL3_ttf/SDL_ttf.h.
// Override with `-PforceMingw=true` if you actually have the headers wired.
@@ -0,0 +1,238 @@
// Klib ABI Dump
// Targets: [linuxArm64, linuxX64, macosArm64, mingwX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
// - Show declarations: true
// Library unique name: <com.bitsycore.compose.sdl:components-resources>
open annotation class org.jetbrains.compose.resources/ExperimentalResourceApi : kotlin/Annotation { // org.jetbrains.compose.resources/ExperimentalResourceApi|null[0]
constructor <init>() // org.jetbrains.compose.resources/ExperimentalResourceApi.<init>|<init>(){}[0]
}
open annotation class org.jetbrains.compose.resources/InternalResourceApi : kotlin/Annotation { // org.jetbrains.compose.resources/InternalResourceApi|null[0]
constructor <init>() // org.jetbrains.compose.resources/InternalResourceApi.<init>|<init>(){}[0]
}
open annotation class org.jetbrains.compose.resources/ResourceContentHash : kotlin/Annotation { // org.jetbrains.compose.resources/ResourceContentHash|null[0]
constructor <init>(kotlin/Int) // org.jetbrains.compose.resources/ResourceContentHash.<init>|<init>(kotlin.Int){}[0]
final val hash // org.jetbrains.compose.resources/ResourceContentHash.hash|{}hash[0]
final fun <get-hash>(): kotlin/Int // org.jetbrains.compose.resources/ResourceContentHash.hash.<get-hash>|<get-hash>(){}[0]
}
final enum class org.jetbrains.compose.resources/DensityQualifier : kotlin/Enum<org.jetbrains.compose.resources/DensityQualifier>, org.jetbrains.compose.resources/Qualifier { // org.jetbrains.compose.resources/DensityQualifier|null[0]
enum entry HDPI // org.jetbrains.compose.resources/DensityQualifier.HDPI|null[0]
enum entry LDPI // org.jetbrains.compose.resources/DensityQualifier.LDPI|null[0]
enum entry MDPI // org.jetbrains.compose.resources/DensityQualifier.MDPI|null[0]
enum entry XHDPI // org.jetbrains.compose.resources/DensityQualifier.XHDPI|null[0]
enum entry XXHDPI // org.jetbrains.compose.resources/DensityQualifier.XXHDPI|null[0]
enum entry XXXHDPI // org.jetbrains.compose.resources/DensityQualifier.XXXHDPI|null[0]
final val dpi // org.jetbrains.compose.resources/DensityQualifier.dpi|{}dpi[0]
final fun <get-dpi>(): kotlin/Int // org.jetbrains.compose.resources/DensityQualifier.dpi.<get-dpi>|<get-dpi>(){}[0]
final val entries // org.jetbrains.compose.resources/DensityQualifier.entries|#static{}entries[0]
final fun <get-entries>(): kotlin.enums/EnumEntries<org.jetbrains.compose.resources/DensityQualifier> // org.jetbrains.compose.resources/DensityQualifier.entries.<get-entries>|<get-entries>#static(){}[0]
final fun valueOf(kotlin/String): org.jetbrains.compose.resources/DensityQualifier // org.jetbrains.compose.resources/DensityQualifier.valueOf|valueOf#static(kotlin.String){}[0]
final fun values(): kotlin/Array<org.jetbrains.compose.resources/DensityQualifier> // org.jetbrains.compose.resources/DensityQualifier.values|values#static(){}[0]
final object Companion { // org.jetbrains.compose.resources/DensityQualifier.Companion|null[0]
final fun selectByDensity(kotlin/Float): org.jetbrains.compose.resources/DensityQualifier // org.jetbrains.compose.resources/DensityQualifier.Companion.selectByDensity|selectByDensity(kotlin.Float){}[0]
final fun selectByValue(kotlin/Int): org.jetbrains.compose.resources/DensityQualifier // org.jetbrains.compose.resources/DensityQualifier.Companion.selectByValue|selectByValue(kotlin.Int){}[0]
}
}
final enum class org.jetbrains.compose.resources/ThemeQualifier : kotlin/Enum<org.jetbrains.compose.resources/ThemeQualifier>, org.jetbrains.compose.resources/Qualifier { // org.jetbrains.compose.resources/ThemeQualifier|null[0]
enum entry DARK // org.jetbrains.compose.resources/ThemeQualifier.DARK|null[0]
enum entry LIGHT // org.jetbrains.compose.resources/ThemeQualifier.LIGHT|null[0]
final val entries // org.jetbrains.compose.resources/ThemeQualifier.entries|#static{}entries[0]
final fun <get-entries>(): kotlin.enums/EnumEntries<org.jetbrains.compose.resources/ThemeQualifier> // org.jetbrains.compose.resources/ThemeQualifier.entries.<get-entries>|<get-entries>#static(){}[0]
final fun valueOf(kotlin/String): org.jetbrains.compose.resources/ThemeQualifier // org.jetbrains.compose.resources/ThemeQualifier.valueOf|valueOf#static(kotlin.String){}[0]
final fun values(): kotlin/Array<org.jetbrains.compose.resources/ThemeQualifier> // org.jetbrains.compose.resources/ThemeQualifier.values|values#static(){}[0]
final object Companion { // org.jetbrains.compose.resources/ThemeQualifier.Companion|null[0]
final fun selectByValue(kotlin/Boolean): org.jetbrains.compose.resources/ThemeQualifier // org.jetbrains.compose.resources/ThemeQualifier.Companion.selectByValue|selectByValue(kotlin.Boolean){}[0]
}
}
abstract interface org.jetbrains.compose.resources/Qualifier // org.jetbrains.compose.resources/Qualifier|null[0]
abstract interface org.jetbrains.compose.resources/ResourceReader { // org.jetbrains.compose.resources/ResourceReader|null[0]
abstract fun getUri(kotlin/String): kotlin/String // org.jetbrains.compose.resources/ResourceReader.getUri|getUri(kotlin.String){}[0]
abstract suspend fun read(kotlin/String): kotlin/ByteArray // org.jetbrains.compose.resources/ResourceReader.read|read(kotlin.String){}[0]
abstract suspend fun readPart(kotlin/String, kotlin/Long, kotlin/Long): kotlin/ByteArray // org.jetbrains.compose.resources/ResourceReader.readPart|readPart(kotlin.String;kotlin.Long;kotlin.Long){}[0]
}
final class org.jetbrains.compose.resources.vector.xmldom/MalformedXMLException : kotlin/Exception { // org.jetbrains.compose.resources.vector.xmldom/MalformedXMLException|null[0]
constructor <init>(kotlin/String?) // org.jetbrains.compose.resources.vector.xmldom/MalformedXMLException.<init>|<init>(kotlin.String?){}[0]
}
final class org.jetbrains.compose.resources/DrawableResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/DrawableResource|null[0]
constructor <init>(kotlin/String, kotlin.collections/Set<org.jetbrains.compose.resources/ResourceItem>) // org.jetbrains.compose.resources/DrawableResource.<init>|<init>(kotlin.String;kotlin.collections.Set<org.jetbrains.compose.resources.ResourceItem>){}[0]
}
final class org.jetbrains.compose.resources/FontResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/FontResource|null[0]
constructor <init>(kotlin/String, kotlin.collections/Set<org.jetbrains.compose.resources/ResourceItem>) // org.jetbrains.compose.resources/FontResource.<init>|<init>(kotlin.String;kotlin.collections.Set<org.jetbrains.compose.resources.ResourceItem>){}[0]
}
final class org.jetbrains.compose.resources/LanguageQualifier : org.jetbrains.compose.resources/Qualifier { // org.jetbrains.compose.resources/LanguageQualifier|null[0]
constructor <init>(kotlin/String) // org.jetbrains.compose.resources/LanguageQualifier.<init>|<init>(kotlin.String){}[0]
final val language // org.jetbrains.compose.resources/LanguageQualifier.language|{}language[0]
final fun <get-language>(): kotlin/String // org.jetbrains.compose.resources/LanguageQualifier.language.<get-language>|<get-language>(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/LanguageQualifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/LanguageQualifier.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // org.jetbrains.compose.resources/LanguageQualifier.toString|toString(){}[0]
}
final class org.jetbrains.compose.resources/MissingResourceException : kotlin/Exception { // org.jetbrains.compose.resources/MissingResourceException|null[0]
constructor <init>(kotlin/String) // org.jetbrains.compose.resources/MissingResourceException.<init>|<init>(kotlin.String){}[0]
}
final class org.jetbrains.compose.resources/PluralStringResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/PluralStringResource|null[0]
constructor <init>(kotlin/String, kotlin/String, kotlin.collections/Set<org.jetbrains.compose.resources/ResourceItem>) // org.jetbrains.compose.resources/PluralStringResource.<init>|<init>(kotlin.String;kotlin.String;kotlin.collections.Set<org.jetbrains.compose.resources.ResourceItem>){}[0]
final val key // org.jetbrains.compose.resources/PluralStringResource.key|{}key[0]
final fun <get-key>(): kotlin/String // org.jetbrains.compose.resources/PluralStringResource.key.<get-key>|<get-key>(){}[0]
}
final class org.jetbrains.compose.resources/RegionQualifier : org.jetbrains.compose.resources/Qualifier { // org.jetbrains.compose.resources/RegionQualifier|null[0]
constructor <init>(kotlin/String) // org.jetbrains.compose.resources/RegionQualifier.<init>|<init>(kotlin.String){}[0]
final val region // org.jetbrains.compose.resources/RegionQualifier.region|{}region[0]
final fun <get-region>(): kotlin/String // org.jetbrains.compose.resources/RegionQualifier.region.<get-region>|<get-region>(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/RegionQualifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/RegionQualifier.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // org.jetbrains.compose.resources/RegionQualifier.toString|toString(){}[0]
}
final class org.jetbrains.compose.resources/ResourceEnvironment { // org.jetbrains.compose.resources/ResourceEnvironment|null[0]
final fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/ResourceEnvironment.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/ResourceEnvironment.hashCode|hashCode(){}[0]
}
final class org.jetbrains.compose.resources/ResourceItem { // org.jetbrains.compose.resources/ResourceItem|null[0]
constructor <init>(kotlin.collections/Set<org.jetbrains.compose.resources/Qualifier>, kotlin/String, kotlin/Long, kotlin/Long) // org.jetbrains.compose.resources/ResourceItem.<init>|<init>(kotlin.collections.Set<org.jetbrains.compose.resources.Qualifier>;kotlin.String;kotlin.Long;kotlin.Long){}[0]
final fun copy(kotlin.collections/Set<org.jetbrains.compose.resources/Qualifier> = ..., kotlin/String = ..., kotlin/Long = ..., kotlin/Long = ...): org.jetbrains.compose.resources/ResourceItem // org.jetbrains.compose.resources/ResourceItem.copy|copy(kotlin.collections.Set<org.jetbrains.compose.resources.Qualifier>;kotlin.String;kotlin.Long;kotlin.Long){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/ResourceItem.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/ResourceItem.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // org.jetbrains.compose.resources/ResourceItem.toString|toString(){}[0]
}
final class org.jetbrains.compose.resources/ScriptQualifier : org.jetbrains.compose.resources/Qualifier { // org.jetbrains.compose.resources/ScriptQualifier|null[0]
constructor <init>(kotlin/String) // org.jetbrains.compose.resources/ScriptQualifier.<init>|<init>(kotlin.String){}[0]
final val script // org.jetbrains.compose.resources/ScriptQualifier.script|{}script[0]
final fun <get-script>(): kotlin/String // org.jetbrains.compose.resources/ScriptQualifier.script.<get-script>|<get-script>(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/ScriptQualifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/ScriptQualifier.hashCode|hashCode(){}[0]
final fun isEmpty(): kotlin/Boolean // org.jetbrains.compose.resources/ScriptQualifier.isEmpty|isEmpty(){}[0]
final fun toString(): kotlin/String // org.jetbrains.compose.resources/ScriptQualifier.toString|toString(){}[0]
}
final class org.jetbrains.compose.resources/StringArrayResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/StringArrayResource|null[0]
constructor <init>(kotlin/String, kotlin/String, kotlin.collections/Set<org.jetbrains.compose.resources/ResourceItem>) // org.jetbrains.compose.resources/StringArrayResource.<init>|<init>(kotlin.String;kotlin.String;kotlin.collections.Set<org.jetbrains.compose.resources.ResourceItem>){}[0]
final val key // org.jetbrains.compose.resources/StringArrayResource.key|{}key[0]
final fun <get-key>(): kotlin/String // org.jetbrains.compose.resources/StringArrayResource.key.<get-key>|<get-key>(){}[0]
}
final class org.jetbrains.compose.resources/StringResource : org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/StringResource|null[0]
constructor <init>(kotlin/String, kotlin/String, kotlin.collections/Set<org.jetbrains.compose.resources/ResourceItem>) // org.jetbrains.compose.resources/StringResource.<init>|<init>(kotlin.String;kotlin.String;kotlin.collections.Set<org.jetbrains.compose.resources.ResourceItem>){}[0]
final val key // org.jetbrains.compose.resources/StringResource.key|{}key[0]
final fun <get-key>(): kotlin/String // org.jetbrains.compose.resources/StringResource.key.<get-key>|<get-key>(){}[0]
}
sealed class org.jetbrains.compose.resources/Resource { // org.jetbrains.compose.resources/Resource|null[0]
open fun equals(kotlin/Any?): kotlin/Boolean // org.jetbrains.compose.resources/Resource.equals|equals(kotlin.Any?){}[0]
open fun hashCode(): kotlin/Int // org.jetbrains.compose.resources/Resource.hashCode|hashCode(){}[0]
}
final object org.jetbrains.compose.resources/ResourceCaches // org.jetbrains.compose.resources/ResourceCaches|null[0]
final val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop[0]
final val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop[0]
final val org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop|#static{}org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop[0]
final val org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop // org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop|#static{}org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop[0]
final val org.jetbrains.compose.resources/LocalResourceReader // org.jetbrains.compose.resources/LocalResourceReader|{}LocalResourceReader[0]
final fun <get-LocalResourceReader>(): androidx.compose.runtime/ProvidableCompositionLocal<org.jetbrains.compose.resources/ResourceReader> // org.jetbrains.compose.resources/LocalResourceReader.<get-LocalResourceReader>|<get-LocalResourceReader>(){}[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawableResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawableResource$stableprop|#static{}org_jetbrains_compose_resources_DrawableResource$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_FontResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_FontResource$stableprop|#static{}org_jetbrains_compose_resources_FontResource$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop|#static{}org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Svg$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Svg$stableprop|#static{}org_jetbrains_compose_resources_ImageCache_Svg$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Vector$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Vector$stableprop|#static{}org_jetbrains_compose_resources_ImageCache_Vector$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_LanguageQualifier$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_LanguageQualifier$stableprop|#static{}org_jetbrains_compose_resources_LanguageQualifier$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_MissingResourceException$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_MissingResourceException$stableprop|#static{}org_jetbrains_compose_resources_MissingResourceException$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_PluralStringResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_PluralStringResource$stableprop|#static{}org_jetbrains_compose_resources_PluralStringResource$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_RegionQualifier$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_RegionQualifier$stableprop|#static{}org_jetbrains_compose_resources_RegionQualifier$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_Resource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_Resource$stableprop|#static{}org_jetbrains_compose_resources_Resource$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceCaches$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceCaches$stableprop|#static{}org_jetbrains_compose_resources_ResourceCaches$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceEnvironment$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceEnvironment$stableprop|#static{}org_jetbrains_compose_resources_ResourceEnvironment$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceItem$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceItem$stableprop|#static{}org_jetbrains_compose_resources_ResourceItem$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_ScriptQualifier$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ScriptQualifier$stableprop|#static{}org_jetbrains_compose_resources_ScriptQualifier$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringArrayResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringArrayResource$stableprop|#static{}org_jetbrains_compose_resources_StringArrayResource$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Array$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Array$stableprop|#static{}org_jetbrains_compose_resources_StringItem_Array$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Plurals$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Plurals$stableprop|#static{}org_jetbrains_compose_resources_StringItem_Plurals$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Value$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Value$stableprop|#static{}org_jetbrains_compose_resources_StringItem_Value$stableprop[0]
final val org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringResource$stableprop // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringResource$stableprop|#static{}org_jetbrains_compose_resources_StringResource$stableprop[0]
final fun (kotlin/ByteArray).org.jetbrains.compose.resources/decodeToImageBitmap(): androidx.compose.ui.graphics/ImageBitmap // org.jetbrains.compose.resources/decodeToImageBitmap|decodeToImageBitmap@kotlin.ByteArray(){}[0]
final fun (kotlin/ByteArray).org.jetbrains.compose.resources/decodeToImageVector(androidx.compose.ui.unit/Density): androidx.compose.ui.graphics.vector/ImageVector // org.jetbrains.compose.resources/decodeToImageVector|decodeToImageVector@kotlin.ByteArray(androidx.compose.ui.unit.Density){}[0]
final fun (kotlin/ByteArray).org.jetbrains.compose.resources/decodeToSvgPainter(androidx.compose.ui.unit/Density): androidx.compose.ui.graphics.painter/Painter // org.jetbrains.compose.resources/decodeToSvgPainter|decodeToSvgPainter@kotlin.ByteArray(androidx.compose.ui.unit.Density){}[0]
final fun org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop_getter|org_jetbrains_compose_resources_plural_PluralRule_Condition_And$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop_getter|org_jetbrains_compose_resources_plural_PluralRule_Condition_Or$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources.plural/org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop_getter|org_jetbrains_compose_resources_plural_PluralRule_Condition_Relation$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources.vector.xmldom/org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop_getter|org_jetbrains_compose_resources_vector_xmldom_MalformedXMLException$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/Font(org.jetbrains.compose.resources/FontResource, androidx.compose.ui.text.font/FontWeight?, androidx.compose.ui.text.font/FontStyle, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.compose.ui.text.font/Font // org.jetbrains.compose.resources/Font|Font(org.jetbrains.compose.resources.FontResource;androidx.compose.ui.text.font.FontWeight?;androidx.compose.ui.text.font.FontStyle;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/Font(org.jetbrains.compose.resources/FontResource, androidx.compose.ui.text.font/FontWeight?, androidx.compose.ui.text.font/FontStyle, androidx.compose.ui.text.font/FontVariation.Settings?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.compose.ui.text.font/Font // org.jetbrains.compose.resources/Font|Font(org.jetbrains.compose.resources.FontResource;androidx.compose.ui.text.font.FontWeight?;androidx.compose.ui.text.font.FontStyle;androidx.compose.ui.text.font.FontVariation.Settings?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/getResourceUri(kotlin/String): kotlin/String // org.jetbrains.compose.resources/getResourceUri|getResourceUri(kotlin.String){}[0]
final fun org.jetbrains.compose.resources/getSystemResourceEnvironment(): org.jetbrains.compose.resources/ResourceEnvironment // org.jetbrains.compose.resources/getSystemResourceEnvironment|getSystemResourceEnvironment(){}[0]
final fun org.jetbrains.compose.resources/imageResource(org.jetbrains.compose.resources/DrawableResource, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.graphics/ImageBitmap // org.jetbrains.compose.resources/imageResource|imageResource(org.jetbrains.compose.resources.DrawableResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawableResource$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_DrawableResource$stableprop_getter|org_jetbrains_compose_resources_DrawableResource$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_FontResource$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_FontResource$stableprop_getter|org_jetbrains_compose_resources_FontResource$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop_getter|org_jetbrains_compose_resources_ImageCache_Bitmap$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Svg$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Svg$stableprop_getter|org_jetbrains_compose_resources_ImageCache_Svg$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Vector$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ImageCache_Vector$stableprop_getter|org_jetbrains_compose_resources_ImageCache_Vector$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_LanguageQualifier$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_LanguageQualifier$stableprop_getter|org_jetbrains_compose_resources_LanguageQualifier$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_MissingResourceException$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_MissingResourceException$stableprop_getter|org_jetbrains_compose_resources_MissingResourceException$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_PluralStringResource$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_PluralStringResource$stableprop_getter|org_jetbrains_compose_resources_PluralStringResource$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_RegionQualifier$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_RegionQualifier$stableprop_getter|org_jetbrains_compose_resources_RegionQualifier$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_Resource$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_Resource$stableprop_getter|org_jetbrains_compose_resources_Resource$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceCaches$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceCaches$stableprop_getter|org_jetbrains_compose_resources_ResourceCaches$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceEnvironment$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceEnvironment$stableprop_getter|org_jetbrains_compose_resources_ResourceEnvironment$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceItem$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ResourceItem$stableprop_getter|org_jetbrains_compose_resources_ResourceItem$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_ScriptQualifier$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_ScriptQualifier$stableprop_getter|org_jetbrains_compose_resources_ScriptQualifier$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringArrayResource$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringArrayResource$stableprop_getter|org_jetbrains_compose_resources_StringArrayResource$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Array$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Array$stableprop_getter|org_jetbrains_compose_resources_StringItem_Array$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Plurals$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Plurals$stableprop_getter|org_jetbrains_compose_resources_StringItem_Plurals$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Value$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringItem_Value$stableprop_getter|org_jetbrains_compose_resources_StringItem_Value$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringResource$stableprop_getter(): kotlin/Int // org.jetbrains.compose.resources/org_jetbrains_compose_resources_StringResource$stableprop_getter|org_jetbrains_compose_resources_StringResource$stableprop_getter(){}[0]
final fun org.jetbrains.compose.resources/painterResource(org.jetbrains.compose.resources/DrawableResource, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.graphics.painter/Painter // org.jetbrains.compose.resources/painterResource|painterResource(org.jetbrains.compose.resources.DrawableResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/pluralStringResource(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/pluralStringResource|pluralStringResource(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/pluralStringResource(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, kotlin/Array<out kotlin/Any>..., androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/pluralStringResource|pluralStringResource(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;kotlin.Array<out|kotlin.Any>...;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/rememberResourceEnvironment(androidx.compose.runtime/Composer?, kotlin/Int): org.jetbrains.compose.resources/ResourceEnvironment // org.jetbrains.compose.resources/rememberResourceEnvironment|rememberResourceEnvironment(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/stringArrayResource(org.jetbrains.compose.resources/StringArrayResource, androidx.compose.runtime/Composer?, kotlin/Int): kotlin.collections/List<kotlin/String> // org.jetbrains.compose.resources/stringArrayResource|stringArrayResource(org.jetbrains.compose.resources.StringArrayResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/stringResource(org.jetbrains.compose.resources/StringResource, androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/stringResource|stringResource(org.jetbrains.compose.resources.StringResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/stringResource(org.jetbrains.compose.resources/StringResource, kotlin/Array<out kotlin/Any>..., androidx.compose.runtime/Composer?, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/stringResource|stringResource(org.jetbrains.compose.resources.StringResource;kotlin.Array<out|kotlin.Any>...;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun org.jetbrains.compose.resources/vectorResource(org.jetbrains.compose.resources/DrawableResource, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.graphics.vector/ImageVector // org.jetbrains.compose.resources/vectorResource|vectorResource(org.jetbrains.compose.resources.DrawableResource;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final suspend fun org.jetbrains.compose.resources/getDrawableResourceBytes(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/DrawableResource): kotlin/ByteArray // org.jetbrains.compose.resources/getDrawableResourceBytes|getDrawableResourceBytes(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.DrawableResource){}[0]
final suspend fun org.jetbrains.compose.resources/getFontResourceBytes(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/FontResource): kotlin/ByteArray // org.jetbrains.compose.resources/getFontResourceBytes|getFontResourceBytes(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.FontResource){}[0]
final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int){}[0]
final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, kotlin/Array<out kotlin/Any>...): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;kotlin.Array<out|kotlin.Any>...){}[0]
final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/PluralStringResource, kotlin/Int): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.PluralStringResource;kotlin.Int){}[0]
final suspend fun org.jetbrains.compose.resources/getPluralString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/PluralStringResource, kotlin/Int, kotlin/Array<out kotlin/Any>...): kotlin/String // org.jetbrains.compose.resources/getPluralString|getPluralString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.PluralStringResource;kotlin.Int;kotlin.Array<out|kotlin.Any>...){}[0]
final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/StringResource): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.StringResource){}[0]
final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/StringResource, kotlin/Array<out kotlin/Any>...): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.StringResource;kotlin.Array<out|kotlin.Any>...){}[0]
final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/StringResource): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.StringResource){}[0]
final suspend fun org.jetbrains.compose.resources/getString(org.jetbrains.compose.resources/StringResource, kotlin/Array<out kotlin/Any>...): kotlin/String // org.jetbrains.compose.resources/getString|getString(org.jetbrains.compose.resources.StringResource;kotlin.Array<out|kotlin.Any>...){}[0]
final suspend fun org.jetbrains.compose.resources/getStringArray(org.jetbrains.compose.resources/ResourceEnvironment, org.jetbrains.compose.resources/StringArrayResource): kotlin.collections/List<kotlin/String> // org.jetbrains.compose.resources/getStringArray|getStringArray(org.jetbrains.compose.resources.ResourceEnvironment;org.jetbrains.compose.resources.StringArrayResource){}[0]
final suspend fun org.jetbrains.compose.resources/getStringArray(org.jetbrains.compose.resources/StringArrayResource): kotlin.collections/List<kotlin/String> // org.jetbrains.compose.resources/getStringArray|getStringArray(org.jetbrains.compose.resources.StringArrayResource){}[0]
final suspend fun org.jetbrains.compose.resources/readResourceBytes(kotlin/String): kotlin/ByteArray // org.jetbrains.compose.resources/readResourceBytes|readResourceBytes(kotlin.String){}[0]
@@ -2078,17 +2078,6 @@ final object com.compose.sdl.icons/IconDefaults { // com.compose.sdl.icons/IconD
final fun <get-LocalContentColor>(): androidx.compose.ui.graphics/Color // com.compose.sdl.icons/IconDefaults.LocalContentColor.<get-LocalContentColor>|<get-LocalContentColor>(){}[0]
}
final object com.compose.sdl.icons/MaterialIconAxisDefaults { // com.compose.sdl.icons/MaterialIconAxisDefaults|null[0]
final const val Fill // com.compose.sdl.icons/MaterialIconAxisDefaults.Fill|{}Fill[0]
final fun <get-Fill>(): kotlin/Float // com.compose.sdl.icons/MaterialIconAxisDefaults.Fill.<get-Fill>|<get-Fill>(){}[0]
final const val Grade // com.compose.sdl.icons/MaterialIconAxisDefaults.Grade|{}Grade[0]
final fun <get-Grade>(): kotlin/Int // com.compose.sdl.icons/MaterialIconAxisDefaults.Grade.<get-Grade>|<get-Grade>(){}[0]
final const val OpticalSize // com.compose.sdl.icons/MaterialIconAxisDefaults.OpticalSize|{}OpticalSize[0]
final fun <get-OpticalSize>(): kotlin/Int // com.compose.sdl.icons/MaterialIconAxisDefaults.OpticalSize.<get-OpticalSize>|<get-OpticalSize>(){}[0]
final const val Weight // com.compose.sdl.icons/MaterialIconAxisDefaults.Weight|{}Weight[0]
final fun <get-Weight>(): kotlin/Int // com.compose.sdl.icons/MaterialIconAxisDefaults.Weight.<get-Weight>|<get-Weight>(){}[0]
}
final object com.compose.sdl.widgets/SplitPaneDefaults { // com.compose.sdl.widgets/SplitPaneDefaults|null[0]
final val DividerThickness // com.compose.sdl.widgets/SplitPaneDefaults.DividerThickness|{}DividerThickness[0]
final fun <get-DividerThickness>(): androidx.compose.ui.unit/Dp // com.compose.sdl.widgets/SplitPaneDefaults.DividerThickness.<get-DividerThickness>|<get-DividerThickness>(){}[0]
@@ -2220,7 +2209,6 @@ final val androidx.compose.foundation/androidx_compose_foundation_MarqueeDefault
final val androidx.compose.foundation/androidx_compose_foundation_MutatorMutex$stableprop // androidx.compose.foundation/androidx_compose_foundation_MutatorMutex$stableprop|#static{}androidx_compose_foundation_MutatorMutex$stableprop[0]
final val androidx.compose.foundation/androidx_compose_foundation_ScrollState$stableprop // androidx.compose.foundation/androidx_compose_foundation_ScrollState$stableprop|#static{}androidx_compose_foundation_ScrollState$stableprop[0]
final val com.compose.sdl.icons/com_compose_sdl_icons_IconDefaults$stableprop // com.compose.sdl.icons/com_compose_sdl_icons_IconDefaults$stableprop|#static{}com_compose_sdl_icons_IconDefaults$stableprop[0]
final val com.compose.sdl.icons/com_compose_sdl_icons_MaterialIconAxisDefaults$stableprop // com.compose.sdl.icons/com_compose_sdl_icons_MaterialIconAxisDefaults$stableprop|#static{}com_compose_sdl_icons_MaterialIconAxisDefaults$stableprop[0]
final val com.compose.sdl.scrollbar/LocalScrollbarStyle // com.compose.sdl.scrollbar/LocalScrollbarStyle|{}LocalScrollbarStyle[0]
final fun <get-LocalScrollbarStyle>(): androidx.compose.runtime/ProvidableCompositionLocal<com.compose.sdl.scrollbar/ScrollbarStyle> // com.compose.sdl.scrollbar/LocalScrollbarStyle.<get-LocalScrollbarStyle>|<get-LocalScrollbarStyle>(){}[0]
final val com.compose.sdl.scrollbar/com_compose_sdl_scrollbar_ScrollbarStyle$stableprop // com.compose.sdl.scrollbar/com_compose_sdl_scrollbar_ScrollbarStyle$stableprop|#static{}com_compose_sdl_scrollbar_ScrollbarStyle$stableprop[0]
@@ -2294,6 +2282,8 @@ final fun (androidx.compose.ui.input.pointer/PointerEvent).androidx.compose.foun
final fun (androidx.compose.ui.input.pointer/PointerEvent).androidx.compose.foundation.gestures/calculateZoom(): kotlin/Float // androidx.compose.foundation.gestures/calculateZoom|calculateZoom@androidx.compose.ui.input.pointer.PointerEvent(){}[0]
final fun (androidx.compose.ui.text/AnnotatedString.Builder).androidx.compose.foundation.text/appendInlineContent(kotlin/String, kotlin/String = ...) // androidx.compose.foundation.text/appendInlineContent|appendInlineContent@androidx.compose.ui.text.AnnotatedString.Builder(kotlin.String;kotlin.String){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.foundation.content/contentReceiver(androidx.compose.foundation.content/ReceiveContentListener): androidx.compose.ui/Modifier // androidx.compose.foundation.content/contentReceiver|contentReceiver@androidx.compose.ui.Modifier(androidx.compose.foundation.content.ReceiveContentListener){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.foundation.draganddrop/dragAndDropSource(kotlin/Function1<androidx.compose.ui.geometry/Offset, androidx.compose.ui.draganddrop/DragAndDropTransferData?>): androidx.compose.ui/Modifier // androidx.compose.foundation.draganddrop/dragAndDropSource|dragAndDropSource@androidx.compose.ui.Modifier(kotlin.Function1<androidx.compose.ui.geometry.Offset,androidx.compose.ui.draganddrop.DragAndDropTransferData?>){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.foundation.draganddrop/dragAndDropSource(kotlin/Function1<androidx.compose.ui.graphics.drawscope/DrawScope, kotlin/Unit>, kotlin/Function1<androidx.compose.ui.geometry/Offset, androidx.compose.ui.draganddrop/DragAndDropTransferData?>): androidx.compose.ui/Modifier // androidx.compose.foundation.draganddrop/dragAndDropSource|dragAndDropSource@androidx.compose.ui.Modifier(kotlin.Function1<androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit>;kotlin.Function1<androidx.compose.ui.geometry.Offset,androidx.compose.ui.draganddrop.DragAndDropTransferData?>){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.foundation.draganddrop/dragAndDropTarget(kotlin/Function1<androidx.compose.ui.draganddrop/DragAndDropEvent, kotlin/Boolean>, androidx.compose.ui.draganddrop/DragAndDropTarget): androidx.compose.ui/Modifier // androidx.compose.foundation.draganddrop/dragAndDropTarget|dragAndDropTarget@androidx.compose.ui.Modifier(kotlin.Function1<androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Boolean>;androidx.compose.ui.draganddrop.DragAndDropTarget){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.foundation.gestures/draggable(androidx.compose.foundation.gestures/DraggableState, androidx.compose.foundation.gestures/Orientation, kotlin/Boolean = ..., androidx.compose.foundation.interaction/MutableInteractionSource? = ..., kotlin/Boolean = ..., kotlin.coroutines/SuspendFunction2<kotlinx.coroutines/CoroutineScope, androidx.compose.ui.geometry/Offset, kotlin/Unit> = ..., kotlin.coroutines/SuspendFunction2<kotlinx.coroutines/CoroutineScope, kotlin/Float, kotlin/Unit> = ..., kotlin/Boolean = ...): androidx.compose.ui/Modifier // androidx.compose.foundation.gestures/draggable|draggable@androidx.compose.ui.Modifier(androidx.compose.foundation.gestures.DraggableState;androidx.compose.foundation.gestures.Orientation;kotlin.Boolean;androidx.compose.foundation.interaction.MutableInteractionSource?;kotlin.Boolean;kotlin.coroutines.SuspendFunction2<kotlinx.coroutines.CoroutineScope,androidx.compose.ui.geometry.Offset,kotlin.Unit>;kotlin.coroutines.SuspendFunction2<kotlinx.coroutines.CoroutineScope,kotlin.Float,kotlin.Unit>;kotlin.Boolean){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.foundation.gestures/draggable2D(androidx.compose.foundation.gestures/Draggable2DState, kotlin/Boolean = ..., androidx.compose.foundation.interaction/MutableInteractionSource? = ..., kotlin/Boolean = ..., kotlin/Function1<androidx.compose.ui.geometry/Offset, kotlin/Unit> = ..., kotlin/Function1<androidx.compose.ui.unit/Velocity, kotlin/Unit> = ..., kotlin/Boolean = ...): androidx.compose.ui/Modifier // androidx.compose.foundation.gestures/draggable2D|draggable2D@androidx.compose.ui.Modifier(androidx.compose.foundation.gestures.Draggable2DState;kotlin.Boolean;androidx.compose.foundation.interaction.MutableInteractionSource?;kotlin.Boolean;kotlin.Function1<androidx.compose.ui.geometry.Offset,kotlin.Unit>;kotlin.Function1<androidx.compose.ui.unit.Velocity,kotlin.Unit>;kotlin.Boolean){}[0]
@@ -2620,14 +2610,13 @@ final fun androidx.compose.foundation/rememberBasicTooltipState(kotlin/Boolean,
final fun androidx.compose.foundation/rememberOverscrollEffect(androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.foundation/OverscrollEffect? // androidx.compose.foundation/rememberOverscrollEffect|rememberOverscrollEffect(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun androidx.compose.foundation/rememberScrollState(kotlin/Int, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.compose.foundation/ScrollState // androidx.compose.foundation/rememberScrollState|rememberScrollState(kotlin.Int;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun com.compose.sdl.icons/IconFontIcon(kotlin/Int, kotlin/String, kotlin/String?, androidx.compose.ui/Modifier?, androidx.compose.ui.graphics/Color, androidx.compose.ui.unit/Dp, kotlin.collections/List<androidx.compose.ui.text.font/FontVariation.Setting>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.compose.sdl.icons/IconFontIcon|IconFontIcon(kotlin.Int;kotlin.String;kotlin.String?;androidx.compose.ui.Modifier?;androidx.compose.ui.graphics.Color;androidx.compose.ui.unit.Dp;kotlin.collections.List<androidx.compose.ui.text.font.FontVariation.Setting>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun com.compose.sdl.icons/MaterialIconAxes(kotlin/Float = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ...): kotlin.collections/List<androidx.compose.ui.text.font/FontVariation.Setting> // com.compose.sdl.icons/MaterialIconAxes|MaterialIconAxes(kotlin.Float;kotlin.Int;kotlin.Int;kotlin.Int){}[0]
final fun com.compose.sdl.icons/com_compose_sdl_icons_IconDefaults$stableprop_getter(): kotlin/Int // com.compose.sdl.icons/com_compose_sdl_icons_IconDefaults$stableprop_getter|com_compose_sdl_icons_IconDefaults$stableprop_getter(){}[0]
final fun com.compose.sdl.icons/com_compose_sdl_icons_MaterialIconAxisDefaults$stableprop_getter(): kotlin/Int // com.compose.sdl.icons/com_compose_sdl_icons_MaterialIconAxisDefaults$stableprop_getter|com_compose_sdl_icons_MaterialIconAxisDefaults$stableprop_getter(){}[0]
final fun com.compose.sdl.modifier/rememberMutableInteractionSource(androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.foundation.interaction/MutableInteractionSource // com.compose.sdl.modifier/rememberMutableInteractionSource|rememberMutableInteractionSource(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun com.compose.sdl.scrollbar/HorizontalScrollbar(com.compose.sdl.scrollbar/ScrollbarAdapter, androidx.compose.ui/Modifier?, kotlin/Boolean, com.compose.sdl.scrollbar/ScrollbarStyle?, androidx.compose.foundation.interaction/MutableInteractionSource?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.compose.sdl.scrollbar/HorizontalScrollbar|HorizontalScrollbar(com.compose.sdl.scrollbar.ScrollbarAdapter;androidx.compose.ui.Modifier?;kotlin.Boolean;com.compose.sdl.scrollbar.ScrollbarStyle?;androidx.compose.foundation.interaction.MutableInteractionSource?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun com.compose.sdl.scrollbar/VerticalScrollbar(com.compose.sdl.scrollbar/ScrollbarAdapter, androidx.compose.ui/Modifier?, kotlin/Boolean, com.compose.sdl.scrollbar/ScrollbarStyle?, androidx.compose.foundation.interaction/MutableInteractionSource?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.compose.sdl.scrollbar/VerticalScrollbar|VerticalScrollbar(com.compose.sdl.scrollbar.ScrollbarAdapter;androidx.compose.ui.Modifier?;kotlin.Boolean;com.compose.sdl.scrollbar.ScrollbarStyle?;androidx.compose.foundation.interaction.MutableInteractionSource?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun com.compose.sdl.scrollbar/com_compose_sdl_scrollbar_ScrollbarStyle$stableprop_getter(): kotlin/Int // com.compose.sdl.scrollbar/com_compose_sdl_scrollbar_ScrollbarStyle$stableprop_getter|com_compose_sdl_scrollbar_ScrollbarStyle$stableprop_getter(){}[0]
final fun com.compose.sdl.scrollbar/defaultScrollbarStyle(): com.compose.sdl.scrollbar/ScrollbarStyle // com.compose.sdl.scrollbar/defaultScrollbarStyle|defaultScrollbarStyle(){}[0]
final fun com.compose.sdl.scrollbar/rememberScrollbarAdapter(androidx.compose.foundation.lazy/LazyListState, androidx.compose.runtime/Composer?, kotlin/Int): com.compose.sdl.scrollbar/ScrollbarAdapter // com.compose.sdl.scrollbar/rememberScrollbarAdapter|rememberScrollbarAdapter(androidx.compose.foundation.lazy.LazyListState;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun com.compose.sdl.scrollbar/rememberScrollbarAdapter(androidx.compose.foundation/ScrollState, androidx.compose.runtime/Composer?, kotlin/Int): com.compose.sdl.scrollbar/ScrollbarAdapter // com.compose.sdl.scrollbar/rememberScrollbarAdapter|rememberScrollbarAdapter(androidx.compose.foundation.ScrollState;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun com.compose.sdl.text/IconText(kotlin/String, kotlin/String, androidx.compose.ui/Modifier?, androidx.compose.ui.graphics/Color, androidx.compose.ui.unit/TextUnit, androidx.compose.ui.text.style/TextAlign, kotlin.collections/List<androidx.compose.ui.text.font/FontVariation.Setting>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.compose.sdl.text/IconText|IconText(kotlin.String;kotlin.String;androidx.compose.ui.Modifier?;androidx.compose.ui.graphics.Color;androidx.compose.ui.unit.TextUnit;androidx.compose.ui.text.style.TextAlign;kotlin.collections.List<androidx.compose.ui.text.font.FontVariation.Setting>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun com.compose.sdl.text/IconText(kotlin/String, kotlin/String, androidx.compose.ui/Modifier?, androidx.compose.ui.graphics/Color, androidx.compose.ui.unit/TextUnit, androidx.compose.ui.text.style/TextAlign?, kotlin.collections/List<androidx.compose.ui.text.font/FontVariation.Setting>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.compose.sdl.text/IconText|IconText(kotlin.String;kotlin.String;androidx.compose.ui.Modifier?;androidx.compose.ui.graphics.Color;androidx.compose.ui.unit.TextUnit;androidx.compose.ui.text.style.TextAlign?;kotlin.collections.List<androidx.compose.ui.text.font.FontVariation.Setting>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
@@ -30,9 +30,12 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.roundToInt
// ==================
@@ -141,6 +144,19 @@ private class LazyListScrollbarAdapter(private val state: LazyListState) : Scrol
get() = (state.layoutInfo.viewportEndOffset - state.layoutInfo.viewportStartOffset).toDouble()
override suspend fun scrollTo(scrollOffset: Double) {
val vDistance = scrollOffset - this.scrollOffset
// Within a viewport, scroll by EXACT pixels so a drag stays smooth. A lazy
// list's pixel geometry is only ESTIMATED (average visible item size), and
// that estimate drifts as differently-sized items scroll through the
// viewport — so index-snapping every frame makes the thumb jump. scrollBy
// sidesteps the estimate entirely. This is what Compose Desktop's own
// LazyListScrollbarAdapter does, and for the same reason.
if (abs(vDistance) <= viewportSize) {
state.scrollBy(vDistance.toFloat())
return
}
// Far jump (track-click paging past a viewport): no exact geometry to lean
// on, so estimate an index/offset from the average and snap to it.
val vAvg = averageItemSize()
if (vAvg <= 0.0) return
val vTarget = scrollOffset.coerceAtLeast(0.0)
@@ -193,7 +209,6 @@ private fun Scrollbar(
val vHoverSource = remember { MutableInteractionSource() }
val vHovered by vHoverSource.collectIsHoveredAsState()
var vDraggingThumb by remember { mutableStateOf(false) }
var vGrab by remember { mutableStateOf(0) }
// onSizeChanged reports PHYSICAL pixels (Option-B density flow), so every
// int/px carried through here — vTrackPx, vThumbLen, vThumbPos — is in
// physical pixels. Passing them into `.dp` doubles again through
@@ -230,15 +245,6 @@ private fun Scrollbar(
val vRawPos = if (vTravel <= 0) 0 else (vTravel * (vOffset / vMaxScroll)).roundToInt()
val vThumbPos = (if (reverseLayout) vTravel - vRawPos else vRawPos).coerceIn(0, vTravel)
// ============
// Map a track-relative position to a scroll offset and apply it.
fun scrollToTrackPos(inThumbTop: Int) {
if (vTravel <= 0) return
val vPos = inThumbTop.coerceIn(0, vTravel)
val vFrac = (if (reverseLayout) vTravel - vPos else vPos).toDouble() / vTravel
vScope.launch { adapter.scrollTo(vFrac * vMaxScroll) }
}
val vThumbLenDp = with(vDensity) { vThumbLen.toDp() }
val vThumbMod = (
if (isVertical) Modifier.offset { IntOffset(0, vThumbPos) }.width(style.thickness).height(vThumbLenDp)
@@ -251,33 +257,78 @@ private fun Scrollbar(
// ============
// Drag the thumb / page on track click — handled on the track so the
// coordinates have a stable (non-moving) origin.
// coordinates have a stable (non-moving) origin. The press is CONSUMED:
// a scrollbar owns the pointer input it handles, so content sharing this
// region (the scrollable beneath, or a parent that also reads pointer
// input) never reacts to the same press. Geometry is read LIVE from the
// adapter at press time — not from the captured render metrics above,
// which go stale between pointerInput relaunches — so a grab after a
// wheel-scroll still hit-tests against the thumb's current position.
Box(
modifier = Modifier
.matchTrackSize(isVertical, with(vDensity) { vTrackPx.toDp() }, style.thickness)
.pointerInput(isVertical, adapter, vTrackPx) {
detectDragGestures(
onDragStart = { offset ->
val vPress = (if (isVertical) offset.y else offset.x).toInt()
if (vPress in vThumbPos..(vThumbPos + vThumbLen)) {
vDraggingThumb = true
vGrab = vPress - vThumbPos
} else {
// Page toward the click by one viewport.
vDraggingThumb = false
val vDir = if (vPress < vThumbPos) -1.0 else 1.0
vScope.launch { adapter.scrollTo(vOffset + vDir * vViewport) }
}
},
onDrag = { change, _ ->
.pointerInput(isVertical, adapter, vTrackPx, reverseLayout) {
awaitEachGesture {
val vDown = awaitFirstDown(requireUnconsumed = false)
vDown.consume()
// Live geometry — stable for the drag's duration (content /
// viewport / track sizes don't change mid-drag).
val vContentPx = adapter.contentSize
val vViewportPx = adapter.viewportSize
val vMaxScrollPx = vContentPx - vViewportPx
if (vMaxScrollPx <= 0.0 || vTrackPx <= 0) return@awaitEachGesture
val vMinLen = style.minimalHeight.value.toInt()
val vLen = (vTrackPx * (vViewportPx / vContentPx)).roundToInt()
.coerceIn(vMinLen.coerceAtMost(vTrackPx), vTrackPx)
val vTravelPx = vTrackPx - vLen
val vRaw = if (vTravelPx <= 0) 0
else (vTravelPx * (adapter.scrollOffset / vMaxScrollPx)).roundToInt()
val vPos = (if (reverseLayout) vTravelPx - vRaw else vRaw).coerceIn(0, vTravelPx)
// Map a track-relative thumb-top to a scroll offset. The
// target uses the LIVE max scroll (read fresh each move),
// not the press-time value — the adapter compares it against
// its live scrollOffset, and mixing a stale scale with a live
// one makes the delta run away (grab at the bottom, drag up,
// and the list overshoots to the middle).
fun scrollToTrackPos(inThumbTop: Int) {
if (vTravelPx <= 0) return
val vLiveMax = adapter.contentSize - adapter.viewportSize
if (vLiveMax <= 0.0) return
val vClamped = inThumbTop.coerceIn(0, vTravelPx)
val vFrac = (if (reverseLayout) vTravelPx - vClamped else vClamped).toDouble() / vTravelPx
vScope.launch { adapter.scrollTo(vFrac * vLiveMax) }
}
val vPress = (if (isVertical) vDown.position.y else vDown.position.x).toInt()
val vGrab: Int
if (vPress in vPos..(vPos + vLen)) {
vDraggingThumb = true
vGrab = vPress - vPos
} else {
// Page toward the click by one viewport.
vDraggingThumb = false
vGrab = 0
val vDir = if (vPress < vPos) -1.0 else 1.0
vScope.launch { adapter.scrollTo(adapter.scrollOffset + vDir * vViewportPx) }
}
// Follow the pressed pointer to release, consuming every
// change so the interaction can't leak. Only a thumb grab
// drags-to-scroll; a track press just consumes.
do {
val vEvent = awaitPointerEvent()
val vChange = vEvent.changes.firstOrNull { it.id == vDown.id } ?: break
if (vDraggingThumb) {
val vCur = (if (isVertical) change.position.y else change.position.x).toInt()
val vCur = (if (isVertical) vChange.position.y else vChange.position.x).toInt()
scrollToTrackPos(vCur - vGrab)
}
},
onDragEnd = { vDraggingThumb = false },
onDragCancel = { vDraggingThumb = false },
)
vChange.consume()
} while (vEvent.changes.any { it.id == vDown.id && it.pressed })
vDraggingThumb = false
}
}
)
}
@@ -0,0 +1,42 @@
// Klib ABI Dump
// Targets: [linuxArm64, linuxX64, macosArm64, mingwX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
// - Show declarations: true
// Library unique name: <com.bitsycore.compose.sdl:material-ripple>
abstract interface androidx.compose.material.ripple/RippleTheme { // androidx.compose.material.ripple/RippleTheme|null[0]
abstract fun defaultColor(androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.graphics/Color // androidx.compose.material.ripple/RippleTheme.defaultColor|defaultColor(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
abstract fun rippleAlpha(androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.material.ripple/RippleAlpha // androidx.compose.material.ripple/RippleTheme.rippleAlpha|rippleAlpha(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final object Companion { // androidx.compose.material.ripple/RippleTheme.Companion|null[0]
final fun defaultRippleAlpha(androidx.compose.ui.graphics/Color, kotlin/Boolean): androidx.compose.material.ripple/RippleAlpha // androidx.compose.material.ripple/RippleTheme.Companion.defaultRippleAlpha|defaultRippleAlpha(androidx.compose.ui.graphics.Color;kotlin.Boolean){}[0]
final fun defaultRippleColor(androidx.compose.ui.graphics/Color, kotlin/Boolean): androidx.compose.ui.graphics/Color // androidx.compose.material.ripple/RippleTheme.Companion.defaultRippleColor|defaultRippleColor(androidx.compose.ui.graphics.Color;kotlin.Boolean){}[0]
}
}
final class androidx.compose.material.ripple/RippleAlpha { // androidx.compose.material.ripple/RippleAlpha|null[0]
constructor <init>(kotlin/Float, kotlin/Float, kotlin/Float, kotlin/Float) // androidx.compose.material.ripple/RippleAlpha.<init>|<init>(kotlin.Float;kotlin.Float;kotlin.Float;kotlin.Float){}[0]
final val draggedAlpha // androidx.compose.material.ripple/RippleAlpha.draggedAlpha|{}draggedAlpha[0]
final fun <get-draggedAlpha>(): kotlin/Float // androidx.compose.material.ripple/RippleAlpha.draggedAlpha.<get-draggedAlpha>|<get-draggedAlpha>(){}[0]
final val focusedAlpha // androidx.compose.material.ripple/RippleAlpha.focusedAlpha|{}focusedAlpha[0]
final fun <get-focusedAlpha>(): kotlin/Float // androidx.compose.material.ripple/RippleAlpha.focusedAlpha.<get-focusedAlpha>|<get-focusedAlpha>(){}[0]
final val hoveredAlpha // androidx.compose.material.ripple/RippleAlpha.hoveredAlpha|{}hoveredAlpha[0]
final fun <get-hoveredAlpha>(): kotlin/Float // androidx.compose.material.ripple/RippleAlpha.hoveredAlpha.<get-hoveredAlpha>|<get-hoveredAlpha>(){}[0]
final val pressedAlpha // androidx.compose.material.ripple/RippleAlpha.pressedAlpha|{}pressedAlpha[0]
final fun <get-pressedAlpha>(): kotlin/Float // androidx.compose.material.ripple/RippleAlpha.pressedAlpha.<get-pressedAlpha>|<get-pressedAlpha>(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // androidx.compose.material.ripple/RippleAlpha.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // androidx.compose.material.ripple/RippleAlpha.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // androidx.compose.material.ripple/RippleAlpha.toString|toString(){}[0]
}
final val androidx.compose.material.ripple/LocalRippleTheme // androidx.compose.material.ripple/LocalRippleTheme|{}LocalRippleTheme[0]
final fun <get-LocalRippleTheme>(): androidx.compose.runtime/ProvidableCompositionLocal<androidx.compose.material.ripple/RippleTheme> // androidx.compose.material.ripple/LocalRippleTheme.<get-LocalRippleTheme>|<get-LocalRippleTheme>(){}[0]
final val androidx.compose.material.ripple/androidx_compose_material_ripple_RippleAlpha$stableprop // androidx.compose.material.ripple/androidx_compose_material_ripple_RippleAlpha$stableprop|#static{}androidx_compose_material_ripple_RippleAlpha$stableprop[0]
final fun androidx.compose.material.ripple/androidx_compose_material_ripple_RippleAlpha$stableprop_getter(): kotlin/Int // androidx.compose.material.ripple/androidx_compose_material_ripple_RippleAlpha$stableprop_getter|androidx_compose_material_ripple_RippleAlpha$stableprop_getter(){}[0]
final fun androidx.compose.material.ripple/createRippleModifierNode(androidx.compose.foundation.interaction/InteractionSource, kotlin/Boolean, androidx.compose.ui.unit/Dp, androidx.compose.ui.graphics/ColorProducer, kotlin/Function0<androidx.compose.material.ripple/RippleAlpha>): androidx.compose.ui.node/DelegatableNode // androidx.compose.material.ripple/createRippleModifierNode|createRippleModifierNode(androidx.compose.foundation.interaction.InteractionSource;kotlin.Boolean;androidx.compose.ui.unit.Dp;androidx.compose.ui.graphics.ColorProducer;kotlin.Function0<androidx.compose.material.ripple.RippleAlpha>){}[0]
final fun androidx.compose.material.ripple/rememberRipple(kotlin/Boolean, androidx.compose.ui.unit/Dp, androidx.compose.ui.graphics/Color, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.compose.foundation/Indication // androidx.compose.material.ripple/rememberRipple|rememberRipple(kotlin.Boolean;androidx.compose.ui.unit.Dp;androidx.compose.ui.graphics.Color;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
File diff suppressed because it is too large Load Diff
+93 -189
View File
@@ -1834,6 +1834,10 @@ abstract interface androidx.compose.ui/UiMediaScope { // androidx.compose.ui/UiM
}
}
abstract interface com.compose.sdl.graphics/EncodedImageDecoder { // com.compose.sdl.graphics/EncodedImageDecoder|null[0]
abstract fun decode(kotlin/ByteArray): androidx.compose.ui.graphics/ImageBitmap? // com.compose.sdl.graphics/EncodedImageDecoder.decode|decode(kotlin.ByteArray){}[0]
}
abstract interface com.compose.sdl.graphics/NativePainterCanvas { // com.compose.sdl.graphics/NativePainterCanvas|null[0]
abstract fun drawNativePainter(kotlin/String, com.compose.sdl.res/ResourceKind, kotlin/Float, kotlin/Float, kotlin/Float, kotlin/Float, androidx.compose.ui.layout/ContentScale, kotlin/Float) // com.compose.sdl.graphics/NativePainterCanvas.drawNativePainter|drawNativePainter(kotlin.String;com.compose.sdl.res.ResourceKind;kotlin.Float;kotlin.Float;kotlin.Float;kotlin.Float;androidx.compose.ui.layout.ContentScale;kotlin.Float){}[0]
}
@@ -2522,9 +2526,23 @@ final class androidx.compose.ui.autofill/AutofillTree { // androidx.compose.ui.a
final fun plusAssign(androidx.compose.ui.autofill/AutofillNode) // androidx.compose.ui.autofill/AutofillTree.plusAssign|plusAssign(androidx.compose.ui.autofill.AutofillNode){}[0]
}
final class androidx.compose.ui.draganddrop/DragAndDropEvent // androidx.compose.ui.draganddrop/DragAndDropEvent|null[0]
final class androidx.compose.ui.draganddrop/DragAndDropEvent { // androidx.compose.ui.draganddrop/DragAndDropEvent|null[0]
final val position // androidx.compose.ui.draganddrop/DragAndDropEvent.position|{}position[0]
final fun <get-position>(): androidx.compose.ui.geometry/Offset // androidx.compose.ui.draganddrop/DragAndDropEvent.position.<get-position>|<get-position>(){}[0]
final val transferData // androidx.compose.ui.draganddrop/DragAndDropEvent.transferData|{}transferData[0]
final fun <get-transferData>(): androidx.compose.ui.draganddrop/DragAndDropTransferData // androidx.compose.ui.draganddrop/DragAndDropEvent.transferData.<get-transferData>|<get-transferData>(){}[0]
}
final class androidx.compose.ui.draganddrop/DragAndDropTransferData // androidx.compose.ui.draganddrop/DragAndDropTransferData|null[0]
final class androidx.compose.ui.draganddrop/DragAndDropTransferData { // androidx.compose.ui.draganddrop/DragAndDropTransferData|null[0]
final val filePaths // androidx.compose.ui.draganddrop/DragAndDropTransferData.filePaths|{}filePaths[0]
final fun <get-filePaths>(): kotlin.collections/List<kotlin/String> // androidx.compose.ui.draganddrop/DragAndDropTransferData.filePaths.<get-filePaths>|<get-filePaths>(){}[0]
final val hasFiles // androidx.compose.ui.draganddrop/DragAndDropTransferData.hasFiles|{}hasFiles[0]
final fun <get-hasFiles>(): kotlin/Boolean // androidx.compose.ui.draganddrop/DragAndDropTransferData.hasFiles.<get-hasFiles>|<get-hasFiles>(){}[0]
final val hasText // androidx.compose.ui.draganddrop/DragAndDropTransferData.hasText|{}hasText[0]
final fun <get-hasText>(): kotlin/Boolean // androidx.compose.ui.draganddrop/DragAndDropTransferData.hasText.<get-hasText>|<get-hasText>(){}[0]
final val text // androidx.compose.ui.draganddrop/DragAndDropTransferData.text|{}text[0]
final fun <get-text>(): kotlin/String? // androidx.compose.ui.draganddrop/DragAndDropTransferData.text.<get-text>|<get-text>(){}[0]
}
final class androidx.compose.ui.draw/CacheDrawScope : androidx.compose.ui.unit/Density { // androidx.compose.ui.draw/CacheDrawScope|null[0]
final val density // androidx.compose.ui.draw/CacheDrawScope.density|{}density[0]
@@ -3654,9 +3672,11 @@ final class androidx.compose.ui.platform/ClipEntry { // androidx.compose.ui.plat
final val clipMetadata // androidx.compose.ui.platform/ClipEntry.clipMetadata|{}clipMetadata[0]
final fun <get-clipMetadata>(): androidx.compose.ui.platform/ClipMetadata // androidx.compose.ui.platform/ClipEntry.clipMetadata.<get-clipMetadata>|<get-clipMetadata>(){}[0]
final fun getImage(): kotlin/ByteArray? // androidx.compose.ui.platform/ClipEntry.getImage|getImage(){}[0]
final fun getPlainText(): kotlin/String? // androidx.compose.ui.platform/ClipEntry.getPlainText|getPlainText(){}[0]
final object Companion { // androidx.compose.ui.platform/ClipEntry.Companion|null[0]
final fun withImage(kotlin/ByteArray): androidx.compose.ui.platform/ClipEntry // androidx.compose.ui.platform/ClipEntry.Companion.withImage|withImage(kotlin.ByteArray){}[0]
final fun withPlainText(kotlin/String): androidx.compose.ui.platform/ClipEntry // androidx.compose.ui.platform/ClipEntry.Companion.withPlainText|withPlainText(kotlin.String){}[0]
}
}
@@ -5348,136 +5368,6 @@ final class com.compose.sdl.element/GraphicsLayerNode : androidx.compose.ui/Modi
final fun <set-translationY>(kotlin/Float) // com.compose.sdl.element/GraphicsLayerNode.translationY.<set-translationY>|<set-translationY>(kotlin.Float){}[0]
}
final class com.compose.sdl.element/MiddleClickModifier : androidx.compose.ui.node/ModifierNodeElement<com.compose.sdl.element/MiddleClickNode> { // com.compose.sdl.element/MiddleClickModifier|null[0]
constructor <init>(kotlin/Function0<kotlin/Unit>) // com.compose.sdl.element/MiddleClickModifier.<init>|<init>(kotlin.Function0<kotlin.Unit>){}[0]
final val onClick // com.compose.sdl.element/MiddleClickModifier.onClick|{}onClick[0]
final fun <get-onClick>(): kotlin/Function0<kotlin/Unit> // com.compose.sdl.element/MiddleClickModifier.onClick.<get-onClick>|<get-onClick>(){}[0]
final fun create(): com.compose.sdl.element/MiddleClickNode // com.compose.sdl.element/MiddleClickModifier.create|create(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl.element/MiddleClickModifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl.element/MiddleClickModifier.hashCode|hashCode(){}[0]
final fun update(com.compose.sdl.element/MiddleClickNode) // com.compose.sdl.element/MiddleClickModifier.update|update(com.compose.sdl.element.MiddleClickNode){}[0]
}
final class com.compose.sdl.element/MiddleClickNode : androidx.compose.ui/Modifier.Node { // com.compose.sdl.element/MiddleClickNode|null[0]
constructor <init>(kotlin/Function0<kotlin/Unit>) // com.compose.sdl.element/MiddleClickNode.<init>|<init>(kotlin.Function0<kotlin.Unit>){}[0]
final var onClick // com.compose.sdl.element/MiddleClickNode.onClick|{}onClick[0]
final fun <get-onClick>(): kotlin/Function0<kotlin/Unit> // com.compose.sdl.element/MiddleClickNode.onClick.<get-onClick>|<get-onClick>(){}[0]
final fun <set-onClick>(kotlin/Function0<kotlin/Unit>) // com.compose.sdl.element/MiddleClickNode.onClick.<set-onClick>|<set-onClick>(kotlin.Function0<kotlin.Unit>){}[0]
}
final class com.compose.sdl.element/OnDragModifier : androidx.compose.ui.node/ModifierNodeElement<com.compose.sdl.element/OnDragNode> { // com.compose.sdl.element/OnDragModifier|null[0]
constructor <init>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>, kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>, kotlin/Function0<kotlin/Unit>) // com.compose.sdl.element/OnDragModifier.<init>|<init>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>;kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>;kotlin.Function0<kotlin.Unit>){}[0]
final val onDrag // com.compose.sdl.element/OnDragModifier.onDrag|{}onDrag[0]
final fun <get-onDrag>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/OnDragModifier.onDrag.<get-onDrag>|<get-onDrag>(){}[0]
final val onEnd // com.compose.sdl.element/OnDragModifier.onEnd|{}onEnd[0]
final fun <get-onEnd>(): kotlin/Function0<kotlin/Unit> // com.compose.sdl.element/OnDragModifier.onEnd.<get-onEnd>|<get-onEnd>(){}[0]
final val onStart // com.compose.sdl.element/OnDragModifier.onStart|{}onStart[0]
final fun <get-onStart>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/OnDragModifier.onStart.<get-onStart>|<get-onStart>(){}[0]
final fun create(): com.compose.sdl.element/OnDragNode // com.compose.sdl.element/OnDragModifier.create|create(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl.element/OnDragModifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl.element/OnDragModifier.hashCode|hashCode(){}[0]
final fun update(com.compose.sdl.element/OnDragNode) // com.compose.sdl.element/OnDragModifier.update|update(com.compose.sdl.element.OnDragNode){}[0]
}
final class com.compose.sdl.element/OnDragNode : androidx.compose.ui/Modifier.Node { // com.compose.sdl.element/OnDragNode|null[0]
constructor <init>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>, kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>, kotlin/Function0<kotlin/Unit>) // com.compose.sdl.element/OnDragNode.<init>|<init>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>;kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>;kotlin.Function0<kotlin.Unit>){}[0]
final var onDrag // com.compose.sdl.element/OnDragNode.onDrag|{}onDrag[0]
final fun <get-onDrag>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/OnDragNode.onDrag.<get-onDrag>|<get-onDrag>(){}[0]
final fun <set-onDrag>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/OnDragNode.onDrag.<set-onDrag>|<set-onDrag>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final var onEnd // com.compose.sdl.element/OnDragNode.onEnd|{}onEnd[0]
final fun <get-onEnd>(): kotlin/Function0<kotlin/Unit> // com.compose.sdl.element/OnDragNode.onEnd.<get-onEnd>|<get-onEnd>(){}[0]
final fun <set-onEnd>(kotlin/Function0<kotlin/Unit>) // com.compose.sdl.element/OnDragNode.onEnd.<set-onEnd>|<set-onEnd>(kotlin.Function0<kotlin.Unit>){}[0]
final var onStart // com.compose.sdl.element/OnDragNode.onStart|{}onStart[0]
final fun <get-onStart>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/OnDragNode.onStart.<get-onStart>|<get-onStart>(){}[0]
final fun <set-onStart>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/OnDragNode.onStart.<set-onStart>|<set-onStart>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
}
final class com.compose.sdl.element/OnPressedModifier : androidx.compose.ui.node/ModifierNodeElement<com.compose.sdl.element/OnPressedNode> { // com.compose.sdl.element/OnPressedModifier|null[0]
constructor <init>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/OnPressedModifier.<init>|<init>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final val handler // com.compose.sdl.element/OnPressedModifier.handler|{}handler[0]
final fun <get-handler>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/OnPressedModifier.handler.<get-handler>|<get-handler>(){}[0]
final fun create(): com.compose.sdl.element/OnPressedNode // com.compose.sdl.element/OnPressedModifier.create|create(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl.element/OnPressedModifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl.element/OnPressedModifier.hashCode|hashCode(){}[0]
final fun update(com.compose.sdl.element/OnPressedNode) // com.compose.sdl.element/OnPressedModifier.update|update(com.compose.sdl.element.OnPressedNode){}[0]
}
final class com.compose.sdl.element/OnPressedNode : androidx.compose.ui/Modifier.Node { // com.compose.sdl.element/OnPressedNode|null[0]
constructor <init>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/OnPressedNode.<init>|<init>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final var handler // com.compose.sdl.element/OnPressedNode.handler|{}handler[0]
final fun <get-handler>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/OnPressedNode.handler.<get-handler>|<get-handler>(){}[0]
final fun <set-handler>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/OnPressedNode.handler.<set-handler>|<set-handler>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
}
final class com.compose.sdl.element/OnTextInputModifier : androidx.compose.ui.node/ModifierNodeElement<com.compose.sdl.element/OnTextInputNode> { // com.compose.sdl.element/OnTextInputModifier|null[0]
constructor <init>(kotlin/Function1<kotlin/String, kotlin/Unit>) // com.compose.sdl.element/OnTextInputModifier.<init>|<init>(kotlin.Function1<kotlin.String,kotlin.Unit>){}[0]
final val handler // com.compose.sdl.element/OnTextInputModifier.handler|{}handler[0]
final fun <get-handler>(): kotlin/Function1<kotlin/String, kotlin/Unit> // com.compose.sdl.element/OnTextInputModifier.handler.<get-handler>|<get-handler>(){}[0]
final fun create(): com.compose.sdl.element/OnTextInputNode // com.compose.sdl.element/OnTextInputModifier.create|create(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl.element/OnTextInputModifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl.element/OnTextInputModifier.hashCode|hashCode(){}[0]
final fun update(com.compose.sdl.element/OnTextInputNode) // com.compose.sdl.element/OnTextInputModifier.update|update(com.compose.sdl.element.OnTextInputNode){}[0]
}
final class com.compose.sdl.element/OnTextInputNode : androidx.compose.ui/Modifier.Node { // com.compose.sdl.element/OnTextInputNode|null[0]
constructor <init>(kotlin/Function1<kotlin/String, kotlin/Unit>) // com.compose.sdl.element/OnTextInputNode.<init>|<init>(kotlin.Function1<kotlin.String,kotlin.Unit>){}[0]
final var handler // com.compose.sdl.element/OnTextInputNode.handler|{}handler[0]
final fun <get-handler>(): kotlin/Function1<kotlin/String, kotlin/Unit> // com.compose.sdl.element/OnTextInputNode.handler.<get-handler>|<get-handler>(){}[0]
final fun <set-handler>(kotlin/Function1<kotlin/String, kotlin/Unit>) // com.compose.sdl.element/OnTextInputNode.handler.<set-handler>|<set-handler>(kotlin.Function1<kotlin.String,kotlin.Unit>){}[0]
}
final class com.compose.sdl.element/PressableModifier : androidx.compose.ui.node/ModifierNodeElement<com.compose.sdl.element/PressableNode> { // com.compose.sdl.element/PressableModifier|null[0]
constructor <init>(kotlin/Function1<kotlin/Boolean, kotlin/Unit>) // com.compose.sdl.element/PressableModifier.<init>|<init>(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
final val onChange // com.compose.sdl.element/PressableModifier.onChange|{}onChange[0]
final fun <get-onChange>(): kotlin/Function1<kotlin/Boolean, kotlin/Unit> // com.compose.sdl.element/PressableModifier.onChange.<get-onChange>|<get-onChange>(){}[0]
final fun create(): com.compose.sdl.element/PressableNode // com.compose.sdl.element/PressableModifier.create|create(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl.element/PressableModifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl.element/PressableModifier.hashCode|hashCode(){}[0]
final fun update(com.compose.sdl.element/PressableNode) // com.compose.sdl.element/PressableModifier.update|update(com.compose.sdl.element.PressableNode){}[0]
}
final class com.compose.sdl.element/PressableNode : androidx.compose.ui/Modifier.Node { // com.compose.sdl.element/PressableNode|null[0]
constructor <init>(kotlin/Function1<kotlin/Boolean, kotlin/Unit>) // com.compose.sdl.element/PressableNode.<init>|<init>(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
final var onChange // com.compose.sdl.element/PressableNode.onChange|{}onChange[0]
final fun <get-onChange>(): kotlin/Function1<kotlin/Boolean, kotlin/Unit> // com.compose.sdl.element/PressableNode.onChange.<get-onChange>|<get-onChange>(){}[0]
final fun <set-onChange>(kotlin/Function1<kotlin/Boolean, kotlin/Unit>) // com.compose.sdl.element/PressableNode.onChange.<set-onChange>|<set-onChange>(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
}
final class com.compose.sdl.element/SecondaryClickModifier : androidx.compose.ui.node/ModifierNodeElement<com.compose.sdl.element/SecondaryClickNode> { // com.compose.sdl.element/SecondaryClickModifier|null[0]
constructor <init>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/SecondaryClickModifier.<init>|<init>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final val onClick // com.compose.sdl.element/SecondaryClickModifier.onClick|{}onClick[0]
final fun <get-onClick>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/SecondaryClickModifier.onClick.<get-onClick>|<get-onClick>(){}[0]
final fun create(): com.compose.sdl.element/SecondaryClickNode // com.compose.sdl.element/SecondaryClickModifier.create|create(){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl.element/SecondaryClickModifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl.element/SecondaryClickModifier.hashCode|hashCode(){}[0]
final fun update(com.compose.sdl.element/SecondaryClickNode) // com.compose.sdl.element/SecondaryClickModifier.update|update(com.compose.sdl.element.SecondaryClickNode){}[0]
}
final class com.compose.sdl.element/SecondaryClickNode : androidx.compose.ui/Modifier.Node { // com.compose.sdl.element/SecondaryClickNode|null[0]
constructor <init>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/SecondaryClickNode.<init>|<init>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final var onClick // com.compose.sdl.element/SecondaryClickNode.onClick|{}onClick[0]
final fun <get-onClick>(): kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> // com.compose.sdl.element/SecondaryClickNode.onClick.<get-onClick>|<get-onClick>(){}[0]
final fun <set-onClick>(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>) // com.compose.sdl.element/SecondaryClickNode.onClick.<set-onClick>|<set-onClick>(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
}
final class com.compose.sdl.graphics/ProjectCanvas : androidx.compose.ui.graphics/Canvas { // com.compose.sdl.graphics/ProjectCanvas|null[0]
constructor <init>() // com.compose.sdl.graphics/ProjectCanvas.<init>|<init>(){}[0]
@@ -5649,8 +5539,6 @@ final class com.compose.sdl.node/ComposeRootHost { // com.compose.sdl.node/Compo
final val applier // com.compose.sdl.node/ComposeRootHost.applier|{}applier[0]
final fun <get-applier>(): androidx.compose.runtime/Applier<*> // com.compose.sdl.node/ComposeRootHost.applier.<get-applier>|<get-applier>(){}[0]
final val autofillTree // com.compose.sdl.node/ComposeRootHost.autofillTree|{}autofillTree[0]
final fun <get-autofillTree>(): androidx.compose.ui.autofill/AutofillTree // com.compose.sdl.node/ComposeRootHost.autofillTree.<get-autofillTree>|<get-autofillTree>(){}[0]
final val density // com.compose.sdl.node/ComposeRootHost.density|{}density[0]
final fun <get-density>(): androidx.compose.ui.unit/Density // com.compose.sdl.node/ComposeRootHost.density.<get-density>|<get-density>(){}[0]
final val focusManager // com.compose.sdl.node/ComposeRootHost.focusManager|{}focusManager[0]
@@ -5665,8 +5553,6 @@ final class com.compose.sdl.node/ComposeRootHost { // com.compose.sdl.node/Compo
final fun <get-layoutDirection>(): androidx.compose.ui.unit/LayoutDirection // com.compose.sdl.node/ComposeRootHost.layoutDirection.<get-layoutDirection>|<get-layoutDirection>(){}[0]
final val softwareKeyboardController // com.compose.sdl.node/ComposeRootHost.softwareKeyboardController|{}softwareKeyboardController[0]
final fun <get-softwareKeyboardController>(): androidx.compose.ui.platform/SoftwareKeyboardController // com.compose.sdl.node/ComposeRootHost.softwareKeyboardController.<get-softwareKeyboardController>|<get-softwareKeyboardController>(){}[0]
final val textInputService // com.compose.sdl.node/ComposeRootHost.textInputService|{}textInputService[0]
final fun <get-textInputService>(): androidx.compose.ui.text.input/TextInputService // com.compose.sdl.node/ComposeRootHost.textInputService.<get-textInputService>|<get-textInputService>(){}[0]
final val textToolbar // com.compose.sdl.node/ComposeRootHost.textToolbar|{}textToolbar[0]
final fun <get-textToolbar>(): androidx.compose.ui.platform/TextToolbar // com.compose.sdl.node/ComposeRootHost.textToolbar.<get-textToolbar>|<get-textToolbar>(){}[0]
final val viewConfiguration // com.compose.sdl.node/ComposeRootHost.viewConfiguration|{}viewConfiguration[0]
@@ -5676,9 +5562,12 @@ final class com.compose.sdl.node/ComposeRootHost { // com.compose.sdl.node/Compo
final fun attach() // com.compose.sdl.node/ComposeRootHost.attach|attach(){}[0]
final fun dispatchKeyEvent(androidx.compose.ui.input.key/KeyEvent): kotlin/Boolean // com.compose.sdl.node/ComposeRootHost.dispatchKeyEvent|dispatchKeyEvent(androidx.compose.ui.input.key.KeyEvent){}[0]
final fun dispatchTextInput(kotlin/String): kotlin/Boolean // com.compose.sdl.node/ComposeRootHost.dispatchTextInput|dispatchTextInput(kotlin.String){}[0]
final fun measureAndLayout() // com.compose.sdl.node/ComposeRootHost.measureAndLayout|measureAndLayout(){}[0]
final fun onPointer(kotlin/Float, kotlin/Float, kotlin/Int, kotlin/Int) // com.compose.sdl.node/ComposeRootHost.onPointer|onPointer(kotlin.Float;kotlin.Float;kotlin.Int;kotlin.Int){}[0]
final fun onDropBegin() // com.compose.sdl.node/ComposeRootHost.onDropBegin|onDropBegin(){}[0]
final fun onDropComplete() // com.compose.sdl.node/ComposeRootHost.onDropComplete|onDropComplete(){}[0]
final fun onDropFile(kotlin/String) // com.compose.sdl.node/ComposeRootHost.onDropFile|onDropFile(kotlin.String){}[0]
final fun onDropPosition(kotlin/Float, kotlin/Float) // com.compose.sdl.node/ComposeRootHost.onDropPosition|onDropPosition(kotlin.Float;kotlin.Float){}[0]
final fun onDropText(kotlin/String) // com.compose.sdl.node/ComposeRootHost.onDropText|onDropText(kotlin.String){}[0]
final fun onPointerRaw(kotlin/Float, kotlin/Float, kotlin/Int, kotlin/Int, kotlin/Long) // com.compose.sdl.node/ComposeRootHost.onPointerRaw|onPointerRaw(kotlin.Float;kotlin.Float;kotlin.Int;kotlin.Int;kotlin.Long){}[0]
final fun onWheel(kotlin/Float, kotlin/Float, kotlin/Float, kotlin/Float, kotlin/Long) // com.compose.sdl.node/ComposeRootHost.onWheel|onWheel(kotlin.Float;kotlin.Float;kotlin.Float;kotlin.Float;kotlin.Long){}[0]
final fun sendAnimationFrame(kotlin/Long) // com.compose.sdl.node/ComposeRootHost.sendAnimationFrame|sendAnimationFrame(kotlin.Long){}[0]
@@ -8387,6 +8276,45 @@ sealed class androidx.compose.ui.text.font/SystemFontFamily : androidx.compose.u
sealed class androidx.compose.ui.text/TtsAnnotation : androidx.compose.ui.text/AnnotatedString.Annotation // androidx.compose.ui.text/TtsAnnotation|null[0]
sealed class com.compose.sdl/AppEvent { // com.compose.sdl/AppEvent|null[0]
final enum class DropPhase : kotlin/Enum<com.compose.sdl/AppEvent.DropPhase> { // com.compose.sdl/AppEvent.DropPhase|null[0]
enum entry BEGIN // com.compose.sdl/AppEvent.DropPhase.BEGIN|null[0]
enum entry COMPLETE // com.compose.sdl/AppEvent.DropPhase.COMPLETE|null[0]
enum entry FILE // com.compose.sdl/AppEvent.DropPhase.FILE|null[0]
enum entry POSITION // com.compose.sdl/AppEvent.DropPhase.POSITION|null[0]
enum entry TEXT // com.compose.sdl/AppEvent.DropPhase.TEXT|null[0]
final val entries // com.compose.sdl/AppEvent.DropPhase.entries|#static{}entries[0]
final fun <get-entries>(): kotlin.enums/EnumEntries<com.compose.sdl/AppEvent.DropPhase> // com.compose.sdl/AppEvent.DropPhase.entries.<get-entries>|<get-entries>#static(){}[0]
final fun valueOf(kotlin/String): com.compose.sdl/AppEvent.DropPhase // com.compose.sdl/AppEvent.DropPhase.valueOf|valueOf#static(kotlin.String){}[0]
final fun values(): kotlin/Array<com.compose.sdl/AppEvent.DropPhase> // com.compose.sdl/AppEvent.DropPhase.values|values#static(){}[0]
}
final class Drop : com.compose.sdl/AppEvent { // com.compose.sdl/AppEvent.Drop|null[0]
constructor <init>(kotlin/UInt, com.compose.sdl/AppEvent.DropPhase, kotlin/Float = ..., kotlin/Float = ..., kotlin/String? = ...) // com.compose.sdl/AppEvent.Drop.<init>|<init>(kotlin.UInt;com.compose.sdl.AppEvent.DropPhase;kotlin.Float;kotlin.Float;kotlin.String?){}[0]
final val data // com.compose.sdl/AppEvent.Drop.data|{}data[0]
final fun <get-data>(): kotlin/String? // com.compose.sdl/AppEvent.Drop.data.<get-data>|<get-data>(){}[0]
final val phase // com.compose.sdl/AppEvent.Drop.phase|{}phase[0]
final fun <get-phase>(): com.compose.sdl/AppEvent.DropPhase // com.compose.sdl/AppEvent.Drop.phase.<get-phase>|<get-phase>(){}[0]
final val windowId // com.compose.sdl/AppEvent.Drop.windowId|{}windowId[0]
final fun <get-windowId>(): kotlin/UInt // com.compose.sdl/AppEvent.Drop.windowId.<get-windowId>|<get-windowId>(){}[0]
final val x // com.compose.sdl/AppEvent.Drop.x|{}x[0]
final fun <get-x>(): kotlin/Float // com.compose.sdl/AppEvent.Drop.x.<get-x>|<get-x>(){}[0]
final val y // com.compose.sdl/AppEvent.Drop.y|{}y[0]
final fun <get-y>(): kotlin/Float // com.compose.sdl/AppEvent.Drop.y.<get-y>|<get-y>(){}[0]
final fun component1(): kotlin/UInt // com.compose.sdl/AppEvent.Drop.component1|component1(){}[0]
final fun component2(): com.compose.sdl/AppEvent.DropPhase // com.compose.sdl/AppEvent.Drop.component2|component2(){}[0]
final fun component3(): kotlin/Float // com.compose.sdl/AppEvent.Drop.component3|component3(){}[0]
final fun component4(): kotlin/Float // com.compose.sdl/AppEvent.Drop.component4|component4(){}[0]
final fun component5(): kotlin/String? // com.compose.sdl/AppEvent.Drop.component5|component5(){}[0]
final fun copy(kotlin/UInt = ..., com.compose.sdl/AppEvent.DropPhase = ..., kotlin/Float = ..., kotlin/Float = ..., kotlin/String? = ...): com.compose.sdl/AppEvent.Drop // com.compose.sdl/AppEvent.Drop.copy|copy(kotlin.UInt;com.compose.sdl.AppEvent.DropPhase;kotlin.Float;kotlin.Float;kotlin.String?){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl/AppEvent.Drop.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl/AppEvent.Drop.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // com.compose.sdl/AppEvent.Drop.toString|toString(){}[0]
}
final class Key : com.compose.sdl/AppEvent { // com.compose.sdl/AppEvent.Key|null[0]
constructor <init>(androidx.compose.ui.input.key/KeyEvent, kotlin/UInt = ...) // com.compose.sdl/AppEvent.Key.<init>|<init>(androidx.compose.ui.input.key.KeyEvent;kotlin.UInt){}[0]
@@ -8473,6 +8401,25 @@ sealed class com.compose.sdl/AppEvent { // com.compose.sdl/AppEvent|null[0]
final fun toString(): kotlin/String // com.compose.sdl/AppEvent.TextInput.toString|toString(){}[0]
}
final class WindowActivation : com.compose.sdl/AppEvent { // com.compose.sdl/AppEvent.WindowActivation|null[0]
constructor <init>(kotlin/UInt = ..., kotlin/Boolean? = ..., kotlin/Boolean? = ...) // com.compose.sdl/AppEvent.WindowActivation.<init>|<init>(kotlin.UInt;kotlin.Boolean?;kotlin.Boolean?){}[0]
final val focused // com.compose.sdl/AppEvent.WindowActivation.focused|{}focused[0]
final fun <get-focused>(): kotlin/Boolean? // com.compose.sdl/AppEvent.WindowActivation.focused.<get-focused>|<get-focused>(){}[0]
final val visible // com.compose.sdl/AppEvent.WindowActivation.visible|{}visible[0]
final fun <get-visible>(): kotlin/Boolean? // com.compose.sdl/AppEvent.WindowActivation.visible.<get-visible>|<get-visible>(){}[0]
final val windowId // com.compose.sdl/AppEvent.WindowActivation.windowId|{}windowId[0]
final fun <get-windowId>(): kotlin/UInt // com.compose.sdl/AppEvent.WindowActivation.windowId.<get-windowId>|<get-windowId>(){}[0]
final fun component1(): kotlin/UInt // com.compose.sdl/AppEvent.WindowActivation.component1|component1(){}[0]
final fun component2(): kotlin/Boolean? // com.compose.sdl/AppEvent.WindowActivation.component2|component2(){}[0]
final fun component3(): kotlin/Boolean? // com.compose.sdl/AppEvent.WindowActivation.component3|component3(){}[0]
final fun copy(kotlin/UInt = ..., kotlin/Boolean? = ..., kotlin/Boolean? = ...): com.compose.sdl/AppEvent.WindowActivation // com.compose.sdl/AppEvent.WindowActivation.copy|copy(kotlin.UInt;kotlin.Boolean?;kotlin.Boolean?){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.compose.sdl/AppEvent.WindowActivation.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.compose.sdl/AppEvent.WindowActivation.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // com.compose.sdl/AppEvent.WindowActivation.toString|toString(){}[0]
}
final class WindowClose : com.compose.sdl/AppEvent { // com.compose.sdl/AppEvent.WindowClose|null[0]
constructor <init>(kotlin/UInt = ...) // com.compose.sdl/AppEvent.WindowClose.<init>|<init>(kotlin.UInt){}[0]
@@ -9299,16 +9246,6 @@ final val androidx.compose.ui.platform/androidx_compose_ui_platform_InspectorVal
final val androidx.compose.ui.platform/androidx_compose_ui_platform_PlatformWindowInsetsProviderNode$stableprop // androidx.compose.ui.platform/androidx_compose_ui_platform_PlatformWindowInsetsProviderNode$stableprop|#static{}androidx_compose_ui_platform_PlatformWindowInsetsProviderNode$stableprop[0]
final val androidx.compose.ui.platform/androidx_compose_ui_platform_ValueElement$stableprop // androidx.compose.ui.platform/androidx_compose_ui_platform_ValueElement$stableprop|#static{}androidx_compose_ui_platform_ValueElement$stableprop[0]
final val androidx.compose.ui.platform/androidx_compose_ui_platform_ValueElementSequence$stableprop // androidx.compose.ui.platform/androidx_compose_ui_platform_ValueElementSequence$stableprop|#static{}androidx_compose_ui_platform_ValueElementSequence$stableprop[0]
final val androidx.compose.ui.platform/currentClipboard // androidx.compose.ui.platform/currentClipboard|{}currentClipboard[0]
final fun <get-currentClipboard>(): androidx.compose.ui.platform/ClipboardManager // androidx.compose.ui.platform/currentClipboard.<get-currentClipboard>|<get-currentClipboard>(){}[0]
final val androidx.compose.ui.platform/defaultClipboard // androidx.compose.ui.platform/defaultClipboard|{}defaultClipboard[0]
final fun <get-defaultClipboard>(): androidx.compose.ui.platform/Clipboard // androidx.compose.ui.platform/defaultClipboard.<get-defaultClipboard>|<get-defaultClipboard>(){}[0]
final val androidx.compose.ui.platform/defaultClipboardManager // androidx.compose.ui.platform/defaultClipboardManager|{}defaultClipboardManager[0]
final fun <get-defaultClipboardManager>(): androidx.compose.ui.platform/ClipboardManager // androidx.compose.ui.platform/defaultClipboardManager.<get-defaultClipboardManager>|<get-defaultClipboardManager>(){}[0]
final val androidx.compose.ui.platform/platformClipboard // androidx.compose.ui.platform/platformClipboard|{}platformClipboard[0]
final fun <get-platformClipboard>(): androidx.compose.ui.platform/Clipboard // androidx.compose.ui.platform/platformClipboard.<get-platformClipboard>|<get-platformClipboard>(){}[0]
final val androidx.compose.ui.platform/platformClipboardManager // androidx.compose.ui.platform/platformClipboardManager|{}platformClipboardManager[0]
final fun <get-platformClipboardManager>(): androidx.compose.ui.platform/ClipboardManager // androidx.compose.ui.platform/platformClipboardManager.<get-platformClipboardManager>|<get-platformClipboardManager>(){}[0]
final val androidx.compose.ui.platform/safeContent // androidx.compose.ui.platform/safeContent|@androidx.compose.ui.platform.PlatformWindowInsets{}safeContent[0]
final fun (androidx.compose.ui.platform/PlatformWindowInsets).<get-safeContent>(): androidx.compose.ui.platform/PlatformInsets // androidx.compose.ui.platform/safeContent.<get-safeContent>|<get-safeContent>@androidx.compose.ui.platform.PlatformWindowInsets(){}[0]
final val androidx.compose.ui.platform/safeDrawing // androidx.compose.ui.platform/safeDrawing|@androidx.compose.ui.platform.PlatformWindowInsets{}safeDrawing[0]
@@ -9424,22 +9361,6 @@ final val com.compose.sdl.element/com_compose_sdl_element_ClipModifier$stablepro
final val com.compose.sdl.element/com_compose_sdl_element_ClipNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_ClipNode$stableprop|#static{}com_compose_sdl_element_ClipNode$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerModifier$stableprop // com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerModifier$stableprop|#static{}com_compose_sdl_element_GraphicsLayerModifier$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerNode$stableprop|#static{}com_compose_sdl_element_GraphicsLayerNode$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_MiddleClickModifier$stableprop // com.compose.sdl.element/com_compose_sdl_element_MiddleClickModifier$stableprop|#static{}com_compose_sdl_element_MiddleClickModifier$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_MiddleClickNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_MiddleClickNode$stableprop|#static{}com_compose_sdl_element_MiddleClickNode$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_OnDragModifier$stableprop // com.compose.sdl.element/com_compose_sdl_element_OnDragModifier$stableprop|#static{}com_compose_sdl_element_OnDragModifier$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_OnDragNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_OnDragNode$stableprop|#static{}com_compose_sdl_element_OnDragNode$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_OnPressedModifier$stableprop // com.compose.sdl.element/com_compose_sdl_element_OnPressedModifier$stableprop|#static{}com_compose_sdl_element_OnPressedModifier$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_OnPressedNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_OnPressedNode$stableprop|#static{}com_compose_sdl_element_OnPressedNode$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_OnTextInputModifier$stableprop // com.compose.sdl.element/com_compose_sdl_element_OnTextInputModifier$stableprop|#static{}com_compose_sdl_element_OnTextInputModifier$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_OnTextInputNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_OnTextInputNode$stableprop|#static{}com_compose_sdl_element_OnTextInputNode$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_PressableModifier$stableprop // com.compose.sdl.element/com_compose_sdl_element_PressableModifier$stableprop|#static{}com_compose_sdl_element_PressableModifier$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_PressableNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_PressableNode$stableprop|#static{}com_compose_sdl_element_PressableNode$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_SecondaryClickModifier$stableprop // com.compose.sdl.element/com_compose_sdl_element_SecondaryClickModifier$stableprop|#static{}com_compose_sdl_element_SecondaryClickModifier$stableprop[0]
final val com.compose.sdl.element/com_compose_sdl_element_SecondaryClickNode$stableprop // com.compose.sdl.element/com_compose_sdl_element_SecondaryClickNode$stableprop|#static{}com_compose_sdl_element_SecondaryClickNode$stableprop[0]
final val com.compose.sdl.graphics/a8 // com.compose.sdl.graphics/a8|@androidx.compose.ui.graphics.Color{}a8[0]
final fun (androidx.compose.ui.graphics/Color).<get-a8>(): kotlin/Int // com.compose.sdl.graphics/a8.<get-a8>|<get-a8>@androidx.compose.ui.graphics.Color(){}[0]
final val com.compose.sdl.graphics/b8 // com.compose.sdl.graphics/b8|@androidx.compose.ui.graphics.Color{}b8[0]
final fun (androidx.compose.ui.graphics/Color).<get-b8>(): kotlin/Int // com.compose.sdl.graphics/b8.<get-b8>|<get-b8>@androidx.compose.ui.graphics.Color(){}[0]
final val com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_Close$stableprop // com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_Close$stableprop|#static{}com_compose_sdl_graphics_PathCommand_Close$stableprop[0]
final val com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_CubicTo$stableprop // com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_CubicTo$stableprop|#static{}com_compose_sdl_graphics_PathCommand_CubicTo$stableprop[0]
final val com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_LineTo$stableprop // com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_LineTo$stableprop|#static{}com_compose_sdl_graphics_PathCommand_LineTo$stableprop[0]
@@ -9450,8 +9371,6 @@ final val com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectImageBitmap$s
final val com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPaint$stableprop // com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPaint$stableprop|#static{}com_compose_sdl_graphics_ProjectPaint$stableprop[0]
final val com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPath$stableprop // com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPath$stableprop|#static{}com_compose_sdl_graphics_ProjectPath$stableprop[0]
final val com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPathIterator$stableprop // com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPathIterator$stableprop|#static{}com_compose_sdl_graphics_ProjectPathIterator$stableprop[0]
final val com.compose.sdl.graphics/g8 // com.compose.sdl.graphics/g8|@androidx.compose.ui.graphics.Color{}g8[0]
final fun (androidx.compose.ui.graphics/Color).<get-g8>(): kotlin/Int // com.compose.sdl.graphics/g8.<get-g8>|<get-g8>@androidx.compose.ui.graphics.Color(){}[0]
final val com.compose.sdl.graphics/gradientCenter // com.compose.sdl.graphics/gradientCenter|@androidx.compose.ui.graphics.RadialGradient{}gradientCenter[0]
final fun (androidx.compose.ui.graphics/RadialGradient).<get-gradientCenter>(): androidx.compose.ui.geometry/Offset // com.compose.sdl.graphics/gradientCenter.<get-gradientCenter>|<get-gradientCenter>@androidx.compose.ui.graphics.RadialGradient(){}[0]
final val com.compose.sdl.graphics/gradientCenter // com.compose.sdl.graphics/gradientCenter|@androidx.compose.ui.graphics.SweepGradient{}gradientCenter[0]
@@ -9478,8 +9397,6 @@ final val com.compose.sdl.graphics/gradientTileMode // com.compose.sdl.graphics/
final fun (androidx.compose.ui.graphics/LinearGradient).<get-gradientTileMode>(): androidx.compose.ui.graphics/TileMode // com.compose.sdl.graphics/gradientTileMode.<get-gradientTileMode>|<get-gradientTileMode>@androidx.compose.ui.graphics.LinearGradient(){}[0]
final val com.compose.sdl.graphics/gradientTileMode // com.compose.sdl.graphics/gradientTileMode|@androidx.compose.ui.graphics.RadialGradient{}gradientTileMode[0]
final fun (androidx.compose.ui.graphics/RadialGradient).<get-gradientTileMode>(): androidx.compose.ui.graphics/TileMode // com.compose.sdl.graphics/gradientTileMode.<get-gradientTileMode>|<get-gradientTileMode>@androidx.compose.ui.graphics.RadialGradient(){}[0]
final val com.compose.sdl.graphics/r8 // com.compose.sdl.graphics/r8|@androidx.compose.ui.graphics.Color{}r8[0]
final fun (androidx.compose.ui.graphics/Color).<get-r8>(): kotlin/Int // com.compose.sdl.graphics/r8.<get-r8>|<get-r8>@androidx.compose.ui.graphics.Color(){}[0]
final val com.compose.sdl.icons/com_compose_sdl_icons_IconFont$stableprop // com.compose.sdl.icons/com_compose_sdl_icons_IconFont$stableprop|#static{}com_compose_sdl_icons_IconFont$stableprop[0]
final val com.compose.sdl.input/com_compose_sdl_input_LegacyPointerEvent$stableprop // com.compose.sdl.input/com_compose_sdl_input_LegacyPointerEvent$stableprop|#static{}com_compose_sdl_input_LegacyPointerEvent$stableprop[0]
final val com.compose.sdl.layout/intOffset // com.compose.sdl.layout/intOffset|@androidx.compose.ui.layout.LayoutCoordinates{}intOffset[0]
@@ -9512,12 +9429,14 @@ final val com.compose.sdl.window/com_compose_sdl_window_PopupHostState$stablepro
final val com.compose.sdl/LocalComposeNativeWindow // com.compose.sdl/LocalComposeNativeWindow|{}LocalComposeNativeWindow[0]
final fun <get-LocalComposeNativeWindow>(): androidx.compose.runtime/ProvidableCompositionLocal<com.compose.sdl/ComposeNativeWindow> // com.compose.sdl/LocalComposeNativeWindow.<get-LocalComposeNativeWindow>|<get-LocalComposeNativeWindow>(){}[0]
final val com.compose.sdl/com_compose_sdl_AppEvent$stableprop // com.compose.sdl/com_compose_sdl_AppEvent$stableprop|#static{}com_compose_sdl_AppEvent$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_Drop$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_Drop$stableprop|#static{}com_compose_sdl_AppEvent_Drop$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_Key$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_Key$stableprop|#static{}com_compose_sdl_AppEvent_Key$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_MouseWheel$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_MouseWheel$stableprop|#static{}com_compose_sdl_AppEvent_MouseWheel$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_Pointer$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_Pointer$stableprop|#static{}com_compose_sdl_AppEvent_Pointer$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_Quit$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_Quit$stableprop|#static{}com_compose_sdl_AppEvent_Quit$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_RedrawNeeded$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_RedrawNeeded$stableprop|#static{}com_compose_sdl_AppEvent_RedrawNeeded$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_TextInput$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_TextInput$stableprop|#static{}com_compose_sdl_AppEvent_TextInput$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_WindowActivation$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_WindowActivation$stableprop|#static{}com_compose_sdl_AppEvent_WindowActivation$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_WindowClose$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_WindowClose$stableprop|#static{}com_compose_sdl_AppEvent_WindowClose$stableprop[0]
final val com.compose.sdl/com_compose_sdl_AppEvent_WindowResized$stableprop // com.compose.sdl/com_compose_sdl_AppEvent_WindowResized$stableprop|#static{}com_compose_sdl_AppEvent_WindowResized$stableprop[0]
final val com.compose.sdl/com_compose_sdl_ComposeNativeWindow$stableprop // com.compose.sdl/com_compose_sdl_ComposeNativeWindow$stableprop|#static{}com_compose_sdl_ComposeNativeWindow$stableprop[0]
@@ -9655,6 +9574,8 @@ final var androidx.compose.ui/isDialogAnimationEnabled // androidx.compose.ui/is
final var androidx.compose.ui/useLegacyRenderNodeLayers // androidx.compose.ui/useLegacyRenderNodeLayers|@androidx.compose.ui.ComposeUiFlags{}useLegacyRenderNodeLayers[0]
final fun (androidx.compose.ui/ComposeUiFlags).<get-useLegacyRenderNodeLayers>(): kotlin/Boolean // androidx.compose.ui/useLegacyRenderNodeLayers.<get-useLegacyRenderNodeLayers>|<get-useLegacyRenderNodeLayers>@androidx.compose.ui.ComposeUiFlags(){}[0]
final fun (androidx.compose.ui/ComposeUiFlags).<set-useLegacyRenderNodeLayers>(kotlin/Boolean) // androidx.compose.ui/useLegacyRenderNodeLayers.<set-useLegacyRenderNodeLayers>|<set-useLegacyRenderNodeLayers>@androidx.compose.ui.ComposeUiFlags(kotlin.Boolean){}[0]
final var com.compose.sdl.graphics/encodedImageDecoder // com.compose.sdl.graphics/encodedImageDecoder|{}encodedImageDecoder[0]
final fun <get-encodedImageDecoder>(): com.compose.sdl.graphics/EncodedImageDecoder? // com.compose.sdl.graphics/encodedImageDecoder.<get-encodedImageDecoder>|<get-encodedImageDecoder>(){}[0]
final var com.compose.sdl.res/currentImageLoader // com.compose.sdl.res/currentImageLoader|{}currentImageLoader[0]
final fun <get-currentImageLoader>(): com.compose.sdl.res/ImageLoader // com.compose.sdl.res/currentImageLoader.<get-currentImageLoader>|<get-currentImageLoader>(){}[0]
final fun <set-currentImageLoader>(com.compose.sdl.res/ImageLoader) // com.compose.sdl.res/currentImageLoader.<set-currentImageLoader>|<set-currentImageLoader>(com.compose.sdl.res.ImageLoader){}[0]
@@ -9703,9 +9624,6 @@ final fun (androidx.compose.ui.graphics/Canvas).androidx.compose.ui.graphics/sca
final fun (androidx.compose.ui.graphics/Color).androidx.compose.ui.graphics/compositeOver(androidx.compose.ui.graphics/Color): androidx.compose.ui.graphics/Color // androidx.compose.ui.graphics/compositeOver|compositeOver@androidx.compose.ui.graphics.Color(androidx.compose.ui.graphics.Color){}[0]
final fun (androidx.compose.ui.graphics/Color).androidx.compose.ui.graphics/luminance(): kotlin/Float // androidx.compose.ui.graphics/luminance|luminance@androidx.compose.ui.graphics.Color(){}[0]
final fun (androidx.compose.ui.graphics/Color).androidx.compose.ui.graphics/toArgb(): kotlin/Int // androidx.compose.ui.graphics/toArgb|toArgb@androidx.compose.ui.graphics.Color(){}[0]
final fun (androidx.compose.ui.graphics/Color).com.compose.sdl.graphics/blend(androidx.compose.ui.graphics/Color, kotlin/Float): androidx.compose.ui.graphics/Color // com.compose.sdl.graphics/blend|blend@androidx.compose.ui.graphics.Color(androidx.compose.ui.graphics.Color;kotlin.Float){}[0]
final fun (androidx.compose.ui.graphics/Color).com.compose.sdl.graphics/darken(kotlin/Float): androidx.compose.ui.graphics/Color // com.compose.sdl.graphics/darken|darken@androidx.compose.ui.graphics.Color(kotlin.Float){}[0]
final fun (androidx.compose.ui.graphics/Color).com.compose.sdl.graphics/lighten(kotlin/Float): androidx.compose.ui.graphics/Color // com.compose.sdl.graphics/lighten|lighten@androidx.compose.ui.graphics.Color(kotlin.Float){}[0]
final fun (androidx.compose.ui.graphics/ImageBitmap).androidx.compose.ui.graphics/toPixelMap(kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/IntArray = ..., kotlin/Int = ..., kotlin/Int = ...): androidx.compose.ui.graphics/PixelMap // androidx.compose.ui.graphics/toPixelMap|toPixelMap@androidx.compose.ui.graphics.ImageBitmap(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.IntArray;kotlin.Int;kotlin.Int){}[0]
final fun (androidx.compose.ui.graphics/Matrix).androidx.compose.ui.graphics/isIdentity(): kotlin/Boolean // androidx.compose.ui.graphics/isIdentity|isIdentity@androidx.compose.ui.graphics.Matrix(){}[0]
final fun (androidx.compose.ui.graphics/Path).androidx.compose.ui.graphics/addOutline(androidx.compose.ui.graphics/Outline) // androidx.compose.ui.graphics/addOutline|addOutline@androidx.compose.ui.graphics.Path(androidx.compose.ui.graphics.Outline){}[0]
@@ -9908,12 +9826,6 @@ final fun (androidx.compose.ui/Modifier).androidx.compose.ui/preferredFrameRate(
final fun (androidx.compose.ui/Modifier).androidx.compose.ui/preferredFrameRate(kotlin/Float): androidx.compose.ui/Modifier // androidx.compose.ui/preferredFrameRate|preferredFrameRate@androidx.compose.ui.Modifier(kotlin.Float){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.ui/sensitiveContent(kotlin/Boolean = ...): androidx.compose.ui/Modifier // androidx.compose.ui/sensitiveContent|sensitiveContent@androidx.compose.ui.Modifier(kotlin.Boolean){}[0]
final fun (androidx.compose.ui/Modifier).androidx.compose.ui/zIndex(kotlin/Float): androidx.compose.ui/Modifier // androidx.compose.ui/zIndex|zIndex@androidx.compose.ui.Modifier(kotlin.Float){}[0]
final fun (androidx.compose.ui/Modifier).com.compose.sdl.modifier/onDrag(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> = ..., kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit> = ..., kotlin/Function0<kotlin/Unit> = ...): androidx.compose.ui/Modifier // com.compose.sdl.modifier/onDrag|onDrag@androidx.compose.ui.Modifier(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>;kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>;kotlin.Function0<kotlin.Unit>){}[0]
final fun (androidx.compose.ui/Modifier).com.compose.sdl.modifier/onMiddleClick(kotlin/Function0<kotlin/Unit>): androidx.compose.ui/Modifier // com.compose.sdl.modifier/onMiddleClick|onMiddleClick@androidx.compose.ui.Modifier(kotlin.Function0<kotlin.Unit>){}[0]
final fun (androidx.compose.ui/Modifier).com.compose.sdl.modifier/onPressed(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>): androidx.compose.ui/Modifier // com.compose.sdl.modifier/onPressed|onPressed@androidx.compose.ui.Modifier(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final fun (androidx.compose.ui/Modifier).com.compose.sdl.modifier/onSecondaryClick(kotlin/Function2<kotlin/Int, kotlin/Int, kotlin/Unit>): androidx.compose.ui/Modifier // com.compose.sdl.modifier/onSecondaryClick|onSecondaryClick@androidx.compose.ui.Modifier(kotlin.Function2<kotlin.Int,kotlin.Int,kotlin.Unit>){}[0]
final fun (androidx.compose.ui/Modifier).com.compose.sdl.modifier/onTextInput(kotlin/Function1<kotlin/String, kotlin/Unit>): androidx.compose.ui/Modifier // com.compose.sdl.modifier/onTextInput|onTextInput@androidx.compose.ui.Modifier(kotlin.Function1<kotlin.String,kotlin.Unit>){}[0]
final fun (androidx.compose.ui/Modifier).com.compose.sdl.modifier/pressable(kotlin/Function1<kotlin/Boolean, kotlin/Unit>): androidx.compose.ui/Modifier // com.compose.sdl.modifier/pressable|pressable@androidx.compose.ui.Modifier(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
final fun (kotlin.collections/List<androidx.compose.ui.graphics.vector/PathNode>).androidx.compose.ui.graphics.vector/toPath(androidx.compose.ui.graphics/Path = ...): androidx.compose.ui.graphics/Path // androidx.compose.ui.graphics.vector/toPath|toPath@kotlin.collections.List<androidx.compose.ui.graphics.vector.PathNode>(androidx.compose.ui.graphics.Path){}[0]
final fun (kotlin/ByteArray).androidx.compose.ui.graphics/decodeToImageBitmap(): androidx.compose.ui.graphics/ImageBitmap // androidx.compose.ui.graphics/decodeToImageBitmap|decodeToImageBitmap@kotlin.ByteArray(){}[0]
final fun (kotlin/CharSequence).androidx.compose.ui.text/substring(androidx.compose.ui.text/TextRange): kotlin/String // androidx.compose.ui.text/substring|substring@kotlin.CharSequence(androidx.compose.ui.text.TextRange){}[0]
@@ -10168,6 +10080,7 @@ final fun androidx.compose.ui.platform/androidx_compose_ui_platform_ValueElement
final fun androidx.compose.ui.platform/androidx_compose_ui_platform_ValueElementSequence$stableprop_getter(): kotlin/Int // androidx.compose.ui.platform/androidx_compose_ui_platform_ValueElementSequence$stableprop_getter|androidx_compose_ui_platform_ValueElementSequence$stableprop_getter(){}[0]
final fun androidx.compose.ui.platform/findComposeDefaultViewModelStoreOwner(androidx.compose.runtime/Composer?, kotlin/Int): androidx.lifecycle/ViewModelStoreOwner? // androidx.compose.ui.platform/findComposeDefaultViewModelStoreOwner|findComposeDefaultViewModelStoreOwner(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun androidx.compose.ui.platform/findDefaultNavigationEventDispatcherOwner(androidx.compose.runtime/Composer?, kotlin/Int): androidx.navigationevent/NavigationEventDispatcherOwner? // androidx.compose.ui.platform/findDefaultNavigationEventDispatcherOwner|findDefaultNavigationEventDispatcherOwner(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
final fun androidx.compose.ui.platform/platformClipboard(): androidx.compose.ui.platform/Clipboard // androidx.compose.ui.platform/platformClipboard|platformClipboard(){}[0]
final fun androidx.compose.ui.scene/androidx_compose_ui_scene_ComposeScenePointer$stableprop_getter(): kotlin/Int // androidx.compose.ui.scene/androidx_compose_ui_scene_ComposeScenePointer$stableprop_getter|androidx_compose_ui_scene_ComposeScenePointer$stableprop_getter(){}[0]
final fun androidx.compose.ui.semantics/androidx_compose_ui_semantics_AccessibilityAction$stableprop_getter(): kotlin/Int // androidx.compose.ui.semantics/androidx_compose_ui_semantics_AccessibilityAction$stableprop_getter|androidx_compose_ui_semantics_AccessibilityAction$stableprop_getter(){}[0]
final fun androidx.compose.ui.semantics/androidx_compose_ui_semantics_CollectionInfo$stableprop_getter(): kotlin/Int // androidx.compose.ui.semantics/androidx_compose_ui_semantics_CollectionInfo$stableprop_getter|androidx_compose_ui_semantics_CollectionInfo$stableprop_getter(){}[0]
@@ -10303,18 +10216,6 @@ final fun com.compose.sdl.element/com_compose_sdl_element_ClipModifier$stablepro
final fun com.compose.sdl.element/com_compose_sdl_element_ClipNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_ClipNode$stableprop_getter|com_compose_sdl_element_ClipNode$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerModifier$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerModifier$stableprop_getter|com_compose_sdl_element_GraphicsLayerModifier$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_GraphicsLayerNode$stableprop_getter|com_compose_sdl_element_GraphicsLayerNode$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_MiddleClickModifier$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_MiddleClickModifier$stableprop_getter|com_compose_sdl_element_MiddleClickModifier$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_MiddleClickNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_MiddleClickNode$stableprop_getter|com_compose_sdl_element_MiddleClickNode$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_OnDragModifier$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_OnDragModifier$stableprop_getter|com_compose_sdl_element_OnDragModifier$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_OnDragNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_OnDragNode$stableprop_getter|com_compose_sdl_element_OnDragNode$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_OnPressedModifier$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_OnPressedModifier$stableprop_getter|com_compose_sdl_element_OnPressedModifier$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_OnPressedNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_OnPressedNode$stableprop_getter|com_compose_sdl_element_OnPressedNode$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_OnTextInputModifier$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_OnTextInputModifier$stableprop_getter|com_compose_sdl_element_OnTextInputModifier$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_OnTextInputNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_OnTextInputNode$stableprop_getter|com_compose_sdl_element_OnTextInputNode$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_PressableModifier$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_PressableModifier$stableprop_getter|com_compose_sdl_element_PressableModifier$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_PressableNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_PressableNode$stableprop_getter|com_compose_sdl_element_PressableNode$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_SecondaryClickModifier$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_SecondaryClickModifier$stableprop_getter|com_compose_sdl_element_SecondaryClickModifier$stableprop_getter(){}[0]
final fun com.compose.sdl.element/com_compose_sdl_element_SecondaryClickNode$stableprop_getter(): kotlin/Int // com.compose.sdl.element/com_compose_sdl_element_SecondaryClickNode$stableprop_getter|com_compose_sdl_element_SecondaryClickNode$stableprop_getter(){}[0]
final fun com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_Close$stableprop_getter(): kotlin/Int // com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_Close$stableprop_getter|com_compose_sdl_graphics_PathCommand_Close$stableprop_getter(){}[0]
final fun com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_CubicTo$stableprop_getter(): kotlin/Int // com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_CubicTo$stableprop_getter|com_compose_sdl_graphics_PathCommand_CubicTo$stableprop_getter(){}[0]
final fun com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_LineTo$stableprop_getter(): kotlin/Int // com.compose.sdl.graphics/com_compose_sdl_graphics_PathCommand_LineTo$stableprop_getter|com_compose_sdl_graphics_PathCommand_LineTo$stableprop_getter(){}[0]
@@ -10325,6 +10226,7 @@ final fun com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectImageBitmap$s
final fun com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPaint$stableprop_getter(): kotlin/Int // com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPaint$stableprop_getter|com_compose_sdl_graphics_ProjectPaint$stableprop_getter(){}[0]
final fun com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPath$stableprop_getter(): kotlin/Int // com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPath$stableprop_getter|com_compose_sdl_graphics_ProjectPath$stableprop_getter(){}[0]
final fun com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPathIterator$stableprop_getter(): kotlin/Int // com.compose.sdl.graphics/com_compose_sdl_graphics_ProjectPathIterator$stableprop_getter|com_compose_sdl_graphics_ProjectPathIterator$stableprop_getter(){}[0]
final fun com.compose.sdl.graphics/decodeEncodedImageBitmap(kotlin/ByteArray): androidx.compose.ui.graphics/ImageBitmap? // com.compose.sdl.graphics/decodeEncodedImageBitmap|decodeEncodedImageBitmap(kotlin.ByteArray){}[0]
final fun com.compose.sdl.icons/com_compose_sdl_icons_IconFont$stableprop_getter(): kotlin/Int // com.compose.sdl.icons/com_compose_sdl_icons_IconFont$stableprop_getter|com_compose_sdl_icons_IconFont$stableprop_getter(){}[0]
final fun com.compose.sdl.input/com_compose_sdl_input_LegacyPointerEvent$stableprop_getter(): kotlin/Int // com.compose.sdl.input/com_compose_sdl_input_LegacyPointerEvent$stableprop_getter|com_compose_sdl_input_LegacyPointerEvent$stableprop_getter(){}[0]
final fun com.compose.sdl.node/com_compose_sdl_node_ComposeRootHost$stableprop_getter(): kotlin/Int // com.compose.sdl.node/com_compose_sdl_node_ComposeRootHost$stableprop_getter|com_compose_sdl_node_ComposeRootHost$stableprop_getter(){}[0]
@@ -10358,12 +10260,14 @@ final fun com.compose.sdl.window/com_compose_sdl_window_PopupHostState$stablepro
final fun com.compose.sdl.window/createPopupHostState(): com.compose.sdl.window/PopupHostState // com.compose.sdl.window/createPopupHostState|createPopupHostState(){}[0]
final fun com.compose.sdl/appDataDir(kotlin/String, kotlin/String): kotlin/String? // com.compose.sdl/appDataDir|appDataDir(kotlin.String;kotlin.String){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent$stableprop_getter|com_compose_sdl_AppEvent$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_Drop$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_Drop$stableprop_getter|com_compose_sdl_AppEvent_Drop$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_Key$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_Key$stableprop_getter|com_compose_sdl_AppEvent_Key$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_MouseWheel$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_MouseWheel$stableprop_getter|com_compose_sdl_AppEvent_MouseWheel$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_Pointer$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_Pointer$stableprop_getter|com_compose_sdl_AppEvent_Pointer$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_Quit$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_Quit$stableprop_getter|com_compose_sdl_AppEvent_Quit$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_RedrawNeeded$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_RedrawNeeded$stableprop_getter|com_compose_sdl_AppEvent_RedrawNeeded$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_TextInput$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_TextInput$stableprop_getter|com_compose_sdl_AppEvent_TextInput$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_WindowActivation$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_WindowActivation$stableprop_getter|com_compose_sdl_AppEvent_WindowActivation$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_WindowClose$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_WindowClose$stableprop_getter|com_compose_sdl_AppEvent_WindowClose$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_AppEvent_WindowResized$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_AppEvent_WindowResized$stableprop_getter|com_compose_sdl_AppEvent_WindowResized$stableprop_getter(){}[0]
final fun com.compose.sdl/com_compose_sdl_ComposeNativeWindow$stableprop_getter(): kotlin/Int // com.compose.sdl/com_compose_sdl_ComposeNativeWindow$stableprop_getter|com_compose_sdl_ComposeNativeWindow$stableprop_getter(){}[0]
+1 -1
View File
@@ -32,7 +32,7 @@ plugins {
val useSdl3Everywhere = (findProperty("renderer") as? String) == "sdl3"
// All targets pull headers from the in-repo static build tree at <repo>/libs,
// populated by scripts/build-sdl/build-all.sh (or the per-library scripts).
// populated by scripts/build-sdl/build-all.py (optionally one library at a time).
// Same paths on macOS / Linux / Windows — the .def files are pathless and
// rely on these -I injections. Cross-target cinterop indexing under
// kotlin.mpp.enableCInteropCommonization is fine: clang silently ignores -I
@@ -6,7 +6,7 @@ headerFilter = SDL3/**
# archive location comes from :ui build.gradle.kts (-libraryPath) because
# .def files can't reference $rootDir. The linker options below list the
# system frameworks / libs SDL3's static build depends on (see the Libs
# line in libs/SDL3/lib/pkgconfig/sdl3.pc after build-sdl3.sh runs) —
# line in libs/SDL3/lib/pkgconfig/sdl3.pc after build-all.py runs) —
# cinterop propagates them through the klib to the final app link.
staticLibraries = libSDL3.a
@@ -5,6 +5,6 @@ depends = sdl3
# libSDL3_image.a + its vendored codec archives (PNG/JPG built-in via stb,
# WEBP, SVG built-in) are all baked into the cinterop klib. See sdl3.def —
# the static archive location is injected via :ui build.gradle.kts
# (-libraryPath). build-sdl3-image.sh normalises libzlibstatic.a → libz.a
# on Windows so the same filename works everywhere.
# (-libraryPath). build-all.py's sdl3-image step normalises libzlibstatic.a
# → libz.a on Windows so the same filename works everywhere.
staticLibraries = libSDL3_image.a libpng16.a libz.a libwebp.a libwebpdemux.a libwebpmux.a libsharpyuv.a
+55 -7
View File
@@ -13,6 +13,31 @@ plugins {
// Skip mingwX64 on non-Windows hosts; see root build.gradle.kts.
val vHostSupportsMingw = rootProject.extra["vHostSupportsMingw"] as Boolean
// JVM parity target's Compose version — the dev build matching the pinned
// COMPOSE_CORE_REF (see scripts/compose-fork/compose.properties). material3
// rides its OWN release train: same +dev build number, different base
// version. Gradle orders "+dev" qualifiers BELOW the plain version, so
// currentOs's beta01 would win conflict resolution — force the core-repo
// groups on every jvm configuration. Umbrella-repo groups (desktop,
// components) are NOT forced: their dev numbering differs.
val vComposeJvmVersion = "1.12.0-beta01+dev4324"
val vComposeM3JvmVersion = "1.12.0-alpha03+dev4324"
val vComposeJvmForced = mapOf(
"org.jetbrains.compose.runtime" to vComposeJvmVersion,
"org.jetbrains.compose.ui" to vComposeJvmVersion,
"org.jetbrains.compose.foundation" to vComposeJvmVersion,
"org.jetbrains.compose.animation" to vComposeJvmVersion,
"org.jetbrains.compose.material" to vComposeJvmVersion,
"org.jetbrains.compose.material3" to vComposeM3JvmVersion,
)
configurations.configureEach {
if (name.startsWith("jvm")) {
resolutionStrategy.eachDependency {
vComposeJvmForced[requested.group]?.let { useVersion(it) }
}
}
}
kotlin {
jvm()
@@ -72,30 +97,36 @@ kotlin {
// substitute it for the vendored :components-resources below
// (the Maven artifact ships no mingwX64/linux klibs).
implementation(compose.components.resources)
// Common API on both stacks (its native/jvm actuals pick the
// right rendering pipeline) — usable from shared screens.
implementation(project(":material-symbols"))
}
}
nativeMain {
dependencies {
implementation(project(":window"))
implementation(project(":material3"))
implementation(project(":material-symbols"))
implementation(project(":navigation3-ui"))
implementation("androidx.lifecycle:lifecycle-viewmodel-navigation3:2.11.0")
}
}
jvmMain {
dependencies {
implementation("org.jetbrains.compose.runtime:runtime:1.12.0-alpha03")
implementation("org.jetbrains.compose.foundation:foundation:1.12.0-alpha03")
implementation("org.jetbrains.compose.animation:animation:1.12.0-alpha03")
implementation("org.jetbrains.compose.material3:material3:1.12.0-alpha03")
implementation("org.jetbrains.compose.ui:ui:1.12.0-alpha03")
// The +dev build matching scripts/compose-fork's pinned
// COMPOSE_CORE_REF — byte-exact parity with the vendored
// sources, and (unlike the published beta01) its desktop
// loadTypeface applies Font.variationSettings, so the
// Material Symbols variable-font axes work on JVM.
implementation("org.jetbrains.compose.runtime:runtime:$vComposeJvmVersion")
implementation("org.jetbrains.compose.foundation:foundation:$vComposeJvmVersion")
implementation("org.jetbrains.compose.animation:animation:$vComposeJvmVersion")
implementation("org.jetbrains.compose.material3:material3:$vComposeM3JvmVersion")
implementation("org.jetbrains.compose.ui:ui:$vComposeJvmVersion")
implementation("androidx.navigation3:navigation3-runtime:1.2.0-alpha05")
implementation("org.jetbrains.androidx.navigation3:navigation3-ui:1.2.0-alpha02")
implementation("androidx.lifecycle:lifecycle-viewmodel-navigation3:2.11.0")
implementation(compose.desktop.currentOs)
implementation("org.jetbrains.compose.material:material-icons-extended:1.7.3")
}
}
}
@@ -238,3 +269,20 @@ for (variant in variants) {
}
}
}
// ==================
// MARK: Stage Material Symbols fonts onto the JVM classpath
// ==================
// The :material-symbols JVM actual loads each style's font from the classpath
// at font/<Style>.ttf — the JVM analog of the data.kres font/ entries above.
// Same style detection: only the fonts the sources actually reference.
tasks.named<ProcessResources>("jvmProcessResources") {
val vSymbolsProject = rootProject.project(":material-symbols")
for (vStyle in vUsedStyles) {
@Suppress("UNCHECKED_CAST")
val vFontFile = vSymbolsProject.extra["iconFontFile$vStyle"] as Provider<RegularFile>
val vDownloadTask = vSymbolsProject.extra["iconFontDownloadTask$vStyle"] as TaskProvider<*>
from(vFontFile) { into("font") }
dependsOn(vDownloadTask)
}
}
@@ -60,6 +60,7 @@ val CoreScreens: List<DemoScreen> = listOf(
val Material3Screens: List<DemoScreen> = listOf(
// Ordered by family: typography, buttons, inputs, containers, navigation, overlays.
DemoScreen("Text") { TextScreen() },
DemoScreen("Icons") { IconsScreen() },
DemoScreen("Buttons") { ButtonsScreen() },
DemoScreen("ButtonsExtra") { M3ButtonsExtraScreen() },
DemoScreen("Fab") { FabScreen() },
@@ -86,10 +87,17 @@ val Navigation3Screens: List<DemoScreen> = listOf(
DemoScreen("Navigation3") { Navigation3Screen() },
)
// Screens that aren't a Compose-artifact showcase: project utility modules
// (Material Symbols engine, ...).
val OthersScreens: List<DemoScreen> = listOf(
DemoScreen("MaterialSymbols") { MaterialSymbolsScreen() },
)
val commonCategories: List<DemoCategory> = listOf(
DemoCategory("core", "Core", CoreScreens),
DemoCategory("material3", "Material3", Material3Screens),
DemoCategory("navigation3", "Navigation3", Navigation3Screens),
DemoCategory("others", "Others", OthersScreens),
)
/* Platform extras that fill the dropdown dynamically: screens that can't be
@@ -0,0 +1,26 @@
package demo.shim
import androidx.compose.ui.platform.ClipEntry
// ==================
// MARK: DemoClipboard — portable ClipEntry construction / reading
// ==================
/* Upstream Compose has no COMMON way to build or read a ClipEntry — every
platform actual differs. This port mirrors upstream's macOS actual
(ClipEntry.withPlainText / getPlainText / getImage), while upstream JVM
desktop wraps an AWT Transferable and exposes nothing text-typed. Shared
screens go through these four helpers; each stack maps them 1:1 onto its
native shape. */
/* A ClipEntry carrying plain text, for Clipboard.setClipEntry. */
expect fun demoClipEntryOfText(text: String): ClipEntry
/* A ClipEntry carrying an image from already-PNG-encoded bytes. */
expect fun demoClipEntryOfPng(pngBytes: ByteArray): ClipEntry
/* The entry's plain text, or null when it carries none. */
expect fun ClipEntry.demoPlainText(): String?
/* The entry's image as PNG-encoded bytes, or null when it carries none. */
expect fun ClipEntry.demoPngImage(): ByteArray?
@@ -5,16 +5,18 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.compose.sdl.icons.MaterialSymbols
import com.compose.sdl.icons.material.symbols.MaterialSymbolsOutlined
// ==================
// MARK: DemoIcon — platform-neutral icon token
// ==================
/* A platform-neutral icon reference for the shared demo screens. On native it
maps to a Material Symbols codepoint rendered by the project icon-font engine
(com.compose.sdl.icons); on a future jvm target it maps to an
androidx.compose.material.icons ImageVector. Shared screens reference icons
ONLY through this enum, so the exact same code compiles on both stacks. */
/* A platform-neutral icon reference for the shared demo screens. Now that
:material-symbols exposes its API from commonMain (native draws through the
port's IconFont pipeline, JVM through upstream variable-font text), the
token resolves to a Material Symbols codepoint on BOTH stacks — the same
glyphs render everywhere, which is exactly what the parity build compares. */
enum class DemoIcon {
Home, Search, Person, Settings, Add, Edit, Favorite, Star, Close, Image,
Delete, Share, Check, MoreVert, ContentCopy, KeyboardArrowDown, Bookmark,
@@ -22,15 +24,50 @@ enum class DemoIcon {
ExpandMore, ExpandLess,
}
/* Renders [icon]. The portable stand-in for material3's Icon(imageVector, …) /
the project's MaterialSymbolsOutlined(codepoint, …). Deliberately exposes no
variable-font axes (fill / weight / grade / opticalSize) — those have no
upstream analog. */
/* Renders [icon] via MaterialSymbolsOutlined — the portable stand-in for
material3's Icon(imageVector, …). Color.Unspecified tint falls back to
material3's LocalContentColor inside MaterialSymbolsOutlined. */
@Composable
expect fun DemoIcon(
fun DemoIcon(
icon: DemoIcon,
contentDescription: String? = null,
modifier: Modifier = Modifier,
tint: Color = Color.Unspecified,
size: Dp = 24.dp,
)
) {
val codepoint = when (icon) {
DemoIcon.Home -> MaterialSymbols.Home
DemoIcon.Search -> MaterialSymbols.Search
DemoIcon.Person -> MaterialSymbols.Person
DemoIcon.Settings -> MaterialSymbols.Settings
DemoIcon.Add -> MaterialSymbols.Add
DemoIcon.Edit -> MaterialSymbols.Edit
DemoIcon.Favorite -> MaterialSymbols.Favorite
DemoIcon.Star -> MaterialSymbols.Star
DemoIcon.Close -> MaterialSymbols.Close
DemoIcon.Image -> MaterialSymbols.Image
DemoIcon.Delete -> MaterialSymbols.Delete
DemoIcon.Share -> MaterialSymbols.Share
DemoIcon.Check -> MaterialSymbols.Check
DemoIcon.MoreVert -> MaterialSymbols.MoreVert
DemoIcon.ContentCopy -> MaterialSymbols.ContentCopy
DemoIcon.KeyboardArrowDown -> MaterialSymbols.KeyboardArrowDown
DemoIcon.Bookmark -> MaterialSymbols.Bookmark
DemoIcon.Notifications -> MaterialSymbols.Notifications
DemoIcon.Menu -> MaterialSymbols.Menu
DemoIcon.ArrowBack -> MaterialSymbols.ArrowBack
DemoIcon.Save -> MaterialSymbols.Save
DemoIcon.Refresh -> MaterialSymbols.Refresh
DemoIcon.Download -> MaterialSymbols.Download
DemoIcon.Upload -> MaterialSymbols.Upload
DemoIcon.ExpandMore -> MaterialSymbols.ExpandMore
DemoIcon.ExpandLess -> MaterialSymbols.ExpandLess
}
MaterialSymbolsOutlined(
icon = codepoint,
contentDescription = contentDescription,
modifier = modifier,
tint = tint,
size = size,
)
}
@@ -21,12 +21,11 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.Clipboard
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import demo.shim.demoDecodeImage
import demo.shim.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.readResourceBytes
@@ -43,9 +42,10 @@ private const val kLogoResourcePath = "composeResources/demo.generated.resources
// ==================
/* Round-trips text AND PNG images through the OS clipboard via LocalClipboard
(suspend, ClipEntry-typed) over SDL3's SDL_[GS]etClipboardText and
SDL_[GS]etClipboardData("image/png"). Text and images share one Compose
ClipEntry: getPlainText() vs getImage() surface whichever is present.
(suspend, ClipEntry-typed) — on native over SDL3's SDL_[GS]etClipboardText /
SDL_[GS]etClipboardData("image/png"), on JVM over the AWT clipboard.
ClipEntry construction/reading has NO common upstream API (each platform
actual differs), so this screen goes through demo.shim.DemoClipboard.
Non-composable callers reach it by capturing LocalClipboard.current +
rememberCoroutineScope() in composition and passing both into the callback. */
@Composable
@@ -68,8 +68,9 @@ internal fun ClipboardScreen() {
)
// ============
// Text copy — ClipEntry.withPlainText / getPlainText.
Section("Copy text", "scope.launch { clipboard.setClipEntry(ClipEntry.withPlainText(...)) }") {
// Text copy — demoClipEntryOfText (ClipEntry.withPlainText on native,
// AWT StringSelection on JVM).
Section("Copy text", "scope.launch { clipboard.setClipEntry(demoClipEntryOfText(...)) }") {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
OutlinedTextField(
value = draft,
@@ -78,7 +79,7 @@ internal fun ClipboardScreen() {
modifier = Modifier.fillMaxWidth(),
)
Button(onClick = {
scope.launch { clipboard.setClipEntry(ClipEntry.withPlainText(draft)) }
scope.launch { clipboard.setClipEntry(demoClipEntryOfText(draft)) }
note = "Copied ${draft.length} char(s) to the system clipboard."
}) {
Text("Copy text", color = MaterialTheme.colorScheme.onPrimary)
@@ -92,7 +93,7 @@ internal fun ClipboardScreen() {
// image-aware app (Preview / Paint / a browser tab) to verify.
Section(
"Copy image",
"readResourceBytes(\"drawable/compose_logo.png\") → ClipEntry.withImage(pngBytes). " +
"readResourceBytes(\"drawable/compose_logo.png\") → demoClipEntryOfPng(pngBytes). " +
"Paste into another app to verify.",
) {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
@@ -115,7 +116,7 @@ internal fun ClipboardScreen() {
scope.launch {
val vBytes = runCatching { readResourceBytes(kLogoResourcePath) }.getOrNull()
if (vBytes != null) {
clipboard.setClipEntry(ClipEntry.withImage(vBytes))
clipboard.setClipEntry(demoClipEntryOfPng(vBytes))
note = "Copied ${vBytes.size} byte(s) of PNG to the system clipboard."
} else {
note = "Failed to load the source image."
@@ -130,15 +131,15 @@ internal fun ClipboardScreen() {
// ============
// Paste — surfaces whatever's on the clipboard: text OR image.
Section("Paste", "clipboard.getClipEntry()?.getPlainText() / .getImage()") {
Section("Paste", "clipboard.getClipEntry()?.demoPlainText() / .demoPngImage()") {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Button(onClick = {
pastedText = null; pastedImage = null; pastedNote = null
scope.launch {
val vEntry = clipboard.getClipEntry()
val vImage = vEntry?.getImage()
val vText = vEntry?.getPlainText()
val vImage = vEntry?.demoPngImage()
val vText = vEntry?.demoPlainText()
when {
vImage != null -> {
pastedImage = demoDecodeImage(vImage)
@@ -212,6 +213,6 @@ internal fun ClipboardScreen() {
modern Clipboard is intentionally suspend-only. */
private fun copyFromPlainFunction(scope: CoroutineScope, clipboard: Clipboard) {
scope.launch {
clipboard.setClipEntry(ClipEntry.withPlainText("Set from a plain, non-@Composable function."))
clipboard.setClipEntry(demoClipEntryOfText("Set from a plain, non-@Composable function."))
}
}
@@ -0,0 +1,58 @@
package screens
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.compose.sdl.icons.MaterialSymbols
import com.compose.sdl.icons.material.symbols.MaterialSymbolsOutlined
import com.compose.sdl.icons.material.symbols.MaterialSymbolsRounded
import com.compose.sdl.icons.material.symbols.MaterialSymbolsSharp
import demo.generated.resources.Res
import demo.generated.resources.heart
import org.jetbrains.compose.resources.painterResource
// ==================
// MARK: Icons screen — material3 icon APIs
// ==================
/* material3 icon APIs: Icon(painter) over compose resources and IconButton. */
@Composable
internal fun IconsScreen() {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
ScreenTitle(
"Icons",
"material3 Icon(painter) over compose resources + IconButton.",
)
Section("Painter-based Icon", "Reads from composeResources/drawable/*.xml (Android vector) or *.svg.") {
Row(verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Icon(painter = painterResource(Res.drawable.heart), contentDescription = "Heart")
Text("painterResource(Res.drawable.heart)", fontSize = 13.sp)
}
}
Section("IconButton", "40dp clickable circle. Hover overlay matches Material 1.") {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
IconButton(onClick = {}) {
MaterialSymbolsOutlined(MaterialSymbols.Favorite,
tint = MaterialTheme.colorScheme.primary, fill = 1f, weight = 600)
}
IconButton(onClick = {}) {
MaterialSymbolsRounded(MaterialSymbols.Share,
tint = MaterialTheme.colorScheme.onSurface)
}
IconButton(onClick = {}) {
MaterialSymbolsSharp(MaterialSymbols.MoreVert,
tint = MaterialTheme.colorScheme.onSurface)
}
}
}
}
}
@@ -1,11 +1,14 @@
package screens
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -14,22 +17,29 @@ import com.compose.sdl.icons.MaterialSymbols
import com.compose.sdl.icons.material.symbols.MaterialSymbolsOutlined
import com.compose.sdl.icons.material.symbols.MaterialSymbolsRounded
import com.compose.sdl.icons.material.symbols.MaterialSymbolsSharp
import demo.generated.resources.Res
import demo.generated.resources.heart
import org.jetbrains.compose.resources.painterResource
// ==================
// MARK: Material Symbols screen — variable-font icon engine showcase
// ==================
/* The :material-symbols engine demos: the four variable-font axes (FILL /
wght / GRAD / opsz), the animated fill transition, and the three style
families. Runs the SAME shared code on native (IconFont over SDL3_ttf-fork
/ Skia) and JVM (Skiko direct) — differences between the builds = port
bugs. */
@Composable
internal fun IconsScreen() {
internal fun MaterialSymbolsScreen() {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
ScreenTitle(
"Icons",
"MaterialSymbolsOutlined / Rounded / Sharp — drop the dependency, call the composable, font auto-installs.",
"Material Symbols",
"MaterialSymbolsOutlined / Rounded / Sharp — drop the dependency, call the composable, " +
"font auto-installs. Four variable-font axes as direct parameters.",
)
// ============
// Variable-font axes — the priority demo. The four axes are direct
// parameters on the icon composable, so each row pins everything
// except one. Skia honours every axis; SDL3_ttf 3.2 ignores them.
// Variable-font axes — each row pins everything except one axis.
// Skia, the SDL3_ttf fork (TTF_SetFontAxisValue) and the JVM build
// (Skiko Typeface.makeClone) all honour the axes.
Section(
"Weight (wght 100..700)",
@@ -75,6 +85,24 @@ internal fun IconsScreen() {
}
}
// ============
// Animated fill — the modern M3 selected-state icon transition:
// the FILL axis (and tint) animate on click instead of swapping
// between two static icons.
Section(
"Animated fill",
"Click an icon: animateFloatAsState drives the FILL axis (plus tint) — " +
"the Material 3 selected-state transition, morphing the glyph instead of swapping it.",
) {
Row(verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(20.dp)) {
AnimatedFillIcon(MaterialSymbols.Favorite, "Favorite")
AnimatedFillIcon(MaterialSymbols.Star, "Star")
AnimatedFillIcon(MaterialSymbols.Bookmark, "Bookmark")
AnimatedFillIcon(MaterialSymbols.Notifications, "Notifications")
}
}
Section(
"Grade (GRAD -25..200)",
"Fine weight nudge without changing the glyph footprint. Negative = thinner, positive = heavier.",
@@ -157,15 +185,7 @@ internal fun IconsScreen() {
}
// ============
// Style families + drawable icons.
Section("Painter-based Icon", "Reads from composeResources/drawable/*.xml (Android vector) or *.svg.") {
Row(verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Icon(painter = painterResource(Res.drawable.heart), contentDescription = "Heart")
Text("painterResource(Res.drawable.heart)", fontSize = 13.sp)
}
}
// Style families.
val vNames = listOf(
"Home" to MaterialSymbols.Home,
@@ -246,22 +266,31 @@ internal fun IconsScreen() {
}
}
}
}
}
Section("IconButton", "40dp clickable circle. Hover overlay matches Material 1.") {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
IconButton(onClick = {}) {
MaterialSymbolsOutlined(MaterialSymbols.Favorite,
tint = MaterialTheme.colorScheme.primary, fill = 1f, weight = 600)
}
IconButton(onClick = {}) {
MaterialSymbolsRounded(MaterialSymbols.Share,
tint = MaterialTheme.colorScheme.onSurface)
}
IconButton(onClick = {}) {
MaterialSymbolsSharp(MaterialSymbols.MoreVert,
tint = MaterialTheme.colorScheme.onSurface)
}
}
/* One click-toggled icon: FILL 0↔1 and tint animate together — swap-free
selected-state feedback, the Material 3 expressive icon pattern. */
@Composable
private fun AnimatedFillIcon(codepoint: Int, label: String) {
var vSelected by remember { mutableStateOf(false) }
val vFill by animateFloatAsState(
targetValue = if (vSelected) 1f else 0f,
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
label = "fill",
)
val vTint by animateColorAsState(
targetValue = if (vSelected) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant,
animationSpec = tween(durationMillis = 300),
label = "tint",
)
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(2.dp)) {
IconButton(onClick = { vSelected = !vSelected }) {
MaterialSymbolsOutlined(codepoint, size = 28.dp, tint = vTint, fill = vFill)
}
Text(label, fontSize = 11.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f))
}
}
@@ -0,0 +1,56 @@
@file:OptIn(ExperimentalComposeUiApi::class)
package demo.shim
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.asAwtTransferable
import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection
import java.awt.datatransfer.Transferable
import java.awt.datatransfer.UnsupportedFlavorException
import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
/* JVM (Compose Desktop) actuals: upstream's desktop ClipEntry wraps an AWT
Transferable — text rides DataFlavor.stringFlavor, images ride
DataFlavor.imageFlavor as a decoded java.awt.Image (PNG bytes are
en/decoded with ImageIO at the edges). */
actual fun demoClipEntryOfText(text: String): ClipEntry = ClipEntry(StringSelection(text))
actual fun demoClipEntryOfPng(pngBytes: ByteArray): ClipEntry = ClipEntry(PngTransferable(pngBytes))
actual fun ClipEntry.demoPlainText(): String? {
val transferable = asAwtTransferable ?: return null
if (!transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) return null
return runCatching { transferable.getTransferData(DataFlavor.stringFlavor) as? String }.getOrNull()
}
actual fun ClipEntry.demoPngImage(): ByteArray? {
val transferable = asAwtTransferable ?: return null
if (!transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) return null
val image = runCatching { transferable.getTransferData(DataFlavor.imageFlavor) as? java.awt.Image }
.getOrNull() ?: return null
val buffered = image as? BufferedImage ?: BufferedImage(
image.getWidth(null).coerceAtLeast(1),
image.getHeight(null).coerceAtLeast(1),
BufferedImage.TYPE_INT_ARGB,
).also { it.createGraphics().apply { drawImage(image, 0, 0, null); dispose() } }
val out = ByteArrayOutputStream()
val ok = runCatching { ImageIO.write(buffered, "png", out) }.getOrDefault(false)
return if (ok) out.toByteArray() else null
}
/* Serves PNG bytes as DataFlavor.imageFlavor — AWT's canonical image exchange
format, so pasting into other desktop apps works. */
private class PngTransferable(private val pngBytes: ByteArray) : Transferable {
override fun getTransferDataFlavors(): Array<DataFlavor> = arrayOf(DataFlavor.imageFlavor)
override fun isDataFlavorSupported(flavor: DataFlavor?): Boolean = flavor == DataFlavor.imageFlavor
override fun getTransferData(flavor: DataFlavor?): Any =
if (flavor == DataFlavor.imageFlavor)
ImageIO.read(ByteArrayInputStream(pngBytes)) ?: throw UnsupportedFlavorException(flavor)
else throw UnsupportedFlavorException(flavor)
}
@@ -1,15 +1,19 @@
@file:OptIn(ExperimentalComposeUiApi::class)
package demo.shim
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.draganddrop.DragAndDropEvent
import androidx.compose.ui.draganddrop.awtTransferable
import java.awt.datatransfer.DataFlavor
import java.io.File
/* JVM (Compose Desktop) actual: DragAndDropEvent wraps an AWT
* java.awt.dnd.DropTargetDropEvent whose transferable exposes the payload
* through the standard AWT flavor system. */
/* JVM (Compose Desktop) actual: DragAndDropEvent exposes the payload through
* the AWT flavor system via the awtTransferable extension (throws when the
* event carries no transferable — hence the runCatching). */
actual fun DragAndDropEvent.demoReadFilePaths(): List<String> {
val transferable = runCatching { nativeEvent.transferable }.getOrNull() ?: return emptyList()
val transferable = runCatching { awtTransferable }.getOrNull() ?: return emptyList()
if (!transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) return emptyList()
@Suppress("UNCHECKED_CAST")
val files = transferable.getTransferData(DataFlavor.javaFileListFlavor) as? List<File> ?: return emptyList()
@@ -17,7 +21,7 @@ actual fun DragAndDropEvent.demoReadFilePaths(): List<String> {
}
actual fun DragAndDropEvent.demoReadText(): String? {
val transferable = runCatching { nativeEvent.transferable }.getOrNull() ?: return null
val transferable = runCatching { awtTransferable }.getOrNull() ?: return null
if (!transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) return null
return runCatching { transferable.getTransferData(DataFlavor.stringFlavor) as? String }.getOrNull()
}
@@ -1,60 +0,0 @@
package demo.shim
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
// JVM actual: map the token to an upstream Material Icons vector (material-icons
// -extended) and draw it with material3's Icon — the direct counterpart to the
// native actual's MaterialSymbols codepoint + font glyph.
@Composable
actual fun DemoIcon(
icon: DemoIcon,
contentDescription: String?,
modifier: Modifier,
tint: Color,
size: Dp,
) {
val vector: ImageVector = when (icon) {
DemoIcon.Home -> Icons.Filled.Home
DemoIcon.Search -> Icons.Filled.Search
DemoIcon.Person -> Icons.Filled.Person
DemoIcon.Settings -> Icons.Filled.Settings
DemoIcon.Add -> Icons.Filled.Add
DemoIcon.Edit -> Icons.Filled.Edit
DemoIcon.Favorite -> Icons.Filled.Favorite
DemoIcon.Star -> Icons.Filled.Star
DemoIcon.Close -> Icons.Filled.Close
DemoIcon.Image -> Icons.Filled.Image
DemoIcon.Delete -> Icons.Filled.Delete
DemoIcon.Share -> Icons.Filled.Share
DemoIcon.Check -> Icons.Filled.Check
DemoIcon.MoreVert -> Icons.Filled.MoreVert
DemoIcon.ContentCopy -> Icons.Filled.ContentCopy
DemoIcon.KeyboardArrowDown -> Icons.Filled.KeyboardArrowDown
DemoIcon.Bookmark -> Icons.Filled.Bookmark
DemoIcon.Notifications -> Icons.Filled.Notifications
DemoIcon.Menu -> Icons.Filled.Menu
DemoIcon.ArrowBack -> Icons.Filled.ArrowBack
DemoIcon.Save -> Icons.Filled.Save
DemoIcon.Refresh -> Icons.Filled.Refresh
DemoIcon.Download -> Icons.Filled.Download
DemoIcon.Upload -> Icons.Filled.Upload
DemoIcon.ExpandMore -> Icons.Filled.ExpandMore
DemoIcon.ExpandLess -> Icons.Filled.ExpandLess
}
Icon(
imageVector = vector,
contentDescription = contentDescription,
modifier = modifier.size(size),
tint = if (tint.isSpecified) tint else LocalContentColor.current,
)
}
@@ -1,8 +1,8 @@
package demo.shim
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import org.jetbrains.skia.Image
import org.jetbrains.skiko.toComposeImageBitmap
actual fun demoDecodeImage(bytes: ByteArray): ImageBitmap? =
runCatching { Image.makeFromEncoded(bytes).toComposeImageBitmap() }.getOrNull()
@@ -17,11 +17,6 @@ actual fun getPlatformCategories(): List<DemoCategory> = listOf(
// IconFontIcon / IconText glyph engine + desktop scrollbars — no upstream analog.
DemoScreen("FoundationExtra") { FoundationExtraScreen() },
)),
DemoCategory("material3", "Material3", listOf(
// Icons demos the variable-font axes + Outlined/Rounded/Sharp families — project-only,
// no upstream analog, so it can't be common.
DemoScreen("Icons") { IconsScreen() },
)),
DemoCategory("native", "Native", listOf(
DemoScreen("Window") { WindowScreen() },
DemoScreen("Dispatchers") { DispatchersScreen() },
@@ -0,0 +1,15 @@
package demo.shim
import androidx.compose.ui.platform.ClipEntry
/* Native actuals: 1:1 onto the port's ClipEntry, which mirrors upstream's
macOS actual (withPlainText / getPlainText) extended with the PNG image
path (withImage / getImage over SDL_[GS]etClipboardData "image/png"). */
actual fun demoClipEntryOfText(text: String): ClipEntry = ClipEntry.withPlainText(text)
actual fun demoClipEntryOfPng(pngBytes: ByteArray): ClipEntry = ClipEntry.withImage(pngBytes)
actual fun ClipEntry.demoPlainText(): String? = getPlainText()
actual fun ClipEntry.demoPngImage(): ByteArray? = getImage()
@@ -1,55 +0,0 @@
package demo.shim
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.unit.Dp
import com.compose.sdl.icons.MaterialSymbols
import com.compose.sdl.icons.material.symbols.MaterialSymbolsOutlined
// Native actual: resolve the token to a Material Symbols codepoint and draw it
// through the Outlined variable font (SDL3_ttf / FreeType or Skia).
@Composable
actual fun DemoIcon(
icon: DemoIcon,
contentDescription: String?,
modifier: Modifier,
tint: Color,
size: Dp,
) {
val codepoint = when (icon) {
DemoIcon.Home -> MaterialSymbols.Home
DemoIcon.Search -> MaterialSymbols.Search
DemoIcon.Person -> MaterialSymbols.Person
DemoIcon.Settings -> MaterialSymbols.Settings
DemoIcon.Add -> MaterialSymbols.Add
DemoIcon.Edit -> MaterialSymbols.Edit
DemoIcon.Favorite -> MaterialSymbols.Favorite
DemoIcon.Star -> MaterialSymbols.Star
DemoIcon.Close -> MaterialSymbols.Close
DemoIcon.Image -> MaterialSymbols.Image
DemoIcon.Delete -> MaterialSymbols.Delete
DemoIcon.Share -> MaterialSymbols.Share
DemoIcon.Check -> MaterialSymbols.Check
DemoIcon.MoreVert -> MaterialSymbols.MoreVert
DemoIcon.ContentCopy -> MaterialSymbols.ContentCopy
DemoIcon.KeyboardArrowDown -> MaterialSymbols.KeyboardArrowDown
DemoIcon.Bookmark -> MaterialSymbols.Bookmark
DemoIcon.Notifications -> MaterialSymbols.Notifications
DemoIcon.Menu -> MaterialSymbols.Menu
DemoIcon.ArrowBack -> MaterialSymbols.ArrowBack
DemoIcon.Save -> MaterialSymbols.Save
DemoIcon.Refresh -> MaterialSymbols.Refresh
DemoIcon.Download -> MaterialSymbols.Download
DemoIcon.Upload -> MaterialSymbols.Upload
DemoIcon.ExpandMore -> MaterialSymbols.ExpandMore
DemoIcon.ExpandLess -> MaterialSymbols.ExpandLess
}
// Color.Unspecified → let MaterialSymbolsOutlined fall back to LocalContentColor.
if (tint.isSpecified) {
MaterialSymbolsOutlined(codepoint, contentDescription, modifier, tint = tint, size = size)
} else {
MaterialSymbolsOutlined(codepoint, contentDescription, modifier, size = size)
}
}
-180
View File
@@ -1,180 +0,0 @@
#!/usr/bin/env bash
# Shared helpers for scripts/build-sdl/*.sh — host OS detection, path
# translation for CMake, Ninja discovery, and toolchain wiring. Each build
# script sources this file so it can drive the same CMake configure/build/
# install flow on macOS, Linux, and Windows without conditionals sprinkled
# through the script bodies.
#
# Assumes on every host: git, cmake in PATH. Ninja is fetched into
# libs/.build/ninja-bin/ when absent. On Windows the fetch also uses
# python + curl (Git Bash defaults).
# detect_host — normalises `uname -s` into macos / linux / windows.
detect_host() {
case "$(uname -s)" in
Darwin) echo "macos" ;;
Linux) echo "linux" ;;
MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
*) echo "unknown" ;;
esac
}
# cmake_path <path> — return `path` in the form CMake expects for the current
# host. On Windows CMake is a native .exe that wants Windows-style paths;
# elsewhere plain Unix paths work.
cmake_path() {
if [ "${BUILD_SDL_HOST:-}" = "windows" ]; then
cygpath -m "$1"
else
printf '%s' "$1"
fi
}
# find_ninja <build_dir> — echo the CMake-visible path to a ninja binary. If
# no ninja is on PATH, fetches the platform-appropriate release binary once
# into <build_dir>/ninja-bin/.
find_ninja() {
local build_dir="$1"
if command -v ninja >/dev/null 2>&1; then
echo "ninja"
return
fi
local host="${BUILD_SDL_HOST:-$(detect_host)}"
local dir="$build_dir/ninja-bin"
mkdir -p "$dir"
if [ ! -x "$dir/ninja" ] && [ ! -f "$dir/ninja.exe" ]; then
echo ">> fetching ninja for $host" >&2
local url
case "$host" in
macos) url="https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip" ;;
linux) url="https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip" ;;
windows) url="https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip" ;;
*) echo "ERROR: no ninja binary for host $host" >&2; return 1 ;;
esac
curl -fL -o "$dir/ninja.zip" "$url"
if [ "$host" = "windows" ]; then
python -c "import zipfile,sys;zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])" \
"$dir/ninja.zip" "$dir"
else
(cd "$dir" && unzip -q -o ninja.zip)
chmod +x "$dir/ninja"
fi
fi
if [ -f "$dir/ninja.exe" ]; then
cmake_path "$dir/ninja.exe"
else
echo "$dir/ninja"
fi
}
# setup_toolchain — export CC / CXX so CMake picks up the right compiler for
# static K/N-compatible builds:
# * macOS → clang / clang++ from Xcode command line tools
# * Linux → system gcc / g++
# * Windows → K/N's own bundled mingw-w64 gcc (matches its CRT) for C, and
# the SYSTEM g++ for C++ (K/N's mingw is C-only). Set KN_MINGW=0
# to force the system gcc if the K/N one is unavailable.
setup_toolchain() {
local host="${BUILD_SDL_HOST:-$(detect_host)}"
case "$host" in
macos)
export CC="${CC:-clang}"
export CXX="${CXX:-clang++}"
;;
linux)
export CC="${CC:-gcc}"
export CXX="${CXX:-g++}"
;;
windows)
if [ -z "${CXX:-}" ]; then
local sys_gxx
sys_gxx="$(command -v g++ 2>/dev/null || true)"
[ -f "${sys_gxx}.exe" ] && sys_gxx="${sys_gxx}.exe"
if [ -n "$sys_gxx" ]; then
export CXX="$(cygpath -m "$sys_gxx")"
echo ">> C++ compiler (K/N mingw is C-only): $CXX" >&2
else
echo ">> WARNING: no system g++ on PATH; SDL3's CMake configure needs a working" >&2
echo ">> C++ compiler and will fail. Install mingw-w64 g++." >&2
fi
fi
if [ "${KN_MINGW:-1}" != "0" ]; then
local kn_bin
kn_bin="$(ls -d "${KONAN_DATA_DIR:-$HOME/.konan}"/dependencies/*mingw*/bin 2>/dev/null | head -1 || true)"
if [ -n "$kn_bin" ] && [ -x "$kn_bin/gcc.exe" ]; then
export PATH="$kn_bin:$PATH"
echo ">> using Kotlin/Native bundled mingw: $(gcc --version | head -1)" >&2
else
echo ">> WARNING: K/N bundled mingw not found; using system gcc." >&2
fi
fi
export CC="${CC:-gcc}"
;;
*)
echo "ERROR: unsupported host: $host" >&2
exit 1
;;
esac
}
# ==================
# MARK: Manifest — build-sdl.properties
# ==================
# One file at scripts/build-sdl/build-sdl.properties defines the URL + ref for
# every static library the build scripts pull. Format is `KEY=value`, with
# `KEY_<host>=value` per-host overrides (host = macos / linux / windows).
#
# manifest_value <KEY> — echo the winning value, honoring:
# 1. Environment variable of that name (highest priority)
# 2. KEY_<host> in build-sdl.properties (only when host matches)
# 3. KEY in build-sdl.properties (base)
# Prints nothing if none set — pair with require_manifest to make the key
# mandatory. MANIFEST_FILE can be pointed at a different file for testing.
MANIFEST_FILE="${MANIFEST_FILE:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/build-sdl.properties}"
manifest_value() {
local vKey="$1"
local vEnv
eval "vEnv=\${$vKey:-}"
if [ -n "$vEnv" ]; then
printf '%s\n' "$vEnv"
return
fi
[ -f "$MANIFEST_FILE" ] || return 0
local vHost="${BUILD_SDL_HOST:-$(detect_host)}"
local vHostVal
vHostVal="$(sed -n "s/^${vKey}_${vHost}=//p" "$MANIFEST_FILE" | head -1)"
if [ -n "$vHostVal" ]; then
printf '%s\n' "$vHostVal"
return
fi
sed -n "s/^${vKey}=//p" "$MANIFEST_FILE" | head -1
}
# require_manifest <KEY> — echo manifest_value, or fail with a clear error if
# the key resolves to an empty string (no env var, no override, no base).
require_manifest() {
local vKey="$1"
local vVal
vVal="$(manifest_value "$vKey")"
if [ -z "$vVal" ]; then
echo "ERROR: $vKey not defined — add it to $MANIFEST_FILE or export it as an env var" >&2
exit 1
fi
printf '%s\n' "$vVal"
}
# extra_cmake_args — echo host-specific extra CMake arguments (e.g. macOS
# arch pin). Meant to be interpolated unquoted into a cmake command line.
extra_cmake_args() {
local host="${BUILD_SDL_HOST:-$(detect_host)}"
case "$host" in
macos)
# Port targets macosArm64 only. Force the arch so a universal-
# arch host build stays trimmed and matches the K/N target.
echo "-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0"
;;
*) : ;;
esac
}
+592
View File
@@ -0,0 +1,592 @@
#!/usr/bin/env python3
"""
One-shot dependency setup — builds every native dependency this project links
into the executable as a STATIC library, installing each into <repo>/libs/.
Runs on macOS, Linux, and Windows with plain Python 3 — no Git Bash, curl or
cygpath needed. Replaces the former build-*.sh scripts.
Build order matters — later libraries link the earlier ones:
1. freetype -> libs/FreeType/lib/libfreetype.a
2. sdl3 -> libs/SDL3/lib/libSDL3.a
3. sdl3-image -> libs/SDL3_image (vendored PNG/JPG/SVG/WEBP; needs SDL3)
4. sdl3-ttf -> libs/SDL3_ttf (variable-font-axes patch; needs SDL3 + FreeType)
Usage:
python3 build-all.py build everything, in order
python3 build-all.py sdl3 sdl3-ttf build a subset (canonical order enforced)
Everything is static + HarfBuzz/plutosvg-free, so the app links to a clean
<app> + data.kres with no runtime .dylib / .so / .dll alongside.
Requires on every host: git, cmake, python 3. Ninja is fetched into
libs/.build/ninja-bin when absent. Windows also needs a mingw-w64 g++ on PATH
(for SDL3's GameInput backend; K/N's bundled mingw is C-only). URLs + refs for
each library come from build-sdl.properties; edit it to change versions or
repos, or set the corresponding env vars (SDL_URL / SDL_REF / SDL_IMAGE_URL /
SDL_IMAGE_REF / SDL_TTF_URL / SDL_TTF_REF / FREETYPE_URL / FREETYPE_TAG) for a
one-off override.
"""
import argparse
import os
import platform
import shutil
import stat
import subprocess
import sys
import urllib.request
import zipfile
from pathlib import Path
# ==================
# MARK: Constants
# ==================
kToolsDir = Path(__file__).resolve().parent
kRepoRoot = kToolsDir.parent.parent
kLibsDir = kRepoRoot / "libs"
kBuildOrder = ["freetype", "sdl3", "sdl3-image", "sdl3-ttf"]
# Compile flags shared by every library: size-optimised and sectioned so the
# final link's --gc-sections can drop every function the app never calls.
kSizeFlags = "-ffunction-sections -fdata-sections -Os"
kNinjaVersion = "1.12.1"
kNinjaZipByHost = {
"macos": "ninja-mac.zip",
"linux": "ninja-linux.zip",
"windows": "ninja-win.zip",
}
# Windows: mingw-w64's <setjmp.h> emits external references to
# __intrinsic_setjmpex from any TU using setjmp() (freetype, libpng, ...).
# K/N's LLD sysroot for mingwX64 doesn't provide this symbol and the LLD
# built by K/N 2.4.0 doesn't accept --defsym as a linker workaround, so a
# tiny stub aliases the symbol to msvcrt's _setjmpex (see bakeSetjmpStub).
kSetjmpStubSource = """#include <setjmp.h>
extern int __cdecl _setjmpex(jmp_buf _Buf, void *_Ctx);
/** Alias mingw-w64's __intrinsic_setjmpex onto msvcrt's _setjmpex. */
int __cdecl __intrinsic_setjmpex(jmp_buf _Buf, void *_Ctx) {
return _setjmpex(_Buf, _Ctx);
}
"""
# ==================
# MARK: Helpers
# ==================
def detectHost():
"""Normalise the running platform into macos / linux / windows / unknown.
BUILD_SDL_HOST in the environment overrides detection (testing hook)."""
vOverride = os.environ.get("BUILD_SDL_HOST", "")
if vOverride:
return vOverride
vSystem = platform.system()
if vSystem == "Darwin":
return "macos"
if vSystem == "Linux":
return "linux"
if vSystem == "Windows" or vSystem.startswith(("MINGW", "MSYS", "CYGWIN")):
return "windows"
return "unknown"
def run(inArgs, inCwd=None):
"""Echo and run a command, aborting the whole build on a non-zero exit."""
vArgs = [str(vArg) for vArg in inArgs]
print(">> $ " + " ".join(vArgs), flush=True)
subprocess.run(vArgs, cwd=inCwd, check=True)
def forceRmtree(inPath):
"""rm -rf equivalent that clears read-only bits before retrying — git
object files in cloned trees are read-only on Windows and would make a
plain shutil.rmtree fail."""
vPath = Path(inPath)
if not vPath.exists():
return
def clearAndRetry(inFunc, inTarget, inExcInfo):
"""onerror/onexc hook: make the entry writable and retry the delete."""
os.chmod(inTarget, stat.S_IWRITE)
inFunc(inTarget)
if sys.version_info >= (3, 12):
shutil.rmtree(vPath, onexc=clearAndRetry)
else:
shutil.rmtree(vPath, onerror=clearAndRetry)
def findNinja(inBuildDir, inHost):
"""Return the CMake-visible path to a ninja binary. If no ninja is on
PATH, fetches the platform-appropriate release binary once into
<inBuildDir>/ninja-bin."""
if shutil.which("ninja"):
return "ninja"
vDir = Path(inBuildDir) / "ninja-bin"
vDir.mkdir(parents=True, exist_ok=True)
vExe = vDir / ("ninja.exe" if inHost == "windows" else "ninja")
if not vExe.is_file():
vZipName = kNinjaZipByHost.get(inHost)
if vZipName is None:
raise SystemExit("ERROR: no ninja binary for host " + inHost)
print(">> fetching ninja for " + inHost, flush=True)
vUrl = ("https://github.com/ninja-build/ninja/releases/download/v"
+ kNinjaVersion + "/" + vZipName)
vZipPath = vDir / "ninja.zip"
urllib.request.urlretrieve(vUrl, vZipPath)
with zipfile.ZipFile(vZipPath) as vZip:
vZip.extractall(vDir)
if inHost != "windows":
vExe.chmod(vExe.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
return vExe.as_posix()
def setupToolchain(inHost):
"""Export CC / CXX so CMake picks up the right compiler for static
K/N-compatible builds, returning (cc, cxx):
macos -> clang / clang++ from Xcode command line tools
linux -> system gcc / g++
windows -> K/N's own bundled mingw-w64 gcc (matches its CRT) for C, and
the SYSTEM g++ for C++ (K/N's mingw is C-only). Set KN_MINGW=0
to force the system gcc if the K/N one is unavailable."""
if inHost == "macos":
vCc = os.environ.get("CC") or "clang"
vCxx = os.environ.get("CXX") or "clang++"
elif inHost == "linux":
vCc = os.environ.get("CC") or "gcc"
vCxx = os.environ.get("CXX") or "g++"
elif inHost == "windows":
vCxx = os.environ.get("CXX") or ""
if not vCxx:
vSysGxx = shutil.which("g++")
if vSysGxx:
vCxx = Path(vSysGxx).as_posix()
print(">> C++ compiler (K/N mingw is C-only): " + vCxx)
else:
print(">> WARNING: no system g++ on PATH; SDL3's CMake configure needs a working")
print(">> C++ compiler and will fail. Install mingw-w64 g++.")
if os.environ.get("KN_MINGW", "1") != "0":
vKonanDir = Path(os.environ.get("KONAN_DATA_DIR") or (Path.home() / ".konan"))
vKnBin = next((vBin for vBin in sorted(vKonanDir.glob("dependencies/*mingw*/bin"))
if (vBin / "gcc.exe").is_file()), None)
if vKnBin is not None:
os.environ["PATH"] = str(vKnBin) + os.pathsep + os.environ["PATH"]
vGccVersion = subprocess.run(["gcc", "--version"], capture_output=True,
text=True).stdout.splitlines()[0]
print(">> using Kotlin/Native bundled mingw: " + vGccVersion)
else:
print(">> WARNING: K/N bundled mingw not found; using system gcc.")
vCc = os.environ.get("CC") or "gcc"
else:
raise SystemExit("ERROR: unsupported host: " + inHost)
os.environ["CC"] = vCc
if vCxx:
os.environ["CXX"] = vCxx
return vCc, vCxx
def extraCmakeArgs(inHost):
"""Host-specific extra CMake arguments. The port targets macosArm64 only —
force the arch so a universal-arch host build stays trimmed and matches
the K/N target."""
if inHost == "macos":
return ["-DCMAKE_OSX_ARCHITECTURES=arm64", "-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0"]
return []
# ==================
# MARK: Manifest — build-sdl.properties
# ==================
# One file at scripts/build-sdl/build-sdl.properties defines the URL + ref for
# every static library this script pulls. Format is `KEY=value`, with
# `KEY_<host>=value` per-host overrides (host = macos / linux / windows).
def manifestValue(inKey, inHost):
"""Resolve inKey, honoring:
1. Environment variable of that name (highest priority)
2. KEY_<host> in build-sdl.properties (only when host matches)
3. KEY in build-sdl.properties (base)
Returns "" if none set — pair with requireManifest to make the key
mandatory. MANIFEST_FILE can be pointed at a different file for testing."""
vEnv = os.environ.get(inKey, "")
if vEnv:
return vEnv
vFile = Path(os.environ.get("MANIFEST_FILE") or (kToolsDir / "build-sdl.properties"))
if not vFile.is_file():
return ""
vBase = ""
vHostVal = ""
for vLine in vFile.read_text(encoding="utf-8").splitlines():
vLine = vLine.strip()
if not vLine or vLine.startswith("#") or "=" not in vLine:
continue
vName, vValue = vLine.split("=", 1)
if vName == inKey + "_" + inHost and not vHostVal:
vHostVal = vValue.strip()
elif vName == inKey and not vBase:
vBase = vValue.strip()
return vHostVal or vBase
def requireManifest(inKey, inHost):
"""manifestValue, or fail with a clear error if the key resolves empty."""
vVal = manifestValue(inKey, inHost)
if not vVal:
raise SystemExit("ERROR: " + inKey + " not defined — add it to "
+ str(kToolsDir / "build-sdl.properties") + " or export it as an env var")
return vVal
# ==================
# MARK: Shared build flow
# ==================
def cloneIfChanged(inBuildDir, inUrl, inRef, inBlobFilter=False):
"""Clone inUrl at inRef into <inBuildDir>/src, re-cloning whenever URL/ref
changes so switching versions is clean. inBlobFilter uses a blobless full
clone + checkout instead of --depth 1 -b, so inRef can be any commit.
Returns the src path."""
vSrc = Path(inBuildDir) / "src"
vMarker = Path(inBuildDir) / ".source"
vWant = inUrl + " " + inRef
vHave = vMarker.read_text(encoding="utf-8").strip() if vMarker.is_file() else ""
if not (vSrc / "CMakeLists.txt").is_file() or vHave != vWant:
print(">> cloning " + inUrl + " @ " + inRef)
forceRmtree(vSrc)
if inBlobFilter:
run(["git", "clone", "--filter=blob:none", inUrl, vSrc])
run(["git", "-C", vSrc, "checkout", "--quiet", inRef])
else:
run(["git", "clone", "--depth", "1", "-b", inRef, inUrl, vSrc])
vMarker.write_text(vWant + "\n", encoding="utf-8")
return vSrc
def cmakeConfigure(inSrc, inOut, inNinja, inCc, inHost, inPrefix, inExtra):
"""Run the CMake configure shared by every library (Ninja, Release,
static, PIC, size-sectioned C flags) plus per-library inExtra args."""
run(["cmake", "-S", Path(inSrc).as_posix(), "-B", Path(inOut).as_posix(), "-G", "Ninja",
"-DCMAKE_MAKE_PROGRAM=" + inNinja,
"-DCMAKE_C_COMPILER=" + inCc,
"-DCMAKE_BUILD_TYPE=Release",
"-DBUILD_SHARED_LIBS=OFF",
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON"]
+ inExtra
+ ["-DCMAKE_C_FLAGS=" + kSizeFlags]
+ extraCmakeArgs(inHost)
+ ["-DCMAKE_INSTALL_PREFIX=" + Path(inPrefix).as_posix()])
def cmakeBuildInstall(inOut):
"""cmake --build then --install of an already-configured build tree."""
run(["cmake", "--build", Path(inOut).as_posix()])
run(["cmake", "--install", Path(inOut).as_posix()])
# ==================
# MARK: FreeType
# ==================
def buildFreetype(inHost, inCc, inCxx):
"""Build a self-contained static FreeType into libs/FreeType. Optional
dependencies (PNG / HarfBuzz / Brotli / BZip2 / system zlib) are DISABLED —
only core + variable-font (MM / GX) support is kept, which is all the SDL3
renderer needs for Material Symbols axes and variable Roboto. The result
is a static libfreetype.a with NO external DLLs / dylibs that links
straight into the Kotlin/Native binary (the freetype.def does -lfreetype)."""
vBuild = kLibsDir / ".build" / "freetype"
vBuild.mkdir(parents=True, exist_ok=True)
vNinja = findNinja(vBuild, inHost)
vUrl = requireManifest("FREETYPE_URL", inHost)
vTag = requireManifest("FREETYPE_TAG", inHost)
vSrc = cloneIfChanged(vBuild, vUrl, vTag)
print(">> configuring (static, optional deps off)")
cmakeConfigure(vSrc, vBuild / "out", vNinja, inCc, inHost, kLibsDir / "FreeType", [
"-DFT_DISABLE_HARFBUZZ=ON", "-DFT_DISABLE_PNG=ON", "-DFT_DISABLE_BROTLI=ON",
"-DFT_DISABLE_BZIP2=ON", "-DFT_DISABLE_ZLIB=ON",
])
print(">> building + installing")
cmakeBuildInstall(vBuild / "out")
print(">> done: " + str(kLibsDir / "FreeType") + " (static libfreetype.a, no runtime deps)")
# ==================
# MARK: SDL3
# ==================
def buildSdl3(inHost, inCc, inCxx):
"""Build a static SDL3 (no shared lib) into libs/SDL3 so the app links
SDL3 straight into the executable. The set of system libraries SDL3 needs
when static is recorded in libs/SDL3/lib/pkgconfig/sdl3-static.pc
(Libs.private) after this runs; the app's linker line has to include those.
On Windows SDL >= 3.4 needs a working C++ compiler (GameInput backend) —
see setupToolchain. The D3D12 render driver and SDL_GPU subsystem are
forced OFF because K/N's mingw dxgi1_6.h is too old for SDL >= 3.4's
IDXGIFactory6 / DXGI_GPU_PREFERENCE_* usage. D3D11 (default Windows
driver) is unaffected, and this project only ever uses SDL_Render."""
vBuild = kLibsDir / ".build" / "sdl3src"
vBuild.mkdir(parents=True, exist_ok=True)
vNinja = findNinja(vBuild, inHost)
vUrl = requireManifest("SDL_URL", inHost)
vRef = requireManifest("SDL_REF", inHost)
vSrc = cloneIfChanged(vBuild, vUrl, vRef)
vOut = vBuild / "out"
vPrefix = vBuild / "prefix"
vExtra = [
"-DSDL_SHARED=OFF", "-DSDL_STATIC=ON",
"-DSDL_TESTS=OFF", "-DSDL_TEST_LIBRARY=OFF", "-DSDL_EXAMPLES=OFF",
"-DSDL_INSTALL_TESTS=OFF",
"-DCMAKE_CXX_FLAGS=" + kSizeFlags,
]
if inHost == "windows":
# Kill the D3D12 driver + GPU subsystem (K/N mingw dxgi1_6.h too old).
vExtra += ["-DSDL_RENDER_D3D12=OFF", "-DSDL_GPU=OFF"]
if inHost == "linux":
# SDL3's CMake feature-detection sees glibc's memfd_create and
# posix_spawn_file_actions_addchdir_np on modern hosts (>= 2.27 /
# 2.29), but K/N's LLD sysroot for linuxX64 doesn't have them at final
# app link. Pre-set the cache vars so check_symbol_exists honors our
# OFF value (it only checks vars that aren't already cached) and SDL3
# falls back to its portable implementations.
vExtra += ["-DHAVE_MEMFD_CREATE=0", "-DHAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP=0"]
print(">> configuring (static, size-sectioned)")
forceRmtree(vOut)
cmakeConfigure(vSrc, vOut, vNinja, inCc, inHost, vPrefix, vExtra)
print(">> building + installing")
forceRmtree(vPrefix)
cmakeBuildInstall(vOut)
vDest = kLibsDir / "SDL3"
print(">> installing into " + str(vDest))
forceRmtree(vDest)
vDest.mkdir(parents=True)
shutil.copytree(vPrefix / "include", vDest / "include")
shutil.copytree(vPrefix / "lib", vDest / "lib")
if inHost == "windows":
bakeSetjmpStub(vBuild, vDest, inCc)
print(">> done: " + str(vDest) + " (static libSDL3.a, no shared lib)")
vPc = vDest / "lib" / "pkgconfig" / "sdl3-static.pc"
if vPc.is_file():
print(">> SDL3 static system libs (Libs.private):")
for vLine in vPc.read_text(encoding="utf-8").splitlines():
if vLine.startswith("Libs.private:"):
print(" " + vLine[len("Libs.private:"):].strip())
def bakeSetjmpStub(inBuildDir, inSdl3Dir, inCc):
"""Compile kSetjmpStubSource and append its object into libSDL3.a so the
archive travels with everything needed to link consumers (see the constant
for why mingwX64 needs the __intrinsic_setjmpex alias)."""
print(">> baking __intrinsic_setjmpex alias stub into libSDL3.a")
vCFile = Path(inBuildDir) / "mingw_setjmp_stub.c"
vObj = Path(inBuildDir) / "mingw_setjmp_stub.o"
vCFile.write_text(kSetjmpStubSource, encoding="utf-8")
run([inCc, "-c", "-Os", vCFile.as_posix(), "-o", vObj.as_posix()])
run(["ar", "rcs", (Path(inSdl3Dir) / "lib" / "libSDL3.a").as_posix(), vObj.as_posix()])
# ==================
# MARK: SDL3_image
# ==================
def buildSdl3Image(inHost, inCc, inCxx):
"""Build a static SDL3_image into libs/SDL3_image plus the static archives
of its vendored codecs (zlib, libpng, libwebp). Formats: PNG + JPG
(built-in stb backend) + WEBP + SVG (and the built-in BMP/GIF/QOI/TGA/...
which need no external codec). AVIF / TIFF / JXL are OFF — they pull the
very large dav1d / aom / libjxl submodules we don't init.
Static archives don't bundle their dependencies, so every vendored *.a is
copied out of the build tree next to libSDL3_image.a; the app's linker
line lists them. Needs sdl3 built FIRST (links against libs/SDL3)."""
if not (kLibsDir / "SDL3" / "lib").is_dir():
raise SystemExit("ERROR: static SDL3 not found — run `python3 "
+ str(kToolsDir / "build-all.py") + " sdl3` first")
vBuild = kLibsDir / ".build" / "sdl3imgsrc"
vBuild.mkdir(parents=True, exist_ok=True)
vNinja = findNinja(vBuild, inHost)
vUrl = requireManifest("SDL_IMAGE_URL", inHost)
vRef = requireManifest("SDL_IMAGE_REF", inHost)
vSrc = cloneIfChanged(vBuild, vUrl, vRef)
vOut = vBuild / "out"
vPrefix = vBuild / "prefix"
print(">> fetching vendored codec submodules (zlib, libpng, libwebp)")
run(["git", "-C", vSrc, "submodule", "update", "--init", "--depth", "1",
"external/zlib", "external/libpng", "external/libwebp"])
print(">> configuring (static, vendored PNG/WEBP + stb JPG + built-in SVG; AVIF/TIF/JXL off)")
forceRmtree(vOut)
cmakeConfigure(vSrc, vOut, vNinja, inCc, inHost, vPrefix, [
"-DCMAKE_CXX_COMPILER=" + (inCxx or inCc),
"-DCMAKE_PREFIX_PATH=" + (kLibsDir / "SDL3").as_posix(),
"-DSDLIMAGE_VENDORED=ON",
"-DSDLIMAGE_DEPS_SHARED=OFF",
"-DSDLIMAGE_SAMPLES=OFF", "-DSDLIMAGE_TESTS=OFF", "-DSDLIMAGE_INSTALL=ON",
"-DSDLIMAGE_PNG=ON", "-DSDLIMAGE_JPG=ON", "-DSDLIMAGE_WEBP=ON", "-DSDLIMAGE_SVG=ON",
"-DSDLIMAGE_AVIF=OFF", "-DSDLIMAGE_TIF=OFF", "-DSDLIMAGE_JXL=OFF",
])
print(">> building + installing")
forceRmtree(vPrefix)
cmakeBuildInstall(vOut)
vDest = kLibsDir / "SDL3_image"
print(">> installing into " + str(vDest) + " (+ vendored codec archives)")
forceRmtree(vDest)
(vDest / "lib").mkdir(parents=True)
shutil.copytree(vPrefix / "include", vDest / "include")
for vLib in (vPrefix / "lib").rglob("libSDL3_image*.a"):
shutil.copy2(vLib, vDest / "lib" / vLib.name)
# Harvest the vendored codec static archives from the build tree.
for vLib in vOut.rglob("*.a"):
if vLib.name != "libSDL3_image.a":
shutil.copy2(vLib, vDest / "lib" / vLib.name)
# libpng installs both libpng.a and an identical libpng16.a — keep one.
(vDest / "lib" / "libpng.a").unlink(missing_ok=True)
# Normalize zlib archive name across platforms: on Windows CMake produces
# libzlibstatic.a, elsewhere libz.a. The cinterop staticLibraries directive
# lists a single filename per platform, so drop a libz.a alias if only the
# libzlibstatic.a form exists.
vZlibStatic = vDest / "lib" / "libzlibstatic.a"
if vZlibStatic.is_file() and not (vDest / "lib" / "libz.a").is_file():
shutil.copy2(vZlibStatic, vDest / "lib" / "libz.a")
print(">> done: " + str(vDest))
print(">> static archives present:")
for vLib in sorted((vDest / "lib").iterdir()):
print(" " + vLib.name)
# ==================
# MARK: SDL3_ttf
# ==================
def buildSdl3Ttf(inHost, inCc, inCxx):
"""Build a static SDL3_ttf into libs/SDL3_ttf. Defaults (from
build-sdl.properties) point at the in-house fork
https://github.com/bitsycore/SDL_ttf @ variable-font-axes: upstream
SDL3_ttf has no public API to set OpenType variable-font axes
(wght / wdth / opsz / GRAD / FILL ...); the fork adds it so the renderer
can drive axes through TTF_SetFontAxisValue() instead of bypassing SDL_ttf
and talking to FreeType directly.
Static: a static archive doesn't bundle its dependencies, so the app's
final link pulls in libs/FreeType and libs/SDL3 to resolve SDL_ttf's
symbols. HarfBuzz and plutosvg are OFF, matching this repo's HarfBuzz-free
FreeType build. Needs sdl3 + freetype built FIRST."""
if not (kLibsDir / "SDL3" / "lib" / "cmake" / "SDL3" / "SDL3Config.cmake").is_file():
raise SystemExit("ERROR: SDL3 not found in libs/SDL3 — run `python3 "
+ str(kToolsDir / "build-all.py") + " sdl3` first")
if not (kLibsDir / "FreeType" / "lib" / "libfreetype.a").is_file():
raise SystemExit("ERROR: FreeType not found in libs/FreeType — run `python3 "
+ str(kToolsDir / "build-all.py") + " freetype` first")
vBuild = kLibsDir / ".build" / "sdl3_ttf"
vBuild.mkdir(parents=True, exist_ok=True)
vNinja = findNinja(vBuild, inHost)
vUrl = requireManifest("SDL_TTF_URL", inHost)
vRef = requireManifest("SDL_TTF_REF", inHost)
vSrc = cloneIfChanged(vBuild, vUrl, vRef, inBlobFilter=True)
vOut = vBuild / "out"
vPrefix = vBuild / "prefix"
print(">> configuring (non-vendored: libs/FreeType + libs/SDL3; HarfBuzz/plutosvg off)")
forceRmtree(vOut)
cmakeConfigure(vSrc, vOut, vNinja, inCc, inHost, vPrefix, [
"-DCMAKE_PREFIX_PATH=" + (kLibsDir / "SDL3").as_posix() + ";"
+ (kLibsDir / "FreeType").as_posix(),
"-DSDLTTF_VENDORED=OFF",
"-DSDLTTF_HARFBUZZ=OFF",
"-DSDLTTF_PLUTOSVG=OFF",
"-DSDLTTF_SAMPLES=OFF",
"-DSDLTTF_INSTALL=ON",
])
print(">> building + installing")
forceRmtree(vPrefix)
cmakeBuildInstall(vOut)
vDest = kLibsDir / "SDL3_ttf"
print(">> installing into " + str(vDest))
forceRmtree(vDest)
vDest.mkdir(parents=True)
shutil.copytree(vPrefix / "include", vDest / "include")
shutil.copytree(vPrefix / "lib", vDest / "lib")
print(">> done: " + str(vDest) + " (libSDL3_ttf.a from " + vRef + ", static, no HarfBuzz)")
# ==================
# MARK: Entry point
# ==================
kBuilders = {
"freetype": buildFreetype,
"sdl3": buildSdl3,
"sdl3-image": buildSdl3Image,
"sdl3-ttf": buildSdl3Ttf,
}
def printNextSteps(inHost):
"""Print the per-host gradle command that links the freshly built libs."""
if inHost == "macos":
print("Build the app (links everything into a self-contained macosArm64 binary):")
print(" ./gradlew :demo:runDebugExecutableMacosArm64")
print(" ./gradlew :apidemo:runDebugExecutableMacosArm64")
elif inHost == "linux":
print("Build the app (links everything into a self-contained Linux binary):")
print(" ./gradlew :demo:runDebugExecutableLinuxX64")
print(" ./gradlew :apidemo:runDebugExecutableLinuxX64")
elif inHost == "windows":
print("Build the app (links everything into a DLL-free .exe + data.kres):")
print(" gradlew.bat :apidemo:runDebugExecutableMingwX64")
print(" gradlew.bat :demo:runDebugExecutableMingwX64")
def main():
"""Parse the library selection, wire the host toolchain, and run the
selected build steps in canonical order."""
vParser = argparse.ArgumentParser(
description="Build the static SDL3 / SDL3_ttf / SDL3_image / FreeType "
+ "libraries into <repo>/libs/ (see build-sdl.properties for versions).")
vParser.add_argument("libs", nargs="*", metavar="lib",
help="libraries to build: " + " ".join(kBuildOrder) + " (default: all, in order)")
vArgs = vParser.parse_args()
for vName in vArgs.libs:
if vName not in kBuildOrder:
vParser.error("unknown library '" + vName + "' (choose from: "
+ " ".join(kBuildOrder) + ")")
vWanted = set(vArgs.libs) if vArgs.libs else set(kBuildOrder)
vSelected = [vName for vName in kBuildOrder if vName in vWanted]
vHost = detectHost()
if vHost == "unknown":
raise SystemExit("ERROR: unsupported host — this script runs on macOS, Linux, and Windows.")
print(">> host: " + vHost)
os.environ["BUILD_SDL_HOST"] = vHost
vCc, vCxx = setupToolchain(vHost)
for vName in vSelected:
print("")
print("=" * 60)
print("== " + vName)
print("=" * 60)
kBuilders[vName](vHost, vCc, vCxx)
print("")
print("=" * 60)
print("== Built (static) into libs/: " + ", ".join(vSelected))
print("=" * 60)
printNextSteps(vHost)
if __name__ == "__main__":
try:
main()
except subprocess.CalledProcessError as vError:
raise SystemExit("ERROR: command failed with exit code "
+ str(vError.returncode) + ": " + " ".join(vError.cmd))
-69
View File
@@ -1,69 +0,0 @@
#!/usr/bin/env bash
# One-shot dependency setup — builds every native dependency this project
# links into the executable as a STATIC library, installing each into
# <repo>/libs/. Runs on macOS, Linux, and Windows (Git Bash).
#
# Build order matters — later libraries link the earlier ones:
# 1. FreeType → libs/FreeType/lib/libfreetype.a
# 2. SDL3 → libs/SDL3/lib/libSDL3.a
# 3. SDL3_image → libs/SDL3_image (vendored PNG/JPG/SVG/WEBP; needs SDL3)
# 4. SDL3_ttf → libs/SDL3_ttf (variable-font-axes patch; needs
# SDL3 + FreeType)
#
# Everything is static + HarfBuzz/plutosvg-free, so the app links to a clean
# <app> + data.kres with no runtime .dylib / .so / .dll alongside.
#
# Requires on every host: git, cmake. Ninja is fetched into libs/.build/ninja-bin
# when absent. Windows also needs Python + curl (Git Bash defaults) plus a
# mingw-w64 g++ on PATH (for SDL3's GameInput backend; K/N's bundled mingw is
# C-only). URLs + refs for each library come from build-sdl.properties; edit
# it to change versions or repos, or set the corresponding env vars (SDL_URL /
# SDL_REF / SDL_IMAGE_URL / SDL_IMAGE_REF / SDL_TTF_URL / SDL_TTF_REF /
# FREETYPE_URL / FREETYPE_TAG) for a one-off override.
set -euo pipefail
TOOLS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./_lib.sh
source "$TOOLS/_lib.sh"
export BUILD_SDL_HOST="$(detect_host)"
if [ "$BUILD_SDL_HOST" = "unknown" ]; then
echo "ERROR: unsupported host — this script runs on macOS, Linux, and Git Bash on Windows." >&2
exit 1
fi
echo ">> host: $BUILD_SDL_HOST"
step() {
echo ""
echo "============================================================"
echo "== $1"
echo "============================================================"
bash "$TOOLS/$1"
}
step build-freetype.sh
step build-sdl3.sh
step build-sdl3-image.sh
step build-sdl3-ttf.sh
echo ""
echo "============================================================"
echo "== All dependencies built (static) into libs/"
echo "============================================================"
case "$BUILD_SDL_HOST" in
macos)
echo "Build the app (links everything into a self-contained macosArm64 binary):"
echo " ./gradlew :demo:runDebugExecutableMacosArm64"
echo " ./gradlew :apidemo:runDebugExecutableMacosArm64"
;;
linux)
echo "Build the app (links everything into a self-contained Linux binary):"
echo " ./gradlew :demo:runDebugExecutableLinuxX64"
echo " ./gradlew :apidemo:runDebugExecutableLinuxX64"
;;
windows)
echo "Build the app (links everything into a DLL-free .exe + data.kres):"
echo " gradlew.bat :apidemo:runDebugExecutableMingwX64"
echo " gradlew.bat :demo:runDebugExecutableMingwX64"
;;
esac
-59
View File
@@ -1,59 +0,0 @@
#!/usr/bin/env bash
# Build a self-contained static FreeType and install it into <repo>/libs/FreeType.
# Runs on macOS, Linux, and Windows (Git Bash) — see _lib.sh for host handling.
#
# Optional dependencies (PNG / HarfBuzz / Brotli / BZip2 / system zlib) are
# DISABLED — only core + variable-font (MM / GX) support is kept, which is all
# the SDL3 renderer needs for Material Symbols axes and variable Roboto.
#
# The result is a static libfreetype.a with NO external DLLs / dylibs that
# links straight into the Kotlin/Native binary (the freetype.def does
# `-lfreetype`). URL + ref come from scripts/build-sdl/build-sdl.properties
# (FREETYPE_URL / FREETYPE_TAG); a same-named env var overrides for one-offs.
set -euo pipefail
TOOLS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./_lib.sh
source "$TOOLS/_lib.sh"
BUILD_SDL_HOST="$(detect_host)"
setup_toolchain
FT_URL="$(require_manifest FREETYPE_URL)"
FT_TAG="$(require_manifest FREETYPE_TAG)"
REPO="$(cd "$TOOLS/../.." && pwd)"
LIBS="$REPO/libs"
BUILD="$LIBS/.build/freetype"
mkdir -p "$BUILD"
NINJA="$(find_ninja "$BUILD")"
# Re-clone whenever URL / tag changes so switching versions is clean.
MARKER="$BUILD/.source"
WANT="$FT_URL $FT_TAG"
if [ ! -f "$BUILD/src/CMakeLists.txt" ] || [ "$(cat "$MARKER" 2>/dev/null)" != "$WANT" ]; then
echo ">> cloning FreeType $FT_TAG"
rm -rf "$BUILD/src"
git clone --depth 1 -b "$FT_TAG" "$FT_URL" "$BUILD/src"
echo "$WANT" > "$MARKER"
fi
echo ">> configuring (static, optional deps off)"
# shellcheck disable=SC2046
cmake -S "$(cmake_path "$BUILD/src")" -B "$(cmake_path "$BUILD/out")" -G Ninja \
-DCMAKE_MAKE_PROGRAM="$NINJA" \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DFT_DISABLE_HARFBUZZ=ON -DFT_DISABLE_PNG=ON -DFT_DISABLE_BROTLI=ON \
-DFT_DISABLE_BZIP2=ON -DFT_DISABLE_ZLIB=ON \
-DCMAKE_C_FLAGS="-ffunction-sections -fdata-sections -Os" \
$(extra_cmake_args) \
-DCMAKE_INSTALL_PREFIX="$(cmake_path "$LIBS/FreeType")"
echo ">> building + installing"
cmake --build "$(cmake_path "$BUILD/out")"
cmake --install "$(cmake_path "$BUILD/out")"
echo ">> done: $LIBS/FreeType (static libfreetype.a, no runtime deps)"
-99
View File
@@ -1,99 +0,0 @@
#!/usr/bin/env bash
# Build a static SDL3_image and install it into <repo>/libs/SDL3_image. Runs
# on macOS, Linux, and Windows (Git Bash) — see _lib.sh for host handling.
#
# Produces libSDL3_image.a plus the static archives of its vendored codecs
# (zlib, libpng, libwebp) so the app links image decoding straight into the
# executable — no runtime .so / .dylib / .dll alongside.
#
# Formats: PNG + JPG (built-in stb backend) + WEBP + SVG (and the built-in
# BMP/GIF/QOI/TGA/... which need no external codec). AVIF / TIFF / JXL are OFF
# — they pull the very large dav1d / aom / libjxl submodules we don't init.
#
# Static archives don't bundle their dependencies, so we copy every vendored
# *.a out of the build tree next to libSDL3_image.a; the app's linker line
# lists them. URL + ref come from scripts/build-sdl/build-sdl.properties
# (SDL_IMAGE_URL / SDL_IMAGE_REF); a same-named env var overrides for one-offs.
#
# Run build-sdl3.sh FIRST (SDL3_image links against libs/SDL3).
set -euo pipefail
TOOLS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./_lib.sh
source "$TOOLS/_lib.sh"
BUILD_SDL_HOST="$(detect_host)"
setup_toolchain
SDL_IMAGE_URL="$(require_manifest SDL_IMAGE_URL)"
SDL_IMAGE_REF="$(require_manifest SDL_IMAGE_REF)"
REPO="$(cd "$TOOLS/../.." && pwd)"
LIBS="$REPO/libs"
BUILD="$LIBS/.build/sdl3imgsrc"
SRC="$BUILD/src"
OUT="$BUILD/out"
PREFIX="$BUILD/prefix"
mkdir -p "$BUILD"
[ -d "$LIBS/SDL3/lib" ] || { echo "ERROR: static SDL3 not found — run scripts/build-sdl/build-sdl3.sh first" >&2; exit 1; }
NINJA="$(find_ninja "$BUILD")"
# Re-clone whenever URL / ref changes so switching versions is clean.
MARKER="$BUILD/.source"
WANT="$SDL_IMAGE_URL $SDL_IMAGE_REF"
if [ ! -f "$SRC/CMakeLists.txt" ] || [ "$(cat "$MARKER" 2>/dev/null)" != "$WANT" ]; then
echo ">> cloning SDL_image $SDL_IMAGE_REF"
rm -rf "$SRC"
git clone --depth 1 -b "$SDL_IMAGE_REF" "$SDL_IMAGE_URL" "$SRC"
echo "$WANT" > "$MARKER"
fi
echo ">> fetching vendored codec submodules (zlib, libpng, libwebp)"
git -C "$SRC" submodule update --init --depth 1 \
external/zlib external/libpng external/libwebp
echo ">> configuring (static, vendored PNG/WEBP + stb JPG + built-in SVG; AVIF/TIF/JXL off)"
rm -rf "$OUT"
# shellcheck disable=SC2046
cmake -S "$(cmake_path "$SRC")" -B "$(cmake_path "$OUT")" -G Ninja \
-DCMAKE_MAKE_PROGRAM="$NINJA" \
-DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="${CXX:-$CC}" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$(cmake_path "$LIBS/SDL3")" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DSDLIMAGE_VENDORED=ON \
-DSDLIMAGE_DEPS_SHARED=OFF \
-DSDLIMAGE_SAMPLES=OFF -DSDLIMAGE_TESTS=OFF -DSDLIMAGE_INSTALL=ON \
-DSDLIMAGE_PNG=ON -DSDLIMAGE_JPG=ON -DSDLIMAGE_WEBP=ON -DSDLIMAGE_SVG=ON \
-DSDLIMAGE_AVIF=OFF -DSDLIMAGE_TIF=OFF -DSDLIMAGE_JXL=OFF \
-DCMAKE_C_FLAGS="-ffunction-sections -fdata-sections -Os" \
$(extra_cmake_args) \
-DCMAKE_INSTALL_PREFIX="$(cmake_path "$PREFIX")"
echo ">> building + installing"
rm -rf "$PREFIX"
cmake --build "$(cmake_path "$OUT")"
cmake --install "$(cmake_path "$OUT")"
echo ">> installing into $LIBS/SDL3_image (+ vendored codec archives)"
rm -rf "$LIBS/SDL3_image"
mkdir -p "$LIBS/SDL3_image/lib"
cp -R "$PREFIX/include" "$LIBS/SDL3_image/"
find "$PREFIX/lib" -name 'libSDL3_image*.a' -exec cp {} "$LIBS/SDL3_image/lib/" \;
# Harvest the vendored codec static archives from the build tree.
find "$OUT" -name '*.a' ! -name 'libSDL3_image.a' -exec cp -f {} "$LIBS/SDL3_image/lib/" \;
# libpng installs both libpng.a and an identical libpng16.a — keep one.
rm -f "$LIBS/SDL3_image/lib/libpng.a"
# Normalize zlib archive name across platforms: on Windows CMake produces
# libzlibstatic.a, elsewhere libz.a. The cinterop staticLibraries directive
# lists a single filename per platform, so drop a libz.a alias if only the
# libzlibstatic.a form exists.
if [ -f "$LIBS/SDL3_image/lib/libzlibstatic.a" ] && [ ! -f "$LIBS/SDL3_image/lib/libz.a" ]; then
cp "$LIBS/SDL3_image/lib/libzlibstatic.a" "$LIBS/SDL3_image/lib/libz.a"
fi
echo ">> done: $LIBS/SDL3_image"
echo ">> static archives present:"
ls "$LIBS/SDL3_image/lib/" | sed 's/^/ /'
-90
View File
@@ -1,90 +0,0 @@
#!/usr/bin/env bash
# Build a static SDL3_ttf and install it into <repo>/libs/SDL3_ttf. Runs on
# macOS, Linux, and Windows (Git Bash) — see _lib.sh for host handling.
#
# Defaults (from scripts/build-sdl/build-sdl.properties) point at the in-house
# fork:
#
# https://github.com/bitsycore/SDL_ttf (branch: variable-font-axes)
#
# Upstream SDL3_ttf has no public API to set OpenType variable-font axes
# (wght / wdth / opsz / GRAD / FILL ...); the fork adds it so the renderer
# can drive axes through TTF_SetFontAxisValue() instead of bypassing SDL_ttf
# and talking to FreeType directly.
#
# URL + ref come from scripts/build-sdl/build-sdl.properties (SDL_TTF_URL /
# SDL_TTF_REF); a same-named env var overrides for one-offs. Swapping either
# value re-clones automatically. Examples:
# SDL_TTF_URL=https://github.com/libsdl-org/SDL_ttf.git SDL_TTF_REF=main ./build-sdl3-ttf.sh
# SDL_TTF_REF=release-3.2.2 ./build-sdl3-ttf.sh
#
# Static: produces libSDL3_ttf.a (no shared lib). A static archive doesn't
# bundle its dependencies, so the app's final link pulls in libs/FreeType
# (libfreetype.a) and libs/SDL3 (libSDL3.a) to resolve SDL_ttf's symbols.
# HarfBuzz and plutosvg are OFF, matching this repo's HarfBuzz-free FreeType
# build. Run build-sdl3.sh and build-freetype.sh FIRST.
set -euo pipefail
TOOLS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./_lib.sh
source "$TOOLS/_lib.sh"
BUILD_SDL_HOST="$(detect_host)"
setup_toolchain
SDL_TTF_URL="$(require_manifest SDL_TTF_URL)"
SDL_TTF_REF="$(require_manifest SDL_TTF_REF)"
REPO="$(cd "$TOOLS/../.." && pwd)"
LIBS="$REPO/libs"
BUILD="$LIBS/.build/sdl3_ttf"
SRC="$BUILD/src"
OUT="$BUILD/out"
PREFIX="$BUILD/prefix"
MARKER="$BUILD/.source"
mkdir -p "$BUILD"
[ -f "$LIBS/SDL3/lib/cmake/SDL3/SDL3Config.cmake" ] || { echo "ERROR: SDL3 not found in libs/SDL3 — run scripts/build-sdl/build-sdl3.sh first" >&2; exit 1; }
[ -f "$LIBS/FreeType/lib/libfreetype.a" ] || { echo "ERROR: FreeType not found in libs/FreeType — run scripts/build-sdl/build-freetype.sh first" >&2; exit 1; }
NINJA="$(find_ninja "$BUILD")"
# Re-clone whenever URL/ref changes so switching versions is clean.
WANT="$SDL_TTF_URL $SDL_TTF_REF"
if [ ! -f "$SRC/CMakeLists.txt" ] || [ "$(cat "$MARKER" 2>/dev/null)" != "$WANT" ]; then
echo ">> cloning $SDL_TTF_URL @ $SDL_TTF_REF"
rm -rf "$SRC"
git clone --filter=blob:none "$SDL_TTF_URL" "$SRC"
git -C "$SRC" checkout --quiet "$SDL_TTF_REF"
echo "$WANT" > "$MARKER"
fi
echo ">> configuring (non-vendored: libs/FreeType + libs/SDL3; HarfBuzz/plutosvg off)"
rm -rf "$OUT"
# shellcheck disable=SC2046
cmake -S "$(cmake_path "$SRC")" -B "$(cmake_path "$OUT")" -G Ninja \
-DCMAKE_MAKE_PROGRAM="$NINJA" \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$(cmake_path "$LIBS/SDL3");$(cmake_path "$LIBS/FreeType")" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DSDLTTF_VENDORED=OFF \
-DSDLTTF_HARFBUZZ=OFF \
-DSDLTTF_PLUTOSVG=OFF \
-DSDLTTF_SAMPLES=OFF \
-DSDLTTF_INSTALL=ON \
-DCMAKE_C_FLAGS="-ffunction-sections -fdata-sections -Os" \
$(extra_cmake_args) \
-DCMAKE_INSTALL_PREFIX="$(cmake_path "$PREFIX")"
echo ">> building + installing"
rm -rf "$PREFIX"
cmake --build "$(cmake_path "$OUT")"
cmake --install "$(cmake_path "$OUT")"
echo ">> installing into $LIBS/SDL3_ttf"
rm -rf "$LIBS/SDL3_ttf"
mkdir -p "$LIBS/SDL3_ttf"
cp -R "$PREFIX/include" "$PREFIX/lib" "$LIBS/SDL3_ttf/"
echo ">> done: $LIBS/SDL3_ttf (libSDL3_ttf.a from $SDL_TTF_REF, static, no HarfBuzz)"
-123
View File
@@ -1,123 +0,0 @@
#!/usr/bin/env bash
# Build a static SDL3 and install it into <repo>/libs/SDL3. Runs on macOS,
# Linux, and Windows (Git Bash) — see _lib.sh for host handling.
#
# Produces libSDL3.a (no shared lib) so the app links SDL3 straight into the
# executable — clean <app> + data.kres with no runtime .dylib / .so / .dll
# alongside. Compiled with -ffunction-sections -fdata-sections so the final
# link's --gc-sections can drop every SDL function the app never calls.
#
# The set of system libraries SDL3 needs when static is recorded in
# libs/SDL3/lib/pkgconfig/sdl3-static.pc (Libs.private) after this runs; the
# app's linker line has to include those. URL + ref come from
# scripts/build-sdl/build-sdl.properties (SDL_URL / SDL_REF); a same-named env
# var overrides for one-offs. Swapping either value re-clones automatically.
#
# On Windows SDL >= 3.4 needs a working C++ compiler (GameInput backend). See
# _lib.sh's setup_toolchain: system g++ is captured for CXX because K/N's
# bundled mingw is C-only. The D3D12 render driver and SDL_GPU subsystem are
# forced OFF because K/N's mingw dxgi1_6.h is too old for SDL >= 3.4's
# IDXGIFactory6 / DXGI_GPU_PREFERENCE_* usage. D3D11 (default Windows driver)
# is unaffected, and this project only ever uses SDL_Render.
set -euo pipefail
TOOLS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./_lib.sh
source "$TOOLS/_lib.sh"
BUILD_SDL_HOST="$(detect_host)"
setup_toolchain
SDL_URL="$(require_manifest SDL_URL)"
SDL_REF="$(require_manifest SDL_REF)"
REPO="$(cd "$TOOLS/../.." && pwd)"
LIBS="$REPO/libs"
BUILD="$LIBS/.build/sdl3src"
SRC="$BUILD/src"
OUT="$BUILD/out"
PREFIX="$BUILD/prefix"
mkdir -p "$BUILD"
NINJA="$(find_ninja "$BUILD")"
# Re-clone whenever URL/ref changes so switching versions is clean.
MARKER="$BUILD/.source"
WANT="$SDL_URL $SDL_REF"
if [ ! -f "$SRC/CMakeLists.txt" ] || [ "$(cat "$MARKER" 2>/dev/null)" != "$WANT" ]; then
echo ">> cloning SDL $SDL_REF"
rm -rf "$SRC"
git clone --depth 1 -b "$SDL_REF" "$SDL_URL" "$SRC"
echo "$WANT" > "$MARKER"
fi
# Windows-only extras: kill the D3D12 driver + GPU subsystem (K/N mingw
# dxgi1_6.h too old). On macOS/Linux SDL picks Metal/Cocoa or Wayland/X11
# based on what's available in the host SDK.
WIN_ONLY=""
if [ "$BUILD_SDL_HOST" = "windows" ]; then
WIN_ONLY="-DSDL_RENDER_D3D12=OFF -DSDL_GPU=OFF"
fi
# Linux-only extras: SDL3's CMake feature-detection sees glibc's memfd_create
# and posix_spawn_file_actions_addchdir_np on modern hosts (>= 2.27 / 2.29),
# but K/N's LLD sysroot for linuxX64 doesn't have them at final app link.
# Pre-set the cache vars so check_symbol_exists honors our OFF value (it only
# checks vars that aren't already cached) and SDL3 falls back to its portable
# implementations.
LINUX_ONLY=""
if [ "$BUILD_SDL_HOST" = "linux" ]; then
LINUX_ONLY="-DHAVE_MEMFD_CREATE=0 -DHAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP=0"
fi
echo ">> configuring (static, size-sectioned)"
rm -rf "$OUT"
# shellcheck disable=SC2046
cmake -S "$(cmake_path "$SRC")" -B "$(cmake_path "$OUT")" -G Ninja \
-DCMAKE_MAKE_PROGRAM="$NINJA" \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DSDL_SHARED=OFF -DSDL_STATIC=ON \
-DSDL_TESTS=OFF -DSDL_TEST_LIBRARY=OFF -DSDL_EXAMPLES=OFF \
-DSDL_INSTALL_TESTS=OFF \
$WIN_ONLY \
$LINUX_ONLY \
-DCMAKE_C_FLAGS="-ffunction-sections -fdata-sections -Os" \
-DCMAKE_CXX_FLAGS="-ffunction-sections -fdata-sections -Os" \
$(extra_cmake_args) \
-DCMAKE_INSTALL_PREFIX="$(cmake_path "$PREFIX")"
echo ">> building + installing"
rm -rf "$PREFIX"
cmake --build "$(cmake_path "$OUT")"
cmake --install "$(cmake_path "$OUT")"
echo ">> installing into $LIBS/SDL3"
rm -rf "$LIBS/SDL3"
mkdir -p "$LIBS/SDL3"
cp -R "$PREFIX/include" "$PREFIX/lib" "$LIBS/SDL3/"
# Windows: mingw-w64's <setjmp.h> emits external references to
# __intrinsic_setjmpex from any TU using setjmp() (freetype, libpng, ...).
# K/N's LLD sysroot for mingwX64 doesn't provide this symbol and the LLD
# built by K/N 2.4.0 doesn't accept --defsym as a linker workaround.
# Compile a tiny stub that aliases the symbol to msvcrt's _setjmpex, and
# append its object into libSDL3.a so the archive travels with everything
# needed to link consumers.
if [ "$BUILD_SDL_HOST" = "windows" ]; then
echo ">> baking __intrinsic_setjmpex alias stub into libSDL3.a"
cat > "$BUILD/mingw_setjmp_stub.c" <<'CSTUB'
#include <setjmp.h>
extern int __cdecl _setjmpex(jmp_buf _Buf, void *_Ctx);
int __cdecl __intrinsic_setjmpex(jmp_buf _Buf, void *_Ctx) {
return _setjmpex(_Buf, _Ctx);
}
CSTUB
"$CC" -c -Os "$BUILD/mingw_setjmp_stub.c" -o "$BUILD/mingw_setjmp_stub.o"
ar rcs "$LIBS/SDL3/lib/libSDL3.a" "$BUILD/mingw_setjmp_stub.o"
fi
echo ">> done: $LIBS/SDL3 (static libSDL3.a, no shared lib)"
echo ">> SDL3 static system libs (Libs.private):"
sed -n 's/^Libs.private:/ /p' "$LIBS/SDL3/lib/pkgconfig/sdl3-static.pc" 2>/dev/null || true
+393 -94
View File
@@ -1,126 +1,425 @@
#!/usr/bin/env python3
# Compose-API coverage: for each androidx.compose.* package, count how many of
# upstream's public declarations we also expose.
#
# Uses the same klib.api parsing as compose-fidelity-check.py — a declaration is
# identified by its `<pkg>/<name>` decl-id, extracted from the trailing
# `// <decl-id>|<mangle>` comment the binary-compatibility-validator plugin
# emits.
#
# Inputs:
# - our dumps: <module>/api/<module>.klib.api (`./gradlew apiDump`)
# - official dumps: cmp-ref clone (default: ../cmp-ref, then $CMP_REF)
#
# Output: a per-package table sorted by upstream size, with covered/total and a
# coverage bar. Packages we don't touch are hidden by default; pass --all to
# include them.
"""
Compose API coverage + fidelity — one script, two questions, per upstream module:
1. COVERAGE how much of each upstream module's public API do we also expose?
(per-module and per-package tables with percentages)
2. FIDELITY do we expose anything under the mirrored package prefixes that
upstream does NOT have? (invented / divergent public surface — the old
compose-fidelity-check.py, now folded in)
Both sides are Kotlin binary-compatibility-validator klib dumps, so "covered"
means the declaration exists with a compatible public shape: each dump line
carries a trailing `// <decl-id>|<mangle>` comment and we compare normalised
<decl-id> sets.
Inputs
ours: **/api/*.klib.api in this repo (regenerate: ./gradlew apiDump)
upstream: the ref clones scripts/compose-fork/sync.sh maintains —
../cmp-ref JetBrains/compose-multiplatform-core ($CMP_REF)
../cmp-ref-<name> any other repo ($CMP_REF_<NAME>)
Usage
python3 scripts/compose-coverage.py # per-module table + fidelity summary
python3 scripts/compose-coverage.py --missing # list upstream decls we DON'T cover
python3 scripts/compose-coverage.py --missing ui-util # ...for one module only
python3 scripts/compose-coverage.py --packages # per-package breakdown
python3 scripts/compose-coverage.py --extras # list every divergent decl
python3 scripts/compose-coverage.py --all # with --packages: include packages upstream lacks
Output is informational, not a hard gate (exit 0 unless inputs are missing) —
review divergences against CLAUDE.md's vendoring/fidelity rules. Known quirks
of upstream's checked-in dumps: some lag their own sources or were generated
with different BCV versions/filters (e.g. material3 misses recent experimental
API its own sources have; ui-backhandler's dump holds nothing but $stable
noise), so "extras" are review candidates, not automatically invented API.
"""
import argparse
import os
import re
import subprocess
import sys
from pathlib import Path
kRoot = Path(__file__).resolve().parent.parent
if len(sys.argv) > 1 and not sys.argv[1].startswith("--"):
kRefDir = Path(sys.argv[1])
elif os.environ.get("CMP_REF"):
kRefDir = Path(os.environ["CMP_REF"])
elif (kRoot.parent / "cmp-ref").exists():
kRefDir = kRoot.parent / "cmp-ref"
else:
kRefDir = Path("C:/Dev/cmp-ref")
kShowAll = "--all" in sys.argv
# ==================
# MARK: Targets — add a line here to track a new upstream repo / module
# ==================
# repo last path segment of the upstream repo URL. Resolves to the sync.sh
# ref clone: ../cmp-ref for compose-multiplatform-core, ../cmp-ref-<repo>
# for anything else — overridable via CMP_REF / CMP_REF_<REPO>.
# upstream module dir inside that repo; its api/<name>.klib.api is the reference.
# ours module dir in THIS repo whose dump mirrors it. Several upstream
# modules may map to one of ours (ui-graphics / ui-text merge into :ui).
_ACCESSOR = re.compile(r"\.<(get|set)-[^>]+>$")
kCoreRepo = "compose-multiplatform-core"
kUmbrellaRepo = "compose-multiplatform"
kTargets = [
# repo upstream module dir our module dir
(kCoreRepo, "compose/ui/ui-util", "compose/ui/ui-util"),
(kCoreRepo, "compose/ui/ui-geometry", "compose/ui/ui-geometry"),
(kCoreRepo, "compose/ui/ui-unit", "compose/ui/ui-unit"),
(kCoreRepo, "compose/ui/ui-backhandler", "compose/ui/ui-backhandler"),
(kCoreRepo, "compose/ui/ui", "compose/ui/ui"),
(kCoreRepo, "compose/ui/ui-graphics", "compose/ui/ui"),
(kCoreRepo, "compose/ui/ui-text", "compose/ui/ui"),
(kCoreRepo, "compose/animation/animation-core", "compose/animation/animation-core"),
(kCoreRepo, "compose/animation/animation", "compose/animation/animation"),
(kCoreRepo, "compose/animation/animation-graphics", "compose/animation/animation-graphics"),
(kCoreRepo, "compose/foundation/foundation-layout", "compose/foundation/foundation-layout"),
(kCoreRepo, "compose/foundation/foundation", "compose/foundation/foundation"),
(kCoreRepo, "compose/material/material-ripple", "compose/material/material-ripple"),
(kCoreRepo, "compose/material3/material3", "compose/material3/material3"),
(kCoreRepo, "navigation3/navigation3-ui", "navigation3/navigation3-ui"),
(kUmbrellaRepo, "components/resources/library", "components/resources/library"),
]
# Packages under these prefixes are MIRRORED surface: anything we expose here
# that no upstream target has is divergent/invented API and shows up in the
# fidelity report. Project packages (com.compose.sdl.*, material symbols, ...)
# are deliberately absent — they never count as divergent.
kMirroredPrefixes = (
"androidx.compose.",
"androidx.navigation3.",
"org.jetbrains.compose.resources",
)
# The K/N targets this port builds. Upstream dumps are merged across THEIR
# targets (ios / js / macos / wasm) with `// Targets: [...]` sections gating
# target-specific decls; anything gated to targets outside this set (e.g.
# ios-only enableTraceOSLog) can never exist here and is excluded from the
# comparison, so the tables reflect the COMMON surface we can actually mirror.
kOurTargets = {"linuxX64", "linuxArm64", "macosArm64", "mingwX64"}
# ==================
# MARK: klib.api parsing
# ==================
# Property accessors collapse onto the property, constructors onto the class,
# so ours/upstream compare on stable names.
kAccessorRe = re.compile(r"\.<(get|set)-[^>]+>$")
kCtorRe = re.compile(r"\.<init>.*$")
# Merged-dump structure markers: the first `// Targets:` line is the file
# header (every target the dump covers); later ones gate the next declaration
# (and its whole block, for classes) to a subset. `// Alias:` names groups.
kTargetSectionRe = re.compile(r"^// Targets: \[([^\]]*)\]")
kAliasRe = re.compile(r"^// Alias: (\w+) => \[([^\]]*)\]")
def decl_ids(in_path):
def parseTargetList(inText, inAliases):
"""'ios, macosArm64' -> the expanded set of concrete target names."""
vSet = set()
for vName in (vPart.strip() for vPart in inText.split(",")):
if vName:
vSet |= inAliases.get(vName, {vName})
return vSet
def declIds(inPath):
"""Extract the normalised public decl-ids from a BCV .klib.api dump as
(ids, skippedIds). A decl line ends in `// <decl-id>|<mangle>`; header
comments have no `|` and no `<pkg>/<name>` shape, so requiring both filters
them out. Declarations gated by a `// Targets: [...]` section that shares
no target with kOurTargets (ios / js / wasm-only API) can never exist on
this port: they land in skippedIds — out of the coverage denominator but
still known-upstream for the fidelity check."""
vIds = set()
for vLine in in_path.read_text(encoding="utf-8", errors="ignore").splitlines():
vSkipped = set()
vAliases = {}
vAllTargets = None # the file header's full target list; None until seen
vScopes = [] # target set in force for each currently open block
vPending = None # targets a section comment set for the NEXT decl
for vLine in inPath.read_text(encoding="utf-8", errors="ignore").splitlines():
vStripped = vLine.strip()
vAlias = kAliasRe.match(vStripped)
if vAlias:
vAliases[vAlias.group(1)] = parseTargetList(vAlias.group(2), vAliases)
continue
vSection = kTargetSectionRe.match(vStripped)
if vSection:
vTargets = parseTargetList(vSection.group(1), vAliases)
if vAllTargets is None:
vAllTargets = vTargets
else:
vPending = vTargets
continue
vIdx = vLine.find("// ")
if vIdx < 0:
vCode = vLine[:vIdx] if vIdx >= 0 else vLine
vComment = vLine[vIdx + 3:] if vIdx >= 0 else ""
vEffective = vPending if vPending is not None else (vScopes[-1] if vScopes else vAllTargets)
if "|" in vComment:
vId = vComment.split("|", 1)[0].strip()
if "/" in vId and "$stable" not in vId: # skip headers + stability props
vId = kCtorRe.sub("", kAccessorRe.sub("", vId))
if vEffective is not None and not (vEffective & kOurTargets):
vSkipped.add(vId)
else:
vIds.add(vId)
# Track blocks on the code part only — mangle comments contain `{}`.
for _ in range(vCode.count("{")):
vScopes.append(vEffective)
for _ in range(vCode.count("}")):
if vScopes:
vScopes.pop()
if vCode.strip() or "|" in vComment:
vPending = None # a section comment only scopes the decl right after it
return vIds, vSkipped
def packageOf(inId):
"""'androidx.compose.ui.unit/Density.toDpSize' -> 'androidx.compose.ui.unit'."""
return inId.split("/", 1)[0]
# Module dir segments too generic to identify a target on their own.
kGenericSegments = {"library", "core"}
def targetLabel(inModule):
"""Short display label for an upstream module dir: its last path segment,
plus the parent segment when the last one alone is generic
('components/resources/library' -> 'resources/library')."""
vParts = inModule.split("/")
if vParts[-1] in kGenericSegments and len(vParts) > 1:
return vParts[-2] + "/" + vParts[-1]
return vParts[-1]
# ==================
# MARK: Input discovery
# ==================
def refCloneDir(inRepo):
"""Resolve the local ref-clone dir for an upstream repo name, mirroring
scripts/compose-fork/sync.py: ../cmp-ref for compose-multiplatform-core
(override $CMP_REF), ../cmp-ref-<repo> otherwise (override $CMP_REF_<REPO>)."""
if inRepo == kCoreRepo:
return Path(os.environ.get("CMP_REF") or (kRoot.parent / "cmp-ref"))
vEnv = os.environ.get("CMP_REF_" + re.sub(r"[^A-Za-z0-9]+", "_", inRepo).upper())
return Path(vEnv) if vEnv else kRoot.parent / ("cmp-ref-" + inRepo)
def refHead(inDir):
"""Short HEAD of a ref clone, or '?' when git can't answer."""
try:
vOut = subprocess.run(["git", "-C", str(inDir), "rev-parse", "--short", "HEAD"],
capture_output=True, text=True)
return vOut.stdout.strip() or "?"
except OSError:
return "?"
def upstreamApiFile(inRepo, inModule):
"""Path to the single .klib.api reference dump of an upstream module, or
None when the clone / dump isn't there (sparse checkout not synced)."""
vApiDir = refCloneDir(inRepo) / inModule / "api"
vFiles = sorted(vApiDir.glob("*.klib.api"))
return vFiles[0] if vFiles else None
def loadOurDumps():
"""All of this repo's api/*.klib.api dumps (any nesting depth, skipping
build intermediates and libs/), as {module-dir-posix: {decl-ids}}."""
vDumps = {}
for vFile in sorted(kRoot.rglob("*.klib.api")):
vRel = vFile.relative_to(kRoot)
if vFile.parent.name != "api" or {"build", "libs", ".git"} & set(vRel.parts):
continue
vId = vLine[vIdx + 3:].split("|", 1)[0].strip()
if not vId.startswith("androidx.compose"):
continue
if "$stable" in vId:
continue
vId = _ACCESSOR.sub("", vId)
vId = re.sub(r"\.<init>.*$", "", vId)
vIds.add(vId)
return vIds
vDumps[vFile.parent.parent.relative_to(kRoot).as_posix()] = declIds(vFile)[0]
return vDumps
# ==================
# MARK: Report
# ==================
def package_of(in_id):
return in_id.split("/", 1)[0]
def bar(in_pct, in_width=20):
vFilled = int(round(in_pct * in_width / 100))
return "#" * vFilled + "." * (in_width - vFilled)
def bar(inPct, inWidth=20):
"""Render a 0-100 percentage as a #/. progress bar."""
vFilled = int(round(inPct * inWidth / 100))
return "#" * vFilled + "." * (inWidth - vFilled)
def main():
vRefFiles = sorted(kRefDir.glob("compose/**/api/*.klib.api"))
if not vRefFiles:
print(f"!! No official klib.api under {kRefDir}", file=sys.stderr)
"""Load both ABI universes, print the per-module coverage table, then the
optional per-package table and divergent-decl listing."""
vParser = argparse.ArgumentParser(description="Compose API coverage + fidelity vs upstream klib dumps.")
vParser.add_argument("--missing", nargs="?", const="", metavar="MODULE",
help="list upstream decls we don't cover, grouped by package (optionally one module label, e.g. ui-util)")
vParser.add_argument("--packages", action="store_true", help="per-package breakdown table")
vParser.add_argument("--extras", action="store_true", help="list every divergent decl (old fidelity-check output)")
vParser.add_argument("--all", action="store_true", help="with --packages: include our packages upstream lacks")
vArgs = vParser.parse_args()
# ============
# Load upstream refs, target by target
vUpstreamByTarget = {} # {target-label: {decl-ids}}
vOursDirByTarget = {} # {target-label: our module dir}
vMissingRefs = []
vSkippedUnion = set()
for vRepo, vModule, vOursDir in kTargets:
vLabel = targetLabel(vModule)
vFile = upstreamApiFile(vRepo, vModule)
if vFile is None:
vMissingRefs.append((vRepo, vModule))
continue
vUpstreamByTarget[vLabel], vSkipped = declIds(vFile)
vSkippedUnion |= vSkipped
vOursDirByTarget[vLabel] = vOursDir
if not vUpstreamByTarget:
print("!! No upstream klib.api dumps found -- run scripts/compose-fork/sync.sh first "
+ "(or point CMP_REF / CMP_REF_<REPO> at the clones).", file=sys.stderr)
return 1
vAllUpstream = set().union(*vUpstreamByTarget.values())
# Map each upstream .klib.api file to a short label for grouping.
vUpstream = {} # {pkg: {decl_ids}}
vUpstreamModules = {} # {pkg: {module_labels}}
for vF in vRefFiles:
vModule = vF.parent.parent.name # e.g. "foundation-layout"
for vId in decl_ids(vF):
vPkg = package_of(vId)
vUpstream.setdefault(vPkg, set()).add(vId)
vUpstreamModules.setdefault(vPkg, set()).add(vModule)
vOurFiles = sorted(kRoot.glob("*/api/*.klib.api")) + sorted(kRoot.glob("*/*/api/*.klib.api"))
if not vOurFiles:
print("!! No local api/*.klib.api dumps — run `./gradlew apiDump` first.", file=sys.stderr)
# ============
# Load our dumps
vOurDumps = loadOurDumps()
if not vOurDumps:
print("!! No local api/*.klib.api dumps -- run `./gradlew apiDump` first.", file=sys.stderr)
return 1
vOurs = set().union(*vOurDumps.values())
vOursMirrored = {vId for vId in vOurs if vId.startswith(kMirroredPrefixes)}
# Fidelity baseline includes target-gated upstream decls: implementing one
# of those is coverage beyond our targets, not invented API.
vDivergent = vOursMirrored - vAllUpstream - vSkippedUnion
vOurs = set()
for vF in vOurFiles:
vOurs |= decl_ids(vF)
# ============
# Warnings — a target whose module has no local dump reports fake 0%
for vRepo, vModule in vMissingRefs:
print(f"!! upstream dump missing: {refCloneDir(vRepo) / vModule}/api/*.klib.api "
+ "-- re-run scripts/compose-fork/sync.sh (target skipped)", file=sys.stderr)
vNoDump = sorted({vDir for vDir in vOursDirByTarget.values() if vDir not in vOurDumps})
for vDir in vNoDump:
print(f"!! no local dump for {vDir} -- its rows undercount; run `./gradlew apiDump`", file=sys.stderr)
if vMissingRefs or vNoDump:
print(file=sys.stderr)
# Compute per-package stats.
vRows = []
for vPkg, vUpstreamSet in vUpstream.items():
vCovered = vUpstreamSet & vOurs
vExtra = {vId for vId in vOurs if package_of(vId) == vPkg} - vUpstreamSet
vPct = 100.0 * len(vCovered) / len(vUpstreamSet) if vUpstreamSet else 0.0
vRows.append((vPkg, len(vCovered), len(vUpstreamSet), vPct, len(vExtra),
",".join(sorted(vUpstreamModules[vPkg]))))
# Also count our extras in packages upstream doesn't have here.
vOurPkgs = {package_of(vId) for vId in vOurs}
for vPkg in vOurPkgs - set(vUpstream.keys()):
vExtra = sum(1 for vId in vOurs if package_of(vId) == vPkg)
vRows.append((vPkg, 0, 0, 0.0, vExtra, "(not in ref)"))
vRows.sort(key=lambda r: -r[2])
print(f"Official refs: {len(vRefFiles)} klib.api files")
print(f"Our dumps: {len(vOurFiles)} files, {len(vOurs)} androidx.compose.* decls")
vRepoNotes = ", ".join(f"{vRepo} @ {refHead(refCloneDir(vRepo))}"
for vRepo in dict.fromkeys(vT[0] for vT in kTargets))
print(f"Upstream: {len(vUpstreamByTarget)} modules, {len(vAllUpstream)} decls"
+ f" ({len(vSkippedUnion)} decls for targets we don't build excluded) ({vRepoNotes})")
print(f"Ours: {len(vOurDumps)} dumps, {len(vOurs)} decls ({len(vOursMirrored)} under mirrored prefixes)")
print()
vFmt = " {:<52} {:>6} / {:>5} {:>5}% {} extras={:<4} {}"
print(vFmt.format("package", "ours", "up", "cov", " " * 20, "extra", "upstream module"))
print(" " + "-" * 130)
vTotalCov = 0
vTotalUp = 0
for vPkg, vC, vU, vPct, vE, vMod in vRows:
if vU == 0 and not kShowAll:
# ============
# Attribute each divergent decl to the target owning its package
vPkgOwner = {} # {pkg: target-label with the most decls in pkg}
vPkgCount = {}
for vLabel, vIds in vUpstreamByTarget.items():
for vId in vIds:
vPkg = packageOf(vId)
vCount = vPkgCount.get((vPkg, vLabel), 0) + 1
vPkgCount[(vPkg, vLabel)] = vCount
if vCount > vPkgCount.get((vPkg, vPkgOwner.get(vPkg)), 0):
vPkgOwner[vPkg] = vLabel
vExtrasByTarget = {}
vOrphanExtras = set() # divergent decls in packages NO upstream target has
for vId in vDivergent:
vOwner = vPkgOwner.get(packageOf(vId))
if vOwner is None:
vOrphanExtras.add(vId)
else:
vExtrasByTarget.setdefault(vOwner, set()).add(vId)
# ============
# Per-module table (kTargets order)
vFmt = " {:<38} {:<28} {:>6} / {:>6} {:>5} {} {:>6}"
print(vFmt.format("upstream module", "ours", "cover", "total", "cov", " " * 20, "extras"))
print(" " + "-" * 118)
vTotalCov, vTotalUp = 0, 0
for vRepo, vModule, vOursDir in kTargets:
vLabel = targetLabel(vModule)
if vLabel not in vUpstreamByTarget:
continue
vTotalCov += vC
vTotalUp += vU
print(vFmt.format(vPkg, vC, vU, f"{vPct:.0f}", bar(vPct), vE, vMod))
if vTotalUp:
vOverall = 100.0 * vTotalCov / vTotalUp
vUp = vUpstreamByTarget[vLabel]
vCov = len(vUp & vOurs)
vPct = 100.0 * vCov / len(vUp) if vUp else 0.0
vTotalCov += vCov
vTotalUp += len(vUp)
vPctStr = f"{vPct:.0f}%" if vUp else "-"
vOursNote = vOursDir.rsplit("/", 1)[-1] + (" (NO DUMP)" if vOursDir in vNoDump else "")
print(vFmt.format(vModule, vOursNote, vCov, len(vUp), vPctStr,
bar(vPct), len(vExtrasByTarget.get(vLabel, ()))))
print(" " + "-" * 118)
vOverall = 100.0 * vTotalCov / vTotalUp if vTotalUp else 0.0
print(vFmt.format("TOTAL", "", vTotalCov, vTotalUp, f"{vOverall:.0f}%", bar(vOverall), len(vDivergent)))
print()
# ============
# Missing decl listing (--missing [MODULE])
if vArgs.missing is not None:
if vArgs.missing and vArgs.missing not in vUpstreamByTarget:
print(f"!! unknown module label '{vArgs.missing}' -- valid: "
+ " ".join(vUpstreamByTarget), file=sys.stderr)
return 1
for vRepo, vModule, vOursDir in kTargets:
vLabel = targetLabel(vModule)
if vLabel not in vUpstreamByTarget or (vArgs.missing and vLabel != vArgs.missing):
continue
vMissing = sorted(vUpstreamByTarget[vLabel] - vOurs)
if not vMissing:
continue
print(f"Missing from {vLabel} -- upstream has, we don't ({len(vMissing)} decls):")
vByPkg = {}
for vId in vMissing:
vByPkg.setdefault(packageOf(vId), []).append(vId)
for vPkg in sorted(vByPkg):
print(f" [{vPkg}] ({len(vByPkg[vPkg])})")
for vId in vByPkg[vPkg]:
print(f" {vId.split('/', 1)[1]}")
print()
# ============
# Per-package table (--packages)
if vArgs.packages:
vUpByPkg = {} # {pkg: {decl-ids}}
vPkgModules = {} # {pkg: {target-labels}}
for vLabel, vIds in vUpstreamByTarget.items():
for vId in vIds:
vUpByPkg.setdefault(packageOf(vId), set()).add(vId)
vPkgModules.setdefault(packageOf(vId), set()).add(vLabel)
vRows = []
for vPkg, vUp in vUpByPkg.items():
vCov = len(vUp & vOurs)
vExtra = sum(1 for vId in vDivergent if packageOf(vId) == vPkg)
vRows.append((vPkg, vCov, len(vUp), vExtra, ",".join(sorted(vPkgModules[vPkg]))))
if vArgs.all:
for vPkg in {packageOf(vId) for vId in vOursMirrored} - set(vUpByPkg):
vExtra = sum(1 for vId in vOursMirrored if packageOf(vId) == vPkg)
vRows.append((vPkg, 0, 0, vExtra, "(not upstream)"))
vRows.sort(key=lambda vRow: -vRow[2])
vPkgFmt = " {:<52} {:>6} / {:>6} {:>5} {} {:>6} {}"
print(vPkgFmt.format("package", "cover", "total", "cov", " " * 20, "extras", "upstream module"))
print(" " + "-" * 130)
print(vFmt.format("TOTAL (shown)", vTotalCov, vTotalUp, f"{vOverall:.0f}", bar(vOverall), "", ""))
vPkgCov = vPkgUp = 0
for vPkg, vCov, vUp, vExtra, vMods in vRows:
vPct = 100.0 * vCov / vUp if vUp else 0.0
vPkgCov += vCov
vPkgUp += vUp
print(vPkgFmt.format(vPkg, vCov, vUp, f"{vPct:.0f}%" if vUp else "-", bar(vPct), vExtra, vMods))
vPkgPct = 100.0 * vPkgCov / vPkgUp if vPkgUp else 0.0
print(" " + "-" * 130)
print(vPkgFmt.format("TOTAL", vPkgCov, vPkgUp, f"{vPkgPct:.0f}%", bar(vPkgPct), len(vDivergent), ""))
print()
# ============
# Fidelity: divergent decl listing (--extras) or one-line summary
if vArgs.extras:
print(f"Divergent -- under mirrored prefixes but not upstream ({len(vDivergent)} decls):")
vByPkg = {}
for vId in sorted(vDivergent):
vByPkg.setdefault(packageOf(vId), []).append(vId)
for vPkg in sorted(vByPkg):
vOwner = vPkgOwner.get(vPkg, "(package not upstream)")
print(f" [{vPkg}] ({len(vByPkg[vPkg])}) -> {vOwner}")
for vId in vByPkg[vPkg]:
print(f" {vId.split('/', 1)[1]}")
else:
vOrphanNote = f" ({len(vOrphanExtras)} in packages upstream lacks)" if vOrphanExtras else ""
print(f"Fidelity: {len(vDivergent)} decls under mirrored prefixes not found upstream{vOrphanNote}"
+ " -- run with --extras to list them.")
return 0
-103
View File
@@ -1,103 +0,0 @@
#!/usr/bin/env python3
# Compose-API fidelity check: diff this project's PUBLIC androidx.compose.* ABI
# against the official Compose klib ABI, flagging declarations we expose that
# upstream Compose does not (invented / divergent public surface).
#
# Inputs:
# - our dumps: <module>/api/<module>.klib.api (generated by `./gradlew apiDump`,
# Kotlin binary-compatibility-validator plugin)
# - official dumps: the JetBrains/compose-multiplatform-core clone's
# compose/**/api/*.klib.api (default: C:/Dev/cmp-ref)
#
# Usage: ./gradlew apiDump && python3 scripts/compose-fidelity-check.py [cmp-ref-dir]
#
# Both files use the same binary-compatibility-validator klib format, where each
# declaration carries a trailing `// <decl-id>|<mangle>` comment. We compare the
# <decl-id> path (normalised) as a set, so a name present in ours but not upstream
# is surfaced. NOTE: the reference clone is sparse (ui / foundation / animation /
# runtime) — material and desktop-only foundation (e.g. Scrollbar) have no upstream
# file here, so declarations in those areas show up as "not upstream" and are
# expected project surface, not bugs. Treat the output as candidates to review
# against CLAUDE.md's fidelity rules, not a hard gate.
import os
import sys
import re
from pathlib import Path
kRoot = Path(__file__).resolve().parent.parent
# cmp-ref clone: explicit arg, then $CMP_REF, then <repo>/../cmp-ref (where
# scripts/compose-fork/sync.sh puts it), then the old Windows default. Portable
# across Windows / macOS / Linux.
if len(sys.argv) > 1:
kRefDir = Path(sys.argv[1])
elif os.environ.get("CMP_REF"):
kRefDir = Path(os.environ["CMP_REF"])
elif (kRoot.parent / "cmp-ref").exists():
kRefDir = kRoot.parent / "cmp-ref"
else:
kRefDir = Path("C:/Dev/cmp-ref")
# decl-id is the text between "// " and the first "|". Strip accessor / ctor
# noise so a property and its getter collapse to one name.
_ACCESSOR = re.compile(r"\.<(get|set)-[^>]+>$")
def decl_ids(in_path):
vIds = set()
for vLine in in_path.read_text(encoding="utf-8", errors="ignore").splitlines():
vIdx = vLine.find("// ")
if vIdx < 0:
continue
vId = vLine[vIdx + 3:].split("|", 1)[0].strip()
if not vId.startswith("androidx.compose"):
continue
if "$stable" in vId: # Compose-compiler-generated stability props — not real API
continue
vId = _ACCESSOR.sub("", vId)
vId = re.sub(r"\.<init>.*$", "", vId) # constructors -> owning class
vIds.add(vId)
return vIds
def package_of(in_id):
# "androidx.compose.ui.unit/Density.toDpSize" -> "androidx.compose.ui.unit"
return in_id.split("/", 1)[0]
def main():
vOfficial = set()
vRefFiles = sorted(kRefDir.glob("compose/**/api/*.klib.api"))
for vF in vRefFiles:
vOfficial |= decl_ids(vF)
if not vOfficial:
print(f"!! No official klib.api found under {kRefDir} — clone JetBrains/compose-multiplatform-core there.")
return 1
vOurFiles = sorted(kRoot.glob("*/api/*.klib.api")) + sorted(kRoot.glob("*/*/api/*.klib.api"))
if not vOurFiles:
print("!! No local api/*.klib.api dumps — run `./gradlew apiDump` first.")
return 1
vOurs = {}
for vF in vOurFiles:
for vId in decl_ids(vF):
vOurs.setdefault(vId, vF.parent.parent.name)
vDivergent = sorted(set(vOurs) - vOfficial)
print(f"Official refs: {len(vRefFiles)} klib.api files, {len(vOfficial)} androidx.compose.* decls")
print(f"Our dumps: {len(vOurFiles)} files, {len(vOurs)} androidx.compose.* decls")
print(f"Not found in upstream Compose: {len(vDivergent)} decls\n")
vByPkg = {}
for vId in vDivergent:
vByPkg.setdefault(package_of(vId), []).append(vId)
for vPkg in sorted(vByPkg):
print(f" [{vPkg}] ({len(vByPkg[vPkg])})")
for vId in vByPkg[vPkg]:
print(f" {vId.split('/', 1)[1]}")
return 0
if __name__ == "__main__":
sys.exit(main())
+2 -11
View File
@@ -70,20 +70,11 @@ def main():
vSb.append("// MARK: MaterialSymbols (codepoints)")
vSb.append("// ==================")
vSb.append("")
vSb.append("/* GENERATED — do not edit by hand. Regenerate with")
vSb.append("/** GENERATED — do not edit by hand. Regenerate with")
vSb.append(" scripts/generate-material-symbols.py (downloads Google's .codepoints")
vSb.append(" file for the Material Symbols variable font and emits every glyph as a")
vSb.append(" const). The codepoints are identical across the outlined / rounded /")
vSb.append(" sharp styles, so these constants resolve against whichever style module")
vSb.append(" (:material-symbols:{outlined,rounded,sharp}) is installed.")
vSb.append("")
vSb.append(" Usage:")
vSb.append("")
vSb.append(" import com.compose.sdl.icons.MaterialSymbols")
vSb.append(" import com.compose.sdl.icons.material.symbols.outlined.MaterialSymbolsOutlined")
vSb.append("")
vSb.append(" MaterialSymbolsOutlined.install() // once at startup")
vSb.append(" Icon(codepoint = MaterialSymbols.Home, fontFamily = MaterialSymbolsOutlined.Family)")
vSb.append(" sharp styles.")
vSb.append("")
vSb.append(" Shipping all names is free: with -PsubsetIcons=true each app's font is")
vSb.append(f" hb-subset down to only the glyphs it references. {len(vEntries)} icons. */")
+67 -19
View File
@@ -1,15 +1,27 @@
// :material-symbols — codepoint constants + all three Material Symbols style
// composables (Outlined / Rounded / Sharp) in one module.
// composables (Outlined / Rounded / Sharp) in one module, for BOTH stacks:
//
// Previously each style lived in its own :material-symbols:outlined /
// :rounded / :sharp sub-module and apps opted in by listing the style
// dependencies they wanted. The three styles are 100% pure Kotlin
// (`object MaterialSymbolsOutlined { … }`) — no cinterops, no per-target
// actuals, no reason to be separate modules. Consolidated so apps just
// `implementation(project(":material-symbols"))` once and get all three;
// the app's data.kres Zip task decides which style FONT to actually
// bundle by scanning the app's Kotlin sources for MaterialSymbols<Style>
// call sites (see :apidemo / :demo build files).
// commonMain — the MaterialSymbols codepoints + the PUBLIC API
// (MaterialSymbols<Style> objects, axis defaults) so
// consumers' shared code and IDE analysis resolve it.
// nativeMain — actual renderer over the port's IconFont pipeline
// (:foundation IconFontIcon; IconFont itself handles the
// SDL3 / Skia renderer split — this module never sees it).
// jvmMain — actual renderer over Skiko directly (Typeface.makeClone
// per axes combination + TextLine on the native canvas);
// the upstream Font(variationSettings) route is unusable —
// its FontCache drops the settings from the cache key.
//
// commonMain compiles against the OFFICIAL Maven Compose artifacts (metadata
// + jvm resolve them); the NATIVE target configurations substitute ui /
// material3 for the port's project modules — the FULL-COMMONIZATION BRIDGE
// declared once in the root build (the Maven artifacts ship no mingw/linux
// klibs). The runtime is the official klib everywhere, never substituted.
//
// The app owns the font files: the data.kres Zip task bundles the styles an
// app's sources actually reference (native), and the app's
// jvmProcessResources stages the same fonts onto the JVM classpath at
// font/<file>.ttf (see :demo's build file).
//
// Publication artifactId (when set up): compose-desktop-material-symbols.
@@ -28,7 +40,34 @@ plugins {
// Skip mingwX64 on non-Windows hosts; see root build.gradle.kts.
val vHostSupportsMingw = rootProject.extra["vHostSupportsMingw"] as Boolean
// JVM stack version — the dev build matching the pinned COMPOSE_CORE_REF
// (scripts/compose-fork/compose.properties): byte-exact parity with the
// vendored sources, and (unlike published beta01) its desktop loadTypeface
// applies Font.variationSettings — required for the icon axes on JVM.
// material3 rides its own release train (same +dev build, different base).
// Gradle orders "+dev" BELOW the plain version, so force the core-repo
// groups on jvm configurations (mirrors :demo's forcing).
val vComposeJvmVersion = "1.12.0-beta01+dev4324"
val vComposeM3JvmVersion = "1.12.0-alpha03+dev4324"
val vComposeJvmForced = mapOf(
"org.jetbrains.compose.runtime" to vComposeJvmVersion,
"org.jetbrains.compose.ui" to vComposeJvmVersion,
"org.jetbrains.compose.foundation" to vComposeJvmVersion,
"org.jetbrains.compose.animation" to vComposeJvmVersion,
"org.jetbrains.compose.material" to vComposeJvmVersion,
"org.jetbrains.compose.material3" to vComposeM3JvmVersion,
)
configurations.configureEach {
if (name.startsWith("jvm")) {
resolutionStrategy.eachDependency {
vComposeJvmForced[requested.group]?.let { useVersion(it) }
}
}
}
kotlin {
jvm()
linuxArm64()
linuxX64()
macosArm64()
@@ -38,16 +77,25 @@ kotlin {
sourceSets {
commonMain.dependencies {
// The MaterialSymbols<Style> objects emit Icon(...) via
// com.compose.sdl.icons.IconFontIcon and reference
// MaterialIconAxisDefaults / IconDefaults — those live in
// :foundation (com.compose.sdl.icons package), which
// itself already api-depends on :ui.
// The official runtime klibs — the SAME artifact/version the port
// itself uses (:ui pins it); resolves for metadata, jvm AND native.
api("org.jetbrains.compose.runtime:runtime:1.11.1")
// Official Maven coordinates for the API surface (Modifier / Color
// / Dp / material3's LocalContentColor). Metadata + jvm resolve
// them from Maven; NATIVE configurations substitute them for
// project(":ui") / project(":material3") (root build bridge).
api("org.jetbrains.compose.ui:ui:$vComposeJvmVersion")
api("org.jetbrains.compose.material3:material3:$vComposeM3JvmVersion")
}
nativeMain.dependencies {
// The native actual draws via com.compose.sdl.icons.IconFontIcon —
// project code in :foundation (which api-depends on :ui).
api(project(":foundation"))
// Default tint follows upstream material3 Icon:
// `tint: Color = LocalContentColor.current` — needs material3's
// LocalContentColor (provided by Surface / button content / …).
api(project(":material3"))
}
jvmMain.dependencies {
// BasicText for the JVM actual — same version as :demo's jvm
// parity target.
api("org.jetbrains.compose.foundation:foundation:$vComposeJvmVersion")
}
}
}
@@ -0,0 +1,112 @@
@file:Suppress("PropertyName", "ConstPropertyName")
package com.compose.sdl.icons.material.symbols
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
// ==================
// MARK: MaterialSymbolsIcon — COMMON API
// ==================
/* The public Material Symbols surface, declared in commonMain so consumers'
shared code (and the IDE's common analysis) resolve it directly. Rendering
goes through the internal expect below:
native — the port's IconFont pipeline (:foundation IconFontIcon; the
SDL3 / Skia renderer split is handled inside :ui — this module
never sees it). Fonts come from data.kres.
jvm — upstream Compose Desktop: a classpath ResourceFont carrying the
variable-font axes via FontVariation.Settings + BasicText.
The app owns the font files for both stacks (data.kres Zip task on native,
jvm resources at font/<Style>.ttf on JVM — see :demo's build file). */
sealed class MaterialSymbolsIcon {
/* Font-family key the glyphs render under (also the install/cache key). */
abstract val Family: String
/* Where the app is expected to ship this style's variable font. */
abstract val DefaultResourcePath: String
@Composable
operator fun invoke(
icon: Int,
contentDescription: String? = null,
modifier: Modifier = Modifier,
tint: Color = Color.Unspecified,
size: Dp = 24.dp,
fill: Float = MaterialIconAxisDefaults.Fill,
weight: Int = MaterialIconAxisDefaults.Weight,
grade: Int = MaterialIconAxisDefaults.Grade,
opticalSize: Int = MaterialIconAxisDefaults.OpticalSize,
) {
renderMaterialSymbol(
style = this,
codepoint = icon,
contentDescription = contentDescription,
modifier = modifier,
// Default tint mirrors upstream material3 Icon: LocalContentColor.
tint = if (tint.isSpecified) tint else LocalContentColor.current,
size = size,
fill = fill,
weight = weight,
grade = grade,
opticalSize = opticalSize,
)
}
}
object MaterialSymbolsOutlined : MaterialSymbolsIcon() {
override val Family: String = "material-symbols-outlined"
override val DefaultResourcePath: String = "font/MaterialSymbolsOutlined.ttf"
}
object MaterialSymbolsSharp : MaterialSymbolsIcon() {
override val Family: String = "material-symbols-sharp"
override val DefaultResourcePath: String = "font/MaterialSymbolsSharp.ttf"
}
object MaterialSymbolsRounded : MaterialSymbolsIcon() {
override val Family: String = "material-symbols-rounded"
override val DefaultResourcePath: String = "font/MaterialSymbolsRounded.ttf"
}
/* Default positions of the four Material Symbols variable-font axes. */
object MaterialIconAxisDefaults {
const val Fill: Float = 0f
const val Weight: Int = 400
const val Grade: Int = 0
const val OpticalSize: Int = 24
}
/* Per-stack glyph renderer — receives the tint already resolved. */
@Composable
internal expect fun renderMaterialSymbol(
style: MaterialSymbolsIcon,
codepoint: Int,
contentDescription: String?,
modifier: Modifier,
tint: Color,
size: Dp,
fill: Float,
weight: Int,
grade: Int,
opticalSize: Int,
)
/* Converts a Unicode codepoint to a String, handling supplementary-plane
codepoints (≥0x10000) via UTF-16 surrogate pairs. */
internal fun codepointToString(codepoint: Int): String {
if (codepoint < 0x10000) return Char(codepoint).toString()
val adjusted = codepoint - 0x10000
val high = (0xD800 or (adjusted shr 10)).toChar()
val low = (0xDC00 or (adjusted and 0x3FF)).toChar()
return charArrayOf(high, low).concatToString()
}
@@ -0,0 +1,140 @@
package com.compose.sdl.icons.material.symbols
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import org.jetbrains.skia.Data
import org.jetbrains.skia.FontMgr
import org.jetbrains.skia.TextLine
import org.jetbrains.skia.Font as SkFont
import org.jetbrains.skia.FontVariation as SkFontVariation
import org.jetbrains.skia.Paint as SkPaint
import org.jetbrains.skia.Typeface as SkTypeface
// ==================
// MARK: renderMaterialSymbol — JVM actual (Skiko direct)
// ==================
/* Draws the glyph straight through Skiko (Compose Desktop's rendering
backend): the style's variable font is loaded once, Typeface.makeClone
instantiates the FILL / wght / GRAD / opsz axes per combination, and the
glyph is drawn as a TextLine on the native Skia canvas.
Deliberately NOT the upstream Font(variationSettings) + BasicText route:
the published desktop artifacts ignore variationSettings at typeface load,
and even where they don't, FontCache keys typefaces by identity/weight/
style WITHOUT the variation settings — every axes combination of the same
resource collapses onto the first one loaded. Skiko-direct sidesteps both.
The app supplies the font on the JVM classpath at the style's
DefaultResourcePath (see :demo's jvmProcessResources wiring). */
private val baseTypefaces = HashMap<String, SkTypeface?>()
private val variantTypefaces = HashMap<String, SkTypeface>()
/* The style's font as loaded from the classpath (no axes applied), null when
the app didn't bundle it — warned once, icon renders blank. */
private fun baseTypeface(style: MaterialSymbolsIcon): SkTypeface? {
if (style.Family in baseTypefaces) return baseTypefaces[style.Family]
val bytes = MaterialSymbolsIcon::class.java.classLoader
.getResourceAsStream(style.DefaultResourcePath)?.use { it.readBytes() }
val typeface = bytes?.let { FontMgr.default.makeFromData(Data.makeFromBytes(it)) }
if (typeface == null) {
println(
"${style.Family}: font missing at \"${style.DefaultResourcePath}\" on the JVM classpath. " +
"Add the font to your app's jvm resources."
)
}
baseTypefaces[style.Family] = typeface
return typeface
}
/* One clone per (style, axes) combination; the default axes use the font's
own default position, no clone needed. Fill is quantised to 1/100 steps so
ANIMATED fill values (arbitrary floats every frame) hit a bounded set of
cached clones instead of growing the cache forever. */
private fun variantTypeface(
style: MaterialSymbolsIcon,
fill: Float,
weight: Int,
grade: Int,
opticalSize: Int,
): SkTypeface? {
val base = baseTypeface(style) ?: return null
@Suppress("NAME_SHADOWING")
val fill = kotlin.math.round(fill * 100f) / 100f
if (fill == MaterialIconAxisDefaults.Fill &&
weight == MaterialIconAxisDefaults.Weight &&
grade == MaterialIconAxisDefaults.Grade &&
opticalSize == MaterialIconAxisDefaults.OpticalSize
) return base
val key = "${style.Family}/$fill/$weight/$grade/$opticalSize"
return variantTypefaces.getOrPut(key) {
base.makeClone(
arrayOf(
SkFontVariation("FILL", fill),
SkFontVariation("wght", weight.toFloat()),
SkFontVariation("GRAD", grade.toFloat()),
SkFontVariation("opsz", opticalSize.toFloat()),
)
)
}
}
@Composable
internal actual fun renderMaterialSymbol(
style: MaterialSymbolsIcon,
codepoint: Int,
contentDescription: String?,
modifier: Modifier,
tint: Color,
size: Dp,
fill: Float,
weight: Int,
grade: Int,
opticalSize: Int,
) {
val semanticsModifier =
if (contentDescription != null) {
Modifier.semantics {
this.contentDescription = contentDescription
this.role = Role.Image
}
} else Modifier
Box(
modifier
.size(size)
.then(semanticsModifier)
.drawWithCache {
val typeface = variantTypeface(style, fill, weight, grade, opticalSize)
if (typeface == null) {
onDrawBehind {}
} else {
val skFont = SkFont(typeface, this.size.height)
val line = TextLine.make(codepointToString(codepoint), skFont)
val paint = SkPaint().apply {
color = tint.toArgb()
isAntiAlias = true
}
// Center the glyph: x from the measured advance, baseline
// from the line's ascent/descent (ascent is negative).
val x = (this.size.width - line.width) / 2f
val y = this.size.height / 2f - (line.ascent + line.descent) / 2f
onDrawBehind {
drawIntoCanvas { it.nativeCanvas.drawTextLine(line, x, y, paint) }
}
}
}
)
}
@@ -1,112 +0,0 @@
@file:Suppress("PropertyName", "ConstPropertyName")
package com.compose.sdl.icons.material.symbols
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.unit.Dp
import com.compose.sdl.icons.IconDefaults
import com.compose.sdl.icons.IconFont
import com.compose.sdl.icons.IconFontIcon
import com.compose.sdl.loadComposeResourceBytes
import com.compose.sdl.text.TextRendererCapabilities
sealed class MaterialSymbolsIcon {
abstract val Family: String
abstract val DefaultResourcePath: String
private var isInstalled = false
internal fun install(resourcePath: String = DefaultResourcePath): Boolean {
if (isInstalled) return true
val vBytes = loadComposeResourceBytes(resourcePath)
if (vBytes == null) {
println(
"$Family: font missing at \"$resourcePath\" in data.kres. " +
"Add the font to your app's resources."
)
return false
}
IconFont.registerIcon(Family, vBytes)
isInstalled = true
if (TextRendererCapabilities.supportsFontVariations == false) {
println(
"$Family: the active text renderer does not support variable-font axes. " +
"Icons will render at the font's default position (wght=${MaterialIconAxisDefaults.Weight}, FILL=${MaterialIconAxisDefaults.Fill}, GRAD=${MaterialIconAxisDefaults.Grade}, opsz=${MaterialIconAxisDefaults.OpticalSize}). " +
"fill / weight / grade / opticalSize parameters are ignored."
)
}
return true
}
@Composable
operator fun invoke(
icon: Int,
contentDescription: String? = null,
modifier: Modifier = Modifier,
tint: Color = Color.Unspecified,
size: Dp = IconDefaults.DefaultIconSize,
fill: Float = MaterialIconAxisDefaults.Fill,
weight: Int = MaterialIconAxisDefaults.Weight,
grade: Int = MaterialIconAxisDefaults.Grade,
opticalSize: Int = MaterialIconAxisDefaults.OpticalSize,
) {
install()
IconFontIcon(
codepoint = icon,
fontFamily = Family,
contentDescription = contentDescription,
modifier = modifier,
tint = if (tint.isSpecified) tint else LocalContentColor.current,
size = size,
fontVariationSettings = materialIconAxes(fill, weight, grade, opticalSize),
)
}
}
object MaterialSymbolsOutlined : MaterialSymbolsIcon() {
override val Family: String = "material-symbols-outlined"
override val DefaultResourcePath: String = "font/MaterialSymbolsOutlined.ttf"
}
object MaterialSymbolsSharp : MaterialSymbolsIcon() {
override val Family: String = "material-symbols-sharp"
override val DefaultResourcePath: String = "font/MaterialSymbolsSharp.ttf"
}
object MaterialSymbolsRounded : MaterialSymbolsIcon() {
override val Family: String = "material-symbols-rounded"
override val DefaultResourcePath: String = "font/MaterialSymbolsRounded.ttf"
}
object MaterialIconAxisDefaults {
const val Fill: Float = 0f
const val Weight: Int = 400
const val Grade: Int = 0
const val OpticalSize: Int = 24
}
private fun materialIconAxes(
fill: Float = MaterialIconAxisDefaults.Fill,
weight: Int = MaterialIconAxisDefaults.Weight,
grade: Int = MaterialIconAxisDefaults.Grade,
opticalSize: Int = MaterialIconAxisDefaults.OpticalSize,
): List<FontVariation.Setting> {
if (fill == MaterialIconAxisDefaults.Fill &&
weight == MaterialIconAxisDefaults.Weight &&
grade == MaterialIconAxisDefaults.Grade &&
opticalSize == MaterialIconAxisDefaults.OpticalSize
) return emptyList()
return listOf(
FontVariation.Setting("FILL", fill),
FontVariation.Setting("wght", weight.toFloat()),
FontVariation.Setting("GRAD", grade.toFloat()),
FontVariation.Setting("opsz", opticalSize.toFloat()),
)
}
@@ -0,0 +1,92 @@
package com.compose.sdl.icons.material.symbols
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.unit.Dp
import com.compose.sdl.icons.IconFont
import com.compose.sdl.icons.IconFontIcon
import com.compose.sdl.loadComposeResourceBytes
import com.compose.sdl.text.TextRendererCapabilities
// ==================
// MARK: renderMaterialSymbol — native actual (port IconFont pipeline)
// ==================
/* Draws through :foundation's IconFontIcon; IconFont / the text renderers in
:ui handle the SDL3-vs-Skia split, so this module stays renderer-unaware.
The style's variable font is loaded once per family from data.kres. */
private val installedFamilies = mutableSetOf<String>()
/* Registers the style's font with IconFont on first use. Returns false (and
warns) when the app's data.kres doesn't carry the font — the icon then
renders as blank, same as before. */
private fun ensureInstalled(style: MaterialSymbolsIcon): Boolean {
if (style.Family in installedFamilies) return true
val bytes = loadComposeResourceBytes(style.DefaultResourcePath)
if (bytes == null) {
println(
"${style.Family}: font missing at \"${style.DefaultResourcePath}\" in data.kres. " +
"Add the font to your app's resources."
)
return false
}
IconFont.registerIcon(style.Family, bytes)
installedFamilies.add(style.Family)
if (TextRendererCapabilities.supportsFontVariations == false) {
println(
"${style.Family}: the active text renderer does not support variable-font axes. " +
"Icons will render at the font's default position (wght=${MaterialIconAxisDefaults.Weight}, FILL=${MaterialIconAxisDefaults.Fill}, GRAD=${MaterialIconAxisDefaults.Grade}, opsz=${MaterialIconAxisDefaults.OpticalSize}). " +
"fill / weight / grade / opticalSize parameters are ignored."
)
}
return true
}
@Composable
internal actual fun renderMaterialSymbol(
style: MaterialSymbolsIcon,
codepoint: Int,
contentDescription: String?,
modifier: Modifier,
tint: Color,
size: Dp,
fill: Float,
weight: Int,
grade: Int,
opticalSize: Int,
) {
ensureInstalled(style)
IconFontIcon(
codepoint = codepoint,
fontFamily = style.Family,
contentDescription = contentDescription,
modifier = modifier,
tint = tint,
size = size,
fontVariationSettings = materialIconAxes(fill, weight, grade, opticalSize),
)
}
/* Axis list for IconFontIcon — empty at the defaults so the renderers skip
the variable-font instancing path entirely. */
private fun materialIconAxes(
fill: Float,
weight: Int,
grade: Int,
opticalSize: Int,
): List<FontVariation.Setting> {
if (fill == MaterialIconAxisDefaults.Fill &&
weight == MaterialIconAxisDefaults.Weight &&
grade == MaterialIconAxisDefaults.Grade &&
opticalSize == MaterialIconAxisDefaults.OpticalSize
) return emptyList()
return listOf(
FontVariation.Setting("FILL", fill),
FontVariation.Setting("wght", weight.toFloat()),
FontVariation.Setting("GRAD", grade.toFloat()),
FontVariation.Setting("opsz", opticalSize.toFloat()),
)
}