mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 23:07:35 +00:00
Ladybird: Add Android View for WebView and paint into it from bindings
This allows our WebViewImplementationNative class to paint into an Android Bitmap class from the onDraw() view event. We also set the viewport geometry from the view since we're setting the Kotlin Bitmap at the same time.
This commit is contained in:
parent
8d05ff1fe7
commit
1d369cb722
9 changed files with 162 additions and 31 deletions
|
@ -72,6 +72,7 @@ dependencies {
|
|||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
|
|
@ -4,31 +4,78 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibWebView/ViewImplementation.h>
|
||||
#include <android/bitmap.h>
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
|
||||
using namespace WebView;
|
||||
|
||||
namespace {
|
||||
|
||||
Gfx::BitmapFormat to_gfx_bitmap_format(i32 f)
|
||||
{
|
||||
switch (f) {
|
||||
case ANDROID_BITMAP_FORMAT_RGBA_8888:
|
||||
return Gfx::BitmapFormat::BGRA8888;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
class WebViewImplementationNative : public WebView::ViewImplementation {
|
||||
public:
|
||||
virtual Gfx::IntRect viewport_rect() const override { return {}; }
|
||||
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint) const override { return {}; }
|
||||
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint) const override { return {}; }
|
||||
virtual Gfx::IntRect viewport_rect() const override { return m_viewport_rect; }
|
||||
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint p) const override { return p; }
|
||||
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint p) const override { return p; }
|
||||
virtual void update_zoom() override { }
|
||||
|
||||
void paint_into_bitmap(void* android_bitmap_raw, AndroidBitmapInfo const& info)
|
||||
{
|
||||
// Software bitmaps only for now!
|
||||
VERIFY((info.flags & ANDROID_BITMAP_FLAGS_IS_HARDWARE) == 0);
|
||||
|
||||
auto android_bitmap = MUST(Gfx::Bitmap::create_wrapper(to_gfx_bitmap_format(info.format), { info.width, info.height }, 1, info.stride, android_bitmap_raw));
|
||||
Gfx::Painter painter(android_bitmap);
|
||||
if (auto* bitmap = m_client_state.has_usable_bitmap ? m_client_state.front_bitmap.bitmap.ptr() : m_backup_bitmap.ptr())
|
||||
painter.blit({ 0, 0 }, *bitmap, bitmap->rect());
|
||||
else
|
||||
painter.clear_rect(painter.clip_rect(), Gfx::Color::Magenta);
|
||||
|
||||
// Convert our internal BGRA into RGBA. This will be slowwwwwww
|
||||
// FIXME: Don't do a color format swap here.
|
||||
for (auto y = 0; y < android_bitmap->height(); ++y) {
|
||||
auto* scanline = android_bitmap->scanline(y);
|
||||
for (auto x = 0; x < android_bitmap->width(); ++x) {
|
||||
auto current_pixel = scanline[x];
|
||||
u32 alpha = (current_pixel & 0xFF000000U) >> 24;
|
||||
u32 red = (current_pixel & 0x00FF0000U) >> 16;
|
||||
u32 green = (current_pixel & 0x0000FF00U) >> 8;
|
||||
u32 blue = (current_pixel & 0x000000FFU);
|
||||
scanline[x] = (alpha << 24U) | (blue << 16U) | (green << 8U) | red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_viewport_geometry(int w, int h)
|
||||
{
|
||||
m_viewport_rect = { { 0, 0 }, { w, h } };
|
||||
}
|
||||
|
||||
static jclass global_class_reference;
|
||||
static jfieldID instance_pointer_field;
|
||||
|
||||
private:
|
||||
Gfx::IntRect m_viewport_rect;
|
||||
};
|
||||
jclass WebViewImplementationNative::global_class_reference;
|
||||
jfieldID WebViewImplementationNative::instance_pointer_field;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_serenityos_ladybird_WebViewImplementationNative_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */)
|
||||
Java_org_serenityos_ladybird_WebViewImplementation_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */)
|
||||
{
|
||||
auto local_class = env->FindClass("org/serenityos/ladybird/WebViewImplementationNative");
|
||||
auto local_class = env->FindClass("org/serenityos/ladybird/WebViewImplementation");
|
||||
if (!local_class)
|
||||
TODO();
|
||||
WebViewImplementationNative::global_class_reference = reinterpret_cast<jclass>(env->NewGlobalRef(local_class));
|
||||
|
@ -40,7 +87,7 @@ Java_org_serenityos_ladybird_WebViewImplementationNative_00024Companion_nativeCl
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_org_serenityos_ladybird_WebViewImplementationNative_nativeObjectInit(JNIEnv*, jobject /* thiz */)
|
||||
Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectInit(JNIEnv*, jobject /* thiz */)
|
||||
{
|
||||
auto instance = reinterpret_cast<jlong>(new WebViewImplementationNative);
|
||||
__android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "New WebViewImplementationNative at %p", reinterpret_cast<void*>(instance));
|
||||
|
@ -48,8 +95,30 @@ Java_org_serenityos_ladybird_WebViewImplementationNative_nativeObjectInit(JNIEnv
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_serenityos_ladybird_WebViewImplementationNative_nativeObjectDispose(JNIEnv*, jobject /* thiz */, jlong instance)
|
||||
Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectDispose(JNIEnv*, jobject /* thiz */, jlong instance)
|
||||
{
|
||||
delete reinterpret_cast<WebViewImplementationNative*>(instance);
|
||||
__android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "Destroyed WebViewImplementationNative at %p", reinterpret_cast<void*>(instance));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_serenityos_ladybird_WebViewImplementation_nativeDrawIntoBitmap(JNIEnv* env, jobject /* thiz */, jlong instance, jobject bitmap)
|
||||
{
|
||||
auto* impl = reinterpret_cast<WebViewImplementationNative*>(instance);
|
||||
|
||||
AndroidBitmapInfo bitmap_info = {};
|
||||
void* pixels = nullptr;
|
||||
AndroidBitmap_getInfo(env, bitmap, &bitmap_info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
if (pixels)
|
||||
impl->paint_into_bitmap(pixels, bitmap_info);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_serenityos_ladybird_WebViewImplementation_nativeSetViewportGeometry(JNIEnv*, jobject /* thiz */, jlong instance, jint w, jint h)
|
||||
{
|
||||
auto* impl = reinterpret_cast<WebViewImplementationNative*>(instance);
|
||||
impl->set_viewport_geometry(w, h);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,8 @@ package org.serenityos.ladybird
|
|||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import android.util.AttributeSet
|
||||
import org.serenityos.ladybird.databinding.ActivityMainBinding
|
||||
import org.serenityos.ladybird.TransferAssets
|
||||
|
||||
class LadybirdActivity : AppCompatActivity() {
|
||||
|
||||
|
@ -20,11 +19,13 @@ class LadybirdActivity : AppCompatActivity() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
resourceDir = TransferAssets.transferAssets(this)
|
||||
initNativeCode(resourceDir)
|
||||
view = WebViewImplementationNative()
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
view = binding.webView
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -32,7 +33,7 @@ class LadybirdActivity : AppCompatActivity() {
|
|||
super.onDestroy()
|
||||
}
|
||||
|
||||
private lateinit var view: WebViewImplementationNative
|
||||
private lateinit var view: WebView
|
||||
|
||||
/**
|
||||
* A native method that is implemented by the 'ladybird' native library,
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.lang.String;
|
|||
|
||||
public class TransferAssets {
|
||||
/**
|
||||
@return new ladybird resource root
|
||||
* @return new ladybird resource root
|
||||
*/
|
||||
static public String transferAssets(Context context) {
|
||||
Log.d("Ladybird", "Hello from java");
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package org.serenityos.ladybird
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
|
||||
// FIXME: This should (eventually) implement NestedScrollingChild3 and ScrollingView
|
||||
class WebView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
|
||||
private val viewImpl = WebViewImplementation()
|
||||
private lateinit var contentBitmap: Bitmap
|
||||
|
||||
fun dispose() {
|
||||
viewImpl.dispose()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
contentBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
// FIXME: Account for scroll offset when view supports scrolling
|
||||
viewImpl.setViewportGeometry(w, h)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
viewImpl.drawIntoBitmap(contentBitmap);
|
||||
canvas?.drawBitmap(contentBitmap, 0f, 0f, null)
|
||||
}
|
||||
}
|
|
@ -6,19 +6,20 @@
|
|||
|
||||
package org.serenityos.ladybird
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Wrapper around WebView::ViewImplementation for use by Kotlin
|
||||
*/
|
||||
class WebViewImplementationNative {
|
||||
class WebViewImplementation {
|
||||
// Instance Pointer to native object, very unsafe :)
|
||||
private var nativeInstance = nativeObjectInit()
|
||||
|
||||
init {
|
||||
Log.d(
|
||||
"Ladybird",
|
||||
"New WebViewImplementationNative (Kotlin) with nativeInstance ${this.nativeInstance}"
|
||||
"New WebViewImplementation (Kotlin) with nativeInstance ${this.nativeInstance}"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -27,9 +28,20 @@ class WebViewImplementationNative {
|
|||
nativeInstance = 0
|
||||
}
|
||||
|
||||
fun drawIntoBitmap(bitmap: Bitmap) {
|
||||
nativeDrawIntoBitmap(nativeInstance, bitmap)
|
||||
}
|
||||
|
||||
fun setViewportGeometry(w: Int, h: Int) {
|
||||
nativeSetViewportGeometry(nativeInstance, w, h)
|
||||
}
|
||||
|
||||
private external fun nativeObjectInit(): Long
|
||||
private external fun nativeObjectDispose(instance: Long)
|
||||
|
||||
private external fun nativeDrawIntoBitmap(instance: Long, bitmap: Bitmap)
|
||||
private external fun nativeSetViewportGeometry(instance: Long, w: Int, h: Int)
|
||||
|
||||
companion object {
|
||||
/*
|
||||
* We use a static class initializer to allow the native code to cache some
|
|
@ -1,19 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".LadybirdActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sample_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize">
|
||||
<!-- FIXME: Add Navigation, URL bar, Tab interactions, etc -->
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_scrollFlags="scroll|snap|enterAlways"/>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<org.serenityos.ladybird.WebView
|
||||
android:id="@+id/web_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".LadybirdActivity" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
@ -12,5 +12,7 @@
|
|||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue