add table library; fix lineends and tabs; some fixes in widget(table.remove instead of table[n] = nil); a test "every-frame rect render" for gui

This commit is contained in:
triplefox 2012-12-03 22:53:10 -08:00
parent 79fe7d2ab6
commit b26085efb4
6 changed files with 365 additions and 345 deletions

View File

@ -1,28 +1,25 @@
Basic goals: Basic goals:
1. Layout compiler 1. Layout compiler
Three stages of support: Three stages of support:
Absolute positioning and alignment Absolute positioning and alignment
Relative positioning and alignment Relative positioning and alignment
Packing + reflow Packing + reflow
Packing is considerably more complex than the first two but has huge benefits. Packing is considerably more complex than the first two but has huge benefits.
Compilation on existing widgets should be allowed, and parameters should be adjustable via funcpointers Compilation on existing widgets should be allowed, and parameters should be adjustable via funcpointers
(so that tweening is possible) (so that tweening is possible)
2. Event model 2. Event model
Click (down+up) Click (down+up)
Slide (down-hold-up) Slide (down-hold-up)
Raw down, raw up Raw down, raw up
Rectangle collision detection Rectangle collision detection
3. Widgets 3. Widgets
Button Button
Modal button Modal button
Label text Label text
Paragraph text Paragraph text
Markdown(or similar) text Markdown(or similar) text
Slider Slider
Checkbox Checkbox
Frame + clipping Frame + clipping
Color picker Color picker
**************************************Make sure this is a unix-format textfile**************************

4
lua.c
View File

@ -163,11 +163,13 @@ void icelua_loadbasefuncs(lua_State *L)
lua_pushcfunction(L, luaopen_base); lua_pushcfunction(L, luaopen_base);
lua_call(L, 0, 0); lua_call(L, 0, 0);
// here's the other two // here's the other three
lua_pushcfunction(L, luaopen_string); lua_pushcfunction(L, luaopen_string);
lua_call(L, 0, 0); lua_call(L, 0, 0);
lua_pushcfunction(L, luaopen_math); lua_pushcfunction(L, luaopen_math);
lua_call(L, 0, 0); lua_call(L, 0, 0);
lua_pushcfunction(L, luaopen_table);
lua_call(L, 0, 0);
// overwrite dofile/loadfile. // overwrite dofile/loadfile.
lua_pushcfunction(L, icelua_fn_base_loadfile); lua_pushcfunction(L, icelua_fn_base_loadfile);

View File

@ -601,6 +601,7 @@ function client.hook_render()
if players and players[players.current] then if players and players[players.current] then
players[players.current].show_hud() players[players.current].show_hud()
end end
gui_rect_frame_test()
end end
client.hook_tick = h_tick_init client.hook_tick = h_tick_init

View File

@ -28,29 +28,29 @@ DIR_PKG_MAP = DIR_PKG_MAP or "pkg/maps"
MAP_DEFAULT = MAP_DEFAULT or DIR_PKG_MAP.."/mesa.vxl" MAP_DEFAULT = MAP_DEFAULT or DIR_PKG_MAP.."/mesa.vxl"
LIB_LIST = LIB_LIST or { LIB_LIST = LIB_LIST or {
DIR_PKG_LIB.."/icegui/widgets.lua", DIR_PKG_LIB.."/icegui/widgets.lua",
DIR_PKG_LIB.."/lib_bits.lua", DIR_PKG_LIB.."/lib_bits.lua",
DIR_PKG_LIB.."/lib_collect.lua", DIR_PKG_LIB.."/lib_collect.lua",
DIR_PKG_LIB.."/lib_gui.lua", DIR_PKG_LIB.."/lib_gui.lua",
DIR_PKG_LIB.."/lib_map.lua", DIR_PKG_LIB.."/lib_map.lua",
DIR_PKG_LIB.."/lib_namegen.lua", DIR_PKG_LIB.."/lib_namegen.lua",
DIR_PKG_LIB.."/lib_pmf.lua", DIR_PKG_LIB.."/lib_pmf.lua",
DIR_PKG_LIB.."/lib_sdlkey.lua", DIR_PKG_LIB.."/lib_sdlkey.lua",
DIR_PKG_LIB.."/lib_util.lua", DIR_PKG_LIB.."/lib_util.lua",
DIR_PKG_LIB.."/lib_vector.lua", DIR_PKG_LIB.."/lib_vector.lua",
DIR_PKG_LIB.."/obj_player.lua", DIR_PKG_LIB.."/obj_player.lua",
DIR_PKG_LIB.."/obj_intent.lua", DIR_PKG_LIB.."/obj_intent.lua",
} }
-- load libs -- load libs
local i local i
for i=1,#LIB_LIST do for i=1,#LIB_LIST do
local asdf_qwerty = i local asdf_qwerty = i
i = nil i = nil
dofile(LIB_LIST[asdf_qwerty]) dofile(LIB_LIST[asdf_qwerty])
i = asdf_qwerty i = asdf_qwerty
end end
i = nil i = nil
@ -93,184 +93,184 @@ WPN_RIFLE = 1
weapon_models = {} weapon_models = {}
weapons = { weapons = {
[WPN_RIFLE] = function (plr) [WPN_RIFLE] = function (plr)
local this = {} this.this = this local this = {} this.this = this
this.cfg = { this.cfg = {
dmg = { dmg = {
head = 100, head = 100,
body = 49, body = 49,
legs = 33, legs = 33,
}, },
ammo_clip = 10, ammo_clip = 10,
ammo_reserve = 50, ammo_reserve = 50,
time_fire = 1/2, time_fire = 1/2,
time_reload = 2.5, time_reload = 2.5,
recoil_x = 0.0001, recoil_x = 0.0001,
recoil_y = -0.05, recoil_y = -0.05,
name = "Rifle" name = "Rifle"
} }
function this.restock() function this.restock()
this.ammo_clip = this.cfg.ammo_clip this.ammo_clip = this.cfg.ammo_clip
this.ammo_reserve = this.cfg.ammo_reserve this.ammo_reserve = this.cfg.ammo_reserve
end end
function this.reset() function this.reset()
this.t_fire = nil this.t_fire = nil
this.t_reload = nil this.t_reload = nil
this.reloading = false this.reloading = false
this.restock() this.restock()
end end
this.reset() this.reset()
local function prv_fire(sec_current) local function prv_fire(sec_current)
local sya = math.sin(plr.angy) local sya = math.sin(plr.angy)
local cya = math.cos(plr.angy) local cya = math.cos(plr.angy)
local sxa = math.sin(plr.angx) local sxa = math.sin(plr.angx)
local cxa = math.cos(plr.angx) local cxa = math.cos(plr.angx)
local fwx,fwy,fwz local fwx,fwy,fwz
fwx,fwy,fwz = sya*cxa, sxa, cya*cxa fwx,fwy,fwz = sya*cxa, sxa, cya*cxa
-- perform a trace -- perform a trace
local d,cx1,cy1,cz1,cx2,cy2,cz2 local d,cx1,cy1,cz1,cx2,cy2,cz2
d,cx1,cy1,cz1,cx2,cy2,cz2 d,cx1,cy1,cz1,cx2,cy2,cz2
= trace_map_ray_dist(plr.x+sya*0.4,plr.y,plr.z+cya*0.4, fwx,fwy,fwz, 127.5) = trace_map_ray_dist(plr.x+sya*0.4,plr.y,plr.z+cya*0.4, fwx,fwy,fwz, 127.5)
d = d or 127.5 d = d or 127.5
-- see if there's anyone we can kill -- see if there's anyone we can kill
local hurt_idx = nil local hurt_idx = nil
local hurt_part = nil local hurt_part = nil
local hurt_dist = d*d local hurt_dist = d*d
local i,j local i,j
for i=1,players.max do for i=1,players.max do
local p = players[i] local p = players[i]
if p and p ~= plr and p.alive then if p and p ~= plr and p.alive then
local dx = p.x-plr.x local dx = p.x-plr.x
local dy = p.y-plr.y+0.1 local dy = p.y-plr.y+0.1
local dz = p.z-plr.z local dz = p.z-plr.z
for j=1,3 do for j=1,3 do
local dd = dx*dx+dy*dy+dz*dz local dd = dx*dx+dy*dy+dz*dz
local dotk = dx*fwx+dy*fwy+dz*fwz local dotk = dx*fwx+dy*fwy+dz*fwz
local dot = math.sqrt(dd-dotk*dotk) local dot = math.sqrt(dd-dotk*dotk)
if dot < 0.55 and dd < hurt_dist then if dot < 0.55 and dd < hurt_dist then
hurt_idx = i hurt_idx = i
hurt_dist = dd hurt_dist = dd
hurt_part = ({"head","body","legs"})[j] hurt_part = ({"head","body","legs"})[j]
break break
end end
dy = dy + 1.0 dy = dy + 1.0
end end
end end
end end
if hurt_idx then if hurt_idx then
-- TODO: ship this off to the server! -- TODO: ship this off to the server!
players[hurt_idx].gun_damage( players[hurt_idx].gun_damage(
hurt_part, this.cfg.dmg[hurt_part], plr) hurt_part, this.cfg.dmg[hurt_part], plr)
elseif cx2 then elseif cx2 then
-- TODO: block health rather than instant block removal -- TODO: block health rather than instant block removal
map_block_break(cx2,cy2,cz2) map_block_break(cx2,cy2,cz2)
end end
-- TODO: fire a tracer -- TODO: fire a tracer
-- apply recoil -- apply recoil
-- attempting to emulate classic behaviour provided i have it right -- attempting to emulate classic behaviour provided i have it right
plr.recoil(sec_current, this.cfg.recoil_y, this.cfg.recoil_x) plr.recoil(sec_current, this.cfg.recoil_y, this.cfg.recoil_x)
end end
function this.reload() function this.reload()
if this.ammo_clip ~= this.cfg.ammo_clip then if this.ammo_clip ~= this.cfg.ammo_clip then
if this.ammo_reserve ~= 0 then if this.ammo_reserve ~= 0 then
if not this.reloading then if not this.reloading then
this.reloading = true this.reloading = true
plr.zooming = false plr.zooming = false
this.t_reload = nil this.t_reload = nil
end end end end end end
end end
function this.click(button, state) function this.click(button, state)
if button == 1 then if button == 1 then
-- LMB -- LMB
if this.ammo_clip > 0 then if this.ammo_clip > 0 then
this.firing = state this.firing = state
else else
this.firing = false this.firing = false
-- TODO: play sound -- TODO: play sound
end end
elseif button == 3 then elseif button == 3 then
-- RMB -- RMB
if state and not this.reloading then if state and not this.reloading then
plr.zooming = not plr.zooming plr.zooming = not plr.zooming
end end
end end
end end
function this.get_model() function this.get_model()
return weapon_models[WPN_RIFLE] return weapon_models[WPN_RIFLE]
end end
function this.draw(px, py, pz, ya, xa, ya2) function this.draw(px, py, pz, ya, xa, ya2)
client.model_render_bone_global(this.get_model(), 0, client.model_render_bone_global(this.get_model(), 0,
px, py, pz, ya, xa, ya2, 3) px, py, pz, ya, xa, ya2, 3)
end end
function this.tick(sec_current, sec_delta) function this.tick(sec_current, sec_delta)
if this.reloading then if this.reloading then
if not this.t_reload then if not this.t_reload then
this.t_reload = sec_current + this.cfg.time_reload this.t_reload = sec_current + this.cfg.time_reload
end end
if sec_current >= this.t_reload then if sec_current >= this.t_reload then
local adelta = this.cfg.ammo_clip - this.ammo_clip local adelta = this.cfg.ammo_clip - this.ammo_clip
if adelta > this.ammo_reserve then if adelta > this.ammo_reserve then
adelta = this.ammo_reserve adelta = this.ammo_reserve
end end
this.ammo_reserve = this.ammo_reserve - adelta this.ammo_reserve = this.ammo_reserve - adelta
this.ammo_clip = this.ammo_clip + adelta this.ammo_clip = this.ammo_clip + adelta
this.t_reload = nil this.t_reload = nil
this.reloading = false this.reloading = false
plr.arm_rest_right = 0 plr.arm_rest_right = 0
else else
local tremain = this.t_reload - sec_current local tremain = this.t_reload - sec_current
local telapsed = this.cfg.time_reload - tremain local telapsed = this.cfg.time_reload - tremain
local roffs = math.min(tremain,telapsed) local roffs = math.min(tremain,telapsed)
roffs = math.min(roffs,0.3)/0.3 roffs = math.min(roffs,0.3)/0.3
plr.arm_rest_right = roffs plr.arm_rest_right = roffs
end end
elseif this.firing and this.ammo_clip == 0 then elseif this.firing and this.ammo_clip == 0 then
this.firing = false this.firing = false
elseif this.firing and ((not this.t_fire) or sec_current >= this.t_fire) then elseif this.firing and ((not this.t_fire) or sec_current >= this.t_fire) then
prv_fire(sec_current) prv_fire(sec_current)
this.t_fire = this.t_fire or sec_current this.t_fire = this.t_fire or sec_current
this.t_fire = this.t_fire + this.cfg.time_fire this.t_fire = this.t_fire + this.cfg.time_fire
if this.t_fire < sec_current then if this.t_fire < sec_current then
this.t_fire = sec_current this.t_fire = sec_current
end end
this.ammo_clip = this.ammo_clip - 1 this.ammo_clip = this.ammo_clip - 1
-- TODO: poll: do we want to require a new click per shot? -- TODO: poll: do we want to require a new click per shot?
end end
if this.t_fire and this.t_fire < sec_current then if this.t_fire and this.t_fire < sec_current then
this.t_fire = nil this.t_fire = nil
end end
end end
return this return this
end, end,
} }
weapons_enabled = {} weapons_enabled = {}
@ -278,50 +278,50 @@ weapons_enabled[WPN_RIFLE] = true
-- teams -- teams
teams = { teams = {
[0] = { [0] = {
name = "Blue Master Race", name = "Blue Master Race",
color_mdl = {16,32,128}, color_mdl = {16,32,128},
color_chat = {0,0,255}, color_chat = {0,0,255},
}, },
[1] = { [1] = {
name = "Green Master Race", name = "Green Master Race",
color_mdl = {16,128,32}, color_mdl = {16,128,32},
color_chat = {0,192,0}, color_chat = {0,192,0},
}, },
} }
cpalette_base = { cpalette_base = {
0x7F,0x7F,0x7F, 0x7F,0x7F,0x7F,
0xFF,0x00,0x00, 0xFF,0x00,0x00,
0xFF,0x7F,0x00, 0xFF,0x7F,0x00,
0xFF,0xFF,0x00, 0xFF,0xFF,0x00,
0x00,0xFF,0x00, 0x00,0xFF,0x00,
0x00,0xFF,0xFF, 0x00,0xFF,0xFF,
0x00,0x00,0xFF, 0x00,0x00,0xFF,
0xFF,0x00,0xFF, 0xFF,0x00,0xFF,
} }
cpalette = {} cpalette = {}
do do
local i,j local i,j
for i=0,7 do for i=0,7 do
local r,g,b local r,g,b
r = cpalette_base[i*3+1] r = cpalette_base[i*3+1]
g = cpalette_base[i*3+2] g = cpalette_base[i*3+2]
b = cpalette_base[i*3+3] b = cpalette_base[i*3+3]
for j=0,3 do for j=0,3 do
local cr = math.floor((r*j)/3) local cr = math.floor((r*j)/3)
local cg = math.floor((g*j)/3) local cg = math.floor((g*j)/3)
local cb = math.floor((b*j)/3) local cb = math.floor((b*j)/3)
cpalette[#cpalette+1] = {cr,cg,cb} cpalette[#cpalette+1] = {cr,cg,cb}
end end
for j=1,4 do for j=1,4 do
local cr = r + math.floor(((255-r)*j)/4) local cr = r + math.floor(((255-r)*j)/4)
local cg = g + math.floor(((255-g)*j)/4) local cg = g + math.floor(((255-g)*j)/4)
local cb = b + math.floor(((255-b)*j)/4) local cb = b + math.floor(((255-b)*j)/4)
cpalette[#cpalette+1] = {cr,cg,cb} cpalette[#cpalette+1] = {cr,cg,cb}
end end
end end
end end
damage_blk = {} damage_blk = {}

View File

@ -1,4 +1,9 @@
-- look into actual drawing functionality!~ -- look into actual drawing functionality!~
-- the client drawing code requires manual memory management:
-- we have to allocate a buffer and draw pixels to it
-- this code can't deal with that problem...
-- lib_gui will have to provide a layer that takes the abstract APIs here and adds drawing functions
-- on top. the abstract API can assist by adding a "dirty" flag so that cache management is straightforward.
-- sketch listener and collision system: -- sketch listener and collision system:
-- rect and layers detection (derive layers from hierarchy) -- rect and layers detection (derive layers from hierarchy)
@ -11,6 +16,7 @@
-- 3. iterate through the children, moving them to the positions and sizes desired as specified by the packing mode. -- 3. iterate through the children, moving them to the positions and sizes desired as specified by the packing mode.
-- also something to note - when we draw we have to pass a clip rectangle upwards so that scrolling is possible. -- also something to note - when we draw we have to pass a clip rectangle upwards so that scrolling is possible.
-- this isn't strictly necessary, but if the possibility is there, use it!
local P = {} local P = {}
@ -18,106 +24,107 @@ local widget_mt = {}
function widget_mt.__add(a, b) a.add_child(b) return a end function widget_mt.__add(a, b) a.add_child(b) return a end
function widget_mt.__sub(a, b) a.remove_child(b) return a end function widget_mt.__sub(a, b) a.remove_child(b) return a end
function widget_mt.__tostring(a) function widget_mt.__tostring(a)
return a.x.."x "..a.y.."y "..a.relx().."rx "..a.rely().."ry" return a.x.."x "..a.y.."y "..a.relx().."rx "..a.rely().."ry"
end end
function P.widget(options) function P.widget(options)
local this = {x = options.x or 0, y = options.y or 0, local this = {x = options.x or 0, y = options.y or 0,
parent = options.parent or nil, parent = options.parent or nil,
children = options.children or {}, children = options.children or {},
align_x = options.align_x or 0.5, align_y = options.align_y or 0.5} align_x = options.align_x or 0.5, align_y = options.align_y or 0.5}
local width = options.width or 0 local width = options.width or 0
local height = options.height or 0 local height = options.height or 0
local margin_left = options.margin_left or 0 local margin_left = options.margin_left or 0
local margin_right = options.margin_right or 0 local margin_right = options.margin_right or 0
local margin_top = options.margin_top or 0 local margin_top = options.margin_top or 0
local margin_bottom = options.margin_bottom or 0 local margin_bottom = options.margin_bottom or 0
-- align 0 = top-left -- align 0 = top-left
-- align 1 = bottom-right -- align 1 = bottom-right
-- align 0.5 = center -- align 0.5 = center
function this.width() return width end function this.width() return width end
function this.height() return height end function this.height() return height end
function this.margin_left() return margin_left end function this.margin_left() return margin_left end
function this.margin_top() return margin_top end function this.margin_top() return margin_top end
function this.margin_right() return margin_right end function this.margin_right() return margin_right end
function this.margin_bottom() return margin_bottom end function this.margin_bottom() return margin_bottom end
function this.min_width() return width end function this.min_width() return width end
function this.min_height() return height end function this.min_height() return height end
function this.relx() function this.relx()
local pos = this.x - (this.width() * this.align_x) local pos = this.x - (this.width() * this.align_x)
if this.parent == nil then return pos if this.parent == nil then return pos
else return pos + this.parent.relx() end else return pos + this.parent.relx() end
end end
function this.rely() function this.rely()
local pos = this.y - (this.height() * this.align_y) local pos = this.y - (this.height() * this.align_y)
if this.parent == nil then return pos if this.parent == nil then return pos
else return pos + this.parent.rely() end else return pos + this.parent.rely() end
end end
function this.l() return this.relx() end function this.l() return this.relx() end
function this.t() return this.rely() end function this.t() return this.rely() end
function this.r() return this.relx() + this.width() end function this.r() return this.relx() + this.width() end
function this.b() return this.rely() + this.bottom() end function this.b() return this.rely() + this.height() end
function this.cx() return this.relx() + this.width() * 0.5 end function this.cx() return this.relx() + this.width() * 0.5 end
function this.cy() return this.rely() + this.height() * 0.5 end function this.cy() return this.rely() + this.height() * 0.5 end
function this.inner() function this.inner()
local l = this.l() + this.margin_left() local l = this.l() + this.margin_left()
local t = this.t() + this.margin_top() local t = this.t() + this.margin_top()
local r = this.r() - this.margin_right() local r = this.r() - this.margin_right()
local b = this.b() - this.margin_bottom() local b = this.b() - this.margin_bottom()
return {x=l, y=t, left=l, top=t, right=r, bottom=b, return {x=l, y=t, left=l, top=t, right=r, bottom=b,
width=r-l, height=b-t, cx=l+(r-l)*0.5, cy=t+(b-t)*0.5} width=r-l, height=b-t, cx=l+(r-l)*0.5, cy=t+(b-t)*0.5}
end end
function this.detach() if this.parent then this.parent.children[this] = nil; this.parent = nil end end function this.aabb(x, y, w, h)
function this.add_child(child) child.detach(); child.parent = this; this.children[child] = child end return not (this.l()>x or this.r()<x+w or this.t()>y or this.b()<y+h)
function this.remove_child(child) child.detach() end end
function this.remove_all_children() for k,child in pairs(this.children) do this.remove_child(child) end end
function this.set_parent(parent) this.detach(); this.parent = parent; parent.children[this] = this end function this.collide(x, y, w, h)
function this.despawn() -- very simple aabb collision for mousing. returns the "first and deepest child".
for k,child in pairs(this.children) do child.despawn() end this.remove_all_children() w = w or 1
end h = h or 1
local hit = this.aabb(x, y, w, h)
setmetatable(this, widget_mt) local result = this
for k, v in pairs(this.children) do
result = v.collide(x, y, w, h) or this
end
return result
end
function this.detach() if this.parent then table.remove(this.parent.children, this) this.parent = nil end end
function this.add_child(child) child.detach(); child.parent = this; this.children[child] = child end
function this.remove_child(child) child.detach() end
function this.remove_all_children() for k,child in pairs(this.children) do this.remove_child(child) end end
function this.set_parent(parent) this.detach(); this.parent = parent; parent.children[this] = this end
function this.despawn()
for k,child in pairs(this.children) do child.despawn() end this.remove_all_children()
end
setmetatable(this, widget_mt)
return this return this
end
function P.widget_frame(x, y, width, height)
local w = widget{x=x, y=y, width=width, height=height}
-- presumably this is a drawing widget.
-- the key distinction between this API and the Flash monstrosity I made is that I can readily mix
-- drawing widgets and non-drawing widgets.
-- so then, if a packer is just another child, that means that it can inherit the size of the parent's inner.
-- but what the children "see" reported is what the packer wants to make available.
-- bottom-up traversal. Now I'm remembering this part.
-- most custom widgets use their reported width and height, and that's that, but packers don't.
return w
end end
if _REQUIREDNAME == nil then if _REQUIREDNAME == nil then
widgets = P widgets = P
else else
_G[_REQUIREDNAME] = P _G[_REQUIREDNAME] = P
end end
local test = P.widget{x=100, y=100} local test = P.widget{x=100, y=100, width=100, height=100}
print(test) print(test)
local test2 = P.widget{x=100, y=100} local test2 = P.widget{x=100, y=100, width=100, height=100}
print(test2)
test2.set_parent(test) test2.set_parent(test)
print(test2) print(test2)
print(test.collide(150,150))
return P return P

View File

@ -105,4 +105,17 @@ function gui_string_edit(str, maxlen, key, modif)
return str return str
end end
function gui_rect_frame_test()
-- someday this will grow up to be a real rectangle renderer
local img = common.img_new(32, 32)
for x = 0, 31, 1 do
for y = 0, 31, 1 do
common.img_pixel_set(img, x, y, 0xFFFF0000)
end
end
client.img_blit(img, 0, 0)
common.img_free(img)
end
end end