From 47d914ccc163ffbf2258c53a3f9a01fd413416f4 Mon Sep 17 00:00:00 2001 From: Erik Hofman Date: Fri, 10 May 2019 15:09:29 +0200 Subject: [PATCH] Add a new Dial widget based on the knob of the Slider widget --- CMakeLists.txt | 1 + include/nanogui/dial.h | 56 ++++++++++++++++ include/nanogui/nanogui.h | 1 + src/dial.cpp | 130 ++++++++++++++++++++++++++++++++++++++ src/example1.cpp | 29 +++++++++ 5 files changed, 217 insertions(+) create mode 100644 include/nanogui/dial.h create mode 100644 src/dial.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5447dd1d7a..c800aaab66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -330,6 +330,7 @@ add_library(nanogui-obj OBJECT include/nanogui/combobox.h src/combobox.cpp include/nanogui/progressbar.h src/progressbar.cpp include/nanogui/slider.h src/slider.cpp + include/nanogui/dial.h src/dial.cpp include/nanogui/messagedialog.h src/messagedialog.cpp include/nanogui/textbox.h src/textbox.cpp include/nanogui/imagepanel.h src/imagepanel.cpp diff --git a/include/nanogui/dial.h b/include/nanogui/dial.h new file mode 100644 index 0000000000..993805630d --- /dev/null +++ b/include/nanogui/dial.h @@ -0,0 +1,56 @@ +/* + nanogui/dial.h -- Fractional dial widget with mouse control + + NanoGUI was developed by Wenzel Jakob . + The widget drawing code is based on the NanoVG demo application + by Mikko Mononen. + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE.txt file. +*/ +/** \file */ + +#pragma once + +#include + +NAMESPACE_BEGIN(nanogui) + +/** + * \class Dial dial.h nanogui/dial.h + * + * \brief Fractional dial widget with mouse control. + */ +class NANOGUI_EXPORT Dial : public Widget { +public: + Dial(Widget *parent); + + float value() const { return mValue; } + void setValue(float value) { mValue = value; } + + std::pair range() const { return mRange; } + void setRange(std::pair range) { mRange = range; } + + std::function callback() const { return mCallback; } + void setCallback(const std::function &callback) { mCallback = callback; } + + std::function finalCallback() const { return mFinalCallback; } + void setFinalCallback(const std::function &callback) { mFinalCallback = callback; } + + virtual Vector2i preferredSize(NVGcontext *ctx) const override; + virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override; + virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override; + virtual void draw(NVGcontext* ctx) override; + virtual void save(Serializer &s) const override; + virtual bool load(Serializer &s) override; + +protected: + float mValue; + std::function mCallback; + std::function mFinalCallback; + std::pair mRange; +public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW +}; + +NAMESPACE_END(nanogui) diff --git a/include/nanogui/nanogui.h b/include/nanogui/nanogui.h index 0661d3f452..85c68b74f3 100644 --- a/include/nanogui/nanogui.h +++ b/include/nanogui/nanogui.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/dial.cpp b/src/dial.cpp new file mode 100644 index 0000000000..4c5d005a8c --- /dev/null +++ b/src/dial.cpp @@ -0,0 +1,130 @@ +/* + nanogui/dial.cpp -- Fractional dial widget with mouse control + + NanoGUI was developed by Wenzel Jakob . + The widget drawing code is based on the NanoVG demo application + by Mikko Mononen. + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE.txt file. +*/ + +#include +#include +#include +#include +#include + +using Eigen::Rotation2Df; + +NAMESPACE_BEGIN(nanogui) + +Dial::Dial(Widget *parent) + : Widget(parent), mValue(0.0f), mRange(0.f, 1.f) { +} + +Vector2i Dial::preferredSize(NVGcontext *) const { + return Vector2i(40, 40); +} + +bool Dial::mouseDragEvent(const Vector2i &p, const Vector2i & /* rel */, + int /* button */, int /* modifiers */) { + if (!mEnabled) + return false; + + Vector2f pos = (p - mPos - mSize/2).cast(); + float value = 0.5f + 0.5f*atan2f(pos.x(), -pos.y())/NVG_PI; + value = -0.1f + 1.2f*value; + + value = value * (mRange.second - mRange.first) + mRange.first; + mValue = std::min(std::max(value, mRange.first), mRange.second); + if (mCallback) + mCallback(mValue); + + return true; +} + +bool Dial::mouseButtonEvent(const Vector2i &p, int /* button */, bool down, int /* modifiers */) { + if (!mEnabled) + return false; + + if (down) { + Vector2f pos = (p - mPos - mSize/2).cast(); + float kr = 0.5f * (mSize.y() * 0.4f); + + if (pos.squaredNorm() >= kr*kr) { + float value = 0.5f + 0.5f*atan2f(pos.x(), -pos.y())/NVG_PI; + value = -0.1f + 1.2f*value; + + value = value * (mRange.second - mRange.first) + mRange.first; + mValue = std::min(std::max(value, mRange.first), mRange.second); + } + if (mCallback) + mCallback(mValue); + } else if (mFinalCallback) { + mFinalCallback(mValue); + } + + return true; +} + +void Dial::draw(NVGcontext* ctx) { + Vector2f center = mPos.cast() + mSize.cast() * 0.5f; + float kr = (int) (mSize.y() * 0.4f), kshadow = 2; + + Vector2f dialPos(center.x(), center.y() + 0.5f); + + NVGpaint dial = nvgLinearGradient(ctx, + mPos.x(), center.y() - kr, mPos.x(), center.y() + kr, + mTheme->mBorderLight, mTheme->mBorderMedium); + NVGpaint dialReverse = nvgLinearGradient(ctx, + mPos.x(), center.y() - kr, mPos.x(), center.y() + kr, + mTheme->mBorderMedium, + mTheme->mBorderLight); + + NVGpaint dialFace = nvgRadialGradient(ctx, + dialPos.x(), dialPos.y(), kr - kshadow, + kr + kshadow, Color(150, mEnabled ? 255 : 100), mTheme->mTransparent); + + nvgBeginPath(ctx); + nvgCircle(ctx, dialPos.x(), dialPos.y(), kr); + nvgStrokeColor(ctx, mTheme->mBorderDark); + nvgFillPaint(ctx, dial); + nvgStroke(ctx); + nvgFill(ctx); + nvgBeginPath(ctx); + nvgCircle(ctx, dialPos.x(), dialPos.y(), kr - kshadow); + nvgFillPaint(ctx, dialFace); + nvgStrokePaint(ctx, dialReverse); + nvgStroke(ctx); + nvgFill(ctx); + + Vector2f notchPos(0.0f, 0.8f*(kr - 1.5f*kshadow)); + float value = (mValue - mRange.first)/(mRange.second - mRange.first); + float theta = 2.0f*NVG_PI*(0.1f + 0.8f*value); + Rotation2Df t(theta); + notchPos = t*notchPos; + notchPos += dialPos; + + nvgBeginPath(ctx); + nvgCircle(ctx, notchPos.x(), notchPos.y(), 0.15f*kr); + nvgFillColor(ctx, Color(mEnabled ? 50 : 100, 150)); + nvgStrokePaint(ctx, dial); + nvgStroke(ctx); + nvgFill(ctx); +} + +void Dial::save(Serializer &s) const { + Widget::save(s); + s.set("value", mValue); + s.set("range", mRange); +} + +bool Dial::load(Serializer &s) { + if (!Widget::load(s)) return false; + if (!s.get("value", mValue)) return false; + if (!s.get("range", mRange)) return false; + return true; +} + +NAMESPACE_END(nanogui) diff --git a/src/example1.cpp b/src/example1.cpp index 545c5c0bf8..9b37efc1e0 100644 --- a/src/example1.cpp +++ b/src/example1.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include // Includes for the GLTexture class. @@ -334,6 +336,33 @@ class ExampleApplication : public nanogui::Screen { textBox->setFontSize(20); textBox->setAlignment(TextBox::Alignment::Right); + new Label(window, "Dial and text box", "sans-bold"); + + panel = new Widget(window); + panel->setLayout(new BoxLayout(Orientation::Horizontal, + Alignment::Middle, 0, 20)); + + Dial *dial = new Dial(panel); + dial->setValue(0.01f); + dial->setFixedWidth(80); + + textBox = new TextBox(panel); + textBox->setFixedSize(Vector2i(60, 25)); + textBox->setValue("0.01"); + dial->setCallback([textBox](float value) { + value = 0.01f + 99.99f*powf(value, 5.0f); + std::ostringstream sval; + sval.precision(2); sval << std::fixed << value; + textBox->setValue(sval.str()); + }); + dial->setFinalCallback([&](float value) { + value = 0.01f + 99.99f*powf(value, 5.0f); + cout << "Final dial value: " << value << endl; + }); + textBox->setFixedSize(Vector2i(60,25)); + textBox->setFontSize(20); + textBox->setAlignment(TextBox::Alignment::Right); + window = new Window(this, "Misc. widgets"); window->setPosition(Vector2i(425,15)); window->setLayout(new GroupLayout());