Module:UI/Core

From EarthMC
Jump to navigation Jump to search

Documentation for this module may be created at Module:UI/Core/doc

local loader = require('Module:Loader')

local makeClass, yesno, inventorySlot, random =
	loader.require('Class', 'Yesno', 'Inventory slot', 'Random')
local colorData = loader.loadData('Color/Data')
local slot = inventorySlot.slot

local fillmode = function(s)
	if s == 'border' then -- all valid fill modes here
		return s
	else
		return yesno(s, true)
	end
end
local backslashSubstitute = function(s)
	-- substitute every '\<char>' but those that have special meaning for tooltips generation, e.g. '\&'
	s = s:gsub('\\\\', '\254'):gsub('\\([^&/])', '%1'):gsub('\254', '\\\\')
	return s
end
local backslashSubstituteStrict = function (s)
	-- substitute all '\<char>'
	s = s:gsub('\\\\', '\254'):gsub('\\(.)', '%1'):gsub('\254', '\\\\')
	return s
end
local makeGotoClass = function (id)
	return (id and id ~= 'none') and 'goto-' .. id:gsub('^goto%-', '')
end
---------------------------------------------------------------------------------
-- Main Interface Class
---------------------------------------------------------------------------------
local Interface = makeClass('Interface', {
	aprilFools = false,
	---------------------------------------------------------------------------------
	-- constructor(topText: string, id?: string, goBack?: table, hide?: boolean, fill?: boolean, rows?: number, cols?: number)
	-- 
	-- Constructor function for the methods provided. Creates the actual UI.
	---------------------------------------------------------------------------------
	constructor = function (self, ...)
		local topText, id, goBack, hide, fill, rows, cols, noarrow, noclose, arrow_, close_, inline
		if type(({...})[1]) == 'table' then
			local u = ({...})[1]
			topText, id, goBack, hide, fill, rows, cols, noarrow, noclose, arrow_, close_, inline =
				u.topText or u[1],
				u.id,
				type(u.goBack) == 'table' and u.goBack or {
					text = u.return_text or u.goback_text or u['return'] or u.goback,
					link = u.return_link or u.goback_link,
					id = u.return_id or u.goback_id,
				},
				u.hide,
				u.fill,
				u.rows,
				u.cols,
				u.noarrow,
				u.noclose,
				u.arrow or u.arrow_,
				u.close or u.close_,
				u.inline
		else
			topText, id, goBack, hide, fill, rows, cols, noarrow, noclose, arrow_, close_, inline = ...
		end
		goBack = goBack or {}
		rows, cols = tonumber(rows) or 6, tonumber(cols) or 9
		fill, noarrow, noclose, inline = fillmode(fill), yesno(noarrow, false), yesno(noclose, false), yesno(inline, false)
		local arrowX, arrowY, closeX, closeY
		self.randomColor = colorData.MCColors[random.number{16}] -- for april fools only
		self.hide = yesno(hide)
		self.id = id
		self.slots = {}
		self.custom = {}
		self.html = mw.html.create('div')
			:attr{ class='mcui mcui-Chest pixel-image' .. (inline and '' or ' mcui-centered') }
		if topText then
			self.html:tag('div'):attr{ class = 'mcui-header' }
				:wikitext(backslashSubstituteStrict(topText))
		end
		
		if arrow_ then
			if arrow_:match('^%d+%s*,%s*%d+$') then
				arrowX, arrowY = arrow_:match('^(%d+)%s*,%s*(%d+)$')
			elseif not yesno(arrow_) or arrow_:match('[Nn]one') then
				noarrow = true
			end
		end
		arrowX = tonumber(arrowX) or rows
		arrowY = tonumber(arrowY) or 4
		
		if close_ then
			if close_:match('^%d+%s*,%s*%d+$') then
				closeX, closeY = close_:match('^(%d+)%s*,%s*(%d+)$')
			elseif not yesno(close_) or close_:match('[Nn]one') then
				noclose = true
			end
		end
		closeX = tonumber(closeX) or rows
		closeY = tonumber(closeY) or 5
		
		for x = 1, rows, 1 do
			self.slots[x] = {}
			self.custom[x] = {}
			for y = 1, cols, 1 do
				if not noarrow and x == arrowX and y == arrowY then
					self:setSlot(x, y, {
						'Go Back',
						text = goBack.text and '&7' .. goBack.text or 'none',
						link = goBack.link or 'none',
						title = '&aGo Back',
						class = makeGotoClass(goBack.id),
					})
				elseif not noclose and x == closeX and y == closeY then
					self:setSlot(x, y, { 'Close' })
				else
					if fill == 'border' then
						if x == 1 or x == rows or y == 1 or y == cols then
							self:setBlankSlot(x, y)
						else
							self:setEmptySlot(x, y)
						end
					elseif not fill then
						self:setEmptySlot(x, y)
					else
						self:setBlankSlot(x, y)
					end
				end
			end
		end
	end,
	---------------------------------------------------------------------------------
	-- setSlot(x: number, y: number, args: table, isCustom?: boolean)
	-- 
	-- Sets a slot according to the `x` and `y` parameters provided, with the arguments
	-- to the parser as `args`.
	---------------------------------------------------------------------------------
	setSlot = function (self, x, y, args, isCustom)
		checkType(1, x, 'number')
		checkType(2, y, 'number')
		checkType(3, args, { 'table', 'string' })
		
		local tp = type(args)
		
		if tp == 'table' or (tp == 'string' and args:match('<.->.-</.->')) then
			self.slots[x] = self.slots[x] or {}
			self.custom[x] = self.custom[x] or {}
			self.slots[x][y] = args
			self.custom[x][y] = not not isCustom
		elseif tp == 'string' then
			assertTrue(self.slots[x] and self.slots[x][y], 'slot in posistion (%d, %d) does not exist', 2, x, y)[args] = isCustom
		end
		
		return self
	end,
	---------------------------------------------------------------------------------
	-- getSlot(x: number, y: number, prop?: string)
	-- 
	-- Gets a slot according to the `x` and `y` parameters provided, with an option parameter
	-- of `prop` to get a particular slot property.
	---------------------------------------------------------------------------------
	getSlot = function (self, x, y, prop)
		if prop then
			return assertTrue(self.slots[x] and self.slots[x][y], 'slot in posistion (%d, %d) does not exist', 2, x, y)[prop]
		else
			return self.slots[x][y]
		end
	end,
	---------------------------------------------------------------------------------
	-- hasSlot(x: number, y: number)
	-- 
	-- Checks if the UI has a specific slot based on `x` and `y`.
	-- NOTICE: THIS IS CURRENTLY UNUSABLE
	---------------------------------------------------------------------------------
	hasSlot = function (self, x, y)
		return not not self.slots[x][y]
	end,
	---------------------------------------------------------------------------------
	-- isEmptySlot(x: number, y: number)
	-- 
	-- Checks if a slot based on `x` and `y` is not blank.
	---------------------------------------------------------------------------------
	isEmptySlot = function (self, x, y)
		return self.slots[x][y][1] == '' or self.slots[x][y][1] == 'Blank'
	end,
	---------------------------------------------------------------------------------
	-- setBlankSlot(x: number, y: number)
	-- 
	-- Sets a blank slot according to the `x` and `y` parameters provided.
	---------------------------------------------------------------------------------
	setBlankSlot = function (self, x, y)
		if self.aprilFools then
			return self:setSlot(x, y, { ('Blank (%s)'):format(self.randomColor) })
		end
		return self:setSlot(x, y, { 'Blank' })
	end,
	---------------------------------------------------------------------------------
	-- setEmptySlot(x: number, y: number)
	-- 
	-- Sets a empty slot according to the `x` and `y` parameters provided.
	---------------------------------------------------------------------------------
	setEmptySlot = function (self, x, y)
		return self:setSlot(x, y, { '' })
	end,
	---------------------------------------------------------------------------------
	-- __tostring()
	-- 
	-- __tostring metamethod for the class. Parses the `slots` to an acual string.
	---------------------------------------------------------------------------------
	__tostring = function (self)
		for x, xValue in ipairs(self.slots) do
			local row = self.html:tag('div'):addClass('mcui-row')
			for y, yValue in ipairs(xValue) do
				row:wikitext(backslashSubstitute(self.custom[x][y] and yValue or slot(yValue)))
			end
		end
		return self.id
			and tostring(mw.html.create('div')
				:node(self.html)
				:attr{ 
					id = self.id and 'ui-' .. self.id:gsub('^ui%-', ''), 
					class = self.id and 'sbw-ui-tab-content', 
					style = self.hide and 'display: none;' or nil,
				} 
			:done())
			or tostring(self.html)
	end,
	static = {
		makeGotoClass = makeGotoClass
	},
})

return Interface