From 906ef972628dee3b2c849f029e9b7ba66fa6868b Mon Sep 17 00:00:00 2001 From: spvessel Date: Fri, 11 Dec 2020 11:59:34 +0300 Subject: [PATCH] Added browser slices support --- cef/README.md | 1 + .../org/jetbrains/compose/desktop/App.kt | 42 ++- .../compose/desktop/browser/Browser.kt | 5 + .../compose/desktop/browser/BrowserSlicer.kt | 299 ++++++++++++++++++ .../compose/desktop/browser/BrowserState.kt | 83 ----- .../compose/desktop/browser/BrowserView.kt | 240 ++++++++++++++ .../desktop/browser/CefBrowserWrapper.kt | 108 ++----- .../compose/desktop/browser/CefView.kt | 104 ------ 8 files changed, 601 insertions(+), 281 deletions(-) create mode 100644 cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/Browser.kt create mode 100644 cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserSlicer.kt delete mode 100644 cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserState.kt create mode 100644 cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt delete mode 100644 cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefView.kt diff --git a/cef/README.md b/cef/README.md index e57a1c11d2..5a05995c2a 100644 --- a/cef/README.md +++ b/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. diff --git a/cef/src/main/kotlin/org/jetbrains/compose/desktop/App.kt b/cef/src/main/kotlin/org/jetbrains/compose/desktop/App.kt index cfbe1a9ecc..74d118887d 100644 --- a/cef/src/main/kotlin/org/jetbrains/compose/desktop/App.kt +++ b/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) { + 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) { +private fun AddressBar(browser: Browser, url: MutableState) { Surface( color = Color.Transparent, modifier = Modifier @@ -74,7 +83,7 @@ private fun AddressBar(browser: BrowserState, url: MutableState) { 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) { } @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() + } + } + } } } diff --git a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/Browser.kt b/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/Browser.kt new file mode 100644 index 0000000000..a1022b17f2 --- /dev/null +++ b/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) +} diff --git a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserSlicer.kt b/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserSlicer.kt new file mode 100644 index 0000000000..6093bd8e5c --- /dev/null +++ b/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 + private lateinit var recomposer: MutableState + private var browser: CefBrowserWrapper? = null + private val isReady = mutableStateOf(false) + fun isReady(): Boolean { + return isReady.value + } + + private var slices = mutableListOf() + 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) { + 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() + ) + } + } + } + } +} diff --git a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserState.kt b/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserState.kt deleted file mode 100644 index e378e40dd1..0000000000 --- a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserState.kt +++ /dev/null @@ -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 - } -} diff --git a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt b/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt new file mode 100644 index 0000000000..4e0d84ef85 --- /dev/null +++ b/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 + private lateinit var recomposer: MutableState + 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) { + 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() + ) + } + } + } + } +} diff --git a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefBrowserWrapper.kt b/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefBrowserWrapper.kt index 101885b9ac..6e6d5d3aef 100644 --- a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefBrowserWrapper.kt +++ b/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() } -} \ No newline at end of file + + 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 + } \ No newline at end of file diff --git a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefView.kt b/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefView.kt deleted file mode 100644 index bc36af2141..0000000000 --- a/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/CefView.kt +++ /dev/null @@ -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() - } -}