Jump to content

Ultra Software Company Blog

  • entries
    185
  • comments
    1,247
  • views
    563,148

Contributors to this blog

Leadwerks GUI


Josh

5,443 views

 Share

After a lot of research and development, Leadwerks GUI is almost ready to release.  The goal with this system was to create an in-game GUI that was customizable, extendable, and could also serve as a windowed GUI for application development in the future.

Widgets

The GUI system is based on the Widget class.  Once a GUI is created on a rendering context you can add widgets to it.  Each widget is a rectangular container with a padding area.  The widgets can be arranged in a hierarchy and their bounds will clip the contents of their child widgets, both for rendering and mouse interaction.

The GUI is automatically rendered onto the screen inside the call to Context:Sync(),

Widget Scripts

Each widget has a Lua script to control rendering and behavior, similar to the way Lua scripts work in our entity system.  The script assigned to a widget controls what type of widget it is, how it looks, and how it interacts with mouse and keyboard input.  A set of widget scripts are provided to create a variety of controls including buttons, checkboxes, text entry boxes, list boxes, text display areas, choice boxes, sliders, and more.

You can create your own widget scripts to add new types of controls, like for an RPG interface or something else.  The script below shows how the tabber widget is implemented.

--Styles
if Style==nil then Style={} end
if Style.Panel==nil then Style.Panel={} end
Style.Panel.Border=1
Style.Panel.Group=2

--Initial values
Script.indent=1
Script.tabsize = iVec2(72,28)
Script.textindent=6
Script.tabradius=5

function Script:Start()	
	self.widget:SetPadding(self.indent,self.indent,self.tabsize.y+self.indent,self.indent)
end

function Script:MouseLeave()
	if self.hovereditem~=nil then
		self.hovereditem = nil
		local scale = self.widget:GetGUI():GetScale()
		local pos = self.widget:GetPosition(true)
		local sz = self.widget:GetSize(true)
		self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
		--self.widget:Redraw()
	end
end

function Script:Draw(x,y,width,height)
	local gui = self.widget:GetGUI()
	local pos = self.widget:GetPosition(true)
	local sz = self.widget:GetSize(true)
	local scale = self.widget:GetGUI():GetScale()
	local n
	local sel =  self.widget:GetSelectedItem()
	
	--Draw border
	gui:SetColor(0)
	gui:DrawRect(pos.x,pos.y+self.tabsize.y*scale,sz.width,sz.height-self.tabsize.y*scale,1)
	
	--Draw unselected tabs
	for n=0,self.widget:CountItems()-1 do
		if n~=sel then
			self:DrawTab(n)
		end
	end
	
	--Draw selected tab
	if sel>-1 then
		self:DrawTab(sel)
	end
	
	---Panel background
	gui:SetColor(0.25)
	gui:DrawRect(pos.x+1,pos.y+self.tabsize.y*scale+1,sz.width-2,sz.height-self.tabsize.y*scale-2)
end

function Script:DrawTab(n)
	local gui = self.widget:GetGUI()
	local pos = self.widget:GetPosition(true)
	local sz = self.widget:GetSize(true)
	local scale = self.widget:GetGUI():GetScale()
	local s = self.widget:GetItemText(n)
	
	local textoffset=2*scale
	if self.widget:GetSelectedItem()==n then
		textoffset=0
	end
	
	local leftpadding=0
	local rightpadding=0
	if self.widget:GetSelectedItem()==n then
		gui:SetColor(0.25)
		if n>0 then
			leftpadding = scale*1
		end
		rightpadding = scale*1
	else
		gui:SetColor(0.2)
	end
	gui:DrawRect(-leftpadding+pos.x+n*(self.tabsize.x)*scale,textoffset+pos.y,rightpadding+leftpadding+self.tabsize.x*scale+1,self.tabsize.y*scale+self.tabradius*scale+1,0,self.tabradius*scale)
	gui:SetColor(0)
	gui:DrawRect(-leftpadding+pos.x+n*(self.tabsize.x)*scale,textoffset+pos.y,rightpadding+leftpadding+self.tabsize.x*scale+1,self.tabsize.y*scale+self.tabradius*scale+1,1,self.tabradius*scale)
	
	if self.widget:GetSelectedItem()~=n then
		gui:SetColor(0)
		gui:DrawLine(pos.x+n*self.tabsize.x*scale,pos.y+self.tabsize.y*scale,pos.x+n*self.tabsize.x*scale+self.tabsize.x*scale,pos.y+self.tabsize.y*scale)
	end
	if self.hovereditem==n and self.widget:GetSelectedItem()~=n then
		gui:SetColor(1)
	else
		gui:SetColor(0.7)
	end
	gui:DrawText(s,pos.x+(n*self.tabsize.x+self.textindent)*scale,textoffset+pos.y+self.textindent*scale,(self.tabsize.x-self.textindent*2)*scale-2,(self.tabsize.y-self.textindent*2)*scale-1,Text.VCenter+Text.Center)

end

function Script:MouseDown(button,x,y)
	if button==Mouse.Left then
		if self.hovereditem~=self.widget:GetSelectedItem() and self.hovereditem~=nil then
			self.widget.selection=self.hovereditem
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,self.hovereditem)
		end
	elseif button==Mouse.Right then
		if self.hovereditem~=self.widget:GetSelectedItem() and self.hovereditem~=nil then
			EventQueue:Emit(Event.WidgetMenu,self.widget,self.hovereditem,x,y)		
		end
	end
end

function Script:KeyDown(keycode)
	if keycode==Key.Right or keycode==Key.Down then
		local item = self.widget:GetSelectedItem() + 1
		if item<self.widget:CountItems() then
			self.widget.selection=item
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,item)
		end
	elseif keycode==Key.Left or keycode==Key.Up then
		local item = self.widget:GetSelectedItem() - 1
		if item>-1 and self.widget:CountItems()>0 then
			self.widget.selection=item
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,item)
		end
	elseif keycode==Key.Tab then
		local item = self.widget:GetSelectedItem() + 1
		if item>self.widget:CountItems()-1 then
			item=0
		end
		if self.widget:CountItems()>1 then
			self.widget.selection=item
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,item)
		end		
	end
end

function Script:MouseMove(x,y)
	local prevhovereditem = self.hovereditem
	self.hovereditem = nil
	local scale = self.widget:GetGUI():GetScale()
	local sz = self.widget:GetSize(true)
	if x>=0 and y>=0 and x<sz.width and y<self.tabsize.y*scale then
		local item = math.floor(x / (self.tabsize.x*scale))
		if item>=0 and item<self.widget:CountItems() then
			self.hovereditem=item
		end
	end
	if self.hovereditem==self.widget:GetSelectedItem() and prevhovereditem==nil then
		return
	end
	if self.hovereditem==nil and prevhovereditem==self.widget:GetSelectedItem() then
		return
	end
	if prevhovereditem~=self.hovereditem then
		local pos = self.widget:GetPosition(true)
		local sz = self.widget:GetSize(true)
		self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
	end
end

Widget Rendering

Widgets are buffered and rendered with an advanced system that draws only the portions of the screen that need to be updated.  The GUI is rendered into a texture, and then the composite image is drawn onscreen.  This means you can have very complex interfaces rendering in real-time game menus with virtually no performance cost.

By default, no images are used to render the UI so you don't have to include any extra files in your project.

Widget Items

Each widget stores a list of items you can add, remove, and edit.  These are useful for list boxes, choice boxes, and other custom widgets.

GUI Events

Leadwerks 4.4 introduces a new concept into your code, the event queue.  This stores a list of events that have occurred.  When you retrieve an event it is removed from the stack:

	while EventQueue:Peek() do
		local event = EventQueue:Wait()
		if event.source == widget then
			print("OK!")
		end
	end

Resolution Independence

Leadwerks GUI is designed to operate at any resolution.  Creation and positioning of widgets uses a coordinate system based on a 1080p monitor, but the GUI can use a global scale to make the interface scale up or down to accommodate any DPI including 4K and 8K monitors.  The image below is rendering the interface at 200% scaling on a 4K monitor.

gui.thumb.png.cdc37a2be840446845db2915d57a754c.png

A default script will be included that you can include from Main.lua to build up a menu system for starting and quitting games, and handling common graphical features and other settings.

Image2.thumb.jpg.86e408079029dc5f8e15be9ee8487159.jpg

Leadwerks GUI will be released in Leadwerks Game Engine 4.4.

  • Like 1
  • Upvote 10
 Share

10 Comments


Recommended Comments

Looks awesome Josh, can't wait to use it.

quick question: if you set the game time/speed to 0, does this affect the UI? This is usefull for when you want to pause the games (read: no updateworld calls) but still want to have a working UI.

  • Upvote 2
Link to comment
On ‎5‎/‎30‎/‎2017 at 5:37 AM, AggrorJorn said:

Looks awesome Josh, can't wait to use it.

quick question: if you set the game time/speed to 0, does this affect the UI? This is usefull for when you want to pause the games (read: no updateworld calls) but still want to have a working UI.

The GUI system is not affected by timing in any way.  It's completely event-based.

Link to comment

I think this design is really cool! It'll be interesting to see what type of controls people come up with and put up on the Workshop!

Link to comment

OMG! This update is awesome!! I am going to design game soon afterwards I get my written documents type up. Leadwerks is improving greatly. Please, Team Leadwerks keep on thriving on Leadwerks Engine. I desire to design the best video product I can invest into Leadwerks Engine. You lured away from Unreal Engine, especially the heavy cost Unreal charges and Unity charges even more than everyone, ridiculous. :wacko::unsure:

  • Upvote 1
Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...