forked from adamdruppe/arsd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
simpledisplay.d
23307 lines (19442 loc) · 688 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
// https://dpaste.dzfl.pl/7a77355acaec
/+
To share some stuff between two opengl threads:
windows
https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
linux
https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
+/
// Search for: FIXME: leaks if multithreaded gc
// https://freedesktop.org/wiki/Specifications/XDND/
// https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
// https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html
// https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html
// on Mac with X11: -L-L/usr/X11/lib
/+
* I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works
Progress bar in taskbar
- i can probably just set a property on the window...
it sets that prop to an integer 0 .. 100. Taskbar
deletes it or window deletes it when it is handled.
- prolly display it as a nice little line at the bottom.
from gtk:
#define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS"
#define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE"
>+ if (cardinal > 0)
>+ {
>+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
>+ xid,
>+ gdk_x11_get_xatom_by_name_for_display (display, atom_name),
>+ XA_CARDINAL, 32,
>+ PropModeReplace,
>+ (guchar *) &cardinal, 1);
>+ }
>+ else
>+ {
>+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
>+ xid,
>+ gdk_x11_get_xatom_by_name_for_display (display, atom_name));
>+ }
from Windows:
see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
interface
CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 );
auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”));
listen for msg, return TRUE
interface->SetProgressState(hwnd, TBPF_NORMAL);
interface->SetProgressValue(hwnd, 40, 100);
My new notification system.
- use a unix socket? or a x property? or a udp port?
- could of course also get on the dbus train but ugh.
- it could also reply with the info as a string for easy remote examination.
+/
/*
Event Loop would be nices:
* add on idle - runs when nothing else happens
* which can specify how long to yield for
* send messages without a recipient window
* setTimeout
* setInterval
*/
/*
Classic games I want to add:
* my tetris clone
* pac man
*/
/*
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 caret 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 (often abbreviated to "sdpy") 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.
Installation_instructions:
`simpledisplay.d` does not have any dependencies outside the
operating system and `color.d`, so it should just work most the
time, but there are a few caveats on some systems:
On Win32, you can pass `-L/subsystem:windows` if you don't want a
console to be automatically allocated.
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`.
If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`;
note the "w".
I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you,
but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi).
See [EnableWindowsSubsystem] for more information.
$(PITFALL
With the Windows subsystem, there is no console, so standard writeln will throw!
You can use [sdpyPrintDebugString] instead of stdio writeln instead which will
create a console as needed.
)
On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command.
On Ubuntu, you might need to install X11 development libraries to
successfully link.
$(CONSOLE
$ sudo apt-get install libglc-dev
$ sudo apt-get install libx11-dev
)
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.
$(WARNING
There should only be one GUI thread per application,
and all windows should be created in it and your
event loop should run there.
To do otherwise is undefined behavior and has no
cross platform guarantees.
)
$(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!
$(H2 Using with fibers)
simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor).
$(H2 Topics)
$(H3 $(ID topic-windows) Windows)
The [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.
$(H3 $(ID 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 [SimpleWindow.eventLoop|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 arsd.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 (deprecated) [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.
You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway.
$(H3 $(ID topic-notification-areas) Notification area (aka systray) icons)
Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like.
See the [NotificationAreaIcon] class.
$(H3 $(ID topic-input-handling) Input handling)
There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.
See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent].
$(H3 $(ID topic-2d-drawing) 2d Drawing)
To draw on your window, use the [SimpleWindow.draw] method. It returns a [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 arsd.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, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead.
FIXME Add example of 2d opengl drawing here.
$(H3 $(ID 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.
Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame.
To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon].
simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program.
This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color:
---
import arsd.simpledisplay;
void main() {
auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing);
float otherColor = 0.0;
float colorDelta = 0.05;
window.redrawOpenGlScene = delegate() {
glLoadIdentity();
glBegin(GL_QUADS);
glColor3f(1.0, otherColor, 0);
glVertex3f(-0.8, -0.8, 0);
glColor3f(1.0, otherColor, 1.0);
glVertex3f(0.8, -0.8, 0);
glColor3f(0, 1.0, otherColor);
glVertex3f(0.8, 0.8, 0);
glColor3f(otherColor, 0, 1.0);
glVertex3f(-0.8, 0.8, 0);
glEnd();
};
window.eventLoop(50, () {
otherColor += colorDelta;
if(otherColor > 1.0) {
otherColor = 1.0;
colorDelta = -0.05;
}
if(otherColor < 0) {
otherColor = 0;
colorDelta = 0.05;
}
// at the end of the timer, we have to request a redraw
// or we won't see the changes.
window.redrawOpenGlSceneSoon();
});
}
---
My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow].
$(H3 $(ID topic-modern-opengl) Modern OpenGL)
simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too.
This example program shows how you can set up a shader to draw a rectangle:
---
import arsd.simpledisplay;
// based on https://learnopengl.com/Getting-started/Hello-Triangle
void main() {
// First thing we do, before creating the window, is declare what version we want.
setOpenGLContextVersion(3, 3);
// turning off legacy compat is required to use version 3.3 and newer
openGLContextCompatible = false;
uint VAO;
OpenGlShader shader;
// then we can create the window.
auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
// additional setup needs to be done when it is visible, simpledisplay offers a property
// for exactly that:
window.visibleForTheFirstTime = delegate() {
// now with the window loaded, we can start loading the modern opengl functions.
// you MUST set the context first.
window.setAsCurrentOpenGlContext;
// then load the remainder of the library
gl3.loadDynamicLibrary();
// now you can create the shaders, etc.
shader = new OpenGlShader(
OpenGlShader.Source(GL_VERTEX_SHADER, `
#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
`),
OpenGlShader.Source(GL_FRAGMENT_SHADER, `
#version 330 core
out vec4 FragColor;
uniform vec4 mycolor;
void main() {
FragColor = mycolor;
}
`),
);
// and do whatever other setup you want.
float[] vertices = [
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
];
uint[] indices = [ // note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
];
uint VBO, EBO;
glGenVertexArrays(1, &VAO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
glEnableVertexAttribArray(0);
// the library will set the initial viewport and trigger our first draw,
// so these next two lines are NOT needed. they are just here as comments
// to show what would happen next.
// glViewport(0, 0, window.width, window.height);
// window.redrawOpenGlSceneNow();
};
// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
// it is our render method.
window.redrawOpenGlScene = delegate() {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shader.shaderProgram);
// the shader helper class has methods to set uniforms too
shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
};
window.eventLoop(0);
}
---
This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread.
$(H3 $(ID vulkan) Vulkan)
See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings:
https://github.com/adamdruppe/VulkanizeDSdpy
https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo
$(H3 $(ID topic-images) Displaying images)
You can also load PNG images using [arsd.png].
---
// dmd example.d simpledisplay.d color.d png.d
import arsd.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 $(ID topic-sprites) Sprites)
The [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.
[Sprite] is also the only facility that currently supports alpha blending without using OpenGL .
$(H3 $(ID topic-clipboard) Clipboard)
The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time.
It also has helpers for handling X-specific events.
$(H3 $(ID topic-dnd) Drag and Drop)
See [enableDragAndDrop] and [draggable].
$(H3 $(ID 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, [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 arsd.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 [Timer] class may be combined at will.
---
import arsd.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 $(ID 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 $(ID topic-os-extension) Extending with OS-specific functionality)
`handleNativeEvent` and `handleNativeGlobalEvent`.
$(H3 $(ID 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 $(ID 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 Platform-specific tips and tricks)
X_tips:
On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release.
$(H4 apitrace)
Out of the box, simpledisplay might not work as expected in combination with
[apitrace](https://apitrace.github.io).
However it can be instructed to specifically load the GL/GLX wrapper libraries provided by apitrace instead of
the system libraries. This should restore the lost functionality.
$(NUMBERED_LIST
* Compile with `-version=apitrace`.
* When launching such a simpledisplay app, it must be able to locate the apitrace wrapper libraries.
* Running the app will generate an apitrace trace file.
It should print a log message similar to "apitrace: loaded into /directory" during startup.
)
There are multiple ways to enable a simpledisplay app to locate the wrapper libraries.
One way to achieved this is by pointing the `LD_LIBRARY_PATH` environment variable to the directory containing
those wrappers.
```sh
LD_LIBRARY_PATH=/path/to/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
# e.g.
LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp
```
Alternatively, the simpledisplay app can also be launched via $(I apitrace).
```sh
apitrace trace -a gl ./myapp
```
Another way that seems to work is to preload `glxtrace.so` through `LD_PRELOAD`.
```sh
LD_PRELOAD=/path/to/apitrace/wrappers/glxtrace.so ./myapp
```
Windows_tips:
You can add icons or manifest files to your exe using a resource file.
To create a Windows .ico file, use the gimp or something. I'll write a helper
program later.
Create `yourapp.rc`:
```rc
1 ICON filename.ico
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
```
And `yourapp.exe.manifest`:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="CompanyName.ProductName.YourApplication"
type="win32"
/>
<description>Your application description here.</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style -->
<!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text -->
<!-- to render crisply in DPI-unaware contexts -->
<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
</windowsSettings>
</application>
</assembly>
```
You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`.
Doing this lets you opt into various new things since Windows XP.
See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests
$(H2 Tips)
$(H3 Name conflicts)
simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import:
---
static import sdpy = arsd.simpledisplay;
import arsd.simpledisplay : SimpleWindow;
void main() {
auto window = new SimpleWindow();
sdpy.EventLoop.get.run();
}
---
$(H2 $(ID 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.
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 and you can
ping me, adam_d_ruppe, and I'll usually see it if I'm around.
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-2021. Released under the Boost Software License.
Building documentation: use my adrdox generator, `dub run adrdox`.
Examples:
$(DIV $(ID Event-example))
$(H3 $(ID event-example) 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 arsd.simpledisplay;
import std.conv;
void main() {
auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
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));
}
);
}
---
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.
$(COMMENT
This program displays a pie chart. Clicking on a color will increase its share of the pie.
---
---
)
History:
Initial release in April 2011.
simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`.
On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement.
On October 5, 2024, apitrace support was added for Linux targets.
+/
module arsd.simpledisplay;
import arsd.core;
// FIXME: tetris demo
// FIXME: space invaders demo
// FIXME: asteroids demo
/++ $(ID Pong-example)
$(H3 Pong)
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.
+/
version(demos)
unittest {
// dmd example.d simpledisplay.d color.d
import arsd.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;
}
}
);
}
}
/++ $(H3 $(ID example-minesweeper) Minesweeper)
This minesweeper demo shows how we can implement another classic
game with simpledisplay and shows some mouse input and basic output
code.
+/
version(demos)
unittest {
import arsd.simpledisplay;
enum GameSquare {
mine = 0,
clear,
m1, m2, m3, m4, m5, m6, m7, m8
}
enum UserSquare {
unknown,
revealed,
flagged,
questioned
}
enum GameState {
inProgress,
lose,
win
}
GameSquare[] board;
UserSquare[] userState;
GameState gameState;
int boardWidth;
int boardHeight;
bool isMine(int x, int y) {
if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
return false;
return board[y * boardWidth + x] == GameSquare.mine;
}
GameState reveal(int x, int y) {
if(board[y * boardWidth + x] == GameSquare.clear) {
floodFill(userState, boardWidth, boardHeight,
UserSquare.unknown, UserSquare.revealed,
x, y,
(x, y) {
if(board[y * boardWidth + x] == GameSquare.clear)
return true;
else {
userState[y * boardWidth + x] = UserSquare.revealed;
return false;
}
});
} else {
userState[y * boardWidth + x] = UserSquare.revealed;