From b26085efb492241a6fcc3d89b4448e7569f5f28d Mon Sep 17 00:00:00 2001 From: triplefox Date: Mon, 3 Dec 2012 22:53:10 -0800 Subject: [PATCH] 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 --- docs/gui_design.txt | 49 ++-- lua.c | 4 +- pkg/base/client_start.lua | 1 + pkg/base/common.lua | 464 ++++++++++++++++++------------------ pkg/base/icegui/widgets.lua | 179 +++++++------- pkg/base/lib_gui.lua | 13 + 6 files changed, 365 insertions(+), 345 deletions(-) diff --git a/docs/gui_design.txt b/docs/gui_design.txt index a618fe5..80fe726 100644 --- a/docs/gui_design.txt +++ b/docs/gui_design.txt @@ -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 \ No newline at end of file diff --git a/lua.c b/lua.c index dd867e2..46f20cb 100644 --- a/lua.c +++ b/lua.c @@ -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); diff --git a/pkg/base/client_start.lua b/pkg/base/client_start.lua index d9953af..3ed99c1 100644 --- a/pkg/base/client_start.lua +++ b/pkg/base/client_start.lua @@ -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 diff --git a/pkg/base/common.lua b/pkg/base/common.lua index 799be90..c8e75f4 100644 --- a/pkg/base/common.lua +++ b/pkg/base/common.lua @@ -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 = {} diff --git a/pkg/base/icegui/widgets.lua b/pkg/base/icegui/widgets.lua index bfcdd12..f68582f 100644 --- a/pkg/base/icegui/widgets.lua +++ b/pkg/base/icegui/widgets.lua @@ -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()y or this.b()