egra: better, faster, and more flexible vertical gradients in agg mini
[iv.d.git] / tuing / xmain.d
blob2fdf04317dafaafc4b625e2812af568bc4ee088d
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 xmain /*is aliced*/;
21 import iv.tuing;
22 import iv.vfs.io;
25 // ////////////////////////////////////////////////////////////////////////// //
26 __gshared HistoryManager hisman;
29 // ////////////////////////////////////////////////////////////////////////// //
30 private class FuiHistoryWindow : FuiWindow {
31 alias onMyEvent = super.onMyEvent;
32 alias onBubbleEvent = super.onBubbleEvent;
34 FuiEditLine el;
35 FuiListBox hlb;
37 this (FuiEditLine ael) {
38 assert(ael !is null);
39 this.connectListeners();
40 super();
41 el = ael;
42 caption = "History";
43 frame = Frame.Small;
44 minSize.w = 14;
45 hlb = new FuiListBox(this);
46 hlb.aligning = hlb.Align.Stretch;
47 foreach_reverse (immutable idx; 0..hisman.count(el)) hlb.addItem(hisman.item(el, idx));
48 if (hlb.count > 1) hlb.curitem = 1;
49 hlb.defctl = true;
50 hlb.escctl = true;
51 hlb.maxSize.w = ttyw-8;
52 hlb.maxSize.h = ttyh-8;
53 /*this works
54 addEventListener(this, (FuiEventClose evt) {
55 ttyBeep;
56 });
60 override void onBubbleEvent (FuiEventKey evt) {
61 if (evt.key == "Enter" && hlb !is null) {
62 auto it = hlb.curitem;
63 if (it >= 0 && it < hisman.count(el)) {
64 (new FuiEventHistoryReply(el, hlb[it])).post;
65 //it = hisman.count(el)-it-1;
66 hisman.activate(el, it);
69 super.onBubbleEvent(evt);
72 static void create (FuiEditLine el) {
73 if (el is null) return;
74 auto desk = el.getDesk;
75 if (desk is null) return;
76 if (hisman is null) return;
77 if (!hisman.has(el) || hisman.count(el) < 1) return;
78 auto win = new FuiHistoryWindow(el);
79 fuiLayout(win);
80 win.positionUnderControl(el);
81 tuidesk.addPopup(win);
86 // ////////////////////////////////////////////////////////////////////////// //
87 public class HistoryManager {
88 public:
89 enum MaxHistory = 128;
91 string[][string] history;
93 public:
94 this () { this.connectListeners(); }
96 void onEvent (FuiEventHistoryQuery evt) {
97 if (auto el = cast(FuiEditLine)evt.sourcectl) {
98 evt.eat();
99 FuiHistoryWindow.create(el);
103 bool has (FuiControl ctl) {
104 //if (ctl.id.length == 0) return;
105 //VFile("/home/ketmar/back/D/prj/edgap/zzz", "a").writeln("check: '", id, "'");
106 return ((ctl.id in history) !is null);
109 int count (FuiControl ctl) {
110 if (auto listp = ctl.id in history) return listp.length;
111 return 0;
114 // 0: oldest
115 const(char)[] item (FuiControl ctl, int idx) {
116 if (idx < 0) return null;
117 if (auto listp = ctl.id in history) {
118 return (idx < listp.length ? (*listp)[idx] : null);
120 return null;
123 // this can shrink history; should correctly process duplicates
124 void add (FuiControl ctl, const(char)[] value) {
125 if (value.length == 0 || ctl.id.length == 0) return;
126 if (auto listp = ctl.id in history) {
127 // check for existing item
128 foreach (immutable idx; 0..listp.length) {
129 if ((*listp)[idx] == value) {
130 // move to bottom
131 activate(ctl, cast(int)idx);
132 return;
135 if (listp.length > MaxHistory) (*listp).length = MaxHistory;
136 if (listp.length == MaxHistory) {
137 // remove oldest item
138 foreach (immutable c; 1..listp.length) (*listp)[c-1] = (*listp)[c];
139 (*listp)[$-1] = value.idup;
140 } else {
141 (*listp) ~= value.idup;
143 } else {
144 string[] list;
145 list ~= value.idup;
146 history[ctl.id] = list;
150 void clear (FuiControl ctl) {
151 if (auto listp = ctl.id in history) {
152 if (listp.length) {
153 (*listp).length = 0;
154 (*listp).assumeSafeAppend;
159 // usually moves item to bottom
160 void activate (FuiControl ctl, int idx) {
161 if (idx < 0) return;
162 if (auto listp = ctl.id in history) {
163 if (listp.length > 1 && idx < listp.length-1) {
164 // move item to bottom
165 auto s = (*listp)[0];
166 foreach (immutable c; 1..listp.length) (*listp)[c-1] = (*listp)[c];
167 (*listp)[$-1] = s;
174 // ////////////////////////////////////////////////////////////////////////// //
175 public class FuiTextLine : FuiControl {
176 alias onMyEvent = super.onMyEvent;
178 this (FuiControl aparent, string atext) {
179 this.connectListeners();
180 FuiControl ctl = (aparent !is null ? aparent.lastChild : null);
181 if (ctl !is null) ctl.lineBreak = true;
182 super(aparent);
183 caption = atext;
184 lp.minSize.w = cast(int)atext.length;
185 lp.orientation = lp.Orientation.Horizontal;
186 lp.aligning = lp.Align.Stretch;
187 lp.minSize.h = lp.maxSize.h = 1;
188 lp.lineBreak = true;
191 protected override void drawSelf (XtWindow win) {
192 win.color = palColor!"def"();
193 win.fill(0, 0, win.width, win.height);
194 win.writeStrAt(0, 0, caption);
199 // ////////////////////////////////////////////////////////////////////////// //
200 FuiWindow createWin (bool closeOnBlur=false) {
201 import std.format : format;
202 if (hisman is null) hisman = new HistoryManager();
203 __gshared int counter;
204 auto win = new FuiWindow();
205 //win.minSize = FuiSize(30, 7);
206 win.caption = "Test Window %s".format(counter++);
207 new FuiTextLine(win, "hello, i am the first text line");
208 new FuiHLine(win);
209 new FuiTextLine(win, "hello, i am the second text line");
210 new FuiHLine(win);
211 if (auto box = new FuiHBox(win)) {
212 auto lbl0 = new FuiLabel(box, "&first:", "bx0");
213 lbl0.hgroup = "label0group";
214 if (auto btn = new FuiButton(box, "button &0")) { btn.id = "bx0"; btn.lineBreak = true; }
215 auto lbl1 = new FuiLabel(box, "&second:", "bx1");
216 lbl1.hgroup = "label0group";
217 //if (auto btn = new FuiButton(box, "button &1")) { btn.id = "bx1"; btn.lineBreak = true; }
218 if (auto edt = new FuiEditLine(box, "default text")) {
219 edt.minSize.w = 30;
220 edt.aligning = edt.Align.Stretch;
221 edt.id = "bx1";
222 edt.lineBreak = true;
224 if (auto btn = new FuiCheckBox(box, "checkbox &2", "cbgroup0")) { btn.lineBreak = true; }
225 if (auto pan = new FuiPanel(box, "Radio")) {
226 new FuiRadio(pan, "radio &3", "rbgroup0", 3);
227 new FuiRadio(pan, "radio &4", "rbgroup0", 4);
228 new FuiRadio(pan, "radio &5", "rbgroup0", 5);
231 new FuiTextLine(win, "hello, i am the third text line");
232 new FuiHLine(win);
233 if (auto box = new FuiHBox(win)) {
234 new FuiSpan(box);
235 (new FuiButton(box, "&OK")).defctl = true;
236 new FuiButton(box, "&Cancel");
237 new FuiSpan(box);
239 new FuiHLine(win);
240 if (closeOnBlur) {
241 if (auto lb = new FuiEditor(win, "line 1\nlist with tab!: \t2\nlast 3")) {
242 //lb.ed.gotoXY(0, 0);
243 lb.ed.visualtabs = true;
244 //lb.ed.tabsize = 8;
245 win.defaultFocus = lb;
247 } else {
248 if (auto lb = new FuiListBox(win)) {
249 //lb.minSize.w = 24;
250 //lb.minSize.h = 16;
251 lb.allowmarks = true;
252 foreach (immutable idx; 0..24) {
253 import std.format : format;
254 lb.addItem("item #%s".format(idx));
258 if (closeOnBlur) win.onBlur = (FuiControl self) { if (auto w = cast(FuiWindow)self) w.close; };
259 fuiLayout(win);
260 if (closeOnBlur) {
261 win.pos = FuiPoint((ttyw-win.size.w)/2, (ttyh-win.size.h)/2);
262 } else {
263 import std.random : uniform;
264 win.pos = FuiPoint(uniform!"[]"(0, ttyw-win.size.w), uniform!"[]"(0, ttyh-win.size.h));
266 return win;
270 // ////////////////////////////////////////////////////////////////////////// //
271 void main (string[] args) {
272 if (ttyIsRedirected) assert(0, "no redirections, please");
273 xtInit();
275 auto ttymode = ttyGetMode();
276 scope(exit) {
277 normalScreen();
278 ttySetMode(ttymode);
280 ttySetRaw();
281 altScreen();
283 bool doQuit = false;
284 addEventListener((FuiEventQuit evt) { doQuit = true; });
286 foreach (immutable _; 0..2) tuidesk.addWindow(createWin());
287 tuidesk.addWindow(createWin(true));
289 tuidesk.registerHotKey("M-A", () {
290 if (auto ed = cast(FuiEditLine)tuidesk.focused) {
291 ttyBeep;
292 auto str = ed.getText!string;
293 if (str.length) hisman.add(ed, str);
297 while (!doQuit) {
298 foreach (immutable _; 0..100) {
299 while (ebusSafeDelay == 0) processEvents();
300 if (ebusSafeDelay != 0) break;
302 tuidesk.draw();
303 xtFlush();
304 //{ import core.memory : GC; GC.collect(); GC.minimize(); }
305 if (doQuit) break;
306 if (ttyIsKeyHit || ebusSafeDelay < 0) {
307 if (ebusSafeDelay < 0) { import core.memory : GC; GC.collect(); GC.minimize(); }
308 auto key = ttyReadKey(ebusSafeDelay, TtyDefaultEscWait);
309 if (key.key == TtyEvent.Key.Error) break;
310 if (key.key == TtyEvent.Key.Unknown) continue;
311 if (key == "C-c") break;
312 //if (key.key == TtyEvent.Key.Escape) { (new FuiEventQuit).post; continue; }
313 if (key.key == TtyEvent.Key.ModChar && key.ctrl && !key.alt && !key.shift && key.ch == 'L') { xtFullRefresh(); continue; }
314 tuidesk.queue(key);