1/*
2 src/vscrollpanel.cpp -- Adds a vertical scrollbar around a widget
3 that is too big to fit into a certain area
4
5 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
6 The widget drawing code is based on the NanoVG demo application
7 by Mikko Mononen.
8
9 All rights reserved. Use of this source code is governed by a
10 BSD-style license that can be found in the LICENSE.txt file.
11*/
12
13#include <nanogui/vscrollpanel.h>
14#include <nanogui/theme.h>
15#include <nanogui/opengl.h>
16#include <nanogui/serializer/core.h>
17
18NAMESPACE_BEGIN(nanogui)
19
20VScrollPanel::VScrollPanel(Widget *parent)
21 : Widget(parent), mChildPreferredHeight(0), mScroll(0.0f), mUpdateLayout(false) { }
22
23void VScrollPanel::performLayout(NVGcontext *ctx) {
24 Widget::performLayout(ctx);
25
26 if (mChildren.empty())
27 return;
28 if (mChildren.size() > 1)
29 throw std::runtime_error("VScrollPanel should have one child.");
30
31 Widget *child = mChildren[0];
32 mChildPreferredHeight = child->preferredSize(ctx).y();
33
34 if (mChildPreferredHeight > mSize.y()) {
35 child->setPosition(Vector2i(0, -mScroll*(mChildPreferredHeight - mSize.y())));
36 child->setSize(Vector2i(mSize.x()-12, mChildPreferredHeight));
37 } else {
38 child->setPosition(Vector2i::Zero());
39 child->setSize(mSize);
40 mScroll = 0;
41 }
42 child->performLayout(ctx);
43}
44
45Vector2i VScrollPanel::preferredSize(NVGcontext *ctx) const {
46 if (mChildren.empty())
47 return Vector2i::Zero();
48 return mChildren[0]->preferredSize(ctx) + Vector2i(12, 0);
49}
50
51bool VScrollPanel::mouseDragEvent(const Vector2i &p, const Vector2i &rel,
52 int button, int modifiers) {
53 if (!mChildren.empty() && mChildPreferredHeight > mSize.y()) {
54 float scrollh = height() *
55 std::min(1.0f, height() / (float)mChildPreferredHeight);
56
57 mScroll = std::max((float) 0.0f, std::min((float) 1.0f,
58 mScroll + rel.y() / (float)(mSize.y() - 8 - scrollh)));
59 mUpdateLayout = true;
60 return true;
61 } else {
62 return Widget::mouseDragEvent(p, rel, button, modifiers);
63 }
64}
65
66bool VScrollPanel::scrollEvent(const Vector2i &p, const Vector2f &rel) {
67 if (!mChildren.empty() && mChildPreferredHeight > mSize.y()) {
68 float scrollAmount = rel.y() * (mSize.y() / 20.0f);
69 float scrollh = height() *
70 std::min(1.0f, height() / (float)mChildPreferredHeight);
71
72 mScroll = std::max((float) 0.0f, std::min((float) 1.0f,
73 mScroll - scrollAmount / (float)(mSize.y() - 8 - scrollh)));
74 mUpdateLayout = true;
75 return true;
76 } else {
77 return Widget::scrollEvent(p, rel);
78 }
79}
80
81void VScrollPanel::draw(NVGcontext *ctx) {
82 if (mChildren.empty())
83 return;
84 Widget *child = mChildren[0];
85 child->setPosition(Vector2i(0, -mScroll*(mChildPreferredHeight - mSize.y())));
86 mChildPreferredHeight = child->preferredSize(ctx).y();
87 float scrollh = height() *
88 std::min(1.0f, height() / (float) mChildPreferredHeight);
89
90 if (mUpdateLayout)
91 child->performLayout(ctx);
92
93 nvgSave(ctx);
94 nvgTranslate(ctx, mPos.x(), mPos.y());
95 nvgIntersectScissor(ctx, 0, 0, mSize.x(), mSize.y());
96 if (child->visible())
97 child->draw(ctx);
98 nvgRestore(ctx);
99
100 if (mChildPreferredHeight <= mSize.y())
101 return;
102
103 NVGpaint paint = nvgBoxGradient(
104 ctx, mPos.x() + mSize.x() - 12 + 1, mPos.y() + 4 + 1, 8,
105 mSize.y() - 8, 3, 4, Color(0, 32), Color(0, 92));
106 nvgBeginPath(ctx);
107 nvgRoundedRect(ctx, mPos.x() + mSize.x() - 12, mPos.y() + 4, 8,
108 mSize.y() - 8, 3);
109 nvgFillPaint(ctx, paint);
110 nvgFill(ctx);
111
112 paint = nvgBoxGradient(
113 ctx, mPos.x() + mSize.x() - 12 - 1,
114 mPos.y() + 4 + (mSize.y() - 8 - scrollh) * mScroll - 1, 8, scrollh,
115 3, 4, Color(220, 100), Color(128, 100));
116
117 nvgBeginPath(ctx);
118 nvgRoundedRect(ctx, mPos.x() + mSize.x() - 12 + 1,
119 mPos.y() + 4 + 1 + (mSize.y() - 8 - scrollh) * mScroll, 8 - 2,
120 scrollh - 2, 2);
121 nvgFillPaint(ctx, paint);
122 nvgFill(ctx);
123}
124
125void VScrollPanel::save(Serializer &s) const {
126 Widget::save(s);
127 s.set("childPreferredHeight", mChildPreferredHeight);
128 s.set("scroll", mScroll);
129}
130
131bool VScrollPanel::load(Serializer &s) {
132 if (!Widget::load(s)) return false;
133 if (!s.get("childPreferredHeight", mChildPreferredHeight)) return false;
134 if (!s.get("scroll", mScroll)) return false;
135 return true;
136}
137
138NAMESPACE_END(nanogui)
139