Ultralight game loop integration

This commit is contained in:
Simon Lübeß
2024-09-10 18:57:39 +02:00
parent c717da0080
commit 65992126c0
4 changed files with 3816 additions and 171 deletions
+80
View File
@@ -0,0 +1,80 @@
<html>
<head>
<style type="text/css">
body {
background: linear-gradient(0deg, #6a11ea, #7270ed);
color: #e8e2fd;
font-family: -apple-system, 'Segoe UI Light', Ubuntu, Arial, sans-serif;
font-weight: 200;
padding: 0;
overflow: hidden;
}
#msg, #btn {
text-align: center;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 0;
}
#msg {
font-size: 54px;
width: 85%;
}
#btn {
width: 60%;
}
button {
background: linear-gradient(0deg, #6a11ea, #7270ed);
color: #e8e2fd;
border-radius: 60px;
border: none;
font-size: 36px;
padding: 0.3em;
height: 2em;
box-shadow: 6px 6px #4814b5;
border: 1px solid #4814b5;
font-family: -apple-system, 'Segoe UI Light', Ubuntu, Arial, sans-serif;
}
button:active {
box-shadow: none;
margin-left: 6px;
margin-top: 6px;
background: linear-gradient(0deg, #7270ed, #6a11ea);
}
#msg {
visibility: hidden;
opacity: 0;
top: 10%;
}
.transition #btn {
visibility: hidden;
opacity: 0;
top: 90%;
transition: visibility 0s 0.3s, opacity 0.3s linear, top 0.3s linear;
}
.transition #msg {
visibility: visible;
opacity: 1;
top: 50%;
transition: opacity 0.3s linear, top 0.3s linear;
transition-delay: 0.1s;
}
</style>
<script type="text/javascript">
function $(id) {
return document.getElementById(id);
}
function HandleClick() {
document.body.classList.add('transition');
$('msg').innerHTML = GetMessage();
}
</script>
</head>
<body>
<div id="msg"></div>
<button id="btn" onclick="HandleClick();">Click Me!</button>
</body>
</html>
File diff suppressed because it is too large Load Diff
Binary file not shown.
+251 -152
View File
@@ -1,213 +1,312 @@
using System; using System;
using GlitchyEngine; using GlitchyEngine;
using Ultralight;
using Ultralight.CAPI; using Ultralight.CAPI;
using GlitchyEngine.Renderer;
using GlitchyEngine.Math;
using GlitchyEngine.Events;
using GlitchyEngine.ImGui;
using ImGui;
namespace GlitchyEditor; namespace GlitchyEditor;
class UltralightLayer : Layer class UltralightLayer : Layer
{ {
public const String htmlString =
"""
<html>
<head>
<style type="text/css">
body {
margin: 0;
padding: 0;
overflow: hidden;
color: black;
font-family: Arial;
background: linear-gradient(-45deg, #acb4ff, #f5d4e2);
display: flex;
justify-content: center;
align-items: center;
}
div {
width: 350px;
height: 350px;
text-align: center;
border-radius: 25px;
background: linear-gradient(-45deg, #e5eaf9, #f9eaf6);
box-shadow: 0 7px 18px -6px #8f8ae1;
}
h1 {
padding: 1em;
}
p {
background: white;
padding: 2em;
margin: 40px;
border-radius: 25px;
}
</style>
</head>
<body>
<div>
<h1>Hello World!</h1>
<p>Welcome to Ultralight!</p>
</div>
</body>
</html>
""";
public this() public this()
{ {
RenderToPng(); NoApp();
//RenderApp();
} }
private static bool done = false; private static void OnAppUpdate(void* user_data)
private static void OnFinishLoading(void* user_data, ULView caller,
uint64 frame_id, bool is_main_frame, ULString url)
{ {
/// //UltralightLayer layer = (UltralightLayer)Internal.UnsafeCastToObject(user_data);
/// Our page is done when the main frame is finished loading. //layer.OnAppUpdate();
///
if (is_main_frame)
{
///
/// Set our done flag to true to exit the Run loop.
///
done = true;
}
} }
private void RenderToPng() private static void OnClose(void* user_data, ULWindow window)
{ {
Log.ClientLogger.Info("OnClose...");
ulAppQuit(app);
}
private static void OnResize(void* user_data, ULWindow window, uint32 width, uint32 height)
{
Log.ClientLogger.Info($"OnResize: {width} {height}");
ulOverlayResize(overlay, width, height);
}
/// ///
/// Setup our config. /// This callback is bound to a JavaScript function on the page.
/// ///
/// @note: static JSValueRef GetMessage(JSContextRef ctx, JSObjectRef fn, JSObjectRef thisObject, uint32 argumentCount, JSValueRef* arguments, JSValueRef* exception) {
/// We don't set any config options in this sample but you could set your own options here.
/// ///
/// Create a JavaScript String from a C-string, initialize it with our
/// welcome message.
///
JSStringRef str = JSStringCreateWithUTF8CString("Hello from Beef!");
///
/// Create a garbage-collected JSValue using the String we just created.
///
/// **Note**:
/// Both JSValueRef and JSObjectRef types are garbage-collected types. (And actually,
/// JSObjectRef is just a typedef of JSValueRef, they share definitions).
///
/// The garbage collector in JavaScriptCore periodically scans the entire stack to check if
/// there are any active JSValueRefs, and marks those with no references for destruction.
///
/// If you happen to store a JSValueRef/JSObjectRef in heap memory or in memory unreachable
/// by the stack-based garbage-collector, you should explicitly call JSValueProtect() and
/// JSValueUnprotect() on the reference to ensure it is kept alive.
///
JSValueRef value = JSValueMakeString(ctx, str);
///
/// Release the string we created earlier (we only Release what we Create).
///
JSStringRelease(str);
return value;
}
private static void OnDOMReady(void* user_data, ULView caller, uint64 frame_id, bool is_main_frame, ULString url)
{
Log.ClientLogger.Info("OnDOMReady");
///
/// Acquire the page's JavaScript execution context.
///
/// This locks the JavaScript context so we can modify it safely on this thread, we need to
/// unlock it when we're done via ulViewUnlockJSContext().
///
JSContextRef ctx = ulViewLockJSContext(view);
///
/// Create a JavaScript String containing the name of our callback.
///
JSStringRef name = JSStringCreateWithUTF8CString("GetMessage");
///
/// Create a garbage-collected JavaScript function that is bound to our native C callback
/// 'GetMessage()'.
///
JSObjectRef func = JSObjectMakeFunctionWithCallback(ctx, name, => GetMessage);
///
/// Store our function in the page's global JavaScript object so that it is accessible from the
/// page as 'GetMessage()'.
///
/// The global JavaScript object is also known as 'window' in JS.
///
JSObjectSetProperty(ctx, JSContextGetGlobalObject(ctx), name, func, 0, null);
///
/// Release the JavaScript String we created earlier.
///
JSStringRelease(name);
///
/// Unlock the JS context so other threads can modify JavaScript state.
///
ulViewUnlockJSContext(view);
}
ULRenderer renderer;
private void NoApp()
{
ULConfig config = ulCreateConfig(); ULConfig config = ulCreateConfig();
///
/// We must provide our own Platform API handlers since we're not using ulCreateApp().
///
/// The Platform API handlers we can set are:
///
/// | | ulCreateRenderer() | ulCreateApp() |
/// |-------------------|--------------------|---------------|
/// | FileSystem | **Required** | *Provided* |
/// | FontLoader | **Required** | *Provided* |
/// | Clipboard | *Optional* | *Provided* |
/// | GPUDriver | *Optional* | *Provided* |
/// | Logger | *Optional* | *Provided* |
/// | SurfaceDefinition | *Provided* | *Provided* |
///
/// The only Platform API handlers we are required to provide are file system and font loader.
///
/// In this sample we will use AppCore's font loader and file system via
/// ulEnablePlatformFontLoader() and ulEnablePlatformFileSystem() respectively.
///
/// You can replace these with your own implementations later.
///
ulEnablePlatformFontLoader(); ulEnablePlatformFontLoader();
ULString fileSystemPath = ulCreateString(@"D:\Development\Projects\Beef\GlitchyEngine\GlitchyEditor\assets");
ulEnablePlatformFileSystem(fileSystemPath);
ulDestroyString(fileSystemPath);
/*ULString styleSheet = ulCreateString("body { background: purple; }");
ulConfigSetUserStylesheet(config, styleSheet);
ulDestroyString(styleSheet);*/
renderer = ulCreateRenderer(config);
CreateView();
}
Texture2D texture ~ _.ReleaseRef();
private void CopybitmapToTexture(ULBitmap bitmap)
{
void* pixels = ulBitmapLockPixels(bitmap);
uint32 width = ulBitmapGetWidth(bitmap);
uint32 height = ulBitmapGetHeight(bitmap);
uint32 stride = ulBitmapGetRowBytes(bitmap);
Span<Color> colors = Span<Color>((.)pixels, 500 * 500);
texture.SetData<uint32>((.)pixels, 0, 0, width, height, 0, 0);
ulBitmapUnlockPixels(bitmap);
}
public override void Update(GameTime gameTime)
{
ulUpdate(renderer);
ulRender(renderer);
ULBitmapSurface surface = ulViewGetSurface(view);
if (surface != null && !ulIntRectIsEmpty(ulSurfaceGetDirtyBounds(surface)))
{
CopybitmapToTexture(ulBitmapSurfaceGetBitmap(surface));
ulSurfaceClearDirtyBounds(surface);
}
}
private void CreateView()
{
ULViewConfig viewConfig = ulCreateViewConfig();
ulViewConfigSetIsAccelerated(viewConfig, false);
view = ulCreateView(renderer, 500, 500, viewConfig, null);
ulDestroyViewConfig(viewConfig);
ULString url = ulCreateString("file:///app.html");
ulViewLoadURL(view, url);
ulDestroyString(url);
//surface = ulViewGetSurface(view);
// TODO: Dynamic
Texture2DDesc desc = .(500, 500, .B8G8R8A8_UNorm, usage: .Default, cpuAccess: .Write);
texture = new Texture2D(desc);
texture.[Friend]Identifier = .("TestTexture");
Application.Instance.ContentManager.ManageAsset(texture);
}
public override void OnEvent(GlitchyEngine.Events.Event event)
{
EventDispatcher dispatcher = EventDispatcher(event);
dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(e));
/*
dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e));
dispatcher.Dispatch<KeyPressedEvent>(scope (e) => OnKeyPressed(e));
dispatcher.Dispatch<MouseScrolledEvent>(scope (e) => OnMouseScrolled(e));*/
}
private bool OnImGuiRender(ImGuiRenderEvent event)
{
if (ImGui.Begin("Testlul"))
{
ImGui.Image(texture, .(500, 500));
}
ImGui.End();
return false;
}
static ULApp app;
static ULWindow window;
static ULOverlay overlay;
static ULView view;
private void RenderApp()
{
/// ///
/// Use AppCore's file system singleton to load file:/// URLs from the OS. /// Create default settings/config
/// ///
ULString base_dir = ulCreateString("./assets/"); ULSettings settings = ulCreateSettings();
ulEnablePlatformFileSystem(base_dir); ulSettingsSetForceCPURenderer(settings, true);
ulDestroyString(base_dir); ULConfig config = ulCreateConfig();
ULString fileSystemPath = ulCreateString(@"D:\Development\Projects\Beef\GlitchyEngine\GlitchyEditor\assets");
ulSettingsSetFileSystemPath(settings, fileSystemPath);
ulDestroyString(fileSystemPath);
/// ///
/// Use AppCore's default logger to write the log file to disk. /// Create our App
/// ///
ULString log_path = ulCreateString("./ultralight.log"); app = ulCreateApp(settings, config);
ulEnableDefaultLogger(log_path);
ulDestroyString(log_path);
/// ///
/// Create our renderer using the Config we just set up. /// Register a callback to handle app update logic.
/// ///
/// The Renderer singleton maintains the lifetime of the library and is required before creating ulAppSetUpdateCallback(app, => OnAppUpdate, null);
/// any Views. It should outlive any Views.
///
/// You should set up any platform handlers before creating this.
///
ULRenderer renderer = ulCreateRenderer(config);
///
/// Done using settings/config, make sure to destroy anything we create
///
ulDestroySettings(settings);
ulDestroyConfig(config); ulDestroyConfig(config);
/// ///
/// Create our View. /// Create our window, make it 500x500 with a titlebar and resize handles.
/// ///
/// Views are sized containers for loading and displaying web content. window = ulCreateWindow(ulAppGetMainMonitor(app), 500, 500, false,
/// (uint32)(ULWindowFlags.kWindowFlags_Titled | ULWindowFlags.kWindowFlags_Resizable));
/// Let's set a 2x DPI scale and disable GPU acceleration so we can render to a bitmap.
///
ULViewConfig view_config = ulCreateViewConfig();
ulViewConfigSetInitialDeviceScale(view_config, 2.0);
ulViewConfigSetIsAccelerated(view_config, false);
ULView view = ulCreateView(renderer, 1600, 800, view_config, null);
ulDestroyViewConfig(view_config);
/// ///
/// Register OnFinishLoading() callback with our View. /// Set our window title.
/// ///
ulViewSetFinishLoadingCallback(view, => OnFinishLoading, null); ulWindowSetTitle(window, "Ultralight Sample 6 - Intro to C API");
/// ///
/// Load a local HTML file into the View (uses the file system defined above). /// Register a callback to handle window close.
/// ///
/// @note: ulWindowSetCloseCallback(window, => OnClose, null);
/// This operation may not complete immediately-- we will call ulUpdate() continuously
/// and wait for the OnFinishLoading event before rendering our View.
///
/// Views can also load remote URLs, try replacing the code below with:
///
/// ULString url_string = ulCreateString("https://en.wikipedia.org");
/// ulViewLoadURL(view, url_string);
/// ulDestroyString(url_string);
///
ULString url_string = ulCreateString("file:///page.html");
ulViewLoadURL(view, url_string);
ulDestroyString(url_string);
Log.ClientLogger.Info("Starting Run(), waiting for page to load...");
/// ///
/// Continuously update until OnFinishLoading() is called below (which sets done = true). /// Register a callback to handle window resize.
/// ///
/// @note: ulWindowSetResizeCallback(window, => OnResize, null);
/// Calling ulUpdate() handles any pending network requests, resource loads, and
/// JavaScript timers.
///
while (!done)
{
ulUpdate(renderer);
}
/// ///
/// Render our View. /// Create an overlay same size as our window at 0,0 (top-left) origin. Overlays also create an
/// HTML view for us to display content in.
/// ///
/// @note: /// **Note**:
/// Calling ulRender will render any dirty Views to their respective Surfaces. /// Ownership of the view remains with the overlay since we don't explicitly create it.
/// ///
ulRender(renderer); overlay = ulCreateOverlay(window, ulWindowGetWidth(window), ulWindowGetHeight(window), 0, 0);
/// ///
/// Get our View's rendering surface. /// Get the overlay's view.
/// ///
ULSurface surface = ulViewGetSurface(view); view = ulOverlayGetView(overlay);
/// ///
/// Get the underlying bitmap. /// Register a callback to handle our view's DOMReady event. We will use this event to setup any
/// JavaScript <-> C bindings and initialize our page.
/// ///
/// @note We're using the default surface definition which is BitmapSurface, you can override ulViewSetDOMReadyCallback(view, => OnDOMReady, null);
/// the surface implementation via ulPlatformSetSurfaceDefinition()
/// ulViewSetFailLoadingCallback(view, (user_data, caller, frame_id, is_main_frame, url, description, error_domain, error_code) => {
ULBitmap bitmap = ulBitmapSurfaceGetBitmap(surface); Log.ClientLogger.Error("Errorrr");
}, null);
/// ///
/// Write our bitmap to a PNG in the current working directory. /// Load a file from the FileSystem.
/// ///
ulBitmapWritePNG(bitmap, "result.png"); /// **IMPORTANT**: Make sure `file:///` has three (3) forward slashes.
///
/// **Note**: You can configure the base path for the FileSystem in the Settings we passed to
/// ulCreateApp earlier.
///
ULString url = ulCreateString("file:///app.html");
Log.ClientLogger.Info("Saved a render of our page to result.png."); var v = ulStringGetData(url);
ulViewLoadURL(view, url);
ulDestroyString(url);
ulAppRun(app);
} }
} }