UI: re-added nuklear ui
fixed compile errors in original revert commit and updated nuklear to latest version
also fixed a few warnings in the nuklear code and a memory leak in the original
NuklearApp.cpp code
use cmd and var lua bindings
added push and pop window commands
allow to modify nuklear config on a per-app-basis
use commonlua functions and added buffer check
use SDL_stdinc
allow to override the application script
this allows us to directly edit and hot reload the lua scripts from the source tree
just export UI_SCRIPT=/home/mgerhardy/dev/engine/src/client/lua/ui/client.lua
This reverts commit 1fb662810e
.
Demo code taken from https://github.com/DeXP/nuklear-webdemo - Public Domain
7
Makefile
|
@ -141,6 +141,11 @@ update-glslang:
|
|||
rm -rf src/tools/glslang/StandAlone
|
||||
cp -r $(UPDATEDIR)/glslang.sync/StandAlone src/tools/glslang/
|
||||
|
||||
update-nuklear:
|
||||
$(call UPDATE_GIT,nuklear,https://github.com/Immediate-Mode-UI/Nuklear)
|
||||
cp $(UPDATEDIR)/nuklear.sync/nuklear.h src/modules/ui/nuklear/private
|
||||
cp $(UPDATEDIR)/nuklear.sync/demo/overview.c src/tests/testnuklear
|
||||
|
||||
# currently not part of updatelibs - intentional - we adopted the original code.
|
||||
update-simplexnoise:
|
||||
$(call UPDATE_GIT,simplexnoise,https://github.com/simongeilfus/SimplexNoise.git)
|
||||
|
@ -149,7 +154,7 @@ update-simplexnoise:
|
|||
# TODO native file dialog support
|
||||
# TODO simpleai support
|
||||
# TODO lua support
|
||||
updatelibs: update-libuv update-stb update-googletest update-benchmark update-backward update-dearimgui update-flatbuffers update-enet update-glm update-sdl2 update-glslang
|
||||
updatelibs: update-nuklear update-libuv update-stb update-googletest update-benchmark update-backward update-dearimgui update-flatbuffers update-enet update-glm update-sdl2 update-glslang
|
||||
$(MAKE) -C $(BUILDDIR) update-libs
|
||||
|
||||
windows:
|
||||
|
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 655 B |
After Width: | Height: | Size: 460 B |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 583 B |
After Width: | Height: | Size: 533 B |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 561 B |
After Width: | Height: | Size: 819 B |
After Width: | Height: | Size: 648 B |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 610 B |
After Width: | Height: | Size: 703 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 566 B |
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 520 B |
After Width: | Height: | Size: 601 B |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 30 KiB |
|
@ -1,2 +1,3 @@
|
|||
add_subdirectory(imgui)
|
||||
add_subdirectory(turbobadger)
|
||||
add_subdirectory(nuklear)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
set(SRCS
|
||||
NuklearApp.cpp NuklearApp.h
|
||||
NuklearNode.cpp NuklearNode.h
|
||||
Nuklear.h
|
||||
|
||||
Console.cpp Console.h
|
||||
|
||||
LUAUIApp.cpp LUAUIApp.h
|
||||
LUAFunctions.cpp LUAFunctions.h
|
||||
|
||||
private/nuklear.h
|
||||
)
|
||||
set(FILES
|
||||
shared/font.ttf
|
||||
)
|
||||
engine_add_module(TARGET nuklear SRCS ${SRCS} FILES ${FILES} DEPENDENCIES render commonlua voxelrender)
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "Console.h"
|
||||
#include "Nuklear.h"
|
||||
#include "NuklearApp.h"
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
Console::Console(struct nk_context* ctx) :
|
||||
Super(), _ctx(ctx) {
|
||||
}
|
||||
|
||||
void Console::drawString(int x, int y, const glm::ivec4& color, int colorIndex, const char* str, int len) {
|
||||
const struct nk_user_font *font = _ctx->style.font;
|
||||
struct nk_command_buffer* cmdBuf = nk_window_get_canvas(_ctx);
|
||||
const int width = font->width(font->userdata, font->height, str, len);
|
||||
const struct nk_rect& rect = nk_rect(x, y, width, font->height);
|
||||
nk_draw_text(cmdBuf, rect, str,
|
||||
strlen(str), font, nk_rgba(0, 0, 0, 255), nk_rgba(color.r, color.g, color.b, color.a));
|
||||
}
|
||||
|
||||
void Console::afterRender(const math::Rect<int> &rect) {
|
||||
nk_end(_ctx);
|
||||
}
|
||||
|
||||
void Console::beforeRender(const math::Rect<int> &rect) {
|
||||
const struct nk_rect nkrect{(float)rect.getMinX(), (float)rect.getMinZ(), (float)rect.getMaxX(), (float)rect.getMaxZ()};
|
||||
nk_begin(_ctx, "in-game-console", nkrect, NK_WINDOW_NO_SCROLLBAR);
|
||||
}
|
||||
|
||||
int Console::lineHeight() {
|
||||
const struct nk_user_font *styleFont = _ctx->style.font;
|
||||
const int lineHeight = styleFont->height;
|
||||
return lineHeight;
|
||||
}
|
||||
|
||||
glm::ivec2 Console::stringSize(const char* s, int length) {
|
||||
const struct nk_user_font *styleFont = _ctx->style.font;
|
||||
return glm::ivec2(styleFont->width(styleFont->userdata, styleFont->height, s, length), lineHeight());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/Console.h"
|
||||
#include "Nuklear.h"
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
class NuklearApp;
|
||||
|
||||
/**
|
||||
* @ingroup UI
|
||||
*/
|
||||
class Console : public util::Console {
|
||||
private:
|
||||
using Super = util::Console;
|
||||
struct nk_context* _ctx = nullptr;
|
||||
|
||||
void drawString(int x, int y, const glm::ivec4& color, int colorIndex, const char* str, int len) override;
|
||||
int lineHeight() override;
|
||||
glm::ivec2 stringSize(const char* s, int length) override;
|
||||
void afterRender(const math::Rect<int> &rect) override;
|
||||
void beforeRender(const math::Rect<int> &rect) override;
|
||||
|
||||
public:
|
||||
Console(struct nk_context* ctx);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* @file
|
||||
* @brief LUA bindings for nuklear ui.
|
||||
* @note Most of this stuff is shamelessly copied over from nuklear-love lua bindings
|
||||
* @ingroup UI
|
||||
*
|
||||
* LOVE-Nuklear - MIT licensed; no warranty implied; use at your own risk.
|
||||
* authored from 2015-2016 by Micha Mettke
|
||||
* adapted to LOVE in 2016 by Kevin Harrison
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commonlua/LUAFunctions.h"
|
||||
#include "Nuklear.h"
|
||||
#include "core/Assert.h"
|
||||
#include <vector>
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
/**
|
||||
* @brief Extended window start with separated title and identifier to allow multiple windows with same name but not title
|
||||
* @par name (optional) The name of the window - if not given, the name will be the title. Needs to be persistent over
|
||||
* frames to identify the window
|
||||
* @par title The title of the window displayed inside header if flag @c title or either @c closable or @c minimized was set
|
||||
* @par x Position of the window
|
||||
* @par y Position of the window
|
||||
* @par w Size of the window
|
||||
* @par h Size of the window
|
||||
* @par flags Window flags (scrollbar, scroll auto hide, minimizable, background, scalable, closable, movable, border, title)
|
||||
* with a number of different window behaviors
|
||||
* @note If you do not define @c scalable or @c moveable you can set window position and size every frame
|
||||
* @return @c true if the window can be filled up with widgets from this point until @c windowEnd or @c false otherwise
|
||||
*/
|
||||
extern int uilua_window_begin(lua_State *s);
|
||||
|
||||
/**
|
||||
* @brief Needs to be called at the end of the window building process to process scaling,
|
||||
* scrollbars and general cleanup. All widget calls after this functions will result in
|
||||
* asserts or no state changes
|
||||
*/
|
||||
extern int uilua_window_end(lua_State *s);
|
||||
|
||||
/**
|
||||
* @return A rectangle with screen position and size of the currently processed window.
|
||||
* @note IMPORTANT: only call this function between calls `windowBegin` and `windowEnd`
|
||||
*/
|
||||
extern int uilua_window_get_bounds(lua_State *s);
|
||||
|
||||
/**
|
||||
* @return The position of the currently processed window.
|
||||
* @note IMPORTANT: only call this function between calls `windowBegin` and `windowEnd`
|
||||
*/
|
||||
extern int uilua_window_get_position(lua_State *s);
|
||||
|
||||
/**
|
||||
* @return The size with width and height of the currently processed window.
|
||||
* @note IMPORTANT: only call this function between calls `windowBegin` and `windowEnd`
|
||||
*/
|
||||
extern int uilua_window_get_size(lua_State *s);
|
||||
|
||||
/**
|
||||
* @return The position and size of the currently visible and non-clipped space inside the currently processed window.
|
||||
* @note IMPORTANT: only call this function between calls `windowBegin` and `windowEnd`
|
||||
*/
|
||||
extern int uilua_window_get_content_region(lua_State *s);
|
||||
|
||||
extern int uilua_model(lua_State *s);
|
||||
extern int uilua_edit(lua_State *s);
|
||||
extern int uilua_text(lua_State *s);
|
||||
extern int uilua_push_scissor(lua_State *s);
|
||||
extern int uilua_label(lua_State *s);
|
||||
extern int uilua_image(lua_State *s);
|
||||
extern int uilua_button(lua_State *s);
|
||||
|
||||
extern int uilua_window_has_focus(lua_State *s);
|
||||
extern int uilua_window_is_collapsed(lua_State *s);
|
||||
extern int uilua_window_is_hidden(lua_State *s);
|
||||
extern int uilua_window_is_active(lua_State *s);
|
||||
extern int uilua_window_is_hovered(lua_State *s);
|
||||
extern int uilua_window_is_any_hovered(lua_State *s);
|
||||
extern int uilua_item_is_any_active(lua_State *s);
|
||||
extern int uilua_window_set_bounds(lua_State *s);
|
||||
extern int uilua_window_set_position(lua_State *s);
|
||||
extern int uilua_window_set_size(lua_State *s);
|
||||
extern int uilua_window_set_focus(lua_State *s);
|
||||
extern int uilua_window_close(lua_State *s);
|
||||
extern int uilua_window_collapse(lua_State *s);
|
||||
extern int uilua_window_expand(lua_State *s);
|
||||
extern int uilua_window_show(lua_State *s);
|
||||
extern int uilua_window_hide(lua_State *s);
|
||||
extern int uilua_layout_row(lua_State *s);
|
||||
extern int uilua_layout_row_begin(lua_State *s);
|
||||
extern int uilua_layout_row_push(lua_State *s);
|
||||
extern int uilua_layout_row_end(lua_State *s);
|
||||
extern int uilua_layout_space_begin(lua_State *s);
|
||||
extern int uilua_layout_space_push(lua_State *s);
|
||||
extern int uilua_layout_space_end(lua_State *s);
|
||||
extern int uilua_layout_space_bounds(lua_State *s);
|
||||
extern int uilua_layout_space_to_screen(lua_State *s);
|
||||
extern int uilua_layout_space_to_local(lua_State *s);
|
||||
extern int uilua_layout_space_rect_to_screen(lua_State *s);
|
||||
extern int uilua_layout_space_rect_to_local(lua_State *s);
|
||||
extern int uilua_layout_ratio_from_pixel(lua_State *s);
|
||||
extern int uilua_group_begin(lua_State *s);
|
||||
extern int uilua_group_end(lua_State *s);
|
||||
extern int uilua_tree_push(lua_State *s);
|
||||
extern int uilua_tree_pop(lua_State *s);
|
||||
extern int uilua_button_set_behavior(lua_State *s);
|
||||
extern int uilua_button_push_behavior(lua_State *s);
|
||||
extern int uilua_button_pop_behavior(lua_State *s);
|
||||
extern int uilua_checkbox(lua_State *s);
|
||||
extern int uilua_radio(lua_State *s);
|
||||
extern int uilua_selectable(lua_State *s);
|
||||
extern int uilua_slider(lua_State *s);
|
||||
extern int uilua_progress(lua_State *s);
|
||||
extern int uilua_color_picker(lua_State *s);
|
||||
extern int uilua_property(lua_State *s);
|
||||
extern int uilua_popup_begin(lua_State *s);
|
||||
extern int uilua_popup_close(lua_State *s);
|
||||
extern int uilua_popup_end(lua_State *s);
|
||||
extern int uilua_combobox(lua_State *s);
|
||||
extern int uilua_combobox_begin(lua_State *s);
|
||||
extern int uilua_combobox_item(lua_State *s);
|
||||
extern int uilua_combobox_close(lua_State *s);
|
||||
extern int uilua_combobox_end(lua_State *s);
|
||||
extern int uilua_contextual_begin(lua_State *s);
|
||||
extern int uilua_contextual_item(lua_State *s);
|
||||
extern int uilua_contextual_close(lua_State *s);
|
||||
extern int uilua_contextual_end(lua_State *s);
|
||||
extern int uilua_tooltip(lua_State *s);
|
||||
extern int uilua_tooltip_begin(lua_State *s);
|
||||
extern int uilua_tooltip_end(lua_State *s);
|
||||
extern int uilua_menubar_begin(lua_State *s);
|
||||
extern int uilua_menubar_end(lua_State *s);
|
||||
extern int uilua_menu_begin(lua_State *s);
|
||||
extern int uilua_menu_item(lua_State *s);
|
||||
extern int uilua_menu_close(lua_State *s);
|
||||
extern int uilua_menu_end(lua_State *s);
|
||||
extern int uilua_spacing(lua_State *s);
|
||||
extern int uilua_style_default(lua_State *s);
|
||||
extern int uilua_style_load_colors(lua_State *s);
|
||||
extern int uilua_style_set_font(lua_State *s);
|
||||
extern int uilua_style_push(lua_State *s);
|
||||
extern int uilua_style_pop(lua_State *s);
|
||||
extern int uilua_style(lua_State *s);
|
||||
extern int uilua_widget_bounds(lua_State *s);
|
||||
extern int uilua_widget_position(lua_State *s);
|
||||
extern int uilua_widget_size(lua_State *s);
|
||||
extern int uilua_widget_width(lua_State *s);
|
||||
extern int uilua_widget_height(lua_State *s);
|
||||
extern int uilua_widget_is_hovered(lua_State *s);
|
||||
|
||||
extern int uilua_window_push(lua_State *s);
|
||||
extern int uilua_window_pop(lua_State *s);
|
||||
extern int uilua_window_root(lua_State *s);
|
||||
extern int uilua_global_alpha(lua_State *s);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "LUAUIApp.h"
|
||||
#include "LUAFunctions.h"
|
||||
#include "core/io/Filesystem.h"
|
||||
#include "core/Log.h"
|
||||
#include "core/command/Command.h"
|
||||
#include "core/Trace.h"
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
LUAUIApp::LUAUIApp(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer) :
|
||||
Super(metric, filesystem, eventBus, timeProvider, texturePool, meshRenderer, textureAtlasRenderer), _lua(false) {
|
||||
}
|
||||
|
||||
LUAUIApp::~LUAUIApp() {
|
||||
}
|
||||
|
||||
core::AppState LUAUIApp::onInit() {
|
||||
const core::AppState state = Super::onInit();
|
||||
|
||||
if (!_texturePool->init()) {
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
const core::String& path = core::string::format("ui/%s.lua", appname().c_str());
|
||||
_uiScriptPath = core::Var::get("ui_script", path)->strVal();
|
||||
|
||||
if (!reload()) {
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
core::Command::registerCommand("ui_reload", [this] (const core::CmdArgs&) {
|
||||
reload();
|
||||
});
|
||||
|
||||
core::Command::registerCommand("ui_pop", [this] (const core::CmdArgs&) {
|
||||
popWindow();
|
||||
});
|
||||
|
||||
core::Command::registerCommand("ui_push", [this] (const core::CmdArgs& args) {
|
||||
for (const auto& a : args) {
|
||||
pushWindow(a);
|
||||
}
|
||||
});
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
core::AppState LUAUIApp::onCleanup() {
|
||||
_texturePool->shutdown();
|
||||
return Super::onCleanup();
|
||||
}
|
||||
|
||||
void LUAUIApp::popup(const core::String& message) {
|
||||
pushWindow("popup", message);
|
||||
}
|
||||
|
||||
bool LUAUIApp::onRenderUI() {
|
||||
core_trace_scoped(LUAAIAppOnRenderUI);
|
||||
|
||||
if (_windowStack.empty() && !_rootWindow.empty()) {
|
||||
pushWindow(_rootWindow);
|
||||
}
|
||||
|
||||
lua_State* state = _lua.state();
|
||||
WindowStack copy(_windowStack);
|
||||
const size_t windowCount = copy.size();
|
||||
const size_t limit = _skipUntilReload >= 0 ? _skipUntilReload : windowCount;
|
||||
for (size_t i = 0u; i < limit; ++i) {
|
||||
const auto& window = copy[i];
|
||||
lua_getglobal(state, window.id.c_str());
|
||||
if (lua_isnil(state, -1)) {
|
||||
Log::error("window: %s: wasn't found", window.id.c_str());
|
||||
_skipUntilReload = i;
|
||||
break;
|
||||
}
|
||||
const int argc = window.parameter.empty() ? 0 : 1;
|
||||
if (argc == 1) {
|
||||
lua_pushstring(state, window.parameter.c_str());
|
||||
}
|
||||
const int ret = lua_pcall(state, argc, 0, 0);
|
||||
if (ret != LUA_OK) {
|
||||
Log::error("window: %s: execution error: %s", window.id.c_str(), lua_tostring(state, -1));
|
||||
_skipUntilReload = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LUAUIApp::setGlobalAlpha(float alpha) {
|
||||
_config.global_alpha = glm::clamp(alpha, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void LUAUIApp::rootWindow(const core::String& id) {
|
||||
popWindow(_windowStack.size());
|
||||
_rootWindow = id;
|
||||
Log::info("Root window %s", _rootWindow.c_str());
|
||||
}
|
||||
|
||||
void LUAUIApp::pushWindow(const core::String& id, const core::String& parameter) {
|
||||
if (id.empty()) {
|
||||
return;
|
||||
}
|
||||
_windowStack.emplace(id, parameter);
|
||||
Log::info("Push window %s", id.c_str());
|
||||
}
|
||||
|
||||
void LUAUIApp::popWindow(int amount) {
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
if (_windowStack.size() == 0) {
|
||||
break;
|
||||
}
|
||||
Log::info("Pop window %s", _windowStack.top().id.c_str());
|
||||
_windowStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
bool LUAUIApp::reload() {
|
||||
const bool console = _console.isActive();
|
||||
if (console) {
|
||||
_console.toggle();
|
||||
}
|
||||
|
||||
core_assert_always(_lua.resetState());
|
||||
|
||||
const luaL_Reg uiFuncs[] = {
|
||||
{"globalAlpha", uilua_global_alpha},
|
||||
{"rootWindow", uilua_window_root},
|
||||
{"windowPush", uilua_window_push},
|
||||
{"windowPop", uilua_window_pop},
|
||||
|
||||
{"windowBegin", uilua_window_begin},
|
||||
{"windowEnd", uilua_window_end},
|
||||
{"getWindowBounds", uilua_window_get_bounds},
|
||||
{"getWindowPos", uilua_window_get_position},
|
||||
{"getWindowSize", uilua_window_get_size},
|
||||
{"getWindowContentRegion", uilua_window_get_content_region},
|
||||
|
||||
{"model", uilua_model},
|
||||
{"edit", uilua_edit},
|
||||
{"text", uilua_text},
|
||||
{"label", uilua_label},
|
||||
{"image", uilua_image},
|
||||
{"checkbox", uilua_checkbox},
|
||||
{"radio", uilua_radio},
|
||||
{"selectable", uilua_selectable},
|
||||
{"slider", uilua_slider},
|
||||
{"progress", uilua_progress},
|
||||
{"colorpicker", uilua_color_picker},
|
||||
{"property", uilua_property},
|
||||
|
||||
{"button", uilua_button},
|
||||
{"buttonSetBehaviour", uilua_button_set_behavior},
|
||||
{"buttonPushBehaviour", uilua_button_push_behavior},
|
||||
{"buttonPopBehaviour", uilua_button_pop_behavior},
|
||||
|
||||
{"hasWindowFocus", uilua_window_has_focus},
|
||||
{"isWindowCollapsed", uilua_window_is_collapsed},
|
||||
{"isWindowHidden", uilua_window_is_hidden},
|
||||
{"isWindowActive", uilua_window_is_active},
|
||||
{"isWindowHovered", uilua_window_is_hovered},
|
||||
{"isAnyWindowHovered", uilua_window_is_any_hovered},
|
||||
{"isAnythingActive", uilua_item_is_any_active},
|
||||
{"setWindowBounds", uilua_window_set_bounds},
|
||||
{"setWindowPosition", uilua_window_set_position},
|
||||
{"setWindowSize", uilua_window_set_size},
|
||||
{"setWindowFocus", uilua_window_set_focus},
|
||||
{"windowClose", uilua_window_close},
|
||||
{"windowCollapse", uilua_window_collapse},
|
||||
{"windowExpand", uilua_window_expand},
|
||||
{"windowShow", uilua_window_show},
|
||||
{"windowHide", uilua_window_hide},
|
||||
|
||||
{"layoutRow", uilua_layout_row},
|
||||
{"layoutRowBegin", uilua_layout_row_begin},
|
||||
{"layoutRowPush", uilua_layout_row_push},
|
||||
{"layoutRowEnd", uilua_layout_row_end},
|
||||
{"layoutSpaceBegin", uilua_layout_space_begin},
|
||||
{"layoutSpacePush", uilua_layout_space_push},
|
||||
{"layoutSpaceEnd", uilua_layout_space_end},
|
||||
{"getLayoutSpaceBounds", uilua_layout_space_bounds},
|
||||
{"layoutSpaceToScreen", uilua_layout_space_to_screen},
|
||||
{"layoutSpaceToLocal", uilua_layout_space_to_local},
|
||||
{"layoutSpaceRectToScreen", uilua_layout_space_rect_to_screen},
|
||||
{"layoutSpaceRectToLocal", uilua_layout_space_rect_to_local},
|
||||
{"layoutSpaceRatioFromPixel", uilua_layout_ratio_from_pixel},
|
||||
|
||||
{"groupBegin", uilua_group_begin},
|
||||
{"groupEnd", uilua_group_end},
|
||||
|
||||
{"treePush", uilua_tree_push},
|
||||
{"treePop", uilua_tree_pop},
|
||||
|
||||
{"popupBegin", uilua_popup_begin},
|
||||
{"popupClose", uilua_popup_close},
|
||||
{"popupEnd", uilua_popup_end},
|
||||
|
||||
{"combobox", uilua_combobox},
|
||||
{"comboboxBegin", uilua_combobox_begin},
|
||||
{"comboboxItem", uilua_combobox_item},
|
||||
{"comboboxClose", uilua_combobox_close},
|
||||
{"comboboxEnd", uilua_combobox_end},
|
||||
|
||||
{"contextualBegin", uilua_contextual_begin},
|
||||
{"contextualItem", uilua_contextual_item},
|
||||
{"contextualClose", uilua_contextual_close},
|
||||
{"contextualEnd", uilua_contextual_end},
|
||||
|
||||
{"tooltip", uilua_tooltip},
|
||||
{"tooltipBegin", uilua_tooltip_begin},
|
||||
{"tooltipEnd", uilua_tooltip_end},
|
||||
|
||||
{"menubarBegin", uilua_menubar_begin},
|
||||
{"menubarEnd", uilua_menubar_end},
|
||||
|
||||
{"menuBegin", uilua_menu_begin},
|
||||
{"menuItem", uilua_menu_item},
|
||||
{"menuClose", uilua_menu_close},
|
||||
{"menuEnd", uilua_menu_end},
|
||||
|
||||
{"styleDefault", uilua_style_default},
|
||||
{"styleLoadColors", uilua_style_load_colors},
|
||||
{"styleSetFont", uilua_style_set_font},
|
||||
{"stylePush", uilua_style_push},
|
||||
{"stylePop", uilua_style_pop},
|
||||
{"style", uilua_style},
|
||||
|
||||
{"getWidgetBounds", uilua_widget_bounds},
|
||||
{"getWidgetPosition", uilua_widget_position},
|
||||
{"getWidgetSize", uilua_widget_size},
|
||||
{"getWidgetWidth", uilua_widget_width},
|
||||
{"getWidgetHeight", uilua_widget_height},
|
||||
{"isWidgetHovered", uilua_widget_is_hovered},
|
||||
|
||||
{"spacing", uilua_spacing},
|
||||
|
||||
{"scissor", uilua_push_scissor},
|
||||
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
_lua.newGlobalData<struct nk_context>("context", &_ctx);
|
||||
_lua.newGlobalData<struct nkc_context>("ccontext", &_cctx);
|
||||
_lua.newGlobalData<LUAUIApp>("app", this);
|
||||
_lua.newGlobalData<video::TexturePool>("texturepool", _texturePool.get());
|
||||
_lua.reg("ui", uiFuncs);
|
||||
|
||||
lua_newtable(_lua.state());
|
||||
lua_setglobal(_lua.state(), "stack");
|
||||
lua_pop(_lua.state(), 1);
|
||||
|
||||
clua_vecregister<glm::vec2>(_lua.state());
|
||||
clua_vecregister<glm::vec3>(_lua.state());
|
||||
clua_vecregister<glm::vec4>(_lua.state());
|
||||
clua_vecregister<glm::ivec2>(_lua.state());
|
||||
clua_vecregister<glm::ivec3>(_lua.state());
|
||||
clua_vecregister<glm::ivec4>(_lua.state());
|
||||
clua_cmdregister(_lua.state());
|
||||
clua_varregister(_lua.state());
|
||||
clua_logregister(_lua.state());
|
||||
configureLUA(_lua);
|
||||
|
||||
const io::FilesystemPtr& fs = filesystem();
|
||||
const core::String& luaScript = fs->load(_uiScriptPath);
|
||||
if (luaScript.empty()) {
|
||||
Log::error("Could not load ui script from '%s'", _uiScriptPath.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!_lua.load(luaScript)) {
|
||||
Log::error("Could not execute lua script from '%s': %s", _uiScriptPath.c_str(), _lua.error().c_str());
|
||||
return false;
|
||||
}
|
||||
_skipUntilReload = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NuklearApp.h"
|
||||
#include "commonlua/LUA.h"
|
||||
#include "video/TexturePool.h"
|
||||
#include "core/collection/Stack.h"
|
||||
#include "core/String.h"
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
/**
|
||||
* @brief The lua UI application is using a lua script with in @code ui/$appname$.lua @endcode to assemble the
|
||||
* UI. This script is automatically reloaded if it is changed in the filesystem.
|
||||
* @ingroup UI
|
||||
*/
|
||||
class LUAUIApp : public NuklearApp {
|
||||
private:
|
||||
using Super = NuklearApp;
|
||||
protected:
|
||||
lua::LUA _lua;
|
||||
int _skipUntilReload = -1;
|
||||
struct WindowStackElement {
|
||||
inline WindowStackElement(const core::String& _id = "", const core::String& _parameter = "") :
|
||||
id(_id), parameter(_parameter) {
|
||||
}
|
||||
core::String id; /**< lua function name */
|
||||
core::String parameter; /**< optional lua function parameter */
|
||||
};
|
||||
using WindowStack = core::Stack<WindowStackElement, 64>;
|
||||
WindowStack _windowStack;
|
||||
core::String _rootWindow;
|
||||
core::String _uiScriptPath;
|
||||
|
||||
virtual void configureLUA(lua::LUA&) {}
|
||||
public:
|
||||
LUAUIApp(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer);
|
||||
virtual ~LUAUIApp();
|
||||
|
||||
void rootWindow(const core::String& id);
|
||||
void pushWindow(const core::String& id, const core::String& parameter = "");
|
||||
void popWindow(int amount = 1);
|
||||
void popup(const core::String& message);
|
||||
void setGlobalAlpha(float alpha);
|
||||
|
||||
core::AppState onInit() override;
|
||||
core::AppState onCleanup() override;
|
||||
|
||||
bool reload();
|
||||
bool onRenderUI() override;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "core/Assert.h"
|
||||
#include "core/Common.h"
|
||||
#include <SDL_stdinc.h>
|
||||
|
||||
#define NK_INCLUDE_FIXED_TYPES
|
||||
#define NK_INCLUDE_STANDARD_IO
|
||||
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
|
||||
#define NK_INCLUDE_FONT_BAKING
|
||||
#define NK_INCLUDE_STANDARD_VARARGS
|
||||
#define NK_INCLUDE_DEFAULT_FONT
|
||||
#define NK_ASSERT core_assert
|
||||
#define STBTT_malloc(x,u) core_malloc(x)
|
||||
#define STBTT_free(x,u) core_free(x)
|
||||
#define NK_MEMSET core_memset
|
||||
#define NK_MEMCPY core_memcpy
|
||||
#define NK_SQRT
|
||||
|
||||
/**
|
||||
* @addtogroup UI
|
||||
* @{
|
||||
*/
|
||||
#include "private/nuklear.h"
|
||||
/**
|
||||
* @}
|
||||
*/
|
|
@ -0,0 +1,526 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "NuklearApp.h"
|
||||
#include "video/Renderer.h"
|
||||
#include "video/ScopedViewPort.h"
|
||||
#include "voxelrender/CachedMeshRenderer.h"
|
||||
#include "video/TexturePool.h"
|
||||
#include "video/TextureAtlasRenderer.h"
|
||||
#include "core/UTF8.h"
|
||||
#include "core/Log.h"
|
||||
#include "core/Color.h"
|
||||
#include "core/io/Filesystem.h"
|
||||
#include "core/Assert.h"
|
||||
#include "voxel/MaterialColor.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
static const int MAX_VERTEX_MEMORY = 32768 * sizeof(NuklearApp::Vertex);
|
||||
static const int MAX_ELEMENT_MEMORY = 65536;
|
||||
|
||||
NuklearApp::NuklearApp(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer) :
|
||||
Super(metric, filesystem, eventBus, timeProvider), _console(&_ctx),
|
||||
_camera(video::CameraType::FirstPerson, video::CameraMode::Orthogonal),
|
||||
_texturePool(texturePool), _meshRenderer(meshRenderer), _textureAtlasRenderer(textureAtlasRenderer) {
|
||||
_cctx.ctx = &_ctx;
|
||||
_cctx.meshRenderer = _meshRenderer;
|
||||
_cctx.textureAtlasRenderer = _textureAtlasRenderer;
|
||||
}
|
||||
|
||||
NuklearApp::~NuklearApp() {
|
||||
}
|
||||
|
||||
bool NuklearApp::onMouseWheel(int32_t x, int32_t y) {
|
||||
if (_console.onMouseWheel(x, y)) {
|
||||
return true;
|
||||
}
|
||||
if (Super::onMouseWheel(x, y)) {
|
||||
return true;
|
||||
}
|
||||
nk_input_scroll(&_ctx, nk_vec2((float) x, (float) y));
|
||||
return true;
|
||||
}
|
||||
|
||||
void NuklearApp::onMouseMotion(int32_t x, int32_t y, int32_t relX, int32_t relY) {
|
||||
if (_ctx.input.mouse.grabbed) {
|
||||
const int x = (int) _ctx.input.mouse.prev.x;
|
||||
const int y = (int) _ctx.input.mouse.prev.y;
|
||||
nk_input_motion(&_ctx, x + relX, y + relY);
|
||||
} else {
|
||||
nk_input_motion(&_ctx, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void NuklearApp::onMouseButtonPress(int32_t x, int32_t y, uint8_t button, uint8_t clicks) {
|
||||
if (_console.onMouseButtonPress(x, y, button)) {
|
||||
return;
|
||||
}
|
||||
if (button == SDL_BUTTON_LEFT) {
|
||||
if (clicks > 1) {
|
||||
nk_input_button(&_ctx, NK_BUTTON_DOUBLE, x, y, true);
|
||||
}
|
||||
nk_input_button(&_ctx, NK_BUTTON_LEFT, x, y, true);
|
||||
} else if (button == SDL_BUTTON_MIDDLE) {
|
||||
nk_input_button(&_ctx, NK_BUTTON_MIDDLE, x, y, true);
|
||||
} else if (button == SDL_BUTTON_RIGHT) {
|
||||
nk_input_button(&_ctx, NK_BUTTON_RIGHT, x, y, true);
|
||||
}
|
||||
}
|
||||
|
||||
void NuklearApp::onMouseButtonRelease(int32_t x, int32_t y, uint8_t button) {
|
||||
if (_console.isActive()) {
|
||||
return;
|
||||
}
|
||||
if (button == SDL_BUTTON_LEFT) {
|
||||
nk_input_button(&_ctx, NK_BUTTON_LEFT, x, y, false);
|
||||
} else if (button == SDL_BUTTON_MIDDLE) {
|
||||
nk_input_button(&_ctx, NK_BUTTON_MIDDLE, x, y, false);
|
||||
} else if (button == SDL_BUTTON_RIGHT) {
|
||||
nk_input_button(&_ctx, NK_BUTTON_RIGHT, x, y, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool NuklearApp::onTextInput(const core::String& text) {
|
||||
if (_console.onTextInput(text)) {
|
||||
return true;
|
||||
}
|
||||
const char *c = text.c_str();
|
||||
for (;;) {
|
||||
const int key = core::utf8::next(&c);
|
||||
if (key == -1) {
|
||||
return true;
|
||||
}
|
||||
nk_glyph glyph;
|
||||
memcpy(glyph, &key, NK_UTF_SIZE);
|
||||
nk_input_glyph(&_ctx, glyph);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NuklearApp::onKeyEvent(int32_t sym, int16_t modifier, bool down) {
|
||||
bool ctrl = (modifier & KMOD_CTRL) != 0;
|
||||
if (sym == SDLK_RSHIFT || sym == SDLK_LSHIFT) {
|
||||
nk_input_key(&_ctx, NK_KEY_SHIFT, down);
|
||||
} else if (sym == SDLK_DELETE) {
|
||||
nk_input_key(&_ctx, NK_KEY_DEL, down);
|
||||
} else if (sym == SDLK_RETURN) {
|
||||
nk_input_key(&_ctx, NK_KEY_ENTER, down);
|
||||
} else if (sym == SDLK_TAB) {
|
||||
nk_input_key(&_ctx, NK_KEY_TAB, down);
|
||||
} else if (sym == SDLK_BACKSPACE) {
|
||||
nk_input_key(&_ctx, NK_KEY_BACKSPACE, down);
|
||||
} else if (sym == SDLK_HOME) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_START, down);
|
||||
nk_input_key(&_ctx, NK_KEY_SCROLL_START, down);
|
||||
} else if (sym == SDLK_END) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_END, down);
|
||||
nk_input_key(&_ctx, NK_KEY_SCROLL_END, down);
|
||||
} else if (sym == SDLK_PAGEDOWN) {
|
||||
nk_input_key(&_ctx, NK_KEY_SCROLL_DOWN, down);
|
||||
} else if (sym == SDLK_PAGEUP) {
|
||||
nk_input_key(&_ctx, NK_KEY_SCROLL_UP, down);
|
||||
} else if (sym == SDLK_z) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_UNDO, down && ctrl);
|
||||
} else if (sym == SDLK_r) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_REDO, down && ctrl);
|
||||
} else if (sym == SDLK_c) {
|
||||
nk_input_key(&_ctx, NK_KEY_COPY, down && ctrl);
|
||||
} else if (sym == SDLK_v) {
|
||||
nk_input_key(&_ctx, NK_KEY_PASTE, down && ctrl);
|
||||
} else if (sym == SDLK_x) {
|
||||
nk_input_key(&_ctx, NK_KEY_CUT, down && ctrl);
|
||||
} else if (sym == SDLK_b) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_LINE_START, down && ctrl);
|
||||
} else if (sym == SDLK_e) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_LINE_END, down && ctrl);
|
||||
} else if (sym == SDLK_UP) {
|
||||
nk_input_key(&_ctx, NK_KEY_UP, down);
|
||||
} else if (sym == SDLK_DOWN) {
|
||||
nk_input_key(&_ctx, NK_KEY_DOWN, down);
|
||||
} else if (sym == SDLK_LEFT) {
|
||||
if (ctrl) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_WORD_LEFT, down);
|
||||
} else {
|
||||
nk_input_key(&_ctx, NK_KEY_LEFT, down);
|
||||
}
|
||||
} else if (sym == SDLK_RIGHT) {
|
||||
if (ctrl) {
|
||||
nk_input_key(&_ctx, NK_KEY_TEXT_WORD_RIGHT, down);
|
||||
} else {
|
||||
nk_input_key(&_ctx, NK_KEY_RIGHT, down);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NuklearApp::onKeyPress(int32_t key, int16_t modifier) {
|
||||
if (_console.onKeyPress(key, modifier)) {
|
||||
return true;
|
||||
}
|
||||
if (Super::onKeyPress(key, modifier)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return onKeyEvent(key, modifier, true);
|
||||
}
|
||||
|
||||
bool NuklearApp::onKeyRelease(int32_t key, int16_t modifier) {
|
||||
if (_console.isActive()) {
|
||||
return true;
|
||||
}
|
||||
if (Super::onKeyRelease(key, modifier)) {
|
||||
return true;
|
||||
}
|
||||
return onKeyEvent(key, modifier, false);
|
||||
}
|
||||
|
||||
static void nk_sdl_clipbard_paste(nk_handle usr, struct nk_text_edit *edit) {
|
||||
if (!SDL_HasClipboardText()) {
|
||||
return;
|
||||
}
|
||||
const char *text = SDL_GetClipboardText();
|
||||
nk_textedit_paste(edit, text, nk_strlen(text));
|
||||
}
|
||||
|
||||
static void nk_sdl_clipbard_copy(nk_handle usr, const char *text, int len) {
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
char* str = new char[len + 1];
|
||||
memcpy(str, text, (size_t) len);
|
||||
str[len] = '\0';
|
||||
SDL_SetClipboardText(str);
|
||||
delete[] str;
|
||||
}
|
||||
|
||||
static void* nk_core_alloc(nk_handle, void *, nk_size size) {
|
||||
return core_malloc(size);
|
||||
}
|
||||
|
||||
static void nk_core_free(nk_handle, void *old) {
|
||||
return core_free(old);
|
||||
}
|
||||
|
||||
struct nk_font *NuklearApp::loadFontFile(const char *filename, int fontSize) {
|
||||
const io::FilePtr& file = filesystem()->open(filename, io::FileMode::Read);
|
||||
if (!file) {
|
||||
Log::warn("Can't load font. Could not open '%s'", filename);
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t *fontData = nullptr;
|
||||
const size_t fontDataSize = file->read((void **) &fontData);
|
||||
if (fontDataSize == 0) {
|
||||
Log::warn("Can't load font. Could not read '%s'", filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct nk_font *font = nk_font_atlas_add_from_memory(&_atlas, fontData, fontDataSize, fontSize, nullptr);
|
||||
delete[] fontData;
|
||||
return font;
|
||||
}
|
||||
|
||||
struct nk_image NuklearApp::loadImageFile(const char *filename) {
|
||||
video::TexturePtr tex = _texturePool->load(filename);
|
||||
if (!tex) {
|
||||
Log::warn("Could not load image: '%s'", filename);
|
||||
tex = _emptyTexture;
|
||||
}
|
||||
const video::Id handle = tex->handle();
|
||||
const int width = tex->width();
|
||||
const int height = tex->height();
|
||||
struct nk_image image;
|
||||
image.handle = nk_handle_id(handle);
|
||||
image.w = width;
|
||||
image.h = height;
|
||||
image.region[0] = 0;
|
||||
image.region[1] = 0;
|
||||
image.region[2] = image.region[0] + width;
|
||||
image.region[3] = image.region[1] + height;
|
||||
return image;
|
||||
}
|
||||
|
||||
core::AppState NuklearApp::onInit() {
|
||||
const core::AppState state = Super::onInit();
|
||||
SDL_StartTextInput();
|
||||
showCursor(false);
|
||||
centerMousePosition();
|
||||
video::checkError();
|
||||
if (state != core::AppState::Running) {
|
||||
return state;
|
||||
}
|
||||
|
||||
_emptyTexture = video::createEmptyTexture("**empty**");
|
||||
_fontTexture = video::createEmptyTexture("**font**");
|
||||
|
||||
struct nk_allocator alloc;
|
||||
alloc.userdata.ptr = nullptr;
|
||||
alloc.alloc = nk_core_alloc;
|
||||
alloc.free = nk_core_free;
|
||||
if (nk_init(&_ctx, &alloc, nullptr) == 0) {
|
||||
Log::error("Could not init the ui");
|
||||
return core::AppState::InitFailure;
|
||||
};
|
||||
_ctx.clip.copy = nk_sdl_clipbard_copy;
|
||||
_ctx.clip.paste = nk_sdl_clipbard_paste;
|
||||
_ctx.clip.userdata = nk_handle_ptr(this);
|
||||
nk_font_atlas_init(&_atlas, &alloc);
|
||||
nk_font_atlas_begin(&_atlas);
|
||||
nk_buffer_init(&_cmds, &alloc, 4096);
|
||||
|
||||
uint8_t *fontData = nullptr; // raw ttf data
|
||||
size_t fontDataSize = 0u;
|
||||
const io::FilePtr& file = filesystem()->open("font.ttf", io::FileMode::Read);
|
||||
if (file) {
|
||||
fontDataSize = file->read((void **) &fontData);
|
||||
} else {
|
||||
Log::warn("Failed to load font.ttf");
|
||||
}
|
||||
|
||||
for (int i = 0; i < lengthof(_fonts); ++i) {
|
||||
const int fontSize = glm::round(_fontSizes[i] * _dpiVerticalFactor);
|
||||
if (fontDataSize > 0u) {
|
||||
_fonts[i] = nk_font_atlas_add_from_memory(&_atlas, fontData, fontDataSize, fontSize, nullptr);
|
||||
}
|
||||
if (_fonts[i] == nullptr) {
|
||||
_fonts[i] = nk_font_atlas_add_default(&_atlas, fontSize, nullptr);
|
||||
}
|
||||
}
|
||||
delete[] fontData;
|
||||
|
||||
initUIFonts();
|
||||
|
||||
int w, h;
|
||||
const void *image = nk_font_atlas_bake(&_atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
|
||||
_fontTexture->upload(w, h, (const uint8_t*) image);
|
||||
nk_font_atlas_end(&_atlas, nk_handle_id((int)_fontTexture->handle()), &_null);
|
||||
nk_style_load_all_cursors(&_ctx, _atlas.cursors);
|
||||
|
||||
if (_atlas.default_font) {
|
||||
nk_style_set_font(&_ctx, &_atlas.default_font->handle);
|
||||
} else if (_fonts[0]) {
|
||||
nk_style_set_font(&_ctx, &_fonts[0]->handle);
|
||||
}
|
||||
|
||||
if (!_shader.setup()) {
|
||||
Log::error("Could not load the ui shader");
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
if (!voxel::initDefaultMaterialColors()) {
|
||||
Log::error("Failed to initialize the material colors");
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
if (!_meshRenderer->init()) {
|
||||
Log::error("Could not initialize the mesh renderer");
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
if (!_textureAtlasRenderer->init()) {
|
||||
Log::error("Could not initialize the texture atlas renderer");
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
_vertexBufferIndex = _vbo.create();
|
||||
if (_vertexBufferIndex < 0) {
|
||||
Log::error("Failed to create ui vbo");
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
_vbo.setMode(_vertexBufferIndex, video::BufferMode::Stream);
|
||||
|
||||
_elementBufferIndex = _vbo.create(nullptr, 0, video::BufferType::IndexBuffer);
|
||||
if (_elementBufferIndex < 0) {
|
||||
Log::error("Failed to create ui ibo");
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
_camera = video::uiCamera(glm::ivec2(0), frameBufferDimension(), windowDimension());
|
||||
|
||||
_vbo.addAttribute(_shader.getColorAttribute(_vertexBufferIndex, &Vertex::r, true));
|
||||
_vbo.addAttribute(_shader.getTexcoordAttribute(_vertexBufferIndex, &Vertex::u));
|
||||
_vbo.addAttribute(_shader.getPosAttribute(_vertexBufferIndex, &Vertex::x));
|
||||
|
||||
if (!_vbo.update(_vertexBufferIndex, nullptr, MAX_VERTEX_MEMORY)) {
|
||||
Log::error("Failed to upload vertex buffer data with %i bytes", MAX_VERTEX_MEMORY);
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
if (!_vbo.update(_elementBufferIndex, nullptr, MAX_ELEMENT_MEMORY)) {
|
||||
Log::error("Failed to upload index buffer data with %i bytes", MAX_ELEMENT_MEMORY);
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
static const struct nk_draw_vertex_layout_element vertexLayout[] = {
|
||||
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(Vertex, x)},
|
||||
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(Vertex, u)},
|
||||
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(Vertex, r)},
|
||||
{NK_VERTEX_LAYOUT_END}
|
||||
};
|
||||
core_memset(&_config, 0, sizeof(_config));
|
||||
_config.vertex_layout = vertexLayout;
|
||||
_config.vertex_size = sizeof(Vertex);
|
||||
_config.vertex_alignment = NK_ALIGNOF(Vertex);
|
||||
initUIConfig(_config);
|
||||
initUISkin();
|
||||
|
||||
if (!_console.init()) {
|
||||
Log::error("Failed to initialize the console");
|
||||
return core::AppState::InitFailure;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void NuklearApp::initUISkin() {
|
||||
}
|
||||
|
||||
void NuklearApp::initUIConfig(struct nk_convert_config& config) {
|
||||
config.null = _null;
|
||||
config.circle_segment_count = 22;
|
||||
config.curve_segment_count = 22;
|
||||
config.arc_segment_count = 22;
|
||||
config.global_alpha = 1.0f;
|
||||
config.shape_AA = NK_ANTI_ALIASING_ON;
|
||||
config.line_AA = NK_ANTI_ALIASING_ON;
|
||||
}
|
||||
|
||||
void NuklearApp::onWindowResize(int windowWidth, int windowHeight) {
|
||||
Super::onWindowResize(windowWidth, windowHeight);
|
||||
_camera.init(glm::zero<glm::ivec2>(), frameBufferDimension(), windowDimension());
|
||||
}
|
||||
|
||||
core::AppState NuklearApp::onConstruct() {
|
||||
const core::AppState state = Super::onConstruct();
|
||||
_console.construct();
|
||||
return state;
|
||||
}
|
||||
|
||||
struct nk_font* NuklearApp::font(int size) {
|
||||
int bestDelta = 10000;
|
||||
int fontIndex = 0;
|
||||
for (int i = 0; i < lengthof(_fonts); ++i) {
|
||||
if (_fontSizes[i] == size) {
|
||||
return _fonts[i];
|
||||
}
|
||||
const int delta = _fontSizes[i] - size;
|
||||
if (delta < bestDelta) {
|
||||
fontIndex = i;
|
||||
}
|
||||
}
|
||||
return _fonts[fontIndex];
|
||||
}
|
||||
|
||||
core::AppState NuklearApp::onRunning() {
|
||||
_console.update(_deltaFrameMillis);
|
||||
|
||||
beforeUI();
|
||||
|
||||
nk_input_begin(&_ctx);
|
||||
core::AppState state = Super::onRunning();
|
||||
nk_input_motion(&_ctx, _mousePos.x, _mousePos.y);
|
||||
nk_input_end(&_ctx);
|
||||
|
||||
if (!onRenderUI()) {
|
||||
if (_ctx.current) {
|
||||
nk_end(&_ctx);
|
||||
}
|
||||
nk_clear(&_ctx);
|
||||
return state;
|
||||
}
|
||||
|
||||
const math::Rect<int> rect(0, 0, _frameBufferDimension.x, _frameBufferDimension.y);
|
||||
_console.render(rect, _deltaFrameMillis);
|
||||
|
||||
video::ScopedShader scopedShader(_shader);
|
||||
_shader.setViewprojection(_camera.projectionMatrix());
|
||||
_shader.setModel(glm::mat4(1.0f));
|
||||
_shader.setTexture(video::TextureUnit::Zero);
|
||||
|
||||
video::ScopedViewPort scopedViewPort(0, 0, _frameBufferDimension.x, _frameBufferDimension.y);
|
||||
video::enable(video::State::Blend);
|
||||
video::blendEquation(video::BlendEquation::Add);
|
||||
video::blendFunc(video::BlendMode::SourceAlpha, video::BlendMode::OneMinusSourceAlpha);
|
||||
video::disable(video::State::CullFace);
|
||||
video::disable(video::State::DepthTest);
|
||||
video::enable(video::State::Scissor);
|
||||
|
||||
void *vertices = _vbo.mapData(_vertexBufferIndex, video::AccessMode::Write);
|
||||
if (vertices == nullptr) {
|
||||
Log::warn("Failed to map vertices");
|
||||
return core::AppState::Cleanup;
|
||||
}
|
||||
void *elements = _vbo.mapData(_elementBufferIndex, video::AccessMode::Write);
|
||||
if (elements == nullptr) {
|
||||
Log::warn("Failed to map indices");
|
||||
return core::AppState::Cleanup;
|
||||
}
|
||||
|
||||
struct nk_buffer vbuf;
|
||||
struct nk_buffer ebuf;
|
||||
nk_buffer_init_fixed(&vbuf, vertices, (nk_size) MAX_VERTEX_MEMORY);
|
||||
nk_buffer_init_fixed(&ebuf, elements, (nk_size) MAX_ELEMENT_MEMORY);
|
||||
|
||||
const bool convertRes = nk_convert(&_ctx, &_cmds, &vbuf, &ebuf, &_config) == NK_CONVERT_SUCCESS;
|
||||
|
||||
Log::trace("vertices buffer size: %i", (int)vbuf.size);
|
||||
Log::trace("index buffer size: %i", (int)ebuf.size);
|
||||
|
||||
_vbo.unmapData(_vertexBufferIndex);
|
||||
_vbo.unmapData(_elementBufferIndex);
|
||||
|
||||
if (convertRes) {
|
||||
const nk_draw_index *offset = nullptr;
|
||||
const struct nk_draw_command *cmd;
|
||||
nk_draw_foreach(cmd, &_ctx, &_cmds) {
|
||||
if (!cmd->elem_count) {
|
||||
continue;
|
||||
}
|
||||
video::bindTexture(video::TextureUnit::Zero, video::TextureType::Texture2D, cmd->texture.id);
|
||||
video::scissor(cmd->clip_rect.x, cmd->clip_rect.y, cmd->clip_rect.w, cmd->clip_rect.h);
|
||||
video::drawElements(video::Primitive::Triangles, (size_t) cmd->elem_count, video::mapType<nk_draw_index>(), (void*) offset);
|
||||
offset += cmd->elem_count;
|
||||
}
|
||||
} else {
|
||||
Log::warn("Could not convert draw command to vbo data");
|
||||
}
|
||||
|
||||
_vbo.unbind();
|
||||
|
||||
nk_clear(&_ctx);
|
||||
return state;
|
||||
}
|
||||
|
||||
core::AppState NuklearApp::onCleanup() {
|
||||
nk_font_atlas_clear(&_atlas);
|
||||
nk_buffer_free(&_cmds);
|
||||
nk_free(&_ctx);
|
||||
|
||||
_console.shutdown();
|
||||
_shader.shutdown();
|
||||
_meshRenderer->shutdown();
|
||||
_textureAtlasRenderer->shutdown();
|
||||
_vbo.shutdown();
|
||||
if (_emptyTexture) {
|
||||
_emptyTexture->shutdown();
|
||||
}
|
||||
if (_fontTexture) {
|
||||
_fontTexture->shutdown();
|
||||
}
|
||||
|
||||
return Super::onCleanup();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video/WindowedApp.h"
|
||||
#include "video/Camera.h"
|
||||
#include "video/Buffer.h"
|
||||
#include "video/Texture.h"
|
||||
#include "Console.h"
|
||||
#include "RenderShaders.h"
|
||||
#include "NuklearNode.h"
|
||||
#include <memory>
|
||||
|
||||
namespace video {
|
||||
class TexturePool;
|
||||
using TexturePoolPtr = std::shared_ptr<TexturePool>;
|
||||
|
||||
class TextureAtlasRenderer;
|
||||
using TextureAtlasRendererPtr = core::SharedPtr<TextureAtlasRenderer>;
|
||||
}
|
||||
|
||||
namespace voxelrender {
|
||||
class CachedMeshRenderer;
|
||||
using CachedMeshRendererPtr = core::SharedPtr<CachedMeshRenderer>;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
/**
|
||||
* @ingroup UI
|
||||
*/
|
||||
class NuklearApp: public video::WindowedApp {
|
||||
private:
|
||||
using Super = video::WindowedApp;
|
||||
public:
|
||||
struct Vertex {
|
||||
float x, y;
|
||||
float u, v;
|
||||
union {
|
||||
struct { uint8_t r, g, b, a; };
|
||||
uint32_t col;
|
||||
};
|
||||
};
|
||||
protected:
|
||||
struct nk_context _ctx;
|
||||
struct nkc_context _cctx;
|
||||
struct nk_font_atlas _atlas;
|
||||
struct nk_draw_null_texture _null;
|
||||
struct nk_buffer _cmds;
|
||||
struct nk_convert_config _config;
|
||||
|
||||
// first is the default font size
|
||||
enum {
|
||||
FONT_22, FONT_16, FONT_30, FONT_40, FONT_MAX
|
||||
};
|
||||
static constexpr int _fontSizes[FONT_MAX] { 16, 22, 30, 40 };
|
||||
struct nk_font *_fonts[FONT_MAX];
|
||||
|
||||
Console _console;
|
||||
|
||||
shader::TextureShader _shader;
|
||||
video::Camera _camera;
|
||||
video::Buffer _vbo;
|
||||
video::TexturePtr _fontTexture;
|
||||
video::TexturePtr _emptyTexture;
|
||||
video::TexturePoolPtr _texturePool;
|
||||
voxelrender::CachedMeshRendererPtr _meshRenderer;
|
||||
video::TextureAtlasRendererPtr _textureAtlasRenderer;
|
||||
|
||||
int32_t _vertexBufferIndex = -1;
|
||||
int32_t _elementBufferIndex = -1;
|
||||
|
||||
int loadModelFile(const char *filename);
|
||||
struct nk_font *loadFontFile(const char *filename, int fontSize);
|
||||
struct nk_image loadImageFile(const char *filename);
|
||||
|
||||
bool onKeyEvent(int32_t sym, int16_t modifier, bool down);
|
||||
|
||||
virtual bool onKeyRelease(int32_t key, int16_t modifier) override;
|
||||
virtual bool onKeyPress(int32_t key, int16_t modifier) override;
|
||||
virtual bool onTextInput(const core::String& text) override;
|
||||
virtual bool onMouseWheel(int32_t x, int32_t y) override;
|
||||
virtual void onMouseMotion(int32_t x, int32_t y, int32_t relX, int32_t relY) override;
|
||||
virtual void onMouseButtonPress(int32_t x, int32_t y, uint8_t button, uint8_t clicks) override;
|
||||
virtual void onMouseButtonRelease(int32_t x, int32_t y, uint8_t button) override;
|
||||
|
||||
/**
|
||||
* @brief Fonts are baked into a texture atlas. Your only chance to use
|
||||
* the default atlas is to add your fonts in this method.
|
||||
* @sa initUIConfig()
|
||||
* @sa initUISkin()
|
||||
*/
|
||||
virtual void initUIFonts() {}
|
||||
/**
|
||||
* @brief Hook to change the nuklear config before it is used.
|
||||
* @sa initUIFonts()
|
||||
* @sa initUISkin()
|
||||
*/
|
||||
virtual void initUIConfig(struct nk_convert_config& config);
|
||||
/**
|
||||
* @brief Allows you to modify the ui skin. All fonts are loaded
|
||||
* and baked at this point.
|
||||
* @sa initUIFonts()
|
||||
* @sa initUIConfig()
|
||||
*/
|
||||
virtual void initUISkin();
|
||||
|
||||
virtual void beforeUI() {}
|
||||
virtual bool onRenderUI() = 0;
|
||||
|
||||
public:
|
||||
NuklearApp(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer);
|
||||
virtual ~NuklearApp();
|
||||
|
||||
/**
|
||||
* @brief Find the best matching font for the given size
|
||||
*/
|
||||
struct nk_font* font(int size);
|
||||
|
||||
virtual void onWindowResize(int windowWidth, int windowHeight) override;
|
||||
virtual core::AppState onConstruct() override;
|
||||
virtual core::AppState onInit() override;
|
||||
virtual core::AppState onRunning() override;
|
||||
virtual core::AppState onCleanup() override;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#define NK_IMPLEMENTATION
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "NuklearNode.h"
|
||||
#undef NK_IMPLEMENTATION
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
#include "core/Assert.h"
|
||||
#include "video/Renderer.h"
|
||||
#include "core/Color.h"
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
void nkc_model(struct nkc_context* cctx, struct nkc_model* model) {
|
||||
const int modelId = cctx->meshRenderer->addMesh(model->modelPath);
|
||||
if (modelId == -1) {
|
||||
return;
|
||||
}
|
||||
struct nk_context* ctx = cctx->ctx;
|
||||
core_assert(ctx->current);
|
||||
core_assert(ctx->current->layout);
|
||||
if (!ctx->current || !ctx->current->layout) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct nk_rect bounds;
|
||||
if (!nk_widget(&bounds, ctx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const glm::ivec2 size(bounds.w, bounds.h);
|
||||
model->camera.init(glm::ivec2(0), size, size);
|
||||
model->camera.update();
|
||||
|
||||
const nk_color& c = ctx->style.window.background;
|
||||
const glm::vec4 prevColor = video::currentClearColor();
|
||||
video::clearColor(core::Color::fromRGBA(c.r, c.g, c.b, c.a));
|
||||
|
||||
constexpr glm::mat4 translate(1.0f);
|
||||
constexpr double tau = glm::two_pi<double>();
|
||||
const double orientation = glm::mod(model->omegaY * model->timeSeconds, tau);
|
||||
const glm::mat4 rot = glm::rotate(translate, (float)orientation, glm::up);
|
||||
const glm::mat4 modelMatrix = glm::scale(rot, glm::vec3(model->scale));
|
||||
|
||||
const video::TextureAtlasRendererPtr& atlasRenderer = cctx->textureAtlasRenderer;
|
||||
const video::TextureAtlasData& atlas = atlasRenderer->beginRender(modelId, bounds.w, bounds.h);
|
||||
cctx->meshRenderer->setModelMatrix(modelId, modelMatrix);
|
||||
cctx->meshRenderer->render(modelId, model->camera);
|
||||
atlasRenderer->endRender();
|
||||
video::clearColor(prevColor);
|
||||
|
||||
struct nk_image image;
|
||||
image.handle = nk_handle_id(atlas.handle);
|
||||
image.w = atlas.texWidth;
|
||||
image.h = atlas.texHeight;
|
||||
// TODO: y axis is flipped due to framebuffer y-flipped rendering
|
||||
image.region[0] = atlas.sx * (float)image.w;
|
||||
image.region[1] = atlas.sy * (float)image.h;
|
||||
image.region[2] = (atlas.tx - atlas.sx) * (float)image.w;
|
||||
image.region[3] = (atlas.ty - atlas.sy) * (float)image.h;
|
||||
static constexpr struct nk_color white{255, 255, 255, 255};
|
||||
struct nk_window *win = ctx->current;
|
||||
nk_draw_image(&win->buffer, bounds, &image, white);
|
||||
}
|
||||
|
||||
void nkc_text(struct nkc_context* ctx, const char *string, nk_flags alignment, const struct nk_color &color, const struct nk_user_font *font) {
|
||||
struct nk_rect bounds;
|
||||
nk_panel_alloc_space(&bounds, ctx->ctx);
|
||||
|
||||
const struct nk_style *style = &ctx->ctx->style;
|
||||
struct nk_text nktext;
|
||||
nktext.padding = style->text.padding;
|
||||
nktext.background = style->window.background;
|
||||
nktext.text = color;
|
||||
|
||||
struct nk_window *win = ctx->ctx->current;
|
||||
nk_widget_text(&win->buffer, bounds, string, SDL_strlen(string), &nktext, alignment, font);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "Nuklear.h"
|
||||
#include <stdint.h>
|
||||
#include "video/Camera.h"
|
||||
#include "video/TextureAtlasRenderer.h"
|
||||
#include "voxelrender/CachedMeshRenderer.h"
|
||||
|
||||
namespace ui {
|
||||
namespace nuklear {
|
||||
|
||||
struct nkc_context {
|
||||
nk_context* ctx = nullptr;
|
||||
voxelrender::CachedMeshRendererPtr meshRenderer;
|
||||
video::TextureAtlasRendererPtr textureAtlasRenderer;
|
||||
};
|
||||
|
||||
struct nkc_model {
|
||||
const char* modelPath;
|
||||
double timeSeconds = 0.0f;
|
||||
float scale = 1.0f;
|
||||
float omegaY = 0.0f;
|
||||
video::Camera camera;
|
||||
};
|
||||
|
||||
void nkc_model(struct nkc_context*, struct nkc_model* model);
|
||||
void nkc_text(struct nkc_context* ctx, const char *string, nk_flags alignment, const struct nk_color &color, const struct nk_user_font *font);
|
||||
|
||||
}
|
||||
}
|
|
@ -4,8 +4,10 @@ add_subdirectory(testplane)
|
|||
add_subdirectory(testglslcomp)
|
||||
add_subdirectory(testglslgeom)
|
||||
add_subdirectory(testimgui)
|
||||
add_subdirectory(testnuklear)
|
||||
add_subdirectory(testturbobadger)
|
||||
add_subdirectory(testgpumc)
|
||||
add_subdirectory(testluaui)
|
||||
add_subdirectory(testcamera)
|
||||
add_subdirectory(testoctree)
|
||||
add_subdirectory(testoctreevisit)
|
||||
|
|
|
@ -24,6 +24,10 @@ Test the dearimgui integration
|
|||
|
||||
Test geometry shader integration
|
||||
|
||||
# testnuklear
|
||||
|
||||
Test the nuklear imgui integration
|
||||
|
||||
# testcomputetexture3d
|
||||
|
||||
Test the OpenCL 3d texture integration of a 3d voxel volume (rendered as 2d side view)
|
||||
|
@ -73,6 +77,10 @@ Conversion of OpenCL marching cubes taken from: https://github.com/smistad/GPU-M
|
|||
|
||||
Renders the turbobadger demo.
|
||||
|
||||
# testluaui
|
||||
|
||||
Test the nuklear lua ui binding
|
||||
|
||||
# testoctreevisit
|
||||
|
||||
Visit the frustum in the octree.
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
project(testluaui)
|
||||
set(SRCS
|
||||
TestLUAUI.h TestLUAUI.cpp
|
||||
)
|
||||
set(LUA_SRCS
|
||||
ui/${PROJECT_NAME}.lua
|
||||
)
|
||||
set(FILES
|
||||
voxel/assets/north-dir.vox
|
||||
)
|
||||
|
||||
engine_add_executable(TARGET ${PROJECT_NAME} SRCS ${SRCS} WINDOWED FILES ${FILES} LUA_SRCS ${LUA_SRCS} NOINSTALL)
|
||||
engine_target_link_libraries(TARGET ${PROJECT_NAME} DEPENDENCIES nuklear)
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
#include "TestLUAUI.h"
|
||||
#include "core/io/Filesystem.h"
|
||||
#include "video/TexturePool.h"
|
||||
#include "video/TextureAtlasRenderer.h"
|
||||
#include "core/TimeProvider.h"
|
||||
#include "core/EventBus.h"
|
||||
#include "core/metric/Metric.h"
|
||||
#include "voxelformat/MeshCache.h"
|
||||
#include "voxelrender/CachedMeshRenderer.h"
|
||||
|
||||
TestLUAUI::TestLUAUI(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer) :
|
||||
Super(metric, filesystem, eventBus, timeProvider, texturePool, meshRenderer, textureAtlasRenderer) {
|
||||
init(ORGANISATION, "testluaui");
|
||||
pushWindow("overview");
|
||||
pushWindow("calculator");
|
||||
pushWindow("stylewin");
|
||||
pushWindow("modelwin");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const voxelformat::MeshCachePtr& meshCache = std::make_shared<voxelformat::MeshCache>();
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer = core::make_shared<voxelrender::CachedMeshRenderer>(meshCache);
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer = core::make_shared<video::TextureAtlasRenderer>();
|
||||
const core::EventBusPtr& eventBus = std::make_shared<core::EventBus>();
|
||||
const io::FilesystemPtr& filesystem = std::make_shared<io::Filesystem>();
|
||||
const core::TimeProviderPtr& timeProvider = std::make_shared<core::TimeProvider>();
|
||||
const video::TexturePoolPtr& texturePool = std::make_shared<video::TexturePool>(filesystem);
|
||||
const metric::MetricPtr& metric = std::make_shared<metric::Metric>();
|
||||
TestLUAUI app(metric, filesystem, eventBus, timeProvider, texturePool, meshRenderer, textureAtlasRenderer);
|
||||
return app.startMainLoop(argc, argv);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui/nuklear/LUAUIApp.h"
|
||||
|
||||
class TestLUAUI: public ui::nuklear::LUAUIApp {
|
||||
private:
|
||||
using Super = ui::nuklear::LUAUIApp;
|
||||
public:
|
||||
TestLUAUI(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer);
|
||||
};
|
|
@ -0,0 +1,290 @@
|
|||
-- An overview of most of the supported widgets.
|
||||
-- Simple calculator example
|
||||
|
||||
local ops = {'+', '-', '*', '/'}
|
||||
local a, b, op = '0'
|
||||
|
||||
local function clear()
|
||||
a, b, op = '0'
|
||||
end
|
||||
|
||||
local function digit(d)
|
||||
if op then
|
||||
if b == nil or b == '0' then
|
||||
b = d
|
||||
else
|
||||
b = b..d
|
||||
end
|
||||
else
|
||||
if a == '0' then
|
||||
a = d
|
||||
else
|
||||
a = a..d
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function decimal()
|
||||
if op then
|
||||
b = b or '0'
|
||||
b = b:find('.') and b or b..'.'
|
||||
else
|
||||
a = a:find('.') and a or a..'.'
|
||||
end
|
||||
end
|
||||
|
||||
local function equals()
|
||||
if not tonumber(b) then
|
||||
return
|
||||
end
|
||||
if op == '+' then
|
||||
a, b, op = tostring(tonumber(a) + tonumber(b))
|
||||
elseif op == '-' then
|
||||
a, b, op = tostring(tonumber(a) - tonumber(b))
|
||||
elseif op == '*' then
|
||||
a, b, op = tostring(tonumber(a) * tonumber(b))
|
||||
elseif op == '/' then
|
||||
a, b, op = tostring(tonumber(a) / tonumber(b))
|
||||
end
|
||||
end
|
||||
|
||||
local function operator(o)
|
||||
if op then
|
||||
equals()
|
||||
end
|
||||
op = o
|
||||
end
|
||||
|
||||
local function display()
|
||||
return b or a
|
||||
end
|
||||
|
||||
local checkA = {value = false}
|
||||
local checkB = {value = true}
|
||||
local radio = {value = 'A'}
|
||||
local selectA = {value = false}
|
||||
local selectB = {value = true}
|
||||
local slider = {value = 0.2}
|
||||
local progress = {value = 1}
|
||||
local colorPicker = {value = '#ff0000'}
|
||||
local property = {value = 6}
|
||||
local edit = {value = 'Edit text'}
|
||||
local comboA = {value = 1, items = {'A', 'B', 'C'}}
|
||||
local modelScale = 1.0
|
||||
local modelOmegaY = {value = 0.0}
|
||||
local modelCameraPos = vec3.new(0, 50, 100);
|
||||
local modelCameraTarget = vec3.new(0, 0, 0)
|
||||
|
||||
function overview()
|
||||
ui.styleDefault()
|
||||
if ui.windowBegin('Overview', 100, 100, 600, 450, 'border', 'movable', 'title') then
|
||||
ui.menubarBegin()
|
||||
ui.layoutRowBegin('static', 25, 5);
|
||||
ui.layoutRowPush(45);
|
||||
if ui.menuBegin('Menu', nil, 120, 90) then
|
||||
ui.layoutRow('dynamic', 40, 1)
|
||||
ui.menuItem('Item A')
|
||||
ui.menuItem('Item B')
|
||||
ui.menuItem('Item C')
|
||||
ui.menuEnd()
|
||||
end
|
||||
ui.menubarEnd()
|
||||
ui.layoutRow('dynamic', 400, 3)
|
||||
if ui.groupBegin('Group 1', 'border') then
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
ui.label('Left label')
|
||||
ui.label('Centered label', 'centered')
|
||||
ui.label('Right label', 'right')
|
||||
ui.label('Colored label', 'left', '#ff0000')
|
||||
if ui.treePush('tab', 'Tree Tab') then
|
||||
if ui.treePush('node', 'Tree Node 1') then
|
||||
ui.label('Label 1')
|
||||
ui.treePop()
|
||||
end
|
||||
if ui.treePush('node', 'Tree Node 2') then
|
||||
ui.label('Label 2')
|
||||
ui.treePop()
|
||||
end
|
||||
ui.treePop()
|
||||
end
|
||||
ui.spacing(1)
|
||||
if ui.button('Button') then
|
||||
print('button pressed!')
|
||||
end
|
||||
ui.spacing(1)
|
||||
ui.checkbox('Checkbox A', checkA)
|
||||
ui.checkbox('Checkbox B', checkB)
|
||||
ui.groupEnd()
|
||||
end
|
||||
if ui.groupBegin('Group 2', 'border') then
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
ui.label('Radio buttons:')
|
||||
ui.layoutRow('dynamic', 30, 3)
|
||||
ui.radio('A', radio)
|
||||
ui.radio('B', radio)
|
||||
ui.radio('C', radio)
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
ui.selectable('Selectable A', selectA)
|
||||
ui.selectable('Selectable B', selectB)
|
||||
ui.layoutRow('dynamic', 30, {.35, .65})
|
||||
ui.label('Slider:')
|
||||
ui.slider(0, slider, 1, 0.05)
|
||||
ui.label('Progress:')
|
||||
ui.progress(progress, 10, true)
|
||||
ui.layoutRow('dynamic', 30, 2)
|
||||
ui.spacing(2)
|
||||
ui.label('Color picker:')
|
||||
ui.button(nil, colorPicker.value)
|
||||
ui.layoutRow('dynamic', 90, 1)
|
||||
ui.colorpicker(colorPicker)
|
||||
ui.groupEnd()
|
||||
end
|
||||
if ui.groupBegin('Group 3', 'border') then
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
ui.property('Property', 0, property, 10, 0.25, 0.05)
|
||||
ui.spacing(1)
|
||||
ui.label('Edit:')
|
||||
ui.layoutRow('dynamic', 90, 1)
|
||||
ui.edit('box', edit)
|
||||
ui.layoutRow('dynamic', 5, 1)
|
||||
ui.spacing(1)
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
ui.label('Combobox:')
|
||||
ui.combobox(comboA, comboA.items)
|
||||
ui.layoutRow('dynamic', 5, 1)
|
||||
ui.spacing(1)
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
if ui.isWidgetHovered() then
|
||||
ui.tooltip('Test tooltip')
|
||||
end
|
||||
local x, y, w, h = ui.getWidgetBounds()
|
||||
if ui.contextualBegin(100, 100, x, y, w, h) then
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
ui.contextualItem('Item A')
|
||||
ui.contextualItem('Item B')
|
||||
ui.contextualEnd()
|
||||
end
|
||||
ui.label('Contextual (Right click me)')
|
||||
ui.groupEnd()
|
||||
end
|
||||
end
|
||||
ui.windowEnd()
|
||||
end
|
||||
|
||||
function modelwin()
|
||||
ui.styleDefault()
|
||||
if ui.windowBegin('Model', 600, 250, 1024, 768, 'border', 'movable', 'title') then
|
||||
ui.layoutRow('dynamic', 768, { 0.2, 0.8 })
|
||||
ui.groupBegin('modelsettings', 'border')
|
||||
ui.layoutRow('dynamic', 30, 1)
|
||||
modelScale = ui.property('Scale', 0, modelScale, 10, 0.25, 0.05)
|
||||
ui.property('Omega', 0, modelOmegaY, 10, 0.1, 0.01)
|
||||
ui.property('Camera pos', -1000, modelCameraPos, 1000, 1, 1)
|
||||
ui.property('Camera target', -1000, modelCameraTarget, 1000, 1, 1)
|
||||
ui.groupEnd()
|
||||
|
||||
ui.groupBegin('model', 'border')
|
||||
ui.layoutRow('dynamic', 768, 1)
|
||||
ui.stylePushBackground('#444444')
|
||||
local options = {
|
||||
scale = modelScale,
|
||||
omegaY = modelOmegaY.value,
|
||||
cameraPos = modelCameraPos,
|
||||
cameraTarget = modelCameraTarget
|
||||
}
|
||||
ui.model('assets/north-dir.vox', options)
|
||||
ui.stylePopBackground()
|
||||
ui.groupEnd()
|
||||
end
|
||||
ui.windowEnd()
|
||||
end
|
||||
|
||||
function calculator()
|
||||
ui.styleDefault()
|
||||
if ui.windowBegin('Calculator', 50, 50, 180, 250, 'border', 'movable', 'title') then
|
||||
ui.layoutRow('dynamic', 35, 1)
|
||||
ui.label(display(), 'right')
|
||||
ui.layoutRow('dynamic', 35, 4)
|
||||
for i=1,16 do
|
||||
if i >= 13 and i < 16 then
|
||||
if i == 13 then
|
||||
if ui.button('C') then
|
||||
clear()
|
||||
end
|
||||
if ui.button('0') then
|
||||
digit('0')
|
||||
end
|
||||
if ui.button('=') then
|
||||
equals()
|
||||
end
|
||||
end
|
||||
elseif i % 4 ~= 0 then
|
||||
local d = tostring(math.floor(i / 4) * 3 + (i % 4))
|
||||
if ui.button(d) then
|
||||
digit(d)
|
||||
end
|
||||
else
|
||||
local o = ops[math.floor(i / 4)]
|
||||
if ui.button(o) then
|
||||
operator(o)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
ui.windowEnd()
|
||||
end
|
||||
|
||||
local colors = {
|
||||
['text'] = '#afafaf',
|
||||
['window'] = '#2d2d2d',
|
||||
['header'] = '#282828',
|
||||
['border'] = '#414141',
|
||||
['button'] = '#323232',
|
||||
['button hover'] = '#282828',
|
||||
['button active'] = '#232323',
|
||||
['toggle'] = '#646464',
|
||||
['toggle hover'] = '#787878',
|
||||
['toggle cursor'] = '#2d2d2d',
|
||||
['select'] = '#2d2d2d',
|
||||
['select active'] = '#232323',
|
||||
['slider'] = '#262626',
|
||||
['slider cursor'] = '#646464',
|
||||
['slider cursor hover'] = '#787878',
|
||||
['slider cursor active'] = '#969696',
|
||||
['property'] = '#262626',
|
||||
['edit'] = '#262626',
|
||||
['edit cursor'] = '#afafaf',
|
||||
['combo'] = '#2d2d2d',
|
||||
['chart'] = '#787878',
|
||||
['chart color'] = '#2d2d2d',
|
||||
['chart color highlight'] = '#ff0000',
|
||||
['scrollbar'] = '#282828',
|
||||
['scrollbar cursor'] = '#646464',
|
||||
['scrollbar cursor hover'] = '#787878',
|
||||
['scrollbar cursor active'] = '#969696',
|
||||
['tab header'] = '#282828'
|
||||
}
|
||||
|
||||
local colorNames = {}
|
||||
|
||||
for name,_ in pairs(colors) do
|
||||
colorNames[#colorNames + 1] = name
|
||||
end
|
||||
|
||||
function stylewin()
|
||||
ui.styleLoadColors(colors)
|
||||
if ui.windowBegin('Style', 400, 50, 350, 450, 'border', 'movable', 'title', 'scrollbar') then
|
||||
ui.layoutRow('dynamic', 25, 2)
|
||||
for _,name in ipairs(colorNames) do
|
||||
ui.label(name..':')
|
||||
local color = colors[name]
|
||||
if ui.comboboxBegin(nil, color, 200, 200) then
|
||||
ui.layoutRow('dynamic', 90, 1)
|
||||
color = ui.colorPicker(color)
|
||||
colors[name] = color
|
||||
ui.comboboxEnd()
|
||||
end
|
||||
end
|
||||
end
|
||||
ui.windowEnd()
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
project(testnuklear)
|
||||
set(SRCS
|
||||
TestNuklear.h TestNuklear.cpp
|
||||
)
|
||||
set(FILES
|
||||
testnuklear/images/image8.png
|
||||
testnuklear/images/image3.png
|
||||
testnuklear/images/image5.png
|
||||
testnuklear/images/image6.png
|
||||
testnuklear/images/image9.png
|
||||
testnuklear/images/image7.png
|
||||
testnuklear/images/image4.png
|
||||
testnuklear/images/image1.png
|
||||
testnuklear/images/image2.png
|
||||
testnuklear/icon/pen.png
|
||||
testnuklear/icon/volume.png
|
||||
testnuklear/icon/home.png
|
||||
testnuklear/icon/prev.png
|
||||
testnuklear/icon/tools.png
|
||||
testnuklear/icon/next.png
|
||||
testnuklear/icon/stop.png
|
||||
testnuklear/icon/music.png
|
||||
testnuklear/icon/copy.png
|
||||
testnuklear/icon/play.png
|
||||
testnuklear/icon/unchecked.png
|
||||
testnuklear/icon/export.png
|
||||
testnuklear/icon/plane.png
|
||||
testnuklear/icon/pause.png
|
||||
testnuklear/icon/computer.png
|
||||
testnuklear/icon/wifi.png
|
||||
testnuklear/icon/font.png
|
||||
testnuklear/icon/checked.png
|
||||
testnuklear/icon/img.png
|
||||
testnuklear/icon/directory.png
|
||||
testnuklear/icon/settings.png
|
||||
testnuklear/icon/text.png
|
||||
testnuklear/icon/cloud.png
|
||||
testnuklear/icon/rocket.png
|
||||
testnuklear/icon/edit.png
|
||||
testnuklear/icon/default.png
|
||||
testnuklear/icon/delete.png
|
||||
testnuklear/icon/movie.png
|
||||
testnuklear/icon/desktop.png
|
||||
testnuklear/icon/phone.png
|
||||
testnuklear/extra_font/Roboto-Regular.ttf
|
||||
)
|
||||
engine_add_executable(TARGET ${PROJECT_NAME} SRCS ${SRCS} FILES ${FILES} WINDOWED NOINSTALL)
|
||||
engine_target_link_libraries(TARGET ${PROJECT_NAME} DEPENDENCIES nuklear)
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "TestNuklear.h"
|
||||
#include "testcore/TestAppMain.h"
|
||||
#include "core/io/Filesystem.h"
|
||||
#include "ui/nuklear/Nuklear.h"
|
||||
#include "video/TexturePool.h"
|
||||
#include "voxelformat/MeshCache.h"
|
||||
#include "voxelrender/CachedMeshRenderer.h"
|
||||
#include "video/TextureAtlasRenderer.h"
|
||||
#include "overview.c"
|
||||
#include "extended.c"
|
||||
#include "node_editor.c"
|
||||
#include "style.c"
|
||||
|
||||
TestNuklear::TestNuklear(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer) :
|
||||
Super(metric, filesystem, eventBus, timeProvider, texturePool, meshRenderer, textureAtlasRenderer) {
|
||||
init(ORGANISATION, "testnuklear");
|
||||
}
|
||||
|
||||
void TestNuklear::initUIFonts() {
|
||||
const char *fontFile = "extra_font/Roboto-Regular.ttf";
|
||||
_media.font_14 = loadFontFile(fontFile, 14.0f);
|
||||
_media.font_18 = loadFontFile(fontFile, 18.0f);
|
||||
_media.font_20 = loadFontFile(fontFile, 20.0f);
|
||||
_media.font_22 = loadFontFile(fontFile, 22.0f);
|
||||
}
|
||||
|
||||
core::AppState TestNuklear::onInit() {
|
||||
const core::AppState state = Super::onInit();
|
||||
|
||||
_media.unchecked = loadImageFile("icon/unchecked.png");
|
||||
_media.checked = loadImageFile("icon/checked.png");
|
||||
_media.rocket = loadImageFile("icon/rocket.png");
|
||||
_media.cloud = loadImageFile("icon/cloud.png");
|
||||
_media.pen = loadImageFile("icon/pen.png");
|
||||
_media.play = loadImageFile("icon/play.png");
|
||||
_media.pause = loadImageFile("icon/pause.png");
|
||||
_media.stop = loadImageFile("icon/stop.png");
|
||||
_media.next = loadImageFile("icon/next.png");
|
||||
_media.prev = loadImageFile("icon/prev.png");
|
||||
_media.tools = loadImageFile("icon/tools.png");
|
||||
_media.dir = loadImageFile("icon/directory.png");
|
||||
_media.copy = loadImageFile("icon/copy.png");
|
||||
_media.convert = loadImageFile("icon/export.png");
|
||||
_media.del = loadImageFile("icon/delete.png");
|
||||
_media.edit = loadImageFile("icon/edit.png");
|
||||
_media.menu[0] = loadImageFile("icon/home.png");
|
||||
_media.menu[1] = loadImageFile("icon/phone.png");
|
||||
_media.menu[2] = loadImageFile("icon/plane.png");
|
||||
_media.menu[3] = loadImageFile("icon/wifi.png");
|
||||
_media.menu[4] = loadImageFile("icon/settings.png");
|
||||
_media.menu[5] = loadImageFile("icon/volume.png");
|
||||
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
char buffer[256];
|
||||
sprintf(buffer, "images/image%d.png", (i + 1));
|
||||
_media.images[i] = loadImageFile(buffer);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
bool TestNuklear::onRenderUI() {
|
||||
overview(&_ctx);
|
||||
node_editor(&_ctx);
|
||||
set_style(&_ctx, THEME_BLUE);
|
||||
basic_demo(&_ctx, &_media);
|
||||
button_demo(&_ctx, &_media);
|
||||
grid_demo(&_ctx, &_media);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const voxelformat::MeshCachePtr& meshCache = std::make_shared<voxelformat::MeshCache>();
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer = core::make_shared<voxelrender::CachedMeshRenderer>(meshCache);
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer = core::make_shared<video::TextureAtlasRenderer>();
|
||||
const core::EventBusPtr& eventBus = std::make_shared<core::EventBus>();
|
||||
const io::FilesystemPtr& filesystem = std::make_shared<io::Filesystem>();
|
||||
const core::TimeProviderPtr& timeProvider = std::make_shared<core::TimeProvider>();
|
||||
const video::TexturePoolPtr& texturePool = std::make_shared<video::TexturePool>(filesystem);
|
||||
const metric::MetricPtr& metric = std::make_shared<metric::Metric>();
|
||||
TestNuklear app(metric, filesystem, eventBus, timeProvider, texturePool, meshRenderer, textureAtlasRenderer);
|
||||
return app.startMainLoop(argc, argv);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui/nuklear/NuklearApp.h"
|
||||
|
||||
class TestNuklear: public ui::nuklear::NuklearApp {
|
||||
private:
|
||||
using Super = ui::nuklear::NuklearApp;
|
||||
public:
|
||||
TestNuklear(const metric::MetricPtr& metric,
|
||||
const io::FilesystemPtr& filesystem,
|
||||
const core::EventBusPtr& eventBus,
|
||||
const core::TimeProviderPtr& timeProvider,
|
||||
const video::TexturePoolPtr& texturePool,
|
||||
const voxelrender::CachedMeshRendererPtr& meshRenderer,
|
||||
const video::TextureAtlasRendererPtr& textureAtlasRenderer);
|
||||
|
||||
void initUIFonts() override;
|
||||
|
||||
core::AppState onInit() override;
|
||||
bool onRenderUI() override;
|
||||
};
|
|
@ -0,0 +1,427 @@
|
|||
// https://github.com/DeXP/nuklear-webdemo
|
||||
// data/testnuklear and code in Public Domain
|
||||
|
||||
struct media {
|
||||
struct nk_font *font_14;
|
||||
struct nk_font *font_18;
|
||||
struct nk_font *font_20;
|
||||
struct nk_font *font_22;
|
||||
|
||||
struct nk_image unchecked;
|
||||
struct nk_image checked;
|
||||
struct nk_image rocket;
|
||||
struct nk_image cloud;
|
||||
struct nk_image pen;
|
||||
struct nk_image play;
|
||||
struct nk_image pause;
|
||||
struct nk_image stop;
|
||||
struct nk_image prev;
|
||||
struct nk_image next;
|
||||
struct nk_image tools;
|
||||
struct nk_image dir;
|
||||
struct nk_image copy;
|
||||
struct nk_image convert;
|
||||
struct nk_image del;
|
||||
struct nk_image edit;
|
||||
struct nk_image images[9];
|
||||
struct nk_image menu[6];
|
||||
};
|
||||
|
||||
static struct media _media;
|
||||
|
||||
/* ===============================================================
|
||||
*
|
||||
* CUSTOM WIDGET
|
||||
*
|
||||
* ===============================================================*/
|
||||
static int
|
||||
ui_piemenu(struct nk_context *ctx, struct nk_vec2 pos, float radius,
|
||||
struct nk_image *icons, int item_count)
|
||||
{
|
||||
int ret = -1;
|
||||
struct nk_rect total_space;
|
||||
struct nk_rect bounds;
|
||||
int active_item = 0;
|
||||
|
||||
/* pie menu popup */
|
||||
struct nk_color border = ctx->style.window.border_color;
|
||||
struct nk_style_item background = ctx->style.window.fixed_background;
|
||||
ctx->style.window.fixed_background = nk_style_item_hide();
|
||||
ctx->style.window.border_color = nk_rgba(0,0,0,0);
|
||||
|
||||
total_space = nk_window_get_content_region(ctx);
|
||||
ctx->style.window.spacing = nk_vec2(0,0);
|
||||
ctx->style.window.padding = nk_vec2(0,0);
|
||||
|
||||
if (nk_popup_begin(ctx, NK_POPUP_STATIC, "piemenu", NK_WINDOW_NO_SCROLLBAR,
|
||||
nk_rect(pos.x - total_space.x - radius, pos.y - radius - total_space.y,
|
||||
2*radius,2*radius)))
|
||||
{
|
||||
int i = 0;
|
||||
struct nk_command_buffer* out = nk_window_get_canvas(ctx);
|
||||
const struct nk_input *in = &ctx->input;
|
||||
|
||||
total_space = nk_window_get_content_region(ctx);
|
||||
ctx->style.window.spacing = nk_vec2(4,4);
|
||||
ctx->style.window.padding = nk_vec2(8,8);
|
||||
nk_layout_row_dynamic(ctx, total_space.h, 1);
|
||||
nk_widget(&bounds, ctx);
|
||||
|
||||
/* outer circle */
|
||||
nk_fill_circle(out, bounds, nk_rgb(50,50,50));
|
||||
{
|
||||
/* circle buttons */
|
||||
float step = (2 * 3.141592654f) / (float)(MAX(1,item_count));
|
||||
float a_min = 0; float a_max = step;
|
||||
|
||||
struct nk_vec2 center = nk_vec2(bounds.x + bounds.w / 2.0f, bounds.y + bounds.h / 2.0f);
|
||||
struct nk_vec2 drag = nk_vec2(in->mouse.pos.x - center.x, in->mouse.pos.y - center.y);
|
||||
float angle = (float)atan2(drag.y, drag.x);
|
||||
if (angle < -0.0f) angle += 2.0f * 3.141592654f;
|
||||
active_item = (int)(angle/step);
|
||||
|
||||
for (i = 0; i < item_count; ++i) {
|
||||
struct nk_rect content;
|
||||
float rx, ry, dx, dy, a;
|
||||
nk_fill_arc(out, center.x, center.y, (bounds.w/2.0f),
|
||||
a_min, a_max, (active_item == i) ? nk_rgb(45,100,255): nk_rgb(60,60,60));
|
||||
|
||||
/* separator line */
|
||||
rx = bounds.w/2.0f; ry = 0;
|
||||
dx = rx * (float)cos(a_min) - ry * (float)sin(a_min);
|
||||
dy = rx * (float)sin(a_min) + ry * (float)cos(a_min);
|
||||
nk_stroke_line(out, center.x, center.y,
|
||||
center.x + dx, center.y + dy, 1.0f, nk_rgb(50,50,50));
|
||||
|
||||
/* button content */
|
||||
a = a_min + (a_max - a_min)/2.0f;
|
||||
rx = bounds.w/2.5f; ry = 0;
|
||||
content.w = 30; content.h = 30;
|
||||
content.x = center.x + ((rx * (float)cos(a) - ry * (float)sin(a)) - content.w/2.0f);
|
||||
content.y = center.y + (rx * (float)sin(a) + ry * (float)cos(a) - content.h/2.0f);
|
||||
nk_draw_image(out, content, &icons[i], nk_rgb(255,255,255));
|
||||
a_min = a_max; a_max += step;
|
||||
}
|
||||
}
|
||||
{
|
||||
/* inner circle */
|
||||
struct nk_rect inner;
|
||||
inner.x = bounds.x + bounds.w/2 - bounds.w/4;
|
||||
inner.y = bounds.y + bounds.h/2 - bounds.h/4;
|
||||
inner.w = bounds.w/2; inner.h = bounds.h/2;
|
||||
nk_fill_circle(out, inner, nk_rgb(45,45,45));
|
||||
|
||||
/* active icon content */
|
||||
bounds.w = inner.w / 2.0f;
|
||||
bounds.h = inner.h / 2.0f;
|
||||
bounds.x = inner.x + inner.w/2 - bounds.w/2;
|
||||
bounds.y = inner.y + inner.h/2 - bounds.h/2;
|
||||
nk_draw_image(out, bounds, &icons[active_item], nk_rgb(255,255,255));
|
||||
}
|
||||
nk_layout_space_end(ctx);
|
||||
if (!nk_input_is_mouse_down(&ctx->input, NK_BUTTON_RIGHT)) {
|
||||
nk_popup_close(ctx);
|
||||
ret = active_item;
|
||||
}
|
||||
} else ret = -2;
|
||||
ctx->style.window.spacing = nk_vec2(4,4);
|
||||
ctx->style.window.padding = nk_vec2(8,8);
|
||||
nk_popup_end(ctx);
|
||||
|
||||
ctx->style.window.fixed_background = background;
|
||||
ctx->style.window.border_color = border;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ===============================================================
|
||||
*
|
||||
* GRID
|
||||
*
|
||||
* ===============================================================*/
|
||||
static void
|
||||
grid_demo(struct nk_context *ctx, struct media *media)
|
||||
{
|
||||
static char text[3][64];
|
||||
static int text_len[3];
|
||||
static const char *items[] = {"Item 0","item 1","item 2"};
|
||||
static int selected_item = 0;
|
||||
static int check = 1;
|
||||
|
||||
int i;
|
||||
nk_style_set_font(ctx, &media->font_20->handle);
|
||||
if (nk_begin(ctx, "Grid Demo", nk_rect(600, 350, 275, 250),
|
||||
NK_WINDOW_TITLE|NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|
|
||||
NK_WINDOW_NO_SCROLLBAR))
|
||||
{
|
||||
nk_style_set_font(ctx, &media->font_18->handle);
|
||||
nk_layout_row_dynamic(ctx, 30, 2);
|
||||
nk_label(ctx, "Floating point:", NK_TEXT_RIGHT);
|
||||
nk_edit_string(ctx, NK_EDIT_FIELD, text[0], &text_len[0], 64, nk_filter_float);
|
||||
nk_label(ctx, "Hexadecimal:", NK_TEXT_RIGHT);
|
||||
nk_edit_string(ctx, NK_EDIT_FIELD, text[1], &text_len[1], 64, nk_filter_hex);
|
||||
nk_label(ctx, "Binary:", NK_TEXT_RIGHT);
|
||||
nk_edit_string(ctx, NK_EDIT_FIELD, text[2], &text_len[2], 64, nk_filter_binary);
|
||||
nk_label(ctx, "Checkbox:", NK_TEXT_RIGHT);
|
||||
nk_checkbox_label(ctx, "Check me", &check);
|
||||
nk_label(ctx, "Combobox:", NK_TEXT_RIGHT);
|
||||
if (nk_combo_begin_label(ctx, items[selected_item], nk_vec2(nk_widget_width(ctx), 200))) {
|
||||
nk_layout_row_dynamic(ctx, 25, 1);
|
||||
for (i = 0; i < 3; ++i)
|
||||
if (nk_combo_item_label(ctx, items[i], NK_TEXT_LEFT))
|
||||
selected_item = i;
|
||||
nk_combo_end(ctx);
|
||||
}
|
||||
}
|
||||
nk_end(ctx);
|
||||
nk_style_set_font(ctx, &media->font_14->handle);
|
||||
}
|
||||
|
||||
/* ===============================================================
|
||||
*
|
||||
* BUTTON DEMO
|
||||
*
|
||||
* ===============================================================*/
|
||||
static void
|
||||
ui_header(struct nk_context *ctx, struct media *media, const char *title)
|
||||
{
|
||||
nk_style_set_font(ctx, &media->font_18->handle);
|
||||
nk_layout_row_dynamic(ctx, 20, 1);
|
||||
nk_label(ctx, title, NK_TEXT_LEFT);
|
||||
}
|
||||
|
||||
static void
|
||||
ui_widget(struct nk_context *ctx, struct media *media, float height)
|
||||
{
|
||||
static const float ratio[] = {0.15f, 0.85f};
|
||||
nk_style_set_font(ctx, &media->font_22->handle);
|
||||
nk_layout_row(ctx, NK_DYNAMIC, height, 2, ratio);
|
||||
nk_spacing(ctx, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
ui_widget_centered(struct nk_context *ctx, struct media *media, float height)
|
||||
{
|
||||
static const float ratio[] = {0.15f, 0.50f, 0.35f};
|
||||
nk_style_set_font(ctx, &media->font_22->handle);
|
||||
nk_layout_row(ctx, NK_DYNAMIC, height, 3, ratio);
|
||||
nk_spacing(ctx, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
button_demo(struct nk_context *ctx, struct media *media)
|
||||
{
|
||||
static int option = 1;
|
||||
static int toggle0 = 1;
|
||||
static int toggle1 = 0;
|
||||
static int toggle2 = 1;
|
||||
|
||||
nk_style_set_font(ctx, &media->font_20->handle);
|
||||
nk_begin(ctx, "Button Demo", nk_rect(50,50,255,610),
|
||||
NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|NK_WINDOW_TITLE);
|
||||
|
||||
/*------------------------------------------------
|
||||
* MENU
|
||||
*------------------------------------------------*/
|
||||
nk_menubar_begin(ctx);
|
||||
{
|
||||
/* toolbar */
|
||||
nk_layout_row_static(ctx, 40, 40, 4);
|
||||
if (nk_menu_begin_image(ctx, "Music", media->play, nk_vec2(110,120)))
|
||||
{
|
||||
/* settings */
|
||||
nk_layout_row_dynamic(ctx, 25, 1);
|
||||
nk_menu_item_image_label(ctx, media->play, "Play", NK_TEXT_RIGHT);
|
||||
nk_menu_item_image_label(ctx, media->stop, "Stop", NK_TEXT_RIGHT);
|
||||
nk_menu_item_image_label(ctx, media->pause, "Pause", NK_TEXT_RIGHT);
|
||||
nk_menu_item_image_label(ctx, media->next, "Next", NK_TEXT_RIGHT);
|
||||
nk_menu_item_image_label(ctx, media->prev, "Prev", NK_TEXT_RIGHT);
|
||||
nk_menu_end(ctx);
|
||||
}
|
||||
nk_button_image(ctx, media->tools);
|
||||
nk_button_image(ctx, media->cloud);
|
||||
nk_button_image(ctx, media->pen);
|
||||
}
|
||||
nk_menubar_end(ctx);
|
||||
|
||||
/*------------------------------------------------
|
||||
* BUTTON
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Push buttons");
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_label(ctx, "Push me"))
|
||||
fprintf(stdout, "pushed!\n");
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_image_label(ctx, media->rocket, "Styled", NK_TEXT_CENTERED))
|
||||
fprintf(stdout, "rocket!\n");
|
||||
|
||||
/*------------------------------------------------
|
||||
* REPEATER
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Repeater");
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_label(ctx, "Press me"))
|
||||
fprintf(stdout, "pressed!\n");
|
||||
|
||||
/*------------------------------------------------
|
||||
* TOGGLE
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Toggle buttons");
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_image_label(ctx, (toggle0) ? media->checked: media->unchecked, "Toggle", NK_TEXT_LEFT))
|
||||
toggle0 = !toggle0;
|
||||
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_image_label(ctx, (toggle1) ? media->checked: media->unchecked, "Toggle", NK_TEXT_LEFT))
|
||||
toggle1 = !toggle1;
|
||||
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_image_label(ctx, (toggle2) ? media->checked: media->unchecked, "Toggle", NK_TEXT_LEFT))
|
||||
toggle2 = !toggle2;
|
||||
|
||||
/*------------------------------------------------
|
||||
* RADIO
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Radio buttons");
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_symbol_label(ctx, (option == 0)?NK_SYMBOL_CIRCLE_OUTLINE:NK_SYMBOL_CIRCLE_SOLID, "Select", NK_TEXT_LEFT))
|
||||
option = 0;
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_symbol_label(ctx, (option == 1)?NK_SYMBOL_CIRCLE_OUTLINE:NK_SYMBOL_CIRCLE_SOLID, "Select", NK_TEXT_LEFT))
|
||||
option = 1;
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_symbol_label(ctx, (option == 2)?NK_SYMBOL_CIRCLE_OUTLINE:NK_SYMBOL_CIRCLE_SOLID, "Select", NK_TEXT_LEFT))
|
||||
option = 2;
|
||||
|
||||
/*------------------------------------------------
|
||||
* CONTEXTUAL
|
||||
*------------------------------------------------*/
|
||||
nk_style_set_font(ctx, &media->font_18->handle);
|
||||
if (nk_contextual_begin(ctx, NK_WINDOW_NO_SCROLLBAR, nk_vec2(150, 300), nk_window_get_bounds(ctx))) {
|
||||
nk_layout_row_dynamic(ctx, 30, 1);
|
||||
if (nk_contextual_item_image_label(ctx, media->copy, "Clone", NK_TEXT_RIGHT))
|
||||
fprintf(stdout, "pressed clone!\n");
|
||||
if (nk_contextual_item_image_label(ctx, media->del, "Delete", NK_TEXT_RIGHT))
|
||||
fprintf(stdout, "pressed delete!\n");
|
||||
if (nk_contextual_item_image_label(ctx, media->convert, "Convert", NK_TEXT_RIGHT))
|
||||
fprintf(stdout, "pressed convert!\n");
|
||||
if (nk_contextual_item_image_label(ctx, media->edit, "Edit", NK_TEXT_RIGHT))
|
||||
fprintf(stdout, "pressed edit!\n");
|
||||
nk_contextual_end(ctx);
|
||||
}
|
||||
nk_style_set_font(ctx, &media->font_14->handle);
|
||||
nk_end(ctx);
|
||||
}
|
||||
|
||||
/* ===============================================================
|
||||
*
|
||||
* BASIC DEMO
|
||||
*
|
||||
* ===============================================================*/
|
||||
static void
|
||||
basic_demo(struct nk_context *ctx, struct media *media)
|
||||
{
|
||||
static int image_active;
|
||||
static int check0 = 1;
|
||||
static int check1 = 0;
|
||||
static size_t prog = 80;
|
||||
static int selected_item = 0;
|
||||
static int selected_image = 3;
|
||||
static int selected_icon = 0;
|
||||
static const char *items[] = {"Item 0","item 1","item 2"};
|
||||
static int piemenu_active = 0;
|
||||
static struct nk_vec2 piemenu_pos;
|
||||
|
||||
int i = 0;
|
||||
nk_style_set_font(ctx, &media->font_20->handle);
|
||||
nk_begin(ctx, "Basic Demo", nk_rect(320, 50, 275, 610),
|
||||
NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|NK_WINDOW_TITLE);
|
||||
|
||||
/*------------------------------------------------
|
||||
* POPUP BUTTON
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Popup & Scrollbar & Images");
|
||||
ui_widget(ctx, media, 35);
|
||||
if (nk_button_image_label(ctx, media->dir, "Images", NK_TEXT_CENTERED))
|
||||
image_active = !image_active;
|
||||
|
||||
/*------------------------------------------------
|
||||
* SELECTED IMAGE
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Selected Image");
|
||||
ui_widget_centered(ctx, media, 100);
|
||||
nk_image(ctx, media->images[selected_image]);
|
||||
|
||||
/*------------------------------------------------
|
||||
* IMAGE POPUP
|
||||
*------------------------------------------------*/
|
||||
if (image_active) {
|
||||
if (nk_popup_begin(ctx, NK_POPUP_STATIC, "Image Popup", 0, nk_rect(265, 0, 320, 220))) {
|
||||
nk_layout_row_static(ctx, 82, 82, 3);
|
||||
for (i = 0; i < 9; ++i) {
|
||||
if (nk_button_image(ctx, media->images[i])) {
|
||||
selected_image = i;
|
||||
image_active = 0;
|
||||
nk_popup_close(ctx);
|
||||
}
|
||||
}
|
||||
nk_popup_end(ctx);
|
||||
}
|
||||
}
|
||||
/*------------------------------------------------
|
||||
* COMBOBOX
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Combo box");
|
||||
ui_widget(ctx, media, 40);
|
||||
if (nk_combo_begin_label(ctx, items[selected_item], nk_vec2(nk_widget_width(ctx), 200))) {
|
||||
nk_layout_row_dynamic(ctx, 35, 1);
|
||||
for (i = 0; i < 3; ++i)
|
||||
if (nk_combo_item_label(ctx, items[i], NK_TEXT_LEFT))
|
||||
selected_item = i;
|
||||
nk_combo_end(ctx);
|
||||
}
|
||||
|
||||
ui_widget(ctx, media, 40);
|
||||
if (nk_combo_begin_image_label(ctx, items[selected_icon], media->images[selected_icon], nk_vec2(nk_widget_width(ctx), 200))) {
|
||||
nk_layout_row_dynamic(ctx, 35, 1);
|
||||
for (i = 0; i < 3; ++i)
|
||||
if (nk_combo_item_image_label(ctx, media->images[i], items[i], NK_TEXT_RIGHT))
|
||||
selected_icon = i;
|
||||
nk_combo_end(ctx);
|
||||
}
|
||||
|
||||
/*------------------------------------------------
|
||||
* CHECKBOX
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Checkbox");
|
||||
ui_widget(ctx, media, 30);
|
||||
nk_checkbox_label(ctx, "Flag 1", &check0);
|
||||
ui_widget(ctx, media, 30);
|
||||
nk_checkbox_label(ctx, "Flag 2", &check1);
|
||||
|
||||
/*------------------------------------------------
|
||||
* PROGRESSBAR
|
||||
*------------------------------------------------*/
|
||||
ui_header(ctx, media, "Progressbar");
|
||||
ui_widget(ctx, media, 35);
|
||||
nk_progress(ctx, &prog, 100, nk_true);
|
||||
|
||||
/*------------------------------------------------
|
||||
* PIEMENU
|
||||
*------------------------------------------------*/
|
||||
if (nk_input_is_mouse_click_down_in_rect(&ctx->input, NK_BUTTON_RIGHT,
|
||||
nk_window_get_bounds(ctx),nk_true)){
|
||||
piemenu_pos = ctx->input.mouse.pos;
|
||||
piemenu_active = 1;
|
||||
}
|
||||
|
||||
if (piemenu_active) {
|
||||
int ret = ui_piemenu(ctx, piemenu_pos, 140, &media->menu[0], 6);
|
||||
if (ret == -2) piemenu_active = 0;
|
||||
if (ret != -1) {
|
||||
fprintf(stdout, "piemenu selected: %d\n", ret);
|
||||
piemenu_active = 0;
|
||||
}
|
||||
}
|
||||
nk_style_set_font(ctx, &media->font_14->handle);
|
||||
nk_end(ctx);
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
/* nuklear - v1.00 - public domain */
|
||||
/* This is a simple node editor just to show a simple implementation and that
|
||||
* it is possible to achieve it with this library. While all nodes inside this
|
||||
* example use a simple color modifier as content you could change them
|
||||
* to have your custom content depending on the node time.
|
||||
* Biggest difference to most usual implementation is that this example does
|
||||
* not have connectors on the right position of the property that it links.
|
||||
* This is mainly done out of laziness and could be implemented as well but
|
||||
* requires calculating the position of all rows and add connectors.
|
||||
* In addition adding and removing nodes is quite limited at the
|
||||
* moment since it is based on a simple fixed array. If this is to be converted
|
||||
* into something more serious it is probably best to extend it.*/
|
||||
struct node {
|
||||
int ID;
|
||||
char name[32];
|
||||
struct nk_rect bounds;
|
||||
float value;
|
||||
struct nk_color color;
|
||||
int input_count;
|
||||
int output_count;
|
||||
struct node *next;
|
||||
struct node *prev;
|
||||
};
|
||||
|
||||
struct node_link {
|
||||
int input_id;
|
||||
int input_slot;
|
||||
int output_id;
|
||||
int output_slot;
|
||||
struct nk_vec2 in;
|
||||
struct nk_vec2 out;
|
||||
};
|
||||
|
||||
struct node_linking {
|
||||
int active;
|
||||
struct node *node;
|
||||
int input_id;
|
||||
int input_slot;
|
||||
};
|
||||
|
||||
struct node_editor {
|
||||
int initialized;
|
||||
struct node node_buf[32];
|
||||
struct node_link links[64];
|
||||
struct node *begin;
|
||||
struct node *end;
|
||||
int node_count;
|
||||
int link_count;
|
||||
struct nk_rect bounds;
|
||||
struct node *selected;
|
||||
int show_grid;
|
||||
struct nk_vec2 scrolling;
|
||||
struct node_linking linking;
|
||||
};
|
||||
static struct node_editor nodeEditor;
|
||||
|
||||
static void
|
||||
node_editor_push(struct node_editor *editor, struct node *node)
|
||||
{
|
||||
if (!editor->begin) {
|
||||
node->next = NULL;
|
||||
node->prev = NULL;
|
||||
editor->begin = node;
|
||||
editor->end = node;
|
||||
} else {
|
||||
node->prev = editor->end;
|
||||
if (editor->end)
|
||||
editor->end->next = node;
|
||||
node->next = NULL;
|
||||
editor->end = node;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
node_editor_pop(struct node_editor *editor, struct node *node)
|
||||
{
|
||||
if (node->next)
|
||||
node->next->prev = node->prev;
|
||||
if (node->prev)
|
||||
node->prev->next = node->next;
|
||||
if (editor->end == node)
|
||||
editor->end = node->prev;
|
||||
if (editor->begin == node)
|
||||
editor->begin = node->next;
|
||||
node->next = NULL;
|
||||
node->prev = NULL;
|
||||
}
|
||||
|
||||
static struct node*
|
||||
node_editor_find(struct node_editor *editor, int ID)
|
||||
{
|
||||
struct node *iter = editor->begin;
|
||||
while (iter) {
|
||||
if (iter->ID == ID)
|
||||
return iter;
|
||||
iter = iter->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
node_editor_add(struct node_editor *editor, const char *name, struct nk_rect bounds,
|
||||
struct nk_color col, int in_count, int out_count)
|
||||
{
|
||||
static int IDs = 0;
|
||||
struct node *node;
|
||||
NK_ASSERT((nk_size)editor->node_count < lengthof(editor->node_buf));
|
||||
node = &editor->node_buf[editor->node_count++];
|
||||
node->ID = IDs++;
|
||||
node->value = 0;
|
||||
node->color = nk_rgb(255, 0, 0);
|
||||
node->input_count = in_count;
|
||||
node->output_count = out_count;
|
||||
node->color = col;
|
||||
node->bounds = bounds;
|
||||
strcpy(node->name, name);
|
||||
node_editor_push(editor, node);
|
||||
}
|
||||
|
||||
static void
|
||||
node_editor_link(struct node_editor *editor, int in_id, int in_slot,
|
||||
int out_id, int out_slot)
|
||||
{
|
||||
struct node_link *link;
|
||||
NK_ASSERT((nk_size)editor->link_count < lengthof(editor->links));
|
||||
link = &editor->links[editor->link_count++];
|
||||
link->input_id = in_id;
|
||||
link->input_slot = in_slot;
|
||||
link->output_id = out_id;
|
||||
link->output_slot = out_slot;
|
||||
}
|
||||
|
||||
static void
|
||||
node_editor_init(struct node_editor *editor)
|
||||
{
|
||||
memset(editor, 0, sizeof(*editor));
|
||||
editor->begin = NULL;
|
||||
editor->end = NULL;
|
||||
node_editor_add(editor, "Source", nk_rect(40, 10, 180, 220), nk_rgb(255, 0, 0), 0, 1);
|
||||
node_editor_add(editor, "Source", nk_rect(40, 260, 180, 220), nk_rgb(0, 255, 0), 0, 1);
|
||||
node_editor_add(editor, "Combine", nk_rect(400, 100, 180, 220), nk_rgb(0,0,255), 2, 2);
|
||||
node_editor_link(editor, 0, 0, 2, 0);
|
||||
node_editor_link(editor, 1, 0, 2, 1);
|
||||
editor->show_grid = nk_true;
|
||||
}
|
||||
|
||||
static int
|
||||
node_editor(struct nk_context *ctx)
|
||||
{
|
||||
int n = 0;
|
||||
struct nk_rect total_space;
|
||||
const struct nk_input *in = &ctx->input;
|
||||
struct nk_command_buffer *canvas;
|
||||
struct node *updated = 0;
|
||||
struct node_editor *nodedit = &nodeEditor;
|
||||
|
||||
if (!nodeEditor.initialized) {
|
||||
node_editor_init(&nodeEditor);
|
||||
nodeEditor.initialized = 1;
|
||||
}
|
||||
|
||||
if (nk_begin(ctx, "NodeEdit", nk_rect(0, 620, 800, 600),
|
||||
NK_WINDOW_BORDER|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_MOVABLE|NK_WINDOW_CLOSABLE))
|
||||
{
|
||||
/* allocate complete window space */
|
||||
canvas = nk_window_get_canvas(ctx);
|
||||
total_space = nk_window_get_content_region(ctx);
|
||||
nk_layout_space_begin(ctx, NK_STATIC, total_space.h, nodedit->node_count);
|
||||
{
|
||||
struct node *it = nodedit->begin;
|
||||
struct nk_rect size = nk_layout_space_bounds(ctx);
|
||||
|
||||
if (nodedit->show_grid) {
|
||||
/* display grid */
|
||||
float x, y;
|
||||
const float grid_size = 32.0f;
|
||||
const struct nk_color grid_color = nk_rgb(50, 50, 50);
|
||||
for (x = (float)fmod(size.x - nodedit->scrolling.x, grid_size); x < size.w; x += grid_size)
|
||||
nk_stroke_line(canvas, x+size.x, size.y, x+size.x, size.y+size.h, 1.0f, grid_color);
|
||||
for (y = (float)fmod(size.y - nodedit->scrolling.y, grid_size); y < size.h; y += grid_size)
|
||||
nk_stroke_line(canvas, size.x, y+size.y, size.x+size.w, y+size.y, 1.0f, grid_color);
|
||||
}
|
||||
|
||||
/* execute each node as a movable group */
|
||||
struct nk_panel *node;
|
||||
while (it) {
|
||||
/* calculate scrolled node window position and size */
|
||||
nk_layout_space_push(ctx, nk_rect(it->bounds.x - nodedit->scrolling.x,
|
||||
it->bounds.y - nodedit->scrolling.y, it->bounds.w, it->bounds.h));
|
||||
|
||||
/* execute node window */
|
||||
if (nk_group_begin(ctx, it->name, NK_WINDOW_MOVABLE|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER|NK_WINDOW_TITLE))
|
||||
{
|
||||
/* always have last selected node on top */
|
||||
|
||||
node = nk_window_get_panel(ctx);
|
||||
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, node->bounds) &&
|
||||
(!(it->prev && nk_input_mouse_clicked(in, NK_BUTTON_LEFT,
|
||||
nk_layout_space_rect_to_screen(ctx, node->bounds)))) &&
|
||||
nodedit->end != it)
|
||||
{
|
||||
updated = it;
|
||||
}
|
||||
|
||||
/* ================= NODE CONTENT =====================*/
|
||||
nk_layout_row_dynamic(ctx, 25, 1);
|
||||
nk_button_color(ctx, it->color);
|
||||
it->color.r = (nk_byte)nk_propertyi(ctx, "#R:", 0, it->color.r, 255, 1,1);
|
||||
it->color.g = (nk_byte)nk_propertyi(ctx, "#G:", 0, it->color.g, 255, 1,1);
|
||||
it->color.b = (nk_byte)nk_propertyi(ctx, "#B:", 0, it->color.b, 255, 1,1);
|
||||
it->color.a = (nk_byte)nk_propertyi(ctx, "#A:", 0, it->color.a, 255, 1,1);
|
||||
/* ====================================================*/
|
||||
nk_group_end(ctx);
|
||||
}
|
||||
{
|
||||
/* node connector and linking */
|
||||
float space;
|
||||
struct nk_rect bounds;
|
||||
bounds = nk_layout_space_rect_to_local(ctx, node->bounds);
|
||||
bounds.x += nodedit->scrolling.x;
|
||||
bounds.y += nodedit->scrolling.y;
|
||||
it->bounds = bounds;
|
||||
|
||||
/* output connector */
|
||||
space = node->bounds.h / (float)((it->output_count) + 1);
|
||||
for (n = 0; n < it->output_count; ++n) {
|
||||
struct nk_rect circle;
|
||||
circle.x = node->bounds.x + node->bounds.w-4;
|
||||
circle.y = node->bounds.y + space * (float)(n+1);
|
||||
circle.w = 8; circle.h = 8;
|
||||
nk_fill_circle(canvas, circle, nk_rgb(100, 100, 100));
|
||||
|
||||
/* start linking process */
|
||||
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true)) {
|
||||
nodedit->linking.active = nk_true;
|
||||
nodedit->linking.node = it;
|
||||
nodedit->linking.input_id = it->ID;
|
||||
nodedit->linking.input_slot = n;
|
||||
}
|
||||
|
||||
/* draw curve from linked node slot to mouse position */
|
||||
if (nodedit->linking.active && nodedit->linking.node == it &&
|
||||
nodedit->linking.input_slot == n) {
|
||||
struct nk_vec2 l0 = nk_vec2(circle.x + 3, circle.y + 3);
|
||||
struct nk_vec2 l1 = in->mouse.pos;
|
||||
nk_stroke_curve(canvas, l0.x, l0.y, l0.x + 50.0f, l0.y,
|
||||
l1.x - 50.0f, l1.y, l1.x, l1.y, 1.0f, nk_rgb(100, 100, 100));
|
||||
}
|
||||
}
|
||||
|
||||
/* input connector */
|
||||
space = node->bounds.h / (float)((it->input_count) + 1);
|
||||
for (n = 0; n < it->input_count; ++n) {
|
||||
struct nk_rect circle;
|
||||
circle.x = node->bounds.x-4;
|
||||
circle.y = node->bounds.y + space * (float)(n+1);
|
||||
circle.w = 8; circle.h = 8;
|
||||
nk_fill_circle(canvas, circle, nk_rgb(100, 100, 100));
|
||||
if (nk_input_is_mouse_released(in, NK_BUTTON_LEFT) &&
|
||||
nk_input_is_mouse_hovering_rect(in, circle) &&
|
||||
nodedit->linking.active && nodedit->linking.node != it) {
|
||||
nodedit->linking.active = nk_false;
|
||||
node_editor_link(nodedit, nodedit->linking.input_id,
|
||||
nodedit->linking.input_slot, it->ID, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
it = it->next;
|
||||
}
|
||||
|
||||
/* reset linking connection */
|
||||
if (nodedit->linking.active && nk_input_is_mouse_released(in, NK_BUTTON_LEFT)) {
|
||||
nodedit->linking.active = nk_false;
|
||||
nodedit->linking.node = NULL;
|
||||
fprintf(stdout, "linking failed\n");
|
||||
}
|
||||
|
||||
/* draw each link */
|
||||
for (n = 0; n < nodedit->link_count; ++n) {
|
||||
struct node_link *link = &nodedit->links[n];
|
||||
struct node *ni = node_editor_find(nodedit, link->input_id);
|
||||
struct node *no = node_editor_find(nodedit, link->output_id);
|
||||
float spacei = node->bounds.h / (float)((ni->output_count) + 1);
|
||||
float spaceo = node->bounds.h / (float)((no->input_count) + 1);
|
||||
struct nk_vec2 l0 = nk_layout_space_to_screen(ctx,
|
||||
nk_vec2(ni->bounds.x + ni->bounds.w, 3.0f + ni->bounds.y + spacei * (float)(link->input_slot+1)));
|
||||
struct nk_vec2 l1 = nk_layout_space_to_screen(ctx,
|
||||
nk_vec2(no->bounds.x, 3.0f + no->bounds.y + spaceo * (float)(link->output_slot+1)));
|
||||
|
||||
l0.x -= nodedit->scrolling.x;
|
||||
l0.y -= nodedit->scrolling.y;
|
||||
l1.x -= nodedit->scrolling.x;
|
||||
l1.y -= nodedit->scrolling.y;
|
||||
nk_stroke_curve(canvas, l0.x, l0.y, l0.x + 50.0f, l0.y,
|
||||
l1.x - 50.0f, l1.y, l1.x, l1.y, 1.0f, nk_rgb(100, 100, 100));
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
/* reshuffle nodes to have least recently selected node on top */
|
||||
node_editor_pop(nodedit, updated);
|
||||
node_editor_push(nodedit, updated);
|
||||
}
|
||||
|
||||
/* node selection */
|
||||
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nk_layout_space_bounds(ctx))) {
|
||||
it = nodedit->begin;
|
||||
nodedit->selected = NULL;
|
||||
nodedit->bounds = nk_rect(in->mouse.pos.x, in->mouse.pos.y, 100, 200);
|
||||
while (it) {
|
||||
struct nk_rect b = nk_layout_space_rect_to_screen(ctx, it->bounds);
|
||||
b.x -= nodedit->scrolling.x;
|
||||
b.y -= nodedit->scrolling.y;
|
||||
if (nk_input_is_mouse_hovering_rect(in, b))
|
||||
nodedit->selected = it;
|
||||
it = it->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* contextual menu */
|
||||
if (nk_contextual_begin(ctx, 0, nk_vec2(100, 220), nk_window_get_bounds(ctx))) {
|
||||
const char *grid_option[] = {"Show Grid", "Hide Grid"};
|
||||
nk_layout_row_dynamic(ctx, 25, 1);
|
||||
if (nk_contextual_item_label(ctx, "New", NK_TEXT_CENTERED))
|
||||
node_editor_add(nodedit, "New", nk_rect(400, 260, 180, 220),
|
||||
nk_rgb(255, 255, 255), 1, 2);
|
||||
if (nk_contextual_item_label(ctx, grid_option[nodedit->show_grid],NK_TEXT_CENTERED))
|
||||
nodedit->show_grid = !nodedit->show_grid;
|
||||
nk_contextual_end(ctx);
|
||||
}
|
||||
}
|
||||
nk_layout_space_end(ctx);
|
||||
|
||||
/* window content scrolling */
|
||||
if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ctx)) &&
|
||||
nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) {
|
||||
nodedit->scrolling.x += in->mouse.delta.x;
|
||||
nodedit->scrolling.y += in->mouse.delta.y;
|
||||
}
|
||||
}
|
||||
nk_end(ctx);
|
||||
return !nk_window_is_closed(ctx, "NodeEdit");
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK};
|
||||
|
||||
void
|
||||
set_style(struct nk_context *ctx, enum theme theme)
|
||||
{
|
||||
struct nk_color table[NK_COLOR_COUNT];
|
||||
if (theme == THEME_WHITE) {
|
||||
table[NK_COLOR_TEXT] = nk_rgba(70, 70, 70, 255);
|
||||
table[NK_COLOR_WINDOW] = nk_rgba(175, 175, 175, 255);
|
||||
table[NK_COLOR_HEADER] = nk_rgba(175, 175, 175, 255);
|
||||
table[NK_COLOR_BORDER] = nk_rgba(0, 0, 0, 255);
|
||||
table[NK_COLOR_BUTTON] = nk_rgba(185, 185, 185, 255);
|
||||
table[NK_COLOR_BUTTON_HOVER] = nk_rgba(170, 170, 170, 255);
|
||||
table[NK_COLOR_BUTTON_ACTIVE] = nk_rgba(160, 160, 160, 255);
|
||||
table[NK_COLOR_TOGGLE] = nk_rgba(150, 150, 150, 255);
|
||||
table[NK_COLOR_TOGGLE_HOVER] = nk_rgba(120, 120, 120, 255);
|
||||
table[NK_COLOR_TOGGLE_CURSOR] = nk_rgba(175, 175, 175, 255);
|
||||
table[NK_COLOR_SELECT] = nk_rgba(190, 190, 190, 255);
|
||||
table[NK_COLOR_SELECT_ACTIVE] = nk_rgba(175, 175, 175, 255);
|
||||
table[NK_COLOR_SLIDER] = nk_rgba(190, 190, 190, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR] = nk_rgba(80, 80, 80, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR_HOVER] = nk_rgba(70, 70, 70, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR_ACTIVE] = nk_rgba(60, 60, 60, 255);
|
||||
table[NK_COLOR_PROPERTY] = nk_rgba(175, 175, 175, 255);
|
||||
table[NK_COLOR_EDIT] = nk_rgba(150, 150, 150, 255);
|
||||
table[NK_COLOR_EDIT_CURSOR] = nk_rgba(0, 0, 0, 255);
|
||||
table[NK_COLOR_COMBO] = nk_rgba(175, 175, 175, 255);
|
||||
table[NK_COLOR_CHART] = nk_rgba(160, 160, 160, 255);
|
||||
table[NK_COLOR_CHART_COLOR] = nk_rgba(45, 45, 45, 255);
|
||||
table[NK_COLOR_CHART_COLOR_HIGHLIGHT] = nk_rgba( 255, 0, 0, 255);
|
||||
table[NK_COLOR_SCROLLBAR] = nk_rgba(180, 180, 180, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR] = nk_rgba(140, 140, 140, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = nk_rgba(150, 150, 150, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = nk_rgba(160, 160, 160, 255);
|
||||
table[NK_COLOR_TAB_HEADER] = nk_rgba(180, 180, 180, 255);
|
||||
nk_style_from_table(ctx, table);
|
||||
} else if (theme == THEME_RED) {
|
||||
table[NK_COLOR_TEXT] = nk_rgba(190, 190, 190, 255);
|
||||
table[NK_COLOR_WINDOW] = nk_rgba(30, 33, 40, 215);
|
||||
table[NK_COLOR_HEADER] = nk_rgba(181, 45, 69, 220);
|
||||
table[NK_COLOR_BORDER] = nk_rgba(51, 55, 67, 255);
|
||||
table[NK_COLOR_BUTTON] = nk_rgba(181, 45, 69, 255);
|
||||
table[NK_COLOR_BUTTON_HOVER] = nk_rgba(190, 50, 70, 255);
|
||||
table[NK_COLOR_BUTTON_ACTIVE] = nk_rgba(195, 55, 75, 255);
|
||||
table[NK_COLOR_TOGGLE] = nk_rgba(51, 55, 67, 255);
|
||||
table[NK_COLOR_TOGGLE_HOVER] = nk_rgba(45, 60, 60, 255);
|
||||
table[NK_COLOR_TOGGLE_CURSOR] = nk_rgba(181, 45, 69, 255);
|
||||
table[NK_COLOR_SELECT] = nk_rgba(51, 55, 67, 255);
|
||||
table[NK_COLOR_SELECT_ACTIVE] = nk_rgba(181, 45, 69, 255);
|
||||
table[NK_COLOR_SLIDER] = nk_rgba(51, 55, 67, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR] = nk_rgba(181, 45, 69, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR_HOVER] = nk_rgba(186, 50, 74, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR_ACTIVE] = nk_rgba(191, 55, 79, 255);
|
||||
table[NK_COLOR_PROPERTY] = nk_rgba(51, 55, 67, 255);
|
||||
table[NK_COLOR_EDIT] = nk_rgba(51, 55, 67, 225);
|
||||
table[NK_COLOR_EDIT_CURSOR] = nk_rgba(190, 190, 190, 255);
|
||||
table[NK_COLOR_COMBO] = nk_rgba(51, 55, 67, 255);
|
||||
table[NK_COLOR_CHART] = nk_rgba(51, 55, 67, 255);
|
||||
table[NK_COLOR_CHART_COLOR] = nk_rgba(170, 40, 60, 255);
|
||||
table[NK_COLOR_CHART_COLOR_HIGHLIGHT] = nk_rgba( 255, 0, 0, 255);
|
||||
table[NK_COLOR_SCROLLBAR] = nk_rgba(30, 33, 40, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR] = nk_rgba(64, 84, 95, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = nk_rgba(70, 90, 100, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = nk_rgba(75, 95, 105, 255);
|
||||
table[NK_COLOR_TAB_HEADER] = nk_rgba(181, 45, 69, 220);
|
||||
nk_style_from_table(ctx, table);
|
||||
} else if (theme == THEME_BLUE) {
|
||||
table[NK_COLOR_TEXT] = nk_rgba(20, 20, 20, 255);
|
||||
table[NK_COLOR_WINDOW] = nk_rgba(202, 212, 214, 215);
|
||||
table[NK_COLOR_HEADER] = nk_rgba(137, 182, 224, 220);
|
||||
table[NK_COLOR_BORDER] = nk_rgba(140, 159, 173, 255);
|
||||
table[NK_COLOR_BUTTON] = nk_rgba(137, 182, 224, 255);
|
||||
table[NK_COLOR_BUTTON_HOVER] = nk_rgba(142, 187, 229, 255);
|
||||
table[NK_COLOR_BUTTON_ACTIVE] = nk_rgba(147, 192, 234, 255);
|
||||
table[NK_COLOR_TOGGLE] = nk_rgba(177, 210, 210, 255);
|
||||
table[NK_COLOR_TOGGLE_HOVER] = nk_rgba(182, 215, 215, 255);
|
||||
table[NK_COLOR_TOGGLE_CURSOR] = nk_rgba(137, 182, 224, 255);
|
||||
table[NK_COLOR_SELECT] = nk_rgba(177, 210, 210, 255);
|
||||
table[NK_COLOR_SELECT_ACTIVE] = nk_rgba(137, 182, 224, 255);
|
||||
table[NK_COLOR_SLIDER] = nk_rgba(177, 210, 210, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR] = nk_rgba(137, 182, 224, 245);
|
||||
table[NK_COLOR_SLIDER_CURSOR_HOVER] = nk_rgba(142, 188, 229, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR_ACTIVE] = nk_rgba(147, 193, 234, 255);
|
||||
table[NK_COLOR_PROPERTY] = nk_rgba(210, 210, 210, 255);
|
||||
table[NK_COLOR_EDIT] = nk_rgba(210, 210, 210, 225);
|
||||
table[NK_COLOR_EDIT_CURSOR] = nk_rgba(20, 20, 20, 255);
|
||||
table[NK_COLOR_COMBO] = nk_rgba(210, 210, 210, 255);
|
||||
table[NK_COLOR_CHART] = nk_rgba(210, 210, 210, 255);
|
||||
table[NK_COLOR_CHART_COLOR] = nk_rgba(137, 182, 224, 255);
|
||||
table[NK_COLOR_CHART_COLOR_HIGHLIGHT] = nk_rgba( 255, 0, 0, 255);
|
||||
table[NK_COLOR_SCROLLBAR] = nk_rgba(190, 200, 200, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR] = nk_rgba(64, 84, 95, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = nk_rgba(70, 90, 100, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = nk_rgba(75, 95, 105, 255);
|
||||
table[NK_COLOR_TAB_HEADER] = nk_rgba(156, 193, 220, 255);
|
||||
nk_style_from_table(ctx, table);
|
||||
} else if (theme == THEME_DARK) {
|
||||
table[NK_COLOR_TEXT] = nk_rgba(210, 210, 210, 255);
|
||||
table[NK_COLOR_WINDOW] = nk_rgba(57, 67, 71, 215);
|
||||
table[NK_COLOR_HEADER] = nk_rgba(51, 51, 56, 220);
|
||||
table[NK_COLOR_BORDER] = nk_rgba(46, 46, 46, 255);
|
||||
table[NK_COLOR_BUTTON] = nk_rgba(48, 83, 111, 255);
|
||||
table[NK_COLOR_BUTTON_HOVER] = nk_rgba(58, 93, 121, 255);
|
||||
table[NK_COLOR_BUTTON_ACTIVE] = nk_rgba(63, 98, 126, 255);
|
||||
table[NK_COLOR_TOGGLE] = nk_rgba(50, 58, 61, 255);
|
||||
table[NK_COLOR_TOGGLE_HOVER] = nk_rgba(45, 53, 56, 255);
|
||||
table[NK_COLOR_TOGGLE_CURSOR] = nk_rgba(48, 83, 111, 255);
|
||||
table[NK_COLOR_SELECT] = nk_rgba(57, 67, 61, 255);
|
||||
table[NK_COLOR_SELECT_ACTIVE] = nk_rgba(48, 83, 111, 255);
|
||||
table[NK_COLOR_SLIDER] = nk_rgba(50, 58, 61, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR] = nk_rgba(48, 83, 111, 245);
|
||||
table[NK_COLOR_SLIDER_CURSOR_HOVER] = nk_rgba(53, 88, 116, 255);
|
||||
table[NK_COLOR_SLIDER_CURSOR_ACTIVE] = nk_rgba(58, 93, 121, 255);
|
||||
table[NK_COLOR_PROPERTY] = nk_rgba(50, 58, 61, 255);
|
||||
table[NK_COLOR_EDIT] = nk_rgba(50, 58, 61, 225);
|
||||
table[NK_COLOR_EDIT_CURSOR] = nk_rgba(210, 210, 210, 255);
|
||||
table[NK_COLOR_COMBO] = nk_rgba(50, 58, 61, 255);
|
||||
table[NK_COLOR_CHART] = nk_rgba(50, 58, 61, 255);
|
||||
table[NK_COLOR_CHART_COLOR] = nk_rgba(48, 83, 111, 255);
|
||||
table[NK_COLOR_CHART_COLOR_HIGHLIGHT] = nk_rgba(255, 0, 0, 255);
|
||||
table[NK_COLOR_SCROLLBAR] = nk_rgba(50, 58, 61, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR] = nk_rgba(48, 83, 111, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = nk_rgba(53, 88, 116, 255);
|
||||
table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = nk_rgba(58, 93, 121, 255);
|
||||
table[NK_COLOR_TAB_HEADER] = nk_rgba(48, 83, 111, 255);
|
||||
nk_style_from_table(ctx, table);
|
||||
} else {
|
||||
nk_style_default(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|