Garry's Mod Wiki

Introduction To Using Derma

Introduction

This is the first in a series of tutorials that aim to take you from zero knowledge on derma, through all the essentials, to a solid foundation towards creating your first UI project.

Welcome to world of User Interfaces (UI) specifically within Garry's Mod. Before we begin lets define a clear vocabulary. The source engine by default draws its UI using VGUI (Valves Graphical User Interface) and CPP. In Garry's Mod We Use Lua and Derma. Derma was created by Garry and TAD2020 to make drawing UI's easier. Under the hood our Lua code will access the VGUI code allowing us to create, customize, and destroy UI elements.

Chapter 1: Panel (DPanel)

Panels are the root to root to everything, most (not all) other elements that you use and create will be comprised of these panels. The engine provides a VGUI control called "Panel" this is a control that has almost no default properties by default. Derma introduces "DPanel" a control that inherits from the engine panel while extending its functionality to be more flexible. Most of the time it will be better to use DPanel so that will be our focus.

Creating a Panel

In order to use a panel you will need to create one. The most common method is vgui.Create like so

local myFirstPanel = vgui.Create( "DPanel" )

This code snippet creates a DPanel object in Lua and stores in the variable myFirstPanel so that we may access it. We can then use this variable to run functions or adjust properties like:

  • Size - Width and Height of the Panel
  • Position - Where the top-left corner is
  • Draw Settings - Color and similar properties

Adjusting Properties

Panels don't make up much on their own, instead they act similarly to Lego bricks, allowing you to combine and connect multiple instances of them to create bigger components.

As mentioned before Panels have properties like Size, Position, and Draw Settings (better known as the Paint function, which we'll get to later). Part of what Derma does is provide us with Lua functions to adjust these properties.

Here is an example of some of the more common functions so you can experiment with them.

myFirstPanel:SetSize( 200, 100 ) -- Makes the width 200 pixels and the height 100 pixels myFirstPanel:SetPos( 50, 50 ) -- Sets the Panel 50 pixels from the left, then 50 from the top. myFirstPanel:Center() -- Move the panel to the center of its parent (useful for locating)

These should be fairly self-explanatory, however, its important to keep in mind how these functions use their numbers. Panels are drawn from the top-left, to the bottom-right. This means that when you use :SetSize(), the position of the top-left pixel will remain in the same spot while the bottom right pixel shoots out. Same thing with :SetPos(), this function will move the top-left pixel to those local coordinates on the screen (top-left of your screen being 0,0)

These functions are interpreted as they are read. If i use :Center() then :SetSize( w, h ) the panel will not appear center since it was centered first then the size changed.

Controlling Visibility

As you read this section, use vgui_visualizelayout 1 to help you spot any hidden panels. This function will make panels flash red when they UPDATE.

Visibility is important for controlling what you can and cant see within the elements. Derma provides us with a simple command Panel:SetVisible. Panels will automatically be shown and hidden based off of their parents state. Meaning if you hide a panel, all its children will hide to.

The visibility of panel is different than a transparent panel. A transparent panel will still update and act as though it is visible, while a hidden panel will not update correctly.

a DPanel can be made transparent by using DPanel:SetPaintBackground

Conclusion

That's it, everything you need to get started. Before starting the next chapter, experiment with different functions found in the Panel class to see what is possible. A good checkpoint is figuring out how to remove any created panels with Panel:Remove before continuing to more complex areas.

Here is a codeblock detailing everything we covered in this chapter:

-- Create the DPanel local myFirstPanel = vgui.Create( "DPanel" ) -- Set its size (Width: 200px, Height: 100px) myFirstPanel:SetSize( 200, 100 ) -- Set its position (X: 300px from left, Y: 200px from top) -- (Trying slightly different coords for visibility) myFirstPanel:SetPos( 300, 200 ) -- Make it paint its background myFirstPanel:SetPaintBackground( true ) -- Set the background color to blue (R=0, G=0, B=255, Alpha=255) myFirstPanel:SetBackgroundColor( Color( 0, 0, 255, 255 ) ) -- Make sure it's visible (usually is by default if not parented to hidden) myFirstPanel:SetVisible( true )

Chapter 2: Labels & Buttons (DLabel / DButton)

The last chapter was all about creating rectangles, however, UI's do not just consist of rectangles. We want our UI to have text and some interaction with it, that's where Labels and Buttons come into play. For derma these elements are DLabel and DButton.

Now before we get started using these elements its crucial to remember inheritance. DLabel derives from an engine based panel called Label while the element, DButton derives from DLabel. Its important to remember this cascading inheritance as it will help you remember what functions you can and cannot access within these panels. That is why These elements were separated into the next chapter.

While there are are exceptions, elements that derive from the engine panel, are typically used for things like layout and grouping. Elements that derive from the engine label panel, are more used for mouse interaction.

Adding a Label

Before we add our labels and buttons lets create a base panel:

-- Any functions explained in previous examples will not be commented for clarity local myBasePanel = vgui.Create( "DPanel" ) myBasePanel:SetSize( 300, 150 ) myBasePanel:Center() myBasePanel:SetPaintBackground( true ) myBasePanel:SetBackgroundColor( Color( 0, 0, 255, 255 ) ) myBasePanel:MakePopup() -- Make it stay on top and release the mouse for to capture clicks.

Now to add a DLabel

local myTitleLabel = vgui.Create( "DLabel", myBasePanel ) -- Using the second arg to parent it 'myBasePanel' -- Set the text it should display myTitleLabel:SetText( "My Awesome Panel" ) -- Set its position *relative* to the top-left corner of myBasePanel myTitleLabel:SetPos( 10, 10 ) -- Automatically adjust the label's width to fit the text myTitleLabel:SizeToContents() -- Optional: Change the text color myTitleLabel:SetTextColor( color_white ) -- Using built in default color to get white

Adding text to your panel is really that simple!

Adding a Button

Now lets add a DButton somewhere on myBasePanel that will close the panel for us. Hopefully you found some way of triggering panel removal in chapter 1, so this will make panel management easier.

local myCloseButton = vgui.Create( "DButton", myBasePanel ) myCloseButton:SetText( "Close Me" ) -- Set its size (Buttons often need an explicit size unlike how we set our label) myCloseButton:SetSize( 100, 30 ) -- Position it near the bottom right of the panel -- We calculate position based on the panel's size local panelW, panelH = myBasePanel:GetSize() myCloseButton:SetPos( panelW - 110, panelH - 40 ) -- 10px padding from right/bottom -- we got the offset numbers from doing 'size + padding',

Easy! now that we have our button setup, if you click it, you'll notice it does nothing. This is because we have not setup any behavior in the relevant hooks.

Since DButton inherits its methods from DLabel, and DLabel has the DLabel:DoClick method. We can use that hook for our button. If this doesn't make complete sense take a moment to break down how and why we are able to use this hook. Understanding inheritance now is crucial as controls get more complex.

Lets define this function somewhere below the creation of our myCloseButton panel.

-- Define what happens when myCloseButton is clicked myCloseButton.DoClick = function( theButton ) -- 'theButton' is the button that was clicked (myCloseButton in this case) print( "Close button clicked!" ) -- Let's make the button remove the whole panel myBasePanel:Remove() end
the function can also be written like
function myCloseButton:DoClick() ... end

the only difference between these two is how the Lua Self Keyword is handled

The button we created is very simple, it simply prints our message then removes our base panel, causing it and all children to be marked for deletion.

Conclusion

You should now have an even stronger understanding of basic panel control. Experiment around with creating buttons that do different things. Maybe a calculator? we will cover the layout system in Chapter 3

All together the code looks like:

-- Any functions explained in previous examples will not be commented for clarity local myBasePanel = vgui.Create( "DPanel" ) myBasePanel:SetSize( 300, 150 ) myBasePanel:Center() myBasePanel:SetPaintBackground( true ) myBasePanel:SetBackgroundColor( Color( 0, 0, 255, 255 ) ) myBasePanel:MakePopup() local myTitleLabel = vgui.Create( "DLabel", myBasePanel ) myTitleLabel:SetText( "My Awesome Panel" ) myTitleLabel:SetPos( 10, 10 ) myTitleLabel:SizeToContents() myTitleLabel:SetTextColor( color_white ) local myCloseButton = vgui.Create( "DButton", myBasePanel ) myCloseButton:SetText( "Close Me" ) myCloseButton:SetSize( 100, 30 ) local panelW, panelH = myBasePanel:GetSize() myCloseButton:SetPos( panelW - 110, panelH - 40 ) myCloseButton.DoClick = function( theButton ) print( "Close button clicked!" ) myBasePanel:Remove() end

Chapter 3: The Layout System

The previous chapters were all about creating elements. While we did cover some basic positioning using Panel:SetPos that really only works best for static elements. If you have layered elements and the parent changes its size you want to be able to update the children appropriately.

PerformLayout

Panel:PerformLayout is the backbone of the layout system. Whenever a panel needs to know how to arrange its children this hook is called. This could be called automatically like on panel creation, resizing, or when panels are added or removed. Or, manually with Panel:InvalidateLayout however this usually wont be necessary.

You will never need to call Panel:PerformLayout manually, the system will always call it for you after the layout is invalidated. If this needs to be done, do so by calling Panel:InvalidateLayout

Common Layout Techniques

Here lets break down some of the most common ways to arrange our panels within the PerformLayout hook.

1. Manual Position

You already know this! this just involves specifying the exact coordinates and dimension that you want. this offers precise control but limits the about of flexible or dynamic design that you can implement.

Examples of Manual Positioning from Chapter 2

myTitleLabel:SetPos( 10, 10 ) myCloseButton:SetPos( panelW - 110, panelH - 40 ) myCloseButton:SetSize( 100, 30 )

2. Docking

Docking is very powerful. You tell the child to stick to one or more edges of its parent and fill the rest of the space available.

The Panel:Dock function takes numbers 1 - 5 to denote how to dock the panel. However instead of remembering what these numbers mean we can instead use the DOCK enum to make our code more readable.

Dock Enum Value Description
NODOCK 0 Don't Dock
FILL 1 Fill up as much of the remaining space as we can that isnt taken up by other panels.
LEFT 2 Stick to the left wall of the parent maximizing the height of the panel.
RIGHT 3 Stick to the right wall of the parent maximizing the height of the panel.
TOP 4 Stick to the Top wall of the parent maximizing width.
BOTTOM 5 Stick to the Bottom wall of the parent maximizing width.

Now if you employ this method its important to remember that the order in which you dock the children matters!

-- Create the base panel (same as before) local myBasePanel = vgui.Create( "DPanel" ) myBasePanel:SetSize( 300, 150 ) myBasePanel:Center() myBasePanel:MakePopup() myBasePanel:SetPaintBackground( true ) myBasePanel:SetBackgroundColor( Color( 0, 50, 150, 255 ) ) -- Create the Title Label local myTitleLabel = vgui.Create( "DLabel", myBasePanel ) myTitleLabel:SetText( "My Awesome Panel" ) myTitleLabel:SetTextColor( Color( 255, 255, 255, 255 ) ) myTitleLabel:SetTall( 25 ) -- Give it a fixed height for docking myTitleLabel:Dock( TOP ) -- Stick to the top! -- Create the Close Button local myCloseButton = vgui.Create( "DButton", myBasePanel ) myCloseButton:SetText( "Close Me" ) myCloseButton:SetTall( 30 ) -- Give it a fixed height for docking myCloseButton:Dock( BOTTOM ) -- Stick to the bottom! -- Button Action (same as before) myCloseButton.DoClick = function( theButton ) print( "Close button clicked!" ) myBasePanel:Remove() end

After you run this code you'll notice that that the title and the close buttons automatically positioned and sized themselves relevant to the parent. Now we can continue to improve the look of these functions using Panel:DockMargin and Panel:DockPadding. Margin refers to the distance between other children of the parent. Padding refers to shrinking the panel away from the edge it can fill up. If this is still confusing you can look up the difference in web-development because the functionality is the same.

myTitleLabel:DockMargin( 5, 5, 5, 0 ) -- Left, Top, Right, Bottom padding myCloseButton:DockMargin( 5, 0, 5, 5 ) -- Left, Top, Right, Bottom padding

3. Sizing to Content

Sometimes, instead of filling up the parent, you want the parent to shrink to its children. This is where Panel:SizeToContents and Panel:SizeToChildren come into play. You may remember :SizeToContents() from chapter 2 where we used it on our DLabel to make it just big enough for its text. :SizeToChildren() is different from this as it calculates the bounding box of all the panels children and sets the size to the minimum size that can accommodate that bounding box.

-- Imagine a panel containing only one label local container = vgui.Create( "DPanel" ) container:SetPos(100, 100) container:SetPaintBackground(true) container:SetBackgroundColor(Color(200, 200, 200, 255)) local label = vgui.Create( "DLabel", container ) label:SetText( "This text determines the panel size!" ) label:SetPos( 5, 5 ) -- Position inside the container label:SizeToContents() -- Label fits its text -- Now, make the container fit the label container:SizeToChildren( true, true ) -- Resize width and height

4. Alignment

These functions help position elements within the space allocated by their parent (often used after docking or setting a size). For example, Panel:SetContentAlignment would center the text within the DLabel's bounds. Many panels also have helper functions like Panel:Center which centers the panel within its parent.

Other Alignment Functions:

Container Panels & Layout Logic

While the basic DPanel's PerformLayout might just respect manual positions and docking, specialized container panels have much smarter PerformLayout functions.

  • DPanelList: Its PerformLayout arranges children vertically (like a list), adding spacing automatically.
  • DIconLayout: Its PerformLayout arranges children in a grid, wrapping them to the next line when they run out of space horizontally.
  • DForm Arranges labels and controls neatly in rows, often used for settings menus.

We will explore these specialized panels later in the next chapter.

Under the Hood: How Layout Happens

  1. Trigger: Something happens that invalidates the layout of the given panel
  2. Panel:PerformLayout Call
  3. Execution: Whatever is in the :PerformLayout() hook is executed

Conclusion

This chapter finished covering how the layout system works and different methods a user can use to organize their panels.

In the next chapter we will cover Specialized Container Panels.

Chapter 4: Container Panels

In chapter 3 we covered arranging elements using the different layout methods and the :PerformLayout() hook. While this is great for placing things like buttons or labels, if we have a long list of items these methods can quickly become redundant. This is where we container panels become useful. Container panels are specialized panels that are specifically designed to hold and organize a collection of other panels.

Popular Container Panels: DScrollPanel, DTileLayout, DIconLayout, DListLayout, DGrid, DDrawer

Scrolling with DScrollPanel

The most popular container panel is DScrollPanel. This panel has one goal, allowing you to scroll through its child elements. It does this by detecting mouse scrolls or creating a scroll bar that can be used to reposition the child elements in a scroll like manner.

Now typically you don't add your actual interactive elements to the DScrollPanel Itself. Instead, you create a basic empty Panel and add your elements to that. This is so the DScrollPanel can scroll that invisible grouping panel keeping all the interactive elements in their relative positions to each other during scrolling.

local frame = vgui.Create( "DFrame" ) frame:SetSize( 200, 250 ) frame:Center() frame:SetTitle( "Scroll Example" ) frame:MakePopup() local scroll = vgui.Create( "DScrollPanel", frame ) scroll:Dock( FILL ) local groupingPnl = vgui.Create( "DPanel", scroll ) groupingPnl:SetTall( 500 ) -- Make it tall so we can actually scroll groupingPnl:Dock( FILL ) groupingPnl:SetPaintBackground( false ) -- we want it invisible -- Add interactive panels that you want to scroll for i = 1, 20 do local label = vgui.Create( "DLabel", groupingPnl ) label:SetText( "Item Number " .. i ) label:SetPos( 10, 5 + (i-1) * 20 ) -- Position manually *within* groupingPnl label:SizeToContents() end

Grid Layouts with `DIconLayout`

What if you want to arrange items in a grid, like icons on a desktop or items in an inventory grid? DIconLayout is designed for this. It arranges its children in rows and columns.

-- Create the main frame local frame = vgui.Create( "DFrame" ) frame:SetSize( 220, 200 ) -- A bit wider frame:Center() frame:SetTitle( "IconLayout Example" ) frame:MakePopup() -- Create a scroll panel first (DIconLayout doesn't scroll by default) local scroll = vgui.Create( "DScrollPanel", frame ) scroll:Dock( FILL ) -- Create the DIconLayout *inside* the scroll panel local icons = vgui.Create( "DIconLayout", scroll ) icons:Dock( FILL ) icons:SetSpaceX( 5 ) -- Horizontal space between icons icons:SetSpaceY( 5 ) -- Vertical space between icons icons:SetBorder( 5 ) -- Space around the inside edge -- Add items (small buttons representing icons) for i = 1, 30 do local iconButton = vgui.Create( "DButton", icons ) -- Parent is icons layout iconButton:SetText( i ) iconButton:SetSize( 40, 40 ) -- Give the "icons" a size -- No need to call AddItem for DIconLayout, just set parent! -- DIconLayout automatically detects children added to it. end

Other Containers (DListView, DForm, etc.)

There are other specialized containers:

  • DListView: Creates spreadsheet-like views with columns and rows. Great for scoreboards or detailed lists. Adding items involves AddLine and specifying data for each column.
  • DForm: Useful for creating settings menus. It helps arrange labels and controls (like checkboxes, text entries) in neat rows.
  • DCategoryList: Creates collapsible categories, each containing other panels.

These build upon the same principles: they have specialized PerformLayout functions to arrange their children in specific ways.

Conclusion

While most elements are meant to be visible and interacted with, some are not, some aid the developers lots of time and code complexity by reducing multiple instances of similar code into one spot

The next chapter will be covering skinning and painting to make your panels look pretty.

Chapter 5: Skinning and Painting

Now that we've covered container panels in the last chapter we now know all the basics to creating, handling, and moving panels around. However we have not yet covered how they look. This is where painting and skinning will come into play. Before getting started you should know the difference between these two terms. Painting refers to painting a specific panel, this will usually involve libraries like Draw and Surface. Skinning is a different approach, this involves creating a "skin" using libraries like GWEN and creating UI Sprite sheets that the paint functions will reference.

Each have their own strengths and drawbacks but in general, painting is easier to do but must be done for each individual panel. Skinning takes more investment upfront but can provide a consistent look across all elements that is all controlled in one place.

Drawing in Derma

Skinning provides a "default" consistent look for all the panels. Painting can allow you to override this look for a specific panel. Painting a panel will always override the the default look whether that be from a skin or some other source.

The Paint Function

Every single panel will have a :Paint( w, h ) function. This is what the panel will use to determine how its suppose to look. This hook is called every single frame the panel is visible. The paint hook is always called with two arguments, the width and height of the current panel (denoted as w and h commonly).

Here is an example of two ways to construct a Paint Hook Self is Explicit

PANEL.Paint = function( self, w, h ) ... end

or self being implicit

function PANEL:Paint( w, h ) ... end

either choice is correct and comes down to personal preference. See Beginner_Tutorial_Self for more information about self handling

The Painting System

The tools you use to draw will always come from the Surface library. This library provides everything you need to make any kind of UI you desire. However sometimes more complex tasks are harder to do in surface so the Draw library was created, this library is still the surface library underneath but has more convenient functions and functions that do more complex surface tricks under the hood. Overall if something can be done in just surface, do it in surface. If something is complex and hard to do in surface use draw.

Here we give you some (not all) of the functions available to you in surface:

you'll notice how we have functions like Surface.SetDrawColor and Surface.SetFont. This is because surface works by storing the last input from those functions to use in all other functions like surface.DrawRect. You will always want to set the color or Font before attempting to draw using surface because failing to do so may result in drawing the wrong color/font.

He are some (not all of the functions available with draw:

Here if you were to investigate the source code for these functions you would find that they are all just a sequence of surface functions. You could write out these functions in the pure surface format and get the same results but the purpose of this library is convince and ease of use of these more complex surface tactics.

The Skinning System

Every single panel you create will have a paint function, but filling these with nearly identical surface and draw functions just to get a consistent look can be incredibly repetitive and a waste.

This where Skins come into play. Each Skin defines the default look and feel for all standard Derma elements. It contains standardized colors, fonts, or even default paint functions for different types of elements like buttons, labels, and frames.

This is all done using derma.SkinHook, sometimes Derma_Hook which is just a fancy wrapper for the first function but used often enough it warranted mentioning here.

Inside many default derma panel's paint functions you will find either of these functions written like

derma.SkinHook( "Paint", "Button", self, w, h )

this line essentially asks the current skin how to perform the Paint operation for a button type element, passing its panel table and the dimensions.

as a result the following function is called within the skin

function SKIN:PaintButton( panel, w, h ) ... end

Garry's Mod comes with one skin by default, the default.lua skin. This is located within lua/skins/ for a more indepth breakdown on this skin see Derma_Skin_Creation

Using the Paint Hook

From previous examples you should be familiar the with the code below:

-- Create the base frame local frame = vgui.Create( "DFrame" ) frame:SetSize( 200, 150 ) frame:Center() frame:SetTitle( "Custom Paint Example" ) frame:MakePopup() -- Create a standard DButton local myButton = vgui.Create( "DButton", frame ) myButton:SetText( "Hover or Click Me" ) myButton:SetSize( 160, 40 ) myButton:SetPos( 20, 40 ) -- Position it inside the frame -- Basic action myButton.DoClick = function( btn ) print( "Button Clicked!" ) end

Now, let's tell this specific button (myButton) to use a different Paint function.

-- Define our custom paint function for myButton myButton.Paint = function( panel, w, h ) -- 'panel' is myButton itself -- 'w' is the button's width (160) -- 'h' is the button's height (40) -- ** Our custom drawing code will go here! ** -- !! Important: Return true to stop the default text drawing !! -- If we draw text ourselves, we return true. -- If we wanted the default skin to still draw the text after our background, -- we would NOT return true (or return false/nil). -- For this example, we'll draw everything ourselves. return true end

We've assigned a new function directly to myButton.Paint. Now, every frame, instead of calling the default DButton paint logic (which uses derma.SkinHook), Garry's Mod will call our function.

Let's add the drawing code inside our custom Paint function. We want:

  • A green background normally.
  • A brighter green background when hovered.
  • A darker green background when clicked (pressed down).
  • White text.
-- Define our custom paint function for myButton myButton.Paint = function( panel, w, h ) -- Check the button's state local isHovered = panel:IsHovered() local isDown = panel:IsDown() -- Determine background color based on state local bgColor if isDown then bgColor = Color( 0, 100, 0, 255 ) -- Dark Green elseif isHovered then bgColor = Color( 0, 200, 0, 255 ) -- Bright Green else bgColor = Color( 0, 150, 0, 255 ) -- Normal Green end -- Draw the background rectangle surface.SetDrawColor( bgColor ) surface.DrawRect( 0, 0, w, h ) -- Fill the entire panel (0,0 to w,h) -- Draw the text surface.SetFont( "DermaDefault" ) surface.SetTextColor( Color( 255, 255, 255, 255 ) ) -- White -- Calculate text position to center it local text = panel:GetText() -- Get the button's text local textW, textH = surface.GetTextSize( text ) local textX = (w - textW) / 2 local textY = (h - textH) / 2 surface.SetTextPos( textX, textY ) surface.DrawText( text ) -- We handled everything, including text. Stop default drawing. return true end

You should see a window with a green button. When you move your mouse over it, it turns bright green. When you click and hold, it turns dark green. The text stays white throughout. You've successfully overridden the default button appearance!

Conclusion

Now that we covered Painting and skinning that completes your knowledge on the introduction to Derma. Hopefully by now you've amassed enough knowledge to start experimenting with creating some basic UI elements and customizing them to your liking.

Congratulations and welcome to Derma!