Skip to content

Instantly share code, notes, and snippets.

@RednibCoding
Last active April 30, 2025 12:21
Show Gist options
  • Save RednibCoding/bb9542a03fb29370c0b4951c589a8b62 to your computer and use it in GitHub Desktop.
Save RednibCoding/bb9542a03fb29370c0b4951c589a8b62 to your computer and use it in GitHub Desktop.
BlitzMax immediate mode gui
SuperStrict
Include "Gui.bmx"
Const wwidth:Int = 1280
Const wheight:Int = 980
Graphics wwidth, wheight
' === GUI STATE ===
Local sliderValue1:Float = 0.3
Local sliderValue2:Float = 0.75
Local sliderValue3:Float = 0.5
Local dropdownOptions:String[] = ["Option A", "Option B", "Option C"]
Local dropdownSelected:Int
Local listItems:String[] = ["Item 1", "Item 2", "Item 3", "Item 4"]
Local listSelected:Int
Local toggleA:Int = False
Local toggleB:Int = False
Local toggleC:Int = False
Local checkFeature:Int = True
Local checkFullscreen:Int = False
Local gridToggles:Int[9] ' ← 9 unique toggle states
Local username:String = ""
Local showModal:Int = False
Local appleImg:TImage = LoadImage("apple.png")
Local progressVal:Float = 0.42
Local tabNames:String[] = ["General", "Audio", "Video", "Advanced"]
Local currentTab:Int = 0
Repeat
Local mx:Int = MouseX()
Local my:Int = MouseY()
Local md:Int = MouseDown(1)
Local mzs:Int = MouseZSpeed()
Cls
' =============================
' === Render Your Game Here ===
' =============================
' === Render GUI afterwards ===
GuiInit(mx, my, md, mzs, wwidth, wheight) ' Must be called before any other Gui function
' === PANEL 1: GENERAL CONTROLS ===
GuiBegin(0, 0, 400)
GuiPanel()
GuiLabel("General Controls")
GuiScrollviewBegin("General", wheight- 30)
GuiLabel("Single Column:")
GuiButton("Click Me")
GuiToggleButton("Toggle A", toggleA)
GuiCheckbox("Enable Feature", checkFeature)
GuiSpacer(8)
GuiLabel("Slider Controls:")
GuiRowBegin(2)
GuiLabel("Speed:")
GuiSlider(sliderValue1, 0, 1)
GuiRowEnd()
GuiRowBegin(2)
GuiLabel("Weight:")
GuiSlider(sliderValue2, 0, 1)
GuiRowEnd()
GuiSpacer(16)
GuiLabel("List:")
GuiListView(listItems, listSelected)
GuiSpacer(8)
GuiDropdown("A nice dropdown", dropdownOptions, dropdownSelected)
GuiSpacer(8)
If GuiButton("Show Modal") Then showModal = True
If GuiButton("Show Toast") Then GuiToast("Toast is shown for 3 sec!", 80, 160, 220, 3000, False)
GuiSpacer(24)
For Local i:Int = 0 Until 20
If GuiButton("Item " + i) Then print("Nice "+ i)
Next
GuiScrollviewEnd()
GuiEnd()
' === PANEL 2: VISUAL TOGGLES ===
GuiBegin(405, 0, 300)
GuiPanel()
GuiLabel("Image Toggles")
GuiLabel("Image Buttons:")
GuiRowBegin(2)
GuiImageButton(appleImg, 64, 64, "Static", 0)
GuiImageToggleButton(appleImg, toggleB, 64, 64, "Toggle", 0)
GuiRowEnd()
GuiSpacer(12)
GuiLabel("Toggle Grid:")
GuiGridBegin(3)
For Local i:Int = 0 Until gridToggles.length
GuiImageToggleButton(appleImg, gridToggles[i], 64, 64, "", 1)
Next
GuiGridEnd()
GuiEnd()
' === PANEL 3: USER + SLIDERS ===
GuiBegin(710, 0, 250)
GuiPanel()
GuiLabel("User Input & Sliders")
GuiLabel("Enter Username:")
username = GuiTextInput("username", username)
GuiLabel("Hello, " + username)
GuiSpacer(8)
GuiLabel("Fine Tune Sliders:")
GuiRowBegin(2)
GuiLabel("Gamma:" + Int(sliderValue3*100))
GuiSlider(sliderValue3, 0, 1)
GuiRowEnd()
GuiSpacer(8)
GuiLabel("Progress Bars:")
progressVal = sliderValue3
GuiProgressBar(progressVal, 0, 1)
GuiScrollviewBegin("Test", 160)
For Local i:Int = 0 Until gridToggles.length
If GuiToggleButton("Item " + i, gridToggles[i]) Then print("Nice "+ i)
Next
GuiScrollviewEnd()
GuiEnd()
' === PANEL 4: SETTINGS TABS ===
GuiBegin(965, 0, 315)
GuiPanel()
GuiLabel("Settings")
GuiTabBar(tabNames, currentTab)
Select currentTab
Case 0
GuiCheckbox("Auto Save", toggleC)
GuiButton("Run Benchmark")
Case 1
GuiRowBegin(2)
GuiLabel("Volume:")
GuiSlider(sliderValue2, 0, 1)
GuiRowEnd()
Case 2
GuiCheckbox("Fullscreen", checkFullscreen)
GuiButton("Video Settings")
Case 3
GuiLabel("Debug Mode:")
GuiToggleButton("Enable Logs", toggleA)
End Select
GuiEnd()
' === MODAL ===
If showModal Then
GuiModalBegin("Confirmation", 320, 160)
GuiLabel("Are you sure?")
GuiSpacer(8)
GuiRowBegin(2)
If GuiButton("Confirm") Then
showModal = False
GuiToast("Action confirmed!", 30, 180, 80, 2000)
End If
If GuiButton("Cancel") Then
showModal = False
End If
GuiRowEnd()
GuiModalEnd()
End If
GuiFinalize() ' Must be called after all Gui functions
Flip
Until KeyHit(KEY_ESCAPE) Or AppTerminate()
' === GUI STATE SETUP ===
Type TGuiState
Field winWidth:Int, winHeight:int
Field mouseX:Int, mouseY:Int
Field guiMouseOver:Int
Field mouseWheelDelta:Int
Field mouseDown:Int, lastMouseDown:Int
Field spacing:Int = 4
Field widgetHeight:Int = 24
Field cursorX:Int, cursorY:Int
Field panelX:Int, panelY:Int, panelWidth:Int
Field storedPanelX:Int
Field storedPanelY:Int
Field storedPanelWidth:Int
Field storedCursorX:Int
Field storedCursorY:Int
Field inRow:Int
Field rowStartX:Int
Field rowCursorX:Int
Field rowMaxHeight:Int
Field rowColumns:Int
Field rowElementWidth:Int
Field inGrid:Int
Field gridColumns:Int
Field gridIndex:Int
Field gridStartX:Int
Field gridCellWidth:Int
Field gridRowHeight:Int
Field activePopup:TDropdownPopup = Null
Field dropdownOpenStates:TMap = New TMap
Field justOpenedPopupId:String
Field popupBlocking:Int
Field capturingModal:Int
Field insideModal:Int
Field modalActive:Int
Field modalWidth:Int
Field modalHeight:Int
Field modalTitle:String
Field modalX:Int
Field modalY:Int
Field modalDrawQueue:TList ' list of DrawCommand
Field toastTime:Int = 0
Field toastText:String = ""
Field toastR:Int, toastG:Int, toastB:Int
Field toastDuration:Int = 0
Field toastActive:Int = False
Field toastAlignTop:Int = True
Field activeInputId:String
Field inputCursorTimer:Int
Field inScrollview:Int
Field scrollviewX:Int
Field scrollviewY:Int
Field scrollviewW:Int
Field scrollviewH:Int
Field scrollviewContentH:Int
Field savedOriginX:Int
Field savedOriginY:Int
Field scrollOffsets:TMap = New TMap()
Field currentScrollId:String
Field backupColorR:Int
Field backupColorG:Int
Field backupColorB:Int
Field backupColorA:Float
Field backupBlend:Int
Field primaryColor: TGuiColor = New TGuiColor(55, 95, 125)
Field primaryAccentColor: TGuiColor = New TGuiColor(76, 114, 143)
Field secondaryColor: TGuiColor = New TGuiColor(55, 55, 61)
Field secondaryAccentColor: TGuiColor = New TGuiColor(80, 80, 90)
Field backgroundColor: TGuiColor = New TGuiColor(31, 31, 31)
Field backgroundColorDark: TGuiColor = New TGuiColor(22, 22, 22)
Field textColor: TGuiColor = New TGuiColor(220, 220, 220)
Method Reset()
cursorX = 0
cursorY = 0
panelX = 0
panelY = 0
panelWidth = 200
End Method
End Type
Global _theGuiState:TGuiState = New TGuiState
Type TDrawCmd
Field fn:Int ' 0=rect,1=text,2=imageRect…
Field image:TImage
Field args:Object[] ' blitz arrays of ints/floats/strings
End Type
Type TDropdownPopup
Field id:String
Field x:Int, y:Int, w:Int, h:Int
Field selected:Int Ptr
Field open:Int Ptr
Field options:String[]
Field onSelectionChangedCallback(idx:Int)
End Type
Type TScrollviewState
Field offset:Int
Field dragging:Int
Field dragStartY:Int
Field initialOffsetY:Int
Field needsScrollbar:Int
End Type
Type TGuiColor
Field r:Int
Field g:Int
Field b:Int
Method New (r:Int, g:Int, b:Int)
self.r = r
self.g = g
self.b = b
End Method
End Type
Function GuiInit(mx:Int, my:Int, mdown:Int, mouseWheelDelta:Int, winWidth:int, winHeight:int)
_theGuiState.mouseX = mx
_theGuiState.mouseY = my
_theGuiState.lastMouseDown = _theGuiState.mouseDown
_theGuiState.mouseDown = mdown
_theGuiState.mouseWheelDelta = mouseWheelDelta
_theGuiState.winWidth = winWidth
_theGuiState.winHeight = winHeight
_theGuiState.guiMouseOver = False
_StoreColors()
' Needed for the modal backdrop
SetBlend(ALPHABLEND)
End Function
Function GuiFinalize()
' At last, render all dropdown popups
If (_theGuiState.modalActive)
_theGuiState.popupBlocking = True
EndIf
_GuiRenderModal()
_GuiRenderDropdownPopups()
_GuiRenderToast()
_RestoreColors()
SetBlend(_theGuiState.backupBlend)
End Function
Function GuiMouseOver:Int()
Return _theGuiState.guiMouseOver
End Function
Function GuiCurrentY:Int()
Return _theGuiState.cursorY
End Function
' === THEMEING FUNCTIONS ===
Function GuiSetPrimaryColor(normal:TGuiColor, accent:TGuiColor)
_theGuiState.primaryColor = normal
_theGuiState.primaryAccentColor = accent
EndFunction
Function GuiSetSecondaryColor(normal:TGuiColor, accent:TGuiColor)
_theGuiState.secondaryColor = normal
_theGuiState.secondaryAccentColor = accent
EndFunction
Function GuiSetBackgroundColor(normal:TGuiColor, dark:TGuiColor)
_theGuiState.backgroundColor = normal
_theGuiState.backgroundColorDark = dark
EndFunction
Function GuiSetTextColor(color:TGuiColor)
_theGuiState.textColor = color
EndFunction
' === GUI FUNCTIONS ===
Function GuiBegin(x:Int, y:Int, width:Int = 200)
_theGuiState.Reset()
_theGuiState.panelX = x
_theGuiState.panelY = y
_theGuiState.panelWidth = width
_theGuiState.cursorX = x
_theGuiState.cursorY = y
End Function
Function GuiEnd()
' Restore the color settings
_RestoreColors()
End Function
Function GuiModalBegin(title:String, w:Int, h:Int)
' make sure we’re not already inside a row, grid or scrollview
If _theGuiState.inRow Or _theGuiState.inGrid Or _theGuiState.inScrollview Then
' RuntimeError("GuiModalBegin(): cannot open a modal while inside a row/grid/scrollview")
Print("GuiModalBegin(): cannot open a modal while inside a row/grid/scrollview")
End
End If
' Enter modal mode
_theGuiState.modalActive = True
_theGuiState.popupBlocking = True
_theGuiState.capturingModal = True
_theGuiState.insideModal = True
' Store modal window parameters
_theGuiState.modalTitle = title
_theGuiState.modalWidth = w
_theGuiState.modalHeight = h
_theGuiState.modalX = (_theGuiState.winWidth - w) / 2
_theGuiState.modalY = (_theGuiState.winHeight - h) / 2
' Start with an empty queue of draw commands
_theGuiState.modalDrawQueue = CreateList()
_theGuiState.justOpenedPopupId = ""
' Snapshot your entire layout state so we can restore it later
_GuiStoreLayout()
' Begin a new gui section at the modal’s top‐left (below its title bar)
GuiBegin(_theGuiState.modalX, _theGuiState.modalY + 28 + 8, w)
End Function
Function GuiModalEnd()
GuiEnd()
_theGuiState.capturingModal = False
_GuiRestoreLayout()
_theGuiState.insideModal = False
End Function
' === GUI LAYOUT FUNCTIONS ===
Function GuiRowBegin(columns:Int)
_theGuiState.inRow = True
_theGuiState.rowColumns = columns
_theGuiState.rowStartX = _theGuiState.cursorX
_theGuiState.rowCursorX = _theGuiState.cursorX
_theGuiState.rowMaxHeight = 0
_theGuiState.rowElementWidth = (_theGuiState.panelWidth - 16 - (_theGuiState.spacing * (columns - 1))) / columns
End Function
Function GuiRowEnd()
_theGuiState.cursorY :+ _theGuiState.rowMaxHeight + _theGuiState.spacing
_theGuiState.cursorX = _theGuiState.rowStartX
_theGuiState.inRow = False
End Function
Function GuiGridBegin(columns:Int)
_theGuiState.inGrid = True
_theGuiState.gridColumns = columns
_theGuiState.gridIndex = 0
_theGuiState.gridStartX = _theGuiState.cursorX + 8
_theGuiState.gridCellWidth = (_theGuiState.panelWidth - 16 - (_theGuiState.spacing * (columns - 1))) / columns
_theGuiState.gridRowHeight = 0
End Function
Function GuiGridEnd()
If _theGuiState.gridRowHeight > 0 Then
_theGuiState.cursorY :+ _theGuiState.gridRowHeight + _theGuiState.spacing
End If
_theGuiState.inGrid = False
End Function
Function GuiScrollviewBegin(id:String, height:Int)
If _theGuiState.capturingModal Then
RuntimeError("Scrollviews inside modals are not supported")
EndIf
If _theGuiState.inScrollview
RuntimeError("Nested Scrollviews are not supported")
EndIf
If _theGuiState.inRow Or _theGuiState.inGrid
RuntimeError("Scrollviews inside rows/grid not supported")
EndIf
_theGuiState.inScrollview = True
_theGuiState.currentScrollId = id
_theGuiState.scrollviewX = _theGuiState.panelX
_theGuiState.scrollviewY = _theGuiState.cursorY
_theGuiState.scrollviewW = _theGuiState.panelWidth
_theGuiState.scrollviewH = height
_theGuiState.scrollviewContentH = 0
Local state:TScrollviewState = TScrollviewState(_theGuiState.scrollOffsets.ValueForKey(id))
If Not state Then
state = New TScrollviewState
_theGuiState.scrollOffsets.Insert(id, state)
End If
' Mouse wheel scroll
If _theGuiState.mouseX >= _theGuiState.scrollviewX And _theGuiState.mouseX <= _theGuiState.scrollviewX + _theGuiState.scrollviewW And ..
_theGuiState.mouseY >= _theGuiState.scrollviewY And _theGuiState.mouseY <= _theGuiState.scrollviewY + height Then
state.offset :- _theGuiState.mouseWheelDelta * 20
End If
If state.offset < 0 Then state.offset = 0
' Reduce width for scrollbar space
If state.needsScrollbar Then
_theGuiState.panelWidth = _theGuiState.scrollviewW - 4
EndIf
SetViewport(_theGuiState.scrollviewX, _theGuiState.scrollviewY, _theGuiState.scrollviewW, height)
SetOrigin(0, -state.offset)
_theGuiState.cursorY = _theGuiState.scrollviewY
End Function
Function GuiScrollviewEnd()
SetOrigin(_theGuiState.savedOriginX, _theGuiState.savedOriginY)
SetViewport(0, 0, _theGuiState.winWidth, _theGuiState.winHeight)
Local x:Int = _theGuiState.scrollviewX + _theGuiState.scrollviewW - 8
Local y:Int = _theGuiState.scrollviewY
Local w:Int = 8
Local h:Int = _theGuiState.scrollviewH
Local contentH:Int = _theGuiState.scrollviewContentH
Local id:String = _theGuiState.currentScrollId
Local state:TScrollviewState = TScrollviewState(_theGuiState.scrollOffsets.ValueForKey(id))
If Not state Then state = New TScrollviewState
If contentH > h Then
state.needsScrollbar = True
Local handleH:Int = Max(20, Int(Float(h) / Float(contentH) * h))
Local maxScroll:Int = contentH - h
Local handleY:Int = y + Int(Float(state.offset) / Float(maxScroll) * (h - handleH))
_GuiSetColor(_theGuiState.backgroundColorDark.r, _theGuiState.backgroundColorDark.g, _theGuiState.backgroundColorDark.b)
_GuiDrawRect(x, y, w, h)
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x, handleY, w, handleH)
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And ..
_theGuiState.mouseY >= handleY And _theGuiState.mouseY <= handleY + handleH
If hot And _theGuiState.mouseDown = 1 And _theGuiState.lastMouseDown = 0 Then
state.dragging = True
state.dragStartY = _theGuiState.mouseY
state.initialOffsetY = state.offset
End If
If state.dragging Then
If _theGuiState.mouseDown = 0 Then
state.dragging = False
Else
Local dy:Int = _theGuiState.mouseY - state.dragStartY
Local maxY:Int = h - handleH
state.offset = state.initialOffsetY + Int(Float(dy) / Float(maxY) * Float(maxScroll))
state.offset = _Clamp(state.offset, 0, maxScroll)
End If
End If
Else
state.offset = 0
state.needsScrollbar = False
End If
If contentH > h Then
state.offset = _Clamp(state.offset, 0, contentH - h)
Else
state.offset = 0
End If
_theGuiState.cursorY = _theGuiState.scrollviewY + _theGuiState.scrollviewH + _theGuiState.spacing
_theGuiState.inScrollview = False
_theGuiState.currentScrollId = ""
' Restore full panel width
_theGuiState.panelWidth = _theGuiState.scrollviewW
End Function
' === WIDGETS ===
Function GuiPanel(height:Int=0)
If _theGuiState.capturingModal Then
RuntimeError("Panel inside modal not supported")
EndIf
' Panel bounds
Local x:Int = _theGuiState.panelX
Local y:Int = _theGuiState.panelY
Local w:Int = _theGuiState.panelWidth
Local h:Int = height
If h = 0 Then h = _theGuiState.winHeight
' Mouse‑over test
If _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And _theGuiState.mouseY >= y And _theGuiState.mouseY <= y + h Then
_theGuiState.guiMouseOver = True
EndIf
' Draw border
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x, y, w, h)
' Draw background
_GuiSetColor(_theGuiState.backgroundColor.r, _theGuiState.backgroundColor.g, _theGuiState.backgroundColor.b)
_GuiDrawRect(x + 1, y + 1, w - 2, h - 2)
' Start laying out inside the panel
_theGuiState.cursorX = x + _theGuiState.spacing
_theGuiState.cursorY = y + _theGuiState.spacing
End Function
Function GuiSpacer(pixels:Int = 0)
If _theGuiState.inGrid Then
' Skip a grid cell and adjust row height if needed
_theGuiState.gridIndex :+ 1
If pixels > _theGuiState.gridRowHeight Then
_theGuiState.gridRowHeight = pixels
End If
If _theGuiState.gridIndex Mod _theGuiState.gridColumns = 0 Then
_theGuiState.cursorY :+ _theGuiState.gridRowHeight + _theGuiState.spacing
_theGuiState.gridRowHeight = 0
End If
Return
End If
If _theGuiState.inRow Then
If pixels > 0 Then
' Apply horizontal spacing
_theGuiState.rowCursorX :+ pixels
If pixels > _theGuiState.rowMaxHeight Then
_theGuiState.rowMaxHeight = pixels
End If
Else
' Skip one column in the row (like a dummy element)
_theGuiState.rowCursorX :+ _theGuiState.rowElementWidth + _theGuiState.spacing
End If
Return
End If
' Default vertical spacing when not in grid or row
_theGuiState.cursorY :+ pixels
End Function
Function GuiLabel(text:String, centerX:Int = False, centerY:Int = True, colspan:Int = 1)
Local w:Int = _theGuiState.panelWidth - 16
Local h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
Local textW:Int = TextWidth(text)
Local textH:Int = TextHeight(text)
' Horizontal alignment
Local drawX:Int = x
If centerX Then drawX = x + (w - textW) / 2
' Vertical alignment
Local drawY:Int = y
If centerY Then drawY = y + (h - textH) / 2
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(text, drawX, drawY)
h = Max(textH, h)
_UpdateScrollViewContentHeight(h)
_EndWidget(h)
End Function
Function GuiColorRect:Int(r:Int, g:Int, b:Int, height:Int = 0, label:String = "", colspan:Int = 1)
Local x:Int, y:Int, w:Int, h:Int
' determine widget height override
If height > 0 Then
h = height
Else
h = _theGuiState.widgetHeight
End If
' layout + hit‐rect
_BeginWidget(colspan, x, y, w, h)
Local my:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And my >= y And my <= y + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
' pick fill color (lighten on hot)
Local fr:Int = r, fg:Int = g, fb:Int = b
If hot Then
fr = Min(fr + 20, 255)
fg = Min(fg + 20, 255)
fb = Min(fb + 20, 255)
End If
_GuiSetColor(fr, fg, fb)
_GuiDrawRect(x, y, w, h)
' draw centered label if any
If label <> "" Then
Local tw:Int = TextWidth(label)
Local th:Int = TextHeight(label)
Local tx:Int = x + (w - tw) / 2
Local ty:Int = y + (h - th) / 2
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(label, tx, ty)
End If
_EndWidget(h)
If _theGuiState.popupBlocking And Not _theGuiState.insideModal Then Return 0
Return clicked
End Function
Function GuiButton:Int(label:String, secondary:Int=False, colspan:Int = 1)
Local w:Int, h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And mouseY >= y And mouseY <= y + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
If hot Then
If secondary Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
Else
_GuiSetColor(_theGuiState.primaryAccentColor.r, _theGuiState.primaryAccentColor.g, _theGuiState.primaryAccentColor.b)
EndIf
Else
If secondary Then
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
Else
_GuiSetColor(_theGuiState.primaryColor.r, _theGuiState.primaryColor.g, _theGuiState.primaryColor.b)
EndIf
End If
_GuiDrawRect(x, y, w, h)
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
Local textWidth:Int = TextWidth(label)
Local textHeight:Int = TextHeight(label)
_GuiDrawText(label, x + (w - textWidth) / 2, y + (h - textHeight) / 2)
_EndWidget(h)
If _theGuiState.popupBlocking And Not _theGuiState.insideModal Then Return 0
Return clicked
End Function
Function GuiToggleButton:Int(label:String, state:Int Var, colspan:Int = 1)
Local w:Int, h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And mouseY >= y And mouseY <= y + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
If clicked And (Not _theGuiState.popupBlocking Or _theGuiState.insideModal) Then state = Not state
' Background and border
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x, y, w, h)
If state Then
_GuiSetColor(_theGuiState.primaryAccentColor.r, _theGuiState.primaryAccentColor.g, _theGuiState.primaryAccentColor.b)
ElseIf hot Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
Else
_GuiSetColor(_theGuiState.backgroundColorDark.r, _theGuiState.backgroundColorDark.g, _theGuiState.backgroundColorDark.b)
End If
_GuiDrawRect(x + 1, y + 1, w - 2, h - 2)
' Label
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
Local textWidth:Int = TextWidth(label)
Local textHeight:Int = TextHeight(label)
_GuiDrawText(label, x + (w - textWidth) / 2, y + (h - textHeight) / 2)
_EndWidget(h)
Return clicked
End Function
Function GuiImageButton:Int(img:TImage, w:Int, h:Int, text:String = "", align:Int = 1, secondary:Int = False, colspan:Int = 1)
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And mouseY >= y And mouseY <= y + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
' Background
If hot Then
If secondary Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
Else
_GuiSetColor(_theGuiState.primaryAccentColor.r, _theGuiState.primaryAccentColor.g, _theGuiState.primaryAccentColor.b)
EndIf
Else
If secondary Then
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
Else
_GuiSetColor(_theGuiState.primaryColor.r, _theGuiState.primaryColor.g, _theGuiState.primaryColor.b)
EndIf
End If
_GuiDrawRect(x, y, w, h)
' DRAW IMAGE + TEXT
If img Then
Local iw:Int = ImageWidth(img)
Local ih:Int = ImageHeight(img)
Local scaleX:Float = Float(w - 4) / iw
Local scaleY:Float = Float(h - 4) / ih
Local scale:Float = Min(scaleX, scaleY)
Local drawW:Float = iw * scale
Local drawH:Float = ih * scale
Local textW:Int = 0
If text <> "" Then
textW = TextWidth(text)
End If
Local margin:Int = 4
Select align
Case 0 ' image left
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, x + 6, y + (h - drawH) / 2, drawW, drawH)
Case 2 ' image right
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, x + w - drawW - 6, y + (h - drawH) / 2, drawW, drawH)
Case 1 ' image + text (or just image) centered
Local startX:Float
If text <> "" Then
' center image + text together
Local totalW:Float = drawW + margin + textW
startX = x + (w - totalW) / 2
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, startX, y + (h - drawH) / 2, drawW, drawH)
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(text, startX + drawW + margin, y + (h - TextHeight(text)) / 2)
text = "" ' consume so leftover text won't draw again
Else
' no text → just center the image
startX = x + (w - drawW) / 2
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, startX, y + (h - drawH) / 2, drawW, drawH)
End If
End Select
End If
' DRAW ONLY TEXT IF ANY REMAINS
If text <> "" Then
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(text, x + (w - TextWidth(text)) / 2, y + (h - TextHeight(text)) / 2)
End If
_EndWidget(h)
If _theGuiState.popupBlocking And Not _theGuiState.insideModal Then Return 0
Return clicked
End Function
Function GuiImageToggleButton:Int(img:TImage, state:Int Var, w:Int, h:Int, text:String = "", align:Int = 1, colspan:Int = 1)
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And mouseY >= y And mouseY <= y + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
If clicked And (Not _theGuiState.popupBlocking Or _theGuiState.insideModal) Then
state = Not state
EndIf
' Frame
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x, y, w, h)
If state Then
_GuiSetColor(_theGuiState.primaryAccentColor.r, _theGuiState.primaryAccentColor.g, _theGuiState.primaryAccentColor.b)
ElseIf hot Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
Else
_GuiSetColor(_theGuiState.backgroundColorDark.r, _theGuiState.backgroundColorDark.g, _theGuiState.backgroundColorDark.b)
EndIf
_GuiDrawRect(x + 1, y + 1, w - 2, h - 2)
' DRAW IMAGE + TEXT
If img Then
Local iw:Int = ImageWidth(img)
Local ih:Int = ImageHeight(img)
Local scaleX:Float = Float(w - 4) / iw
Local scaleY:Float = Float(h - 4) / ih
Local scale:Float = Min(scaleX, scaleY)
Local drawW:Float = iw * scale
Local drawH:Float = ih * scale
Local textW:Int = 0
If text <> "" Then textW = TextWidth(text)
Local margin:Int = 4
Select align
Case 0
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, x + 6, y + (h - drawH) / 2, drawW, drawH)
Case 2
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, x + w - drawW - 6, y + (h - drawH) / 2, drawW, drawH)
Case 1
Local startX:Float
If text <> "" Then
' center image + text
Local totalW:Float = drawW + margin + textW
startX = x + (w - totalW) / 2
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, startX, y + (h - drawH) / 2, drawW, drawH)
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(text, startX + drawW + margin, y + (h - TextHeight(text)) / 2)
text = "" ' consume text so the later block doesn't redraw it
Else
' no text: just center image
startX = x + (w - drawW) / 2
_GuiSetColor(255, 255, 255)
_GuiDrawImageRect(img, startX, y + (h - drawH) / 2, drawW, drawH)
EndIf
End Select
EndIf
' DRAW ONLY TEXT IF ANY REMAINS
If text <> "" Then
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(text, x + (w - TextWidth(text)) / 2, y + (h - TextHeight(text)) / 2)
EndIf
_EndWidget(h)
Return clicked
End Function
Function GuiCheckbox:Int(label:String, state:Int Var, colspan:Int = 1)
Local h:Int = _theGuiState.widgetHeight
Local w:Int
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + h And mouseY >= y And mouseY <= y + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
If clicked And (Not _theGuiState.popupBlocking Or _theGuiState.insideModal) Then
state = Not state
End If
' Outer box
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x + 2, y + 2, h - 4, h - 4)
' Inner fill if checked
If state Then
_GuiSetColor(_theGuiState.primaryAccentColor.r, _theGuiState.primaryAccentColor.g, _theGuiState.primaryAccentColor.b)
_GuiDrawRect(x + 4, y + 4, h - 8, h - 8)
End If
' Label text
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(label, x + h + 8, y + (h - TextHeight(label)) / 2)
_EndWidget(h)
Return clicked
End Function
Function GuiTextInput:String(id:String, text:String Var, maxLen:Int = 256, onBlur(value:String) = Null, colspan:Int = 1)
Local w:Int, h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
' hit‐testing
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And mouseY >= y And mouseY <= y + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
' block any interaction outside the modal
Local blockedOutside:Int = _theGuiState.popupBlocking And Not _theGuiState.insideModal
' if we’re allowed to, mark GUI‐over so clicks don’t leak
If hot And Not blockedOutside Then
_theGuiState.guiMouseOver = True
End If
' focus/defocus
If Not blockedOutside Then
If clicked Then
_theGuiState.activeInputId = id
_theGuiState.inputCursorTimer = MilliSecs()
ElseIf _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1 And Not hot
If _theGuiState.activeInputId = id Then
_theGuiState.activeInputId = ""
If onBlur Then onBlur(text)
End If
End If
End If
_UpdateScrollViewContentHeight(h)
' text entry when focused
If _theGuiState.activeInputId = id Then
Local c:Int = GetChar()
While c
If c = 8 Then
If text.length > 0 Then text = Left(text, text.length - 1)
ElseIf c >= 32 And c < 127
If text.length < maxLen Then text :+ Chr(c)
End If
c = GetChar()
Wend
End If
' drawing
If _theGuiState.activeInputId = id Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
Else
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
EndIf
_GuiDrawRect(x, y, w, h)
_GuiSetColor(_theGuiState.backgroundColorDark.r, _theGuiState.backgroundColorDark.g, _theGuiState.backgroundColorDark.b)
_GuiDrawRect(x+1, y+1, w-2, h-2)
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(text, x + 4, y + (h - TextHeight(text)) / 2)
' blinking cursor
If _theGuiState.activeInputId = id Then
Local t:Int = MilliSecs() - _theGuiState.inputCursorTimer
If (t / 500 Mod 2) = 0 Then
Local tw:Int = TextWidth(text)
_GuiDrawRect(x + 4 + tw, y + 4, 2, h - 8)
End If
End If
_EndWidget(h)
Return text
End Function
Function GuiSlider:Float(value:Float Var, min:Float, max:Float, onValueChanged(value:Float) = Null, colspan:Int = 1)
Local w:Int, h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x, y + h / 2 - 4, w, 8)
Local sliderX:Int = x + (value - min) / (max - min) * w
_GuiSetColor(_theGuiState.primaryColor.r, _theGuiState.primaryColor.g, _theGuiState.primaryColor.b)
_GuiDrawRect(sliderX - 6, y + h / 2 - 6, 12, 12)
Local mouseY:Int = _GuiMouseY()
If _theGuiState.mouseDown = 1 And mouseY >= y And mouseY <= y + h And _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w
If Not _theGuiState.popupBlocking Or _theGuiState.insideModal
value = Min + Float(_theGuiState.mouseX - x) / Float(w) * (max - min)
If onValueChanged Then onValueChanged(value)
End If
End If
_EndWidget(h)
Return value
End Function
Function GuiProgressBar(current:Float, min:Float, max:Float, colspan:Int = 1)
Local w:Int, h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
Local value:Float = _Clamp((current - min) / (max - min), 0.0, 1.0)
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x, y + h / 2 - 4, w, 8)
_GuiSetColor(_theGuiState.primaryColor.r, _theGuiState.primaryColor.g, _theGuiState.primaryColor.b)
_GuiDrawRect(x, y + h / 2 - 4, w * value, 8)
_EndWidget(h)
End Function
Function GuiDropdown:Int(id:String, options:String[], selected:Int Var, onSelectionChanged(idx:Int) = Null, colspan:Int = 1)
Local w:Int, h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
' === Get open state for this dropdown ===
Local open:Int = False
If _theGuiState.dropdownOpenStates.Contains(id) Then
open = Int(String(_theGuiState.dropdownOpenStates.ValueForKey(id)))
End If
' === Handle toggle ===
If Not _theGuiState.popupBlocking Or _theGuiState.insideModal
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And mouseY >= y And mouseY <= y + h
If hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1 Then
open = Not open
_theGuiState.dropdownOpenStates.Insert(id, String(open))
If open Then _theGuiState.justOpenedPopupId = id
End If
End If
' === Draw main box ===
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
_GuiDrawRect(x, y, w, h)
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
_GuiDrawRect(x + 1, y + 1, w - 2, h - 2)
If selected < options.length And selected >= 0 Then
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(options[selected], x + 8, y + (h - TextHeight(options[selected])) / 2)
EndIf
_GuiDrawText("v", x + w - TextWidth("v") - 8, y + (h - TextHeight("v")) / 2)
_EndWidget(h)
' === Setup popup data ===
Local state:TScrollviewState = TScrollviewState(_theGuiState.scrollOffsets.ValueForKey(_theGuiState.currentScrollId))
Local offset:Int
If state Then offset = state.offset
If open Then
_theGuiState.activePopup = New TDropdownPopup
_theGuiState.activePopup.id = id
_theGuiState.activePopup.x = x
_theGuiState.activePopup.y = y + h - offset
_theGuiState.activePopup.w = w
_theGuiState.activePopup.h = h
_theGuiState.activePopup.options = options
_theGuiState.activePopup.onSelectionChangedCallback = onSelectionChanged
_theGuiState.activePopup.selected = VarPtr selected
_theGuiState.activePopup.open = VarPtr open
ElseIf _theGuiState.activePopup And _theGuiState.activePopup.id = id Then
_theGuiState.activePopup = Null
End If
Return selected
End Function
Function GuiListView:Int(items:String[], selected:Int Var, onSelectionChanged(idx:Int) = Null, colspan:Int = 1)
Local w:Int, h:Int = _theGuiState.widgetHeight
Local x:Int, y:Int
_BeginWidget(colspan, x, y, w, h)
' Draw each item row
For Local i:Int = 0 Until items.length
Local itemY:Int = _theGuiState.cursorY + i * h
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= x And _theGuiState.mouseX <= x + w And mouseY >= itemY And mouseY <= itemY + h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
If Not _theGuiState.popupBlocking Or _theGuiState.insideModal
If clicked Then
selected = i
If onSelectionChanged Then onSelectionChanged(i)
EndIf
End If
If i = selected Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
ElseIf hot Then
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
Else
_GuiSetColor(_theGuiState.backgroundColorDark.r, _theGuiState.backgroundColorDark.g, _theGuiState.backgroundColorDark.b)
End If
_GuiDrawRect(x, itemY, w, h)
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(items[i], x + 8, itemY + (h - TextHeight(items[i])) / 2)
Next
' Update scrollview height
_UpdateScrollViewContentHeight(items.length * h)
_EndWidget(items.length * h)
Return selected
End Function
Function GuiTabBar:Int(tabs:String[], selected:Int Var, colspan:Int = 1)
Local tabCount:Int = tabs.length
If tabCount = 0 Then Return -1
Local tabHeight:Int = _theGuiState.widgetHeight
Local tabWidth:Int, totalWidth:Int
Local x:Int, y:Int
_BeginWidget(colspan, x, y, totalWidth, tabHeight)
tabWidth = (totalWidth - (_theGuiState.spacing * (tabCount - 1))) / tabCount
For Local i:Int = 0 Until tabCount
Local tx:Int = x + i * (tabWidth + _theGuiState.spacing)
Local mouseY:Int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= tx And _theGuiState.mouseX <= tx + tabWidth And mouseY >= y And mouseY <= y + tabHeight
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
If Not _theGuiState.popupBlocking Or _theGuiState.insideModal
If clicked Then selected = i
EndIf
' Draw background
If selected = i Then
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
ElseIf hot Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
Else
_GuiSetColor(_theGuiState.backgroundColorDark.r, _theGuiState.backgroundColorDark.g, _theGuiState.backgroundColorDark.b)
End If
_GuiDrawRect(tx, y, tabWidth, tabHeight)
' Label
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
Local textW:Int = TextWidth(tabs[i])
Local textH:Int = TextHeight(tabs[i])
_GuiDrawText(tabs[i], tx + (tabWidth - textW) / 2, y + (tabHeight - textH) / 2)
' Active underline
If selected = i Then
_GuiSetColor(_theGuiState.primaryColor.r, _theGuiState.primaryColor.g, _theGuiState.primaryColor.b)
_GuiDrawRect(tx, y + tabHeight - 3, tabWidth, 3)
End If
Next
' Layout update
_EndWidget(tabHeight)
Return selected
End Function
Function GuiToast(text:String, r:Int, g:Int, b:Int, timeMS:Int, alignTop:Int = False)
If Not _theGuiState.toastActive Then
_theGuiState.toastText = text
_theGuiState.toastR = r
_theGuiState.toastG = g
_theGuiState.toastB = b
_theGuiState.toastDuration = timeMS
_theGuiState.toastTime = MilliSecs()
_theGuiState.toastActive = True
_theGuiState.toastAlignTop = alignTop
End If
End Function
' === Helpers Private Functions ===
Function _Clamp:Float(value:Float, minVal:Float, maxVal:Float)
If value < minVal Then Return minVal
If value > maxVal Then Return maxVal
Return value
End Function
Function _StoreColors()
' Save the current color settings so we can restore it after gui drawing
GetColor(_theGuiState.backupColorR, _theGuiState.backupColorG, _theGuiState.backupColorB)
_theGuiState.backupColorA = GetAlpha()
_theGuiState.backupBlend = GetBlend()
End Function
Function _RestoreColors()
SetColor(_theGuiState.backupColorR, _theGuiState.backupColorG, _theGuiState.backupColorB)
SetAlpha(_theGuiState.backupColorA)
End Function
Function _GuiMouseY:Int()
If _theGuiState.inScrollview Then
Local state:TScrollviewState = TScrollviewState(_theGuiState.scrollOffsets.ValueForKey(_theGuiState.currentScrollId))
If state Then
Return _theGuiState.mouseY + state.offset
End If
End If
Return _theGuiState.mouseY
End Function
Function _UpdateScrollViewContentHeight(widgetHeight:Int)
If _theGuiState.inScrollview Then
Local bottom:Int = (_theGuiState.cursorY + widgetHeight) - _theGuiState.scrollviewY
If bottom > _theGuiState.scrollviewContentH Then
_theGuiState.scrollviewContentH = bottom
End If
End If
End Function
Function _GuiRenderDropdownPopups()
_theGuiState.popupBlocking = False
If Not _theGuiState.activePopup Then Return
Local p:TDropdownPopup = _theGuiState.activePopup
_UpdateScrollViewContentHeight(p.h)
' Mouse over popup? Block interaction behind
If _theGuiState.mouseX >= p.x And _theGuiState.mouseX <= p.x + p.w And _theGuiState.mouseY >= p.y And _theGuiState.mouseY <= p.y + p.h * p.options.length Then
_theGuiState.popupBlocking = True
End If
For Local i:Int = 0 Until p.options.length
Local itemY:Int = p.y + i * p.h
Local mouseY:int = _GuiMouseY()
Local hot:Int = _theGuiState.mouseX >= p.x And _theGuiState.mouseX <= p.x + p.w And mouseY >= itemY And mouseY <= itemY + p.h
Local clicked:Int = hot And _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1
If clicked
p.selected[0] = i
If p.onSelectionChangedCallback Then
p.onSelectionChangedCallback(i)
EndIf
p.open[0] = False
_theGuiState.activePopup = Null
_theGuiState.dropdownOpenStates.Insert(p.id, String(0))
Return
End If
If p.selected[0] = i Then
_GuiSetColor(_theGuiState.secondaryAccentColor.r, _theGuiState.secondaryAccentColor.g, _theGuiState.secondaryAccentColor.b)
ElseIf hot Then
_GuiSetColor(_theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b)
Else
_GuiSetColor(_theGuiState.backgroundColorDark.r, _theGuiState.backgroundColorDark.g, _theGuiState.backgroundColorDark.b)
End If
_GuiDrawRect(p.x, itemY, p.w, p.h)
_GuiSetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
_GuiDrawText(p.options[i], p.x + 8, itemY + (p.h - TextHeight(p.options[i])) / 2)
Next
' Close if clicked outside (skip if just opened this frame)
If _theGuiState.mouseDown = 0 And _theGuiState.lastMouseDown = 1 Then
If _theGuiState.justOpenedPopupId <> p.id Then
If Not (_theGuiState.mouseX >= p.x And _theGuiState.mouseX <= p.x + p.w And _theGuiState.mouseY >= p.y And _theGuiState.mouseY <= p.y + p.h * p.options.length) Then
p.open[0] = False
_theGuiState.activePopup = Null
_theGuiState.dropdownOpenStates.Insert(p.id, String(0))
End If
End If
End If
_theGuiState.justOpenedPopupId = ""
End Function
Function _GuiRenderToast()
If Not _theGuiState.toastActive Then Return
Local elapsed:Int = MilliSecs() - _theGuiState.toastTime
If elapsed > _theGuiState.toastDuration Then
_theGuiState.toastActive = False
Return
End If
Local w:Int = _theGuiState.winWidth
Local h:Int = 32
Local y:Int = 0
If Not _theGuiState.toastAlignTop Then
y = _theGuiState.winHeight - h
End If
SetColor(_theGuiState.toastR, _theGuiState.toastG, _theGuiState.toastB)
DrawRect(0, y, w, h)
SetColor(_theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b)
Local textW:Int = TextWidth(_theGuiState.toastText)
Local textH:Int = TextHeight(_theGuiState.toastText)
DrawText(_theGuiState.toastText, (w - textW) / 2, y + (h - textH) / 2)
End Function
' === For deferred modal rendering (so the modal is always ontop) ===
Function _GuiSetColor(r:int, g:Int, b:Int)
If _theGuiState.capturingModal
Local cmd:TDrawCmd = New TDrawCmd
cmd.fn = 0
cmd.args = [String(r),String(g), String(b)]
_theGuiState.modalDrawQueue.AddLast(cmd)
Else
SetColor(r, g, b)
EndIf
End Function
Function _GuiDrawText(s:String,x:Float,y:Float)
If _theGuiState.capturingModal
Local cmd:TDrawCmd = New TDrawCmd
cmd.fn = 1
cmd.args = [s, String(x), String(y)]
_theGuiState.modalDrawQueue.AddLast(cmd)
Else
DrawText(s, x, y)
EndIf
End Function
Function _GuiDrawRect(x:Float,y:Float,w:Float,h:Float)
If _theGuiState.capturingModal
Local cmd:TDrawCmd = New TDrawCmd
cmd.fn = 2
cmd.args = [String(x),String(y),String(w),String(h)]
_theGuiState.modalDrawQueue.AddLast(cmd)
Else
DrawRect(x,y,w,h)
EndIf
End Function
Function _GuiDrawImageRect(image:TImage, x:Float, y:Float, w:Float, h:Float, frame:Int=0)
If _theGuiState.capturingModal Then
Local cmd:TDrawCmd = New TDrawCmd
cmd.fn = 3
cmd.image = image
cmd.args = [ String(x), String(y), String(w), String(h), String(frame) ]
_theGuiState.modalDrawQueue.AddLast( cmd )
Else
DrawImageRect(image, x, y, w, h, frame)
EndIf
End Function
Function _GuiRenderModal()
If Not _theGuiState.modalActive Then Return
_theGuiState.guiMouseOver = True
' 1) Fade backdrop
SetColor 0, 0, 0
SetAlpha 0.7
DrawRect(0, 0, _theGuiState.winWidth, _theGuiState.winHeight)
SetAlpha 1.0
' 2) Modal window frame + title
SetColor _theGuiState.backgroundColor.r, _theGuiState.backgroundColor.g, _theGuiState.backgroundColor.b
DrawRect(_theGuiState.modalX, _theGuiState.modalY, _theGuiState.modalWidth, _theGuiState.modalHeight)
SetColor _theGuiState.secondaryColor.r, _theGuiState.secondaryColor.g, _theGuiState.secondaryColor.b
DrawRect(_theGuiState.modalX, _theGuiState.modalY, _theGuiState.modalWidth, 28)
SetColor _theGuiState.textColor.r, _theGuiState.textColor.g, _theGuiState.textColor.b
DrawText(_theGuiState.modalTitle, _theGuiState.modalX + 8, _theGuiState.modalY + 6)
' 3) Replay every queued widget-draw inside it
For Local cmd:TDrawCmd = EachIn _theGuiState.modalDrawQueue
Select cmd.fn
Case 0
SetColor(Int(String(cmd.args[0])), Int(String(cmd.args[1])), Int(String(cmd.args[2])))
Case 1
DrawText(String(cmd.args[0]), Float(String(cmd.args[1])), Float(String(cmd.args[2])))
Case 2
DrawRect(Float(String(cmd.args[0])), Float(String(cmd.args[1])),
Float(String(cmd.args[2])), Float(String(cmd.args[3])))
Case 3
DrawImageRect(cmd.image,
Float(String(cmd.args[0])), Float(String(cmd.args[1])),
Float(String(cmd.args[2])), Float(String(cmd.args[3])),
Int(String(cmd.args[4])))
End Select
Next
' 4) Done—clear the queue and finally exit modal mode
_theGuiState.modalDrawQueue.Clear()
_theGuiState.modalActive = False
_theGuiState.popupBlocking = False
End Function
Function _BeginWidget(colspan:Int, x:Int Var, y:Int Var, width:Int Var, height:Int Var)
If _theGuiState.inGrid Then
Local col:Int = _theGuiState.gridIndex Mod _theGuiState.gridColumns
_theGuiState.cursorX = _theGuiState.gridStartX + col * (_theGuiState.gridCellWidth + _theGuiState.spacing)
width = _theGuiState.gridCellWidth * colspan + _theGuiState.spacing * (colspan - 1)
_theGuiState.gridIndex :+ colspan
ElseIf _theGuiState.inRow Then
_theGuiState.cursorX = _theGuiState.rowCursorX
width = _theGuiState.rowElementWidth * colspan + _theGuiState.spacing * (colspan - 1)
_theGuiState.rowCursorX :+ width + _theGuiState.spacing
Else
_theGuiState.cursorX = _theGuiState.panelX
width = _theGuiState.panelWidth - 16
End If
Local rightSpace:Int = 8
If _theGuiState.inGrid Then rightSpace = 0
x = _theGuiState.cursorX + rightSpace
y = _theGuiState.cursorY
_UpdateScrollViewContentHeight(height)
End Function
Function _EndWidget(height:Int)
If _theGuiState.inGrid Then
If height > _theGuiState.gridRowHeight Then _theGuiState.gridRowHeight = height
If _theGuiState.gridIndex Mod _theGuiState.gridColumns = 0 Then
_theGuiState.cursorY :+ _theGuiState.gridRowHeight + _theGuiState.spacing
_theGuiState.gridRowHeight = 0
End If
ElseIf _theGuiState.inRow Then
If height > _theGuiState.rowMaxHeight Then _theGuiState.rowMaxHeight = height
Else
_theGuiState.cursorY :+ height + _theGuiState.spacing
End If
EndFunction
Function _GuiStoreLayout()
_theGuiState.storedPanelX = _theGuiState.panelX
_theGuiState.storedPanelY = _theGuiState.panelY
_theGuiState.storedPanelWidth = _theGuiState.panelWidth
_theGuiState.storedCursorX = _theGuiState.cursorX
_theGuiState.storedCursorY = _theGuiState.cursorY
End Function
Function _GuiRestoreLayout()
_theGuiState.panelX = _theGuiState.storedPanelX
_theGuiState.panelY = _theGuiState.storedPanelY
_theGuiState.panelWidth = _theGuiState.storedPanelWidth
_theGuiState.cursorX = _theGuiState.storedCursorX
_theGuiState.cursorY = _theGuiState.storedCursorY
End Function
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment