Skip to content
Snippets Groups Projects
LogDisplay.lua 8.44 KiB
Newer Older
  • Learn to ignore specific revisions
  • DarkGod's avatar
    DarkGod committed
    -- Copyright (C) 2009 - 2015 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"
    
    dg's avatar
    dg committed
    require "engine.ui.Base"
    
    DarkGod's avatar
    DarkGod committed
    local Shader = require "engine.Shader"
    
    local Mouse = require "engine.Mouse"
    
    dg's avatar
    dg committed
    local Slider = require "engine.ui.Slider"
    
    dg's avatar
    dg committed
    --- Module that handles message history in a mouse wheel scrollable zone
    
    dg's avatar
    dg committed
    module(..., package.seeall, class.inherit(engine.ui.Base))
    
    
    --- Creates the log zone
    function _M:init(x, y, w, h, max, fontname, fontsize, color, bgcolor)
    	self.color = color or {255,255,255}
    
    	if type(bgcolor) ~= "string" then
    		self.bgcolor = bgcolor or {0,0,0}
    	else
    		self.bgcolor = {0,0,0}
    		self.bg_image = bgcolor
    	end
    
    	self.font = core.display.newFont(fontname or "/data/font/DroidSans.ttf", fontsize or 12)
    
    	self.font_h = self.font:lineSkip()
    	self.log = {}
    	getmetatable(self).__call = _M.call
    
    dg's avatar
    dg committed
    	self.max_log = max or 4000
    
    	self.scroll = 0
    	self.changed = true
    
    dg's avatar
    dg committed
    	self.cache = {}
    
    dg's avatar
    dg committed
    	setmetatable(self.cache, {__mode="v"})
    
    dg's avatar
    dg committed
    
    --	if config.settings.log_to_disk then self.out_f = fs.open("/game-log-"..(game and type(game) == "table" and game.__mod_info and game.__mod_info.short_name or "default").."-"..os.time()..".txt", "w") end
    
    function _M:enableShadow(v)
    	self.shadow = v
    end
    
    
    DarkGod's avatar
    DarkGod committed
    	self.fading = v
    
    --- Resize the display area
    function _M:resize(x, y, w, h)
    	self.display_x, self.display_y = math.floor(x), math.floor(y)
    	self.w, self.h = math.floor(w), math.floor(h)
    
    dg's avatar
    dg committed
    	self.fw, self.fh = self.w - 4, self.font:lineSkip()
    	self.max_display = math.floor(self.h / self.fh)
    
    
    	if self.bg_image then
    		local fill = core.display.loadImage(self.bg_image)
    		local fw, fh = fill:getSize()
    		self.bg_surface = core.display.newSurface(w, h)
    		self.bg_surface:erase(0, 0, 0)
    		for i = 0, w, fw do for j = 0, h, fh do
    			self.bg_surface:merge(fill, i, j)
    		end end
    
    dg's avatar
    dg committed
    		self.bg_texture, self.bg_texture_w, self.bg_texture_h = self.bg_surface:glTexture()
    
    dg's avatar
    dg committed
    	self.scrollbar = Slider.new{size=self.h - 20, max=1, inverse=true}
    
    
    	self.mouse = Mouse.new()
    	self.mouse.delegate_offset_x = self.display_x
    	self.mouse.delegate_offset_y = self.display_y
    	self.mouse:registerZone(0, 0, self.w, self.h, function(button, x, y, xrel, yrel, bx, by, event) self:mouseEvent(button, x, y, xrel, yrel, bx, by, event) end)
    
    	for i = 1, #self.log do
    		if timestamp and self.log[i].timestamp <= timestamp then break end
    
    		if not extra then
    			log[#log+1] = self.log[i].str
    		else
    			log[#log+1] = {str=self.log[i].str, src=self.log[i]}
    		end
    	end
    
    Alex Ksandra's avatar
    Alex Ksandra committed
    function _M:getLogLast()
    
    	if not self.log[1] then return 0 end
    	return self.log[1].timestamp
    end
    
    
    --- Make a dialog popup with the full log
    function _M:showLogDialog(title, shadow)
    
    	local log = self:getLog()
    	local d = require_first("mod.dialogs.ShowLog", "engine.dialogs.ShowLog").new(title or "Message Log", shadow, {log=log})
    
    local urlfind = (lpeg.P"http://" + lpeg.P"https://") * (1-lpeg.P" ")^0
    local urlmatch = lpeg.anywhere(lpeg.C(urlfind))
    
    
    --- Appends text to the log
    -- This method is set as the call methamethod too, this means it is usable like this:<br/>
    -- log = LogDisplay.new(...)<br/>
    -- log("foo %s", s)
    function _M:call(str, ...)
    
    dg's avatar
    dg committed
    	str = str or ""
    
    dg's avatar
    dg committed
    	str = str:format(...)
    	print("[LOG]", str)
    
    	local tstr = str:toString()
    
    dg's avatar
    dg committed
    	if self.out_f then self.out_f:write(tstr:removeColorCodes()) self.out_f:write("\n") end
    
    
    	local url = urlmatch:match(tstr)
    	if url then
    		tstr = tstr:lpegSub(urlfind, "#LIGHT_BLUE##{italic}#"..url.."#{normal}##LAST#")
    	end
    
    	table.insert(self.log, 1, {str=tstr, timestamp = core.game.getTime(), url=url})
    
    dg's avatar
    dg committed
    	while #self.log > self.max_log do
    
    dg's avatar
    dg committed
    		local old = table.remove(self.log)
    		self.cache[old] = nil
    
    dg's avatar
    dg committed
    	self.max = #self.log
    
    --- Gets the last log line
    function _M:getNewestLine()
    	if self.log[1] then return self.log[1].str end
    	return nil
    end
    
    
    --- Clear the log
    function _M:empty()
    
    dg's avatar
    dg committed
    	self.cache = {}
    
    	self.log = {}
    	self.changed = true
    end
    
    --- Get Last Lines From Log
    -- @param number number of lines to retrieve
    function _M:getLines(number)
    	local from = number
    	if from > #self.log then from = #self.log end
    	local lines = { }
    	for i = from, 1, -1 do
    
    		lines[#lines+1] = tostring(self.log[i].str)
    
    function _M:onMouse(fct)
    	self.on_mouse = fct
    end
    
    function _M:mouseEvent(button, x, y, xrel, yrel, bx, by, event)
    	if button == "wheelup" then self:scrollUp(1)
    	elseif button == "wheeldown" then self:scrollUp(-1)
    	else
    		if not self.on_mouse or not self.dlist then return end
    		local citem = nil
    		local ci
    		for i = 1, #self.dlist do
    			local item = self.dlist[i]
    			if item.dh and by >= item.dh - self.mouse.delegate_offset_y then citem = self.dlist[i] ci=i break end
    		end
    		if citem then
    			local sub_es = {}
    
    			for e, _ in pairs(citem.item._dduids) do sub_es[#sub_es+1] = e end
    
    
    			if citem.url and button == "left" and event == "button" then
    
    				util.browserOpenUrl(citem.url, {is_external=true})
    
    			else
    				self.on_mouse(citem, sub_es, button, event, x, y, xrel, yrel, bx, by)
    			end
    
    		else
    			self.on_mouse(nil, nil, button, event, x, y, xrel, yrel, bx, by)
    		end
    	end
    end
    
    
    function _M:display()
    	-- If nothing changed, return the same surface as before
    
    dg's avatar
    dg committed
    	if not self.changed then return end
    
    dg's avatar
    dg committed
    	-- Erase and the display
    	self.dlist = {}
    	local h = 0
    
    	local old_style = self.font:getStyle()
    
    dg's avatar
    dg committed
    	for z = 1 + self.scroll, #self.log do
    		local stop = false
    
    dg's avatar
    dg committed
    		local gen
    		if self.cache[tstr] then
    			gen = self.cache[tstr]
    		else
    
    			gen = self.font:draw(tstr, self.w, 255, 255, 255, false, true)
    
    dg's avatar
    dg committed
    			self.cache[tstr] = gen
    		end
    
    dg's avatar
    dg committed
    		for i = #gen, 1, -1 do
    
    DarkGod's avatar
    DarkGod committed
    			self.dlist[#self.dlist+1] = {item=gen[i], date=self.log[z].reset_fade or self.log[z].timestamp, url=self.log[z].url}
    
    dg's avatar
    dg committed
    			h = h + self.fh
    			if h > self.h - self.fh then stop=true break end
    
    dg's avatar
    dg committed
    		if stop then break end
    
    	self.font:setStyle(old_style)
    
    dg's avatar
    dg committed
    	return
    
    function _M:toScreen()
    	self:display()
    
    dg's avatar
    dg committed
    	if self.bg_texture then self.bg_texture:toScreenFull(self.display_x, self.display_y, self.w, self.h, self.bg_texture_w, self.bg_texture_h) end
    
    	local shader = Shader.default.textoutline and Shader.default.textoutline.shad
    
    dg's avatar
    dg committed
    	local h = self.display_y + self.h -  self.fh
    	for i = 1, #self.dlist do
    
    		local item = self.dlist[i].item
    
    		local fade = 1
    
    		if self.fading and self.fading > 0 then
    
    			fade = now - self.dlist[i].date
    			if fade < self.fading * 1000 then fade = 1
    			elseif fade < self.fading * 2000 then fade = (self.fading * 2000 - fade) / (self.fading * 1000)
    			else fade = 0 end
    
    			self.dlist[i].faded = fade
    
    		self.dlist[i].dh = h
    
    DarkGod's avatar
    DarkGod committed
    		if self.shadow then
    
    				shader:use(true)
    
    				shader:uniOutlineSize(0.7, 0.7)
    				shader:uniTextSize(item._tex_w, item._tex_h)
    
    DarkGod's avatar
    DarkGod committed
    			else
    				item._tex:toScreenFull(self.display_x+2, h+2, item.w, item.h, item._tex_w, item._tex_h, 0,0,0, self.shadow * fade)
    			end
    		end
    
    		item._tex:toScreenFull(self.display_x, h, item.w, item.h, item._tex_w, item._tex_h, 1, 1, 1, fade)
    
    		if self.shadow and shader then shader:use(false) end
    
    		for e, d in pairs(item._dduids) do e:toScreen(nil, self.display_x + d.x, h, d.w, d.w, fade, false, false) end
    
    dg's avatar
    dg committed
    		h = h - self.fh
    	end
    
    
    dg's avatar
    dg committed
    		self.scrollbar.pos = self.scroll
    		self.scrollbar.max = self.max - self.max_display + 1
    		self.scrollbar:display(self.display_x + self.w - self.scrollbar.w, self.display_y)
    
    dg's avatar
    dg committed
    	end
    
    --- Scroll the zone
    -- @param i number representing how many lines to scroll
    function _M:scrollUp(i)
    	self.scroll = self.scroll + i
    	if self.scroll > #self.log - 1 then self.scroll = #self.log - 1 end
    	if self.scroll < 0 then self.scroll = 0 end
    	self.changed = true
    
    	self:resetFade()
    end
    
    function _M:resetFade()
    	local log = self.log
    
    	-- Reset fade
    
    DarkGod's avatar
    DarkGod committed
    	for i = 1,#log do
    		log[i].reset_fade = core.game.getTime()
    	end