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, either version 3 of the License, or
10 * (at your option) any later version.
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
.egtui
.tui
/*is aliced*/;
26 import iv
.egtui
.editor
;
27 public import iv
.egtui
.layout
;
29 import iv
.egtui
.types
;
30 import iv
.egtui
.utils
;
31 import iv
.egtui
.dialogs
: dialogHistory
;
34 // ////////////////////////////////////////////////////////////////////////// //
35 enum FuiContinue
= -666;
38 // ////////////////////////////////////////////////////////////////////////// //
40 uint def
; // default color
41 uint sel
; // sel is also focus
42 uint mark
; // marked text
43 uint marksel
; // active marked text
45 uint input
; // input field
46 uint inputmark
; // input field marked text (?)
47 uint inputunchanged
; // unchanged input field
48 uint reverse
; // reversed text
49 uint title
; // window title
50 uint disabled
; // disabled text
57 // ////////////////////////////////////////////////////////////////////////// //
58 enum TuiPaletteNormal
= 0;
59 enum TuiPaletteError
= 1;
61 __gshared Palette
[2] tuiPalette
; // default palette
63 shared static this () {
66 tuiPalette
[TuiPaletteNormal
].def
= XtColorFB
!(ttyRgb2Color(0xd0, 0xd0, 0xd0), ttyRgb2Color(0x4e, 0x4e, 0x4e)); // 252,239
68 tuiPalette
[TuiPaletteNormal
].sel
= XtColorFB
!(ttyRgb2Color(0xda, 0xda, 0xda), ttyRgb2Color(0x00, 0x5f, 0x5f)); // 253,23
69 tuiPalette
[TuiPaletteNormal
].mark
= XtColorFB
!(ttyRgb2Color(0xff, 0xff, 0x00), ttyRgb2Color(0x5f, 0x5f, 0x5f)); // 226,59
70 tuiPalette
[TuiPaletteNormal
].marksel
= XtColorFB
!(ttyRgb2Color(0xff, 0xff, 0x87), ttyRgb2Color(0x00, 0x5f, 0x87)); // 228,24
71 tuiPalette
[TuiPaletteNormal
].gauge
= XtColorFB
!(ttyRgb2Color(0xbc, 0xbc, 0xbc), ttyRgb2Color(0x5f, 0x87, 0x87)); // 250,66
72 tuiPalette
[TuiPaletteNormal
].input
= XtColorFB
!(ttyRgb2Color(0xd7, 0xd7, 0xaf), ttyRgb2Color(0x26, 0x26, 0x26)); // 187,235
73 tuiPalette
[TuiPaletteNormal
].inputmark
= XtColorFB
!(ttyRgb2Color(0xff, 0xff, 0x87), ttyRgb2Color(0x00, 0x5f, 0x5f)); // 228,23
74 //tuiPalette[TuiPaletteNormal].inputunchanged = XtColorFB!(ttyRgb2Color(0xff, 0xff, 0xff), ttyRgb2Color(0x26, 0x26, 0x26)); // 144,235
75 tuiPalette
[TuiPaletteNormal
].inputunchanged
= XtColorFB
!(ttyRgb2Color(0xff, 0xff, 0xff), ttyRgb2Color(0x00, 0x00, 0x40));
76 tuiPalette
[TuiPaletteNormal
].reverse
= XtColorFB
!(ttyRgb2Color(0xe4, 0xe4, 0xe4), ttyRgb2Color(0x5f, 0x87, 0x87)); // 254,66
77 tuiPalette
[TuiPaletteNormal
].title
= XtColorFB
!(ttyRgb2Color(0xd7, 0xaf, 0x87), ttyRgb2Color(0x4e, 0x4e, 0x4e)); // 180,239
78 tuiPalette
[TuiPaletteNormal
].disabled
= XtColorFB
!(ttyRgb2Color(0x94, 0x94, 0x94), ttyRgb2Color(0x4e, 0x4e, 0x4e)); // 246,239
80 tuiPalette
[TuiPaletteNormal
].hot
= XtColorFB
!(ttyRgb2Color(0xff, 0xaf, 0x00), ttyRgb2Color(0x4e, 0x4e, 0x4e)); // 214,239
81 tuiPalette
[TuiPaletteNormal
].hotsel
= XtColorFB
!(ttyRgb2Color(0xff, 0xaf, 0x00), ttyRgb2Color(0x00, 0x5f, 0x5f)); // 214,23
83 tuiPalette
[TuiPaletteError
] = tuiPalette
[TuiPaletteNormal
];
84 tuiPalette
[TuiPaletteError
].def
= XtColorFB
!(ttyRgb2Color(0xff, 0xff, 0xd7), ttyRgb2Color(0x5f, 0x00, 0x00)); // 230,52
85 tuiPalette
[TuiPaletteError
].sel
= XtColorFB
!(ttyRgb2Color(0xe4, 0xe4, 0xe4), ttyRgb2Color(0x00, 0x5f, 0x5f)); // 254,23
86 tuiPalette
[TuiPaletteError
].hot
= XtColorFB
!(ttyRgb2Color(0xff, 0x5f, 0x5f), ttyRgb2Color(0x5f, 0x00, 0x00)); // 203,52
87 tuiPalette
[TuiPaletteError
].hotsel
= XtColorFB
!(ttyRgb2Color(0xff, 0x5f, 0x5f), ttyRgb2Color(0x00, 0x5f, 0x5f)); // 203,23
88 tuiPalette
[TuiPaletteError
].title
= XtColorFB
!(ttyRgb2Color(0xff, 0xff, 0x5f), ttyRgb2Color(0x5f, 0x00, 0x00)); // 227,52
90 if (termType
== TermType
.linux
) {
92 tuiPalette
[TuiPaletteNormal
].def
= XtColorFB
!(ttyRgb2Color(0xd0, 0xd0, 0xd0), ttyRgb2Color(0x18, 0x18, 0xb2));
93 tuiPalette
[TuiPaletteNormal
].title
= XtColorFB
!(ttyRgb2Color(0xd7, 0xaf, 0x87), ttyRgb2Color(0x18, 0x18, 0xb2));
94 tuiPalette
[TuiPaletteNormal
].disabled
= XtColorFB
!(ttyRgb2Color(0x94, 0x94, 0x94), ttyRgb2Color(0x18, 0x18, 0xb2));
95 tuiPalette
[TuiPaletteNormal
].hot
= XtColorFB
!(ttyRgb2Color(0xff, 0xaf, 0x00), ttyRgb2Color(0x18, 0x18, 0xb2));
100 // ////////////////////////////////////////////////////////////////////////// //
101 private int visStrLen(bool dohot
=true) (const(char)[] s
) nothrow @trusted @nogc {
102 if (s
.length
> int.max
) s
= s
[0..int.max
];
103 int res
= cast(int)s
.length
;
104 if (res
&& s
.ptr
[0] == '\x01') --res
; // left
105 else if (res
&& s
.ptr
[0] == '\x02') --res
; // right
106 else if (res
&& s
.ptr
[0] == '\x03') --res
; // center (default)
109 auto ampos
= s
.indexOf('&');
110 if (ampos
< 0 || s
.length
-ampos
< 2) break;
111 if (s
.ptr
[ampos
+1] != '&') { --res
; break; }
119 private char visHotChar (const(char)[] s
) nothrow @trusted @nogc {
120 bool prevWasAmp
= false;
121 foreach (char ch
; s
) {
123 prevWasAmp
= !prevWasAmp
;
124 } else if (prevWasAmp
) {
132 // ////////////////////////////////////////////////////////////////////////// //
133 private const(char)[] getz (const(char)[] s
) nothrow @trusted @nogc {
134 foreach (immutable idx
, char ch
; s
) if (ch
== 0) return s
[0..idx
];
139 private void setz (char[] dest
, const(char)[] s
) nothrow @trusted @nogc {
141 if (s
.length
>= dest
.length
) s
= s
[0..dest
.length
-1];
142 if (s
.length
) dest
[0..s
.length
] = s
[];
146 // ////////////////////////////////////////////////////////////////////////// //
147 enum FuiCtlUserFlags
: ushort {
152 // ////////////////////////////////////////////////////////////////////////// //
153 enum FuiCtlType
: ubyte {
163 //WARNING: button-likes should be last!
170 // called when dialog is closed; -1 means "esc"; you can change `modalLastResult` to change dialog return value
171 alias TuiDialogCloseCB
= void delegate (FuiContext ctx
, int res
);
172 // onchange callback; return FuiContinue to continue, -1 to exit via "esc", or control id to return from `modalDialog()`
173 alias TuiActionCB
= int delegate (FuiContext ctx
, int self
);
174 // draw widget; rc is in global space
175 alias TuiDrawCB
= void delegate (FuiContext ctx
, int self
, FuiRect rc
);
176 // process event; return `true` if event was eaten
177 alias TuiEventCB
= bool delegate (FuiContext ctx
, int self
, FuiEvent ev
);
179 mixin template FuiCtlHeader() {
182 char[64] id
= 0; // widget it
183 char[128] caption
= 0; // widget caption
185 // onchange callback; return FuiContinue to continue, -1 to exit via "esc", or control id to return from `modalDialog()`
187 // draw widget; rc is in global space
189 // process event; return `true` if event was eaten
197 static assert(FuiCtlHead
.actcb
.offsetof
%8 == 0);
200 //TODO: make delegate this scoped?
201 bool setActionCB (FuiContext ctx
, int item
, TuiActionCB cb
) {
202 if (auto data
= ctx
.itemIntr
!FuiCtlHead(item
)) {
210 //TODO: make delegate this scoped?
211 bool setDrawCB (FuiContext ctx
, int item
, TuiDrawCB cb
) {
212 if (auto data
= ctx
.itemIntr
!FuiCtlHead(item
)) {
220 //TODO: make delegate this scoped?
221 bool setEventCB (FuiContext ctx
, int item
, TuiEventCB cb
) {
222 if (auto data
= ctx
.itemIntr
!FuiCtlHead(item
)) {
230 bool setCaption (FuiContext ctx
, int item
, const(char)[] caption
) {
231 if (auto data
= ctx
.item
!FuiCtlHead(item
)) {
232 data
.caption
.setz(caption
);
233 auto cpt
= data
.caption
.getz
;
234 auto lp
= ctx
.layprops(item
);
235 /*if (lp.minSize.w < cpt.length)*/ lp
.minSize
.w
= cast(int)cpt
.length
;
242 // ////////////////////////////////////////////////////////////////////////// //
243 void postClose (FuiContext ctx
, int res
) {
244 if (!ctx
.valid
) return;
245 ctx
.queueEvent(0, FuiEvent
.Type
.Close
, res
);
249 // ////////////////////////////////////////////////////////////////////////// //
250 enum FuiDialogFrameType
: ubyte {
256 struct FuiCtlRootPanel
{
258 FuiDialogFrameType frame
;
261 // history manager for this dialog
262 FuiHistoryManager hisman
;
263 TuiDialogCloseCB closecb
;
267 bool dialogCloseCB (FuiContext ctx
, TuiDialogCloseCB dcb
) {
268 if (auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0)) {
275 TuiDialogCloseCB
dialogCloseCB (FuiContext ctx
) {
276 if (auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0)) return data
.closecb
;
282 void dialogMoving (FuiContext ctx
, bool v
) {
283 if (!ctx
.valid
) return;
284 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
288 bool dialogMoving (FuiContext ctx
) {
289 if (!ctx
.valid
) return false;
290 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
295 void dialogCaption (FuiContext ctx
, const(char)[] text
) {
296 if (!ctx
.valid
) return;
297 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
298 data
.caption
.setz(text
);
302 FuiDialogFrameType
dialogFrame (FuiContext ctx
) {
303 if (!ctx
.valid
) return FuiDialogFrameType
.Normal
;
304 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
308 void dialogFrame (FuiContext ctx
, FuiDialogFrameType t
) {
309 if (!ctx
.valid
) return;
310 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
311 auto lp
= ctx
.layprops(0);
313 case FuiDialogFrameType
.Normal
:
314 data
.frame
= FuiDialogFrameType
.Normal
;
322 case FuiDialogFrameType
.Small
:
323 data
.frame
= FuiDialogFrameType
.Small
;
335 void dialogEnterClose (FuiContext ctx
, bool enterclose
) {
336 if (!ctx
.valid
) return;
337 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
338 data
.enterclose
= enterclose
;
342 bool dialogEnterClose (FuiContext ctx
) {
343 if (!ctx
.valid
) return false;
344 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
345 return data
.enterclose
;
349 FuiHistoryManager
dialogHistoryManager (FuiContext ctx
) {
350 if (auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0)) return data
.hisman
;
355 void dialogHistoryManager (FuiContext ctx
, FuiHistoryManager hisman
) {
356 if (auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0)) data
.hisman
= hisman
;
360 // ////////////////////////////////////////////////////////////////////////// //
361 struct FuiCtlCustomBox
{
363 align(8) void* udata
;
367 int custombox (FuiContext ctx
, int parent
, const(char)[] id
=null) {
368 if (!ctx
.valid
) return -1;
369 auto item
= ctx
.addItem
!FuiCtlSpan(parent
);
370 with (ctx
.layprops(item
)) {
374 aligning
= FuiLayoutProps
.Align
.Stretch
;
377 //clickMask |= FuiLayoutProps.Buttons.Left;
378 //canBeFocused = true;
381 auto data
= ctx
.item
!FuiCtlCustomBox(item
);
382 data
.type
= FuiCtlType
.CustomBox
;
385 data.drawcb = delegate (FuiContext ctx, int self, FuiRect rc) {
386 auto win = XtWindow(rc.x, rc.y, rc.w, rc.h);
387 win.color = ctx.palColor!"def"(item);
389 win.fill(0, 0, rc.w, rc.h);
396 // ////////////////////////////////////////////////////////////////////////// //
402 int span (FuiContext ctx
, int parent
, bool ahorizontal
) {
403 if (!ctx
.valid
) return -1;
404 auto item
= ctx
.addItem
!FuiCtlSpan(parent
);
405 with (ctx
.layprops(item
)) {
408 horizontal
= ahorizontal
;
409 aligning
= (ahorizontal ? FuiLayoutProps
.Align
.Stretch
: FuiLayoutProps
.Align
.Start
);
411 auto data
= ctx
.item
!FuiCtlSpan(item
);
412 data
.type
= FuiCtlType
.Invisible
;
413 //data.pal = Palette.init;
417 int hspan (FuiContext ctx
, int parent
) { return ctx
.span(parent
, true); }
418 int vspan (FuiContext ctx
, int parent
) { return ctx
.span(parent
, false); }
421 int spacer (FuiContext ctx
, int parent
) {
422 if (!ctx
.valid
) return -1;
423 auto item
= ctx
.addItem
!FuiCtlSpan(parent
);
424 with (ctx
.layprops(item
)) {
428 aligning
= FuiLayoutProps
.Align
.Start
;
430 auto data
= ctx
.item
!FuiCtlSpan(item
);
431 data
.type
= FuiCtlType
.Invisible
;
432 //data.pal = Palette.init;
437 int hline (FuiContext ctx
, int parent
) {
438 if (!ctx
.valid
) return -1;
439 auto item
= ctx
.addItem
!FuiCtlSpan(parent
);
440 with (ctx
.layprops(item
)) {
445 aligning
= FuiLayoutProps
.Align
.Stretch
;
446 minSize
.h
= maxSize
.h
= 1;
448 auto data
= ctx
.item
!FuiCtlSpan(item
);
449 data
.type
= FuiCtlType
.HLine
;
450 //data.pal = Palette.init;
451 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
452 auto lp
= ctx
.layprops(self
);
453 auto win
= XtWindow
.fullscreen
;
454 win
.color
= ctx
.palColor
!"def"(self
);
455 if (lp
.parent
== 0) {
456 auto rlp
= ctx
.layprops(0);
458 final switch (ctx
.dialogFrame
) {
459 case FuiDialogFrameType
.Normal
:
461 x1
= x0
+(rlp
.position
.w
-2);
463 case FuiDialogFrameType
.Small
:
465 x1
= x0
+rlp
.position
.w
;
468 win
.hline(x0
, rc
.y
, x1
-x0
);
470 win
.hline(rc
.x
, rc
.y
, rc
.w
);
476 // ////////////////////////////////////////////////////////////////////////// //
482 int box (FuiContext ctx
, int parent
, bool ahorizontal
, const(char)[] id
=null) {
483 if (!ctx
.valid
) return -1;
484 auto item
= ctx
.addItem
!FuiCtlBox(parent
);
485 with (ctx
.layprops(item
)) {
486 flex
= (ahorizontal ?
0 : 1);
487 horizontal
= ahorizontal
;
488 aligning
= (ahorizontal ? FuiLayoutProps
.Align
.Stretch
: FuiLayoutProps
.Align
.Start
);
491 auto data
= ctx
.item
!FuiCtlBox(item
);
492 data
.type
= FuiCtlType
.Box
;
493 //data.pal = Palette.init;
498 int hbox (FuiContext ctx
, int parent
, const(char)[] id
=null) { return ctx
.box(parent
, true, id
); }
499 int vbox (FuiContext ctx
, int parent
, const(char)[] id
=null) { return ctx
.box(parent
, false, id
); }
502 // ////////////////////////////////////////////////////////////////////////// //
508 int panel (FuiContext ctx
, int parent
, const(char)[] caption
, bool ahorizontal
, const(char)[] id
=null) {
509 if (!ctx
.valid
) return -1;
510 auto item
= ctx
.addItem
!FuiCtlPanel(parent
);
511 with (ctx
.layprops(item
)) {
512 flex
= (ahorizontal ?
0 : 1);
513 horizontal
= ahorizontal
;
514 aligning
= (ahorizontal ? FuiLayoutProps
.Align
.Stretch
: FuiLayoutProps
.Align
.Start
);
515 minSize
= FuiSize(visStrLen(caption
)+4, 2);
521 auto data
= ctx
.item
!FuiCtlPanel(item
);
522 data
.type
= FuiCtlType
.Panel
;
523 //data.pal = Palette.init;
525 data
.caption
.setz(caption
);
526 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
527 auto data
= ctx
.item
!FuiCtlPanel(self
);
528 auto win
= XtWindow
.fullscreen
;
529 win
.color
= ctx
.palColor
!"def"(self
);
530 win
.frame
!true(rc
.x
, rc
.y
, rc
.w
, rc
.h
);
531 win
.tuiWriteStr
!("center", true)(rc
.x
+1, rc
.y
, rc
.w
-2, data
.caption
.getz
, ctx
.palColor
!"title"(self
), ctx
.palColor
!"hot"(self
));
536 int hpanel (FuiContext ctx
, int parent
, const(char)[] caption
, const(char)[] id
=null) { return ctx
.panel(parent
, caption
, true, id
); }
537 int vpanel (FuiContext ctx
, int parent
, const(char)[] caption
, const(char)[] id
=null) { return ctx
.panel(parent
, caption
, false, id
); }
540 // ////////////////////////////////////////////////////////////////////////// //
541 struct FuiCtlEditLine
{
543 align(8) TtyEditor ed
;
545 static assert(FuiCtlEditLine
.ed
.offsetof
%4 == 0);
548 private int editlinetext(bool text
) (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] deftext
=null, bool utfuck
=false) {
549 if (!ctx
.valid
) return -1;
550 auto item
= ctx
.addItem
!FuiCtlEditLine(parent
);
551 with (ctx
.layprops(item
)) {
554 clickMask |
= FuiLayoutProps
.Buttons
.Left
;
557 //aligning = (ahorizontal ? FuiLayoutProps.Align.Stretch : FuiLayoutProps.Align.Start);
558 int mw
= (deftext
.length
< 10 ?
10 : deftext
.length
> 50 ?
50 : cast(int)deftext
.length
);
559 minSize
= FuiSize(mw
, 1);
560 maxSize
= FuiSize(int.max
-1024, 1);
562 auto data
= ctx
.item
!FuiCtlEditLine(item
);
564 data
.type
= FuiCtlType
.EditText
;
566 data
.type
= FuiCtlType
.EditLine
;
568 //data.pal = Palette.init;
570 data
.ed
= new TtyEditor(0, 0, 10, 1, !text
); // size will be fixed later
571 data
.ed
.utfuck
= utfuck
;
572 data
.ed
.setNewText(deftext
);
573 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
574 auto data
= ctx
.item
!FuiCtlEditLine(self
);
575 auto lp
= ctx
.layprops(self
);
577 ed
.hideStatus
= true;
578 ed
.moveResize(rc
.x
+(ed
.singleline ?
0 : 1), rc
.y
, rc
.w
, rc
.h
);
580 ed
.dontSetCursor
= (self
!= ctx
.focused
);
582 if (self
== ctx
.focused
) {
583 ed
.clrBlock
= ctx
.palColor
!"inputmark"(self
);
584 ed
.clrText
= ctx
.palColor
!"input"(self
);
585 ed
.clrTextUnchanged
= ctx
.palColor
!"inputunchanged"(self
);
587 ed
.clrBlock
= ctx
.palColor
!"inputmark"(self
);
588 ed
.clrText
= ctx
.palColor
!"input"(self
);
589 ed
.clrTextUnchanged
= ctx
.palColor
!"inputunchanged"(self
);
592 ed
.clrBlock
= ctx
.palColor
!"disabled"(self
);
593 ed
.clrText
= ctx
.palColor
!"disabled"(self
);
594 ed
.clrTextUnchanged
= ctx
.palColor
!"disabled"(self
);
596 //auto oldcolor = xtGetColor();
597 //scope(exit) xtSetColor(oldcolor);
600 data
.eventcb
= delegate (FuiContext ctx
, int self
, FuiEvent ev
) {
601 if (ev
.item
!= self
) return false;
602 auto eld
= ctx
.item
!FuiCtlEditLine(self
);
603 final switch (ev
.type
) {
604 case FuiEvent
.Type
.None
:
606 case FuiEvent
.Type
.Char
: // param0: dchar; param1: mods&buttons
608 k
.key
= TtyEvent
.Key
.Char
;
610 if (eld
.ed
.processKey(k
)) {
611 if (eld
.actcb
!is null) {
612 auto rr
= eld
.actcb(ctx
, ev
.item
);
613 if (rr
>= -1) ctx
.postClose(rr
);
618 case FuiEvent
.Type
.Key
: // param0: sdpy keycode; param1: mods&buttons
620 if (eld
.ed
.singleline
) {
621 if (auto hisman
= ctx
.dialogHistoryManager
) {
622 if (auto data
= ctx
.item
!FuiCtlEditLine(self
)) {
623 auto eid
= data
.id
.getz
;
624 if (eid
.length
&& hisman
.has(eid
)) {
625 if (ev
.key
== "M-H") {
627 if (auto lp
= ctx
.layprops(self
)) {
628 auto pt
= ctx
.toGlobal(self
, FuiPoint(0, 0));
629 auto hidx
= dialogHistory(hisman
, eid
, pt
.x
, pt
.y
);
631 auto s
= hisman
.item(eid
, hidx
);
632 eld
.ed
.setNewText(s
, false); // don't clear on type
633 hisman
.activate(eid
, hidx
);
634 if (eld
.actcb
!is null) {
635 auto rr
= eld
.actcb(ctx
, ev
.item
);
636 if (rr
>= -1) ctx
.postClose(rr
);
647 if (eld
.ed
.processKey(ev
.key
)) {
648 if (eld
.actcb
!is null) {
649 auto rr
= eld
.actcb(ctx
, ev
.item
);
650 if (rr
>= -1) ctx
.postClose(rr
);
655 case FuiEvent
.Type
.Click
: // mouse click; param0: buttton index; param1: mods&buttons
656 if (eld
.ed
.processClick(ev
.bidx
, ev
.x
, ev
.y
)) return true;
658 case FuiEvent
.Type
.Double
: // mouse double-click; param0: buttton index; param1: mods&buttons
660 case FuiEvent
.Type
.Close
: // close dialog; param0: return id
668 // `actcb` will be called *after* editor was changed (or not changed, who knows)
669 int editline (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] deftext
=null, bool utfuck
=false) {
670 return editlinetext
!false(ctx
, parent
, id
, deftext
, utfuck
);
674 // `actcb` will be called *after* editor was changed (or not changed, who knows)
675 int edittext (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] deftext
=null, bool utfuck
=false) {
676 return editlinetext
!true(ctx
, parent
, id
, deftext
, utfuck
);
680 TtyEditor
editlineEditor (FuiContext ctx
, int item
) {
681 if (auto data
= ctx
.item
!FuiCtlEditLine(item
)) {
682 if (data
.type
== FuiCtlType
.EditLine
) return data
.ed
;
688 TtyEditor
edittextEditor (FuiContext ctx
, int item
) {
689 if (auto data
= ctx
.item
!FuiCtlEditLine(item
)) {
690 if (data
.type
== FuiCtlType
.EditText
) return data
.ed
;
696 char[] editlineGetText (FuiContext ctx
, int item
) {
697 if (auto edl
= ctx
.itemAs
!"editline"(item
)) {
699 if (ed
is null) return null;
702 res
.reserve(rng
.length
);
703 foreach (char ch
; rng
) res
~= ch
;
710 char[] edittextGetText (FuiContext ctx
, int item
) {
711 if (auto edl
= ctx
.itemAs
!"edittext"(item
)) {
713 if (ed
is null) return null;
716 res
.reserve(rng
.length
);
717 foreach (char ch
; rng
) res
~= ch
;
724 // ////////////////////////////////////////////////////////////////////////// //
725 mixin template FuiCtlBtnLike() {
728 bool spaceClicks
= true;
729 bool enterClicks
= false;
731 // internal function, does some action on "click"
732 // will be called before `actcb`
733 // return `false` if click should not be processed further (no `actcb` will be called)
734 bool delegate (FuiContext ctx
, int self
) doclickcb
;
737 struct FuiCtlBtnLikeHead
{
742 private bool btnlikeClick (FuiContext ctx
, int item
, int clickButton
=-1) {
743 if (auto lp
= ctx
.layprops(item
)) {
744 if (!lp
.visible || lp
.disabled ||
(clickButton
>= 0 && lp
.clickMask
== 0)) return false;
745 auto data
= ctx
.item
!FuiCtlBtnLikeHead(item
);
746 bool clicked
= false;
747 if (clickButton
>= 0) {
748 foreach (ubyte shift
; 0..8) {
749 if (shift
== clickButton
&& (lp
.clickMask
&(1<<shift
)) != 0) { clicked
= true; break; }
755 if (data
.doclickcb
!is null) {
756 if (!data
.doclickcb(ctx
, item
)) return false;
758 if (data
.actcb
!is null) {
759 auto rr
= data
.actcb(ctx
, item
);
772 private int buttonLike(T
, FuiCtlType type
) (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] text
) {
773 if (!ctx
.valid
) return -1;
774 auto item
= ctx
.addItem
!T(parent
);
775 auto data
= ctx
.item
!T(item
);
777 //data.pal = Palette.init;
778 if (text
.length
> 255) text
= text
[0..255];
779 if (id
.length
> 255) id
= id
[0..255];
781 data
.caption
.setz(text
);
783 data
.hotchar
= visHotChar(text
).toupper
;
787 with (ctx
.layprops(item
)) {
789 minSize
= FuiSize(visStrLen(data
.caption
.getz
), 1);
790 clickMask |
= FuiLayoutProps
.Buttons
.Left
;
792 data
.eventcb
= delegate (FuiContext ctx
, int self
, FuiEvent ev
) {
793 final switch (ev
.type
) {
794 case FuiEvent
.Type
.None
:
796 case FuiEvent
.Type
.Char
: // param0: dchar; param1: mods&buttons
797 if (ev
.item
!= self
) return false;
798 auto data
= ctx
.item
!FuiCtlBtnLikeHead(self
);
799 if (!data
.spaceClicks
) return false;
800 if (ev
.ch
!= ' ') return false;
801 if (auto lp
= ctx
.layprops(self
)) {
802 if (!lp
.visible || lp
.disabled
) return false;
803 if (lp
.canBeFocused
) ctx
.focused
= self
;
804 return ctx
.btnlikeClick(self
);
807 case FuiEvent
.Type
.Key
: // param0: sdpy keycode; param1: mods&buttons
808 auto data
= ctx
.item
!FuiCtlBtnLikeHead(self
);
809 auto lp
= ctx
.layprops(self
);
810 if (!lp
.visible || lp
.disabled
) return false;
811 if (data
.enterClicks
&& ev
.item
== self
&& ev
.key
== "Enter") {
812 if (lp
.canBeFocused
) ctx
.focused
= self
;
813 return ctx
.btnlikeClick(self
);
815 if (ev
.key
.key
!= TtyEvent
.Key
.ModChar || ev
.key
.ctrl ||
!ev
.key
.alt || ev
.key
.shift
) return false;
816 if (data
.hotchar
!= ev
.key
.ch
) return false;
817 if (lp
.canBeFocused
) ctx
.focused
= self
;
818 ctx
.btnlikeClick(self
);
820 case FuiEvent
.Type
.Click
: // mouse click; param0: buttton index; param1: mods&buttons
821 if (ev
.item
== self
) return ctx
.btnlikeClick(self
, ev
.bidx
);
823 case FuiEvent
.Type
.Double
: // mouse double-click; param0: buttton index; param1: mods&buttons
825 case FuiEvent
.Type
.Close
: // close dialog; param0: return id
833 // ////////////////////////////////////////////////////////////////////////// //
840 int label (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] text
, const(char)[] destid
=null) {
841 auto res
= ctx
.buttonLike
!(FuiCtlLabel
, FuiCtlType
.Label
)(parent
, id
, text
);
842 with (ctx
.layprops(res
)) {
844 canBeFocused
= false;
846 auto data
= ctx
.item
!FuiCtlLabel(res
);
847 data
.dest
.setz(destid
);
848 if (destid
.length
== 0) {
850 ctx
.layprops(res
).minSize
.w
= visStrLen
!false(data
.caption
.getz
);
851 ctx
.layprops(res
).clickMask
= 0;
853 data
.spaceClicks
= data
.enterClicks
= false;
854 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
855 auto data
= ctx
.item
!FuiCtlLabel(self
);
856 auto lp
= ctx
.layprops(self
);
857 auto win
= XtWindow
.fullscreen
;
860 anorm
= ctx
.palColor
!"def"(self
);
861 ahot
= ctx
.palColor
!"hot"(self
);
863 anorm
= ahot
= ctx
.palColor
!"disabled"(self
);
866 win
.tuiWriteStr
!("right", false)(rc
.x
, rc
.y
, rc
.w
, data
.caption
.getz
, anorm
, ahot
);
868 win
.tuiWriteStr
!("left", false, false)(rc
.x
, rc
.y
, rc
.w
, data
.caption
.getz
, anorm
, ahot
);
871 data
.doclickcb
= delegate (FuiContext ctx
, int self
) {
872 auto data
= ctx
.item
!FuiCtlLabel(self
);
873 auto did
= ctx
[data
.dest
.getz
];
874 if (did
<= 0) return false;
875 if (auto lp
= ctx
.layprops(did
)) {
876 if (lp
.canBeFocused
&& lp
.visible
&& lp
.enabled
) {
887 // ////////////////////////////////////////////////////////////////////////// //
888 struct FuiCtlButton
{
892 int button (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] text
) {
893 auto item
= ctx
.buttonLike
!(FuiCtlButton
, FuiCtlType
.Button
)(parent
, id
, text
);
895 with (ctx
.layprops(item
)) {
896 //clickMask |= FuiLayoutProps.Buttons.Left;
900 auto data
= ctx
.item
!FuiCtlButton(item
);
901 data
.spaceClicks
= data
.enterClicks
= true;
902 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
903 auto data
= ctx
.item
!FuiCtlButton(self
);
904 auto lp
= ctx
.layprops(self
);
905 auto win
= XtWindow
.fullscreen
;
909 if (ctx
.focused
!= self
) {
910 anorm
= ctx
.palColor
!"def"(self
);
911 ahot
= ctx
.palColor
!"hot"(self
);
913 anorm
= ctx
.palColor
!"sel"(self
);
914 ahot
= ctx
.palColor
!"hotsel"(self
);
917 anorm
= ahot
= ctx
.palColor
!"disabled"(self
);
920 bool def
= ((lp
.userFlags
&FuiCtlUserFlags
.Default
) != 0);
922 win
.writeCharsAt
!true(rc
.x
, rc
.y
, 1, '`');
923 } else if (rc
.w
== 2) {
924 win
.writeStrAt(rc
.x
, rc
.y
, (def ?
"<>" : "[]"));
925 } else if (rc
.w
> 2) {
926 win
.writeCharsAt(rc
.x
, rc
.y
, rc
.w
, ' ');
928 win
.writeCharsAt(rc
.x
+1, rc
.y
, 1, '<');
929 win
.writeCharsAt(rc
.x
+rc
.w
-2, rc
.y
, 1, '>');
931 win
.writeCharsAt(rc
.x
, rc
.y
, 1, '[');
932 win
.writeCharsAt(rc
.x
+rc
.w
-1, rc
.y
, 1, ']');
934 win
.tuiWriteStr
!("center", false)(rc
.x
+2, rc
.y
, rc
.w
-4, data
.caption
.getz
, anorm
, ahot
, &hotx
);
936 if (ctx
.focused
== self
) win
.gotoXY(hotx
, rc
.y
);
938 data
.doclickcb
= delegate (FuiContext ctx
, int self
) {
939 // send "close" to root
948 // ////////////////////////////////////////////////////////////////////////// //
949 private void drawCheckRadio(string type
) (FuiContext ctx
, int self
, FuiRect rc
, const(char)[] text
, bool marked
) {
950 static assert(type
== "checkbox" || type
== "radio");
951 auto lp
= ctx
.layprops(self
);
952 auto win
= XtWindow
.fullscreen
;
956 if (ctx
.focused
!= self
) {
957 anorm
= ctx
.palColor
!"def"(self
);
958 ahot
= ctx
.palColor
!"hot"(self
);
960 anorm
= ctx
.palColor
!"sel"(self
);
961 ahot
= ctx
.palColor
!"hotsel"(self
);
964 anorm
= ahot
= ctx
.palColor
!"disabled"(self
);
967 win
.writeCharsAt(rc
.x
, rc
.y
, rc
.w
, ' ');
970 static if (type
== "checkbox") markCh
= 'x';
971 else static if (type
== "radio") markCh
= '*';
972 else static assert(0, "wtf?!");
975 win
.writeCharsAt(rc
.x
, rc
.y
, 1, markCh
);
976 } else if (rc
.w
== 2) {
977 win
.writeCharsAt(rc
.x
, rc
.y
, 1, markCh
);
978 win
.writeCharsAt(rc
.x
+1, rc
.y
, 1, ' ');
979 } else if (rc
.w
> 2) {
980 static if (type
== "checkbox") win
.writeStrAt(rc
.x
, rc
.y
, "[ ]");
981 else static if (type
== "radio") win
.writeStrAt(rc
.x
, rc
.y
, "( )");
982 else static assert(0, "wtf?!");
983 if (markCh
!= ' ') win
.writeCharsAt(rc
.x
+1, rc
.y
, 1, markCh
);
985 win
.tuiWriteStr
!("left", false)(rc
.x
+4, rc
.y
, rc
.w
-4, text
, anorm
, ahot
);
987 if (ctx
.focused
== self
) win
.gotoXY(hotx
, rc
.y
);
996 int checkbox (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] text
, bool* var
) {
997 auto item
= ctx
.buttonLike
!(FuiCtlCheck
, FuiCtlType
.Check
)(parent
, id
, text
);
999 auto data
= ctx
.item
!FuiCtlCheck(item
);
1000 data
.spaceClicks
= true;
1001 data
.enterClicks
= false;
1003 with (ctx
.layprops(item
)) {
1004 //clickMask |= FuiLayoutProps.Buttons.Left;
1005 canBeFocused
= true;
1008 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
1009 auto data
= ctx
.item
!FuiCtlCheck(self
);
1010 bool marked
= (data
.var
!is null ?
*data
.var
: false);
1011 ctx
.drawCheckRadio
!"checkbox"(self
, rc
, data
.caption
.getz
, marked
);
1013 data
.doclickcb
= delegate (FuiContext ctx
, int self
) {
1014 auto data
= ctx
.item
!FuiCtlCheck(self
);
1015 if (data
.var
!is null) *data
.var
= !*data
.var
;
1023 // ////////////////////////////////////////////////////////////////////////// //
1024 struct FuiCtlRadio
{
1025 mixin FuiCtlBtnLike
;
1026 int gid
; // radio group index; <0: standalone
1030 private int countRadio (FuiContext ctx
, int* var
) {
1031 if (!ctx
.valid || var
is null) return -1;
1033 foreach (int fid
; 1..ctx
.length
) {
1034 if (auto data
= ctx
.item
!FuiCtlRadio(fid
)) {
1035 if (data
.type
== FuiCtlType
.Radio
) {
1036 if (data
.var
is var
) ++res
;
1043 int radio (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] text
, int* var
) {
1044 auto item
= ctx
.buttonLike
!(FuiCtlRadio
, FuiCtlType
.Radio
)(parent
, id
, text
);
1046 auto gid
= ctx
.countRadio(var
);
1047 auto data
= ctx
.item
!FuiCtlRadio(item
);
1048 data
.spaceClicks
= true;
1049 data
.enterClicks
= false;
1052 with (ctx
.layprops(item
)) {
1054 //clickMask |= FuiLayoutProps.Buttons.Left;
1055 canBeFocused
= true;
1058 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
1059 auto data
= ctx
.item
!FuiCtlRadio(self
);
1060 bool marked
= (data
.var ?
(*data
.var
== data
.gid
) : false);
1061 ctx
.drawCheckRadio
!"radio"(self
, rc
, data
.caption
.getz
, marked
);
1063 data
.doclickcb
= delegate (FuiContext ctx
, int self
) {
1064 auto data
= ctx
.item
!FuiCtlRadio(self
);
1065 if (data
.var
!is null) *data
.var
= data
.gid
;
1073 // ////////////////////////////////////////////////////////////////////////// //
1074 struct FuiCtlTextView
{
1078 char[0] text
; // this *will* be modified by wrapper; '\1' is "soft wrap"; '\0' is EOT
1082 // width (postition.w) must be already determined
1083 int textviewHeight (FuiContext ctx
, int item
) {
1084 if (auto lp
= ctx
.layprops(item
)) {
1085 int w
= lp
.position
.w
;
1086 if (w
< 1) w
= lp
.minSize
.w
;
1087 if (w
< 1) w
= lp
.maxSize
.w
;
1088 if (w
< 1) return 0;
1089 if (auto data
= ctx
.itemAs
!"textview"(item
)) {
1092 data
.textlen
= calcTextBoundsEx(c
, r
, data
.text
.ptr
[0..data
.textlen
], w
);
1101 // `actcb` will be called *after* editor was changed (or not changed, who knows)
1102 int textview (FuiContext ctx
, int parent
, const(char)[] id
, const(char)[] text
) {
1103 if (!ctx
.valid
) return -1;
1104 if (text
.length
> 256*1024) throw new Exception("text view: text too long");
1105 auto item
= ctx
.addItem
!FuiCtlTextView(parent
, cast(int)text
.length
+256);
1106 with (ctx
.layprops(item
)) {
1108 aligning
= FuiLayoutProps
.Align
.Stretch
;
1109 clickMask |
= FuiLayoutProps
.Buttons
.WheelUp|FuiLayoutProps
.Buttons
.WheelDown
;
1110 //canBeFocused = true;
1112 auto data
= ctx
.item
!FuiCtlTextView(item
);
1113 data
.type
= FuiCtlType
.TextView
;
1114 //data.pal = Palette.init;
1116 data
.textlen
= cast(uint)text
.length
;
1117 if (text
.length
> 0) {
1118 data
.text
.ptr
[0..text
.length
] = text
[];
1120 data
.textlen
= calcTextBoundsEx(c
, r
, data
.text
.ptr
[0..data
.textlen
], ttyw
-8);
1121 //!//with (ctx.layprops(item).maxSize) { w = c; h = r; }
1122 //!//with (ctx.layprops(item).minSize) { w = 2; h = 1; }
1123 //with (ctx.layprops(item).minSize) { w = c+2; h = r; }
1125 //if (minsz > ttyh-ttyh/3) minsz = ttyh-ttyh/3;
1126 with (ctx
.layprops(item
).minSize
) { w
= c
+2; h
= 3; }
1127 with (ctx
.layprops(item
).maxSize
) { w
= c
+2; h
= r
; }
1129 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
1130 if (rc
.w
< 1 || rc
.h
< 1) return;
1131 auto data
= ctx
.item
!FuiCtlTextView(self
);
1132 auto lp
= ctx
.layprops(self
);
1133 //auto win = XtWindow.fullscreen;
1134 auto win
= XtWindow(rc
.x
, rc
.y
, rc
.w
, rc
.h
);
1136 win
.color
= ctx
.palColor
!"def"(self
);
1138 win
.color
= ctx
.palColor
!"disabled"(self
);
1140 //win.fill(rc.x, rc.y, rc.w, rc.h);
1141 win
.fill(0, 0, rc
.w
, rc
.h
);
1144 data
.textlen
= calcTextBoundsEx(c
, r
, data
.text
.ptr
[0..data
.textlen
], rc
.w
/*-lp.padding.left-lp.padding.right*/);
1145 if (data
.topline
< 0) data
.topline
= 0;
1146 if (data
.topline
+rc
.h
>= r
) {
1147 data
.topline
= r
-rc
.h
;
1148 if (data
.topline
< 0) data
.topline
= 0;
1150 bool wantSBar
= (r
> rc
.h
);
1151 int xofs
= (wantSBar ?
2 : 1);
1153 int ty
= -data
.topline
;
1154 int talign
= 0; // 0: left; 1: right; 2: center;
1155 if (data
.textlen
> 0 && data
.text
.ptr
[0] >= 1 && data
.text
.ptr
[0] <= 3) {
1156 talign
= data
.text
.ptr
[tpos
]-1;
1159 while (tpos
< data
.textlen
&& ty
< rc
.h
) {
1161 while (epos
< data
.textlen
&& data
.text
.ptr
[epos
] != '\n' && data
.text
.ptr
[epos
] != '\6') {
1162 if (epos
-tpos
== rc
.w
) break;
1165 final switch (talign
) {
1167 win
.writeStrAt(xofs
, ty
, data
.text
.ptr
[tpos
..epos
]);
1170 win
.writeStrAt(rc
.w
-xofs
-(epos
-tpos
), ty
, data
.text
.ptr
[tpos
..epos
]);
1173 win
.writeStrAt(xofs
+(rc
.w
-xofs
-(epos
-tpos
))/2, ty
, data
.text
.ptr
[tpos
..epos
]);
1176 if (epos
< data
.textlen
&& data
.text
.ptr
[epos
] <= ' ') {
1177 if (data
.textlen
-epos
> 1 && data
.text
.ptr
[epos
] == '\n' && data
.text
.ptr
[epos
+1] >= 1 && data
.text
.ptr
[epos
+1] <= 3) {
1179 talign
= data
.text
.ptr
[epos
]-1;
1191 //win.color = atext;
1192 win
.vline(1, 0, rc
.h
);
1193 int last
= data
.topline
+rc
.h
;
1194 if (last
> r
) last
= r
;
1196 foreach (int yy
; 0..rc
.h
) win
.writeCharsAt
!true(0, yy
, 1, (yy
<= last ?
'a' : ' '));
1200 data
.eventcb
= delegate (FuiContext ctx
, int self
, FuiEvent ev
) {
1201 if (ev
.item
!= self
) return false;
1202 final switch (ev
.type
) {
1203 case FuiEvent
.Type
.None
:
1205 case FuiEvent
.Type
.Char
: // param0: dchar; param1: mods&buttons
1207 case FuiEvent
.Type
.Key
: // param0: sdpy keycode; param1: mods&buttons
1208 auto lp
= ctx
.layprops(self
);
1210 if (!lp
.visible || lp
.disabled
) return false;
1214 if (auto tv
= ctx
.itemAs
!"textview"(self
)) {
1215 if (ev
.key
== "Up") { --tv
.topline
; return true; }
1216 if (ev
.key
== "Down") { ++tv
.topline
; return true; }
1217 if (ev
.key
== "Home") { tv
.topline
= 0; return true; }
1218 if (ev
.key
== "End") { tv
.topline
= int.max
/2; return true; }
1219 int pgstep
= lp
.position
.h
-1;
1220 if (pgstep
< 1) pgstep
= 1;
1221 if (ev
.key
== "PageUp") { tv
.topline
-= pgstep
; return true; }
1222 if (ev
.key
== "PageDown") { tv
.topline
+= pgstep
; return true; }
1225 case FuiEvent
.Type
.Click
: // mouse click; param0: buttton index; param1: mods&buttons
1227 case FuiEvent
.Type
.Double
: // mouse double-click; param0: buttton index; param1: mods&buttons
1229 case FuiEvent
.Type
.Close
: // close dialog; param0: return id
1237 // ////////////////////////////////////////////////////////////////////////// //
1238 struct FuiCtlListBoxItem
{
1244 struct FuiCtlListBox
{
1246 int itemCount
; // total number of items
1250 uint firstItemOffset
; // in layout buffer
1251 uint lastItemOffset
; // in layout buffer
1252 uint topItemOffset
; // in layout buffer
1256 // return `true` if it has scrollbar
1257 bool listboxNormPage (FuiContext ctx
, int item
) {
1258 auto data
= ctx
.item
!FuiCtlListBox(item
);
1259 if (data
is null) return false;
1260 auto lp
= ctx
.layprops(item
);
1261 // make current item visible
1262 if (data
.itemCount
== 0) return false;
1263 // sanitize current item (just in case, it should be sane always)
1264 if (data
.curItem
< 0) data
.curItem
= 0;
1265 if (data
.curItem
>= data
.itemCount
) data
.curItem
= data
.itemCount
-1;
1266 int oldtop
= data
.topItem
;
1267 if (data
.topItem
> data
.itemCount
-lp
.position
.h
) data
.topItem
= data
.itemCount
-lp
.position
.h
;
1268 if (data
.topItem
< 0) data
.topItem
= 0;
1269 if (data
.curItem
< data
.topItem
) {
1270 data
.topItem
= data
.curItem
;
1271 } else if (data
.topItem
+lp
.position
.h
<= data
.curItem
) {
1272 data
.topItem
= data
.curItem
-lp
.position
.h
+1;
1273 if (data
.topItem
< 0) data
.topItem
= 0;
1275 if (data
.topItem
!= oldtop || data
.topItemOffset
== 0) {
1276 data
.topItemOffset
= ctx
.listboxItemOffset(item
, data
.topItem
);
1277 assert(data
.topItemOffset
!= 0);
1279 bool wantSBar
= false;
1280 // should i draw a scrollbar?
1281 if (lp
.position
.w
> 2 && (data
.topItem
> 0 || data
.topItem
+lp
.position
.h
< data
.itemCount
)) {
1289 // `actcb` will be called *after* editor was changed (or not changed, who knows)
1290 int listbox (FuiContext ctx
, int parent
, const(char)[] id
) {
1291 if (!ctx
.valid
) return -1;
1292 auto item
= ctx
.addItem
!FuiCtlListBox(parent
);
1293 if (item
== -1) return -1;
1294 with (ctx
.layprops(item
)) {
1296 aligning
= FuiLayoutProps
.Align
.Stretch
;
1297 clickMask |
= FuiLayoutProps
.Buttons
.Left|FuiLayoutProps
.Buttons
.WheelUp|FuiLayoutProps
.Buttons
.WheelDown
;
1298 canBeFocused
= true;
1299 minSize
= FuiSize(5, 2);
1301 auto data
= ctx
.item
!FuiCtlListBox(item
);
1302 data
.type
= FuiCtlType
.ListBox
;
1303 //data.pal = Palette.init;
1305 data
.drawcb
= delegate (FuiContext ctx
, int self
, FuiRect rc
) {
1306 auto data
= ctx
.item
!FuiCtlListBox(self
);
1307 auto lp
= ctx
.layprops(self
);
1308 auto win
= XtWindow
.fullscreen
;
1310 uint atext
, asel
, agauge
;
1312 atext
= ctx
.palColor
!"def"(self
);
1313 asel
= ctx
.palColor
!"sel"(self
);
1314 agauge
= ctx
.palColor
!"gauge"(self
);
1316 atext
= asel
= agauge
= ctx
.palColor
!"disabled"(self
);
1319 win
.fill(rc
.x
, rc
.y
, rc
.w
, rc
.h
);
1320 // make current item visible
1321 if (data
.itemCount
== 0) return;
1322 // sanitize current item (just in case, it should be sane always)
1323 if (data
.curItem
< 0) data
.curItem
= 0;
1324 if (data
.curItem
>= data
.itemCount
) data
.curItem
= data
.itemCount
-1;
1325 int oldtop
= data
.topItem
;
1326 if (data
.topItem
> data
.itemCount
-rc
.h
) data
.topItem
= data
.itemCount
-rc
.h
;
1327 if (data
.topItem
< 0) data
.topItem
= 0;
1328 if (data
.curItem
< data
.topItem
) {
1329 data
.topItem
= data
.curItem
;
1330 } else if (data
.topItem
+rc
.h
<= data
.curItem
) {
1331 data
.topItem
= data
.curItem
-rc
.h
+1;
1332 if (data
.topItem
< 0) data
.topItem
= 0;
1334 if (data
.topItem
!= oldtop || data
.topItemOffset
== 0) {
1335 data
.topItemOffset
= ctx
.listboxItemOffset(self
, data
.topItem
);
1336 assert(data
.topItemOffset
!= 0);
1338 bool wantSBar
= false;
1341 // should i draw a scrollbar?
1342 if (wdt
> 2 && (data
.topItem
> 0 || data
.topItem
+rc
.h
< data
.itemCount
)) {
1353 auto itofs
= data
.topItemOffset
;
1354 auto curit
= data
.topItem
;
1355 while (itofs
!= 0 && y
< rc
.h
) {
1356 auto it
= ctx
.structAtOfs
!FuiCtlListBoxItem(itofs
);
1357 auto t
= it
.text
.ptr
[0..it
.length
];
1358 if (t
.length
> wdt
) t
= t
[0..wdt
];
1359 if (curit
== data
.curItem
) {
1362 win
.writeCharsAt(x
-(wantSBar ?
0 : 1), rc
.y
+y
, wdt
+(wantSBar ?
0 : 1), ' ');
1363 if (self
== ctx
.focused
) win
.gotoXY(x
-(wantSBar ?
0 : 1), rc
.y
+y
);
1367 win
.writeStrAt(x
, rc
.y
+y
, t
);
1376 win
.vline(x
+1, rc
.y
, rc
.h
);
1377 win
.color
= agauge
; //atext;
1378 int last
= data
.topItem
+rc
.h
;
1379 if (last
> data
.itemCount
) last
= data
.itemCount
;
1380 last
= rc
.h
*last
/data
.itemCount
;
1381 foreach (int yy
; 0..rc
.h
) win
.writeCharsAt
!true(x
, rc
.y
+yy
, 1, (yy
<= last ?
'a' : ' '));
1384 data
.eventcb
= delegate (FuiContext ctx
, int self
, FuiEvent ev
) {
1385 if (ev
.item
!= self
) return false;
1386 final switch (ev
.type
) {
1387 case FuiEvent
.Type
.None
:
1389 case FuiEvent
.Type
.Char
: // param0: dchar; param1: mods&buttons
1391 case FuiEvent
.Type
.Key
: // param0: sdpy keycode; param1: mods&buttons
1392 if (auto lp
= ctx
.layprops(self
)) {
1393 if (!lp
.visible || lp
.disabled
) return false;
1397 if (auto lbox
= ctx
.itemAs
!"listbox"(self
)) {
1399 auto lp
= ctx
.layprops(ev
.item
);
1400 if (ev
.key
== "Up") {
1401 if (--lbox
.curItem
< 0) lbox
.curItem
= 0;
1404 if (ev
.key
== "S-Up") {
1405 if (--lbox
.topItem
< 0) lbox
.topItem
= 0;
1406 lbox
.topItemOffset
= 0; // invalidate
1409 if (ev
.key
== "Down") {
1410 if (lbox
.itemCount
> 0) {
1411 if (++lbox
.curItem
>= lbox
.itemCount
) lbox
.curItem
= lbox
.itemCount
-1;
1415 if (ev
.key
== "S-Down") {
1416 if (lbox
.topItem
+lp
.position
.h
< lbox
.itemCount
) {
1418 lbox
.topItemOffset
= 0; // invalidate
1422 if (ev
.key
== "Home") {
1426 if (ev
.key
== "End") {
1427 if (lbox
.itemCount
> 0) lbox
.curItem
= lbox
.itemCount
-1;
1430 if (ev
.key
== "PageUp") {
1431 if (lbox
.curItem
> lbox
.topItem
) {
1432 lbox
.curItem
= lbox
.topItem
;
1433 } else if (lp
.position
.h
> 1) {
1434 if ((lbox
.curItem
-= lp
.position
.h
-1) < 0) lbox
.curItem
= 0;
1438 if (ev
.key
== "PageDown") {
1439 if (lbox
.curItem
< lbox
.topItem
+lp
.position
.h
-1) {
1440 lbox
.curItem
= lbox
.topItem
+lp
.position
.h
-1;
1441 } else if (lp
.position
.h
> 1 && lbox
.itemCount
> 0) {
1442 if ((lbox
.curItem
+= lp
.position
.h
-1) >= lbox
.itemCount
) lbox
.curItem
= lbox
.itemCount
-1;
1448 ctx
.listboxNormPage(self
);
1449 auto oldCI
= lbox
.curItem
;
1451 ctx
.listboxNormPage(self
);
1452 if (oldCI
!= lbox
.curItem
&& lbox
.actcb
!is null) {
1453 auto rr
= lbox
.actcb(ctx
, self
);
1454 if (rr
>= -1) ctx
.postClose(rr
);
1460 case FuiEvent
.Type
.Click
: // mouse click; param0: buttton index; param1: mods&buttons
1461 if (auto lbox
= ctx
.itemAs
!"listbox"(self
)) {
1462 ctx
.listboxNormPage(self
);
1463 auto oldCI
= lbox
.curItem
;
1464 if (ev
.bidx
== FuiLayoutProps
.Button
.WheelUp
) {
1465 if (--lbox
.curItem
< 0) lbox
.curItem
= 0;
1466 } else if (ev
.bidx
== FuiLayoutProps
.Button
.WheelDown
) {
1467 if (lbox
.itemCount
> 0) {
1468 if (++lbox
.curItem
>= lbox
.itemCount
) lbox
.curItem
= lbox
.itemCount
-1;
1470 } else if (ev
.x
> 0) {
1471 int it
= lbox
.topItem
+ev
.y
;
1474 ctx
.listboxNormPage(self
);
1475 if (oldCI
!= lbox
.curItem
&& lbox
.actcb
!is null) {
1476 auto rr
= lbox
.actcb(ctx
, self
);
1477 if (rr
>= -1) ctx
.postClose(rr
);
1481 case FuiEvent
.Type
.Double
: // mouse double-click; param0: buttton index; param1: mods&buttons
1483 case FuiEvent
.Type
.Close
: // close dialog; param0: return id
1491 void listboxItemAdd (FuiContext ctx
, int item
, const(char)[] text
) {
1492 if (auto data
= ctx
.itemAs
!"listbox"(item
)) {
1493 int len
= (text
.length
< 255 ?
cast(int)text
.length
: 255);
1495 auto it
= ctx
.addStruct
!FuiCtlListBoxItem(itofs
, len
);
1497 if (text
.length
<= 255) {
1498 it
.text
.ptr
[0..text
.length
] = text
[];
1500 it
.text
.ptr
[0..256] = text
[0..256];
1501 it
.text
.ptr
[253..256] = '.';
1503 if (data
.maxWidth
< len
) data
.maxWidth
= len
;
1504 if (data
.firstItemOffset
== 0) {
1505 data
.firstItemOffset
= itofs
;
1506 data
.topItemOffset
= itofs
;
1508 auto prev
= ctx
.structAtOfs
!FuiCtlListBoxItem(data
.lastItemOffset
);
1509 prev
.nextofs
= itofs
;
1511 data
.lastItemOffset
= itofs
;
1513 auto lp
= ctx
.layprops(item
);
1514 if (lp
.parent
>= 0) {
1515 auto pp
= ctx
.layprops(lp
.parent
);
1516 assert(pp
!is null);
1517 int maxW
= pp
.maxSize
.w
-(lp
.parent
== 0 ?
2 : 0);
1518 if (lp
.minSize
.w
< len
+3) lp
.minSize
.w
= len
+3;
1519 if (maxW
> 0 && lp
.minSize
.w
> maxW
) lp
.minSize
.w
= maxW
;
1520 int maxH
= pp
.maxSize
.h
-(lp
.parent
== 0 ?
2 : 0);
1521 if (lp
.minSize
.h
< data
.itemCount
) lp
.minSize
.h
= data
.itemCount
;
1522 if (maxH
> 0 && lp
.minSize
.h
> maxH
) lp
.minSize
.h
= maxH
;
1523 if (lp
.minSize
.h
> lp
.maxSize
.h
) lp
.minSize
.h
= lp
.maxSize
.h
;
1525 if (lp
.minSize
.w
< len
+3) lp
.minSize
.w
= len
+3;
1526 if (lp
.minSize
.w
> lp
.maxSize
.w
) lp
.minSize
.w
= lp
.maxSize
.w
;
1527 if (lp
.minSize
.h
< data
.itemCount
) lp
.minSize
.h
= data
.itemCount
;
1528 if (lp
.minSize
.h
> lp
.maxSize
.h
) lp
.minSize
.h
= lp
.maxSize
.h
;
1534 int listboxMaxItemWidth (FuiContext ctx
, int item
) {
1535 if (auto data
= ctx
.itemAs
!"listbox"(item
)) return data
.maxWidth
+2;
1540 int listboxItemCount (FuiContext ctx
, int item
) {
1541 if (auto data
= ctx
.itemAs
!"listbox"(item
)) return data
.itemCount
;
1546 int listboxItemCurrent (FuiContext ctx
, int item
) {
1547 if (auto data
= ctx
.itemAs
!"listbox"(item
)) return data
.curItem
;
1552 void listboxItemSetCurrent (FuiContext ctx
, int item
, int cur
) {
1553 if (auto data
= ctx
.itemAs
!"listbox"(item
)) {
1554 if (data
.itemCount
> 0) {
1555 if (cur
< 0) cur
= 0;
1556 if (cur
> data
.itemCount
) cur
= data
.itemCount
-1;
1563 private uint listboxItemOffset (FuiContext ctx
, int item
, int inum
) {
1564 if (auto data
= ctx
.itemAs
!"listbox"(item
)) {
1565 if (inum
> data
.itemCount
) inum
= data
.itemCount
-1;
1566 if (inum
< 0) inum
= 0;
1567 uint itofs
= data
.firstItemOffset
;
1568 while (inum
-- > 0) {
1570 auto it
= ctx
.structAtOfs
!FuiCtlListBoxItem(itofs
);
1571 if (it
.nextofs
== 0) break;
1580 // ////////////////////////////////////////////////////////////////////////// //
1581 // `id` is element id
1582 public class FuiHistoryManager
{
1585 abstract bool has (const(char)[] id
);
1586 abstract int count (const(char)[] id
);
1587 abstract const(char)[] item (const(char)[] id
, int idx
); // 0: oldest
1588 abstract void add (const(char)[] id
, const(char)[] value
); // this can shrink history; should correctly process duplicates
1589 abstract void clear (const(char)[] id
);
1590 abstract void activate (const(char)[] id
, int idx
); // usually moves item to bottom
1594 // ////////////////////////////////////////////////////////////////////////// //
1595 // returned value valid until first layout change
1596 const(char)[] itemId (FuiContext ctx
, int item
) nothrow @trusted @nogc {
1597 if (auto lp
= ctx
.item
!FuiCtlHead(item
)) return lp
.id
.getz
;
1602 // ////////////////////////////////////////////////////////////////////////// //
1603 // return item id or -1
1604 int findById (FuiContext ctx
, const(char)[] id
) nothrow @trusted @nogc {
1605 foreach (int fid
; 0..ctx
.length
) {
1606 if (auto data
= ctx
.item
!FuiCtlHead(fid
)) {
1607 if (data
.id
.getz
== id
) return fid
;
1614 auto itemAs(string type
) (FuiContext ctx
, int item
) nothrow @trusted @nogc {
1615 if (!ctx
.valid
) return null;
1616 static if (type
.strEquCI("hline")) {
1617 enum ctp
= FuiCtlType
.HLine
;
1618 alias tp
= FuiCtlSpan
;
1619 } else static if (type
.strEquCI("box") || type
.strEquCI("vbox") || type
.strEquCI("hbox")) {
1620 enum ctp
= FuiCtlType
.Box
;
1621 alias tp
= FuiCtlBox
;
1622 } else static if (type
.strEquCI("panel") || type
.strEquCI("vpanel") || type
.strEquCI("hpanel")) {
1623 enum ctp
= FuiCtlType
.Panel
;
1624 alias tp
= FuiCtlPanel
;
1625 } else static if (type
.strEquCI("editline")) {
1626 enum ctp
= FuiCtlType
.EditLine
;
1627 alias tp
= FuiCtlEditLine
;
1628 } else static if (type
.strEquCI("edittext")) {
1629 enum ctp
= FuiCtlType
.EditText
;
1630 alias tp
= FuiCtlEditLine
;
1631 } else static if (type
.strEquCI("label")) {
1632 enum ctp
= FuiCtlType
.Label
;
1633 alias tp
= FuiCtlLabel
;
1634 } else static if (type
.strEquCI("button")) {
1635 enum ctp
= FuiCtlType
.Button
;
1636 alias tp
= FuiCtlButton
;
1637 } else static if (type
.strEquCI("check") || type
.strEquCI("checkbox")) {
1638 enum ctp
= FuiCtlType
.Check
;
1639 alias tp
= FuiCtlCheck
;
1640 } else static if (type
.strEquCI("radio") || type
.strEquCI("radio")) {
1641 enum ctp
= FuiCtlType
.Radio
;
1642 alias tp
= FuiCtlRadio
;
1643 } else static if (type
.strEquCI("textview")) {
1644 enum ctp
= FuiCtlType
.TextView
;
1645 alias tp
= FuiCtlTextView
;
1646 } else static if (type
.strEquCI("listbox")) {
1647 enum ctp
= FuiCtlType
.ListBox
;
1648 alias tp
= FuiCtlListBox
;
1649 } else static if (type
.strEquCI("custombox")) {
1650 enum ctp
= FuiCtlType
.CustomBox
;
1651 alias tp
= FuiCtlCustomBox
;
1653 static assert(0, "invalid control type: '"~type
~"'");
1655 if (auto data
= ctx
.item
!FuiCtlHead(item
)) {
1656 if (data
.type
== ctp
) return ctx
.item
!tp(item
);
1662 auto itemAs(string type
) (FuiContext ctx
, const(char)[] id
) nothrow @trusted @nogc {
1663 if (!ctx
.valid
) return null;
1664 foreach (int fid
; 0..ctx
.length
) {
1665 if (auto data
= ctx
.itemAs
!type(fid
)) {
1666 if (data
.id
.getz
== id
) return data
;
1673 auto firstItemOfType(string type
) (FuiContext ctx
) nothrow @trusted @nogc {
1674 if (!ctx
.valid
) return null;
1675 foreach (int fid
; 0..ctx
.length
) {
1676 if (auto it
= ctx
.itemAs
!type(fid
)) return it
;
1682 FuiCtlType
itemType (FuiContext ctx
, int item
) nothrow @trusted @nogc {
1683 if (!ctx
.valid
) return FuiCtlType
.Invisible
;
1684 if (auto data
= ctx
.itemIntr
!FuiCtlHead(item
)) return data
.type
;
1685 return FuiCtlType
.Invisible
;
1689 // ////////////////////////////////////////////////////////////////////////// //
1690 void focusFirst (FuiContext ctx
) nothrow @trusted @nogc {
1691 if (!ctx
.valid
) return;
1692 auto fid
= ctx
.focused
;
1693 if (fid
< 1 || fid
>= ctx
.length
) fid
= 0;
1694 auto lp
= ctx
.layprops(fid
);
1695 if (fid
< 1 || fid
>= ctx
.length ||
!lp
.visible ||
!lp
.enabled ||
!lp
.canBeFocused
) {
1696 for (fid
= 1; fid
< ctx
.length
; ++fid
) {
1697 lp
= ctx
.layprops(fid
);
1698 if (lp
is null) continue;
1699 if (lp
.visible
&& lp
.enabled
&& lp
.canBeFocused
) {
1708 int findDefault (FuiContext ctx
) nothrow @trusted @nogc {
1709 if (!ctx
.valid
) return -1;
1710 foreach (int fid
; 0..ctx
.length
) {
1711 if (auto lp
= ctx
.layprops(fid
)) {
1712 if (lp
.visible
&& lp
.enabled
&& lp
.canBeFocused
&& (lp
.userFlags
&FuiCtlUserFlags
.Default
) != 0) return fid
;
1719 // ////////////////////////////////////////////////////////////////////////// //
1720 void dialogPalette (FuiContext ctx
, int palidx
) {
1721 if (palidx
< 0 || palidx
>= tuiPalette
.length
) palidx
= 0;
1722 if (auto data
= ctx
.itemIntr
!FuiCtlHead(0)) {
1723 data
.pal
= tuiPalette
[palidx
];
1728 void palColor(string name
) (FuiContext ctx
, int itemid
, uint clr
) {
1729 if (auto data
= ctx
.item
!FuiCtlHead(itemid
)) {
1730 mixin("data.pal."~name
~" = clr;");
1735 // ////////////////////////////////////////////////////////////////////////// //
1736 uint palColor(string name
) (FuiContext ctx
, int itemid
) {
1739 if (auto data
= ctx
.itemIntr
!FuiCtlHead(itemid
)) {
1740 res
= mixin("data.pal."~name
);
1742 itemid
= ctx
.layprops(itemid
).parent
;
1744 res
= mixin("tuiPalette[TuiPaletteNormal]."~name
);
1748 return (res ? res
: XtColorFB
!(ttyRgb2Color(0xd0, 0xd0, 0xd0), ttyRgb2Color(0x4e, 0x4e, 0x4e))); // 252,239
1752 // ////////////////////////////////////////////////////////////////////////// //
1753 private void tuiWriteStr(string defcenter
, bool spaces
, bool dohot
=true) (auto ref XtWindow win
, int x
, int y
, int w
, const(char)[] s
, uint attr
, uint hotattr
, int* hotx
=null) {
1754 static assert(defcenter
== "left" || defcenter
== "right" || defcenter
== "center");
1755 if (hotx
!is null) *hotx
= x
;
1758 static if (spaces
) { x
+= 1; w
-= 2; }
1759 if (w
< 1 || s
.length
== 0) return;
1760 int sx
= x
, ex
= x
+w
-1;
1761 auto vislen
= visStrLen
!dohot(s
);
1762 if (s
.ptr
[0] == '\x01') {
1763 // left, nothing to do
1765 } else if (s
.ptr
[0] == '\x02') {
1769 } else if (s
.ptr
[0] == '\x03') {
1771 auto len
= visStrLen(s
);
1775 static if (defcenter
== "left") {
1776 } else static if (defcenter
== "right") {
1778 } else static if (defcenter
== "center") {
1784 static if (spaces
) {
1785 win
.writeCharsAt(x
-1, y
, 1, ' ');
1787 if (ee
<= ex
+1) win
.writeCharsAt(ee
, y
, 1, ' ');
1789 while (s
.length
> 0) {
1790 if (dohot
&& s
.length
> 1 && s
.ptr
[0] == '&' && s
.ptr
[1] != '&') {
1791 if (!wasHot
&& hotx
!is null) *hotx
= x
;
1792 if (x
>= sx
&& x
<= ex
) {
1793 if (hotattr
&& !wasHot
) win
.color
= hotattr
;
1794 win
.writeCharsAt(x
, y
, 1, s
.ptr
[1]);
1799 } else if (dohot
&& s
.length
> 1 && s
.ptr
[0] == '&' && s
.ptr
[1] != '&') {
1800 if (x
>= sx
&& x
<= ex
) win
.writeCharsAt(x
, y
, 1, s
.ptr
[0]);
1803 if (x
>= sx
&& x
<= ex
) win
.writeCharsAt(x
, y
, 1, s
.ptr
[0]);
1811 // ////////////////////////////////////////////////////////////////////////// //
1812 // we have to draw shadow separately, so it won't get darken each time
1813 void drawShadow (FuiContext ctx
) nothrow @trusted @nogc {
1814 if (!ctx
.valid
) return;
1815 auto lp
= ctx
.layprops(0);
1816 if (lp
is null) return;
1817 auto rc
= lp
.position
;
1818 //xtShadowBox(rc.x+rc.w, rc.y+1, 2, rc.h-1);
1819 //xtHShadow(rc.x+2, rc.y+rc.h, rc.w);
1820 auto win
= XtWindow(rc
.x
, rc
.y
, rc
.w
+2, rc
.h
+1);
1821 win
.shadowBox(rc
.w
, 1, 2, rc
.h
-1);
1822 win
.hshadow(2, rc
.h
, rc
.w
);
1826 void draw (FuiContext ctx
) {
1827 if (!ctx
.valid
) return;
1829 void drawItem (int item
, FuiPoint g
) {
1830 auto lp
= ctx
.layprops(item
);
1831 if (lp
is null) return;
1832 if (!lp
.visible
) return;
1834 // convert local coords to global coords
1835 auto rc
= lp
.position
;
1836 // don't shift root panes, it is already shifted
1842 void drawRootFrame (bool secondpass
=false) {
1843 auto dlgdata
= ctx
.itemIntr
!FuiCtlRootPanel(0);
1844 auto anorm
= ctx
.palColor
!"def"(item
);
1845 auto atitle
= ctx
.palColor
!"title"(item
);
1846 auto win
= XtWindow
.fullscreen
;
1847 win
.color
= (!dlgdata
.moving ? anorm
: atitle
);
1848 if (rc
.w
> 0 && rc
.h
> 0) {
1849 auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0);
1850 if (!secondpass
) win
.fill(rc
.x
, rc
.y
, rc
.w
, rc
.h
);
1851 final switch (data
.frame
) {
1852 case FuiDialogFrameType
.Normal
:
1853 win
.frame
!false(rc
.x
+1, rc
.y
+1, rc
.w
-2, rc
.h
-2);
1854 win
.tuiWriteStr
!("center", true)(rc
.x
+1, rc
.y
+1, rc
.w
-2, data
.caption
.getz
, atitle
, atitle
);
1856 case FuiDialogFrameType
.Small
:
1857 win
.frame
!false(rc
.x
, rc
.y
, rc
.w
, rc
.h
);
1858 win
.tuiWriteStr
!("center", true)(rc
.x
+1, rc
.y
, rc
.w
-2, data
.caption
.getz
, atitle
, atitle
);
1864 bool root
= (item
== 0);
1865 if (item
== 0) drawRootFrame();
1867 auto head
= ctx
.itemIntr
!FuiCtlHead(item
);
1868 if (head
.drawcb
!is null) head
.drawcb(ctx
, item
, rc
);
1872 item
= lp
.firstChild
;
1873 lp
= ctx
.layprops(item
);
1875 while (lp
!is null) {
1876 drawItem(item
, rc
.pos
);
1877 item
= lp
.nextSibling
;
1878 lp
= ctx
.layprops(item
);
1883 auto dlgdata
= ctx
.itemIntr
!FuiCtlRootPanel(0);
1884 if (dlgdata
.moving
) drawRootFrame(true);
1888 drawItem(0, ctx
.layprops(0).position
.pos
);
1892 // ////////////////////////////////////////////////////////////////////////// //
1893 // returns `true` if event was consumed
1894 bool processEvent (FuiContext ctx
, FuiEvent ev
) {
1895 if (!ctx
.valid
) return false;
1897 if (auto rd
= ctx
.itemIntr
!FuiCtlHead(0)) {
1898 if (rd
.eventcb
!is null) {
1899 if (rd
.eventcb(ctx
, ev
.item
, ev
)) return true;
1904 if (auto lp
= ctx
.layprops(ev
.item
)) {
1905 if (lp
.visible
&& !lp
.disabled
) {
1906 auto data
= ctx
.itemIntr
!FuiCtlHead(ev
.item
);
1907 assert(data
!is null);
1908 if (data
.eventcb
!is null) {
1909 //{ import iv.vfs.io; VFile("z00.log", "a").writeln("ev.item=", ev.item); }
1910 if (data
.eventcb(ctx
, ev
.item
, ev
)) return true;
1916 // event is not processed
1917 if (ev
.type
== FuiEvent
.Type
.Char
) {
1918 // broadcast char as ModChar
1920 if (ch
> ' ' && ch
< 256) ch
= toupper(cast(char)ch
);
1921 ev
.type
= FuiEvent
.Type
.Key
;
1922 ev
.keyp
= TtyEvent
.init
;
1923 ev
.keyp
.key
= TtyEvent
.Key
.ModChar
;
1924 ev
.keyp
.ctrl
= false;
1926 ev
.keyp
.shift
= false;
1928 //assert(ev.type == FuiEvent.Type.Key);
1930 if (ev
.type
!= FuiEvent
.Type
.Key
) return false;
1933 if (ev
.key
== "Enter") {
1934 // either current or default
1935 auto def
= ctx
.findDefault
;
1936 if (def
>= 0) return ctx
.btnlikeClick(def
);
1937 if (auto rd
= ctx
.itemIntr
!FuiCtlRootPanel(0)) {
1938 if (rd
.enterclose
) {
1939 // send "close" to root with root as result
1946 if (ev
.key
== "Up" || ev
.key
== "Left") { ctx
.focusPrev(); return true; }
1947 if (ev
.key
== "Down" || ev
.key
== "Right") { ctx
.focusNext(); return true; }
1948 // broadcast ModChar, so widgets can process hotkeys
1949 if (ev
.key
.key
== TtyEvent
.Key
.ModChar
&& !ev
.key
.ctrl
&& ev
.key
.alt
&& !ev
.key
.shift
) {
1950 auto res
= ctx
.findNextEx(0, (int id
) {
1951 if (auto lp
= ctx
.layprops(id
)) {
1952 if (lp
.visible
&& !lp
.disabled
) {
1953 auto data
= ctx
.item
!FuiCtlHead(id
);
1954 if (data
.eventcb
!is null) {
1955 if (data
.eventcb(ctx
, id
, ev
)) return true;
1961 if (res
>= 0) return true;
1967 // ////////////////////////////////////////////////////////////////////////// //
1968 private __gshared
bool windowMovingMouse
, windowMovingKeys
;
1969 private __gshared
int wmX
, wmY
;
1970 private __gshared
bool screenSaved
;
1971 public __gshared
int modalLastResult
= -1;
1973 private __gshared FuiContext
[] modalStack
;
1975 @property bool modalHasOpenDialogs () nothrow @trusted @nogc { return (modalStack
.length
> 0); }
1978 // ////////////////////////////////////////////////////////////////////////// //
1979 void modalDialogRestoreScreen () {
1981 screenSaved
= false;
1987 // this will restore old screen before saving it again
1988 void modalDialogSaveScreen () {
1989 modalDialogRestoreScreen();
1991 if (modalStack.length == 0) return;
1992 if (!ctx.valid) return false;
1993 auto lp = ctx.layprops(0);
1994 if (lp is null) return false;
1996 lp.position.x, lp.position.y,
1997 lp.position.w+2, lp.position.h+1
2001 xtPushArea(0, 0, ttyw
, ttyh
);
2005 // redraw all dialogs
2006 void modalDialogDraw () {
2007 modalDialogSaveScreen();
2008 foreach (immutable idx
, FuiContext ctx
; modalStack
) {
2009 if (!ctx
.valid
) continue;
2011 if (idx
== 0 && (windowMovingMouse || windowMovingKeys
)) ctx
.dialogMoving
= true;
2012 scope(exit
) if (idx
== 0 && (windowMovingMouse || windowMovingKeys
)) ctx
.dialogMoving
= false;
2018 // modalLastResult should be set
2019 private void closeTopDialog () {
2020 modalDialogRestoreScreen();
2021 if (modalStack
.length
== 0) return;
2022 auto ctx
= modalStack
[$-1];
2023 windowMovingMouse
= false;
2024 windowMovingKeys
= false;
2025 modalStack
[$-1] = FuiContext
.init
;
2026 modalStack
.length
-= 1;
2027 modalStack
.assumeSafeAppend
;
2028 if (auto data
= ctx
.itemIntr
!FuiCtlRootPanel(0)) {
2029 if (data
.closecb
!is null) data
.closecb(ctx
, modalLastResult
);
2031 { import core
.memory
: GC
; GC
.collect
; GC
.minimize
; }
2035 // returns `true` if dialog was closed, and then `modalLastResult` will contain result
2036 // return FuiContinue, clicked item index or -1 for esc
2037 int modalDialogProcessKey (TtyEvent key
, bool* closed
=null) {
2040 if (closed
!is null) *closed
= false;
2042 while (modalStack
.length
> 0) {
2043 if (modalStack
[$-1].valid
) break;
2044 modalStack
.length
-= 1;
2045 modalStack
.assumeSafeAppend
;
2048 if (modalStack
.length
== 0) {
2049 { import core
.memory
: GC
; GC
.collect
; GC
.minimize
; }
2050 windowMovingMouse
= false;
2051 windowMovingKeys
= false;
2052 modalLastResult
= -1;
2053 if (closed
!is null) *closed
= true;
2057 ctx
= modalStack
[$-1];
2059 int processContextEvents () {
2061 while (ctx
.hasEvents
) {
2062 auto ev
= ctx
.getEvent();
2063 if (ev
.type
== FuiEvent
.Type
.Close
) return ev
.result
;
2064 if (ctx
.processEvent(ev
)) continue;
2065 if (ev
.type
== FuiEvent
.Type
.Key
&& (ev
.key
== "^F3" || ev
.key
== "S-F3")) windowMovingKeys
= true;
2070 int res
= processContextEvents();
2072 if (closed
!is null) *closed
= true;
2073 modalLastResult
= res
;
2075 return modalLastResult
;
2078 //auto key = ttyReadKey(-1, TtyDefaultEscWait);
2079 if (key
.key
== TtyEvent
.Key
.Error
) return FuiContinue
;
2080 if (key
.key
== TtyEvent
.Key
.Unknown
) return FuiContinue
;
2081 if (!windowMovingKeys
&& key
.key
== TtyEvent
.Key
.Escape
) {
2082 if (closed
!is null) *closed
= true;
2083 modalLastResult
= -1;
2085 return modalLastResult
;
2088 //if (key == "^L") { xtFullRefresh(); continue; }
2092 // move dialog with keys
2093 if (key
== "M-Left") dx
= -1;
2094 if (key
== "M-Right") dx
= 1;
2095 if (key
== "M-Up") dy
= -1;
2096 if (key
== "M-Down") dy
= 1;
2098 if (windowMovingKeys
) {
2099 if (key
== "Left") dx
= -1;
2100 if (key
== "Right") dx
= 1;
2101 if (key
== "Up") dy
= -1;
2102 if (key
== "Down") dy
= 1;
2105 // move dialog with mouse
2106 if (windowMovingMouse
&& key
.mouse
) {
2107 if (key
.mrelease
&& key
.button
== TtyEvent
.MButton
.Left
) {
2108 windowMovingMouse
= false;
2119 ctx
.layprops(0).position
.x
= ctx
.layprops(0).position
.x
+dx
;
2120 ctx
.layprops(0).position
.y
= ctx
.layprops(0).position
.y
+dy
;
2126 if (windowMovingKeys
) {
2127 if (key
== "Escape") windowMovingKeys
= false;
2131 if (windowMovingMouse
) return FuiContinue
;
2134 //TODO: check for no frame when we'll get that
2135 if (key
.mpress
&& key
.button
== TtyEvent
.MButton
.Left
&& key
.x
>= ctx
.layprops(0).position
.x
&& key
.x
< ctx
.layprops(0).position
.x
+ctx
.layprops(0).position
.w
) {
2136 if ((key
.y
== ctx
.layprops(0).position
.y
) ||
(ctx
.dialogFrame
== FuiDialogFrameType
.Normal
&& key
.y
== ctx
.layprops(0).position
.y
+1)) {
2137 windowMovingMouse
= true;
2145 ctx
.keyboardEvent(key
);
2147 res
= processContextEvents();
2149 if (closed
!is null) *closed
= true;
2150 modalLastResult
= res
;
2152 return modalLastResult
;
2159 // initialize and push dialog
2160 // return `false` if dialog was not initialized and pushed
2161 bool modalDialogInit(bool docenter
=true) (FuiContext ctx
) {
2162 if (!ctx
.valid
) return false;
2164 static if (docenter
) {
2165 if (auto lp
= ctx
.layprops(0)) {
2166 if (lp
.position
.x
== 0) lp
.position
.x
= (ttyw
-lp
.position
.w
)/2;
2167 if (lp
.position
.y
== 0) lp
.position
.y
= (ttyh
-lp
.position
.h
)/2;
2176 // close top-level dialog
2177 // return `false` if there are no open dialogs
2178 bool modalCloseDialog () {
2179 if (modalStack
.length
) {
2180 modalLastResult
= -1;
2188 // ////////////////////////////////////////////////////////////////////////// //
2189 // returns clicked item or -1 for esc
2190 int modalDialog(bool docenter
=true) (FuiContext ctx
) {
2191 if (!ctx
.modalDialogInit
!docenter()) return -1;
2192 scope(exit
) modalDialogRestoreScreen();
2198 auto key
= ttyReadKey(-1, TtyDefaultEscWait
);
2199 if (key
== "^L") { modalDialogRestoreScreen(); xtFullRefresh(); break; }
2200 if (key
.key
== TtyEvent
.Key
.Error
) { modalCloseDialog(); return modalLastResult
; }
2201 modalDialogProcessKey(key
, &closed
);
2202 if (closed
) return modalLastResult
;
2203 } while (ttyIsKeyHit
);