mirror of
https://github.com/bitsycore/compose-desktop-native.git
synced 2026-10-05 18:57:26 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c09109e6e | ||
|
|
5804128e1d | ||
|
|
04d1df58de | ||
|
|
a87ff2d7f6 |
@@ -813,6 +813,18 @@ Two residuals, neither a text-metrics bug:
|
||||
(0.29% at +0px vs 2.13% at +1px), where before +2px halved the error. The residual 0.29%
|
||||
is rasterizer AA.
|
||||
|
||||
- **Icon-font glyphs rendered LOW - ✅ FIXED.** Material Symbols sat 2-4px lower on native
|
||||
than on jvm, the offset scaling with icon size (~13% of it). Cause: the JVM actual centres
|
||||
the glyph explicitly (`y = height/2 - (ascent+descent)/2`), while `IconFontIcon` placed
|
||||
`IconText` in a Box at the DEFAULT TopStart alignment - and the glyph's natural text box is
|
||||
taller than the nominal icon size. Fixed by centring, with
|
||||
`wrapContentSize(unbounded = true)` so the text measures at its natural size first:
|
||||
`Modifier.size()` imposes FIXED constraints, so without it nothing overflows and
|
||||
contentAlignment is a no-op (a first attempt without it changed literally nothing).
|
||||
gear/download/lock are now pixel-exact; the eye is 1px off from half-pixel rounding (odd
|
||||
glyph height centred in an even box; jvm draws at a float y, native placement is integer).
|
||||
Whole-window >32/255 drift 0.29% -> 0.17%.
|
||||
|
||||
- **Missing glyph in the session header.** The leading icon next to "Untitled session"
|
||||
renders as a `.notdef` TOFU on jvm and as NOTHING on native - so the glyph is absent from
|
||||
the subsetted Material Symbols font on both, and the two stacks just disagree on how to
|
||||
|
||||
+15
-1
@@ -2,7 +2,9 @@ package com.compose.sdl.icons
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontVariation
|
||||
@@ -36,9 +38,21 @@ fun IconFontIcon(
|
||||
size: Dp = IconDefaults.DefaultIconSize,
|
||||
fontVariationSettings: List<FontVariation.Setting>? = null,
|
||||
) {
|
||||
Box(modifier = modifier.size(size)) {
|
||||
// CENTER the glyph in the icon box. The glyph's natural text box is TALLER than
|
||||
// `size` - its height is the font's ascent+descent, and Material Symbols' em box
|
||||
// overshoots the nominal icon size - so the glyph used to render ~13% of the icon
|
||||
// size too LOW. This mirrors the JVM actual's explicit baseline math
|
||||
// (`y = height/2 - (ascent+descent)/2` in MaterialSymbolsIcon.jvm.kt): IconText
|
||||
// sets no lineHeight, so its box height IS ascent+descent and centering that box
|
||||
// centers the same span the JVM centers.
|
||||
//
|
||||
// `wrapContentSize(unbounded = true)` is load-bearing: Modifier.size() hands the
|
||||
// child FIXED constraints, so without it the text is measured at exactly `size`,
|
||||
// nothing overflows, and contentAlignment has nothing to align.
|
||||
Box(modifier = modifier.size(size), contentAlignment = Alignment.Center) {
|
||||
com.compose.sdl.text.IconText(
|
||||
text = codepointToString(codepoint),
|
||||
modifier = Modifier.wrapContentSize(Alignment.Center, unbounded = true),
|
||||
fontFamily = fontFamily,
|
||||
color = tint,
|
||||
fontSize = size.value.sp,
|
||||
|
||||
+18
-1
@@ -10,6 +10,7 @@ import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.PathSensitive
|
||||
import org.gradle.api.tasks.PathSensitivity
|
||||
import org.gradle.work.DisableCachingByDefault
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
import java.util.zip.Inflater
|
||||
@@ -91,10 +92,23 @@ internal fun installAppIcon(inProject: Project, inExt: ComposeDesktopNativeExten
|
||||
}
|
||||
// Link the object last: the mingw link tasks must run windres first (the
|
||||
// path is referenced via linkerOpts, added reflectively in NativeApplication).
|
||||
//
|
||||
// `inputs.files(task)` rather than a bare `dependsOn`: dependsOn only orders
|
||||
// the two, it does NOT make the object's CONTENT part of the link's up-to-date
|
||||
// check. The .o path reaches the linker as a linkerOpts STRING, which is an
|
||||
// input only as a string - so editing an icon PNG rebuilt the .ico and the .o
|
||||
// and then left the link UP-TO-DATE, silently shipping an .exe with the OLD
|
||||
// icon embedded. Declaring the task's outputs as link inputs carries the
|
||||
// dependency AND invalidates on content.
|
||||
inProject.tasks.matching {
|
||||
it.name == "linkDebugExecutableMingwX64" || it.name == "linkReleaseExecutableMingwX64" ||
|
||||
it.name == "runDebugExecutableMingwX64" || it.name == "runReleaseExecutableMingwX64"
|
||||
}.configureEach { it.dependsOn(vWindresTask) }
|
||||
}.configureEach { task ->
|
||||
task.inputs.files(vWindresTask)
|
||||
.withPropertyName("composeNativeAppIconObject")
|
||||
.withPathSensitivity(PathSensitivity.NONE)
|
||||
task.dependsOn(vWindresTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +116,7 @@ internal fun installAppIcon(inProject: Project, inExt: ComposeDesktopNativeExten
|
||||
// MARK: Tasks
|
||||
// ==================
|
||||
|
||||
@DisableCachingByDefault(because = "trivial PNG decode; caching would cost more than it saves")
|
||||
abstract class GenerateRgbaIconsTask : DefaultTask() {
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.NONE)
|
||||
@@ -124,6 +139,7 @@ abstract class GenerateRgbaIconsTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
@DisableCachingByDefault(because = "trivial byte concatenation of already-local PNGs")
|
||||
abstract class GenerateIcoTask : DefaultTask() {
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.NONE)
|
||||
@@ -140,6 +156,7 @@ abstract class GenerateIcoTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
@DisableCachingByDefault(because = "shells out to the host windres; not portable across machines")
|
||||
abstract class CompileWindowsIconResourceTask : DefaultTask() {
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.NONE)
|
||||
|
||||
+21
@@ -88,6 +88,26 @@ private fun registerDataKresTasks(project: Project) {
|
||||
val compress = project.providers.gradleProperty("compressResources").orNull?.toBoolean() ?: false
|
||||
task.entryCompression = if (compress) ZipEntryCompression.DEFLATED else ZipEntryCompression.STORED
|
||||
task.duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
// MULTI-MODULE: source from Kotlin's per-target resource AGGREGATE, which
|
||||
// already merges this project's OWN composeResources with those of every
|
||||
// dependency module (each under its own `composeResources/<package>/`).
|
||||
// Reading the project's `preparedResources/` instead - as this did - silently
|
||||
// dropped every library module's resources from data.kres, and forced a
|
||||
// single `resPackage` onto the lot, which is wrong the moment more than one
|
||||
// module contributes.
|
||||
//
|
||||
// The aggregate directory is already rooted at `composeResources/`, so it is
|
||||
// copied in with NO `into()` remapping.
|
||||
val aggregateName = "${target.replaceFirstChar { it.lowercase() }}AggregateResources"
|
||||
if (aggregateName in project.tasks.names) {
|
||||
val aggregated = project.layout.buildDirectory.dir(
|
||||
"kotlin-multiplatform-resources/aggregated-resources/${target.replaceFirstChar { it.lowercase() }}"
|
||||
)
|
||||
task.from(aggregated)
|
||||
task.dependsOn(project.tasks.named(aggregateName))
|
||||
} else {
|
||||
// Fallback for Kotlin versions without the resources-aggregation tasks:
|
||||
// this project's own resources only (the pre-existing behaviour).
|
||||
val resPackage = project.resolveResourcePackage()
|
||||
val prepareNames = sourceSets
|
||||
.map { "prepareComposeResourcesTaskFor" + it.replaceFirstChar { c -> c.uppercase() } }
|
||||
@@ -102,6 +122,7 @@ private fun registerDataKresTasks(project: Project) {
|
||||
task.dependsOn(project.tasks.named(prepareName))
|
||||
}
|
||||
}
|
||||
}
|
||||
project.tasks.matching { it.name == linkName }.configureEach { it.dependsOn(zipTask) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user