Add C++ implementation of custom knob widget and example
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
parent
e0003dded5
commit
2da51dd7cd
|
@ -1,19 +1,33 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(nanogui_helloworld)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(nanogui_experiments
|
||||
VERSION 1.0
|
||||
DESCRIPTION "NanoGUI experiments"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||
set(NANOGUI_REPO "https://github.com/SpotlightKid/nanogui" CACHE STRING "nanoGUi repository URL or path")
|
||||
set(NANOGUI_BUILD_EXAMPLES OFF)
|
||||
set(NANOGUI_BUILD_PYTHON OFF)
|
||||
set(NANOGUI_BUILD_SHARED OFF)
|
||||
|
||||
add_subdirectory(lib/nanogui)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(nanogui
|
||||
GIT_REPOSITORY ${NANOGUI_REPO}
|
||||
GIT_TAG nanogui-experiments
|
||||
GIT_SHALLOW true
|
||||
SOURCE_DIR lib/nanogui
|
||||
)
|
||||
FetchContent_MakeAvailable(nanogui)
|
||||
|
||||
include_directories(lib/nanogui/include)
|
||||
include_directories(${NANOGUI_EXTRA_INCS})
|
||||
add_definitions(${NANOGUI_EXTRA_DEFS})
|
||||
|
||||
set_property(TARGET glfw glfw_objects nanogui PROPERTY FOLDER "dependencies")
|
||||
|
||||
add_executable(nanogui_helloworld nanogui_helloworld.cpp)
|
||||
target_compile_features(nanogui_helloworld PRIVATE cxx_std_17)
|
||||
target_link_libraries(nanogui_helloworld nanogui ${NANOGUI_EXTRA_LIBS})
|
||||
|
||||
add_executable(nanogui_knobs nanogui_knobs.cpp fancyknob.cpp)
|
||||
target_compile_features(nanogui_knobs PRIVATE cxx_std_17)
|
||||
target_link_libraries(nanogui_knobs nanogui ${NANOGUI_EXTRA_LIBS})
|
||||
|
|
47
README.md
47
README.md
|
@ -2,22 +2,67 @@
|
|||
|
||||
![NanoGUI Knobs example app](nanogui_knobs.png)
|
||||
|
||||
## Quickstart
|
||||
## Building
|
||||
|
||||
### Python
|
||||
|
||||
Set up virtual environment providing nanogui Python bindings:
|
||||
|
||||
```con
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
(venv) pip install -r requirenments.txt
|
||||
```
|
||||
|
||||
Run helloworld examples:
|
||||
|
||||
```con
|
||||
(venv) python3 nanogui_helloworld.py
|
||||
(venv) python3 nanogui_custowidget.py
|
||||
```
|
||||
|
||||
### C++
|
||||
|
||||
Configure build with *CMake*:
|
||||
|
||||
```con
|
||||
cmake -B build -S .
|
||||
```
|
||||
|
||||
(This will checkout the NanoGUI library from GitHub into the build directory.
|
||||
You can use `-DNANOGUI_REPO=<repo path or URL>` to change from where Git will
|
||||
retrieve the repository.)
|
||||
|
||||
Build the examples:
|
||||
|
||||
```con
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Run hellworld example:
|
||||
|
||||
```con
|
||||
./build/nanogui_helloworld
|
||||
```
|
||||
|
||||
|
||||
## Knobs Example
|
||||
|
||||
|
||||
### Python
|
||||
|
||||
```con
|
||||
(venv) python3 nanogui_knobs.py
|
||||
```
|
||||
|
||||
|
||||
### C++
|
||||
|
||||
```con
|
||||
./build/nanogui_knobs
|
||||
```
|
||||
|
||||
|
||||
### Key and Mouse Bindings
|
||||
|
||||
| | | |
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include <nanogui/opengl.h>
|
||||
#include <nanogui/theme.h>
|
||||
|
||||
#include "fancyknob.hpp"
|
||||
|
||||
NAMESPACE_BEGIN(nanogui)
|
||||
|
||||
FancyKnob::FancyKnob(Widget* parent, int rad)
|
||||
: Widget(parent)
|
||||
, m_value(0.0f)
|
||||
, m_min_val(0.0f)
|
||||
, m_max_val(100.0f)
|
||||
, m_increment(0.1f)
|
||||
, m_fine_mode(false)
|
||||
, m_scroll_speed(2.0f)
|
||||
, m_gauge_width(0.125f)
|
||||
, m_indicator_size(0.35f)
|
||||
{
|
||||
// TBD: getters & setters for all colors
|
||||
set_gauge_color(Color(255, 80, 80, 255));
|
||||
m_knob_color_1 = Color(86, 92, 95, 255);
|
||||
m_knob_color_2 = Color(39, 42, 43, 255);
|
||||
m_outline_color_1 = Color(190, 190, 190, 0);
|
||||
m_outline_color_2 = Color(23, 23, 23, 255);
|
||||
m_scroll_increment = (m_max_val - m_min_val) / 100.0 * m_scroll_speed;
|
||||
m_drag_increment = (m_max_val - m_min_val) / 100.0;
|
||||
set_cursor(Cursor::Hand);
|
||||
Widget::set_size({ rad, rad });
|
||||
}
|
||||
|
||||
const float FancyKnob::m_pi = std::acos(-1.0f);
|
||||
|
||||
Vector2i FancyKnob::preferred_size(NVGcontext*) const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void FancyKnob::set_min_value(float value)
|
||||
{
|
||||
m_min_val = value;
|
||||
m_scroll_increment = (m_max_val - m_min_val) / 100.0 * m_scroll_speed;
|
||||
m_drag_increment = (m_max_val - m_min_val) / 100.0;
|
||||
}
|
||||
|
||||
void FancyKnob::set_max_value(float value)
|
||||
{
|
||||
m_max_val = value;
|
||||
m_scroll_increment = (m_max_val - m_min_val) / 100.0 * m_scroll_speed;
|
||||
m_drag_increment = (m_max_val - m_min_val) / 100.0;
|
||||
}
|
||||
|
||||
void FancyKnob::set_scroll_speed(float value)
|
||||
{
|
||||
m_scroll_speed = value;
|
||||
m_scroll_increment = (m_max_val - m_min_val) / 100.0 * m_scroll_speed;
|
||||
}
|
||||
|
||||
void FancyKnob::set_gauge_color(const Color& color)
|
||||
{
|
||||
m_gauge_color = color;
|
||||
NVGcolor bg = nvgLerpRGBA(Color(40, 40, 40, 255), m_gauge_color, 0.3f);
|
||||
m_gauge_bg_color = Color(bg.r, bg.g, bg.b, bg.a);
|
||||
}
|
||||
|
||||
bool FancyKnob::adjust_value(float value, float incr)
|
||||
{
|
||||
if (m_fine_mode)
|
||||
incr = m_increment;
|
||||
|
||||
float new_val = m_value + value * incr;
|
||||
new_val = std::max(m_min_val, std::min(m_max_val, new_val));
|
||||
|
||||
if (new_val != m_value) {
|
||||
m_value = new_val;
|
||||
|
||||
if (m_callback)
|
||||
m_callback(m_value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FancyKnob::scroll_event(const Vector2i& p, const Vector2f& rel)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return false;
|
||||
|
||||
adjust_value(rel[1], m_scroll_increment);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FancyKnob::mouse_enter_event(const Vector2i& p, bool enter)
|
||||
{
|
||||
if (enter)
|
||||
request_focus();
|
||||
|
||||
set_focused(enter);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FancyKnob::mouse_drag_event(const Vector2i& p, const Vector2i& rel, int /* button */, int /* modifiers */)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return false;
|
||||
|
||||
adjust_value((float)-rel[1], m_drag_increment);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FancyKnob::mouse_button_event(const Vector2i& /* p */, int button, bool down, int modifiers)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return false;
|
||||
|
||||
if (button == GLFW_MOUSE_BUTTON_1 && modifiers & GLFW_MOD_CONTROL && down) {
|
||||
if (m_value != m_default) {
|
||||
m_value = m_default;
|
||||
|
||||
if (m_callback)
|
||||
m_callback(m_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FancyKnob::keyboard_event(int key, int scancode, int action, int modifiers)
|
||||
{
|
||||
if (key == GLFW_KEY_LEFT_SHIFT || key == GLFW_KEY_RIGHT_SHIFT) {
|
||||
if (action == GLFW_PRESS)
|
||||
m_fine_mode = true;
|
||||
else if (action == GLFW_RELEASE)
|
||||
m_fine_mode = false;
|
||||
return true;
|
||||
} else if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||
if (key == GLFW_KEY_UP) {
|
||||
adjust_value(1, m_drag_increment);
|
||||
return true;
|
||||
} else if (key == GLFW_KEY_DOWN) {
|
||||
adjust_value(-1, m_drag_increment);
|
||||
return true;
|
||||
} else if (key == GLFW_KEY_PAGE_UP) {
|
||||
adjust_value(10, m_drag_increment);
|
||||
return true;
|
||||
} else if (key == GLFW_KEY_PAGE_DOWN) {
|
||||
adjust_value(-10, m_drag_increment);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FancyKnob::draw(NVGcontext* ctx)
|
||||
{
|
||||
float height = (float)m_size.y();
|
||||
float radius = height / 2.0;
|
||||
float gauge_width = radius * m_gauge_width;
|
||||
float margin = gauge_width / 2.0;
|
||||
float percent_filled = (m_value - m_min_val) / (m_max_val - m_min_val);
|
||||
float knob_diameter = (radius - gauge_width) * 2.0 - margin;
|
||||
float indicator_length = radius * m_indicator_size;
|
||||
float indicator_start = radius - margin - indicator_length;
|
||||
|
||||
nvgSave(ctx);
|
||||
nvgTranslate(ctx, m_pos.x(), m_pos.y());
|
||||
|
||||
// Gauge (background)
|
||||
nvgBeginPath(ctx);
|
||||
|
||||
nvgStrokeWidth(ctx, gauge_width);
|
||||
nvgStrokeColor(ctx, m_gauge_bg_color);
|
||||
nvgLineCap(ctx, NVG_ROUND);
|
||||
nvgArc(ctx, radius, radius, radius - margin, 0.75 * m_pi, 0.25 * m_pi, NVG_CW);
|
||||
nvgStroke(ctx);
|
||||
|
||||
// Gauge (fill)
|
||||
nvgBeginPath(ctx);
|
||||
|
||||
nvgStrokeWidth(ctx, gauge_width);
|
||||
nvgStrokeColor(ctx, m_gauge_color);
|
||||
nvgLineCap(ctx, NVG_ROUND);
|
||||
nvgArc(
|
||||
ctx,
|
||||
radius,
|
||||
radius,
|
||||
radius - margin,
|
||||
0.75 * m_pi,
|
||||
(0.75 + 1.5 * percent_filled) * m_pi,
|
||||
NVG_CW);
|
||||
nvgStroke(ctx);
|
||||
|
||||
// Knob
|
||||
nvgBeginPath(ctx);
|
||||
|
||||
nvgStrokeWidth(ctx, 2.0);
|
||||
NVGpaint outline_paint = nvgLinearGradient(
|
||||
ctx,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
height - 10,
|
||||
m_outline_color_1,
|
||||
m_outline_color_2);
|
||||
nvgStrokePaint(ctx, outline_paint);
|
||||
|
||||
NVGpaint knob_paint = nvgLinearGradient(
|
||||
ctx, radius, gauge_width, radius, knob_diameter, m_knob_color_1, m_knob_color_2);
|
||||
nvgFillPaint(ctx, knob_paint);
|
||||
|
||||
nvgCircle(ctx, radius, radius, knob_diameter / 2.0);
|
||||
nvgFill(ctx);
|
||||
nvgStroke(ctx);
|
||||
|
||||
// Indicator
|
||||
nvgBeginPath(ctx);
|
||||
|
||||
nvgTranslate(ctx, radius, radius);
|
||||
nvgRotate(ctx, (2.0 + ((percent_filled - 0.5) * 1.5)) * m_pi);
|
||||
|
||||
nvgStrokeColor(ctx, m_gauge_color);
|
||||
nvgStrokeWidth(ctx, gauge_width);
|
||||
nvgLineCap(ctx, NVG_ROUND);
|
||||
nvgMoveTo(ctx, 0, -indicator_start);
|
||||
nvgLineTo(ctx, 0, -(indicator_start + indicator_length));
|
||||
nvgStroke(ctx);
|
||||
|
||||
nvgRestore(ctx);
|
||||
nvgClosePath(ctx);
|
||||
}
|
||||
|
||||
NAMESPACE_END(nanogui)
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef FANCYKNOB_H
|
||||
#define FANCYKNOB_H
|
||||
|
||||
#include <nanogui/widget.h>
|
||||
|
||||
NAMESPACE_BEGIN(nanogui)
|
||||
|
||||
class NANOGUI_EXPORT FancyKnob : public Widget {
|
||||
public:
|
||||
FancyKnob(Widget* parent, int rad = 80);
|
||||
|
||||
float value() const { return m_value; }
|
||||
void set_value(float value)
|
||||
{
|
||||
if (value != m_value)
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
float min_default() const { return m_default; }
|
||||
void set_default(float value) { m_default = value; }
|
||||
|
||||
float min_value() const { return m_min_val; }
|
||||
void set_min_value(float value);
|
||||
|
||||
float max_value() const { return m_max_val; }
|
||||
void set_max_value(float value);
|
||||
|
||||
float increment() const { return m_increment; }
|
||||
void set_increment(float value) { m_increment = value; }
|
||||
|
||||
float scroll_speed() const { return m_scroll_speed; }
|
||||
void set_scroll_speed(float value);
|
||||
|
||||
const Color& gauge_color() const { return m_gauge_color; }
|
||||
void set_gauge_color(const Color& color);
|
||||
|
||||
std::function<void(float)> callback() const { return m_callback; }
|
||||
void set_callback(const std::function<void(float)>& callback) { m_callback = callback; }
|
||||
|
||||
virtual Vector2i preferred_size(NVGcontext* ctx) const override;
|
||||
virtual bool mouse_enter_event(const Vector2i& p, bool enter);
|
||||
virtual bool mouse_drag_event(const Vector2i& p, const Vector2i& rel, int button, int modifiers) override;
|
||||
virtual bool mouse_button_event(const Vector2i& p, int button, bool down, int modifiers) override;
|
||||
virtual bool scroll_event(const Vector2i& p, const Vector2f& rel) override;
|
||||
virtual bool keyboard_event(int key, int scancode, int action, int modifiers);
|
||||
virtual void draw(NVGcontext* ctx) override;
|
||||
|
||||
protected:
|
||||
bool adjust_value(float value, float incr);
|
||||
|
||||
private:
|
||||
static const float m_pi;
|
||||
|
||||
// colors
|
||||
Color
|
||||
m_gauge_color,
|
||||
m_gauge_bg_color,
|
||||
m_knob_color_1,
|
||||
m_knob_color_2,
|
||||
m_outline_color_1,
|
||||
m_outline_color_2;
|
||||
|
||||
// sizes, value and range
|
||||
float
|
||||
m_value,
|
||||
m_min_val,
|
||||
m_max_val,
|
||||
m_default,
|
||||
m_increment,
|
||||
m_scroll_speed,
|
||||
m_scroll_increment,
|
||||
m_drag_increment,
|
||||
/* value gauge width as ratio of knob radius */
|
||||
m_gauge_width,
|
||||
/* value indicator line length as ratio of knob radius */
|
||||
m_indicator_size;
|
||||
|
||||
// behaviour
|
||||
bool m_fine_mode = false;
|
||||
std::function<void(float)> m_callback;
|
||||
};
|
||||
|
||||
NAMESPACE_END(nanogui)
|
||||
|
||||
#endif // FANCYKNOB_H
|
|
@ -1 +0,0 @@
|
|||
../../nanogui
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
src/nanogui_customwidget.cpp -- C++ version of an customwidget example application
|
||||
*/
|
||||
|
||||
#include <nanogui/button.h>
|
||||
#include <nanogui/label.h>
|
||||
#include <nanogui/layout.h>
|
||||
#include <nanogui/opengl.h>
|
||||
#include <nanogui/screen.h>
|
||||
#include <nanogui/textbox.h>
|
||||
#include <nanogui/widget.h>
|
||||
#include <nanogui/window.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "fancyknob.hpp"
|
||||
|
||||
using namespace nanogui;
|
||||
|
||||
struct KnobSpec {
|
||||
std::string name;
|
||||
float default_value;
|
||||
float min_value;
|
||||
float max_value;
|
||||
float increment;
|
||||
std::string unit;
|
||||
Vector4i color;
|
||||
};
|
||||
|
||||
KnobSpec knobs[4] = {
|
||||
{ "Attack", 0.0, 0.0, 5.0, 0.01, "s", { 224, 128, 128, 255 } },
|
||||
{ "Decay", 0.2, 0.0, 5.0, 0.01, "s", { 128, 224, 128, 255 } },
|
||||
{ "Sustain", 100.0, 0.0, 100.0, 0.01, "%", { 128, 128, 224, 255 } },
|
||||
{ "Release", 0.2, 0.0, 5.0, 0.01, "s", { 224, 224, 128, 255 } },
|
||||
};
|
||||
|
||||
class KnobsApplication : public Screen {
|
||||
public:
|
||||
KnobsApplication()
|
||||
: Screen(Vector2i(452, 250), "NanoGUI Knobs")
|
||||
{
|
||||
inc_ref();
|
||||
set_background(Color(96, 96, 96, 255));
|
||||
|
||||
window = new Window(this, "Envelope");
|
||||
window->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 20, 20));
|
||||
resize_event(size());
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Widget* box = new Widget(window);
|
||||
box->set_layout(new BoxLayout(Orientation::Vertical, Alignment::Middle, 0, 10));
|
||||
|
||||
KnobSpec knob = knobs[i];
|
||||
FancyKnob* k = new FancyKnob(box);
|
||||
k->set_default(knob.default_value);
|
||||
k->set_value(knob.default_value);
|
||||
k->set_min_value(knob.min_value);
|
||||
k->set_max_value(knob.max_value);
|
||||
k->set_increment(knob.increment);
|
||||
k->set_gauge_color(Color(knob.color));
|
||||
|
||||
FloatBox<float>* entry = new FloatBox<float>(box);
|
||||
entry->set_fixed_size({ 88, 24 });
|
||||
entry->set_font_size(20);
|
||||
entry->set_editable(true);
|
||||
entry->set_spinnable(true);
|
||||
entry->set_value(k->value());
|
||||
entry->number_format("%02.2f");
|
||||
entry->set_min_value(k->min_value());
|
||||
entry->set_max_value(k->max_value());
|
||||
entry->set_value_increment(k->increment());
|
||||
entry->set_units(knob.unit);
|
||||
entry->set_callback([k](float f) { k->set_value(f); });
|
||||
|
||||
k->set_callback([entry, knob](float f) {
|
||||
entry->set_value(f);
|
||||
std::cout << "'" << knob.name << "' value: " << f << std::endl;
|
||||
});
|
||||
|
||||
Label* l = new Label(box, knob.name, "sans", 20);
|
||||
}
|
||||
|
||||
perform_layout();
|
||||
}
|
||||
|
||||
virtual bool resize_event(const Vector2i& size)
|
||||
{
|
||||
window->set_fixed_size(size);
|
||||
window->set_size(size);
|
||||
window->center();
|
||||
window->perform_layout(nvg_context());
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool keyboard_event(int key, int scancode, int action, int modifiers)
|
||||
{
|
||||
if (Screen::keyboard_event(key, scancode, action, modifiers))
|
||||
return true;
|
||||
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
|
||||
set_visible(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void draw(NVGcontext* ctx)
|
||||
{
|
||||
/* Draw the user interface */
|
||||
Screen::draw(ctx);
|
||||
}
|
||||
|
||||
private:
|
||||
Window* window;
|
||||
};
|
||||
|
||||
int main(int /* argc */, char** /* argv */)
|
||||
{
|
||||
try {
|
||||
nanogui::init();
|
||||
|
||||
/* scoped variables */ {
|
||||
ref<KnobsApplication> app = new KnobsApplication();
|
||||
app->dec_ref();
|
||||
app->draw_all();
|
||||
app->set_visible(true);
|
||||
nanogui::mainloop(1 / 60.f * 1000);
|
||||
}
|
||||
|
||||
nanogui::shutdown();
|
||||
} catch (const std::exception& e) {
|
||||
std::string error_msg = std::string("Caught a fatal error: ") + std::string(e.what());
|
||||
#if defined(_WIN32)
|
||||
MessageBoxA(nullptr, error_msg.c_str(), NULL, MB_ICONERROR | MB_OK);
|
||||
#else
|
||||
std::cerr << error_msg << std::endl;
|
||||
#endif
|
||||
return -1;
|
||||
} catch (...) {
|
||||
std::cerr << "Caught an unknown error!" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue