Unverified Commit 113f6ec6 authored by Roman Sedaikin's avatar Roman Sedaikin Committed by GitHub

IOS: rotation support, dpi support, clocks sample. Partial commonization [SystemTheme]. (#284)

parent bc57de3b
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skia.paragraph.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle
import org.jetbrains.skia.paragraph.TextStyle
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkikoPointerEvent
import org.jetbrains.skiko.isLeftClick
import org.jetbrains.skiko.currentSystemTheme
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.PI
class Clocks: SkikoView {
private var frame = 0
private var xpos = 0.0
private var ypos = 0.0
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.default)
override fun onRender(canvas: Canvas, width: Int, height: Int, currentTimestamp: Long) {
val watchFill = Paint().apply { color = 0xFFFFFFFF.toInt() }
val watchStroke = Paint().apply {
color = 0xFF000000.toInt()
mode = PaintMode.STROKE
strokeWidth = 1f
}
val watchStrokeAA = Paint().apply {
color = 0xFF000000.toInt()
mode = PaintMode.STROKE
strokeWidth = 1f
}
val watchFillHover = Paint().apply { color = 0xFFE4FF01.toInt() }
for (x in 0 .. (width - 50) step 50) {
for (y in 20 .. (height - 50) step 50) {
val hover = xpos > x + 0 && xpos < x + 50 && ypos > y + 0 && ypos < y + 50
val fill = if (hover) watchFillHover else watchFill
val stroke = if (x > width / 2) watchStrokeAA else watchStroke
canvas.drawOval(Rect.makeXYWH(x + 5f, y + 5f, 40f, 40f), fill)
canvas.drawOval(Rect.makeXYWH(x + 5f, y + 5f, 40f, 40f), stroke)
var angle = 0f
while (angle < 2f * PI) {
canvas.drawLine(
(x + 25 - 17 * sin(angle)),
(y + 25 + 17 * cos(angle)),
(x + 25 - 20 * sin(angle)),
(y + 25 + 20 * cos(angle)),
stroke
)
angle += (2.0 * PI / 12.0).toFloat()
}
val time = (currentTimestamp / 1E6) % 60000 +
(x.toFloat() / width * 5000).toLong() +
(y.toFloat() / width * 5000).toLong()
val angle1 = (time.toFloat() / 5000 * 2f * PI).toFloat()
canvas.drawLine(x + 25f, y + 25f,
x + 25f - 15f * sin(angle1),
y + 25f + 15 * cos(angle1),
stroke)
val angle2 = (time / 60000 * 2f * PI).toFloat()
canvas.drawLine(x + 25f, y + 25f,
x + 25f - 10f * sin(angle2),
y + 25f + 10f * cos(angle2),
stroke)
}
}
val style = ParagraphStyle()
val title = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphics API: Metal ✿゚ $currentSystemTheme")
.popStyle()
.build()
title.layout(Float.POSITIVE_INFINITY)
title.paint(canvas, 5f, 5f)
val frames = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFFFFAA0A.toInt()).setFontSize(25f))
.addText("Frames: ${frame++}")
.popStyle()
.build()
frames.layout(Float.POSITIVE_INFINITY)
frames.paint(canvas, xpos.toFloat(), ypos.toFloat())
}
override fun onPointerEvent(event: SkikoPointerEvent) {
if (event.isLeftClick) {
xpos = event.x
ypos = event.y
}
}
}
\ No newline at end of file
...@@ -8,7 +8,7 @@ import org.jetbrains.skiko.SkikoViewController ...@@ -8,7 +8,7 @@ import org.jetbrains.skiko.SkikoViewController
import platform.UIKit.* import platform.UIKit.*
import platform.Foundation.* import platform.Foundation.*
fun makeApp(): SkikoView = BouncingBalls() fun makeApp(): SkikoView = Clocks()
fun main() { fun main() {
val args = emptyArray<String>() val args = emptyArray<String>()
......
package org.jetbrains.skiko
enum class SystemTheme {
DARK,
LIGHT,
UNKNOWN
}
\ No newline at end of file
...@@ -22,7 +22,7 @@ actual open class SkiaLayer( ...@@ -22,7 +22,7 @@ actual open class SkiaLayer(
set(value) { throw UnsupportedOperationException() } set(value) { throw UnsupportedOperationException() }
actual val contentScale: Float actual val contentScale: Float
get() = 1.0f get() = view.contentScaleFactor.toFloat()
actual var fullscreen: Boolean actual var fullscreen: Boolean
get() = true get() = true
...@@ -88,8 +88,11 @@ actual open class SkiaLayer( ...@@ -88,8 +88,11 @@ actual open class SkiaLayer(
private val contextHandler = MetalContextHandler(this) private val contextHandler = MetalContextHandler(this)
fun update(nanoTime: Long) { fun update(nanoTime: Long) {
val pictureWidth = (width * contentScale).coerceAtLeast(0.0F) val (w, h) = view.frame.useContents {
val pictureHeight = (height * contentScale).coerceAtLeast(0.0F) size.width to size.height
}
val pictureWidth = (w.toFloat() * contentScale).coerceAtLeast(0.0F)
val pictureHeight = (h.toFloat() * contentScale).coerceAtLeast(0.0F)
val bounds = Rect.makeWH(pictureWidth, pictureHeight) val bounds = Rect.makeWH(pictureWidth, pictureHeight)
val canvas = pictureRecorder.beginRecording(bounds) val canvas = pictureRecorder.beginRecording(bounds)
......
...@@ -8,6 +8,7 @@ import platform.UIKit.UIEvent ...@@ -8,6 +8,7 @@ import platform.UIKit.UIEvent
import platform.UIKit.UIScreen import platform.UIKit.UIScreen
import platform.UIKit.UIViewController import platform.UIKit.UIViewController
import platform.UIKit.setFrame import platform.UIKit.setFrame
import platform.UIKit.contentScaleFactor
@ExportObjCClass @ExportObjCClass
class SkikoViewController : UIViewController { class SkikoViewController : UIViewController {
...@@ -41,6 +42,7 @@ class SkikoViewController : UIViewController { ...@@ -41,6 +42,7 @@ class SkikoViewController : UIViewController {
val layer = SkiaLayer().apply { val layer = SkiaLayer().apply {
skikoView = appFactory(this) skikoView = appFactory(this)
} }
view.contentScaleFactor = UIScreen.mainScreen.scale
view.setFrame(CGRectMake(0.0, 0.0, width, height)) view.setFrame(CGRectMake(0.0, 0.0, width, height))
layer.attachTo(this.view) layer.attachTo(this.view)
} }
......
package org.jetbrains.skiko
import platform.UIKit.*
import platform.UIKit.UIUserInterfaceStyle.*
val currentSystemTheme: SystemTheme
get() = when (UITraitCollection.currentTraitCollection.userInterfaceStyle) {
UIUserInterfaceStyleDark -> SystemTheme.DARK
UIUserInterfaceStyleLight -> SystemTheme.LIGHT
else -> SystemTheme.UNKNOWN
}
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import kotlinx.cinterop.useContents
import org.jetbrains.skia.ColorSpace import org.jetbrains.skia.ColorSpace
import org.jetbrains.skia.Surface import org.jetbrains.skia.Surface
import org.jetbrains.skia.SurfaceColorFormat import org.jetbrains.skia.SurfaceColorFormat
...@@ -23,12 +24,27 @@ internal class MetalContextHandler(layer: SkiaLayer) : ContextHandler(layer) { ...@@ -23,12 +24,27 @@ internal class MetalContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
return true return true
} }
private var currentWidth = 0
private var currentHeight = 0
private fun isSizeChanged(width: Int, height: Int): Boolean {
if (width != currentWidth || height != currentHeight) {
currentWidth = width
currentHeight = height
return true
}
return false
}
override fun initCanvas() { override fun initCanvas() {
disposeCanvas() disposeCanvas()
val scale = layer.contentScale val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0) val (w, h) = layer.view.frame.useContents {
val h = (layer.height * scale).toInt().coerceAtLeast(0) (size.width * scale).toInt().coerceAtLeast(0) to (size.height * scale).toInt().coerceAtLeast(0)
}
if (isSizeChanged(w, h)) {
metalRedrawer.syncSize()
}
renderTarget = metalRedrawer.makeRenderTarget(w, h) renderTarget = metalRedrawer.makeRenderTarget(w, h)
......
...@@ -4,6 +4,7 @@ import kotlinx.cinterop.addressOf ...@@ -4,6 +4,7 @@ import kotlinx.cinterop.addressOf
import kotlinx.cinterop.autoreleasepool import kotlinx.cinterop.autoreleasepool
import kotlinx.cinterop.objcPtr import kotlinx.cinterop.objcPtr
import kotlinx.cinterop.usePinned import kotlinx.cinterop.usePinned
import kotlinx.cinterop.useContents
import org.jetbrains.skia.BackendRenderTarget import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
...@@ -17,6 +18,7 @@ import platform.QuartzCore.CAMetalDrawableProtocol ...@@ -17,6 +18,7 @@ import platform.QuartzCore.CAMetalDrawableProtocol
import platform.QuartzCore.CAMetalLayer import platform.QuartzCore.CAMetalLayer
import platform.QuartzCore.kCAGravityTopLeft import platform.QuartzCore.kCAGravityTopLeft
import kotlin.system.getTimeNanos import kotlin.system.getTimeNanos
import platform.CoreGraphics.CGSizeMake
internal class MetalRedrawer( internal class MetalRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
...@@ -26,7 +28,11 @@ internal class MetalRedrawer( ...@@ -26,7 +28,11 @@ internal class MetalRedrawer(
internal val device = MTLCreateSystemDefaultDevice()!! internal val device = MTLCreateSystemDefaultDevice()!!
private val queue = device.newCommandQueue()!! private val queue = device.newCommandQueue()!!
private var currentDrawable: CAMetalDrawableProtocol? = null private var currentDrawable: CAMetalDrawableProtocol? = null
private val metalLayer = MetalLayer(this.layer, device) private val metalLayer = MetalLayer()
init {
metalLayer.init(this.layer, device)
}
private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) { private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) {
if (layer.isShowing()) { if (layer.isShowing()) {
...@@ -48,7 +54,13 @@ internal class MetalRedrawer( ...@@ -48,7 +54,13 @@ internal class MetalRedrawer(
} }
override fun syncSize() { override fun syncSize() {
println("TODO: implement syncSize()") metalLayer.contentsScale = layer.contentScale.toDouble()
val (w, h) = layer.view.frame.useContents {
size.width to size.height
}
metalLayer.frame = layer.view.frame
metalLayer.init(layer, device)
metalLayer.drawableSize = CGSizeMake(w * metalLayer.contentsScale, h * metalLayer.contentsScale)
} }
override fun needRedraw() { override fun needRedraw() {
...@@ -88,11 +100,15 @@ internal class MetalRedrawer( ...@@ -88,11 +100,15 @@ internal class MetalRedrawer(
} }
} }
class MetalLayer( class MetalLayer : CAMetalLayer {
private val skiaLayer: SkiaLayer, private lateinit var skiaLayer: SkiaLayer
theDevice: MTLDeviceProtocol
) : CAMetalLayer() { @OverrideInit
init { constructor(): super()
@OverrideInit
constructor(layer: Any): super(layer)
fun init(skiaLayer: SkiaLayer, theDevice: MTLDeviceProtocol) {
this.setNeedsDisplayOnBoundsChange(true) this.setNeedsDisplayOnBoundsChange(true)
this.removeAllAnimations() this.removeAllAnimations()
// TODO: looks like a bug in K/N interop. // TODO: looks like a bug in K/N interop.
......
...@@ -88,7 +88,7 @@ extern "C" ...@@ -88,7 +88,7 @@ extern "C"
return (float) getDpiScaleByDisplay(dsi_x11->display); return (float) getDpiScaleByDisplay(dsi_x11->display);
} }
JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_SystemThemeKt_getCurrentSystemTheme(JNIEnv *env, jobject topLevel) JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_SystemTheme_1jvmKt_getCurrentSystemTheme(JNIEnv *env, jobject topLevel)
{ {
// Unknown. // Unknown.
return 2; return 2;
......
...@@ -28,7 +28,7 @@ extern "C" ...@@ -28,7 +28,7 @@ extern "C"
return (jlong) dsi_win->hwnd; return (jlong) dsi_win->hwnd;
} }
JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_SystemThemeKt_getCurrentSystemTheme(JNIEnv *env, jobject topLevel) JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_SystemTheme_1jvmKt_getCurrentSystemTheme(JNIEnv *env, jobject topLevel)
{ {
auto subkey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; auto subkey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
auto name = L"AppsUseLightTheme"; auto name = L"AppsUseLightTheme";
......
package org.jetbrains.skiko package org.jetbrains.skiko
enum class SystemTheme {
DARK,
LIGHT,
UNKNOWN
}
val currentSystemTheme: SystemTheme val currentSystemTheme: SystemTheme
get() = when (getCurrentSystemTheme()) { get() = when (getCurrentSystemTheme()) {
0 -> SystemTheme.LIGHT 0 -> SystemTheme.LIGHT
......
...@@ -188,7 +188,7 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxDisableT ...@@ -188,7 +188,7 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_PlatformOperationsKt_osxDisableT
}); });
} }
JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_SystemThemeKt_getCurrentSystemTheme(JNIEnv *env, jobject topLevel) JNIEXPORT jint JNICALL Java_org_jetbrains_skiko_SystemTheme_1jvmKt_getCurrentSystemTheme(JNIEnv *env, jobject topLevel)
{ {
NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
if ([@"Dark" isEqualToString:osxMode]) { if ([@"Dark" isEqualToString:osxMode]) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment