arsd.simpledisplay

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

Public Imports

iv.glbinds
public import iv.glbinds;
Undocumented in source.
arsd.color
public import arsd.color;
Undocumented in source.

Members

Classes

AlphaSprite
class AlphaSprite
Undocumented in source.
ConicalGradient
class ConicalGradient

A conical gradient goes from color to color around a circumference from a center point.

ExperimentalTextComponent2
class ExperimentalTextComponent2
Undocumented in source.
FilesDropHandler
class FilesDropHandler

A simple handler for making your window accept drops of files, issued to you as file names.

GenericDropHandlerBase
class GenericDropHandlerBase

A generic helper base class for making a drop handler with a preference list of custom types. This is the base for TextDropHandler and FilesDropHandler and you can use it for your own droppers too.

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.

Gradient
class Gradient

Represents a display-side gradient pseudo-image. Actually construct it with LinearGradient, RadialGradient, or ConicalGradient.

Image
class Image

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

LinearGradient
class LinearGradient

Creates a linear gradient between p1 and p2.

MouseCursor
class MouseCursor

Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). See GenericCursor.

NotificationAreaIcon
class NotificationAreaIcon
Undocumented in source.
OGL
class OGL

A static container of experimental types and value constructors for opengl 3+ shaders.

OpenGlShader
class OpenGlShader
Undocumented in source.
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.

RadialGradient
class RadialGradient

A radial gradient goes from color to color based on distance from the center. It is like rings of color.

SimpleWindow
class SimpleWindow

The flagship window class.

Sprite
class Sprite
Undocumented in source.
TextDropHandler
class TextDropHandler

A simple handler for making your window accept drops of plain text.

Timer
class Timer

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

WindowsHandleReader
class WindowsHandleReader

Lets you add HANDLEs to the event loop. Not meant to be used for async I/O per se, but for other handles (it can only handle a few handles at a time.) Only works on certain types of handles! See: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjectsex

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

BlockingMode
enum BlockingMode

Blocking mode for event loop calls associated with a window instance.

DragAndDropAction
enum DragAndDropAction
GenericCursorType
enum GenericCursorType

Note that there is no exact appearance guaranteed for any of these items; it may change appearance on different operating systems or future simpledisplay versions.

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(ubyte[] data, int size)

Loads a true type font using arsd.ttf that can be drawn as images on windows through a ScreenPainter. That module must be compiled in if you choose to use this function.

customScalingFactorForMonitor
float customScalingFactorForMonitor(int monitorNumber)

Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"].

demandAttention
void demandAttention(SimpleWindow window, bool needs)
void demandAttention(Window window, bool needs)

X-specific. Use SimpleWindow.requestAttention instead for most cases.

displayImage
void displayImage(Image image, SimpleWindow win, BlockingMode bm)

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.

doDragDrop
int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action)
This is not yet stable and may break in future versions without notice.
doXNextEvent
bool doXNextEvent(Display* display)

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

draggable
DraggableData draggable(string s)
This is not yet stable and may break in future versions without notice.
enableDragAndDrop
void enableDragAndDrop(SimpleWindow window, DropHandler handler)
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 arsd.eventloop.loop.

getAtomName
string getAtomName(Atom atom, Display* display)

Platform-specific for X11 - gets atom names as a string.

getClipboardImage
void getClipboardImage(SimpleWindow clipboardOwner, void delegate(MemoryImage) 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 image from the clipboard.

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.

getDpi
float[2] getDpi()

Returns the logical DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor.

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)
getX11Selection
void getX11Selection(SimpleWindow window, void delegate(in char[]) handler, Time timestamp)
getX11Selection
void getX11Selection(SimpleWindow window, void delegate(MemoryImage) handler)

Gets the image on the clipboard, if there is one. Added July 2020.

guiThreadExists
bool guiThreadExists()

Returns true if a gui thread exists, that is, a thread running the simpledisplay.d event loop. All windows must be exclusively created and managed by a single thread.

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

Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See GlobalHotkeyManager for Linux. Maybe some day I will merge these.

runInGuiThread
bool runInGuiThread(void delegate() dg)

Runs the given code in the GUI thread when its event loop is available, blocking until it completes. This allows you to create and manipulate windows from another thread without invoking undefined behavior.

sdpyPrintDebugString
void sdpyPrintDebugString(T t)

Function to help temporarily print debugging info. It will bypass any stdout/err redirection and go to the controlling tty or console (attaching to the parent and/or allocating one as needed on Windows. Please note it may overwrite output from other programs in the parent and the allocated one will not survive if your program crashes. Use the fileOverride to print to a log file instead if you are in one of those situations).

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)

See SyntheticInput.sendSyntheticInput instead for cross-platform applications.

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, void delegate() after)

The after delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now! (note to self july 2020... why did i do that?!)

thisThreadRunningGui
bool thisThreadRunningGui()

Returns true if this thread is either running or set to be running the simpledisplay.d gui core event loop because it owns windows.

transparencyMaskFromMemoryImage
Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window)

Call XFreePixmap on the return value.

trapXErrors
XErrorEvent[] trapXErrors(void delegate() dg)

Platform-specific for X11. Traps errors for the duration of dg. Avoid calling this from inside a call to this.

trueColorImageFromNativeHandle
TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height, int x, int y)

Implementation used by SimpleWindow.takeScreenshot.

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
CapableOfHandlingNativeEvent
interface CapableOfHandlingNativeEvent

Used internal to dispatch events to various classes.

DraggableData
interface DraggableData

Interface to describe data being dragged. See also draggable helper function.

DrawableFont
interface DrawableFont

An interface representing a font that is drawn with custom facilities.

DropHandler
interface DropHandler
This is not yet stable and may break in future versions without notice.
MeasurableFont
interface MeasurableFont

Interface with the common functionality for font measurements between OperatingSystemFont and DrawableFont.

Mixin templates

EnableWindowsSubsystem
mixintemplate EnableWindowsSubsystem()

Adds the necessary pragmas to your application to use the Windows gui subsystem. If you mix this in above your main function, you no longer need to use the linker flags explicitly. It does the necessary version blocks for various compilers and runtimes.

ExperimentalTextComponent
mixintemplate ExperimentalTextComponent()
Undocumented in source.
NativeScreenPainterImplementation
mixintemplate NativeScreenPainterImplementation()
Undocumented in source.
NativeScreenPainterImplementation
mixintemplate NativeScreenPainterImplementation()
Undocumented in source.
NativeSimpleWindowImplementation
mixintemplate NativeSimpleWindowImplementation()
Undocumented in source.

Properties

ApplicationName
string ApplicationName [@property getter]

Sets the application name.

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

gluSuccessfullyLoaded
bool gluSuccessfullyLoaded;
justCommunication
SimpleWindow justCommunication;

Magic pseudo-window for just posting events to a global queue.

librariesSuccessfullyLoaded
bool librariesSuccessfullyLoaded;
openGlLibrariesSuccessfullyLoaded
bool openGlLibrariesSuccessfullyLoaded;
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

DropPackage
struct DropPackage

An opaque structure representing dropped data. It contains private, platform-specific data that your drop function should simply forward.

EventLoop
struct EventLoop

If you want to get more control over the event loop, you can use this.

EventLoopImpl
struct EventLoopImpl
Undocumented in source.
GenericCursor
struct GenericCursor

You get one by GenericCursor.SomeTime. See GenericCursorType for a list of types.

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.

SyntheticInput
struct SyntheticInput

Allows for sending synthetic input to the X server via the Xtst extension or on Windows using SendInput.

WCharzBuffer
struct WCharzBuffer
Undocumented in source.

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.

mouseDoubleClickTimeout
int mouseDoubleClickTimeout;

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

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:

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.

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.

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

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.

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!

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

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.

Event loops

The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries.

The most common scenario is creating a window, then calling window.eventLoop when setup is complete. You can pass several handlers to the eventLoop method right there:

// dmd example.d simpledisplay.d color.d
import arsd.simpledisplay;
void main() {
	auto window = new SimpleWindow(200, 200);
	window.eventLoop(0,
	  delegate (dchar) { /* got a character key press */ }
	);
}
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.get.run, though since it will automatically terminate when there are no open windows, you will want to have one anyway.

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.

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.

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.

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() or to queue a redraw after processing the next batch of pending events, use 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.

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:

1 module opengl3test;
2 import arsd.simpledisplay;
3 
4 // based on https://learnopengl.com/Getting-started/Hello-Triangle
5 
6 void main() {
7 	// First thing we do, before creating the window, is declare what version we want.
8 	setOpenGLContextVersion(3, 3);
9 	// turning off legacy compat is required to use version 3.3 and newer
10 	openGLContextCompatible = false;
11 
12 	uint VAO;
13 	OpenGlShader shader;
14 
15 	// then we can create the window.
16 	auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing);
17 
18 	// additional setup needs to be done when it is visible, simpledisplay offers a property
19 	// for exactly that:
20 	window.visibleForTheFirstTime = delegate() {
21 		// now with the window loaded, we can start loading the modern opengl functions.
22 
23 		// you MUST set the context first.
24 		window.setAsCurrentOpenGlContext;
25 		// then load the remainder of the library
26 		gl3.loadDynamicLibrary();
27 
28 		// now you can create the shaders, etc.
29 		shader = new OpenGlShader(
30 			OpenGlShader.Source(GL_VERTEX_SHADER, `
31 				#version 330 core
32 				layout (location = 0) in vec3 aPos;
33 				void main() {
34 					gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
35 				}
36 			`),
37 			OpenGlShader.Source(GL_FRAGMENT_SHADER, `
38 				#version 330 core
39 				out vec4 FragColor;
40 				uniform vec4 mycolor;
41 				void main() {
42 					FragColor = mycolor;
43 				}
44 			`),
45 		);
46 
47 		// and do whatever other setup you want.
48 
49 		float[] vertices = [
50 			0.5f,  0.5f, 0.0f,  // top right
51 			0.5f, -0.5f, 0.0f,  // bottom right
52 			-0.5f, -0.5f, 0.0f,  // bottom left
53 			-0.5f,  0.5f, 0.0f   // top left 
54 		];
55 		uint[] indices = [  // note that we start from 0!
56 			0, 1, 3,  // first Triangle
57 			1, 2, 3   // second Triangle
58 		];
59 		uint VBO, EBO;
60 		glGenVertexArrays(1, &VAO);
61 		// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
62 		glBindVertexArray(VAO);
63 
64 		glGenBuffers(1, &VBO);
65 		glGenBuffers(1, &EBO);
66 
67 		glBindBuffer(GL_ARRAY_BUFFER, VBO);
68 		glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
69 
70 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
71 		glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
72 
73 		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
74 		glEnableVertexAttribArray(0);
75 
76 		// the library will set the initial viewport and trigger our first draw,
77 		// so these next two lines are NOT needed. they are just here as comments
78 		// to show what would happen next.
79 
80 		// glViewport(0, 0, window.width, window.height);
81 		// window.redrawOpenGlSceneNow();
82 	};
83 
84 	// this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;`
85 	// it is our render method.
86 	window.redrawOpenGlScene = delegate() {
87 		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
88 		glClear(GL_COLOR_BUFFER_BIT);
89 
90 		glUseProgram(shader.shaderProgram);
91 
92 		// the shader helper class has methods to set uniforms too
93 		shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0);
94 
95 		glBindVertexArray(VAO);
96 		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null);
97 	};
98 
99 	window.eventLoop(0);
100 }

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.

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.

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 .

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.

Drag and Drop

See enableDragAndDrop and draggable.

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.

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

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.

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:

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

Tips

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();
}

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.

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.

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

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 }

Minesweeper

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