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
local top = state.y
widget('scroll_container[%s,%s;%s,%s;%s;vertical]',
state.x, state.y, state.w, state.h,
def.id)
local y = 0
local x,w = state.x,state.w
-- state.x = state.x + .75
-- state.w = state.w - .75
local widgs = {}
for _, w in ipairs(def) do
local src, st = starlit.ui.build(w, state)
table.insert(widgs, {
y=y, src=src
})
y=y + state.spacing + st.h
state.w = math.max(state.w, st.w)
end
local xo, barred = 0, false
if y-top > state.h then
barred, xo = true, .60
end
for k,v in ipairs(widgs) do
widget('container[%s,%s]%scontainer_end[]', xo, v.y, v.src)
end
widget('scroll_container_end[]')
if barred then
widget('scrollbaroptions[max=%s]scrollbar[%s,%s;%s,%s;vertical;%s;]',
(y-top-state.h)*10, x, state.y, .5, state.h,
def.id)
end
state.x = x
state.w = w + state.padding
state.h = state.h + state.padding/2
state.y = state.y + state.h
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(def.color or 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
-- l = string.format('style_type[scrollbar;bgcolor=#1155ff]') .. 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);
};
}