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:
1. Layout compiler
Three stages of support:
Absolute positioning and alignment
Relative positioning and alignment
Packing + reflow
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
(so that tweening is possible)
2. Event model
Click (down+up)
Slide (down-hold-up)
Raw down, raw up
Rectangle collision detection
3. Widgets
Button
Modal button
Label text
Paragraph text
Markdown(or similar) text
Slider
Checkbox
Frame + clipping
Color picker
**************************************Make sure this is a unix-format textfile**************************
1. Layout compiler
Three stages of support:
Absolute positioning and alignment
Relative positioning and alignment
Packing + reflow
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
(so that tweening is possible)
2. Event model
Click (down+up)
Slide (down-hold-up)
Raw down, raw up
Rectangle collision detection
3. Widgets
Button
Modal button
Label text
Paragraph text
Markdown(or similar) text
Slider
Checkbox
Frame + clipping
Color picker

4
lua.c
View File

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

View File

@ -1,4 +1,9 @@
-- 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:
-- 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.
-- 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 = {}
@ -18,106 +24,107 @@ local widget_mt = {}
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.__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
function P.widget(options)
local this = {x = options.x or 0, y = options.y or 0,
parent = options.parent or nil,
children = options.children or {},
align_x = options.align_x or 0.5, align_y = options.align_y or 0.5}
local width = options.width or 0
local height = options.height or 0
local margin_left = options.margin_left or 0
local margin_right = options.margin_right or 0
local margin_top = options.margin_top or 0
local margin_bottom = options.margin_bottom or 0
-- align 0 = top-left
-- align 1 = bottom-right
-- align 0.5 = center
function this.width() return width end
function this.height() return height end
function this.margin_left() return margin_left end
function this.margin_top() return margin_top end
function this.margin_right() return margin_right end
function this.margin_bottom() return margin_bottom end
function this.min_width() return width end
function this.min_height() return height end
function this.relx()
local pos = this.x - (this.width() * this.align_x)
if this.parent == nil then return pos
else return pos + this.parent.relx() end
end
function this.rely()
local pos = this.y - (this.height() * this.align_y)
if this.parent == nil then return pos
else return pos + this.parent.rely() end
end
function this.l() return this.relx() end
function this.t() return this.rely() end
function this.r() return this.relx() + this.width() end
function this.b() return this.rely() + this.bottom() 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.inner()
local l = this.l() + this.margin_left()
local t = this.t() + this.margin_top()
local r = this.r() - this.margin_right()
local b = this.b() - this.margin_bottom()
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}
end
function this.detach() if this.parent then this.parent.children[this] = nil; 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)
local this = {x = options.x or 0, y = options.y or 0,
parent = options.parent or nil,
children = options.children or {},
align_x = options.align_x or 0.5, align_y = options.align_y or 0.5}
local width = options.width or 0
local height = options.height or 0
local margin_left = options.margin_left or 0
local margin_right = options.margin_right or 0
local margin_top = options.margin_top or 0
local margin_bottom = options.margin_bottom or 0
-- align 0 = top-left
-- align 1 = bottom-right
-- align 0.5 = center
function this.width() return width end
function this.height() return height end
function this.margin_left() return margin_left end
function this.margin_top() return margin_top end
function this.margin_right() return margin_right end
function this.margin_bottom() return margin_bottom end
function this.min_width() return width end
function this.min_height() return height end
function this.relx()
local pos = this.x - (this.width() * this.align_x)
if this.parent == nil then return pos
else return pos + this.parent.relx() end
end
function this.rely()
local pos = this.y - (this.height() * this.align_y)
if this.parent == nil then return pos
else return pos + this.parent.rely() end
end
function this.l() return this.relx() end
function this.t() return this.rely() end
function this.r() return this.relx() + this.width() end
function this.b() return this.rely() + this.height() 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.inner()
local l = this.l() + this.margin_left()
local t = this.t() + this.margin_top()
local r = this.r() - this.margin_right()
local b = this.b() - this.margin_bottom()
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}
end
function this.aabb(x, y, w, h)
return not (this.l()>x or this.r()<x+w or this.t()>y or this.b()<y+h)
end
function this.collide(x, y, w, h)
-- very simple aabb collision for mousing. returns the "first and deepest child".
w = w or 1
h = h or 1
local hit = this.aabb(x, y, w, h)
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
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
return this
end
if _REQUIREDNAME == nil then
widgets = P
widgets = P
else
_G[_REQUIREDNAME] = P
end
local test = P.widget{x=100, y=100}
local test = P.widget{x=100, y=100, width=100, height=100}
print(test)
local test2 = P.widget{x=100, y=100}
print(test2)
local test2 = P.widget{x=100, y=100, width=100, height=100}
test2.set_parent(test)
print(test2)
print(test.collide(150,150))
return P

View File

@ -105,4 +105,17 @@ function gui_string_edit(str, maxlen, key, modif)
return str
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