spvessel
4 years ago
13 changed files with 1278 additions and 0 deletions
@ -0,0 +1,6 @@
|
||||
# |
||||
# https://help.github.com/articles/dealing-with-line-endings/ |
||||
# |
||||
# These are explicitly windows files and should use crlf |
||||
*.bat text eol=crlf |
||||
|
@ -0,0 +1,5 @@
|
||||
# Ignore Gradle project-specific cache directory |
||||
.gradle |
||||
|
||||
# Ignore Gradle build output directory |
||||
build |
@ -0,0 +1,16 @@
|
||||
CEF integration for Desktop Jetpack Compose. |
||||
|
||||
Setup: |
||||
1. Clone the java-cef repository (``git clone https://bitbucket.org/chromiumembedded/java-cef.git``) into ``/third_party`` directory. |
||||
2. Apply patch ``/third_party/java-cef-jb-compose-patch/jb_compose_support.patch`` to ``/third_party/java-cef`` |
||||
3. Download [skiko-jvm-0.1.6.jar](https://github.com/JetBrains/skiko/releases) library and copy it to ``/third_party/java-cef/third_party/jogamp/jar`` directory. |
||||
4. Follow instructions to compile java-cef ([BranchesAndBuilding](https://bitbucket.org/chromiumembedded/java-cef/wiki/BranchesAndBuilding.md)) **until you reach step 3 of the instruction**. |
||||
5. Make **jcef.jar** - execute command in terminal (from ``/third_party/java-cef/tools`` directory): |
||||
Windows: ``make_jar.bat win64`` |
||||
Linux: ``make_jar.sh linux64`` |
||||
6. Copy **jcef.jar** file from ``/third_party/java-cef/out/win64`` (or ``/third_party/java-cef/out/linux64`` for Linux) to ``/libs`` directory. |
||||
|
||||
Run example: |
||||
To run application execute in terminal: ``./gradlew run`` |
||||
|
||||
PS. Mac OS X is currently not supported. |
@ -0,0 +1,30 @@
|
||||
import org.jetbrains.compose.compose |
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
||||
|
||||
plugins { |
||||
kotlin("jvm") version "1.4.0" |
||||
id("org.jetbrains.compose") version "0.1.0-unmerged30" |
||||
application |
||||
} |
||||
|
||||
repositories { |
||||
google() |
||||
jcenter() |
||||
maven("https://packages.jetbrains.team/maven/p/ui/dev") |
||||
} |
||||
|
||||
dependencies { |
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) |
||||
implementation(compose.desktop.all) |
||||
} |
||||
|
||||
tasks.withType<KotlinCompile>().configureEach { |
||||
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" |
||||
} |
||||
|
||||
val libraryPath = "third_party/java-cef/jcef_build/native/Release" |
||||
|
||||
application { |
||||
applicationDefaultJvmArgs = listOf("-Djava.library.path=$libraryPath") |
||||
mainClassName = "org.jetbrains.compose.desktop.AppKt" |
||||
} |
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionPath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip |
||||
zipStoreBase=GRADLE_USER_HOME |
||||
zipStorePath=wrapper/dists |
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh |
||||
|
||||
# |
||||
# Copyright 2015 the original author or authors. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# https://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
# |
||||
|
||||
############################################################################## |
||||
## |
||||
## Gradle start up script for UN*X |
||||
## |
||||
############################################################################## |
||||
|
||||
# Attempt to set APP_HOME |
||||
# Resolve links: $0 may be a link |
||||
PRG="$0" |
||||
# Need this for relative symlinks. |
||||
while [ -h "$PRG" ] ; do |
||||
ls=`ls -ld "$PRG"` |
||||
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||
if expr "$link" : '/.*' > /dev/null; then |
||||
PRG="$link" |
||||
else |
||||
PRG=`dirname "$PRG"`"/$link" |
||||
fi |
||||
done |
||||
SAVED="`pwd`" |
||||
cd "`dirname \"$PRG\"`/" >/dev/null |
||||
APP_HOME="`pwd -P`" |
||||
cd "$SAVED" >/dev/null |
||||
|
||||
APP_NAME="Gradle" |
||||
APP_BASE_NAME=`basename "$0"` |
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' |
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value. |
||||
MAX_FD="maximum" |
||||
|
||||
warn () { |
||||
echo "$*" |
||||
} |
||||
|
||||
die () { |
||||
echo |
||||
echo "$*" |
||||
echo |
||||
exit 1 |
||||
} |
||||
|
||||
# OS specific support (must be 'true' or 'false'). |
||||
cygwin=false |
||||
msys=false |
||||
darwin=false |
||||
nonstop=false |
||||
case "`uname`" in |
||||
CYGWIN* ) |
||||
cygwin=true |
||||
;; |
||||
Darwin* ) |
||||
darwin=true |
||||
;; |
||||
MINGW* ) |
||||
msys=true |
||||
;; |
||||
NONSTOP* ) |
||||
nonstop=true |
||||
;; |
||||
esac |
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM. |
||||
if [ -n "$JAVA_HOME" ] ; then |
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||
# IBM's JDK on AIX uses strange locations for the executables |
||||
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||
else |
||||
JAVACMD="$JAVA_HOME/bin/java" |
||||
fi |
||||
if [ ! -x "$JAVACMD" ] ; then |
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the |
||||
location of your Java installation." |
||||
fi |
||||
else |
||||
JAVACMD="java" |
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the |
||||
location of your Java installation." |
||||
fi |
||||
|
||||
# Increase the maximum file descriptors if we can. |
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
||||
MAX_FD_LIMIT=`ulimit -H -n` |
||||
if [ $? -eq 0 ] ; then |
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
||||
MAX_FD="$MAX_FD_LIMIT" |
||||
fi |
||||
ulimit -n $MAX_FD |
||||
if [ $? -ne 0 ] ; then |
||||
warn "Could not set maximum file descriptor limit: $MAX_FD" |
||||
fi |
||||
else |
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
||||
fi |
||||
fi |
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock |
||||
if $darwin; then |
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
||||
fi |
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java |
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"` |
||||
|
||||
# We build the pattern for arguments to be converted via cygpath |
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
||||
SEP="" |
||||
for dir in $ROOTDIRSRAW ; do |
||||
ROOTDIRS="$ROOTDIRS$SEP$dir" |
||||
SEP="|" |
||||
done |
||||
OURCYGPATTERN="(^($ROOTDIRS))" |
||||
# Add a user-defined pattern to the cygpath arguments |
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
||||
fi |
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
||||
i=0 |
||||
for arg in "$@" ; do |
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
||||
else |
||||
eval `echo args$i`="\"$arg\"" |
||||
fi |
||||
i=`expr $i + 1` |
||||
done |
||||
case $i in |
||||
0) set -- ;; |
||||
1) set -- "$args0" ;; |
||||
2) set -- "$args0" "$args1" ;; |
||||
3) set -- "$args0" "$args1" "$args2" ;; |
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
||||
esac |
||||
fi |
||||
|
||||
# Escape application args |
||||
save () { |
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
||||
echo " " |
||||
} |
||||
APP_ARGS=`save "$@"` |
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules |
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
||||
|
||||
exec "$JAVACMD" "$@" |
@ -0,0 +1,104 @@
|
||||
@rem |
||||
@rem Copyright 2015 the original author or authors. |
||||
@rem |
||||
@rem Licensed under the Apache License, Version 2.0 (the "License"); |
||||
@rem you may not use this file except in compliance with the License. |
||||
@rem You may obtain a copy of the License at |
||||
@rem |
||||
@rem https://www.apache.org/licenses/LICENSE-2.0 |
||||
@rem |
||||
@rem Unless required by applicable law or agreed to in writing, software |
||||
@rem distributed under the License is distributed on an "AS IS" BASIS, |
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
@rem See the License for the specific language governing permissions and |
||||
@rem limitations under the License. |
||||
@rem |
||||
|
||||
@if "%DEBUG%" == "" @echo off |
||||
@rem ########################################################################## |
||||
@rem |
||||
@rem Gradle startup script for Windows |
||||
@rem |
||||
@rem ########################################################################## |
||||
|
||||
@rem Set local scope for the variables with windows NT shell |
||||
if "%OS%"=="Windows_NT" setlocal |
||||
|
||||
set DIRNAME=%~dp0 |
||||
if "%DIRNAME%" == "" set DIRNAME=. |
||||
set APP_BASE_NAME=%~n0 |
||||
set APP_HOME=%DIRNAME% |
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter. |
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi |
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" |
||||
|
||||
@rem Find java.exe |
||||
if defined JAVA_HOME goto findJavaFromJavaHome |
||||
|
||||
set JAVA_EXE=java.exe |
||||
%JAVA_EXE% -version >NUL 2>&1 |
||||
if "%ERRORLEVEL%" == "0" goto init |
||||
|
||||
echo. |
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||
echo. |
||||
echo Please set the JAVA_HOME variable in your environment to match the |
||||
echo location of your Java installation. |
||||
|
||||
goto fail |
||||
|
||||
:findJavaFromJavaHome |
||||
set JAVA_HOME=%JAVA_HOME:"=% |
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
||||
|
||||
if exist "%JAVA_EXE%" goto init |
||||
|
||||
echo. |
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
||||
echo. |
||||
echo Please set the JAVA_HOME variable in your environment to match the |
||||
echo location of your Java installation. |
||||
|
||||
goto fail |
||||
|
||||
:init |
||||
@rem Get command-line arguments, handling Windows variants |
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args |
||||
|
||||
:win9xME_args |
||||
@rem Slurp the command line arguments. |
||||
set CMD_LINE_ARGS= |
||||
set _SKIP=2 |
||||
|
||||
:win9xME_args_slurp |
||||
if "x%~1" == "x" goto execute |
||||
|
||||
set CMD_LINE_ARGS=%* |
||||
|
||||
:execute |
||||
@rem Setup the command line |
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
||||
|
||||
|
||||
@rem Execute Gradle |
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% |
||||
|
||||
:end |
||||
@rem End local scope for the variables with windows NT shell |
||||
if "%ERRORLEVEL%"=="0" goto mainEnd |
||||
|
||||
:fail |
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
||||
rem the _cmd.exe /c_ return code! |
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
||||
exit /b 1 |
||||
|
||||
:mainEnd |
||||
if "%OS%"=="Windows_NT" endlocal |
||||
|
||||
:omega |
@ -0,0 +1,8 @@
|
||||
pluginManagement { |
||||
repositories { |
||||
gradlePluginPortal() |
||||
maven("https://packages.jetbrains.team/maven/p/ui/dev") |
||||
} |
||||
} |
||||
|
||||
rootProject.name = "example-cef-compose" |
@ -0,0 +1,89 @@
|
||||
package org.jetbrains.compose.desktop |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.MutableState |
||||
import androidx.compose.desktop.AppWindow |
||||
import androidx.compose.foundation.layout.Column |
||||
import androidx.compose.foundation.layout.Row |
||||
import androidx.compose.foundation.layout.Spacer |
||||
import androidx.compose.foundation.layout.width |
||||
import androidx.compose.foundation.layout.height |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.foundation.layout.preferredHeight |
||||
import androidx.compose.foundation.layout.preferredSize |
||||
import androidx.compose.foundation.layout.padding |
||||
import androidx.compose.material.Surface |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.compose.ui.unit.IntSize |
||||
import androidx.compose.foundation.shape.CircleShape |
||||
import androidx.compose.material.CircularProgressIndicator |
||||
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 |
||||
|
||||
fun main() { |
||||
|
||||
AppWindow("CEF-compose", IntSize(800, 800)).show { |
||||
Surface( |
||||
modifier = Modifier.fillMaxSize(), |
||||
color = Color.DarkGray |
||||
) { |
||||
Column { |
||||
AddressBar() |
||||
Spacer(Modifier.height(10.dp)) |
||||
WebView() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
private fun AddressBar() { |
||||
Surface( |
||||
color = Color.Transparent, |
||||
modifier = Modifier |
||||
.preferredHeight(58.dp) |
||||
.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 0.dp) |
||||
) { |
||||
Row { |
||||
TextField( |
||||
backgroundColor = Color.White, |
||||
activeColor = Color.DarkGray, |
||||
inactiveColor = Color.DarkGray, |
||||
value = BrowserState.url.value, |
||||
onValueChange = { |
||||
BrowserState.url.value = it |
||||
}, |
||||
modifier = Modifier.weight(1f), |
||||
shape = CircleShape, |
||||
label = { } |
||||
) |
||||
Spacer(Modifier.width(10.dp)) |
||||
Button( |
||||
backgroundColor = Color(16, 180, 140), |
||||
modifier = Modifier.preferredHeight(48.dp), |
||||
shape = CircleShape, |
||||
onClick = { BrowserState.loadURL(BrowserState.url.value) } |
||||
) { |
||||
Text(text = "Go!") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
private fun WebView() { |
||||
Surface( |
||||
color = Color.Gray, |
||||
modifier = Modifier.fillMaxSize().padding(10.dp) |
||||
) { |
||||
if (BrowserState.isReady()) { |
||||
CefView() |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,89 @@
|
||||
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 androidx.compose.desktop.AppWindowAmbient |
||||
import org.jetbrains.skija.Bitmap |
||||
import org.jetbrains.skiko.HardwareLayer |
||||
import javax.swing.JFrame |
||||
import org.cef.CefApp |
||||
|
||||
object BrowserState { |
||||
public val url = mutableStateOf("https://www.google.com") |
||||
|
||||
private val isReady = mutableStateOf(false) |
||||
private val frames = mutableStateOf(0) |
||||
private lateinit var browser: ComposeBrowserWrapper |
||||
|
||||
fun isReady(): Boolean { |
||||
return isReady.value |
||||
} |
||||
|
||||
fun loadURL(url: String) { |
||||
if (!isReady.value) { |
||||
val app = AppManager.getCurrentFocusedWindow() |
||||
if (app != null) { |
||||
init(app, url) |
||||
} |
||||
} else { |
||||
isReady.value = false |
||||
browser.loadURL(url) |
||||
isReady.value = true |
||||
} |
||||
} |
||||
|
||||
fun init(app: AppFrame, url: String) { |
||||
val window = app.window |
||||
if (!window.isVisible()) { |
||||
return |
||||
} |
||||
var layer = getHardwareLayer(window) |
||||
if (layer == null) { |
||||
throw Error("Browser initialization failed!") |
||||
} |
||||
browser = ComposeBrowserWrapper( |
||||
startURL = url, |
||||
layer = layer |
||||
) |
||||
isReady.value = true |
||||
} |
||||
|
||||
fun getBitmap(): Bitmap { |
||||
frames.value++ |
||||
return browser.getBitmap() |
||||
} |
||||
|
||||
fun onLayout(x: Int, y: Int, width: Int, height: Int) { |
||||
browser.onLayout(x, y, width, height) |
||||
} |
||||
|
||||
fun onActive() { |
||||
browser.onActive() |
||||
} |
||||
|
||||
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,106 @@
|
||||
package org.jetbrains.compose.desktop.browser |
||||
|
||||
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 |
||||
import androidx.compose.ui.layout.globalPosition |
||||
import androidx.compose.ui.onPositioned |
||||
import androidx.compose.material.Surface |
||||
import androidx.compose.ui.Modifier |
||||
|
||||
import org.jetbrains.skija.IRect |
||||
import org.jetbrains.skija.Bitmap |
||||
|
||||
import androidx.compose.ui.graphics.drawscope.drawCanvas |
||||
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) |
||||
|
||||
@Composable |
||||
fun CefView() { |
||||
CefLayout(BrowserState) |
||||
} |
||||
|
||||
@Composable |
||||
fun CefLayout(browser: BrowserState) { |
||||
|
||||
var bitmap by mutableStateOf(browser.getBitmap()) |
||||
|
||||
browser.onInvalidate( |
||||
onInvalidate = { |
||||
bitmap = browser.getBitmap() |
||||
}) |
||||
|
||||
CefCanvas(bitmap, browser) |
||||
|
||||
onActive { |
||||
browser.onActive() |
||||
} |
||||
|
||||
onDispose { |
||||
browser.onDismiss() |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
@OptIn( |
||||
ExperimentalFocus::class, |
||||
ExperimentalFoundationApi::class |
||||
) |
||||
fun CefCanvas(bitmap: Bitmap, browser: BrowserState) { |
||||
val focusRequester = FocusRequester() |
||||
|
||||
Canvas( |
||||
modifier = Modifier |
||||
.fillMaxSize() |
||||
.onResized(browser) |
||||
.onPositioned { 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() } |
||||
) { |
||||
drawCanvas { canvas, size -> |
||||
canvas.nativeCanvas.drawBitmapRect(bitmap, IRect(0, 0, width.value, height.value).toRect()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun Modifier.onResized(browser: BrowserState) = Modifier.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) |
||||
} |
||||
} |
@ -0,0 +1,152 @@
|
||||
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.skiko.HardwareLayer |
||||
|
||||
open class ComposeBrowserWrapper { |
||||
private var offset = IntOffset(0, 0) |
||||
private var isFocused = false |
||||
private var cefFocus = true |
||||
private val browser: BrowserView |
||||
public var onInvalidate: (() -> Unit)? = null |
||||
|
||||
constructor(layer: HardwareLayer, startURL: String) { |
||||
if (!CefApp.startup(arrayOf(""))) { |
||||
throw Error("CEF initialization failed!") |
||||
} |
||||
val settings = CefSettings() |
||||
settings.windowless_rendering_enabled = true |
||||
val cefApp = CefApp.getInstance(settings) |
||||
val client = cefApp.createClient() |
||||
|
||||
browser = object : BrowserView(layer, client, startURL, null) { |
||||
public override fun onBitmapChanged(popup: Boolean, buffer: ByteBuffer, width: Int, height: Int) { |
||||
super.onBitmapChanged(popup, buffer, width, height) |
||||
onInvalidate?.invoke() |
||||
} |
||||
} |
||||
|
||||
client.addFocusHandler(object : CefFocusHandlerAdapter() { |
||||
public override fun onGotFocus(cefBrowser: CefBrowser) { |
||||
if (cefFocus) { |
||||
return |
||||
} |
||||
cefFocus = true |
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner() |
||||
browser.onFocusGained() |
||||
} |
||||
|
||||
public override fun onTakeFocus(cefBrowser: CefBrowser, next: Boolean) { |
||||
cefFocus = false |
||||
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) |
||||
} |
||||
|
||||
fun onActive() { |
||||
browser.onStart() |
||||
} |
||||
|
||||
fun onDismiss() { |
||||
CefApp.getInstance().dispose() |
||||
} |
||||
} |
@ -0,0 +1,483 @@
|
||||
diff --git a/build.xml b/build.xml
|
||||
index ce2d8d7..d0afff6 100644
|
||||
--- a/build.xml
|
||||
+++ b/build.xml
|
||||
@@ -18,6 +18,10 @@
|
||||
<include name="gluegen-rt-natives-macosx-universal.jar"/>
|
||||
<include name="jogl-all.jar"/>
|
||||
<include name="jogl-all-natives-macosx-universal.jar"/>
|
||||
+ <!-- SKIKO: copy skiko and kotlin-stdlib artifacts to "third_party/jogamp/jar" directory -->
|
||||
+ <include name="skiko-jvm-0.1.6.jar"/>
|
||||
+ <include name="kotlin-stdlib-1.4.0.jar"/>
|
||||
+ <!-- SKIKO -->
|
||||
</fileset>
|
||||
<fileset dir="third_party/junit" includes="junit*.jar"/>
|
||||
</path>
|
||||
@@ -71,6 +75,10 @@
|
||||
<include name="gluegen-rt-natives-macosx-universal.jar" />
|
||||
<include name="jogl-all.jar" />
|
||||
<include name="jogl-all-natives-macosx-universal.jar" />
|
||||
+ <!-- SKIKO: copy skiko and kotlin-stdlib artifacts to "third_party/jogamp/jar" directory -->
|
||||
+ <include name="skiko-jvm-0.1.6.jar"/>
|
||||
+ <include name="kotlin-stdlib-1.4.0.jar"/>
|
||||
+ <!-- SKIKO -->
|
||||
</classpath>
|
||||
<classpath dir="third_party/junit" includes="junit*.jar"/>
|
||||
<option value="-Djava.library.path=$APP_ROOT/Contents/Java/:$APP_ROOT/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries" />
|
||||
diff --git a/java/org/cef/browser/BrowserDropTargetListener.java b/java/org/cef/browser/BrowserDropTargetListener.java
|
||||
new file mode 100644
|
||||
index 0000000..5baaa86
|
||||
--- /dev/null
|
||||
+++ b/java/org/cef/browser/BrowserDropTargetListener.java
|
||||
@@ -0,0 +1,123 @@
|
||||
+// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights
|
||||
+// reserved. Use of this source code is governed by a BSD-style license that
|
||||
+// can be found in the LICENSE file.
|
||||
+
|
||||
+package org.cef.browser;
|
||||
+
|
||||
+import org.cef.callback.CefDragData;
|
||||
+import org.cef.misc.EventFlags;
|
||||
+
|
||||
+import java.awt.datatransfer.DataFlavor;
|
||||
+import java.awt.datatransfer.Transferable;
|
||||
+import java.awt.dnd.DnDConstants;
|
||||
+import java.awt.dnd.DropTargetDragEvent;
|
||||
+import java.awt.dnd.DropTargetDropEvent;
|
||||
+import java.awt.dnd.DropTargetEvent;
|
||||
+import java.awt.dnd.DropTargetListener;
|
||||
+import java.io.File;
|
||||
+import java.util.List;
|
||||
+
|
||||
+class BrowserDropTargetListener implements DropTargetListener {
|
||||
+ private BrowserView browser_;
|
||||
+ private CefDragData dragData_ = null;
|
||||
+ private int dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY;
|
||||
+ private int dragModifiers_ = EventFlags.EVENTFLAG_NONE;
|
||||
+ private int acceptOperations_ = DnDConstants.ACTION_COPY;
|
||||
+
|
||||
+ BrowserDropTargetListener(BrowserView browser) {
|
||||
+ browser_ = browser;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void dragEnter(DropTargetDragEvent event) {
|
||||
+ CreateDragData(event);
|
||||
+ browser_.dragTargetDragEnter(
|
||||
+ dragData_, event.getLocation(), dragModifiers_, dragOperations_);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void dragExit(DropTargetEvent event) {
|
||||
+ AssertDragData();
|
||||
+ browser_.dragTargetDragLeave();
|
||||
+ ClearDragData();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void dragOver(DropTargetDragEvent event) {
|
||||
+ AssertDragData();
|
||||
+ browser_.dragTargetDragOver(event.getLocation(), dragModifiers_, dragOperations_);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void dropActionChanged(DropTargetDragEvent event) {
|
||||
+ AssertDragData();
|
||||
+ acceptOperations_ = event.getDropAction();
|
||||
+ switch (acceptOperations_) {
|
||||
+ case DnDConstants.ACTION_LINK:
|
||||
+ dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_LINK;
|
||||
+ dragModifiers_ =
|
||||
+ EventFlags.EVENTFLAG_CONTROL_DOWN | EventFlags.EVENTFLAG_SHIFT_DOWN;
|
||||
+ break;
|
||||
+ case DnDConstants.ACTION_COPY:
|
||||
+ dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY;
|
||||
+ dragModifiers_ = EventFlags.EVENTFLAG_CONTROL_DOWN;
|
||||
+ break;
|
||||
+ case DnDConstants.ACTION_MOVE:
|
||||
+ dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_MOVE;
|
||||
+ dragModifiers_ = EventFlags.EVENTFLAG_SHIFT_DOWN;
|
||||
+ break;
|
||||
+ case DnDConstants.ACTION_NONE:
|
||||
+ // The user did not select an action, so use COPY as the default.
|
||||
+ dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY;
|
||||
+ dragModifiers_ = EventFlags.EVENTFLAG_NONE;
|
||||
+ acceptOperations_ = DnDConstants.ACTION_COPY;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void drop(DropTargetDropEvent event) {
|
||||
+ AssertDragData();
|
||||
+ browser_.dragTargetDrop(event.getLocation(), dragModifiers_);
|
||||
+ event.acceptDrop(acceptOperations_);
|
||||
+ event.dropComplete(true);
|
||||
+ ClearDragData();
|
||||
+ }
|
||||
+
|
||||
+ private void CreateDragData(DropTargetDragEvent event) {
|
||||
+ assert dragData_ == null;
|
||||
+ dragData_ = createDragData(event);
|
||||
+ dropActionChanged(event);
|
||||
+ }
|
||||
+
|
||||
+ private void AssertDragData() {
|
||||
+ assert dragData_ != null;
|
||||
+ }
|
||||
+
|
||||
+ private void ClearDragData() {
|
||||
+ dragData_ = null;
|
||||
+ }
|
||||
+
|
||||
+ private static CefDragData createDragData(DropTargetDragEvent event) {
|
||||
+ CefDragData dragData = CefDragData.create();
|
||||
+
|
||||
+ Transferable transferable = event.getTransferable();
|
||||
+ DataFlavor[] flavors = transferable.getTransferDataFlavors();
|
||||
+ for (DataFlavor flavor : flavors) {
|
||||
+ try {
|
||||
+ // TODO(JCEF): Add support for other flavor types.
|
||||
+ if (flavor.isFlavorJavaFileListType()) {
|
||||
+ List<File> files = (List<File>) transferable.getTransferData(flavor);
|
||||
+ for (File file : files) {
|
||||
+ dragData.addFile(file.getPath(), file.getName());
|
||||
+ }
|
||||
+ }
|
||||
+ } catch (Exception e) {
|
||||
+ // Data is no longer available or of unsupported flavor.
|
||||
+ e.printStackTrace();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return dragData;
|
||||
+ }
|
||||
+}
|
||||
diff --git a/java/org/cef/browser/BrowserView.java b/java/org/cef/browser/BrowserView.java
|
||||
new file mode 100644
|
||||
index 0000000..fdc44ac
|
||||
--- /dev/null
|
||||
+++ b/java/org/cef/browser/BrowserView.java
|
||||
@@ -0,0 +1,209 @@
|
||||
+package org.cef.browser;
|
||||
+
|
||||
+import org.cef.CefClient;
|
||||
+import org.cef.callback.CefDragData;
|
||||
+import org.cef.handler.CefRenderHandler;
|
||||
+import org.cef.handler.CefScreenInfo;
|
||||
+
|
||||
+import java.awt.Component;
|
||||
+import java.awt.Cursor;
|
||||
+import java.awt.Graphics;
|
||||
+import java.awt.Point;
|
||||
+import java.awt.Rectangle;
|
||||
+import java.awt.dnd.DropTarget;
|
||||
+import java.awt.event.FocusEvent;
|
||||
+import java.awt.event.FocusListener;
|
||||
+import java.awt.event.KeyEvent;
|
||||
+import java.awt.event.KeyListener;
|
||||
+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.image.BufferedImage;
|
||||
+import java.util.concurrent.CompletableFuture;
|
||||
+import java.nio.ByteBuffer;
|
||||
+import java.util.concurrent.locks.Lock;
|
||||
+import java.util.concurrent.locks.ReentrantLock;
|
||||
+
|
||||
+import javax.swing.MenuSelectionManager;
|
||||
+import javax.swing.SwingUtilities;
|
||||
+import javax.swing.JFrame;
|
||||
+
|
||||
+import org.jetbrains.skija.Bitmap;
|
||||
+import org.jetbrains.skiko.OpenGLApi;
|
||||
+import org.jetbrains.skiko.HardwareLayer;
|
||||
+
|
||||
+public class BrowserView extends CefBrowser_N implements CefRenderHandler {
|
||||
+ private BrowserViewBitmap bitmapHandler;
|
||||
+ private HardwareLayer canvas;
|
||||
+ private long windowHandle = 0;
|
||||
+ private Rectangle browserRect = new Rectangle(0, 0, 1, 1); // Work around CEF issue #1437.
|
||||
+ private Point screenPoint = new Point(0, 0);
|
||||
+ private Lock locker = new ReentrantLock();
|
||||
+
|
||||
+ public BrowserView(HardwareLayer canvas, CefClient client, String url, CefRequestContext context) {
|
||||
+ this(canvas, client, url, context, null, null);
|
||||
+ }
|
||||
+
|
||||
+ private BrowserView(HardwareLayer canvas, CefClient client, String url, CefRequestContext context,
|
||||
+ BrowserView parent, Point inspectAt) {
|
||||
+ super(client, url, context, parent, inspectAt);
|
||||
+ this.canvas = canvas;
|
||||
+ bitmapHandler = new BrowserViewBitmap();
|
||||
+ new DropTarget(canvas, new BrowserDropTargetListener(this));
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void createImmediately() {
|
||||
+ createBrowserIfRequired(false);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Component getUIComponent() {
|
||||
+ return canvas;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public CefRenderHandler getRenderHandler() {
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ protected CefBrowser_N createDevToolsBrowser(CefClient client, String url, CefRequestContext context,
|
||||
+ CefBrowser_N parent, Point inspectAt) {
|
||||
+ return new BrowserView(canvas, client, url, context, (BrowserView) this, inspectAt);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Rectangle getViewRect(CefBrowser browser) {
|
||||
+ return browserRect;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Point getScreenPoint(CefBrowser browser, Point viewPoint) {
|
||||
+ Point sp = new Point(screenPoint);
|
||||
+ sp.translate(viewPoint.x, viewPoint.y);
|
||||
+ return sp;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onPopupShow(CefBrowser browser, boolean show) {
|
||||
+ if (!show) {
|
||||
+ bitmapHandler.clearPopupRects();
|
||||
+ invalidate();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onPopupSize(CefBrowser browser, Rectangle size) {
|
||||
+ bitmapHandler.onPopupSize(size);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width,
|
||||
+ int height) {
|
||||
+ onBitmapChanged(popup, buffer, width, height);
|
||||
+ }
|
||||
+
|
||||
+ public void onBitmapChanged(boolean popup, ByteBuffer buffer, int width, int height) {
|
||||
+ bitmapHandler.setBitmapData(popup, buffer, width, height);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onCursorChange(CefBrowser browser, final int cursorType) {
|
||||
+ SwingUtilities.invokeLater(() -> {
|
||||
+ canvas.setCursor(new Cursor(cursorType));
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void updateDragCursor(CefBrowser browser, int operation) {
|
||||
+ }
|
||||
+
|
||||
+ public void onMouseEvent(MouseEvent event) {
|
||||
+ event.translatePoint(-browserRect.x, -browserRect.y);
|
||||
+ sendMouseEvent(event);
|
||||
+ }
|
||||
+
|
||||
+ public void onMouseScrollEvent(MouseWheelEvent event) {
|
||||
+ event.translatePoint(browserRect.x, browserRect.y);
|
||||
+ sendMouseWheelEvent(event);
|
||||
+ }
|
||||
+
|
||||
+ public void onKeyEvent(KeyEvent event) {
|
||||
+ sendKeyEvent(event);
|
||||
+ }
|
||||
+
|
||||
+ public void onResized(int x, int y, int width, int height) {
|
||||
+ browserRect.setBounds(x, y, width, height);
|
||||
+ screenPoint = canvas.getLocationOnScreen();
|
||||
+ wasResized(width, height);
|
||||
+ }
|
||||
+
|
||||
+ public void onFocusGained() {
|
||||
+ if (windowHandle != 0) {
|
||||
+ MenuSelectionManager.defaultManager().clearSelectedPath();
|
||||
+ setFocus(true);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void onFocusLost() {
|
||||
+ if (windowHandle != 0) {
|
||||
+ setFocus(false);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void dispose() {
|
||||
+ bitmapHandler.clean();
|
||||
+ }
|
||||
+
|
||||
+ public void onStart() {
|
||||
+ SwingUtilities.invokeLater(() -> {
|
||||
+ createBrowserIfRequired(true);
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ public Bitmap getBitmap() {
|
||||
+ return bitmapHandler.getBitmap();
|
||||
+ }
|
||||
+
|
||||
+ private void createBrowserIfRequired(boolean hasParent) {
|
||||
+ long windowHandle = 0;
|
||||
+ if (hasParent) {
|
||||
+ windowHandle = getWindowHandle();
|
||||
+ }
|
||||
+ if (getNativeRef("CefBrowser") == 0) {
|
||||
+ if (getParentBrowser() != null) {
|
||||
+ createDevTools(getParentBrowser(), getClient(), windowHandle, true, false, null, getInspectAt());
|
||||
+ } else {
|
||||
+ createBrowser(getClient(), windowHandle, getUrl(), true, false, null, getRequestContext());
|
||||
+ }
|
||||
+ } else {
|
||||
+ setFocus(true);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private synchronized long getWindowHandle() {
|
||||
+ if (windowHandle == 0) {
|
||||
+ windowHandle = canvas.getWindowHandle();
|
||||
+ }
|
||||
+ return windowHandle;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public CompletableFuture<BufferedImage> createScreenshot(boolean nativeResolution) {
|
||||
+ throw new UnsupportedOperationException("BrowserView:createScreenshot - Not implemented, yet.\n");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean getScreenInfo(CefBrowser browser, CefScreenInfo screenInfo) {
|
||||
+ // TODO Auto-generated method stub
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
diff --git a/java/org/cef/browser/BrowserViewBitmap.java b/java/org/cef/browser/BrowserViewBitmap.java
|
||||
new file mode 100644
|
||||
index 0000000..036075b
|
||||
--- /dev/null
|
||||
+++ b/java/org/cef/browser/BrowserViewBitmap.java
|
||||
@@ -0,0 +1,107 @@
|
||||
+package org.cef.browser;
|
||||
+
|
||||
+import java.awt.Rectangle;
|
||||
+import java.nio.ByteBuffer;
|
||||
+import java.util.concurrent.locks.Lock;
|
||||
+import java.util.concurrent.locks.ReentrantLock;
|
||||
+
|
||||
+import org.jetbrains.skija.ColorAlphaType;
|
||||
+import org.jetbrains.skija.Bitmap;
|
||||
+import org.jetbrains.skija.ImageInfo;
|
||||
+
|
||||
+class BrowserViewBitmap {
|
||||
+
|
||||
+ private Bitmap bitmap = null;
|
||||
+ private byte[] pixels = null;
|
||||
+ private int width = 0;
|
||||
+ private int height = 0;
|
||||
+ private Rectangle popupRect = new Rectangle(0, 0, 0, 0);
|
||||
+ private Rectangle popupOriginRect = new Rectangle(0, 0, 0, 0);
|
||||
+ private boolean popup = false;
|
||||
+
|
||||
+ private Lock lock = new ReentrantLock();
|
||||
+
|
||||
+ void clean() {
|
||||
+ width = height = 0;
|
||||
+ }
|
||||
+
|
||||
+ private byte[] getBytes(ByteBuffer buffer, int width, int height) {
|
||||
+ byte[] pixels = new byte[buffer.capacity()];
|
||||
+ int index = 0;
|
||||
+ for (int y = 0; y < height; y++) {
|
||||
+ for (int x = 0; x < width; x++) {
|
||||
+ pixels[index++] = buffer.get((x + y * width) * 4 + 0);
|
||||
+ pixels[index++] = buffer.get((x + y * width) * 4 + 1);
|
||||
+ pixels[index++] = buffer.get((x + y * width) * 4 + 2);
|
||||
+ pixels[index++] = buffer.get((x + y * width) * 4 + 3);
|
||||
+ }
|
||||
+ }
|
||||
+ return pixels;
|
||||
+ }
|
||||
+
|
||||
+ Bitmap getBitmap() {
|
||||
+ lock.lock();
|
||||
+ try {
|
||||
+ if (bitmap == null) {
|
||||
+ init();
|
||||
+ }
|
||||
+ return bitmap;
|
||||
+ } finally {
|
||||
+ lock.unlock();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ void init() {
|
||||
+ bitmap = new Bitmap();
|
||||
+ bitmap.allocPixels(ImageInfo.makeS32((int) width, (int) height, ColorAlphaType.PREMUL));
|
||||
+ }
|
||||
+
|
||||
+ void setBitmapData(boolean popup, ByteBuffer buffer, int width, int height) {
|
||||
+ lock.lock();
|
||||
+ try {
|
||||
+ this.popup = popup;
|
||||
+ if (this.width != width || this.height != height) {
|
||||
+ this.height = height;
|
||||
+ this.width = width;
|
||||
+ init();
|
||||
+ }
|
||||
+ pixels = getBytes(buffer, width, height);
|
||||
+ bitmap.installPixels(bitmap.getImageInfo(), pixels, width * 4);
|
||||
+ } finally {
|
||||
+ lock.unlock();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ void onPopupSize(Rectangle rect) {
|
||||
+ if (rect.width <= 0 || rect.height <= 0)
|
||||
+ return;
|
||||
+ popupOriginRect = rect;
|
||||
+ popupRect = getPopupRectInWebView(popupOriginRect);
|
||||
+ }
|
||||
+
|
||||
+ Rectangle getPopupRect() {
|
||||
+ return (Rectangle) popupRect.clone();
|
||||
+ }
|
||||
+
|
||||
+ Rectangle getPopupRectInWebView(Rectangle originalRect) {
|
||||
+ Rectangle rc = originalRect;
|
||||
+ if (rc.x < 0)
|
||||
+ rc.x = 0;
|
||||
+ if (rc.y < 0)
|
||||
+ rc.y = 0;
|
||||
+ if (rc.x + rc.width > width)
|
||||
+ rc.x = width - rc.width;
|
||||
+ if (rc.y + rc.height > height)
|
||||
+ rc.y = height - rc.height;
|
||||
+ if (rc.x < 0)
|
||||
+ rc.x = 0;
|
||||
+ if (rc.y < 0)
|
||||
+ rc.y = 0;
|
||||
+ return rc;
|
||||
+ }
|
||||
+
|
||||
+ void clearPopupRects() {
|
||||
+ popupRect.setBounds(0, 0, 0, 0);
|
||||
+ popupOriginRect.setBounds(0, 0, 0, 0);
|
||||
+ }
|
||||
+}
|
Loading…
Reference in new issue