Merge pull request #206 from MihailRis/devtools

DevTools
This commit is contained in:
MihailRis 2024-05-17 17:28:42 +03:00 committed by GitHub
commit 56ab8c5c84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1319 additions and 174 deletions

22
res/layouts/console.xml Normal file
View File

@ -0,0 +1,22 @@
<container color='#00000080' size='400' size-func="unpack(gui.get_viewport())">
<container size-func="gui.get_viewport()[1],gui.get_viewport()[2]-40">
<textbox
id='log'
color='0'
autoresize='true'
margin='0'
editable='false'
multiline='true'
size-func="gui.get_viewport()[1],40"
gravity="bottom-left"
></textbox>
</container>
<textbox id='prompt'
consumer='submit'
margin='0'
gravity='bottom-left'
size-func="gui.get_viewport()[1],40"
onup="on_history_up()"
ondown="on_history_down()">
</textbox>
</container>

View File

@ -0,0 +1,53 @@
history = session.get_entry("commands_history")
history_pointer = #history
function setup_variables()
local x,y,z = player.get_pos(hud.get_player())
console.set('pos.x', x)
console.set('pos.y', y)
console.set('pos.z', z)
end
function on_history_up()
if history_pointer == 0 then
return
end
document.prompt.text = history[history_pointer]
document.prompt.caret = -1
history_pointer = history_pointer - 1
end
function on_history_down()
if history_pointer == #history-1 then
return
end
history_pointer = history_pointer + 1
document.prompt.text = history[history_pointer + 1]
document.prompt.caret = -1
end
function add_to_history(text)
table.insert(history, text)
history_pointer = #history
end
function submit(text)
add_to_history(text)
setup_variables()
local status, result = pcall(function() return console.execute(text) end)
if result ~= nil then
local prevtext = document.log.text
if #prevtext == 0 then
document.log:paste(tostring(result))
else
document.log:paste('\n'..tostring(result))
end
end
document.prompt.text = ""
document.prompt.focused = true
end
function on_open()
document.prompt.focused = true
end

89
res/scripts/stdcmd.lua Normal file
View File

@ -0,0 +1,89 @@
local SEPARATOR = "________________"
SEPARATOR = SEPARATOR..SEPARATOR..SEPARATOR
function build_scheme(command)
local str = command.name.." "
for i,arg in ipairs(command.args) do
if arg.optional then
str = str.."["..arg.name.."] "
else
str = str.."<"..arg.name.."> "
end
end
return str
end
console.add_command(
"help name:str=''",
"Show help infomation for the specified command",
function (args, kwargs)
local name = args[1]
if #name == 0 then
local commands = console.get_commands_list()
table.sort(commands)
local str = "Available commands:"
for i,k in ipairs(commands) do
str = str.."\n "..build_scheme(console.get_command_info(k))
end
return str.."\nuse 'help <command>'"
end
local command = console.get_command_info(name)
if command == nil then
return string.format("command %q not found", name)
end
local where = ""
local str = SEPARATOR.."\n"..command.description.."\n"..name.." "
for i,arg in ipairs(command.args) do
where = where.."\n "..arg.name.." - "..arg.type
if arg.optional then
str = str.."["..arg.name.."] "
where = where.." (optional)"
else
str = str.."<"..arg.name.."> "
end
end
if #command.args then
str = str.."\nwhere"..where
end
return str.."\n"..SEPARATOR
end
)
console.add_command(
"obj.tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z",
"Teleport object",
function (args, kwargs)
player.set_pos(unpack(args))
end
)
console.add_command(
"echo value:str",
"Print value to the console",
function (args, kwargs)
return args[1]
end
)
console.add_command(
"time.set value:num",
"Set day time [0..1] where 0 is midnight, 0.5 is noon",
function (args, kwargs)
return world.set_day_time(args[1])
end
)
console.add_command(
"blocks.fill id:str x:num~pos.x y:num~pos.y z:num~pos.z w:int h:int d:int",
"Fill specified zone with blocks",
function (args, kwargs)
local name, x, y, z, w, h, d = unpack(args)
local id = block.index(name)
for ly=0,h-1 do
for lz=0,d-1 do
for lx=0,w-1 do
block.set(x+lx, y+ly, z+lz, id)
end
end
end
return tostring(w*h*d).." blocks set"
end
)

View File

@ -9,6 +9,7 @@ world.generators.default=Default
world.generators.flat=Flat
# Bindings
devtools.console=Console
movement.forward=Forward
movement.back=Back
movement.left=Left

View File

@ -56,6 +56,7 @@ settings.UI Sounds=Звуки Интерфейса
settings.V-Sync=Вертикальная Синхронизация
# Управление
devtools.console=Консоль
movement.forward=Вперёд
movement.back=Назад
movement.left=Влево

View File

@ -154,8 +154,13 @@ void BasicParser::expectNewLine() {
}
}
void BasicParser::goBack() {
if (pos) pos--;
void BasicParser::goBack(size_t count) {
if (pos < count) {
throw std::runtime_error("pos < jump");
}
if (pos) {
pos -= count;
}
}
char BasicParser::peek() {
@ -166,6 +171,13 @@ char BasicParser::peek() {
return source[pos];
}
char BasicParser::peekNoJump() {
if (pos >= source.length()) {
throw error("unexpected end");
}
return source[pos];
}
std::string_view BasicParser::readUntil(char c) {
int start = pos;
while (hasNext() && source[pos] != c) {

View File

@ -31,11 +31,11 @@ inline bool is_whitespace(int c) {
}
inline bool is_identifier_start(int c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '.';
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '.';
}
inline bool is_identifier_part(int c) {
return is_identifier_start(c) || is_digit(c);
return is_identifier_start(c) || is_digit(c) || c == '-';
}
inline int hexchar2int(int c) {
@ -86,7 +86,7 @@ protected:
void expect(const std::string& substring);
bool isNext(const std::string& substring);
void expectNewLine();
void goBack();
void goBack(size_t count=1);
int64_t parseSimpleInt(int base);
dynamic::Value parseNumber(int sign);
@ -99,6 +99,7 @@ public:
std::string parseName();
bool hasNext();
char peek();
char peekNoJump();
char nextChar();
BasicParser(std::string_view file, std::string_view source);

View File

@ -36,14 +36,6 @@ inline void newline(
}
}
void stringify(
const Value& value,
std::stringstream& ss,
int indent,
const std::string& indentstr,
bool nice
);
void stringifyObj(
const Map* obj,
std::stringstream& ss,
@ -91,6 +83,8 @@ void stringifyValue(
ss << *num;
} else if (auto str = std::get_if<std::string>(&value)) {
ss << util::escape(*str);
} else {
ss << "null";
}
}

View File

@ -24,6 +24,7 @@ void corecontent::setup(ContentBuilder* builder) {
}
void corecontent::setup_bindings() {
Events::bind(BIND_DEVTOOLS_CONSOLE, inputtype::keyboard, keycode::GRAVE_ACCENT);
Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W);
Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S);
Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D);

View File

@ -9,6 +9,7 @@ inline const std::string CORE_AIR = "core:air";
inline const std::string TEXTURE_NOTFOUND = "notfound";
// built-in bindings
inline const std::string BIND_DEVTOOLS_CONSOLE = "devtools.console";
inline const std::string BIND_MOVE_FORWARD = "movement.forward";
inline const std::string BIND_MOVE_BACK = "movement.back";
inline const std::string BIND_MOVE_LEFT = "movement.left";

View File

@ -242,6 +242,20 @@ size_t Map::size() const {
return values.size();
}
static const std::string TYPE_NAMES[] {
"none",
"map",
"list",
"string",
"number",
"bool",
"integer",
};
const std::string& dynamic::type_name(const Value& value) {
return TYPE_NAMES[value.index()];
}
List_sptr dynamic::create_list(std::initializer_list<Value> values) {
return std::make_shared<List>(values);
}
@ -249,3 +263,19 @@ List_sptr dynamic::create_list(std::initializer_list<Value> values) {
Map_sptr dynamic::create_map(std::initializer_list<std::pair<const std::string, Value>> entries) {
return std::make_shared<Map>(entries);
}
number_t dynamic::get_number(const Value& value) {
if (auto num = std::get_if<number_t>(&value)) {
return *num;
} else if (auto num = std::get_if<integer_t>(&value)) {
return *num;
}
throw std::runtime_error("cannot cast "+type_name(value)+" to number");
}
integer_t dynamic::get_integer(const Value& value) {
if (auto num = std::get_if<integer_t>(&value)) {
return *num;
}
throw std::runtime_error("cannot cast "+type_name(value)+" to integer");
}

View File

@ -35,8 +35,16 @@ namespace dynamic {
integer_t
>;
const std::string& type_name(const Value& value);
List_sptr create_list(std::initializer_list<Value> values={});
Map_sptr create_map(std::initializer_list<std::pair<const std::string, Value>> entries={});
number_t get_number(const Value& value);
integer_t get_integer(const Value& value);
inline bool is_numeric(const Value& value) {
return std::holds_alternative<number_t>(value) ||
std::holds_alternative<integer_t>(value);
}
class List {
public:
@ -125,6 +133,24 @@ namespace dynamic {
Map& put(std::string key, std::unique_ptr<List> value) {
return put(key, List_sptr(value.release()));
}
Map& put(std::string key, int value) {
return put(key, Value(static_cast<integer_t>(value)));
}
Map& put(std::string key, unsigned int value) {
return put(key, Value(static_cast<integer_t>(value)));
}
Map& put(std::string key, int64_t value) {
return put(key, Value(static_cast<integer_t>(value)));
}
Map& put(std::string key, float value) {
return put(key, Value(static_cast<number_t>(value)));
}
Map& put(std::string key, double value) {
return put(key, Value(static_cast<number_t>(value)));
}
Map& put(std::string key, bool value) {
return put(key, Value(static_cast<bool>(value)));
}
Map& put(std::string key, const Value& value);
void remove(const std::string& key);

View File

@ -23,6 +23,7 @@
#include "graphics/core/Shader.hpp"
#include "graphics/ui/GUI.hpp"
#include "logic/EngineController.hpp"
#include "logic/CommandsInterpreter.hpp"
#include "logic/scripting/scripting.hpp"
#include "util/listutil.hpp"
#include "util/platform.hpp"
@ -59,7 +60,8 @@ inline void create_channel(Engine* engine, std::string name, NumberSetting& sett
}
Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths)
: settings(settings), settingsHandler(settingsHandler), paths(paths)
: settings(settings), settingsHandler(settingsHandler), paths(paths),
interpreter(std::make_unique<cmd::CommandsInterpreter>())
{
corecontent::setup_bindings();
loadSettings();
@ -198,6 +200,7 @@ Engine::~Engine() {
}
content.reset();
assets.reset();
interpreter.reset();
gui.reset();
logger.info() << "gui finished";
audio::close();
@ -211,6 +214,10 @@ EngineController* Engine::getController() {
return controller.get();
}
cmd::CommandsInterpreter* Engine::getCommandsInterpreter() {
return interpreter.get();
}
PacksManager Engine::createPacksManager(const fs::path& worldFolder) {
PacksManager manager;
manager.setSources({

View File

@ -34,6 +34,10 @@ namespace gui {
class GUI;
}
namespace cmd {
class CommandsInterpreter;
}
class initialize_error : public std::runtime_error {
public:
initialize_error(const std::string& message) : std::runtime_error(message) {}
@ -52,6 +56,7 @@ class Engine : public util::ObjectsKeeper {
std::queue<runnable> postRunnables;
std::recursive_mutex postRunnablesMutex;
std::unique_ptr<EngineController> controller;
std::unique_ptr<cmd::CommandsInterpreter> interpreter;
std::vector<std::string> basePacks {"base"};
uint64_t frame = 0;
@ -136,6 +141,7 @@ public:
void saveScreenshot();
EngineController* getController();
cmd::CommandsInterpreter* getCommandsInterpreter();
PacksManager createPacksManager(const fs::path& worldFolder);

View File

@ -56,7 +56,7 @@ std::unique_ptr<UiDocument> UiDocument::read(scriptenv penv, std::string name, f
auto xmldoc = xml::parse(file.u8string(), text);
auto env = penv == nullptr
? scripting::get_root_environment()
? scripting::create_doc_environment(scripting::get_root_environment(), name)
: scripting::create_doc_environment(penv, name);
gui::UiXmlReader reader(env);

View File

@ -204,6 +204,9 @@ void Hud::processInput(bool visible) {
setPause(true);
}
}
if (!pause && Events::active(BIND_DEVTOOLS_CONSOLE)) {
showOverlay(assets->getLayout("core:console"), false);
}
if (!Window::isFocused() && !pause && !isInventoryOpen()) {
setPause(true);
}
@ -501,7 +504,7 @@ void Hud::updateElementsPosition(const Viewport& viewport) {
));
}
secondUI->setPos(glm::vec2(
glm::min(width/2-invwidth/2, width-caWidth-10-invwidth),
glm::min(width/2-invwidth/2, width-caWidth-(inventoryView ? 10 : 0)-invwidth),
height/2-totalHeight/2
));
}

View File

@ -27,7 +27,7 @@ void menus::create_version_label(Engine* engine) {
auto gui = engine->getGUI();
auto text = ENGINE_VERSION_STRING+" debug build";
gui->add(guiutil::create(
"<label z-index='1000' color='#FFFFFF80' gravity='bottom-left' margin='4'>"
"<label z-index='1000' color='#FFFFFF80' gravity='top-right' margin='4'>"
+text+
"</label>"
));

View File

@ -12,15 +12,19 @@
using namespace gui;
TextBox::TextBox(std::wstring placeholder, glm::vec4 padding)
: Panel(glm::vec2(200,32), padding, 0),
input(L""),
placeholder(placeholder) {
: Panel(glm::vec2(200,32), padding, 0),
input(L""),
placeholder(placeholder)
{
setOnUpPressed(nullptr);
setOnDownPressed(nullptr);
label = std::make_shared<Label>(L"");
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
add(label);
setHoverColor(glm::vec4(0.05f, 0.1f, 0.2f, 0.75f));
textInitX = label->getPos().x;
scrollable = true;
}
void TextBox::draw(const DrawContext* pctx, Assets* assets) {
@ -28,9 +32,9 @@ void TextBox::draw(const DrawContext* pctx, Assets* assets) {
font = assets->getFont(label->getFontName());
if (!isFocused())
if (!isFocused()) {
return;
}
glm::vec2 pos = calcPos();
glm::vec2 size = getSize();
@ -78,7 +82,7 @@ void TextBox::drawBackground(const DrawContext* pctx, Assets*) {
batch->texture(nullptr);
auto subctx = pctx->sub();
subctx.setScissors(glm::vec4(pos.x, pos.y, size.x, size.y));
subctx.setScissors(glm::vec4(pos.x, pos.y-0.5, size.x, size.y+1));
if (valid) {
if (isFocused() && !multiline) {
@ -115,9 +119,30 @@ void TextBox::drawBackground(const DrawContext* pctx, Assets*) {
line++;
} while (line < label->getLinesNumber() && label->isFakeLine(line));
}
refreshLabel();
}
void TextBox::refreshLabel() {
label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f));
label->setText(getText());
if (autoresize && font) {
auto size = getSize();
int newy = glm::min(static_cast<int>(parent->getSize().y),
static_cast<int>(
label->getLinesNumber() *
label->getLineInterval() *
font->getLineHeight()) + 1
);
if (newy != static_cast<int>(size.y)) {
size.y = newy;
setSize(size);
if (positionfunc) {
pos = positionfunc();
}
}
}
if (multiline && font) {
setScrollable(true);
uint height = label->getLinesNumber() * font->getLineHeight() * label->getLineInterval();
@ -140,9 +165,7 @@ void TextBox::paste(const std::wstring& text) {
input = left + text + right;
}
input.erase(std::remove(input.begin(), input.end(), '\r'), input.end());
// refresh label lines configuration for correct setCaret work
label->setText(input);
refreshLabel();
setCaret(caret + text.length());
validate();
}
@ -186,8 +209,9 @@ void TextBox::extendSelection(int index) {
size_t TextBox::getLineLength(uint line) const {
size_t position = label->getTextLineOffset(line);
size_t lineLength = label->getTextLineOffset(line+1)-position;
if (lineLength == 0)
if (lineLength == 0) {
lineLength = input.length() - position + 1;
}
return lineLength;
}
@ -255,6 +279,14 @@ void TextBox::setOnEditStart(runnable oneditstart) {
onEditStart = oneditstart;
}
void TextBox::setAutoResize(bool flag) {
this->autoresize = flag;
}
bool TextBox::isAutoResize() const {
return autoresize;
}
void TextBox::onFocus(GUI* gui) {
Panel::onFocus(gui);
if (onEditStart){
@ -337,7 +369,7 @@ void TextBox::click(GUI*, int x, int y) {
}
void TextBox::mouseMove(GUI*, int x, int y) {
int index = calcIndexAt(x, y);
ssize_t index = calcIndexAt(x, y);
setCaret(index);
extendSelection(index);
resetMaxLocalCaret();
@ -347,10 +379,94 @@ void TextBox::resetMaxLocalCaret() {
maxLocalCaret = caret - label->getTextLineOffset(label->getLineByTextIndex(caret));
}
void TextBox::stepLeft(bool shiftPressed, bool breakSelection) {
uint previousCaret = this->caret;
size_t caret = breakSelection ? selectionStart : this->caret;
if (caret > 0) {
if (caret > input.length()) {
setCaret(input.length()-1);
} else {
setCaret(caret-1);
}
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
} else {
setCaret(caret);
resetSelection();
}
resetMaxLocalCaret();
}
void TextBox::stepRight(bool shiftPressed, bool breakSelection) {
uint previousCaret = this->caret;
size_t caret = breakSelection ? selectionEnd : this->caret;
if (caret < input.length()) {
setCaret(caret+1);
caretLastMove = Window::time();
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
} else {
setCaret(caret);
resetSelection();
}
resetMaxLocalCaret();
}
void TextBox::stepDefaultDown(bool shiftPressed, bool breakSelection) {
uint previousCaret = this->caret;
uint caret = breakSelection ? selectionEnd : this->caret;
uint caretLine = label->getLineByTextIndex(caret);
if (caretLine < label->getLinesNumber()-1) {
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine+1)-1);
setCaret(label->getTextLineOffset(caretLine+1) + offset);
} else {
setCaret(input.length());
}
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
}
void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) {
uint previousCaret = this->caret;
uint caret = breakSelection ? selectionStart : this->caret;
uint caretLine = label->getLineByTextIndex(caret);
if (caretLine > 0) {
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine-1)-1);
setCaret(label->getTextLineOffset(caretLine-1) + offset);
} else {
setCaret(0UL);
}
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
}
void TextBox::performEditingKeyboardEvents(keycode key) {
bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT);
bool breakSelection = getSelectionLength() != 0 && !shiftPressed;
uint previousCaret = caret;
if (key == keycode::BACKSPACE) {
if (!eraseSelected() && caret > 0 && input.length() > 0) {
if (caret > input.length()) {
@ -369,86 +485,21 @@ void TextBox::performEditingKeyboardEvents(keycode key) {
if (multiline) {
paste(L"\n");
} else {
defocus();
if (validate() && consumer) {
consumer(label->getText());
}
defocus();
}
} else if (key == keycode::TAB) {
paste(L" ");
} else if (key == keycode::LEFT) {
uint caret = breakSelection ? selectionStart : this->caret;
if (caret > 0) {
if (caret > input.length()) {
setCaret(input.length()-1);
} else {
setCaret(caret-1);
}
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
} else {
setCaret(caret);
resetSelection();
}
resetMaxLocalCaret();
stepLeft(shiftPressed, breakSelection);
} else if (key == keycode::RIGHT) {
uint caret = breakSelection ? selectionEnd : this->caret;
if (caret < input.length()) {
setCaret(caret+1);
caretLastMove = Window::time();
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
} else {
setCaret(caret);
resetSelection();
}
resetMaxLocalCaret();
} else if (key == keycode::UP) {
uint caret = breakSelection ? selectionStart : this->caret;
uint caretLine = label->getLineByTextIndex(caret);
if (caretLine > 0) {
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine-1)-1);
setCaret(label->getTextLineOffset(caretLine-1) + offset);
} else {
setCaret(0);
}
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
} else if (key == keycode::DOWN) {
uint caret = breakSelection ? selectionEnd : this->caret;
uint caretLine = label->getLineByTextIndex(caret);
if (caretLine < label->getLinesNumber()-1) {
uint offset = std::min(size_t(maxLocalCaret), getLineLength(caretLine+1)-1);
setCaret(label->getTextLineOffset(caretLine+1) + offset);
} else {
setCaret(input.length());
}
if (shiftPressed) {
if (selectionStart == selectionEnd) {
selectionOrigin = previousCaret;
}
extendSelection(this->caret);
} else {
resetSelection();
}
stepRight(shiftPressed, breakSelection);
} else if (key == keycode::UP && onUpPressed) {
onUpPressed();
} else if (key == keycode::DOWN && onDownPressed) {
onDownPressed();
}
}
@ -502,6 +553,30 @@ std::shared_ptr<UINode> TextBox::getAt(glm::vec2 pos, std::shared_ptr<UINode> se
return UINode::getAt(pos, self);
}
void TextBox::setOnUpPressed(runnable callback) {
if (callback == nullptr) {
onUpPressed = [this]() {
bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT);
bool breakSelection = getSelectionLength() != 0 && !shiftPressed;
stepDefaultUp(shiftPressed, breakSelection);
};
} else {
onUpPressed = callback;
}
}
void TextBox::setOnDownPressed(runnable callback) {
if (callback == nullptr) {
onDownPressed = [this]() {
bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT);
bool breakSelection = getSelectionLength() != 0 && !shiftPressed;
stepDefaultDown(shiftPressed, breakSelection);
};
} else {
onDownPressed = callback;
}
}
void TextBox::setTextSupplier(wstringsupplier supplier) {
this->supplier = supplier;
}
@ -553,12 +628,12 @@ std::wstring TextBox::getSelection() const {
return input.substr(selectionStart, selectionEnd-selectionStart);
}
uint TextBox::getCaret() const {
size_t TextBox::getCaret() const {
return caret;
}
void TextBox::setCaret(uint position) {
this->caret = std::min(size_t(position), input.length());
void TextBox::setCaret(size_t position) {
this->caret = std::min(static_cast<size_t>(position), input.length());
caretLastMove = Window::time();
int width = label->getSize().x;
@ -569,13 +644,22 @@ void TextBox::setCaret(uint position) {
if (offset < 0) {
scrolled(1);
} else if (offset >= getSize().y) {
scrolled(-1);
offset -= getSize().y;
scrolled(-glm::ceil(offset/static_cast<double>(scrollStep)+0.5f));
}
uint lcaret = caret - label->getTextLineOffset(line);
int realoffset = font->calcWidth(input, lcaret)-int(textOffset)+2;
if (realoffset-width > 0) {
setTextOffset(textOffset + realoffset-width);
} else if (realoffset < 0) {
setTextOffset(std::max(textOffset + realoffset, 0U));
setTextOffset(std::max(textOffset + realoffset, 0LU));
}
}
void TextBox::setCaret(ssize_t position) {
if (position < 0) {
setCaret(static_cast<size_t>(input.length() + position + 1));
} else {
setCaret(static_cast<size_t>(position));
}
}

View File

@ -20,12 +20,14 @@ namespace gui {
wstringconsumer consumer = nullptr;
wstringchecker validator = nullptr;
runnable onEditStart = nullptr;
runnable onUpPressed;
runnable onDownPressed;
bool valid = true;
/// @brief text input pointer, value may be greather than text length
uint caret = 0;
size_t caret = 0;
/// @brief actual local (line) position of the caret on vertical move
uint maxLocalCaret = 0;
uint textOffset = 0;
size_t maxLocalCaret = 0;
size_t textOffset = 0;
int textInitX;
/// @brief last time of the caret was moved (used for blink animation)
double caretLastMove = 0.0;
@ -37,11 +39,16 @@ namespace gui {
bool multiline = false;
bool editable = true;
bool autoresize = false;
void stepLeft(bool shiftPressed, bool breakSelection);
void stepRight(bool shiftPressed, bool breakSelection);
void stepDefaultDown(bool shiftPressed, bool breakSelection);
void stepDefaultUp(bool shiftPressed, bool breakSelection);
size_t normalizeIndex(int index);
int calcIndexAt(int x, int y) const;
void paste(const std::wstring& text);
void setTextOffset(uint x);
void erase(size_t start, size_t length);
bool eraseSelected();
@ -57,12 +64,16 @@ namespace gui {
void resetMaxLocalCaret();
void performEditingKeyboardEvents(keycode key);
void refreshLabel();
public:
TextBox(
std::wstring placeholder,
glm::vec4 padding=glm::vec4(4.0f)
);
void paste(const std::wstring& text);
virtual void setTextSupplier(wstringsupplier supplier);
/// @brief Consumer called on stop editing text (textbox defocus)
@ -101,11 +112,15 @@ namespace gui {
/// @brief Get current caret position in text
/// @return integer in range [0, text.length()]
virtual uint getCaret() const;
virtual size_t getCaret() const;
/// @brief Set caret position in the text
/// @param position integer in range [0, text.length()]
virtual void setCaret(uint position);
virtual void setCaret(size_t position);
/// @brief Set caret position in the text
/// @param position integer in range [-text.length(), text.length()]
virtual void setCaret(ssize_t position);
/// @brief Select part of the text
/// @param start index of the first selected character
@ -140,6 +155,9 @@ namespace gui {
/// @brief Set runnable called on textbox focus
virtual void setOnEditStart(runnable oneditstart);
virtual void setAutoResize(bool flag);
virtual bool isAutoResize() const;
virtual void onFocus(GUI*) override;
virtual void refresh() override;
virtual void doubleClick(GUI*, int x, int y) override;
@ -151,6 +169,8 @@ namespace gui {
virtual void typed(unsigned int codepoint) override;
virtual void keyPressed(keycode key) override;
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void setOnUpPressed(runnable callback);
virtual void setOnDownPressed(runnable callback);
};
}

View File

@ -60,12 +60,12 @@ UINode* UINode::getParent() const {
}
UINode* UINode::listenAction(onaction action) {
actions.push_back(action);
actions.listen(action);
return this;
}
UINode* UINode::listenDoubleClick(onaction action) {
doubleClickCallbacks.push_back(action);
doubleClickCallbacks.listen(action);
return this;
}
@ -76,18 +76,14 @@ void UINode::click(GUI*, int, int) {
void UINode::doubleClick(GUI* gui, int x, int y) {
pressed = true;
if (isInside(glm::vec2(x, y))) {
for (auto callback : doubleClickCallbacks) {
callback(gui);
}
doubleClickCallbacks.notify(gui);
}
}
void UINode::mouseRelease(GUI* gui, int x, int y) {
pressed = false;
if (isInside(glm::vec2(x, y))) {
for (auto callback : actions) {
callback(gui);
}
actions.notify(gui);
}
}

View File

@ -21,6 +21,25 @@ namespace gui {
using onaction = std::function<void(GUI*)>;
using onnumberchange = std::function<void(GUI*, double)>;
class ActionsSet {
std::unique_ptr<std::vector<onaction>> callbacks;
public:
void listen(onaction callback) {
if (callbacks == nullptr) {
callbacks = std::make_unique<std::vector<onaction>>();
}
callbacks->push_back(callback);
}
void notify(GUI* gui) {
if (callbacks) {
for (auto& callback : *callbacks) {
callback(gui);
}
}
}
};
enum class Align {
left, center, right,
@ -87,9 +106,9 @@ namespace gui {
/// @brief size supplier for the element (called on parent element size update)
vec2supplier sizefunc = nullptr;
/// @brief 'onclick' callbacks
std::vector<onaction> actions;
ActionsSet actions;
/// @brief 'ondoubleclick' callbacks
std::vector<onaction> doubleClickCallbacks;
ActionsSet doubleClickCallbacks;
UINode(glm::vec2 size);
public:

View File

@ -50,6 +50,30 @@ static Gravity gravity_from_string(const std::string& str) {
return Gravity::none;
}
static runnable create_runnable(
const UiXmlReader& reader,
xml::xmlelement element,
const std::string& name
) {
if (element->has(name)) {
std::string text = element->attr(name).getText();
if (!text.empty()) {
return scripting::create_runnable(
reader.getEnvironment(), text, reader.getFilename()
);
}
}
return nullptr;
}
static onaction create_action(UiXmlReader& reader, xml::xmlelement element, const std::string& name) {
auto callback = create_runnable(reader, element, name);
if (callback == nullptr) {
return nullptr;
}
return [callback](GUI*) {callback();};
}
/* Read basic UINode properties */
static void _readUINode(UiXmlReader& reader, xml::xmlelement element, UINode& node) {
if (element->has("id")) {
@ -119,28 +143,12 @@ static void _readUINode(UiXmlReader& reader, xml::xmlelement element, UINode& no
));
}
if (element->has("onclick")) {
std::string text = element->attr("onclick").getText();
if (!text.empty()) {
auto callback = scripting::create_runnable(
reader.getEnvironment(), text, reader.getFilename()
);
node.listenAction([callback](GUI*) {
callback();
});
}
if (auto onclick = create_action(reader, element, "onclick")) {
node.listenAction(onclick);
}
if (element->has("ondoubleclick")) {
std::string text = element->attr("ondoubleclick").getText();
if (!text.empty()) {
auto callback = scripting::create_runnable(
reader.getEnvironment(), text, reader.getFilename()
);
node.listenDoubleClick([callback](GUI*) {
callback();
});
}
if (auto ondoubleclick = create_action(reader, element, "ondoubleclick")) {
node.listenDoubleClick(ondoubleclick);
}
}
@ -323,11 +331,12 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
if (element->has("text-wrap")) {
textbox->setTextWrapping(element->attr("text-wrap").asBool());
}
if (element->has("editable")) {
textbox->setEditable(element->attr("editable").asBool());
}
if (element->has("autoresize")) {
textbox->setAutoResize(element->attr("autoresize").asBool());
}
if (element->has("consumer")) {
textbox->setTextConsumer(scripting::create_wstring_consumer(
reader.getEnvironment(),
@ -335,7 +344,6 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
reader.getFilename()
));
}
if (element->has("supplier")) {
textbox->setTextSupplier(scripting::create_wstring_supplier(
reader.getEnvironment(),
@ -356,6 +364,12 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
reader.getFilename()
));
}
if (auto onUpPressed = create_runnable(reader, element, "onup")) {
textbox->setOnUpPressed(onUpPressed);
}
if (auto onDownPressed = create_runnable(reader, element, "ondown")) {
textbox->setOnDownPressed(onDownPressed);
}
return textbox;
}

View File

@ -0,0 +1,405 @@
#include "CommandsInterpreter.hpp"
#include "../coders/commons.hpp"
#include "../util/stringutil.hpp"
#include <iostream>
using namespace cmd;
inline bool is_cmd_identifier_part(char c, bool allowColon) {
return is_identifier_part(c) || c == '.' || c == '$' ||
(allowColon && c == ':');
}
inline bool is_cmd_identifier_start(char c) {
return (is_identifier_start(c) || c == '.' || c == '$');
}
class CommandParser : BasicParser {
std::string parseIdentifier(bool allowColon) {
char c = peek();
if (!is_identifier_start(c) && c != '$') {
if (c == '"') {
pos++;
return parseString(c);
}
throw error("identifier expected");
}
int start = pos;
while (hasNext() && is_cmd_identifier_part(source[pos], allowColon)) {
pos++;
}
return std::string(source.substr(start, pos-start));
}
std::unordered_map<std::string, ArgType> types {
{"num", ArgType::number},
{"int", ArgType::integer},
{"str", ArgType::string},
{"sel", ArgType::selector},
{"enum", ArgType::enumvalue},
};
public:
CommandParser(std::string_view filename, std::string_view source)
: BasicParser(filename, source) {
}
ArgType parseType() {
if (peek() == '[') {
return ArgType::enumvalue;
}
std::string name = parseIdentifier(false);
auto found = types.find(name);
if (found != types.end()) {
return found->second;
} else {
throw error("unknown type "+util::quote(name));
}
}
dynamic::Value parseValue() {
char c = peek();
if (is_cmd_identifier_start(c) || c == '@') {
auto str = parseIdentifier(true);
if (str == "true") {
return true;
} else if (str == "false") {
return false;
} else if (str == "none" || str == "nil" || str == "null") {
return dynamic::NONE;
}
return str;
}
if (c == '"' || c == '\'') {
nextChar();
return parseString(c);
}
if (c == '+' || c == '-') {
nextChar();
return parseNumber(c == '-' ? -1 : 1);
}
if (is_digit(c)) {
return parseNumber(1);
}
throw error("invalid character '"+std::string({c})+"'");
}
std::string parseEnum() {
if (peek() == '[') {
nextChar();
if (peek() == ']') {
throw error("empty enumeration is not allowed");
}
auto enumvalue = "|"+std::string(readUntil(']'))+"|";
size_t offset = enumvalue.find(' ');
if (offset != std::string::npos) {
goBack(enumvalue.length()-offset);
throw error("use '|' as separator, not a space");
}
nextChar();
return enumvalue;
} else {
expect('$');
goBack();
return parseIdentifier(false);
}
}
Argument parseArgument() {
std::string name = parseIdentifier(false);
expect(':');
ArgType type = parseType();
std::string enumname = "";
if (type == ArgType::enumvalue) {
enumname = parseEnum();
}
bool optional = false;
dynamic::Value def {};
dynamic::Value origin {};
bool loop = true;
while (hasNext() && loop) {
char c = peek();
switch (c) {
case '=':
nextChar();
optional = true;
def = parseValue();
break;
case '~':
nextChar();
origin = parseValue();
break;
default:
loop = false;
break;
}
}
return Argument {name, type, optional, def, origin, enumname};
}
Command parseScheme(executor_func executor, std::string_view description) {
std::string name = parseIdentifier(true);
std::vector<Argument> args;
std::unordered_map<std::string, Argument> kwargs;
while (hasNext()) {
if (peek() == '{') {
nextChar();
while (peek() != '}') {
Argument arg = parseArgument();
kwargs[arg.name] = arg;
}
nextChar();
} else {
args.push_back(parseArgument());
}
}
return Command(
name, std::move(args), std::move(kwargs),
std::string(description), executor
);
}
inline parsing_error argumentError(
const std::string& argname,
const std::string& message
) {
return error("argument "+util::quote(argname)+": "+message);
}
inline parsing_error typeError(
const std::string& argname,
const std::string& expected,
const dynamic::Value& value
) {
return argumentError(
argname, expected+" expected, got "+dynamic::type_name(value)
);
}
template<typename T>
inline bool typeCheck(Argument* arg, const dynamic::Value& value, const std::string& tname) {
if (!std::holds_alternative<T>(value)) {
if (arg->optional) {
return false;
} else {
throw typeError(arg->name, tname, value);
}
}
return true;
}
inline bool selectorCheck(Argument* arg, const dynamic::Value& value) {
if (auto string = std::get_if<std::string>(&value)) {
if ((*string)[0] == '@') {
if (!util::is_integer((*string).substr(1))) {
throw argumentError(arg->name, "invalid selector");
}
return true;
}
}
if (arg->optional) {
return false;
} else {
throw typeError(arg->name, "selector", value);
}
}
bool typeCheck(Argument* arg, const dynamic::Value& value) {
switch (arg->type) {
case ArgType::enumvalue: {
if (auto* string = std::get_if<std::string>(&value)) {
auto& enumname = arg->enumname;
if (enumname.find("|"+*string+"|") == std::string::npos) {
throw error("argument "+util::quote(arg->name)+
": invalid enumeration value");
}
} else {
if (arg->optional) {
return false;
}
throw typeError(arg->name, "enumeration value", value);
}
break;
}
case ArgType::number:
if (!dynamic::is_numeric(value)) {
if (arg->optional) {
return false;
} else {
throw typeError(arg->name, "number", value);
}
}
break;
case ArgType::selector:
return selectorCheck(arg, value);
case ArgType::integer:
return typeCheck<integer_t>(arg, value, "integer");
case ArgType::string:
if (!std::holds_alternative<std::string>(value)) {
return !arg->optional;
}
break;
}
return true;
}
dynamic::Value fetchOrigin(CommandsInterpreter* interpreter, Argument* arg) {
if (dynamic::is_numeric(arg->origin)) {
return arg->origin;
} else if (auto string = std::get_if<std::string>(&arg->origin)) {
return (*interpreter)[*string];
}
return dynamic::NONE;
}
dynamic::Value applyRelative(
Argument* arg,
dynamic::Value value,
dynamic::Value origin
) {
if (origin.index() == 0) {
return value;
}
try {
if (arg->type == ArgType::number) {
return dynamic::get_number(origin) + dynamic::get_number(value);
} else {
return dynamic::get_integer(origin) + dynamic::get_integer(value);
}
} catch (std::runtime_error& err) {
throw argumentError(arg->name, err.what());
}
}
dynamic::Value parseRelativeValue(CommandsInterpreter* interpreter, Argument* arg) {
if (arg->type != ArgType::number && arg->type != ArgType::integer) {
throw error("'~' operator is only allowed for numeric arguments");
}
nextChar();
auto origin = fetchOrigin(interpreter, arg);
if (peekNoJump() == ' ' || !hasNext()) {
return origin;
}
auto value = parseValue();
if (origin.index() == 0) {
return value;
}
return applyRelative(arg, value, origin);
}
inline dynamic::Value performKeywordArg(
CommandsInterpreter* interpreter, Command* command, const std::string& key
) {
if (auto arg = command->getArgument(key)) {
nextChar();
auto value = peek() == '~'
? parseRelativeValue(interpreter, arg)
: parseValue();
typeCheck(arg, value);
return value;
} else {
throw error("unknown keyword "+util::quote(key));
}
}
Prompt parsePrompt(CommandsInterpreter* interpreter) {
auto repo = interpreter->getRepository();
std::string name = parseIdentifier(true);
auto command = repo->get(name);
if (command == nullptr) {
throw error("unknown command "+util::quote(name));
}
auto args = dynamic::create_list();
auto kwargs = dynamic::create_map();
int arg_index = 0;
while (hasNext()) {
bool relative = false;
dynamic::Value value = dynamic::NONE;
if (peek() == '~') {
relative = true;
value = 0L;
nextChar();
}
if (hasNext() && peekNoJump() != ' ') {
value = parseValue();
if (auto string = std::get_if<std::string>(&value)) {
if ((*string)[0] == '$') {
value = (*interpreter)[string->substr(1)];
}
}
// keyword argument
if (!relative && hasNext() && peek() == '=') {
auto key = std::get<std::string>(value);
kwargs->put(key, performKeywordArg(interpreter, command, key));
}
}
// positional argument
Argument* arg = nullptr;
do {
if (arg) {
if (auto string = std::get_if<std::string>(&arg->def)) {
if ((*string)[0] == '$') {
args->put((*interpreter)[string->substr(1)]);
} else {
args->put(arg->def);
}
} else {
args->put(arg->def);
}
}
arg = command->getArgument(arg_index++);
if (arg == nullptr) {
throw error("extra positional argument");
}
if (arg->origin.index() && relative) {
break;
}
} while (!typeCheck(arg, value));
if (relative) {
value = applyRelative(arg, value, fetchOrigin(interpreter, arg));
}
args->put(value);
}
while (auto arg = command->getArgument(arg_index++)) {
if (!arg->optional) {
throw error("missing argument "+util::quote(arg->name));
} else {
args->put(arg->def);
}
}
return Prompt {command, args, kwargs};
}
};
Command Command::create(std::string_view scheme, std::string_view description, executor_func executor) {
return CommandParser("<string>", scheme).parseScheme(executor, description);
}
void CommandsRepository::add(
std::string_view scheme,
std::string_view description,
executor_func executor
) {
Command command = Command::create(scheme, description, executor);
commands[command.getName()] = command;
}
Command* CommandsRepository::get(const std::string& name) {
auto found = commands.find(name);
if (found == commands.end()) {
return nullptr;
}
return &found->second;
}
Prompt CommandsInterpreter::parse(std::string_view text) {
return CommandParser("<string>", text).parsePrompt(this);
}

View File

@ -0,0 +1,159 @@
#ifndef LOGIC_COMMANDS_INTERPRETER_HPP_
#define LOGIC_COMMANDS_INTERPRETER_HPP_
#include "../data/dynamic.hpp"
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>
namespace cmd {
enum class ArgType {
number, integer, enumvalue, selector, string
};
inline std::string argtype_name(ArgType type) {
switch (type) {
case ArgType::number: return "number";
case ArgType::integer: return "integer";
case ArgType::enumvalue: return "enumeration";
case ArgType::selector: return "selector";
case ArgType::string: return "string";
default: return "<unknown>";
}
}
struct Argument {
std::string name;
ArgType type;
bool optional;
dynamic::Value def;
dynamic::Value origin;
std::string enumname;
};
class Command;
class CommandsInterpreter;
struct Prompt {
Command* command;
dynamic::List_sptr args; // positional arguments list
dynamic::Map_sptr kwargs; // keyword arguments table
};
using executor_func = std::function<dynamic::Value(
CommandsInterpreter*,
dynamic::List_sptr args,
dynamic::Map_sptr kwargs
)>;
class Command {
std::string name;
std::vector<Argument> args;
std::unordered_map<std::string, Argument> kwargs;
std::string description;
executor_func executor;
public:
Command() {}
Command(
std::string name,
std::vector<Argument> args,
std::unordered_map<std::string, Argument> kwargs,
std::string description,
executor_func executor
) : name(name),
args(std::move(args)),
kwargs(std::move(kwargs)),
description(description),
executor(executor) {}
Argument* getArgument(size_t index) {
if (index >= args.size())
return nullptr;
return &args[index];
}
Argument* getArgument(const std::string& keyword) {
auto found = kwargs.find(keyword);
if (found == kwargs.end()) {
return nullptr;
}
return &found->second;
}
dynamic::Value execute(CommandsInterpreter* interpreter, const Prompt& prompt) {
return executor(interpreter, prompt.args, prompt.kwargs);
}
const std::string& getName() const {
return name;
}
const std::vector<Argument>& getArgs() const {
return args;
}
const std::unordered_map<std::string, Argument>& getKwArgs() const {
return kwargs;
}
const std::string& getDescription() const {
return description;
}
static Command create(
std::string_view scheme,
std::string_view description,
executor_func
);
};
class CommandsRepository {
std::unordered_map<std::string, Command> commands;
public:
void add(
std::string_view scheme,
std::string_view description,
executor_func
);
Command* get(const std::string& name);
const std::unordered_map<std::string, Command> getCommands() const {
return commands;
}
};
class CommandsInterpreter {
std::unique_ptr<CommandsRepository> repository;
std::unordered_map<std::string, dynamic::Value> variables;
public:
CommandsInterpreter() : repository(std::make_unique<CommandsRepository>()) {}
CommandsInterpreter(const CommandsInterpreter&) = delete;
CommandsInterpreter(std::unique_ptr<CommandsRepository> repository)
: repository(std::move(repository)){}
Prompt parse(std::string_view text);
dynamic::Value execute(std::string_view input) {
return execute(parse(input));
}
dynamic::Value execute(const Prompt& prompt) {
return prompt.command->execute(this, prompt);
}
dynamic::Value& operator[](const std::string& name) {
return variables[name];
}
CommandsRepository* getRepository() const {
return repository.get();
}
};
}
#endif // LOGIC_COMMANDS_INTERPRETER_HPP_

View File

@ -12,6 +12,10 @@ inline std::string LAMBDAS_TABLE = "$L";
static debug::Logger logger("lua-state");
namespace scripting {
extern lua::LuaState* state;
}
lua::luaerror::luaerror(const std::string& message) : std::runtime_error(message) {
}
@ -123,19 +127,20 @@ void lua::LuaState::remove(const std::string& name) {
}
void lua::LuaState::createLibs() {
openlib("audio", audiolib, 0);
openlib("block", blocklib, 0);
openlib("core", corelib, 0);
openlib("file", filelib, 0);
openlib("gui", guilib, 0);
openlib("input", inputlib, 0);
openlib("inventory", inventorylib, 0);
openlib("item", itemlib, 0);
openlib("json", jsonlib, 0);
openlib("pack", packlib, 0);
openlib("player", playerlib, 0);
openlib("time", timelib, 0);
openlib("world", worldlib, 0);
openlib("audio", audiolib);
openlib("block", blocklib);
openlib("console", consolelib);
openlib("core", corelib);
openlib("file", filelib);
openlib("gui", guilib);
openlib("input", inputlib);
openlib("inventory", inventorylib);
openlib("item", itemlib);
openlib("json", jsonlib);
openlib("pack", packlib);
openlib("player", playerlib);
openlib("time", timelib);
openlib("world", worldlib);
addfunc("print", lua_wrap_errors<l_print>);
}
@ -149,8 +154,8 @@ void lua::LuaState::loadbuffer(int env, const std::string& src, const std::strin
}
}
int lua::LuaState::call(int argc) {
if (lua_pcall(L, argc, LUA_MULTRET, 0)) {
int lua::LuaState::call(int argc, int nresults) {
if (lua_pcall(L, argc, nresults, 0)) {
throw lua::luaerror(lua_tostring(L, -1));
}
return 1;
@ -165,7 +170,7 @@ int lua::LuaState::callNoThrow(int argc) {
}
int lua::LuaState::eval(int env, const std::string& src, const std::string& file) {
auto srcText = "return ("+src+")";
auto srcText = "return "+src;
loadbuffer(env, srcText, file);
return call(0);
}
@ -351,7 +356,7 @@ dynamic::Value lua::LuaState::tovalue(int idx) {
}
default:
throw std::runtime_error(
"lua type "+std::to_string(type)+" is not supported"
"lua type "+std::string(luaL_typename(L, type))+" is not supported"
);
}
}
@ -364,9 +369,9 @@ bool lua::LuaState::isfunction(int idx) {
return lua_isfunction(L, idx);
}
void lua::LuaState::openlib(const std::string& name, const luaL_Reg* libfuncs, int nup) {
void lua::LuaState::openlib(const std::string& name, const luaL_Reg* libfuncs) {
lua_newtable(L);
luaL_setfuncs(L, libfuncs, nup);
luaL_setfuncs(L, libfuncs, 0);
lua_setglobal(L, name.c_str());
}
@ -377,7 +382,7 @@ const std::string lua::LuaState::storeAnonymous() {
return funcName;
}
runnable lua::LuaState::createRunnable() {
std::shared_ptr<std::string> lua::LuaState::createLambdaHandler() {
auto ptr = reinterpret_cast<ptrdiff_t>(lua_topointer(L, -1));
auto name = util::mangleid(ptr);
lua_getglobal(L, LAMBDAS_TABLE.c_str());
@ -385,13 +390,17 @@ runnable lua::LuaState::createRunnable() {
lua_setfield(L, -2, name.c_str());
lua_pop(L, 2);
std::shared_ptr<std::string> funcptr(new std::string(name), [=](auto* name) {
return std::shared_ptr<std::string>(new std::string(name), [=](auto* name) {
lua_getglobal(L, LAMBDAS_TABLE.c_str());
lua_pushnil(L);
lua_setfield(L, -2, name->c_str());
lua_pop(L, 1);
delete name;
});
}
runnable lua::LuaState::createRunnable() {
auto funcptr = createLambdaHandler();
return [=]() {
lua_getglobal(L, LAMBDAS_TABLE.c_str());
lua_getfield(L, -1, funcptr->c_str());
@ -399,6 +408,23 @@ runnable lua::LuaState::createRunnable() {
};
}
scripting::common_func lua::LuaState::createLambda() {
auto funcptr = createLambdaHandler();
return [=](const std::vector<dynamic::Value>& args) {
lua_getglobal(L, LAMBDAS_TABLE.c_str());
lua_getfield(L, -1, funcptr->c_str());
for (const auto& arg : args) {
pushvalue(arg);
}
if (call(args.size(), 1)) {
auto result = tovalue(-1);
pop(1);
return result;
}
return dynamic::Value(dynamic::NONE);
};
}
int lua::LuaState::createEnvironment(int parent) {
int id = nextEnvironment++;

View File

@ -3,6 +3,7 @@
#include "lua_commons.hpp"
#include "../scripting_functional.hpp"
#include "../../../data/dynamic.hpp"
#include "../../../delegates.hpp"
@ -26,6 +27,8 @@ namespace lua {
void logError(const std::string& text);
void removeLibFuncs(const char* libname, const char* funcs[]);
void createLibs();
std::shared_ptr<std::string> createLambdaHandler();
public:
LuaState();
~LuaState();
@ -56,11 +59,11 @@ namespace lua {
const char* tostring(int idx);
bool isstring(int idx);
bool isfunction(int idx);
int call(int argc);
int call(int argc, int nresults=-1);
int callNoThrow(int argc);
int execute(int env, const std::string& src, const std::string& file="<string>");
int eval(int env, const std::string& src, const std::string& file="<eval>");
void openlib(const std::string& name, const luaL_Reg* libfuncs, int nup);
void openlib(const std::string& name, const luaL_Reg* libfuncs);
void addfunc(const std::string& name, lua_CFunction func);
bool getglobal(const std::string& name);
void setglobal(const std::string& name);
@ -68,6 +71,8 @@ namespace lua {
bool rename(const std::string& from, const std::string& to);
void remove(const std::string& name);;
runnable createRunnable();
scripting::common_func createLambda();
int createEnvironment(int parent);
void removeEnvironment(int id);
const std::string storeAnonymous();

View File

@ -20,6 +20,7 @@ extern const luaL_Reg timelib [];
extern const luaL_Reg worldlib [];
extern const luaL_Reg jsonlib [];
extern const luaL_Reg inputlib [];
extern const luaL_Reg consolelib [];
// Lua Overrides
extern int l_print(lua_State* L);

View File

@ -64,6 +64,9 @@ int l_set_block(lua_State* L) {
if (id < 0 || size_t(id) >= scripting::indices->countBlockDefs()) {
return 0;
}
if (!scripting::level->chunks->get(x, y, z)) {
return 0;
}
scripting::level->chunks->set(x, y, z, id, states);
scripting::level->lighting->onBlockSet(x,y,z, id);
if (!noupdate)

View File

@ -0,0 +1,120 @@
#include "api_lua.hpp"
#include "lua_commons.hpp"
#include "LuaState.hpp"
#include "../scripting.hpp"
#include "../../CommandsInterpreter.hpp"
#include "../../../engine.hpp"
#include "../../../coders/commons.hpp"
namespace scripting {
extern lua::LuaState* state;
}
using namespace scripting;
static int l_add_command(lua_State* L) {
if (!lua_isstring(L, 1) || !lua_isstring(L, 2) || !lua_isfunction(L, 3)) {
throw std::runtime_error("invalid argument type");
}
auto scheme = lua_tostring(L, 1);
auto description = lua_tostring(L, 2);
lua_pushvalue(L, 3);
auto func = state->createLambda();
try {
engine->getCommandsInterpreter()->getRepository()->add(
scheme, description, [func](auto, auto args, auto kwargs) {
return func({args, kwargs});
}
);
} catch (const parsing_error& err) {
throw std::runtime_error(("command scheme error:\n"+err.errorLog()).c_str());
}
return 0;
}
static int l_execute(lua_State* L) {
auto prompt = lua_tostring(L, 1);
auto result = engine->getCommandsInterpreter()->execute(prompt);
state->pushvalue(result);
return 1;
}
static int l_set(lua_State* L) {
auto name = lua_tostring(L, 1);
auto value = state->tovalue(2);
(*engine->getCommandsInterpreter())[name] = value;
return 0;
}
static int l_get_commands_list(lua_State* L) {
auto interpreter = engine->getCommandsInterpreter();
auto repo = interpreter->getRepository();
const auto& commands = repo->getCommands();
lua_createtable(L, commands.size(), 0);
size_t index = 1;
for (const auto& entry : commands) {
lua_pushstring(L, entry.first.c_str());
lua_rawseti(L, -2, index++);
}
return 1;
}
static int l_get_command_info(lua_State* L) {
auto name = lua_tostring(L, 1);
auto interpreter = engine->getCommandsInterpreter();
auto repo = interpreter->getRepository();
auto command = repo->get(name);
const auto& args = command->getArgs();
const auto& kwargs = command->getKwArgs();
lua_createtable(L, 0, 4);
lua_pushstring(L, name);
lua_setfield(L, -2, "name");
lua_pushstring(L, command->getDescription().c_str());
lua_setfield(L, -2, "description");
lua_createtable(L, args.size(), 0);
for (size_t i = 0; i < args.size(); i++) {
auto& arg = args.at(i);
lua_createtable(L, 0, 2);
lua_pushstring(L, arg.name.c_str());
lua_setfield(L, -2, "name");
lua_pushstring(L, cmd::argtype_name(arg.type).c_str());
lua_setfield(L, -2, "type");
if (arg.optional) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "optional");
}
lua_rawseti(L, -2, i+1);
}
lua_setfield(L, -2, "args");
lua_createtable(L, 0, kwargs.size());
for (auto& entry : kwargs) {
auto& arg = entry.second;
lua_createtable(L, 0, 1);
lua_pushstring(L, cmd::argtype_name(arg.type).c_str());
lua_setfield(L, -2, "type");
lua_setfield(L, -2, arg.name.c_str());
}
lua_setfield(L, -2, "kwargs");
return 1;
}
const luaL_Reg consolelib [] = {
{"add_command", lua_wrap_errors<l_add_command>},
{"execute", lua_wrap_errors<l_execute>},
{"set", lua_wrap_errors<l_set>},
{"get_commands_list", lua_wrap_errors<l_get_commands_list>},
{"get_command_info", lua_wrap_errors<l_get_command_info>},
{NULL, NULL}
};

View File

@ -70,6 +70,14 @@ static int l_menu_reset(lua_State* L) {
return 0;
}
static int l_textbox_paste(lua_State* L) {
auto node = getDocumentNode(L);
auto box = dynamic_cast<TextBox*>(node.node.get());
auto text = lua_tostring(L, 2);
box->paste(util::str2wstr_utf8(text));
return 0;
}
static int l_container_add(lua_State* L) {
auto docnode = getDocumentNode(L);
auto node = dynamic_cast<Container*>(docnode.node.get());
@ -121,6 +129,13 @@ static int p_get_back(UINode* node) {
return 0;
}
static int p_get_paste(UINode* node) {
if (dynamic_cast<TextBox*>(node)) {
return state->pushcfunction(l_textbox_paste);
}
return 0;
}
static int p_get_page(UINode* node) {
if (auto menu = dynamic_cast<Menu*>(node)) {
return state->pushstring(menu->getCurrent().name);
@ -186,6 +201,13 @@ static int p_is_valid(UINode* node) {
return 0;
}
static int p_get_caret(UINode* node) {
if (auto box = dynamic_cast<TextBox*>(node)) {
return state->pushinteger(static_cast<integer_t>(box->getCaret()));
}
return 0;
}
static int p_get_placeholder(UINode* node) {
if (auto box = dynamic_cast<TextBox*>(node)) {
return state->pushstring(util::wstr2str_utf8(box->getPlaceholder()));
@ -277,6 +299,7 @@ static int l_gui_getattr(lua_State* L) {
{"clear", p_get_clear},
{"placeholder", p_get_placeholder},
{"valid", p_is_valid},
{"caret", p_get_caret},
{"text", p_get_text},
{"editable", p_get_editable},
{"value", p_get_value},
@ -289,6 +312,7 @@ static int l_gui_getattr(lua_State* L) {
{"page", p_get_page},
{"back", p_get_back},
{"reset", p_get_reset},
{"paste", p_get_paste},
{"inventory", p_get_inventory},
{"focused", p_get_focused},
};
@ -337,6 +361,11 @@ static void p_set_text(UINode* node, int idx) {
box->setText(util::str2wstr_utf8(state->tostring(idx)));
}
}
static void p_set_caret(UINode* node, int idx) {
if (auto box = dynamic_cast<TextBox*>(node)) {
box->setCaret(static_cast<ssize_t>(state->tointeger(idx)));
}
}
static void p_set_editable(UINode* node, int idx) {
if (auto box = dynamic_cast<TextBox*>(node)) {
box->setEditable(state->toboolean(idx));
@ -422,6 +451,7 @@ static int l_gui_setattr(lua_State* L) {
{"placeholder", p_set_placeholder},
{"text", p_set_text},
{"editable", p_set_editable},
{"caret", p_set_caret},
{"value", p_set_value},
{"min", p_set_min},
{"max", p_set_max},

View File

@ -10,6 +10,7 @@
#include "../../../graphics/ui/elements/InventoryView.hpp"
#include "../../../items/Inventories.hpp"
#include "../../../logic/BlocksController.hpp"
#include "../../../objects/Player.hpp"
#include "../../../util/stringutil.hpp"
#include "../../../voxels/Block.hpp"
#include "../../../voxels/Chunks.hpp"
@ -121,6 +122,12 @@ static int l_hud_get_block_inventory(lua_State* L) {
return 1;
}
static int l_hud_get_player(lua_State* L) {
auto player = scripting::hud->getPlayer();
lua_pushinteger(L, player->getId());
return 1;
}
const luaL_Reg hudlib [] = {
{"open_inventory", lua_wrap_errors<l_hud_open_inventory>},
{"close_inventory", lua_wrap_errors<l_hud_close_inventory>},
@ -131,5 +138,6 @@ const luaL_Reg hudlib [] = {
{"close", lua_wrap_errors<l_hud_close>},
{"pause", lua_wrap_errors<l_hud_pause>},
{"resume", lua_wrap_errors<l_hud_resume>},
{"get_player", lua_wrap_errors<l_hud_get_player>},
{NULL, NULL}
};

View File

@ -7,6 +7,7 @@
#else
#include <lua.hpp>
#endif
#include <string>
#include <exception>
namespace lua {

View File

@ -51,6 +51,7 @@ void scripting::initialize(Engine* engine) {
state = new lua::LuaState();
load_script(fs::path("stdlib.lua"));
load_script(fs::path("stdcmd.lua"));
}
scriptenv scripting::get_root_environment() {

View File

@ -3,10 +3,16 @@
#include "../../typedefs.hpp"
#include "../../delegates.hpp"
#include "../../data/dynamic.hpp"
#include <glm/glm.hpp>
#include <string>
#include <string>
namespace scripting {
using common_func = std::function<dynamic::Value(const std::vector<dynamic::Value>&)>;
runnable create_runnable(
const scriptenv& env,
const std::string& src,

View File

@ -20,7 +20,7 @@ Hud* scripting::hud = nullptr;
void scripting::on_frontend_init(Hud* hud) {
scripting::hud = hud;
scripting::state->openlib("hud", hudlib, 0);
scripting::state->openlib("hud", hudlib);
for (auto& pack : scripting::engine->getContentPacks()) {
state->emit_event(pack.id + ".hudopen", [&] (lua::LuaState* state) {

View File

@ -151,7 +151,7 @@ std::wstring util::str2wstr_utf8(const std::string s) {
return std::wstring(chars.data(), chars.size());
}
bool util::is_integer(std::string text) {
bool util::is_integer(const std::string& text) {
for (char c : text) {
if (c < '0' || c > '9')
return false;
@ -159,7 +159,7 @@ bool util::is_integer(std::string text) {
return true;
}
bool util::is_integer(std::wstring text) {
bool util::is_integer(const std::wstring& text) {
for (wchar_t c : text) {
if (c < L'0' || c > L'9')
return false;

View File

@ -20,8 +20,8 @@ namespace util {
uint32_t decode_utf8(uint& size, const char* bytes);
std::string wstr2str_utf8(const std::wstring ws);
std::wstring str2wstr_utf8(const std::string s);
bool is_integer(std::string text);
bool is_integer(std::wstring text);
bool is_integer(const std::string& text);
bool is_integer(const std::wstring& text);
bool is_valid_filename(std::wstring name);
void ltrim(std::string &s);