1 /* Invisible Vector Library
2 * simple FlexBox-based TUI engine
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv
.egtui
.parser
/*is aliced*/;
27 import iv
.egtui
.types
;
30 // ////////////////////////////////////////////////////////////////////////// //
31 void parse(Vars
...) (ref FuiContext ctx
, const(char)[] text
) {
34 auto rmd
= ctx
.maxDimensions
;
35 if (rmd
.w
< 1) rmd
.w
= ttyw
;
36 if (rmd
.h
< 1) rmd
.h
= ttyh
;
37 ctx
.maxDimensions
= rmd
;
39 // stupid "reusable string" thingy
40 static struct DynStr
{
43 @disable this (this); // no copies
44 ~this () nothrow { delete buf
; buf
= null; }
46 void reset () nothrow {
53 @property int pos () const pure nothrow @safe @nogc { pragma(inline
, true); return cast(int)buf
.length
; }
54 @property void pos (int v
) nothrow {
56 buf
.length
= (v
> 0 ? v
: 0);
61 const(char)[] opSlice () const pure nothrow @safe @nogc { pragma(inline
, true); return buf
[]; }
62 const(char)[] opSlice (usize lo
, usize hi
) const pure nothrow @safe @nogc { pragma(inline
, true); return buf
[lo
..hi
]; }
64 char opIndex (usize idx
) const pure nothrow @safe @nogc { pragma(inline
, true); return buf
[idx
]; }
66 @property usize
length () const pure nothrow @safe @nogc { pragma(inline
, true); return buf
.length
; }
67 alias opDollar
= length
;
69 void put (const(char)[] s
...) nothrow @safe { pragma(inline
, true); buf
~= s
; }
71 @property bool empty () const pure nothrow @safe @nogc { pragma(inline
, true); return (buf
.length
== 0); }
72 @property const(char)[] getz () const pure nothrow @safe @nogc { pragma(inline
, true); return buf
[]; }
81 while (text
.length
> 0) {
82 if (text
.length
> 1 && text
.ptr
[0] == '/' && text
.ptr
[1] == '/') {
83 while (text
.length
&& text
.ptr
[0] != '\n') text
= text
[1..$];
84 } else if (text
.length
> 1 && text
.ptr
[0] == '#') {
85 while (text
.length
&& text
.ptr
[0] != '\n') text
= text
[1..$];
86 } else if (text
.length
> 1 && text
.ptr
[0] == '/' && text
.ptr
[1] == '*') {
89 if (text
.length
< 2) { text
= text
[$..$]; break; }
90 if (text
.ptr
[0] == '*' && text
.ptr
[1] == '/') { text
= text
[2..$]; break; }
93 } else if (text
.length
> 1 && text
.ptr
[0] == '/' && text
.ptr
[1] == '+') {
97 if (text
.length
< 2) { text
= text
[$..$]; break; }
98 if (text
.ptr
[0] == '+' && text
.ptr
[1] == '/') {
100 if (--level
== 0) break;
103 if (text
.ptr
[0] == '/' && text
.ptr
[1] == '+') {
110 } else if (text
.ptr
[0] <= ' ') {
119 bool processVarSubst () {
120 if (text
.length
< 2) return false;
121 if (text
.ptr
[0] == '\\' && text
.ptr
[1] == '$') {
126 if (text
.ptr
[0] != '$') return false;
127 if (text
.ptr
[1] == '$') {
132 auto stpos
= tokenbuf
.pos
;
133 if (text
.ptr
[1].isalpha || text
.ptr
[1].isalpha
== '_') {
135 while (text
.length
> 0) {
136 char ch
= text
.ptr
[0];
137 if (ch
!= '_' && ch
!= '-') {
138 if (ch
<= ' ' ||
!isalnum(ch
)) break;
143 } else if (text
.ptr
[1] == '{') {
145 while (pos
< text
.length
) {
146 if (text
.ptr
[pos
] == '\n') return false;
147 if (text
.ptr
[pos
] == '}') break;
150 if (pos
>= text
.length
) return false;
151 tokenbuf
.put(text
[2..pos
]);
152 text
= text
[pos
+1..$];
156 auto name
= tokenbuf
[stpos
..$];
157 //{ import iv.vfs.io; VFile("z00.log", "a").writeln(name.quote); }
158 foreach (immutable idx
, /*auto*/ var
; Vars
) {
159 static if (!is(typeof(var
) == function) && !is(typeof(var
) == delegate)) {
160 import std
.conv
: to
;
161 static if (is(typeof({auto s
= var
.to
!string
;}))) {
162 if (Vars
[idx
].stringof
== name
) {
163 tokenbuf
.pos
= stpos
;
164 tokenbuf
.put(Vars
[idx
].to
!string
);
178 if (text
.length
== 0) { eof
= true; return; }
179 if (text
.ptr
[0] == '"' || text
.ptr
[0] == '\'' || text
.ptr
[0] == '`') {
180 char ech
= text
.ptr
[0];
184 while (text
.length
> 0) {
186 if (processVarSubst()) continue;
188 if (ch
== ech
) break;
189 if (ch
== '\\' && text
.length
> 0) {
193 case 't': ch
= '\t'; break;
194 case 'n': ch
= '\n'; break;
195 case 'L': ch
= '\x01'; break;
196 case 'R': ch
= '\x02'; break;
197 case 'C': ch
= '\x03'; break;
199 if (ch
== 'x' || ch
== 'X') {
200 if (text
.length
== 0) throw new Exception("invalid escape");
201 int n
= digitInBase(text
.ptr
[0], 16);
202 if (n
< 0) throw new Exception("invalid escape");
204 if (text
.length
&& digitInBase(text
.ptr
[0], 16) >= 0) {
205 n
= n
*16+digitInBase(text
.ptr
[0], 16);
209 } else if (ch
.isalnum
) {
210 throw new Exception("invalid escape");
217 if (ch
!= ech
) throw new Exception("unterminated string");
219 if (text
.length
&& text
.ptr
[0] == ':') {
223 } else if (processVarSubst()) {
225 if (text
.length
&& text
.ptr
[0] == ':') {
229 } else if (text
.ptr
[0].isalnum || text
.ptr
[0] == '_' || text
.ptr
[0] == '-') {
230 while (text
.length
> 0) {
231 char ch
= text
.ptr
[0];
232 if (ch
!= '_' && ch
!= '-') {
233 if (ch
<= ' ' ||
!isalnum(ch
)) break;
239 if (text
.length
&& text
.ptr
[0] == ':') {
244 assert(text
.length
> 0);
245 tokenbuf
.put(text
.ptr
[0]);
250 const(char)[] token () { pragma(inline
, true); return tokenbuf
.getz
; }
260 if (token
== "{") skipGroup();
261 if (token
== "}") break;
266 import std
.conv
: to
;
267 if (!hasValue
) throw new Exception("number value expected");
269 if (/*tkstr ||*/ eof
) throw new Exception("number expected");
270 if (hasValue
) throw new Exception("number expected");
272 if (t
.length
== 0 ||
!t
.ptr
[0].isdigit
) throw new Exception("number expected");
277 if (!hasValue
) return 1;
279 if (/*tkstr ||*/ eof
) throw new Exception("boolean expected");
280 if (hasValue
) throw new Exception("boolean expected");
282 if (t
.strEquCI("yes") || t
.strEquCI("tan") || t
.strEquCI("true") || t
.strEquCI("y") || t
.strEquCI("t")) return 1;
283 if (t
.strEquCI("no") || t
.strEquCI("ona") || t
.strEquCI("false") || t
.strEquCI("n") || t
.strEquCI("f") || t
.strEquCI("o")) return 0;
284 throw new Exception("boolean expected, but got '"~t
.idup
~"'");
287 void getStr (ref DynStr dest
) {
288 if (!hasValue
) throw new Exception("string value expected");
290 if (eof
) throw new Exception("string value expected");
295 int getClickMask () {
296 if (!hasValue
) throw new Exception("clickmask value expected");
298 if (hasValue || eof
) throw new Exception("clickmask expected");
299 auto t
= token
.xstrip
;
301 while (t
.length
> 0) {
303 while (pos
< t
.length
&& t
.ptr
[0] > ' ' && t
.ptr
[0] != ',') ++pos
;
307 if (n
.strEquCI("left")) res |
= FuiLayoutProps
.Buttons
.Left
;
308 else if (n
.strEquCI("right")) res |
= FuiLayoutProps
.Buttons
.Right
;
309 else if (n
.strEquCI("middle")) res |
= FuiLayoutProps
.Buttons
.Middle
;
310 else throw new Exception("invalid button name: '"~n
.idup
~"'");
312 while (t
.length
&& (t
.ptr
[0] <= ' ' || t
.ptr
[0] == ',')) t
= t
[1..$];
319 int paddingLeft
= -1;
320 int paddingRight
= -1;
322 int paddingBottom
= -1;
323 int flex
= -1, spacing
= -1;
324 int clickMask
= -1, doubleMask
= -1;
326 int canbefocused
= -1;
331 int minw
= -1, minh
= -1;
332 int maxw
= -1, maxh
= -1;
344 DynStr bindfuncAction
;
346 DynStr bindfuncEvent
;
349 foreach (ref v
; this.tupleof
) {
350 static if (is(typeof(v
) == DynStr
)) v
.reset
;
351 else static if (is(typeof(v
) == int)) v
= -1;
358 if (ts
.strEquCI("flex")) { flex
= getInt(); return true; }
359 if (ts
.strEquCI("vertical")) { if (getBool
) hv
= 1; else hv
= 0; return true; }
360 if (ts
.strEquCI("horizontal")) { if (getBool
) hv
= 0; else hv
= 1; return true; }
361 if (ts
.strEquCI("orientation")) {
362 if (!hasValue
) throw new Exception("value expected");
364 if (eof
) throw new Exception("value expected");
365 if (token
.strEquCI("horizontal")) hv
= 0;
366 else if (token
.strEquCI("vertical")) hv
= 1;
367 else throw new Exception("invalid orientation: '"~token
.idup
~"'");
370 if (ts
.strEquCI("align")) {
371 if (!hasValue
) throw new Exception("value expected");
373 if (eof
) throw new Exception("value expected");
374 if (token
.strEquCI("center")) wpalign
= FuiLayoutProps
.Align
.Center
;
375 else if (token
.strEquCI("start")) wpalign
= FuiLayoutProps
.Align
.Start
;
376 else if (token
.strEquCI("end")) wpalign
= FuiLayoutProps
.Align
.End
;
377 else if (token
.strEquCI("stretch")) wpalign
= FuiLayoutProps
.Align
.Stretch
;
378 else if (token
.strEquCI("expand")) wpalign
= FuiLayoutProps
.Align
.Stretch
;
379 else throw new Exception("invalid align: '"~token
.idup
~"'");
382 if (ts
.strEquCI("lineBreak") || ts
.strEquCI("line-break")) { linebreak
= getBool
; return true; }
383 if (ts
.strEquCI("canBeFocused") || ts
.strEquCI("can-be-focused")) { canbefocused
= getBool
; return true; }
384 if (ts
.strEquCI("id")) { getStr(id
); return true; }
385 if (ts
.strEquCI("caption")) { getStr(caption
); return true; }
386 if (ts
.strEquCI("text")) { getStr(caption
); return true; } // alias for "caption"
387 if (ts
.strEquCI("dest")) { getStr(dest
); return true; }
388 if (ts
.strEquCI("hgroup")) { getStr(hgroup
); return true; }
389 if (ts
.strEquCI("vgroup")) { getStr(vgroup
); return true; }
390 if (ts
.strEquCI("bindVar") || ts
.strEquCI("bind-var")) { getStr(bindvar
); return true; }
391 //if (ts.strEquCI("bindFunc") || ts.strEquCI("bind-func")) { getStr(bindfuncAction); return true; }
392 if (ts
.strEquCI("onaction") || ts
.strEquCI("on-action")) { getStr(bindfuncAction
); return true; }
393 if (ts
.strEquCI("ondraw") || ts
.strEquCI("on-draw")) { getStr(bindfuncDraw
); return true; }
394 if (ts
.strEquCI("onevent") || ts
.strEquCI("on-event")) { getStr(bindfuncEvent
); return true; }
395 if (ts
.strEquCI("paddingLeft") || ts
.strEquCI("padding-left")) { paddingLeft
= getInt(); return true; }
396 if (ts
.strEquCI("paddingTop") || ts
.strEquCI("padding-top")) { paddingTop
= getInt(); return true; }
397 if (ts
.strEquCI("paddingRight") || ts
.strEquCI("padding-right")) { paddingRight
= getInt(); return true; }
398 if (ts
.strEquCI("paddingBottom") || ts
.strEquCI("padding-bottom")) { paddingBottom
= getInt(); return true; }
399 if (ts
.strEquCI("spacing")) { spacing
= getInt(); return true; }
400 if (ts
.strEquCI("width")) { minw
= maxw
= getInt(); return true; }
401 if (ts
.strEquCI("height")) { minh
= maxh
= getInt(); return true; }
402 if (ts
.strEquCI("min-width")) { minw
= getInt(); return true; }
403 if (ts
.strEquCI("min-height")) { minh
= getInt(); return true; }
404 if (ts
.strEquCI("max-width")) { maxw
= getInt(); return true; }
405 if (ts
.strEquCI("max-height")) { maxh
= getInt(); return true; }
406 if (ts
.strEquCI("default")) { def
= getBool(); return true; }
407 if (ts
.strEquCI("disabled")) { disabled
= getBool(); return true; }
408 if (ts
.strEquCI("enabled")) { disabled
= 1-getBool(); return true; }
409 if (ts
.strEquCI("smallFrame") || ts
.strEquCI("small-frame")) { smallframe
= getBool(); return true; }
410 if (ts
.strEquCI("clickMask") || ts
.strEquCI("click-mask")) { clickMask
= getClickMask(); return true; }
411 if (ts
.strEquCI("doubleMask") || ts
.strEquCI("double-mask")) { doubleMask
= getClickMask(); return true; }
412 if (ts
.strEquCI("alert")) { alert
= getBool(); return true; }
413 if (ts
.strEquCI("utfuck")) { utfuck
= getBool(); return true; }
414 if (ts
.strEquCI("enter-close")) { enterclose
= getBool(); return true; }
415 if (ts
.strEquCI("reset-size")) { resetsize
= getBool(); return true; }
422 bool isGoodWidgetName (const(char)[] s
) {
424 s
.strEquCI("span") || s
.strEquCI("hspan") || s
.strEquCI("vspan") ||
425 s
.strEquCI("hline") ||
426 s
.strEquCI("spacer") ||
427 s
.strEquCI("box") || s
.strEquCI("hbox") || s
.strEquCI("vbox") ||
428 s
.strEquCI("panel") || s
.strEquCI("hpanel") || s
.strEquCI("vpanel") ||
429 s
.strEquCI("label") ||
430 s
.strEquCI("button") ||
431 s
.strEquCI("checkbox") ||
432 s
.strEquCI("radio") ||
433 s
.strEquCI("editline") || s
.strEquCI("edittext") ||
434 s
.strEquCI("textview") ||
435 s
.strEquCI("listbox") ||
436 s
.strEquCI("custombox");
439 int bindFuncTo(string type
) (int item
) {
440 static assert(type
== "Action" || type
== "Draw" || type
== "Event", "invalid type");
441 static if (type
== "Action") bool bfe
= props
.bindfuncAction
.empty
;
442 else static if (type
== "Draw") bool bfe
= props
.bindfuncDraw
.empty
;
443 else static if (type
== "Event") bool bfe
= props
.bindfuncEvent
.empty
;
445 foreach (immutable idx
, /*auto*/ var
; Vars
) {
446 static if (is(typeof(var
) == function) ||
is(typeof(var
) == delegate)) {
447 static if (type
== "Action") {
448 static if (is(typeof((dg
){int n
= dg(FuiContext
.init
, 666);}(&Vars
[idx
])))) {
449 if ((&Vars
[idx
]).stringof
[1..$].xstrip
== props
.bindfuncAction
.getz
) {
450 ctx
.setActionCB(item
, &Vars
[idx
]);
454 } else static if (type
== "Draw") {
455 static if (is(typeof((dg
){dg(FuiContext
.init
, 666, FuiRect
.init
);}(&Vars
[idx
])))) {
456 if ((&Vars
[idx
]).stringof
[1..$].xstrip
== props
.bindfuncDraw
.getz
) {
457 ctx
.setDrawCB(item
, &Vars
[idx
]);
461 } else static if (type
== "Event") {
462 static if (is(typeof((dg
){bool b
= dg(FuiContext
.init
, 666, FuiEvent
.init
);}(&Vars
[idx
])))) {
463 if ((&Vars
[idx
]).stringof
[1..$].xstrip
== props
.bindfuncEvent
.getz
) {
464 ctx
.setEventCB(item
, &Vars
[idx
]);
475 int bindFuncs (int item
) {
476 bindFuncTo
!"Action"(item
);
477 bindFuncTo
!"Draw"(item
);
478 bindFuncTo
!"Event"(item
);
482 int createWidget (int parent
) {
483 auto widn
= widname
.getz
;
484 if (widn
.strEquCI("span")) return bindFuncs(ctx
.span(parent
, (props
.hv
<= 0)));
485 if (widn
.strEquCI("hspan")) return bindFuncs(ctx
.hspan(parent
));
486 if (widn
.strEquCI("vspan")) return bindFuncs(ctx
.vspan(parent
));
487 if (widn
.strEquCI("spacer")) return bindFuncs(ctx
.spacer(parent
));
488 if (widn
.strEquCI("hline")) return bindFuncs(ctx
.hline(parent
));
489 if (widn
.strEquCI("box")) return bindFuncs(ctx
.box(parent
, (props
.hv
<= 0), props
.id
.getz
));
490 if (widn
.strEquCI("hbox")) return bindFuncs(ctx
.hbox(parent
, props
.id
.getz
));
491 if (widn
.strEquCI("vbox")) return bindFuncs(ctx
.vbox(parent
, props
.id
.getz
));
492 if (widn
.strEquCI("panel")) return bindFuncs(ctx
.panel(parent
, props
.caption
.getz
, (props
.hv
<= 0), props
.id
.getz
));
493 if (widn
.strEquCI("hpanel")) return bindFuncs(ctx
.hpanel(parent
, props
.caption
.getz
, props
.id
.getz
));
494 if (widn
.strEquCI("vpanel")) return bindFuncs(ctx
.vpanel(parent
, props
.caption
.getz
, props
.id
.getz
));
495 if (widn
.strEquCI("label")) return bindFuncs(ctx
.label(parent
, props
.id
.getz
, props
.caption
.getz
, props
.dest
.getz
));
496 if (widn
.strEquCI("button")) return bindFuncs(ctx
.button(parent
, props
.id
.getz
, props
.caption
.getz
));
497 if (widn
.strEquCI("editline")) return bindFuncs(ctx
.editline(parent
, props
.id
.getz
, props
.caption
.getz
, props
.utfuck
> 0));
498 if (widn
.strEquCI("edittext")) return bindFuncs(ctx
.edittext(parent
, props
.id
.getz
, props
.caption
.getz
, props
.utfuck
> 0));
499 if (widn
.strEquCI("textview")) return bindFuncs(ctx
.textview(parent
, props
.id
.getz
, props
.caption
.getz
));
500 if (widn
.strEquCI("listbox")) return bindFuncs(ctx
.listbox(parent
, props
.id
.getz
));
501 if (widn
.strEquCI("custombox")) return bindFuncs(ctx
.custombox(parent
, props
.id
.getz
));
502 if (widn
.strEquCI("checkbox")) {
503 if (!props
.bindvar
.empty
) {
504 foreach (int idx
, /*auto*/ var
; Vars
) {
505 static if (is(typeof(var
) == bool)) {
506 if (Vars
[idx
].stringof
== props
.bindvar
.getz
) {
507 return bindFuncs(ctx
.checkbox(parent
, props
.id
.getz
, props
.caption
.getz
, &Vars
[idx
]));
512 return bindFuncs(ctx
.checkbox(parent
, props
.id
.getz
, props
.caption
.getz
, null));
514 if (widn
.strEquCI("radio")) {
515 if (!props
.bindvar
.empty
) {
516 foreach (int idx
, /*auto*/ var
; Vars
) {
517 static if (is(typeof(var
) == int)) {
518 if (Vars
[idx
].stringof
== props
.bindvar
.getz
) {
519 return bindFuncs(ctx
.radio(parent
, props
.id
.getz
, props
.caption
.getz
, &Vars
[idx
]));
524 return bindFuncs(ctx
.radio(parent
, props
.id
.getz
, props
.caption
.getz
, null));
526 throw new Exception("unknown widget: '"~widn
.idup
~"'");
530 void readProps(bool doWidgetCreation
=true) (int parent
) {
531 bool readGroup
= true;
536 static if (doWidgetCreation
) {
537 if (!isGoodWidgetName(token
)) throw new Exception("widget name expected");
539 readGroup
= hasValue
;
544 auto lp
= ctx
.layprops(item
);
545 if (props
.paddingLeft
>= 0) lp
.padding
.left
= props
.paddingLeft
;
546 if (props
.paddingTop
>= 0) lp
.padding
.top
= props
.paddingTop
;
547 if (props
.paddingRight
>= 0) lp
.padding
.right
= props
.paddingRight
;
548 if (props
.paddingBottom
>= 0) lp
.padding
.bottom
= props
.paddingBottom
;
549 if (props
.flex
>= 0) lp
.flex
= props
.flex
;
550 if (props
.spacing
>= 0) lp
.spacing
= props
.spacing
;
551 if (props
.clickMask
>= 0) lp
.clickMask
= cast(ubyte)props
.clickMask
;
552 if (props
.doubleMask
>= 0) lp
.doubleMask
= cast(ubyte)props
.doubleMask
;
553 if (props
.linebreak
>= 0) lp
.lineBreak
= (props
.linebreak
!= 0);
554 if (props
.canbefocused
>= 0) lp
.canBeFocused
= (props
.canbefocused
!= 0);
555 if (props
.hv
>= 0) lp
.vertical
= (props
.hv
!= 0);
556 if (props
.def
>= 0) lp
.userFlags
= (lp
.userFlags
&~cast(uint)FuiCtlUserFlags
.Default
)|
(props
.def
> 0 ? FuiCtlUserFlags
.Default
: 0);
557 if (props
.wpalign
>= 0) lp
.aligning
= cast(FuiLayoutProps
.Align
)props
.wpalign
;
558 if (props
.resetsize
> 0) {
559 lp
.minSize
= FuiSize(1, 1);
560 lp
.maxSize
= FuiSize(int.max
-1024, int.max
-1024);
562 if (props
.minw
>= 0) lp
.minSize
.w
= props
.minw
;
563 if (props
.minh
>= 0) lp
.minSize
.h
= props
.minh
;
564 if (props
.maxw
>= 0) lp
.maxSize
.w
= props
.maxw
;
565 if (props
.maxh
>= 0) lp
.maxSize
.h
= props
.maxh
;
566 if (props
.disabled
>= 0) lp
.disabled
= (props
.disabled
> 0);
567 if (!props
.hgroup
.empty
) {
568 auto id
= ctx
[props
.hgroup
.getz
];
569 if (id
< 0) throw new Exception("no widget with id '"~props
.hgroup
.getz
.idup
~"'");
572 if (!props
.vgroup
.empty
) {
573 auto id
= ctx
[props
.vgroup
.getz
];
574 if (id
< 0) throw new Exception("no widget with id '"~props
.vgroup
.getz
.idup
~"'");
578 ctx
.dialogCaption(props
.caption
.getz
);
579 if (props
.smallframe
>= 0) ctx
.dialogFrame(props
.smallframe
> 0 ? FuiDialogFrameType
.Small
: FuiDialogFrameType
.Normal
);
580 if (props
.alert
>= 0) ctx
.dialogPalette(props
.alert
> 0 ? TuiPaletteError
: TuiPaletteNormal
);
581 if (props
.enterclose
>= 0) ctx
.dialogEnterClose(props
.enterclose
> 0);
587 if (doWidgetCreation
) {
589 if (eof || tkstr || token
!= "{") throw new Exception("group expected");
592 // collect properties
597 if (ts
== "}") break;
598 if (!hasValue
&& !tkstr
&& ts
== ",") continue;
601 if (eof
) throw new Exception("unclosed group");
602 if (token
!= "}") throw new Exception("unclosed group");
605 if (props
.parseProp()) continue;
606 if (ts
.strEquCI("items")) {
607 if (widname
.getz
!= "listbox") throw new Exception("'"~widname
.getz
.idup
~"' can't have items");
610 if (eof || token
!= "{") throw new Exception("items expected");
615 if (!isGoodWidgetName(ts
)) throw new Exception("invalid token: '"~ts
.idup
~"'");
617 static if (doWidgetCreation
) item
= createWidget(parent
);
619 // second pass: process children, add items
624 if (!tkstr
&& token
== "}") break;
625 if (!hasValue
&& !tkstr
&& token
== ",") continue;
626 if (isGoodWidgetName(token
)) {
628 } else if (token
.strEquCI("items")) {
629 //if (widname.getz != "listbox") throw new Exception("'"~widname.getz.idup~"' can't have items");
632 if (eof || token
!= "{") throw new Exception("items expected");
635 if (eof
) throw new Exception("unclosed item group");
636 if (token
== "}") break;
637 if (!hasValue
&& !tkstr
&& token
== ",") continue;
638 if (hasValue
) throw new Exception("item can't have any value");
639 ctx
.listboxItemAdd(item
, token
);
641 auto lp
= ctx
.layprops(item
);
642 if (lp
.minSize
.w
<= 3) lp
.minSize
.w
= ctx
.listboxMaxItemWidth(item
);
643 if (lp
.minSize
.h
< 1) {
644 int fh
= (ctx
.dialogFrame
== FuiDialogFrameType
.Normal ?
4 : 2);
645 auto mh
= ctx
.listboxItemCount(item
);
646 if (mh
> ttyh
-fh
) mh
= ttyh
-fh
;
651 if (!tkstr
&& token
== "{") throw new Exception("orphaned group");
655 if (doWidgetCreation
) item
= createWidget(parent
);
659 //{ import iv.vfs.io; writeln("text: ", text); }