tag options window (M-O)
[chiroptera.git] / egui / widgets.d
blob7c064d13209cad777c4b3f222a25a20a2c415816
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module egui.widgets /*is aliced*/;
18 private:
20 import arsd.simpledisplay;
22 import egfx;
24 import iv.alice;
25 import iv.cmdcon;
26 import iv.strex;
27 import iv.utfutil;
29 import egui.subwindows;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public abstract class Widget {
34 SubWindow parent;
35 int wx, wy, ww, wh;
36 bool tabStop = true;
38 public:
39 void delegate (Widget self) onAction;
41 private:
42 int svcx0, svcy0, svcx1, svcy1; // saved clip, valid after `setupClip()` call
44 public:
45 this (SubWindow aparent) {
46 if (aparent is null) throw new Exception("no parent");
47 aparent.addWidget(this);
50 final @property bool active () nothrow @safe @nogc { return (parent !is null ? (parent.activeWidget is this) : false); }
52 final @property int x0 () const nothrow @safe { pragma(inline, true); return (parent !is null ? parent.winx+parent.clientOffsetX+wx : 0); }
53 final @property int y0 () const nothrow @safe { pragma(inline, true); return (parent !is null ? parent.winy+parent.clientOffsetY+wy : 0); }
54 final @property int x1 () const nothrow @safe { pragma(inline, true); return (parent !is null ? parent.winx+parent.clientOffsetX+wx+ww-1 : 0); }
55 final @property int y1 () const nothrow @safe { pragma(inline, true); return (parent !is null ? parent.winy+parent.clientOffsetY+wy+wh-1 : 0); }
57 final @property int width () const nothrow @safe @nogc { pragma(inline, true); return ww; }
58 final @property int height () const nothrow @safe @nogc { pragma(inline, true); return wh; }
60 final @property void width (int v) nothrow @safe @nogc { pragma(inline, true); ww = v; }
61 final @property void height (int v) nothrow @safe @nogc { pragma(inline, true); wh = v; }
63 protected void setupClip () {
64 parent.setupClientClip();
65 gxClipRect.intersect(GxRect(GxPoint(x0, y0), GxPoint(x1, y1)));
66 svcx0 = gxClipRect.x0;
67 svcy0 = gxClipRect.y0;
68 svcx1 = gxClipRect.x1;
69 svcy1 = gxClipRect.y1;
72 protected void restoreClip () {
73 gxClipRect.x0 = svcx0;
74 gxClipRect.y0 = svcy0;
75 gxClipRect.x1 = svcx1;
76 gxClipRect.y1 = svcy1;
79 // this is called with set clip
80 protected void doPaint () {}
82 void onPaint () {
83 if (parent is null || ww < 1 || wh < 1) return;
84 setupClip();
85 doPaint();
88 // return `true` if event was eaten
89 bool onKey (KeyEvent event) { return false; }
90 bool onMouse (MouseEvent event) { return false; }
91 bool onChar (dchar ch) { return false; }
95 // ////////////////////////////////////////////////////////////////////////// //
96 public class LabelWidget : Widget {
97 public:
98 enum HAlign {
99 Left,
100 Center,
101 Right,
104 enum VAlign {
105 Top,
106 Center,
107 Bottom,
110 private:
111 string mText;
113 public:
114 HAlign halign = HAlign.Left;
115 VAlign valign = VAlign.Center;
116 int hpad = 0;
117 int vpad = 0;
119 public:
120 this (SubWindow aparent, string atext, HAlign horiz=HAlign.Left, VAlign vert=VAlign.Center) {
121 super(aparent);
122 mText = atext;
123 ww = gxTextWidthUtf(atext);
124 wh = gxTextHeightUtf;
125 tabStop = false;
126 halign = horiz;
127 valign = vert;
130 @property string text () const nothrow @safe @nogc { return mText; }
131 @property void text (string v) nothrow @safe @nogc { mText = v; }
133 protected override void doPaint () {
134 if (mText.length == 0) return;
135 int x;
136 final switch (halign) {
137 case HAlign.Left: x = x0+hpad; break;
138 case HAlign.Center: x = x0+(width-gxTextWidthUtf(mText))/2; break;
139 case HAlign.Right: x = x0+width-hpad-gxTextWidthUtf(mText); break;
141 int y;
142 final switch (valign) {
143 case VAlign.Top: y = y0+vpad; break;
144 case VAlign.Center: y = y0+(height-gxTextHeightUtf)/2; break;
145 case VAlign.Bottom: y = y0+height-vpad-gxTextHeightUtf; break;
147 //gxDrawTextUtf(x0+(width-gxTextWidthUtf(mText))/2, y0+(height-gxTextHeightUtf)/2, mText, parent.clrWinText);
148 gxDrawTextUtf(x, y, mText, parent.clrWinText);
153 // ////////////////////////////////////////////////////////////////////////// //
154 public class CheckboxWidget : Widget {
155 protected:
156 string title;
157 string hotch;
158 string hotkey;
159 int hotxpos;
160 bool mChecked;
162 public:
163 this (SubWindow aparent, string atitle, string ahotkey=null) {
164 super(aparent);
165 title = atitle;
166 hotkey = ahotkey;
167 if (title.length > 1) {
168 auto umpos = title.indexOf('&');
169 if (umpos >= 0 && umpos < title.length-1) {
170 hotch = title[umpos+1..$];
171 while (hotch.utflen > 1) hotch = hotch.utfchop;
172 title = title[0..umpos]~title[umpos+1..$];
173 hotxpos = gxTextWidthUtf(title[0..umpos]);
174 if (umpos > 0) hotxpos += 1;
175 if (ahotkey is null) hotkey = "M-"~hotch;
178 ww = gxTextWidthUtf(title)+6+gxTextWidthUtf("[")+gxTextWidthUtf("x")+gxTextWidthUtf("]")+6;
179 wh = gxTextHeightUtf+4;
182 @property bool checked () const nothrow @safe @nogc { return mChecked; }
183 @property void checked (bool v) nothrow @safe @nogc { mChecked = v; }
185 protected override void doPaint () {
187 uint bclr = (active ? gxRGB!(255, 255, 255) : gxRGB!(180, 180, 180));
188 uint fclr = (active ? gxRGB!(0, 0, 60) : gxRGB!(0, 0, 0));
189 gxFillRect(x0+1, y0+1, width-2, height-2, bclr);
190 gxHLine(x0+1, y0, width-2, bclr);
191 gxHLine(x0+1, y1, width-2, bclr);
192 gxVLine(x0, y0+1, height-2, bclr);
193 gxVLine(x1, y0+1, height-2, bclr);
196 uint fclr = (active ? gxRGB!(255, 255, 255) : gxRGB!(192, 192, 192));
197 uint xclr = (active ? gxRGB!(0, 255, 0) : gxRGB!(0, 192, 0));
199 if (active) gxFillRect(x0, y0, width, height, gxRGB!(0, 0, 64));
201 gxClipRect.shrinkBy(1, 1);
202 int tx = x0+3; //(width-gxTextWidthUtf(title))/2;
203 int ty = y0+(height-gxTextHeightUtf)/2;
205 gxDrawTextUtf(tx, ty, "[", fclr);
206 tx += gxTextWidthUtf("[");
208 if (mChecked) gxDrawTextUtf(tx, ty, "x", xclr);
209 tx += gxTextWidthUtf("x");
211 gxDrawTextUtf(tx, ty, "]", fclr);
212 tx += gxTextWidthUtf("]")+6;
214 gxDrawTextUtf(tx, ty, title, fclr);
215 if (hotch.length) gxHLine(tx+hotxpos, ty+gxTextHeightUtf, gxTextWidthUtf(hotch), fclr);
218 override bool onKey (KeyEvent event) {
219 if (!event.pressed) return super.onKey(event);
220 if (hotkey.length && event == hotkey) {
221 parent.activeWidget = this;
222 if (onAction !is null) onAction(this); else mChecked = !mChecked;
223 return true;
225 if (!active) return false;
226 if (event == "Up" || event == "Left") { parent.doShiftTab(); return true; }
227 if (event == "Down" || event == "Right") { parent.doTab(); return true; }
228 if (event == "Enter" || event == "Space") {
229 if (onAction !is null) onAction(this); else mChecked = !mChecked;
230 return true;
232 return super.onKey(event);
235 override bool onMouse (MouseEvent event) {
236 int mx, my;
237 event.mouse2xy(mx, my);
238 if (mx >= x0 && my >= y0 && mx <= x1 && my <= y1) {
239 if (event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
240 parent.activeWidget = this;
241 } else if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) {
242 if (active && onAction !is null) onAction(this); else mChecked = !mChecked;
244 return true;
246 return false;
251 // ////////////////////////////////////////////////////////////////////////// //
252 public class ButtonWidget : Widget {
253 protected:
254 string title;
255 string hotch;
256 string hotkey;
257 int hotxpos;
259 public:
260 this (SubWindow aparent, string atitle, string ahotkey=null) {
261 super(aparent);
262 title = atitle;
263 hotkey = ahotkey;
264 if (title.length > 1) {
265 auto umpos = title.indexOf('&');
266 if (umpos >= 0 && umpos < title.length-1) {
267 hotch = title[umpos+1..$];
268 while (hotch.utflen > 1) hotch = hotch.utfchop;
269 title = title[0..umpos]~title[umpos+1..$];
270 hotxpos = gxTextWidthUtf(title[0..umpos]);
271 if (umpos > 0) hotxpos += 1;
274 ww = gxTextWidthUtf(title)+6;
275 wh = gxTextHeightUtf+4;
278 protected override void doPaint () {
279 uint bclr = (active ? gxRGB!(255, 255, 255) : gxRGB!(180, 180, 180));
280 uint fclr = (active ? gxRGB!(0, 0, 60) : gxRGB!(0, 0, 0));
281 gxFillRect(x0+1, y0+1, width-2, height-2, bclr);
282 gxHLine(x0+1, y0, width-2, bclr);
283 gxHLine(x0+1, y1, width-2, bclr);
284 gxVLine(x0, y0+1, height-2, bclr);
285 gxVLine(x1, y0+1, height-2, bclr);
286 gxClipRect.shrinkBy(1, 1);
287 int tx = x0+(width-gxTextWidthUtf(title))/2;
288 int ty = y0+(height-gxTextHeightUtf)/2;
289 gxDrawTextUtf(tx, ty, title, fclr);
290 if (hotch.length) gxHLine(tx+hotxpos, ty+gxTextHeightUtf, gxTextWidthUtf(hotch), fclr);
293 override bool onKey (KeyEvent event) {
294 if (!event.pressed) return super.onKey(event);
295 if (hotkey.length && event == hotkey) { if (onAction !is null) onAction(this); return true; }
296 if (!active) return false;
297 if (event == "Up" || event == "Left") { parent.doShiftTab(); return true; }
298 if (event == "Down" || event == "Right") { parent.doTab(); return true; }
299 if (event == "Enter" || event == "Space") { if (onAction !is null) onAction(this); return true; }
300 return super.onKey(event);
303 override bool onMouse (MouseEvent event) {
304 int mx, my;
305 event.mouse2xy(mx, my);
306 if (mx >= x0 && my >= y0 && mx <= x1 && my <= y1) {
307 if (event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
308 parent.activeWidget = this;
309 } else if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) {
310 if (active && onAction !is null) onAction(this);
312 return true;
314 return false;
319 // ////////////////////////////////////////////////////////////////////////// //
320 public class ButtonExWidget : ButtonWidget {
321 public:
322 this (SubWindow aparent, string atitle, string ahotkey=null) {
323 super(aparent, atitle, ahotkey);
324 wh = wh+1;
327 protected override void doPaint () {
328 uint bclr = (active ? gxRGB!(0x93, 0xb6, 0xfc) : gxRGB!(0x73, 0x96, 0xdc));
329 uint hclr = (active ? gxRGB!(0xa3, 0xc6, 0xff) : gxRGB!(0x83, 0xa6, 0xec));
330 uint fclr = (active ? gxRGB!(0xff, 0xff, 0xff) : gxRGB!(0xff, 0xff, 0xff));
331 uint brc = gxRGB!(0x40, 0x70, 0xcf);
332 gxFillRect(x0+1, y0+1, width-2, height-2, bclr);
333 gxDrawRect(x0, y0, width, height, brc);
334 gxHLine(x0+1, y0+1, width-2, hclr);
335 gxClipRect.shrinkBy(1, 1);
336 int tx = x0+(width-gxTextWidthUtf(title))/2;
337 int ty = y0+(height-gxTextHeightUtf)/2;
338 gxDrawTextUtf(tx, ty, title, fclr);
339 if (hotch.length) gxHLine(tx+hotxpos, ty+gxTextHeightUtf, gxTextWidthUtf(hotch), fclr);
344 // ////////////////////////////////////////////////////////////////////////// //
345 public final class SimpleListBoxWidget : Widget {
346 private:
347 string[] mItems;
348 Object[] mItemData;
349 int mTopIdx;
350 int mCurIdx;
352 public:
353 this (SubWindow aparent) {
354 super(aparent);
357 @property int curidx () const nothrow @safe @nogc { return (mCurIdx >= 0 && mCurIdx < mItems.length ? mCurIdx : 0); }
359 @property void curidx (int idx) nothrow @safe @nogc {
360 if (mItems.length == 0) return;
361 if (idx < 0) idx = 0;
362 if (idx >= mItems.length) idx = cast(int)(mItems.length-1);
363 mCurIdx = idx;
366 @property int length () const nothrow @safe @nogc { return cast(int)mItems.length; }
367 @property string opIndex (usize idx) const nothrow @safe @nogc { return (idx < mItems.length ? mItems[idx] : null); }
368 @property Object itemData (usize idx) nothrow @safe @nogc { return (idx < mItems.length ? mItemData[idx] : null); }
370 void appendItem(T:const(char)[]) (T s, Object o=null) {
371 //conwriteln("new item: ", s);
372 static if (is(T == typeof(null))) {
373 mItems ~= null;
374 } else static if (is(T == string)) {
375 mItems ~= s;
376 } else {
377 mItems ~= s.idup;
379 mItemData ~= o;
382 @property int visibleItemsCount () const nothrow @trusted {
383 pragma(inline, true);
384 return (height < gxTextHeightUtf*2 ? 1 : height/gxTextHeightUtf);
387 void makeCursorVisible () {
388 if (mItems.length == 0) return;
389 if (mCurIdx < 0) mCurIdx = 0;
390 if (mCurIdx >= mItems.length) mCurIdx = cast(int)(mItems.length-1);
391 if (mCurIdx < mTopIdx) { mTopIdx = mCurIdx; return; }
392 int icnt = visibleItemsCount-1;
393 if (mTopIdx+icnt < mCurIdx) mTopIdx = mCurIdx-icnt;
396 protected override void doPaint () {
397 makeCursorVisible();
398 gxFillRect(x0, y0, ww, wh, gxRGB!(0, 0, 60));
399 int y = 0;
400 int idx = mTopIdx;
401 while (idx < mItems.length && y < wh) {
402 if (idx >= 0) {
403 uint clr = gxRGB!(255, 255, 0);
404 if (idx == mCurIdx) {
405 gxFillRect(x0, y0+y, ww, gxTextHeightUtf, gxRGB!(0, 110, 110));
406 clr = gxRGB!(255, 255, 255);
408 gxClipRect.intersect(GxRect(GxPoint(x0, y0), GxPoint(x1-1, y1)));
409 gxDrawTextUtf(gxClipRect.x0+1, gxClipRect.y0+y, mItems[idx], clr);
410 restoreClip();
412 ++idx;
413 y += gxTextHeightUtf;
417 override bool onKey (KeyEvent event) {
418 if (!active) return false;
419 if (!event.pressed) return super.onKey(event);
420 if (event == "Up") { curidx = curidx-1; return true; }
421 if (event == "Down") { curidx = curidx+1; return true; }
422 if (event == "Home") { curidx = 0; return true; }
423 if (event == "End") { curidx = cast(int)(mItems.length-1); return true; }
424 if (event == "PageUp") {
425 makeCursorVisible();
426 if (curidx > mTopIdx) {
427 curidx = mTopIdx;
428 } else {
429 curidx = curidx-(visibleItemsCount-1);
431 return true;
433 if (event == "PageDown") {
434 makeCursorVisible();
435 int icnt = visibleItemsCount-1;
436 if (icnt) {
437 if (mTopIdx+icnt < curidx) {
438 curidx = mTopIdx+icnt;
439 } else {
440 curidx = curidx+icnt;
443 return true;
445 return super.onKey(event);
448 override bool onMouse (MouseEvent event) {
449 if (!active) return false;
450 int mx, my;
451 event.mouse2xy(mx, my);
452 if (mx >= x0 && my >= y0 && mx <= x1 && my <= y1) {
453 makeCursorVisible();
454 int idx = mTopIdx+(my-y0)/gxTextHeightUtf;
455 if (event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
456 if (curidx == idx) {
457 onAction(this);
458 } else {
459 curidx = idx;
461 } else if (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp) {
462 curidx = curidx-1;
463 } else if (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown) {
464 curidx = curidx+1;
466 return true;
468 return false;