|
| 1 | +/* |
| 2 | + nanogui/dial.cpp -- Fractional dial widget with mouse control |
| 3 | +
|
| 4 | + NanoGUI was developed by Wenzel Jakob <[email protected]>. |
| 5 | + The widget drawing code is based on the NanoVG demo application |
| 6 | + by Mikko Mononen. |
| 7 | +
|
| 8 | + All rights reserved. Use of this source code is governed by a |
| 9 | + BSD-style license that can be found in the LICENSE.txt file. |
| 10 | +*/ |
| 11 | + |
| 12 | +#include <nanogui/dial.h> |
| 13 | +#include <nanogui/theme.h> |
| 14 | +#include <nanogui/opengl.h> |
| 15 | +#include <nanogui/serializer/core.h> |
| 16 | +#include <Eigen/Geometry> |
| 17 | + |
| 18 | +using Eigen::Rotation2Df; |
| 19 | + |
| 20 | +NAMESPACE_BEGIN(nanogui) |
| 21 | + |
| 22 | +Dial::Dial(Widget *parent) |
| 23 | + : Widget(parent), mValue(0.0f), mRange(0.f, 1.f) { |
| 24 | +} |
| 25 | + |
| 26 | +Vector2i Dial::preferredSize(NVGcontext *) const { |
| 27 | + return Vector2i(40, 40); |
| 28 | +} |
| 29 | + |
| 30 | +bool Dial::mouseDragEvent(const Vector2i &p, const Vector2i & /* rel */, |
| 31 | + int /* button */, int /* modifiers */) { |
| 32 | + if (!mEnabled) |
| 33 | + return false; |
| 34 | + |
| 35 | + Vector2f pos = (p - mPos - mSize/2).cast<float>(); |
| 36 | + float value = 0.5f + 0.5f*atan2f(pos.x(), -pos.y())/NVG_PI; |
| 37 | + value = -0.1f + 1.2f*value; |
| 38 | + |
| 39 | + value = value * (mRange.second - mRange.first) + mRange.first; |
| 40 | + mValue = std::min(std::max(value, mRange.first), mRange.second); |
| 41 | + if (mCallback) |
| 42 | + mCallback(mValue); |
| 43 | + |
| 44 | + return true; |
| 45 | +} |
| 46 | + |
| 47 | +bool Dial::mouseButtonEvent(const Vector2i &p, int /* button */, bool down, int /* modifiers */) { |
| 48 | + if (!mEnabled) |
| 49 | + return false; |
| 50 | + |
| 51 | + if (down) { |
| 52 | + Vector2f pos = (p - mPos - mSize/2).cast<float>(); |
| 53 | + float kr = 0.5f * (mSize.y() * 0.4f); |
| 54 | + |
| 55 | + if (pos.squaredNorm() >= kr*kr) { |
| 56 | + float value = 0.5f + 0.5f*atan2f(pos.x(), -pos.y())/NVG_PI; |
| 57 | + value = -0.1f + 1.2f*value; |
| 58 | + |
| 59 | + value = value * (mRange.second - mRange.first) + mRange.first; |
| 60 | + mValue = std::min(std::max(value, mRange.first), mRange.second); |
| 61 | + } |
| 62 | + if (mCallback) |
| 63 | + mCallback(mValue); |
| 64 | + } else if (mFinalCallback) { |
| 65 | + mFinalCallback(mValue); |
| 66 | + } |
| 67 | + |
| 68 | + return true; |
| 69 | +} |
| 70 | + |
| 71 | +void Dial::draw(NVGcontext* ctx) { |
| 72 | + Vector2f center = mPos.cast<float>() + mSize.cast<float>() * 0.5f; |
| 73 | + float kr = (int) (mSize.y() * 0.4f), kshadow = 2; |
| 74 | + |
| 75 | + Vector2f dialPos(center.x(), center.y() + 0.5f); |
| 76 | + |
| 77 | + NVGpaint dial = nvgLinearGradient(ctx, |
| 78 | + mPos.x(), center.y() - kr, mPos.x(), center.y() + kr, |
| 79 | + mTheme->mBorderLight, mTheme->mBorderMedium); |
| 80 | + NVGpaint dialReverse = nvgLinearGradient(ctx, |
| 81 | + mPos.x(), center.y() - kr, mPos.x(), center.y() + kr, |
| 82 | + mTheme->mBorderMedium, |
| 83 | + mTheme->mBorderLight); |
| 84 | + |
| 85 | + NVGpaint dialFace = nvgRadialGradient(ctx, |
| 86 | + dialPos.x(), dialPos.y(), kr - kshadow, |
| 87 | + kr + kshadow, Color(150, mEnabled ? 255 : 100), mTheme->mTransparent); |
| 88 | + |
| 89 | + nvgBeginPath(ctx); |
| 90 | + nvgCircle(ctx, dialPos.x(), dialPos.y(), kr); |
| 91 | + nvgStrokeColor(ctx, mTheme->mBorderDark); |
| 92 | + nvgFillPaint(ctx, dial); |
| 93 | + nvgStroke(ctx); |
| 94 | + nvgFill(ctx); |
| 95 | + nvgBeginPath(ctx); |
| 96 | + nvgCircle(ctx, dialPos.x(), dialPos.y(), kr - kshadow); |
| 97 | + nvgFillPaint(ctx, dialFace); |
| 98 | + nvgStrokePaint(ctx, dialReverse); |
| 99 | + nvgStroke(ctx); |
| 100 | + nvgFill(ctx); |
| 101 | + |
| 102 | + Vector2f notchPos(0.0f, 0.8f*(kr - 1.5f*kshadow)); |
| 103 | + float value = (mValue - mRange.first)/(mRange.second - mRange.first); |
| 104 | + float theta = 2.0f*NVG_PI*(0.1f + 0.8f*value); |
| 105 | + Rotation2Df t(theta); |
| 106 | + notchPos = t*notchPos; |
| 107 | + notchPos += dialPos; |
| 108 | + |
| 109 | + nvgBeginPath(ctx); |
| 110 | + nvgCircle(ctx, notchPos.x(), notchPos.y(), 0.15f*kr); |
| 111 | + nvgFillColor(ctx, Color(mEnabled ? 50 : 100, 150)); |
| 112 | + nvgStrokePaint(ctx, dial); |
| 113 | + nvgStroke(ctx); |
| 114 | + nvgFill(ctx); |
| 115 | +} |
| 116 | + |
| 117 | +void Dial::save(Serializer &s) const { |
| 118 | + Widget::save(s); |
| 119 | + s.set("value", mValue); |
| 120 | + s.set("range", mRange); |
| 121 | +} |
| 122 | + |
| 123 | +bool Dial::load(Serializer &s) { |
| 124 | + if (!Widget::load(s)) return false; |
| 125 | + if (!s.get("value", mValue)) return false; |
| 126 | + if (!s.get("range", mRange)) return false; |
| 127 | + return true; |
| 128 | +} |
| 129 | + |
| 130 | +NAMESPACE_END(nanogui) |
0 commit comments