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
;
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 {
46 mixin template FuiCtlHead() {
51 // ////////////////////////////////////////////////////////////////////////// //
52 align(1) struct FuiCtlSpan
{
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
)) {
64 horizontal
= ahorizontal
;
65 aligning
= (ahorizontal ? FuiLayoutProps
.Align
.Stretch
: FuiLayoutProps
.Align
.Start
);
67 auto data
= ctx
.item
!FuiCtlSpan(item
);
68 data
.type
= FuiCtlType
.Invisible
;
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
{
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
)) {
88 horizontal
= ahorizontal
;
89 aligning
= (ahorizontal ? FuiLayoutProps
.Align
.Stretch
: FuiLayoutProps
.Align
.Start
);
91 auto data
= ctx
.item
!FuiCtlBox(item
);
92 data
.type
= FuiCtlType
.Box
;
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
{
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
)) {
112 horizontal
= ahorizontal
;
113 aligning
= (ahorizontal ? FuiLayoutProps
.Align
.Stretch
: FuiLayoutProps
.Align
.Start
);
115 auto data
= ctx
.item
!FuiCtlPanel(item
);
116 data
.type
= FuiCtlType
.Panel
;
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
);
130 static if (is(typeof(T
.htaligh
))) {
131 data
.htaligh
= BND_LEFT
;
132 if (text
.length
&& text
[0] == '\x01') {
134 data
.htaligh
= BND_CENTER
;
135 } else if (text
.length
&& text
[0] == '\x02') {
137 data
.htaligh
= BND_RIGHT
;
141 data
.iconid
= iconid
;
142 auto font
= bndGetFont();
143 if (font
>= 0 && ctx
.vg
!is null) {
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
);
153 ctx
.layprops(item
).minSize
= FuiSize(cast(int)text
.length
*8+4, 10);
155 with (ctx
.layprops(item
)) {
162 // ////////////////////////////////////////////////////////////////////////// //
163 align(1) struct FuiCtlLabel
{
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
{
186 int button (FuiContext ctx
, int parent
, string text
, int iconid
=-1) {
187 auto item
= ctx
.buttonLike
!(FuiCtlButton
, FuiCtlType
.Button
)(parent
, text
, iconid
);
189 with (ctx
.layprops(item
)) {
191 clickMask |
= FuiLayoutProps
.Buttons
.Left
;
199 // ////////////////////////////////////////////////////////////////////////// //
200 align(1) struct FuiCtlCheck
{
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
);
212 auto data
= ctx
.item
!FuiCtlCheck(item
);
214 with (ctx
.layprops(item
)) {
216 clickMask |
= FuiLayoutProps
.Buttons
.Left
;
218 minSize
.w
+= cast(int)BND_OPTION_WIDTH
;
219 if (minSize
.h
< BND_OPTION_HEIGHT
) minSize
.h
= cast(int)BND_OPTION_HEIGHT
;
226 // ////////////////////////////////////////////////////////////////////////// //
227 align(1) struct FuiCtlRadio
{
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
);
239 auto data
= ctx
.item
!FuiCtlRadio(item
);
241 with (ctx
.layprops(item
)) {
243 clickMask |
= FuiLayoutProps
.Buttons
.Left
;
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
;
268 if (!lp
.enabled
) nvg
.globalAlpha(0.5); //else if (item == ctx.focused) nvg.globalAlpha(0.7);
269 scope(exit
) nvg
.globalAlpha(1.0);
272 bndBackground(nvg
, rc
.x
, rc
.y
, rc
.w
, rc
.h
);
273 nvg
.scissor(rc
.x
, rc
.y
, rc
.w
, rc
.h
);
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
);
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
);
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
);
292 case FuiCtlType
.Check
:
293 auto data
= ctx
.item
!FuiCtlCheck(item
);
294 auto oic0
= bndGetTheme
.optionTheme
.innerColor
;
295 auto oic1
= bndGetTheme
.optionTheme
.innerSelectedColor
;
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
);
306 case FuiCtlType
.Radio
:
307 auto data
= ctx
.item
!FuiCtlCheck(item
);
308 auto oic0
= bndGetTheme
.optionTheme
.innerColor
;
309 auto oic1
= bndGetTheme
.optionTheme
.innerSelectedColor
;
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
);
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
328 scope(exit
) nvg
.restore();
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
);
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
:
350 case FuiEvent
.Type
.Char
: // param0: dchar; param1: mods&buttons
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
);
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));
369 case FuiCtlType
.Check
:
370 if (ev
.key
== Key
.Space
) {
371 auto data
= ctx
.item
!FuiCtlCheck(ev
.item
);
373 if (data
.var
!is null) {
374 *data
.var
= !*data
.var
;
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));
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
);
398 if (data
.var
!is null) {
399 *data
.var
= !*data
.var
;
408 case FuiEvent
.Type
.Double
: // mouse double-click; param0: buttton index; param1: mods&buttons