Windows / GUI Functionality in Blitz+

Blitz3D Forums/Blitz3D Beginners Area/Windows / GUI Functionality in Blitz+

thalamus(Posted 2004) [#1]
Hi,

I'm pretty new to Blitz+, but I've started work on a Windows-based Map Editor and I'm looking for some resources on the Windows-based GUI functions.

The documentation that comes with Blitz+ is all very well, but there are precious few examples of how this all fits together, and some of the functionality (Create a window, update it, create gadgets, assign to group, release gadget, etc., etc.,) can be somewhat daunting.

I *refuse* to use a GUI editor - it makes more sense (to me, anyway!) to learn how these things are done :)

My question is - does anyone know of any decent tutorials or articles which cover the GUI aspect of Blitz+?

Cheers,

Andy/Thalamus


soja(Posted 2004) [#2]
I don't, but it's fairly straightforward after looking at some examples. I would start out with a nice event-based barebones:
wndMain=CreateWindow("",200,200,200,200)
Repeat
	Select WaitEvent()
		Case $803 : End
	End Select
Forever

You can put in all the events you want to watch for in the Select WaitEvent() structure, including GadgetAction events (for when you have multiple gadgets, you can put a Select EventSource() in that event's case to figure out what gadget was used) and Timer events if you want to do something every 60th of a second, for example, render a game. Just say CreateTimer(60) upon initialization.

As for what you're asking for...
Create a window: Use the CreateWindow command, that's really all there is to it.

Update a window: All the normal window operations update normally. You can use other Window commands AND some gadget commands on windows to hide them, disable them, resize them programmatically, etc.

Create Gadgets: It's as simple as using any of the "Create..." functions listed under their respective gadgets (in the docs). Just set the parent to be the window handle (wndMain in this case).

Assign to group: When you create the gadgets, instead of assigning the parent to be wndMain, first create a panel (CreatePanel) whose parent is wndMain, then create the gadgets that you want to be grouped and assign their parent to be the panel.

Release gadgets: It's as simple as calling Freegadget, which will remove the gadget and any of its children. It works with windows too.

Take a look at the samples, and ask any questions you might have.


Ross C(Posted 2004) [#3]
Hey, you could try:

http://www.blitzcoder.com/

They have alot of tutorials and guides :o)


thalamus(Posted 2004) [#4]
So a gadget is basically *anything* the user interacts with?

And you have to assign a gadget to a particular window (hence, group)?

I'm currently ploughing through the samples, dissecting them piece by piece, but it is hard going sometimes. WHat it really needs is a nice big tutorial taking people through every simple step... maybe I'll wind up writing it :)

Thanks for the help though, chaps - expect to see a lot more questions posted over the weekend ;)


soja(Posted 2004) [#5]
So a gadget is basically *anything* the user interacts with?

In BlitzPlus, pretty much, except for maybe menus.
And you have to assign a gadget to a particular window (hence, group)?

Yes, all gadgets have to have a particular group (parent) -- even windows. (Their group is "0" -- the desktop.)


thalamus(Posted 2004) [#6]
Cheers, soja. Any other useful rules of thumb I should bear in mind? :)


soja(Posted 2004) [#7]
Not off the top of my head, but I'd be glad to pipe in if you have any more questions or run into problems.


thalamus(Posted 2004) [#8]
Ok, I seem to be getting into the swing of things a little more. Couple of questions, though:

1. It doesn't seem possible to add spacer lines to a toolbar. The only way around this I can think of is to add a spacer icon to the toolbar graphic but disable it in the the program so it can't be clicked on.

2. Is there any way to add keyboard shortcuts to menus? What about nested menus, too?

3. Is there any way to force the size of a window when it is resized? E.g. the window can only be multiples of 32.


soja(Posted 2004) [#9]
1) You're probably right, but I've never done a toolbar.
2) Prefix the "menu item &letter" with an ampersand (&), and the following letter will be underlined. That's not really a blitz-specific thing, just a general programming language/Windows thing that I know from Visual Studio.
3) The only thing I can think of is to watch for event $802 (window size) in your event loop, and force it not to change unless the mouse moves (mousex(), mousey()) changes > 32 pixels?


skn3(Posted 2004) [#10]
It is only possible to add spacers via some WinApi calls. Check the code archives
( http://www.blitzbasic.com/codearcs/codearcs.php?code=704 )

You can add keyboard shortcuts. Perhaps you should read the docs ;)

Shortcut keys can be implemented using the HotKeyEvent function. If you wish to show the keyboard shortcut in the menu, you should separate it from the main text using Chr$(8) or Chr$(9). This will ensure that it is correctly positioned in the menu. Chr$(8) results in right aligned shortcuts, whereas Chr$(9) results in left aligned shortcuts. See the example for details.

ALT shortcut keys can be implemented by preceding the desired character in text$ with an ampersand (&). For example, if text$ is set to "&Copy", then the keyboard shortcut ALT-C will trigger the menu event.


To force the size you would simply put a test for event $802 in your main loop, and then resize your window gadget according to your speciffic requirement.

$802 - Window size
Generated when the user sizes a window. EventSource contains the handle of the window gadget that has sized, and EventX and EventY contain the new size of the window.



thalamus(Posted 2004) [#11]
>> Perhaps you should read the docs ;)

I did - I just negelected to check out the online docs as opposed to the ones built into the program :)

Cheers for the pointers - things are working beautifully :)


thalamus(Posted 2004) [#12]
Is there a way to check when the mouse *isn't* over the canvas?


soja(Posted 2004) [#13]
You could keep track of events $205 and $206 (Mouse Enter, Mouse Leave)

You could also get the absolute position of the cursor in screen coords (MouseX(), MouseY()) and figure out if those coords fall within the coundaries of the canvas.


thalamus(Posted 2004) [#14]
>> You could keep track of events $205 and $206 (Mouse Enter, Mouse Leave)

So how would I use this to check if the mouse was over a particular canvas?


soja(Posted 2004) [#15]
If one of the events fire, use Select on EventSource(), and it will tell you which canvas it happened to. You will have to keep a variable (canvas1=createcanvas...) of the canvas handle when you create it to check, though.

The only case where I can see where this wouldn't work is when the program first starts, and the mouse pointer starts inside the canvas right away. Well, you can always use MouseX and MouseY in that case, or just check the MouseMove event.


thalamus(Posted 2004) [#16]
>> If one of the events fire, use Select on EventSource(), and it will tell you which canvas it happened to.

I'm still wrestling with this... grrr...


CS_TBL(Posted 2004) [#17]
I made one rather big app once .. that was my first B+ thing that had more than 20 lines of code .. after that I knew almost all ins & outs of B+ ..

Just make something useful, not 'big' by concept, but some small tool.. it'll turn out to grow big anyway :) That first 'big' app of mine was an image-converter that loaded bmp images with 16 colors, and the app included a palette-manager in which you could move around and modify some colors (with some simple RGB thing). Such a simple concept became many hundreds of lines..

Map-editors er even simpler to make if you already know some 2d commands & stuff..


thalamus(Posted 2004) [#18]
Well I've coded the Commodore 64 in 6501, and the PC in 80x86 assembly, so I'm not alien to programming in general. I can't really complain about the learning curve for Blitz+, it's just that the docs and tutorials could be a little weightier.

I'm certain I'll end up writing a GUI tutorial... ;)


CS_TBL(Posted 2004) [#19]
The tutorials could be a bit more uniform.. they're a bit of a mixed-bag in terms of style. Anyway, all the gadget-stuff more or less works the same.. if you know one, you know all, and you'll learn it in no-time. In a week you're laughing about this post :)


thalamus(Posted 2004) [#20]
I'll be laughing when I get this "which canvas is the mouse over" problem sorted...


CS_TBL(Posted 2004) [#21]
like this? (run in debugmode!)

app=CreateWindow("blah",0,0,640,480)

Dim c(15)
For t=0 To 15
	c(t)=CreateCanvas(16+t*32,0,32,80,app)

	SetBuffer CanvasBuffer(c(t))
		ClsColor Rnd(0,255),Rnd(0,255),Rnd(0,255)
		Cls
	FlipCanvas c(t)
	
Next

SetBuffer DesktopBuffer()

quit=False
;-------------------------------------------------------
Repeat
	WaitEvent()
	If EventID()=$803 quit=True
	
	For t=0 To 15
		If EventSource()=c(t)
			DebugLog "canvas c("+t+")"
		EndIf
	Next
	
Until quit
;-------------------------------------------------------
; optional cleanup duties here..
End



thalamus(Posted 2004) [#22]
Ok, your code works, mine doesn't, and so I have been struggling to work out why my checks for events $205 and $206 didn't work.

Here's my code as-was (apologies for poor layout):

;----------
Repeat

e = WaitEvent (fps)

Select e

Case EVENT_Close : End

Case EVENT_Menu
Select EventData()
Case 2 : loadmap2()
Case 3 : savemap()
End Select
End Select

Until e = EVENT_Timer
;----------

Now, that seems to work fine - the menu and close commands work fine by checking (e).

But if I add the following:

;----------
Case $205
If EventSource() = canvas
canvasactive=1
EndIf
;----------

...it point blank refuses to work. :/

The only way I can get it to work is to add:

;----------
If EventSource()=canvas
canvasactive=1
EndIf
;----------

I appreciate that there are a few things that I might be misunderstanding - the code loop was copied from another program and I *thought* I understood it correctly.

What I don't understand is:

a) why it fails to recognise the "Case $205"

b) why I can do a select on "WaitEvent" and don't have to use "EventID"

Help :(


soja(Posted 2004) [#23]
a) I'm not sure without seeing the whole of your code, but you've got to make sure you've put it in the right place (e.g. after "select e"). I'm also not sure when it drops out (what's EVENT_Timer?). It would help to see the whole loop and pertinent information in [ code ] [ / code ] brackets.

b) When you select on WaitEvent, you *do* select on the EventID, because WaitEvent returns an event ID.


thalamus(Posted 2004) [#24]
a) EVENT_Timer = $4001

Code as follows:

;----------------------------------------
; Frame timing stuff...

limit=CreateTimer(60) 				; FPS rate

;----------------------------------------
; Main loop...

Repeat

	SetBuffer CanvasBuffer (canvas2)
	Cls
	SetBuffer CanvasBuffer (canvas)
	Cls
	
	update_map(0)
	update_grid()
	update_tilestrip()
	control()
	update_cursor()
	text_display ()

;----------------------------------------

	Repeat
	
	e = WaitEvent()


	If EventSource()=canvas
		canvasactive=1
		canvasactive2=0
	EndIf

	If EventSource()=canvas2
		canvasactive=0
		canvasactive2=1
	EndIf


	Select e
			
		Case EVENT_Close : End
			
		Case EVENT_Menu
			Select EventData()
				Case 2 : loadmap2()
				Case 3 : savemap()
			End Select		
		
	End Select				

	Until e = EVENT_Timer
	
VWait:FlipCanvas canvas:FlipCanvas canvas2

Until (KeyHit (1))

;----------------------------------------



CS_TBL(Posted 2004) [#25]
I'd say that the code could be organised somewhat better ..

Anyway.. try to study this map-editor example below.. I think I broke my record in fast-map-editor-coding :)

It doesn't save the maps, but that's easy to implement..

; miniminiminiminiminiminiminiminiminiminiminiminiminiminimap-editor - by CS^TBL
app=CreateWindow("blah",0,0,640,480)

Global c1=CreateCanvas(8,8,32,256,app)
Global c2=CreateCanvas(56,8,512,256,app)
Global img,currentpalette=0,maplmd=False

Dim map(32,16)

loadimg=CreateButton("IMG",8,8+256+4,32,24,app)

UpdatePalette(True)

quit=False
;------------------------------------------------------------------------
Repeat
	WaitEvent()
	If EventID()=$803 quit=True ; window-close / alt-f4
	


	If EventID()=$401 ; generic gadget action.. like a buttonpress
	
		If EventSource()=loadimg
			; request a file
			whichfile$=RequestFile$("load image..","jpg")
			
			If whichfile$<>"" ; did we succeed?
				If img FreeImage img ; first get rid of the old image
				img=LoadImage(whichfile$)
				UpdatePalette(True)
			EndIf
			
		EndIf
	EndIf
	
	If EventSource()=c1 ; the palette? ------------------------------------------------

		If EventID()=$203 ; mousemove
			x=EventX()/16
			y=EventY()/16
			
			UpdatePalette(False)
				Color 255,255,255:Rect x*16,y*16,16,16,False
				Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False
			FlipCanvas c1
		EndIf

		If EventID()=$206 ; mouseleave
			UpdatePalette(True)
		EndIf

		If EventID()=$201 ; mouseclick
			x=EventX()/16
			y=EventY()/16
			currentpalette=(x Mod 2)+y*2
			UpdatePalette(True)
		EndIf

	EndIf



	If EventSource()=c2 ; the map? ------------------------------------------------

		If EventID()=$203 ; mousemove
			x=EventX()/16
			y=EventY()/16
			UpdateMap(False)
				Color 255,255,255:Rect x*16,y*16,16,16,False
				Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False
			FlipCanvas c2
		EndIf

		If EventID()=$206 ; mouseleave
			UpdateMap(True)
		EndIf

		If EventID()=$201 ; mouseclick
			maplmd=True
		EndIf

		If EventID()=$202 ; mouseclick
			maplmd=False
		EndIf
		
		If maplmd
			x=EventX()/16
			y=EventY()/16
			
			If x<0 x=0  ; make sure you clip! otherwise you'll get errors!
			If y<0 y=0
			If x>31 x=31
			If y>15 y=15
			
			map(x,y)=currentpalette
		
			UpdateMap(False)
			Color 255,255,255:Rect x*16,y*16,16,16,False
			Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False
			FlipCanvas c2

		EndIf

	EndIf



Until quit

FreeImage img

End

;------------------------------------------------------------------------
Function UpdatePalette(swap)

	SetBuffer CanvasBuffer(c1)
		Cls
		If img DrawBlock img,0,0
		
		x=currentpalette Mod 2
		y=currentpalette /2
		
		Color 255,255,255:Rect x*16,y*16,16,16,False
		Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False
		
	If swap FlipCanvas c1

End Function

;------------------------------------------------------------------------
Function UpdateMap(swap)

	SetBuffer CanvasBuffer(c2)
		Cls

		For y=0 To 15
			For x=0 To 31
			
				tile=map(x,y)
				px=tile Mod 2
				py=tile / 2
				
				If img DrawBlockRect img,x*16,y*16,px*16,py*16,16,16
				
			Next
		Next

	If swap FlipCanvas c2

End Function




thalamus(Posted 2004) [#26]
>> I'd say that the code could be organised somewhat better ..

Definitely. The code as it stands is a legacy of bits and pieces I've picked up from various places (the map editor began as a hybrid of two existing editors). If I'd had a decent tutorial or a chunk of example code such as yours at the beginning, it wouldn't look as shabby as it does :)

Restructuring my main loop is something I've been meaning to do for a while, though - there's a couple of things I'd do slightly differently (though I *really* like your maplmd/maprmd system), but yours is a nice, crisp, well-presented example. Cheers :)


>> Anyway.. try to study this map-editor example below..

So basically, my code wasn't working because I should have been checking the EventSource and *then* the EventID...? Blimey!


CS_TBL(Posted 2004) [#27]
Remember that EventSource(), EventID() etc. is nothing magical at all.. they're just variables containing an int-value (4 bytes)!

So

If A
  If B
  Endif
Endif


means the same as:

If EventSource()=c2
  If EventID()=$203
  Endif
Endif


So, if a certain programflow suits you better, then go for it..

If you need to do a lot of ID-type checks on the same gadget then it's only logical that you put the EventSource() at the top of the tree (like I did when reading out the canvas-related actions).

However, if you have 20 buttons (where only ID $401 is useful) on the screen then you usually want to check them the other way around.

If EventID()=$401 ; buttonpress/gadgetaction
  If EventSource()=LoadIMG
  Endif
  If EventSource()=SaveIMG
  Endif
  If EventSource()=LoadMAP
  Endif
  If EventSource()=SaveMAP
  Endif
Endif


However.. you get events when there are events.. EventID() is only to specify which event.
If you've a button, then there's only the $401 event. You don't need to specify $401 then as you know it'll always be $401 anyway.

So, compare the last above example with this one:

app=CreateWindow("",0,0,170,170)

b=CreateButton("s",5,5,30,30,app)

Repeat
	WaitEvent()
	If EventID()=$803 quit=True
	If EventSource()=b
		quit=True
	EndIf
Until quit
End


If you click the button, the app ends.. without "$401" anywhere in the source :)