starlit  ui.lua at [108df84ed3]

File mods/starlit/ui.lua artifact 5149fd5b74 part of check-in 108df84ed3


local lib = starlit.mod.lib

starlit.ui = {}

starlit.type.ui = lib.class {
	name = 'starlit:ui';
	__index = {
		action = function(self, user, state, fields)
			local pg = self.pages[state.page or 'index']
			if not pg then return end
			if pg.handle then
				local redraw, reset = pg.handle(state, user, fields)
				if reset then pg.setupState(state,user) end
				if redraw then self:show(user) end
			end
			if fields.quit then self:cb('onClose', user) end
		end;
		cb = function(self, name, user, ...)
			local state = self:begin(user)
			if self[name] then self[name](state, user, ...) end
			local pcb = self.pages[state.page][name] 
			if pcb then pcb(state, user, ...) end
		end;
		begin = function(self, user, page, ...)
			local state = starlit.activeUI[user.name]
			if state and state.form ~= self.id then
				state = nil
				starlit.activeUI[user.name] = nil
			end
			local created = state == nil

			if not state then
				state = {
					page = page or 'index';
					form = self.id;
					self = self;
					close = function() self:close(user) end;
				}
				starlit.activeUI[user.name] = state
				self:cb('setupState', user, ...)
			elseif page ~= nil and state.page ~= page then
				state.page = page
				local psetup = self.pages[state.page].setupState
				if psetup then psetup(state,user, ...) end
			end
			return state, created
		end;
		render = function(self, state, user)
			return self.pages[state.page].render(state, user)
		end;
		show = function(self, user)
			local state = self:begin(user)
			minetest.show_formspec(user.name, self.id,self:render(state, user))
		end;
		open = function(self, user, page, ...)
			user:suitSound 'starlit-nav'
			self:begin(user, page, ...)
			self:show(user)
		end;
		close = function(self, user)
			local state = starlit.activeUI[user.name]
			if state and state.form == self.id then
				self:cb('onClose', user)
				starlit.activeUI[user.name] = nil
				minetest.close_formspec(user.name, self.id)
			end
		end;
	};
	construct = function(p)
		if not p.id then error('UI missing id') end
		p.pages = p.pages or {}
		return p
	end;
}

function starlit.interface.install(ui)
	starlit.interface.link(ui.id, ui)
end

function starlit.ui.build(def, parent)
	local clr = def.color
	if clr and lib.color.id(clr) then
		clr = clr:to_hsl_o()
	end
	local state = {
		x = (def.x or 0);
		y = (def.y or 0);
		w = def.w or 0, h = def.h or 0;
		fixed = def.fixed or false;
		spacing = def.spacing or 0;
		padding = def.padding or 0;
		align = def.align or (parent and parent.align) or 'left';
		lines = {};
		mode = def.mode or (parent and parent.mode or nil); -- hw or sw
		gen = (parent and parent.gen or 0) + 1;
		fg = def.fg or (parent and parent.fg);
		color = clr or (parent and parent.color) or {
			hue = 260, sat = 0, lum = 0
		};
	}
	local lines = state.lines
	local cmod = string.format('^[hsl:%s:%s:%s',
		state.color.hue, state.color.sat*0xff, state.color.lum*0xff)

	local E = minetest.formspec_escape
	if state.padding/2 > state.x then state.x = state.padding/2 end
	if state.padding/2 > state.y then state.y = state.padding/2 end

	local function btnColorDef(sel)
		local function climg(state,img)
			local selstr
			if sel == nil then
				selstr = string.format(
					'button%s,' ..
					'button_exit%s,' ..
					'image_button%s,' ..
					'item_image_button%s',
					state, state, state, state)
			else
				selstr = E(sel) .. state
			end

			return string.format('%s[%s;' ..
					'bgimg=%s;'               ..
					'bgimg_middle=16;'        ..
					'content_offset=0,0'      ..
				']', sel and 'style' or 'style_type',
				selstr, E(img..'^[resize:48x48'..cmod))
		end

		return climg('',         'starlit-ui-button-sw.png')       ..
		       climg(':hovered', 'starlit-ui-button-sw-hover.png') ..
		       climg(':pressed', 'starlit-ui-button-sw-press.png')
	end
	local function widget(...)
		table.insert(lines, string.format(...))
	end
	local specializedTooltip = false
	if def.kind == 'vert' then
		for _, w in ipairs(def) do
			local src, st = starlit.ui.build(w, state)
			widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src)
			state.y=state.y + state.spacing + st.h
			state.w = math.max(state.w, st.w)
		end
		state.y = state.y - state.spacing
		state.w = state.w + state.padding
		state.h = state.y + state.padding/2
	elseif def.kind == 'hztl' then
		for _, w in ipairs(def) do
			local src, st = starlit.ui.build(w, state)
			widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src)
			-- TODO alignments
			state.x=state.x + state.spacing + st.w
			state.h = math.max(state.h, st.h)
		end
		state.h = state.h + state.padding
		state.w = state.x + state.padding/2
	elseif def.kind == 'hwrap' then
		local idx, rowH, totalH = 0,0,0
		local cols = def.cols or math.huge
		local startX = state.x
		local maxX = startX
		for _, w in ipairs(def) do idx = idx + 1
			local src, st = starlit.ui.build(w, state)
			-- TODO alignments
			rowH = math.max(rowH, st.h)
			if idx > cols or def.w and (state.x + state.spacing + st.w > def.w) then
				totalH = totalH + rowH
				state.x = startX
				rowH,idx = 0,0
				state.y = state.y + state.spacing + st.h
			end
			widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src)
			state.x=state.x + state.spacing + st.w
			maxX = math.max(state.x, maxX)
		end
		totalH = totalH + rowH
		state.h = math.max(state.h, totalH) + state.padding
		state.w = state.x + state.padding/2
		state.x = maxX
	elseif def.kind == 'pane' then
		widget('scroll_container[%s,%s;%s,%s;%s;vertical]',
			state.x, state.y, state.w, state.h,
			def.id)
		local y = 0
		for _, w in ipairs(def) do
			local src, st = starlit.ui.build(w, state)
			widget('container[%s,%s]%scontainer_end[]', 0, y, src)
			y=y + state.spacing + st.h
			state.w = math.max(state.w, st.w)
		end
		widget('scroll_container_end[]')
		if y > state.h then
			widget('scrollbar[%s,%s;%s,%s;vertical;%s;]',
				state.x, state.y, .5, state.h,
				def.id)
		end
		state.w = state.w + state.padding
		state.h = state.h + state.padding/2
	elseif def.kind == 'list' then
		local slotTypes = {
			plain = {hue = 200, sat = -.1, lum = 0};
-- 			element = {hue = 20, sat = -.3, lum = 0};
			chip = {hue = 0, sat = -1, lum = 0};
-- 			psi = {hue = 300, sat = 0, lum = 0};
			power = {hue = 50, sat = 0, lum = .2};
		}
		local img
		if state.mode == 'hw' then
			img = lib.image('starlit-ui-slot-physical.png');
		else
			img = lib.image('starlit-ui-slot.png'):shift(slotTypes[def.listContent or 'plain']);
		end
		local spac = state.spacing
		local target = def.target
		if not target and def.node then
			target=string.format('nodemeta:%s,%s,%s', def.node.x,def.node.y,def.node.z)
		end
		widget('style_type[list;spacing=%s,%s]',spac,spac)
		assert(def.w and def.h, 'ui-lists require a fixed size')
		for lx = 0, def.w-1 do
		for ly = 0, def.h-1 do
			local ox, oy = state.x + lx*(1+spac), state.y + ly*(1+spac)
			table.insert(lines, string.format('image[%s,%s;1.1,1.1;%s]', ox-0.05,oy-0.05, img:render()))
		end end
		table.insert(lines, string.format('listcolors[#00000000;#ffffff10]')) -- FIXME
		table.insert(lines, string.format('list[%s;%s;%s,%s;%s,%s;%s]',
			E(target), E(def.inv),
			state.x, state.y,
			def.w,   def.h,
			def.idx))
		local sm = 1
		state.w = def.w * sm + (spac * (def.w - 1))
		state.h = def.h * sm + (spac * (def.h - 1))
	elseif def.kind == 'contact' then
		if def.color then table.insert(lines, btnColorDef(def.id)) end
		local img = def.img
		local desc
		if def.item then
			img  = ItemStack(def.item):get_name()
			desc = ItemStack(def.item):get_description()
		end
		desc = def.desc or desc
		widget('%simage_button%s[%s,%s;%s,%s;%s;%s;%s]',
			def.item and 'item_' or '',
			def.close and '_exit' or '',
			state.x, state.y, def.w, def.h,
			E(img), E(def.id), E(def.label or ''))
		if desc then
			widget('tooltip[%s;%s]', E(def.id), E(desc))
			specializedTooltip = true
		end
	elseif def.kind == 'button' then
		if def.color then table.insert(lines, btnColorDef(def.id)) end
		local label = E(def.label or '')
		if state.fg then label = lib.color(state.fg):fmt(label) end
		widget('button%s[%s,%s;%s,%s;%s;%s]',
			def.close and '_exit' or '',
			state.x, state.y, def.w, def.h,
			E(def.id), label)
		if def.desc then
			widget('tooltip[%s;%s]', E(def.id), E(def.desc))
			specializedTooltip = true
		end
	elseif def.kind == 'img' then
		widget('%s[%s,%s;%s,%s;%s]',
			def.item and 'item_image' or 'image',
			state.x, state.y, def.w, def.h, E(def.item or def.img))
	elseif def.kind == 'label' then
		local txt = E(def.text)
		if state.fg then txt = lib.color(state.fg):fmt(txt) end
		widget('label[%s,%s;%s]',
			state.x, state.y + def.h*.5, txt)
	elseif def.kind == 'text' then
		-- TODO paragraph formatter
		widget('hypertext[%s,%s;%s,%s;%s;%s]',
			state.x, state.y, def.w, def.h, E(def.id), E(def.text))
	elseif def.kind == 'hbar' or def.kind == 'vbar' then -- TODO fancy image bars
		local cl = lib.color(state.color)
		local fg = state.fg or cl:readable(.8,1)
		local wfac, hfac = 1,1
		local clamp = math.min(math.max(def.fac or 0, 0), 1)
		if def.kind == 'hbar'
			then wfac = wfac * clamp
			else hfac = hfac * clamp
		end
		local x,y, w,h = state.x, state.y, def.w, def.h
		widget('box[%s,%s;%s,%s;%s]',
			x,y, w,h, cl:brighten(0.2):hex())
		if clamp > 0 then
			widget('box[%s,%s;%s,%s;%s]',
				x, y + (h*(1-hfac)), w * wfac, h * hfac, cl:hex())
		end
		if def.text then
			widget('hypertext[%s,%s;%s,%s;;%s]',
				state.x, state.y, def.w, def.h,
				string.format('<global halign=center valign=middle color=%s>%s', fg:hex(), E(def.text)))
		end
	end

	if def.desc and not specializedTooltip then
		local coord
		if def.id then
			coord = E(def.id)
		else
			coord = string.format("%s,%s;%s,%s", state.x, state.y, def.w, def.h)
		end
		widget('tooltip[%s;%s;#000000;#ffffff]', coord, E(def.desc))
	end

	local originX = (parent and parent.x or 0)
	local originY = (parent and parent.y or 0)
	local l = table.concat(lines)
	-- if state.fixed and (state.w < state.x or state.h < state.y) then
	-- 	l = string.format('scroll_container[%s,%s;%s,%s;scroll_%s;%s]%sscroll_container_end[]',
	-- 		(parent and parent.x or 0), (parent and parent.y or 0),
	-- 		state.w, state.h, state.gen,
	-- 		(state.x > state.w) and 'horizontal' or 'vertical', l)
	-- end
	

	if def.mode or def.container then
		if def.mode then
			l = string.format('background9[%s,%s;%s,%s;%s;false;64]',
					originX, originY, state.w, state.h,
					E(string.format('starlit-ui-bg-%s.png%s^[resize:128x128',
						(def.mode == 'sw') and 'digital'
											or 'panel', cmod))) .. l
		end
		if parent == nil or state.color ~= parent.color then
			l = btnColorDef() .. l
		end
	end
	if not parent then
		return string.format('formspec_version[6]size[%s,%s]%s', state.w, state.h, l), state
	else
		return l, state
	end
end

starlit.ui.tooltip = lib.ui.tooltipper {
	colors = {
		-- generic notes
		neutral = lib.color(.5,.5,.5);
		good    = lib.color(.2,1,.2);
		bad     = lib.color(1,.2,.2);
		info    = lib.color(.4,.4,1);
		-- chip notes
		schemaic = lib.color(.2,.7,1);
		ability  = lib.color(.7,.2,1);
		driver   = lib.color(1,.7,.2);
	};
}