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
import platform.UIKit.*
import platform.Foundation.*
fun makeApp(): SkikoView = BouncingBalls()
fun makeApp(): SkikoView = Clocks()
fun main() {
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(
set(value) { throw UnsupportedOperationException() }
actual val contentScale: Float
get() = 1.0f
get() = view.contentScaleFactor.toFloat()
actual var fullscreen: Boolean
get() = true
......@@ -88,8 +88,11 @@ actual open class SkiaLayer(
private val contextHandler = MetalContextHandler(this)
fun update(nanoTime: Long) {
val pictureWidth = (width * contentScale).coerceAtLeast(0.0F)
val pictureHeight = (height * contentScale).coerceAtLeast(0.0F)
val (w, h) = view.frame.useContents {
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 canvas = pictureRecorder.beginRecording(bounds)
......
......@@ -8,6 +8,7 @@ import platform.UIKit.UIEvent
import platform.UIKit.UIScreen
import platform.UIKit.UIViewController
import platform.UIKit.setFrame
import platform.UIKit.contentScaleFactor
@ExportObjCClass
class SkikoViewController : UIViewController {
......@@ -41,6 +42,7 @@ class SkikoViewController : UIViewController {
val layer = SkiaLayer().apply {
skikoView = appFactory(this)
}
view.contentScaleFactor = UIScreen.mainScreen.scale
view.setFrame(CGRectMake(0.0, 0.0, width, height))
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
import kotlinx.cinterop.useContents
import org.jetbrains.skia.ColorSpace
import org.jetbrains.skia.Surface
import org.jetbrains.skia.SurfaceColorFormat
......@@ -23,12 +24,27 @@ internal class MetalContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
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() {
disposeCanvas()
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
val (w, h) = layer.view.frame.useContents {
(size.width * scale).toInt().coerceAtLeast(0) to (size.height * scale).toInt().coerceAtLeast(0)
}
if (isSizeChanged(w, h)) {
metalRedrawer.syncSize()
}
renderTarget = metalRedrawer.makeRenderTarget(w, h)
......
......@@ -4,6 +4,7 @@ import kotlinx.cinterop.addressOf
import kotlinx.cinterop.autoreleasepool
import kotlinx.cinterop.objcPtr
import kotlinx.cinterop.usePinned
import kotlinx.cinterop.useContents
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.*
......@@ -17,6 +18,7 @@ import platform.QuartzCore.CAMetalDrawableProtocol
import platform.QuartzCore.CAMetalLayer
import platform.QuartzCore.kCAGravityTopLeft
import kotlin.system.getTimeNanos
import platform.CoreGraphics.CGSizeMake
internal class MetalRedrawer(
private val layer: SkiaLayer,
......@@ -26,7 +28,11 @@ internal class MetalRedrawer(
internal val device = MTLCreateSystemDefaultDevice()!!
private val queue = device.newCommandQueue()!!
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) {
if (layer.isShowing()) {
......@@ -48,7 +54,13 @@ internal class MetalRedrawer(
}
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() {
......@@ -88,11 +100,15 @@ internal class MetalRedrawer(
}
}
class MetalLayer(
private val skiaLayer: SkiaLayer,
theDevice: MTLDeviceProtocol
) : CAMetalLayer() {
init {
class MetalLayer : CAMetalLayer {
private lateinit var skiaLayer: SkiaLayer
@OverrideInit
constructor(): super()
@OverrideInit
constructor(layer: Any): super(layer)
fun init(skiaLayer: SkiaLayer, theDevice: MTLDeviceProtocol) {
this.setNeedsDisplayOnBoundsChange(true)
this.removeAllAnimations()
// TODO: looks like a bug in K/N interop.
......
......@@ -88,7 +88,7 @@ extern "C"
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.
return 2;
......
......@@ -28,7 +28,7 @@ extern "C"
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 name = L"AppsUseLightTheme";
......
package org.jetbrains.skiko
enum class SystemTheme {
DARK,
LIGHT,
UNKNOWN
}
val currentSystemTheme: SystemTheme
get() = when (getCurrentSystemTheme()) {
0 -> SystemTheme.LIGHT
......
......@@ -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"];
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