1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 04:07:45 +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:
Andrew Kaster 2023-09-03 15:19:41 +02:00 committed by Andrew Kaster
parent 8d05ff1fe7
commit 1d369cb722
9 changed files with 162 additions and 31 deletions

View file

@ -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")

View file

@ -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);
}

View file

@ -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,

View file

@ -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");

View file

@ -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)
}
}

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -150,7 +150,7 @@ elseif(ANDROID)
Android/src/main/cpp/LadybirdActivity.cpp
Android/src/main/cpp/WebViewImplementationNative.cpp
)
target_link_libraries(ladybird PRIVATE log)
target_link_libraries(ladybird PRIVATE log jnigraphics)
else()
# TODO: Check for other GUI frameworks here when we move them in-tree
# For now, we can export a static library of common files for chromes to link to