1 ///
2 module nanogui.vscrollpanel;
3 
4 /*
5     nanogui/vscrollpanel.h -- Adds a vertical scrollbar around a widget
6     that is too big to fit into a certain area
7 
8     NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
9     The widget drawing code is based on the NanoVG demo application
10     by Mikko Mononen.
11 
12     All rights reserved. Use of this source code is governed by a
13     BSD-style license that can be found in the LICENSE.txt file.
14 */
15 import std.algorithm : min, max;
16 import nanogui.widget;
17 import nanogui.common : MouseButton, Vector2f, Vector2i, NanoContext;
18 
19 /**
20  * Adds a vertical scrollbar around a widget that is too big to fit into
21  * a certain area.
22  */
23 class VScrollPanel : Widget {
24 public:
25     this(Widget parent)
26     {
27         super(parent);
28         mChildPreferredHeight = 0;
29         mScroll = 0.0f;
30         mUpdateLayout = false;
31     }
32 
33     /// Return the current scroll amount as a value between 0 and 1. 0 means scrolled to the top and 1 to the bottom.
34     float scroll() const { return mScroll; }
35     /// Set the scroll amount to a value between 0 and 1. 0 means scrolled to the top and 1 to the bottom.
36     void setScroll(float scroll) { mScroll = scroll; }
37 
38     override void performLayout(NanoContext ctx)
39     {
40         super.performLayout(ctx);
41 
42         if (mChildren.empty)
43             return;
44         if (mChildren.length > 1)
45             throw new Exception("VScrollPanel should have one child.");
46 
47         Widget child = mChildren[0];
48         mChildPreferredHeight = child.preferredSize(ctx).y;
49 
50         if (mChildPreferredHeight > mSize.y)
51         {
52             auto y = cast(int) (-mScroll*(mChildPreferredHeight - mSize.y));
53             child.position(Vector2i(0, y));
54             child.size(Vector2i(mSize.x-12, mChildPreferredHeight));
55         }
56         else 
57         {
58             child.position(Vector2i(0, 0));
59             child.size(mSize);
60             mScroll = 0;
61         }
62         child.performLayout(ctx);
63     }
64 
65     override Vector2i preferredSize(NanoContext ctx) const
66     {
67         if (mChildren.empty)
68             return Vector2i(0, 0);
69         return mChildren[0].preferredSize(ctx) + Vector2i(12, 0);
70     }
71     
72     override bool mouseDragEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers)
73     {
74         if (!mChildren.empty && mChildPreferredHeight > mSize.y) {
75             float scrollh = height *
76                 min(1.0f, height / cast(float)mChildPreferredHeight);
77 
78             mScroll = max(cast(float) 0.0f, min(cast(float) 1.0f,
79                         mScroll + rel.y / cast(float)(mSize.y - 8 - scrollh)));
80             mUpdateLayout = true;
81             return true;
82         } else {
83             return super.mouseDragEvent(p, rel, button, modifiers);
84         }
85     }
86 
87     override bool scrollEvent(Vector2i p, Vector2f rel)
88     {
89         if (!mChildren.empty && mChildPreferredHeight > mSize.y)
90         {
91             const scrollAmount = rel.y * (mSize.y / 20.0f);
92             float scrollh = height *
93                 min(1.0f, height / cast(float)mChildPreferredHeight);
94 
95             mScroll = max(cast(float) 0.0f, min(cast(float) 1.0f,
96                     mScroll - scrollAmount / cast(float)(mSize.y - 8 - scrollh)));
97             mUpdateLayout = true;
98             return true;
99         } else {
100             return super.scrollEvent(p, rel);
101         }
102     }
103 
104     override void draw(ref NanoContext ctx)
105     {
106         if (mChildren.empty)
107             return;
108         Widget child = mChildren[0];
109         auto y = cast(int) (-mScroll*(mChildPreferredHeight - mSize.y));
110         child.position(Vector2i(0, y));
111         mChildPreferredHeight = child.preferredSize(ctx).y;
112         float scrollh = height *
113             min(1.0f, height / cast(float) mChildPreferredHeight);
114 
115         if (mUpdateLayout)
116             child.performLayout(ctx);
117 
118         ctx.save;
119         ctx.translate(mPos.x, mPos.y);
120         ctx.intersectScissor(0, 0, mSize.x, mSize.y);
121         if (child.visible)
122             child.draw(ctx);
123         ctx.restore;
124 
125         if (mChildPreferredHeight <= mSize.y)
126             return;
127 
128         NVGPaint paint = ctx.boxGradient(
129             mPos.x + mSize.x - 12 + 1, mPos.y + 4 + 1, 8,
130             mSize.y - 8, 3, 4, Color(0, 0, 0, 32), Color(0, 0, 0, 92));
131         ctx.beginPath;
132         ctx.roundedRect(mPos.x + mSize.x - 12, mPos.y + 4, 8,
133                     mSize.y - 8, 3);
134         ctx.fillPaint(paint);
135         ctx.fill;
136 
137         paint = ctx.boxGradient(
138             mPos.x + mSize.x - 12 - 1,
139             mPos.y + 4 + (mSize.y - 8 - scrollh) * mScroll - 1, 8, scrollh,
140             3, 4, Color(220, 220, 220, 100), Color(128, 128, 128, 100));
141 
142         ctx.beginPath;
143         ctx.roundedRect(mPos.x + mSize.x - 12 + 1,
144                     mPos.y + 4 + 1 + (mSize.y - 8 - scrollh) * mScroll, 8 - 2,
145                     scrollh - 2, 2);
146         ctx.fillPaint(paint);
147         ctx.fill;
148     }
149     // override void save(Serializer &s) const;
150     // override bool load(Serializer &s);
151 protected:
152     int mChildPreferredHeight;
153     float mScroll;
154     bool mUpdateLayout;
155 }