garrysmod
garrysmod copied to clipboard
Add DDialogBox element
DDialogBox is a customizable dialog box implementation.
Out of the box, it contains a label, an accept button, and a reject button. Each of these components may be customized or disabled as desired. Arbitrary VGUI elements may be added by passing either an existing panel or a panel class name to the AddPanel function. The panel must be docked to TOP or the layout will probably be incorrect. After custom elements have been added, Invalidate() must be called to automatically resize the dialog box.
The OnAccept and OnReject callback functions will be fired once the user clicks one of the available option buttons. If you want to perform some action once the dialog has been acknowledged/confirmed, you should override one or both of these functions.
It is modeless by default, but can be made modal with the existing Panel:DoModal function.
Examples
Simple information dialog
-- Regular dialog example
local dialog = vgui.Create( "DDialogBox" )
dialog:SetIcon( "icon16/information.png" )
dialog:SetTitle( "Example Dialog" )
dialog:SetText( "This is an example dialog.\n\nInform the user about something here." )
dialog:SetRejectButtonEnabled( false )

Color selection dialog
-- Color selector example
local dialog = vgui.Create( "DDialogBox" )
dialog:SetIcon( "icon16/color_wheel.png" )
dialog:SetTitle( "Choose Color" )
dialog:SetText( "" )
dialog:SetWide( 280 ) -- Carefully selected for color swatch alignment
local mixer = dialog:Add( "DColorMixer" )
function dialog:OnAccept()
local color = mixer:GetColor()
-- Do something with the chosen color
end
function dialog:OnReject()
-- User cancelled, do something...
end

Text input dialog
-- Text input example
local dialog = vgui.Create( "DDialogBox" )
dialog:SetIcon( "icon16/application_form.png" )
dialog:SetTitle( "Choose A Name" )
dialog:SetText( "" )
local firstNameInput = dialog:Add( "DTextEntry" )
firstNameInput:SetPlaceholderText( "First name" )
firstNameInput:DockMargin( 0, 0, 0, 0 )
local lastNameInput = dialog:Add( "DTextEntry" )
lastNameInput:SetPlaceholderText( "Last name" )
lastNameInput:DockMargin( 0, 4, 0, 0 )
lastNameInput.OnEnter = function()
dialog:OnAccept()
dialog:Close()
end
function dialog:OnAccept()
local firstName = firstNameInput:GetText()
local lastName = lastNameInput:GetText()
print( "Hello, " .. firstName .. " " .. lastName )
end

Custom accept/reject button texts
local dialog = vgui.Create( "DDialogBox" )
dialog:SetIcon( "icon16/eye.png" )
dialog:SetTitle( "Example Dialog" )
dialog:SetText( "This is an example dialog." )
dialog:SetRejectButtonEnabled( false )
dialog:SetAcceptButtonText( "I'm a little teapot" )

Just what I was looking for earlier (not that it's hard to do yourself)
The variable naming (as in, the casing) might not be completely consistent with the rest of the codebase, but then again, much of the vgui element code aren't...
Why do you introduce a new AddPanel instead of the commonly used Add though?
Also, why SetLabelText and not SetMessage or SetContent, and when no message is set it should implicitly disable the label instead of the extra function
Generally, it might need a few more tweaks to be in line with the rest of the vgui elements, because I don't see having to explicitly call Invalidate being a good idea
And maybe provide a GenerateExample that has a few settings and a button to open the dialog, so it's easy to see and how to use going forward
This is a great addition, amazing job. The above comment sums up pretty much everything. I don't think having to call Invalidate all the time is very noob friendly either. You could add assert checks for the docking.
Also, why
SetLabelTextand notSetMessageorSetContent
Because I named it something even worse before SetLabelText.
when no message is set it should implicitly disable the label instead of the extra function
Good idea, I'll add that in, thanks.
Why do you introduce a new
AddPanelinstead of the commonly usedAddthough?
~~I was a little afraid of overriding Add in case somebody wanted to do something outside of what AddPanel does.~~
I don't see having to explicitly call Invalidate being a good idea
I wrote a majority of this many months ago. I think I struggled with getting it to work any other way. I'll try again, but if anyone can point out how to make it work like this, it would be great not to require Invalidate.
And maybe provide a GenerateExample that has a few settings and a button to open the dialog, so it's easy to see and how to use going forward
I tried to do this before opening the PR, but it wasn't showing up in derma_controls. I'll try again in case restarting fixes it.
Added GenerateExample:

You could add assert checks for the docking.
~~I'm not sure how to do this without looping through docked elements in Invalidate. I can Dock( TOP ) during Add which would spare the user from having to do it.~~
I've changed it to dock added panels to top during Add and the dialog box is automatically invalidated.
Adding this to the base game seems kind of silly to me for a few reasons.
- I don't actually know how useful this will be, or how often it will be used
- Most people will just rewrite this for their own reasons anyway, having a builtin component for it is kind of redundant, its something that will only really be used by beginners.
- Its very extra to have a whole separate component for this, it is just a DFrame with a few buttons, maybe if it had some more functionality.
Your second point could be applied to pretty much all existing elements, so that's rather moot
The third point is valid, but at the same time saves a bit of trouble if all you want is a quick (modal) dialog to notify or warn the user of something. Having a standardized and customizable element in the base allows you to worry less about it and just use it.
Pretty much all gui frameworks/libraries in existence have such an element, so I don't see why this shouldn't. And yes, even VGui has one, but with the more commonly used name: MessageBox
Bringing this code more in line with that one would be ideal. Especially because you can port most existing derma elements (and the stuff you make with it) into native VGui without much adjustment, as most Methods are identical.
My second point can and does apply to most elements, except most of those elements are actually somewhat useful or have some sort of functionality that's relatively complex to emulate, DFrame dragging and resizing, DScrollPanel's... scrolling, DNumSlider, ect. This doesn't stick out as an element that has any real pros to it, there's nothing beginners will gain, nonbeginners will rewrite the element in the first place because there's nothing complex about it.
It saves time for beginners, I don't mean to be insulting but if you have a hard time adding 2 buttons to a DFrame.... you have bigger things to worry about.
Also you seem to be forgetting the quick and dirty popups we have (Derma_Message, Derma_Query and Derma_StringRequest), want to warn a user? great use these, this just adds an extra step that's not all that powerful.
I dont have a problem with helping beginners, I do have a problem with cluttering the codebase with just more useless stuff.
tl;dr we should remove DPanel.
Those 3 functions are actually quite useful, but it feels like they aren't really highlighted much. (First time I've heard of them) Especially because most Derma* functions are mostly for internal use of some sort. And looking at their code shows quite a bit of duplicated code, which could be simplified with this element present.
A drawback on those however is that they are all modals (so they'd cover my use cases at least) But this element is non-modal by default, giving the ability for more use cases than simple notifications
"cluttering the codebase with useless stuff" is quite subjective though
You skipped over the main point, which is that this wont add anything really useful. And not being a modal isn't a feature, it really does just become a DFrame with a few buttons at that point.
I reinvented this wheel often enough that I decided it was worth spending time writing a generic implementation of it 18 months before buffing away my special use case functionality to submit it as a pull request. What if somebody decides to continue re-inventing the wheel anyways? Believe it or not, there is very little I can do about that.