Skip to content

Commit

Permalink
Merge pull request #444 from ShadelessFox/canvas-ui-node
Browse files Browse the repository at this point in the history
Add `canvas` UI node
  • Loading branch information
MihailRis authored Jan 19, 2025
2 parents eaa64cb + 5ee44a0 commit 0b2e089
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 2 deletions.
19 changes: 19 additions & 0 deletions doc/en/scripting/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,25 @@ Properties:
| ----- | ------ | ---- | ----- | ------------ |
| src | string | yes | yes | texture name |

## Canvas

Properties:

| Title | Type | Read | Write | Description |
|-------|--------| ---- |-------|-------------|
| data | Canvas | yes | no | canvas data |

Methods:

| Method | Description |
|----------------------------------------------------------|---------------------------------------------------------|
| data:at(x: int, y: int) | returns an RGBA pixel at the given coordinates |
| data:set(x: int, y: int, rgba: int) | updates an RGBA pixel at the given coordinates |
| data:set(x: int, y: int, r: int, g: int, b: int) | updates an RGBA pixel at the given coordinates |
| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | updates an RGBA pixel at the given coordinates |
| data:update() | applies changes to the canvas and uploads it to the GPU |


## Inventory

Properties:
Expand Down
6 changes: 5 additions & 1 deletion doc/en/xml-ui-layouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ Inner text is a button text.
- `src` - name of an image stored in textures folder. Extension is not specified. Type: string.
Example: *gui/error*

## *textbox*
## *canvas*

- _No additional attributes_

## *textbox*

Inner text - initially entered text

Expand Down
19 changes: 19 additions & 0 deletions doc/ru/scripting/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,25 @@ document["worlds-panel"]:clear()
| -------- | ------ | ------ | ------ | --------------------- |
| src | string | да | да | отображаемая текстура |


## Холст (canvas)

Свойства:

| Название | Тип | Чтение | Запись | Описание |
|----------|--------| ------ |--------|----------------|
| data | Canvas | да | нет | пиксели холста |

Методы:

| Метод | Описание |
|----------------------------------------------------------|-----------------------------------------------------|
| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам |
| data:set(x: int, y: int, rgba: int) | изменяет RGBA пиксель по указанным координатам |
| data:set(x: int, y: int, r: int, g: int, b: int) | изменяет RGBA пиксель по указанным координатам |
| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | изменяет RGBA пиксель по указанным координатам |
| data:update() | применяет изменения и загружает холст в видеопамять |

## Inventory (inventory)

Свойства:
Expand Down
4 changes: 4 additions & 0 deletions doc/ru/xml-ui-layouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@

- `src` - имя изображения в папке textures без указания расширения. Тип: строка. Например `gui/error`

## Холст - *canvas*

- _Нет дополнительных свойств_

## Текстовое поле - *textbox*

Внутренний текст - изначально введенный текст
Expand Down
19 changes: 19 additions & 0 deletions src/graphics/ui/elements/Canvas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "Canvas.hpp"

#include "graphics/core/Batch2D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Texture.hpp"

gui::Canvas::Canvas(ImageFormat inFormat, glm::uvec2 inSize) : UINode(inSize) {
ImageData data {inFormat, inSize.x, inSize.y};
mTexture = Texture::from(&data);
}

void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) {
auto pos = calcPos();
auto col = calcColor();

auto batch = pctx.getBatch2D();
batch->texture(mTexture.get());
batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, true, col);
}
25 changes: 25 additions & 0 deletions src/graphics/ui/elements/Canvas.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include "UINode.hpp"
#include "graphics/core/ImageData.hpp"
#include "graphics/core/Texture.hpp"

class Texture;

namespace gui {
class Canvas final : public UINode {
public:
explicit Canvas(ImageFormat inFormat, glm::uvec2 inSize);

~Canvas() override = default;

void draw(const DrawContext& pctx, const Assets& assets) override;

[[nodiscard]] std::shared_ptr<::Texture> texture() const {
return mTexture;
}
private:
std::shared_ptr<::Texture> mTexture;
std::unique_ptr<ImageData> mData;
};
}
14 changes: 14 additions & 0 deletions src/graphics/ui/gui_xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "elements/Image.hpp"
#include "elements/Menu.hpp"
#include "elements/Button.hpp"
#include "elements/Canvas.hpp"
#include "elements/CheckBox.hpp"
#include "elements/TextBox.hpp"
#include "elements/TrackBar.hpp"
Expand Down Expand Up @@ -455,6 +456,18 @@ static std::shared_ptr<UINode> readImage(
return image;
}

static std::shared_ptr<UINode> readCanvas(
const UiXmlReader& reader, const xml::xmlelement& element
) {
auto size = glm::uvec2{32, 32};
if (element.has("size")) {
size = element.attr("size").asVec2();
}
auto image = std::make_shared<Canvas>(ImageFormat::rgba8888, size);
_readUINode(reader, element, *image);
return image;
}

static std::shared_ptr<UINode> readTrackBar(
const UiXmlReader& reader, const xml::xmlelement& element
) {
Expand Down Expand Up @@ -634,6 +647,7 @@ static std::shared_ptr<UINode> readPageBox(UiXmlReader& reader, const xml::xmlel
UiXmlReader::UiXmlReader(const scriptenv& env) : env(env) {
contextStack.emplace("");
add("image", readImage);
add("canvas", readCanvas);
add("label", readLabel);
add("panel", readPanel);
add("button", readButton);
Expand Down
9 changes: 9 additions & 0 deletions src/logic/scripting/lua/libs/libgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "engine/Engine.hpp"
#include "frontend/locale.hpp"
#include "graphics/ui/elements/Button.hpp"
#include "graphics/ui/elements/Canvas.hpp"
#include "graphics/ui/elements/CheckBox.hpp"
#include "graphics/ui/elements/Image.hpp"
#include "graphics/ui/elements/InventoryView.hpp"
Expand Down Expand Up @@ -323,6 +324,13 @@ static int p_get_src(UINode* node, lua::State* L) {
return 0;
}

static int p_get_data(UINode* node, lua::State* L) {
if (auto canvas = dynamic_cast<Canvas*>(node)) {
return lua::newuserdata<lua::LuaCanvas>(L, canvas->texture());
}
return 0;
}

static int p_get_add(UINode* node, lua::State* L) {
if (dynamic_cast<Container*>(node)) {
return lua::pushcfunction(L, lua::wrap<l_container_add>);
Expand Down Expand Up @@ -464,6 +472,7 @@ static int l_gui_getattr(lua::State* L) {
{"inventory", p_get_inventory},
{"focused", p_get_focused},
{"cursor", p_get_cursor},
{"data", p_get_data},
};
auto func = getters.find(attr);
if (func != getters.end()) {
Expand Down
23 changes: 23 additions & 0 deletions src/logic/scripting/lua/lua_custom_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
struct fnl_state;
class Heightmap;
class VoxelFragment;
class Texture;
class ImageData;

namespace lua {
class Userdata {
Expand Down Expand Up @@ -90,4 +92,25 @@ namespace lua {
inline static std::string TYPENAME = "VoxelFragment";
};
static_assert(!std::is_abstract<LuaVoxelFragment>());

class LuaCanvas : public Userdata {
public:
explicit LuaCanvas(std::shared_ptr<Texture> inTexture);
~LuaCanvas() override = default;

const std::string& getTypeName() const override {
return TYPENAME;
}

[[nodiscard]] Texture& texture() const { return *mTexture; }

[[nodiscard]] ImageData& data() const { return *mData; }

static int createMetatable(lua::State*);
inline static std::string TYPENAME = "Canvas";
private:
std::shared_ptr<Texture> mTexture;
std::unique_ptr<ImageData> mData;
};
static_assert(!std::is_abstract<LuaCanvas>());
}
1 change: 1 addition & 0 deletions src/logic/scripting/lua/lua_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ void lua::init_state(State* L, StateType stateType) {
newusertype<LuaBytearray>(L);
newusertype<LuaHeightmap>(L);
newusertype<LuaVoxelFragment>(L);
newusertype<LuaCanvas>(L);
}

void lua::initialize(const EnginePaths& paths, const CoreParameters& params) {
Expand Down
2 changes: 1 addition & 1 deletion src/logic/scripting/lua/lua_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ namespace lua {
inline int newuserdata(lua::State* L, Args&&... args) {
const auto& found = usertypeNames.find(typeid(T));
void* ptr = lua_newuserdata(L, sizeof(T));
new (ptr) T(args...);
new (ptr) T(std::forward<Args>(args)...);

if (found == usertypeNames.end()) {
log_error(
Expand Down
140 changes: 140 additions & 0 deletions src/logic/scripting/lua/usertypes/lua_type_canvas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#include <unordered_map>

#include "graphics/core/ImageData.hpp"
#include "graphics/core/Texture.hpp"
#include "logic/scripting/lua/lua_custom_types.hpp"
#include "logic/scripting/lua/lua_util.hpp"

using namespace lua;

LuaCanvas::LuaCanvas(std::shared_ptr<Texture> inTexture)
: mTexture(std::move(inTexture)) {
mData = mTexture->readData();
}

union RGBA {
uint8_t rgba[4];
uint32_t raw;
};

static RGBA* get_at(const ImageData& data, uint index) {
if (index >= data.getWidth() * data.getHeight()) {
return nullptr;
}
return reinterpret_cast<RGBA*>(data.getData() + index * sizeof(RGBA));
}

static RGBA* get_at(const ImageData& data, uint x, uint y) {
return get_at(data, y * data.getWidth() + x);
}

static RGBA* get_at(State* L, uint x, uint y) {
if (auto texture = touserdata<LuaCanvas>(L, 1)) {
return get_at(texture->data(), x, y);
}
return nullptr;
}

static int l_at(State* L) {
auto x = static_cast<uint>(tointeger(L, 2));
auto y = static_cast<uint>(tointeger(L, 3));

if (auto pixel = get_at(L, x, y)) {
return pushinteger(L, pixel->raw);
}

return 0;
}

static int l_set(State* L) {
auto x = static_cast<uint>(tointeger(L, 2));
auto y = static_cast<uint>(tointeger(L, 3));

if (auto pixel = get_at(L, x, y)) {
switch (gettop(L)) {
case 4:
pixel->raw = static_cast<uint>(tointeger(L, 4));
return 1;
case 6:
pixel->rgba[0] = static_cast<ubyte>(tointeger(L, 4));
pixel->rgba[1] = static_cast<ubyte>(tointeger(L, 5));
pixel->rgba[2] = static_cast<ubyte>(tointeger(L, 6));
pixel->rgba[3] = 255;
return 1;
case 7:
pixel->rgba[0] = static_cast<ubyte>(tointeger(L, 4));
pixel->rgba[1] = static_cast<ubyte>(tointeger(L, 5));
pixel->rgba[2] = static_cast<ubyte>(tointeger(L, 6));
pixel->rgba[3] = static_cast<ubyte>(tointeger(L, 7));
return 1;
default:
return 0;
}
}

return 0;
}

static int l_update(State* L) {
if (auto texture = touserdata<LuaCanvas>(L, 1)) {
texture->texture().reload(texture->data());
}
return 0;
}

static std::unordered_map<std::string, lua_CFunction> methods {
{"at", lua::wrap<l_at>},
{"set", lua::wrap<l_set>},
{"update", lua::wrap<l_update>}
};

static int l_meta_index(State* L) {
auto texture = touserdata<LuaCanvas>(L, 1);
if (texture == nullptr) {
return 0;
}
auto& data = texture->data();
if (isnumber(L, 2)) {
if (auto pixel = get_at(data, static_cast<uint>(tointeger(L, 2)))) {
return pushinteger(L, pixel->raw);
}
}
if (isstring(L, 2)) {
auto name = tostring(L, 2);
if (!strcmp(name, "width")) {
return pushinteger(L, data.getWidth());
}
if (!strcmp(name, "height")) {
return pushinteger(L, data.getHeight());
}
if (auto func = methods.find(tostring(L, 2)); func != methods.end()) {
return pushcfunction(L, func->second);
}
}
return 0;
}

static int l_meta_newindex(State* L) {
auto texture = touserdata<LuaCanvas>(L, 1);
if (texture == nullptr) {
return 0;
}
auto& data = texture->data();
if (isnumber(L, 2) && isnumber(L, 3)) {
if (auto pixel = get_at(data, static_cast<uint>(tointeger(L, 2)))) {
pixel->raw = static_cast<uint>(tointeger(L, 3));
return 1;
}
return 1;
}
return 0;
}

int LuaCanvas::createMetatable(State* L) {
createtable(L, 0, 3);
pushcfunction(L, lua::wrap<l_meta_index>);
setfield(L, "__index");
pushcfunction(L, lua::wrap<l_meta_newindex>);
setfield(L, "__newindex");
return 1;
}

0 comments on commit 0b2e089

Please sign in to comment.