Browse Source

Added browser slices support

pull/210/head 0.3.0-build135
spvessel 3 years ago
parent
commit
906ef97262
  1. 1
      cef/README.md
  2. 42
      cef/src/main/kotlin/org/jetbrains/compose/desktop/App.kt
  3. 5
      cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/Browser.kt
  4. 299
      cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserSlicer.kt
  5. 83
      cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserState.kt
  6. 240
      cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt
  7. 108
      cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefBrowserWrapper.kt
  8. 104
      cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefView.kt

1
cef/README.md

@ -2,5 +2,6 @@ CEF integration for Desktop Jetpack Compose.
Run example:
To run application execute in terminal: ``./gradlew run``
To run application in browser sliced mode execute in terminal: ``./gradlew run --args="slices"``
PS. Mac OS X is currently not supported.

42
cef/src/main/kotlin/org/jetbrains/compose/desktop/App.kt

@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.MutableState
import androidx.compose.desktop.Window
import androidx.compose.desktop.WindowEvents
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -22,18 +23,26 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.TextField
import androidx.compose.material.Button
import androidx.compose.foundation.Text
import org.jetbrains.compose.desktop.browser.BrowserState
import org.jetbrains.compose.desktop.browser.CefView
import org.jetbrains.compose.desktop.browser.Browser
import org.jetbrains.compose.desktop.browser.BrowserView
import org.jetbrains.compose.desktop.browser.BrowserSlicer
fun main(args: Array<String>) {
val browser = when {
args.isEmpty() -> BrowserView()
args[0] == "slices" -> BrowserSlicer(IntSize(800, 700))
else -> {
BrowserView()
}
}
fun main() {
val browser = BrowserState()
val url = mutableStateOf("https://www.google.com")
Window(
title = "CEF-compose",
size = IntSize(800, 800),
size = IntSize(900, 900),
events = WindowEvents(
onFocusGet = { browser.loadURL(url.value) }
onFocusGet = { browser.load(url.value) }
)
) {
Surface(
@ -50,7 +59,7 @@ fun main() {
}
@Composable
private fun AddressBar(browser: BrowserState, url: MutableState<String>) {
private fun AddressBar(browser: Browser, url: MutableState<String>) {
Surface(
color = Color.Transparent,
modifier = Modifier
@ -74,7 +83,7 @@ private fun AddressBar(browser: BrowserState, url: MutableState<String>) {
Button(
modifier = Modifier.preferredHeight(48.dp),
shape = CircleShape,
onClick = { browser.loadURL(url.value) }
onClick = { browser.load(url.value) }
) {
Text(text = "Go!")
}
@ -83,11 +92,24 @@ private fun AddressBar(browser: BrowserState, url: MutableState<String>) {
}
@Composable
private fun WebView(browser: BrowserState) {
private fun WebView(browser: Browser) {
Surface(
color = Color.Gray,
modifier = Modifier.fillMaxSize().padding(10.dp)
) {
CefView(browser)
when (browser) {
is BrowserView -> {
browser.view()
}
is BrowserSlicer -> {
Column {
browser.slice(0, 200)
Spacer(Modifier.height(30.dp))
browser.slice(200, 200)
Spacer(Modifier.height(30.dp))
browser.tail()
}
}
}
}
}

5
cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/Browser.kt

@ -0,0 +1,5 @@
package org.jetbrains.compose.desktop.browser
interface Browser {
fun load(url: String)
}

299
cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserSlicer.kt

@ -0,0 +1,299 @@
package org.jetbrains.compose.desktop.browser
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.AppFrame
import androidx.compose.foundation.background
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.clickable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.focus
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.isFocused
import androidx.compose.ui.focusObserver
import androidx.compose.ui.focusRequester
import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.globalPosition
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntSize
import java.awt.Component
import java.awt.Point
import java.awt.event.KeyEvent
import java.awt.event.KeyAdapter
import java.awt.event.KeyListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
import java.awt.event.MouseWheelEvent
import java.awt.event.MouseWheelListener
import java.awt.event.MouseMotionAdapter
import javax.swing.JFrame
import org.jetbrains.skija.IRect
import org.jetbrains.skija.Bitmap
import org.jetbrains.skija.ImageInfo
import org.jetbrains.skija.ColorAlphaType
import org.jetbrains.skiko.HardwareLayer
class BrowserSlicer(val size: IntSize) : Browser {
private lateinit var bitmap: MutableState<Bitmap>
private lateinit var recomposer: MutableState<Any>
private var browser: CefBrowserWrapper? = null
private val isReady = mutableStateOf(false)
fun isReady(): Boolean {
return isReady.value
}
private var slices = mutableListOf<BrowserSlice>()
private var tail: BrowserSlice? = null
private var entire: BrowserSlice? = null
@Composable
fun full() {
if (isReady()) {
invalidate()
entire = remember { BrowserSlice(this, 0, size.height) }
entire!!.view(bitmap.value, recomposer)
}
}
@Composable
fun slice(offset: Int, height: Int) {
if (isReady()) {
invalidate()
val slice = BrowserSlice(this, offset, height)
slices.add(slice)
slice.view(bitmap.value, recomposer)
}
}
@Composable
fun tail() {
if (isReady()) {
invalidate()
var offset = 0
for (slice in slices) {
val bottom = slice.offset + slice.height
if (offset < bottom) {
offset = bottom
}
}
tail = remember { BrowserSlice(this, offset, size.height - offset) }
tail!!.view(bitmap.value, recomposer)
}
}
fun updateSize(size: IntSize) {
browser?.onLayout(0, 0, size.width, size.height)
}
override fun load(url: String) {
if (browser == null) {
val frame = AppManager.focusedWindow
if (frame != null) {
val window = frame.window
if (!window.isVisible()) {
return
}
var layer = getHardwareLayer(window)
if (layer == null) {
throw Error("Browser initialization failed!")
}
browser = CefBrowserWrapper(
startURL = url,
layer = layer
)
browser?.onActive()
updateSize(size)
addListeners(layer)
isReady.value = true
}
return
}
browser?.loadURL(url)
isReady.value = true
}
fun dismiss() {
browser?.onDismiss()
}
private fun getHardwareLayer(window: JFrame): HardwareLayer? {
val components = window.getContentPane().getComponents()
for (component in components) {
if (component is HardwareLayer) {
return component
}
}
return null
}
private fun addListeners(layer: Component) {
layer.addMouseListener(object : MouseAdapter() {
override fun mousePressed(event: MouseEvent) {
val slice = isInLayer(event)
if (slice != null) {
event.translatePoint(-slice.x, -slice.y + slice.offset)
browser?.onMouseEvent(event)
}
}
override fun mouseReleased(event: MouseEvent) {
val slice = isInLayer(event)
if (slice != null) {
event.translatePoint(-slice.x, -slice.y + slice.offset)
browser?.onMouseEvent(event)
}
}
})
layer.addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseMoved(event: MouseEvent) {
val slice = isInLayer(event)
if (slice != null) {
event.translatePoint(-slice.x, -slice.y + slice.offset)
browser?.onMouseEvent(event)
}
}
override fun mouseDragged(event: MouseEvent) {
val slice = isInLayer(event)
if (slice != null) {
event.translatePoint(-slice.x, -slice.y + slice.offset)
browser?.onMouseEvent(event)
}
}
})
layer.addMouseWheelListener(object : MouseWheelListener {
override fun mouseWheelMoved(event: MouseWheelEvent) {
val slice = isInLayer(event)
if (slice != null) {
event.translatePoint(-slice.x, -slice.y + slice.offset)
browser?.onMouseScrollEvent(event)
}
}
})
layer.addKeyListener(object : KeyAdapter() {
override fun keyPressed(event: KeyEvent) {
browser?.onKeyEvent(event)
}
override fun keyReleased(event: KeyEvent) {
browser?.onKeyEvent(event)
}
override fun keyTyped(event: KeyEvent) {
browser?.onKeyEvent(event)
}
})
}
private fun isInLayer(event: MouseEvent): BrowserSlice? {
if (entire != null && isHovered(event.point, entire!!)) {
return entire
}
if (tail != null && isHovered(event.point, tail!!)) {
return tail
}
for (slice in slices) {
if (isHovered(event.point, slice)) {
return slice
}
}
return null
}
private fun isHovered(point: Point, slice: BrowserSlice): Boolean {
if (
point.x >= slice.x &&
point.x <= slice.x + size.width &&
point.y >= slice.y &&
point.y <= slice.y + slice.height
) {
return true
}
return false
}
internal fun getBitmap(): Bitmap {
return browser!!.getBitmap()
}
private var invalidated = false
@Composable
private fun invalidate() {
if (!invalidated) {
bitmap = remember { mutableStateOf(emptyBitmap) }
recomposer = remember { mutableStateOf(Any()) }
browser!!.onInvalidate = {
bitmap.value = getBitmap()
recomposer.value = Any()
}
invalidated = true
}
}
}
private class BrowserSlice(val handler: BrowserSlicer, val offset: Int, val height: Int) {
var x: Int = 0
private set
var y: Int = 0
private set
@OptIn(
ExperimentalFocus::class,
ExperimentalFoundationApi::class
)
@Composable
fun view(bitmap: Bitmap, recomposer: MutableState<Any>) {
val focusRequester = FocusRequester()
Box (
modifier = Modifier.background(color = Color.White)
.size(handler.size.width.dp, height.dp)
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(handler.size.width, height) {
placeable.placeRelative(0, 0)
}
}
.onGloballyPositioned { coordinates ->
x = coordinates.globalPosition.x.toInt()
y = coordinates.globalPosition.y.toInt()
}
.focusRequester(focusRequester)
.focus()
.clickable(indication = null) { focusRequester.requestFocus() }
) {
Canvas(
modifier = Modifier.size(handler.size.width.dp, height.dp)
) {
drawIntoCanvas { canvas ->
recomposer.value
canvas.nativeCanvas.drawBitmapIRect(
bitmap,
IRect(0, offset, handler.size.width, offset + height),
IRect(0, 0, handler.size.width, height).toRect()
)
}
}
}
}
}

83
cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserState.kt

@ -1,83 +0,0 @@
package org.jetbrains.compose.desktop.browser
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.AppFrame
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.MutableState
import org.jetbrains.skija.Bitmap
import org.jetbrains.skiko.HardwareLayer
import javax.swing.JFrame
import org.cef.CefApp
class BrowserState {
private val url = mutableStateOf("")
private val isReady = mutableStateOf(false)
private lateinit var browser: CefBrowserWrapper
fun isReady(): Boolean {
return isReady.value
}
fun loadURL(url: String) {
if (!this::browser.isInitialized) {
val frame = AppManager.focusedWindow
if (frame != null) {
onActive(frame, url)
}
return
}
isReady.value = false
browser.loadURL(url)
isReady.value = true
}
fun getBitmap(): Bitmap {
return browser.getBitmap()
}
fun onLayout(x: Int, y: Int, width: Int, height: Int) {
browser.onLayout(x, y, width, height)
}
fun onActive(frame: AppFrame, url: String) {
val window = frame.window
if (!window.isVisible()) {
return
}
var layer = getHardwareLayer(window)
if (layer == null) {
throw Error("Browser initialization failed!")
}
browser = CefBrowserWrapper(
startURL = url,
layer = layer
)
browser.onActive()
isReady.value = true
}
fun onDismiss() {
browser.onDismiss()
}
fun setFocused(value: Boolean) {
browser.setFocused(value)
}
fun onInvalidate(onInvalidate: (() -> Unit)?) {
browser.onInvalidate = onInvalidate
}
private fun getHardwareLayer(window: JFrame): HardwareLayer? {
val components = window.getContentPane().getComponents()
for (component in components) {
if (component is HardwareLayer) {
return component
}
}
return null
}
}

240
cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt

@ -0,0 +1,240 @@
package org.jetbrains.compose.desktop.browser
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.AppFrame
import androidx.compose.foundation.background
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.onDispose
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.globalPosition
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import java.awt.Component
import java.awt.event.KeyEvent
import java.awt.event.KeyAdapter
import java.awt.event.KeyListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
import java.awt.event.MouseWheelEvent
import java.awt.event.MouseWheelListener
import java.awt.event.MouseMotionAdapter
import org.cef.CefApp
import javax.swing.JFrame
import org.jetbrains.skija.IRect
import org.jetbrains.skija.Bitmap
import org.jetbrains.skija.ImageInfo
import org.jetbrains.skija.ColorAlphaType
import org.jetbrains.skiko.HardwareLayer
//EXPERIMENTAL FOCUS API
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.focus
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.isFocused
import androidx.compose.ui.focusObserver
import androidx.compose.ui.focusRequester
import androidx.compose.foundation.clickable
class BrowserView : Browser {
private lateinit var bitmap: MutableState<Bitmap>
private lateinit var recomposer: MutableState<Any>
internal var browser: CefBrowserWrapper? = null
private val isReady = mutableStateOf(false)
fun isReady(): Boolean {
return isReady.value
}
internal var location = IntOffset.Zero
internal var size = IntSize.Zero
private var layout: BrowserLayout? = null
@Composable
fun view() {
if (isReady()) {
invalidate()
layout = remember { BrowserLayout(this) }
layout!!.view(bitmap.value, recomposer)
}
}
private var invalidated = false
@Composable
private fun invalidate() {
if (!invalidated) {
bitmap = remember { mutableStateOf(emptyBitmap) }
recomposer = remember { mutableStateOf(Any()) }
browser!!.onInvalidate = {
bitmap.value = browser!!.getBitmap()
recomposer.value = Any()
}
invalidated = true
}
}
internal fun updateBounds() {
browser?.onLayout(location.x, location.y, size.width, size.height)
}
override fun load(url: String) {
if (browser == null) {
val frame = AppManager.focusedWindow
if (frame != null) {
val window = frame.window
if (!window.isVisible()) {
return
}
var layer = getHardwareLayer(window)
if (layer == null) {
throw Error("Browser initialization failed!")
}
browser = CefBrowserWrapper(
startURL = url,
layer = layer
)
browser?.onActive()
addListeners(layer)
isReady.value = true
}
return
}
browser?.loadURL(url)
isReady.value = true
}
fun dismiss() {
browser?.onDismiss()
}
private fun getHardwareLayer(window: JFrame): HardwareLayer? {
val components = window.getContentPane().getComponents()
for (component in components) {
if (component is HardwareLayer) {
return component
}
}
return null
}
private fun addListeners(layer: Component) {
layer.addMouseListener(object : MouseAdapter() {
override fun mousePressed(event: MouseEvent) {
if (isInLayer(event)) {
browser?.onMouseEvent(event)
}
}
override fun mouseReleased(event: MouseEvent) {
if (isInLayer(event)) {
browser?.onMouseEvent(event)
}
}
})
layer.addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseMoved(event: MouseEvent) {
if (isInLayer(event)) {
browser?.onMouseEvent(event)
}
}
override fun mouseDragged(event: MouseEvent) {
if (isInLayer(event)) {
browser?.onMouseEvent(event)
}
}
})
layer.addMouseWheelListener(object : MouseWheelListener {
override fun mouseWheelMoved(event: MouseWheelEvent) {
if (isInLayer(event)) {
browser?.onMouseScrollEvent(event)
}
}
})
layer.addKeyListener(object : KeyAdapter() {
override fun keyPressed(event: KeyEvent) {
browser?.onKeyEvent(event)
}
override fun keyReleased(event: KeyEvent) {
browser?.onKeyEvent(event)
}
override fun keyTyped(event: KeyEvent) {
browser?.onKeyEvent(event)
}
})
}
private fun isInLayer(event: MouseEvent): Boolean {
if (
event.x >= location.x &&
event.x <= location.x + size.width &&
event.y >= location.y &&
event.y <= location.y + size.height
) {
return true
}
return false
}
}
private class BrowserLayout(val handler: BrowserView) {
@OptIn(
ExperimentalFocus::class,
ExperimentalFoundationApi::class
)
@Composable
fun view(bitmap: Bitmap, recomposer: MutableState<Any>) {
val focusRequester = FocusRequester()
Box (
modifier = Modifier.background(color = Color.White)
.fillMaxSize()
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
handler.size = IntSize(placeable.width, placeable.height)
handler.updateBounds()
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
.onGloballyPositioned { coordinates ->
handler.location = IntOffset(
coordinates.globalPosition.x.toInt(),
coordinates.globalPosition.y.toInt()
)
}
.focusRequester(focusRequester)
.focus()
.clickable(indication = null) { focusRequester.requestFocus() }
) {
Canvas(
modifier = Modifier.size(handler.size.width.dp, handler.size.height.dp)
) {
drawIntoCanvas { canvas ->
recomposer.value
canvas.nativeCanvas.drawBitmapRect(
bitmap,
IRect(0, 0, handler.size.width.toInt(), handler.size.height.toInt()).toRect()
)
}
}
}
}
}

108
cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefBrowserWrapper.kt

@ -1,34 +1,22 @@
package org.jetbrains.compose.desktop.browser
import androidx.compose.ui.unit.IntOffset
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.MouseMotionAdapter
import java.awt.KeyboardFocusManager
import java.nio.ByteBuffer
import org.cef.CefApp
import org.cef.CefClient
import org.cef.CefSettings
import org.cef.browser.CefBrowser
import org.cef.browser.BrowserView
import org.cef.handler.CefFocusHandlerAdapter
import org.jetbrains.skija.Bitmap
import org.jetbrains.skija.ImageInfo
import org.jetbrains.skija.ColorAlphaType
import org.jetbrains.skiko.HardwareLayer
class CefBrowserWrapper {
private var offset = IntOffset(0, 0)
private var isFocused = false
private var cefFocus = true
private val browser: BrowserView
public var onInvalidate: (() -> Unit)? = null
@ -64,86 +52,17 @@ class CefBrowserWrapper {
browser.onFocusLost()
}
})
layer.addMouseListener(object : MouseAdapter() {
override fun mousePressed(event: MouseEvent) {
if (isInLayer(event)) {
browser.onMouseEvent(event)
}
}
override fun mouseReleased(event: MouseEvent) {
if (isInLayer(event)) {
browser.onMouseEvent(event)
}
}
})
layer.addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseMoved(event: MouseEvent) {
if (isInLayer(event)) {
browser.onMouseEvent(event)
}
}
override fun mouseDragged(event: MouseEvent) {
if (isInLayer(event)) {
browser.onMouseEvent(event)
}
}
})
layer.addMouseWheelListener(object : MouseWheelListener {
override fun mouseWheelMoved(event: MouseWheelEvent) {
if (isInLayer(event)) {
browser.onMouseScrollEvent(event)
}
}
})
layer.addKeyListener(object : KeyAdapter() {
override fun keyPressed(event: KeyEvent) {
if (!isFocused) {
return
}
browser.onKeyEvent(event)
}
override fun keyReleased(event: KeyEvent) {
if (!isFocused) {
return
}
browser.onKeyEvent(event)
}
override fun keyTyped(event: KeyEvent) {
if (!isFocused) {
return
}
browser.onKeyEvent(event)
}
})
}
private fun isInLayer(event: MouseEvent): Boolean {
val x = event.x
val y = event.y
if (x > offset.x && y > offset.y) {
return true
}
return false
}
fun loadURL(url: String) {
browser.loadURL(url)
}
fun setFocused(value: Boolean) {
isFocused = value
}
fun getBitmap(): Bitmap {
return browser.getBitmap()
}
fun onLayout(x: Int, y: Int, width: Int, height: Int) {
offset = IntOffset(x, y)
browser.onResized(x, y, width, height)
}
@ -154,4 +73,25 @@ class CefBrowserWrapper {
fun onDismiss() {
CefApp.getInstance().dispose()
}
}
fun onMouseEvent(event: MouseEvent) {
browser.onMouseEvent(event)
}
fun onMouseScrollEvent(event: MouseWheelEvent) {
browser.onMouseScrollEvent(event)
}
fun onKeyEvent(event: KeyEvent) {
if (cefFocus) {
browser.onKeyEvent(event)
}
}
}
internal val emptyBitmap: Bitmap
get() {
val bitmap = Bitmap()
bitmap.allocPixels(ImageInfo.makeS32(1, 1, ColorAlphaType.PREMUL))
return bitmap
}

104
cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefView.kt

@ -1,104 +0,0 @@
package org.jetbrains.compose.desktop.browser
import androidx.compose.desktop.AppWindowAmbient
import androidx.compose.runtime.Composable
import androidx.compose.runtime.emptyContent
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.onActive
import androidx.compose.runtime.onDispose
import androidx.compose.runtime.remember
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.globalPosition
import androidx.compose.material.Surface
import androidx.compose.ui.Modifier
import org.jetbrains.skija.IRect
import org.jetbrains.skija.Bitmap
import org.jetbrains.skija.ImageInfo
import org.jetbrains.skija.ColorAlphaType
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
//EXPERIMENTAL FOCUS API
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.focus
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.isFocused
import androidx.compose.ui.focusObserver
import androidx.compose.ui.focusRequester
import androidx.compose.foundation.clickable
private val width = mutableStateOf(0)
private val height = mutableStateOf(0)
private val x = mutableStateOf(0)
private val y = mutableStateOf(0)
private val emptyBitmap: Bitmap
get() {
val bitmap = Bitmap()
bitmap.allocPixels(ImageInfo.makeS32(1, 1, ColorAlphaType.PREMUL))
return bitmap
}
@OptIn(
ExperimentalFocus::class,
ExperimentalFoundationApi::class
)
@Composable
fun CefView(browser: BrowserState) {
val bitmap = remember { mutableStateOf(emptyBitmap) }
val forceRecompose = remember { mutableStateOf(Any()) }
val focusRequester = FocusRequester()
if (browser.isReady()) {
browser.onInvalidate {
bitmap.value = browser.getBitmap()
forceRecompose.value = Any()
}
Canvas(
modifier = Modifier
.fillMaxSize()
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
width.value = placeable.width
height.value = placeable.height
browser.onLayout(x.value, y.value, width.value, height.value)
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
.onGloballyPositioned { coordinates ->
x.value = coordinates.globalPosition.x.toInt()
y.value = coordinates.globalPosition.y.toInt()
}
.focusRequester(focusRequester)
.focusObserver { browser.setFocused(it.isFocused) }
.focus()
.clickable(indication = null) { focusRequester.requestFocus() }
) {
drawIntoCanvas { canvas ->
forceRecompose.value
bitmap.value
canvas.nativeCanvas.drawBitmapRect(
bitmap.value,
IRect(0, 0, width.value, height.value).toRect()
)
}
}
}
onDispose {
browser.onDismiss()
}
}
Loading…
Cancel
Save