This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Mads's profile picture

Create advanced GUIs easily

Started by Mads, 01 February 2013 - 07:44 AM
Mads #1
Posted 01 February 2013 - 08:44 AM
This utility allows you to easily create advanced GUIs, with textfields, textboxes, buttons, tabs, scrollbars, etc. It is in very early development stages right now, though.

So far there is not any documentation, as it is very simple to use.

This is the code, if you want to use it:
Spoilerhttp://pastebin.com/j0i5fXAs

local S_WIDTH, S_HEIGHT = term.getSize()

local function cleanExit()
	term.setBackgroundColor(colors.black)
	term.clear()
	term.setCursorPos(1, 1)
	term.setTextColor(colors.yellow)
	print(os.version())
	error()
end

local function inheritsFrom(baseClass)
	local new_class = {}
	local class_mt = {__index = new_class}

	new_class.new = function()
		local newinst = {}
		setmetatable(newinst, class_mt)
		return newinst
	end

	if baseClass then
		setmetatable(new_class, {__index = baseClass})
	end

	return new_class
end


-------------------------------------------------------------
-------------------------------------------------------------
--					GUI Event class					  --
-------------------------------------------------------------
-------------------------------------------------------------
local BUTTON_LEFT = 1;
local BUTTON_RIGHT = 2;
local BUTTON_W_UP = 3;
local BUTTON_W_DOWN = 4;
local BUTTON_W = 5;

local gui_event = {
	x = 0;
	y = 0;
	button = BUTTON_LEFT;
	key = 0;
}
gui_event.__index = gui_event

gui_event.new = function()
	local t = {}
	setmetatable(t, gui_event)

	return t
end

local gui_base = {
	width = 0;
	height = 0;
	x = 0;
	y = 0;
	hasBgColor = true;
	visible = true;
	bgColor = colors.lightBlue;
}
gui_base.__index = gui_base

function gui_base:onMouseEvent(ev)
end

function gui_base:onKeyEvent(ev)
end

function gui_base:draw()
end



-------------------------------------------------------------
-------------------------------------------------------------
--					GUI Button class					 --
-------------------------------------------------------------
-------------------------------------------------------------
local gui_button = inheritsFrom(gui_base)

function gui_button:init(text, x, y, w, h)
	self.textColor = colors.black
	self.bgColor = colors.white
	self.text = text
	self.x = x
	self.y = y
	self.width = w
	self.height = h
	self.onClick = function(x, y) end
	self.centerText = true
	self.clickable = true
end

function gui_button:draw()
	if self.visible then
		term.setTextColor(self.textColor)

		if self.hasBgColor then
			term.setBackgroundColor(self.bgColor)
			local w, h = self.width, self.height
			local x, y = math.floor(self.x), math.floor(self.y)

			-- print("x, y = " .. x .. ", " .. y)

			for _x = 0, w - 1 do
				for _y = 0, h - 1 do
					-- print("Drawing at (" .. _x + x .. ", " .. _y + y .. ")")
					term.setCursorPos(_x + x, _y + y)
					io.write(" ")
				end
			end
		end

		local pX, pY = math.floor(self.x), math.floor(self.y)
		local w, h = self.width, self.height
		local cX, cY = (self.centerText and math.floor(pX + w / 2) - self.text:len() / 2 or pX), (self.centerText and math.floor(pY + h / 2) or pY)
		term.setCursorPos(cX, cY)
		if self.text:len() == 1 then
			term.setCursorPos(cX + 1, cY)
			io.write(self.text)
		else
			io.write(self.text:sub(1, (self.text:len() > w - 1 and w - 1 or self.text:len())))
		end
	end
end

function gui_button:onMouseEvent(ev)
	if self.visible then
		local _x, _y = math.floor(self.x), math.floor(self.y)
		local w, h = self.width, self.height
		-- _x, _y = _x - 1, _y - 1

		-- print("(" .. tostring(ev.x) .. ", " .. tostring(ev.y) .. ")")

		if self.clickable and ev.x >= _x and ev.x < _x + w and ev.y >= _y and ev.y < _y + h then
			self.onClick(self, ev.x, ev.y)
		end
	end
end



-------------------------------------------------------------
-------------------------------------------------------------
--					GUI Textbox class					--
-------------------------------------------------------------
-------------------------------------------------------------
local gui_textfield = inheritsFrom(gui_base)

function gui_textfield:init(text, x, y, w, m_ch)
	self.text = text
	self.textColor = colors.black
	self.bgColor = colors.white
	self.replaceChar = nil
	self.chars = {}
	self.editable = true

	for i = 1, text:len() do
		table.insert(self.chars, text:sub(i, i))
	end

	local _w, h = term.getSize()

	self.x = x
	self.y = y
	self.width = w or 30
	self.height = 1

	--[[
	local sW, sH = self.width, self.height
	
	while sH ~= 1 do
		self.height = self.height - 0.2
		sW, sH = self.width, self.height
	end
	]]

	self.m_ch = m_ch or false
	self.__onClick = function(x, y)
		self:edit()
	end
end

function gui_textfield:draw()
	if self.visible then
		term.setTextColor(self.textColor)

		if self.hasBgColor then
			term.setBackgroundColor(self.bgColor)
			local w, h = self.width, self.height
			local x, y = math.floor(self.x), math.floor(self.y)

			-- print("x, y = " .. x .. ", " .. y)

			for _x = 0, w - 1 do
				for _y = 0, h - 1 do
					-- print("Drawing at (" .. _x + x .. ", " .. _y + y .. ")")
					term.setCursorPos(_x + x, _y + y)
					io.write(" ")
				end
			end
		end

		local pX, pY = math.floor(self.x), math.floor(self.y)
		-- pY = pY - 1
		-- print(pX .. ", " .. pY)
		local w, h = self.width, self.height
		local cX, cY = pX, math.floor(pY + h / 2)
		term.setCursorPos(cX, cY)
		
		local line = self.text
		local _line = line

		if self.replaceChar then
			line = ""

			for i = 1, _line:len() do
				line = line .. self.replaceChar:sub(1, 1)
			end
		end

		io.write(line:sub(1, (line:len() > w - 1 and w - 1 or line:len())))
	end
end

function gui_textfield:edit()
	if self.visible and self.editable then
		local pX, pY = math.floor(self.x), math.floor(self.y)
		-- pX, pY = pX, pY - 1
		-- print(pX .. ", " .. pY)
		local w, h = self.width, self.height
		term.setCursorPos(pX, math.floor(pY + h / 2))
		term.setCursorBlink(true)

		local line = self.text
		local cursorX = line:len()
		local chars = {}

		for i = 1, self.text:len() do
			table.insert(chars, self.text:sub(i, i))
		end

		local function redraw(r_ch)
			term.setTextColor(self.textColor)
			term.setBackgroundColor(self.bgColor)
			term.setCursorPos(pX, math.floor(pY + h / 2))

			line = ""

			for _, c in ipairs(chars) do
				line = line .. c
			end

			local _line = line

			if self.replaceChar then
				line = ""

				for i = 1, _line:len() do
					line = line .. self.replaceChar:sub(1, 1)
				end
			end

			for i = 1, w do
				io.write(" ")
			end

			term.setCursorPos(pX, math.floor(pY + h / 2))

			local currentX = pX

			if line:len() >= w - 1 and cursorX >= w - 1 then
				io.write(line:sub(cursorX - (w - 2), cursorX))
				currentX = pX + cursorX - line:sub(1, cursorX - (w - 1)):len()
			elseif line:len() >= w - 1 and cursorX < w - 1 then
				io.write(line:sub(1, w - 1))
				currentX = pX + cursorX
			else
				io.write(line)
				currentX = pX + cursorX
			end

			line = _line

			term.setCursorPos(currentX, math.floor(pY + h / 2))
		end

		redraw(self.replaceChar)

		while true do
			local ev, key = os.pullEvent()

			if ev == "key" and (m_ch and #chars < m_ch or true) then
				if key == keys.left then
					if cursorX > 0 then
						cursorX = cursorX - 1
					end
				elseif key == keys.right then
					if cursorX < line:len() then
						cursorX = cursorX + 1
					end
				elseif key == keys.enter then
					break
				elseif key == keys.backspace then
					if cursorX > 0 then
						table.remove(chars, cursorX)
						cursorX = cursorX - 1
					end
				elseif key == keys.delete then
					if cursorX < line:len() then
						table.remove(chars, cursorX + 1)
					end
				end
			elseif ev == "char" then
				table.insert(chars, cursorX + 1, key)
				cursorX = cursorX + 1
			end

			redraw()
		end

		term.setCursorBlink(false)
		self.text = line
	end
end

function gui_textfield:setText(t)
	self.text = t

	for i = 1, t:len() do
		self.chars[i] = t:sub(i, i)
	end
end

function gui_textfield:onMouseEvent(ev)
	if self.visible then
		local _x, _y = math.floor(self.x), math.floor(self.y)
		local w, h = self.width, self.height

		if self.editable and ev.x >= _x and ev.x < _x + w and ev.y >= _y and ev.y < _y + h then
			self.__onClick(ev.x, ev.y)
		end
	end
end



-------------------------------------------------------------
-------------------------------------------------------------
--					GUI Label class					  --
-------------------------------------------------------------
-------------------------------------------------------------
local gui_label = inheritsFrom(gui_base)

function gui_label:init(text, x, y)
	self.textColor = colors.black
	self.bgColor = colors.white
	self.text = text
	self.x = x
	self.y = y
	self.centerText = false

	self.width = text:len() + 1
	self.height = 1
end

function gui_label:draw()
	if self.visible then
		term.setTextColor(self.textColor)

		if self.hasBgColor then
			term.setBackgroundColor(self.bgColor)
			local w, h = self.width, self.height
			local x, y = math.floor(self.x), math.floor(self.y)

			-- print("x, y = " .. x .. ", " .. y)

			for _x = 0, w - 1 do
				for _y = 0, h - 1 do
					-- print("Drawing at (" .. _x + x .. ", " .. _y + y .. ")")
					term.setCursorPos(_x + x, _y + y)
					io.write(" ")
				end
			end
		end

		local pX, pY = math.floor(self.x), math.floor(self.y)
		-- print(pX .. ", " .. pY)
		local w, h = self.width, self.height
		local cX, cY = (self.centerText and math.floor(pX + w / 2) - self.text:len() / 2 or pX), (self.centerText and math.floor(pY + h / 2) or pY)
		term.setCursorPos(cX, cY)
		if self.text:len() == 1 then
			term.setCursorPos(cX + 1, cY)
			io.write(self.text)
		else
			io.write(self.text:sub(1, (self.text:len() > w - 1 and w - 1 or self.text:len())))
		end
	end
end



-------------------------------------------------------------
-------------------------------------------------------------
--				 GUI Rectangle class					 --
-------------------------------------------------------------
-------------------------------------------------------------
local gui_rect = inheritsFrom(gui_base)

function gui_rect:init(x, y, w, h, c)
	self.x = x
	self.y = y
	self.width = w
	self.height = h
	self.bgColor = c
end

function gui_rect:draw()
	if self.visible then
		if self.hasBgColor then
			term.setBackgroundColor(self.bgColor)
			local w, h = self.width, self.height
			local x, y = math.floor(self.x), math.floor(self.y)

			-- print("x, y = " .. x .. ", " .. y)

			for _x = 0, w - 1 do
				for _y = 0, h - 1 do
					-- print("Drawing at (" .. _x + x .. ", " .. _y + y .. ")")
					term.setCursorPos(_x + x, _y + y)
					io.write(" ")
				end
			end
		end
	end
end



-------------------------------------------------------------
-------------------------------------------------------------
--					GUI Image class					  --
-------------------------------------------------------------
-------------------------------------------------------------
local gui_image = inheritsFrom(gui_base)

function gui_image:init(path, x, y)
	self.path = path
	self.x = x or 0
	self.y = y or 0
	self.imgData = nil
end

function gui_image:load()
	self.imgData = paintutils.loadImage(self.path)

	if self.imgData == false then
		return false
	end

	return true
end

function gui_image:draw()
	paintutils.drawImage(self.imgData, self.x, self.y)
end



-------------------------------------------------------------
-------------------------------------------------------------
--					 GUI Utilities					   --
-------------------------------------------------------------
-------------------------------------------------------------
local function createMessageBox(title, _msg, _type)
	local host = {}

	local msgBox = gui_rect.new()
	msgBox:init(S_WIDTH / 2 - 19, S_HEIGHT / 2 - 5, 20 * 2, 10)
	msgBox.index = #host + 1
	host.msgBox = msgBox

	local msgBoxTitleBar = gui_rect.new()
	msgBoxTitleBar:init(S_WIDTH / 2 - 19, S_HEIGHT / 2 - 5, 20 * 2, 1)
	msgBoxTitleBar.bgColor = colors.lightGray
	host.msgBoxTitleBar = msgBoxTitleBar

	local msgBoxTitle = gui_label.new()
	msgBoxTitle:init(title, S_WIDTH / 2 - 19, S_HEIGHT / 2 - 5)
	msgBoxTitle.bgColor = msgBoxTitleBar.bgColor
	host.msgBoxTitle = msgBoxTitle

	local msgBoxQuit = gui_button.new()
	msgBoxQuit:init("X", S_WIDTH / 2 - 19 + 20 * 2 - 1, S_HEIGHT / 2 - 5, 1, 1)
	msgBoxQuit.bgColor = colors.red
	msgBoxQuit.textColor = colors.white
	msgBoxQuit.onClick = function(self, x, y)
		for _, el in pairs(host) do
			el.visible = false
		end
	end
	host.msgBoxQuit = msgBoxQuit

	local msgLabel = gui_label.new()
	msgLabel:init(_msg, S_WIDTH / 2 - _msg:len() / 2, S_HEIGHT / 2, _msg:len(), 1)
	msgLabel.bgColor = msgBox.bgColor
	msgLabel.textColor = colors.black

	if _type:lower() == "error" then
		msgLabel.bgColor = colors.red
	end

	host.msgLabel = msgLabel

	return host
end

local function guiPromt(title, _msg, options)
	local host = {}

	local msgBox = gui_rect.new()
	msgBox:init(S_WIDTH / 2 - 19, S_HEIGHT / 2 - 5, 20 * 2, 10 + 1)
	msgBox.index = #host + 1
	host.msgBox = msgBox

	local msgBoxTitleBar = gui_rect.new()
	msgBoxTitleBar:init(S_WIDTH / 2 - 19, S_HEIGHT / 2 - 5, 20 * 2, 1)
	msgBoxTitleBar.bgColor = colors.lightGray
	host.msgBoxTitleBar = msgBoxTitleBar

	local msgBoxTitle = gui_label.new()
	msgBoxTitle:init(title, S_WIDTH / 2 - 19, S_HEIGHT / 2 - 5)
	msgBoxTitle.bgColor = msgBoxTitleBar.bgColor
	host.msgBoxTitle = msgBoxTitle

	local msgBoxQuit = gui_button.new()
	msgBoxQuit:init("X", S_WIDTH / 2 - 19 + 20 * 2 - 1, S_HEIGHT / 2 - 5, 1, 1)
	msgBoxQuit.bgColor = colors.red
	msgBoxQuit.textColor = colors.white
	msgBoxQuit.onClick = function(self, x, y)
		for _, el in pairs(host) do
			el.visible = false
		end
	end
	host.msgBoxQuit = msgBoxQuit

	local msgLabel = gui_label.new()
	msgLabel:init(_msg, S_WIDTH / 2 - _msg:len() / 2, S_HEIGHT / 2, _msg:len(), 1)
	msgLabel.bgColor = msgBox.bgColor
	msgLabel.textColor = colors.black
	host.msgLabel = msgLabel

	local maxLen = options.t:len() > options.f:len() and options.t:len() or options.f:len()

	---[[
	local trueButton = gui_button.new()
	trueButton:init(options.t, S_WIDTH / 2 - 1 - maxLen - 2, S_HEIGHT / 2 + 2, maxLen + 2, 1)
	trueButton.onClick = msgBoxQuit.onClick
	trueButton.bgColor = colors.green
	host.trueButton = trueButton

	local falseButton = gui_button.new()
	falseButton:init(options.f, S_WIDTH / 2 + 1, S_HEIGHT / 2 + 2, maxLen + 2, 1)
	falseButton.onClick = msgBoxQuit.onClick
	falseButton.bgColor = colors.red
	host.falseButton = falseButton
	--]]

	return host
end

This is an example of how to use it(login screen)
Spoilerhttp://pastebin.com/46hv2KBP

local guiElements = {}
local loginScreen = {}

local function initLoginScreen()
	local bg = gui_rect.new()
	bg:init(0, 0, S_WIDTH + 1, S_HEIGHT + 1)
	bg.bgColor = colors.cyan

	local userLabel = gui_label.new()
	userLabel:init("Username: ", S_WIDTH / 2 - 10, S_HEIGHT / 2 + 2, 10, 1)
	userLabel.bgColor = bg.bgColor

	local userField = gui_textfield.new()
	userField:init("", S_WIDTH / 2 + 1, S_HEIGHT / 2 + 2, 15, 1)

	local passLabel = gui_label.new()
	passLabel:init("Password: ", S_WIDTH / 2 - 10, S_HEIGHT / 2 + 2 + 2, 10, 1)
	passLabel.bgColor = bg.bgColor

	local passField = gui_textfield.new()
	passField:init("", S_WIDTH / 2 + 1, S_HEIGHT / 2 + 2 + 2, 15, 1)
	passField.replaceChar = "*"

	table.insert(loginScreen, bg)
	table.insert(loginScreen, userLabel)
	table.insert(loginScreen, userField)
	table.insert(loginScreen, passLabel)
	table.insert(loginScreen, passField)

	local cancelButton = gui_button.new()
	cancelButton:init("Cancel", S_WIDTH / 2 - 10, S_HEIGHT / 2 + 2 + 2 + 2, 8, 3)
	cancelButton.onClick = function(self, x, y)
		cleanExit()
	end
	cancelButton.textColor = bg.bgColor
	table.insert(loginScreen, cancelButton)

	local loginButton = gui_button.new()
	loginButton:init("Log in", S_WIDTH / 2 + 8, S_HEIGHT / 2 + 2 + 2 + 2, 8, 3)
	loginButton.onClick = function(loginB, x, y)
		-- PERFORM LOGIN STUFF

		if success then
			-- Perform success stuff
		else
			userField.editable = false
			passField.editable = false
			loginButton.clickable = false
			cancelButton.clickable = false

			local msg = createMessageBox("Failure", errorMessage, "error")
			msg.msgBoxQuit.onClick = function(self, x, y)
				userField.editable = true
				passField.editable = true
				loginButton.clickable = true
				cancelButton.clickable = true

				for _, el in pairs(msg) do
					el.visible = false
				end
			end

			for _, el in pairs(msg) do
				table.insert(loginScreen, el)
			end
		end
	end
	loginButton.textColor = bg.bgColor
	table.insert(loginScreen, loginButton)
end

initLoginScreen()
guiElements = loginScreen

for _, el in ipairs(guiElements) do
	el:draw()
end



-------------------------------------------------------------
-------------------------------------------------------------
--				   GUI Control loop					  --
-------------------------------------------------------------
-------------------------------------------------------------
while true do
	local ev, p1, p2, p3, p4 = os.pullEvent()

	local gui_ev = gui_event.new()

	if ev == "mouse_click" then
		gui_ev.button = p1
		if p1 == 3 then
			gui_ev.button = BUTTON_W
		end
		gui_ev.x = p2
		gui_ev.y = p3
		
		for _, el in ipairs(guiElements) do
			el:onMouseEvent(gui_ev)
		end		
	elseif ev == "mouse_scroll" then
		gui_ev.button = p1 == 1 and BUTTON_W_DOWN or BUTTON_W_UP
		gui_ev.x = p2
		gui_ev.y = p3
		
		for _, el in ipairs(guiElements) do
			el:onMouseEvent(gui_ev)
		end	
	elseif ev == "key" then
		gui_ev.key = p1

		for _, el in ipairs(guiElements) do
			el:onKeyEvent(gui_ev)
		end	
	end
	
	for _, el in ipairs(guiElements) do
		el:draw()
	end
end

I hope that this will come to good use :)/>
PixelToast #2
Posted 01 February 2013 - 08:47 AM
i dont use GUI apis
i have a weird obsession with using custom rendering
it does the same things as this but its not verry easy to code
AngryTubersLP #3
Posted 06 September 2013 - 03:23 PM
no work?