mirror of https://github.com/mgba-emu/mgba.git
Scripting: Add prototype "buffer" pseudo-TUI interface
This commit is contained in:
parent
f570786d78
commit
28d7bfdffc
|
@ -21,8 +21,10 @@ mLOG_DECLARE_CATEGORY(SCRIPT);
|
||||||
|
|
||||||
struct mCore;
|
struct mCore;
|
||||||
struct mLogger;
|
struct mLogger;
|
||||||
|
struct mScriptTextBuffer;
|
||||||
mSCRIPT_DECLARE_STRUCT(mCore);
|
mSCRIPT_DECLARE_STRUCT(mCore);
|
||||||
mSCRIPT_DECLARE_STRUCT(mLogger);
|
mSCRIPT_DECLARE_STRUCT(mLogger);
|
||||||
|
mSCRIPT_DECLARE_STRUCT(mScriptTextBuffer);
|
||||||
|
|
||||||
struct mScriptBridge;
|
struct mScriptBridge;
|
||||||
struct VFile;
|
struct VFile;
|
||||||
|
@ -41,6 +43,24 @@ struct mScriptEngine {
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mScriptTextBuffer {
|
||||||
|
void (*init)(struct mScriptTextBuffer*, const char* name);
|
||||||
|
void (*deinit)(struct mScriptTextBuffer*);
|
||||||
|
|
||||||
|
void (*setName)(struct mScriptTextBuffer*, const char* text);
|
||||||
|
|
||||||
|
uint32_t (*getX)(const struct mScriptTextBuffer*);
|
||||||
|
uint32_t (*getY)(const struct mScriptTextBuffer*);
|
||||||
|
uint32_t (*cols)(const struct mScriptTextBuffer*);
|
||||||
|
uint32_t (*rows)(const struct mScriptTextBuffer*);
|
||||||
|
|
||||||
|
void (*print)(struct mScriptTextBuffer*, const char* text);
|
||||||
|
void (*clear)(struct mScriptTextBuffer*);
|
||||||
|
void (*setSize)(struct mScriptTextBuffer*, uint32_t cols, uint32_t rows);
|
||||||
|
void (*moveCursor)(struct mScriptTextBuffer*, uint32_t x, uint32_t y);
|
||||||
|
void (*advance)(struct mScriptTextBuffer*, int32_t);
|
||||||
|
};
|
||||||
|
|
||||||
struct mScriptBridge* mScriptBridgeCreate(void);
|
struct mScriptBridge* mScriptBridgeCreate(void);
|
||||||
void mScriptBridgeDestroy(struct mScriptBridge*);
|
void mScriptBridgeDestroy(struct mScriptBridge*);
|
||||||
|
|
||||||
|
@ -64,6 +84,9 @@ void mScriptContextDetachCore(struct mScriptContext*);
|
||||||
void mScriptContextAttachLogger(struct mScriptContext*, struct mLogger*);
|
void mScriptContextAttachLogger(struct mScriptContext*, struct mLogger*);
|
||||||
void mScriptContextDetachLogger(struct mScriptContext*);
|
void mScriptContextDetachLogger(struct mScriptContext*);
|
||||||
|
|
||||||
|
typedef struct mScriptTextBuffer* (*mScriptContextBufferFactory)(void*);
|
||||||
|
void mScriptContextSetTextBufferFactory(struct mScriptContext*, mScriptContextBufferFactory factory, void* cbContext);
|
||||||
|
|
||||||
CXX_GUARD_END
|
CXX_GUARD_END
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -324,7 +324,9 @@ CXX_GUARD_START
|
||||||
#define mSCRIPT_DEFINE_STRUCT_METHOD(TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(TYPE, NAME, NAME)
|
#define mSCRIPT_DEFINE_STRUCT_METHOD(TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(TYPE, NAME, NAME)
|
||||||
|
|
||||||
#define mSCRIPT_DEFINE_STRUCT_INIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(INIT, TYPE, _init, _init)
|
#define mSCRIPT_DEFINE_STRUCT_INIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(INIT, TYPE, _init, _init)
|
||||||
|
#define mSCRIPT_DEFINE_STRUCT_INIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(INIT, TYPE, _init, NAME)
|
||||||
#define mSCRIPT_DEFINE_STRUCT_DEINIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, _deinit)
|
#define mSCRIPT_DEFINE_STRUCT_DEINIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, _deinit)
|
||||||
|
#define mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, NAME)
|
||||||
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get)
|
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get)
|
||||||
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set)
|
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set)
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,11 @@ struct mScriptCoreAdapter {
|
||||||
struct mScriptValue memory;
|
struct mScriptValue memory;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mScriptUILibrary {
|
||||||
|
mScriptContextBufferFactory textBufferFactory;
|
||||||
|
void* textBufferContext;
|
||||||
|
};
|
||||||
|
|
||||||
static uint32_t mScriptMemoryAdapterRead8(struct mScriptMemoryAdapter* adapter, uint32_t address) {
|
static uint32_t mScriptMemoryAdapterRead8(struct mScriptMemoryAdapter* adapter, uint32_t address) {
|
||||||
uint32_t segmentSize = adapter->block.end - adapter->block.start;
|
uint32_t segmentSize = adapter->block.end - adapter->block.start;
|
||||||
uint32_t segmentAddress = address % segmentSize;
|
uint32_t segmentAddress = address % segmentSize;
|
||||||
|
@ -522,3 +527,59 @@ void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger*
|
||||||
void mScriptContextDetachLogger(struct mScriptContext* context) {
|
void mScriptContextDetachLogger(struct mScriptContext* context) {
|
||||||
mScriptContextRemoveGlobal(context, "console");
|
mScriptContextRemoveGlobal(context, "console");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, deinit, 0);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, getX, 0);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, getY, 0);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, cols, 0);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, rows, 0);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, print, 1, CHARP, text);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, clear, 0);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, setSize, 2, U32, cols, U32, rows);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, moveCursor, 2, U32, x, U32, y);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, advance, 1, S32, adv);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, setName, 1, CHARP, name);
|
||||||
|
|
||||||
|
mSCRIPT_DEFINE_STRUCT(mScriptTextBuffer)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(mScriptTextBuffer, deinit)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, getX)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, getY)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, cols)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, rows)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, print)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, clear)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, setSize)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, moveCursor)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, advance)
|
||||||
|
mSCRIPT_DEFINE_DOCSTRING("Set the user-visible name of this buffer")
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, setName)
|
||||||
|
mSCRIPT_DEFINE_END;
|
||||||
|
|
||||||
|
struct mScriptTextBuffer* _mScriptUICreateBuffer(struct mScriptUILibrary* lib, const char* name) {
|
||||||
|
struct mScriptTextBuffer* buffer = lib->textBufferFactory(lib->textBufferContext);
|
||||||
|
buffer->init(buffer, name);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSCRIPT_DECLARE_STRUCT(mScriptUILibrary);
|
||||||
|
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptUILibrary, S(mScriptTextBuffer), createBuffer, _mScriptUICreateBuffer, 1, CHARP, name);
|
||||||
|
|
||||||
|
mSCRIPT_DEFINE_STRUCT(mScriptUILibrary)
|
||||||
|
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptUILibrary, createBuffer)
|
||||||
|
mSCRIPT_DEFINE_END;
|
||||||
|
|
||||||
|
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptUILibrary, createBuffer)
|
||||||
|
mSCRIPT_MAKE_CHARP(NULL)
|
||||||
|
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||||
|
|
||||||
|
void mScriptContextSetTextBufferFactory(struct mScriptContext* context, mScriptContextBufferFactory factory, void* cbContext) {
|
||||||
|
struct mScriptValue* value = mScriptContextEnsureGlobal(context, "ui", mSCRIPT_TYPE_MS_S(mScriptUILibrary));
|
||||||
|
struct mScriptUILibrary* uiLib = value->value.opaque;
|
||||||
|
if (!uiLib) {
|
||||||
|
uiLib = calloc(1, sizeof(*uiLib));
|
||||||
|
value->value.opaque = uiLib;
|
||||||
|
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
|
||||||
|
}
|
||||||
|
uiLib->textBufferFactory = factory;
|
||||||
|
uiLib->textBufferContext = cbContext;
|
||||||
|
}
|
||||||
|
|
|
@ -250,6 +250,7 @@ endif()
|
||||||
if(ENABLE_SCRIPTING)
|
if(ENABLE_SCRIPTING)
|
||||||
list(APPEND SOURCE_FILES
|
list(APPEND SOURCE_FILES
|
||||||
ScriptingController.cpp
|
ScriptingController.cpp
|
||||||
|
ScriptingTextBuffer.cpp
|
||||||
ScriptingView.cpp)
|
ScriptingView.cpp)
|
||||||
|
|
||||||
list(APPEND UI_FILES
|
list(APPEND UI_FILES
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "ScriptingController.h"
|
#include "ScriptingController.h"
|
||||||
|
|
||||||
#include "CoreController.h"
|
#include "CoreController.h"
|
||||||
|
#include "ScriptingTextBuffer.h"
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ ScriptingController::ScriptingController(QObject* parent)
|
||||||
};
|
};
|
||||||
|
|
||||||
mScriptContextAttachLogger(&m_scriptContext, &m_logger);
|
mScriptContextAttachLogger(&m_scriptContext, &m_logger);
|
||||||
|
mScriptContextSetTextBufferFactory(&m_scriptContext, &ScriptingController::createTextBuffer, this);
|
||||||
|
|
||||||
HashTableEnumerate(&m_scriptContext.engines, [](const char* key, void* engine, void* context) {
|
HashTableEnumerate(&m_scriptContext.engines, [](const char* key, void* engine, void* context) {
|
||||||
ScriptingController* self = static_cast<ScriptingController*>(context);
|
ScriptingController* self = static_cast<ScriptingController*>(context);
|
||||||
|
@ -100,3 +102,10 @@ void ScriptingController::runCode(const QString& code) {
|
||||||
VFileDevice vf(code.toUtf8());
|
VFileDevice vf(code.toUtf8());
|
||||||
load(vf);
|
load(vf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mScriptTextBuffer* ScriptingController::createTextBuffer(void* context) {
|
||||||
|
ScriptingController* self = static_cast<ScriptingController*>(context);
|
||||||
|
ScriptingTextBuffer* buffer = new ScriptingTextBuffer(self);
|
||||||
|
emit self->textBufferCreated(buffer);
|
||||||
|
return buffer->textBuffer();
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
class CoreController;
|
class CoreController;
|
||||||
|
class ScriptingTextBuffer;
|
||||||
|
|
||||||
class ScriptingController : public QObject {
|
class ScriptingController : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -36,12 +38,15 @@ signals:
|
||||||
void log(const QString&);
|
void log(const QString&);
|
||||||
void warn(const QString&);
|
void warn(const QString&);
|
||||||
void error(const QString&);
|
void error(const QString&);
|
||||||
|
void textBufferCreated(ScriptingTextBuffer*);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void clearController();
|
void clearController();
|
||||||
void runCode(const QString& code);
|
void runCode(const QString& code);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static mScriptTextBuffer* createTextBuffer(void* context);
|
||||||
|
|
||||||
struct Logger : mLogger {
|
struct Logger : mLogger {
|
||||||
ScriptingController* p;
|
ScriptingController* p;
|
||||||
} m_logger{};
|
} m_logger{};
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
/* Copyright (c) 2013-2022 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 "ScriptingTextBuffer.h"
|
||||||
|
|
||||||
|
#include "GBAApp.h"
|
||||||
|
|
||||||
|
#include <QMutexLocker>
|
||||||
|
#include <QPlainTextDocumentLayout>
|
||||||
|
#include <QTextBlock>
|
||||||
|
|
||||||
|
using namespace QGBA;
|
||||||
|
|
||||||
|
ScriptingTextBuffer::ScriptingTextBuffer(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
m_shim.init = &ScriptingTextBuffer::init;
|
||||||
|
m_shim.deinit = &ScriptingTextBuffer::deinit;
|
||||||
|
m_shim.setName = &ScriptingTextBuffer::setName;
|
||||||
|
m_shim.getX = &ScriptingTextBuffer::getX;
|
||||||
|
m_shim.getY = &ScriptingTextBuffer::getY;
|
||||||
|
m_shim.cols = &ScriptingTextBuffer::cols;
|
||||||
|
m_shim.rows = &ScriptingTextBuffer::rows;
|
||||||
|
m_shim.print = &ScriptingTextBuffer::print;
|
||||||
|
m_shim.clear = &ScriptingTextBuffer::clear;
|
||||||
|
m_shim.setSize = &ScriptingTextBuffer::setSize;
|
||||||
|
m_shim.moveCursor = &ScriptingTextBuffer::moveCursor;
|
||||||
|
m_shim.advance = &ScriptingTextBuffer::advance;
|
||||||
|
m_shim.p = this;
|
||||||
|
m_shim.cursor = QTextCursor(&m_document);
|
||||||
|
|
||||||
|
auto layout = new QPlainTextDocumentLayout(&m_document);
|
||||||
|
m_document.setDocumentLayout(layout);
|
||||||
|
m_document.setDefaultFont(GBAApp::app()->monospaceFont());
|
||||||
|
m_document.setMaximumBlockCount(m_dims.height());
|
||||||
|
|
||||||
|
QTextOption textOption;
|
||||||
|
textOption.setWrapMode(QTextOption::NoWrap);
|
||||||
|
m_document.setDefaultTextOption(textOption);
|
||||||
|
setBufferName(tr("Untitled buffer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::setBufferName(const QString& name) {
|
||||||
|
m_name = name;
|
||||||
|
m_document.setMetaInformation(QTextDocument::DocumentTitle, name);
|
||||||
|
emit bufferNameChanged(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::print(const QString& text) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
QString split(text);
|
||||||
|
m_shim.cursor.beginEditBlock();
|
||||||
|
while (m_shim.cursor.positionInBlock() + split.length() > m_dims.width()) {
|
||||||
|
int cut = m_dims.width() - m_shim.cursor.positionInBlock();
|
||||||
|
if (!m_shim.cursor.atBlockEnd()) {
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
||||||
|
}
|
||||||
|
m_shim.cursor.insertText(split.left(cut));
|
||||||
|
if (m_shim.cursor.atEnd()) {
|
||||||
|
m_shim.cursor.insertBlock();
|
||||||
|
} else {
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, 1);
|
||||||
|
}
|
||||||
|
split = split.mid(cut);
|
||||||
|
}
|
||||||
|
if (!m_shim.cursor.atBlockEnd()) {
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, split.length());
|
||||||
|
}
|
||||||
|
m_shim.cursor.insertText(split);
|
||||||
|
m_shim.cursor.endEditBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::clear() {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_document.clear();
|
||||||
|
m_document.setMetaInformation(QTextDocument::DocumentTitle, m_name);
|
||||||
|
m_shim.cursor = QTextCursor(&m_document);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::setSize(const QSize& size) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_dims = size;
|
||||||
|
m_document.setMaximumBlockCount(m_dims.height());
|
||||||
|
for (int i = 0; i < m_document.blockCount(); ++i) {
|
||||||
|
if (m_document.findBlockByNumber(i).length() - 1 > m_dims.width()) {
|
||||||
|
QTextCursor deleter(m_document.findBlockByNumber(i));
|
||||||
|
deleter.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, size.width());
|
||||||
|
deleter.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
||||||
|
deleter.removeSelectedText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::moveCursor(const QPoint& pos) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::Start);
|
||||||
|
int y = pos.y();
|
||||||
|
if (y >= m_dims.height()) {
|
||||||
|
y = m_dims.height() - 1;
|
||||||
|
}
|
||||||
|
if (y >= m_document.blockCount()) {
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::End);
|
||||||
|
while (y >= m_document.blockCount()) {
|
||||||
|
m_shim.cursor.insertBlock();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = pos.x();
|
||||||
|
if (x >= m_dims.width()) {
|
||||||
|
x = m_dims.width() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x >= m_shim.cursor.block().length()) {
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::EndOfBlock);
|
||||||
|
m_shim.cursor.insertText(QString(x - m_shim.cursor.block().length() + 1, QChar(' ')));
|
||||||
|
} else {
|
||||||
|
m_shim.cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::advance(int increment) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
int x = m_shim.cursor.positionInBlock();
|
||||||
|
int y = m_shim.cursor.blockNumber();
|
||||||
|
x += increment;
|
||||||
|
if (x > 0) {
|
||||||
|
y += x / m_dims.width();
|
||||||
|
x %= m_dims.width();
|
||||||
|
} else if (x < 0) {
|
||||||
|
y += (x - m_dims.width() + 1) / m_dims.width();
|
||||||
|
x %= m_dims.width();
|
||||||
|
if (x) {
|
||||||
|
x += m_dims.width();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locker.unlock();
|
||||||
|
moveCursor({x, y});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::init(struct mScriptTextBuffer* buffer, const char* name) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
if (name) {
|
||||||
|
QMetaObject::invokeMethod(self, "setBufferName", Q_ARG(const QString&, QString::fromUtf8(name)));
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(self, "clear");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::deinit(struct mScriptTextBuffer*) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::setName(struct mScriptTextBuffer* buffer, const char* name) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
QMetaObject::invokeMethod(self, "setBufferName", Q_ARG(const QString&, QString::fromUtf8(name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ScriptingTextBuffer::getX(const struct mScriptTextBuffer* buffer) {
|
||||||
|
const ScriptingBufferShim* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer);
|
||||||
|
QMutexLocker locker(&self->p->m_mutex);
|
||||||
|
return self->cursor.positionInBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ScriptingTextBuffer::getY(const struct mScriptTextBuffer* buffer) {
|
||||||
|
const ScriptingBufferShim* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer);
|
||||||
|
QMutexLocker locker(&self->p->m_mutex);
|
||||||
|
return self->cursor.blockNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ScriptingTextBuffer::cols(const struct mScriptTextBuffer* buffer) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
QMutexLocker locker(&self->m_mutex);
|
||||||
|
return self->m_dims.width();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ScriptingTextBuffer::rows(const struct mScriptTextBuffer* buffer) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
QMutexLocker locker(&self->m_mutex);
|
||||||
|
return self->m_dims.height();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::print(struct mScriptTextBuffer* buffer, const char* text) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
QMetaObject::invokeMethod(self, "print", Q_ARG(const QString&, QString::fromUtf8(text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::clear(struct mScriptTextBuffer* buffer) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
QMetaObject::invokeMethod(self, "clear");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::setSize(struct mScriptTextBuffer* buffer, uint32_t cols, uint32_t rows) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
if (cols > 500) {
|
||||||
|
cols = 500;
|
||||||
|
}
|
||||||
|
if (rows > 10000) {
|
||||||
|
rows = 10000;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(self, "setSize", Q_ARG(QSize, QSize(cols, rows)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::moveCursor(struct mScriptTextBuffer* buffer, uint32_t x, uint32_t y) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
if (x > INT_MAX) {
|
||||||
|
x = INT_MAX;
|
||||||
|
}
|
||||||
|
if (y > INT_MAX) {
|
||||||
|
y = INT_MAX;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(self, "moveCursor", Q_ARG(QPoint, QPoint(x, y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingTextBuffer::advance(struct mScriptTextBuffer* buffer, int32_t adv) {
|
||||||
|
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
|
||||||
|
emit self->advance(adv);
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/* Copyright (c) 2013-2022 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/. */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTextCursor>
|
||||||
|
#include <QTextDocument>
|
||||||
|
|
||||||
|
#include <mgba/core/scripting.h>
|
||||||
|
|
||||||
|
namespace QGBA {
|
||||||
|
|
||||||
|
class ScriptingTextBuffer : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ScriptingTextBuffer(QObject* parent);
|
||||||
|
|
||||||
|
QTextDocument* document() { return &m_document; };
|
||||||
|
mScriptTextBuffer* textBuffer() { return &m_shim; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setBufferName(const QString&);
|
||||||
|
void print(const QString&);
|
||||||
|
void clear();
|
||||||
|
void setSize(const QSize&);
|
||||||
|
void moveCursor(const QPoint&);
|
||||||
|
void advance(int);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void bufferNameChanged(const QString&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ScriptingBufferShim : public mScriptTextBuffer {
|
||||||
|
ScriptingTextBuffer* p;
|
||||||
|
QTextCursor cursor;
|
||||||
|
} m_shim;
|
||||||
|
QTextDocument m_document;
|
||||||
|
QMutex m_mutex;
|
||||||
|
QString m_name;
|
||||||
|
|
||||||
|
static void init(struct mScriptTextBuffer*, const char* name);
|
||||||
|
static void deinit(struct mScriptTextBuffer*);
|
||||||
|
|
||||||
|
static void setName(struct mScriptTextBuffer*, const char* name);
|
||||||
|
|
||||||
|
static uint32_t getX(const struct mScriptTextBuffer*);
|
||||||
|
static uint32_t getY(const struct mScriptTextBuffer*);
|
||||||
|
static uint32_t cols(const struct mScriptTextBuffer*);
|
||||||
|
static uint32_t rows(const struct mScriptTextBuffer*);
|
||||||
|
|
||||||
|
static void print(struct mScriptTextBuffer*, const char* text);
|
||||||
|
static void clear(struct mScriptTextBuffer*);
|
||||||
|
static void setSize(struct mScriptTextBuffer*, uint32_t cols, uint32_t rows);
|
||||||
|
static void moveCursor(struct mScriptTextBuffer*, uint32_t x, uint32_t y);
|
||||||
|
static void advance(struct mScriptTextBuffer*, int32_t);
|
||||||
|
|
||||||
|
QSize m_dims{80, 24};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "GBAApp.h"
|
#include "GBAApp.h"
|
||||||
#include "ScriptingController.h"
|
#include "ScriptingController.h"
|
||||||
|
#include "ScriptingTextBuffer.h"
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
|
@ -24,7 +25,9 @@ ScriptingView::ScriptingView(ScriptingController* controller, QWidget* parent)
|
||||||
connect(m_controller, &ScriptingController::log, m_ui.log, &LogWidget::log);
|
connect(m_controller, &ScriptingController::log, m_ui.log, &LogWidget::log);
|
||||||
connect(m_controller, &ScriptingController::warn, m_ui.log, &LogWidget::warn);
|
connect(m_controller, &ScriptingController::warn, m_ui.log, &LogWidget::warn);
|
||||||
connect(m_controller, &ScriptingController::error, m_ui.log, &LogWidget::error);
|
connect(m_controller, &ScriptingController::error, m_ui.log, &LogWidget::error);
|
||||||
|
connect(m_controller, &ScriptingController::textBufferCreated, this, &ScriptingView::addTextBuffer);
|
||||||
|
|
||||||
|
connect(m_ui.buffers, &QListWidget::currentRowChanged, this, &ScriptingView::selectBuffer);
|
||||||
connect(m_ui.load, &QAction::triggered, this, &ScriptingView::load);
|
connect(m_ui.load, &QAction::triggered, this, &ScriptingView::load);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +44,25 @@ void ScriptingView::load() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptingView::addTextBuffer(ScriptingTextBuffer* buffer) {
|
||||||
|
QTextDocument* document = buffer->document();
|
||||||
|
m_textBuffers.append(buffer);
|
||||||
|
QListWidgetItem* item = new QListWidgetItem(document->metaInformation(QTextDocument::DocumentTitle));
|
||||||
|
connect(buffer, &ScriptingTextBuffer::bufferNameChanged, this, [item](const QString& name) {
|
||||||
|
item->setText(name);
|
||||||
|
});
|
||||||
|
connect(buffer, &QObject::destroyed, this, [this, buffer, item]() {
|
||||||
|
m_textBuffers.removeAll(buffer);
|
||||||
|
m_ui.buffers->removeItemWidget(item);
|
||||||
|
});
|
||||||
|
m_ui.buffers->addItem(item);
|
||||||
|
m_ui.buffers->setCurrentItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptingView::selectBuffer(int index) {
|
||||||
|
m_ui.buffer->setDocument(m_textBuffers[index]->document());
|
||||||
|
}
|
||||||
|
|
||||||
QString ScriptingView::getFilters() const {
|
QString ScriptingView::getFilters() const {
|
||||||
QStringList filters;
|
QStringList filters;
|
||||||
#ifdef USE_LUA
|
#ifdef USE_LUA
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
class ScriptingController;
|
class ScriptingController;
|
||||||
|
class ScriptingTextBuffer;
|
||||||
|
|
||||||
class ScriptingView : public QMainWindow {
|
class ScriptingView : public QMainWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -21,11 +22,16 @@ private slots:
|
||||||
void submitRepl();
|
void submitRepl();
|
||||||
void load();
|
void load();
|
||||||
|
|
||||||
|
void addTextBuffer(ScriptingTextBuffer*);
|
||||||
|
void selectBuffer(int);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString getFilters() const;
|
QString getFilters() const;
|
||||||
|
|
||||||
Ui::ScriptingView m_ui;
|
Ui::ScriptingView m_ui;
|
||||||
|
|
||||||
ScriptingController* m_controller;
|
ScriptingController* m_controller;
|
||||||
|
QList<ScriptingTextBuffer*> m_textBuffers;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,31 +15,8 @@
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
|
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
|
||||||
<item row="1" column="1">
|
<item row="0" column="0" rowspan="3">
|
||||||
<widget class="QLineEdit" name="prompt"/>
|
<widget class="QListWidget" name="buffers">
|
||||||
</item>
|
|
||||||
<item row="1" column="2">
|
|
||||||
<widget class="QPushButton" name="runButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Run</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1" colspan="2">
|
|
||||||
<widget class="QGBA::LogWidget" name="log">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="readOnly">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0" rowspan="2">
|
|
||||||
<widget class="QListWidget" name="loadedScripts">
|
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
|
@ -52,11 +29,39 @@
|
||||||
<height>16777215</height>
|
<height>16777215</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
</widget>
|
||||||
<property name="text">
|
</item>
|
||||||
<string>Console</string>
|
<item row="0" column="1" colspan="2">
|
||||||
</property>
|
<widget class="QPlainTextEdit" name="buffer">
|
||||||
</item>
|
<property name="lineWrapMode">
|
||||||
|
<enum>QPlainTextEdit::NoWrap</enum>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" colspan="2">
|
||||||
|
<widget class="QGBA::LogWidget" name="log">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="prompt"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QPushButton" name="runButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Run</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
Loading…
Reference in New Issue