Writing a GUI system

Advanced game related topics
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

Well, you know where this is going!

"You are my master and i have your slave" :lol: :P
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

Here you go! :D

Code: Select all

; Window Z-Order for Screen GUI (V3)
; Fluid Byte
; August 05, 2008

InitSprite() : InitKeyboard() : InitMouse()

; Screen-GUI Stuff
Structure GUI_WINDOW
	X.w
	Y.w
	Width.w
	Height.w
	Title.s
	Active.b
	Dragging.b
EndStructure

Global NewList gwin.GUI_WINDOW()

Global GUI_LASTID

Procedure GUI_OpenWindow(X,Y,Width,Height,Title.s)
	If GUI_LASTID
		ChangeCurrentElement(gwin(),GUI_LASTID) : gwin()\Active = #False     
	EndIf
	
	AddElement(gwin())
	gwin()\X = X
	gwin()\Y = Y
	gwin()\Width = Width
	gwin()\Height = Height
	gwin()\Title = Title
	gwin()\Active = #True
	
	GUI_LASTID = gwin()
	
	ProcedureReturn GUI_LASTID
EndProcedure

; Open Screen
OpenWindow(0,0,0,640,480,"Z-Order",#PB_Window_SystemMenu | #PB_Window_ScreenCentered)

OpenWindowedScreen(WindowID(0),0,0,640,480,0,0,0)

; GUI Windows
guiWindow1 = GUI_OpenWindow(10,10,320,220,"GUI Window #1")
guiWindow2 = GUI_OpenWindow(150,70,310,220,"GUI Window #2")
guiWindow3 = GUI_OpenWindow(100,150,320,220,"GUI Window #3")
guiWindow4 = GUI_OpenWindow(240,220,320,220,"GUI Window #4")

; Mouse Pointer
lpBuffer = AllocateMemory(630)
UnpackMemory(?Cursor,lpBuffer)
CatchSprite(0,lpBuffer)
TransparentSpriteColor(0,RGB(0,128,128))
FreeMemory(lpBuffer)

; -------------------------------------------------------------------
; MAIN LOOP
; -------------------------------------------------------------------

Repeat
	EventID = WindowEvent()   
	
	ClearScreen(RGB(150,120,50))
	
	ExamineKeyboard() : ExamineMouse()
	
	MX = MouseX() : MY = MouseY()
	
	StartDrawing(ScreenOutput())
	DrawingMode(#PB_2DDrawing_Transparent)
	
	ForEach gwin()
		WX = gwin()\X : WY = gwin()\Y : WW = gwin()\Width : WH = gwin()\Height      
		
		; // Drag Windows //
		
		If gwin()\Dragging
			gwin()\X = MouseX() + MTX
			gwin()\Y = MouseY() + MTY
		EndIf
		
		If MouseButton(1) = 0 : gwin()\Dragging = #False : EndIf
		
		; // Draw Windows //
		
		; * Active Window
		If gwin()\Active = #True
			Box(WX,WY,WW,WH,0)
			Box(WX + 1,WY + 1,WW - 2,WH - 2,RGB(255,220,0))
			Box(WX + 1,WY + 1,WW - 2,30,RGB(255,170,0))
			Line(WX,WY + 30,WW,0)   
			DrawText(WX + 10,WY + 8,gwin()\Title,RGB(100,30,0))     
		Else
			; * Inactive Window
			Box(WX,WY,WW,WH,0)
			Box(WX + 1,WY + 1,WW - 2,WH - 2,RGB(200,170,50))
			Box(WX + 1,WY + 1,WW - 2,30,RGB(180,140,0))
			Line(WX,WY + 30,WW,0)
			DrawText(WX + 10,WY + 8,gwin()\Title,$44aacc)
		EndIf
	Next
		
	StopDrawing()
		
	; // Check Mouseclick //
		
	If CountList(gwin()) ! 0 And MouseButton(1) And GUI_MOUSEBLOCK = #False
		GUI_MOUSEBLOCK = #True
		
		MX = MouseX() : MY = MouseY()
		
		; * Cycle through items from back to front
		LastElement(gwin())         
		
		hMemCurrent = gwin() ; address of last element / first window
		
		Repeat
			WX = gwin()\X : WY = gwin()\Y : WW = gwin()\Width : WH = gwin()\Height
			
			If MX >= WX And MX < (WX + WW) And MY >= WY And MY < (WY + WH)
				; * Detect Titlebar click
				If MY < (WY + 30)		
					MTX = gwin()\X - MX
					MTY = gwin()\Y - MY
					gwin()\Dragging = #True
				EndIf
				
				; * Exit loop and don't do anything if clicked window is the active one
				If gwin() = hMemCurrent : Break : EndIf
				
				; * Retrieve Window title and data from element address in memory
				tmpString$ = gwin()\Title           
				hMemBuffer = AllocateMemory(SizeOf(GUI_WINDOW))           
				CopyMemory(gwin(),hMemBuffer,SizeOf(GUI_WINDOW))
				
				; * Remove selected element (data has been temporarily saved)
				DeleteElement(gwin())
				
				; * Goto end of list and add a new item so it's last and the new topmost window
				LastElement(gwin())           
				AddElement(gwin())
				gwin()\Title = tmpString$
				
				; * Copy window data to address of new element
				CopyMemory(hMemBuffer,gwin(),SizeOf(GUI_WINDOW))
				FreeMemory(hMemBuffer)
				
				; * Activte new window, deactivte old window
				gwin()\Active = #True					
				ChangeCurrentElement(gwin(),hMemCurrent)				                
				gwin()\Active = #False

				Break ; we found our window, get outta here
			EndIf
		Until PreviousElement(gwin()) = 0
	EndIf
	
	If MouseButton(1) = 0 : GUI_MOUSEBLOCK = #False : EndIf
	
	DisplayTransparentSprite(0,MouseX(),MouseY())
	
	FlipBuffers()
Until KeyboardPushed(1) Or EventID = #PB_Event_CloseWindow

; -------------------------------------------------------------------
; DATA SECTION
; -------------------------------------------------------------------

DataSection
	Cursor:
	Data.l $0276434A,$4A720000,$A9B7AACC,$146320D0,$284A6811,$01232023,$9188409D,$F3000461,$20492601,$0A0401E0
	Data.l $E00081C0,$FFC0E015,$09302024,$409C3C04,$66013801,$FE4D02B6,$91FB77FB,$B7C236B7,$BDF63086,$BFEC1EC1
	Data.l $C0F36107,$0F625EF7,$008A083D,$87FFF581,$C4592A11,$4287C926,$3EC90540,$69F6974F,$21E5E328,$DDDE6107
	Data.l $50B353C6,$0FB86C06,$0000D893
	Data.b $90,$48
EndDataSection
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

I just updated the code and fixed a click issue, should be fine now. :)
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
untune
User
User
Posts: 56
Joined: Sun Jul 06, 2008 10:07 pm
Location: Bolton, UK

Post by untune »

Hehe thanks for your help FB, it keeps me going :D

I've been tinkering all afternoon with this, and gotten *somewhere*, but my code has become much more complicated than your example so it's still giving me headaches :D

I've gotten the panels to drag at least, but for some reason if I click one after another then they are both drawn at the same point, unless I click away from them all and deactivate all the panels first. I'm assuming that it's something to do with the linked list, being at the wrong element... it's all a bit too hefty to post any source though... agh!!! :D
untune
User
User
Posts: 56
Joined: Sun Jul 06, 2008 10:07 pm
Location: Bolton, UK

Post by untune »

Solved! Thanks again FB!

Hehe, I don't know if my implementation is the most efficient and it'll probably be broken again by tomorrow but I'm just glad it works :D

The problem was - my panels are no longer just simply rectangular sprites - they comprise of 10 sprites: the top, bottom, left, and right borders, then the top left, top right, bottom left and bottom right corners, then the actual "client area" (I simply refer to this as the area because I'm awkward :P) and finally the header/title bar. This number will probably grow as icons, close buttons etc are added...

Anyways, the problem was that I got it moving the whole panel (borders, title bar, corners etc with the mouse offset variable (as all these are created and drawn relative to the Panel X and Y positions), but the area was being drawn relative to the panel coordinates... and in the structure the area has it's own X/Y coordinates rather than being relative to the panel like the rest... so I just created two more X/Y offset floats specifically for the area and did everything again and voila :D

The other issue was down to not telling it to set Dragging to false when the mouse button was released..

At this rate you'll have your slave back in no time :P

Cheers
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

Actually I have to thank you for asking the Window Z-Order question. You know, I have come along way with BASIC dialects. Starting with RealBASIC on the MAC to DarkBASIC, BlitzBasic and finally PureBasic on the PC. In all of them I created a some sort of Screen GUI wich I never finished. Though I created a scrollbar wich is 100% identical to the Windows one I never got the Z-Order thing to work.

Anyway, that slave is yours now. Take it as a sign of my generosity. :wink:
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
untune
User
User
Posts: 56
Joined: Sun Jul 06, 2008 10:07 pm
Location: Bolton, UK

Post by untune »

That's really cool - I started with DarkBASIC, then DBPro, then PureBasic, then IBasic - about 3 years ago I gave up on my dream of being an indie game designer and grew up a bit :P I just started coding again a month or so ago, IBasic had vanished (just as I suspected it would) and good old PB was still going strong. My problem is my attention span is too short to stick with a project usually but this one is coming on in leaps and bounds which is a great motivator.

But when I started this I was looking at C++ GUI libraries and since I can grasp some C++ when reading it, that's where my knowledge ends :D So I started to look for DarkBASIC and BlitzBASIC code because obviously it's easier to tell what's going on. With your help the most difficult parts have become clear...

How far did you get with yours then? I'm about to start thinking about how to implement gadgets... buttons will be easy... keeping track of and rendering them all in the right order will be a challenge too.

I'm thinking a structure of

Code: Select all

ForEach PanelLinkedList()
    Draw panel
    Draw all child gadgets
Next
But obviously the number of gadgets may vary... in one Blitz library I think a static array was used in the window structure with a limit of 25 I think, but I'm sure there are better ways :D

Oh and thanks for the slave :D
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

untune wrote:My problem is my attention span is too short to stick with a project usually but this one is coming on in leaps and bounds which is a great motivator.
Heh, I got about 200 PB projects lying here and like 5 of them are finished. So I think we share the same kind of disease. :o
untune wrote:How far did you get with yours then?
Buttons? Check. Scrollbars? Check. Progressbars? Check. Windows without Z-Order? Check indeed. I also got some statusbar and menubar code but this is in early beta stage like some other controls.

But I am just asking myself, will your windows be resizable? If so, that would be pain in the ass to code because you would have to take care of the clipping of all gadgets. I know this could be done by using sprites to draw the windows but I guess there would be quite some stuff in my code to adopt to this method.
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
untune
User
User
Posts: 56
Joined: Sun Jul 06, 2008 10:07 pm
Location: Bolton, UK

Post by untune »

Haha well at least I'm not the only one :P

The gadgets are the important thing on my list now: specifically - buttons, edit boxes/text fields, scrollbars, progress bars, sliders, frames, checkboxes, radioboxes, labels, listviews (that incorporate scrolling) and maybe comboboxes and tabs. The majority won't be all that important but they need to be there for versatility.

I had designed my panels to incorporate borders and corners for resizing but I hadn't thought about the clipping issue - after giving it more thought I've decided that I don't really intend to use this GUI for anything really complex, and fixed sizes should be fine - the borders look good anyways and the implementation should allow resizing to be added further down the line if it needs it. I don't know how difficult it would be considering HGE is using flat 3d quads a la Sprite3d - I'm guessing very difficult :D

At the end of the day I just wanted a nice looking GUI for games with a more menu based interface - 4X games and managerial for example... Once I've added something to load a GUI "theme" from an XML or INI file or something, everything should be good.
untune
User
User
Posts: 56
Joined: Sun Jul 06, 2008 10:07 pm
Location: Bolton, UK

Post by untune »

Wehey! I went and broke it :P

Bit of an odd one this, because it seems a bit illogical...

I hadn't really tested my create function out other than in the initial setup so I just stuck in a few lines that let me spawn a new panel at the mouse coordinates when the right mouse button is clicked. Nice and simple.

The problem seems to lie in the code that brings panels to the front -

Code: Select all

; // Get the info from the structure, create temporary structure to store it
    GUI_PANEL_TEMPHEADER = GUI_LIST_PANEL()\Header
    GUI_PANEL_TEMPBUFFER = AllocateMemory(SizeOf(_GUI_PANEL))
    CopyMemory(GUI_LIST_PANEL(), GUI_PANEL_TEMPBUFFER, SizeOf(_GUI_PANEL))
    
; // Remove the element from the list
    DeleteElement(GUI_LIST_PANEL())
    
; // Go to the front (end) of the list and add a new element
    LastElement(GUI_LIST_PANEL())
    AddElement(GUI_LIST_PANEL())
    
; // Copy data from buffer to new element
    GUI_LIST_PANEL()\Header = GUI_PANEL_TEMPHEADER
    CopyMemory(GUI_PANEL_TEMPBUFFER, GUI_LIST_PANEL(), SizeOf(_GUI_PANEL))
    FreeMemory(GUI_PANEL_TEMPBUFFER)
    

; // Deactivate old panel, activate new panel
    ChangeCurrentElement(GUI_LIST_PANEL(), GUI_PANEL_CURRENT)
    GUI_LIST_PANEL()\Active = #False
    LastElement(GUI_LIST_PANEL())
    GUI_LIST_PANEL()\Active = #True
The behaviour is like this, After creating 2 additional panels, and clicking on the others randomly, moving them in and out of focus, the first thing that happens is that the header text in one of the newly spawned panels will go iffy - sometimes it will display as "???", one time it was "?J?", another time it appeared to copy the titlebar text from another panel, one time it borrowed a portion of it... seems very random :D

Then a few more cliks and I get an invalid memory access error... on this line:

Code: Select all

; // Copy data from buffer to new element
*** GUI_LIST_PANEL()\Header = GUI_PANEL_TEMPHEADER
    CopyMemory(GUI_PANEL_TEMPBUFFER, GUI_LIST_PANEL(), SizeOf(_GUI_PANEL))
    FreeMemory(GUI_PANEL_TEMPBUFFER)
Strings aren't my forte so am I missing something here? It's pretty much exactly as Fluid Byte posted it and I find it a bit odd that this only seems to have started since I added this little bit to spawn new panels....

Baffling :D
untune
User
User
Posts: 56
Joined: Sun Jul 06, 2008 10:07 pm
Location: Bolton, UK

Post by untune »

UPDATE: Well I've partially fixed the problem, first by making the buffer variables protected instead of global, and also by changing the header buffer to GUI_PANEL_TEMPBUFFER$.

I have no idea what the difference is for a fixed length string and a .s string, is it to do with how they are stored?

The remaining problem is that randomly the white panes headers will sometimes change to "e Panel"... the whole header string should be "TEST PANEL: White Panel"... so I assume that the "TEST PANEL: Whit" is getting lost somewhere :D

[EDIT] And now also "TEST PAN?" - haha :D

Any ideas?
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

Let me get some coffee and chill for a while ...

I will look into this problem later. :)
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
untune
User
User
Posts: 56
Joined: Sun Jul 06, 2008 10:07 pm
Location: Bolton, UK

Post by untune »

Cheers FB :D

Just been playing around again and it seems neither problem is fixed... now I get an invalid access error on the DeleteElement() line, and the strings are still messing up... I can't think of anything else to try :?
Thalius
Enthusiast
Enthusiast
Posts: 711
Joined: Thu Jul 17, 2003 4:15 pm
Contact:

Post by Thalius »

Protected probably puts em "out of scope" and the copymemory cant access the full structure data which again could render the pointers invalid or respectivelly cause an IMA since its trying to access a protected memoryarea from inside a procedure. ;)

Just an idea.

Cheers,
Thalius
"In 3D there is never enough Time to do Things right,
but there's always enough Time to make them *look* right."
"psssst! i steal signatures... don't tell anyone! ;)"
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

I think it's because the Windows are not created during runtime but before entering the main loop. If you create and delete Windows at runtime you have to reset the focus for each window otherwise things seem to get messed up.

I'm too lazy atm so I will try to figure this out when getting home again.
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
Post Reply