garrysmod icon indicating copy to clipboard operation
garrysmod copied to clipboard

Add DDialogBox element

Open jorjic opened this issue 3 years ago • 7 comments
trafficstars

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 )

image


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

image


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

image


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" )

image

jorjic avatar Aug 30 '22 23:08 jorjic

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

neico avatar Aug 31 '22 01:08 neico

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.

colemclaren avatar Aug 31 '22 06:08 colemclaren

Also, why SetLabelText and not SetMessage or SetContent

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 AddPanel instead of the commonly used Add though?

~~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.

jorjic avatar Aug 31 '22 11:08 jorjic

Added GenerateExample:

image

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.

jorjic avatar Aug 31 '22 11:08 jorjic

Adding this to the base game seems kind of silly to me for a few reasons.

  1. I don't actually know how useful this will be, or how often it will be used
  2. 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.
  3. 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.

garryspins avatar Sep 04 '22 21:09 garryspins

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.

neico avatar Sep 05 '22 08:09 neico

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.

garryspins avatar Sep 05 '22 22:09 garryspins

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

neico avatar Sep 06 '22 04:09 neico

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.

garryspins avatar Sep 06 '22 07:09 garryspins

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.

jorjic avatar Sep 13 '22 20:09 jorjic