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