mirror of https://github.com/mgba-emu/mgba.git
Scripting: Add canvas API
This commit is contained in:
parent
18d0ad6ff9
commit
dda5634189
|
@ -26,10 +26,21 @@ enum VideoLayer {
|
|||
VIDEO_LAYER_BACKGROUND = 0,
|
||||
VIDEO_LAYER_BEZEL,
|
||||
VIDEO_LAYER_IMAGE,
|
||||
VIDEO_LAYER_OVERLAY,
|
||||
VIDEO_LAYER_OVERLAY0,
|
||||
VIDEO_LAYER_OVERLAY1,
|
||||
VIDEO_LAYER_OVERLAY2,
|
||||
VIDEO_LAYER_OVERLAY3,
|
||||
VIDEO_LAYER_OVERLAY4,
|
||||
VIDEO_LAYER_OVERLAY5,
|
||||
VIDEO_LAYER_OVERLAY6,
|
||||
VIDEO_LAYER_OVERLAY7,
|
||||
VIDEO_LAYER_OVERLAY8,
|
||||
VIDEO_LAYER_OVERLAY9,
|
||||
VIDEO_LAYER_MAX
|
||||
};
|
||||
|
||||
#define VIDEO_LAYER_OVERLAY_COUNT VIDEO_LAYER_MAX - VIDEO_LAYER_OVERLAY0
|
||||
|
||||
struct VideoBackend {
|
||||
void (*init)(struct VideoBackend*, WHandle handle);
|
||||
void (*deinit)(struct VideoBackend*);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#define M_SCRIPT_H
|
||||
|
||||
#include <mgba/script/base.h>
|
||||
#include <mgba/script/canvas.h>
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/input.h>
|
||||
#include <mgba/script/macros.h>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef M_SCRIPT_CANVAS_H
|
||||
#define M_SCRIPT_CANVAS_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/macros.h>
|
||||
|
||||
struct VideoBackend;
|
||||
void mScriptContextAttachCanvas(struct mScriptContext* context);
|
||||
void mScriptCanvasUpdate(struct mScriptContext* context);
|
||||
void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -14,6 +14,7 @@
|
|||
#include "MessagePainter.h"
|
||||
|
||||
struct VDir;
|
||||
struct VideoBackend;
|
||||
struct VideoShader;
|
||||
|
||||
namespace QGBA {
|
||||
|
|
|
@ -970,6 +970,12 @@ void Window::gameStopped() {
|
|||
updateTitle();
|
||||
|
||||
if (m_pendingClose) {
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
|
||||
if (m_scripting && proxy) {
|
||||
m_scripting->setVideoBackend(nullptr);
|
||||
}
|
||||
#endif
|
||||
m_display.reset();
|
||||
close();
|
||||
}
|
||||
|
@ -1020,6 +1026,15 @@ void Window::reloadDisplayDriver() {
|
|||
m_display->stopDrawing();
|
||||
detachWidget();
|
||||
}
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_scripting) {
|
||||
m_scripting->setVideoBackend(nullptr);
|
||||
}
|
||||
#endif
|
||||
std::shared_ptr<VideoProxy> proxy;
|
||||
if (m_display) {
|
||||
proxy = m_display->videoProxy();
|
||||
}
|
||||
m_display = std::unique_ptr<QGBA::Display>(Display::create(this));
|
||||
if (!m_display) {
|
||||
LOG(QT, ERROR) << tr("Failed to create an appropriate display device, falling back to software display. "
|
||||
|
@ -1061,8 +1076,15 @@ void Window::reloadDisplayDriver() {
|
|||
|
||||
m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")});
|
||||
|
||||
std::shared_ptr<VideoProxy> proxy = std::make_shared<VideoProxy>();
|
||||
if (!proxy) {
|
||||
proxy = std::make_shared<VideoProxy>();
|
||||
}
|
||||
m_display->setVideoProxy(proxy);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_scripting) {
|
||||
m_scripting->setVideoBackend(proxy->backend());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::reloadAudioDriver() {
|
||||
|
|
|
@ -87,6 +87,10 @@ void ScriptingController::setInputController(InputController* input) {
|
|||
connect(m_inputController, &InputController::updated, this, &ScriptingController::updateGamepad);
|
||||
}
|
||||
|
||||
void ScriptingController::setVideoBackend(VideoBackend* backend) {
|
||||
mScriptCanvasUpdateBackend(&m_scriptContext, backend);
|
||||
}
|
||||
|
||||
bool ScriptingController::loadFile(const QString& path) {
|
||||
VFileDevice vf(path, QIODevice::ReadOnly);
|
||||
if (!vf.isOpen()) {
|
||||
|
@ -304,11 +308,13 @@ void ScriptingController::detachGamepad() {
|
|||
void ScriptingController::init() {
|
||||
mScriptContextInit(&m_scriptContext);
|
||||
mScriptContextAttachStdlib(&m_scriptContext);
|
||||
mScriptContextAttachCanvas(&m_scriptContext);
|
||||
mScriptContextAttachImage(&m_scriptContext);
|
||||
mScriptContextAttachInput(&m_scriptContext);
|
||||
mScriptContextAttachSocket(&m_scriptContext);
|
||||
#ifdef USE_JSON_C
|
||||
mScriptContextAttachStorage(&m_scriptContext);
|
||||
#endif
|
||||
mScriptContextAttachSocket(&m_scriptContext);
|
||||
mScriptContextAttachInput(&m_scriptContext);
|
||||
mScriptContextRegisterEngines(&m_scriptContext);
|
||||
|
||||
mScriptContextAttachLogger(&m_scriptContext, &m_logger);
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
class QKeyEvent;
|
||||
class QTextDocument;
|
||||
|
||||
struct VideoBackend;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
|
@ -36,6 +38,7 @@ public:
|
|||
|
||||
void setController(std::shared_ptr<CoreController> controller);
|
||||
void setInputController(InputController* controller);
|
||||
void setVideoBackend(VideoBackend* backend);
|
||||
|
||||
bool loadFile(const QString& path);
|
||||
bool load(VFileDevice& vf, const QString& name);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
include(ExportDirectory)
|
||||
set(SOURCE_FILES
|
||||
canvas.c
|
||||
context.c
|
||||
input.c
|
||||
image.c
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/script/canvas.h>
|
||||
|
||||
#include <mgba/feature/video-backend.h>
|
||||
#include <mgba/script/base.h>
|
||||
#include <mgba-util/image.h>
|
||||
|
||||
struct mScriptCanvasContext;
|
||||
struct mScriptCanvasLayer {
|
||||
struct VideoBackend* backend;
|
||||
enum VideoLayer layer;
|
||||
struct mImage* image;
|
||||
int x;
|
||||
int y;
|
||||
bool dirty;
|
||||
bool sizeDirty;
|
||||
bool dimsDirty;
|
||||
bool contentsDirty;
|
||||
};
|
||||
|
||||
struct mScriptCanvasContext {
|
||||
struct mScriptCanvasLayer overlays[VIDEO_LAYER_OVERLAY_COUNT];
|
||||
struct VideoBackend* backend;
|
||||
struct mScriptContext* context;
|
||||
uint32_t frameCbid;
|
||||
};
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptCanvasContext);
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptCanvasLayer);
|
||||
|
||||
static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer);
|
||||
static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer);
|
||||
|
||||
static int _getNextAvailableOverlay(struct mScriptCanvasContext* context) {
|
||||
if (!context->backend) {
|
||||
return -1;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
int w = -1, h = -1;
|
||||
context->backend->imageSize(context->backend, VIDEO_LAYER_OVERLAY0 + i, &w, &h);
|
||||
if (w <= 0 && h <= 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void mScriptCanvasContextDeinit(struct mScriptCanvasContext* context) {
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
if (context->overlays[i].image) {
|
||||
mScriptCanvasLayerDestroy(&context->overlays[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend* backend) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas");
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
struct mScriptCanvasContext* canvas = value->value.opaque;
|
||||
canvas->backend = backend;
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
struct mScriptCanvasLayer* layer = &canvas->overlays[i];
|
||||
layer->backend = backend;
|
||||
layer->dirty = true;
|
||||
layer->dimsDirty = true;
|
||||
layer->sizeDirty = true;
|
||||
layer->contentsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void _mScriptCanvasUpdate(struct mScriptCanvasContext* canvas) {
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
mScriptCanvasLayerUpdate(&canvas->overlays[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned _mScriptCanvasWidth(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
unsigned w, h;
|
||||
VideoBackendGetFrameSize(canvas->backend, &w, &h);
|
||||
return w;
|
||||
}
|
||||
|
||||
static unsigned _mScriptCanvasHeight(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
unsigned w, h;
|
||||
VideoBackendGetFrameSize(canvas->backend, &w, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
static int _mScriptCanvasScreenWidth(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
struct mRectangle dims;
|
||||
canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims);
|
||||
return dims.width;
|
||||
}
|
||||
|
||||
static int _mScriptCanvasScreenHeight(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
struct mRectangle dims;
|
||||
canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims);
|
||||
return dims.height;
|
||||
}
|
||||
|
||||
void mScriptCanvasUpdate(struct mScriptContext* context) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas");
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
struct mScriptCanvasContext* canvas = value->value.opaque;
|
||||
_mScriptCanvasUpdate(canvas);
|
||||
}
|
||||
|
||||
static struct mScriptValue* mScriptCanvasLayerCreate(struct mScriptCanvasContext* context, int w, int h) {
|
||||
if (w <= 0 || h <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
int next = _getNextAvailableOverlay(context);
|
||||
if (next < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mScriptCanvasLayer* layer = &context->overlays[next];
|
||||
if (layer->image) {
|
||||
// This shouldn't exist yet
|
||||
abort();
|
||||
}
|
||||
|
||||
layer->image = mImageCreate(w, h, mCOLOR_ABGR8);
|
||||
layer->dirty = true;
|
||||
layer->dimsDirty = true;
|
||||
layer->sizeDirty = true;
|
||||
layer->contentsDirty = true;
|
||||
mScriptCanvasLayerUpdate(layer);
|
||||
|
||||
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasLayer));
|
||||
value->value.opaque = layer;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer) {
|
||||
struct mRectangle frame = {0};
|
||||
if (layer->backend) {
|
||||
layer->backend->setLayerDimensions(layer->backend, layer->layer, &frame);
|
||||
layer->backend->setImageSize(layer->backend, layer->layer, 0, 0);
|
||||
}
|
||||
mImageDestroy(layer->image);
|
||||
layer->image = NULL;
|
||||
}
|
||||
|
||||
static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer) {
|
||||
if (!layer->dirty || !layer->image || !layer->backend) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct VideoBackend* backend = layer->backend;
|
||||
if (layer->sizeDirty) {
|
||||
backend->setImageSize(backend, layer->layer, layer->image->width, layer->image->height);
|
||||
layer->sizeDirty = false;
|
||||
// Resizing the image invalidates the contents in many backends
|
||||
layer->contentsDirty = true;
|
||||
}
|
||||
if (layer->dimsDirty) {
|
||||
struct mRectangle frame = {
|
||||
.x = layer->x,
|
||||
.y = layer->y,
|
||||
.width = layer->image->width,
|
||||
.height = layer->image->height,
|
||||
};
|
||||
backend->setLayerDimensions(backend, layer->layer, &frame);
|
||||
layer->dimsDirty = false;
|
||||
}
|
||||
if (layer->contentsDirty) {
|
||||
backend->setImage(backend, layer->layer, layer->image->data);
|
||||
layer->contentsDirty = false;
|
||||
}
|
||||
layer->dirty = false;
|
||||
}
|
||||
|
||||
|
||||
static void mScriptCanvasLayerSetPosition(struct mScriptCanvasLayer* layer, int32_t x, int32_t y) {
|
||||
layer->x = x;
|
||||
layer->y = y;
|
||||
layer->dimsDirty = true;
|
||||
layer->dirty = true;
|
||||
}
|
||||
|
||||
static void mScriptCanvasLayerInvalidate(struct mScriptCanvasLayer* layer) {
|
||||
layer->contentsDirty = true;
|
||||
layer->dirty = true;
|
||||
}
|
||||
|
||||
void mScriptContextAttachCanvas(struct mScriptContext* context) {
|
||||
struct mScriptCanvasContext* canvas = calloc(1, sizeof(*canvas));
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
canvas->overlays[i].layer = VIDEO_LAYER_OVERLAY0 + i;
|
||||
}
|
||||
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasContext));
|
||||
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
|
||||
value->value.opaque = canvas;
|
||||
|
||||
canvas->context = context;
|
||||
struct mScriptValue* lambda = mScriptObjectBindLambda(value, "update", NULL);
|
||||
canvas->frameCbid = mScriptContextAddCallback(context, "frame", lambda);
|
||||
mScriptValueDeref(lambda);
|
||||
|
||||
mScriptContextSetGlobal(context, "canvas", value);
|
||||
mScriptContextSetDocstring(context, "canvas", "Singleton instance of struct::mScriptCanvasContext");
|
||||
}
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, _deinit, mScriptCanvasContextDeinit, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, W(mScriptCanvasLayer), newLayer, mScriptCanvasLayerCreate, 2, S32, width, S32, height);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, update, _mScriptCanvasUpdate, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, width, _mScriptCanvasWidth, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, height, _mScriptCanvasHeight, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenWidth, _mScriptCanvasScreenWidth, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenHeight, _mScriptCanvasScreenHeight, 0);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mScriptCanvasContext)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
"A canvas that can be used for drawing images on or around the screen."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptCanvasContext)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Create a new layer of a given size. If multiple layers overlap, the most recently created one takes priority.")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, newLayer)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, update)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, width)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, height)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenWidth)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenHeight)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, update, mScriptCanvasLayerInvalidate, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, setPosition, mScriptCanvasLayerSetPosition, 2, S32, x, S32, y);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mScriptCanvasLayer)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
"An individual layer of a drawable canvas."
|
||||
)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Mark the contents of the layer as needed to be repainted")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, update)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Set the position of the layer in the canvas")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, setPosition)
|
||||
mSCRIPT_DEFINE_DOCSTRING("The image that has the pixel contents of the image")
|
||||
mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptCanvasLayer, PS(mImage), image)
|
||||
mSCRIPT_DEFINE_DOCSTRING("The current x (horizontal) position of this layer")
|
||||
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, x)
|
||||
mSCRIPT_DEFINE_DOCSTRING("The current y (vertical) position of this layer")
|
||||
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, y)
|
||||
mSCRIPT_DEFINE_END;
|
Loading…
Reference in New Issue