1 /* 2 * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license. 3 * 4 * Pixel Perfect Engine, concrete.window module 5 */ 6 7 module PixelPerfectEngine.concrete.window; 8 9 import PixelPerfectEngine.graphics.bitmap; 10 import PixelPerfectEngine.graphics.draw; 11 import PixelPerfectEngine.graphics.layers; 12 13 public import PixelPerfectEngine.concrete.elements; 14 public import PixelPerfectEngine.concrete.stylesheet; 15 import PixelPerfectEngine.system.etc; 16 import PixelPerfectEngine.system.inputHandler; 17 18 import std.algorithm.mutation; 19 import std.stdio; 20 import std.conv; 21 import std.file; 22 import std.datetime; 23 24 public class Window : ElementContainer{ 25 private WindowElement[] elements, mouseC, keyboardC, scrollC; 26 public wstring title; 27 public IWindowHandler parent; 28 //public Bitmap16Bit[int] altStyleBrush; 29 public BitmapDrawer output; 30 public int header, sizeX, sizeY; 31 private int moveX, moveY; 32 private bool move, fullUpdate; 33 private string[] extraButtons; 34 public Coordinate position; 35 public StyleSheet customStyle; 36 /** 37 * If the current window doesn't contain a custom StyleSheet, it gets from it's parent. 38 */ 39 public StyleSheet getStyleSheet(){ 40 if(customStyle is null) 41 return parent.getStyleSheet(); 42 return customStyle; 43 } 44 45 public void drawUpdate(WindowElement sender){ 46 /*if(!fullUpdate){ 47 this.draw(); 48 }*/ 49 output.insertBitmap(sender.getPosition().left,sender.getPosition().top,sender.output.output); 50 } 51 52 /*public Bitmap16Bit[wchar] getFontSet(int style){ 53 return parent.getFontSet(style); 54 }*/ 55 /** 56 * Standard constructor. "size" sets both the initial position and the size of the window. 57 * Extra buttons are handled from the StyleSheet, even numbers are unpressed, odd numbers are pressed. 58 */ 59 public this(Coordinate size, wstring title, string[] extraButtons = []){ 60 position = size; 61 output = new BitmapDrawer(position.getXSize, position.getYSize); 62 this.title = title; 63 sizeX = position.getXSize; 64 sizeY = position.getYSize; 65 //style = 0; 66 //closeButton = 2; 67 this.extraButtons = extraButtons; 68 } 69 70 public void addElement(WindowElement we, int eventProperties){ 71 elements ~= we; 72 we.elementContainer = this; 73 if((eventProperties & EventProperties.KEYBOARD) == EventProperties.KEYBOARD){ 74 keyboardC ~= we; 75 } 76 if((eventProperties & EventProperties.MOUSE) == EventProperties.MOUSE){ 77 mouseC ~= we; 78 } 79 if((eventProperties & EventProperties.SCROLL) == EventProperties.SCROLL){ 80 scrollC ~= we; 81 } 82 } 83 84 public void draw(){ 85 86 output.drawFilledRectangle(0, position.getXSize() - 1, 0, position.getYSize() - 1, getStyleSheet().getColor("window")); 87 output.insertBitmap(0,0,getStyleSheet().getImage("closeButtonA")); 88 int x1 = getStyleSheet().getImage("closeButtonA").getX(), y1 = getStyleSheet().getImage("closeButtonA").getY(); 89 /*output.drawRectangle(x1, sizeX - 1, 0, y1, getStyleBrush(header)); 90 output.drawFilledRectangle(x1 + (x1/2), sizeX - 1 - (x1/2), y1/2, y1 - (y1/2), getStyleBrush(header).readPixel(x1/2, y1/2));*/ 91 92 int headerLength = extraButtons.length == 0 ? position.getXSize() - 1 : position.getXSize() - 1 - ((extraButtons.length/2) * x1) ; 93 //drawing the header 94 output.drawLine(x1, headerLength, 0, 0, getStyleSheet().getColor("windowascent")); 95 output.drawLine(x1, x1, 0, y1 - 1, getStyleSheet().getColor("windowascent")); 96 output.drawLine(x1, headerLength, y1 - 1, y1 - 1, getStyleSheet().getColor("windowdescent")); 97 output.drawLine(headerLength, headerLength, 0, y1 - 1, getStyleSheet().getColor("windowdescent")); 98 99 //drawing the border of the window 100 output.drawLine(0, position.getXSize() - 1, y1, y1, getStyleSheet().getColor("windowascent")); 101 output.drawLine(0, 0, y1, position.getYSize() - 1, getStyleSheet().getColor("windowascent")); 102 output.drawLine(0, position.getXSize() - 1, position.getYSize() - 1, position.getYSize() - 1, getStyleSheet().getColor("windowdescent")); 103 output.drawLine(position.getXSize() - 1, position.getXSize() - 1, y1, position.getYSize() - 1, getStyleSheet().getColor("windowdescent")); 104 105 //output.drawText(x1+1, 1, title, getFontSet(0), 1); 106 output.drawText(x1, (y1-getStyleSheet().getFontset("default").getSize())/2, title, getStyleSheet().getFontset("default"),1); 107 fullUpdate = true; 108 foreach(WindowElement we; elements){ 109 we.draw(); 110 //output.insertBitmap(we.getPosition().xa,we.getPosition().ya,we.output.output); 111 } 112 fullUpdate = false; 113 } 114 115 public void passMouseEvent(int x, int y, int state = 0){ 116 //writeln(x, ",", y); 117 if(getStyleSheet.getImage("closeButtonA").getX() > x && getStyleSheet.getImage("closeButtonA").getY() > y && state == 0){ 118 parent.closeWindow(this); 119 return; 120 }else if(getStyleSheet.getImage("closeButtonA").getY() > y && state == 0){ 121 /*if(state == 0 && !move){ 122 move = true; 123 moveY = y; 124 } 125 if(state == 1 && move){ 126 move = false; 127 position.move(x - moveX, y - moveY); 128 parent.moveUpdate(this); 129 }*/ 130 parent.moveUpdate(this); 131 } 132 //x -= position.xa; 133 //y -= position.ya; 134 foreach(WindowElement e; mouseC){ 135 if(e.getPosition().left < x && e.getPosition().right > x && e.getPosition().top < y && e.getPosition().bottom > y){ 136 e.onClick(x - e.getPosition().left, y - e.getPosition().top, state); 137 return; 138 } 139 } 140 141 } 142 public void passScrollEvent(int wX, int wY, int x, int y){ 143 foreach(WindowElement e; scrollC){ 144 if(e.getPosition().left < wX && e.getPosition().right > wX && e.getPosition().top < wX && e.getPosition().bottom > wY){ 145 146 e.onScroll(x, y, wX, wY); 147 return; 148 } 149 } 150 } 151 public void extraButtonEvent(int num){ 152 153 } 154 public void passKeyboardEvent(wchar c, int type, int x, int y){ 155 156 } 157 public void addParent(IWindowHandler wh){ 158 parent = wh; 159 } 160 public void getFocus(WindowElement sender){ 161 162 } 163 public void dropFocus(WindowElement sender){ 164 165 } 166 } 167 168 public class TextInputDialog : Window, ActionListener{ 169 public ActionListener[] al; 170 private TextBox textInput; 171 private string source; 172 173 public this(Coordinate size, string source, wstring title, wstring message, wstring text = ""){ 174 this(size, title); 175 Label msg = new Label(message, "null", Coordinate(8, 20, size.getXSize()-8, 39)); 176 addElement(msg, EventProperties.MOUSE); 177 178 textInput = new TextBox(text, "textInput", Coordinate(8, 40, size.getXSize()-8, 59)); 179 addElement(textInput, EventProperties.MOUSE); 180 181 Button ok = new Button("Ok","ok", Coordinate(size.getXSize()-48,65,size.getXSize()-8,84)); 182 ok.al ~= this; 183 addElement(ok,EventProperties.MOUSE); 184 this.source = source; 185 } 186 187 public this(Coordinate size, wstring title){ 188 super(size, title); 189 } 190 191 public void actionEvent(string source, int type, int value, wstring message){} 192 public void actionEvent(string source, string subSource, int type, int value, wstring message){} 193 public void actionEvent(Event event){ 194 if(event.source == "ok"){ 195 foreach(a; al){ 196 a.actionEvent(new Event(this.source, "TextInputDialog", null, null, textInput.getText(), 0, EventType.TEXTINPUT)); 197 } 198 parent.closeWindow(this); 199 } 200 } 201 } 202 203 public class DefaultDialog : Window, ActionListener{ 204 public ActionListener[] al; 205 private string source; 206 207 public this(Coordinate size, string source, wstring title, wstring message, wstring[] options = ["Ok"]){ 208 this(size, title); 209 //generate text 210 //NOTE: currently only works with one line texts, later on multi-line texts will be added 211 //NOTE: currently only optimized for 8 pixel wide fonts 212 this.source = source; 213 int x1 , x2; 214 //writeln(x1,',',size.getXSize - x1); 215 Label msg = new Label(message, "null", Coordinate(8, 20, size.getXSize()-8, 40)); 216 addElement(msg, EventProperties.MOUSE); 217 218 //generate buttons 219 220 x1 = size.getXSize - 10; 221 Button[] buttons; 222 for(int i; i < options.length; i++){ 223 x2 = x1 - ((options[i].length + 2) * 8); 224 buttons ~= new Button(options[i], to!string(options[i]), Coordinate(x2, 40, x1, 60)); 225 buttons[i].al ~= this; 226 addElement(buttons[i], EventProperties.MOUSE); 227 x1 = x2; 228 } 229 } 230 231 public this(Coordinate size, wstring title){ 232 super(size, title); 233 } 234 235 public void actionEvent(string source, int type, int value, wstring message){ 236 foreach(a; al){ 237 //a.actionEvent(source, this.source, type, value, message); 238 a.actionEvent(new Event(source, this.source, null, null, null, 0, EventType.CLICK)); 239 } 240 } 241 public void actionEvent(string source, string subSource, int type, int value, wstring message){} 242 public void actionEvent(Event event){ 243 foreach(a; al){ 244 //a.actionEvent(source, this.source, type, value, message); 245 a.actionEvent(new Event(event.source, this.source, null, null, null, 0, EventType.CLICK)); 246 } 247 } 248 } 249 250 public class FileDialog : Window, ActionListener{ 251 private ActionListener al; 252 private string source; 253 private string[] filetypes, pathList, driveList; 254 private string directory, filename; 255 private ListBox lb; 256 private TextBox tb; 257 private ListBoxColumn[] columns; 258 private bool save; 259 public static const string subsourceID = "filedialog"; 260 261 262 public this(wstring title, string source, ActionListener a, string[] filetypes, string startDir, bool save = false, string filename = ""){ 263 this(Coordinate(20,20,240,198), title); 264 this.source = source; 265 this.filetypes = filetypes; 266 this.save = save; 267 al = a; 268 directory = startDir; 269 //generate buttons 270 Button[] buttons; 271 buttons ~= new Button("Up","up",Coordinate(4, 154, 54, 174)); 272 buttons ~= new Button("Drive","drv",Coordinate(58, 154, 108, 174)); 273 if(save) 274 buttons ~= new Button("Save","ok",Coordinate(112, 154, 162, 174)); 275 else 276 buttons ~= new Button("Load","ok",Coordinate(112, 154, 162, 174)); 277 buttons ~= new Button("Close","close",Coordinate(166, 154, 216, 174)); 278 for(int i; i < buttons.length; i++){ 279 buttons[i].al ~= this; 280 addElement(buttons[i], EventProperties.MOUSE); 281 } 282 //generate textbox 283 tb = new TextBox(to!wstring(filename), "filename", Coordinate(4, 130, 162, 150)); 284 //tb.addTextInputHandler(tih); 285 tb.al ~= this; 286 addElement(tb, EventProperties.MOUSE); 287 //generate listbox 288 289 //test parameters 290 291 //writeln(dirEntries(startDir, SpanMode.shallow)); 292 293 294 columns ~= ListBoxColumn("Name", ["aaa","aaa","aaa","aaa","aaa","aaa","aaa"]); 295 columns ~= ListBoxColumn("Type", ["bbb","bbb","bbb","bbb","aaa","aaa","aaa"]); 296 //Date format: yyyy-mm-dd hh:mm:ss 297 columns ~= ListBoxColumn("Date", ["yyyy-mm-dd hh:mm:ss","yyyy-mm-dd hh:mm:ss","yyyy-mm-dd hh:mm:ss","yyyy-mm-dd hh:mm:ss","yyyy-mm-dd hh:mm:ss","yyyy-mm-dd hh:mm:ss","yyyy-mm-dd hh:mm:ss"]); 298 spanDir(); 299 300 //writeln(pathList); 301 302 //VSlider vsl = new VSlider(20,5,"sld",Coordinate(200,20,216,124)); 303 //addElement(vsl, EventProperties.MOUSE); 304 //HSlider hsl = new HSlider(200,48,"vsld",Coordinate(4, 108, 200, 124)); 305 //addElement(hsl, EventProperties.MOUSE); 306 lb = new ListBox("lb", Coordinate(4, 20, 216, 126),columns ,[160, 40, 176] ,15); 307 addElement(lb, EventProperties.MOUSE | EventProperties.SCROLL); 308 //scrollC ~= lb; 309 lb.al ~= this; 310 detectDrive(); 311 } 312 313 public this(Coordinate size, wstring title){ 314 super(size, title); 315 } 316 317 private void spanDir(){ 318 pathList.length = 0; 319 columns[0].elements.length = 0; 320 columns[1].elements.length = 0; 321 columns[2].elements.length = 0; 322 foreach(DirEntry de; dirEntries(directory, SpanMode.shallow)){ 323 if(de.isDir){ 324 pathList ~= de.name; 325 columns[0].elements ~= to!wstring(getFilenameFromPath(de.name)); 326 columns[1].elements ~= "<DIR>"; 327 columns[2].elements ~= formatDate(de.timeLastModified); 328 } 329 } 330 foreach(ft; filetypes){ 331 foreach(DirEntry de; dirEntries(directory, ft, SpanMode.shallow)){ 332 if(de.isFile){ 333 pathList ~= de.name; 334 columns[0].elements ~= to!wstring(getFilenameFromPath(de.name, true)); 335 columns[1].elements ~= to!wstring(ft); 336 columns[2].elements ~= formatDate(de.timeLastModified); 337 } 338 } 339 } 340 341 342 } 343 344 private wstring formatDate(SysTime time){ 345 wstring s; 346 s ~= to!wstring(time.year()); 347 s ~= "-"; 348 s ~= to!wstring(time.month()); 349 s ~= "-"; 350 s ~= to!wstring(time.day()); 351 s ~= " "; 352 s ~= to!wstring(time.hour()); 353 s ~= ":"; 354 s ~= to!wstring(time.minute()); 355 s ~= ":"; 356 s ~= to!wstring(time.second()); 357 //writeln(s); 358 return s; 359 } 360 361 private void detectDrive(){ 362 driveList.length = 0; 363 for(char c = 'A'; c <='Z'; c++){ 364 string s; 365 s ~= c; 366 s ~= ":\x5c"; 367 if(exists(s)){ 368 driveList ~= (s); 369 } 370 } 371 //writeln(driveList); 372 } 373 374 private string getFilenameFromPath(string p, bool b = false){ 375 int n, m = p.length; 376 string s; 377 for(int i ; i < p.length ; i++){ 378 if(p[i] == '\x5c'){ 379 n = i; 380 } 381 } 382 //n++; 383 if(b){ 384 for(int i ; i < p.length ; i++){ 385 if(p[i] == '.'){ 386 m = i; 387 } 388 } 389 } 390 for( ; n < m ; n++){ 391 if(p[n] < 128 && p[n] > 31) 392 s ~= p[n]; 393 } 394 return s; 395 } 396 397 private void up(){ 398 int n; 399 for(int i ; i < directory.length ; i++){ 400 if(directory[i] == '\x5c'){ 401 n = i; 402 } 403 } 404 string newdir; 405 for(int i ; i < n ; i++){ 406 newdir ~= directory[i]; 407 } 408 directory = newdir; 409 spanDir(); 410 lb.updateColumns(columns); 411 lb.draw(); 412 } 413 414 private void changeDrive(){ 415 pathList.length = 0; 416 columns[0].elements.length = 0; 417 columns[1].elements.length = 0; 418 columns[2].elements.length = 0; 419 foreach(string drive; driveList){ 420 pathList ~= drive; 421 columns[0].elements ~= to!wstring(drive); 422 columns[1].elements ~= "<DRV>"; 423 columns[2].elements ~= "N/A"; 424 } 425 lb.updateColumns(columns); 426 lb.draw(); 427 } 428 429 private void fileEvent(){ 430 //wstring s = to!wstring(directory); 431 filename = to!string(tb.getText); 432 //al.actionEvent("file", EventType.FILEDIALOGEVENT, 0, s); 433 al.actionEvent(new Event(source, "filedialog", directory, filename, null, 0, EventType.FILEDIALOGEVENT)); 434 parent.closeWindow(this); 435 } 436 437 public void actionEvent(string source, int type, int value, wstring message){ 438 /*if(source == "lb"){ 439 //writeln(value); 440 441 }else if(source == "up"){ 442 up(); 443 }else if(source == "drv"){ 444 changeDrive(); 445 }else if(source == "ok"){ 446 fileEvent(); 447 }else if(source == "close"){ 448 parent.closeWindow(this); 449 }*/ 450 } 451 public void actionEvent(string source, string subSource, int type, int value, wstring message){} 452 public void actionEvent(Event event){ 453 writeln(event.source); 454 switch(event.source){ 455 case "lb": 456 try{ 457 if(isDir(pathList[event.value])){ 458 directory = pathList[event.value]; 459 spanDir(); 460 lb.updateColumns(columns); 461 lb.draw(); 462 463 }else{ 464 filename = getFilenameFromPath(pathList[event.value]); 465 tb.setText(to!wstring(filename)); 466 } 467 }catch(Exception e){ 468 writeln(e.msg); 469 } 470 break; 471 case "up": up(); break; 472 case "drv": changeDrive(); break; 473 case "ok": fileEvent(); break; 474 case "close": parent.closeWindow(this); break; 475 default: break; 476 } 477 } 478 } 479 480 public class WindowHandler : InputListener, MouseListener, IWindowHandler{ 481 private Window[] windows; 482 private int[] priorities; 483 public int screenX, screenY, rasterX, rasterY, moveX, moveY; 484 //public Bitmap16Bit[wchar] basicFont, altFont, alarmFont; 485 public StyleSheet defaultStyle; 486 //public Bitmap16Bit[int] styleBrush; 487 private Bitmap16Bit background; 488 private ISpriteLayer16Bit spriteLayer; 489 private bool moveState, dragEventState; 490 private Window windowToMove, dragEventDest; 491 492 public this(int sx, int sy, int rx, int ry,ISpriteLayer16Bit sl){ 493 screenX = sx; 494 screenY = sy; 495 rasterX = rx; 496 rasterY = ry; 497 spriteLayer = sl; 498 } 499 500 public void addWindow(Window w){ 501 windows ~= w; 502 w.addParent(this); 503 //priorities ~= 666; 504 w.draw(); 505 setWindowToTop(w); 506 507 /*for(int i ; i < windows.length ; i++){ 508 spriteLayer.addSprite(windows[i].output.output, i, windows[i].position); 509 }*/ 510 } 511 512 public void addBackground(Bitmap16Bit b){ 513 background = b; 514 spriteLayer.addSprite(background, 65536, 0, 0); 515 } 516 517 private int whichWindow(Window w){ 518 for(int i ; i < windows.length ; i++){ 519 if(windows[i] == w){ 520 return i; 521 } 522 } 523 return -1; 524 } 525 526 public void setWindowToTop(Window sender){ 527 int s; 528 foreach(Window w; windows){ 529 if(w == sender){ 530 Window ww = windows[s]; 531 windows[s] = windows[0]; 532 windows[0] = ww; 533 updateSpriteOrder(); 534 break; 535 }else{ 536 s++; 537 } 538 } 539 540 } 541 542 private void updateSpriteOrder(){ 543 for(int i ; i < windows.length ; i++){ 544 spriteLayer.removeSprite(i); 545 spriteLayer.addSprite(windows[i].output.output, i, windows[i].position); 546 547 } 548 } 549 550 /*public Bitmap16Bit[wchar] getFontSet(int style){ 551 switch(style){ 552 case 0: return basicFont; 553 case 1: return altFont; 554 case 3: return alarmFont; 555 default: break; 556 } 557 return basicFont; 558 559 }*/ 560 public StyleSheet getStyleSheet(){ 561 return defaultStyle; 562 } 563 public void closeWindow(Window sender){ 564 int p = whichWindow(sender); 565 for(int i ; i < windows.length ; i++) 566 spriteLayer.removeSprite(i); 567 //spriteLayer.removeSprite(p); 568 windows = remove(windows, p); 569 570 updateSpriteOrder(); 571 } 572 573 public void moveUpdate(Window sender){ 574 moveState = true; 575 windowToMove = sender; 576 //writeln(moveState); 577 } 578 public void keyPressed(string ID, Uint32 timestamp, Uint32 devicenumber, Uint32 devicetype){ 579 580 } 581 public void keyReleased(string ID, Uint32 timestamp, Uint32 devicenumber, Uint32 devicetype){ 582 583 } 584 public void mouseButtonEvent(Uint32 which, Uint32 timestamp, Uint32 windowID, Uint8 button, Uint8 state, Uint8 clicks, Sint32 x, Sint32 y){ 585 586 //converting the dimensions 587 double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY); 588 x = to!int(x * xR); 589 y = to!int(y * yR); 590 591 592 if(state == SDL_RELEASED && moveState){ 593 /*windowToMove.position.relMove(x - moveX, y - moveY); 594 spriteLayer.relMoveSprite(whichWindow(windowToMove), x - moveX, y - moveY);*/ 595 //writeln(x - moveX, y - moveY); 596 moveState = false; 597 }else if(state == SDL_RELEASED && moveState){ 598 dragEventState = false; 599 } 600 else if(state == SDL_PRESSED){ 601 moveX = x; 602 moveY = y; 603 604 for(int i ; i < windows.length ; i++){ 605 //writeln(i); 606 if(x >= windows[i].position.left && x <= windows[i].position.right && y >= windows[i].position.top && y <= windows[i].position.bottom){ 607 if(i == 0){ 608 windows[0].passMouseEvent(x - windows[0].position.left, y - windows[0].position.top, 0); 609 if(windows.length !=0){ 610 dragEventState = true; 611 dragEventDest = windows[0]; 612 } 613 return; 614 } 615 else{ 616 setWindowToTop(windows[i]); 617 return; 618 } 619 } 620 } 621 passMouseEvent(x,y); 622 } 623 624 } 625 public void passMouseEvent(int x, int y, int state = 0){ 626 627 } 628 public void mouseWheelEvent(uint type, uint timestamp, uint windowID, uint which, int x, int y, int wX, int wY){ 629 double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY); 630 wX = to!int(wX * xR); 631 wY = to!int(wY * yR); 632 if(windows.length != 0) 633 windows[0].passScrollEvent(wX - windows[0].position.left, wY - windows[0].position.top, y, x); 634 passScrollEvent(wX,wY,x,y); 635 } 636 public void passScrollEvent(int wX, int wY, int x, int y){ 637 638 } 639 public void mouseMotionEvent(uint timestamp, uint windowID, uint which, uint state, int x, int y, int relX, int relY){ 640 double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY); 641 x = to!int(x * xR); 642 y = to!int(y * yR); 643 relX = to!int(relX * xR); 644 relY = to!int(relY * yR); 645 if(state == SDL_PRESSED && moveState){ 646 windowToMove.position.relMove(relX, relY); 647 spriteLayer.relMoveSprite(whichWindow(windowToMove), relX, relY); 648 }else if(state == SDL_PRESSED && dragEventState){ 649 dragEventDest.passMouseEvent(x - dragEventDest.position.left,y - dragEventDest.position.top,-1); 650 } 651 } 652 } 653 654 public interface IWindowHandler{ 655 //public Bitmap16Bit[wchar] getFontSet(int style); 656 public StyleSheet getStyleSheet(); 657 public void closeWindow(Window sender); 658 public void moveUpdate(Window sender); 659 public void setWindowToTop(Window sender); 660 public void addWindow(Window w); 661 } 662 663 public enum EventProperties : uint{ 664 KEYBOARD = 1, 665 MOUSE = 2, 666 SCROLL = 4 667 }