diff --git a/game/engines/default/engine/Entity.lua b/game/engines/default/engine/Entity.lua index 6c582f1b4ef2f2e7574ac217a112671f491b75c4..0a73a661fbb87e90b81d5ca70ccd64de4c473e08 100644 --- a/game/engines/default/engine/Entity.lua +++ b/game/engines/default/engine/Entity.lua @@ -413,6 +413,32 @@ function _M:getEntityFinalSurface(tiles, w, h) return _M.__mo_final_repo[self][id].surface, _M.__mo_final_repo[self][id].tex end +--- Get the entity image as an sdl texture for the given tiles and size +-- @param tiles a Tiles instance that will handle the tiles (usually pass it the current Map.tiles) +-- @param w the width +-- @param h the height +-- @return the sdl texture +function _M:getEntityFinalTexture(tiles, w, h) + local id = w.."x"..h + if _M.__mo_final_repo[self] and _M.__mo_final_repo[self][id] then return _M.__mo_final_repo[self][id].tex end + + local Map = require "engine.Map" + tiles = tiles or Map.tiles + + local mos = {} + local list = {} + self:getMapObjects(tiles, mos, 1) + local listsize = #list + for i = 1, Map.zdepth do + if mos[i] then list[listsize+i] = mos[i] end + end + local tex = core.map.mapObjectsToTexture(w, h, unpack(list)) + if not tex then return nil end + _M.__mo_final_repo[self] = _M.__mo_final_repo[self] or {} + _M.__mo_final_repo[self][id] = {tex=tex} + return _M.__mo_final_repo[self][id].tex +end + --- Get a string that will display in text the texture of this entity function _M:getDisplayString(tstr) if tstr then diff --git a/game/engines/default/engine/Game.lua b/game/engines/default/engine/Game.lua index b5a3ae1889870e9563ba23c106b2334b8c9a5488..d5501fe0a1689ef9cee7f54284ba3835952e83dd 100644 --- a/game/engines/default/engine/Game.lua +++ b/game/engines/default/engine/Game.lua @@ -318,8 +318,8 @@ function _M:unregisterDialog(d) local last = self.dialogs[#self.dialogs] or self if last.key then last.key:setCurrent() end if last.mouse then last.mouse:setCurrent() end - if last.on_recover_focus then last:on_recover_focus() end if self.onUnregisterDialog then self:onUnregisterDialog(d) end + if last.on_recover_focus then last:on_recover_focus() end end --- Do we have a dialog running diff --git a/game/engines/default/engine/PlayerProfile.lua b/game/engines/default/engine/PlayerProfile.lua index ab656b88daa28ff3e65d6e09bc51a9e25f0d990c..bcda3d83369069b57f089c6184fb9f6f3a3867d4 100644 --- a/game/engines/default/engine/PlayerProfile.lua +++ b/game/engines/default/engine/PlayerProfile.lua @@ -256,6 +256,7 @@ end --- Saves a profile data function _M:saveGenericProfile(name, data, no_sync, nowrite) -- Delay when we are currently saving + if not profile then return end if savefile_pipe and savefile_pipe.saving then savefile_pipe:pushGeneric("saveGenericProfile", function() self:saveGenericProfile(name, data, nosync) end) return end if not generic_profile_defs[name] then print("[PROFILE] refusing unknown generic data", name) return end diff --git a/game/engines/default/engine/Tooltip.lua b/game/engines/default/engine/Tooltip.lua index 88cba17b617417a823f34b3157df4109981cada2..836f055af45435547c810e90ecd5a53acba12f23 100644 --- a/game/engines/default/engine/Tooltip.lua +++ b/game/engines/default/engine/Tooltip.lua @@ -20,28 +20,44 @@ require "engine.class" local Base = require "engine.ui.Base" local TextzoneList = require "engine.ui.TextzoneList" +local UIContainer = require "engine.ui.UIContainer" local Map = require "engine.Map" --- A generic tooltip module(..., package.seeall, class.inherit(Base)) -tooltip_bound_x1 = 0 +tooltip_bound_x1 = function() return 0 end tooltip_bound_x2 = function() return game.w end -tooltip_bound_y1 = 0 +tooltip_bound_y1 = function() return 0 end tooltip_bound_y2 = function() return game.h end -function _M:init(fontname, fontsize, color, bgcolor, max) +function _M:init(fontname, fontsize, color, bgcolor, max, lockstatus_icon) self.max = max or 300 self.ui = "simple" - + self.fontsize = fontsize self.font = core.display.newFont(fontname or "/data/font/DroidSans.ttf", fontsize or 12) self.default_ui = { TextzoneList.new{weakstore=true, width=self.max, height=500, variable_height=true, font=self.font, ui=self.ui} } - + self.locked = false + + if lockstatus_icon then + local open, ow, oh = self:getImage(lockstatus_icon.open) + local locked, lw, lh = self:getImage(lockstatus_icon.locked) + self.status_icon = {open = {_tex = open:glTexture(), w = ow, h = oh}, locked = {_tex = locked:glTexture(), w = lw, h = lh} } + end + + self.empty = true + self.inhibit = false self.uis = {} - self.w = self.max + 10 + self.w = self.max self.h = 200 + self.uis_h = 0 + self.pingpong = 0 + self.last_display_x = 0 + self.last_display_y = 0 + self.dest_area = { h = self.h } + self.container = UIContainer.new{width = self.w, height = self.h, dest_area = { h = self.h, fixed = true} } Base.init(self, {}) end @@ -49,79 +65,124 @@ function _M:generate() self.frame = self:makeFrame("ui/tooltip/", self.w + 6, self.h + 6) end ---- Set the tooltip text +--- Set the tooltip text function _M:set(str, ...) + -- if locked change is forbiden + if self.locked then return end + self.pingpong = 0 + if type(str) == "string" then str = ... and str:format(...):toTString() or str:toTString() end if type(str) == "number" then str = tostring(str):toTString() end + if self.add_map_str then + if type(self.add_map_str) == "string" then + str = self.add_map_str:toTString() + elseif type(self.add_map_str) == "table" then + str = tstring{} + str:merge(self.add_map_str) + else + str = self.add_map_str + end + end + local max_w = 0 - local max_str_w = self.add_map_str and (self.add_map_str:toTString()):maxWidth(self.font) or 0 + local max_str_w = 0 local uis = {} + local part if not str.is_tstring then + -- format all texts into tstring and calculate required width for i=1, #str do - if type(str[i]) == "string" then - str[i] = str[i]:toTString() - end - if str[i].is_tstring then - max_str_w = math.max(str[i]:maxWidth(self.font) + 5, max_str_w) - else - max_w = math.max(str[i].w, max_w) + part = str[i] + if part then + if type(part) == "string" then + part = part:toTString() + end + if part.is_tstring then + local width = part:maxWidth(self.font) + if width > max_str_w then max_str_w = width end + elseif part.w > max_w then + max_w = part.w + end end end - max_str_w = math.min(max_str_w, self.max) - max_w = math.max(max_w, max_str_w) + -- clip calculated width to max tooltip width + if self.max < max_str_w then max_str_w = self.max end + if max_str_w > max_w then max_w = max_str_w end local ts = tstring{} - if self.add_map_str then ts:merge(self.add_map_str:toTString()) ts:add("---", true) end + local uis_size = #uis for i=1, #str do - if str[i].is_tstring then - if i > 1 then - if str[i - 1] and str[i - 1].is_tstring then - ts:add(true) - end - ts:add("---", true) - end - ts:merge(str[i]:toTString()) - else - if i > 1 then - if not str[i].suppress_line_break then - ts:add(true, "---") + -- if the item is tstring then merge it with main ts + if str[i] then + if str[i].is_tstring then + if i > 1 then + if str[i - 1] and str[i - 1].is_tstring then + ts:add(true) + end + ts:add("---", true) + end + ts:merge(str[i]) + else + -- if we have more than one item in list then create TextzoneList and output ts that was gathering texts till now + if i > 1 then + -- if the item dont have special flag separe them with separator + if not str[i].suppress_line_break then + ts:add(true, "---") + end + local tz = TextzoneList.new{weakstore=true, width=max_w, height=500, variable_height=true, font=self.font, ui=self.ui} + tz:switchItem(ts, ts) + uis_size = uis_size + 1 + uis[uis_size] = tz end - local tz = TextzoneList.new{weakstore=true, width=max_w, height=500, variable_height=true, font=self.font, ui=self.ui} - tz:switchItem(ts, ts) - uis[#uis + 1] = tz + uis_size = uis_size + 1 + uis[uis_size] = str[i] + + -- reset ts + ts = tstring{} end - uis[#uis + 1] = str[i] - - ts = tstring{} end end local tz = TextzoneList.new{weakstore=true, width=max_w, height=500, variable_height=true, font=self.font, ui=self.ui} tz:switchItem(ts, ts) - uis[#uis + 1] = tz + + uis_size = uis_size + 1 + uis[uis_size] = tz if self.uis == self.default_ui then self:erase() end self.uis = uis self.empty = false + -- whole element is tstring so just put it into default element which is textzone else self:erase() self.default_ui[1]:switchItem(str, str) max_w = self.max self.empty = str:isEmpty() end + + self.container:changeUI(self.uis) local uih = 0 for i = 1, #self.uis do uih = uih + self.uis[i].h end - + + self.uis_h = uih + + -- change width and height to new values self.h = uih + self.frame.b2.h self.w = max_w + self.frame.b4.w - + + local clip_h = self.h + if game.tooltip:tooltip_bound_y2() < clip_h then clip_h = game.tooltip:tooltip_bound_y2() end + + self.container:resize(self.w, clip_h - self.frame.b2.h, self.w, clip_h - self.frame.b2.h) + self.h = clip_h + -- resize background frame self.frame.h = self.h self.frame.w = self.w end function _M:erase() + self.uis_h = 0 self.uis = self.default_ui self.default_ui[1].list = nil self.empty = true @@ -130,28 +191,54 @@ end function _M:display() end function _M:toScreen(x, y, nb_keyframes) - -- We translate and scale opengl matrix to make the popup effect easily - local ox, oy = math.floor(x), math.floor(y) - x, y = ox, oy - local hw, hh = math.floor(self.w * 0.5), math.floor(self.h * 0.5) - local tx, ty = x + hw, y + hh - x, y = -hw, -hh - core.display.glTranslate(tx, ty, 0) + self.last_display_x = x + self.last_display_y = y + if self.inhibited == true then return nil end + nb_keyframes = nb_keyframes or 0 + -- Save current matrix and load coords to default values + core.display.glPush() + core.display.glIdentity() + + local locked_w = ( (self.locked and self.uis_h > self.container.dest_area.h) and self.container.scrollbar.w or 0) + + if not self.locked then + if self.pingpong == 0 then + self.container.scrollbar.pos = self.container.scrollbar.pos + nb_keyframes + if self.container.scrollbar.pos > self.container.scrollbar.max then + self.container.scrollbar.pos = self.container.scrollbar.max + self.pingpong = 1 + end + else + self.container.scrollbar.pos = self.container.scrollbar.pos - nb_keyframes + if self.container.scrollbar.pos < 0 then + self.container.scrollbar.pos = 0 + self.pingpong = 0 + end + end + end + -- Draw the frame and shadow - self:drawFrame(self.frame, x+1, y+1, 0, 0, 0, 0.3) - self:drawFrame(self.frame, x-3, y-3, 1, 1, 1, 0.75) - - -- UI elements - local uih = 0 - for i = 1, #self.uis do - local ui = self.uis[i] - ui:display(x + 5, y + 5 + uih, nb_keyframes, ox + 5, oy + 5 + uih) - uih = uih + ui.h + self:drawFrame(self.frame, x - locked_w, y, 0, 0, 0, 0.3, self.w + locked_w, self.h) -- shadow + if self.locked then + self:drawFrame(self.frame, x - locked_w, y, 1, 1, 1, 1, self.w + locked_w, self.h) -- locked frame + else + self:drawFrame(self.frame, x, y, 1, 1, 1, 0.75) -- unlocked frame + end + + if self.status_icon then + local status_off = ( (self.locked and self.uis_h > self.container.dest_area.h) and 0 or self.container.scrollbar.w) + if self.locked then + self.status_icon.locked._tex:toScreen(x + status_off - self.status_icon.locked.w, y + (self.h - self.status_icon.locked.h) * 0.5, self.status_icon.locked.w, self.status_icon.locked.h) + else + self.status_icon.open._tex:toScreen(x + status_off - self.status_icon.open.w, y + (self.h - self.status_icon.open.h) * 0.5, self.status_icon.open.w, self.status_icon.open.h ) + end end - -- Restore normal opengl matrix - core.display.glTranslate(-tx, -ty, 0) + self.container:display(x + 8 - locked_w, y + 8, nb_keyframes, x + 8 - locked_w, y + 8) + + -- Restore saved opengl matrix + core.display.glPop() end --- Displays the tooltip at the given map coordinates @@ -161,48 +248,46 @@ end -- @param my the screen coordinate to display at, if nil it will be computed from tmy -- @param text a text to display, if nil it will interrogate the map under the mouse using the "tooltip" property -- @param force forces tooltip to refresh -function _M:displayAtMap(tmx, tmy, mx, my, text, force) +function _M:displayAtMap(tmx, tmy, mx, my, text, force, nb_keyframes) if not mx then mx, my = game.level.map:getTileToScreen(tmx, tmy) end - if text then - if text ~= self.old_text then - self:set(text) - self:display() - self.old_text = text - end - else - if self.old_ttmx ~= tmx or self.old_ttmy ~= tmy or (game.paused and self.old_turn ~= game.turn) or force then - self.old_text = "" - self.old_ttmx, self.old_ttmy = tmx, tmy - self.old_turn = game.turn - local ts = self:getTooltipAtMap(tmx, tmy, mx, my) - if ts then - self:set(ts) - self:display() - else - self:erase() + if not self.locked then + if text then + if text ~= self.old_text or force then + self:set(text) + self.old_text = text + end + else + if self.old_ttmx ~= tmx or self.old_ttmy ~= tmy or (game.paused and self.old_turn ~= game.turn) or force then + self.old_text = "" + self.old_ttmx, self.old_ttmy = tmx, tmy + self.old_turn = game.turn + local ts = self:getTooltipAtMap(tmx, tmy, mx, my) + if ts then + self:set(ts) + else + self:erase() + end end end end if not self.empty then - local x1, x2, y1, y2 = util.getval(self.tooltip_bound_x1), util.getval(self.tooltip_bound_x2), util.getval(self.tooltip_bound_y1), util.getval(self.tooltip_bound_y2) + local x1, x2, y1, y2 = self.tooltip_bound_x1(), self.tooltip_bound_x2(), self.tooltip_bound_y1(), self.tooltip_bound_y2() if mx < x1 then mx = x1 end if my < y1 then my = y1 end if mx > x2 - self.w then mx = x2 - self.w end if my > y2 - self.h then my = y2 - self.h end - self.last_display_x = mx - self.last_display_y = my - self:toScreen(mx, my) + self:toScreen(mx, my, nb_keyframes) end end --- Gets the tooltips at the given map coord --- This method can/should be overloaded by a module to provide custom tooltips function _M:getTooltipAtMap(tmx, tmy, mx, my) - local tt = {} + if self.locked then return nil end + local tt = {} local seen = game.level.map.seens(tmx, tmy) local remember = game.level.map.remembers(tmx, tmy) local ctrl_state = core.key.modState("ctrl") @@ -211,8 +296,13 @@ function _M:getTooltipAtMap(tmx, tmy, mx, my) local to_add = game.level.map:checkEntity(tmx, tmy, check_type, "tooltip", game.level.map.actor_player) if to_add then if type(to_add) == "string" then to_add = to_add:toTString() end - tt[#tt+1] = to_add + if to_add.is_tstring then + tt[#tt+1] = to_add + else + table.append(tt, to_add) + end end + return to_add end if seen and not ctrl_state then @@ -230,6 +320,6 @@ function _M:getTooltipAtMap(tmx, tmy, mx, my) if #tt > 0 then return tt end - if self.add_map_str then return self.add_map_str:toTString() end + if self.add_map_str then return self.add_map_str end return nil -end +end \ No newline at end of file diff --git a/game/engines/default/engine/dialogs/ShowAchievements.lua b/game/engines/default/engine/dialogs/ShowAchievements.lua index 76ab1966772a5d5bfb59e32ba0240807315da01b..f7eb1350bc314548bdfdadc7da1690bb160e73dc 100644 --- a/game/engines/default/engine/dialogs/ShowAchievements.lua +++ b/game/engines/default/engine/dialogs/ShowAchievements.lua @@ -44,9 +44,29 @@ function _M:init(title, player) self.c_desc = TextzoneList.new{width=math.floor(self.iw * 0.4 - 10), height=self.ih - self.c_self.h} self:generateList("main") + + local direct_draw= function(item, x, y, w, h, total_w, total_h, loffset_x, loffset_y, dest_area) + -- if there is object and is withing visible bounds + if item.tex and total_h + h > loffset_y and total_h < loffset_y + dest_area.h then + local clip_y_start, clip_y_end = 0, 0 + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + h > loffset_y + dest_area.h then + clip_y_end = total_h + h - loffset_y - dest_area.h + end + + local one_by_tex_h = 1 / h + item.tex[1]:toScreenPrecise(x, y, h, h - clip_y_start - clip_y_end, 0, 1, clip_y_start * one_by_tex_h, (h - clip_y_end) * one_by_tex_h) + return h, h, 0, 0, clip_y_start, clip_y_end + end + return 0, 0, 0, 0, 0, 0 + end - self.c_list = ListColumns.new{width=math.floor(self.iw * 0.6 - 10), height=self.ih - 10 - self.c_self.h, scrollbar=true, sortable=true, columns={ - {name="", width={24,"fixed"}, display_prop="--", direct_draw=function(item, x, y) if item.tex then item.tex[1]:toScreen(x+4, y, 16, 16) end end}, + self.c_list = ListColumns.new{width=math.floor(self.iw * 0.6 - 10), height=self.ih - 10 - self.c_self.h, floating_headers = true, scrollbar=true, sortable=true, columns={ + {name="", width={24,"fixed"}, display_prop="--", direct_draw=direct_draw}, {name="Achievement", width=60, display_prop="name", sort="name"}, {name="When", width=20, display_prop="when", sort="when"}, {name="Who", width=20, display_prop="who", sort="who"}, @@ -77,8 +97,7 @@ function _M:switchTo(kind) elseif kind == "all" then self.c_main.checked = false self.c_self.checked = false end - self.c_list.list = self.list - self.c_list:generate() + self.c_list:setList(self.list) self.c_desc.items = {} self.c_desc:switchItem(nil) end diff --git a/game/engines/default/engine/interface/GameTargeting.lua b/game/engines/default/engine/interface/GameTargeting.lua index 1aea763beb0b1ac1391bde1bf1a277b5ceaaec48..afcd76d98c3bf1c6c0483cb803e490994fb21632 100644 --- a/game/engines/default/engine/interface/GameTargeting.lua +++ b/game/engines/default/engine/interface/GameTargeting.lua @@ -52,16 +52,17 @@ end --- Display the tooltip, if any -function _M:targetDisplayTooltip(dx, dy, force) +function _M:targetDisplayTooltip(dx, dy, force, nb_keyframes) -- Tooltip is displayed over all else if self.level and self.level.map and self.level.map.finished then + local tmx, tmy -- Display a tooltip if available if self.tooltip_x then if type(self.tooltip_x) == "table" then - self.tooltip:toScreen(self.tooltip.last_display_x, self.tooltip.last_display_y) + self.tooltip:toScreen(self.tooltip.last_display_x, self.tooltip.last_display_y, nb_keyframes) else - local tmx, tmy = self.level.map:getMouseTile(self.tooltip_x , self.tooltip_y) - self.tooltip:displayAtMap(tmx, tmy, dx, dy, nil, force) + tmx, tmy = self.level.map:getMouseTile(self.tooltip_x , self.tooltip_y) + self.tooltip:displayAtMap(tmx, tmy, dx, dy, nil, force, nb_keyframes) end end @@ -74,8 +75,8 @@ function _M:targetDisplayTooltip(dx, dy, force) end --- Forces the tooltip to pop with the given text -function _M:tooltipDisplayAtMap(x, y, text, extra, force) - self.tooltip:displayAtMap(nil, nil, x, y, text, force) +function _M:tooltipDisplayAtMap(x, y, text, extra, force, nb_keyframes) + self.tooltip:displayAtMap(nil, nil, x, y, text, force, nb_keyframes) if extra then if extra.up then self.tooltip.last_display_y = self.tooltip.last_display_y - self.tooltip.h end end diff --git a/game/engines/default/engine/ui/Base.lua b/game/engines/default/engine/ui/Base.lua index 14e15f301e3aee44b06b35b65351293b10f5a73b..22fd0146d84edbccfcca7b227639891d477d821b 100644 --- a/game/engines/default/engine/ui/Base.lua +++ b/game/engines/default/engine/ui/Base.lua @@ -114,7 +114,7 @@ function _M:init(t, no_gen) self.font_h = self.font:lineSkip() end end - + if t.ui then self.ui = t.ui end if not no_gen then self:generate() end @@ -160,28 +160,120 @@ function _M:makeFrame(base, w, h) return f end -function _M:drawFrame(f, x, y, r, g, b, a, w, h) - if not f.b7 then return end - - x = math.floor(x) - y = math.floor(y) +function _M:drawFrame(f, x, y, r, g, b, a, w, h, total_w, total_h, loffset_x, loffset_y, clip_area) + if not f.b7 then return 0, 0, 0, 0 end + + loffset_x = loffset_x or 0 + loffset_y = loffset_y or 0 + total_w = total_w or 0 + total_h = total_h or 0 + f.w = w or f.w f.h = h or f.h + + clip_area = clip_area or { h = f.h, w = f.w } - -- Sides - f.b8.t:toScreenFull(x + f.b7.w, y, f.w - f.b7.w - f.b9.w + 1, f.b8.h, f.b8.tw, f.b8.th, r, g, b, a) - f.b2.t:toScreenFull(x + f.b7.w, y + f.h - f.b3.h + 1, f.w - f.b7.w - f.b9.w + 1, f.b2.h, f.b2.tw, f.b2.th, r, g, b, a) - f.b4.t:toScreenFull(x, y + f.b7.h, f.b4.w, f.h - f.b7.h - f.b1.h + 1, f.b4.tw, f.b4.th, r, g, b, a) - f.b6.t:toScreenFull(x + f.w - f.b9.w + 1, y + f.b7.h, f.b6.w, f.h - f.b7.h - f.b1.h + 1, f.b6.tw, f.b6.th, r, g, b, a) + -- check if anything is visible + if total_h + f.h > loffset_y and total_h < loffset_y + clip_area.h then + local clip_y_start = 0 + local clip_y_end = 0 + local total_clip_y_start = 0 + local total_clip_y_end = 0 + + local one_by_tex_h = 1 + local fw = 0 + local fh = 0 + + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + f.b7.h > loffset_y + clip_area.h then + clip_y_end = total_h + f.b7.h - loffset_y - clip_area.h + total_clip_y_end = clip_y_end + end + + -- check if top is visible + if total_h + f.b7.h > loffset_y and total_h < loffset_y + clip_area.h then + one_by_tex_h = 1 / f.b7.th + fw, fh = f.b7.w, f.b7.h + f.b7.t:toScreenPrecise(x, y, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b7.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- left top + + one_by_tex_h = 1 / f.b8.th + fw, fh = f.w - f.b7.w - f.b9.w, f.b8.h + f.b8.t:toScreenPrecise(x + f.b7.w, y, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b8.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- top + + one_by_tex_h = 1 / f.b9.th + fw, fh = f.b9.w, f.b9.h + f.b9.t:toScreenPrecise(x + f.w - f.b9.w, y, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b9.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- right top + total_clip_y_start = clip_y_start + else + total_clip_y_start = f.b7.h + end + total_h = total_h + f.b7.h + + local mid_h = f.h - f.b2.h - f.b8.h + clip_y_start = 0 + clip_y_end = 0 + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + mid_h > loffset_y + clip_area.h then + clip_y_end = total_h + mid_h - loffset_y - clip_area.h + total_clip_y_end = total_clip_y_end + clip_y_end + end + -- check if center is visible + if total_h + mid_h > loffset_y and total_h < loffset_y + clip_area.h then + one_by_tex_h = 1 / f.b4.th + fw, fh = f.b4.w, mid_h + f.b4.t:toScreenPrecise(x, y + f.b7.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b4.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- left + + one_by_tex_h = 1 / f.b6.th + fw, fh = f.b6.w, mid_h + f.b6.t:toScreenPrecise(x + f.w - f.b9.w, y + f.b7.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b6.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- right + + one_by_tex_h = 1 / f.b5.th + fw, fh = f.w - f.b7.w - f.b3.w, mid_h + f.b5.t:toScreenPrecise(x + f.b7.w, y + f.b7.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b5.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- center + total_clip_y_start = total_clip_y_start + clip_y_start + else + total_clip_y_start = total_clip_y_start + mid_h + end + total_h = total_h + mid_h + + clip_y_start = 0 + clip_y_end = 0 + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + f.b2.h > loffset_y + clip_area.h then + clip_y_end = total_h + f.b2.h - loffset_y - clip_area.h + total_clip_y_end = total_clip_y_end + clip_y_end + end + + -- check if bottom is visible + if total_h + f.b2.h > loffset_y and total_h < loffset_y + clip_area.h then + one_by_tex_h = 1 / f.b1.th + fw, fh = f.b1.w, f.b1.h + f.b1.t:toScreenPrecise(x, y + f.h - f.b1.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b1.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- left bottom - -- Body - f.b5.t:toScreenFull(x + f.b7.w, y + f.b7.h, f.w - f.b7.w - f.b3.w + 1, f.h - f.b7.h - f.b3.h + 1, f.b6.tw, f.b6.th, r, g, b, a) + one_by_tex_h = 1 / f.b2.th + fw, fh = f.w - f.b7.w - f.b9.w, f.b2.h + f.b2.t:toScreenPrecise(x + f.b7.w, y + f.h - f.b2.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b2.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- bottom - -- Corners - f.b7.t:toScreenFull(x, y, f.b7.w, f.b7.h, f.b7.tw, f.b7.th, r, g, b, a) - f.b1.t:toScreenFull(x, y + f.h - f.b1.h + 1, f.b1.w, f.b1.h, f.b1.tw, f.b1.th, r, g, b, a) - f.b9.t:toScreenFull(x + f.w - f.b9.w + 1, y, f.b9.w, f.b9.h, f.b9.tw, f.b9.th, r, g, b, a) - f.b3.t:toScreenFull(x + f.w - f.b3.w + 1, y + f.h - f.b3.h + 1, f.b3.w, f.b3.h, f.b3.tw, f.b3.th, r, g, b, a) + one_by_tex_h = 1 / f.b3.th + fw, fh = f.b3.w, f.b3.h + f.b3.t:toScreenPrecise(x + f.w - f.b3.w, y + f.h - f.b3.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b3.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- right bottom + end + + return 0, 0, total_clip_y_start, total_clip_y_end + end + return 0, 0, 0, 0 end function _M:setTextShadow(v) diff --git a/game/engines/default/engine/ui/Dialog.lua b/game/engines/default/engine/ui/Dialog.lua index df596139812de4bb34116e260ad06fc540c1e290..d79e56c8c50925d9761e94511f74368bb80bb8ca 100644 --- a/game/engines/default/engine/ui/Dialog.lua +++ b/game/engines/default/engine/ui/Dialog.lua @@ -618,7 +618,7 @@ function _M:toScreen(x, y, nb_keyframes) if self.first_display then self:firstDisplay() self.first_display = false end - -- Restiore normal opengl matrix + -- Restore normal opengl matrix if zoom < 1 then core.display.glScale() end core.display.glTranslate(-tx, -ty, 0) end diff --git a/game/engines/default/engine/ui/Focusable.lua b/game/engines/default/engine/ui/Focusable.lua index dc628a0789a5f1d4a005002ec2359a13f3575d90..8e507b45dc4fc7778c6676ef7456745e7db2a757 100644 --- a/game/engines/default/engine/ui/Focusable.lua +++ b/game/engines/default/engine/ui/Focusable.lua @@ -23,8 +23,9 @@ require "engine.class" module(..., package.seeall, class.make) can_focus = true -focus_decay_max = 5 +focus_decay_max = 8 focus_decay_max_d = 8 +one_by_focus_decay = 1/32 function _M:setFocus(v) self.focused = v diff --git a/game/engines/default/engine/ui/Inventory.lua b/game/engines/default/engine/ui/Inventory.lua index 4c4ae8b2c78c8be6f61ae7eb725d0782a9ab6ee5..e89db2583e862e42bf067608136f326108a86cce 100644 --- a/game/engines/default/engine/ui/Inventory.lua +++ b/game/engines/default/engine/ui/Inventory.lua @@ -41,6 +41,8 @@ function _M:init(t) self.on_drag = t.on_drag self.on_drag_end = t.on_drag_end self.special_bg = t.special_bg + + self._last_x, _last_y, self._last_ox, self._last_oy = 0, 0, 0, 0 if self.tabslist == nil and self.default_tabslist then if type(self.default_tabslist) == "function" then self.tabslist = self.default_tabslist(self) @@ -67,16 +69,37 @@ function _M:generate() end self.uis[#self.uis+1] = {x=0, y=0, ui=self.c_tabs} end + + local direct_draw= function(item, x, y, w, h, total_w, total_h, loffset_x, loffset_y, dest_area) + -- if there is object and is withing visible bounds + if item.object and total_h + h > loffset_y and total_h < loffset_y + dest_area.h then + local clip_y_start, clip_y_end = 0, 0 + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + h > loffset_y + dest_area.h then + clip_y_end = total_h + h - loffset_y - dest_area.h + end + -- get entity texture with everything it has i.e particles + local texture = item.object:getEntityFinalTexture(nil, h, h) + local one_by_tex_h = 1 / h + texture:toScreenPrecise(x, y, h, h - clip_y_start - clip_y_end, 0, 1, clip_y_start * one_by_tex_h, (h - clip_y_end) * one_by_tex_h) + return h, h, 0, 0, clip_y_start, clip_y_end + end + return 0, 0, 0, 0, 0, 0 + end - self.c_inven = ListColumns.new{width=self.w, height=self.h - (self.c_tabs and self.c_tabs.h or 0), sortable=true, scrollbar=true, columns=self.columns or { - {name="", width={20,"fixed"}, display_prop="char", sort="id"}, - {name="", width={24,"fixed"}, display_prop="object", sort="sortname", direct_draw=function(item, x, y) if item.object then item.object:toScreen(nil, x+4, y, 16, 16) end end}, + self.c_inven = ListColumns.new{width=self.w, height=self.h - (self.c_tabs and self.c_tabs.h or 0), floating_headers = true, sortable=true, scrollbar=true, columns=self.columns or { + {name="", width={33,"fixed"}, display_prop="char", sort="id"}, + {name="", width={24,"fixed"}, display_prop="object", sort="sortname", direct_draw=direct_draw}, {name="Inventory", width=72, display_prop="name", sort="sortname"}, {name="Category", width=20, display_prop="cat", sort="cat"}, {name="Enc.", width=8, display_prop="encumberance", sort="encumberance"}, }, list={}, fct=function(item, sel, button, event) if self.fct then self.fct(item, button, event) end end, - select=function(item, sel) if self.on_select then self.on_select(item, sel) end end, + select=self.on_select, on_drag=function(item) if self.on_drag then self.on_drag(item) end end, on_drag_end=function() if self.on_drag_end then self.on_drag_end() end end, } diff --git a/game/engines/default/engine/ui/ListColumns.lua b/game/engines/default/engine/ui/ListColumns.lua index 9513494a4d4ed636c10ec6802218c0443f3d4d3f..1e69ba3a694b6c983e2b0da8c153f3ce4f5aba39 100644 --- a/game/engines/default/engine/ui/ListColumns.lua +++ b/game/engines/default/engine/ui/ListColumns.lua @@ -26,244 +26,436 @@ local Slider = require "engine.ui.Slider" module(..., package.seeall, class.inherit(Base, Focusable)) function _M:init(t) - self.list = assert(t.list, "no list list") + self.list = assert(t.list, "no list entires") self.columns = assert(t.columns, "no list columns") self.w = assert(t.width, "no list width") - self.h = t.height - self.nb_items = t.nb_items - assert(self.h or self.nb_items, "no list height/nb_items") + assert(t.height or t.nb_items, "no list height/nb_items") + self.nb_rows = t.nb_items + self.font = t.font or self.font + self.row_height = t.item_height or (self.font_h + 6) + self.h = t.height or self.row_height * ( (self.nb_rows or 0) + (self.hide_columns and 1 or 0) ) self.sortable = t.sortable self.scrollbar = t.scrollbar self.fct = t.fct - self.select = t.select + self.on_select = t.select self.on_drag = t.on_drag self.on_drag_end = t.on_drag_end self.all_clicks = t.all_clicks - self.hide_columns = t.hide_columns + self.floating_headers = t.floating_headers or true + self.hide_columns = t.hide_columns or false + self.dest_area = t.dest_area and t.dest_area or { h = self.h } + self.text_shadow = t.text_shadow or self.text_shadow + self.click_select = t.click_select or false + self.only_display = t.only_display or false - self.fh = t.item_height or ls_h + self.max_h = 0 + self.max_h_columns = 0 + self.prevrow = 0 + self.prevclick = 0 + self.last_input_was_keyboard = false + self.scroll_inertia = 0 - self.fh = t.item_height or (self.font_h + 6) + self.mouse_pos = { x = 0, y = 0 } - local w = self.w - if self.scrollbar then w = w - 10 end - for j, col in ipairs(self.columns) do - if type(col.width) == "table" then - if col.width[2] == "fixed" then - w = w - col.width[1] - end + if self.scrollbar then self.scrollbar = Slider.new{size=self.h, max=1} end + + self:setColumns(t.columns) + + Base.init(self, t) +end + +function _M:generate() + -- Create the scrollbar + if self.scrollbar then self.scrollbar.h = self.h - (self.hide_columns and 0 or self.max_h_columns) end + + self.sel = 1 + self:selectColumn(1, true) + + -- Init rows + for i=1, #self.list do + local row = self.list[i] + self:generateRow(row) + self.max_h = self.max_h + row.h + end + if self.scrollbar then + self.scrollbar.max = self.max_h - self.h + self.scrollbar.pos = 0 + end + + self:setupInput() + self:onSelect() +end + +function _M:setupInput() + self.mouse:reset() + self.key:reset() + local colx = 0 + + for i=1, #self.columns do + local col = self.columns[i] + local on_left = function(button, x, y, xrel, yrel, bx, by, event) + if button == "left" and event == "button" then self:selectColumn(i) end end + self.mouse:registerZone(colx, 0, col.width, self.row_height, on_left, nil, ("column header%d"):format(i)) + colx = colx + col.width end - for j, col in ipairs(self.columns) do - if type(col.width) == "table" then - if col.width[2] == "fixed" then - col.width = col.width[1] + + local on_mouse = function(button, x, y, xrel, yrel, bx, by, event) + self.last_input_was_keyboard = false + if button == "wheelup" and event == "button" then self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 5 end + elseif button == "wheeldown" and event == "button" then self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 5 end + end + if button == "middle" and self.scrollbar then + if not self.scroll_drag then + self.scroll_drag = true + self.scroll_drag_x_start = x + self.scroll_drag_y_start = y + else + self.scrollbar.pos = util.minBound(self.scrollbar.pos + y - self.scroll_drag_y_start, 0, self.scrollbar.max) + self.scroll_drag_x_start = x + self.scroll_drag_y_start = y end else - col.width = w * col.width / 100 + self.scroll_drag = false end - col.surface = core.display.newSurface(col.width, self.fh) - col.frame = self:makeFrame(nil, col.width, self.fh) - col.frame_special = self:makeFrame("ui/selector", col.width, self.fh) - col.frame_sel = self:makeFrame("ui/selector-sel", col.width, self.fh) - col.frame_usel = self:makeFrame("ui/selector", col.width, self.fh) - col.frame_col = self:makeFrame("ui/heading", col.width, self.fh) - col.frame_col_sel = self:makeFrame("ui/heading-sel", col.width, self.fh) + if (self.all_clicks or button == "left") and event == "button" then + if self.click_select then + if self.prevclick == self.sel then self:onUse(button, event) end + self:onSelect() + self.prevclick = self.sel + else + self:onUse(button, event) + end + end + if event == "motion" and button == "left" and self.on_drag then self.on_drag(self.list[self.sel], self.sel) end + if button == "drag-end" and self.on_drag_end then self.on_drag_end(self.list[self.sel], self.sel) end + self.mouse_pos = { x = bx, y = by } end - Base.init(self, t) + self.mouse:registerZone(0, self.row_height, self.w, self.h, on_mouse, nil, "list area") + self.key:addBinds{ + ACCEPT = function() self.last_input_was_keyboard = true self:onUse("left", "key") end, + MOVE_UP = function() + self.last_input_was_keyboard = true + self.prevrow = self.sel + self.sel = util.minBound(self.sel - 1, 1, #self.list) end, + MOVE_DOWN = function() + self.last_input_was_keyboard = true + self.prevrow = self.sel + self.sel = util.minBound(self.sel + 1, 1, #self.list) end, + } + + self.key:addCommands{ + [{"_UP","ctrl"}] = function() self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 5 end end, + [{"_DOWN","ctrl"}] = function() self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 5 end end, + _HOME = function() + self.last_input_was_keyboard = true + self.sel = 1 + self.scrollbar.pos = 0 + self:onSelect() + end, + _END = function() + self.last_input_was_keyboard = true + self.sel = self.list and #self.list or 1 + self.scrollbar.pos = self.scrollbar.max + self:onSelect() + end, + _PAGEUP = function() + self.last_input_was_keyboard = false + if self.scrollbar then + local scrollby = self.h - (self.only_display and 0 or (self.hide_columns and 0 or (self.floating_headers and self.max_h_columns or 0))) + self.scrollbar.pos = util.minBound(self.scrollbar.pos - scrollby, 0, self.scrollbar.max) + end + end, + _PAGEDOWN = function() + self.last_input_was_keyboard = false + if self.scrollbar then + local scrollby = self.h - (self.only_display and 0 or (self.hide_columns and 0 or (self.floating_headers and self.max_h_columns or 0))) + self.scrollbar.pos = util.minBound(self.scrollbar.pos + scrollby, 0, self.scrollbar.max) + end + end, + } end -function _M:drawItem(item, nb_keyframes) - nb_keyframes = (nb_keyframes or 0) / 2 - for j, col in ipairs(self.columns) do - if not col.direct_draw then - local fw, fh = col.fw, self.fh +function _M:generateRow(row, force) + local max_h = 0 + row.cells = {} + for j=1, #self.columns do + local col = self.columns[j] + row.cells[j] = row.cells[j] or {} + + -- if color for each column is different + if row.color and type(row.color[1]) == "table" then + color = row.color[j] + row.cells[j].color = color + else + row.color = row.color or {255,255,255} + end - local text - if type(col.display_prop) == "function" then - text = col.display_prop(item) + if not row.cells[j]._tex then + if col.direct_draw then + row.cells[j].w, row.cells[j].h = col.direct_draw(row, 0, 0, col.width, self.row_height, 0, 0, 0, 0, self.dest_area) or self.row_height, self.row_height else - text = item[col.display_prop or col.sort] + if type(col.display_prop) == "function" then + text = col.display_prop(row) + else + text = row[col.display_prop or col.sort] + end + if type(text) ~= "table" or not text.is_tstring then + text = util.getval(text, row) + if type(text) ~= "table" then text = tstring.from(tostring(text)) end + end + + if text.is_tstring then + gen = self.font:draw(text:toString(), text:maxWidth(self.font), 255, 255, 255) + else + gen = self.font:draw(text, text:toTString():maxWidth(self.font), 255, 255, 255) + end + + row.cells[j]._tex, row.cells[j]._tex_w, row.cells[j]._tex_h, row.cells[j].w, row.cells[j].h = gen[1]._tex, gen[1]._tex_w, gen[1]._tex_h, gen[1].w, gen[1].h + + if row.cells[j].w > col.width - 2 * col.frame_sel.b4.w then + row.cells[j].display_offset = { x = 0, x_dir = 0 } + end end - if type(text) ~= "table" or not text.is_tstring then - text = util.getval(text, item) - if type(text) ~= "table" then text = tstring.from(tostring(text)) end + end + if row.cells[j].h > max_h then max_h = row.cells[j].h end + end + row.h = self.row_height or max_h +end + +function _M:drawRow(row, row_i, nb_keyframes, x, y, total_w, total_h, loffset_x, loffset_y, dest_area) + nb_keyframes = (nb_keyframes or 0) * 0.5 + dest_area = dest_area or self.dest_area + local column_w_offset = x + local clip_y_start = 0 + local clip_y_end = 0 + local clip_x_start = 0 + local clip_x_end = 0 + local clip_maxy_start = 0 + local clip_maxy_end = 0 + local clip_maxx_start = 0 + local clip_maxx_end = 0 + local frame_clip_y = 0 + + local one_by_white = 1/255 + + for j = 1, #self.columns do + col = self.columns[j] + clip_y_start = 0 + clip_y_end = 0 + + -- if we only want to display content without being able to select etc. + if not self.only_display then + if self.sel == row_i then + if self.focused then + self:drawFrame(col.frame_sel, column_w_offset, y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area) + else + self:drawFrame(col.frame_usel, column_w_offset, y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area) + end + else + if row.focus_decay then + if self.focused then self:drawFrame(col.frame_sel, column_w_offset, y, 1, 1, 1, row.focus_decay * self.one_by_focus_decay, nil, nil, 0, total_h, 0, loffset_y, dest_area) + else self:drawFrame(col.frame_usel, column_w_offset, y, 1, 1, 1, row.focus_decay * self.one_by_focus_decay, nil, nil, 0, total_h, 0, loffset_y, dest_area) end + else + self:drawFrame(col.frame, column_w_offset, y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area) + end end - local color = item.color or {255,255,255} - if item.color and type(item.color[1]) == "table" then - color = item.color[j] + if row.special_bg then + local c = row.special_bg + if type(c) == "function" then c = c(row) end + if c then + self:drawFrame(col.frame_special, column_w_offset, y, c.r, c.g, c.b, c.a or 1, nil, nil, 0, total_h, 0, loffset_y, dest_area) + end + end + if total_h < loffset_y then + frame_clip_y = loffset_y - total_h end + end + + if clip_y_start > clip_maxy_start then clip_maxy_start = clip_y_start end + + if col.direct_draw then + _, _, clip_x_start, clip_x_end, clip_y_start, clip_y_end = col.direct_draw(row, column_w_offset, y, col.width, self.row_height, total_w, total_h, loffset_x, loffset_y, dest_area) or 0, 0, 0, 0, 0, 0 + frame_clip_y = 0 + elseif row.cells[j]._tex then + local center_h = ( (self.row_height and self.row_height or row.cells[j].h) - row.cells[j].h) * 0.5 - local s = col.surface + -- if it started before visible area then compute its top clip, take centering into account + if total_h + center_h < loffset_y then + clip_y_start = loffset_y - total_h - center_h + frame_clip_y = center_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + row.cells[j].h + center_h > loffset_y + dest_area.h then + clip_y_end = total_h + row.cells[j].h + center_h - loffset_y - dest_area.h + end - s:erase(0, 0, 0, 0) - local test_text = text:toString() - local font_w, _ = self.font:size(test_text) + -- clip clipping to avoid texture display errors + if clip_y_start > row.cells[j].h then clip_y_start = row.cells[j].h end - if font_w > fw then - item.displayx_offset = item.displayx_offset or {} - item.displayx_offset[j] = item.displayx_offset[j] or 0 - item.dir = item.dir or {} - item.dir[j] = item.dir[j] or 0 + local one_by_tex_h = 1 / row.cells[j]._tex_h -- precalculate for using it to multiply instead of division + local one_by_tex_w = 1 / row.cells[j]._tex_w - if item.dir[j] == 0 then - item.displayx_offset[j] = item.displayx_offset[j] - nb_keyframes - if -item.displayx_offset[j] >= font_w - fw + 15 then - item.dir[j] = 1 + if row.cells[j].display_offset then + if self.sel == row_i then + -- if we are going right + if row.cells[j].display_offset.x_dir == 0 then + row.cells[j].display_offset.x = row.cells[j].display_offset.x + nb_keyframes + -- if we are going left + else + row.cells[j].display_offset.x = row.cells[j].display_offset.x - nb_keyframes end - elseif item.dir[j] == 1 then - item.displayx_offset[j] = item.displayx_offset[j] + nb_keyframes - if item.displayx_offset[j] >= 0 then - item.dir[j] = 0 + + -- if we would see too much to right then clip it and change dir + if row.cells[j].display_offset.x >= row.cells[j].w - col.width + 2 * col.frame_sel.b4.w then + row.cells[j].display_offset.x_dir = 1 + row.cells[j].display_offset.x = row.cells[j].w - col.width + 2 * col.frame_sel.b4.w + -- if we would see too much to left then clip it and change dir + elseif row.cells[j].display_offset.x <= 0 then + row.cells[j].display_offset.x_dir = 0 + row.cells[j].display_offset.x = 0 end + else + row.cells[j].display_offset.x = 0 end - - -- We use 1000 and do not cut lines to make sure it draws as much as possible - text:drawOnSurface(s, 10000, nil, self.font, col.frame_sel.b4.w+item.displayx_offset[j], (fh - self.font_h) / 2, color[1], color[2], color[3]) - item.autoscroll = true + if self.text_shadow then row.cells[j]._tex:toScreenPrecise(column_w_offset + 1 + col.frame_sel.b4.w, y + 1 + center_h - frame_clip_y, col.width - 2 * col.frame_sel.b4.w, row.cells[j].h - (clip_y_start + clip_y_end), row.cells[j].display_offset.x * one_by_tex_w, (row.cells[j].display_offset.x + col.width - 2 * col.frame_sel.b4.w) * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end + row.cells[j]._tex:toScreenPrecise(column_w_offset + col.frame_sel.b4.w, y + center_h - frame_clip_y, col.width - 2 * col.frame_sel.b4.w, row.cells[j].h - (clip_y_start + clip_y_end), row.cells[j].display_offset.x * one_by_tex_w, (row.cells[j].display_offset.x + col.width - 2 * col.frame_sel.b4.w) * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, row.color[1] * one_by_white, row.color[2] * one_by_white, row.color[3] * one_by_white, 1.0 ) else - text:drawOnSurface(s, 10000, nil, self.font, col.frame_sel.b4.w, (fh - self.font_h) / 2, color[1], color[2], color[3]) + if self.text_shadow then row.cells[j]._tex:toScreenPrecise(column_w_offset + 1 + col.frame_sel.b4.w, y + 1 + center_h - frame_clip_y, row.cells[j].w, row.cells[j].h - (clip_y_start + clip_y_end), 0, row.cells[j].w * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end + row.cells[j]._tex:toScreenPrecise(column_w_offset + col.frame_sel.b4.w, y + center_h - frame_clip_y, row.cells[j].w, row.cells[j].h - (clip_y_start + clip_y_end), 0, row.cells[j].w * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, row.color[1] * one_by_white, row.color[2] * one_by_white, row.color[3] * one_by_white, 1.0 ) end - - item._tex = item._tex or {} - item._tex[j] = {s:glTexture()} end + clip_y_start = clip_y_start + frame_clip_y + column_w_offset = column_w_offset + col.width + if clip_x_start > clip_maxx_start then clip_maxx_start = clip_x_start end + if clip_x_end > clip_maxx_end then clip_maxx_end = clip_x_end end + if clip_y_start > clip_maxy_start then clip_maxy_start = clip_y_start end + if clip_y_end > clip_maxy_end then clip_maxy_end = clip_y_end end end + return 0, 0, clip_maxy_start, clip_maxy_end end -function _M:generate() - self.mouse:reset() - self.key:reset() - - self.sel = 1 - self.scroll = 1 - self.max = #self.list - self:selectColumn(1, true) - - local fh = self.fh - - if not self.h then self.h = self.nb_items * fh end +function _M:setColumns(columns, force) + local w = self.w + local col_size = #columns + local max_h = 0 - self.max_display = math.floor(self.h / fh) - 1 + if self.scrollbar then w = w - self.scrollbar.w end - -- Draw the scrollbar - if self.scrollbar then - self.scrollbar = Slider.new{size=self.h - fh, max=self.max} + for i=1, col_size do + local col = columns[i] + if type(col.width) == "table" then + if col.width[2] == "fixed" then + w = w - col.width[1] + end + end end - - -- Draw the list columns local colx = 0 - for j, col in ipairs(self.columns) do - local fw = col.width - col.fw = fw - local text = col.name - local s = col.surface + for i=1, col_size do + local col = columns[i] + if type(col.width) == "table" then + if col.width[2] == "fixed" then + col.width = col.width[1] + end + else + col.width = w * col.width * 0.01 + end + + col.frame = self:makeFrame(nil, col.width, self.row_height) + col.frame_special = self:makeFrame("ui/selector", col.width, self.row_height) + col.frame_sel = self:makeFrame("ui/selector-sel", col.width, self.row_height) + col.frame_usel = self:makeFrame("ui/selector", col.width, self.row_height) + col.frame_col = self:makeFrame("ui/heading", col.width, self.row_height) + col.frame_col_sel = self:makeFrame("ui/heading-sel", col.width, self.row_height) self.font:setStyle("bold") - s:erase(0, 0, 0, 0) - s:drawColorStringBlended(self.font, text, col.frame_sel.b4.w, (fh - self.font_h) / 2, 255, 255, 255, true, fw - col.frame_sel.b4.w - col.frame_sel.b6.w) + local gen = self.font:draw(col.name:toString(), col.width, 255, 255, 255) self.font:setStyle("normal") - col._tex, col._tex_w, col._tex_h = s:glTexture() - - self.mouse:registerZone(colx, 0, col.width, self.fh, function(button, x, y, xrel, yrel, bx, by, event) - if button == "left" and event == "button" then self:selectColumn(j) end - end) + col._tex, col._tex_w, col._tex_h, col.w, col.h = gen[1]._tex, gen[1]._tex_w, gen[1]._tex_h, gen[1].w, gen[1].h colx = colx + col.width + if col.h > max_h then max_h = col.h end end + self.max_h_columns = self.hide_columns and 0 or (self.row_height or max_h) + self.max_h = self.max_h_columns - -- Draw the list items - for i, item in ipairs(self.list) do self:drawItem(item) end - - -- Add UI controls - self.mouse:registerZone(0, self.fh, self.w, self.h - (self.hide_columns and 0 or self.fh), function(button, x, y, xrel, yrel, bx, by, event) - if button == "wheelup" and event == "button" then self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1) - elseif button == "wheeldown" and event == "button" then self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end - - if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end - self.sel = util.bound(self.scroll + math.floor(by / self.fh), 1, self.max) - self:onSelect() - if (self.all_clicks or button == "left") and event == "button" then self:onUse(button, event) end - if event == "motion" and button == "left" and self.on_drag then self.on_drag(self.list[self.sel], self.sel) end - if button == "drag-end" and self.on_drag_end then self.on_drag_end(self.list[self.sel], self.sel) end - end) - self.key:addBinds{ - ACCEPT = function() self:onUse("left", "key") end, - MOVE_UP = function() - if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end - self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect() - end, - MOVE_DOWN = function() - if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end - self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect() - end, - } - self.key:addCommands{ - [{"_UP","ctrl"}] = function() self.key:triggerVirtual("MOVE_UP") end, - [{"_DOWN","ctrl"}] = function() self.key:triggerVirtual("MOVE_DOWN") end, - _HOME = function() - if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end - self.sel = 1 - self.scroll = util.scroll(self.sel, self.scroll, self.max_display) - self:onSelect() - end, - _END = function() - if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end - self.sel = self.max - self.scroll = util.scroll(self.sel, self.scroll, self.max_display) - self:onSelect() - end, - _PAGEUP = function() - if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end - self.sel = util.bound(self.sel - self.max_display, 1, self.max) - self.scroll = util.scroll(self.sel, self.scroll, self.max_display) - self:onSelect() - end, - _PAGEDOWN = function() - if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end - self.sel = util.bound(self.sel + self.max_display, 1, self.max) - self.scroll = util.scroll(self.sel, self.scroll, self.max_display) - self:onSelect() - end, - } + self.sel = 1 + self:selectColumn(1, true) +end - self:onSelect() +function _M:setList(list, force) + if list and #list > 0 and (self.list ~= list or force) then + self.list = list + self.sel = util.minBound(self.sel, 1, #self.list) + local oldcol = self.cur_col + self:selectColumn(oldcol or 1, (not oldcol) and true or false, self.sort_reverse) + + self.max_h = self.max_h_columns + for i=1, #self.list do + local row = self.list[i] + if self.focus_decay_max then self.list[i].focus_decay = 0 end + self:generateRow(row) + self.max_h = self.max_h + row.h + end + else + self.list = {} + self.sel = 1 + self.max_h = self.max_h_columns + end + if self.scrollbar then + self.scrollbar.pos = 0 + self.scrollbar.max = self.max_h - self.h + end + self.prevrow = 0 end -function _M:setList(list) - self.list = list - self.max = #self.list - self.sel = util.bound(self.sel, 1, self.max) - self.scroll = util.bound(self.scroll, 1, self.max) - self.scroll = util.scroll(self.sel, self.scroll, self.max_display) +function _M:changeAll(columns, list) + self:setColumns(columns) + self:setList(list) + self:setupInput() +end - local oldcol, oldrev = self.cur_col, self.sort_reverse - self:selectColumn(oldcol or 1, (not oldcol) and true or false, self.sort_reverse) +function _M:onSelect(force) + if self.only_display then return end - for i, item in ipairs(self.list) do self:drawItem(item) end + local row = self.list[self.sel] + -- if not found fall back + if not row then return end +-- self.scroll_inertia = 0 + if self.on_select then self.on_select(row, self.sel) end end -function _M:onSelect(force_refresh) - local item = self.list[self.sel] - if not item or (not force_refresh and self.previtem and self.previtem==item) then return end +function _M:removeRow(row_i) + table.remove(self.list, row_i) +end - if rawget(self, "select") then self.select(item, self.sel) end --- self.previtem = item +function _M:appendList(row) + self.list[#self.list + 1] = row + if (not self.sortable or self.only_display) and not force then return end + local col = self.columns[self.cur_col] + if self.sortable and not force then + local fct = col.sort + if type(fct) == "string" then fct = function(a, b) return a[col.sort] < b[col.sort] end end + if self.sort_reverse and fct then local old=fct fct = function(a, b) return old(b, a) end end + pcall(table.sort, self.list, fct) + end end function _M:onUse(...) - local item = self.list[self.sel] - if not item then return end + if #self.list == 0 or self.only_display then return end + + local row = self.list[self.sel] + if not row then return end self:sound("button") - if item.fct then item:fct(item, self.sel, ...) - else self.fct(item, self.sel, ...) end + if row.fct then row:fct(row, self.sel, ...) + else self.fct(row, self.sel, ...) end end function _M:selectColumn(i, force, reverse) - if not self.sortable and not force then return end + if (not self.sortable or self.only_display) and not force then return end local col = self.columns[i] if not col then return end @@ -273,7 +465,7 @@ function _M:selectColumn(i, force, reverse) else self.sort_reverse = not self.sort_reverse end - if type(reverse) == "boolean" then self.sort_reverse = reverse end + self.sort_reverse = reverse if self.sortable and not force then local fct = col.sort @@ -283,75 +475,157 @@ function _M:selectColumn(i, force, reverse) end end -function _M:display(x, y, nb_keyframes, screen_x, screen_y) +function _M:display(x, y, nb_keyframes, screen_x, screen_y, offset_x, offset_y, local_x, local_y) self.last_display_x = screen_x self.last_display_y = screen_y - local bx, by = x, y - if self.sel then - local item = self.list[self.sel] - if self.previtem and self.previtem~=item then - self.previtem.displayx_offset = {} - self:drawItem(self.previtem) - self.previtem = nil - end - if item and item.autoscroll then - self:drawItem(item, nb_keyframes) - self.previtem = item - end - end + nb_keyframes = nb_keyframes or 0 + offset_x = offset_x and offset_x or 0 + local row = 0 - for j = 1, #self.columns do - local col = self.columns[j] - local y = y - if not self.hide_columns then - if self.cur_col == j then self:drawFrame(col.frame_col, x, y) - else self:drawFrame(col.frame_col_sel, x, y) end - col._tex:toScreenFull(x, y, col.fw, self.fh, col._tex_w, col._tex_h) - y = y + self.fh + if self.scrollbar then + self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.scroll_inertia, 0, self.scrollbar.max) + if self.scroll_inertia > 0 then self.scroll_inertia = math.max(self.scroll_inertia - 1, 0) + elseif self.scroll_inertia < 0 then self.scroll_inertia = math.min(self.scroll_inertia + 1, 0) end + if self.scrollbar.pos == 0 or self.scrollbar.pos == self.scrollbar.max then self.scroll_inertia = 0 end + end - local max = math.min(self.scroll + self.max_display - 1, self.max) - for i = self.scroll, max do - local item = self.list[i] - if not item then break end + -- if we used keyboard then match display to input + if self.scrollbar and self.last_input_was_keyboard then + local columns_h = self.hide_columns and 0 or self.max_h_columns + local mul = self.floating_headers and 2 or 1 -- if we have floating headers we have to take them into account while scrolling + local pos = self.hide_columns and 0 or self.max_h_columns + for i = 1, #self.list do + row = self.list[i] + pos = pos + row.h + -- we've reached selected row if self.sel == i then - if self.focused then self:drawFrame(col.frame_sel, x, y) - else self:drawFrame(col.frame_usel, x, y) end - else - self:drawFrame(col.frame, x, y) - if item.focus_decay then - if self.focused then self:drawFrame(col.frame_sel, x, y, 1, 1, 1, item.focus_decay / self.focus_decay_max_d) - else self:drawFrame(col.frame_usel, x, y, 1, 1, 1, item.focus_decay / self.focus_decay_max_d) end - item.focus_decay = item.focus_decay - nb_keyframes - if item.focus_decay <= 0 then item.focus_decay = nil end + -- check if it was visible if not go scroll over there + if pos - mul * columns_h < self.scrollbar.pos then self.scrollbar.pos = util.minBound(pos - mul * columns_h, 0, self.scrollbar.max) + elseif pos + columns_h > self.scrollbar.pos + self.h then self.scrollbar.pos = util.minBound(pos - self.h, 0, self.scrollbar.max) end + break end + end + end + offset_y = offset_y and offset_y or (self.scrollbar and self.scrollbar.pos or 0) + local_x = local_x and local_x or 0 + local_y = local_y and local_y or 0 + + local loffset_y = offset_y - local_y + local current_y = 0 + local current_x = 0 + local total_h = 0 + local clip_x_start = 0 + local clip_x_end = 0 + local clip_y_start = 0 + local clip_y_end = 0 + local frame_clip_y = 0 + local dest_area = {} + dest_area.h, dest_area.fixed = self.dest_area.h, self.dest_area.fixed + local frame_clip_y_start, frame_clip_y_end + + local max_h = 0 + if not self.hide_columns then + for j = 1, #self.columns do + clip_y_end = 0 + local col = self.columns[j] + local center_h = ( (self.row_height and self.row_height or col.h) - col.h) * 0.5 + + if self.floating_headers then + if self.cur_col == j then self:drawFrame(col.frame_col, x + current_x, y + current_y) + else self:drawFrame(col.frame_col_sel, x + current_x, y + current_y) end + local one_by_tex_h = 1 / col._tex_h + col._tex:toScreenPrecise(x + current_x + col.frame_sel.b4.w, y + current_y + center_h, col.w, col.h - (clip_y_start + clip_y_end), 0, col.w / col._tex_w, clip_y_start * one_by_tex_h, (col.h - clip_y_end) * one_by_tex_h ) + elseif total_h + self.max_h_columns > loffset_y and total_h < loffset_y + dest_area.h then + + if self.cur_col == j then _, _, frame_clip_y_start, frame_clip_y_end = self:drawFrame(col.frame_col, x + current_x, y + current_y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area) + else _, _, frame_clip_y_start, frame_clip_y_end = self:drawFrame(col.frame_col_sel, x + current_x, y + current_y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area) end + self.mouse:updateZone(("column header%d"):format(j), current_x, current_y, col.width, self.row_height - frame_clip_y_start - frame_clip_y_end) + + if self.only_display then frame_clip_y = center_h else frame_clip_y = loffset_y - total_h end + + -- if its visible then compute how much of it needs to be clipped, take centering into account + if total_h + center_h < loffset_y then + clip_y_start = loffset_y - total_h - center_h + frame_clip_y = center_h + end - if item.special_bg then - local c = item.special_bg - if type(c) == "function" then c = c(item) end - if c then - self:drawFrame(col.frame_special, x, y, c.r, c.g, c.b, c.a or 1) + -- if it ended after visible area then compute its bottom clip + if total_h + col.h + center_h > loffset_y + dest_area.h then + clip_y_end = total_h + col.h + center_h - loffset_y - dest_area.h + end + local one_by_tex_h = 1 / col._tex_h + if total_h + col.h > loffset_y and total_h < loffset_y + dest_area.h then + col._tex:toScreenPrecise(x + current_x + col.frame_sel.b4.w, y + current_y + center_h - frame_clip_y, col.w, col.h - (clip_y_start + clip_y_end), 0, col.w / col._tex_w, clip_y_start * one_by_tex_h, (col.h - clip_y_end) * one_by_tex_h ) end end - if col.direct_draw then - col.direct_draw(item, x, y, col.fw, self.fh) - elseif item._tex then - if self.text_shadow then item._tex[j][1]:toScreenFull(x+1, y+1, col.fw, self.fh, item._tex[j][2], item._tex[j][3], 0, 0, 0, self.text_shadow) end - item._tex[j][1]:toScreenFull(x, y, col.fw, self.fh, item._tex[j][2], item._tex[j][3]) - end - item.last_display_x = screen_x + (x - bx) - item.last_display_y = screen_y + (y - by) - y = y + self.fh + if col.h > max_h then max_h = col.h end + current_x = current_x + col.width end + max_h = self.row_height or max_h + + if self.floating_headers then + dest_area.h = dest_area.h - max_h + current_y = current_y + max_h + loffset_y = loffset_y + max_h + elseif total_h + max_h > loffset_y and total_h < loffset_y + dest_area.h then + current_y = current_y + max_h + if not self.only_display then current_y = current_y + total_h - loffset_y end + end + total_h = total_h + max_h + end + + local list_start_y = current_y + self.mouse:updateZone("list area", 0, current_y, self.w, self.h - current_y) - x = x + col.width + -- if list is empty then display only column headers and fall back + if #self.list == 0 then return end + + -- refresh focus decay if any + if self.focus_decay_max then self.list[self.sel].focus_decay = self.focus_decay_max end + + -- if we are too deep then end this + if total_h > loffset_y + dest_area.h then return end + for i = 1, #self.list do + row = self.list[i] + -- if its visible then draw it + if total_h + row.h > loffset_y and total_h < loffset_y + dest_area.h then + _, _, clip_y_start, clip_y_end = self:drawRow(row, i, nb_keyframes, x, y + current_y, 0, total_h, 0, loffset_y, dest_area) + + row.last_display_x = screen_x + row.last_display_y = screen_y + current_y + + -- use display loop to determine which row is selected + if not self.last_input_was_keyboard and self.mouse_pos.y + list_start_y> current_y and self.mouse_pos.y + list_start_y< current_y + row.h - clip_y_start then self.sel = i end + current_y = current_y + row.h - clip_y_start + end + + -- decay focus if any + if row.focus_decay then + row.focus_decay = row.focus_decay - nb_keyframes + if row.focus_decay <= 0 then row.focus_decay = nil end + end + -- add full size of row + total_h = total_h + row.h + -- if we are too deep then end this + if total_h > loffset_y + dest_area.h then break end + end + + -- show scrollbar only if there is one, total size of UI element is greater than visible one and only_display switch is not set + if self.focused and self.scrollbar and self.max_h > self.h and not self.only_display then + if self.hide_columns then + self.scrollbar:display(x + self.w - self.scrollbar.w, y) + else + self.scrollbar:display(x + self.w - self.scrollbar.w, y + max_h) + end end - if self.focused and self.scrollbar then - self.scrollbar.pos = self.sel - self.scrollbar:display(bx + self.w - self.scrollbar.w, by + self.fh) + -- if row was changed then refresh it + if self.prevrow ~= self.sel then + self.prevrow = self.sel + if not self.click_select then self:onSelect() end end end diff --git a/game/engines/default/engine/ui/Separator.lua b/game/engines/default/engine/ui/Separator.lua index 0e1553afcb05555380639081cbc26f40f6753505..789c36bd528220c556e36f8ce36c44f0df384de7 100644 --- a/game/engines/default/engine/ui/Separator.lua +++ b/game/engines/default/engine/ui/Separator.lua @@ -27,6 +27,7 @@ function _M:init(t) self.dir = assert(t.dir, "no separator dir") self.size = assert(t.size, "no separator size") + self.dest_area = {w = 1, h = 1} Base.init(self, t) end @@ -36,26 +37,70 @@ function _M:generate() self.middle = self:getUITexture("ui/border_vert_middle.png") self.bottom = self:getUITexture("ui/border_vert_bottom.png") self.w, self.h = self.middle.w, self.size + else self.left = self:getUITexture("ui/border_hor_left.png") self.middle = self:getUITexture("ui/border_hor_middle.png") self.right = self:getUITexture("ui/border_hor_right.png") self.w, self.h = self.size, self.middle.h end + self.dest_area.w = self.w + self.dest_area.h = self.h end -function _M:display(x, y) +function _M:display(x, y, total_w, nb_keyframes, ox, oy, total_h, loffset_x, loffset_y, dest_area) + dest_area = dest_area or self.dest_area + loffset_x = loffset_x and loffset_x or 0 + loffset_y = loffset_y and loffset_y or 0 + total_w = total_w and total_w or 0 + total_h = total_h and total_h or 0 + + local clip_y_start = 0 + local clip_y_end = 0 + local clip_x_start = 0 + local clip_x_end = 0 + + if total_h < loffset_y then clip_y_start = loffset_y - total_h end + if self.dir == "horizontal" then --- x = x - math.floor(self.top.w / 2) --- y = y - math.floor(self.top.h / 2) - self.top.t:toScreenFull(x, y, self.top.w, self.top.h, self.top.tw, self.top.th) - self.bottom.t:toScreenFull(x, y + self.h - self.bottom.h, self.bottom.w, self.bottom.h, self.bottom.tw, self.bottom.th) - self.middle.t:toScreenFull(x, y + self.top.h, self.middle.w, self.h - self.top.h - self.bottom.h, self.middle.tw, self.middle.th) + if total_h + self.top.h > loffset_y and total_h < loffset_y + dest_area.h then + if total_h + self.top.h > loffset_y + dest_area.h then clip_y_end = total_h + self.top.h - loffset_y - dest_area.h end + local one_by_tex_h = 1 / self.top.th + self.top.t:toScreenPrecise(x, y, self.top.w, self.top.h - clip_y_start - clip_y_end, 0, self.top.w / self.top.tw, clip_y_start * one_by_tex_h, (self.top.h - clip_y_end) * one_by_tex_h) + end + clip_y_end = 0 + + if total_h + self.bottom.h > loffset_y and total_h < loffset_y + dest_area.h then + if total_h + self.bottom.h > loffset_y + dest_area.h then clip_y_end = total_h + self.bottom.h - loffset_y - dest_area.h end + local one_by_tex_h = 1 / self.bottom.th + self.bottom.t:toScreenPrecise(x, y + self.h - self.bottom.h, self.bottom.w, self.bottom.h - clip_y_start - clip_y_end, 0, self.bottom.w / self.bottom.tw, clip_y_start * one_by_tex_h, (self.bottom.h - clip_y_end) * one_by_tex_h) + end + clip_y_end = 0 + + if total_h + self.middle.h > loffset_y and total_h < loffset_y + dest_area.h then + if total_h + self.middle.h > loffset_y + dest_area.h then clip_y_end = total_h + self.middle.h - loffset_y - dest_area.h end + local one_by_tex_h = 1 / self.middle.th + self.middle.t:toScreenPrecise(x, y + self.top.h, self.middle.w, self.h - self.top.h - self.bottom.h - clip_y_start - clip_y_end, 0, self.middle.w / self.middle.tw, clip_y_start * one_by_tex_h, (self.h - self.top.h - self.bottom.h - clip_y_end) * one_by_tex_h) + end else --- x = x - math.floor(self.left.w / 2) --- y = y - math.floor(self.left.h / 2) - self.left.t:toScreenFull(x, y, self.left.w, self.left.h, self.left.tw, self.left.th) - self.right.t:toScreenFull(x + self.w - self.right.w, y, self.right.w, self.right.h, self.right.tw, self.right.th) - self.middle.t:toScreenFull(x + self.left.w, y, self.w - self.left.w - self.right.w, self.middle.h, self.middle.tw, self.middle.th) + if total_h + self.left.h > loffset_y and total_h < loffset_y + dest_area.h then + if total_h + self.left.h > loffset_y + dest_area.h then clip_y_end = total_h + self.left.h - loffset_y - dest_area.h end + local one_by_tex_h = 1 / self.left.th + self.left.t:toScreenPrecise(x, y, self.left.w, self.left.h - clip_y_start - clip_y_end, 0, self.left.w / self.left.tw, clip_y_start * one_by_tex_h, (self.left.h - clip_y_end) * one_by_tex_h) + end + clip_y_end = 0 + + if total_h + self.right.h > loffset_y and total_h < loffset_y + dest_area.h then + if total_h + self.right.h > loffset_y + dest_area.h then clip_y_end = total_h + self.right.h - loffset_y - dest_area.h end + local one_by_tex_h = 1 / self.right.th + self.right.t:toScreenPrecise(x + self.w - self.right.w, y, self.right.w, self.right.h - clip_y_start - clip_y_end, 0, self.right.w / self.right.tw, clip_y_start * one_by_tex_h, (self.right.h - clip_y_end) * one_by_tex_h) + end + clip_y_end = 0 + + if total_h + self.middle.h > loffset_y and total_h < loffset_y + dest_area.h then + if total_h + self.middle.h > loffset_y + dest_area.h then clip_y_end = total_h + self.middle.h - loffset_y - dest_area.h end + local one_by_tex_h = 1 / self.middle.th + self.middle.t:toScreenPrecise(x + self.left.w, y, self.w - self.left.w - self.right.w, self.middle.h - clip_y_start - clip_y_end, 0, (self.w - self.left.w - self.right.w) / self.middle.tw, clip_y_start * one_by_tex_h, (self.middle.h - clip_y_end) * one_by_tex_h) + end end end diff --git a/game/engines/default/engine/ui/Slider.lua b/game/engines/default/engine/ui/Slider.lua index a32d92cc2424ad7942cde4256360e39b1659b108..632d3e92e99020d8125ae7a85eb168ccd21cc7c9 100644 --- a/game/engines/default/engine/ui/Slider.lua +++ b/game/engines/default/engine/ui/Slider.lua @@ -24,10 +24,11 @@ local Base = require "engine.ui.Base" module(..., package.seeall, class.inherit(Base)) function _M:init(t) - self.size = assert(t.size, "no slider size") + self.h = assert(t.size, "no slider size") self.max = assert(t.max, "no slider max") self.pos = t.pos or 0 self.inverse = t.inverse + self.pos = util.minBound(self.pos, 0, self.max) Base.init(self, t) end @@ -37,18 +38,19 @@ function _M:generate() self.middle = self:getUITexture("ui/scrollbar.png") self.bottom = self:getUITexture("ui/scrollbar_bottom.png") self.sel = self:getUITexture("ui/scrollbar-sel.png") - self.w, self.h = self.middle.w, self.size + self.w = self.middle.w + self.pos = util.minBound(self.pos, 0, self.max) end function _M:display(x, y) self.top.t:toScreenFull(x, y, self.top.w, self.top.h, self.top.tw, self.top.th) self.bottom.t:toScreenFull(x, y + self.h - self.bottom.h, self.bottom.w, self.bottom.h, self.bottom.tw, self.bottom.th) self.middle.t:toScreenFull(x, y + self.top.h, self.middle.w, self.h - self.top.h - self.bottom.h, self.middle.tw, self.middle.th) - self.pos = util.bound(self.pos, 0, self.max) + self.pos = util.minBound(self.pos, 0, self.max) if self.inverse then - y = y + self.h - (self.pos * self.size / self.max) + self.sel.h / 2 + y = y + self.h - (self.pos / self.max) * (self.h - self.bottom.h - self.top.h - self.sel.h * 0.5) + self.sel.h * 0.5 else - y = y + (self.pos * self.size / self.max) + self.sel.h / 2 + y = y + (self.pos / self.max) * (self.h - self.bottom.h - self.top.h - self.sel.h * 0.5) + self.sel.h * 0.5 end - self.sel.t:toScreenFull(x - (self.sel.w - self.top.w) / 2, y, self.sel.w, self.sel.h, self.sel.tw, self.sel.th) + self.sel.t:toScreenFull(x - (self.sel.w - self.top.w) * 0.5, y, self.sel.w, self.sel.h, self.sel.tw, self.sel.th) end diff --git a/game/engines/default/engine/ui/Textzone.lua b/game/engines/default/engine/ui/Textzone.lua index 69a25cf460e0fa05c5b90ec36ac4295c4a4d3f9d..ac8911c6c5a7dcc6a441b32910ef494aef762f37 100644 --- a/game/engines/default/engine/ui/Textzone.lua +++ b/game/engines/default/engine/ui/Textzone.lua @@ -27,17 +27,21 @@ module(..., package.seeall, class.inherit(Base, Focusable)) function _M:init(t) self.text = tostring(assert(t.text, "no textzone text")) + + if t.auto_height then t.height = 1 end if t.auto_width then t.width = 1 end + self.w = assert(t.width, "no list width") - if t.auto_height then t.height = 1 end self.h = assert(t.height, "no list height") self.scrollbar = t.scrollbar - self.no_color_bleed = t.no_color_bleed self.auto_height = t.auto_height self.auto_width = t.auto_width + + self.dest_area = t.dest_area and t.dest_area or { h = self.h } + self.color = t.color or {r=255, g=255, b=255} - - if self.auto_width then self.w = 10000 end + self.can_focus = false + self.scroll_inertia = 0 Base.init(self, t) end @@ -45,65 +49,118 @@ end function _M:generate() self.mouse:reset() self.key:reset() - - local text, max_lines, max_w = self.font:draw(self.text:toString(), self.w, self.color.r, self.color.g, self.color.b) - if self.auto_width then - self.w = max_w + + if self.scrollbar then + self.can_focus = true + self.scrollbar = Slider.new{size=self.h, max=1} end - self.scroll = 1 - self.max = max_lines - - local fw, fh = self.w, self.font_h - self.fw, self.fh = fw, fh - if self.auto_height then self.h = self.fh * max_lines end + local gen, max_lines, max_w = self.font:draw(self.text, self.auto_width and self.text:toTString():maxWidth(self.font) or (self.scrollbar and self.w - self.scrollbar.w or self.w), self.color.r, self.color.g, self.color.b) + if self.auto_width then self.w = max_w end + + self.max = max_lines - self.max_display = math.floor(self.h / self.fh) - self.can_focus = false - if self.scrollbar and (self.max_display < self.max) then - self.can_focus = true + if self.auto_height then + self.h = self.font_h * max_lines + self.dest_area.h = self.h end - -- Draw the list items - self.list = text + self.max_display = max_lines * self.font_h + self.list = gen - -- Draw the scrollbar - if self.scrollbar then - self.scrollbar = Slider.new{size=self.h - fh, max=self.max - self.max_display + 1} - end + if self.scrollbar then self.scrollbar.max=self.max_display - self.h end -- Add UI controls self.mouse:registerZone(0, 0, self.w, self.h, function(button, x, y, xrel, yrel, bx, by, event) if button == "wheelup" and event == "button" then self.key:triggerVirtual("MOVE_UP") elseif button == "wheeldown" and event == "button" then self.key:triggerVirtual("MOVE_DOWN") end + if button == "middle" and self.scrollbar then + if not self.scroll_drag then + self.scroll_drag = true + self.scroll_drag_x_start = bx + self.scroll_drag_y_start = by + else + self.scrollbar.pos = util.minBound(self.scrollbar.pos + by - self.scroll_drag_y_start, 0, self.scrollbar.max) + self.scroll_drag_x_start = bx + self.scroll_drag_y_start = by + end + else + self.scroll_drag = false + end end) + self.key:addBinds{ - MOVE_UP = function() self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1) end, - MOVE_DOWN = function() self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end, + MOVE_UP = function() if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 10 end end, + MOVE_DOWN = function() if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 10 end end, + } + + self.key:addCommands{ + _HOME = function() if self.scrollbar then self.scrollbar.pos = 0 end end, + _END = function() if self.scrollbar then self.scrollbar.pos = self.scrollbar.max end end, + _PAGEUP = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos - self.h, 0, self.scrollbar.max) end end, + _PAGEDOWN = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.h, 0, self.scrollbar.max) end end, } end -function _M:spawn(t) - local n = self:cloneFull() - for k, e in pairs(t) do n[k] = e end - n:generate() - return n -end - -function _M:display(x, y) - local bx, by = x, y - local max = math.min(self.scroll + self.max_display - 1, self.max) - for i = self.scroll, max do +function _M:display(x, y, nb_keyframes, screen_x, screen_y, offset_x, offset_y, local_x, local_y) + if not self.list then return end + offset_x = offset_x and offset_x or 0 + offset_y = (offset_y and offset_y) or (self.scrollbar and self.scrollbar.pos or 0) + local_x = local_x and local_x or 0 + local_y = local_y and local_y or 0 + + local loffset_y = offset_y - local_y + local current_y = 0 + local current_x = 0 + local total_h = 0 + local clip_y_start = 0 + local clip_y_end = 0 + + if self.scrollbar then + self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.scroll_inertia, 0, self.scrollbar.max) + if self.scroll_inertia > 0 then self.scroll_inertia = math.max(self.scroll_inertia - 1, 0) + elseif self.scroll_inertia < 0 then self.scroll_inertia = math.min(self.scroll_inertia + 1, 0) + end + if self.scrollbar.pos == 0 or self.scrollbar.pos == self.scrollbar.max then self.scroll_inertia = 0 end + end + + local scroll_w = 0 + if self.focused and self.scrollbar and self.h < self.max_display then + scroll_w = self.w + end + + for i = 1, #self.list do local item = self.list[i] - if not item then break end - if self.text_shadow then item._tex:toScreenFull(x+1, y+1, item.w, item.h, item._tex_w, item._tex_h, 0, 0, 0, self.text_shadow) end - item._tex:toScreenFull(x, y, item.w, item.h, item._tex_w, item._tex_h) - y = y + self.fh + clip_y_start = 0 + clip_y_end = 0 + + -- if item is within visible area bounds + if total_h + item.h > loffset_y and total_h < loffset_y + self.dest_area.h then + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + item.h > loffset_y + self.dest_area.h then + clip_y_end = total_h + item.h - (loffset_y + self.dest_area.h) + end + if item.background then + core.display.drawQuad(x + current_x, y + current_y, item._tex_w, item.h - (clip_y_start + clip_y_end), item.background[1], item.background[2], item.background[3], item.background[4]) + end + + local one_by_tex_h = 1 / item._tex_h + if self.text_shadow then item._tex:toScreenPrecise(x + current_x + 1, y + current_y + 1, item.w, item.h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item.h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end + item._tex:toScreenPrecise(x + current_x, y + current_y, item.w, item.h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item.h - clip_y_end) * one_by_tex_h ) + -- add only visible part of item + current_y = current_y + item.h - clip_y_start + end + -- add full size of item + total_h = total_h + item.h + -- if we are too deep then end this + if total_h > loffset_y + self.dest_area.h then break end end - - if self.focused and self.scrollbar then - self.scrollbar.pos = self.scroll - self.scrollbar:display(bx + self.w - self.scrollbar.w, by) + if self.focused and self.scrollbar and self.h < self.max_display then + self.scrollbar:display(x + self.w - self.scrollbar.w, y) end end diff --git a/game/engines/default/engine/ui/TextzoneList.lua b/game/engines/default/engine/ui/TextzoneList.lua index c8ba182a5ec1e267ca2bc54cb95fa24e723a5b06..9711463f70526be8f51a9db5fb41deee25eff36b 100644 --- a/game/engines/default/engine/ui/TextzoneList.lua +++ b/game/engines/default/engine/ui/TextzoneList.lua @@ -31,21 +31,26 @@ function _M:init(t) if t.weakstore then setmetatable(self.items, {__mode="k"}) end self.cur_item = 0 self.w = assert(t.width, "no list width") - if t.auto_height then t.height = 1 end self.h = assert(t.height, "no list height") self.scrollbar = t.scrollbar - self.no_color_bleed = t.no_color_bleed self.variable_height = t.variable_height - - if self.scrollbar then - self.can_focus = true - end + + self.dest_area = t.dest_area and t.dest_area or { h = self.h } + self.max_h = 0 + self.scroll_inertia = 0 + + if self.scrollbar then self.can_focus = true end Base.init(self, t) self.sep = Separator.new{dir="vertical", size=self.w, ui=self.ui} end +function _M:erase() + self.surface:erase(0,0,0,0) + self.surface:updateTexture(self.texture) +end + function _M:generate() self.mouse:reset() self.key:reset() @@ -54,29 +59,53 @@ function _M:generate() -- Draw the scrollbar if self.scrollbar then - self.scrollbar = Slider.new{size=self.h - self.font_h, max=1} + self.scrollbar = Slider.new{size=self.h, max=1} end -- Add UI controls - self.mouse:registerZone(0, 0, self.w, self.h, function(button, x, y, xrel, yrel, bx, by, event) + local on_mousewheel = function(button, x, y, xrel, yrel, bx, by, event) if button == "wheelup" and event == "button" then self.key:triggerVirtual("MOVE_UP") elseif button == "wheeldown" and event == "button" then self.key:triggerVirtual("MOVE_DOWN") end - end) + if button == "middle" and self.scrollbar then + if not self.scroll_drag then + self.scroll_drag = true + self.scroll_drag_x_start = bx + self.scroll_drag_y_start = by + else + self.scrollbar.pos = util.minBound(self.scrollbar.pos + by - self.scroll_drag_y_start, 0, self.scrollbar.max) + self.scroll_drag_x_start = bx + self.scroll_drag_y_start = by + end + else + self.scroll_drag = false + end + end + + self.mouse:registerZone(0, 0, self.w, self.h, on_mousewheel) self.key:addBinds{ - MOVE_UP = function() if self.scroll then self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1) end end, - MOVE_DOWN = function() if self.scroll then self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end end, + MOVE_UP = function() if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 10 end end, + MOVE_DOWN = function() if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 10 end end + } + + self.key:addCommands{ + _HOME = function() if self.scrollbar then self.scrollbar.pos = 0 end end, + _END = function() if self.scrollbar then self.scrollbar.pos = self.scrollbar.max end end, + _PAGEUP = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos - self.h, 0, self.scrollbar.max) end end, + _PAGEDOWN = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.h, 0, self.scrollbar.max) end end, } end function _M:createItem(item, text) - local old_style = self.font:getStyle() - - local max_display = math.floor(self.h / self.fh) - + local gen = {} -- Draw the list items - local gen = self.font:draw(text:toString(), self.fw, 255, 255, 255) + if self.scrollbar then + gen = self.font:draw(text:toString(), self.fw - self.scrollbar.w , 255, 255, 255) + else + gen = self.font:draw(text:toString(), self.fw, 255, 255, 255) -- bug with UIDs displaying + end + self.max_h = 0 for i = 1, #gen do if gen[i].line_extra then if gen[i].line_extra:sub(1, 7) == "linebg:" then @@ -90,31 +119,33 @@ function _M:createItem(item, text) end end end + if gen[i].is_separator then + self.max_h = self.max_h + (self.fh - self.sep.h) + else + self.max_h = self.max_h + gen[i].h + end end local max = #gen if self.variable_height then - self.h = max * self.fh - max_display = max + self.h = self.max_h + if not self.dest_area.fixed then self.dest_area.h = self.max_h end end - self.items[item] = { - list = gen, - scroll = 1, - max = max, - max_display = max_display, - } - self.font:setStyle(old_style) + self.items[item] = { list = gen, max_h = self.max_h } end -function _M:switchItem(item, create_if_needed) - self.cur_item = item - if create_if_needed then if not self.items[item] then self:createItem(item, create_if_needed) end end +function _M:switchItem(item, create_if_needed, force) + if self.cur_item == item and not force then return true end + if create_if_needed and not self.items[item] then self:createItem(item, create_if_needed) end if not item or not self.items[item] then self.list = nil return false end local d = self.items[item] - - self.scroll = d.scroll + + self.max_h = d.max_h + if self.scrollbar then + self.scrollbar.max = self.max_h - self.h + self.scrollbar.pos = 0 + end self.list = d.list - self.max = d.max self.max_display = d.max_display self.cur_item = item return true @@ -125,31 +156,67 @@ function _M:erase() self.items = {} end -function _M:display(x, y) +--@param x, y - x, y position of displaying +--@param nb_keyframes - +--@param ox, oy - +--@param offset_x, offset_y - offset values of UI element relative to its parent +--@param local_x, local_y - local starting values of UI element relative to its parent +function _M:display(x, y, nb_keyframes, ox, oy, offset_x, offset_y, local_x, local_y) if not self.list then return end - - local bx, by = x, y - local max = math.min(self.scroll + self.max_display - 1, self.max) - for i = self.scroll, max do - local item = self.list[i] - if not item then break end - - if item.background then - core.display.drawQuad(x, y, self.fw, self.fh, item.background[1], item.background[2], item.background[3], item.background[4]) + offset_x = offset_x and offset_x or 0 + offset_y = offset_y and offset_y or (self.scrollbar and self.scrollbar.pos or 0) + local_x = local_x and local_x or 0 + local_y = local_y and local_y or 0 + + if self.scrollbar then + self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.scroll_inertia, 0, self.scrollbar.max) + if self.scroll_inertia > 0 then self.scroll_inertia = math.max(self.scroll_inertia - 1, 0) + elseif self.scroll_inertia < 0 then self.scroll_inertia = math.min(self.scroll_inertia + 1, 0) end - - if item.is_separator then - self.sep:display(x, y + (self.fh - self.sep.h) / 2) - else - if self.text_shadow then item._tex:toScreenFull(x+1, y+1, item.w, item.h, item._tex_w, item._tex_h, 0, 0, 0, self.text_shadow) end - item._tex:toScreenFull(x, y, item.w, item.h, item._tex_w, item._tex_h) + if self.scrollbar.pos == 0 or self.scrollbar.pos == self.scrollbar.max then self.scroll_inertia = 0 end + end + + local loffset_y = offset_y - local_y + local current_y = 0 + local current_x = 0 + local total_h = 0 + local clip_y_start = 0 + local clip_y_end = 0 + for i = 1, #self.list do + local item = self.list[i] + clip_y_start = 0 + clip_y_end = 0 + + local item_h = item.is_separator and (self.fh - self.sep.h) or item.h + -- if item is within visible area bounds + if total_h + item_h > loffset_y and total_h < loffset_y + self.dest_area.h then + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + item_h > loffset_y + self.dest_area.h then + clip_y_end = total_h + item_h - (loffset_y + self.dest_area.h) + end + if item.background then + core.display.drawQuad(x + current_x, y + current_y, item._tex_w, item_h - (clip_y_start + clip_y_end), item.background[1], item.background[2], item.background[3], item.background[4]) + end + if item.is_separator then + self.sep:display(x + current_x, y + current_y + (self.fh - self.sep.h) * 0.5 - clip_y_start, nb_keyframes, ox, oy, 0, total_h + (self.fh - self.sep.h) * 0.5, 0, loffset_y, self.dest_area) + else + local one_by_tex_h = 1 / item._tex_h + if self.text_shadow then item._tex:toScreenPrecise(x + current_x + 1, y + current_y + 1, item.w, item_h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item_h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end + item._tex:toScreenPrecise(x + current_x, y + current_y, item.w, item_h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item_h - clip_y_end) * one_by_tex_h ) + end + -- add only visible part of item + current_y = current_y + item_h - clip_y_start end - y = y + self.fh + -- add full size of item + total_h = total_h + item_h + -- if we are too deep then end this + if total_h > loffset_y + self.dest_area.h then break end end - - if self.focused and self.scrollbar then - self.scrollbar.pos = self.scroll - self.scrollbar.pos = self.max - self.max_display + 1 - self.scrollbar:display(bx + self.w - self.scrollbar.w, by) + if self.focused and self.scrollbar and self.h < self.max_h then + self.scrollbar:display(x + self.w - self.scrollbar.w, y) end end diff --git a/game/engines/default/engine/ui/UIContainer.lua b/game/engines/default/engine/ui/UIContainer.lua new file mode 100644 index 0000000000000000000000000000000000000000..f6610c5b83620cb29a550ce1ebde3924f22834b1 --- /dev/null +++ b/game/engines/default/engine/ui/UIContainer.lua @@ -0,0 +1,114 @@ +-- TE4 - T-Engine 4 +-- Copyright (C) 2009, 2010, 2011 Nicolas Casalini +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see <http://www.gnu.org/licenses/>. +-- +-- Nicolas Casalini "DarkGod" +-- darkgod@te4.org + +require "engine.class" +local Base = require "engine.ui.Base" +local Focusable = require "engine.ui.Focusable" +local Slider = require "engine.ui.Slider" + +--- A generic UI list +module(..., package.seeall, class.inherit(Base, Focusable)) + +function _M:init(t) + self.w = assert(t.width, "no container width") + self.h = assert(t.height, "no container height") + self.dest_area = t.dest_area or { h = self.h } + + self:erase() + + self.scrollbar = Slider.new{size=self.h, max=0} + self.uis_h = 0 + + Base.init(self, t) +end + +function _M:generate() + self.mouse:reset() + self.key:reset() + + -- Add UI controls + local on_mousewheel = function(button, x, y, xrel, yrel, bx, by, event) + if button == "wheelup" and event == "button" then self.key:triggerVirtual("MOVE_UP") + elseif button == "wheeldown" and event == "button" then self.key:triggerVirtual("MOVE_DOWN") + end + end + + self.mouse:registerZone(0, 0, self.w, self.h, on_mousewheel) + + self.key:addBinds{ + MOVE_UP = function() if self.scrollbar.pos and self.uis_h > self.h then self.scrollbar.pos = util.bound(self.scrollbar.pos - 1, 0, self.scrollbar.max) end end, + MOVE_DOWN = function() if self.scrollbar.pos and self.uis_h > self.h then self.scrollbar.pos = util.bound(self.scrollbar.pos + 1, 0, self.scrollbar.max) end end, + } +end + +function _M:erase() + self.uis = {} +end + +function _M:changeUI(uis) + local max_h = 0 + self.uis = uis + for i=1, #self.uis do + max_h = max_h + self.uis[i].h + end + self.uis_h = max_h + self.scrollbar.max = max_h - self.h + if not self.dest_area.fixed then self.dest_area.h = max_h end +end + +function _M:resize(w, h, dest_w, dest_h) + self.w = w + self.h = h + self.dest_area.w = dest_w + self.dest_area.h = dest_h + self.scrollbar.max = self.uis_h - self.h + self.scrollbar.pos = util.minBound(self.scrollbar.pos, 0, self.scrollbar.max) + self.scrollbar.h = dest_h +end + +function _M:display(x, y, nb_keyframes, screen_x, screen_y, offset_x, offset_y, local_x, local_y) + local_x = local_x and local_x or 0 + local_y = local_y and local_y or 0 + + offset_x = offset_x and offset_x or 0 + offset_y = offset_y and offset_y or (self.scrollbar and self.scrollbar.pos or 0) + + local current_y = y + local prev_loffset = 0 + local total_h = 0 + local ui + local first = true + for i=1, #self.uis do + ui = self.uis[i] + ui.dest_area.h = self.dest_area.h + if offset_y <= total_h + self.uis[i].h then + ui:display(x, current_y, nb_keyframes, x, current_y, offset_x, offset_y, local_x, local_y) + current_y = current_y + self.uis[i].h + if total_h < offset_y then current_y = current_y + local_y - offset_y end + end + + local_y = local_y + self.uis[i].h + total_h = total_h + self.uis[i].h + if total_h > offset_y + self.h then break end + end + + if self.focused and self.uis_h > self.h then + self.scrollbar:display(x + self.w - self.scrollbar.w, y) + end +end diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua index 8a5419a424ac3e67dbf2d08104010038bb7940b5..32b3d1f335f3daeb7b7099ff8c54accbf194ad48 100644 --- a/game/engines/default/engine/utils.lua +++ b/game/engines/default/engine/utils.lua @@ -227,10 +227,10 @@ function table.readonly(src) end end return setmetatable(src, { - __newindex = function(src, key, value) - error("Attempt to modify read-only table") - end, - __metatable = false + __newindex = function(src, key, value) + error("Attempt to modify read-only table") + end, + __metatable = false }); end @@ -725,15 +725,19 @@ end function tstring:maxWidth(font) local max_w = 0 + local old_style = font:getStyle() local line_max = 0 local v - local w = game.level.map.tiles.w * 0.5 + local w, h = font:size("") for i = 1, #self do v = self[i] - if type(v) == "string" then line_max = line_max + font:size(v) - elseif type(v) == "table" then if v[1] == "uid" then line_max = line_max + w end + if type(v) == "string" then line_max = line_max + font:size(v) + 1 + elseif type(v) == "table" then if v[1] == "uid" then line_max = line_max + h -- UID surface is same as font size + elseif v[1] == "font" and v[2] == "bold" then font:setStyle("bold") + elseif v[1] == "font" and v[2] == "normal" then font:setStyle("normal") end elseif type(v) == "boolean" then max_w = math.max(max_w, line_max) line_max = 0 end end + font:setStyle(old_style) max_w = math.max(max_w, line_max) return max_w end @@ -1364,11 +1368,17 @@ function util.boundWrap(i, min, max) elseif i > max then i = min end return i end + function util.bound(i, min, max) if min and i < min then i = min elseif max and i > max then i = max end return i end + +function util.minBound(i, min, max) + return math.max(math.min(max, i), min) +end + function util.scroll(sel, scroll, max) if sel > scroll + max - 1 then scroll = sel - max + 1 end if sel < scroll then scroll = sel end diff --git a/game/engines/default/modules/boot/dialogs/Addons.lua b/game/engines/default/modules/boot/dialogs/Addons.lua index 714031c064e5e2db87089e4aadce7d0d777c8a89..b0102bbb5afad53a753c2c73a4c17b9e257e2ad9 100644 --- a/game/engines/default/modules/boot/dialogs/Addons.lua +++ b/game/engines/default/modules/boot/dialogs/Addons.lua @@ -36,8 +36,8 @@ function _M:init() self:generateList() self.c_list = ListColumns.new{width=math.floor(self.iw / 3 - 10), height=self.ih - 10 - self.c_compat.h, scrollbar=true, columns={ - {name="Game Module", width=80, display_prop="name"}, - {name="Version", width=20, display_prop="version_txt"}, + {name="Game Module", width=75, display_prop="name"}, + {name="Version", width=25, display_prop="version_txt"}, }, list=self.list, fct=function(item) end, select=function(item, sel) self:select(item) end} self.c_adds = ListColumns.new{width=math.floor(self.iw * 2 / 3 - 10), height=self.ih - 10 - self.c_compat.h, scrollbar=true, columns={ @@ -70,8 +70,7 @@ end function _M:select(item) if item and item.adds and self.c_adds then - self.c_adds.list = item.adds - self.c_adds:generate() + self.c_adds:setList(item.adds) end end @@ -82,7 +81,7 @@ function _M:switchAddon(item) elseif v == true then config.settings.addons[item.for_module][item.short_name] = false elseif v == false then config.settings.addons[item.for_module][item.short_name] = nil end - self.c_adds:drawItem(item) + --self.c_adds:drawItem(item) local lines = {} lines[#lines+1] = ("addons = {}"):format(w) diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index e3a0fe7032bd70e1c7d5ee340827ebb70f03b3c1..4bb02641302d6176c7edc92520e75f5c6f489eaf 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -1153,17 +1153,17 @@ function _M:display(nb_keyframes) engine.GameTurnBased.display(self, nb_keyframes) - -- Tooltip is displayed over all else, even dialogs + -- Tooltip is displayed over all else, even dialogs but before FBO local mx, my, button = core.mouse.get() self.old_ctrl_state = self.ctrl_state self.ctrl_state = core.key.modState("ctrl") - if self.tooltip.w and mx > self.w - self.tooltip.w and my > Tooltip:tooltip_bound_y2() - self.tooltip.h then - self:targetDisplayTooltip(Map.display_x, self.h, self.old_ctrl_state~=self.ctrl_state ) + -- if tooltip is in way of mouse and its not locked then move it + if self.tooltip.w and mx > self.w - self.tooltip.w and my > Tooltip:tooltip_bound_y2() - self.tooltip.h and not self.tooltip.locked then + self:targetDisplayTooltip(Map.display_x, self.h, self.old_ctrl_state~=self.ctrl_state, nb_keyframes ) else - self:targetDisplayTooltip(self.w, self.h, self.old_ctrl_state~=self.ctrl_state ) - + self:targetDisplayTooltip(self.w, self.h, self.old_ctrl_state~=self.ctrl_state, nb_keyframes ) end if self.full_fbo then @@ -1232,6 +1232,18 @@ do return end self.player:grantQuest("love-melinda") self.player:hasQuest("love-melinda"):melindaCompanion(self.player, "Defiler", "Corruptor") end end, + [{"_UP","ctrl"}] = function() + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max) + end, + [{"_DOWN","ctrl"}] = function() + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max) + end, + [{"_HOME","ctrl"}] = function() + game.tooltip.container.scrollbar.pos = 0 + end, + [{"_END","ctrl"}] = function() + game.tooltip.container.scrollbar.pos = game.tooltip.container.scrollbar.max + end, } self.key.any_key = function(sym) @@ -1464,6 +1476,11 @@ do return end HELP = "EXIT", EXIT = function() + if self.tooltip.locked then + self.tooltip.locked = false + self.tooltip.container.focused = self.tooltip.locked + game.log("Tooltip %s", self.tooltip.locked and "locked" or "unlocked") + end local l = { "resume", "achievements", @@ -1514,6 +1531,22 @@ do return end local ok, err = coroutine.resume(co) if not ok and err then print(debug.traceback(co)) error(err) end end, + + LOCK_TOOLTIP = function() + if not self.tooltip.empty then + self.tooltip.locked = not self.tooltip.locked + self.tooltip.container.focused = self.tooltip.locked + game.log("Tooltip %s", self.tooltip.locked and "locked" or "unlocked") + end + end, + + LOCK_TOOLTIP_COMPARE = function() + if not self.tooltip.empty then + self.tooltip.locked = not self.tooltip.locked + self.tooltip.container.focused = self.tooltip.locked + game.log("Tooltip %s", self.tooltip.locked and "locked" or "unlocked") + end + end, SHOW_MAP = function() game:registerDialog(require("mod.dialogs.ShowMap").new()) @@ -1549,6 +1582,27 @@ function _M:setupMouse(reset) self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel, bx, by, event, extra) self.tooltip.add_map_str = extra and extra.log_str + if game.tooltip.locked then + if button == "wheelup" and event == "button" then + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max) + elseif button == "wheeldown" and event == "button" then + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max) + end + if button == "middle" then + if not game.tooltip.container.draging then + game.tooltip.container.draging = true + game.tooltip.container.drag_x_start = mx + game.tooltip.container.drag_y_start = my + else + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + my - game.tooltip.container.drag_y_start, 0, game.tooltip.container.scrollbar.max) + game.tooltip.container.drag_x_start = mx + game.tooltip.container.drag_y_start = my + end + else + game.tooltip.container.draging = false + end + end + -- Handle targeting if self:targetMouse(button, mx, my, xrel, yrel, event) then return end diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua index 1a55566ca752c9824a35e895a42ed4caccc1ae54..2cd9b5e1c45d5098970f6580f2ad16a27ed9a742 100644 --- a/game/modules/tome/class/Object.lua +++ b/game/modules/tome/class/Object.lua @@ -151,7 +151,7 @@ function _M:descAttribute(attr) return c.dam.."-"..(c.dam*(c.damrange or 1.1)).." power, "..(c.apr or 0).." apr, "..DamageType:get(c.damtype).name.." damage" elseif attr == "SHIELD" then local c = self.special_combat - if c and (game.player:knowTalentType("technique/shield-offense") or game.player:knowTalentType("technique/shield-defense")) or game.player:attr("show_shield_combat") then + if c and (game.player:knowTalentType("technique/shield-offense") or game.player:knowTalentType("technique/shield-defense") or game.player:attr("show_shield_combat")) then return c.dam.." dam, "..c.block.." block" else return c.block.." block" @@ -468,7 +468,7 @@ function _M:getTextualDesc(compare_with) compare_fields(combat, compare_with, field, "physcrit", "%+.1f%%", "Physical crit. chance: ", 1, false, false, add_table) compare_fields(combat, compare_with, field, "physspeed", "%.0f%%", "Attack speed: ", 100, false, true, add_table) - compare_fields(combat, compare_with, field, "block", "%+d", "Block value: ", 1, false, false, add_table) + compare_fields(combat, compare_with, field, "block", "%+d", "Block value: ", 1, false, true, add_table) compare_fields(combat, compare_with, field, "range", "%+d", "Firing range: ", 1, false, false, add_table) compare_fields(combat, compare_with, field, "capacity", "%d", "Capacity: ", 1, false, false, add_table) @@ -1173,19 +1173,9 @@ function _M:getDesc(name_param, compare_with, never_compare) name_param = name_param or {} name_param.do_color = true compare_with = compare_with or {} - if not self:isIdentified() then - desc:merge(self:getName(name_param):toTString()) - desc:add({"color", "WHITE"}, true) - else - desc:merge(self:getName(name_param):toTString()) - desc:add({"color", "WHITE"}, true) - desc:add(true) - desc:add({"color", "ANTIQUE_WHITE"}) - desc:merge(self.desc:toTString()) - desc:add(true, true) - desc:add({"color", "WHITE"}) - end + desc:merge(self:getName(name_param):toTString()) + desc:add({"color", "WHITE"}, true) local reqs = self:getRequirementDesc(game.player) if reqs then desc:merge(reqs) @@ -1204,20 +1194,24 @@ function _M:getDesc(name_param, compare_with, never_compare) desc:add({"color",0x67,0xAD,0x00}, ("%0.2f Encumbrance."):format(self.encumber), {"color", "LAST"}) end if self.ego_bonus_mult then - desc:add(true) - desc:add({"color",0x67,0xAD,0x00}, ("%0.2f Ego Multiplier."):format(1 + self.ego_bonus_mult), {"color", "LAST"}) + desc:add(true, {"color",0x67,0xAD,0x00}, ("%0.2f Ego Multiplier."):format(1 + self.ego_bonus_mult), {"color", "LAST"}) end - desc:add(true, true) - local could_compare = false if not name_param.force_compare and not core.key.modState("ctrl") then if compare_with[1] then could_compare = true end compare_with = {} end + desc:add(true, true) desc:merge(self:getTextualDesc(compare_with)) + if self:isIdentified() then + desc:add(true, true, {"color", "ANTIQUE_WHITE"}) + desc:merge(self.desc:toTString()) + desc:add({"color", "WHITE"}) + end + if could_compare and not never_compare then desc:add(true, {"font","italic"}, {"color","GOLD"}, "Press <control> to compare", {"color","LAST"}, {"font","normal"}) end return desc diff --git a/game/modules/tome/class/PartyMember.lua b/game/modules/tome/class/PartyMember.lua index 1d199229e286ee14c3aa1fa008de6eb648160947..e2c3a027130c15d88705d0d3c8c7660ebdc73fb0 100644 --- a/game/modules/tome/class/PartyMember.lua +++ b/game/modules/tome/class/PartyMember.lua @@ -41,7 +41,7 @@ function _M:tooltip(x, y, seen_by) true, {"color", "TEAL"}, ("Behavior: %s"):format(self.ai_tactic.type or "default"), true, - ("Action radius: %d"):format(self.ai_state.tactic_leash), true, + ("Action radius: %d"):format(self.ai_state.tactic_leash), {"color", "WHITE"} ) return str diff --git a/game/modules/tome/class/Tooltip.lua b/game/modules/tome/class/Tooltip.lua index e00f2977ddb6ad243b3684bc403874d105b9eca0..3bfed72cb8bcffd98bc3729d50a4f5e46e2b6898 100644 --- a/game/modules/tome/class/Tooltip.lua +++ b/game/modules/tome/class/Tooltip.lua @@ -25,12 +25,9 @@ module(..., package.seeall, class.inherit(Tooltip)) tooltip_bound_y2 = function() return game.uiset.map_h_stop_tooltip end -function _M:init(...) - Tooltip.init(self, ...) -end - --- Gets the tooltips at the given map coord function _M:getTooltipAtMap(tmx, tmy, mx, my) + if self.locked then return nil end local tt = {} local seen = game.level.map.seens(tmx, tmy) local remember = game.level.map.remembers(tmx, tmy) @@ -40,25 +37,26 @@ function _M:getTooltipAtMap(tmx, tmy, mx, my) local to_add = game.level.map:checkEntity(tmx, tmy, check_type, "tooltip", game.level.map.actor_player) if to_add then if type(to_add) == "string" then to_add = to_add:toTString() end - tt[#tt+1] = to_add + if to_add.is_tstring then + tt[#tt+1] = to_add + else + table.append(tt, to_add) + end end + return to_add end - if seen and not ctrl_state then - check(Map.PROJECTILE) + if seen or remember and not ctrl_state then + check(Map.TRAP) check(Map.ACTOR) - end - if seen or remember then - local obj = check(Map.OBJECT) - if not ctrl_state or not obj then - check(Map.TRAP) - check(Map.TERRAIN) - end + check(Map.OBJECT) + check(Map.PROJECTILE) + check(Map.TERRAIN) end if #tt > 0 then return tt end - if self.add_map_str then return self.add_map_str:toTString() end + if self.add_map_str then return self.add_map_str end return nil -end +end \ No newline at end of file diff --git a/game/modules/tome/class/uiset/Minimalist.lua b/game/modules/tome/class/uiset/Minimalist.lua index 2e4a3e97fd0c03a13b33a888399f586fdc2a7ef7..33e657fd3acf51438bf10c9ca4fcaee3fd5686a5 100644 --- a/game/modules/tome/class/uiset/Minimalist.lua +++ b/game/modules/tome/class/uiset/Minimalist.lua @@ -1846,17 +1846,17 @@ function _M:setupMouse(mouse) local mx, my = core.mouse.get() if not item or not sub_es or #sub_es == 0 then game.mouse:delegate(button, mx, my, xrel, yrel, nil, nil, event, "playmap") return end - local str = tstring{} + local tooltips = {} for i, e in ipairs(sub_es) do if e.tooltip then - str:merge(e:tooltip():toTString()) - if i < #sub_es then str:add(true, "---", true) - else str:add(true) end + table.append(tooltips, e:tooltip()) + if i < #sub_es then table.append(tooltips, { tstring{ true, "---" } } ) + else table.append(tooltips, { tstring{ true } } ) end end end local extra = {} - extra.log_str = str + extra.log_str = tooltips game.tooltip.old_ttmx = -100 game.mouse:delegate(button, mx, my, xrel, yrel, nil, nil, event, "playmap", extra) end) diff --git a/game/modules/tome/data/keybinds/tome.lua b/game/modules/tome/data/keybinds/tome.lua index 53e5a8833d6eed34bc1cd22f4da68ff923a9c49f..e51a801e475c6e867394669fc4a5f261ddf92bbe 100644 --- a/game/modules/tome/data/keybinds/tome.lua +++ b/game/modules/tome/data/keybinds/tome.lua @@ -205,3 +205,17 @@ defineAction{ group = "interface", name = "Toggle UI display", } + +defineAction{ + default = { "sym:_l:false:true:false:false" }, + type = "LOCK_TOOLTIP", + group = "interface", + name = "Locks tooltip in place", +} + +defineAction{ + default = { "sym:_l:true:true:false:false" }, + type = "LOCK_TOOLTIP_COMPARE", + group = "interface", + name = "Locks tooltip in place while comparing items", +} \ No newline at end of file diff --git a/game/modules/tome/dialogs/LevelupDialog.lua b/game/modules/tome/dialogs/LevelupDialog.lua index b0598c33ee6b05cb5885cf1ee1ed2b89c17fd537..78cf29f747a659eb6a71a171fc684e2c95ad29c3 100644 --- a/game/modules/tome/dialogs/LevelupDialog.lua +++ b/game/modules/tome/dialogs/LevelupDialog.lua @@ -24,6 +24,7 @@ local Dialog = require "engine.ui.Dialog" local Button = require "engine.ui.Button" local Textzone = require "engine.ui.Textzone" local TextzoneList = require "engine.ui.TextzoneList" +local UIContainer = require "engine.ui.UIContainer" local TalentTrees = require "mod.dialogs.elements.TalentTrees" local Separator = require "engine.ui.Separator" local DamageType = require "engine.DamageType" @@ -646,10 +647,7 @@ function _M:createDisplay() if self.no_tooltip then local vsep3 = Separator.new{dir="horizontal", size=self.ih - 20} - self.c_desc = TextzoneList.new{ - width=self.iw - 200 - 530 - 40, height = self.ih, - scrollbar = true, - } + self.c_desc = TextzoneList.new{ width=self.iw - 200 - 530 - 40, height = self.ih, dest_area = { h = self.ih } } ret[#ret+1] = {right=0, top=0, ui=self.c_desc} ret[#ret+1] = {right=self.c_desc.w, top=0, ui=vsep3} end diff --git a/game/modules/tome/dialogs/LorePopup.lua b/game/modules/tome/dialogs/LorePopup.lua index 82e24b77a82995aa46a20333e0ef16150cee17b1..f3a1931271065d974f44499e635df1c44054a264 100644 --- a/game/modules/tome/dialogs/LorePopup.lua +++ b/game/modules/tome/dialogs/LorePopup.lua @@ -73,10 +73,18 @@ function _M:init(l, w, force_height) {left = 3, top = 3 + image.h, ui=c_text}, } end + + local on_end = function() + game.tooltip.inhibited = false + game:unregisterDialog(self) + if fct then + fct() + end + end self:loadUI(uis) - self.key:addBind("EXIT", function() game:unregisterDialog(self) if fct then fct() end end) - self.key:addBind("ACCEPT", function() game:unregisterDialog(self) if fct then fct() end end) + self.key:addBind("EXIT", on_end) + self.key:addBind("ACCEPT", on_end) self:setupUI(true, true) if self.w >= game.w or self.h >= game.h then @@ -94,4 +102,6 @@ function _M:init(l, w, force_height) game:registerDialog(self) game:playSound("actions/read") -end + + game.tooltip.inhibited = true +end \ No newline at end of file diff --git a/game/modules/tome/dialogs/ShowEquipInven.lua b/game/modules/tome/dialogs/ShowEquipInven.lua index e45c5f21e90dacc3b6cb4bd8027f65359af13951..9732c1e681c3dc268ac7e8c6dfaff0b6ef543009 100644 --- a/game/modules/tome/dialogs/ShowEquipInven.lua +++ b/game/modules/tome/dialogs/ShowEquipInven.lua @@ -41,10 +41,8 @@ function _M:init(title, actor, filter, action, on_select) -- Add tooltips self.on_select = function(item) if item.last_display_x and item.object then - local x = nil - if self.focus_ui and self.focus_ui.ui == self.c_inven then - x = self.c_inven._last_ox - game.tooltip.max - end + local x + if self.focus_ui and self.focus_ui.ui == self.c_inven then x = self.c_inven._last_ox - game.tooltip.w end game:tooltipDisplayAtMap(x or item.last_display_x, item.last_display_y, item.object:getDesc({do_color=true}, self.actor:getInven(item.object:wornInven()))) elseif item.last_display_x and item.data and item.data.desc then game:tooltipDisplayAtMap(item.last_display_x, item.last_display_y, item.data.desc, {up=true}) @@ -81,6 +79,43 @@ function _M:init(title, actor, filter, action, on_select) self:loadUI(uis) self:setFocus(self.c_inven) self:setupUI() + + local lock_tooltip = function() + if not game.tooltip.empty then + game.tooltip.locked = not game.tooltip.locked + game.tooltip.container.focused = game.tooltip.locked + game.log("Tooltip %s", game.tooltip.locked and "locked" or "unlocked") + if game.tooltip.locked then + self.old_areas_name = self.mouse.areas_name + self.old_areas = self.mouse.areas + self.mouse:reset() + local on_mouse = function(button, x, y, xrel, yrel, bx, by, event) + if button == "wheelup" and event == "button" then + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max) + elseif button == "wheeldown" and event == "button" then + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max) + end + if button == "middle" then + if not self.scroll_drag then + self.scroll_drag = true + self.scroll_drag_x_start = x + self.scroll_drag_y_start = y + else + game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + y - self.scroll_drag_y_start, 0, game.tooltip.container.scrollbar.max) + self.scroll_drag_x_start = x + self.scroll_drag_y_start = y + end + else + self.scroll_drag = false + end + end + self.mouse:registerZone(0, 0, self.w, self.h, on_mouse) + else + self.mouse.areas_name = self.old_areas_name + self.mouse.areas = self.old_areas + end + end + end engine.interface.PlayerHotkeys:bindAllHotkeys(self.key, function(i) self:defineHotkey(i) end) self.key:addBinds{ @@ -88,7 +123,11 @@ function _M:init(title, actor, filter, action, on_select) if self.focus_ui and self.focus_ui.ui == self.c_inven then self:use(self.c_inven.c_inven.list[self.c_inven.c_inven.sel]) end end, - EXIT = function() game:unregisterDialog(self) end, + EXIT = function() game.tooltip.locked = false game:unregisterDialog(self) end, + LOCK_TOOLTIP = lock_tooltip, + LOCK_TOOLTIP_COMPARE = lock_tooltip, + MOVE_UP = function() if game.tooltip.locked then game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max) end end, + MOVE_DOWN = function() if game.tooltip.locked then game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max) end end, } self.key.any_key = function(sym) diff --git a/game/modules/tome/dialogs/ShowStore.lua b/game/modules/tome/dialogs/ShowStore.lua index de300ae051371ab07ce5f83e00c5a3c2526b34b6..43a75f2484bcb957e1e28e98b331bef443e81f09 100644 --- a/game/modules/tome/dialogs/ShowStore.lua +++ b/game/modules/tome/dialogs/ShowStore.lua @@ -49,10 +49,32 @@ function _M:init(title, store_inven, actor_inven, store_filter, actor_filter, ac on_drag=function(item) self:onDrag(item, "store-sell") end, on_drag_end=function() self:onDragTakeoff("store-buy") end, } + + local direct_draw= function(item, x, y, w, h, total_w, total_h, loffset_x, loffset_y, dest_area) + -- if there is object and is withing visible bounds + if item.object and total_h + h > loffset_y and total_h < loffset_y + dest_area.h then + local clip_y_start, clip_y_end = 0, 0 + -- if it started before visible area then compute its top clip + if total_h < loffset_y then + clip_y_start = loffset_y - total_h + end + -- if it ended after visible area then compute its bottom clip + if total_h + h > loffset_y + dest_area.h then + clip_y_end = total_h + h - loffset_y - dest_area.h + end + -- get entity texture with everything it has i.e particles + local texture = item.object:getEntityFinalTexture(nil, h, h) + local one_by_tex_h = 1 / h + texture:toScreenPrecise(x, y, h, h - clip_y_start - clip_y_end, 0, 1, clip_y_start * one_by_tex_h, (h - clip_y_end) * one_by_tex_h) + return h, h, 0, 0, clip_y_start, clip_y_end + end + return 0, 0, 0, 0, 0, 0 + end + self.c_store = Inventory.new{actor=store_actor, inven=store_inven, filter=store_filter, width=math.floor(self.iw / 2 - 10), height=self.ih - 10, tabslist=false, columns={ {name="", width={20,"fixed"}, display_prop="char", sort="id"}, - {name="", width={24,"fixed"}, display_prop="object", direct_draw=function(item, x, y) item.object:toScreen(nil, x+4, y, 16, 16) end}, + {name="", width={24,"fixed"}, display_prop="object", direct_draw=direct_draw}, {name="Store", width=80, display_prop="name"}, {name="Category", width=20, display_prop="cat"}, {name="Price", width={50,"fixed"}, display_prop=function(item) return self.descprice("buy", item.object) end, sort=function(a, b) return descprice("buy", a.object) < descprice("buy", b.object) end}, @@ -93,12 +115,14 @@ function _M:init(title, store_inven, actor_inven, store_filter, actor_filter, ac elseif self.focus_ui and self.focus_ui.ui == self.c_inven then x = self.c_inven._last_ox - game.tooltip.max end + game:tooltipDisplayAtMap(x or item.last_display_x, item.last_display_y, item.object:getDesc({do_color=true}, game.player:getInven(item.object:wornInven()))) end end self.key.any_key = function(sym) -- Control resets the tooltip - if sym == self.key._LCTRL or sym == self.key._RCTRL then local i = self.cur_item self.cur_item = nil self:select(i) end + if (sym == self.key._LCTRL or sym == self.key._RCTRL) and sym~=self.prev_ctrl then local i = self.cur_item self.cur_item = nil self:select(i, true) end + self.prev_ctrl = sym end end @@ -110,14 +134,18 @@ function _M:updateStore() self:generateList() end -function _M:select(item) - if self.cur_item == item then return end - if item then - if self.on_select then self.on_select(item) end - end +function _M:select(item, force) + if self.cur_item == item and not force then return end + if item then if self.on_select then self.on_select(item) end end self.cur_item = item end +function _M:on_recover_focus() + if self.focus_ui and self.focus_ui.ui == self.c_inven then self:select(self.c_inven.c_inven.list[self.c_inven.c_inven.sel], true) + elseif self.focus_ui and self.focus_ui.ui == self.c_store then self:select(self.c_store.c_inven.list[self.c_store.c_inven.sel], true) + end +end + function _M:on_focus(id, ui) if self.focus_ui and self.focus_ui.ui == self.c_inven then self:select(self.c_inven.c_inven.list[self.c_inven.c_inven.sel]) elseif self.focus_ui and self.focus_ui.ui == self.c_store then self:select(self.c_store.c_inven.list[self.c_store.c_inven.sel]) diff --git a/src/core_lua.c b/src/core_lua.c index f5775ef00bda157df95b869272f928239bef6781..908ee7c6d072d6ca7a354dab6d74a4884209073a 100644 --- a/src/core_lua.c +++ b/src/core_lua.c @@ -1827,6 +1827,24 @@ static int gl_rotate(lua_State *L) return 0; } +static int gl_push(lua_State *L) +{ + glPushMatrix(); + return 0; +} + +static int gl_pop(lua_State *L) +{ + glPopMatrix(); + return 0; +} + +static int gl_identity(lua_State *L) +{ + glLoadIdentity(); + return 0; +} + static int gl_matrix(lua_State *L) { if (lua_toboolean(L, 1)) glPushMatrix(); @@ -2540,6 +2558,9 @@ static const struct luaL_reg displaylib[] = {"glTranslate", gl_translate}, {"glScale", gl_scale}, {"glRotate", gl_rotate}, + {"glPush", gl_push}, + {"glPop", gl_pop}, + {"glIdentity", gl_identity}, {"glColor", gl_color}, {"glMatrix", gl_matrix}, {"glDepthTest", gl_depth_test}, diff --git a/src/map.c b/src/map.c index 577de4b73e39c83bcac93ecbb603c65405ac0dd8..1727ae48f93545d7563788155341f10d5883d8dc 100644 --- a/src/map.c +++ b/src/map.c @@ -31,6 +31,8 @@ //#include "shaders.h" #include "useshader.h" +#include "assert.h" + static const char IS_HEX_KEY = 'k'; /* @@ -487,13 +489,14 @@ static int map_objects_display(lua_State *L) glLoadIdentity(); glOrtho(0, w, 0, h, -101, 101); glMatrixMode( GL_MODELVIEW ); - + CHECKGL(glPushMatrix()); /* Reset The View */ - glLoadIdentity( ); + glLoadIdentity(); + tglClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); CHECKGL(glClear(GL_COLOR_BUFFER_BIT)); - CHECKGL(glLoadIdentity()); + //CHECKGL(glLoadIdentity()); GLfloat vertices[3*4]; GLfloat texcoords[2*4] = { @@ -560,10 +563,11 @@ static int map_objects_display(lua_State *L) // No, dot not it's a static, see upwards // CHECKGL(glDeleteFramebuffersEXT(1, &fbo)); + CHECKGL(glPopMatrix()); glMatrixMode(GL_PROJECTION); CHECKGL(glPopMatrix()); glMatrixMode( GL_MODELVIEW ); - + tglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); @@ -1452,16 +1456,25 @@ static int map_to_screen(lua_State *L) map->used_mx = mx; map->used_my = my; + + int mini = mx - 1, maxi = mx + map->mwidth + 2, minj = my - 1, maxj = my + map->mheight + 2; + + if(mini < 0) + mini = 0; + if(minj < 0) + minj = 0; + if(maxi > map->w) + maxi = map->w; + if(maxj > map->h) + maxj = map->h; // Always display some more of the map to make sure we always see it all for (z = 0; z < map->zdepth; z++) { - for (j = my - 1; j < my + map->mheight + 2; j++) + for (j = minj; j < maxj; j++) { - for (i = mx - 1; i < mx + map->mwidth + 2; i++) + for (i = mini; i < maxi; i++) { - if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h)) continue; - int dx = x + i * map->tile_w; int dy = y + j * map->tile_h + (i & map->is_hex) * map->tile_h / 2; map_object *mo = map->grids[i][j][z]; @@ -1565,13 +1578,24 @@ static int minimap_to_screen(lua_State *L) int ptr; GLubyte *mm = map->minimap; memset(mm, 0, map->mm_rh * map->mm_rw * 4 * sizeof(GLubyte)); + + int mini = mdx, maxi = mdx + mdw, minj = mdy, maxj = mdy + mdh; + + if(mini < 0) + mini = 0; + if(minj < 0) + minj = 0; + if(maxi > map->w) + maxi = map->w; + if(maxj > map->h) + maxj = map->h; + for (z = 0; z < map->zdepth; z++) { - for (i = mdx; i < mdx + mdw; i++) + for (j = minj; j < maxj; j++) { - for (j = mdy; j < mdy + mdh; j++) + for (i = mini; i < maxi; i++) { - if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h)) continue; map_object *mo = map->grids[i][j][z]; if (!mo || mo->mm_r < 0) continue; ptr = (((1+f)*(j-mdy) + (i & f)) * map->mm_rw + (i-mdx)) * 4;