516 lines
18 KiB
Lua
516 lines
18 KiB
Lua
-- Microchip-chat 0.8.8: individually detachable chat tabs.
|
|
-- Vanilla 1.12 / Lua 5.0: no modern chat API or automatic extra chat edit box.
|
|
-- The full channel buffers are shared; each window maintains only a 120-line
|
|
-- rendering page and its own independent scroll position.
|
|
MCWindows = {}
|
|
|
|
local C = nil
|
|
local windows = {}
|
|
local active = nil
|
|
local PAGE, STEP = 120, 24
|
|
|
|
local function clamped(value, lo, hi)
|
|
value = tonumber(value) or lo
|
|
if value < lo then value=lo end
|
|
if value > hi then value=hi end
|
|
return math.floor(value)
|
|
end
|
|
|
|
local function colors(role)
|
|
if C and C.palette then return C.palette(role) end
|
|
return 0.2,0.25,0.32
|
|
end
|
|
|
|
local function texture(frame, alpha, role)
|
|
local t=frame:CreateTexture(nil,"BACKGROUND")
|
|
t:SetTexture("Interface\\Tooltips\\UI-Tooltip-Background")
|
|
t:SetAllPoints(frame)
|
|
local r,g,b=colors(role or "window")
|
|
t:SetVertexColor(r,g,b,alpha)
|
|
return t
|
|
end
|
|
|
|
local function label(parent, value, anchor, relative, rp, x, y)
|
|
local t=parent:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
|
|
t:SetPoint(anchor,relative,rp,x,y)
|
|
t:SetText(value)
|
|
local r,g,b=colors("text")
|
|
t:SetTextColor(r,g,b)
|
|
return t
|
|
end
|
|
|
|
local function button(parent, caption, width, x, clicked)
|
|
local b=CreateFrame("Button",nil,parent)
|
|
b:SetWidth(width);b:SetHeight(24)
|
|
b:SetPoint("LEFT",parent,"LEFT",x,0)
|
|
b.back=texture(b,0.96,"button")
|
|
local hot=b:CreateTexture(nil,"HIGHLIGHT")
|
|
hot:SetTexture("Interface\\Tooltips\\UI-Tooltip-Background")
|
|
hot:SetVertexColor(0.43,0.56,0.65,0.55)
|
|
hot:SetAllPoints(b)
|
|
b.caption=label(b,caption,"CENTER",b,"CENTER",0,0)
|
|
b:SetScript("OnClick",clicked)
|
|
return b
|
|
end
|
|
|
|
local function bucketEnabled(tab,key)
|
|
if key=="LEGACY" then return true end
|
|
if string.sub(key,1,8)=="CHANNEL:" then
|
|
return C.enabled(tab,"CHANNEL",key)
|
|
end
|
|
return C.enabled(tab,key)
|
|
end
|
|
|
|
local function fetch(w,dir,pivot,amount)
|
|
return MCHistory.Window(C.store,dir,pivot,amount,
|
|
function(key) return bucketEnabled(w.tab,key) end,
|
|
function(r) return C.matches(w.tab,r) end)
|
|
end
|
|
|
|
local function drawRows(w)
|
|
w.log:Clear()
|
|
local i,r,text,cr,cg,cb
|
|
for i=1,table.getn(w.rows) do
|
|
r=w.rows[i]
|
|
text,cr,cg,cb=C.render(w.tab,r)
|
|
w.log:AddMessage(text,cr,cg,cb)
|
|
end
|
|
w.count:SetText(table.getn(w.rows).." loaded / "..MCHistory.Count(C.store).." saved")
|
|
end
|
|
|
|
local function jump(w,where)
|
|
local rows,more
|
|
if where=="top" then
|
|
rows,more=fetch(w,"newer",nil,PAGE)
|
|
w.rows=rows;w.older=false;w.newer=more
|
|
drawRows(w)
|
|
w.log:ScrollToTop()
|
|
else
|
|
rows,more=fetch(w,"older",nil,PAGE)
|
|
w.rows=rows;w.older=more;w.newer=false
|
|
drawRows(w)
|
|
w.log:ScrollToBottom()
|
|
end
|
|
end
|
|
|
|
local function shift(w,dir)
|
|
local rows=w.rows
|
|
local n=table.getn(rows)
|
|
if n==0 then return end
|
|
local added,more,newRows,i,retain,first
|
|
if dir=="older" then
|
|
if not w.older then return end
|
|
added,more=fetch(w,"older",rows[1].sequence,STEP)
|
|
if table.getn(added)==0 then w.older=false;return end
|
|
newRows={}
|
|
for i=1,table.getn(added) do table.insert(newRows,added[i]) end
|
|
retain=PAGE-table.getn(added)
|
|
for i=1,math.min(n,retain) do table.insert(newRows,rows[i]) end
|
|
w.older=more
|
|
w.newer=w.newer or n>retain
|
|
w.rows=newRows
|
|
drawRows(w)
|
|
w.log:ScrollToTop()
|
|
for i=1,table.getn(added) do w.log:ScrollDown() end
|
|
else
|
|
if not w.newer then return end
|
|
added,more=fetch(w,"newer",rows[n].sequence,STEP)
|
|
if table.getn(added)==0 then w.newer=false;return end
|
|
retain=PAGE-table.getn(added)
|
|
first=math.max(1,n-retain+1)
|
|
newRows={}
|
|
for i=first,n do table.insert(newRows,rows[i]) end
|
|
for i=1,table.getn(added) do table.insert(newRows,added[i]) end
|
|
w.older=w.older or first>1
|
|
w.newer=more
|
|
w.rows=newRows
|
|
drawRows(w)
|
|
w.log:ScrollToBottom()
|
|
for i=1,table.getn(added) do w.log:ScrollUp() end
|
|
end
|
|
end
|
|
|
|
local function repaintLive(w)
|
|
w.live:Clear()
|
|
-- Fetch only recent matching records; do not expand every saved channel.
|
|
local records=fetch(w,"older",nil,math.max(35,C.cfg.visible*4))
|
|
local i,r,t,cr,cg,cb
|
|
for i=1,table.getn(records) do
|
|
r=records[i]
|
|
if (r.sequence or 0)>(C.sessionStart or 0) and
|
|
(not C.cfg.fading or ((GetTime()-(r.entered or 0)) < C.cfg.hold+C.cfg.fade and
|
|
(GetTime()-(r.entered or 0)) >= 0)) then
|
|
t,cr,cg,cb=C.render(w.tab,r)
|
|
w.live:AddMessage(t,cr,cg,cb)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function resizeLayout(w)
|
|
local font=clamped(w.tab.font or C.cfg.defaultFont,9,20)
|
|
local combat=w.tab.isCombatTab and C.enabled(w.tab,"COMBAT")
|
|
local bar=combat and 28 or 0
|
|
w.root:SetWidth(w.geo.width)
|
|
local available=(UIParent:GetHeight() or 768)-(w.root:GetBottom() or w.geo.y)-38
|
|
local full=math.max(w.geo.height,available)
|
|
if w.geo.historyHeight and w.geo.historyHeight>0 then
|
|
full=math.max(w.geo.height,math.min(full,w.geo.historyHeight))
|
|
end
|
|
w.root:SetHeight(w.expanded and full or w.geo.height)
|
|
w.live:ClearAllPoints()
|
|
w.live:SetPoint("TOPLEFT",w.root,"TOPLEFT",11,-35-bar)
|
|
w.live:SetPoint("BOTTOMRIGHT",w.root,"BOTTOMRIGHT",-11,32)
|
|
w.log:ClearAllPoints()
|
|
w.log:SetPoint("TOPLEFT",w.root,"TOPLEFT",11,-35-bar)
|
|
w.log:SetPoint("BOTTOMRIGHT",w.root,"BOTTOMRIGHT",-11,32)
|
|
w.live:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF",font)
|
|
w.log:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF",font)
|
|
w.live:SetMaxLines(C.cfg.visible)
|
|
w.live:SetTimeVisible(C.cfg.hold)
|
|
w.live:SetFadeDuration(C.cfg.fade)
|
|
w.live:SetFading(C.cfg.fading)
|
|
w.log:SetFading(false)
|
|
if combat then w.combatBar:Show() else w.combatBar:Hide() end
|
|
w.title:SetText(w.tab.name..(w.expanded and " / HISTORY" or ""))
|
|
w.hist.caption:SetText(w.expanded and "END" or "HISTORY")
|
|
if w.expanded then
|
|
w.live:Hide();w.log:Show();w.top:Show();w.count:Show()
|
|
else
|
|
w.live:Show();w.log:Hide();w.top:Hide();w.count:Hide()
|
|
end
|
|
if C.cfg.locked then w.resize:Hide() else w.resize:Show() end
|
|
local r,g,b=colors("window")
|
|
w.back:SetVertexColor(r,g,b,w.expanded and 0.92 or 0.18)
|
|
local tr,tg,tb=colors("text")
|
|
w.title:SetTextColor(tr,tg,tb)
|
|
w.count:SetTextColor(tr,tg,tb)
|
|
w.resizetext:SetTextColor(tr,tg,tb)
|
|
local i
|
|
for i=1,table.getn(w.buttons) do
|
|
local bb=w.buttons[i]
|
|
local br,bg,bbg=colors("button")
|
|
bb.back:SetVertexColor(br,bg,bbg,0.96)
|
|
bb.caption:SetTextColor(tr,tg,tb)
|
|
end
|
|
w.all.caption:SetTextColor((w.tab.combatView or "all")=="all" and 1 or tr,
|
|
(w.tab.combatView or "all")=="all" and 0.87 or tg,
|
|
(w.tab.combatView or "all")=="all" and 0.52 or tb)
|
|
w.me.caption:SetTextColor(w.tab.combatView=="me" and 1 or tr,
|
|
w.tab.combatView=="me" and 0.87 or tg,
|
|
w.tab.combatView=="me" and 0.52 or tb)
|
|
end
|
|
|
|
local function expand(w,on)
|
|
w.expanded=on and true or false
|
|
resizeLayout(w)
|
|
if w.expanded then jump(w,"bottom") else repaintLive(w) end
|
|
end
|
|
|
|
local function wheel(w,delta)
|
|
delta=tonumber(delta) or 0
|
|
if delta>0 then
|
|
if not w.expanded then expand(w,true) end
|
|
if w.log:AtTop() and w.older then shift(w,"older") end
|
|
local i
|
|
for i=1,3 do w.log:ScrollUp() end
|
|
elseif delta<0 then
|
|
if w.expanded then
|
|
if w.log:AtBottom() and w.newer then shift(w,"newer") end
|
|
local i
|
|
for i=1,3 do w.log:ScrollDown() end
|
|
else w.live:ScrollToBottom() end
|
|
end
|
|
end
|
|
|
|
local function saveGeometry(w)
|
|
w.root:StopMovingOrSizing()
|
|
w.geo.x=math.floor(w.root:GetLeft() or w.geo.x or 55)
|
|
w.geo.y=math.floor(w.root:GetBottom() or w.geo.y or 100)
|
|
w.geo.width=clamped(w.root:GetWidth(),400,900)
|
|
if w.expanded then w.geo.historyHeight=clamped(w.root:GetHeight(),160,1200)
|
|
else w.geo.height=clamped(w.root:GetHeight(),160,950) end
|
|
w.root:ClearAllPoints()
|
|
w.root:SetPoint("BOTTOMLEFT",UIParent,"BOTTOMLEFT",w.geo.x,w.geo.y)
|
|
resizeLayout(w)
|
|
end
|
|
|
|
local function combatMode(w,mode)
|
|
w.tab.combatView=mode
|
|
resizeLayout(w)
|
|
repaintLive(w)
|
|
if w.expanded then jump(w,"bottom") end
|
|
end
|
|
|
|
local function hyperlink()
|
|
if MCURLs and MCURLs.HandleClick(arg1) then return end
|
|
if SetItemRef then SetItemRef(arg1,arg2,arg3) end
|
|
end
|
|
|
|
-- Vanilla 1.12 may not propagate SetAlpha to every child region; fade the
|
|
-- header and toolbar recursively so their labels do not remain faintly visible.
|
|
local function fadeControls(frame, alpha)
|
|
frame:SetAlpha(alpha)
|
|
local regions={frame:GetRegions()}
|
|
local i
|
|
for i=1,table.getn(regions) do
|
|
if regions[i] then regions[i]:SetAlpha(alpha) end
|
|
end
|
|
local children={frame:GetChildren()}
|
|
for i=1,table.getn(children) do
|
|
if children[i] then fadeControls(children[i],alpha) end
|
|
end
|
|
end
|
|
|
|
local function createWindow(tab)
|
|
local w={tab=tab,rows={},older=false,newer=false,expanded=false,buttons={},
|
|
controlsAlpha=1,lastHover=GetTime()}
|
|
w.geo=tab.windowGeometry
|
|
if type(w.geo)~="table" then
|
|
w.geo={width=520,height=240,x=math.max(0,(UIParent:GetWidth() or 1024)-590),y=125}
|
|
tab.windowGeometry=w.geo
|
|
end
|
|
w.geo.width=clamped(w.geo.width,400,900)
|
|
w.geo.height=clamped(w.geo.height,160,950)
|
|
w.geo.x=tonumber(w.geo.x) or 55
|
|
w.geo.y=tonumber(w.geo.y) or 125
|
|
w.root=CreateFrame("Frame",nil,UIParent)
|
|
w.root:SetFrameStrata("MEDIUM")
|
|
w.root:SetPoint("BOTTOMLEFT",UIParent,"BOTTOMLEFT",w.geo.x,w.geo.y)
|
|
w.root:SetMovable(true);w.root:SetResizable(true)
|
|
if w.root.SetMinResize then w.root:SetMinResize(400,160) end
|
|
if w.root.SetMaxResize then w.root:SetMaxResize(900,1200) end
|
|
w.root:SetClampedToScreen(true)
|
|
w.root:EnableMouse(true);w.root:EnableMouseWheel(true)
|
|
w.root:SetScript("OnMouseWheel",function() wheel(w,arg1) end)
|
|
w.root:SetScript("OnEnter",function() active=tab end)
|
|
w.back=texture(w.root,0.18,"window")
|
|
w.header=CreateFrame("Button",nil,w.root)
|
|
w.header:SetPoint("TOPLEFT",w.root,"TOPLEFT",0,0)
|
|
w.header:SetPoint("TOPRIGHT",w.root,"TOPRIGHT",0,0)
|
|
w.header:SetHeight(29)
|
|
texture(w.header,0.98,"button")
|
|
w.title=label(w.header,tab.name,"LEFT",w.header,"LEFT",12,0)
|
|
w.header:RegisterForDrag("LeftButton")
|
|
w.header:SetScript("OnDragStart",function()
|
|
active=tab
|
|
if not C.cfg.locked then w.root:StartMoving() end
|
|
end)
|
|
w.header:SetScript("OnDragStop",function()
|
|
if not C.cfg.locked then saveGeometry(w) end
|
|
end)
|
|
w.header:SetScript("OnClick",function() active=tab end)
|
|
w.combatBar=CreateFrame("Frame",nil,w.root)
|
|
w.combatBar:SetHeight(26)
|
|
w.combatBar:SetPoint("TOPLEFT",w.root,"TOPLEFT",9,-31)
|
|
w.combatBar:SetPoint("TOPRIGHT",w.root,"TOPRIGHT",-9,-31)
|
|
texture(w.combatBar,0.96,"button")
|
|
w.all=button(w.combatBar,"ALL EVENTS",106,3,function() combatMode(w,"all") end)
|
|
w.me=button(w.combatBar,"WHAT HAPPENED TO ME",181,114,function() combatMode(w,"me") end)
|
|
table.insert(w.buttons,w.all);table.insert(w.buttons,w.me)
|
|
w.live=CreateFrame("ScrollingMessageFrame",nil,w.root,"MicrochipChatLiveTemplate")
|
|
w.live:SetJustifyH("LEFT")
|
|
w.live:EnableMouse(true);w.live:EnableMouseWheel(true)
|
|
if w.live.SetHyperlinksEnabled then w.live:SetHyperlinksEnabled(true) end
|
|
w.live:SetScript("OnMouseWheel",function() wheel(w,arg1) end)
|
|
w.live:SetScript("OnHyperlinkClick",hyperlink)
|
|
w.log=CreateFrame("ScrollingMessageFrame",nil,w.root,"MicrochipChatHistoryTemplate")
|
|
w.log:SetJustifyH("LEFT");w.log:SetFading(false)
|
|
w.log:SetMaxLines(PAGE+8)
|
|
w.log:EnableMouse(true);w.log:EnableMouseWheel(true)
|
|
if w.log.SetHyperlinksEnabled then w.log:SetHyperlinksEnabled(true) end
|
|
w.log:SetScript("OnMouseWheel",function() wheel(w,arg1) end)
|
|
w.log:SetScript("OnHyperlinkClick",hyperlink)
|
|
w.bar=CreateFrame("Frame",nil,w.root)
|
|
w.bar:SetPoint("BOTTOMLEFT",w.root,"BOTTOMLEFT",0,0)
|
|
w.bar:SetPoint("BOTTOMRIGHT",w.root,"BOTTOMRIGHT",0,0)
|
|
w.bar:SetHeight(29)
|
|
texture(w.bar,0.98,"button")
|
|
w.hist=button(w.bar,"HISTORY",74,7,function() expand(w,not w.expanded) end)
|
|
w.top=button(w.bar,"TOP",42,84,function() if w.expanded then jump(w,"top") end end)
|
|
w.bottom=button(w.bar,"BOTTOM",62,130,function()
|
|
if w.expanded then jump(w,"bottom") else w.live:ScrollToBottom() end
|
|
end)
|
|
w.dock=button(w.bar,"DOCK",54,199,function() MCWindows.Dock(w.tab) end)
|
|
w.options=button(w.bar,"OPTIONS",68,260,function()
|
|
active=w.tab
|
|
if C.showOptions then C.showOptions(w.tab) end
|
|
end)
|
|
table.insert(w.buttons,w.hist);table.insert(w.buttons,w.top)
|
|
table.insert(w.buttons,w.bottom);table.insert(w.buttons,w.dock)
|
|
table.insert(w.buttons,w.options)
|
|
w.count=label(w.root,"","TOPRIGHT",w.root,"TOPRIGHT",-12,-34)
|
|
w.resize=CreateFrame("Button",nil,w.bar)
|
|
w.resize:SetWidth(66);w.resize:SetHeight(25)
|
|
w.resize:SetPoint("RIGHT",w.bar,"RIGHT",-3,0)
|
|
texture(w.resize,0.96,"button")
|
|
w.resizetext=label(w.resize,"RESIZE","CENTER",w.resize,"CENTER",0,0)
|
|
w.resize:RegisterForDrag("LeftButton")
|
|
w.resize:SetScript("OnDragStart",function()
|
|
if not C.cfg.locked then w.resizing=true;w.root:StartSizing("BOTTOMRIGHT") end
|
|
end)
|
|
w.resize:SetScript("OnDragStop",function()
|
|
if w.resizing then w.resizing=false;saveGeometry(w) end
|
|
end)
|
|
w.root:SetScript("OnUpdate",function()
|
|
if not w.root:IsShown() then return end
|
|
if MouseIsOver and MouseIsOver(w.root) then active=tab end
|
|
local hovering=MouseIsOver and (MouseIsOver(w.header) or MouseIsOver(w.bar))
|
|
if hovering then w.lastHover=GetTime() end
|
|
local visible=hovering or w.resizing or
|
|
((GetTime()-w.lastHover)<(C.cfg.controlsHold or 5))
|
|
local target=visible and 1 or 0
|
|
local speed=(arg1 or 0)*2.8
|
|
local before=w.controlsAlpha
|
|
if before<target then w.controlsAlpha=math.min(target,before+speed)
|
|
else w.controlsAlpha=math.max(target,before-speed) end
|
|
if w.controlsAlpha~=before then
|
|
fadeControls(w.header,w.controlsAlpha)
|
|
fadeControls(w.bar,w.controlsAlpha)
|
|
end
|
|
end)
|
|
resizeLayout(w)
|
|
repaintLive(w)
|
|
windows[tab]=w
|
|
return w
|
|
end
|
|
|
|
function MCWindows.Initialize(callbacks)
|
|
C=callbacks
|
|
local i,tab
|
|
-- A partially migrated profile must always have one usable main tab.
|
|
local attached=0
|
|
for i=1,table.getn(C.store.tabs) do
|
|
if not C.store.tabs[i].detached then attached=attached+1 end
|
|
end
|
|
if attached==0 and C.store.tabs[1] then C.store.tabs[1].detached=false end
|
|
for i=1,table.getn(C.store.tabs) do
|
|
tab=C.store.tabs[i]
|
|
if tab.detached then
|
|
local w=windows[tab] or createWindow(tab)
|
|
w.root:Show()
|
|
end
|
|
end
|
|
if C.store.tabs[C.cfg.selected] and C.store.tabs[C.cfg.selected].detached then
|
|
for i=1,table.getn(C.store.tabs) do
|
|
if not C.store.tabs[i].detached then C.cfg.selected=i;break end
|
|
end
|
|
end
|
|
end
|
|
|
|
function MCWindows.IsDetached(tab)
|
|
return tab and tab.detached==true
|
|
end
|
|
|
|
function MCWindows.Detach(tab)
|
|
if not tab or not C then return false end
|
|
if tab.detached then
|
|
MCWindows.Focus(tab)
|
|
return true
|
|
end
|
|
local attached=0
|
|
local i,other
|
|
for i=1,table.getn(C.store.tabs) do
|
|
other=C.store.tabs[i]
|
|
if other~=tab and not other.detached then attached=attached+1 end
|
|
end
|
|
if attached==0 then return false end -- Keep the original dock usable.
|
|
tab.detached=true
|
|
local w=windows[tab] or createWindow(tab)
|
|
w.root:Show()
|
|
w.lastHover=GetTime()
|
|
resizeLayout(w)
|
|
repaintLive(w)
|
|
if w.expanded then jump(w,"bottom") end
|
|
MCWindows.Focus(tab)
|
|
return true
|
|
end
|
|
|
|
function MCWindows.Dock(tab)
|
|
if not tab then return end
|
|
tab.detached=false
|
|
local w=windows[tab]
|
|
if w then w.root:Hide() end
|
|
if active==tab then active=nil end
|
|
if C and C.onDock then C.onDock(tab) end
|
|
end
|
|
|
|
function MCWindows.Focus(tab)
|
|
local w=windows[tab]
|
|
if not w then return end
|
|
w.root:Show()
|
|
if w.root.Raise then w.root:Raise() end
|
|
active=tab
|
|
end
|
|
|
|
function MCWindows.Remove(tab)
|
|
if not tab then return end
|
|
local w=windows[tab]
|
|
if w then w.root:Hide();windows[tab]=nil end
|
|
if active==tab then active=nil end
|
|
end
|
|
|
|
function MCWindows.GetInputAnchor()
|
|
if not active or not active.detached then return nil end
|
|
local w=windows[active]
|
|
if w and w.root:IsShown() then return w.root end
|
|
return nil
|
|
end
|
|
|
|
function MCWindows.UseMainInput()
|
|
active=nil
|
|
end
|
|
|
|
function MCWindows.Refresh()
|
|
if not C then return end
|
|
local tab,w
|
|
for tab,w in pairs(windows) do
|
|
if tab.detached then
|
|
resizeLayout(w)
|
|
repaintLive(w)
|
|
if w.expanded then
|
|
local n=table.getn(w.rows)
|
|
if n>0 then
|
|
-- Preserve the end of this history page, not the global
|
|
-- newest line, when settings or class colors change.
|
|
local pivot=(w.rows[n].sequence or 0)+1
|
|
local rows,more=fetch(w,"older",pivot,PAGE)
|
|
w.rows=rows;w.older=more;w.newer=true
|
|
drawRows(w)
|
|
else jump(w,"bottom") end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Refresh every detached window after the user explicitly deletes history.
|
|
-- Clear both the saved-history page and the transient visible message feed.
|
|
function MCWindows.ClearHistory()
|
|
local tab,w
|
|
for tab,w in pairs(windows) do
|
|
w.rows={}
|
|
w.older=false
|
|
w.newer=false
|
|
w.live:Clear()
|
|
w.log:Clear()
|
|
if w.count then w.count:SetText("0 loaded / 0 saved") end
|
|
end
|
|
end
|
|
|
|
function MCWindows.Message(record)
|
|
if not C then return end
|
|
local tab,w,t,r,g,b
|
|
for tab,w in pairs(windows) do
|
|
if tab.detached and C.matches(tab,record) then
|
|
t,r,g,b=C.render(tab,record)
|
|
w.live:AddMessage(t,r,g,b)
|
|
if w.expanded then
|
|
if not w.newer and w.log:AtBottom() then
|
|
table.insert(w.rows,record)
|
|
if table.getn(w.rows)>PAGE then table.remove(w.rows,1);w.older=true end
|
|
w.log:AddMessage(t,r,g,b)
|
|
else w.newer=true end
|
|
end
|
|
end
|
|
end
|
|
end
|