macosMain.korlibs.render.DefaultGameWindowMacos.kt Maven / Gradle / Ivy
package korlibs.render
import korlibs.datastructure.*
import korlibs.event.*
import korlibs.graphics.*
import korlibs.graphics.gl.*
import korlibs.image.bitmap.*
import korlibs.image.format.*
import korlibs.image.format.ns.*
import korlibs.io.lang.*
import korlibs.math.geom.*
import korlibs.math.geom.Size
import korlibs.memory.*
import korlibs.time.*
import kotlinx.cinterop.*
import kotlinx.coroutines.*
import platform.AppKit.*
import platform.CoreGraphics.*
import platform.CoreVideo.*
import platform.Foundation.*
import platform.JavaRuntimeSupport.*
import platform.darwin.*
import kotlin.native.SharedImmutable
import kotlin.native.concurrent.*
private fun ByteArray.toNsData(): NSData {
val array = this
return memScoped {
array.usePinned { arrayPin ->
NSData.dataWithBytes(arrayPin.startAddressOf, array.size.convert())
}
}
}
class MyNSWindow(contentRect: kotlinx.cinterop.CValue , styleMask: platform.AppKit.NSWindowStyleMask /* = kotlin.ULong */, backing: platform.AppKit.NSBackingStoreType /* = kotlin.ULong */, defer: kotlin.Boolean) : NSWindow(
contentRect, styleMask, backing, defer
) {
}
class MyNSOpenGLView(
val defaultGameWindow: MyDefaultGameWindow,
frame: kotlinx.cinterop.CValue ,
pixelFormat: platform.AppKit.NSOpenGLPixelFormat?
) : NSOpenGLView(frame, pixelFormat), NSTextInputProtocol {
override fun acceptsFirstResponder(): Boolean = true
override fun becomeFirstResponder(): Boolean = true
//fun getHeight() = openglView.bounds.height
fun getHeight() = bounds.height
var lastModifierFlags: Int = 0
//var customCursor: NSCursor? = null
// @TODO: Broken in Kotlin 1.8.0: https://youtrack.jetbrains.com/issue/KT-55653/Since-Kotlin-1.8.0-NSView.resetCursorRects-doesnt-exist-anymore-and-cannot-override-it
//override fun resetCursorRects() {
// val cursor = defaultGameWindow.cursor
// val nsCursor = cursor.nsCursor
// addCursorRect(bounds, nsCursor)
// println("MyNSOpenGLView.resetCursorRects: bounds=${bounds.toRectangle()}, cursor=$cursor, nsCursor=$nsCursor")
//}
fun dispatchFlagIfRequired(event: NSEvent, mask: Int, key: Key) {
val old = (lastModifierFlags and mask) != 0
val new = (event.modifierFlags.toInt() and mask) != 0
if (old == new) return
defaultGameWindow.dispatchKeyEventEx(
type = if (new) KeyEvent.Type.DOWN else KeyEvent.Type.UP,
id = 0,
character = ' ',
key = key,
keyCode = key.ordinal,
shift = event.shift,
ctrl = event.ctrl,
alt = event.alt,
meta = event.meta
)
}
override fun flagsChanged(event: NSEvent) {
dispatchFlagIfRequired(event, NSShiftKeyMask.toInt(), Key.LEFT_SHIFT)
dispatchFlagIfRequired(event, NSControlKeyMask.toInt(), Key.LEFT_CONTROL)
dispatchFlagIfRequired(event, NSAlternateKeyMask.toInt(), Key.LEFT_ALT)
dispatchFlagIfRequired(event, NSCommandKeyMask.toInt(), Key.META)
dispatchFlagIfRequired(event, NSFunctionKeyMask.toInt(), Key.FUNCTION)
dispatchFlagIfRequired(event, NSEventModifierFlagCapsLock.toInt(), Key.CAPS_LOCK)
lastModifierFlags = event.modifierFlags.toInt()
}
private val gestureEvent = GestureEvent()
override fun magnifyWithEvent(event: NSEvent) {
defaultGameWindow.dispatch(gestureEvent.also {
it.type = GestureEvent.Type.MAGNIFY
it.id = 0
it.amount = event.magnification().toFloat()
})
//println("magnifyWithEvent:event=$event")
super.magnifyWithEvent(event)
}
// https://developer.apple.com/documentation/appkit/nsevent
override fun rotateWithEvent(event: NSEvent) {
defaultGameWindow.dispatch(gestureEvent.also {
it.type = GestureEvent.Type.ROTATE
it.id = 0
it.amount = event.rotation.toFloat()
})
super.rotateWithEvent(event)
}
override fun swipeWithEvent(event: NSEvent) {
defaultGameWindow.dispatch(gestureEvent.also {
it.type = GestureEvent.Type.SWIPE
it.id = 0
it.amountX = event.deltaX.toFloat()
it.amountY = event.deltaY.toFloat()
})
//println("swipeWithEvent:event=$event")
super.swipeWithEvent(event)
}
override fun smartMagnifyWithEvent(event: NSEvent) {
defaultGameWindow.dispatch(gestureEvent.also {
it.type = GestureEvent.Type.SMART_MAGNIFY
it.id = 0
it.amount = 1f
})
//println("smartMagnifyWithEvent:event=$event")
super.smartMagnifyWithEvent(event)
}
override fun scrollWheel(event: NSEvent): Unit {
//println("scrollWheel:event=$event")
mouseEvent(MouseEvent.Type.SCROLL, event)
}
override fun mouseUp(event: NSEvent) = mouseEvent(MouseEvent.Type.UP, event)
override fun rightMouseUp(event: NSEvent) = mouseEvent(MouseEvent.Type.UP, event)
override fun otherMouseUp(event: NSEvent) = mouseEvent(MouseEvent.Type.UP, event)
override fun mouseDown(event: NSEvent) = mouseEvent(MouseEvent.Type.DOWN, event)
override fun rightMouseDown(event: NSEvent) = mouseEvent(MouseEvent.Type.DOWN, event)
override fun otherMouseDown(event: NSEvent) = mouseEvent(MouseEvent.Type.DOWN, event)
override fun mouseDragged(event: NSEvent) = mouseEvent(MouseEvent.Type.DRAG, event)
override fun rightMouseDragged(event: NSEvent) = mouseEvent(MouseEvent.Type.DRAG, event)
override fun otherMouseDragged(event: NSEvent) = mouseEvent(MouseEvent.Type.DRAG, event)
override fun mouseMoved(event: NSEvent) = mouseEvent(MouseEvent.Type.MOVE, event)
private fun mouseEvent(etype: MouseEvent.Type, e: NSEvent) {
val ex = e.locationInWindow.x.toInt()
val ey = (getHeight() - e.locationInWindow.y).toInt()
//println("mouseUp($rx,$ry)")
val ebutton = e.buttonNumber.toInt()
val factor = defaultGameWindow.backingScaleFactor
val sx = ex * factor
val sy = ey * factor
defaultGameWindow.dispatchMouseEvent(
id = 0,
type = etype,
x = sx.toInt(),
y = sy.toInt(),
button = when (etype) {
MouseEvent.Type.SCROLL -> MouseButton.BUTTON_WHEEL
else -> button(ebutton)
},
buttons = when (etype) {
MouseEvent.Type.SCROLL -> 0
else -> buttonMask(e.buttonMask.toInt())
},
scrollDeltaX = -e.deltaX.toFloat(), scrollDeltaY = -e.deltaY.toFloat(), scrollDeltaZ = -e.deltaZ.toFloat(),
isShiftDown = e.shift, isCtrlDown = e.ctrl, isAltDown = e.alt, isMetaDown = e.meta,
scrollDeltaMode = MouseEvent.ScrollDeltaMode.PIXEL
)
}
fun buttonMask(mask: Int): Int {
var out = 0
for (n in 0 until 8) {
if (mask.extractBool(n)) out = out or button(n).bits
}
return out
}
fun button(index: Int): MouseButton {
return when (index) {
0 -> MouseButton.LEFT
1 -> MouseButton.RIGHT
2 -> MouseButton.MIDDLE
else -> MouseButton[index]
}
}
var lastFn = false
var lastShift = false
var lastCtrl = false
var lastAlt = false
var lastMeta = false
fun keyDownUp(event: NSEvent, pressed: Boolean, e: NSEvent) {
val str = event.charactersIgnoringModifiers ?: "\u0000"
val c = str.getOrNull(0) ?: '\u0000'
val cc = c.toInt().toChar()
//println("keyDownUp")
val char = cc
val keyCode = event.keyCode.toInt()
val rawKey = KeyCodesToKeys[keyCode] ?: CharToKeys[char] ?: Key.UNKNOWN
val key = when {
(rawKey == Key.BACKSPACE || rawKey == Key.DELETE) -> if (event.fn) Key.DELETE else Key.BACKSPACE
else -> rawKey
}
lastModifierFlags = event.modifierFlags.toInt()
//println("keyDownUp: char=$char, keyCode=${keyCode.toInt()}, key=$key, pressed=$pressed, shift=${e.shift}, ctrl=${e.ctrl}, alt=${e.alt}, meta=${e.meta}, characters=${event.characters}, event.willBeHandledByComplexInputMethod()=${event.willBeHandledByComplexInputMethod()}")
defaultGameWindow.dispatchKeyEventEx(
type = if (pressed) KeyEvent.Type.DOWN else KeyEvent.Type.UP,
id = 0,
character = char,
key = key,
keyCode = keyCode,
shift = e.shift,
ctrl = e.ctrl,
alt = e.alt,
meta = e.meta
)
}
override fun keyDown(event: NSEvent) {
//super.keyDown(event)
lastFn = event.fn
lastShift = event.shift
lastCtrl = event.ctrl
lastAlt = event.alt
lastMeta = event.meta
interpretKeyEvents(listOf(event))
//val inputMethod = event.willBeHandledByComplexInputMethod()
keyDownUp(event, true, event)
}
override fun keyUp(event: NSEvent) {
//super.keyUp(event)
keyDownUp(event, false, event)
}
override fun insertText(string: Any?) {
//println("MyNSOpenGLView.insertText: '$string'")
if (string == null) return
for (char in string.toString()) {
defaultGameWindow.dispatchKeyEventEx(
type = KeyEvent.Type.TYPE,
id = 0,
character = char,
key = Key.UNKNOWN,
keyCode = char.code,
shift = lastShift,
ctrl = lastCtrl,
alt = lastAlt,
meta = lastMeta
)
}
}
var inputRect: Rectangle = Rectangle.ZERO
fun setInputRectangle(windowRect: Rectangle) {
this.inputRect = windowRect
}
// @TODO: Used for example when partially typing japanese. We need to display partial text while typing somehow
override fun setMarkedText(string: Any?, selectedRange: CValue) = Unit//.also { println("setMarkedText: '$string', $selectedRange") }
// @TODO: We should set the rectangle of the text input so IME places completion box at the right place
override fun firstRectForCharacterRange(range: CValue): CValue = NSMakeRect(
0.0.cg, 0.0.cg, 0.0.cg, 0.0.cg
//(this.bounds.left + inputRect.x).toCgFloat(),
//(this.bounds.top + inputRect.y).toCgFloat(),
//(inputRect.width).toCgFloat(),
//(inputRect.height).toCgFloat()
)//.also { println("firstRectForCharacterRange: $range") }
override fun attributedSubstringFromRange(range: CValue): NSAttributedString? = null//.also { println("attributedSubstringFromRange: $range") }
override fun characterIndexForPoint(point: CValue): NSUInteger = 0u//.also { println("characterIndexForPoint: $point") }
override fun conversationIdentifier(): NSInteger = 0//.also { println("conversationIdentifier") }
override fun doCommandBySelector(selector: COpaquePointer?) = Unit//.also { println("doCommandBySelector: $selector") }
override fun hasMarkedText(): Boolean = false//.also { println("hasMarkedText") }
override fun markedRange(): CValue = NSMakeRange(0u, 0u)//.also { println("markedRange") }
override fun selectedRange(): CValue = NSMakeRange(0u, 0u)//.also { println("selectedRange") }
override fun unmarkText() = Unit//.also { println("unmarkText") }
override fun validAttributesForMarkedText(): List<*>? = null//.also { println("validAttributesForMarkedText") }
}
class MyDefaultGameWindow : GameWindow() {
val gameWindow = this
val gameWindowStableRef = StableRef.create(gameWindow)
val app = NSApplication.sharedApplication()
val controller = WinController()
override val dialogInterface: DialogInterfaceMacos = DialogInterfaceMacos { this }
val windowStyle = NSWindowStyleMaskTitled or NSWindowStyleMaskMiniaturizable or
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
@Suppress("OPT_IN_USAGE")
val attrs: UIntArray by lazy {
val antialias = (this.quality != GameWindow.Quality.PERFORMANCE)
val antialiasArray = if (antialias) intArrayOf(
NSOpenGLPFAMultisample.convert(),
NSOpenGLPFASampleBuffers.convert(), 1.convert(),
NSOpenGLPFASamples.convert(), 4.convert()
) else intArrayOf()
intArrayOf(
*antialiasArray,
//NSOpenGLPFAOpenGLProfile,
//NSOpenGLProfileVersion4_1Core,
NSOpenGLPFADoubleBuffer.convert(),
NSOpenGLPFAColorSize.convert(), 24.convert(),
NSOpenGLPFAAlphaSize.convert(), 8.convert(),
NSOpenGLPFADepthSize.convert(), 24.convert(),
NSOpenGLPFAStencilSize.convert(), 8.convert(),
NSOpenGLPFAAccumSize.convert(), 0.convert(),
0.convert()
).asUIntArray()
}
val pixelFormat by lazy {
//println("NSOpenGLPFAStencilSize: $NSOpenGLPFAStencilSize")
attrs.usePinned {
NSOpenGLPixelFormat(it.addressOf(0).reinterpret())
//NSOpenGLPixelFormat.alloc()!!.initWithAttributes(it.addressOf(0).reinterpret())!!
}
}
val windowConfigWidth = 640
val windowConfigHeight = 480
val windowConfigTitle = ""
val windowRect: CValue = run {
val frame = NSScreen.mainScreen()!!.frame
NSMakeRect(
(frame.width * 0.5 - windowConfigWidth * 0.5),
(frame.height * 0.5 - windowConfigHeight * 0.5),
windowConfigWidth.toDouble(),
windowConfigHeight.toDouble()
)
}
private val openglView: MyNSOpenGLView = MyNSOpenGLView(this@MyDefaultGameWindow, NSMakeRect(0.0, 0.0, 16.0, 16.0), pixelFormat)
var timer: NSTimer? = null
override fun setInputRectangle(windowRect: Rectangle) {
openglView.setInputRectangle(windowRect)
}
private var responder: NSResponder
internal val window: NSWindow = MyNSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
setIsVisible(false)
title = windowConfigTitle
opaque = true
hasShadow = true
preferredBackingLocation = NSWindowBackingLocationVideoMemory
hidesOnDeactivate = false
releasedWhenClosed = false
openglView.setFrame(contentRectForFrameRect(frame))
delegate = object : NSObject(), NSWindowDelegateProtocol {
override fun windowShouldClose(sender: NSWindow): Boolean {
//println("windowShouldClose")
return true
}
override fun windowWillClose(notification: NSNotification) {
//println("windowWillClose")
}
override fun windowDidResize(notification: NSNotification) {
doWindowDidResize()
}
}
setAcceptsMouseMovedEvents(true)
setContentView(openglView)
setContentMinSize(NSMakeSize(150.0, 100.0))
responder = object : NSResponder() {
override fun acceptsFirstResponder(): Boolean = true
override fun becomeFirstResponder(): Boolean = true
//external override fun performKeyEquivalent(event: NSEvent): Boolean {
// return true
//}
}
makeFirstResponder(openglView)
//openglView.setNextResponder(responder)
//setNextResponder(responder)
setIsVisible(false)
}
// https://developer.apple.com/documentation/appkit/nscursor
override var cursor: ICursor = Cursor.DEFAULT
set(value) {
if (field == value) return
field = value
if (true) {
field.nsCursor.set()
} else {
// @TODO: Broken in Kotlin 1.8.0: https://youtrack.jetbrains.com/issue/KT-55653/Since-Kotlin-1.8.0-NSView.resetCursorRects-doesnt-exist-anymore-and-cannot-override-it
//window.contentView?.let { window.invalidateCursorRectsForView(it) }
}
}
private fun doWindowDidResize() {
//println("windowDidResize")
val factor = backingScaleFactor
val width = openglView.bounds.width
val height = openglView.bounds.height
val scaledWidth = width * factor
val scaledHeight = height * factor
//macTrace("windowDidResize")
dispatchReshapeEvent(0, 0, scaledWidth.toInt(), scaledHeight.toInt())
doRender(update = false)
}
@Suppress("RemoveRedundantCallsOfConversionMethods")
internal val backingScaleFactor: Float get() = window.backingScaleFactor.toFloat()
internal var lastBackingScaleFactor: Float = 0f
val darwinGamePad = DarwinGameControllerNative()
fun doRender(update: Boolean) {
//println("doRender[0]")
val frameStartTime = PerformanceCounter.reference
//macTrace("render")
//println("doRender[1]")
if (lastBackingScaleFactor != backingScaleFactor) {
lastBackingScaleFactor = backingScaleFactor
doWindowDidResize()
return
}
//println("doRender[2]")
//context?.flushBuffer()
var doRender = !update
if (update) {
darwinGamePad.updateGamepads(gameWindow)
frame(doUpdate = true, doRender = false, frameStartTime = frameStartTime)
if (mustTriggerRender) {
doRender = true
}
}
if (doRender) {
val context = openglView.openGLContext
context?.makeCurrentContext()
//println("doRender[3] : $context")
//ag.clear(Colors.BLACK)
//ag.onRender(ag)
//dispatch(renderEvent)
frame(frameStartTime = frameStartTime)
context?.flushBuffer()
}
//println("doRender[3]")
//println("doRender[4]")
}
override val ag: AG = AGNative()
override val devicePixelRatio: Float
get() {
//return NSScreen.mainScreen?.backingScaleFactor?.toDouble() ?: field
return window.backingScaleFactor.toFloat()
}
override val pixelsPerInch: Float by TimedCache(0.5.seconds) {
val screen = window.screen ?: return@TimedCache 96f
val screenSizeInPixels = screen.visibleFrame.useContents { Size(size.width, size.height) }
val screenSizeInMillimeters = CGDisplayScreenSize(((screen.deviceDescription["NSScreenNumber"]) as NSNumber).unsignedIntValue).useContents { Size(width, height) }
val dpmm = screenSizeInPixels.width / screenSizeInMillimeters.width
val dpi = dpmm / 0.0393701f
//println("screenSizeInPixels=$screenSizeInPixels")
//println("screenSizeInMillimeters=$screenSizeInMillimeters")
//println("dpmm=$dpmm")
//println("dpi=$dpi")
dpi // 1 millimeter -> 0.0393701 inches
}
//override val width: Int get() = window.frame.width.toInt()
//override val height: Int get() = window.frame.height.toInt()
override val width: Int get() = openglView.bounds.width.toInt()
override val height: Int get() = openglView.bounds.height.toInt()
override var title: String = ""
set(value) {
field = value
window.title = value
}
override var icon: Bitmap? = null
set(value) {
field = value
if (value != null) {
app.setApplicationIconImage(NSImage(data = PNG.encode(value).toNsData()))
}
}
override var fullscreen: Boolean
get() = (window.styleMask and NSFullScreenWindowMask) == NSFullScreenWindowMask
set(value) {
if (fullscreen != value) {
window.toggleFullScreen(window)
}
}
override var visible: Boolean
get() = window.visible
set(value) {
window.setIsVisible(value)
if (value) {
window.makeKeyAndOrderFront(this)
}
//if (value) {
// window.makeKeyAndOrderFront(this)
// app.activateIgnoringOtherApps(true)
//} else {
// window.orderOut(this)
//}
}
override fun setSize(width: Int, height: Int) {
//val frame = NSScreen.mainScreen()!!.frame
//val rect = NSMakeRect(
// ((frame.width - width) * 0.5), ((frame.height - height) * 0.5),
// width.toDouble(), height.toDouble()
//)
//window.setFrame(rect, true, false)
window.setContentSize(NSMakeSize(width.cg, height.cg))
window.center()
//window.setFrameTopLeftPoint()
}
override fun close(exitCode: Int) {
window.close()
}
override suspend fun loop(entry: suspend GameWindow.() -> Unit) = autoreleasepool {
val agNativeComponent = Any()
val ag: AG = AGOpenglFactory.create(agNativeComponent).create(agNativeComponent, AGConfig())
val ccontext = kotlin.coroutines.coroutineContext
app.delegate = object : NSObject(), NSApplicationDelegateProtocol {
//private val openglView: AppNSOpenGLView
override fun applicationShouldTerminateAfterLastWindowClosed(app: NSApplication): Boolean {
//println("applicationShouldTerminateAfterLastWindowClosed")
return true
}
override fun applicationWillFinishLaunching(notification: NSNotification) {
//println("applicationWillFinishLaunching")
//window.makeKeyAndOrderFront(this)
}
override fun applicationDidFinishLaunching(notification: NSNotification) {
//val data = decodeImageData(readBytes("icon.jpg"))
//println("${data.width}, ${data.height}")
app.mainMenu = NSMenu().apply {
//this.autoenablesItems = true
addItem(NSMenuItem("Application", null, "").apply {
this.submenu = NSMenu().apply {
//this.autoenablesItems = true
addItem(NSMenuItem("Quit", NSSelectorFromString(WinController::doTerminate.name), "q").apply {
target = controller
//enabled = true
})
}
//enabled = true
})
}
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
app.activateIgnoringOtherApps(true)
openglView.openGLContext?.makeCurrentContext()
try {
macTrace("init[a] -- bb")
macTrace("init[b]")
//println("KoruiWrap.pentry[0]")
//launch(KoruiDispatcher) { // Doesn't work!
//println("KoruiWrap.pentry[1]")
//println("KoruiWrap.entry[0]")
kotlinx.coroutines.GlobalScope.launch(getCoroutineDispatcherWithCurrentContext(ccontext)) {
entry()
}
//println("KoruiWrap.entry[1]")
//}
//println("KoruiWrap.pentry[2]")
doRender(update = true)
val useDisplayLink = Environment["MACOS_USE_DISPLAY_LINK"] != "false"
if (useDisplayLink && createDisplayLink()) {
//timer = NSTimer.scheduledTimerWithTimeInterval(1.0 / 480.0, true, ::timerDisplayLink)
} else {
timer = NSTimer.scheduledTimerWithTimeInterval(1.0 / 60.0, true, ::timer)
}
} catch (e: Throwable) {
e.printStackTrace()
window.close()
}
}
val arena = Arena()
val displayLink = arena.alloc()
private fun checkDisplayLink(code: Int) {
if (code != kCVReturnSuccess) internalException(code)
}
fun createDisplayLink(): Boolean {
//println("createDisplayLink[1]")
return try {
checkDisplayLink(CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), displayLink.ptr))
checkDisplayLink(CVDisplayLinkSetOutputCallback(displayLink.value, staticCFunction(::displayCallback), gameWindowStableRef.asCPointer()))
checkDisplayLink(CVDisplayLinkStart(displayLink.value))
true
} catch (e: InternalException) {
if (displayLink.value != null) {
CVDisplayLinkRelease(displayLink.value)
}
e.printStackTrace()
false
}
}
private fun timer(timer: NSTimer?) {
//println("TIMER")
doRender(update = true)
}
override fun applicationWillTerminate(notification: NSNotification) {
//println("applicationWillTerminate")
// Insert code here to tear down your application
}
}
coroutineDispatcher.executePending(1.seconds)
app.run()
}
override suspend fun clipboardWrite(data: ClipboardData) {
val pasteboard = NSPasteboard.generalPasteboard
pasteboard.declareTypes(listOf(NSPasteboardTypeString), null)
when (data) {
is TextClipboardData -> pasteboard.setString(data.text, NSPasteboardTypeString)
}
}
override suspend fun clipboardRead(): ClipboardData? {
val pasteboard = NSPasteboard.generalPasteboard
val items = pasteboard.pasteboardItems as? List?
if (items.isNullOrEmpty()) return null
return items.last().stringForType(NSPasteboardTypeString)?.let { TextClipboardData(it) }
}
override val hapticFeedbackGenerateSupport: Boolean get() = true
override fun hapticFeedbackGenerate(kind: HapticFeedbackKind) {
NSHapticFeedbackManager.defaultPerformer.performFeedbackPattern(
when (kind) {
HapticFeedbackKind.GENERIC -> NSHapticFeedbackPatternGeneric
HapticFeedbackKind.ALIGNMENT -> NSHapticFeedbackPatternAlignment
HapticFeedbackKind.LEVEL_CHANGE -> NSHapticFeedbackPatternLevelChange
},
NSHapticFeedbackPerformanceTimeNow
)
}
}
actual fun CreateDefaultGameWindow(config: GameWindowCreationConfig): GameWindow = MyDefaultGameWindow()
private val atomicDisplayLinkContext = AtomicReference(null)
@SharedImmutable
val doDisplayCallbackRender: () -> Unit = {
try {
initRuntimeIfNeeded()
val gameWindow = atomicDisplayLinkContext.value?.asStableRef()
gameWindow?.get()?.doRender(update = true)
} catch (e: Throwable) {
e.printStackTrace()
}
}
@Suppress("UNUSED_PARAMETER")
fun displayCallback(
displayLink: CVDisplayLinkRef?,
inNow: CPointer?,
inOutputTime: CPointer?,
flagsIn: CVOptionFlags,
flagsOut: CPointer?,
displayLinkContext: COpaquePointer?
): CVReturn {
initRuntimeIfNeeded()
atomicDisplayLinkContext.value = displayLinkContext
// Wait for this in the case we take more time than the frame time to not collapse this
NSOperationQueue.mainQueue.addOperations(
listOf(NSBlockOperation().also { it.addExecutionBlock(doDisplayCallbackRender) }),
//waitUntilFinished = true
waitUntilFinished = false // ISSUE: https://github.com/korlibs/korge/issues/1078
)
//NSOperationQueue.mainQueue.addOperationWithBlock(doDisplayCallbackRender)
return kCVReturnSuccess
}
class WinController : NSObject() {
@ObjCAction
fun doTerminate() {
NSApplication.sharedApplication.terminate(null)
}
}
@kotlin.native.concurrent.ThreadLocal
val doMacTrace by lazy { Environment["MAC_TRACE"] == "true" }
fun macTrace(str: String) {
if (doMacTrace) println(str)
}
val CValue.x get() = this.useContents { x }
val CValue.y get() = this.useContents { y }
val CValue.left get() = this.useContents { origin.x }
val CValue.top get() = this.useContents { origin.y }
val CValue.width get() = this.useContents { size.width }
val CValue.height get() = this.useContents { size.height }
val NSEvent.fn get() = (modifierFlags and NSFunctionKeyMask) != 0uL
val NSEvent.shift get() = (modifierFlags and NSShiftKeyMask) != 0uL
val NSEvent.ctrl get() = (modifierFlags and NSControlKeyMask) != 0uL
val NSEvent.alt get() = (modifierFlags and NSAlternateKeyMask) != 0uL
val NSEvent.meta get() = (modifierFlags and NSCommandKeyMask) != 0uL
inline val Int.cg: CGFloat get() = this.toDouble()
inline val Double.cg: CGFloat get() = this.toDouble()
val GameWindow.Cursor.nsCursor: NSCursor get() = when (this) {
GameWindow.Cursor.DEFAULT -> NSCursor.arrowCursor
GameWindow.Cursor.CROSSHAIR -> NSCursor.crosshairCursor
GameWindow.Cursor.TEXT -> NSCursor.IBeamCursor
GameWindow.Cursor.HAND -> NSCursor.pointingHandCursor
GameWindow.Cursor.MOVE -> NSCursor.closedHandCursor
GameWindow.Cursor.WAIT -> NSCursor.dragCopyCursor
GameWindow.Cursor.RESIZE_EAST -> NSCursor.resizeRightCursor
GameWindow.Cursor.RESIZE_SOUTH -> NSCursor.resizeDownCursor
GameWindow.Cursor.RESIZE_WEST -> NSCursor.resizeLeftCursor
GameWindow.Cursor.RESIZE_NORTH -> NSCursor.resizeUpCursor
GameWindow.Cursor.RESIZE_NORTH_EAST -> NSCursor.javaResizeNECursor() ?: NSCursor.arrowCursor
GameWindow.Cursor.RESIZE_NORTH_WEST -> NSCursor.javaResizeNWCursor() ?: NSCursor.arrowCursor
GameWindow.Cursor.RESIZE_SOUTH_EAST -> NSCursor.javaResizeSECursor() ?: NSCursor.arrowCursor
GameWindow.Cursor.RESIZE_SOUTH_WEST -> NSCursor.javaResizeSWCursor() ?: NSCursor.arrowCursor
else -> NSCursor.arrowCursor
}
val GameWindow.CustomCursor.nsCursor: NSCursor by extraPropertyThis {
val result = createBitmap()
val image = result.bitmap.toBMP32IfRequired().toNSImage()
NSCursor(image, result.hotspot.toNSPoint())
}
val GameWindow.ICursor.nsCursor: NSCursor get() = when (this) {
is GameWindow.Cursor -> this.nsCursor
is GameWindow.CustomCursor -> this.nsCursor
else -> NSCursor.arrowCursor
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy