Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add knob widget #71

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ add_library(nanogui ${NANOGUI_LIBRARY_TYPE}
include/nanogui/combobox.h src/combobox.cpp
include/nanogui/progressbar.h src/progressbar.cpp
include/nanogui/slider.h src/slider.cpp
include/nanogui/knob.h src/knob.cpp
include/nanogui/messagedialog.h src/messagedialog.cpp
include/nanogui/textbox.h src/textbox.cpp
include/nanogui/textarea.h src/textarea.cpp
Expand Down Expand Up @@ -468,12 +469,14 @@ if (NANOGUI_BUILD_EXAMPLES)
add_executable(example3 src/example3.cpp)
add_executable(example4 src/example4.cpp)
add_executable(example_icons src/example_icons.cpp)
add_executable(example_knob src/example_knob.cpp)

target_link_libraries(example1 nanogui)
target_link_libraries(example2 nanogui)
target_link_libraries(example3 nanogui ${NANOGUI_LIBS}) # For OpenGL
target_link_libraries(example4 nanogui)
target_link_libraries(example_icons nanogui)
target_link_libraries(example_knob nanogui)

# Copy icons for example application
file(COPY resources/icons DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
Expand Down
54 changes: 54 additions & 0 deletions include/nanogui/knob.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef KNOB_H
#define KNOB_H

#include <nanogui/widget.h>

NAMESPACE_BEGIN(nanogui)

class NANOGUI_EXPORT Knob : public Widget {
public:
Knob(Widget *parent, int rad=100);

float value() const { return m_value; }
void set_value(float value) { m_value = value; }

int notches() const { return m_notches; }
void set_notches(int n) { m_notches = n; }

const Color &highlight_color() const { return m_highlight_color; }
void set_highlight_color(const Color &highlight_color) { m_highlight_color = highlight_color; }

std::pair<float, float> range() const { return m_range; }
void set_range(std::pair<float, float> range) { m_range = range; }

std::pair<float, float> highlighted_range() const { return m_highlighted_range; }
void set_highlighted_range(std::pair<float, float> highlighted_range) { m_highlighted_range = highlighted_range; }

std::function<void(float)> callback() const { return m_callback; }
void set_callback(const std::function<void(float)> &callback) { m_callback = callback; }

std::function<void(float)> final_callback() const { return m_final_callback; }
void set_final_callback(const std::function<void(float)> &callback) { m_final_callback = callback; }

virtual Vector2i preferred_size(NVGcontext *ctx) const override;
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 void draw(NVGcontext* ctx) override;

protected:
float m_value;
int m_notches;
std::function<void(float)> m_callback;
std::function<void(float)> m_final_callback;
std::pair<float, float> m_range;
std::pair<float, float> m_highlighted_range;
Color m_highlight_color;

private:
void set_value_by_mouse_position(const Vector2i &p);
static const float m_pi;
};

NAMESPACE_END(nanogui)

#endif // KNOB_H
88 changes: 88 additions & 0 deletions src/example_knob.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include <iostream>

#include <nanogui/screen.h>
#include <nanogui/window.h>
#include <nanogui/layout.h>
#include <nanogui/label.h>
#include <nanogui/slider.h>
#include <nanogui/theme.h>
#include <nanogui/knob.h>

namespace nga {
using namespace nanogui;
using std::cout, std::endl;

class ExampleApplication : public Screen {
public:
ExampleApplication() : Screen(Vector2i(1024, 768), "Knob example") {
inc_ref();
Window *window = new Window(this, "Knob example");
window->set_position(Vector2i(15, 15));
window->set_fixed_size(Vector2i(400, 650));

Theme* th = new Theme(this->nvg_context());
th->m_window_fill_focused = m_theme->m_border_light;
th->m_window_fill_unfocused = m_theme->m_border_light;
window->set_theme(th);

window->set_layout(new BoxLayout(Orientation::Vertical,Alignment::Middle,40,20 ));

new Label(window, "Vertical layout", "sans-bold");

Slider *slider = new Slider(window); //provare argomento this
slider->set_value(0.5f);
slider->set_fixed_width(150);
slider->set_highlighted_range({0.3f,0.5f});

Knob* k = new Knob(window);
k->set_value(0.25f);
k->set_final_callback([](float f){ std::cout << "Knob k final value: " << f << endl; });
k->set_callback([](float f){ std::cout << "Knob k value: " << f << endl; });
k->set_range({35.0f,97.0f});
k->set_highlighted_range({50.0f,60.0f});

Knob* k2 = new Knob(window, 30);
k2->set_value(0.45f);
k2->set_notches(0);

Knob* k3 = new Knob(window, 300);
k3->set_value(0.45f);
k3->set_notches(24);
k3->set_final_callback([](float f){ std::cout << "Knob k3 final value: " << f << endl; });
k3->set_callback([](float f){ std::cout << "Knob k3 value: " << f << endl; });
k3->set_highlighted_range({0.90f,1.0f});

perform_layout();
}
};

}

int main()
{
using std::cout, std::endl;
cout << "Nano GUI Knob example" << endl;
cout << "---------------------" << endl;

try {
nanogui::init();

/* scoped variables */ {
nanogui::ref<nga::ExampleApplication> app = new nga::ExampleApplication();
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());
std::cerr << error_msg << std::endl;
return -1;
} catch (...) {
std::cerr << "Caught an unknown error!" << std::endl;
}

return 0;
}
118 changes: 118 additions & 0 deletions src/knob.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include <nanogui/knob.h>
#include <nanogui/theme.h>
#include <nanogui/opengl.h>

NAMESPACE_BEGIN(nanogui)

Knob::Knob(Widget *parent, int rad)
: Widget(parent), m_value(0.0f), m_notches{12}, m_range(0.f, 1.f),
m_highlighted_range(0.f, 0.f)
{
m_highlight_color = Color(255, 80, 80, 70);
Widget::set_size({rad,rad});
}

const float Knob::m_pi = std::acos(-1.0f);

Vector2i Knob::preferred_size(NVGcontext *) const {
return m_size;
}

bool Knob::mouse_drag_event(const Vector2i &p, const Vector2i & /* rel */,
int /* button */, int /* modifiers */) {
if (!m_enabled)
return false;

set_value_by_mouse_position(p);
if (m_callback)
m_callback(m_value);

return true;
}

bool Knob::mouse_button_event(const Vector2i &p, int /* button */, bool down, int /* modifiers */) {
if (!m_enabled)
return false;

set_value_by_mouse_position(p);
if (m_final_callback && !down)
m_final_callback(m_value);

return true;
}

void Knob::draw(NVGcontext* ctx) {
Vector2f center = Vector2f(m_pos) + Vector2f(m_size) * 0.5f;
float radius = static_cast<float>( width()/2 - 1 );

NVGpaint knob_gradient = nvgRadialGradient(ctx,
center.x(), center.y(), radius * 0.65f, radius,
m_theme->m_border_light, m_theme->m_border_medium);

nvgBeginPath(ctx);
nvgCircle(ctx, center.x(), center.y(), radius);
nvgFillPaint(ctx, knob_gradient);
nvgStroke(ctx);
nvgFill(ctx);

nvgBeginPath(ctx);
nvgMoveTo(ctx,center.x() + 0.7f * radius, center.y());
nvgLineTo(ctx,center.x() + radius, center.y());
nvgFillColor(ctx, Color(200, 200, 200, 200) );
nvgFill(ctx);

float angle_step = (2.0f * m_pi) / static_cast<float>(m_notches);
for(int i=1; i<m_notches; ++i){
nvgBeginPath(ctx);
float angle = static_cast<float>(i) * angle_step;
float begin_radius = 0.9f * radius;
auto begin_notch = center + Vector2f{begin_radius * std::cos(angle), begin_radius * std::sin(angle)};
auto end_notch = center + Vector2f{radius * std::cos(angle), radius * std::sin(angle)};
nvgMoveTo(ctx,begin_notch.x(), begin_notch.y());
nvgLineTo(ctx,end_notch.x(), end_notch.y());
nvgFillColor(ctx, Color(200, 200, 200, 200) );
nvgFill(ctx);
}

auto clove_radius = 0.7f * radius;
nvgBeginPath(ctx);
nvgArc(ctx, center.x(), center.y(), clove_radius, 0.3f * m_pi, 0, NVGwinding::NVG_CCW);
nvgArc(ctx, center.x() + 0.34f * radius, center.y(), 0.5f * clove_radius, 0, 0.5f * m_pi, NVGwinding::NVG_CW);
nvgFillColor(ctx, Color(200, 200, 200, 200) );
nvgStroke(ctx);
nvgFill(ctx);

if( m_highlighted_range.second > m_highlighted_range.first &&
m_highlighted_range.first >= m_range.first &&
m_highlighted_range.second <= m_range.second ){
float angle_begin = 2.0f * m_pi * (m_highlighted_range.first - m_range.first) / (m_range.second - m_range.first);
float angle_end = 2.0f * m_pi * (m_highlighted_range.second - m_range.first) / (m_range.second - m_range.first);

nvgBeginPath(ctx);
nvgArc(ctx, center.x(), center.y(), radius, angle_end, angle_begin, NVGwinding::NVG_CCW);
nvgArc(ctx, center.x(), center.y(), 0.85f * radius, angle_begin, angle_end, NVGwinding::NVG_CW);
nvgFillColor(ctx, m_highlight_color);
nvgFill(ctx);
}

float hand_angle = 2.0f * m_pi * (value() - range().first) / (range().second - range().first);
float hand_dist = 0.8f * radius;
Vector2f hand_center = center + Vector2f{hand_dist * std::cos(hand_angle), hand_dist * std::sin(hand_angle)};
float hand_radius = radius / 8;

nvgBeginPath(ctx);
nvgCircle(ctx, hand_center.x(), hand_center.y(), hand_radius);
nvgFillColor(ctx, Color(200, 200, 200, 255));
nvgStroke(ctx);
nvgFill(ctx);
}

void Knob::set_value_by_mouse_position(const Vector2i &p)
{
Vector2f center = Vector2f(m_pos) + Vector2f(m_size) * 0.5f;
float angle = m_pi/2 - std::atan( ( static_cast<float>(p.x() ) - center.x()) / ( static_cast<float>(p.y() ) - center.y()) );
float value01 = 0.5f * angle / m_pi + ( static_cast<float>(p.y()) - center.y() < 0 ? 0.5f : 0.0f);
m_value = m_range.first + value01 * (m_range.second - m_range.first);
}

NAMESPACE_END(nanogui)