Immediate mode 3D2D UI for Garry's Mod. See some real-world uses here.
- Creating clickable buttons or tracking mouse in an area requires one line of code
- Visibility optimizations to prevent UI from being rendered if it's not visible
- Only a wrapper for
cam.Start3D2D
so allsurface
library functions are still usable - Error handling to prevent clients from crashing if a Lua error happens
Place imgui.lua
somewhere in your addon or gamemode (eg. myaddon/lua/myaddon/imgui.lua
) and make sure it is AddCSLuaFile
d and available to your clientside rendering code.
Using with entities:
-- 3D2D UI should be rendered in translucent pass, so this should be either TRANSLUCENT or BOTH
ENT.RenderGroup = RENDERGROUP_TRANSLUCENT
function ENT:DrawTranslucent()
-- While you can of course use the imgui.Start3D2D function for entities, IMGUI has some special syntax
-- This function automatically calls LocalToWorld and LocalToWorldAngles respectively on position and angles
if imgui.Entity3D2D(self, Vector(0, 0, 50), Angle(0, 90, 90), 0.1) then
-- render things
imgui.End3D2D()
end
end
Using with gamemode rendering hooks:
local imgui = include("imgui.lua") -- imgui.lua should be in same folder and AddCSLuaFile'd
hook.Add("PostDrawTranslucentRenderables", "PaintIMGUI", function(bDrawingSkybox, bDrawingDepth)
-- Don't render during depth pass
if bDrawingDepth then return end
-- Starts the 3D2D context at given position, angle and scale.
-- First 3 arguments are equivalent to cam.Start3D2D arguments.
-- Fourth argument is the distance at which the UI panel won't be rendered anymore
-- Fifth argument is the distance at which the UI will start fading away
-- Function returns boolean indicating whether we should proceed with the rendering, hence the if statement
-- These specific coordinates are for gm_construct at next to spawn
if imgui.Start3D2D(Vector(980, -83, -79), Angle(0, 270, 90), 0.1, 200, 150) then
-- This is a regular 3D2D context, so you can use normal surface functions to draw things
surface.SetDrawColor(255, 127, 0)
surface.DrawRect(0, 0, 100, 20)
-- The main priority of the library is providing interactable panels
-- This creates a clickable text button at x=0, y=30 with width=100, height=25
-- The first argument is text to render inside button
-- The second argument is special font syntax, that dynamically creates font "Roboto" at size 24
-- The special syntax is just for convinience; you can use normal Garry's Mod font names in place
-- The third, fourth, fith and sixth arguments are for x, y, width and height
-- The seventh argument is the border width (optional)
-- The last 3 arguments are for color, hover color, and press color (optional)
if imgui.xTextButton("Foo bar", "!Roboto@24", 0, 30, 100, 25, 1, Color(255,255,255), Color(0,0,255), Color(255,0,0)) then
-- the xTextButton function returns true, if user clicked on this area during this frame
print("yay, we were clicked :D")
end
-- End the 3D2D context
imgui.End3D2D()
end
end)
Prefer entities over global hooks, since they work better with Source engine visiblity optimizations.
Setting the developer
convar to non-zero value draws a debugging panel on top of each IMGUI panel you draw. It shows few pieces of useful information:
- The mouse coordinates or the reason why input is not enabled at the moment
- World position of the TDUI panel, our distance from it and the distance at which the interface is hidden
- World angles of the TDUI panel, the dot product between eye position and the panel and the angle between eye position and the panel
- How many milliseconds did we spend on rendering this UI per frame (averaged over 100 frames/renders)
If you wish to hide the developer panel even with developer
cvar, set imgui.DisableDeveloperMode = true
right after importing the library.
Start 3D2D context (calls cam.Start3D2D
internally)
imgui.Start3D2D(pos, angles, scale, distanceHide, distanceFadeStart)
Start 3D2D context on an entity. Equivalent to imgui.Start3D2D
, except a) pos
/angles
are automatically transformed from local entity coordinates into world coordinates and b) the entity will be ignored in mouse input collision checks.
imgui.Entity3D2D(ent, lpos, lang, scale, distanceHide, distanceFadeStart)
Ends 3D2D Context
imgui.End3D2D()
Retrieves cursor position in 3D2D space. mx
/my
are null if player is not looking at the interface
local mx, my = imgui.CursorPos()
Whether player's 3D2D cursor is within given bounds
local hovering = imgui.IsHovering(x, y, w, h)
Whether player is currently pressing
local pressing = imgui.IsPressing()
Whether player is pressed during this frame. This is guaranteed to only be called once per click
local pressed = imgui.IsPressed()
Draws a rectangle button without any text or content
local wasPressed = imgui.xButton(x, y, w, h, borderWidth, borderClr, hoverClr, pressColor)
Draws a button with text inside. The font
parameter is passed through imgui.xFont
, so special font syntax is supported.
The text is automatically centered within the button.
local wasPressed = imgui.xTextButton(text, font, x, y, w, h, borderWidth, color, hoverClr, pressColor)
Draws a cursor IF the cursor is within given bounds. Note: x
,y
,w
,h
should be the bounds for your whole IMGUI interface, eg. 0, 0, 512, 512
if you draw into the 3d2d space within those bounds.
imgui.xCursor(x, y, w, h)
Retrieves font name usable for Garry's Mod functions based on parameter. See Special font API section below
local fontName = imgui.xFont("!Roboto@24")
Expands the entity's render bounds to cover the whole rectangle passed as 3D2D coordinates. Note: x
,y
,w
,h
should be the bounds for your whole IMGUI interface, eg. 0, 0, 512, 512
if you draw into the 3d2d space within those bounds.
(only usable inside imgui.Entity3D2D
block, before imgui.End3D2D
call)
imgui.ExpandRenderBoundsFromRect(x, y, w, h)
One of the goals of the library is to not crash the client even if something throws an error during the 3D2D context. This is achieved by halting all rendering library-wide if we detect attempt at starting a new 3D2D context without never having called End3D2D
in between.
The recovery protocol triggers automatically if you try to nest 3D2D contexts due to error or any other reason. You'll be notified with a [IMGUI] Starting a new IMGUI context when previous one is still rendering. Shutting down rendering pipeline to prevent crashes..
message in console if that happens. The only way to recover from this error state is to re-initialize the whole library to reset the internal global state of IMGUI. There is no programmatic way to do this at the moment, but you can re-save the imgui.lua
to trigger Lua autorefresh on it, or just reimport it.
IMGUI comes with a simplified method for creating fonts. If you use the built-in functions, such as imgui.xTextButton
, the passed font argument will automatically go through the font syntax parser, but you can also access it directly with imgui.xFont
.
Here's an example of using imgui.xFont
for drawing normal text:
-- Draw 'Foo bar' using Roboto font at font size 30 at 0, 0
draw.SimpleText("Foo bar", imgui.xFont("!Roboto@30"), 0, 0)
You'll probably want to network data from client to server on button press or other clientside action at some point. IMGUI doesn't support this by itself, but netdata is a pretty good fit for entity-based UIs.
Here's an example entity with IMGUI + netdata:
ENT.Type = "anim"
if SERVER then
function ENT:Initialize()
self:SetModel("models/props_phx/construct/glass/glass_plate1x1.mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self.Greeters = {}
end
function ENT:ReceiveNetAction(cl)
table.insert(self.Greeters, cl:Nick())
self:NetDataUpdate()
end
function ENT:NetDataWrite()
net.WriteTable(self.Greeters)
end
end
if CLIENT then
ENT.RenderGroup = RENDERGROUP_BOTH
function ENT:NetDataRead()
self.Greeters = net.ReadTable()
end
function ENT:DrawTranslucent()
local imgui = resort.bar.imgui
if imgui.Entity3D2D(self, Vector(0, 0, 3.5), Angle(0, 0, 0), 0.1) then
if imgui.xTextButton("Say hello", "!Roboto@24", -100, -200, 200, 50, 1) then
self:StartNetAction()
net.SendToServer()
end
if self.Greeters then
for i, nick in pairs(self.Greeters) do
draw.SimpleText(nick, "DermaLarge", 0, -160 + i * 25, nil, TEXT_ALIGN_CENTER)
end
end
imgui.End3D2D()
end
end
end