-- This class is a "text cascade", which is a sequence of windows that change position, size, and/or opacity over time.
-- - It can be configured like the floating damage during battle, with each message rising up like a bubble.
-- - Or it can be configured like the large notification messages that appear in the middle of the screen, with each message showing for a few seconds, then being replaced by the next.
-- - Or it can be configured as a sequence of messages that move away into the background.
Cascade = class(Turbine.UI.Window);
math.degToRad = 2 * math.pi / 360;
function Cascade:Constructor()
Turbine.UI.Window.Constructor(self);
self.queuedWindows = {};
self.windows = {};
self.minimumDelay = 0;
self.prevAddTime = 0;
self:SetSize(self:GetSize());
self:SetPosition(self:GetPosition());
end
-- Accepts a function with parameter p that returns x, y
-- The input (p) is the progress parameter, which ranges from 0 to 1
-- The output is (x, y), where
-- x is the relative position (0 < x < 1) within the Cascade of the center of the item
-- y is the relative position (0 < y < 1) within the Cascade of the center of the item
-- To do: add z later
function Cascade:SetPathFunc(func)
self.pathFunc = func;
end
-- Accepts a function with parameter p that returns a size multiplier, between 0 and 1
-- The input (p) is the progress parameter, which ranges from 0 to 1
-- The output is the number that will be multiplied by the original dimensions to determine the window's size
function Cascade:SetSizeFunc(func)
if (func and self.rotationFunc) then
error("sizeFunc and rotationFunc are mutually exclusive", 2);
end
self.sizeFunc = func;
end
-- Accepts a function with parameter p that returns rotation
-- The input (p) is the progress parameter, which ranges from 0 to 1
-- The output is the rotation, in degrees
function Cascade:SetRotationFunc(func)
if (func and self.sizeFunc) then
error("sizeFunc and rotationFunc are mutually exclusive", 2);
end
self.rotationFunc = func;
end
-- Accepts a function with parameter p that returns opacity
-- The input (p) is the progress parameter, which ranges from 0 to 1
-- The output is the opacity, which ranges from 0 (transparent) to 1 (opaque).
function Cascade:SetOpacityFunc(func)
self.opacityFunc = func;
end
-- Accepts a function with parameter t that returns progress
-- The input (t) is the time elapsed, in seconds
-- The output is progress, a number between 0 and 1
function Cascade:SetProgressFunc(func)
self.progressFunc = func;
end
-- Allows you to specify a minimum time separation between the display of items, which will be queued
function Cascade:SetMinimumDelay(delay)
self.minimumDelay = delay;
end
function Cascade:AddItem(window)
window:SetVisible(false);
window:SetZOrder(self.zOrder);
window.originalSize = { window:GetSize() };
window.originalPosition = { window:GetPosition() };
window.destroy = false;
table.insert(self.queuedWindows, window);
self:SetWantsUpdates(true);
end
--outline = Turbine.UI.Window()
--outline:SetVisible(true);
--outline:SetBackColor(Turbine.UI.Color.Blue);
--outline.interior = Turbine.UI.Control();
--outline.interior:SetPosition(1, 1);
--outline.interior:SetBackColor(Turbine.UI.Color(0, 0, 0, 0));
--outline.interior:SetParent(outline);
--function outline:SetSize(width, height)
-- Turbine.UI.Window.SetSize(outline, width, height);
-- outline.interior:SetSize(width - 2, height - 2);
--end
function Cascade:AgeItem(window, elapsedTime)
-- Get progress as a function of elapsed time
local progress = self.progressFunc(elapsedTime);
if (progress < 1) then
-- Set the window's opacity as a function of progress
if (self.opacityFunc) then
window:SetOpacity(self.opacityFunc(progress));
end
-- Set the window's size as a function of progress
local width, height = unpack(window.originalSize);
if (self.sizeFunc) then
local size = self.sizeFunc(progress);
window:SetStretchMode(1);
width = math.floor(0.5 + width * size);
height = math.floor(0.5 + height * size);
window:SetSize(width, height);
end
local xOffset, yOffset = 0, 0;
if (self.rotationFunc) then
local theta = self.rotationFunc(progress);
local theta_radians = math.degToRad * theta;
local sin_theta, cos_theta = math.sin(theta_radians), math.cos(theta_radians);
local rotation_radius = (width - height) / 2;
local rotation_size = math.max(width, height);
xOffset = rotation_radius * sin_theta;
yOffset = rotation_radius * (cos_theta - 1);
if (width < height) then
xOffset, yOffset = -yOffset, xOffset;
end
window:SetRotation({z = theta})
window:SetSize(rotation_size, rotation_size);
--outline:SetSize(math.max(width, height), math.max(width, height));
--outline:SetPosition(window:GetPosition());
end
-- Set the window's center position as a function of progress
local x, y = unpack(window.originalPosition);
if (self.pathFunc) then
x, y = self.pathFunc(progress);
x = self.left + x * self.width;
y = self.top + y * self.height;
x = x - (width / 2);
y = y - (height / 2);
end
window:SetPosition(math.floor(0.5 + x + xOffset), math.floor(0.5 + y + yOffset));
else -- progress complete
window:SetVisible(false);
window.destroy = true;
end
end
function Cascade:Update()
local currentTime = Turbine.Engine.GetGameTime();
-- If minimum delay time has elapsed, add the next queued window.
if ((#self.queuedWindows > 0) and ((currentTime - self.prevAddTime) >= self.minimumDelay)) then
local window = table.remove(self.queuedWindows, 1);
window.startTime = currentTime;
self.windows[window] = true;
window:SetVisible(true);
self.prevAddTime = currentTime;
end
-- Age all of the existing windows
local empty = true;
for window, _ in pairs(self.windows) do
empty = false;
local elapsedTime = currentTime - window.startTime;
self:AgeItem(window, elapsedTime);
if (window.destroy) then
window:Close();
self.windows[window] = nil;
end
end
-- If there are no windows in the cascade, then no need to call the Update() function.
if (empty and (#self.queuedWindows == 0)) then
self:SetWantsUpdates(false);
end
end
--function Cascade:SetVisible(visible)
-- Turbine.UI.Window.SetVisible(self, visible);
--end
function Cascade:SetZOrder(zOrder)
self.zOrder = zOrder;
Turbine.UI.Window.SetZOrder(self, zOrder);
for window, _ in pairs(self.windows) do
window:SetZOrder(zOrder);
end
end
function Cascade:SetLeft(left)
self.left = left;
Turbine.UI.Window.SetLeft(self, left);
end
function Cascade:SetTop(top)
self.top = top;
Turbine.UI.Window.SetTop(self, top);
end
function Cascade:SetPosition(left, top)
self.left, self.top = left, top;
Turbine.UI.Window.SetPosition(self, left, top);
end
function Cascade:SetWidth(width)
self.width = width;
Turbine.UI.Window.SetWidth(self, width);
end
function Cascade:SetHeight(height)
self.height = height;
Turbine.UI.Window.SetHeight(self, height);
end
function Cascade:SetSize(width, height)
self.width, self.height = width, height;
Turbine.UI.Window.SetSize(self, width, height);
end
Thurallor = Thurallor or {};
Thurallor.UI = Thurallor.UI or {};
Thurallor.UI.Cascade = Cascade;