Garry's Mod Wiki

Revision Difference

Introduction_To_Using_Derma#564595

<cat>Dev.UI</cat> <title>Introduction To Using Derma</title> # 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)](https://en.wikipedia.org/wiki/User_interface) 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)](https://developer.valvesoftware.com/wiki/VGUI_Documentation) 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 <page>vgui.Create</page> like so ```lua 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. ```lua 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 <page>Panel:SetVisible</page>. 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 <page>DPanel:SetPaintBackground</page> ## Conclusion That's it, everything you need to get started. Before starting the next chapter, experiment with different functions found in the <page>Panel</page> class to see what is possible. A good checkpoint is figuring out how to remove any created panels with <page>Panel:Remove</page> before continuing to more complex areas. Here is a codeblock detailing everything we covered in this chapter: ```lua -- 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 <page>DLabel</page> and <page>DButton</page>. Now before we get started using these elements its crucial to remember inheritance. <page>DLabel</page> derives from an engine based panel called <page>Label</page> while the element, <page>DButton</page> derives from <page>DLabel</page>. 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: ```lua -- 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 <page>DLabel</page> ```lua 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 <page>DButton</page> 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. ```lua 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 <page>DButton</page> inherits its methods from <page>DLabel</page>, and <page>DLabel</page> has the <page>DLabel:DoClick</page> 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. ```lua -- 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 ``` <note> the function can also be written like ```lua function myCloseButton:DoClick() ... end ``` the only difference between these two is how the [Lua Self Keyword](Beginner_Tutorial_Self) is handled </note> 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: ```lua -- 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 <page>Panel:SetPos</page> 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⤶ <page>Panel:PerformLayout</page> 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 <page>Panel:InvalidateLayout</page> however this usually wont be necessary.⤶ ⤶ <warning> You will **never** need to call <page>Panel:PerformLayout</page> manually, the system will always call it for you after the layout is invalidated. If this needs to be done, do so by calling <page>Panel:InvalidateLayout</page> </warning> ⤶ ⤶ ## 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⤶ ```lua⤶ 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 <page>Panel:Dock</page> 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 <page>Enums/DOCK</page> 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!⤶ ⤶ ```lua⤶ -- 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 <page>Panel:DockMargin</page> and <page>Panel:DockPadding</page>. `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.⤶ ⤶ ```lua⤶ 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 <page>Panel:SizeToContents</page> and <page>Panel:SizeToChildren</page> 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. ⤶ ⤶ ```lua⤶ -- 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, <page>Panel:SetContentAlignment</page> would center the text *within* the `DLabel`'s bounds. Many panels also have helper functions like <page>Panel:Center</page> which centers the panel within its parent.⤶ ⤶ Other Alignment Functions:⤶ - <page>Panel:AlignBottom</page>⤶ - <page>Panel:AlignLeft</page>⤶ - <page>Panel:AlignRight</page>⤶ - <page>Panel:AlignTop</page>⤶ ⤶ ## 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.⤶ ⤶ * <page>DPanelList</page>: Its `PerformLayout` arranges children vertically (like a list), adding spacing automatically.⤶ * <page>DIconLayout</page>: Its `PerformLayout` arranges children in a grid, wrapping them to the next line when they run out of space horizontally.⤶ * <page>DForm</page> 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. **<page>Panel:PerformLayout</page> 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.