forked from adamdruppe/arsd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
simpledisplay.d
8712 lines (7272 loc) · 264 KB
/
simpledisplay.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Please note when compiling on Win64, you need to explicitly list
// `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows
// subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`
//
// On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd.
/*
FIXME:
Text layout needs a lot of work. Plain drawText is useful but too
limited. It will need some kind of text context thing which it will
update and you can pass it on and get more details out of it.
It will need a bounding box, a current cursor location that is updated
as drawing continues, and various changable facts (which can also be
changed on the painter i guess) like font, color, size, background,
etc.
We can also fetch the carat location from it somehow.
Should prolly be an overload of drawText
blink taskbar / demand attention cross platform. FlashWindow and demandAttention
WS_EX_NOACTIVATE
WS_CHILD - owner and owned vs parent and child. Does X have something similar?
full screen windows. Can just set the atom on X. Windows will be harder.
moving windows. resizing windows.
hide cursor, capture cursor, change cursor.
REMEMBER: simpledisplay does NOT have to do everything! It just needs to make
sure the pieces are there to do its job easily and make other jobs possible.
*/
/++
simpledisplay.d provides basic cross-platform GUI-related functionality,
including creating windows, drawing on them, working with the clipboard,
timers, OpenGL, and more. However, it does NOT provide high level GUI
widgets. See my minigui.d, an extension to this module, for that
functionality.
simpledisplay provides cross-platform wrapping for Windows and Linux
(and perhaps other OSes that use X11), but also does not prevent you
from using the underlying facilities if you need them. It has a goal
of working efficiently over a remote X link (at least as far as Xlib
reasonably allows.)
simpledisplay depends on [arsd.color|color.d], which should be available from the
same place where you got this file. Other than that, however, it has
very few dependencies and ones that don't come with the OS and/or the
compiler are all opt-in.
simpledisplay.d's home base is on my arsd repo on Github. The file is:
https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d
simpledisplay is basically stable. I plan to refactor the internals,
and may add new features and fix bugs, but It do not expect to
significantly change the API. It has been stable a few years already now.
Jump_list:
Don't worry, you don't have to read this whole documentation file!
Check out the [#Event-example] and [#Pong-example] to get started quickly.
The main classes you may want to create are [SimpleWindow], [Timer],
[Image], and [Sprite].
The main functions you'll want are [setClipboardText] and [getClipboardText].
There are also platform-specific functions available such as [XDisplayConnection]
and [GetAtom] for X11, among others.
See the examples and topics list below to learn more.
$(H2 About this documentation)
The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow.
Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples!
All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them.
To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example.
If you need help, email me: [email protected] or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file.
At points, I will talk about implementation details in the documentation. These are sometimes
subject to change, but nevertheless useful to understand what is really going on. You can learn
more about some of the referenced things by searching the web for info about using them from C.
You can always look at the source of simpledisplay.d too for the most authoritative source on
its specific implementation. If you disagree with how I did something, please contact me so we
can discuss it!
Examples:
$(DDOC_ANCHOR Event-example)
This program creates a window and draws events inside them as they
happen, scrolling the text in the window as needed. Run this program
and experiment to get a feel for where basic input events take place
in the library.
---
// dmd example.d simpledisplay.d color.d
import simpledisplay;
import std.conv;
void main() {
auto window = new SimpleWindow(Size(500, 500), "My D App");
int y = 0;
void addLine(string text) {
auto painter = window.draw();
if(y + painter.fontHeight >= window.height) {
painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
y -= painter.fontHeight;
}
painter.outlineColor = Color.red;
painter.fillColor = Color.black;
painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
painter.outlineColor = Color.white;
painter.drawText(Point(10, y), text);
y += painter.fontHeight;
}
window.eventLoop(1000,
() {
addLine("Timer went off!");
},
(KeyEvent event) {
addLine(to!string(event));
},
(MouseEvent event) {
addLine(to!string(event));
},
(dchar ch) {
addLine(to!string(ch));
}
);
}
---
$(DDOC_ANCHOR Pong-example)
This program creates a little Pong-like game. Player one is controlled
with the keyboard. Player two is controlled with the mouse. It demos
the pulse timer, event handling, and some basic drawing.
---
// dmd example.d simpledisplay.d color.d
import simpledisplay;
enum paddleMovementSpeed = 8;
enum paddleHeight = 48;
void main() {
auto window = new SimpleWindow(600, 400, "Pong game!");
int playerOnePosition, playerTwoPosition;
int playerOneMovement, playerTwoMovement;
int playerOneScore, playerTwoScore;
int ballX, ballY;
int ballDx, ballDy;
void serve() {
import std.random;
ballX = window.width / 2;
ballY = window.height / 2;
ballDx = uniform(-4, 4) * 3;
ballDy = uniform(-4, 4) * 3;
if(ballDx == 0)
ballDx = uniform(0, 2) == 0 ? 3 : -3;
}
serve();
window.eventLoop(50, // set a 50 ms timer pulls
// This runs once per timer pulse
delegate () {
auto painter = window.draw();
painter.clear();
// Update everyone's motion
playerOnePosition += playerOneMovement;
playerTwoPosition += playerTwoMovement;
ballX += ballDx;
ballY += ballDy;
// Bounce off the top and bottom edges of the window
if(ballY + 7 >= window.height)
ballDy = -ballDy;
if(ballY - 8 <= 0)
ballDy = -ballDy;
// Bounce off the paddle, if it is in position
if(ballX - 8 <= 16) {
if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
ballDx = -ballDx + 1; // add some speed to keep it interesting
ballDy += playerOneMovement; // and y movement based on your controls too
ballX = 24; // move it past the paddle so it doesn't wiggle inside
} else {
// Missed it
playerTwoScore ++;
serve();
}
}
if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
ballDx = -ballDx - 1;
ballDy += playerTwoMovement;
ballX = window.width - 24;
} else {
// Missed it
playerOneScore ++;
serve();
}
}
// Draw the paddles
painter.outlineColor = Color.black;
painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
// Draw the ball
painter.fillColor = Color.red;
painter.outlineColor = Color.yellow;
painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
// Draw the score
painter.outlineColor = Color.blue;
import std.conv;
painter.drawText(Point(64, 4), to!string(playerOneScore));
painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
},
delegate (KeyEvent event) {
// Player 1's controls are the arrow keys on the keyboard
if(event.key == Key.Down)
playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
if(event.key == Key.Up)
playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
},
delegate (MouseEvent event) {
// Player 2's controls are mouse movement while the left button is held down
if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
if(event.dy > 0)
playerTwoMovement = paddleMovementSpeed;
else if(event.dy < 0)
playerTwoMovement = -paddleMovementSpeed;
} else {
playerTwoMovement = 0;
}
}
);
}
---
If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too.
This program displays a pie chart. Clicking on a color will increase its share of the pie.
---
---
$(H2 Topics)
$(H3 $(DDOC_ANCHOR topic-windows) Windows)
The $(LREF SimpleWindow) class is simpledisplay's flagship feature. It represents a single
window on the user's screen.
You may create multiple windows, if the underlying platform supports it. You may check
`static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by
SimpleWindow's constructor at runtime to handle those cases.
A single running event loop will handle as many windows as needed.
setEventHandlers function
eventLoop function
draw function
title property
$(H3 $(DDOC_ANCHOR topic-event-loops) Event loops)
The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.
The most common scenario is creating a window, then calling `window.eventLoop` when setup is complete. You can pass several handlers to the `eventLoop` method right there:
---
// dmd example.d simpledisplay.d color.d
import simpledisplay;
void main() {
auto window = new SimpleWindow(200, 200);
window.eventLoop(0,
delegate (dchar) { /* got a character key press */ }
);
}
---
$(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.)
On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run.
On Linux, simpledisplay also supports my `arsd.eventloop` module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch.
It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried.
$(H3 $(DDOC_ANCHOR topic-notification-areas) Notification area (aka systray) icons)
Notification area icons are currently only implemented on X11 targets. Windows support will come when I need it (or if someone requests it and I have some time to spend on it).
$(H3 $(DDOC_ANCHOR topic-input-handling) Input handling)
There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
$(H3 $(DDOC_ANCHOR topic-2d-drawing) 2d Drawing)
To draw on your window, use the `window.draw` method. It returns a $(LREF ScreenPainter) structure with drawing methods.
Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example:
---
// dmd example.d simpledisplay.d color.d
import simpledisplay;
void main() {
auto window = new SimpleWindow(200, 200);
{ // introduce sub-scope
auto painter = window.draw(); // begin drawing
/* draw here */
painter.outlineColor = Color.red;
painter.fillColor = Color.black;
painter.drawRectangle(Point(0, 0), 200, 200);
} // end scope, calling `painter`'s destructor, drawing to the screen.
window.eventLoop(0); // handle events
}
---
Painting is done based on two color properties, a pen and a brush.
At this time, the 2d drawing does not support alpha blending. If you need that, use a 2d OpenGL context instead.
FIXME add example of 2d opengl drawing here
$(H3 $(DDOC_ANCHOR topic-3d-drawing) 3d Drawing (or 2d with OpenGL))
simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing.
Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it.
To start, you create a `SimpleWindow` with OpenGL enabled by passing the argument `OpenGlOptions.yes` to the constructor.
Important: the required import libraries are now packaged with dmd on 32 bit Windows for OpenGL support. You may download the necessary .libs from by github, put them in your dmd/windows/lib folder, then pass the `-version=with_opengl` flag to dmd when compiling to opt-in to this feature.
Next, you set `redrawOpenGlScene` to a delegate which draws your frame.
To force a redraw of the scene, call `window.redrawOpenGlSceneNow()`.
Please note that my experience with OpenGL is very out-of-date, and the bindings in simpledisplay reflect that. If you want to use more modern functions, you may have to define the bindings yourself, or import them from another module. However, I believe the OpenGL context creation done in simpledisplay will work for any version.
This example program will draw a rectangle on your window:
---
// On Windows: dmd example.d simpledisplay.d color.d -version=with_opengl
//
// When compiling a 32 bit application on Windows, be sure you
// got opengl32.lib and glu32.lib from my github and added them
// to your `dmd2\windows\lib` directory. When you update dmd,
// remember to ensure those lib files are still there afterward.
//
// Elsewhere : dmd example.d simpledisplay.d color.d
import simpledisplay;
void main() {
}
---
$(H3 $(DDOC_ANCHOR topic-images) Displaying images)
You can also load PNG images using my `png.d`.
---
// dmd example.d simpledisplay.d color.d png.d
import simpledisplay;
import arsd.png;
void main() {
auto image = Image.fromMemoryImage(readPng("image.png"));
displayImage(image);
}
---
Compile with `dmd example.d simpledisplay.d png.d`.
If you find an image file which is a valid png that `arsd.png` fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel.
$(H3 $(DDOC_ANCHOR topic-sprites) Sprites)
The $(LREF Sprite) class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link.
$(H3 $(DDOC_ANCHOR topic-clipboard) Clipboard)
The free functions $(LREF getClipboardText) and $(LREF setClipboardText) consist of simpledisplay's cross-platform clipboard support at this time.
It also has helpers for handling X-specific events.
$(H3 $(DDOC_ANCHOR topic-timers) Timers)
There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, $(LREF Timer).
The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse.
---
import simpledisplay;
void main() {
auto window = new SimpleWindow(400, 400);
// every 100 ms, it will draw a random line
// on the window.
window.eventLoop(100, {
auto painter = window.draw();
import std.random;
// random color
painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
// random line
painter.drawLine(
Point(uniform(0, window.width), uniform(0, window.height)),
Point(uniform(0, window.width), uniform(0, window.height)));
});
}
---
The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish.
The pulse timer and instances of the $(LREF Timer) class may be combined at will.
---
import simpledisplay;
void main() {
auto window = new SimpleWindow(400, 400);
auto timer = new Timer(1000, delegate {
auto painter = window.draw();
painter.clear();
});
window.eventLoop(0);
}
---
Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop.
$(H3 $(DDOC_ANCHOR topic-os-helpers) OS-specific helpers)
simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself.
See also: `xwindows.d` from my github.
$(H3 $(DDOC_ANCHOR topic-os-extension) Extending with OS-specific functionality)
`handleNativeEvent` and `handleNativeGlobalEvent`.
$(H3 $(DDOC_ANCHOR topic-integration) Integration with other libraries)
Integration with a third-party event loop is possible.
On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d.
$(H3 $(DDOC_ANCHOR topic-guis) GUI widgets)
simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you!
Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes.
Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.)
minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
$(H2 $(DDOC_ANCHOR developer-notes) Developer notes)
I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
implementation though.
The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both
suck. If I was rewriting it, I wouldn't do it that way again.
This file must not have any more required dependencies. If you need bindings, add
them right to this file. Once it gets into druntime and is there for a while, remove
bindings from here to avoid conflicts (or put them in an appropriate version block
so it continues to just work on old dmd), but wait a couple releases before making the
transition so this module remains usable with older versions of dmd.
You may have optional dependencies if needed by putting them in version blocks or
template functions. You may also extend the module with other modules with UFCS without
actually editing this - that is nice to do if you can.
Try to make functions work the same way across operating systems. I typically make
it thinly wrap Windows, then emulate that on Linux.
A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding
Phobos! So try to avoid it.
See more comments throughout the source.
I realize this file is fairly large, but over half that is just bindings at the bottom
or documentation at the top. Some of the classes are a bit big too, but hopefully easy
to understand. I suggest you jump around the source by looking for a particular
declaration you're interested in, like `class SimpleWindow` using your editor's search
function, then look at one piece at a time.
$(H2 $(DDOC_ANCHOR about-the-author) Meta)
Authors: Adam D. Ruppe with the help of others. If you need help, please email me with
[email protected] or find me on IRC. Our channel is #d on Freenode. I go by
Destructionator or adam_d_ruppe, depending on which computer I'm logged into.
I live in the eastern United States, so I will most likely not be around at night in
that US east timezone.
License: Copyright Adam D. Ruppe, 2011-2016. Released under the Boost Software License.
Building documentation: You may wish to use the `arsd.ddoc` file from my github with
building the documentation for simpledisplay yourself. It will give it a bit more style.
Simply download the arsd.ddoc file and add it to your compile command when building docs.
`dmd -c simpledisplay.d color.d -D arsd.ddoc`
<hr />
Macros:
M=<a style="font-weight: bold;" href="#$0">$0</a>
L=<a href="$1">$1</a>
TIP=<div class="tip">$0</div>
NOTE=<div class="note">$0</div>
IMPORTANT=<div class="important">$0</div>
PITFALL=<div class="pitfall">$0</div>
WARNING=<div class="warning">$0</div>
+/
module simpledisplay;
// FIXME: icons on Windows don't look quite right, I think the transparency mask is off.
// http://wiki.dlang.org/Simpledisplay.d
// FIXME: SIGINT handler is necessary to clean up shared memory handles upon ctrl+c
// see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
// Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
// but can i control the scroll lock led
// Note: if you are using Image on X, you might want to do:
/*
static if(UsingSimpledisplayX11) {
if(!Image.impl.xshmAvailable) {
// the images will use the slower XPutImage, you might
// want to consider an alternative method to get better speed
}
}
If the shared memory extension is available though, simpledisplay uses it
for a significant speed boost whenever you draw large Images.
*/
// CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing.
// WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()!
/*
Biggest FIXME:
make sure the key event numbers match between X and Windows OR provide symbolic constants on each system
clean up opengl contexts when their windows close
fix resizing the bitmaps/pixmaps
*/
// BTW on Windows:
// -L/SUBSYSTEM:WINDOWS:5.0
// to dmd will make a nice windows binary w/o a console if you want that.
/*
Stuff to add:
use multibyte functions everywhere we can
OpenGL windows
more event stuff
extremely basic windows w/ no decoration for tooltips, splash screens, etc.
resizeEvent
and make the windows non-resizable by default,
or perhaps stretched (if I can find something in X like StretchBlt)
take a screenshot function!
Pens and brushes?
Maybe a global event loop?
Mouse deltas
Key items
*/
/*
From MSDN:
You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate.
Important Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities.
*/
version(html5) {} else {
version(linux)
version = X11;
version(OSX) {
version(OSXCocoa) {}
else { version = X11; }
}
//version = OSXCocoa; // this was written by KennyTM
version(FreeBSD)
version = X11;
version(Solaris)
version = X11;
}
// these are so the static asserts don't trigger unless you want to
// add support to it for an OS
version(Windows)
version = with_timer;
version(linux)
version = with_timer;
/// If you have to get down and dirty with implementation details, this helps figure out if X is available you can `static if(UsingSimpledisplayX11) ...` more reliably than `version()` because `version` is module-local.
version(X11)
enum bool UsingSimpledisplayX11 = true;
else
enum bool UsingSimpledisplayX11 = false;
/// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.
version(Windows)
enum multipleWindowsSupported = true;
else version(X11)
enum multipleWindowsSupported = true;
else
static assert(0);
/++
After selecting a type from $(LREF WindowTypes), you may further customize
its behavior by setting one or more of these flags.
The different window types have different meanings of `normal`. If the
window type already is a good match for what you want to do, you should
just use `WindowFlags.normal`, the default, which will do the right thing
for your users.
+/
enum WindowFlags : int {
normal,
skipTaskbar,
alwaysOnTop,
alwaysOnBottom,
cannotBeActivated,
}
/++
When creating a window, you can pass a type to SimpleWindow's constructor,
then further customize the window by changing `WindowFlags`.
This list is based on the EMWH spec for X11.
$(L http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896)
+/
enum WindowTypes : int {
/// An ordinary application window.
normal,
/// A generic window without a title bar or border. You can draw on the entire area of the screen it takes up and use it as you wish. Remember that users don't really expect these though, so don't use it where a window of any other type is appropriate.
undecorated,
/*
splashScreen, /// a loading splash screen for your application
tooltip, /// A tiny window showing temporary help text or something.
notification, /// A popup bubble notification
comboBoxDropdown,
dropdownMenu,
popupMenu,
dialog,
toolbar
*/
}
private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call
private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features
/**
Set OpenGL context version to use. This has no effect on non-OpenGL windows.
You may want to change context version if you want to use advanced shaders or
other modern OpenGL techinques. This setting doesn't affect already created
windows. You may use version 2.1 as your default, which should be supported
by any box since 2006, so seems to be a reasonable choice.
Note that by default version is set to `0`, which forces SimpleDisplay to use
old context creation code without any version specified. This is the safest
way to init OpenGL, but it may not give you access to advanced features.
See available OpenGL versions here: $(L https://en.wikipedia.org/wiki/OpenGL).
*/
void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); }
/**
Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed
pipeline functions, and without "compatible" mode you won't be able to use
your old non-shader-based code with such contexts. By default SimpleDisplay
creates compatible context, so you can gradually upgrade your OpenGL code if
you want to (or leave it as is, as it should "just work").
*/
@property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; }
/++
The flagship window class.
SimpleWindow tries to make ordinary windows very easy to create and use without locking you
out of more advanced or complex features of the underlying windowing system.
For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")`
and get a suitable window to work with.
From there, you can opt into additional features, like custom resizability and OpenGL support
with the next two constructor arguments. Or, if you need even more, you can set a window type
and customization flags with the final two constructor arguments.
If none of that works for you, you can also create a window using native function calls, then
wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember,
though, if you do this, managing the window is still your own responsibility! Notably, you
will need to destroy it yourself.
+/
class SimpleWindow : CapableOfHandlingNativeEvent {
/++
This creates a window with the given options. The window will be visible and able to receive input as soon as you start your event loop. You may draw on it immediately after creating the window, without needing to wait for the event loop to start if you want.
The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them.
Params:
width = the width of the window's client area, in pixels
height = the height of the window's client area, in pixels
title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the $(LREF SimpleWindow._title) property.
opengl = $(LREF OpenGlOptions) are yes and no. If yes, it creates an OpenGL context on the window.
$(TIP On Windows, you need to compile with `-version=with_opengl` to enable `OpenGlOptions.yes`)
resizable = $(LREF Resizablity) has three options:
$(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.)
$(P `fixedSize` will not allow the user to resize the window.)
$(P `automaticallyScaleIfPossible` will allow the user to resize, but will still present the original size to the API user. The contents you draw will be scaled to the size the user chose. If this scaling is not efficient, the window will be fixed size. The `windowResized` event handler will never be called. This is the default.)
windowType = The type of window you want to make.
customizationFlags = A way to make a window without a border, always on top, skip taskbar, and more. Do not use this if one of the pre-defined $(LREF WindowTypes), given in the `windowType` argument, is a good match for what you need.
parent = the parent window, if applicable
+/
this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizablity resizable = Resizablity.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
this._width = width;
this._height = height;
this.openglMode = opengl;
this.resizability = resizable;
this.windowType = windowType;
this.customizationFlags = customizationFlags;
impl.createWindow(width, height, title is null ? "D Application" : title, opengl, parent);
}
/// Same as above, except using the `Size` struct instead of separate width and height.
this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizablity resizable = Resizablity.automaticallyScaleIfPossible) {
this(size.width, size.height, title, opengl, resizable);
}
/++
Creates a window based on the given $(LREF Image). It's client area
width and height is equal to the image. (A window's client area
is the drawable space inside; it excludes the title bar, etc.)
Windows based on images will not be resizable and do not use OpenGL.
+/
this(Image image, string title = null) {
this(image.width, image.height, title);
this.image = image;
}
/++
Wraps a native window handle with very little additional processing - notably no destruction
this is incomplete so don't use it for much right now. The purpose of this is to make native
windows created through the low level API (so you can use platform-specific options and
other details SimpleWindow does not expose) available to the event loop wrappers.
+/
this(NativeWindowHandle nativeWindow) {
version(Windows)
impl.hwnd = nativeWindow;
else version(X11)
impl.window = nativeWindow;
else static assert(0);
// FIXME: set the size correctly
_width = 1;
_height = 1;
nativeMapping[nativeWindow] = this;
CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this;
_suppressDestruction = true; // so it doesn't try to close
}
/// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example).
/// You'll have to call `close()` manually if you set this delegate.
version(X11) void delegate () closeQuery;
/// This will be called when window visibility was changed.
void delegate (bool becomesVisible) visibilityChanged;
/// This will be called when window becomes visible for the first time.
/// You can do OpenGL initialization here. Note that in X11 you can't call
/// `setAsCurrentOpenGlContext()` right after window creation, or X11 may
/// fail to send reparent and map events (hit that with proprietary NVidia drivers).
private bool _visibleForTheFirstTimeCalled;
void delegate () visibleForTheFirstTime;
/// Returns true if the window has been closed.
final @property bool closed() { return _closed; }
private bool _visible;
/// Returns true if the window is visible (mapped).
final @property bool visible() { return _visible; }
/// Closes the window. If there are no more open windows, the event loop will terminate.
void close() {
impl.closeWindow();
_closed = true;
}
/// Alias for `hidden = false`
void show() {
hidden = false;
}
/// Alias for `hidden = true`
void hide() {
hidden = true;
}
/// Hide cursor when it enters the window.
void hideCursor() {
if (!_closed) impl.hideCursor();
}
/// Don't hide cursor when it enters the window.
void showCursor() {
if (!_closed) impl.showCursor();
}
private bool _hidden;
/// Returns true if the window is hidden.
final @property bool hidden() {
return _hidden;
}
/// Shows or hides the window based on the bool argument.
final @property void hidden(bool b) {
_hidden = b;
version(Windows) {
ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW);
} else version(X11) {
if(b)
//XUnmapWindow(impl.display, impl.window);
XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display));
else
XMapWindow(impl.display, impl.window);
} else static assert(0);
}
/++
Sets your event handlers, without entering the event loop. Useful if you
have multiple windows - set the handlers on each window, then only do eventLoop on your main window.
+/
void setEventHandlers(T...)(T eventHandlers) {
// FIXME: add more events
foreach(handler; eventHandlers) {
static if(__traits(compiles, handleKeyEvent = handler)) {
handleKeyEvent = handler;
} else static if(__traits(compiles, handleCharEvent = handler)) {
handleCharEvent = handler;
} else static if(__traits(compiles, handlePulse = handler)) {
handlePulse = handler;
} else static if(__traits(compiles, handleMouseEvent = handler)) {
handleMouseEvent = handler;
} else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?");
}
}
/// The event loop automatically returns when the window is closed
/// pulseTimeout is given in milliseconds.
final int eventLoop(T...)(
long pulseTimeout, /// set to zero if you don't want a pulse.
T eventHandlers) /// delegate list like std.concurrency.receive
{
setEventHandlers(eventHandlers);
return impl.eventLoop(pulseTimeout);
}
/++
This lets you draw on the window (or its backing buffer) using basic
2D primitives.
Be sure to call this in a limited scope because your changes will not
actually appear on the window until ScreenPainter's destructor runs.
Returns: an instance of $(LREF ScreenPainter), which has the drawing methods
on it to draw on this window.
+/
ScreenPainter draw() {
return impl.getPainter();
}
// This is here to implement the interface we use for various native handlers.
NativeEventHandler getNativeEventHandler() { return handleNativeEvent; }
// maps native window handles to SimpleWindow instances, if there are any
// you shouldn't need this, but it is public in case you do in a native event handler or something
public static SimpleWindow[NativeWindowHandle] nativeMapping;
/// Width of the window's drawable client area, in pixels.
final @property int width() { return _width; }
/// Height of the window's drawable client area, in pixels.
final @property int height() { return _height; }
private int _width;
private int _height;
// HACK: making the best of some copy constructor woes with refcounting
private ScreenPainterImplementation* activeScreenPainter;
private OpenGlOptions openglMode;
private Resizablity resizability;
private WindowTypes windowType;
private int customizationFlags;
version(without_opengl) {} else {
/// Put your code in here that you want to be drawn automatically when your window is uncovered. Set a handler here *before* entering your event loop any time you pass `OpenGlOptions.yes` to the constructor. Ideally, you will set this delegate immediately after constructing the `SimpleWindow`.
void delegate() redrawOpenGlScene;
/// This will allow you to change OpenGL vsync state.
final @property void vsync (bool wait) {
if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
version(X11) {
setAsCurrentOpenGlContext();
glxSetVSync(display, impl.window, wait);
}
}
/// "Lock" this window handle, to do multithreaded synchronization. You probably won't need
/// to call this, as it's not recommended to share window between threads.
private shared int lockCount = 0;
void mtLock () {
version(X11) {
XLockDisplay(this.display);
}
}
/// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need
/// to call this, as it's not recommended to share window between threads.
void mtUnlock () {
version(X11) {
XUnlockDisplay(this.display);
}
}
/// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`.
/// Note that at least NVidia proprietary driver may segfault if you will modify texture fast
/// enough without waiting 'em to finish their frame bussiness.
bool useGLFinish = true;
// FIXME: it should schedule it for the end of the current iteration of the event loop...
/// call this to invoke your delegate. It automatically sets up the context and flips the buffer. If you need to redraw the scene in response to an event, call this.
void redrawOpenGlSceneNow() {
version(X11) if (!this._visible) return; // no need to do this if window is invisible
if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error
if(redrawOpenGlScene is null)
return;
this.mtLock();
scope(exit) this.mtUnlock();
this.setAsCurrentOpenGlContext();
redrawOpenGlScene();
this.swapOpenGlBuffers();
// at least nvidia proprietary crap segfaults on exit if you won't do this and will call glTexSubImage2D() too fast; no, `glFlush()` won't work.
if (useGLFinish) glFinish();
}