Merge pull request #372 from MihailRis/console-tabs

Console Tabs
This commit is contained in:
MihailRis 2024-11-19 08:41:44 +03:00 committed by GitHub
commit 6736613dc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 827 additions and 171 deletions

View File

@ -27,4 +27,7 @@ utf8.upper(text: str) -> str
-- Converts a string to lowercase
utf8.lower(text: str) -> str
-- Escapes a string
utf8.escape(text: str) -> str
```

View File

@ -78,14 +78,18 @@ Properties:
| caret | int | yes | yes | carriage position. `textbox.caret = -1` will set the position to the end of the text |
| editable | bool | yes | yes | text mutability |
| multiline | bool | yes | yes | multiline support |
| lineNumbers | bool | yes | yes | display line numbers |
| textWrap | bool | yes | yes | automatic text wrapping (only with multiline: "true") |
| valid | bool | yes | no | is the entered text correct |
| textColor | vec4 | yes | yes | text color |
Methods:
| Method | Description |
| ----------- | ------------------------------------------------ |
| paste(text) | inserts the specified text at the caret position |
| Method | Description |
| ------------------------- | ---------------------------------------------------------------- |
| paste(text: str) | inserts the specified text at the caret position |
| lineAt(pos: int) -> int | determines the line number by position in the text |
| linePos(line: int) -> int | determines the position of the beginning of the line in the text |
## Slider (trackbar)

View File

@ -56,7 +56,8 @@ Buttons and panels are also containers.
- `padding` - element padding. Type: 4D vector.
*left, top, right, bottom*
`scrollable` - element scrollability. Works on panels only. Type: boolean
- `scrollable` - element scrollability. Type: boolean.
- `scroll-step` - scrolling step. Type: integer.
# Common *panel* attributes
@ -104,7 +105,9 @@ Inner text - initially entered text
- `multiline` - allows display of multiline text.
- `text-wrap` - allows automatic text wrapping (works only with multiline: "true")
- `editable` - determines whether the text can be edited.
- `line-numbers` - enables line numbers display.
- `error-color` - color when entering incorrect data (the text does not pass the validator check). Type: RGBA color.
- `text-color` - text color. Type: RGBA color.
- `validator` - lua function that checks text for correctness. Takes a string as input, returns true if the text is correct.
- `onup` - lua function called when the up arrow is pressed.
- `ondown` - lua function called when the down arrow is pressed.

View File

@ -27,4 +27,7 @@ utf8.upper(text: str) -> str
-- Переводит строку в нижний регистр
utf8.lower(text: str) -> str
-- Экранирует строку
utf8.escape(text: str) -> str
```

View File

@ -4,92 +4,109 @@
## Расширения для table
Создаёт и возвращает копию переданной таблицы путём создания новой и копирования в неё всех элементов из переданной
```lua
function table.copy(t: table) -> table
table.copy(t: table) -> table
```
Возвращает количество пар в переданной таблице
Создаёт и возвращает копию переданной таблицы путём создания новой и копирования в неё всех элементов из переданной.
```lua
function table.count_pairs(t: table) -> integer
table.count_pairs(t: table) -> integer
```
Возвращает один элемент из переданной таблицы на случайной позиции
Возвращает количество пар в переданной таблице.
```lua
function table.random(t: table) -> object
table.random(t: table) -> object
```
Возвращает **true**, если **x** содержится в **t**
Возвращает один элемент из переданной таблицы на случайной позиции.
```lua
function table.has(t: table, x: object) -> bool
table.has(t: table, x: object) -> bool
```
Возвращает индекс обьекта **x** в **t**. Если переданный обьект не содержится в таблице, то функция вернёт значение **-1**
Возвращает **true**, если **x** содержится в **t**.
```lua
function table.index(t: table, x: object) -> integer
table.index(t: table, x: object) -> integer
```
Удаляет элемент **x** из **t**
Возвращает индекс обьекта **x** в **t**. Если переданный обьект не содержится в таблице, то функция вернёт значение **-1**.
```lua
function table.remove_value(t: table, x: object)
table.remove_value(t: table, x: object)
```
Конвертирует переданную таблицу в строку
Удаляет элемент **x** из **t**.
```lua
function table.tostring(t: table) -> string
table.tostring(t: table) -> string
```
Конвертирует переданную таблицу в строку.
## Расширения для string
Разбивает строку **str** на части по указанному разделителю/выражению **separator** и возвращает результат ввиде таблицы из строк. Если **withpattern** равен **true**, то параметр **separator** будет определяться как регулярное выражение
```lua
function string.explode(separator: string, str: string, withpattern: bool) -> table[string]
string.explode(separator: string, str: string, withpattern: bool) -> table[string]
```
Разбивает строку **str** на части по указанному разделителю **delimiter** и возвращает результат ввиде таблицы из строк
Разбивает строку **str** на части по указанному разделителю/выражению **separator** и возвращает результат ввиде таблицы из строк. Если **withpattern** равен **true**, то параметр **separator** будет определяться как регулярное выражение.
```lua
function string.split(str: string, delimiter: string) -> table[string]
string.split(str: string, delimiter: string) -> table[string]
```
Экранирует специальные символы в строке, такие как `()[]+-.$%^?*` в формате `%символ`. Символ `NUL` (`\0`) будет преобразован в `%z`
Разбивает строку **str** на части по указанному разделителю **delimiter** и возвращает результат ввиде таблицы из строк.
```lua
function string.pattern_safe(str: string)
string.pattern_safe(str: string)
```
Разбивает секунды на часы, минуты и миллисекунды и форматирует в **format** с следующим порядком параметров: `минуты, секунды, миллисекунды` и после возвращает результат. Если **format** не указан, то возвращает таблицу, где: **h** - hours, **m** - minutes, **s** - seconds, **ms** - milliseconds
Экранирует специальные символы в строке, такие как `()[]+-.$%^?*` в формате `%символ`. Символ `NUL` (`\0`) будет преобразован в `%z`.
```lua
function string.formatted_time(seconds: number, format: string) -> string | table
string.formatted_time(seconds: number, format: string) -> string | table
```
Заменяет все подстроки в **str**, равные **tofind** на **toreplace** и возвращает строку со всеми измененными подстроками
Разбивает секунды на часы, минуты и миллисекунды и форматирует в **format** с следующим порядком параметров: `минуты, секунды, миллисекунды` и после возвращает результат. Если **format** не указан, то возвращает таблицу, где: **h** - hours, **m** - minutes, **s** - seconds, **ms** - milliseconds.
```lua
function string.replace(str: string, tofind: string, toreplace: string) -> string
string.replace(str: string, tofind: string, toreplace: string) -> string
```
Заменяет все подстроки в **str**, равные **tofind** на **toreplace** и возвращает строку со всеми измененными подстроками.
```lua
string.trim(str: string, char: string) -> string
```
Удаляет все символы, равные **char** из строки **str** с левого и правого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы.
```lua
function string.trim(str: string, char: string) -> string
string.trim_left(str: string, char: string) -> string
```
Удаляет все символы, равные **char** из строки **str** с левого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы.
```lua
function string.trim_left(str: string, char: string) -> string
string.trim_right(str: string, char: string) -> string
```
Удаляет все символы, равные **char** из строки **str** с правого конца и возвращает результат. Если параметр **char** не определен, то будут выбраны все пустые символы.
```lua
function string.trim_right(str: string, char: string) -> string
string.starts_with(str: string, start: string) -> bool
```
Возвращает **true**, если строка **str** начинается на подстроку **start**
```lua
function string.starts_with(str: string, start: string) -> bool
string.ends_with(str: string, endStr: string) -> bool
```
Возвращает **true**, если строка **str** заканчивается на подстроку **endStr**
```lua
function string.ends_with(str: string, endStr: string) -> bool
```
Также важно подметить, что все выше перечисленные функции, расширяющие **string** можно использовать как мета-методы на экземплярах строк, т.е.:
@ -103,39 +120,51 @@ end
Также функции `string.lower` и `string.upper` переопределены на `utf8.lower` и `utf8.upper`
```lua
string.escape(str: string) -> string
```
Экранирует строку. Является псевдонимом `utf8.escape`.
## Расширения для math
Ограничивает число **_in** по лимитам **low** и **high**. Т.е.: Если **_in** больше чем **high** - вернётся **high**, если **_in** меньше чем **low** - вернётся **low**. В противном случае вернётся само число
```lua
function math.clamp(_in, low, high)
math.clamp(_in, low, high)
```
Возвращает случайное дробное число в диапазоне от **low** до **high**
Ограничивает число **_in** по лимитам **low** и **high**. Т.е.: Если **_in** больше чем **high** - вернётся **high**, если **_in** меньше чем **low** - вернётся **low**. В противном случае вернётся само число.
```lua
function math.rand(low, high)
math.rand(low, high)
```
Возвращает случайное дробное число в диапазоне от **low** до **high**.
## Дополнительные глобальные функции
В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список
Возвращает **true**, если переданная таблица является массивом, тоесть если каждый ключ это целое число больше или равное единице и если каждый ключ следует за прошлым
```lua
function is_array(x: table) -> bool
is_array(x: table) -> bool
```
Разбивает путь на две части и возвращает их: входную точку и путь к файлу
Возвращает **true**, если переданная таблица является массивом, тоесть если каждый ключ это целое число больше или равное единице и если каждый ключ следует за прошлым.
```lua
function parse_path(path: string) -> string, string
```
Вызывает функцию **func** **iters** раз, передавая ей аргументы `...`, а после выводит в консоль время в микросекундах, которое прошло с момента вызова **timeit**
Разбивает путь на две части и возвращает их: входную точку и путь к файлу.
```lua
function timeit(iters: integer, func: func, ...)
```
Вызывает остановку корутины до тех пор, пока не пройдёт количество секунд, указанное в **timesec**. Функция может быть использована только внутри корутины
Вызывает функцию **func** **iters** раз, передавая ей аргументы `...`, а после выводит в консоль время в микросекундах, которое прошло с момента вызова **timeit**.
```lua
function sleep(timesec: number)
```
```
Вызывает остановку корутины до тех пор, пока не пройдёт количество секунд, указанное в **timesec**. Функция может быть использована только внутри корутины.

View File

@ -78,14 +78,18 @@ document["worlds-panel"]:clear()
| caret | int | да | да | позиция каретки. `textbox.caret = -1` установит позицию в конец текста |
| editable | bool | да | да | изменяемость текста |
| multiline | bool | да | да | поддержка многострочности |
| lineNumbers | bool | да | да | отображение номеров строк |
| textWrap | bool | да | да | автоматический перенос текста (только при multiline: "true") |
| valid | bool | да | нет | является ли введенный текст корректным |
| textColor | vec4 | да | да | цвет текста |
Методы:
| Метод | Описание |
| ----------- | -------------------------------------------- |
| paste(text) | вставляет указанный текст на позицию каретки |
| Метод | Описание |
| ------------------------- | -------------------------------------------- |
| paste(text: str) | вставляет указанный текст на позицию каретки |
| lineAt(pos: int) -> int | определяет номер строки по позиции в тексте |
| linePos(line: int) -> int | определяет позицию начала строки в тексте |
## Ползунок (trackbar)

View File

@ -59,7 +59,8 @@
В число контейнеров также входят панели и кнопки.
- `padding` - внутренний отступ элемента. Тип: 4D вектор.
Порядок: `"left,top,right,bottom"`
- `scrollable` - возможность скроллинга. Работает только у Panel. Тип: логический.
- `scrollable` - возможность скроллинга. Тип: логический.
- `scroll-step` - шаг скроллинга. Тип: целочисленный.
# Общие атрибуты панелей
@ -105,7 +106,9 @@
- `multiline` - разрешает отображение многострочного текста.
- `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true")
- `editable`- определяет возможность редактирования текста.
- `line-numbers` - включает отображение номеров строк.
- `error-color` - цвет при вводе некорректных данных (текст не проходит проверку валидатора). Тип: RGBA цвет.
- `text-color` - цвет текста. Тип: RGBA цвет.
- `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен.
- `onup` - lua функция вызываемая при нажатии стрелки вверх.
- `ondown` - lua функция вызываемая при нажатии стрелки вниз.

View File

@ -1,5 +1,18 @@
<container color='#00000080' size='400' size-func="unpack(gui.get_viewport())">
<container size-func="gui.get_viewport()[1],gui.get_viewport()[2]-40">
<panel interval="0"
orientation="horizontal"
color="#00000010"
size-func="gui.get_viewport()[1]-350,30">
<button id="s_chat" size="110,30" onclick="modes:set('chat')">@Chat</button>
<button id="s_console" size="110,30" onclick="modes:set('console')">@Console</button>
<button id="s_debug" size="110,30" onclick="modes:set('debug')">@Debug</button>
</panel>
<container pos="0,30" size-func="gui.get_viewport()[1]-350,30" color="#00000020">
<label id="title" pos="8,8"></label>
</container>
<container id="logContainer" pos="0,60"
size-func="unpack(vec2.add(gui.get_viewport(), {0,-100}))">
<textbox
id='log'
color='0'
@ -11,12 +24,33 @@
gravity="bottom-left"
></textbox>
</container>
<container id="editorContainer" pos="0,60" color="#00000080"
size-func="unpack(vec2.add(gui.get_viewport(), {-350,-230}))">
<textbox
id='editor'
color='0'
autoresize='true'
margin='0'
padding='5'
editable='false'
multiline='true'
line-numbers='true'
text-color="#FFFFFFA0"
size-func="gui.get_viewport()[1]-350,40"
gravity="top-left"
text-wrap='false'
scroll-step='50'
></textbox>
</container>
<panel id="traceback" gravity="bottom-left" padding="4" color="#000000A0"
max-length="170" size-func="gui.get_viewport()[1]-350,170">
</panel>
<panel id="problemsLog"
color="#00000010"
position-func="gui.get_viewport()[1]-350,0"
size-func="351,gui.get_viewport()[2]-40"
size-func="350,gui.get_viewport()[2]-40"
padding="5,15,5,15">
<label>@Problems</label>
<label margin="0,0,0,5">@Problems</label>
</panel>
<textbox id='prompt'
consumer='submit'

View File

@ -1,21 +1,101 @@
console_mode = "console"
history = session.get_entry("commands_history")
history_pointer = #history
local warnings_all = {}
local errors_all = {}
local warning_id = 0
events.on("core:warning", function (wtype, text)
local error_id = 0
events.on("core:warning", function (wtype, text, traceback)
local full = wtype..": "..text
if table.has(warnings_all, full) then
return
end
local encoded = base64.encode(bjson.tobytes({frames=traceback}))
document.problemsLog:add(gui.template("problem", {
type="warning", text=full, id=tostring(warning_id)
type="warning",
text=full,
traceback=encoded,
id=tostring(warning_id)
}))
warning_id = warning_id + 1
table.insert(warnings_all, full)
end)
events.on("core:error", function (msg, traceback)
local _, endindex = string.find(msg, ": ")
local full = ""
for i,frame in ipairs(traceback) do
full = full..frame.source..tostring(frame.currentline)
end
if table.has(errors_all, full) then
return
end
local encoded = base64.encode(bjson.tobytes({frames=traceback}))
document.problemsLog:add(gui.template("problem", {
type="error",
text=msg:sub(endindex),
traceback=encoded,
id=tostring(error_id)
}))
error_id = error_id + 1
table.insert(errors_all, full)
end)
events.on("core:open_traceback", function(traceback_b64)
local traceback = bjson.frombytes(base64.decode(traceback_b64))
modes:set('debug')
local tb_list = document.traceback
local srcsize = tb_list.size
tb_list:clear()
tb_list:add("<label enabled='false' margin='2'>@devtools.traceback</label>")
for _, frame in ipairs(traceback.frames) do
local callback = ""
local framestr = ""
if frame.what == "C" then
framestr = "C/C++ "
else
framestr = frame.source..":"..tostring(frame.currentline).." "
if file.exists(frame.source) then
callback = string.format(
"local editor = document.editor "..
"local source = file.read('%s') "..
"editor.text = source "..
"editor.focused = true "..
"time.post_runnable(function()"..
"editor.caret = editor:linePos(%s) "..
"end)",
frame.source, frame.currentline-1
)
else
callback = "document.editor.text = 'Could not open source file'"
end
callback = string.format(
"%s document.title.text = gui.str('File')..' - %s'",
callback,
frame.source
)
end
if frame.name then
framestr = framestr.."("..tostring(frame.name)..")"
end
local color = "#FFFFFF"
if frame.source:starts_with("core:") then
color = "#C0D0C5"
end
tb_list:add(gui.template("stack_frame", {
location=framestr,
color=color,
callback=callback
}))
end
tb_list.size = srcsize
end)
function setup_variables()
local pid = hud.get_player()
local x,y,z = player.get_pos(pid)
@ -56,10 +136,19 @@ function add_to_history(text)
end
function submit(text)
text = text:trim()
add_to_history(text)
if console_mode == "chat" then
if not text:starts_with("/") then
text = "chat "..string.escape(text)
else
text = text:sub(2)
end
end
setup_variables()
text = text:trim()
local name
for s in text:gmatch("%S+") do
name = s
@ -84,6 +173,38 @@ function submit(text)
document.prompt.focused = true
end
function on_open()
document.prompt.focused = true
function set_mode(mode)
local show_prompt = mode == 'chat' or mode == 'console'
document.title.text = ""
document.editorContainer.visible = mode == 'debug'
document.logContainer.visible = mode ~= 'debug'
if mode == 'debug' then
document.root.color = {16, 18, 20, 220}
else
document.root.color = {0, 0, 0, 128}
end
document.traceback.visible = mode == 'debug'
document.prompt.visible = show_prompt
if show_prompt then
document.prompt.focused = true
end
console_mode = mode
end
function on_open(mode)
if modes == nil then
modes = RadioGroup({
chat=document.s_chat,
console=document.s_console,
debug=document.s_debug
}, function (mode)
set_mode(mode)
end, "console")
end
if mode then
modes:set(mode)
end
end

View File

@ -1,9 +1,9 @@
<container size='668,418' color='#0F1E2DB2' context='menu'>
<panel pos='6' size='250' color='0' interval='1'>
<button id='s_aud' onclick='set_page("s_aud", "settings_audio")'>@Audio</button>
<button id='s_dsp' onclick='set_page("s_dsp", "settings_display")'>@Display</button>
<button id='s_gfx' onclick='set_page("s_gfx", "settings_graphics")'>@Graphics</button>
<button id='s_ctl' onclick='set_page("s_ctl", "settings_controls")'>@Controls</button>
<button id='s_aud' onclick='sections:set("audio")'>@Audio</button>
<button id='s_dsp' onclick='sections:set("display")'>@Display</button>
<button id='s_gfx' onclick='sections:set("graphics")'>@Graphics</button>
<button id='s_ctl' onclick='sections:set("controls")'>@Controls</button>
</panel>
<pagebox id='menu' pos='260,6' size='400'>
</pagebox>
@ -11,7 +11,7 @@
<panel margin='6' gravity='bottom-left' size='250' color='0' interval='1'>
<button onclick='menu.page="languages"' id='langs_btn'>-</button>
<button onclick='core.open_folder("user:")'>@Open data folder</button>
<button id='s_rst' onclick='set_page("s_rst", "settings_reset")'>@Reset settings</button>
<button id='s_rst' onclick='sections:set("reset")'>@Reset settings</button>
<button onclick='menu:back()'>@Back</button>
</panel>
</container>

View File

@ -3,15 +3,13 @@ function on_open()
"%s: %s", gui.str("Language", "settings"),
gui.get_locales_info()[core.get_setting("ui.language")].name
)
set_page("s_gfx", "settings_graphics")
end
function set_page(btn, page)
document.s_aud.enabled = true
document.s_dsp.enabled = true
document.s_gfx.enabled = true
document.s_ctl.enabled = true
document.s_rst.enabled = true
document[btn].enabled = false
document.menu.page = page
sections = RadioGroup({
audio=document.s_aud,
display=document.s_dsp,
graphics=document.s_gfx,
controls=document.s_ctl,
reset=document.s_rst
}, function (page)
document.menu.page = "settings_"..page
end, "graphics")
end

View File

@ -1,4 +1,5 @@
<container id="%{id}" size="32" tooltip="%{text}">
<container id="%{id}" size="32" tooltip="%{text}"
onclick="events.emit('core:open_traceback', '%{traceback}')">
<image src="gui/%{type}" size="32"/>
<label pos="36,2">%{text}</label>
<image src="gui/cross" interactive="true" size="16" gravity="top-right"

View File

@ -0,0 +1,3 @@
<label hover-color="#A0A0FF" interactive="true" onclick="%{callback}" color="%{color}">
%{location}
</label>

View File

@ -256,6 +256,14 @@ console.add_command(
end
)
console.add_command(
"chat text:str",
"Send chat message",
function (args, kwargs)
console.log("[you] "..args[1])
end
)
console.cheats = {
"blocks.fill",
"tp",

View File

@ -84,6 +84,32 @@ function Document.new(docname)
})
end
local _RadioGroup = {}
function _RadioGroup.set(self, key)
if type(self) ~= 'table' then
error("called as non-OOP via '.', use radiogroup:set")
end
if self.current then
self.elements[self.current].enabled = true
end
self.elements[key].enabled = false
self.current = key
if self.callback then
self.callback(key)
end
end
function _RadioGroup.__call(self, elements, onset, default)
local group = setmetatable({
elements=elements,
callback=onset,
current=nil
}, {__index=_RadioGroup})
group:set(default)
return group
end
setmetatable(_RadioGroup, _RadioGroup)
RadioGroup = _RadioGroup
_GUI_ROOT = Document.new("core:root")
_MENU = _GUI_ROOT.menu
menu = _MENU

View File

@ -162,6 +162,7 @@ end
string.lower = utf8.lower
string.upper = utf8.upper
string.escape = utf8.escape
local meta = getmetatable("")
@ -227,8 +228,22 @@ function file.readlines(path)
return lines
end
function debug.get_traceback(start)
local frames = {}
local n = 2 + (start or 0)
while true do
local info = debug.getinfo(n)
if info then
table.insert(frames, info)
else
return frames
end
n = n + 1
end
end
package = {
loaded={}
loaded = {}
}
local __cached_scripts = {}
local __warnings_hidden = {}
@ -238,7 +253,7 @@ function on_deprecated_call(name, alternatives)
return
end
__warnings_hidden[name] = true
events.emit("core:warning", "deprecated call", name)
events.emit("core:warning", "deprecated call", name, debug.get_traceback(2))
if alternatives then
debug.warning("deprecated function called ("..name.."), use "..
alternatives.." instead\n"..debug.traceback())
@ -292,3 +307,10 @@ function __scripts_cleanup()
end
end
end
function __vc__error(msg, frame)
if events then
events.emit("core:error", msg, debug.get_traceback(1))
end
return debug.traceback(msg, frame)
end

View File

@ -11,6 +11,8 @@ world.delete-confirm=Do you want to delete world forever?
world.generators.default=Default
world.generators.flat=Flat
devtools.traceback=Traceback (most recent call first)
# Tooltips
graphics.gamma.tooltip=Lighting brightness curve
graphics.backlight.tooltip=Backlight to prevent total darkness

View File

@ -12,7 +12,15 @@ Dependencies=Зависимости
Description=Описание
Converting world...=Выполняется конвертация мира...
Unlimited=Неограниченно
Chat=Чат
Console=Консоль
Log=Лог
Problems=Проблемы
Monitor=Мониторинг
Debug=Отладка
File=Файл
devtools.traceback=Стек вызовов (от последнего)
error.pack-not-found=Не удалось найти пакет
error.dependency-not-found=Используемая зависимость не найдена
pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)?

View File

@ -172,7 +172,9 @@ assetload::postfunc assetload::layout(
return [=](auto assets) {
try {
auto cfg = std::dynamic_pointer_cast<LayoutCfg>(config);
assets->store(UiDocument::read(cfg->env, name, file), name);
assets->store(
UiDocument::read(cfg->env, name, file, "abs:" + file), name
);
} catch (const parsing_error& err) {
throw std::runtime_error(
"failed to parse layout XML '" + file + "':\n" + err.errorLog()

View File

@ -373,7 +373,7 @@ std::string BasicParser::parseString(char quote, bool closeRequired) {
case 'b': ss << '\b'; break;
case 't': ss << '\t'; break;
case 'f': ss << '\f'; break;
case '\'': ss << '\\'; break;
case '\'': ss << '\''; break;
case '"': ss << '"'; break;
case '\\': ss << '\\'; break;
case '/': ss << '/'; break;

View File

@ -250,7 +250,8 @@ std::string Parser::parseText() {
}
nextChar();
}
return std::string(source.substr(start, pos - start));
return Parser("<string>", std::string(source.substr(start, pos - start)))
.parseString('\0', false);
}
inline bool is_xml_identifier_start(char c) {
@ -336,7 +337,7 @@ xmldocument Parser::parse() {
return document;
}
xmldocument xml::parse(const std::string& filename, const std::string& source) {
xmldocument xml::parse(std::string_view filename, std::string_view source) {
Parser parser(filename, source);
return parser.parse();
}

View File

@ -140,6 +140,6 @@ namespace xml {
/// @param source xml source code string
/// @return xml document
extern xmldocument parse(
const std::string& filename, const std::string& source
std::string_view filename, std::string_view source
);
}

View File

@ -485,7 +485,13 @@ void ContentLoader::loadBlock(
auto scriptfile = folder / fs::path("scripts/" + def.scriptName + ".lua");
if (fs::is_regular_file(scriptfile)) {
scripting::load_block_script(env, full, scriptfile, def.rt.funcsset);
scripting::load_block_script(
env,
full,
scriptfile,
pack->id + ":scripts/" + def.scriptName + ".lua",
def.rt.funcsset
);
}
if (!def.hidden) {
auto& item = builder.items.create(full + BLOCK_ITEM_SUFFIX);
@ -511,7 +517,13 @@ void ContentLoader::loadItem(
auto scriptfile = folder / fs::path("scripts/" + def.scriptName + ".lua");
if (fs::is_regular_file(scriptfile)) {
scripting::load_item_script(env, full, scriptfile, def.rt.funcsset);
scripting::load_item_script(
env,
full,
scriptfile,
pack->id + ":scripts/" + def.scriptName + ".lua",
def.rt.funcsset
);
}
}
@ -720,7 +732,11 @@ void ContentLoader::load() {
fs::path scriptFile = folder / fs::path("scripts/world.lua");
if (fs::is_regular_file(scriptFile)) {
scripting::load_world_script(
env, pack->id, scriptFile, runtime->worldfuncsset
env,
pack->id,
scriptFile,
pack->id + ":scripts/world.lua",
runtime->worldfuncsset
);
}
@ -795,7 +811,11 @@ void ContentLoader::load() {
fs::path componentsDir = folder / fs::u8path("scripts/components");
foreach_file(componentsDir, [this](const fs::path& file) {
auto name = pack->id + ":" + file.stem().u8string();
scripting::load_entity_component(name, file);
scripting::load_entity_component(
name,
file,
pack->id + ":scripts/components/" + file.filename().u8string()
);
});
// Process content.json and load defined content units

View File

@ -53,7 +53,12 @@ scriptenv UiDocument::getEnvironment() const {
return env;
}
std::unique_ptr<UiDocument> UiDocument::read(const scriptenv& penv, const std::string& name, const fs::path& file) {
std::unique_ptr<UiDocument> UiDocument::read(
const scriptenv& penv,
const std::string& name,
const fs::path& file,
const std::string& fileName
) {
const std::string text = files::read_string(file);
auto xmldoc = xml::parse(file.u8string(), text);
@ -69,12 +74,16 @@ std::unique_ptr<UiDocument> UiDocument::read(const scriptenv& penv, const std::s
uidocscript script {};
auto scriptFile = fs::path(file.u8string()+".lua");
if (fs::is_regular_file(scriptFile)) {
scripting::load_layout_script(env, name, scriptFile, script);
scripting::load_layout_script(
env, name, scriptFile, fileName + ".lua", script
);
}
return std::make_unique<UiDocument>(name, script, view, env);
}
std::shared_ptr<gui::UINode> UiDocument::readElement(const fs::path& file) {
auto document = read(nullptr, file.filename().u8string(), file);
std::shared_ptr<gui::UINode> UiDocument::readElement(
const fs::path& file, const std::string& fileName
) {
auto document = read(nullptr, file.filename().u8string(), file, fileName);
return document->getRoot();
}

View File

@ -45,6 +45,13 @@ public:
const uidocscript& getScript() const;
scriptenv getEnvironment() const;
static std::unique_ptr<UiDocument> read(const scriptenv& parent_env, const std::string& name, const fs::path& file);
static std::shared_ptr<gui::UINode> readElement(const fs::path& file);
static std::unique_ptr<UiDocument> read(
const scriptenv& parent_env,
const std::string& name,
const fs::path& file,
const std::string& fileName
);
static std::shared_ptr<gui::UINode> readElement(
const fs::path& file, const std::string& fileName
);
};

View File

@ -230,7 +230,11 @@ void Hud::processInput(bool visible) {
}
}
if (!pause && Events::jactive(BIND_DEVTOOLS_CONSOLE)) {
showOverlay(assets->get<UiDocument>("core:console"), false);
showOverlay(
assets->get<UiDocument>("core:console"),
false,
std::string("console")
);
}
if (!Window::isFocused() && !pause && !isInventoryOpen()) {
setPause(true);
@ -465,7 +469,9 @@ void Hud::showExchangeSlot() {
}
void Hud::showOverlay(UiDocument* doc, bool playerInventory) {
void Hud::showOverlay(
UiDocument* doc, bool playerInventory, const dv::value& arg
) {
if (isInventoryOpen()) {
closeInventory();
}
@ -476,7 +482,8 @@ void Hud::showOverlay(UiDocument* doc, bool playerInventory) {
showExchangeSlot();
inventoryOpen = true;
}
add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false));
add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false),
arg);
}
void Hud::openPermanent(UiDocument* doc) {
@ -508,13 +515,13 @@ void Hud::closeInventory() {
cleanup();
}
void Hud::add(const HudElement& element) {
void Hud::add(const HudElement& element, const dv::value& arg) {
gui->add(element.getNode());
auto document = element.getDocument();
if (document) {
auto invview = std::dynamic_pointer_cast<InventoryView>(element.getNode());
auto inventory = invview ? invview->getInventory() : nullptr;
std::vector<dv::value> args;
std::vector<dv::value> args {arg};
args.emplace_back(inventory ? inventory.get()->getId() : 0);
for (int i = 0; i < 3; i++) {
args.emplace_back(static_cast<integer_t>(blockPos[i]));
@ -615,8 +622,11 @@ void Hud::updateElementsPosition(const Viewport& viewport) {
}
if (secondUI->getPositionFunc() == nullptr) {
secondUI->setPos(glm::vec2(
glm::min(width/2-invwidth/2, width-caWidth-(inventoryView ? 10 : 0)-invwidth),
height/2-totalHeight/2
glm::min(
width / 2.f - invwidth / 2.f,
width - caWidth - (inventoryView ? 10 : 0) - invwidth
),
height / 2.f - totalHeight / 2.f
));
}
}

View File

@ -2,6 +2,7 @@
#include "typedefs.hpp"
#include "util/ObjectsKeeper.hpp"
#include "data/dv.hpp"
#include <string>
#include <memory>
@ -173,7 +174,10 @@ public:
/// @brief Show element in inventory-mode
/// @param doc element layout
/// @param playerInventory show player inventory too
void showOverlay(UiDocument* doc, bool playerInventory);
/// @param arg first argument passing to on_open
void showOverlay(
UiDocument* doc, bool playerInventory, const dv::value& arg = nullptr
);
/// @brief Close all open inventories and overlay
void closeInventory();
@ -182,7 +186,7 @@ public:
/// @param doc element layout
void openPermanent(UiDocument* doc);
void add(const HudElement& element);
void add(const HudElement& element, const dv::value& arg=nullptr);
void onRemove(const HudElement& element);
void remove(const std::shared_ptr<gui::UINode>& node);

View File

@ -62,7 +62,10 @@ gui::page_loader_func menus::create_page_loader(Engine* engine) {
auto fullname = "core:pages/"+name;
auto document_ptr = UiDocument::read(
scripting::get_root_environment(), fullname, file
scripting::get_root_environment(),
fullname,
file,
"core:layout/pages/" + name
);
auto document = document_ptr.get();
engine->getAssets()->store(std::move(document_ptr), fullname);
@ -110,7 +113,7 @@ UiDocument* menus::show(Engine* engine, const std::string& name, std::vector<dv:
auto fullname = "core:layouts/"+name;
auto document_ptr = UiDocument::read(
scripting::get_root_environment(), fullname, file
scripting::get_root_environment(), fullname, file, "core:layouts/"+name
);
auto document = document_ptr.get();
engine->getAssets()->store(std::move(document_ptr), fullname);

View File

@ -83,7 +83,12 @@ void LevelScreen::initializePack(ContentPackRuntime* pack) {
const ContentPack& info = pack->getInfo();
fs::path scriptFile = info.folder/fs::path("scripts/hud.lua");
if (fs::is_regular_file(scriptFile)) {
scripting::load_hud_script(pack->getEnvironment(), info.id, scriptFile);
scripting::load_hud_script(
pack->getEnvironment(),
info.id,
scriptFile,
pack->getId() + ":scripts/hud.lua"
);
}
}

View File

@ -91,6 +91,7 @@ DrawContext DrawContext::sub(Flushable* flushable) const {
auto ctx = DrawContext(*this);
ctx.parent = this;
ctx.flushable = flushable;
ctx.scissorsCount = 0;
return ctx;
}
@ -148,7 +149,7 @@ void DrawContext::setBlendMode(BlendMode mode) {
set_blend_mode(mode);
}
void DrawContext::setScissors(glm::vec4 area) {
void DrawContext::setScissors(const glm::vec4& area) {
Window::pushScissor(area);
scissorsCount++;
}

View File

@ -34,6 +34,6 @@ public:
void setDepthTest(bool flag);
void setCullFace(bool flag);
void setBlendMode(BlendMode mode);
void setScissors(glm::vec4 area);
void setScissors(const glm::vec4& area);
void setLineWidth(float width);
};

View File

@ -90,7 +90,7 @@ void Container::draw(const DrawContext* pctx, Assets* assets) {
if (!nodes.empty()) {
batch->flush();
DrawContext ctx = pctx->sub();
ctx.setScissors(glm::vec4(pos.x, pos.y, size.x, size.y));
ctx.setScissors(glm::vec4(pos.x, pos.y, glm::ceil(size.x), glm::ceil(size.y)));
for (const auto& node : nodes) {
if (node->isVisible())
node->draw(pctx, assets);
@ -108,7 +108,7 @@ void Container::drawBackground(const DrawContext* pctx, Assets*) {
auto batch = pctx->getBatch2D();
batch->texture(nullptr);
batch->setColor(color);
batch->rect(pos.x, pos.y, size.x, size.y);
batch->rect(pos.x, pos.y, glm::ceil(size.x), glm::ceil(size.y));
}
void Container::add(const std::shared_ptr<UINode> &node) {
@ -165,6 +165,14 @@ void Container::setSize(glm::vec2 size) {
}
}
int Container::getScrollStep() const {
return scrollStep;
}
void Container::setScrollStep(int step) {
scrollStep = step;
}
void Container::refresh() {
std::stable_sort(nodes.begin(), nodes.end(), [](const auto& a, const auto& b) {
return a->getZIndex() < b->getZIndex();

View File

@ -32,6 +32,8 @@ namespace gui {
void listenInterval(float interval, ontimeout callback, int repeat=-1);
virtual glm::vec2 getContentOffset() override {return glm::vec2(0.0f, scroll);};
virtual void setSize(glm::vec2 size) override;
virtual int getScrollStep() const;
virtual void setScrollStep(int step);
virtual void refresh() override;
const std::vector<std::shared_ptr<UINode>>& getNodes() const;

View File

@ -1,5 +1,6 @@
#include "TextBox.hpp"
#include <sstream>
#include <utility>
#include <algorithm>
@ -14,24 +15,39 @@
using namespace gui;
inline constexpr int LINE_NUMBERS_PANE_WIDTH = 40;
TextBox::TextBox(std::wstring placeholder, glm::vec4 padding)
: Panel(glm::vec2(200,32), padding, 0),
: Container(glm::vec2(200,32)),
padding(padding),
input(L""),
placeholder(std::move(placeholder))
{
setOnUpPressed(nullptr);
setOnDownPressed(nullptr);
setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.75f));
label = std::make_shared<Label>(L"");
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
label->setPos(glm::vec2(
padding.x + LINE_NUMBERS_PANE_WIDTH * showLineNumbers, padding.y
));
add(label);
lineNumbersLabel = std::make_shared<Label>(L"");
lineNumbersLabel->setMultiline(true);
lineNumbersLabel->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
lineNumbersLabel->setVerticalAlign(Align::top);
add(lineNumbersLabel);
setHoverColor(glm::vec4(0.05f, 0.1f, 0.2f, 0.75f));
textInitX = label->getPos().x;
scrollable = true;
scrollStep = 0;
}
void TextBox::draw(const DrawContext* pctx, Assets* assets) {
Panel::draw(pctx, assets);
Container::draw(pctx, assets);
font = assets->get<Font>(label->getFontName());
@ -76,6 +92,44 @@ void TextBox::draw(const DrawContext* pctx, Assets* assets) {
batch->rect(lcoord.x, lcoord.y+label->getLineYOffset(endLine), end, lineHeight);
}
}
if (isFocused() && multiline) {
auto selectionCtx = subctx.sub(batch);
selectionCtx.setBlendMode(BlendMode::addition);
batch->setColor(glm::vec4(1, 1, 1, 0.1f));
uint line = label->getLineByTextIndex(caret);
while (label->isFakeLine(line)) {
line--;
}
do {
int lineY = label->getLineYOffset(line);
int lineHeight = font->getLineHeight() * label->getLineInterval();
batch->setColor(glm::vec4(1, 1, 1, 0.05f));
if (showLineNumbers) {
batch->rect(
lcoord.x - 8,
lcoord.y + lineY,
label->getSize().x,
lineHeight
);
batch->setColor(glm::vec4(1, 1, 1, 0.10f));
batch->rect(
lcoord.x - LINE_NUMBERS_PANE_WIDTH,
lcoord.y + lineY,
LINE_NUMBERS_PANE_WIDTH - 8,
lineHeight
);
} else {
batch->rect(
lcoord.x, lcoord.y + lineY, label->getSize().x, lineHeight
);
}
line++;
} while (line < label->getLinesNumber() && label->isFakeLine(line));
}
}
void TextBox::drawBackground(const DrawContext* pctx, Assets*) {
@ -103,31 +157,31 @@ void TextBox::drawBackground(const DrawContext* pctx, Assets*) {
if (!isFocused() && supplier) {
input = supplier();
}
if (isFocused() && multiline) {
batch->setColor(glm::vec4(1, 1, 1, 0.1f));
glm::vec2 lcoord = label->calcPos();
lcoord.y -= 2;
uint line = label->getLineByTextIndex(caret);
while (label->isFakeLine(line)) {
line--;
}
batch->setColor(glm::vec4(1, 1, 1, 0.05f));
do {
int lineY = label->getLineYOffset(line);
int lineHeight = font->getLineHeight() * label->getLineInterval();
batch->rect(lcoord.x, lcoord.y+lineY, label->getSize().x, lineHeight);
line++;
} while (line < label->getLinesNumber() && label->isFakeLine(line));
}
refreshLabel();
}
void TextBox::refreshLabel() {
label->setColor(glm::vec4(input.empty() ? 0.5f : 1.0f));
label->setColor(textColor * glm::vec4(input.empty() ? 0.5f : 1.0f));
label->setText(input.empty() && !hint.empty() ? hint : getText());
if (showLineNumbers) {
if (lineNumbersLabel->getLinesNumber() != label->getLinesNumber()) {
std::wstringstream ss;
int n = 1;
for (int i = 1; i <= label->getLinesNumber(); i++) {
if (!label->isFakeLine(i-1)) {
ss << n;
n++;
}
if (i + 1 <= label->getLinesNumber()) {
ss << "\n";
}
}
lineNumbersLabel->setText(ss.str());
}
lineNumbersLabel->setPos(padding);
lineNumbersLabel->setColor(glm::vec4(1, 1, 1, 0.25f));
}
if (autoresize && font) {
auto size = getSize();
@ -293,7 +347,7 @@ bool TextBox::isAutoResize() const {
}
void TextBox::onFocus(GUI* gui) {
Panel::onFocus(gui);
Container::onFocus(gui);
if (onEditStart){
setCaret(input.size());
onEditStart();
@ -302,8 +356,11 @@ void TextBox::onFocus(GUI* gui) {
}
void TextBox::refresh() {
Panel::refresh();
Container::refresh();
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
label->setPos(glm::vec2(
padding.x + LINE_NUMBERS_PANE_WIDTH * showLineNumbers, padding.y
));
}
/// @brief Clamp index to range [0, input.length()]
@ -567,6 +624,14 @@ void TextBox::select(int start, int end) {
setCaret(selectionEnd);
}
uint TextBox::getLineAt(size_t position) const {
return label->getLineByTextIndex(position);
}
size_t TextBox::getLinePos(uint line) const {
return label->getTextLineOffset(line);
}
std::shared_ptr<UINode> TextBox::getAt(glm::vec2 pos, std::shared_ptr<UINode> self) {
return UINode::getAt(pos, self);
}
@ -619,6 +684,15 @@ glm::vec4 TextBox::getFocusedColor() const {
return focusedColor;
}
void TextBox::setTextColor(glm::vec4 color) {
this->textColor = color;
}
glm::vec4 TextBox::getTextColor() const {
return textColor;
}
void TextBox::setErrorColor(glm::vec4 color) {
this->invalidColor = color;
}
@ -673,9 +747,11 @@ void TextBox::setCaret(size_t position) {
uint line = label->getLineByTextIndex(caret);
int offset = label->getLineYOffset(line) + getContentOffset().y;
uint lineHeight = font->getLineHeight()*label->getLineInterval();
scrollStep = lineHeight;
if (scrollStep == 0) {
scrollStep = lineHeight;
}
if (offset < 0) {
scrolled(1);
scrolled(-glm::floor(offset/static_cast<double>(scrollStep)+0.5f));
} else if (offset >= getSize().y) {
offset -= getSize().y;
scrolled(-glm::ceil(offset/static_cast<double>(scrollStep)+0.5f));
@ -696,3 +772,20 @@ void TextBox::setCaret(ptrdiff_t position) {
setCaret(static_cast<size_t>(position));
}
}
void TextBox::setPadding(glm::vec4 padding) {
this->padding = padding;
refresh();
}
glm::vec4 TextBox::getPadding() const {
return padding;
}
void TextBox::setShowLineNumbers(bool flag) {
showLineNumbers = flag;
}
bool TextBox::isShowLineNumbers() const {
return showLineNumbers;
}

View File

@ -8,11 +8,14 @@ class Font;
namespace gui {
class Label;
class TextBox : public Panel {
class TextBox : public Container {
protected:
glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f};
glm::vec4 invalidColor {0.1f, 0.05f, 0.03f, 1.0f};
glm::vec4 textColor {1.0f, 1.0f, 1.0f, 1.0f};
glm::vec4 padding {2};
std::shared_ptr<Label> label;
std::shared_ptr<Label> lineNumbersLabel;
/// @brief Current user input
std::wstring input;
/// @brief Text will be used if nothing entered
@ -52,6 +55,7 @@ namespace gui {
bool multiline = false;
bool editable = true;
bool autoresize = false;
bool showLineNumbers = false;
void stepLeft(bool shiftPressed, bool breakSelection);
void stepRight(bool shiftPressed, bool breakSelection);
@ -106,6 +110,9 @@ namespace gui {
virtual void setFocusedColor(glm::vec4 color);
virtual glm::vec4 getFocusedColor() const;
virtual void setTextColor(glm::vec4 color);
virtual glm::vec4 getTextColor() const;
/// @brief Set color of textbox marked by validator as invalid
virtual void setErrorColor(glm::vec4 color);
@ -152,6 +159,16 @@ namespace gui {
/// @param end index of the last selected character + 1
virtual void select(int start, int end);
/// @brief Get number of line at specific position in text
/// @param position target position
/// @return line number
virtual uint getLineAt(size_t position) const;
/// @brief Get specific line text position
/// @param line target line
/// @return line position in text
virtual size_t getLinePos(uint line) const;
/// @brief Check text with validator set with setTextValidator
/// @return true if text is valid
virtual bool validate();
@ -177,12 +194,18 @@ namespace gui {
/// @brief Check if text editing feature is enabled
virtual bool isEditable() const;
virtual void setPadding(glm::vec4 padding);
glm::vec4 getPadding() const;
/// @brief Set runnable called on textbox focus
virtual void setOnEditStart(runnable oneditstart);
virtual void setAutoResize(bool flag);
virtual bool isAutoResize() const;
virtual void setShowLineNumbers(bool flag);
virtual bool isShowLineNumbers() const;
virtual void onFocus(GUI*) override;
virtual void refresh() override;
virtual void doubleClick(GUI*, int x, int y) override;

View File

@ -81,7 +81,7 @@ namespace gui {
/// @brief element color when clicked
glm::vec4 pressedColor {1.0f};
/// @brief element margin (only supported for Panel sub-nodes)
glm::vec4 margin {1.0f};
glm::vec4 margin {0.0f};
/// @brief is element visible
bool visible = true;
/// @brief is mouse over the element

View File

@ -172,6 +172,9 @@ static void _readContainer(UiXmlReader& reader, const xml::xmlelement& element,
if (element->has("scrollable")) {
container.setScrollable(element->attr("scrollable").asBool());
}
if (element->has("scroll-step")) {
container.setScrollStep(element->attr("scroll-step").asInt());
}
for (auto& sub : element->getElements()) {
if (sub->isText())
continue;
@ -342,7 +345,16 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
auto textbox = std::make_shared<TextBox>(placeholder, glm::vec4(0.0f));
textbox->setHint(hint);
_readPanel(reader, element, *textbox);
_readContainer(reader, element, *textbox);
if (element->has("padding")) {
glm::vec4 padding = element->attr("padding").asVec4();
textbox->setPadding(padding);
glm::vec2 size = textbox->getSize();
textbox->setSize(glm::vec2(
size.x + padding.x + padding.z,
size.y + padding.y + padding.w
));
}
textbox->setText(text);
if (element->has("multiline")) {
@ -357,6 +369,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
if (element->has("autoresize")) {
textbox->setAutoResize(element->attr("autoresize").asBool());
}
if (element->has("line-numbers")) {
textbox->setShowLineNumbers(element->attr("line-numbers").asBool());
}
if (element->has("consumer")) {
textbox->setTextConsumer(scripting::create_wstring_consumer(
reader.getEnvironment(),
@ -384,6 +399,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
if (element->has("error-color")) {
textbox->setErrorColor(element->attr("error-color").asColor());
}
if (element->has("text-color")) {
textbox->setTextColor(element->attr("text-color").asColor());
}
if (element->has("validator")) {
textbox->setTextValidator(scripting::create_wstring_validator(
reader.getEnvironment(),

View File

@ -15,6 +15,7 @@
// Libraries
extern const luaL_Reg audiolib[];
extern const luaL_Reg base64lib[];
extern const luaL_Reg bjsonlib[];
extern const luaL_Reg blocklib[];
extern const luaL_Reg cameralib[];

View File

@ -0,0 +1,52 @@
#include "api_lua.hpp"
#include "util/stringutil.hpp"
static int l_encode(lua::State* L) {
if (lua::istable(L, 1)) {
lua::pushvalue(L, 1);
size_t size = lua::objlen(L, 1);
util::Buffer<char> buffer(size);
for (size_t i = 0; i < size; i++) {
lua::rawgeti(L, i + 1);
buffer[i] = lua::tointeger(L, -1);
lua::pop(L);
}
lua::pop(L);
return lua::pushstring(L, util::base64_encode(
reinterpret_cast<const ubyte*>(buffer.data()), buffer.size()
));
} else if (auto bytes = lua::touserdata<lua::LuaBytearray>(L, 1)) {
return lua::pushstring(
L,
util::base64_encode(
bytes->data().data(),
bytes->data().size()
)
);
}
throw std::runtime_error("array or ByteArray expected");
}
static int l_decode(lua::State* L) {
auto buffer = util::base64_decode(lua::require_lstring(L, 1));
if (lua::toboolean(L, 2)) {
lua::createtable(L, buffer.size(), 0);
for (size_t i = 0; i < buffer.size(); i++) {
lua::pushinteger(L, buffer[i] & 0xFF);
lua::rawseti(L, i+1);
}
} else {
lua::newuserdata<lua::LuaBytearray>(L, buffer.size());
auto bytearray = lua::touserdata<lua::LuaBytearray>(L, -1);
bytearray->data().reserve(buffer.size());
std::memcpy(bytearray->data().data(), buffer.data(), buffer.size());
}
return 1;
}
const luaL_Reg base64lib[] = {
{"encode", lua::wrap<l_encode>},
{"decode", lua::wrap<l_decode>},
{NULL, NULL}
};

View File

@ -134,6 +134,24 @@ static int l_move_into(lua::State* L) {
return 0;
}
static int l_get_line_at(lua::State* L) {
auto node = getDocumentNode(L, 1);
auto position = lua::tointeger(L, 2);
if (auto box = dynamic_cast<TextBox*>(node.node.get())) {
return lua::pushinteger(L, box->getLineAt(position));
}
return 0;
}
static int l_get_line_pos(lua::State* L) {
auto node = getDocumentNode(L, 1);
auto line = lua::tointeger(L, 2);
if (auto box = dynamic_cast<TextBox*>(node.node.get())) {
return lua::pushinteger(L, box->getLinePos(line));
}
return 0;
}
static int p_get_inventory(UINode* node, lua::State* L) {
if (auto inventory = dynamic_cast<InventoryView*>(node)) {
auto inv = inventory->getInventory();
@ -221,6 +239,13 @@ static int p_get_track_color(UINode* node, lua::State* L) {
return 0;
}
static int p_get_text_color(UINode* node, lua::State* L) {
if (auto box = dynamic_cast<TextBox*>(node)) {
return lua::pushcolor(L, box->getTextColor());
}
return 0;
}
static int p_is_valid(UINode* node, lua::State* L) {
if (auto box = dynamic_cast<TextBox*>(node)) {
return lua::pushboolean(L, box->validate());
@ -267,6 +292,13 @@ static int p_get_editable(UINode* node, lua::State* L) {
return 0;
}
static int p_get_line_numbers(UINode* node, lua::State* L) {
if (auto box = dynamic_cast<TextBox*>(node)) {
return lua::pushboolean(L, box->isShowLineNumbers());
}
return 0;
}
static int p_get_src(UINode* node, lua::State* L) {
if (auto image = dynamic_cast<Image*>(node)) {
return lua::pushstring(L, image->getTexture());
@ -342,6 +374,12 @@ static int p_move_into(UINode*, lua::State* L) {
static int p_get_focused(UINode* node, lua::State* L) {
return lua::pushboolean(L, node->isFocused());
}
static int p_get_line_at(UINode*, lua::State* L) {
return lua::pushcfunction(L, l_get_line_at);
}
static int p_get_line_pos(UINode*, lua::State* L) {
return lua::pushcfunction(L, l_get_line_pos);
}
static int l_gui_getattr(lua::State* L) {
auto docname = lua::require_string(L, 1);
@ -376,6 +414,9 @@ static int l_gui_getattr(lua::State* L) {
{"caret", p_get_caret},
{"text", p_get_text},
{"editable", p_get_editable},
{"lineNumbers", p_get_line_numbers},
{"lineAt", p_get_line_at},
{"linePos", p_get_line_pos},
{"src", p_get_src},
{"value", p_get_value},
{"min", p_get_min},
@ -383,6 +424,7 @@ static int l_gui_getattr(lua::State* L) {
{"step", p_get_step},
{"trackWidth", p_get_track_width},
{"trackColor", p_get_track_color},
{"textColor", p_get_text_color},
{"checked", p_is_checked},
{"page", p_get_page},
{"back", p_get_back},
@ -462,6 +504,11 @@ static void p_set_editable(UINode* node, lua::State* L, int idx) {
box->setEditable(lua::toboolean(L, idx));
}
}
static void p_set_line_numbers(UINode* node, lua::State* L, int idx) {
if (auto box = dynamic_cast<TextBox*>(node)) {
box->setShowLineNumbers(lua::toboolean(L, idx));
}
}
static void p_set_src(UINode* node, lua::State* L, int idx) {
if (auto image = dynamic_cast<Image*>(node)) {
image->setTexture(lua::require_string(L, idx));
@ -497,6 +544,11 @@ static void p_set_track_color(UINode* node, lua::State* L, int idx) {
bar->setTrackColor(lua::tocolor(L, idx));
}
}
static void p_set_text_color(UINode* node, lua::State* L, int idx) {
if (auto box = dynamic_cast<TextBox*>(node)) {
box->setTextColor(lua::tocolor(L, idx));
}
}
static void p_set_checked(UINode* node, lua::State* L, int idx) {
if (auto box = dynamic_cast<CheckBox*>(node)) {
box->setChecked(lua::toboolean(L, idx));
@ -556,6 +608,7 @@ static int l_gui_setattr(lua::State* L) {
{"hint", p_set_hint},
{"text", p_set_text},
{"editable", p_set_editable},
{"lineNumbers", p_set_line_numbers},
{"src", p_set_src},
{"caret", p_set_caret},
{"value", p_set_value},
@ -564,6 +617,7 @@ static int l_gui_setattr(lua::State* L) {
{"step", p_set_step},
{"trackWidth", p_set_track_width},
{"trackColor", p_set_track_color},
{"textColor", p_set_text_color},
{"checked", p_set_checked},
{"page", p_set_page},
{"inventory", p_set_inventory},

View File

@ -94,6 +94,11 @@ static int l_encode(lua::State* L) {
return lua::pushlstring(L, bytes, count);
}
static int l_escape(lua::State* L) {
auto string = lua::require_lstring(L, 1);
return lua::pushstring(L, util::escape(string));
}
const luaL_Reg utf8lib[] = {
{"tobytes", lua::wrap<l_tobytes>},
{"tostring", lua::wrap<l_tostring>},
@ -103,5 +108,6 @@ const luaL_Reg utf8lib[] = {
{"upper", lua::wrap<l_upper>},
{"lower", lua::wrap<l_lower>},
{"encode", lua::wrap<l_encode>},
{"escape", lua::wrap<l_escape>},
{NULL, NULL}
};

View File

@ -39,6 +39,7 @@ static void remove_lib_funcs(
}
static void create_libs(State* L, StateType stateType) {
openlib(L, "base64", base64lib);
openlib(L, "bjson", bjsonlib);
openlib(L, "block", blocklib);
openlib(L, "core", corelib);
@ -143,7 +144,8 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) {
init_state(L, stateType);
auto resDir = paths.getResourcesFolder();
auto src = files::read_string(resDir / fs::u8path("scripts/stdmin.lua"));
lua::pop(L, lua::execute(L, 0, src, "<stdmin>"));
auto file = resDir / fs::u8path("scripts/stdmin.lua");
auto src = files::read_string(file);
lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua"));
return L;
}

View File

@ -145,10 +145,14 @@ static int l_error_handler(lua_State* L) {
if (!isstring(L, 1)) { // 'message' not a string?
return 1; // keep it intact
}
if (get_from(L, "debug", "traceback")) {
if (getglobal(L, "__vc__error")) {
lua_pushvalue(L, 1); // pass error message
lua_pushinteger(L, 2); // skip this function and traceback
lua_call(L, 2, 1); // call debug.traceback
} if (get_from(L, "debug", "traceback")) {
lua_pushvalue(L, 1);
lua_pushinteger(L, 2);
lua_call(L, 2, 1);
}
return 1;
}
@ -172,8 +176,13 @@ int lua::call_nothrow(State* L, int argc, int nresults) {
pushcfunction(L, l_error_handler);
insert(L, handler_pos);
if (lua_pcall(L, argc, LUA_MULTRET, handler_pos)) {
log_error(tostring(L, -1));
pop(L);
auto errorstr = tostring(L, -1);
if (errorstr) {
log_error(errorstr);
pop(L);
} else {
log_error("");
}
remove(L, handler_pos);
return 0;
}

View File

@ -240,6 +240,11 @@ namespace lua {
inline const char* tostring(lua::State* L, int idx) {
return lua_tostring(L, idx);
}
inline std::string_view tolstring(lua::State* L, int idx) {
size_t len = 0;
auto string = lua_tolstring(L, idx, &len);
return std::string_view(string, len);
}
inline const void* topointer(lua::State* L, int idx) {
return lua_topointer(L, idx);
}

View File

@ -44,7 +44,7 @@ void scripting::load_script(const fs::path& name, bool throwable) {
fs::path file = paths->getResourcesFolder() / fs::path("scripts") / name;
std::string src = files::read_string(file);
auto L = lua::get_main_state();
lua::loadbuffer(L, 0, src, file.u8string());
lua::loadbuffer(L, 0, src, "core:scripts/"+name.u8string());
if (throwable) {
lua::call(L, 0, 0);
} else {
@ -53,11 +53,14 @@ void scripting::load_script(const fs::path& name, bool throwable) {
}
int scripting::load_script(
int env, const std::string& type, const fs::path& file
int env,
const std::string& type,
const fs::path& file,
const std::string& fileName
) {
std::string src = files::read_string(file);
logger.info() << "script (" << type << ") " << file.u8string();
return lua::execute(lua::get_main_state(), env, src, file.u8string());
return lua::execute(lua::get_main_state(), env, src, fileName);
}
void scripting::initialize(Engine* engine) {
@ -657,10 +660,11 @@ void scripting::load_block_script(
const scriptenv& senv,
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
block_funcs_set& funcsset
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "block", file));
lua::pop(lua::get_main_state(), load_script(env, "block", file, fileName));
funcsset.init = register_event(env, "init", prefix + ".init");
funcsset.update = register_event(env, "on_update", prefix + ".update");
funcsset.randupdate =
@ -677,10 +681,11 @@ void scripting::load_item_script(
const scriptenv& senv,
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
item_funcs_set& funcsset
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "item", file));
lua::pop(lua::get_main_state(), load_script(env, "item", file, fileName));
funcsset.init = register_event(env, "init", prefix + ".init");
funcsset.on_use = register_event(env, "on_use", prefix + ".use");
funcsset.on_use_on_block =
@ -690,12 +695,12 @@ void scripting::load_item_script(
}
void scripting::load_entity_component(
const std::string& name, const fs::path& file
const std::string& name, const fs::path& file, const std::string& fileName
) {
auto L = lua::get_main_state();
std::string src = files::read_string(file);
logger.info() << "script (component) " << file.u8string();
lua::loadbuffer(L, 0, src, "C!" + name);
lua::loadbuffer(L, 0, src, fileName);
lua::store_in(L, lua::CHUNKS_TABLE, name);
}
@ -703,10 +708,11 @@ void scripting::load_world_script(
const scriptenv& senv,
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
world_funcs_set& funcsset
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "world", file));
lua::pop(lua::get_main_state(), load_script(env, "world", file, fileName));
register_event(env, "init", prefix + ".init");
register_event(env, "on_world_open", prefix + ":.worldopen");
register_event(env, "on_world_tick", prefix + ":.worldtick");
@ -724,11 +730,12 @@ void scripting::load_layout_script(
const scriptenv& senv,
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
uidocscript& script
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "layout", file));
lua::pop(lua::get_main_state(), load_script(env, "layout", file, fileName));
script.onopen = register_event(env, "on_open", prefix + ".open");
script.onprogress =
register_event(env, "on_progress", prefix + ".progress");

View File

@ -125,11 +125,13 @@ namespace scripting {
/// @param env environment
/// @param prefix pack id
/// @param file item script file
/// @param fileName script file path using the engine format
/// @param funcsset block callbacks set
void load_block_script(
const scriptenv& env,
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
block_funcs_set& funcsset
);
@ -137,15 +139,25 @@ namespace scripting {
/// @param env environment
/// @param prefix pack id
/// @param file item script file
/// @param fileName script file path using the engine format
/// @param funcsset item callbacks set
void load_item_script(
const scriptenv& env,
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
item_funcs_set& funcsset
);
void load_entity_component(const std::string& name, const fs::path& file);
/// @brief Load component script
/// @param name component full name (packid:name)
/// @param file component script file path
/// @param fileName script file path using the engine format
void load_entity_component(
const std::string& name,
const fs::path& file,
const std::string& fileName
);
std::unique_ptr<GeneratorScript> load_generator(
const GeneratorDef& def,
@ -157,10 +169,12 @@ namespace scripting {
/// @param env environment
/// @param packid content-pack id
/// @param file script file path
/// @param fileName script file path using the engine format
void load_world_script(
const scriptenv& env,
const std::string& packid,
const fs::path& file,
const std::string& fileName,
world_funcs_set& funcsset
);
@ -168,11 +182,13 @@ namespace scripting {
/// @param env environment
/// @param prefix pack id
/// @param file item script file
/// @param fileName script file path using the engine format
/// @param script document script info
void load_layout_script(
const scriptenv& env,
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
uidocscript& script
);

View File

@ -6,6 +6,10 @@
namespace scripting {
void load_script(const std::filesystem::path& name, bool throwable);
[[nodiscard]]
int load_script(int env, const std::string& type, const std::filesystem::path& file);
[[nodiscard]] int load_script(
int env,
const std::string& type,
const std::filesystem::path& file,
const std::string& fileName
);
}

View File

@ -76,13 +76,16 @@ void scripting::on_frontend_close() {
}
void scripting::load_hud_script(
const scriptenv& senv, const std::string& packid, const fs::path& file
const scriptenv& senv,
const std::string& packid,
const fs::path& file,
const std::string& fileName
) {
int env = *senv;
std::string src = files::read_string(file);
logger.info() << "loading script " << file.u8string();
lua::execute(lua::get_main_state(), env, src, file.u8string());
lua::execute(lua::get_main_state(), env, src, fileName);
register_event(env, "init", packid + ":.init");
register_event(env, "on_hud_open", packid + ":.hudopen");

View File

@ -22,7 +22,11 @@ namespace scripting {
/// @param env environment id
/// @param packid content-pack id
/// @param file script file path
/// @param fileName script file path using the engine format
void load_hud_script(
const scriptenv &env, const std::string &packid, const fs::path &file
const scriptenv& env,
const std::string& packid,
const fs::path& file,
const std::string& fileName
);
}

View File

@ -7,8 +7,7 @@
#include <sstream>
#include <stdexcept>
// TODO: finish
std::string util::escape(const std::string& s) {
std::string util::escape(std::string_view s) {
std::stringstream ss;
ss << '"';
size_t pos = 0;
@ -318,7 +317,7 @@ std::string util::base64_encode(const ubyte* data, size_t size) {
ending[i - fullsegments] = data[i];
}
size_t trailing = size - fullsegments;
{
if (trailing) {
char output[] = "====";
output[0] = B64ABC[(ending[0] & 0b11111100) >> 2];
output[1] =
@ -364,8 +363,8 @@ util::Buffer<ubyte> util::base64_decode(const char* str, size_t size) {
return bytes;
}
util::Buffer<ubyte> util::base64_decode(const std::string& str) {
return base64_decode(str.c_str(), str.size());
util::Buffer<ubyte> util::base64_decode(std::string_view str) {
return base64_decode(str.data(), str.size());
}
int util::replaceAll(

View File

@ -8,7 +8,7 @@
namespace util {
/// @brief Function used for string serialization in text formats
std::string escape(const std::string& s);
std::string escape(std::string_view s);
/// @brief Function used for error messages
std::string quote(const std::string& s);
@ -63,7 +63,7 @@ namespace util {
std::string base64_encode(const ubyte* data, size_t size);
util::Buffer<ubyte> base64_decode(const char* str, size_t size);
util::Buffer<ubyte> base64_decode(const std::string& str);
util::Buffer<ubyte> base64_decode(std::string_view str);
std::string tohex(uint64_t value);

View File

@ -258,14 +258,14 @@ void Window::pushScissor(glm::vec4 area) {
}
scissorStack.push(scissorArea);
area.z += area.x;
area.w += area.y;
area.z += glm::ceil(area.x);
area.w += glm::ceil(area.y);
area.x = fmax(area.x, scissorArea.x);
area.y = fmax(area.y, scissorArea.y);
area.x = glm::max(area.x, scissorArea.x);
area.y = glm::max(area.y, scissorArea.y);
area.z = fmin(area.z, scissorArea.z);
area.w = fmin(area.w, scissorArea.w);
area.z = glm::min(area.z, scissorArea.z);
area.w = glm::min(area.w, scissorArea.w);
if (area.z < 0.0f || area.w < 0.0f) {
glScissor(0, 0, 0, 0);
@ -273,8 +273,8 @@ void Window::pushScissor(glm::vec4 area) {
glScissor(
area.x,
Window::height - area.w,
std::max(0, int(area.z - area.x)),
std::max(0, int(area.w - area.y))
std::max(0, static_cast<int>(glm::ceil(area.z - area.x))),
std::max(0, static_cast<int>(glm::ceil(area.w - area.y)))
);
}
scissorArea = area;

View File

@ -15,3 +15,19 @@ TEST(stringutil, utf8) {
std::string str2 = util::u32str2str_utf8(u32str);
EXPECT_EQ(str, str2);
}
TEST(stringutil, base64) {
srand(2019);
for (size_t size = 0; size < 30; size++) {
auto bytes = std::make_unique<ubyte[]>(size);
for (int i = 0; i < size; i++) {
bytes[i] = rand();
}
auto base64 = util::base64_encode(bytes.get(), size);
auto decoded = util::base64_decode(base64);
ASSERT_EQ(size, decoded.size());
for (size_t i = 0; i < size; i++) {
ASSERT_EQ(bytes[i], decoded[i]);
}
}
}