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
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.
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:
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:
Now to add a DLabel
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.
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.
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:
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.
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
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!
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.
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.
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
- Trigger: Something happens that invalidates the layout of the given panel
- Panel:PerformLayout Call
- 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.
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.
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
or self being implicit
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:
- Surface.SetDrawColor
- Surface.DrawRect
- Surface.SetFont
- Surface.SetTextColor
- Surface.SetTextPos
- Surface.DrawText
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
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
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:
Now, let's tell this specific button (myButton
) to use a different Paint
function.
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.
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!