Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: Limbo: Tk-ohjelman runko ja linssiefekti

jalski [29.05.2011 19:11:24]

#

Infernolle Limbolla tk-ohjelman runko pohjaksi omille kokeiluille.

Esimerkki näyttää, miten muodostaa yksinkertainen käyttöliittymä ja käsitellä kanavien avulla sen viestit.

Jotta esimerkki ei olisi aivan tylsä, niin toteutamme myös yksinkertaisen ajastimen ja harrastamme hiukan peruspiirto-operaatioita, joiden avulla toteutamme vanhanaikaisen linssi demoeffektin.

implement wmLens;

include "sys.m";
	sys: Sys;

include "draw.m";
	draw: Draw;
	Context, Display, Point, Rect, Image, Screen: import draw;

include "tk.m";
	tk: Tk;
	Toplevel: import tk;

include "tkclient.m";
	tkclient: Tkclient;

include "math.m";
	math: Math;


wmLens: module
{
	init: fn(ctxt: ref Context, argv: list of string);
};


LD: con 256; 			# Lens diameter
DEFAULTDELAY: con 1000; # Eh, default delay?

display : ref Display;
main: ref Toplevel;


# Lens "class"
Lens: adt {
	x, y: int; 					# lens position
	vx, vy: int; 				# speed and direction of the lens
	lensArray: array of int; 	# lens transformation array (look-up table)
	new: fn(x, y, vx, vy, magFactor: int): ref Lens; 					# constructor for the lens object
	update: fn(l: self ref Lens, img: ref Image); 						# update method for the lens object
	draw: fn(l: self ref Lens, source: ref Image, target: ref Image); 	# draw method for the lens object
};


# adt to help setting up lens movement delay
Tick: adt {
	delay: int;
	new: fn(delay : int): ref Tick;
	set: fn(t: self ref Tick, delay: int);
};

# Our simple ticker timer channel and Tick ref adt.
# Let's make them global, so we can use them at will directly from different procs.
tickch: chan of int; 	# our timer sends messages to this channel
tick: ref Tick; 		# see above...

# Let's define our UI in array.
wmlens_cfg := array[] of {
	"frame .s",
	"scale .s.delay -label {Lens movement delay} -from 100 -to 2000 -orient horizontal" +
		" -showvalue 0 -command {send cmd delay}",
	".s.delay set " + string DEFAULTDELAY,
	"pack .s.delay -side left -fill x -expand 1",
	"pack .s -side top -fill both -expand 1"
};



# main procedure
init(ctxt: ref Context, nil: list of string)
{
	sys = load Sys Sys->PATH;
	draw = load Draw Draw->PATH;
	tk = load Tk Tk->PATH;
	tkclient = load Tkclient Tkclient->PATH;
	math = load Math Math->PATH;

	sys->pctl(Sys->NEWPGRP, nil);

	tkclient->init();

	if(ctxt == nil)
		ctxt = tkclient->makedrawcontext();

	display = ctxt.display; # reference to display

	menubut: chan of string;
	(main, menubut) = tkclient->toplevel(ctxt, "", "My first Inferno tk program", 0);

	cmdch := chan of string; # Our UI messages come into this channel
	tk->namechan(main, cmdch, "cmd"); # Let's name it for tk.

	# Now, time to build UI.
	for (i := 0; i < len wmlens_cfg; i++)
		cmd(main, wmlens_cfg[i]);

	tkclient->startinput(main, "ptr"::"kbd"::nil); # This will handle default input for our tk window.
	tkclient->onscreen(main, nil); # Let's make our window visible on screen.

	# It's necessary to initialize ticker timer channel and Tick ref adt.
	tickch = chan of int;
	tick = Tick.new(DEFAULTDELAY);

	spawn runLens(); # Run in own proc, this is our lens program.


	# Next part is a minimal standard loop to handle window input messages.
	# You also notice, we handle our programs command messages via cmdch channel.
	for(;;) alt {
	s := <- main.ctxt.kbd =>
		tk->keyboard(main, s);
	s := <- main.ctxt.ptr =>
		tk->pointer(main, *s);
	s := <- main.ctxt.ctl or
	s = <- main.wreq or
	s = <- menubut =>
		tkclient->wmctl(main, s);
	cmd := <- cmdch =>
		(nil, tokens) := sys->tokenize(cmd, " ");
		if(hd tokens == "delay") {
			tick.set(int hd tl tokens);
		}
	}

}


# This is our lens proc. I like to keep it separate from basic tk window handling stuff.
# That doesn't mean we can't control tk stuff from here or from any other proc.
# We made our Toplevel window global, so we can access it directly from any proc at will.
runLens()
{
	# Let's load our image. These are allocated at display in display memory.
	image:= display.open("/icons/pizzamenu.bit");
	if (image == nil) {
		sys->fprint(sys->fildes(2), "Lens: failed to allocate image\n");
		exit;
	}

	# This is our drawing buffer. Again, it's created in display memory.
	buffer:= display.newimage(image.r, main.image.chans, 0, Draw->Black);
	if (buffer == nil) {
		sys->fprint(sys->fildes(2), "Lens: failed to allocate image\n");
		exit;
	}

	# Let's create our lens object (x, y, vx, vy, magFactor).
	lens:= Lens.new(0, 0, 4, 4, 70);

	# Next we define our lens rectangle. We can pass this to our panel
	# which is used as drawing canvas to tell about the area of the dirty rectangle
	# what needs to be updated. That way the whole panel doesn't necessary need to be refreshed.
	lensr:= Rect(Point(0,0), Point(LD, LD));

	# Time to draw our image to drawing buffer
	# parameters are: image rectangle, image, mask, offset point in source image.
	buffer.draw(image.r, image, nil, Point(0,0));

	# Add our drawing panel to window.
	cmd(main, "panel .p -bd 3 -relief flat");
	cmd(main, "pack .p -fill both -expand 1");

	# This links our buffer image with our panel widget.
	tk->putimage(main, ".p", buffer, nil);
	cmd(main, ".p dirty; update");

	spawn ticker(tickch, tick); # Start our simple ticker timer

	while(1) { # Our lens action main loop
		<- tickch; # Wait for our simple ticker timer.

		lens.update(buffer); # Move lens.
		lens.draw(image, buffer); # Draw lens to buffer.

		# Make sure our panel is updated on screen.
		cmd(main, ".p dirty " + r2s(lensr.addpt(Point(lens.x, lens.y))) + "; update");
	}

}


# Constructor for lens object
Lens.new(lx, ly, lvx, lvy, magFactor: int) : ref Lens
{
	lensArray:= array [LD*LD] of int;

	a, b: int;

	r: int = LD / 2;
	s:= math->sqrt(real (r*r - magFactor*magFactor));

	# Calculate lens transformation array
	for (y:= -r; y < r; y++) {
		for (x:= -r ; x < r; x++) {
			if(real (x*x + y*y) >= s*s) {
				a = x;
				b = y;
			}
			else {
				z:= math->sqrt(real (r*r - x*x - y*y));
				a = int(real (x * magFactor) / z + 0.5);
				b = int(real (y * magFactor) / z + 0.5);
			}
			lensArray[(y + r)*LD + (x + r)] = (b + r) * LD + (a + r);
		}
	}

	return ref Lens (lx, ly, lvx, lvy, lensArray);

}


# Update method for lens object.
# Adds velocity to position and keeps within screen boundaries
Lens.update(l: self ref Lens, img: ref Image)
{
	l.x += l.vx;
	if (l.x < 0 || l.x >= img.r.dx()-LD)
		l.vx=-l.vx;

	l.y += l.vy;
	if (l.y < 0 || l.y >= img.r.dy()-LD)
		l.vy=-l.vy;

}


# Draw method for lens object.
# Look-up where to draw pixels and draw them as small rectangles
Lens.draw(l: self ref Lens, source: ref Image, target: ref Image)
{
	for (i:= 0; i < LD; i ++) {
		for (j:= 0; j < LD; j ++) {
			offset := l.lensArray[(LD * i) + j];
			x:= offset % LD;
			y:= offset / LD;
			target.draw(Rect(Point(0,0), Point(1,1)).addpt(Point(j+l.x, i+l.y)), source, nil, Point(x+l.x, y+l.y));
		}
	}

}


# Constructor for Tick adt
Tick.new(delay: int) : ref Tick
{
	return ref Tick (delay);
}


# Set method for Tick adt
Tick.set(t: self ref Tick, delay: int)
{
	t.delay = delay;
}


# Ticker proc (thread) to provide simple timer.
# Simply sleeps delay duration and then sends
# message to the timer channel.
ticker(tickch: chan of int, tick: ref Tick)
{
	tickch <-= 1;

	while((delay := tick.delay) >= 0) {
		sys->sleep(delay);
		tickch <-= 1;
	}

	tickch <-= 0;
}


# Rectancle to string
r2s(r: Rect): string
{
	return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
}


# This can be helpful for debugging purposes.
# At least it prints error message...
cmd(win: ref Tk->Toplevel, s: string): string
{
	r:= tk->cmd(win, s);
	if (len r > 0 && r[0] == '!') {
		sys->print("error executing '%s': %s\n", s, r[1:]);
	}

	return r;
}

Metabolix [19.02.2012 17:39:31]

#

Lisää kommentteja koodiin, kiitos. Ainakin pitäisi joka funktion ja adt:n kohdalla lyhyesti kertoa, mitä varten ne ovat.

Edit. No nyt näyttää vähän paremmalta. :) Menköön.

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta