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.

Destructor

A destructor is present on this object, but not explicitly documented in the source.

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.

NotificationAreaIcon
class NotificationAreaIcon

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

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.

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.

OpenGlOptions
enum OpenGlOptions

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

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

addEventListener
uint addEventListener(void delegate(ET) dg)

Add listener for custom event. Can be used like this:

beep
void beep()

Emit a beep to get user's attention.

close
void close()

Closes the window. If there are no more open windows, the event loop will terminate.

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

X-specific

doXNextEvent
bool doXNextEvent(Display* display)

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

draw
ScreenPainter draw()

This lets you draw on the window (or its backing buffer) using basic 2D primitives.

drawEllipse
void drawEllipse(int x1, int y1, int x2, int y2)

Arguments are the points of the bounding rectangle

eventLoop
int eventLoop(long pulseTimeout, T eventHandlers)

The event loop automatically returns when the window is closed pulseTimeout is given in milliseconds. If pulseTimeout == 0, no pulse timer is created. The event loop will block until an event arrives or the pulse timer goes off.

focus
void focus()

Sets the input focus to this window.

getAtomName
string getAtomName(Atom atom, Display* display)

Platform specific for X11 - gets atom names as a string

getPrimarySelection
void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler)
getRealTitle
string getRealTitle()

Get the title as set by the window manager. May not match what you attempted to set.

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)
hide
void hide()

Alias for hidden = true

hideCursor
void hideCursor()

Hide cursor when it enters the window.

move
void move(int x, int y)
void move(Point p)

Move window.

moveResize
void moveResize(int x, int y, int w, int h)

Move and resize window (this can be faster and more visually pleasant than doing it separately).

mtLock
void mtLock()

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

mtUnlock
void mtUnlock()

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

opacity
void opacity(double opacity)

Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation.

postEvent
bool postEvent(ET evt, bool replace = false)

Post event to queue. It is safe to call this from non-UI threads. if replace is true, replace all existing events typed ET with the new one (if evt is empty, remove 'em all) Returns true if event was queued. Always returns false if evt is null.

postTimeout
bool postTimeout(ET evt, uint timeoutmsecs, bool replace = false)

Post event to queue. It is safe to call this from non-UI threads. If timeoutmsecs is greater than zero, the event will be delayed for at least timeoutmsecs milliseconds. if replace is true, replace all existing events typed ET with the new one (if evt is empty, remove 'em all) Returns true if event was queued. Always returns false if evt is null.

redrawOpenGlSceneNow
void redrawOpenGlSceneNow()

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.

registerHotKey
int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler)

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

releaseCurrentOpenGlContext
bool releaseCurrentOpenGlContext()

Releases OpenGL context, so it can be reused in, for example, different thread. This is only valid if you passed OpenGlOptions.yes to the constructor. This doesn't throw, returning success flag instead.

releaseInputGrab
void releaseInputGrab()

Releases the grab acquired by grabInput.

removeEventListener
void removeEventListener(uint id)

Remove event listener. It is safe to pass invalid event id here.

Don't use this method in object destructors!
requestAttention
void requestAttention()

Requests attention from the user for this window.

resize
void resize(int w, int h)

Resize window.

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

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

sdpyWindowClass
string sdpyWindowClass()

Get current window class name.

sendDummyEvent
void sendDummyEvent()

Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example.

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)

setAsCurrentOpenGlContext
void setAsCurrentOpenGlContext()

Makes all gl* functions target this window until changed. This is only valid if you passed OpenGlOptions.yes to the constructor.

setAsCurrentOpenGlContextNT
bool setAsCurrentOpenGlContextNT()

Makes all gl* functions target this window until changed. This is only valid if you passed OpenGlOptions.yes to the constructor. This doesn't throw, returning success flag instead.

setEventHandlers
void setEventHandlers(T eventHandlers)

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.

setMaxSize
void setMaxSize(int maxwidth, int maxheight)

Set window maximal size.

setMinSize
void setMinSize(int minwidth, int minheight)

Set window minimal size.

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

setResizeGranularity
void setResizeGranularity(int granx, int grany)

Set window resize step (window size will be changed with the given granularity on supported platforms). Currently only supported on X11.

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)
show
void show()

Alias for hidden = false

showCursor
void showCursor()

Don't hide cursor when it enters the window.

swapOpenGlBuffers
void swapOpenGlBuffers()

simpledisplay always uses double buffering, usually automatically. This manually swaps the OpenGL buffers.

transparencyMaskFromMemoryImage
Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window)

call XFreePixmap on the return value

warpMouse
bool warpMouse(int x, int y)

"Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag.

Manifest constants

multipleWindowsSupported
enum multipleWindowsSupported;

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

Mixins

impl
mixin NativeSimpleWindowImplementation!() impl

The native implementation is available, but you shouldn't use it unless you are familiar with the underlying operating system, don't mind depending on it, and know simpledisplay.d's internals too. It is virtually private; you can hopefully do what you need to do with handleNativeEvent instead.

Properties

GetAtom
Atom GetAtom [@property getter]

Platform specific for X11

closed
bool closed [@property getter]

Returns true if the window has been closed.

eventQueueEmpty
bool eventQueueEmpty [@property getter]

Is our custom event queue empty? Can be used in simple cases to prevent "spamming" window with events it can't cope with. It is safe to call this from non-UI threads.

eventQueued
bool eventQueued [@property getter]

Does our custom event queue contains at least one with the given type? Can be used in simple cases to prevent "spamming" window with events it can't cope with. It is safe to call this from non-UI threads.

focused
bool focused [@property getter]

Returns true if the window is focused.

height
int height [@property getter]

Height of the window's drawable client area, in pixels.

hidden
bool hidden [@property getter]

Returns true if the window is hidden.

hidden
void hidden [@property getter]

Shows or hides the window based on the bool argument.

icon
void icon [@property getter]

Set the icon that is seen in the title bar or taskbar, etc., for the user.

image
void image [@property getter]

Draws an image on the window. This is meant to provide quick look of a static image generated elsewhere.

isOpenGL
bool isOpenGL [@property getter]

true if OpenGL was initialized for this window.

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.

title
void title [@property getter]

Set the window title, which is visible on the window manager title bar, operating system taskbar, etc.

title
string title [@property getter]

Gets the title

visible
bool visible [@property getter]

Returns true if the window is visible (mapped).

vsync
void vsync [@property getter]

This will allow you to change OpenGL vsync state.

width
int width [@property getter]

Width of the window's drawable client area, in pixels.

Static variables

handleNativeGlobalEvent
NativeEventHandler handleNativeGlobalEvent;

This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. If you used to use handleNativeEvent depending on it being static, just change it to use this instead and it will work the same way.

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.

Variables

UsingSimpledisplayCocoa
enum bool UsingSimpledisplayCocoa;

If you have to get down and dirty with implementation details, this helps figure out if Cocoa is available you can static if(UsingSimpledisplayCocoa) ... more reliably than version() because version is module-local.

UsingSimpledisplayWindows
enum bool UsingSimpledisplayWindows;

If you have to get down and dirty with implementation details, this helps figure out if Windows is available you can static if(UsingSimpledisplayWindows) ... more reliably than version() because version is module-local.

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.

closeQuery
void delegate() closeQuery;

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.

handleCharEvent
void delegate(dchar c) handleCharEvent;

Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers.

handleExpose
bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose;

Called when Expose event comes. See Xlib manual to understand the arguments. * Return false if you want Simpledisplay to copy backbuffer, or true if you did it yourself. * You will probably never need to setup this handler, it is for very low-level stuff. * * WARNING! Xlib is multithread-locked when this handles is called!

handleKeyEvent
void delegate(KeyEvent ke) handleKeyEvent;

What follows are the event handlers. These are set automatically by the eventLoop function, but are still public so you can change them later. wasPressed == true means key down. false == key up. Handles a low-level keyboard event. Settable through setEventHandlers.

handleMouseEvent
void delegate(MouseEvent) handleMouseEvent;

Mouse event handler. Settable through setEventHandlers.

handleNativeEvent
NativeEventHandler handleNativeEvent;

Platform specific - handle any native messages this window gets. * * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it.

handlePulse
void delegate() handlePulse;

Handles a timer pulse. Settable through setEventHandlers.

mouseDoubleClickTimeout
int mouseDoubleClickTimeout;

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

onClosing
void delegate() onClosing;

Called inside close() method. Our window is still alive, and we can free various resources. * Sometimes it is easier to setup the delegate instead of subclassing.

onDestroyed
void delegate() onDestroyed;

Called when we received destroy notification. At this stage we cannot do much with our window * (as it is already dead, and it's native handle cannot be used), but we still can do some * last minute cleanup.

onFocusChange
void delegate(bool) onFocusChange;

Called when the focus changes, param is if we have it (true) or are losing it (false).

paintingFinished
void delegate() paintingFinished;

use to redraw child widgets if you use system apis to add stuff

redrawOpenGlScene
void delegate() redrawOpenGlScene;

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.

useGLFinish
bool useGLFinish;

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.

visibilityChanged
void delegate(bool becomesVisible) visibilityChanged;

This will be called when window visibility was changed.

windowResized
void delegate(int width, int height) windowResized;

handle a resize, after it happens. You must construct the window with Resizability.allowResizing for this to ever happen.

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. If using the Cocoa implementation on Mac, you need to pass -L-framework -LCocoa 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 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.

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

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 window.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, 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 arsd.png.

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 ICON filename.ico
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