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
Martin Gerhardy 2020-03-19 20:40:55 +01:00
parent 98c5be6717
commit eb54b6e6b9
68 changed files with 35884 additions and 1 deletions

View File

@ -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:

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,2 +1,3 @@
add_subdirectory(imgui)
add_subdirectory(turbobadger)
add_subdirectory(nuklear)

View File

@ -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)

View File

@ -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());
}
}
}

View File

@ -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);
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
};
}
}

View File

@ -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"
/**
* @}
*/

View File

@ -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();
}
}
}

View File

@ -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;
};
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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

View File

@ -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)

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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");
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
}