feat(editor): command bar
This commit is contained in:
parent
c628fa2b83
commit
e28436b76c
8 changed files with 177 additions and 49 deletions
|
@ -12,8 +12,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
int script_wait(lua_State*);
|
|
||||||
int script_delay(lua_State*);
|
|
||||||
int script_errhandler(lua_State*);
|
int script_errhandler(lua_State*);
|
||||||
|
|
||||||
Script::Script(): Instance(&TYPE) {
|
Script::Script(): Instance(&TYPE) {
|
||||||
|
@ -39,20 +37,6 @@ void Script::Run() {
|
||||||
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
||||||
lua_setfield(Lt, -2, "script");
|
lua_setfield(Lt, -2, "script");
|
||||||
|
|
||||||
InstanceRef(dataModel().value()).PushLuaValue(Lt);
|
|
||||||
lua_setfield(Lt, -2, "game");
|
|
||||||
|
|
||||||
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
|
|
||||||
lua_setfield(Lt, -2, "workspace");
|
|
||||||
|
|
||||||
lua_pushlightuserdata(Lt, scriptContext.get());
|
|
||||||
lua_pushcclosure(Lt, script_wait, 1);
|
|
||||||
lua_setfield(Lt, -2, "wait");
|
|
||||||
|
|
||||||
lua_pushlightuserdata(Lt, scriptContext.get());
|
|
||||||
lua_pushcclosure(Lt, script_delay, 1);
|
|
||||||
lua_setfield(Lt, -2, "delay");
|
|
||||||
|
|
||||||
lua_pop(Lt, 1); // _G
|
lua_pop(Lt, 1); // _G
|
||||||
|
|
||||||
// Push wrapper as thread function
|
// Push wrapper as thread function
|
||||||
|
@ -83,35 +67,6 @@ void Script::Stop() {
|
||||||
// TODO:
|
// TODO:
|
||||||
}
|
}
|
||||||
|
|
||||||
int script_wait(lua_State* L) {
|
|
||||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
|
||||||
float secs = lua_gettop(L) == 0 ? 0.03 : std::max(luaL_checknumber(L, 1), 0.03);
|
|
||||||
if (lua_gettop(L) > 0) lua_pop(L, 1); // pop secs
|
|
||||||
|
|
||||||
scriptContext->PushThreadSleep(L, secs);
|
|
||||||
|
|
||||||
// Yield
|
|
||||||
return lua_yield(L, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int script_delay(lua_State* L) {
|
|
||||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
|
||||||
float secs = std::max(luaL_checknumber(L, 1), 0.03);
|
|
||||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
|
||||||
|
|
||||||
lua_State* Lt = lua_newthread(L); // Create a new thread
|
|
||||||
// I think this is memory abuse??
|
|
||||||
// Wouldn't popping the thread in this case make it eligible for garbage collection?
|
|
||||||
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
|
|
||||||
lua_xmove(L, Lt, 1); // move func
|
|
||||||
lua_pop(L, 1); // pop secs
|
|
||||||
|
|
||||||
// Schedule next run
|
|
||||||
scriptContext->PushThreadSleep(Lt, secs);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int script_errhandler(lua_State* L) {
|
int script_errhandler(lua_State* L) {
|
||||||
std::string errorMessage = lua_tostring(L, -1);
|
std::string errorMessage = lua_tostring(L, -1);
|
||||||
Logger::error(errorMessage);
|
Logger::error(errorMessage);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
#include "scriptcontext.h"
|
#include "scriptcontext.h"
|
||||||
#include "datatypes/cframe.h"
|
#include "datatypes/cframe.h"
|
||||||
#include "datatypes/color3.h"
|
#include "datatypes/color3.h"
|
||||||
|
#include "datatypes/ref.h"
|
||||||
#include "datatypes/vector.h"
|
#include "datatypes/vector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "objects/datamodel.h"
|
||||||
|
#include "objects/service/workspace.h"
|
||||||
#include "timeutil.h"
|
#include "timeutil.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -10,6 +13,8 @@
|
||||||
|
|
||||||
const char* WRAPPER_SRC = "local func, errhandler = ... return function(...) local args = {...} xpcall(function() func(unpack(args)) end, errhandler) end";
|
const char* WRAPPER_SRC = "local func, errhandler = ... return function(...) local args = {...} xpcall(function() func(unpack(args)) end, errhandler) end";
|
||||||
|
|
||||||
|
int g_wait(lua_State*);
|
||||||
|
int g_delay(lua_State*);
|
||||||
static int g_print(lua_State*);
|
static int g_print(lua_State*);
|
||||||
static int g_require(lua_State*);
|
static int g_require(lua_State*);
|
||||||
static const struct luaL_Reg luaglobals [] = {
|
static const struct luaL_Reg luaglobals [] = {
|
||||||
|
@ -50,6 +55,25 @@ void ScriptContext::InitService() {
|
||||||
Color3::PushLuaLibrary(state);
|
Color3::PushLuaLibrary(state);
|
||||||
Instance::PushLuaLibrary(state);
|
Instance::PushLuaLibrary(state);
|
||||||
|
|
||||||
|
// Add other globals
|
||||||
|
lua_getglobal(state, "_G");
|
||||||
|
|
||||||
|
InstanceRef(dataModel().value()).PushLuaValue(state);
|
||||||
|
lua_setfield(state, -2, "game");
|
||||||
|
|
||||||
|
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(state);
|
||||||
|
lua_setfield(state, -2, "workspace");
|
||||||
|
|
||||||
|
lua_pushlightuserdata(state, this);
|
||||||
|
lua_pushcclosure(state, g_wait, 1);
|
||||||
|
lua_setfield(state, -2, "wait");
|
||||||
|
|
||||||
|
lua_pushlightuserdata(state, this);
|
||||||
|
lua_pushcclosure(state, g_delay, 1);
|
||||||
|
lua_setfield(state, -2, "delay");
|
||||||
|
|
||||||
|
lua_pop(state, 1); // _G
|
||||||
|
|
||||||
// Add wrapper function
|
// Add wrapper function
|
||||||
luaL_loadbuffer(state, WRAPPER_SRC, strlen(WRAPPER_SRC), "=PCALL_WRAPPER");
|
luaL_loadbuffer(state, WRAPPER_SRC, strlen(WRAPPER_SRC), "=PCALL_WRAPPER");
|
||||||
lua_setfield(state, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
lua_setfield(state, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||||
|
@ -168,3 +192,32 @@ static int g_require(lua_State* L) {
|
||||||
|
|
||||||
return luaL_error(L, "require is not yet implemented");
|
return luaL_error(L, "require is not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int g_wait(lua_State* L) {
|
||||||
|
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
float secs = lua_gettop(L) == 0 ? 0.03 : std::max(luaL_checknumber(L, 1), 0.03);
|
||||||
|
if (lua_gettop(L) > 0) lua_pop(L, 1); // pop secs
|
||||||
|
|
||||||
|
scriptContext->PushThreadSleep(L, secs);
|
||||||
|
|
||||||
|
// Yield
|
||||||
|
return lua_yield(L, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int g_delay(lua_State* L) {
|
||||||
|
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
float secs = std::max(luaL_checknumber(L, 1), 0.03);
|
||||||
|
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||||
|
|
||||||
|
lua_State* Lt = lua_newthread(L); // Create a new thread
|
||||||
|
// I think this is memory abuse??
|
||||||
|
// Wouldn't popping the thread in this case make it eligible for garbage collection?
|
||||||
|
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
|
||||||
|
lua_xmove(L, Lt, 1); // move func
|
||||||
|
lua_pop(L, 1); // pop secs
|
||||||
|
|
||||||
|
// Schedule next run
|
||||||
|
scriptContext->PushThreadSleep(Lt, secs);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -38,6 +38,8 @@ set(PROJECT_SOURCES
|
||||||
panes/outputtextview.cpp
|
panes/outputtextview.cpp
|
||||||
script/scriptdocument.h
|
script/scriptdocument.h
|
||||||
script/scriptdocument.cpp
|
script/scriptdocument.cpp
|
||||||
|
script/commandedit.h
|
||||||
|
script/commandedit.cpp
|
||||||
aboutdialog.ui
|
aboutdialog.ui
|
||||||
aboutdialog.h
|
aboutdialog.h
|
||||||
aboutdialog.cpp
|
aboutdialog.cpp
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "./ui_mainwindow.h"
|
#include "./ui_mainwindow.h"
|
||||||
|
#include "script/commandedit.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "aboutdialog.h"
|
#include "aboutdialog.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
#include <qevent.h>
|
#include <qevent.h>
|
||||||
#include <qglobal.h>
|
#include <qglobal.h>
|
||||||
#include <qkeysequence.h>
|
#include <qkeysequence.h>
|
||||||
|
#include <qlabel.h>
|
||||||
#include <qmessagebox.h>
|
#include <qmessagebox.h>
|
||||||
#include <qmimedata.h>
|
#include <qmimedata.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
|
@ -117,6 +119,8 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
undoManager.SetUndoStateListener([&]() {
|
undoManager.SetUndoStateListener([&]() {
|
||||||
updateToolbars();
|
updateToolbars();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setUpCommandBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::closeEvent(QCloseEvent* evt) {
|
void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||||
|
@ -142,6 +146,14 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::setUpCommandBar() {
|
||||||
|
CommandEdit* commandEdit;
|
||||||
|
QToolBar* commandBar = ui->commandBar;
|
||||||
|
commandBar->layout()->setSpacing(5);
|
||||||
|
commandBar->addWidget(new QLabel(tr("Command ")));
|
||||||
|
commandBar->addWidget(commandEdit = new CommandEdit());
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::connectActionHandlers() {
|
void MainWindow::connectActionHandlers() {
|
||||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); });
|
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); });
|
||||||
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_MOVE : TOOL_SELECT; updateToolbars(); });
|
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_MOVE : TOOL_SELECT; updateToolbars(); });
|
||||||
|
|
|
@ -69,12 +69,12 @@ public:
|
||||||
private:
|
private:
|
||||||
PlaceDocument* placeDocument;
|
PlaceDocument* placeDocument;
|
||||||
|
|
||||||
|
void setUpCommandBar();
|
||||||
|
void connectActionHandlers();
|
||||||
void updateToolbars();
|
void updateToolbars();
|
||||||
void closeEvent(QCloseEvent* evt) override;
|
void closeEvent(QCloseEvent* evt) override;
|
||||||
ScriptDocument* findScriptWindow(std::shared_ptr<Script>);
|
ScriptDocument* findScriptWindow(std::shared_ptr<Script>);
|
||||||
|
|
||||||
void connectActionHandlers();
|
|
||||||
|
|
||||||
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
|
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
|
||||||
};
|
};
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|
|
@ -172,7 +172,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QToolBar" name="editTools">
|
<widget class="QToolBar" name="editTools">
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>toolBar_3</string>
|
<string>Edit Tools</string>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="toolBarArea">
|
<attribute name="toolBarArea">
|
||||||
<enum>TopToolBarArea</enum>
|
<enum>TopToolBarArea</enum>
|
||||||
|
@ -237,7 +237,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QToolBar" name="toolBar">
|
<widget class="QToolBar" name="toolBar">
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>toolBar</string>
|
<string>Sound Controls</string>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="toolBarArea">
|
<attribute name="toolBarArea">
|
||||||
<enum>TopToolBarArea</enum>
|
<enum>TopToolBarArea</enum>
|
||||||
|
@ -247,6 +247,17 @@
|
||||||
</attribute>
|
</attribute>
|
||||||
<addaction name="actionToggleEditSounds"/>
|
<addaction name="actionToggleEditSounds"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QToolBar" name="commandBar">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Command Bar</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="toolBarArea">
|
||||||
|
<enum>BottomToolBarArea</enum>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="toolBarBreak">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
<action name="actionAddPart">
|
<action name="actionAddPart">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset>
|
||||||
|
|
77
editor/script/commandedit.cpp
Normal file
77
editor/script/commandedit.cpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#include "commandedit.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "lua.h"
|
||||||
|
#include "objects/service/script/scriptcontext.h"
|
||||||
|
#include "luaapis.h" // IWYU pragma: keep
|
||||||
|
#include <qevent.h>
|
||||||
|
#include <qlineedit.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
|
||||||
|
int script_errhandler(lua_State*);
|
||||||
|
|
||||||
|
CommandEdit::CommandEdit(QWidget* parent) : QLineEdit(parent) {
|
||||||
|
connect(this, &QLineEdit::returnPressed, this, &CommandEdit::executeCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandEdit::~CommandEdit() = default;
|
||||||
|
|
||||||
|
void CommandEdit::executeCommand() {
|
||||||
|
std::string command = this->text().toStdString();
|
||||||
|
|
||||||
|
// Execute via Lua
|
||||||
|
auto context = gDataModel->GetService<ScriptContext>();
|
||||||
|
lua_State* L = context->state;
|
||||||
|
|
||||||
|
int top = lua_gettop(L);
|
||||||
|
lua_State* Lt = lua_newthread(L);
|
||||||
|
|
||||||
|
// Push wrapper as thread function
|
||||||
|
lua_getfield(Lt, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||||
|
|
||||||
|
// Load source code and push onto thread as upvalue for wrapper
|
||||||
|
int status = luaL_loadstring(Lt, command.c_str());
|
||||||
|
if (status != LUA_OK) {
|
||||||
|
// Failed to parse/load chunk
|
||||||
|
Logger::error(lua_tostring(Lt, -1));
|
||||||
|
|
||||||
|
lua_settop(L, top);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push our error handler and then generate the wrapped function
|
||||||
|
lua_pushcfunction(Lt, script_errhandler);
|
||||||
|
lua_call(Lt, 2, 1);
|
||||||
|
|
||||||
|
// Resume the thread
|
||||||
|
lua_resume(Lt, 0);
|
||||||
|
|
||||||
|
lua_pop(L, 1); // Pop the thread
|
||||||
|
lua_settop(L, top);
|
||||||
|
|
||||||
|
// Push to history
|
||||||
|
if (commandHistory.size() == 0 || commandHistory.back() != command) {
|
||||||
|
historyIndex = commandHistory.size();
|
||||||
|
commandHistory.push_back(command);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void CommandEdit::keyPressEvent(QKeyEvent* evt) {
|
||||||
|
switch (evt->key()) {
|
||||||
|
case Qt::Key_Up:
|
||||||
|
if (historyIndex > 0)
|
||||||
|
historyIndex--;
|
||||||
|
|
||||||
|
if (commandHistory.size() > 0)
|
||||||
|
this->setText(QString::fromStdString(commandHistory[historyIndex]));
|
||||||
|
return;
|
||||||
|
case Qt::Key_Down:
|
||||||
|
if (historyIndex+1 < (int)commandHistory.size())
|
||||||
|
historyIndex++;
|
||||||
|
|
||||||
|
if (commandHistory.size() > 0)
|
||||||
|
this->setText(QString::fromStdString(commandHistory[historyIndex]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QLineEdit::keyPressEvent(evt);
|
||||||
|
}
|
18
editor/script/commandedit.h
Normal file
18
editor/script/commandedit.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qlineedit.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class CommandEdit : public QLineEdit {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
std::vector<std::string> commandHistory;
|
||||||
|
int historyIndex = 0;
|
||||||
|
|
||||||
|
void executeCommand();
|
||||||
|
public:
|
||||||
|
CommandEdit(QWidget* parent = nullptr);
|
||||||
|
~CommandEdit();
|
||||||
|
|
||||||
|
void keyPressEvent(QKeyEvent *) override;
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue