spvessel
4 years ago
8 changed files with 601 additions and 281 deletions
@ -0,0 +1,5 @@
|
||||
package org.jetbrains.compose.desktop.browser |
||||
|
||||
interface Browser { |
||||
fun load(url: String) |
||||
} |
@ -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() |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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() |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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…
Reference in new issue