Last active
April 30, 2025 12:21
-
-
Save RednibCoding/bb9542a03fb29370c0b4951c589a8b62 to your computer and use it in GitHub Desktop.
BlitzMax immediate mode gui
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
' === 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