SequenceEditor = class(Turbine.UI.Lotro.Window);
function SequenceEditor:Constructor(bar, settings)
Turbine.UI.Lotro.Window.Constructor(self);
self.bar = bar;
self.manager = self.bar.manager;
self.settings = settings;
self:SetText(bar:GetName());
self:SetZOrder(2147483647 - 5);
self:SetResizable(true);
self:SetVisible(true);
if (not self.settings.sequenceEditor) then
self.settings.sequenceEditor = {};
end
if (self.settings.sequenceEditor.position) then
self:SetPosition(unpack(self.settings.sequenceEditor.position));
else
self:SetPosition(self.bar:GetPosition());
end
if (self.settings.sequenceEditor.size) then
self:SetSize(unpack(self.settings.sequenceEditor.size));
else
self:SetSize(470, 187);
end
self:Redraw();
end
function SequenceEditor:SetText(barName)
local title = L:GetText("/SequenceEditor/Title");
title = string.gsub(title, "<name>", barName);
Turbine.UI.Lotro.Window.SetText(self, title);
end
function SequenceEditor:SaveSettings()
self.bar:SaveSettings();
end
function SequenceEditor:Redraw()
local width, height = self:GetSize();
-- Enforce minimum size of 468 x 152.
self:SetMinimumWidth(470);
self:SetMinimumHeight(152);
local xMargin = 16;
local topMargin = 35;
local bottomMargin = 42;
if (not self.slotTabCard) then
self.slotTabCard = Thurallor.UI.TabCard();
self.slotTabCard:SetParent(self);
self.slotTabCard:SetTop(topMargin);
self.slotTabCard:SetWidth(200);
self.slotTabCard:SetTabWidth(120);
self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab"));
end
self.slotTabCard:SetLeft(width - 200 - xMargin);
self.slotTabCard:SetHeight(height - topMargin - bottomMargin);
if (not self.sequenceTabCard) then
self.sequenceTabCard = Thurallor.UI.TabCard();
self.sequenceTabCard:SetParent(self);
self.sequenceTabCard:SetPosition(xMargin, topMargin);
self.sequenceTabCard:SetTabWidth(120);
self.sequenceTabCard:SetTabText(L:GetText("/SequenceEditor/SequenceTab"));
self.slotPanel = Turbine.UI.Label();
end
self.sequenceTabCard:SetSize(self.slotTabCard:GetLeft() - xMargin - 8, self.slotTabCard:GetHeight());
self:DeleteEmptyTrailingSlots();
local slotPanelWidth = self.sequenceTabCard:GetWidth() - 17;
local slotSize = self.settings.slotSize + self.settings.slotSpacing;
self.slotsWide = math.floor((slotPanelWidth - 3) / slotSize);
local slotsHigh = math.max(math.ceil(#self.settings.sequenceItemInfo / self.slotsWide) + 1, 2);
self:ExtendSequenceTo(slotsHigh * self.slotsWide);
self.slotPanel:SetSize(self.slotsWide * slotSize + 3, slotsHigh * slotSize + 3);
self.sequenceTabCard:SetInteriorControl(self.slotPanel);
self.slotPanel:SetBackColor(self.bar.color);
-- Draw some quickslots
if (not self.sequenceItems) then
self.sequenceItems = {};
end
local s = 1;
for y = 1, slotsHigh, 1 do
for x = 1, self.slotsWide, 1 do
self:RebuildSlot(s, x, y);
s = s + 1;
end
end
if (not self.instructions) then
self.instructions = Turbine.UI.Label();
self.instructions:SetParent(self);
self.instructions:SetMouseVisible(false);
self.instructions:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter);
self.instructions:SetFont(Turbine.UI.Lotro.Font.TrajanPro15);
self.instructions:SetMultiline(true);
self.instructions:SetForeColor(Turbine.UI.Color.PaleGoldenrod);
self.instructions:SetText(L:GetText("/SequenceEditor/Instructions"));
end
self.instructions:SetSize(self:GetWidth() - 2 * xMargin, 30);
self.instructions:SetPosition(xMargin, self:GetHeight() - 40);
if (self.Marquee) then
self:SelectSlot(self.Marquee.s);
end
end
function SequenceEditor:SizeChanged()
self:Redraw();
self.settings.sequenceEditor.size = {self:GetSize()};
self:SaveSettings();
end
function SequenceEditor:PositionChanged()
self.settings.sequenceEditor.position = {self:GetPosition()};
self:SaveSettings();
end
function SequenceEditor:DisplaySlot(s)
self.slotTabCard:SetInteriorControl(nil);
self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab"));
if (s == nil) then
return;
end
self:ExtendSequenceTo(s);
local info = self.settings.sequenceItemInfo[s];
if (not info.class) then
-- Empty slot should be rendered as a standard quickslot
info.class = "Turbine.UI.Lotro.Quickslot";
info.temporary = true;
end
-- Update slot tab caption and create properties container
self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab") .. " " .. tostring(s));
self.slotProperties = Turbine.UI.Control();
self.slotTabCard:SetInteriorControl(self.slotProperties);
local width = self.slotTabCard:GetWidth() - 17;
self.slotProperties:SetWidth(width);
--self.slotProperties:SetBackColor(Turbine.UI.Color(1, 0.25, 0.25, 0.25));
-- Create the icon
local object = self:BuildSlot(s, self.slotProperties, nil, 72, 10);
object.numLabel:SetText(nil); -- don't display slot number overlay
object.numLabel.MouseClick = nil; -- disable right-click menu
-- Create a function for adding a centered line of text to the properties control
local AddLabel = function(top, text, color)
local label = Turbine.UI.Label();
label:SetParent(self.slotProperties);
label:SetPosition(0, top);
label:SetSize(180, 16);
top = top + 16;
label:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter);
label:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
label:SetForeColor(color);
label:SetMarkupEnabled(true);
label:SetText(text);
return top, label;
end
-- Create a function for adding a centered text box to the properties control
local AddTextBox = function(top, text, color)
local textBox = Turbine.UI.Lotro.TextBox();
textBox:SetParent(self.slotProperties);
textBox:SetPosition(0, top);
textBox:SetSize(180, 20);
top = top + 20;
textBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
textBox:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
textBox:SetForeColor(color);
textBox:SetText(text);
return top, textBox;
end
-- Create a function for adding a centered checkbox to the properties control
local AddCheckBox = function(top, text, color, checked)
local checkBox = Turbine.UI.Lotro.CheckBox();
checkBox:SetParent(self.slotProperties);
checkBox:SetPosition(0, top);
checkBox:SetSize(180, 20);
top = top + 20;
checkBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
checkBox:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
checkBox:SetForeColor(color);
checkBox:SetText(text);
if (checked) then
checkBox:SetChecked(true);
end
return top, checkBox;
end
-- Create a function for adding a left-justified radio button to the properties control
local AddRadioButton = function(top, text, color, checked)
local button = Thurallor.UI.RadioButton(self.slotProperties, text, checked);
button:SetPosition(0, top);
button:SetSize(180, 16);
top = top + 16;
button:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
button:SetForeColor(color);
return top, button;
end
-- Create a function for adding a drop-down listbox to the properties control
local AddDropDown = function(top, items, color, default)
local dropDown = Thurallor.UI.DropDown(items, default);
dropDown:SetParent(self.slotProperties);
dropDown:SetPosition(0, top);
dropDown:SetWidth(180);
top = top + dropDown:GetHeight();
return top, dropDown;
end
-- Draw the caption
local top = 54;
if (info.class == "Turbine.UI.Lotro.Quickslot") then
top = AddLabel(top, L:GetText("/SequenceEditor/StandardQuickslot"), Turbine.UI.Color.PaleGoldenrod);
elseif (info.class == "Turbine.UI.Control") then
top = AddLabel(top, L:GetText("/SequenceEditor/SpecialSlot") .. ":", Turbine.UI.Color.PaleGoldenrod);
top = AddLabel(top, info.toolTip, Turbine.UI.Color.PaleGoldenrod);
end
-- Draw the arguments
if (info.class == "Turbine.UI.Lotro.Quickslot") then
top = top + 16; -- skip a line
for key, value in pairs(Turbine.UI.Lotro.ShortcutType) do
if (info.type == value) then
top = AddLabel(top, L:GetText("/SequenceEditor/ShortcutType") .. ": " .. L:GetText("/SequenceEditor/ShortcutTypes/" .. key), Turbine.UI.Color.Goldenrod);
break;
end
end
elseif (info.class == "Turbine.UI.Control") then
if (info.type == "GenerateEvent") then
-- Allow the user to specify the event name
top = top + 16; -- skip a line
top = AddLabel(top, L:GetText("/SequenceEditor/EventName") .. ":", Turbine.UI.Color.Goldenrod);
local textBox;
top, textBox = AddTextBox(top, info.eventName, Turbine.UI.Color.White);
textBox.TextChanged = function(sender, args)
local text = sender:GetText();
-- Remove carriage returns
local stripped = string.gsub(text, "\n", "");
if (stripped ~= text) then
sender:SetText(stripped);
text = stripped;
end
info.eventName = text;
self.bar:ShortcutChanged();
end
elseif (info.type == "SetUnequipDestination") then
-- Allow the user to specify the desired bag slot
top = top + 16; -- skip a line
top = AddLabel(top, L:GetText("/SequenceEditor/PreferredBagSlot") .. ":", Turbine.UI.Color.Goldenrod);
local lp = Turbine.Gameplay.LocalPlayer:GetInstance();
local bags = lp:GetBackpack();
local inventory = {}, currentItemName;
for i = 1, bags:GetSize() do
table.insert(inventory, tostring(i));
if (i == info.bagSlot) then
currentItemName = itemName;
end
end
top, dropDown = AddDropDown(top, inventory, Turbine.UI.Color.White, tostring(info.bagSlot));
dropDown:SetWidth(50);
dropDown:SetLeft(65);
dropDown.ItemChanged = function(sender, args)
info.bagSlot = tonumber(args.Text);
self.bar:ShortcutChanged();
end
elseif (string.match(info.type, "^Remove ")) then
-- Allow the user to edit choose the item to be unequipped
top = top + 16; -- skip a line
top = AddLabel(top, L:GetText("/SequenceEditor/EquipmentType") .. ":", Turbine.UI.Color.Goldenrod);
local currentItem = string.sub(info.type, 8);
local prevContext = L:SetContext("/SequenceEditor/EquipmentSlots");
local eqSlots, localEqSlots = L:GetItems(), {};
for slot in values(eqSlots) do
table.insert(localEqSlots, L:GetText(slot));
end
table.sort(localEqSlots);
local dropDown;
top, dropDown = AddDropDown(top, localEqSlots, Turbine.UI.Color.White, L:GetText(currentItem));
dropDown.ItemChanged = function(sender, args)
for slot in values(eqSlots) do
if (L:GetText(slot) == args.Text) then
self:CreateRemovalSlot(s, slot, info.bagSlot);
break;
end
end
end
end
-- Automatic mode is not allowed for slot 1. It also doesn't make sense for "Stop animating".
if ((s > 1) and (info.toolTip ~= L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/StopAnimating"))) then
top = top + 16; -- skip a line
top = AddLabel(top, L:GetText("/SequenceEditor/Activation") .. ":", Turbine.UI.Color.Goldenrod);
local button1, button2;
top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/WithLeftClick"), Turbine.UI.Color.DarkGoldenrod, (not info.automatic));
top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/Automatic"), Turbine.UI.Color.DarkGoldenrod, info.automatic);
Thurallor.UI.RadioButton.LinkPeers({button1, button2});
button1.Clicked = function()
info.automatic = nil;
self.bar:ShortcutChanged();
end
button2.Clicked = function()
info.automatic = true;
self.bar:ShortcutChanged();
end
end
end
if (info.temporary) then
-- Delete temporary empty slot
info.class = nil;
info.temporary = nil;
end
self.slotProperties:SetHeight(top);
end
function SequenceEditor:BuildSlot(s, parent, object, left, top)
-- Get slot info, or initialize to default
local info = self.settings.sequenceItemInfo[s];
if (type(info) ~= "table") then
info = {};
end
if (not info.class) then
-- Empty slot should be rendered as a standard quickslot
info.class = "Turbine.UI.Lotro.Quickslot";
info.temporary = true;
end
-- Discard old object, if it's a different type
if (object and (object.class ~= info.class)) then
if (object.numLabel) then
object.numLabel:SetParent(nil);
end
object:SetParent(nil);
object = nil;
end
-- Create the quickslot object or command icon
if (info.class == "Turbine.UI.Lotro.Quickslot") then
if (not object) then
object = Turbine.UI.Lotro.Quickslot();
end
object.ShortcutChanged = nil;
object:SetShortcut(Turbine.UI.Lotro.Shortcut(info.type, info.Data));
object.isQuickslot = true;
object:SetPosition(left, top);
elseif (info.class == "Turbine.UI.Control") then
if (not object) then
object = Turbine.UI.Control();
end
object:SetBackground(info.background);
object.isQuickslot = false;
object:SetSize(32, 32);
object:SetPosition(left + 3, top + 3);
end
object.class = info.class;
object.s = s;
object:SetParent(parent);
object:SetZOrder(parent:GetZOrder() + 2);
object:SetBlendMode(Turbine.UI.BlendMode.Overlay)
object:SetEnabled(false);
object:SetMouseVisible(false);
-- Create slot number overlay, which accepts mouse events
local numLabel = object.numLabel;
if (not numLabel) then
object.numLabel = Turbine.UI.Label();
numLabel = object.numLabel;
numLabel.object = object;
numLabel:SetParent(parent);
numLabel:SetFont(Turbine.UI.Lotro.Font.TrajanPro15);
numLabel:SetFontStyle(Turbine.UI.FontStyle.Outline);
numLabel:SetOutlineColor(Turbine.UI.Color(1, 0, 0, 0));
numLabel:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter);
numLabel:SetText(tonumber(s));
numLabel:SetSize(32, 32);
numLabel:SetZOrder(object:GetZOrder() + 1);
numLabel:SetMouseVisible(true);
numLabel:SetAllowDrop(true);
end
numLabel:SetVisible(true);
numLabel:SetPosition(left + 3, top + 3);
-- Desired mouse behavior:
-- Left-click should do nothing. (Maybe flash a red 'X' to indicate that nothing happened.)
-- The 'numLabel' object should handle right-click events.
-- The 'quickslot' object should accept 'drop' operations.
-- Ideally the quickslot should be draggable, but this may not be possible.
numLabel.DragDrop = function(sender, args)
local s = sender.object.s;
self:ExtendSequenceTo(s);
self.settings.sequenceItemInfo[s] = {};
local info = self.settings.sequenceItemInfo[s];
info.class = "Turbine.UI.Lotro.Quickslot";
info.type = args.DragDropInfo:GetShortcut():GetType();
info.Data = args.DragDropInfo:GetShortcut():GetData();
self:RebuildSlot(s);
self.bar:ShortcutChanged();
self:Redraw();
self:SelectSlot(s);
-- Don't remove the item/skill from the location where it was dragged from.
args.DragDropInfo:SetSuccessful(false);
end
numLabel.MouseClick = function(sender, args)
-- Left-click selects the slot and shows its properties in the "Slot" tab.
if (args.Button == Turbine.UI.MouseButton.Left) then
self:SelectSlot(sender.object.s);
-- Right-click displays the settings menu.
elseif (args.Button == Turbine.UI.MouseButton.Right) then
self.clickedItem = sender.object.s;
self:ShowContextMenu();
end
end
if (info.temporary) then
-- Delete temporary empty slot
info.class = nil;
info.temporary = nil;
end
return object;
end
function SequenceEditor:RebuildSlot(s, x, y)
local oldObject = self.sequenceItems[s];
-- If x and y aren't specified, then presumably the object has already been positioned previously
if (x == nil) then
x = oldObject.x;
y = oldObject.y;
end
local left, top = self:GetSlotPosition(x, y);
local newObject = self:BuildSlot(s, self.slotPanel, oldObject, left, top);
newObject.x = x;
newObject.y = y;
self.sequenceItems[s] = newObject;
if (self.selectedSlot == s) then
self:DisplaySlot(s);
end
end
function SequenceEditor:SelectSlot(s)
if (self.Marquee) then
self.Marquee:SetParent(nil);
self.Marquee = nil;
end
if (self.selectedSlot) then
local prevObject = self.settings.sequenceItemInfo[self.selectedSlot];
if (prevObject and (prevObject.class == "Turbine.UI.Lotro.Quickslot") and (not prevObject.type)) then
-- Delete empty shortcut
prevObject.class = nil;
end
end
if (s ~= nil) then
self:ExtendSequenceTo(s);
object = self.sequenceItems[s];
self.Marquee = Thurallor.UI.Marquee();
self.Marquee.s = s;
self.Marquee:SetParent(self.slotPanel);
local left, top = self.slotPanel:PointToClient(object.numLabel:PointToScreen(0, 0));
local width, height = object.numLabel:GetSize();
self.Marquee:SetPosition(left - 1, top - 1);
self.Marquee:SetSize(width + 2, height + 2);
self.Marquee:SetMouseVisible(false);
end
self.selectedSlot = s;
self:DisplaySlot(s);
end
function SequenceEditor:GetSlotPosition(x, y)
local left = (x - 1) * (self.settings.slotSize + self.settings.slotSpacing) - self.settings.slotSpacing;
local top = (y - 1) * (self.settings.slotSize + self.settings.slotSpacing) - self.settings.slotSpacing;
return left, top;
end
function SequenceEditor:Closing()
self:DeleteEmptyTrailingSlots();
self.bar:ShortcutChanged();
end
function SequenceEditor:Close()
Turbine.UI.Lotro.Window.Close(self);
end
function SequenceEditor:ShowContextMenu()
-- Build the settings menu.
if (not self.settingsMenu) then
self.settingsMenu = Turbine.UI.ContextMenu();
end
local prevContext = L:SetContext("/SequenceEditor/RightClickMenu");
self:AddSettingsMenuItem(self.settingsMenu, "Root", false);
L:SetContext(prevContext);
self.settingsMenu:ShowMenu();
end
function SequenceEditor:AddSettingsMenuItem(parent, itemName)
local item = Turbine.UI.MenuItem(L:GetText(itemName), true, false);
parent:GetItems():Add(item);
if (itemName == "Root") then
parent:GetItems():Clear();
self:AddSettingsMenuItem(parent, "InsertEmptySlot");
self:AddSettingsMenuItem(parent, "DeleteSlot");
self:AddSettingsMenuItem(parent, "CreateSpecialSlot");
elseif (itemName == "InsertEmptySlot") then
item.Click = function(sender, args)
self:InsertEmptySlot(self.clickedItem);
end
elseif (itemName == ("DeleteSlot")) then
item.Click = function(sender, args)
self:DeleteSlot(self.clickedItem);
end
elseif (itemName == "CreateSpecialSlot") then
local prevContext = L:SetContext("SpecialSlotMenu");
self:AddSettingsMenuItem(item, "RemoveEquipment");
self:AddSettingsMenuItem(item, "StopAnimating");
self:AddSettingsMenuItem(item, "GenerateEvent");
self:AddSettingsMenuItem(item, "SetUnequipDestination");
L:SetContext(prevContext);
elseif (string.find("StopAnimating|GenerateEvent|SetUnequipDestination", itemName)) then
item.Click = function(sender, args)
self:CreateCommandSlot(self.clickedItem, itemName, item:GetText());
end
elseif (itemName == "RemoveEquipment") then
item.Click = function(sender, args)
self:CreateRemovalSlot(self.clickedItem, "Gloves");
end
elseif (string.sub(itemName, 1, 6) == "Remove") then
end
end
function SequenceEditor:CreateCommandSlot(s, slotName, toolTip)
self:ExtendSequenceTo(s);
local info = self.settings.sequenceItemInfo[s];
info.class = "Turbine.UI.Control";
info.background = resources.Icon[slotName];
if (slotName == "StopAnimating") then
info.action = "local item, args = ...; item.bar.animationStopped = true;";
elseif (slotName == "GenerateEvent") then
info.eventName = "New Event";
info.action = "local item, args = ...; item.bar.manager:PropagateEvent(item.eventName);";
elseif (slotName == "SetUnequipDestination") then
info.bagSlot = 1;
info.action = "local item, args = ...; item.bar.manager:SetUnequipDestination(item.bagSlot);";
end
info.type = slotName;
info.toolTip = toolTip;
self:RebuildSlot(s);
self.bar:ShortcutChanged();
self:Redraw();
self:SelectSlot(s);
end
function SequenceEditor:CreateRemovalSlot(s, eqSlotName, bagSlot)
self:ExtendSequenceTo(s);
local info = self.settings.sequenceItemInfo[s];
info.class = "Turbine.UI.Control";
info.background = resources.Icon["Unequip" .. eqSlotName];
info.action = "local item, args = ...; item.bar.manager:Unequip(Turbine.Gameplay.Equipment." .. eqSlotName .. ");";
info.type = "Remove " .. eqSlotName;
local prevContext = L:SetContext("/SequenceEditor");
local toolTip = L:GetText("RemoveItem");
L:SetContext("EquipmentSlots");
local localEqSlotName = L:GetText(eqSlotName);
info.toolTip = string.gsub(toolTip, "<item>", localEqSlotName);
L:SetContext(prevContext);
self:RebuildSlot(s);
self.bar:ShortcutChanged();
self:Redraw();
self:SelectSlot(s);
end
function SequenceEditor:ExtendSequenceTo(s)
if (s <= #self.settings.sequenceItemInfo) then
return false;
end
while (s > #self.settings.sequenceItemInfo) do
table.insert(self.settings.sequenceItemInfo, {});
end
return true;
end
function SequenceEditor:DeleteEmptyTrailingSlots()
local sequenceItemInfo = self.settings.sequenceItemInfo;
for s = #sequenceItemInfo, 1, -1 do
local info = sequenceItemInfo[s];
if (not info.class) then
table.remove(sequenceItemInfo, s);
-- Remove empty slot displays
if (self.sequenceItems) then
local object = self.sequenceItems[s];
if (object) then
if (object.numLabel) then
object.numLabel:SetParent(nil);
end
object:SetParent(nil);
self.sequenceItems[s] = nil;
end
end
else
break;
end
end
end
function SequenceEditor:InsertEmptySlot(s)
if (s > #self.settings.sequenceItemInfo) then
self:ExtendSequenceTo(s);
else
table.insert(self.settings.sequenceItemInfo, s, {});
end
self:Redraw();
self.bar:ShortcutChanged();
self:SelectSlot(s);
end
function SequenceEditor:DeleteSlot(s)
table.remove(self.settings.sequenceItemInfo, s);
self:Redraw();
self.bar:ShortcutChanged();
self:ExtendSequenceTo(s);
self:SelectSlot(nil);
end