1 /**
2  * Widget for displaying an editable textbox.  User must click on widget to edit,
3  * and mouse must be over box while editing.
4  *
5  * Copyright: Copyright Auburn Sounds 2015-2017.
6  * Copyright: Cut Through Recordings 2017.
7  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8  * Authors:   Ethan Reker
9  */
10 module dplug.pbrwidgets.textbox;
11 
12 import dplug.gui.element;
13 import dplug.core.nogc;
14 import dplug.core.vec;
15 import dplug.window.window : getCharFromKey;
16 
17 private import core.stdc.stdlib : malloc, free;
18 private import core.stdc.stdio : snprintf, printf;
19 private import core.stdc..string : strcmp, strlen;
20 
21 deprecated("Use the UITextBox name instead of UITextbox")
22     alias UITextbox = UITextBox;
23 
24 class UITextBox : UIElement
25 {
26 public:
27 nothrow:
28 @nogc:
29 
30     this(UIContext context, Font font, int textSize, RGBA textColor = RGBA(200, 200, 200, 255), RGBA backgroundColor = RGBA(0, 0, 0, 255))
31     {
32         super(context);
33         _font = font;
34         _textSize = textSize;
35         _textColor = textColor;
36         _backgroundColor = backgroundColor;
37         charBuffer = makeVec!char();
38     }
39 
40     ~this()
41     {
42     }
43 
44     @property const(char)[] getText()
45     {
46         return displayString();
47     }
48 
49     override void onDraw(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects)
50     {
51         float textPosx = position.width * 0.5f;
52         float textPosy = position.height * 0.5f;
53 
54         foreach(dirtyRect; dirtyRects)
55         {
56             auto croppedDiffuse = diffuseMap.cropImageRef(dirtyRect);
57             vec2f positionInDirty = vec2f(textPosx, textPosy) - dirtyRect.min;
58 
59             croppedDiffuse.fillAll(_backgroundColor);
60             croppedDiffuse.fillText(_font, displayString(), _textSize, 0.5, _textColor, positionInDirty.x, positionInDirty.y);
61         }
62     }
63 
64     override bool onMouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate)
65     {
66         // Left click
67         _isActive = true;
68 
69         setDirtyWhole();
70         return true;
71     }
72 
73     override void onMouseEnter()
74     {
75         setDirtyWhole();
76     }
77 
78     override void onMouseExit()
79     {
80         _isActive = false;
81         setDirtyWhole();
82     }
83 
84     override bool onKeyDown(Key key)
85     {
86         if(_isActive)
87         {
88             const char c = cast(char)getCharFromKey(key);
89             if(c == '\t')
90             {
91                 if (charBuffer.length > 0)
92                 {
93                     charBuffer.popBack();
94                 }
95             }
96             else if(c != '\0')
97                 charBuffer.pushBack(c);
98             setDirtyWhole();
99             return true;
100         }
101 
102         return false;
103     }
104 
105 private:
106 
107     Font _font;
108     int _textSize;
109     RGBA _textColor;
110     RGBA _backgroundColor;
111     bool _isActive;
112     Vec!char charBuffer;
113 
114     const(char)[] displayString() nothrow @nogc
115     {
116         return charBuffer[];
117     }
118 
119     final bool containsPoint(int x, int y)
120     {
121         box2i subSquare = getSubsquare();
122         float centerx = (subSquare.min.x + subSquare.max.x - 1) * 0.5f;
123         float centery = (subSquare.min.y + subSquare.max.y - 1) * 0.5f;
124 
125         float minx = centerx - (_position.width / 2);
126         float maxx = centerx + (_position.width / 2);
127         float miny = centery - (_position.height / 2);
128         float maxy = centery + (_position.height / 2);
129 
130         return x > minx && x < maxx && y > miny && y < maxy;
131     }
132 
133     /// Returns: largest square centered in _position
134     final box2i getSubsquare() pure const
135     {
136         // We'll draw entirely in the largest centered square in _position.
137         box2i subSquare;
138         if (_position.width > _position.height)
139         {
140             int offset = (_position.width - _position.height) / 2;
141             int minX = offset;
142             subSquare = box2i(minX, 0, minX + _position.height, _position.height);
143         }
144         else
145         {
146             int offset = (_position.height - _position.width) / 2;
147             int minY = offset;
148             subSquare = box2i(0, minY, _position.width, minY + _position.width);
149         }
150         return subSquare;
151     }
152 
153     final float getRadius() pure const
154     {
155         return getSubsquare().width * 0.5f;
156 
157     }
158 
159     final vec2f getCenter() pure const
160     {
161         box2i subSquare = getSubsquare();
162         float centerx = (subSquare.min.x + subSquare.max.x - 1) * 0.5f;
163         float centery = (subSquare.min.y + subSquare.max.y - 1) * 0.5f;
164         return vec2f(centerx, centery);
165     }
166 
167 }