Skip to content

Commit 8c80141

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents cf7cd09 + 1bd3058 commit 8c80141

File tree

20 files changed

+239
-29
lines changed

20 files changed

+239
-29
lines changed

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Create
4+
url: https://youtrack.jetbrains.com/newIssue?project=SKIKO
5+
about: Please report new issues to the JetBrains YouTrack

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ jobs:
120120
./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pskiko.test.onci=true :skiko:tvosX64Test
121121
# tvosSimulatorArm64Test will build the binary but the tests will be skipped due to X64 host machine
122122
./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pskiko.test.onci=true :skiko:tvosSimulatorArm64Test
123-
- uses: actions/upload-artifact@v2
123+
- uses: actions/upload-artifact@v4
124124
if: always()
125125
with:
126126
name: test-reports-macos

skiko/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ kotlin.code.style=official
22
deploy.version=0.0.0
33

44

5-
dependencies.skia=m126-1d69d9b-2
5+
dependencies.skia=m126-d2aaacc35d-4
66

77
# you can override general skia dependencies by passing platform-specific property:
88
# dependencies.skia.android-arm64

skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import javax.swing.JPanel
1818
import javax.swing.SwingUtilities
1919
import javax.swing.SwingUtilities.isEventDispatchThread
2020
import javax.swing.UIManager
21+
import javax.swing.event.AncestorEvent
22+
import javax.swing.event.AncestorListener
23+
import kotlin.math.ceil
24+
import kotlin.math.floor
2125

2226
actual open class SkiaLayer internal constructor(
2327
externalAccessibleFactory: ((Component) -> Accessible)? = null,
@@ -133,6 +137,16 @@ actual open class SkiaLayer internal constructor(
133137
@Suppress("LeakingThis")
134138
add(backedLayer)
135139

140+
addAncestorListener(object : AncestorListener {
141+
override fun ancestorAdded(event: AncestorEvent?) = Unit
142+
143+
override fun ancestorRemoved(event: AncestorEvent?) = Unit
144+
145+
override fun ancestorMoved(event: AncestorEvent?) {
146+
revalidate()
147+
}
148+
})
149+
136150
backedLayer.addHierarchyListener {
137151
if (it.changeFlags and HierarchyEvent.SHOWING_CHANGED.toLong() != 0L) {
138152
checkShowing()
@@ -143,7 +157,7 @@ actual open class SkiaLayer internal constructor(
143157
addPropertyChangeListener("graphicsContextScaleTransform") {
144158
Logger.debug { "graphicsContextScaleTransform changed for $this" }
145159
latestReceivedGraphicsContextScaleTransform = it.newValue as AffineTransform
146-
redrawer?.syncSize()
160+
revalidate()
147161
notifyChange(PropertyKind.ContentScale)
148162

149163
// Workaround for JBR-5259
@@ -191,7 +205,7 @@ actual open class SkiaLayer internal constructor(
191205
redrawer?.setVisible(isShowing)
192206
}
193207
if (isShowing) {
194-
redrawer?.syncSize()
208+
redrawer?.syncBounds()
195209
repaint()
196210
}
197211
}
@@ -252,7 +266,7 @@ actual open class SkiaLayer internal constructor(
252266
private val redrawerManager = RedrawerManager<Redrawer>(properties.renderApi) { renderApi, oldRedrawer ->
253267
oldRedrawer?.dispose()
254268
val newRedrawer = renderFactory.createRedrawer(this, renderApi, analytics, properties)
255-
newRedrawer.syncSize()
269+
newRedrawer.syncBounds()
256270
newRedrawer
257271
}
258272

@@ -316,12 +330,16 @@ actual open class SkiaLayer internal constructor(
316330

317331
override fun doLayout() {
318332
Logger.debug { "doLayout on $this" }
319-
backedLayer.setBounds(0, 0, roundSize(width), roundSize(height))
333+
backedLayer.setBounds(
334+
0,
335+
0,
336+
adjustSizeToContentScale(contentScale, width),
337+
adjustSizeToContentScale(contentScale, height)
338+
)
320339
backedLayer.validate()
321-
redrawer?.syncSize()
340+
redrawer?.syncBounds()
322341
}
323342

324-
325343
override fun paint(g: java.awt.Graphics) {
326344
Logger.debug { "Paint called on: $this" }
327345
checkContentScale()
@@ -338,7 +356,7 @@ actual open class SkiaLayer internal constructor(
338356
// Please note that calling redraw during layout might break software renderers,
339357
// so applying this fix only for Direct3D case.
340358
if (renderApi == GraphicsApi.DIRECT3D && isShowing) {
341-
redrawer?.syncSize()
359+
redrawer?.syncBounds()
342360
tryRedrawImmediately()
343361
}
344362
}
@@ -579,18 +597,6 @@ actual open class SkiaLayer internal constructor(
579597
}
580598
}
581599

582-
private fun roundSize(value: Int): Int {
583-
var rounded = value * contentScale
584-
val diff = rounded - rounded.toInt()
585-
// We check values close to 0.5 and edit the size to avoid white lines glitch
586-
if (diff > 0.4f && diff < 0.6f) {
587-
rounded = value + 1f
588-
} else {
589-
rounded = value.toFloat()
590-
}
591-
return rounded.toInt()
592-
}
593-
594600
fun requestNativeFocusOnAccessible(accessible: Accessible?) {
595601
backedLayer.requestNativeFocusOnAccessible(accessible)
596602
}
@@ -637,3 +643,28 @@ internal fun Canvas.clipRectBy(rectangle: ClipRectangle, scale: Float) {
637643
true
638644
)
639645
}
646+
647+
// TODO Recheck this method validity in 2 cases - full Window content, and a Panel content
648+
// issue: https://youtrack.jetbrains.com/issue/CMP-5447/Window-white-line-on-the-bottom-before-resizing
649+
// suggestions: https://github.com/JetBrains/skiko/pull/988#discussion_r1763219300
650+
// possible issues:
651+
// - isn't obvious why 0.4/0.6 is used
652+
// - increasing it by one, we avoid 1px white line, but we cut the content by 1px
653+
// - it probably doesn't work correctly in a Panel content case - we don't need to adjust in this case
654+
/**
655+
* Increases the value of width/height by one if necessary,
656+
* to avoid 1px white line between Canvas and the bounding window.
657+
* 1px white line appears when users resizes the window:
658+
* - it is resized in Px (125, 126, 127,...)
659+
* - the canvas is resized in Points (with 1.25 scale, it will be 100, 100, 101)
660+
* during converting Int AWT points to actual screen pixels.
661+
*/
662+
private fun adjustSizeToContentScale(contentScale: Float, value: Int): Int {
663+
val scaled = value * contentScale
664+
val diff = scaled - floor(scaled)
665+
return if (diff > 0.4f && diff < 0.6f) {
666+
value + 1
667+
} else {
668+
value
669+
}
670+
}

skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ internal class MetalRedrawer(
169169
}
170170
}
171171

172-
override fun syncSize() = synchronized(drawLock) {
172+
override fun syncBounds() = synchronized(drawLock) {
173173
check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
174174
val rootPane = getRootPane(layer)
175175
val globalPosition = convertPoint(layer.backedLayer, 0, 0, rootPane)

skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import java.awt.BorderLayout
2828
import java.awt.Color
2929
import java.awt.Dimension
3030
import java.awt.event.*
31+
import javax.swing.Box
3132
import javax.swing.JFrame
3233
import javax.swing.JLayeredPane
3334
import javax.swing.JPanel
@@ -261,6 +262,48 @@ class SkiaLayerTest {
261262
}
262263
}
263264

265+
@Test
266+
fun `move without redrawing`() = uiTest {
267+
val window = JFrame()
268+
val layer = SkiaLayer(
269+
properties = SkiaLayerProperties(renderApi = renderApi)
270+
)
271+
272+
layer.renderDelegate = object : SkikoRenderDelegate {
273+
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
274+
canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), Paint().apply {
275+
color = Color.RED.rgb
276+
})
277+
}
278+
}
279+
layer.size = Dimension(100, 100)
280+
val box = Box.createVerticalBox().apply {
281+
add(layer)
282+
}
283+
box.setBounds(0, 0, 100, 100)
284+
285+
try {
286+
val panel = JLayeredPane()
287+
panel.add(box)
288+
window.contentPane.add(panel)
289+
window.setLocation(200, 200)
290+
window.size = Dimension(200, 200)
291+
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
292+
window.isUndecorated = true
293+
window.isVisible = true
294+
295+
delay(1000)
296+
screenshots.assert(window.bounds, "frame1")
297+
298+
box.setBounds(100, 0, 100, 100)
299+
delay(1000)
300+
screenshots.assert(window.bounds, "frame2")
301+
} finally {
302+
layer.dispose()
303+
window.close()
304+
}
305+
}
306+
264307
@Test
265308
fun `resize window`() = uiTest {
266309
val window = UiTestWindow()
@@ -698,6 +741,8 @@ class SkiaLayerTest {
698741
// SOFTWARE_COMPAT fails because it's just too slow
699742
excludeRenderApis = listOf(GraphicsApi.SOFTWARE_COMPAT)
700743
) {
744+
assumeTrue(hostOs == OS.MacOS) // since the test has 'metal' in its name (it is flaky on Windows)
745+
701746
val renderTimes = mutableListOf<Long>()
702747
val renderer = object: SkikoRenderDelegate {
703748
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {

skiko/src/commonMain/kotlin/org/jetbrains/skia/paragraph/StrutStyle.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ class StrutStyle internal constructor(ptr: NativePointer) : Managed(ptr, _Finali
211211
return this
212212
}
213213

214+
@Deprecated("Replaced by topRatio")
214215
var isHalfLeading: Boolean
215216
get() = try {
216217
Stats.onNativeCall()
@@ -222,6 +223,8 @@ class StrutStyle internal constructor(ptr: NativePointer) : Managed(ptr, _Finali
222223
setHalfLeading(value)
223224
}
224225

226+
// Same as topRatio = halfLeading ? 0.5f : -1.0f
227+
@Deprecated("Replaced by topRatio")
225228
fun setHalfLeading(value: Boolean): StrutStyle {
226229
try {
227230
Stats.onNativeCall()
@@ -232,6 +235,29 @@ class StrutStyle internal constructor(ptr: NativePointer) : Managed(ptr, _Finali
232235
return this
233236
}
234237

238+
// [0..1]: the ratio of ascent to ascent+descent
239+
// -1: proportional to the ascent/descent
240+
var topRatio: Float
241+
get() = try {
242+
Stats.onNativeCall()
243+
StrutStyle_nGetTopRatio(_ptr)
244+
} finally {
245+
reachabilityBarrier(this)
246+
}
247+
set(value) {
248+
setTopRatio(value)
249+
}
250+
251+
fun setTopRatio(topRatio: Float): StrutStyle {
252+
try {
253+
Stats.onNativeCall()
254+
StrutStyle_nSetTopRatio(_ptr, topRatio)
255+
} finally {
256+
reachabilityBarrier(this)
257+
}
258+
return this
259+
}
260+
235261
private object _FinalizerHolder {
236262
val PTR = StrutStyle_nGetFinalizer()
237263
}
@@ -321,3 +347,11 @@ private external fun _nIsHalfLeading(ptr: NativePointer): Boolean
321347
@ExternalSymbolName("org_jetbrains_skia_paragraph_StrutStyle__1nSetHalfLeading")
322348
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_StrutStyle__1nSetHalfLeading")
323349
private external fun _nSetHalfLeading(ptr: NativePointer, value: Boolean)
350+
351+
@ExternalSymbolName("org_jetbrains_skia_paragraph_StrutStyle__1nGetTopRatio")
352+
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_StrutStyle__1nGetTopRatio")
353+
private external fun StrutStyle_nGetTopRatio(ptr: NativePointer): Float
354+
355+
@ExternalSymbolName("org_jetbrains_skia_paragraph_StrutStyle__1nSetTopRatio")
356+
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_StrutStyle__1nSetTopRatio")
357+
private external fun StrutStyle_nSetTopRatio(ptr: NativePointer, value: Float)

skiko/src/commonMain/kotlin/org/jetbrains/skia/paragraph/TextStyle.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ class TextStyle internal constructor(ptr: NativePointer) : Managed(ptr, _Finaliz
323323
return this
324324
}
325325

326+
@Deprecated("Replaced by topRatio")
326327
var isHalfLeading: Boolean
327328
get() = try {
328329
Stats.onNativeCall()
@@ -334,6 +335,8 @@ class TextStyle internal constructor(ptr: NativePointer) : Managed(ptr, _Finaliz
334335
setHalfLeading(value)
335336
}
336337

338+
// Same as topRatio = halfLeading ? 0.5f : -1.0f
339+
@Deprecated("Replaced by topRatio")
337340
fun setHalfLeading(value: Boolean): TextStyle {
338341
try {
339342
Stats.onNativeCall()
@@ -344,6 +347,29 @@ class TextStyle internal constructor(ptr: NativePointer) : Managed(ptr, _Finaliz
344347
return this
345348
}
346349

350+
// [0..1]: the ratio of ascent to ascent+descent
351+
// -1: proportional to the ascent/descent
352+
var topRatio: Float
353+
get() = try {
354+
Stats.onNativeCall()
355+
TextStyle_nGetTopRatio(_ptr)
356+
} finally {
357+
reachabilityBarrier(this)
358+
}
359+
set(value) {
360+
setTopRatio(value)
361+
}
362+
363+
fun setTopRatio(topRatio: Float): TextStyle {
364+
try {
365+
Stats.onNativeCall()
366+
TextStyle_nSetTopRatio(_ptr, topRatio)
367+
} finally {
368+
reachabilityBarrier(this)
369+
}
370+
return this
371+
}
372+
347373
var letterSpacing: Float
348374
get() = try {
349375
Stats.onNativeCall()
@@ -560,6 +586,14 @@ private external fun TextStyle_nGetHalfLeading(ptr: NativePointer): Boolean
560586
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_TextStyle__1nSetHalfLeading")
561587
private external fun TextStyle_nSetHalfLeading(ptr: NativePointer, value: Boolean)
562588

589+
@ExternalSymbolName("org_jetbrains_skia_paragraph_TextStyle__1nGetTopRatio")
590+
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_TextStyle__1nGetTopRatio")
591+
private external fun TextStyle_nGetTopRatio(ptr: NativePointer): Float
592+
593+
@ExternalSymbolName("org_jetbrains_skia_paragraph_TextStyle__1nSetTopRatio")
594+
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_TextStyle__1nSetTopRatio")
595+
private external fun TextStyle_nSetTopRatio(ptr: NativePointer, value: Float)
596+
563597
@ExternalSymbolName("org_jetbrains_skia_paragraph_TextStyle__1nGetBaselineShift")
564598
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_TextStyle__1nGetBaselineShift")
565599
private external fun TextStyle_nGetBaselineShift(ptr: NativePointer): Float

skiko/src/commonMain/kotlin/org/jetbrains/skiko/redrawer/Redrawer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ internal interface Redrawer {
44
fun dispose()
55
fun needRedraw()
66
fun redrawImmediately()
7-
fun syncSize() = Unit
7+
fun syncBounds() = Unit
88
fun setVisible(isVisible: Boolean) = Unit
99
val renderInfo: String
1010
}

skiko/src/commonTest/kotlin/org/jetbrains/skiko/paragraph/TextStyleTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ class TextStyleTest {
143143
}
144144
}
145145

146+
@Test
147+
fun textStyleTopRatioTest() {
148+
TextStyle().use { textStyle ->
149+
assertEquals(-1f, textStyle.topRatio)
150+
textStyle.topRatio = 0.42f
151+
assertEquals(0.42f, textStyle.topRatio, 0.001f)
152+
}
153+
}
154+
146155
@Test
147156
fun textStyleMetricsContainsMeaningfulValues() = runTest {
148157
val jbMono = Typeface.makeFromResource(jbMonoPath)

0 commit comments

Comments
 (0)