Erweiterbare und Skinnbare GUI + benötigte weitere Klassen

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

M0rgenstern

Betreff: Erweiterbare und Skinnbare GUI + benötigte weitere Klassen

BeitragSo, März 06, 2011 16:08
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo Leute.
Nachdem ich gestern Abend und heute damit zugebracht habe eine GUI zu programmieren möchte ich sie euch nicht vorenthalten.
Bisher gibt es eigentlich nur zwei Komponenten: Fenster und Buttons.
Die Fenster können als Vollbildfenster erstellt werden und sind dann unsichtbar oder als sichtbare Komponenten in beliebigen Größen.
Die Buttons haben eine im Type vorgegebene Größe. Die kann abgeändert werden, ich rate jedoch dazu die Grafiken in der gleichen Größe zu erstellen. Die Grafiken werden zwar entsprechend skaliert, aber das könnte unter Umständen blöd aussehen.
Der Unterschied zwischen Vollbildfenstern und kleineren Fenstern mag seltsam anmuten, jedoch ist es so, dass jedes GUI Element (also z.B. Buttons) nur zu einem Fenster gehören kann. Ein alleinstehendes GUI Element kann es nicht geben.
Man kann also über ein Fenster eine ganze Reihe Buttons ein und ausschalten, man hat aber auch die Möglichkeit einzelne Buttons ein- und auszuschalten.

Ich muss eins dazu sagen:
Die GUI baut auf drei von mir selbst geschriebenen Klassen auf. Darunter zwei Verwaltungsklassen für Bilder und eine Vektorklasse. Die drei Klassen kommen also auch hier in diesen Beitrag.
Außerdem wird ein INIParser benötigt. Der Einfachheit halber stelle ich auch den, den ich noch habe rein.
Ihr könnt für alles beliebige Grafiken verwenden, der entsprechende Pfad muss dann halt in der INI Datei reingeschrieben werden und der Pfad für die INI Datei muss auch in der GUI geändert werden.

Langer Rede kurzer Sinn, hier die Klassen:

Vektorklasse:
BlitzMax: [AUSKLAPPEN]
Type TVector2D 'A Vector Class which can do any Operation on 2D Vectors
Field _fXComponent:Float 'X-Component of the Vector
Field _fYComponent:Float 'Y-Component of the Vector
Field _fAbsoluteValue:Float 'The Absoulte Value (Length) of the Vector

Method Get_X:Float() 'Method to get the X-Component
Return Self._fXComponent
End Method
Method Set_X(px:Float) 'Method to set the X-Component
Self._fXComponent = px
Self._fAbsoluteValue = Self.CalcLength()
End Method

Method Get_Y:Float() 'Method to get the Y-Component
Return Self._fYComponent
End Method
Method Set_Y(py:Float) 'Method to set the Y-Component
Self._fYComponent = py
Self._fAbsoluteValue = Self.CalcLength()
End Method

Method Get_Length:Float() 'Method to get the Absolute Value (Length)
Return Self._fAbsoluteValue
End Method

Method PrintValues()
Print "X-Component: " + Self._fXComponent
Print "Y-Component: " + Self._fYComponent
Print "Absolute Value: " + Self._fAbsoluteValue
End Method

Method GetValuesAsString:String()
Local tmpString:String = ("X/Y: " + Self._fXComponent + " / " + Self._fYComponent + " |vec|: " + Self._fAbsoluteValue)
Return tmpString
End Method

'Constructor of the Class
Function Create:TVector2D(pX:Float, pY:Float)
Local tmpVec2D:TVector2D = New TVector2D
tmpVec2D._fXComponent = pX
tmpVec2D._fYComponent = pY
tmpVec2D._fAbsoluteValue = tmpVec2D.CalcLength()
Return tmpVec2D
End Function

'"Constructor" for a Zero Vector
Function Zero:TVector2D()
Local tmpVec2D:TVector2D = New TVector2D
tmpVec2D = TVector2D.Create(0.0, 0.0) 'Just calls the Constructor with the parameters 0, 0
Return tmpVec2D
End Function

'Calculates the Length of a Vector
Method CalcLength:Float()
Return Sqr((Self._fXComponent * Self._fXComponent) + (Self._fYComponent * Self._fYComponent))
End Method

'Calculates the distance between two Vectors
Function CalcDistance:Float(pVec1:TVector2D, pVec2:TVector2D)
Return Sqr(((pVec1._fXComponent - pVec2._fXComponent) * (pvec1._fXComponent - pvec2._fXComponent)) + ((pvec1._fYComponent - pvec2._fYComponent) * (pvec1._fYComponent - pvec2._fYComponent)))
End Function

'Add two Vectors
Function Add:TVector2D(pVec1:TVector2D, pVec2:TVector2D)
Local tmpVec:TVector2D = New TVector2D
tmpVec = TVector2D.Create(pVec1._fXComponent + pVec2._fXComponent, pVec1._fYComponent + pVec2._fYComponent)
Return tmpVec
End Function

'Subtract two Vectors (vec1-vec2)
Function Subtract:TVector2D(pVec1:TVector2D, pVec2:TVector2D)
Local tmpVec:TVector2D = New TVector2D
tmpVec = TVector2D.Create(pVec1._fXComponent - pVec2._fXComponent, pVec1._fYComponent - pVec2._fYComponent)
Return tmpVec
End Function

'Multiplies a Vector with a number (makes the Vector shorter or longer)
Method Multiply:TVector2D(pFactor:Float)
Local tmpVec:TVector2D = New TVector2D
tmpVec = TVector2D.Create(pFactor * Self._fXComponent, pFactor * Self._fYComponent)
Return tmpVec
End Method

'Calculates the Scalar Product of two Vectors
Function ScalarProduct:Float(pVec1:TVector2D, pVec2:TVector2D)
Return ((pVec1._fXComponent * pVec2._fXComponent) + (pVec1._fYComponent * pVec2._fYComponent))
End Function

'Calculates the angle between two Vectors
Function AngleBetween:Int(pVec1:TVector2D, pVec2:TVector2D)
Local tmpResult:Float
'tmpResult =
tmpResult = ScalarProduct(pVec1, pVec2) / (pVec1._fAbsoluteValue * pVec2._fAbsoluteValue)
If ((tmpResult <= 1) And (tmpResult >= - 1)) Then 'Need to be a number between -1 and 1
Return Floor(ACos(tmpResult) + 0.5)
Else
Return (-1) 'Else it will be -1 (because the result of acos is between 180 and 0)
EndIf
End Function

'Tests if two Vectors are orthogonal (right angled)
Function AreOrthogonal:Int(pVec1:TVector2D, pVec2:TVector2D)
Local tmpValue:Float
tmpValue = ScalarProduct(pVec1, pVec2)
If tmpValue = 0 Then
Return True
Else
Return False
EndIf
End Function

'Calculates the orthogonal projection of Vec1 on Vec2
Function OrthogonalProjection:TVector2D(pVec1:TVector2D, pVec2:TVector2D)
Local tmpValue:Float
Local tmpVec:TVector2D = New TVector2D
tmpVec = TVector2D.Zero()
tmpValue = ScalarProduct(pVec1, pVec2.Standardise())
tmpVec = pVec2.Standardise().Multiply(tmpValue)
tmpVec.CalcLength()
Return tmpVec
End Function

'Calculates the unit Vector
Method Standardise:TVector2D()
Local tmpVec:TVector2D = New TVector2D
Local tmpValue:Float
tmpValue = 1 / Self._fAbsoluteValue
tmpVec = Self.Multiply(tmpValue)
Return tmpVec
End Method
End Type


Bilderklassen:
BlitzMax: [AUSKLAPPEN]
AutoMidHandle(True)
Type TLoadedImage 'Diese Klasse verwaltet die Bilder die in den Speicher geladen werden
Global lAllImages:TList = New TList

'Eigenschaften des Bildes
Field tiImage:TImage
Field sPath:String
Field sName:String
Field iWidth:Int, iHeight:Int
Field bAnimated:Byte

'Folgende nur für animierte Bilder
Field iCellWidth:Int = 0, iCellHeight:Int = 0
Field iFramesInTotal:Int = 0
Field iTimer:Int = 0

Method New()
lAllImages.AddLast(Self) 'Bild wird automatisch der Liste hinzugefügt
End Method

Method Destroy()
lAllImages.Remove(Self) 'Der Destruktor löscht das Bild automatisch aus der Liste
End Method

Function Create:TLoadedImage(sPath:String, sName:String) 'Konstruktor für ein nicht animiertes Bild
'* Überprüfen ob das Bild nicht schon vorhanden ist *
For Local Img:TLoadedImage = EachIn lAllImages
If (Img.sName = sName) Then
If (Img.sPath = sPath) Then
Return Null
EndIf
EndIf
Next

Local Image:TLoadedImage = New TLoadedImage

Image.sPath = sPath
Image.sName = sName
Image.bAnimated = False
Image.tiImage = LoadImage(sPath)
Image.iWidth = ImageWidth(Image.tiImage)
Image.iheight = ImageHeight(Image.tiImage)

Return Image
End Function

'*Konstruktor für ein animiertes Bild*
Function CreateAnimated:TLoadedImage(sPath:String, sName:String, iCellWidth:Int, iCellHeight:Int, iFramesInTotal:Int, iTimer:Int)
For Local Img:TLoadedImage = EachIn lAllImages
If (Img.sName = sName) Then
If (Img.sPath = sPath) Then
Return Null
EndIf
EndIf
Next

Local Image:TLoadedImage = New TLoadedImage

Image.sPath = sPath
Image.sName = sName
Image.bAnimated = True
Image.tiImage = LoadAnimImage(sPath, iCellwidth, iCellheight, 0, iFramesInTotal)
Image.iWidth = ImageWidth(Image.tiImage)
Image.iheight = ImageHeight(Image.tiImage)

Image.iCellwidth = iCellwidth
Image.iCellheight = iCellheight
Image.iFramesInTotal = iFramesInTotal
Image.iTimer = iTimer

Return Image
End Function

Function GetImage:TLoadedImage(sName:String) 'Ein bestimmtes Bild anhand des Namens zurückgeben
'Diese Function könnte noch genauer suchen wenn auch nach dem Pfad gesucht wird, jedoch kann das Problem von doppelten Namen durch
'Den Programmierer umgangen werden
For Local img:TLoadedImage = EachIn lAllImages
If (Lower(img.sname) = Lower(sName)) Then
Return img
EndIf
Next
Return Null
End Function
End Type

Type TDrawnImage 'Diese Klasse stellt ein Bild für jedes Objekt dar. Also: 100 Gegner = 10 Instanzen dieser Klasse
'Eigenschaften des Bildes
Field tiImage:TImage
Field iWidth:Int, iHeight:Int
Field bAnimated:Byte

'Folgende nur für animierte Bilder
Field iFramesInTotal:Int = 0
Field iActiveFrame:Int = 0
Field iTimer:Int = 0
Field iLastUpDate:Int = 0

Function Create:TDrawnImage(img:TLoadedImage)
Local Image:TDrawnImage = New TDrawnImage

Image.tiImage = img.tiImage
Image.bAnimated = img.bAnimated

If (img.bAnimated) Then
Image.iWidth = img.iCellwidth
Image.iHeight = img.iCellHeight
Image.iFramesInTotal = img.iFramesInTotal
Image.iTimer = img.iTimer
Image.iActiveFrame = 0
Image.iLastUpDate = MilliSecs()
Else
Image.iWidth = img.iWidth
Image.iHeight = img.iHeight
EndIf

Return Image
End Function

Method Animate() 'Das Bild wird automatisch animiert (Zeitsteuerung)
If (bAnimated)
If ((iTimer + iLastUpDate) < MilliSecs()) Then
iActiveFrame = (iActiveFrame + 1) Mod iFramesInTotal
iLastUpDate = MilliSecs()
EndIf
EndIf
End Method

Method AnimateManual() 'Das Bild wird einmal einen Animationsschritt weiter gebracht
If (bAnimated)
iActiveFrame = (iActiveFrame + 1) Mod iFramesInTotal
EndIf
End Method

Method Draw(v2dPos:TVector2D) 'Das Bild wird mit der momentanen Animation gemalt
DrawImage(tiImage, v2dPos.Get_X(), v2dPos.Get_Y(), iActiveFrame)
End Method

End Type


INIParser:
BlitzMax: [AUSKLAPPEN]
Function GetValue:String(sLine:String) 'Gibt den Wert, also das was rechts vom "=" steht zurück


If Instr(sLine, "=") > 0 Then
Return Right(sLine, Len(sLine) - Instr(sLine, "="))
Else
Return ""
EndIf

End Function

Function GetLeftPart:String(sLine:String) 'Gibt die Bezeichnung, also das was links vom "=" steht zurück
If Instr(sLine, "=") > 0 Then
Return Left(sLine, Instr(sLine, "=") - 1)
Else
Return ""
EndIf

End Function

Function SearchValue:String(sSearchTerm:String, fileINI:TStream, sFile:String) 'Sucht nach einem Schlüsselwort, aber nur in einer Sektion

Local sValue:String, sLine:String

While Not Eof(fileINI)
sLine = ReadLine(fileINI)
If Lower(Trim(GetLeftPart(sLine))) = Lower(sSearchTerm) Then
Return GetValue(sLine)
EndIf
If Left(sLine, 1) = "[" Then RuntimeError("Wert '" + sSearchTerm + "' konnte in dieser Sektion nicht gefunden werden")
Wend
RuntimeError("Wert '" + sSearchTerm + "' in '" + sFile + "' konnte nicht gefunden werden")

End Function

Function SearchInFile:String(sSection:String, sSearchTerm:String, sFile:String) 'Sucht in der Datei zuerst nach einer Sektion und dann nach dem Schlüsselwort

Local sValue:String, sLine:String, fileINI:TStream

sSection = "[" + sSection + "]"

If FileType(sFile) = 1 Then
fileINI = ReadFile(sFile)
While Not Eof(fileINI)
sLine = ReadLine(fileINI)
If Lower(sLine) = Lower(sSection) Then
sValue = SearchValue(sSearchTerm, fileINI, sFile)
CloseFile fileINI
Return Trim(sValue:String)
EndIf
Wend
RuntimeError("Sektion '" + sSection + "' in '" + sFile + "' konnte nicht gefunden werden")
Else
RuntimeError "Datei '" + sFile + "' existiert nicht."
EndIf

End Function



Die GUI:
BlitzMax: [AUSKLAPPEN]
Type TGui 'Type für die GUI
Global sWindowImageName:String
Global sButtonImageName:String
Global sINIPath:String = "Data/bin/GUI.ini" 'Unter Umständen abändern!

Function Render() 'Sorgt dafür, dass die komplette GUI angezeigt wird
For Local Win:TWindow = EachIn TWindow.lAllWindows 'Alle Fenster werden durchgegangen
If Win.bActive Then 'Wenn das Fenster nicht aktiv ist, wird es nicht beachtet
Win.Draw() 'Erst das Fenster malen
For Local Button:TButton = EachIn Win.lAllButtons
If Button.bActive
Button.Draw() 'Alle Buttons die zum Fenster gehören malen
EndIf
Next
EndIf
Next
End Function

Function Update(fxPos:Float, fyPos:Float, bMouseHit:Byte) 'Sorgt dafür, dass alle GUI Elemente und Ereignisse verarbeitet werden
Local Moved:Byte = False
For Local Win:TWindow = EachIn TWindow.lAllWindows 'Alle Fenster
If Win.bActive Then 'Nur wenn das Fenster aktiv ist
If Not Moved Then 'Wenn noch kein anderes Fenster bewegt wurd
If MouseDown(1) Then
Moved = Win.Move(fxPos, fyPos) 'Das Fenster bewegen wenn die Maus gedrückt ist
EndIf
EndIf
For Local Button:TButton = EachIn Win.lAllButtons
If Button.bActive
Button.IsOver(fxPos, fyPos)
Button.Click(bMouseHit) 'Alle zugehörigen Buttons auf Klicks überprüfen
EndIf
Next
EndIf
Next
End Function

Function Init() 'Bilder der einzelnen GUI-Elemente werden hier geladen
sWindowImageName = SearchInFile("Windows", "Imagename", sIniPath)
TLoadedImage.Create(SearchInFile("Windows", "Imagepath", sIniPath), sWindowImageName)

sButtonImageName = SearchInFile("Buttons", "Imagename", sIniPath)
TLoadedImage.CreateAnimated(SearchInFile("Buttons", "Imagepath", sINIPath), sButtonImageName, Int(SearchInFile("Buttons", "Cellwidth", sIniPath)), Int(SearchInFile("Buttons", "Cellheight", siniPath)), Int(SearchInFile("Buttons", "Totalframes", sINIPath)), 0)
End Function

End Type

Type TWindow 'Klasse für das Fenster
Global lAllWindows:TList = New TList 'Alle erstellten Fenster

Field lAllButtons:TList 'Alle zum Fenster gehörigen Buttons
Field v2dPosition:TVector2D 'Die Position
Field v2dSize:TVector2D 'Fenstermaße
Field bFullScreen:Byte 'Ob das Fenster den ganzen Bildschirm einnimmt
Field bActive:Byte 'Ob das Fenster aktiv ist
Field sName:String 'Name des Fensters
Field diImage:TDrawnImage
Field v2dImageScale:TVector2D 'Skalierungsfaktor für das Bild, falls das Fenster nicht genauso groß ist wie die Grafik

Function GetWindow:TWindow(sName:String) 'Sucht nach einem bestimmten Fenster über den Namen des Fensters
For Local win:TWindow = EachIn lAllWindows
If (Lower(win.sName) = Lower(sName)) Then
Return win 'Liefert ein Fenster zurück, wenn es gefunden wurde
EndIf
Next
Return Null 'Ansonsten NULL
End Function

Method New()
lAllWindows.AddLast(Self) 'Jedes neu erstellte Fenster wird in der Liste gespeichert
End Method

Method Destroy()
lAllWindows.Remove(Self) 'Jedes gelöschte Fenster wird aus der Liste entfernt
End Method

Function GetAllowedName:String(sName:String) 'Fenster dürfen nicht den gleichen Namen haben
Local bFoundName:Byte = False
Local i:Int = 1
If lAllWindows.IsEmpty() Then Return sName
Repeat
For Local wnd:TWindow = EachIn lAllWindows
If Lower(wnd.sName) = Lower(sName) Then 'Also setzt man einfach ein 1, 12, 123 etc dran
sName = sName + i
i = i + 1
Else
bFoundName = True
EndIf
Next
Until bFoundName 'irgendwann muss ein Name gefunden werden
Return sName
End Function

Function CreateFullscreen:TWindow(sName:String, bActive:Byte) 'Fenster im Vollbildmodus erstellen
sName = GetAllowedName(sName) 'Erst einen erlaubten Namen geben lassen

Local Window:TWindow = New TWindow

Window.lAllButtons = New TList 'Liste neu erstellen
Window.bFullScreen = True
Window.sName = sName
Window.bActive = bActive
Window.diImage = Null
Window.v2dImageScale = Null
Window.v2dPosition = Null 'Vollbild braucht keine Position
Window.v2dSize = Null 'Und auch keine Ausmaße

Return Window
End Function

Function Create:TWindow(sName:String, bActive:Byte, fxPos:Float, fyPos:Float, fxSize:Float, fySize:Float) '"Normales" Fenster erstellen
sName = GetAllowedName(sName) 'Erst einen erlaubten Namen finden

Local Window:TWindow = New TWindow

Window.lAllButtons = New TList
Window.bFullscreen = False
Window.sName = sName
Window.bActive = bActive
Window.diImage = TDrawnImage.Create(TLoadedImage.GetImage(TGUi.sWindowImageName))
Window.v2dPosition = TVector2d.Create(fxPos, fyPos) 'Die Positions festlegen
Window.v2dSize = TVector2d.Create(fxSize, fySize) 'Die Größe festlegen
Window.v2dImageScale = TVector2D.Create(Float(fxSize / Float(Window.diImage.iWidth)), Float(fysize / Float(Window.diImage.iHeight)))

Return Window
End Function

Method Draw() 'Hiermit wird das Fenster gezeichnet
If (Not bFullscreen)
SetColor(255,255,255)
SetScale(v2dImageScale.Get_X(), v2dImageScale.Get_Y())
diImage.Draw(v2dPosition)
SetScale(1.0,1.0)
SetColor(10,10,10) 'Die Schrift ist schwarz
DrawText(sName, v2dPosition.Get_X() - v2dSize.Get_X()/3 - TextWidth(sName)/2, v2dPosition.Get_y() - v2dSize.Get_y()/2 + v2dSize.get_y()/10 - TextHeight(sName)/2)
SetColor(255,255,255)
EndIf
End Method

Method Move:Byte(fxPos:Float, fyPos:Float) 'Das Fenster bewegen
If (Not bFullscreen) Then 'Das geht nur, wenn das Fenster kein Vollbild ist
If (InRectangle(fxPos, fyPos, v2dPosition.Get_x()-v2dSize.Get_X()/2, v2dPosition.Get_y()-v2dSize.Get_Y()/2, v2dSize.Get_x(), v2dSize.Get_y()/7)) Then 'Wenn die Maus sich auf der Statusleiste befindet
v2dPosition = Tvector2d.Create(fxpos, fyPos + v2dSize.Get_Y()/2 - 5) 'Position verändern
For Local Button:TButton = EachIn lAllButtons 'Alle zugehörigen Buttons müssen auch versetzt werden
Button.v2dPosition = TVector2d.Create(v2dPosition.Get_x()-v2dSize.Get_X()/2 + Button.v2dRelativePosition.Get_X(), v2dPosition.Get_y()-v2dSize.Get_Y()/2 + Button.v2dRelativePosition.Get_Y())
Next
Return True
EndIf
EndIf
Return False
End Method
End Type

Type TButton 'Type für die Buttons
Global lAllButtons:TList = New TList 'In der Liste werden alle Buttons gespeichert
Global v2dSize:TVector2D = TVector2D.Create(150, 50) 'Alle Buttons haben die gleiche Größe

Field fpFunction:Int() 'Funktionspointer
Field v2dPosition:TVector2D, v2dRelativePosition:TVector2D 'echte Bildschirmpositions und die relative Bildschirmposition (auf Fenstern)
Field sName:String 'Name und Beschriftung des buttons
Field bActive:Byte 'Ob der Button aktiv ist
Field bOver:Byte
Field diImage:TDrawnImage
Field v2dImageScale:Tvector2D
Field wParentWindow:TWindow 'Das Fenster zu dem der Button gehört

Method New()
lAllButtons.Addlast(Self) 'Button wird der Liste automatisch hinzugefügt
End Method

Method Destroy()
lAllButtons.Remove(Self) 'Buttons wird automatisch von der Liste entfernt
End Method

Function Create:TButton(sName:String, bActive:Byte, fxPos:Float, fyPos:Float, fpfunction:Int(), sParentName:String) 'Konstruktor
Local Button:TButton = New TButton

Local wParent:TWindow

For Local Win:TWindow = EachIn TWindow.lAllwindows
If Lower(Win.sName) = Lower(sParentName) Then wParent = Win 'Das richtige Fenster wird gesucht
Next

Button.sName = sName
Button.bActive = bActive
Button.wParentWindow = wParent
Button.fpFunction = fpfunction
Button.bOver = False
Button.diImage = TDrawnImage.Create(TLoadedImage.GetImage(TGUi.sButtonImageName))
Button.v2dRelativePosition = TVector2D.Create(fxPos, fyPos) 'Relative Position bleibt immer gleich
Button.v2dImageScale = TVector2D.Create(Float(v2dSize.Get_X() / Float(Button.diImage.iWidth)), Float(v2dSize.Get_Y() / Float(Button.diImage.iHeight)))

If (wParent.v2dPosition = Null) Then 'Wenn das Parent-Fenster ein Vollbildfenster ist
Button.v2dPosition = TVector2D.Create(fxPos, fyPos) 'Dann wird der Button direkt im Fenster ausgerichtet
Else
'Ansonsten wird der Button relativ zum Fenster ausgerichtet
Button.v2dPosition = TVector2D.Create(wParent.v2dPosition.Get_x()-wParent.v2dSize.Get_x()/2 + fxPos, wParent.v2dPosition.Get_y()-wParent.v2dSize.Get_y()/2 + fyPos)
EndIf

wParent.lAllButtons.AddLast(Button) 'Den Button nocht in die Liste des Fensters einfügen

Return Button
End Function

Method Draw() 'Hier wird der Button gemalt
SetColor(255, 255, 255)
SetScale(v2dImageScale.get_X(), v2dImageScale.Get_Y())
diImage.Draw(v2dPosition)
SetScale(1.0, 1.0)
SetColor(10, 10, 10)
DrawText(sname, v2dPosition.Get_x() - TextWidth(sName)/2, v2dPosition.Get_y() - TextHeight(sName)/2)
SetColor(255,255,255)
End Method

Function GetButton:TButton(sWindowName:String, sButtonName:String) 'Einen bestimmen Button finden
For Local win:TWindow = EachIn TWindow.lAllWindows
If (Lower(win.sName) = Lower(sWindowName)) Then
For Local Button:TButton = EachIn win.lAllButtons
If (Lower(Button.sName) = Lower(sButtonName)) Then
Return Button
EndIf
Next
EndIf
Next
Return Null
End Function

Method IsOver(fxPos:Float, fyPos:Float) 'Überprüft ob die Maus innerhalb des Buttons ist
If (InRectangle(fxpos, fypos, v2dPosition.Get_x()-v2dSize.Get_X()/2, v2dPosition.Get_y()-v2dSize.get_y()/2, TButton.v2dSize.Get_x(), TButton.v2dSize.Get_y())) Then
If bover = False Then diImage.AnimateManual()
bOver = True
Else
If bOver Then diImage.AnimateManual()
bOver = False
EndIf
End Method

Method Click(Hit:Byte) 'Funktion die den Funktionspointer des Buttons aufruft, wenn darauf geklickt wird
If bOver Then
If (Hit) Then
fpFunction
EndIf
EndIf
End Method
End Type


Der Inhalt der momentanen INI Datei:
Zitat:
[Windows]
Imagename = GUIWinImage
Imagepath = Data/gfx/GUI/Window.png

[Buttons]
Imagename = GUIButImage
Imagepath = Data/gfx/GUI/ButtonCamouflage.png
CellWidth = 150
Cellheight = 50
Totalframes = 2


Die Buttongrafik: https://www.blitzforum.de/upload/file.php?id=10379
Die Fenstergrafik (die mir nicht so gut gelungen ist): https://www.blitzforum.de/upload/file.php?id=10380

Und hier ist noch ein kleines Testprogramm.
Der erste Teil demonstriert einfach verschiedene Fenstergrößen.
Wenn man dann Escape drückt kommt man in den zweiten Teil. Dort ist ein kleines Menü aufgebaut.
JEDER Button erfüllt hier eine Funktion.

BlitzMax: [AUSKLAPPEN]
SuperStrict
AutoMidHandle(1)
SeedRnd(MilliSecs())
Include "Includes/GUI.bmx"
Include "Includes/ImageTypes.bmx"
Include "Includes/InIParser.bmx"
Include "Includes/SmallFunctions.bmx"
Include "Includes/VectorClass.bmx"

?MacOS
SetGraphicsDriver GLMax2DDriver()
Graphics(1024, 768)

TGUI.Init()

Global color:Int[] = [0,0,0]

Function test()
color[0] = Rand(0,255)
color[1] = Rand(0,255)
color[2] = Rand(0,255)
End Function

'sName:String, bActive:Byte, fxPos:Float, fyPos:Float, fxSize:Float, fySize:Float
'sName:String, bActive:Byte, fxPos:Float, fyPos:Float, fpfunction:Int(), sParentName:String
TWindow.Create("Optionen", True, 300, 300, 200, 100)
TWindow.Create("Menue", True, 500, 400, 300, 50)
TWindow.Create("Game", True, 300, 100, 400, 200)
TButton.Create("Change", True, 100, 100, test, "Game")
Local MyBut:TButton = TButton.GetButton("Game", "Me")
If MyBut <> Null Then DebugLog MyBut.sName

Repeat
Cls
SetClsColor(color[0], color[1], color[2])
Local mHit:Byte = MouseHit(1)
TGUI.Update(MouseX(), MouseY(), mHit)
TGui.Render()
Flip 0
Until KeyHit(Key_Escape)

TWindow.lAllWindows.Clear()
TButton.lAllButtons.Clear()

Function ChangeToOptions()
tWindow.GetWindow("Mainmenue").bActive = False
TWindow.GetWindow("Options").bActive = True
End Function

Function ChangeToMain()
TWindow.GetWindow("Mainmenue").bActive = True
TWindow.GetWindow("Options").bActive = False
TWindow.GetWindow("Sound").bActive = False
End Function

Function Sounds()
tWindow.GetWindow("Sound").bActive = Not tWindow.GetWindow("Sound").bActive
End Function

Function Extras()
TButton.GetButton("Sound", "Change").bActive = Not TButton.GetButton("Sound", "Change").bActive
End Function

TWindow.CreateFullScreen("Mainmenue", True)
TWindow.CreateFullScreen("Options", False)
TWindow.Create("Sound", False, GraphicsWidth()/2, GraphicsHeight()/2+100, 400, 300)
TButton.Create("Options", True, GraphicsWidth()/2, GraphicsHeight()/2, ChangeToOptions, "Mainmenue")
TButton.Create("Main", True, GraphicsWidth()/2, 150, ChangeToMain, "Options")
TButton.Create("Sound", True, GraphicsWidth()/2, 250, Sounds, "Options")
TButton.Create("Extra", True, 200, 120, Extras, "Sound")
TButton.Create("Change", False, 200, 190, test, "Sound")

Repeat
Cls
SetClsColor(color[0], color[1], color[2])
Local mHit:Byte = MouseHit(1)
TGUI.Update(MouseX(), MouseY(), mHit)
TGui.Render()
Flip 0
Until KeyHit(Key_Escape)


Über Kritik oder Anregung würde ich mich sehr freuen.
Hoffe, dass jemand was damit anfangen kann.

Achja, ich weiß dass im Titel erweiterbar steht: Die GUI ist im Prinzip auch leicht mit neuen Elementen erweiterbar. Dazu muss man der Fensterklasse nur eine neue Liste für das entsprechende Element spendieren und das Element implementieren. In der TGUI Klasse muss dann nur noch die entsprechende Liste in den Fenstern durchgegangen werden.

lg, M0rgenstern

blackgecko

BeitragSo, März 06, 2011 19:15
Antworten mit Zitat
Benutzer-Profile anzeigen
Also ohne das getestet zu haben fällt mir auf, dass du im Testcode den Kompiler-Flag für MacOS setzt und gar nicht wieder schließt Confused
2. Ich würde deine GUI sehr gern testen, aber mir die Codes zusammenzukopieren und dann noch in den Dateien rumzueditieren ist mir zu aufwändig. Pack doch bitte alles was man braucht in ein Archiv, das man in einem Rutsch runterladen kann Wink
So long and thanks for all the fish.
Fedora 17 | Windows 7 || BlitzPlus | BlitzMax
Rechtschreibflame GO!!! Deppenapostroph | SeidSeit | Deppenakzent | DassDas | Deppenleerzeichen | TodTot | enzigste.info - Ja, ich sammel die.

M0rgenstern

BeitragSo, März 06, 2011 19:53
Antworten mit Zitat
Benutzer-Profile anzeigen
Hast ja Recht, tut mir Leid.
So ists einfacher: https://www.blitzforum.de/upload/file.php?id=10381

Das mit der Kompilerflag: Ich arbeite momentan auf nem Mac, von daher war die eh unnütz.
Ist jetzt entfernt.

Lg, M0rgenstern

empet

BeitragSo, März 06, 2011 22:25
Antworten mit Zitat
Benutzer-Profile anzeigen
habs auch mal getestet und mir ist aufgefallen, dass man das fenster verliert wenn man zu schnell mit der maus fährt. vielleicht kannst du das noch beheben. irgendwie prüfen ob man beim letzten rendern ein fenster in der hand hatte und dann (wenn mousedown=1) immer noch verschieben.

blackgecko

BeitragMo, März 07, 2011 23:33
Antworten mit Zitat
Benutzer-Profile anzeigen
Also wie schon gesagt, Verschieben von Fenstern ist sehr hakelig, besonders beim Verschieben nach oben.
Ich hab auch mal ein bisschen durch den Code geschaut. Vom Aufbau gar nicht schlecht, Funktionspointer bei den Buttons, gefällt mir.
Aber eine Sache muss ich doch heftig kritisieren: Beim Erstellen eines Buttons muss man als Parent den Namen des Fensters (also das was in der Titelleiste steht) angeben!
Finde ich vorsichtig ausgedrückt eine sehr fragwürdige Methode. Warum nicht einfach die Variable des Fensters mitgeben?
BlitzMax: [AUSKLAPPEN]
Function Create:TButton(sName:String, bActive:Byte, fxPos:Float, fyPos:Float, fpfunction:Int(), sParentName:String)
'wird zu
Function Create:TButton(sName:String, bActive:Byte, fxPos:Float, fyPos:Float, fpfunction:Int(), parentWin:TWindow)
Dann kannst du dir auch die aufwändige Schleife danach sparen um das richtige Fenster zu suchen und du hast nicht mehr das Problem dass mehrere Fenster nicht den gleichen Namen haben dürfen (Oder hat das noch einen anderen Grund?)
Bleibt noch zu sagen, dass das kleine Beispielprogramm meine CPU-Auslastung auf 100% hochjagt - warum auch immer.
So long and thanks for all the fish.
Fedora 17 | Windows 7 || BlitzPlus | BlitzMax
Rechtschreibflame GO!!! Deppenapostroph | SeidSeit | Deppenakzent | DassDas | Deppenleerzeichen | TodTot | enzigste.info - Ja, ich sammel die.

M0rgenstern

BeitragDi, März 08, 2011 13:53
Antworten mit Zitat
Benutzer-Profile anzeigen
Also, wegen der Fenstersache: Das ist mir auch schon aufgefallen und ich weiß im Prinzip auch, woran es liegt.
Muss da nur ein wenig feintuning bei der Berechnung etc betreiben, dann sollte das passen.

@blackgecko:
1. Bei mir bleibt die CPU Auslastung konstant unter 30%, warum das bei dir also so ist, weiß ich nicht. Versuch mal in das Beispielprogramm nen Timer einzubauen, momentan läufts nämlich mit der maximal möglichen Framezahl, weil ich keinen Timer eingebaut habe.
2. Das was du mit dem Fensternamen bemängelst war ein Knackpunkt bei der Implementation. Ich wollte es zuerst so machen, wie du vorschlägst. Aber: Dann muss man jedes Fenster folgendermaßen erstellen:
BlitzMax: [AUSKLAPPEN]
Local/Global Options:TWindow = TWindow.Create(...)

Das heißt man hat X Instanzen in Variablen da rumliegen. Ich fand das einfach die unschönere Lösung als sich den Namen zu merken.
Wie gesagt, ich finds einfach unnötig, die Fenster in einer Liste UND als Instanzen vorliegen zu haben.
Vielleicht überleg ich mir noch ne andere Lösung.

Lg, M0rgenstern

DaysShadow

BeitragDi, März 08, 2011 16:28
Antworten mit Zitat
Benutzer-Profile anzeigen
Da musst du aber noch ein wenig mehr betreiben als Feintuning.

Klicke ich in die Titelleiste verschiebt sich das Fenster schon, die Fenster werden nicht sortiert, sodass das aktive Fenster unter den inaktiven bleibt und die inaktiven Fenster bewegen sich wenn ich ein anderes unter dessen Titelleiste schiebe.

Zudem treibt dein Programm einen Quadcore mit 3,4 GHz insgesamt auf über 40%, das ist meiner Meinung nach inakzeptabel, denn wie soll das bitte bei kleineren Rechnern aussehen?
Dazu ist dein Beispiel ja nichtmal wirklich umfangreich.

Da musst du nochmal ordentlich ran.
Blessed is the mind too small for doubt
 

Macintosh

BeitragMi, März 09, 2011 15:53
Antworten mit Zitat
Benutzer-Profile anzeigen
Hey, ich arbeite zz. auch an einer UI für BMax.
Diese ist schon relativ fortgeschritten und nutzt FBO's für das rendern der views.
Somit wird ein View nur neu gezeichnet, wenn das auch benötigt wird.

Zu deiner UI:
Das bewegen der Fenster ist leider sehr schwieirig, auch springt es immer zum mittelpunkt, also die maus.
Funktionspointer für die Button actions zu nutzen halte ich auch nicht für so gut, am besten währe hier reflection.
Ansonsten ganz nett :)

M0rgenstern

BeitragSo, März 13, 2011 23:03
Antworten mit Zitat
Benutzer-Profile anzeigen
@DaysShadow: Also, ich weiß nicht, was das ist, aber bei mir ist die Auslastung viel viel geringer und ich habe keinen so Leistungsfähigen PC.
Was genau meinst du mit mein Beispielprogramm ist nicht wirklich umfangreich? Da ist im Prinzip alles gezeigt was man machen kann, was fehlt dir denn?

@Macintosh: Das mit den Fenstern ist mir wie gesagt aufgefallen.
Auf die Gefahr hin mich zu outen, aber: Was zu Hölle ist reflection?

Lg, M0rgenstern

Xaymar

ehemals "Cgamer"

BeitragSo, März 13, 2011 23:09
Antworten mit Zitat
Benutzer-Profile anzeigen
BlitzMax: [AUSKLAPPEN]
Function Hello:Object()
Return New HelloWorld
End Function

Local a:Object = Hello()

If HelloWorld(a) <> Null

EndIf

Das afaik
Warbseite

M0rgenstern

BeitragSo, März 13, 2011 23:15
Antworten mit Zitat
Benutzer-Profile anzeigen
Sorry, aber wie kann man das für Buttons nutzen?

Lg, M0rgenstern
 

n-Halbleiter

BeitragSo, März 13, 2011 23:40
Antworten mit Zitat
Benutzer-Profile anzeigen
@Xaymar: Das ist Polymorphismus in Ansätzen.

Reflection (Modul BRL.Reflection) ist die Möglichkeit, anhand von Namen Types und darin enthaltene Methoden und Felder anzusprechen. Es gibt auf bb.com - für die Leute, die mehr Funktionen benötigen - eine Erweiterung, die mehr Funktionen mit sich bringt (so kann man bspw. Pointer und Funktionen nutzen).

Beispielcodes gibt's in der Dokumentation, allerdings mag das Arrayverhalten nicht direkt einleuchtend sein.

@Morgenstern: Du kannst Reflection zum Beispiel nutzen, indem du deinen Buttons eine TMethod als Feld gibst, die dann bei einem Klick aufgerufen wird.
mfg, Calvin
Maschine: Intel Core2 Duo E6750, 4GB DDR2-Ram, ATI Radeon HD4850, Win 7 x64 und Ubuntu 12.04 64-Bit
Ploing!
Blog

"Die Seele einer jeden Ordnung ist ein großer Papierkorb." - Kurt Tucholsky (09.01.1890 - 21.12.1935)

DaysShadow

BeitragSo, März 13, 2011 23:44
Antworten mit Zitat
Benutzer-Profile anzeigen
Mit "nicht umfangreich" meine ich, dass nicht viel Inhalt in deinem Beispiel vorhanden ist, dass die Auslastung rechtfertigen würde.
Dein Beispielprogramm allein schwankt um die 25% Auslastung und was zeigst du? 4 Fenster und 5 Buttons.
Meinst du ernsthaft, dass die 25% Auslastung beanspruchen sollten?
Bzw. frag doch mal ganz ehrlich dich selbst, ob du fremden Code nutzen würdest der diese Eigenschaft hätte?

Ich habe bei meinen Projekten noch einen GUI-Versuch von mir, von dem ich nicht behaupten würde, dass er meinen jetzigen Ansprüchen genügen würde und er ist auch nicht vollständig.
Damit kann ich allerdings ebenfalls skinbare Fenster und Buttons anzeigen, die Objekte sind geordnet und reagieren wie man es erwartet wenn man sie in der Titelleiste zieht.
Mein Testprogramm dazu schwankt bei mir um die 0-2% Auslastung.
Meine Ansichten sind halt beim Programmieren so ausgelegt, dass die Auslastung dem Aufwand entsprechen muss und allgemein versuche ich immer die niedrigste Auslastung mit meinem Code zu erzielen.
Wenn er nichts hochgradig komplexes verarbeitet, erwarte ich eben, dass die Auslastung z.B. <= 5% ist.

Ich will dein Projekt ja nicht schlecht machen, allerdings denke ich ganz einfach, dass dein Code erhebliche Überarbeitung bis zum Rewrite braucht oder du irgendwo, grob gesagt, Mist geschrieben hast der eben zum Angesprochenen führt.
Kann auch sein, dass ich da zu hart bin, da mich die Sache mit der Auslastung so lange quälen würde bis ich es behoben hätte.
Es ist gut, wenn es in meinen Augen perfekt ist Wink

Naja, viel Text, aber vielleicht gibt es dir ja zu denken.
Blessed is the mind too small for doubt
  • Zuletzt bearbeitet von DaysShadow am So, März 13, 2011 23:54, insgesamt einmal bearbeitet

Xaymar

ehemals "Cgamer"

BeitragSo, März 13, 2011 23:47
Antworten mit Zitat
Benutzer-Profile anzeigen
@n-halbleiter: Ich dachte immer reflection=reflektieren -> eine variable steht für eine bestimmte sache, hier Object/Type
Warbseite
 

n-Halbleiter

BeitragSo, März 13, 2011 23:55
Antworten mit Zitat
Benutzer-Profile anzeigen
Das wäre Polymorphismus. Wink

Quasi-Definition: Eine Objektreferenz kann in verschiedene Überklassen "gecastet" werden, bzw. als diese gespeichert werden (macht z.B. bei Containerklassen Sinn).

Memo an mich: Nicht einfach an die englischen Begriffe die deutsche Endung ranpacken, im Deutschen heißt das ganze "Polymorphie", im Englischen "polymophism". Wikipedia sagt dazu
mfg, Calvin
Maschine: Intel Core2 Duo E6750, 4GB DDR2-Ram, ATI Radeon HD4850, Win 7 x64 und Ubuntu 12.04 64-Bit
Ploing!
Blog

"Die Seele einer jeden Ordnung ist ein großer Papierkorb." - Kurt Tucholsky (09.01.1890 - 21.12.1935)
 

Macintosh

BeitragMo, März 14, 2011 1:59
Antworten mit Zitat
Benutzer-Profile anzeigen
Mit der Reflection und dem Button:

Der Button besitzt 2 Felder.
Ein mal sein Target (ein Objekt) und als Zweites eine TMethod die im Target aufgerufen werden soll.
Dann kannst du dir deine Controller-Klasse schreiben, und ihre Instanz als target des Buttons setzen.

Der Kann dann auch der Klasse methoden aufrufen.

Das ist weit aus sauebrer und man braucht die views/gadgets nicht mehr als globale wie wenn du Funktionspointer benutzt.

M0rgenstern

BeitragMo, März 14, 2011 15:05
Antworten mit Zitat
Benutzer-Profile anzeigen
So, also, wegen der Reflection: Sorry, aber ich hab kaum was verstanden.
Wäre lieb, wenn mir das bitte jemand etwas grundlegender erklären könnte.

@DaysShadow:
Du hast Recht, wenn nichts komplexes berechnet wird etc sollte ein Programm kaum Auslastung haben und 25% sind einfach zu viel, da hast du auch Recht.
Ich habe jetzt extra was ausprobiert und: Meine erste Vermutung, die ich schon schrieb, war richtig. Ich wollte nämlich anfangs wissen, wie viele FPS mit der GUI momentan möglich sind und habe den Timer rausgelassen.
Jetzt habe ich den Timer nur mal in die zweite Schleife eingebaut, aber nicht in die erste. Und siehe da: zwischen 1 und 4% Auslastung, was ich für vollkommen in Ordnung halte, oder wie siehst du das?
Es liegt also nicht an nem groben Schlitzer in der GUI sondern daran, dass ich keinen Timer benutzt habe.

Lg, M0rgenstern
 

n-Halbleiter

BeitragMo, März 14, 2011 16:27
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich dachte, das was ich schrieb, sei grundlegend genug. Wink

Egal, nochmal zum mitschreiben:

BlitzMax-Dokumentation hat Folgendes geschrieben:
BlitzMax provides limited support for a form of runtime reflection.

Using reflection, programs can 'inspect' objects and types at runtime. You can determine the fields and methods contained in an object or type, set and get object fields and invoke - or 'call' - object methods.


Bedeutet: Du kannst über die Namen, die du im Quellcode genutzt hast, deine Types und Objekte ansprechen und deren Werte verändern.
mfg, Calvin
Maschine: Intel Core2 Duo E6750, 4GB DDR2-Ram, ATI Radeon HD4850, Win 7 x64 und Ubuntu 12.04 64-Bit
Ploing!
Blog

"Die Seele einer jeden Ordnung ist ein großer Papierkorb." - Kurt Tucholsky (09.01.1890 - 21.12.1935)

M0rgenstern

BeitragMo, März 14, 2011 22:16
Antworten mit Zitat
Benutzer-Profile anzeigen
Okay, ich hab mir jetzt mal die brl.Reflections angesehen und verstehe jetzt was Reflections sind.
Was ich aber nicht verstehe: Wie hilft das bei Buttons weiter, bzw warum macht das Buttons einfacher, bzw wie kann man das für Buttons benutzen?
Kann mir bitte jemand dazu ein Beispiel dazu geben, wie das funktioneirt, weil ich mir das momentan nicht vorstellen kann.

Lg, M0rgenstern

BladeRunner

Moderator

BeitragDi, März 15, 2011 7:34
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich persönlich würde dir im Normalfall sogar eher davon abraten, da Reflection zumindest bei größerem Gebrauch recht schwerfällig sein kann. Ansonsten ist es aber eine schöne Sache, denn Du kannst all deinen Objekten passende Methoden mitgeben und die mittels eines frei setzbaren Strings(!) aufrufen.
Zu Diensten, Bürger.
Intel T2300, 2.5GB DDR 533, Mobility Radeon X1600 Win XP Home SP3
Intel T8400, 4GB DDR3, Nvidia GF9700M GTS Win 7/64
B3D BMax MaxGUI

Stolzer Gewinner des BAC#48, #52 & #92

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group