flexlay2: respect maximum box size
[iv.d.git] / nanovega / fui / controls.d
blob234f95a0aea2217ee729a471f3a2e6fcee16f997
1 /* Invisible Vector Library
2 * simple FlexBox-based UI layout engine, suitable for using in
3 * immediate mode GUI libraries.
5 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
6 * Understanding is not required. Only obedience.
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, version 3 of the License ONLY.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 module iv.nanovega.fui.controls /*is aliced*/;
22 import arsd.simpledisplay;
24 import iv.alice;
25 import iv.nanovega.nanovega;
26 import iv.nanovega.blendish;
28 import iv.nanovega.fui.engine;
31 // ////////////////////////////////////////////////////////////////////////// //
32 __gshared auto fuiFocusColor = BND_COLOR_ACTIVE; //nvgRGB(86, 128, 194);
35 // ////////////////////////////////////////////////////////////////////////// //
36 enum FuiCtlType : ubyte {
37 Invisible,
38 Box,
39 Panel,
40 Label,
41 Button,
42 Check,
43 Radio,
46 mixin template FuiCtlHead() {
47 FuiCtlType type;
51 // ////////////////////////////////////////////////////////////////////////// //
52 align(1) struct FuiCtlSpan {
53 align(1):
54 mixin FuiCtlHead;
58 int span (FuiContext ctx, int parent, bool ahorizontal) {
59 if (!ctx.valid) return -1;
60 auto item = ctx.addItem!FuiCtlSpan(parent);
61 with (ctx.layprops(item)) {
62 flex = 1;
63 visible = false;
64 horizontal = ahorizontal;
65 aligning = (ahorizontal ? FuiLayoutProps.Align.Stretch : FuiLayoutProps.Align.Start);
67 auto data = ctx.item!FuiCtlSpan(item);
68 data.type = FuiCtlType.Invisible;
69 return item;
72 int hspan (FuiContext ctx, int parent) { return ctx.span(parent, true); }
73 int vspan (FuiContext ctx, int parent) { return ctx.span(parent, false); }
76 // ////////////////////////////////////////////////////////////////////////// //
77 align(1) struct FuiCtlBox {
78 align(1):
79 mixin FuiCtlHead;
83 int box (FuiContext ctx, int parent, bool ahorizontal) {
84 if (!ctx.valid) return -1;
85 auto item = ctx.addItem!FuiCtlBox(parent);
86 with (ctx.layprops(item)) {
87 flex = 1;
88 horizontal = ahorizontal;
89 aligning = (ahorizontal ? FuiLayoutProps.Align.Stretch : FuiLayoutProps.Align.Start);
91 auto data = ctx.item!FuiCtlBox(item);
92 data.type = FuiCtlType.Box;
93 return item;
96 int hbox (FuiContext ctx, int parent) { return ctx.box(parent, true); }
97 int vbox (FuiContext ctx, int parent) { return ctx.box(parent, false); }
100 // ////////////////////////////////////////////////////////////////////////// //
101 align(1) struct FuiCtlPanel {
102 align(1):
103 mixin FuiCtlHead;
107 int panel (FuiContext ctx, int parent, bool ahorizontal) {
108 if (!ctx.valid) return -1;
109 auto item = ctx.addItem!FuiCtlPanel(parent);
110 with (ctx.layprops(item)) {
111 flex = 1;
112 horizontal = ahorizontal;
113 aligning = (ahorizontal ? FuiLayoutProps.Align.Stretch : FuiLayoutProps.Align.Start);
115 auto data = ctx.item!FuiCtlPanel(item);
116 data.type = FuiCtlType.Panel;
117 return item;
120 int hpanel (FuiContext ctx, int parent) { return ctx.panel(parent, true); }
121 int vpanel (FuiContext ctx, int parent) { return ctx.panel(parent, false); }
124 // ////////////////////////////////////////////////////////////////////////// //
125 private int buttonLike(T, FuiCtlType type) (FuiContext ctx, int parent, string text, int iconid=-1) {
126 if (!ctx.valid) return -1;
127 auto item = ctx.addItem!T(parent);
128 auto data = ctx.item!T(item);
129 data.type = type;
130 static if (is(typeof(T.htaligh))) {
131 data.htaligh = BND_LEFT;
132 if (text.length && text[0] == '\x01') {
133 text = text[1..$];
134 data.htaligh = BND_CENTER;
135 } else if (text.length && text[0] == '\x02') {
136 text = text[1..$];
137 data.htaligh = BND_RIGHT;
140 data.text = text;
141 data.iconid = iconid;
142 auto font = bndGetFont();
143 if (font >= 0 && ctx.vg !is null) {
145 float[4] bounds;
146 ctx.vg.textBounds(0, 0, text, bounds);
147 ctx.layprops(item).minSize = FuiSize(cast(int)(bounds[2]-bounds[0])+4, cast(int)(bounds[3]-bounds[1])+4);
149 auto w = cast(int)bndLabelWidth(ctx.vg, iconid, text);
150 auto h = cast(int)bndLabelHeight(ctx.vg, iconid, text, w);
151 ctx.layprops(item).minSize = FuiSize(w+2, h);
152 } else {
153 ctx.layprops(item).minSize = FuiSize(cast(int)text.length*8+4, 10);
155 with (ctx.layprops(item)) {
156 flex = 1;
158 return item;
162 // ////////////////////////////////////////////////////////////////////////// //
163 align(1) struct FuiCtlLabel {
164 align(1):
165 mixin FuiCtlHead;
167 string text;
168 int iconid;
169 int htaligh;
172 int label (FuiContext ctx, int parent, string text, int iconid=-1) {
173 return ctx.buttonLike!(FuiCtlLabel, FuiCtlType.Label)(parent, text, iconid);
177 // ////////////////////////////////////////////////////////////////////////// //
178 align(1) struct FuiCtlButton {
179 align(1):
180 mixin FuiCtlHead;
182 string text;
183 int iconid;
186 int button (FuiContext ctx, int parent, string text, int iconid=-1) {
187 auto item = ctx.buttonLike!(FuiCtlButton, FuiCtlType.Button)(parent, text, iconid);
188 if (item >= 0) {
189 with (ctx.layprops(item)) {
190 flex = 1;
191 clickMask |= FuiLayoutProps.Buttons.Left;
192 canBeFocused = true;
195 return item;
199 // ////////////////////////////////////////////////////////////////////////// //
200 align(1) struct FuiCtlCheck {
201 align(1):
202 mixin FuiCtlHead;
204 string text;
205 int iconid;
206 bool* var;
209 int checkbox (FuiContext ctx, int parent, string text, bool* var=null, int iconid=-1) {
210 auto item = ctx.buttonLike!(FuiCtlCheck, FuiCtlType.Check)(parent, text, iconid);
211 if (item >= 0) {
212 auto data = ctx.item!FuiCtlCheck(item);
213 data.var = var;
214 with (ctx.layprops(item)) {
215 flex = 1;
216 clickMask |= FuiLayoutProps.Buttons.Left;
217 canBeFocused = true;
218 minSize.w += cast(int)BND_OPTION_WIDTH;
219 if (minSize.h < BND_OPTION_HEIGHT) minSize.h = cast(int)BND_OPTION_HEIGHT;
222 return item;
226 // ////////////////////////////////////////////////////////////////////////// //
227 align(1) struct FuiCtlRadio {
228 align(1):
229 mixin FuiCtlHead;
231 string text;
232 int iconid;
233 int* var;
236 int radio (FuiContext ctx, int parent, string text, int* var=null, int iconid=-1) {
237 auto item = ctx.buttonLike!(FuiCtlRadio, FuiCtlType.Radio)(parent, text, iconid);
238 if (item >= 0) {
239 auto data = ctx.item!FuiCtlRadio(item);
240 data.var = var;
241 with (ctx.layprops(item)) {
242 flex = 1;
243 clickMask |= FuiLayoutProps.Buttons.Left;
244 canBeFocused = true;
245 minSize.w += 14;
248 return item;
252 // ////////////////////////////////////////////////////////////////////////// //
253 void draw (FuiContext ctx, NVGContext avg=null) {
254 if (!ctx.valid) return;
255 auto nvg = (avg !is null ? avg : ctx.vg);
256 if (nvg is null) return;
258 void drawItem (int item, FuiPoint g) {
259 auto lp = ctx.layprops(item);
260 if (lp is null) return;
261 if (!lp.visible) return;
263 // convert local coords to global coords
264 auto rc = lp.position;
265 rc.xp += g.x;
266 rc.yp += g.y;
268 if (!lp.enabled) nvg.globalAlpha(0.5); //else if (item == ctx.focused) nvg.globalAlpha(0.7);
269 scope(exit) nvg.globalAlpha(1.0);
271 if (item == 0) {
272 bndBackground(nvg, rc.x, rc.y, rc.w, rc.h);
273 nvg.scissor(rc.x, rc.y, rc.w, rc.h);
274 } else {
275 final switch (ctx.item!FuiCtlSpan(item).type) {
276 case FuiCtlType.Invisible: break;
277 case FuiCtlType.Box: break;
278 case FuiCtlType.Panel:
279 bndBevel(nvg, rc.x, rc.y, rc.w, rc.h);
280 break;
281 case FuiCtlType.Label:
282 auto data = ctx.item!FuiCtlLabel(item);
283 bndLabel(nvg, rc.x, rc.y, rc.w, rc.h, data.iconid, data.text, data.htaligh);
284 break;
285 case FuiCtlType.Button:
286 auto data = ctx.item!FuiCtlButton(item);
287 auto oic = bndGetTheme.toolTheme.innerColor;
288 scope(exit) bndGetTheme.toolTheme.innerColor = oic;
289 if (ctx.focused == item) bndGetTheme.toolTheme.innerColor = fuiFocusColor;
290 bndToolButton(nvg, rc.x, rc.y, rc.w, rc.h, BND_CORNER_NONE, (lp.active ? BND_ACTIVE : lp.hovered ? BND_HOVER : BND_DEFAULT), data.iconid, data.text);
291 break;
292 case FuiCtlType.Check:
293 auto data = ctx.item!FuiCtlCheck(item);
294 auto oic0 = bndGetTheme.optionTheme.innerColor;
295 auto oic1 = bndGetTheme.optionTheme.innerSelectedColor;
296 scope(exit) {
297 bndGetTheme.optionTheme.innerColor = oic0;
298 bndGetTheme.optionTheme.innerSelectedColor = oic1;
300 if (ctx.focused == item) {
301 bndGetTheme.optionTheme.innerColor = fuiFocusColor;
302 bndGetTheme.optionTheme.innerSelectedColor = fuiFocusColor;
304 bndOptionButton(nvg, rc.x, rc.y, rc.w, rc.h, (data.var !is null && *data.var ? BND_ACTIVE : lp.hovered ? BND_HOVER : BND_DEFAULT), data.text);
305 break;
306 case FuiCtlType.Radio:
307 auto data = ctx.item!FuiCtlCheck(item);
308 auto oic0 = bndGetTheme.optionTheme.innerColor;
309 auto oic1 = bndGetTheme.optionTheme.innerSelectedColor;
310 scope(exit) {
311 bndGetTheme.optionTheme.innerColor = oic0;
312 bndGetTheme.optionTheme.innerSelectedColor = oic1;
314 if (ctx.focused == item) {
315 bndGetTheme.optionTheme.innerColor = fuiFocusColor;
316 bndGetTheme.optionTheme.innerSelectedColor = fuiFocusColor;
318 bndRadioButton2(nvg, rc.x, rc.y, rc.w, rc.h, BND_CORNER_NONE, (/*lp.active ? BND_ACTIVE :*/ lp.hovered ? BND_HOVER : BND_DEFAULT), data.iconid, data.text);
319 break;
322 // draw children
323 item = lp.firstChild;
324 lp = ctx.layprops(item);
325 if (lp is null) return;
326 // as we will setup scissors, we need to save and restore state
327 nvg.save();
328 scope(exit) nvg.restore();
329 // setup scissors
330 nvg.intersectScissor(rc.x, rc.y, rc.w, rc.h);
331 while (lp !is null) {
332 drawItem(item, rc.pos);
333 item = lp.nextSibling;
334 lp = ctx.layprops(item);
338 nvg.save();
339 scope(exit) nvg.restore();
340 drawItem(0, ctx.layprops(0).position.pos);
344 // ////////////////////////////////////////////////////////////////////////// //
345 // returns `true` if event was consumed
346 bool fuiProcessEvent (FuiContext ctx, FuiEvent ev) {
347 final switch (ev.type) {
348 case FuiEvent.Type.None:
349 return false;
350 case FuiEvent.Type.Char: // param0: dchar; param1: mods&buttons
351 return false;
352 case FuiEvent.Type.Key: // param0: sdpy keycode; param1: mods&buttons
353 if (auto lp = ctx.layprops(ev.item)) {
354 if (lp.disabled || !lp.canBeFocused) return false;
355 switch (ctx.item!FuiCtlSpan(ev.item).type) {
356 case FuiCtlType.Button:
357 if (ev.key == Key.Space) {
358 auto data = ctx.item!FuiCtlButton(ev.item);
359 if (lp.clickMask) {
360 uint bidx = uint.max;
361 foreach (ubyte shift; 0..8) if (lp.clickMask&(1<<shift)) { bidx = shift; break; }
362 if (bidx != uint.max) {
363 ctx.queueEvent(ev.item, FuiEvent.Type.Click, bidx, ctx.lastButtons|(ctx.lastMods<<8));
364 return true;
368 break;
369 case FuiCtlType.Check:
370 if (ev.key == Key.Space) {
371 auto data = ctx.item!FuiCtlCheck(ev.item);
372 if (lp.clickMask) {
373 if (data.var !is null) {
374 *data.var = !*data.var;
375 return true;
376 } else {
377 uint bidx = uint.max;
378 foreach (ubyte shift; 0..8) if (lp.clickMask&(1<<shift)) { bidx = shift; break; }
379 if (bidx != uint.max) {
380 ctx.queueEvent(ev.item, FuiEvent.Type.Click, bidx, ctx.lastButtons|(ctx.lastMods<<8));
381 return true;
386 break;
387 default:
390 return false;
391 case FuiEvent.Type.Click: // mouse click; param0: buttton index; param1: mods&buttons
392 if (auto lp = ctx.layprops(ev.item)) {
393 if (lp.disabled || !lp.canBeFocused) return false;
394 switch (ctx.item!FuiCtlSpan(ev.item).type) {
395 case FuiCtlType.Check:
396 auto data = ctx.item!FuiCtlCheck(ev.item);
397 if (lp.clickMask) {
398 if (data.var !is null) {
399 *data.var = !*data.var;
400 return true;
403 break;
404 default:
407 return false;
408 case FuiEvent.Type.Double: // mouse double-click; param0: buttton index; param1: mods&buttons
409 return false;