Jump to content
  • entries
    940
  • comments
    5,894
  • views
    863,938

Leadwerks GUI


Josh

4,215 views

 Share

Leadwerks GUI is now functioning, on the beta branch, Windows only, Lua interpreter only.

 

blogentry-1-0-70977700-1468169858_thumb.jpg

 

GUI Class

static GUI* Create(Context* context)

Creates a new GUI

 

Widget Class

static Widget* Create(const int x, const int y, const int width, const int height, Widget* parent, const int style=0)

Creates a new Widget. Widgets can be made into buttons, dropdown boxes, or anything else by attaching a script.

 

virtual bool SetScript(const std::string& path, const bool start = true)

Sets a widget's script for drawing and logic

 

virtual void SetText(const std::string& text)

Sets a text string for the widget that can be retrieved with GetText().

 

The widget script will call GUI drawing commands:

virtual void DrawImage(Image* image, const int x, const int y);//lua

virtual void DrawImage(Image* image, const int x, const int y, const int width, const int height);//lua

virtual void SetColor(const float r, const float g, const float b);//lua

virtual void SetColor(const float r, const float g, const float b, const float a);//lua

virtual void DrawRect(const int x, const int y, const int width, const int height, const int fillmode = 0, const int radius = 0);//lua

virtual void DrawText(std::string& text, const int x, const int y, const int width, const int height, const int style = 0);//lua

virtual void DrawLine(const int x0, const int y0, const int x1, const int y1);//lua

 

Images are also supported (these are loaded from a tex file):

static Image* Load(std::string& path, GUI* gui);

 

Images are a little funny. If the GUI is context-based it will internally use a texture. If the GUI is window-based the image will store a bitmap that varies with the operating system. The point is, the GUI drawing commands will work the same on either.

 

The button script looks like this and provides a fully functional button:

Script.pushed=false
Script.hovered=false

function Script:Draw()
   --System:Print("Paint Button")

   local pos = self.widget:GetPosition(true)
   local gui = self.widget:GetGUI()

   gui:SetColor(1,1,1,1)

   if self.pushed then
       gui:SetColor(0.2,0.2,0.2)
   else
       if self.hovered then
           gui:SetColor(0.3,0.3,0.3)
       else
           gui:SetColor(0.25,0.25,0.25)
       end
   end
   gui:DrawRect(pos.x,pos.y,self.widget.size.width,self.widget.size.height,0,3)

   gui:SetColor(0.9,0.9,0.9)

   local text = self.widget:GetText()
   if text~="" then
       if self.pushed then
           gui:DrawText(text,pos.x+1,pos.y+1,self.widget.size.width,self.widget.size.height,Text.Center+Text.VCenter)    
       else
           gui:DrawText(text,pos.x,pos.y,self.widget.size.width,self.widget.size.height,Text.Center+Text.VCenter)    
       end
   end

   gui:DrawRect(pos.x,pos.y,self.widget.size.width,self.widget.size.height,1,3)
end

function Script:MouseEnter(x,y)
   self.hovered = true
   self.widget:Redraw()
end

function Script:MouseLeave(x,y)
   self.hovered = false
   self.widget:Redraw()
end

function Script:MouseMove(x,y)
   --System:Print("MouseMove")
end

function Script:MouseDown(button,x,y)
   --System:Print("MouseDown")
   self.pushed=true
   self.widget:Redraw()
end

function Script:MouseUp(button,x,y)
   --System:Print("MouseUp")
   local gui = self.widget:GetGUI()
   self.pushed=false
   if self.hovered then
       EventQueue:Emit(Event.WidgetAction,self.widget)
   end
   self.widget:Redraw()
end

function Script:KeyDown(button,x,y)
   --System:Print("KeyDown")
end

function Script:KeyUp(button,x,y)
   --System:Print("KeyUp")    
end

 

The button uses the new EventQueue class to emit an event:

EventQueue:Emit(Event.WidgetAction,self.widget)

 

The main script can then poll events and find out when the button is pushed. This code is inserted into the main loop to do that:

    while EventQueue:Peek() do
       local event = EventQueue:Wait()
       if event.id == Event.WidgetAction then
           if event.source == button then
               System:Print("The button was pressed!")
           end
       end
   end

 

Support for event callbacks will also be added.

 

The full main script to create a GUI and handle events looks like this:

--Initialize Steamworks (optional)
Steamworks:Initialize()

--Set the application title
title="$PROJECT_TITLE"

--Create a window
local windowstyle = window.Titlebar + window.Resizable-- + window.Hidden
if System:GetProperty("fullscreen")=="1" then windowstyle=windowstyle+window.FullScreen end
window=Window:Create(title,0,0,System:GetProperty("screenwidth","1024"),System:GetProperty("screenheight","768"),windowstyle)
--window:HideMouse()

--Create the graphics context
context=Context:Create(window)
if context==nil then return end

--Create a GUI
local gui = GUI:Create(context)

--Create a new widget
local button = Widget:Create(20,20,300,50,gui:GetBase())

--Set the widget's script to make it a button
button:SetScript("Scripts/GUI/Button.lua")

--Set the button text
button:SetText("Button")

--Create a world
world=World:Create()
world:SetLightQuality((System:GetProperty("lightquality","1")))

--Load a map
local mapfile = System:GetProperty("map","Maps/start.map")
if Map:Load(mapfile)==false then return end

--window:Show()

while window:KeyDown(Key.Escape)==false do

--Process events
while EventQueue:Peek() do
	local event = EventQueue:Wait()
	if event.id == Event.WidgetAction then
		if event.source == button then
			System:Print("The button was pressed!")
		end
	end
end

--If window has been closed, end the program
if window:Closed() then break end

--Handle map change
if changemapname~=nil then

	--Clear all entities
	world:Clear()

	--Load the next map
	Time:Pause()
	if Map:Load("Maps/"..changemapname..".map")==false then return end
	Time:Resume()

	changemapname = nil
end	

--Update the app timing
Time:Update()

--Update the world
world:Update()

--Render the world
world:Render()

--Render statistics
context:SetBlendMode(Blend.Alpha)
if DEBUG then
	context:SetColor(1,0,0,1)
	context:DrawText("Debug Mode",2,2)
	context:SetColor(1,1,1,1)
	context:DrawStats(2,22)
	context:SetBlendMode(Blend.Solid)
else
	--Toggle statistics on and off
	if (window:KeyHit(Key.F11)) then showstats = not showstats end
	if showstats then
		context:SetColor(1,1,1,1)
		context:DrawText("FPS: "..Math:Round(Time:UPS()),2,2)
	end
end

--Refresh the screen
context:Sync(true)

end

 

At this point the system contains everything you need to begin writing your own widget scripts. Small changes may occur in the API before the feature is finalized, but this is pretty close to the final product.

  • Like 1
  • Upvote 8
 Share

6 Comments


Recommended Comments

There is also a Widget::SetPadding(left,top,right,bottom) function that can be used to add padding to a container widget. For example, a tabbed panel would probably add about 20 pixels padding on the top for the '0' y-position starts. Not tested thoroughly, but should work.

 

Note that in the button drawing function I called GetPosition(true) to get the global position of the button widget. If the parent had any padding, or if it was the child of a child widget, the local and global positions would be different values. All drawing commands are in global coordinates, i.e. relative to the top-left corner of the screen.

 

Drawing of complex UIs will be fast thanks to the partial screen refresh system.

 

Widget::Redraw() should be called any time a widget's appearance changes. Never call this in the Widget::Draw() function or you will create an infinite loop.

 

I'm going to focus on the framework and let you guys experiment with specific widgets. Hopefully the community will come up with some cool stuff to try.

  • Upvote 1
Link to comment

What does that button look like on a 640x480 screen vs a 1920x1080. ie are you handling scaling for us?

Link to comment

So a widget is like a single button and a gui is a collection of widgets/buttons? So you have a different gui for say a pause menu and maybe a different gui for main menu and a different one for a hud? Is this better for a hud instead of the previous way?

Link to comment

You could create a single GUI and hide/show multiple parent widgets to show different pages of a menu.

 

What does that button look like on a 640x480 screen vs a 1920x1080. ie are you handling scaling for us?

Right now it will be whatever pixel input you give it. I'm not sure how scaling will work.

 

I don't think I would use this for a HUD. A HUD is only a display, so this offers no benefit to that. Even if it was a circular menu or something, you still have to write the same Lua code to handle the widget, so this doesn't really help. What this does really well is handle parenting, padding, events, and hierarchies.

Link to comment

Working with DPI scaling now. It is a bit difficult, but I think if the scaling is performed at the right times it can be made simple.

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