Scripting: Add prototype "buffer" pseudo-TUI interface

This commit is contained in:
Vicki Pfau 2022-05-19 00:16:40 -07:00
parent f570786d78
commit 28d7bfdffc
11 changed files with 449 additions and 30 deletions

View File

@ -21,8 +21,10 @@ mLOG_DECLARE_CATEGORY(SCRIPT);
struct mCore;
struct mLogger;
struct mScriptTextBuffer;
mSCRIPT_DECLARE_STRUCT(mCore);
mSCRIPT_DECLARE_STRUCT(mLogger);
mSCRIPT_DECLARE_STRUCT(mScriptTextBuffer);
struct mScriptBridge;
struct VFile;
@ -41,6 +43,24 @@ struct mScriptEngine {
#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);
void mScriptBridgeDestroy(struct mScriptBridge*);
@ -64,6 +84,9 @@ void mScriptContextDetachCore(struct mScriptContext*);
void mScriptContextAttachLogger(struct mScriptContext*, struct mLogger*);
void mScriptContextDetachLogger(struct mScriptContext*);
typedef struct mScriptTextBuffer* (*mScriptContextBufferFactory)(void*);
void mScriptContextSetTextBufferFactory(struct mScriptContext*, mScriptContextBufferFactory factory, void* cbContext);
CXX_GUARD_END
#endif

View File

@ -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_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_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_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set)

View File

@ -161,6 +161,11 @@ struct mScriptCoreAdapter {
struct mScriptValue memory;
};
struct mScriptUILibrary {
mScriptContextBufferFactory textBufferFactory;
void* textBufferContext;
};
static uint32_t mScriptMemoryAdapterRead8(struct mScriptMemoryAdapter* adapter, uint32_t address) {
uint32_t segmentSize = adapter->block.end - adapter->block.start;
uint32_t segmentAddress = address % segmentSize;
@ -522,3 +527,59 @@ void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger*
void mScriptContextDetachLogger(struct mScriptContext* context) {
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;
}

View File

@ -250,6 +250,7 @@ endif()
if(ENABLE_SCRIPTING)
list(APPEND SOURCE_FILES
ScriptingController.cpp
ScriptingTextBuffer.cpp
ScriptingView.cpp)
list(APPEND UI_FILES

View File

@ -6,6 +6,7 @@
#include "ScriptingController.h"
#include "CoreController.h"
#include "ScriptingTextBuffer.h"
using namespace QGBA;
@ -37,6 +38,7 @@ ScriptingController::ScriptingController(QObject* parent)
};
mScriptContextAttachLogger(&m_scriptContext, &m_logger);
mScriptContextSetTextBufferFactory(&m_scriptContext, &ScriptingController::createTextBuffer, this);
HashTableEnumerate(&m_scriptContext.engines, [](const char* key, void* engine, void* context) {
ScriptingController* self = static_cast<ScriptingController*>(context);
@ -100,3 +102,10 @@ void ScriptingController::runCode(const QString& code) {
VFileDevice vf(code.toUtf8());
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();
}

View File

@ -18,6 +18,8 @@
namespace QGBA {
class CoreController;
class ScriptingTextBuffer;
class ScriptingController : public QObject {
Q_OBJECT
@ -36,12 +38,15 @@ signals:
void log(const QString&);
void warn(const QString&);
void error(const QString&);
void textBufferCreated(ScriptingTextBuffer*);
public slots:
void clearController();
void runCode(const QString& code);
private:
static mScriptTextBuffer* createTextBuffer(void* context);
struct Logger : mLogger {
ScriptingController* p;
} m_logger{};

View File

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

View File

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

View File

@ -7,6 +7,7 @@
#include "GBAApp.h"
#include "ScriptingController.h"
#include "ScriptingTextBuffer.h"
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::warn, m_ui.log, &LogWidget::warn);
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);
}
@ -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 {
QStringList filters;
#ifdef USE_LUA

View File

@ -10,6 +10,7 @@
namespace QGBA {
class ScriptingController;
class ScriptingTextBuffer;
class ScriptingView : public QMainWindow {
Q_OBJECT
@ -21,11 +22,16 @@ private slots:
void submitRepl();
void load();
void addTextBuffer(ScriptingTextBuffer*);
void selectBuffer(int);
private:
QString getFilters() const;
Ui::ScriptingView m_ui;
ScriptingController* m_controller;
QList<ScriptingTextBuffer*> m_textBuffers;
};
}

View File

@ -15,31 +15,8 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
<item row="1" column="1">
<widget class="QLineEdit" name="prompt"/>
</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">
<item row="0" column="0" rowspan="3">
<widget class="QListWidget" name="buffers">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -52,11 +29,39 @@
<height>16777215</height>
</size>
</property>
<item>
<property name="text">
<string>Console</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QPlainTextEdit" name="buffer">
<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>
</item>
</layout>