Skip to content
This repository was archived by the owner on Dec 18, 2017. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions Assets/cqui_toplayer.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,115 @@
--This is a layer that lives at the very top of the UI. It's an excellent place for catching inputs globally or displaying a custom overlay
-- This layer is the home of the paradox bar
include("InstanceManager");

--Members
local groupStackIM = InstanceManager:new("ParadoxBarGroupInstance", "Top", Controls.ParadoxBarStack); -- IM for the groups for the notifications
local groupStackInstances: table = {}; --Instances of the groupings for the notifications
local groupStacks: table = {}; --Instance managers for the individual notifications

--These following tables define the various behaviors a notification can contain, modifying a given effect will affect ALL notifications which use them.
--Feel free to add new behaviors here as necessary

--Paradoxbar sound
local paradoxBarSound = {
["addSound"] = function() UI.PlaySound("NOTIFICATION_MISC_NEUTRAL"); end,
["removeSound"] = function() UI.PlaySound("Map_Pin_Remove"); end
}

--Paradoxbar functions
local paradoxBarFuncs = {
["deleteOnRMB"] = function(instance, group)
instance.Button:RegisterCallback(Mouse.eRClick, function() RemoveNotification(instance, group); end);
end,
["debugPrint"] = function() print("Debug tooltip created"); end
}

--Paradoxbar behavior bundles
local paradoxBarBundles = {
["standard"] = function(instance, group) paradoxBarFuncs["deleteOnRMB"](instance, group); paradoxBarSound["addSound"](); end
}

--These are the default notification templates
--Feel free to add to this as necessary

--Paradoxbar stock data
local paradoxBarStock = {
["debug"] = {"Debug", "Controls_Circle", "This is a debug tooltip", "Dbg", {paradoxBarFuncs["debugPrint"], paradoxBarBundles["standard"]}},
["debug2"] = {"Debug2", "Controls_Circle", "This is a debug tooltip", "Dbg2", {paradoxBarFuncs["debugPrint"], paradoxBarBundles["standard"]}}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should be done with key/value pairs instead of indexes. It is much easier to get the meaning when you read the code further down:
instance.Text:SetText(defaults[4]); vs instance.Text:SetText(defaults.Text);
And it makes it safer to not mess it up when adding new properties to the table/array, if you add a new value before the text then you have change all usages of defaults[4] to defaults[5].

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be implementing this suggestion now, thanks for the heads up 👍


-- This function handles adding new notifications to the paradox bar
-- Group is used for the purposes of chunking notifications together and is mandatory
-- If an ID is supplied, paradoxBarStock is checked for a matching ID and loads presets if available. If additional values are supplied, they are used to overwrite the default values
-- text is drawn directly on top of the icon and is meant to be used lightly. It will look ugly if you use any more than a few characters
-- funcs is an array of functions used for adding behavior to the notification. This is where closing behavior and other intricacies are defined
function AddNotification(ID, group, icon, tooltip, text, funcs)
local defaults = paradoxBarStock[ID];
-- Group must be defined somehow or else we give up here
if(not group) then
group = defaults[1];
if(not group) then return; end
end

--Creates a group for a specific notification group if it does not exist
if(groupStackInstances[group] == nil) then
groupStackInstances[group] = groupStackIM:GetInstance();
groupStacks[group] = InstanceManager:new("ParadoxBarInstance", "Top", groupStackInstances[group].Stack);
end
instance = groupStacks[group]:GetInstance();

--Applies defaults where appropriate
if(defaults) then
if(defaults[2]) then
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small thing and I'm not sure how expensive these UI functions are (I guess it does nothing until a new render pass is done) But I'm not a fan of calling a function that will be called again just to overwrite the last call.

Here we already know if icon is passed so maybe change the check to if(defaults[2] and not icon) or rewrite it in some other way.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't called every render pass, this function is meant to be called once each time a new notification event is thrown. As for the UI functions starting at line 55, these only get called once when a new notification group is being created. These are relatively heavy functions, but no more potential for a performance hit compared to what you'd see when multiple vanilla notifications are created.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concerning your second point, I'm struggling to figure out what you mean, could you elaborate?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the second point: if you call AddNotification with an icon and the ID have defaults you will call
instance.Icon:SetTexture(defaults["icon"]); and then instance.Icon:SetTexture(icon);
Making the first call unnecessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that is somewhat inefficient. Alright, I'll patch that up

instance.Icon:SetTexture(defaults[2]);
end
if(defaults[3]) then
instance.Button:LocalizeAndSetToolTip(defaults[3]);
end
if(defaults[4]) then
instance.Text:SetText(defaults[4]);
end
if(defaults[5] and not funcs) then
funcs = defaults[5]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always overwrite the passed funcs if there is a default. It might be what you are after but it does not follow the pattern that the rest of the function parameters are following in that passed parameters is used instead of default.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this behavior is different because we want overriding a template to allow full control of the notification. In the scenario where we merely append new functions to the list, it would only be possible to extend a given template's behavior, as opposed to completely manipulate it. This comes with the consequence of being more verbose, but I feel more control is better if you're in a situation requiring that you create a custom notification with one-off behavior in the first place

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow what you mean. Is the idea that calls to AddNotification only should pass funcs if it is a one off, ie if the passed ID does not have defaults?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I mean, yes. If you plan to be using a specific type of notification more often than just once, it is advisable to compose a template first.

That being said, that's why these behavior bundles are included, so that function composition is more convenient and idiomatic for the purposes of composing those one-offs.

end
end
if(icon) then
instance.Icon:SetTexture(icon);
end
if(tooltip) then
instance.Button:LocalizeAndSetToolTip(tooltip);
end
if(text) then
instance.Text:SetText(text);
end
--Invokes functions
if(funcs) then
for _,v in ipairs(funcs) do
v(instance, group, icon, tooltip, text);
end
end
--Reveals completed notification
instance.Top:SetHide(false);
end

function RemoveNotification(instance, group)
instance.SlideAnimation:Reverse();
instance.SlideAnimation:RegisterEndCallback(function()
groupStacks[group]:ReleaseInstance(instance);
--This is a hacky workaround since releasing an instance doesn't necessarily actually remove it from a given stack
instance.Top:SetID("Exhausted");
for _,v in ipairs(groupStackInstances[group].Stack:GetChildren()) do
if(v:GetID() ~= "Exhausted") then return; end
end
--Removes the group stack from the main notification stack
Controls.ParadoxBarStack:ReleaseChild(groupStackInstances[group].Top);
groupStackInstances[group] = nil;
end)
end

function Initialize()
groupStackIM:ResetInstances();
LuaEvents.CQUI_AddNotification.Add(AddNotification);
ContextPtr:SetHide(false);
end

Expand Down
51 changes: 50 additions & 1 deletion Assets/cqui_toplayer.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This context is as high up on the z-axis as possible, which means it is excellent for handling mouse inputs globally and for elements that always need to appear above other things -->
<Context Name="CQUI_TopLayer">
<Container ID="Frame" Size="Full,Full">
<!-- Add an element as child of this container to place it free-floating -->

<!-- Add an element as child of this container to place it relative to where the vanilla controls reside. Set Alpha to 0.5 to visualize standins -->
<Container ID="Frame" Size="Full,Full">
<!-- Positional mockup of the top half of the UI-->
<Stack ID="StandinStackTop" Anchor="C,T" StackGrowth="Down">
<!-- Standin for the topbar area -->
<Box Anchor="C,T" Size="Full, 30" Color="Green" Alpha="0"/>
<Container ID="UnderTopBarArea" Anchor="C,T" Size="Full, 0" AutoSize="V">
<Stack Anchor="L,T" StackGrowth="Right">
<!-- Standin for the worldtracker -->
<Box Anchor="L,T" Size="370,270" Color="Red" Alpha="0"/>
<!-- CQUI paradox bar -->
<Stack ID="ParadoxBarStack" Anchor="L,T" StackGrowth="Right" StackPadding="-4"/>
</Stack>
<!-- Standin for the right button panel and diplo ribbon (when considerably full)-->
<Box Anchor="R,T" Size="650,140" Color="Beige" Alpha="0"/>
</Container>
</Stack>
<!-- Positional mockup of the bottom half of the UI -->
<Stack ID="StandinStackBottom" Anchor="C,B" StackGrowth="Up">
<Container ID="BottomArea" Anchor="C,B" Size="Full,0" AutoSize="V">
<!-- Standin for the minimap at maximum size -->
<Box Anchor="L,B" Size="780, 420" Color="Green" Alpha="0"/>
<Stack ID="StandinStackRight" Anchor="R,B" StackGrowth="Up">
<!-- Standin for the actionpanel/unitpanel area -->
<Box Anchor="R,B" Size="560, 220" Color="Red" Alpha="0"/>
<!-- Standin for the notification area -->
<Box Anchor="R,B" Size="500,parent-400" Color="Gold" Alpha="0"/>
</Stack>
</Container>
</Stack>
</Container>

<Instance Name="ParadoxBarInstance">
<Container ID="Top" AutoSize="1">
<SlideAnim ID="SlideAnimation" Size="35,35" Offset="0,0" Begin="0,-10" EndOffset="0,20" Cycle="Once" Function="OutQuad" Speed="7">
<Button ID="Button" Size="40,40" Tooltip="">
<Image Texture="Controls_CircleBacking45" Size="35,35" Anchor="C,C" Offset="0,1" StretchMode="Fill" Sampler="Linear"/>
<Image ID="Icon" Anchor="C,C" Size="35,35" Texture="" StretchMode="Fill" Sampler="Linear" />
<Label ID="Text" Anchor="C,C" Style="FontFlair14" Color="Green" FontStyle="Glow" String="Blah" Offset="0,1"/>
</Button>
</SlideAnim>
</Container>
</Instance>
<Instance Name="ParadoxBarGroupInstance">
<Container ID="Top" AutoSize="1">
<Grid Style="EnhancedToolTip" Size="55,40" Color="255,200,100,255" InnerPadding="10,5" AutoSize="V">
<Stack ID="Stack" Offset="2,-7" Anchor="L,T" StackGrowth="Down" StackPadding="-7"/>
</Grid>
</Container>
</Instance>
</Context>