simple display (from: GUI library for D)

Adam D. Ruppe destructionator at gmail.com
Fri Apr 8 13:26:07 PDT 2011


We discussed this first in the GUI library thread, but since it
meandered so much, I decided to split off into a new subject. Much
of what I say here will be old to anyone who saw the previous thread.
There's some new stuff nearer to the bottom though.

I, with input from others, have started writing a little module
for simple uses of a display. You can write to a bitmap, display it
to a window, and handle events all in an easy way. The basics are
cross platform, but you can use native function calls too.

http://arsdnet.net/dcode/simpledisplay.d

It's still very much in progress, but I think it's now at the point
where the direction I'm taking it is clear.

First, the simplest usage:

====
import simpledisplay;

void main() {
	auto image = new Image(255, 255);

	foreach(a; 0..255)
	foreach(b; 0..255)
		image.putPixel(a, b, Color(a, b, ((a + b) % 16) * 16));

	image.display();
}
===

Compile: dmd test.d simpledisplay.d


When run, it will pop up a window with a gradient thing with little
bars through it. Press any key and the window closes, exiting the
program.

On Windows, it uses GDI functions. On other systems, it tries to
use X Windows. Linux is the only one I've personally tested, but it
should work on OSX and FreeBSD too (without changing the code).


If you are making some data, then just want to display it, this
works. But, what if you want some interactivity?

Here's a simple HSL color picker:
http://arsdnet.net/dcode/simpledisplay_test2.d

Use these keys to pick your color: q/a change hue. s/w changes S.
e/d changes L. Press ESC to exit. Your color in rgb is printed to
stdout.


Here's the relevant parts of the code:

void main() {
	auto image = new Image(255, 255);
	auto win = new SimpleWindow(image);
	win.eventLoop(0,
		(dchar c) {
			writeln("Got a char event: ", c);

			if(c == 'q')
				h += 15;
                        // ...snip...

			foreach(a; 0..255)
			foreach(b; 0..255)
				image.putPixel(a, b, fromHsl(h, s, l));

			win.image = image;
		},
		(int key) {
			writeln("Got a keydown event: ", key);
			if(key == KEY_ESCAPE) {
				auto color = fromHsl(h, s, l);
				writefln("%02x%02x%02x", color.r, color.g, color.b);
				win.close();
			}
		});
}

First, we create an image. Then, a window based on that image. At
line two, your window will show up on the screen, just like
image.display() did above. But, here, instead of blocking, it
moves on to a user specified event loop.

SimpleWindow.eventLoop was inspired by std.concurrency.receive.
The first parameter is a pulse timer amount (not yet implemented).
If you set one, you'll get one of your delegates called at the
requested interval. The idea there is to make animations or games
easy to get started.

After that comes a list of delegates. They are matched to different
events by their signature. (dchar c) {} means a key was pressed,
and it passes you the character corresponding to that key.

(int c) {} is almost certain to change, but right now it matches
to key press events, and passes the platform-specific keycode. I'm
planning to put symbolic constants in with the same name across
platform to make that easier to use, but the values themselves are
likely to be platform-specific.



When we get a key event here, the image is redrawn with a new
color. win.image = image tells it you want the image redrawn with
the new image. It's a blunt instrument, but a simple and fairly
effective one.



The advantage of this approach is you can just have fun with
those pixels and handle keys, all right there inside main() - no
need to do subclasses just for a simple program. At the same time,
you can move those event handlers around easily enough, or even
change them at runtime, simply by using named delegates and assignment.

I plan to add more event possibilities so you can actually
make this do some good work for you. Ultimately, they'll also
be an (XEvent) {} and (HWND, LPARAM, WPARAM) event for when what
I provide isn't good enough, so you can always build more off it.
It's meant to be simple, but not horribly limiting.


======


Since last time I posted, I've reorganized the code inside
simpledisplay.d quite a bit. Instead of version Windows vs version
linux, it's now Windows and X11, reflecting the fact that X is
available on many operating systems. (Including Windows, actually,
but meh).

The Color struct is now rgba, but it doesn't use a. I like the
suggestion to template on different color spaces, but haven't gotten
around to that yet.

Next, Image and SimpleWindow are done pretty differently than before.
Now, the platform specific parts are put into a separate mixin
template: NativeImageImplementation!() and NativeSimpleWindowImpl...().
The public methods forward to those.

The reason for this is three fold:

a) It makes the public code a lot easier on the eyes. It was getting
into spaghetti version territory for a bit there... now it's nice
and clean.

b) The implementations can now be moved to their own modules. I haven't
simply because a single file is easier to distribute and play with,
but the final thing probably should split it up, and now it can.

c) It should be more obvious when something isn't implemented for an
operating system, while keeping the public ddoc all in one place.
versions


Before, Image was simply a byte array in a single format, meaning
to get to the native OS format for blitting, it had to be copied. Now,
the actual format is hidden inside impl. I'll add operator overloads
or structs (I really like Nick's idea of a fast vs cropped access
point) to hide it.

In the mean time though, the putPixel function forwards to the OS
implementation, which writes right to the image format expected there.

It's still not ultimately efficient, but it's better than copying
it twice every time we make a change.

I still like the idea of a set binary format being available so
we can save as a file, but that will be made a conversion function
instead of the default use. Actually, probably some kind of forward
range. Then the file save functions can accept those ranges and
translate them into .bmp or .png files as it streams in. (probably
image.byScanLine would be pretty useful for this!)

My HSL -> RGB converter is also in simpledisplay.d. It's actually
less than 500 lines, excluding the bindings pasted in at the bottom.
Not too bad.


More information about the Digitalmars-d mailing list