Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb61666031 | ||
|
c4e9020157 | ||
|
0c0b71d20a | ||
|
bf2709874a | ||
|
207dcc009d | ||
|
43f697915d | ||
|
a2bc7709eb | ||
|
9e7c924c79 | ||
|
b59f501180 | ||
|
f5268009cd | ||
|
787ed32c9a | ||
|
eeb1218b58 | ||
|
477c16f4bc |
14 changed files with 823 additions and 412 deletions
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "RNG Fight",
|
"name": "UIM Manager",
|
||||||
"tree": {
|
"tree": {
|
||||||
"$className": "DataModel",
|
"$className": "DataModel",
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
local RunService = game:GetService("RunService")
|
||||||
|
local UserInputService = game:GetService("UserInputService")
|
||||||
local replicated = game:GetService("ReplicatedStorage")
|
local replicated = game:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
|
local Draggable = require(replicated.Modules.Managers.UIManager.UIObjects.Draggable)
|
||||||
local types = require(replicated.Modules.Managers.UIManager.Types)
|
local types = require(replicated.Modules.Managers.UIManager.Types)
|
||||||
local Component = require(replicated.Modules.Managers.UIManager.Component)
|
local Component = require(replicated.Modules.Managers.UIManager.Component)
|
||||||
|
|
||||||
|
@ -15,11 +18,33 @@ component:OnBuild(function(self: types.Component): ()
|
||||||
|
|
||||||
self.Frame.Parent = self.Parent.Frame
|
self.Frame.Parent = self.Parent.Frame
|
||||||
|
|
||||||
task.delay(2, self.Update, self, "Size")
|
-- local draggablePage = Draggable.new("Test")
|
||||||
|
-- draggablePage:OnBuild(function(draggable: types.Draggable)
|
||||||
|
-- local button = Instance.new("TextButton")
|
||||||
|
-- button.Size = UDim2.fromScale(1, 1)
|
||||||
|
-- button.Position = UDim2.fromScale(0, 0)
|
||||||
|
-- button.BackgroundColor3 = Color3.fromHSV(0.805556, 0.611765, 1.000000)
|
||||||
|
-- button.Parent = self.Frame
|
||||||
|
-- draggable.DragButton = button
|
||||||
|
-- end)
|
||||||
|
-- draggablePage:OnDragStart(function(draggable: types.Draggable)
|
||||||
|
-- self:Update("DragLoop", { DragState = true })
|
||||||
|
-- end)
|
||||||
|
-- draggablePage:OnDragStop(function(draggable: types.Draggable)
|
||||||
|
-- print("stopped!!!")
|
||||||
|
-- end)
|
||||||
|
|
||||||
|
-- draggablePage:Build(self)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
component:OnUpdate("Size", function(self: types.Component, _: {}?)
|
component:OnUpdate("DragLoop", function(self: types.Component, parameters: { DragState: boolean }?)
|
||||||
self.Frame.Size = UDim2.fromScale(0.1, 0.1)
|
if parameters and parameters.DragState then
|
||||||
|
self.Container:Connect(RunService.Heartbeat, function()
|
||||||
|
print(UserInputService:GetMouseLocation().X)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
self.Container:Clean()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return component
|
return component
|
|
@ -6,19 +6,21 @@ local Page = require(replicated.Modules.Managers.UIManager.Page)
|
||||||
local page = Page.new(script.Name)
|
local page = Page.new(script.Name)
|
||||||
|
|
||||||
page:OnBuild(function(self: types.Page): ()
|
page:OnBuild(function(self: types.Page): ()
|
||||||
self.Frame = Instance.new("Frame")
|
local frame = Instance.new("Frame")
|
||||||
|
frame.Visible = false
|
||||||
|
frame.Size = UDim2.fromScale(0.5, 0.5)
|
||||||
|
frame.Position = UDim2.fromScale(0.5, 0.5)
|
||||||
|
frame.Parent = self.Parent.ScreenGui
|
||||||
|
|
||||||
self.Frame.Visible = false
|
self.Frame = frame
|
||||||
self.Frame.Size = UDim2.fromScale(0.2, 0.2)
|
self.Container:Add(frame) --Because we creating it each time, and not just locating it from the cloned ScreenGui, we need to make sure it gets cleaned on page close.
|
||||||
self.Frame.Position = UDim2.fromScale(0.5, 0.5)
|
|
||||||
|
|
||||||
self.Frame.Parent = self.Parent.ScreenGui
|
|
||||||
|
|
||||||
for _, component: Instance in script.Components:GetChildren() do
|
for _, component: Instance in script.Components:GetChildren() do
|
||||||
if not component:IsA("ModuleScript") then continue end
|
if not component:IsA("ModuleScript") then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
self:AddComponent(require(component))
|
self:AddComponent(require(component)).AsOpened()
|
||||||
self:OpenComponent(component.Name)
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,13 @@ window:OnBuild(function(self: types.Window): ()
|
||||||
|
|
||||||
for _, page: Instance in script.Pages:GetChildren() do
|
for _, page: Instance in script.Pages:GetChildren() do
|
||||||
if not page:IsA("ModuleScript") then continue end
|
if not page:IsA("ModuleScript") then continue end
|
||||||
|
|
||||||
|
if page.Name == DEFAULT_PAGE then
|
||||||
|
self:AddPage(require(page)).AsOpened()
|
||||||
|
else
|
||||||
self:AddPage(require(page))
|
self:AddPage(require(page))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
self:OpenPage(DEFAULT_PAGE)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return window
|
return window
|
|
@ -5,12 +5,3 @@ uiManager:Build(script.Parent.Test:GetChildren() :: {})
|
||||||
local default = uiManager:GetWindow("window")
|
local default = uiManager:GetWindow("window")
|
||||||
|
|
||||||
uiManager:OpenWindow(default)
|
uiManager:OpenWindow(default)
|
||||||
|
|
||||||
task.wait(10)
|
|
||||||
|
|
||||||
uiManager:CloseWindow(default)
|
|
||||||
|
|
||||||
task.wait(10)
|
|
||||||
|
|
||||||
uiManager:OpenWindow(default)
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
local types = require(script.Parent.Types)
|
local types = require(script.Parent.Types)
|
||||||
|
|
||||||
local button: types.ButtonClass = {} :: types.ButtonClass
|
local button = {} :: types.ButtonClass
|
||||||
button["__index"] = button
|
button["__index"] = button
|
||||||
|
|
||||||
local module = {}
|
local module = {}
|
||||||
|
|
||||||
function module.new(name: string): types.Button
|
function module.new(name: string): types.Button
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
|
Status = "Stored",
|
||||||
Name= name,
|
Name= name,
|
||||||
_ = {},
|
_ = {},
|
||||||
}, button)
|
}, button)
|
||||||
|
@ -34,6 +35,14 @@ function button:Build(parent: types.Page | types.Component)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function button:GetStatus(): "Built" | "Stored"
|
||||||
|
return self._.Status
|
||||||
|
end
|
||||||
|
|
||||||
|
function button:StatusIs(status: "Built" | "Stored")
|
||||||
|
return status == self._.Status
|
||||||
|
end
|
||||||
|
|
||||||
function button:Open()
|
function button:Open()
|
||||||
if self.ButtonGui then
|
if self.ButtonGui then
|
||||||
self.ButtonGui.Active = true
|
self.ButtonGui.Active = true
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
local objectList = require(script.Parent.ObjectList)
|
||||||
local types = require(script.Parent.Types)
|
local types = require(script.Parent.Types)
|
||||||
|
|
||||||
local component: types.ComponentClass = {} :: types.ComponentClass
|
local component = {} :: types.ComponentClass
|
||||||
component["__index"] = component
|
component["__index"] = component
|
||||||
|
|
||||||
local module = {}
|
local module = {}
|
||||||
|
@ -8,51 +9,18 @@ local module = {}
|
||||||
function module.new(name: string): types.Component
|
function module.new(name: string): types.Component
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
Name= name,
|
Name= name,
|
||||||
Buttons = {
|
Buttons = objectList.new(),
|
||||||
Active = {},
|
|
||||||
Stored = {},
|
|
||||||
},
|
|
||||||
|
|
||||||
_ = {
|
_ = {
|
||||||
UpdateCallbacks = {},
|
UpdateCallbacks = {},
|
||||||
},
|
},
|
||||||
}, component)
|
}, component)
|
||||||
|
|
||||||
|
self.Buttons.Parent = self
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[ Buttons ]]
|
|
||||||
|
|
||||||
function component:BuildButtons()
|
|
||||||
for _, button: types.Button in self.Buttons.Stored do
|
|
||||||
button:Build(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function component:AddButton(button: types.Button)
|
|
||||||
self.Buttons.Stored[button.Name] = button
|
|
||||||
end
|
|
||||||
|
|
||||||
function component:CloseAllButtons()
|
|
||||||
for _, button in self.Buttons.Active do
|
|
||||||
button:Close()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Buttons.Active = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function component:RemoveButtons()
|
|
||||||
self:CloseAllButtons()
|
|
||||||
|
|
||||||
for _, button in self.Buttons.Stored do
|
|
||||||
button:Remove()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Buttons.Stored = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
function component:OnBuild(callback: (self: types.Component) -> ()): ()
|
function component:OnBuild(callback: (self: types.Component) -> ()): ()
|
||||||
self._.OnBuild = callback
|
self._.OnBuild = callback
|
||||||
end
|
end
|
||||||
|
@ -80,6 +48,16 @@ function component:Build(parent: types.Page): ()
|
||||||
if self._.OnBuild then
|
if self._.OnBuild then
|
||||||
self._.OnBuild(self)
|
self._.OnBuild(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self._.Status = "Built"
|
||||||
|
end
|
||||||
|
|
||||||
|
function component:GetStatus(): "Built" | "Stored"
|
||||||
|
return self._.Status
|
||||||
|
end
|
||||||
|
|
||||||
|
function component:StatusIs(status: "Built" | "Stored")
|
||||||
|
return status == self._.Status
|
||||||
end
|
end
|
||||||
|
|
||||||
function component:Update(command: string, parameters: {}?): ()
|
function component:Update(command: string, parameters: {}?): ()
|
||||||
|
@ -111,17 +89,19 @@ function component:Close(): ()
|
||||||
end
|
end
|
||||||
|
|
||||||
function component:Clean(): ()
|
function component:Clean(): ()
|
||||||
self:CloseAllButtons()
|
self.Buttons:CleanAll()
|
||||||
|
|
||||||
if self.Container then
|
if self.Container then
|
||||||
self.Container:Remove()
|
self.Container:Remove()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self._.Status = "Stored"
|
||||||
end
|
end
|
||||||
|
|
||||||
function component:Remove(): ()
|
function component:Remove(): ()
|
||||||
self:Clean()
|
self:Clean()
|
||||||
|
|
||||||
self:RemoveButtons()
|
self.Buttons:RemoveAll()
|
||||||
|
|
||||||
if self.Frame then
|
if self.Frame then
|
||||||
self.Frame:Destroy()
|
self.Frame:Destroy()
|
||||||
|
|
126
src/Shared/Modules/Managers/UIManager/ObjectList.luau
Normal file
126
src/Shared/Modules/Managers/UIManager/ObjectList.luau
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
local types = require(script.Parent.Types)
|
||||||
|
|
||||||
|
local module = {}
|
||||||
|
|
||||||
|
local list = {}
|
||||||
|
list["__index"] = list
|
||||||
|
|
||||||
|
function module.new<T, P>(): types.ObjectList<T, P>
|
||||||
|
local self = setmetatable({
|
||||||
|
Parent = nil,
|
||||||
|
|
||||||
|
Opened = {},
|
||||||
|
Built = {},
|
||||||
|
Stored = {},
|
||||||
|
}, list)
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:Add(object: types.ListableObject)
|
||||||
|
self.Stored[object.Name] = object
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:Get(name: string): types.ListableObject?
|
||||||
|
return self.Stored[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:Remove(name: string)
|
||||||
|
self:Clean(name)
|
||||||
|
self.Stored[name] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:RemoveAll()
|
||||||
|
self:CleanAll()
|
||||||
|
self.Stored = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:Open(name: string, mode: ("Weighted" | "Forced")?)
|
||||||
|
local objectToBuild = self.Built[name]
|
||||||
|
if objectToBuild and objectToBuild:StatusIs("Built") then
|
||||||
|
if mode and mode == "Weighted" and objectToBuild.Weight then
|
||||||
|
for openName: string, status: boolean in self.Opened do
|
||||||
|
local openObject = self.Built[openName]
|
||||||
|
if status == true and openObject.Weight and openObject.Weight < objectToBuild.Weight then
|
||||||
|
self:Close(openName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif mode == "Forced" then
|
||||||
|
self:CloseAll()
|
||||||
|
end
|
||||||
|
|
||||||
|
objectToBuild:Open()
|
||||||
|
self.Opened[name] = true
|
||||||
|
|
||||||
|
self.LastOpenedName = self.RecentlyOpenedName or name
|
||||||
|
self.RecentlyOpenedName = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:OpenAll()
|
||||||
|
for name: string, _: types.ListableObject in self.Stored do
|
||||||
|
self:Open(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:OpenPrevious()
|
||||||
|
self:Open(self.LastOpenedName, "Forced")
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:Close(name: string)
|
||||||
|
if self.Built[name] then
|
||||||
|
self.Built[name]:Close()
|
||||||
|
self.Opened[name] = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:CloseAll()
|
||||||
|
for name: string, status: boolean in self.Opened do
|
||||||
|
if status == true then
|
||||||
|
self:Close(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.Opened = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:Build(name: string)
|
||||||
|
local object = self.Stored[name]
|
||||||
|
if object and not object:StatusIs("Built") then
|
||||||
|
object:Build(self.Parent)
|
||||||
|
self.Built[name] = object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:BuildAll()
|
||||||
|
for _, object in self.Stored do
|
||||||
|
if object:StatusIs("Built") then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
task.spawn(function()
|
||||||
|
object:Build(self.Parent)
|
||||||
|
self.Built[object.Name] = object
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:Clean(name: string)
|
||||||
|
local object = self.Built[name]
|
||||||
|
if object then
|
||||||
|
if object["Clean"] then
|
||||||
|
self.Built[name]:Clean()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.Built[name] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function list:CleanAll()
|
||||||
|
for _, object in self.Stored do
|
||||||
|
self:Clean(object.Name)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.Built = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return module
|
|
@ -1,6 +1,7 @@
|
||||||
|
local objectList = require(script.Parent.ObjectList)
|
||||||
local types = require(script.Parent.Types)
|
local types = require(script.Parent.Types)
|
||||||
|
|
||||||
local page: types.PageClass = {} :: types.PageClass
|
local page = {} :: types.PageClass
|
||||||
page["__index"] = page
|
page["__index"] = page
|
||||||
|
|
||||||
local module = {}
|
local module = {}
|
||||||
|
@ -10,136 +11,18 @@ function module.new(name: string): types.Page
|
||||||
Name= name,
|
Name= name,
|
||||||
Weight = 0,
|
Weight = 0,
|
||||||
|
|
||||||
Buttons = {
|
Buttons = objectList.new() :: types.ObjectList<types.Button, types.Page>,
|
||||||
Active = {},
|
Components = objectList.new(),
|
||||||
Stored = {},
|
|
||||||
},
|
|
||||||
Components = {
|
|
||||||
Open = {},
|
|
||||||
Stored = {},
|
|
||||||
},
|
|
||||||
|
|
||||||
_ = {},
|
_ = {},
|
||||||
}, page)
|
}, page)
|
||||||
|
|
||||||
|
self.Buttons.Parent = self
|
||||||
|
self.Components.Parent = self
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[ Buttons ]]
|
|
||||||
|
|
||||||
function page:BuildButtons()
|
|
||||||
for _, button: types.Button in self.Buttons.Stored do
|
|
||||||
button:Build(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:AddButton(button: types.Button)
|
|
||||||
self.Buttons.Stored[button.Name] = button
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:GetButton(button: string): types.Component
|
|
||||||
return self.Buttons.Stored[button]
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:OpenButton(component: string)
|
|
||||||
local buttonModule= self.Buttons.Stored[component]
|
|
||||||
if buttonModule then
|
|
||||||
buttonModule:Build(self)
|
|
||||||
buttonModule:Open()
|
|
||||||
self.Buttons.Active[component] = buttonModule
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:CloseButton(component: string)
|
|
||||||
local buttonModule= self.Buttons.Active[component]
|
|
||||||
if buttonModule then
|
|
||||||
buttonModule:Close()
|
|
||||||
buttonModule:Clean()
|
|
||||||
self.Buttons.Active[component] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:CloseAllButtons()
|
|
||||||
for _, button: types.Button in self.Buttons.Active do
|
|
||||||
button:Close()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Buttons.Active = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:RemoveButtons()
|
|
||||||
self:CloseAllButtons()
|
|
||||||
|
|
||||||
for _, button: types.Button in self.Buttons.Stored do
|
|
||||||
button:Remove()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Buttons.Stored = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
--[[ Components ]]
|
|
||||||
|
|
||||||
function page:BuildComponents()
|
|
||||||
for _, component: types.Button in self.Components.Stored do
|
|
||||||
component:Build(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:AddComponent(component: types.Component)
|
|
||||||
self.Components.Stored[component.Name] = component
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:GetComponent(component: string): types.Component
|
|
||||||
return self.Components.Stored[component]
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:OpenComponent(component: string)
|
|
||||||
local componentModule= self.Components.Stored[component]
|
|
||||||
if componentModule then
|
|
||||||
componentModule:Build(self)
|
|
||||||
componentModule:Open()
|
|
||||||
self.Components.Open[component] = componentModule
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:CloseComponent(component: string)
|
|
||||||
local componentModule= self.Components.Open[component]
|
|
||||||
if componentModule then
|
|
||||||
componentModule:Close()
|
|
||||||
componentModule:Clean()
|
|
||||||
self.Components.Open[component] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:CleanComponents()
|
|
||||||
for _, component: types.Component in self.Components.Open do
|
|
||||||
component:Close()
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, component: types.Component in self.Components.Stored do
|
|
||||||
component:Clean()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Components.Open = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:RemoveComponents()
|
|
||||||
for _, component: types.Component in self.Components.Open do
|
|
||||||
component:Close()
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, component: types.Component in self.Components.Stored do
|
|
||||||
component:Remove()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Components.Open = {}
|
|
||||||
self.Components.Stored = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
--[[ Page ]]
|
--[[ Page ]]
|
||||||
|
|
||||||
function page:OnBuild(callback: (self: types.Page) -> ())
|
function page:OnBuild(callback: (self: types.Page) -> ())
|
||||||
|
@ -159,11 +42,22 @@ function page:Build(parent: types.Window)
|
||||||
|
|
||||||
self.Parent = parent
|
self.Parent = parent
|
||||||
|
|
||||||
self.Container = parent.Manager.Packages.Trove.new()
|
local manager = parent.Parent
|
||||||
|
self.Container = manager.Packages.Trove.new()
|
||||||
|
|
||||||
if self._.OnBuild then
|
if self._.OnBuild then
|
||||||
self._.OnBuild(self)
|
self._.OnBuild(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self._.Status = "Built"
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:GetStatus(): "Built" | "Stored"
|
||||||
|
return self._.Status
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:StatusIs(status: "Built" | "Stored")
|
||||||
|
return status == self._.Status
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:Open()
|
function page:Open()
|
||||||
|
@ -193,19 +87,24 @@ function page:Close()
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:Clean()
|
function page:Clean()
|
||||||
self:CleanComponents()
|
self.Components:CleanAll()
|
||||||
self:CloseAllButtons()
|
self.Buttons:CleanAll()
|
||||||
|
|
||||||
if self.Container then
|
if self.Container then
|
||||||
self.Container:Remove()
|
self.Container:Destroy()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self._.Status = "Stored"
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:Remove()
|
function page:Remove()
|
||||||
self:Clean()
|
self:Clean()
|
||||||
|
|
||||||
self:RemoveComponents()
|
self.Components:CloseAll()
|
||||||
self:RemoveButtons()
|
self.Components:RemoveAll()
|
||||||
|
|
||||||
|
self.Buttons:CloseAll()
|
||||||
|
self.Buttons:RemoveAll()
|
||||||
|
|
||||||
if self.Frame then
|
if self.Frame then
|
||||||
self.Frame:Destroy()
|
self.Frame:Destroy()
|
||||||
|
@ -215,7 +114,7 @@ end
|
||||||
--[[ Helper ]]
|
--[[ Helper ]]
|
||||||
|
|
||||||
function page:GetManager(): types.Manager
|
function page:GetManager(): types.Manager
|
||||||
return self.Parent.Manager
|
return self.Parent.Parent
|
||||||
end
|
end
|
||||||
|
|
||||||
return module
|
return module
|
24
src/Shared/Modules/Managers/UIManager/Template.luau
Normal file
24
src/Shared/Modules/Managers/UIManager/Template.luau
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
local types = require(script.Parent.Types)
|
||||||
|
|
||||||
|
local module = {}
|
||||||
|
|
||||||
|
local template = {}
|
||||||
|
template["__index"] = template
|
||||||
|
|
||||||
|
function module.new<Object, MethodOptions, Parameters>(): types.Template<Object, MethodOptions, Parameters>
|
||||||
|
return setmetatable({
|
||||||
|
Files = {},
|
||||||
|
} :: types.TemplateClass<Object, MethodOptions, Parameters>, template) :: types.Template<Object, MethodOptions, Parameters>
|
||||||
|
end
|
||||||
|
|
||||||
|
function template:Create<Object, MethodOptions, Parameters>(method: MethodOptions, callback: (self: Object, parameters: Parameters?) -> ()): ()
|
||||||
|
self.Files[method] = callback
|
||||||
|
end
|
||||||
|
|
||||||
|
function template:Fire<Object, MethodOptions, Parameters>(method: MethodOptions, object: Object, parameters: Parameters?): ()
|
||||||
|
if self.Files[method] then
|
||||||
|
self.Files[method](object, parameters)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return module
|
|
@ -1,33 +1,105 @@
|
||||||
local replicated = game:GetService("ReplicatedStorage")
|
local replicated = game:GetService("ReplicatedStorage")
|
||||||
|
local signal = require(replicated.Modules.Utility.Signal)
|
||||||
local trove = require(replicated.Modules.Utility.Trove)
|
local trove = require(replicated.Modules.Utility.Trove)
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
|
--[[ == Template == ]]
|
||||||
|
|
||||||
|
type TemplateClass<Object, MethodOptions, Parameters> = {
|
||||||
|
Files: {
|
||||||
|
[string]: (self: Object, parameters: {}?) -> ()
|
||||||
|
},
|
||||||
|
Create: ((self: Template<Object, MethodOptions, Parameters>, method: MethodOptions, callback: (self: Object, parameters: Parameters?) -> ()) -> ()),
|
||||||
|
Fire: ((self: Template<Object, MethodOptions, Parameters>, method: MethodOptions, object: Object, parameters: Parameters?) -> ()),
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Template<Object, MethodOptions, Parameters> = typeof(setmetatable({} :: TemplateClass<Object, MethodOptions, Parameters>, {}))
|
||||||
|
|
||||||
|
--[[ == ObjectList == ]]
|
||||||
|
|
||||||
|
export type ListableObject = (Button | Component | Page)
|
||||||
|
|
||||||
|
export type ObjectListClass<LO, P> = {
|
||||||
|
Parent: P,
|
||||||
|
|
||||||
|
Opened: { [string]: boolean },
|
||||||
|
Built: { [string]: LO },
|
||||||
|
Stored: { [string]: LO },
|
||||||
|
|
||||||
|
RecentlyOpenedName: string,
|
||||||
|
LastOpenedName: string,
|
||||||
|
|
||||||
|
Add: (self: ObjectList<LO, P>, object: LO) -> (),
|
||||||
|
Get: (self: ObjectList<LO, P>, name: string) -> (LO?),
|
||||||
|
Remove: (self: ObjectList<LO, P>, name: string) -> (),
|
||||||
|
RemoveAll: (self: ObjectList<LO, P>) -> (),
|
||||||
|
Build: (self: ObjectList<LO, P>, name: string) -> (),
|
||||||
|
BuildAll: (self: ObjectList<LO, P>) -> (),
|
||||||
|
Open: (self: ObjectList<LO, P>, name: string, mode: ("Weighted" | "Forced")?) -> (),
|
||||||
|
OpenAll: (self: ObjectList<LO, P>) -> (),
|
||||||
|
OpenPrevious: (self: ObjectList<LO, P>) -> (),
|
||||||
|
Close: (self: ObjectList<LO, P>, name: string) -> (),
|
||||||
|
CloseAll: (self: ObjectList<LO, P>) -> (),
|
||||||
|
Clean: (self: ObjectList<LO, P>, name: string) -> (),
|
||||||
|
CleanAll: (self: ObjectList<LO, P>) -> (),
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ObjectList<LO, P> = typeof(setmetatable({} :: ObjectListClass<LO, P>, {}))
|
||||||
|
|
||||||
|
|
||||||
|
--[[ == Interfaces == ]]
|
||||||
|
|
||||||
|
type BuildableObject<Self, Parent> = {
|
||||||
|
Parent: Parent,
|
||||||
|
|
||||||
|
_: {
|
||||||
|
Status: "Stored" | "Built",
|
||||||
|
|
||||||
|
OnBuild: (self: Self) -> ()?,
|
||||||
|
},
|
||||||
|
|
||||||
|
OnBuild: (self: Self, callback: (self: Self) -> ()) -> (),
|
||||||
|
Build: (self: Self, parent: Parent) -> (),
|
||||||
|
|
||||||
|
GetStatus: (self: Self) -> ("Stored" | "Built"),
|
||||||
|
StatusIs: (self: Self, status: "Stored" | "Built") -> (boolean),
|
||||||
|
}
|
||||||
|
|
||||||
|
type CleanableObject<Self> = {
|
||||||
|
Clean: (self: Self) -> (),
|
||||||
|
Remove: (self: Self) -> (),
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderableObject<Self> = {
|
||||||
|
_: {
|
||||||
|
OnOpen: (self: Self) -> ()?,
|
||||||
|
OnClose: (self: Self) -> ()?,
|
||||||
|
},
|
||||||
|
|
||||||
|
OnOpen: (self: Self, callback: (self: Self) -> ()) -> (),
|
||||||
|
OnClose: (self: Self, callback: (self: Self) -> ()) -> (),
|
||||||
|
|
||||||
|
Open: (self: Self) -> (),
|
||||||
|
Close: (self: Self) -> (),
|
||||||
|
}
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
export type ButtonEvents = "Pressed" | "Released" | "Hovered" | "HoverLeft"
|
||||||
|
|
||||||
export type ButtonClass = {
|
export type ButtonClass = {
|
||||||
Parent: Page | Component,
|
|
||||||
Name: string,
|
Name: string,
|
||||||
ButtonGui: GuiButton,
|
ButtonGui: GuiButton,
|
||||||
|
|
||||||
_: {
|
_: {
|
||||||
OnBuild: (self: Button) -> ()?,
|
|
||||||
OnOpen: (self: Button) -> ()?,
|
|
||||||
OnClose: (self: Button) -> ()?,
|
|
||||||
|
|
||||||
OnPressed: (self: Button) -> ()?,
|
OnPressed: (self: Button) -> ()?,
|
||||||
OnReleased: (self: Button) -> ()?,
|
OnReleased: (self: Button) -> ()?,
|
||||||
OnHovered: (self: Button) -> ()?,
|
OnHovered: (self: Button) -> ()?,
|
||||||
OnHoverLeft: (self: Button) -> ()?,
|
OnHoverLeft: (self: Button) -> ()?,
|
||||||
},
|
},
|
||||||
|
|
||||||
OnBuild: (self: Button, callback: (self: Button) -> ()) -> (),
|
|
||||||
OnOpen: (self: Button, callback: (self: Button) -> ()) -> (),
|
|
||||||
OnClose: (self: Button, callback: (self: Button) -> ()) -> (),
|
|
||||||
|
|
||||||
Build: (self: Button, parent: Page | Component) -> (),
|
|
||||||
Open: (self: Button) -> (),
|
|
||||||
Close: (self: Button) -> (),
|
|
||||||
|
|
||||||
OnPressed: (self: Button, callback: (self: Button) -> ()) -> (),
|
OnPressed: (self: Button, callback: (self: Button) -> ()) -> (),
|
||||||
OnReleased: (self: Button, callback: (self: Button) -> ()) -> (),
|
OnReleased: (self: Button, callback: (self: Button) -> ()) -> (),
|
||||||
OnHovered: (self: Button, callback: (self: Button) -> ()) -> (),
|
OnHovered: (self: Button, callback: (self: Button) -> ()) -> (),
|
||||||
|
@ -39,23 +111,19 @@ export type ButtonClass = {
|
||||||
HoverLeft: (self: Button) -> (),
|
HoverLeft: (self: Button) -> (),
|
||||||
|
|
||||||
Remove: (self: Button) -> (),
|
Remove: (self: Button) -> (),
|
||||||
}
|
} & BuildableObject<Button, Page | Component> & RenderableObject<Button>
|
||||||
|
|
||||||
export type Button = typeof(setmetatable({} :: ButtonClass, {}))
|
export type Button = typeof(setmetatable({} :: ButtonClass, {}))
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
export type ComponentClass = {
|
export type ComponentClass = {
|
||||||
Parent: Page,
|
|
||||||
Name: string,
|
Name: string,
|
||||||
Frame: Frame,
|
Frame: Frame,
|
||||||
|
|
||||||
Container: trove.Trove,
|
Container: trove.Trove,
|
||||||
|
|
||||||
Buttons: {
|
Buttons: ObjectList<Button, Component>,
|
||||||
Active: { [string]: Button },
|
|
||||||
Stored: { [string]: Button },
|
|
||||||
},
|
|
||||||
|
|
||||||
BuildButtons: (self: Component) -> (),
|
BuildButtons: (self: Component) -> (),
|
||||||
AddButton: (self: Component, button: Button) -> (),
|
AddButton: (self: Component, button: Button) -> (),
|
||||||
|
@ -63,143 +131,57 @@ export type ComponentClass = {
|
||||||
RemoveButtons: (self: Component) -> (),
|
RemoveButtons: (self: Component) -> (),
|
||||||
|
|
||||||
_: {
|
_: {
|
||||||
OnBuild: (self: Component) -> ()?,
|
|
||||||
OnOpen: (self: Component) -> ()?,
|
|
||||||
OnClose: (self: Component) -> ()?,
|
|
||||||
|
|
||||||
UpdateCallbacks: { [string]: (self: Page, parameters: {}) -> () },
|
UpdateCallbacks: { [string]: (self: Page, parameters: {}) -> () },
|
||||||
},
|
},
|
||||||
|
|
||||||
OnBuild: (self: Component, callback: (self: Component) -> ()) -> (),
|
|
||||||
OnUpdate: (self: Component, command: string, callback: (self: Component) -> ()) -> (),
|
OnUpdate: (self: Component, command: string, callback: (self: Component) -> ()) -> (),
|
||||||
OnOpen: (self: Component, callback: (self: Component) -> ()) -> (),
|
|
||||||
OnClose: (self: Component, callback: (self: Component) -> ()) -> (),
|
|
||||||
|
|
||||||
Build: (self: Component, parent: Page) -> (),
|
|
||||||
Update: (self: Component, command: string, parameters: {}?) -> (),
|
Update: (self: Component, command: string, parameters: {}?) -> (),
|
||||||
Open: (self: Component) -> (),
|
} & BuildableObject<Component, Page> & CleanableObject<Component> & RenderableObject<Component>
|
||||||
Close: (self: Component) -> (),
|
|
||||||
|
|
||||||
Clean: (self: Component) -> (),
|
|
||||||
Remove: (self: Component) -> (),
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Component = typeof(setmetatable({} :: ComponentClass, {}))
|
export type Component = typeof(setmetatable({} :: ComponentClass, {}))
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
export type PageClass = {
|
export type PageClass = {
|
||||||
Parent: Window,
|
|
||||||
Name: string,
|
Name: string,
|
||||||
Weight: number,
|
Weight: number,
|
||||||
Frame: Frame,
|
Frame: Frame,
|
||||||
|
|
||||||
Container: trove.Trove,
|
Container: trove.Trove,
|
||||||
|
|
||||||
Buttons: {
|
Buttons: ObjectList<Button, Page>,
|
||||||
Active: { [string]: Button },
|
Components: ObjectList<Component, Page>,
|
||||||
Stored: { [string]: Button },
|
|
||||||
},
|
|
||||||
|
|
||||||
BuildButtons: (self: Page) -> (),
|
|
||||||
AddButton: (self: Page, button: Button) -> (),
|
|
||||||
GetButton: (self: Page, button: string) -> (),
|
|
||||||
OpenButton: (self: Page, button: string) -> (),
|
|
||||||
CloseButton: (self: Page, button: string) -> (),
|
|
||||||
CloseAllButtons: (self: Page) -> (),
|
|
||||||
RemoveButtons: (self: Page) -> (),
|
|
||||||
|
|
||||||
Components: {
|
|
||||||
Open: { [string]: Component },
|
|
||||||
Stored: { [string]: Component },
|
|
||||||
},
|
|
||||||
|
|
||||||
BuildComponents: (self: Page) -> (),
|
|
||||||
AddComponent: (self: Page, component: Component) -> (),
|
|
||||||
GetComponent: (self: Page, component: string) -> (),
|
|
||||||
OpenComponent: (self: Page, component: string) -> (),
|
|
||||||
CloseComponent: (self: Page, component: string) -> (),
|
|
||||||
CleanComponents: (self: Page) -> (),
|
|
||||||
RemoveComponents: (self: Page) -> (),
|
|
||||||
|
|
||||||
_: {
|
|
||||||
OnBuild: (self: Page) -> ()?,
|
|
||||||
OnOpen: (self: Page) -> ()?,
|
|
||||||
OnClose: (self: Page) -> ()?,
|
|
||||||
},
|
|
||||||
|
|
||||||
OnBuild: (self: Page, callback: (self: Page) -> ()) -> (),
|
|
||||||
OnOpen: (self: Page, callback: (self: Page) -> ()) -> (),
|
|
||||||
OnClose: (self: Page, callback: (self: Page) -> ()) -> (),
|
|
||||||
|
|
||||||
Build: (self: Page, parent: Window) -> (),
|
|
||||||
Open: (self: Page) -> (),
|
|
||||||
Back: (self: Page) -> (),
|
|
||||||
Close: (self: Page) -> (),
|
|
||||||
|
|
||||||
Clean: (self: Page) -> (),
|
|
||||||
Remove: (self: Page) -> (),
|
|
||||||
|
|
||||||
GetManager: (self: Window) -> (Manager),
|
GetManager: (self: Window) -> (Manager),
|
||||||
}
|
} & BuildableObject<Page, Window> & CleanableObject<Page> & RenderableObject<Page>
|
||||||
|
|
||||||
export type Page = typeof(setmetatable({} :: PageClass, {}))
|
export type Page = typeof(setmetatable({} :: PageClass, {}))
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
export type WindowClass = {
|
export type WindowClass = {
|
||||||
Manager: Manager,
|
|
||||||
Name: string,
|
Name: string,
|
||||||
ScreenGui: ScreenGui,
|
ScreenGui: ScreenGui,
|
||||||
Pages: {
|
Pages: ObjectList<Page, Window>,
|
||||||
Open: { [string]: Page },
|
|
||||||
Stored: { [string]: Page },
|
--[[ Not used, at the moment.]]
|
||||||
|
Events: {
|
||||||
|
PageOpened: signal.Signal<Page, boolean>,
|
||||||
|
PageClosed: signal.Signal<Page, boolean>,
|
||||||
},
|
},
|
||||||
|
} & BuildableObject<Window, Manager> & CleanableObject<Window> & RenderableObject<Window>
|
||||||
BuildPages: (self: Window) -> (),
|
|
||||||
AddPage: (self: Window, page: Page) -> (),
|
|
||||||
GetPage: (self: Window, page: string) -> (),
|
|
||||||
OpenPage: (self: Window, page: string, command: "Weighted" | "Forced"?) -> (),
|
|
||||||
OpenLastPage: (self: Window) -> (),
|
|
||||||
ClosePage: (self: Window, page: string) -> (),
|
|
||||||
|
|
||||||
--[=[
|
|
||||||
Will close and clean all open pages. \
|
|
||||||
If a middleware function is given, closing will be dependent on the returned boolean value of said middleware during an iterative process. \
|
|
||||||
An example use case may include closing pages based on their weighted value.
|
|
||||||
|
|
||||||
@param middleware function(Page) ```returns``` (boolean)
|
|
||||||
]=]
|
|
||||||
CloseAllPages: (self: Window, middleware: (Page) -> (boolean)) -> (),
|
|
||||||
RemoveAllPages: (self: Window) -> (),
|
|
||||||
CleanPages: (self: Window) -> (),
|
|
||||||
|
|
||||||
_: {
|
|
||||||
RecentlyOpenedPageName: string,
|
|
||||||
LastOpenedPageName: string,
|
|
||||||
|
|
||||||
OnBuild: (self: Window) -> ()?,
|
|
||||||
OnOpen: (self: Window) -> ()?,
|
|
||||||
OnClose: (self: Window) -> ()?,
|
|
||||||
},
|
|
||||||
|
|
||||||
OnBuild: (self: Window, callback: (self: Window) -> ()) -> (),
|
|
||||||
OnOpen: (self: Window, callback: (self: Window) -> ()) -> (),
|
|
||||||
OnClose: (self: Window, callback: (self: Window) -> ()) -> (),
|
|
||||||
|
|
||||||
Build: (self: Window, manager: Manager) -> (),
|
|
||||||
Open: (self: Window) -> (),
|
|
||||||
Close: (self: Window) -> (),
|
|
||||||
|
|
||||||
Clean: (self: Window) -> (),
|
|
||||||
Remove: (self: Window) -> (),
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Window = typeof(setmetatable({} :: WindowClass, {}))
|
export type Window = typeof(setmetatable({} :: WindowClass, {}))
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
export type Manager = {
|
export type Manager = {
|
||||||
|
Events: {
|
||||||
|
WindowOpened: signal.Signal<Window, boolean>,
|
||||||
|
WindowClosed: signal.Signal<Window, boolean>,
|
||||||
|
},
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
Useful packages that may be used globally by all windows, pages, and so forth.
|
Useful packages that may be used globally by all windows, pages, and so forth.
|
||||||
]=]
|
]=]
|
||||||
|
@ -231,6 +213,13 @@ export type Manager = {
|
||||||
]=]
|
]=]
|
||||||
OpenWindow: (self: Manager, window: Window) -> (),
|
OpenWindow: (self: Manager, window: Window) -> (),
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
Build and open a ```Window``` object and log its opened state based on the given name.
|
||||||
|
|
||||||
|
@param name ```string```
|
||||||
|
]=]
|
||||||
|
OpenWindowByName: (self: Manager, name: string) -> (),
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
Close and remove a ```Window``` object and log its state change.
|
Close and remove a ```Window``` object and log its state change.
|
||||||
|
|
||||||
|
@ -257,6 +246,9 @@ export type Manager = {
|
||||||
Clean: (self: Manager) -> (),
|
Clean: (self: Manager) -> (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
return {}
|
return {}
|
|
@ -1,6 +1,9 @@
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local objectList = require(script.Parent.ObjectList)
|
||||||
|
local signal = require(ReplicatedStorage.Modules.Utility.Signal)
|
||||||
local types = require(script.Parent.Types)
|
local types = require(script.Parent.Types)
|
||||||
|
|
||||||
local window: types.WindowClass = {} :: types.WindowClass
|
local window = {} :: types.WindowClass
|
||||||
window["__index"] = window
|
window["__index"] = window
|
||||||
|
|
||||||
local module = {}
|
local module = {}
|
||||||
|
@ -8,97 +11,22 @@ local module = {}
|
||||||
function module.new(name: string): types.Window
|
function module.new(name: string): types.Window
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
Name= name,
|
Name= name,
|
||||||
Pages = {
|
|
||||||
Open = {},
|
Pages = objectList.new(),
|
||||||
Stored = {},
|
|
||||||
|
Events = {
|
||||||
|
PageOpened = signal.new(),
|
||||||
|
PageClosed = signal.new(),
|
||||||
},
|
},
|
||||||
|
|
||||||
_ = {},
|
_ = {},
|
||||||
}, window)
|
}, window)
|
||||||
|
|
||||||
|
self.Pages.Parent = self
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function window:BuildPages()
|
|
||||||
for _, page: types.Page in self.Pages.Stored do
|
|
||||||
page:Build(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:AddPage(page: types.Page)
|
|
||||||
self.Pages.Stored[page.Name] = page
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:GetPage(page: string): types.Page
|
|
||||||
return self.Pages.Stored[page]
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:OpenPage(page: string, command: "Weighted" | "Forced"?)
|
|
||||||
local pageModule: types.Page = self.Pages.Stored[page]
|
|
||||||
if pageModule then
|
|
||||||
if command then
|
|
||||||
if command:upper() == "WEIGHTED" then
|
|
||||||
self:CloseAllPages(function(openPage: types.Page)
|
|
||||||
return openPage.Weight < pageModule.Weight
|
|
||||||
end)
|
|
||||||
elseif command:upper() == "FORCED" then
|
|
||||||
self:CloseAllPages()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
pageModule:Build(self)
|
|
||||||
pageModule:Open()
|
|
||||||
self.Pages.Open[page] = pageModule
|
|
||||||
|
|
||||||
self._.LastOpenedPageName = self._.RecentlyOpenedPageName or page
|
|
||||||
self._.RecentlyOpenedPageName = page
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:OpenLastPage()
|
|
||||||
self:OpenPage(self._.LastOpenedPageName)
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:ClosePage(page: string)
|
|
||||||
local pageModule: types.Page = self.Pages.Open[page]
|
|
||||||
if pageModule then
|
|
||||||
pageModule:Close()
|
|
||||||
pageModule:Clean()
|
|
||||||
self.Pages.Open[page] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:CloseAllPages(middleware: (types.Page) -> (boolean)?)
|
|
||||||
for _, page: types.Page in self.Pages.Open do
|
|
||||||
if not middleware or not middleware(page) then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
page:Close()
|
|
||||||
page:Clean()
|
|
||||||
|
|
||||||
self.Pages.Open[page.Name] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:CleanPages()
|
|
||||||
for _, page: types.Page in self.Pages.Open do
|
|
||||||
page:Close()
|
|
||||||
page:Clean()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Pages.Open = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:RemoveAllPages()
|
|
||||||
for _, page: types.Page in self.Pages.Open do
|
|
||||||
page:Remove()
|
|
||||||
end
|
|
||||||
|
|
||||||
self.Pages.Open = {}
|
|
||||||
self.Pages.Stored = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function window:OnBuild(callback: (self: types.Window) -> ())
|
function window:OnBuild(callback: (self: types.Window) -> ())
|
||||||
self._.OnBuild = callback
|
self._.OnBuild = callback
|
||||||
end
|
end
|
||||||
|
@ -114,11 +42,21 @@ end
|
||||||
function window:Build(manager: types.Manager)
|
function window:Build(manager: types.Manager)
|
||||||
self:Clean()
|
self:Clean()
|
||||||
|
|
||||||
self.Manager = manager
|
self.Parent = manager
|
||||||
|
|
||||||
if self._.OnBuild then
|
if self._.OnBuild then
|
||||||
self._.OnBuild(self)
|
self._.OnBuild(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self._.Status = "Built"
|
||||||
|
end
|
||||||
|
|
||||||
|
function window:GetStatus(): "Built" | "Stored"
|
||||||
|
return self._.Status
|
||||||
|
end
|
||||||
|
|
||||||
|
function window:StatusIs(status: "Built" | "Stored"): boolean
|
||||||
|
return status == self._.Status
|
||||||
end
|
end
|
||||||
|
|
||||||
function window:Open()
|
function window:Open()
|
||||||
|
@ -142,12 +80,13 @@ function window:Close()
|
||||||
end
|
end
|
||||||
|
|
||||||
function window:Clean()
|
function window:Clean()
|
||||||
self:CleanPages()
|
self.Pages:CleanAll()
|
||||||
|
|
||||||
|
self._.Status = "Stored"
|
||||||
end
|
end
|
||||||
|
|
||||||
function window:Remove()
|
function window:Remove()
|
||||||
self:Clean()
|
self:Clean()
|
||||||
self:RemovePages()
|
|
||||||
|
|
||||||
if self.ScreenGui then
|
if self.ScreenGui then
|
||||||
self.ScreenGui:Destroy()
|
self.ScreenGui:Destroy()
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local replicated = game:GetService("ReplicatedStorage")
|
local replicated = game:GetService("ReplicatedStorage")
|
||||||
|
local signal = require(ReplicatedStorage.Modules.Utility.Signal)
|
||||||
local trove = require(replicated.Modules.Utility.Trove)
|
local trove = require(replicated.Modules.Utility.Trove)
|
||||||
local types = require(script.Types)
|
local types = require(script.Types)
|
||||||
|
|
||||||
local manager: types.Manager = {
|
local manager: types.Manager = {
|
||||||
|
Events = {
|
||||||
|
WindowOpened = signal.new(),
|
||||||
|
WindowClosed = signal.new(),
|
||||||
|
},
|
||||||
|
|
||||||
Packages = {Trove = trove},
|
Packages = {Trove = trove},
|
||||||
|
|
||||||
Windows = {
|
Windows = {
|
||||||
|
@ -11,6 +18,8 @@ local manager: types.Manager = {
|
||||||
},
|
},
|
||||||
} :: types.Manager
|
} :: types.Manager
|
||||||
|
|
||||||
|
--[[ Window Methods ]]
|
||||||
|
|
||||||
function manager:GetWindow(name: string)
|
function manager:GetWindow(name: string)
|
||||||
return self.Windows.Stored[name]
|
return self.Windows.Stored[name]
|
||||||
end
|
end
|
||||||
|
@ -20,6 +29,12 @@ function manager:OpenWindow(window: types.Window)
|
||||||
window:Open()
|
window:Open()
|
||||||
|
|
||||||
self.Windows.Open[window.Name] = window
|
self.Windows.Open[window.Name] = window
|
||||||
|
|
||||||
|
self.Events.WindowOpened:Fire(window, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function manager:OpenWindowByName(name: string)
|
||||||
|
self:OpenWindow(self:GetWindow(name))
|
||||||
end
|
end
|
||||||
|
|
||||||
function manager:CloseWindow(window: types.Window)
|
function manager:CloseWindow(window: types.Window)
|
||||||
|
@ -27,12 +42,13 @@ function manager:CloseWindow(window: types.Window)
|
||||||
window:Remove()
|
window:Remove()
|
||||||
|
|
||||||
self.Windows.Open[window.Name] = nil
|
self.Windows.Open[window.Name] = nil
|
||||||
|
|
||||||
|
self.Events.WindowClosed:Fire(window, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
function manager:CloseAllWindows()
|
function manager:CloseAllWindows()
|
||||||
for _, window in self.Windows.Open do
|
for _, window in self.Windows.Open do
|
||||||
window:Close()
|
self:CloseWindow(window)
|
||||||
window:Remove()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.Windows.Open = {}
|
self.Windows.Open = {}
|
||||||
|
|
405
src/Shared/Modules/Utility/Signal.lua
Normal file
405
src/Shared/Modules/Utility/Signal.lua
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- Batched Yield-Safe Signal Implementation --
|
||||||
|
-- This is a Signal class which has effectively identical behavior to a --
|
||||||
|
-- normal RBXScriptSignal, with the only difference being a couple extra --
|
||||||
|
-- stack frames at the bottom of the stack trace when an error is thrown. --
|
||||||
|
-- This implementation caches runner coroutines, so the ability to yield in --
|
||||||
|
-- the signal handlers comes at minimal extra cost over a naive signal --
|
||||||
|
-- implementation that either always or never spawns a thread. --
|
||||||
|
-- --
|
||||||
|
-- API: --
|
||||||
|
-- local Signal = require(THIS MODULE) --
|
||||||
|
-- local sig = Signal.new() --
|
||||||
|
-- local connection = sig:Connect(function(arg1, arg2, ...) ... end) --
|
||||||
|
-- sig:Fire(arg1, arg2, ...) --
|
||||||
|
-- connection:Disconnect() --
|
||||||
|
-- sig:DisconnectAll() --
|
||||||
|
-- local arg1, arg2, ... = sig:Wait() --
|
||||||
|
-- --
|
||||||
|
-- License: --
|
||||||
|
-- Licensed under the MIT license. --
|
||||||
|
-- --
|
||||||
|
-- Authors: --
|
||||||
|
-- stravant - July 31st, 2021 - Created the file. --
|
||||||
|
-- sleitnick - August 3rd, 2021 - Modified for Knit. --
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- Signal types
|
||||||
|
export type Connection = {
|
||||||
|
Disconnect: (self: Connection) -> (),
|
||||||
|
Destroy: (self: Connection) -> (),
|
||||||
|
Connected: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Signal<T...> = {
|
||||||
|
Fire: (self: Signal<T...>, T...) -> (),
|
||||||
|
FireDeferred: (self: Signal<T...>, T...) -> (),
|
||||||
|
Connect: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
|
||||||
|
Once: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
|
||||||
|
DisconnectAll: (self: Signal<T...>) -> (),
|
||||||
|
GetConnections: (self: Signal<T...>) -> { Connection },
|
||||||
|
Destroy: (self: Signal<T...>) -> (),
|
||||||
|
Wait: (self: Signal<T...>) -> T...,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- The currently idle thread to run the next handler on
|
||||||
|
local freeRunnerThread = nil
|
||||||
|
|
||||||
|
-- Function which acquires the currently idle handler runner thread, runs the
|
||||||
|
-- function fn on it, and then releases the thread, returning it to being the
|
||||||
|
-- currently idle one.
|
||||||
|
-- If there was a currently idle runner thread already, that's okay, that old
|
||||||
|
-- one will just get thrown and eventually GCed.
|
||||||
|
local function acquireRunnerThreadAndCallEventHandler(fn, ...)
|
||||||
|
local acquiredRunnerThread = freeRunnerThread
|
||||||
|
freeRunnerThread = nil
|
||||||
|
fn(...)
|
||||||
|
-- The handler finished running, this runner thread is free again.
|
||||||
|
freeRunnerThread = acquiredRunnerThread
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Coroutine runner that we create coroutines of. The coroutine can be
|
||||||
|
-- repeatedly resumed with functions to run followed by the argument to run
|
||||||
|
-- them with.
|
||||||
|
local function runEventHandlerInFreeThread(...)
|
||||||
|
acquireRunnerThreadAndCallEventHandler(...)
|
||||||
|
while true do
|
||||||
|
acquireRunnerThreadAndCallEventHandler(coroutine.yield())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Signal
|
||||||
|
@interface SignalConnection
|
||||||
|
.Connected boolean
|
||||||
|
.Disconnect (SignalConnection) -> ()
|
||||||
|
|
||||||
|
Represents a connection to a signal.
|
||||||
|
```lua
|
||||||
|
local connection = signal:Connect(function() end)
|
||||||
|
print(connection.Connected) --> true
|
||||||
|
connection:Disconnect()
|
||||||
|
print(connection.Connected) --> false
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
|
||||||
|
-- Connection class
|
||||||
|
local Connection = {}
|
||||||
|
Connection.__index = Connection
|
||||||
|
|
||||||
|
function Connection.new(signal, fn)
|
||||||
|
return setmetatable({
|
||||||
|
Connected = true,
|
||||||
|
_signal = signal,
|
||||||
|
_fn = fn,
|
||||||
|
_next = false,
|
||||||
|
}, Connection)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Connection:Disconnect()
|
||||||
|
if not self.Connected then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.Connected = false
|
||||||
|
|
||||||
|
-- Unhook the node, but DON'T clear it. That way any fire calls that are
|
||||||
|
-- currently sitting on this node will be able to iterate forwards off of
|
||||||
|
-- it, but any subsequent fire calls will not hit it, and it will be GCed
|
||||||
|
-- when no more fire calls are sitting on it.
|
||||||
|
if self._signal._handlerListHead == self then
|
||||||
|
self._signal._handlerListHead = self._next
|
||||||
|
else
|
||||||
|
local prev = self._signal._handlerListHead
|
||||||
|
while prev and prev._next ~= self do
|
||||||
|
prev = prev._next
|
||||||
|
end
|
||||||
|
if prev then
|
||||||
|
prev._next = self._next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Connection.Destroy = Connection.Disconnect
|
||||||
|
|
||||||
|
-- Make Connection strict
|
||||||
|
setmetatable(Connection, {
|
||||||
|
__index = function(_tb, key)
|
||||||
|
error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
|
||||||
|
end,
|
||||||
|
__newindex = function(_tb, key, _value)
|
||||||
|
error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Signal
|
||||||
|
@type ConnectionFn (...any) -> ()
|
||||||
|
|
||||||
|
A function connected to a signal.
|
||||||
|
]=]
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@class Signal
|
||||||
|
|
||||||
|
Signals allow events to be dispatched and handled.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```lua
|
||||||
|
local signal = Signal.new()
|
||||||
|
|
||||||
|
signal:Connect(function(msg)
|
||||||
|
print("Got message:", msg)
|
||||||
|
end)
|
||||||
|
|
||||||
|
signal:Fire("Hello world!")
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
local Signal = {}
|
||||||
|
Signal.__index = Signal
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
Constructs a new Signal
|
||||||
|
|
||||||
|
@return Signal
|
||||||
|
]=]
|
||||||
|
function Signal.new<T...>(): Signal<T...>
|
||||||
|
local self = setmetatable({
|
||||||
|
_handlerListHead = false,
|
||||||
|
_proxyHandler = nil,
|
||||||
|
}, Signal)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
Constructs a new Signal that wraps around an RBXScriptSignal.
|
||||||
|
|
||||||
|
@param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap
|
||||||
|
@return Signal
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```lua
|
||||||
|
local signal = Signal.Wrap(workspace.ChildAdded)
|
||||||
|
signal:Connect(function(part) print(part.Name .. " added") end)
|
||||||
|
Instance.new("Part").Parent = workspace
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal.Wrap<T...>(rbxScriptSignal: RBXScriptSignal): Signal<T...>
|
||||||
|
assert(
|
||||||
|
typeof(rbxScriptSignal) == "RBXScriptSignal",
|
||||||
|
"Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal)
|
||||||
|
)
|
||||||
|
local signal = Signal.new()
|
||||||
|
signal._proxyHandler = rbxScriptSignal:Connect(function(...)
|
||||||
|
signal:Fire(...)
|
||||||
|
end)
|
||||||
|
return signal
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
Checks if the given object is a Signal.
|
||||||
|
|
||||||
|
@param obj any -- Object to check
|
||||||
|
@return boolean -- `true` if the object is a Signal.
|
||||||
|
]=]
|
||||||
|
function Signal.Is(obj: any): boolean
|
||||||
|
return type(obj) == "table" and getmetatable(obj) == Signal
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@param fn ConnectionFn
|
||||||
|
@return SignalConnection
|
||||||
|
|
||||||
|
Connects a function to the signal, which will be called anytime the signal is fired.
|
||||||
|
```lua
|
||||||
|
signal:Connect(function(msg, num)
|
||||||
|
print(msg, num)
|
||||||
|
end)
|
||||||
|
|
||||||
|
signal:Fire("Hello", 25)
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal:Connect(fn)
|
||||||
|
local connection = Connection.new(self, fn)
|
||||||
|
if self._handlerListHead then
|
||||||
|
connection._next = self._handlerListHead
|
||||||
|
self._handlerListHead = connection
|
||||||
|
else
|
||||||
|
self._handlerListHead = connection
|
||||||
|
end
|
||||||
|
return connection
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@deprecated v1.3.0 -- Use `Signal:Once` instead.
|
||||||
|
@param fn ConnectionFn
|
||||||
|
@return SignalConnection
|
||||||
|
]=]
|
||||||
|
function Signal:ConnectOnce(fn)
|
||||||
|
return self:Once(fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@param fn ConnectionFn
|
||||||
|
@return SignalConnection
|
||||||
|
|
||||||
|
Connects a function to the signal, which will be called the next time the signal fires. Once
|
||||||
|
the connection is triggered, it will disconnect itself.
|
||||||
|
```lua
|
||||||
|
signal:Once(function(msg, num)
|
||||||
|
print(msg, num)
|
||||||
|
end)
|
||||||
|
|
||||||
|
signal:Fire("Hello", 25)
|
||||||
|
signal:Fire("This message will not go through", 10)
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal:Once(fn)
|
||||||
|
local connection
|
||||||
|
local done = false
|
||||||
|
connection = self:Connect(function(...)
|
||||||
|
if done then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
done = true
|
||||||
|
connection:Disconnect()
|
||||||
|
fn(...)
|
||||||
|
end)
|
||||||
|
return connection
|
||||||
|
end
|
||||||
|
|
||||||
|
function Signal:GetConnections()
|
||||||
|
local items = {}
|
||||||
|
local item = self._handlerListHead
|
||||||
|
while item do
|
||||||
|
table.insert(items, item)
|
||||||
|
item = item._next
|
||||||
|
end
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Disconnect all handlers. Since we use a linked list it suffices to clear the
|
||||||
|
-- reference to the head handler.
|
||||||
|
--[=[
|
||||||
|
Disconnects all connections from the signal.
|
||||||
|
```lua
|
||||||
|
signal:DisconnectAll()
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal:DisconnectAll()
|
||||||
|
local item = self._handlerListHead
|
||||||
|
while item do
|
||||||
|
item.Connected = false
|
||||||
|
item = item._next
|
||||||
|
end
|
||||||
|
self._handlerListHead = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Signal:Fire(...) implemented by running the handler functions on the
|
||||||
|
-- coRunnerThread, and any time the resulting thread yielded without returning
|
||||||
|
-- to us, that means that it yielded to the Roblox scheduler and has been taken
|
||||||
|
-- over by Roblox scheduling, meaning we have to make a new coroutine runner.
|
||||||
|
--[=[
|
||||||
|
@param ... any
|
||||||
|
|
||||||
|
Fire the signal, which will call all of the connected functions with the given arguments.
|
||||||
|
```lua
|
||||||
|
signal:Fire("Hello")
|
||||||
|
|
||||||
|
-- Any number of arguments can be fired:
|
||||||
|
signal:Fire("Hello", 32, {Test = "Test"}, true)
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal:Fire(...)
|
||||||
|
local item = self._handlerListHead
|
||||||
|
while item do
|
||||||
|
if item.Connected then
|
||||||
|
if not freeRunnerThread then
|
||||||
|
freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
|
||||||
|
end
|
||||||
|
task.spawn(freeRunnerThread, item._fn, ...)
|
||||||
|
end
|
||||||
|
item = item._next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@param ... any
|
||||||
|
|
||||||
|
Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse.
|
||||||
|
```lua
|
||||||
|
signal:FireDeferred("Hello")
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal:FireDeferred(...)
|
||||||
|
local item = self._handlerListHead
|
||||||
|
while item do
|
||||||
|
task.defer(item._fn, ...)
|
||||||
|
item = item._next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@return ... any
|
||||||
|
@yields
|
||||||
|
|
||||||
|
Yields the current thread until the signal is fired, and returns the arguments fired from the signal.
|
||||||
|
Yielding the current thread is not always desirable. If the desire is to only capture the next event
|
||||||
|
fired, using `Once` might be a better solution.
|
||||||
|
```lua
|
||||||
|
task.spawn(function()
|
||||||
|
local msg, num = signal:Wait()
|
||||||
|
print(msg, num) --> "Hello", 32
|
||||||
|
end)
|
||||||
|
signal:Fire("Hello", 32)
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal:Wait()
|
||||||
|
local waitingCoroutine = coroutine.running()
|
||||||
|
local connection
|
||||||
|
local done = false
|
||||||
|
connection = self:Connect(function(...)
|
||||||
|
if done then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
done = true
|
||||||
|
connection:Disconnect()
|
||||||
|
task.spawn(waitingCoroutine, ...)
|
||||||
|
end)
|
||||||
|
return coroutine.yield()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
Cleans up the signal.
|
||||||
|
|
||||||
|
Technically, this is only necessary if the signal is created using
|
||||||
|
`Signal.Wrap`. Connections should be properly GC'd once the signal
|
||||||
|
is no longer referenced anywhere. However, it is still good practice
|
||||||
|
to include ways to strictly clean up resources. Calling `Destroy`
|
||||||
|
on a signal will also disconnect all connections immediately.
|
||||||
|
```lua
|
||||||
|
signal:Destroy()
|
||||||
|
```
|
||||||
|
]=]
|
||||||
|
function Signal:Destroy()
|
||||||
|
self:DisconnectAll()
|
||||||
|
local proxyHandler = rawget(self, "_proxyHandler")
|
||||||
|
if proxyHandler then
|
||||||
|
proxyHandler:Disconnect()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make signal strict
|
||||||
|
setmetatable(Signal, {
|
||||||
|
__index = function(_tb, key)
|
||||||
|
error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
|
||||||
|
end,
|
||||||
|
__newindex = function(_tb, key, _value)
|
||||||
|
error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
new = Signal.new,
|
||||||
|
Wrap = Signal.Wrap,
|
||||||
|
Is = Signal.Is,
|
||||||
|
}
|
Loading…
Reference in a new issue