1 module dinu.mainWindow;
2 
3 
4 import dinu;
5  
6  
7 __gshared:
8 
9 
10 private double em1;
11 
12 int em(double mod){
13     return cast(int)(round(em1*mod));
14 }
15 
16 double eerp(double current, double target, double speed){
17     auto dir = current > target ? -1 : 1;
18     auto spd = abs(target-current)*speed+speed;
19     spd = spd.min(abs(target-current)).max(0);
20     return current + spd*dir;
21 }
22 
23 struct Screen {
24     int x, y, w, h;
25 }
26 
27 
28 Screen[int] screens(Display* display){
29     int count;
30     auto screenInfo = XineramaQueryScreens(display, &count);
31     Screen[int] res;
32     foreach(screen; screenInfo[0..count])
33         res[screen.screen_number] = Screen(screen.x_org, screen.y_org, screen.width, screen.height);
34     XFree(screenInfo);
35     return res;
36 
37 }
38 
39 
40 auto mapKeyToChar(ws.wm.Window window, XKeyEvent* e, void delegate(dchar) cb){
41     char[25] str;
42     KeySym ks;
43     Status st;
44     size_t l = Xutf8LookupString(window.inputContext, e, str.ptr, 25, &ks, &st);
45     foreach(dchar c; str[0..l])
46         if(!c.isControl)
47             cb(c);
48 }
49 
50 
51 class WindowMain: ws.wm.Window {
52 
53     ws.wm.Window resultWindow;
54     int padding;
55     int animationY;
56     bool shouldClose;
57     long lastUpdate;
58     double animStart;
59     double scrollCurrent = 0;
60     double selectCurrent = 0;
61 
62     Animation windowAnimation;
63 
64     //GlContext context;
65 
66     this(){
67         super(1, 1, "dinu", true);
68         draw.setFont(options.font, 12);
69         auto screens = screens(display);
70         if(options.screen !in screens){
71             "Screen %s does not exist".format(options.screen).writeln;
72             options.screen = screens.keys[0];
73         }
74         auto screen = screens[options.screen];
75         em1 = draw.fontHeight*1.2;
76         //context.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
77         //context.enable(GL_BLEND);
78         resize([
79             options.w ? options.w : screen.w,
80             1.em*(options.lines+1)+0.8.em
81         ]);
82         move([
83             options.x + screen.x,
84             options.y - size.h
85         ]);
86         show;
87         grabKeyboard;
88         padding = 0.4.em;
89         lastUpdate = (now*1000).lround;
90         windowAnimation = new AnimationExpIn(pos.y, 0, (0.1+size.h/4000.0)*options.animationMove);
91         wm.on([
92             KeyPress: (XEvent* e){ keyboard(cast(Keyboard.key)XLookupKeysym(&e.xkey,0), true); this.mapKeyToChar(&e.xkey, &keyboard); },
93             KeyRelease: (XEvent* e) => keyboard(cast(Keyboard.key)XLookupKeysym(&e.xkey,0), false)
94         ]);
95     }
96 
97     /+
98     override void drawInit(){
99         context = new GlContext(windowHandle);
100         draw = new GlDraw(context);
101     }
102     +/
103 
104     override void resized(int[2] size){
105         super.resized(size);
106         onDraw;
107     }
108 
109     void update(){
110         auto cur = (now*1000).lround;
111         auto delta = cur-lastUpdate;
112         lastUpdate = cur;
113         int targetY = cast(int)windowAnimation.calculate+options.y;
114         if(windowAnimation.done && shouldClose){
115             writeln("CLOSE");
116             super.close;
117             return;
118         }else if(targetY != pos.y){
119             move([pos.x, targetY]);
120         }
121         auto matches = output.idup;
122         auto selected = commandBuilder.selected < -1 ? -commandBuilder.selected-2 : -1;
123         auto scrollTarget = min(max(0, cast(long)matches.length-cast(long)options.lines), max(0, selected+1-options.lines/2));
124         if(options.animations > 0){
125             scrollCurrent = scrollCurrent.eerp(scrollTarget, delta/150.0);
126             selectCurrent = selectCurrent.eerp(commandBuilder.selected, delta/50.0);
127         }else{
128             scrollCurrent = scrollTarget;
129             selectCurrent = commandBuilder.selected;
130         }
131     }
132 
133     override void onDraw(){
134         if(hidden)
135             return;
136         assert(thread_isMainThread);
137 
138         int separator = (size.w*options.ratio).to!int;
139         drawOutput([0, size.h-1.em*options.lines], [size.w, 1.em*options.lines], separator);
140         drawInput([0, 0], [size.w, size.h-1.em*options.lines], separator);
141         super.onDraw;
142     }
143 
144     void drawInput(int[2] pos, int[2] size, int sep){
145         auto paddingVert = 0.2.em;
146         draw.setColor(options.colorBg);
147         draw.rect(pos, size);
148         draw.setColor(options.colorInputBg);
149         draw.rect([sep, pos.y+paddingVert], [size.w - sep*2, 1]);
150 
151         // cwd
152         int textY = pos.y + ((size.h - draw.fontHeight)/2.0).lround.to!int;
153         //draw.setColor([1,0,0,0.1]);
154         //draw.rect([0,textY], [size.w, draw.fontHeight]);
155         auto context = getcwd.replace("~".expandTilde, "~").split("/");
156         auto partAdvance = draw.width(context[$-1]~"/");
157         draw.setColor(options.colorHint);
158         draw.text([pos.x+sep-partAdvance, textY], context[0..$-1].join("/"), 1.4);
159         if(context.length > 1)
160             draw.text([pos.x+sep-partAdvance+draw.width("/"), textY], "/", 1.4);
161         draw.setColor(options.colorOutput);
162         draw.text([pos.x+sep, textY], context[$-1], 1.4);
163         
164         draw.clip([pos.x+sep, pos.y], [size.w - sep*2, size.h]);
165         int width = draw.width(commandBuilder.cursorPart ~ "..");
166         int offset = -max(0, width-size.w - sep*2);
167 
168         // cursor
169         auto selStart = min(commandBuilder.cursor, commandBuilder.cursorStart);
170         auto selEnd = max(commandBuilder.cursor, commandBuilder.cursorStart);
171         int cursorOffset = padding+offset+pos.x+sep+draw.width(commandBuilder.finishedPart);
172         int selpos = cursorOffset+draw.width(commandBuilder.text[0..selEnd].to!string);
173         if(commandBuilder.cursorStart != commandBuilder.cursor){
174             auto start = cursorOffset+draw.width(commandBuilder.text[0..selStart].to!string);
175             draw.setColor(options.colorHint);
176             draw.rect([start, pos.y+paddingVert*2], [selpos-start, size.y-paddingVert*4]);
177         }
178         int curpos = cursorOffset+draw.width(commandBuilder.text[0..commandBuilder.cursor].to!string);
179         draw.setColor(options.colorInput);
180         draw.rect([curpos, pos.y+paddingVert*2], [1, size.y-paddingVert*4]);
181 
182         // input
183         int textStart = offset+pos.x+sep+padding;
184         if(!commandBuilder.commandSelected){
185             draw.text([textStart, textY], commandBuilder.toString, 0);
186         }else{
187             auto xoff = textStart+commandBuilder.commandSelected[0].draw(draw, [textStart, textY], false, []);
188             draw.setColor(options.colorInput);
189             foreach(param; commandBuilder.command[1..$])
190                 xoff += draw.text([xoff, textY], ' ' ~ param.to!string, 0);
191         }
192         draw.noclip;
193         //draw.text([10, textY], commandBuilder.toString.expandTilde.buildNormalizedPath, 0);
194     }
195 
196     void drawOutput(int[2] pos, int[2] size, int sep){
197         draw.setColor(options.colorOutputBg);
198         draw.rect(pos, size);
199         auto output = output.idup;
200         auto selected = commandBuilder.selected < -1 ? -commandBuilder.selected-2 : -1;
201         auto start = cast(size_t)scrollCurrent;
202         auto textOffset = ((1.em - draw.fontHeight)/2.0).lround.to!int;
203         if(selectCurrent < -1){
204             draw.setColor(options.colorHintBg);
205             draw.rect([pos.x+sep, cast(int)(pos.y + size.h*(-2-selectCurrent-scrollCurrent)/cast(double)options.lines)], [size.w-sep*2, 1.em]);
206         }
207         foreach(i, match; output[start..min($, start+options.lines+1)]){
208             int y = cast(int)(pos.y + size.h*(i-(scrollCurrent-start))/cast(double)options.lines) + textOffset;
209             draw.clip([pos.x, pos.y], [size.w-sep, size.h]);
210             match.draw(draw, [pos.x+sep+padding, y], start+i == selected, []);
211             draw.noclip;
212             debug(Score){
213                 draw.setColor(options.colorInput);
214                 draw.text([size.w-sep, y], match.score.to!string);
215             }
216         }
217         if(output.length > 15){
218             double scrollbarHeight = size.h/(max(1.0, (cast(long)output.length-cast(long)14).log2));
219             int scrollbarOffset = cast(int)((size.h - scrollbarHeight) * (scrollCurrent/(max(1.0, output.length-15))));
220             draw.setColor(options.colorHintBg);
221             draw.rect([size.w-sep-0.2.em, scrollbarOffset], [0.2.em, cast(int)scrollbarHeight]);
222         }
223     }
224 
225     void showOutput(){
226         if(hidden)
227             return;
228         options.lines = 15;
229         int height = 1.em*(options.lines+1)+0.8.em-1;
230         resize([size.w, height]);
231         move([pos.x, pos.y-height+size.h]);
232         windowAnimation = new AnimationExpIn(pos.y-height+size.h, options.y, (0.1+size.h/4000.0)*options.animationMove);
233     }
234 
235     override void close(){
236         XUngrabKeyboard(wm.displayHandle, CurrentTime);
237         commandBuilder.destroy;
238         windowAnimation = new AnimationExpOut(pos.y, -size.h, (0.1+size.h/4000.0)*options.animationMove);
239         shouldClose = true;
240         //super.close();
241     }
242 
243     bool[Keyboard.key] keys;
244 
245     void keyboard(Keyboard.key key, bool pressed){
246         keys[key] = pressed;
247         if(!pressed)
248             return;
249         auto control = keys.get(Keyboard.control, false) || keys.get(Keyboard.controlR, false);
250         auto shift = keys.get(Keyboard.shift, false) || keys.get(Keyboard.shiftR, false);
251         if(control)
252             switch(key){
253                 case XK_q:			key = XK_Escape; break;
254                 case XK_u:			commandBuilder.deleteLeft; return;
255                 case XK_BackSpace:	commandBuilder.deleteWordLeft; return;
256                 case XK_Delete:		commandBuilder.deleteWordRight; return;
257                 case XK_j:			commandBuilder.moveLeft; return;
258                 case XK_semicolon:	commandBuilder.moveRight; return;
259                 case XK_V:
260                 //case XK_v:			XConvertSelection(display, clip, utf8, utf8, handle, CurrentTime); return;
261                 case XK_a:			commandBuilder.selectAll; return;
262                 default: break;
263             }
264         switch(key){
265             case XK_Escape:			.close(); return;
266             case XK_Delete:			commandBuilder.delChar; return;
267             case XK_BackSpace:		commandBuilder.delBackChar; return;
268             case XK_Left:			commandBuilder.moveLeft(control); return;
269             case XK_Right:			commandBuilder.moveRight(control); return;
270             case XK_Down:			commandBuilder.select(commandBuilder.selected+1); return;
271             case XK_Tab:			if(!shift){
272                                         commandBuilder.select(commandBuilder.selected+1); return;
273                                     }else{
274                                         goto case XK_Up;
275                                     }
276             case XK_Up:
277                                     if(!options.lines && commandBuilder.selected == -1){
278                                         showOutput;
279                                     }else
280                                         commandBuilder.select(commandBuilder.selected-1);
281                                     return;
282             case XK_Page_Up:		commandBuilder.select(commandBuilder.selected-15);
283                                     if(commandBuilder.selected < 0)
284                                         showOutput;
285                                     break;
286             case XK_Page_Down:		commandBuilder.select(commandBuilder.selected+15); break;
287             case XK_Return:
288             case XK_KP_Enter:
289                                     commandBuilder.run(!control);
290                                     if(shift && !options.lines){
291                                         showOutput;
292                                     }
293                                     if(!control && !shift)
294                                         .close();
295                                     return;
296             case Keyboard.shift:
297             case Keyboard.shiftR:	commandBuilder.shiftDown = !commandBuilder.shiftDown; break;
298             default: break;
299         }
300         onDraw;
301     }
302 
303     void keyboard(dchar key){
304         commandBuilder.insert(key.to!dstring);
305         onDraw;
306     }
307 
308     override void onPaste(string text){
309         commandBuilder.insert(text.to!dstring);
310     }
311 
312     void grabKeyboard(){
313         foreach(i; 0..100){
314             if(XGrabKeyboard(display, windowHandle, true, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
315                 return;
316             Thread.sleep(dur!"msecs"(10));
317         }
318         .close();
319         assert(0, "cannot grab keyboard");
320     }
321 
322 }
323