View mode: basic / threaded / horizontal-split · Log in · Help
April 08, 2011
simple display (from: GUI library for D)
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.
April 08, 2011
Re: simple display (from: GUI library for D)
Adam D. Ruppe:

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

I'd like something like this in Phobos, or if this is not possible, a cabal-install-like command away. I sometimes use Python PyGame or Processing.


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

Instead of:
image.putPixel(a, b, Color(a, b, ((a + b) % 16) * 16));
I suggest something simpler like:
image[a, b] = std.typecons.tuple(a, b, ((a + b) % 16) * 16);

Instead of:
image.display();
I suggest something like this, to swap the two image buffers:
image.flip();

Plus maybe some image.event(); reader with callbacks...

Bye,
bearophile
April 08, 2011
Re: simple display (from: GUI library for D)
simpledisplay.d - line 267:
int lol, wtf;

lol, wtf? :p

Btw, an exception will be thrown on unhandled key events, e.g. just hit CTRL:
object.Exception@.\simpledisplay.d(299): GetMessage failed

Not a good thing if you use shortcut keys to move windows around.
April 08, 2011
Re: simple display (from: GUI library for D)
== Quote from Adam D. Ruppe (destructionator@gmail.com)'s article
> 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.

Can it render text?  If so, I'll probably port Plot2kill to it if it becomes part
of Phobos at some point.  It would be kind of cool to have minimal Plot2kill
functionality with zero third-party dependencies.  If it's so basic that it can't
even do text rendering, then I have my doubts about whether there are very many
use cases for something so simple.
April 09, 2011
Re: simple display (from: GUI library for D)
bearophile wrote:
> I'd like something like this in Phobos

Me too. It's still pretty far from good enough right now, but
that's where I want ultimately want it.

My only concern is I don't want Phobos to depend on Xlib unless
the module is actually imported. I don't think it will be a problem,
but if it means a hello world won't run on a text only machine, that
won't be ok.

> I suggest something simpler like:

Yeah, moving to opIndex is something I plan to do. But, I don't
really like using a tuple for this - a regular struct makes it
a little clearer what's going on, and can be made more efficient.
(In the other thread, Nick suggested making it a simple uint
internally, using property functions to emulate other outside
faces. I like that.)

A struct might also have accessors with different color types
too.

> I suggest something like this, to swap the two image buffers

That's not really what you're doing there - display actually
pops up a new window and waits for the user to close it.

> Plus maybe some image.event(); reader with callbacks...

What would it do?
April 09, 2011
Re: simple display (from: GUI library for D)
dsimcha wrote:
> Can it render text?

Not yet, but it's on the list. Anything that's reasonably easy
in both Windows API and Xlib should be supported here. At the least,
text, lines, rectangles - all the basics.
April 09, 2011
Re: simple display (from: GUI library for D)
Andrej Mitrovic:
> lol, wtf? :p

My brilliant variable names! It reflects my feelings toward parts
of the bmp format I forgot about when first writing it. I didn't
account for the padding at first, then said "wtf" and added it...
then "lol"'ed at myself for not doing it right the first time.

You should have saw the original C version though:

if(hbmp == NULL)
    goto fuck;

:)

> Btw, an exception will be thrown on unhandled key events, e.g.
> just hit CTRL:

That's weird, I've never seen GetMessage fail... ever. I'll have
to try it in real Windows later (I'm on linux / Wine right now).
April 09, 2011
Re: simple display (from: GUI library for D)
Adam D. Ruppe:

> My only concern is I don't want Phobos to depend on Xlib unless
> the module is actually imported. I don't think it will be a problem,
> but if it means a hello world won't run on a text only machine, that
> won't be ok.

I agree.


> But, I don't
> really like using a tuple for this - a regular struct makes it
> a little clearer what's going on, and can be made more efficient.

OK. (But for this module I think usage simplicity is more important than raw speed. Users that need more performance or more features are going to use something else and better. The idea here is to keep simple the things that are simple).


> (In the other thread, Nick suggested making it a simple uint
> internally, using property functions to emulate other outside
> faces. I like that.)

OK.


> > I suggest something like this, to swap the two image buffers
> 
> That's not really what you're doing there - display actually
> pops up a new window and waits for the user to close it.

Indeed, I was suggesting to open the window at the top of the program, and at the bottom of the program to swap the two buffers and show the drawn one.


> > Plus maybe some image.event(); reader with callbacks...
> 
> What would it do?

Read the events, like window closing, key pressed, mouse movements, etc. Take a look at Processing API, for ideas.

There are many situations where a simple built-in graphics is useful. Beside the example of Processing (that has anti-aliasing), there are many programs/small games that need just some 2D graphics:
http://rosettacode.org/wiki/Animate_a_pendulum

Bye,
bearophile
April 09, 2011
Re: simple display (from: GUI library for D)
bearophile wrote:
> OK. (But for this module I think usage simplicity is more
> important than raw speed.

The struct is at least equal in simplicity:

image[x, y] = Color(r, g, b);

vs

image[x, y] = tuple(r, g, b);

> Indeed, I was suggesting to open the window at the top of the
> program, and at the bottom of the program to swap the two buffers
> and show the drawn one.

Sure, that's what happens in my second example.

Right now it uses "window.image = newImage;" to flip it but I
think I want to change that.

> Read the events, like window closing, key pressed, mouse
> movements, etc. Take a look at Processing API, for ideas.

Yes, my window object does this too. See the example here:
http://arsdnet.net/dcode/simpledisplay_test2.d

win.eventLoop() does it.

> http://rosettacode.org/wiki/Animate_a_pendulum

Heh, I think D will be winning that by Monday!
April 09, 2011
Re: simple display (from: GUI library for D)
Adam D. Ruppe:

> The struct is at least equal in simplicity:
> 
> image[x, y] = Color(r, g, b);
> 
> vs
> 
> image[x, y] = tuple(r, g, b);

A tuple is simpler, there is no new name to remember and use, new type to define, and 3-tuples come out of other generic computations, like zip:

foreach (col; zip(reds, greens, blues))
   image[x, y] = col;

Generally D programmers probably need to learn to use tuples a bit more seriously and often :-)


> Yes, my window object does this too. See the example here:
> http://arsdnet.net/dcode/simpledisplay_test2.d
> 
> win.eventLoop() does it.

I see. But those lambdas are a bit too much large put there. I suggest true named function moved elsewhere.

Some other notes to your code:
- generic imports like stdio are probably better before the most specific ones (this is done in well written Python code);
- Inside the (dchar c) {} a switch seems better.
- In Wrapped struct why are you using a checkLimits() instead of an invariant()?


> > http://rosettacode.org/wiki/Animate_a_pendulum
> 
> Heh, I think D will be winning that by Monday!

Good, but Rosettacode site isn't a competition :-)

Bye,
bearophile
« First   ‹ Prev
1 2 3 4 5
Top | Discussion index | About this forum | D home