arsd.simpledisplay

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 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.

Members

Classes

GlobalHotkey
class GlobalHotkey

Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing * instead of delegates, you can subclass this, and override doHandle() method.

GlobalHotkeyManager
class GlobalHotkeyManager

Global hotkey manager. It contains static methods to manage global hotkeys.

Image
class Image

Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program.

NotificationAreaIcon
class NotificationAreaIcon

Provides an icon on the system notification area (also known as the system tray).

OperatingSystemFont
class OperatingSystemFont

Represents a font loaded off the operating system or the X server.

PosixFdReader
class PosixFdReader

Lets you add files to the event loop for reading. Use at your own risk.

SimpleWindow
class SimpleWindow

The flagship window class.

Sprite
class Sprite

Sprites are optimized for fast drawing on the screen, but slow for direct pixel access. They are best for drawing a relatively unchanging image repeatedly on the screen.

Timer
class Timer

A timer that will trigger your function on a given interval.

XDisplayConnection
class XDisplayConnection

Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display*

Enums

Key
enum Key

Do not trust the numeric values as they are platform-specific. Always use the symbolic name.

ModifierState
enum ModifierState

State of keys on mouse events, especially motion.

MouseButton
enum MouseButton

The names assume a right-handed mouse. These are bitwise combined on the events that use them

MouseEventType
enum MouseEventType

Type of a MouseEvent

OpenGlOptions
enum OpenGlOptions

Determines if you want an OpenGL context created on the new window.

RasterOp
enum RasterOp

ScreenPainter operations can use different operations to combine the color with the color on screen.

Resizability
enum Resizability

When you create a SimpleWindow, you can see its resizability to be one of these via the constructor...

TextAlignment
enum TextAlignment

Alignment for $(ScreenPainter.drawText). Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or.

WindowFlags
enum WindowFlags

After selecting a type from WindowTypes, you may further customize its behavior by setting one or more of these flags.

WindowTypes
enum WindowTypes

When creating a window, you can pass a type to SimpleWindow's constructor, then further customize the window by changing WindowFlags.

Functions

arsdTtfFont
DrawableFont arsdTtfFont(in ubyte[] data, int size)

Loads a true type font using arsd.ttf. That module must be compiled in if you choose to use this function.

demandAttention
void demandAttention(SimpleWindow window, bool needs = true)

X-specific

displayImage
void displayImage(Image image, SimpleWindow win = null)

A convenience function to pop up a window displaying the image. If you pass a win, it will draw the image in it. Otherwise, it will create a window with the size of the image and run its event loop, closing when a key is pressed.

doXNextEvent
bool doXNextEvent(Display* display)

Platform-specific, you might use it when doing a custom event loop

flushGui
void flushGui()

Flushes any pending gui buffers. Necessary if you are using with_eventloop with X - flush after you create your windows but before you call loop()

getClipboardText
void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver)

this does a delegate because it is actually an async call on X... the receiver may never be called if the clipboard is empty or unavailable gets plain text from the clipboard

getPrimarySelection
void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler)
getWindowNetWmIcon
TrueColorImage getWindowNetWmIcon(Window window)

X-specific

getX11PropertyData
void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType)
getX11Selection
void getX11Selection(SimpleWindow window, void delegate(in char[]) handler)
registerHotKey
int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler)

Platform-specific for Windows. Registers a global hotkey. Returns a registration ID.

sdpyWindowClass
void sdpyWindowClass(const(char)[] v)

Set window class name for all following new SimpleWindow() calls.

sdpyWindowClass
string sdpyWindowClass()

Get current window class name.

sendSyntheticInput
void sendSyntheticInput(wstring s)

Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application)

setClipboardText
void setClipboardText(SimpleWindow clipboardOwner, string text)

copies some text to the clipboard

setOpenGLContextVersion
void setOpenGLContextVersion(ubyte hi, ubyte lo)

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.

setPrimarySelection
void setPrimarySelection(SimpleWindow window, string text)

Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later

setSecondarySelection
void setSecondarySelection(SimpleWindow window, string text)

Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later

setX11Selection
void setX11Selection(SimpleWindow window, string text)
unregisterHotKey
void unregisterHotKey(SimpleWindow window, int id)

Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey.

Interfaces

CapableOfBeingDrawnUpon
interface CapableOfBeingDrawnUpon
DrawableFont
interface DrawableFont

An interface representing a font.

Properties

GetAtom
Atom GetAtom [@property getter]

Platform specific for X11

openGLContextAllowFallback
void openGLContextAllowFallback [@property getter]

Set to true to allow creating OpenGL context with lower version than requested instead of throwing. If fallback was activated (or legacy OpenGL was requested), openGLContextFallbackActivated() will return true.

openGLContextCompatible
void openGLContextCompatible [@property getter]

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").

openGLContextFallbackActivated
bool openGLContextFallbackActivated [@property getter]

After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context.

Static variables

xfontstr
string xfontstr

This is the default font used. You might change this before doing anything else with the library if you want to try something else. Surround that in static if(UsingSimpledisplayX11) for cross-platform compatibility.

Structs

KeyEvent
struct KeyEvent

Keyboard press and release events

MouseEvent
struct MouseEvent

Listen for this on your event listeners if you are interested in mouse action.

Pen
struct Pen

This gives a few more options to drawing lines and such

ScreenPainter
struct ScreenPainter

The 2D drawing proxy. You acquire one of these with SimpleWindow.draw rather than constructing it directly. Then, it is reference counted so you can pass it at around and when the last ref goes out of scope, the buffered drawing activities are all carried out.

Variables

UsingSimpledisplayX11
enum bool UsingSimpledisplayX11

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.

mouseDoubleClickTimeout
int mouseDoubleClickTimeout

double click timeout. X only, you probably shouldn't change this.

multipleWindowsSupported
auto multipleWindowsSupported

Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception./// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception.

Detailed Description

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:

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 Win32, you can pass -L/subsystem:windows if you don't want a console to be automatically allocated.

On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd.

On Ubuntu, you might need to install X11 development libraries to successfully link.

$ 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.

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 - 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: destructionator@gmail.com 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

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.

1 // dmd example.d simpledisplay.d color.d
2 import arsd.simpledisplay;
3 import std.conv;
4 
5 void main() {
6 	auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
7 
8 	int y = 0;
9 
10 	void addLine(string text) {
11 		auto painter = window.draw();
12 
13 		if(y + painter.fontHeight >= window.height) {
14 			painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
15 			y -= painter.fontHeight;
16 		}
17 
18 		painter.outlineColor = Color.red;
19 		painter.fillColor = Color.black;
20 		painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
21 
22 		painter.outlineColor = Color.white;
23 
24 		painter.drawText(Point(10, y), text);
25 
26 		y += painter.fontHeight;
27 	}
28 
29 	window.eventLoop(1000,
30 	  () {
31 		addLine("Timer went off!");
32 	  },
33 	  (KeyEvent event) {
34 		addLine(to!string(event));
35 	  },
36 	  (MouseEvent event) {
37 		addLine(to!string(event));
38 	  },
39 	  (dchar ch) {
40 		addLine(to!string(ch));
41 	  }
42 	);
43 }

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.

Topics

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.

setEventHandlers function eventLoop function draw function title property

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:

1 // dmd example.d simpledisplay.d color.d
2 import arsd.simpledisplay;
3 void main() {
4 	auto window = new SimpleWindow(200, 200);
5 	window.eventLoop(0,
6 	  delegate (dchar) { /* got a character key press */ }
7 	);
8 }
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.

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).

Input handling

There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events.

2d Drawing

To draw on your window, use the window.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:

1 // dmd example.d simpledisplay.d color.d
2 import arsd.simpledisplay;
3 void main() {
4 	auto window = new SimpleWindow(200, 200);
5 	{ // introduce sub-scope
6 		auto painter = window.draw(); // begin drawing
7 		/* draw here */
8 		painter.outlineColor = Color.red;
9 		painter.fillColor = Color.black;
10 		painter.drawRectangle(Point(0, 0), 200, 200);
11 	} // end scope, calling `painter`'s destructor, drawing to the screen.
12 	window.eventLoop(0); // handle events
13 }

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

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 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:

1 // dmd example.d simpledisplay.d color.d
2 import arsd.simpledisplay;
3 
4 void main() {
5 
6 }

Displaying images

You can also load PNG images using my png.d.

1 // dmd example.d simpledisplay.d color.d png.d
2 import arsd.simpledisplay;
3 import arsd.png;
4 
5 void main() {
6 	auto image = Image.fromMemoryImage(readPng("image.png"));
7 	displayImage(image);
8 }

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.

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.

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.

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.

1 import arsd.simpledisplay;
2 
3 void main() {
4 	auto window = new SimpleWindow(400, 400);
5 	// every 100 ms, it will draw a random line
6 	// on the window.
7 	window.eventLoop(100, {
8 		auto painter = window.draw();
9 
10 		import std.random;
11 		// random color
12 		painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256));
13 		// random line
14 		painter.drawLine(
15 			Point(uniform(0, window.width), uniform(0, window.height)),
16 			Point(uniform(0, window.width), uniform(0, window.height)));
17 
18 	});
19 }

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.

1 import arsd.simpledisplay;
2 
3 void main() {
4 	auto window = new SimpleWindow(400, 400);
5 	auto timer = new Timer(1000, delegate {
6 		auto painter = window.draw();
7 		painter.clear();
8 	});
9 
10 	window.eventLoop(0);
11 }

Timers are currently only implemented on Windows, using SetTimer and Linux, using timerfd_create. These deliver timeout messages through your application event loop.

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.

Extending with OS-specific functionality

handleNativeEvent and handleNativeGlobalEvent.

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.

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.

Platform-specific tips and tricks

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:

1 1 ICON filename.ico
2 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"

And yourapp.exe.manifest:

1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3 <assemblyIdentity
4     version="1.0.0.0"
5     processorArchitecture="*"
6     name="CompanyName.ProductName.YourApplication"
7     type="win32"
8 />
9 <description>Your application description here.</description>
10 <dependency>
11     <dependentAssembly>
12 	<assemblyIdentity
13 	    type="win32"
14 	    name="Microsoft.Windows.Common-Controls"
15 	    version="6.0.0.0"
16 	    processorArchitecture="*"
17 	    publicKeyToken="6595b64144ccf1df"
18 	    language="*"
19 	/>
20     </dependentAssembly>
21 </dependency>
22 </assembly>

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.

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.

1 // dmd example.d simpledisplay.d color.d
2 import arsd.simpledisplay;
3 
4 enum paddleMovementSpeed = 8;
5 enum paddleHeight = 48;
6 
7 void main() {
8 	auto window = new SimpleWindow(600, 400, "Pong game!");
9 
10 	int playerOnePosition, playerTwoPosition;
11 	int playerOneMovement, playerTwoMovement;
12 	int playerOneScore, playerTwoScore;
13 
14 	int ballX, ballY;
15 	int ballDx, ballDy;
16 
17 	void serve() {
18 		import std.random;
19 
20 		ballX = window.width / 2;
21 		ballY = window.height / 2;
22 		ballDx = uniform(-4, 4) * 3;
23 		ballDy = uniform(-4, 4) * 3;
24 		if(ballDx == 0)
25 			ballDx = uniform(0, 2) == 0 ? 3 : -3;
26 	}
27 
28 	serve();
29 
30 	window.eventLoop(50, // set a 50 ms timer pulls
31 		// This runs once per timer pulse
32 		delegate () {
33 			auto painter = window.draw();
34 
35 			painter.clear();
36 
37 			// Update everyone's motion
38 			playerOnePosition += playerOneMovement;
39 			playerTwoPosition += playerTwoMovement;
40 
41 			ballX += ballDx;
42 			ballY += ballDy;
43 
44 			// Bounce off the top and bottom edges of the window
45 			if(ballY + 7 >= window.height)
46 				ballDy = -ballDy;
47 			if(ballY - 8 <= 0)
48 				ballDy = -ballDy;
49 
50 			// Bounce off the paddle, if it is in position
51 			if(ballX - 8 <= 16) {
52 				if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) {
53 					ballDx = -ballDx + 1; // add some speed to keep it interesting
54 					ballDy += playerOneMovement; // and y movement based on your controls too
55 					ballX = 24; // move it past the paddle so it doesn't wiggle inside
56 				} else {
57 					// Missed it
58 					playerTwoScore ++;
59 					serve();
60 				}
61 			}
62 
63 			if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1
64 				if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) {
65 					ballDx = -ballDx - 1;
66 					ballDy += playerTwoMovement;
67 					ballX = window.width - 24;
68 				} else {
69 					// Missed it
70 					playerOneScore ++;
71 					serve();
72 				}
73 			}
74 
75 			// Draw the paddles
76 			painter.outlineColor = Color.black;
77 			painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight));
78 			painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight));
79 
80 			// Draw the ball
81 			painter.fillColor = Color.red;
82 			painter.outlineColor = Color.yellow;
83 			painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7));
84 
85 			// Draw the score
86 			painter.outlineColor = Color.blue;
87 			import std.conv;
88 			painter.drawText(Point(64, 4), to!string(playerOneScore));
89 			painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore));
90 
91 		},
92 		delegate (KeyEvent event) {
93 			// Player 1's controls are the arrow keys on the keyboard
94 			if(event.key == Key.Down)
95 				playerOneMovement = event.pressed ? paddleMovementSpeed : 0;
96 			if(event.key == Key.Up)
97 				playerOneMovement = event.pressed ? -paddleMovementSpeed : 0;
98 
99 		},
100 		delegate (MouseEvent event) {
101 			// Player 2's controls are mouse movement while the left button is held down
102 			if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) {
103 				if(event.dy > 0)
104 					playerTwoMovement = paddleMovementSpeed;
105 				else if(event.dy < 0)
106 					playerTwoMovement = -paddleMovementSpeed;
107 			} else {
108 				playerTwoMovement = 0;
109 			}
110 		}
111 	);
112 }

This minesweeper demo shows how we can implement another classic game with simpledisplay and shows some mouse input and basic output code.

1 import arsd.simpledisplay;
2 
3 enum GameSquare {
4 	mine = 0,
5 	clear,
6 	m1, m2, m3, m4, m5, m6, m7, m8
7 }
8 
9 enum UserSquare {
10 	unknown,
11 	revealed,
12 	flagged,
13 	questioned
14 }
15 
16 enum GameState {
17 	inProgress,
18 	lose,
19 	win
20 }
21 
22 GameSquare[] board;
23 UserSquare[] userState;
24 GameState gameState;
25 int boardWidth;
26 int boardHeight;
27 
28 bool isMine(int x, int y) {
29 	if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight)
30 		return false;
31 	return board[y * boardWidth + x] == GameSquare.mine;
32 }
33 
34 GameState reveal(int x, int y) {
35 	if(board[y * boardWidth + x] == GameSquare.clear) {
36 		floodFill(userState, boardWidth, boardHeight,
37 			UserSquare.unknown, UserSquare.revealed,
38 			x, y,
39 			(x, y) {
40 				if(board[y * boardWidth + x] == GameSquare.clear)
41 					return true;
42 				else {
43 					userState[y * boardWidth + x] = UserSquare.revealed;
44 					return false;
45 				}
46 			});
47 	} else {
48 		userState[y * boardWidth + x] = UserSquare.revealed;
49 		if(isMine(x, y))
50 			return GameState.lose;
51 	}
52 
53 	foreach(state; userState) {
54 		if(state == UserSquare.unknown || state == UserSquare.questioned)
55 			return GameState.inProgress;
56 	}
57 
58 	return GameState.win;
59 }
60 
61 void initializeBoard(int width, int height, int numberOfMines) {
62 	boardWidth = width;
63 	boardHeight = height;
64 	board.length = width * height;
65 
66 	userState.length = width * height;
67 	userState[] = UserSquare.unknown; 
68 
69 	import std.algorithm, std.random, std.range;
70 
71 	board[] = GameSquare.clear;
72 
73 	foreach(minePosition; randomSample(iota(0, board.length), numberOfMines))
74 		board[minePosition] = GameSquare.mine;
75 
76 	int x;
77 	int y;
78 	foreach(idx, ref square; board) {
79 		if(square == GameSquare.clear) {
80 			int danger = 0;
81 			danger += isMine(x-1, y-1)?1:0;
82 			danger += isMine(x-1, y)?1:0;
83 			danger += isMine(x-1, y+1)?1:0;
84 			danger += isMine(x, y-1)?1:0;
85 			danger += isMine(x, y+1)?1:0;
86 			danger += isMine(x+1, y-1)?1:0;
87 			danger += isMine(x+1, y)?1:0;
88 			danger += isMine(x+1, y+1)?1:0;
89 
90 			square = cast(GameSquare) (danger + 1);
91 		}
92 
93 		x++;
94 		if(x == width) {
95 			x = 0;
96 			y++;
97 		}
98 	}
99 }
100 
101 void redraw(SimpleWindow window) {
102 	import std.conv;
103 
104 	auto painter = window.draw();
105 
106 	painter.clear();
107 
108 	final switch(gameState) with(GameState) {
109 		case inProgress:
110 			break;
111 		case win:
112 			painter.fillColor = Color.green;
113 			painter.drawRectangle(Point(0, 0), window.width, window.height);
114 			return;
115 		case lose:
116 			painter.fillColor = Color.red;
117 			painter.drawRectangle(Point(0, 0), window.width, window.height);
118 			return;
119 	}
120 
121 	int x = 0;
122 	int y = 0;
123 
124 	foreach(idx, square; board) {
125 		auto state = userState[idx];
126 
127 		final switch(state) with(UserSquare) {
128 			case unknown:
129 				painter.outlineColor = Color.black;
130 				painter.fillColor = Color(128,128,128);
131 
132 				painter.drawRectangle(
133 					Point(x * 20, y * 20),
134 					20, 20
135 				);
136 			break;
137 			case revealed:
138 				if(square == GameSquare.clear) {
139 					painter.outlineColor = Color.white;
140 					painter.fillColor = Color.white;
141 
142 					painter.drawRectangle(
143 						Point(x * 20, y * 20),
144 						20, 20
145 					);
146 				} else {
147 					painter.outlineColor = Color.black;
148 					painter.fillColor = Color.white;
149 
150 					painter.drawText(
151 						Point(x * 20, y * 20),
152 						to!string(square)[1..2],
153 						Point(x * 20 + 20, y * 20 + 20),
154 						TextAlignment.Center | TextAlignment.VerticalCenter);
155 				}
156 			break;
157 			case flagged:
158 				painter.outlineColor = Color.black;
159 				painter.fillColor = Color.red;
160 				painter.drawRectangle(
161 					Point(x * 20, y * 20),
162 					20, 20
163 				);
164 			break;
165 			case questioned:
166 				painter.outlineColor = Color.black;
167 				painter.fillColor = Color.yellow;
168 				painter.drawRectangle(
169 					Point(x * 20, y * 20),
170 					20, 20
171 				);
172 			break;
173 		}
174 
175 		x++;
176 		if(x == boardWidth) {
177 			x = 0;
178 			y++;
179 		}
180 	}
181 
182 }
183 
184 void main() {
185 	auto window = new SimpleWindow(200, 200);
186 
187 	initializeBoard(10, 10, 10);
188 
189 	redraw(window);
190 	window.eventLoop(0,
191 		delegate (MouseEvent me) {
192 			if(me.type != MouseEventType.buttonPressed)
193 				return;
194 			auto x = me.x / 20;
195 			auto y = me.y / 20;
196 			if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) {
197 				if(me.button == MouseButton.left) {
198 					gameState = reveal(x, y);
199 				} else {
200 					userState[y*boardWidth+x] = UserSquare.flagged;
201 				}
202 				redraw(window);
203 			}
204 		}
205 	);
206 }

Meta

Authors

Adam D. Ruppe with the help of others. If you need help, please email me with destructionator@gmail.com 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-2017. 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

Suggestion Box / Bug Report