UI Manager
This commit is contained in:
parent
d48160c61e
commit
13a17ee7a4
9 changed files with 1162 additions and 0 deletions
15
src/Client/Test/window.lua
Normal file
15
src/Client/Test/window.lua
Normal file
|
@ -0,0 +1,15 @@
|
|||
local replicated = game:GetService("ReplicatedStorage")
|
||||
|
||||
local types = require(replicated.Modules.Managers.UIManager.Types)
|
||||
local Window = require(replicated.Modules.Managers.UIManager.Window)
|
||||
|
||||
local window = Window.new(script.Name)
|
||||
|
||||
window:OnBuild(function(self: types.Window): ()
|
||||
self.ScreenGui = Instance.new("ScreenGui")
|
||||
|
||||
self.ScreenGui.Enabled = false
|
||||
self.ScreenGui.Parent = game:GetService("Players").LocalPlayer.PlayerGui
|
||||
end)
|
||||
|
||||
return window
|
11
src/Client/test.client.lua
Normal file
11
src/Client/test.client.lua
Normal file
|
@ -0,0 +1,11 @@
|
|||
local replicated = game:GetService("ReplicatedStorage")
|
||||
local uiManager = require(replicated.Modules.Managers.UIManager)
|
||||
uiManager:Build(script.Parent.Test:GetChildren() :: {})
|
||||
|
||||
local default = uiManager:GetWindow("window")
|
||||
|
||||
uiManager:OpenWindow(default)
|
||||
|
||||
task.wait(10)
|
||||
|
||||
uiManager:CloseWindow(default)
|
101
src/Shared/Modules/Managers/UIManager/Button.lua
Normal file
101
src/Shared/Modules/Managers/UIManager/Button.lua
Normal file
|
@ -0,0 +1,101 @@
|
|||
local types = require(script.Parent.Types)
|
||||
|
||||
local button: types.ButtonClass = {} :: types.ButtonClass
|
||||
button["__index"] = button
|
||||
|
||||
local module = {}
|
||||
|
||||
function module.new(name: string): types.Button
|
||||
local self = setmetatable({
|
||||
Name= name,
|
||||
_ = {},
|
||||
}, button)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function button:OnBuild(callback: (self: types.Button) -> ())
|
||||
self._.OnBuild = callback
|
||||
end
|
||||
|
||||
function button:OnOpen(callback: (self: types.Button) -> ())
|
||||
self._.OnOpen = callback
|
||||
end
|
||||
|
||||
function button:OnClose(callback: (self: types.Button) -> ())
|
||||
self._.OnClose = callback
|
||||
end
|
||||
|
||||
function button:Build(parent: types.Page | types.Component)
|
||||
self.Parent = parent
|
||||
|
||||
if self._.OnBuild then
|
||||
self._.OnBuild(self)
|
||||
end
|
||||
end
|
||||
|
||||
function button:Open()
|
||||
if self.ButtonGui then
|
||||
self.ButtonGui.Active = true
|
||||
self.ButtonGui.Visible = true
|
||||
end
|
||||
|
||||
if self._.OnOpen then
|
||||
self._.OnOpen(self)
|
||||
end
|
||||
end
|
||||
|
||||
function button:Close()
|
||||
if self.ButtonGui then
|
||||
self.ButtonGui.Active = false
|
||||
self.ButtonGui.Visible = false
|
||||
end
|
||||
|
||||
if self._.OnClose then
|
||||
self._.OnClose(self)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Events ]]
|
||||
|
||||
function button:OnPressed(callback: (self: types.Button) -> ())
|
||||
self._.OnPressed = callback
|
||||
end
|
||||
|
||||
function button:OnReleased(callback: (self: types.Button) -> ())
|
||||
self._.OnReleased = callback
|
||||
end
|
||||
|
||||
function button:OnHovered(callback: (self: types.Button) -> ())
|
||||
self._.OnHovered = callback
|
||||
end
|
||||
|
||||
function button:OnHoverLeft(callback: (self: types.Button) -> ())
|
||||
self._.OnHoverLeft = callback
|
||||
end
|
||||
|
||||
function button:Pressed()
|
||||
if self._.OnPressed then
|
||||
self._.OnPressed(self)
|
||||
end
|
||||
end
|
||||
|
||||
function button:Released()
|
||||
if self._.OnReleased then
|
||||
self._.OnReleased(self)
|
||||
end
|
||||
end
|
||||
|
||||
function button:Hovered()
|
||||
if self._.OnHovered then
|
||||
self._.OnHovered(self)
|
||||
end
|
||||
end
|
||||
|
||||
function button:HoverLeft()
|
||||
if self._.OnHoverLeft then
|
||||
self._.OnHoverLeft(self)
|
||||
end
|
||||
end
|
||||
|
||||
return module
|
101
src/Shared/Modules/Managers/UIManager/Component.lua
Normal file
101
src/Shared/Modules/Managers/UIManager/Component.lua
Normal file
|
@ -0,0 +1,101 @@
|
|||
local types = require(script.Parent.Types)
|
||||
|
||||
local component: types.ComponentClass = {} :: types.ComponentClass
|
||||
component["__index"] = component
|
||||
|
||||
local module = {}
|
||||
|
||||
function module.new(name: string): types.Component
|
||||
local self = setmetatable({
|
||||
Name= name,
|
||||
_ = {},
|
||||
}, component)
|
||||
|
||||
return self
|
||||
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:RemoveButtons()
|
||||
self.Buttons.Active = {}
|
||||
self.Buttons.Stored = {}
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
function component:OnBuild(callback: (self: types.Component) -> ()): ()
|
||||
self._.OnBuild = callback
|
||||
end
|
||||
|
||||
function component:OnUpdate(command: string, callback: (self: types.Component, parameters: {}) -> ()): ()
|
||||
self._.UpdateCallbacks[command] = callback
|
||||
end
|
||||
|
||||
function component:OnOpen(callback: (self: types.Component) -> ()): ()
|
||||
self._.OnOpen = callback
|
||||
end
|
||||
|
||||
function component:OnClose(callback: (self: types.Component) -> ()): ()
|
||||
self._.OnClose = callback
|
||||
end
|
||||
|
||||
function component:Build(parent: types.Page): ()
|
||||
self:Clean()
|
||||
|
||||
self.Parent = parent
|
||||
|
||||
local window = parent.Parent
|
||||
self.Container = window.Manager.Packages.Trove.new()
|
||||
|
||||
if self._.OnBuild then
|
||||
self._.OnBuild(self)
|
||||
end
|
||||
end
|
||||
|
||||
function component:Update(command: string, parameters: {}): ()
|
||||
if self._.UpdateCallbacks[command] then
|
||||
self._.UpdateCallbacks[command](self, parameters)
|
||||
else
|
||||
warn("No update function exists inside of component.", self.Name, "| command:", command)
|
||||
end
|
||||
end
|
||||
|
||||
function component:Open(): ()
|
||||
if self.Frame then
|
||||
self.Frame.Visible = true
|
||||
end
|
||||
|
||||
if self._.OnOpen then
|
||||
self._.OnOpen(self)
|
||||
end
|
||||
end
|
||||
|
||||
function component:Close(): ()
|
||||
if self.Frame then
|
||||
self.Frame.Visible = false
|
||||
end
|
||||
|
||||
if self._.OnClose then
|
||||
self._.OnClose(self)
|
||||
end
|
||||
end
|
||||
|
||||
function component:Clean(): ()
|
||||
if self.Container then
|
||||
self.Container:Remove()
|
||||
end
|
||||
|
||||
self:RemoveButtons()
|
||||
end
|
||||
|
||||
return module
|
139
src/Shared/Modules/Managers/UIManager/Page.lua
Normal file
139
src/Shared/Modules/Managers/UIManager/Page.lua
Normal file
|
@ -0,0 +1,139 @@
|
|||
local types = require(script.Parent.Types)
|
||||
|
||||
local page: types.PageClass = {} :: types.PageClass
|
||||
page["__index"] = page
|
||||
|
||||
local module = {}
|
||||
|
||||
function module.new(name: string): types.Page
|
||||
local self = setmetatable({
|
||||
Name= name,
|
||||
Buttons = {
|
||||
Active = {},
|
||||
Stored = {},
|
||||
},
|
||||
Components = {
|
||||
Open = {},
|
||||
Stored = {},
|
||||
},
|
||||
|
||||
_ = {},
|
||||
}, page)
|
||||
|
||||
return self
|
||||
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:RemoveButtons()
|
||||
self.Buttons.Active = {}
|
||||
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:RemoveComponents()
|
||||
for _, component: types.Component in self.Components.Open do
|
||||
component:Clean()
|
||||
end
|
||||
|
||||
self.Components.Open = {}
|
||||
self.Components.Stored = {}
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
--[[ Page ]]
|
||||
|
||||
function page:OnBuild(callback: (self: types.Page) -> ())
|
||||
self._.OnBuild = callback
|
||||
end
|
||||
|
||||
function page:OnOpen(callback: (self: types.Page) -> ())
|
||||
self._.OnOpen = callback
|
||||
end
|
||||
|
||||
function page:OnClose(callback: (self: types.Page) -> ())
|
||||
self._.OnClose = callback
|
||||
end
|
||||
|
||||
function page:Build(parent: types.Window)
|
||||
self:Clean()
|
||||
|
||||
self.Parent = parent
|
||||
|
||||
self.Container = parent.Manager.Packages.Trove.new()
|
||||
|
||||
if self._.OnBuild then
|
||||
self._.OnBuild(self)
|
||||
end
|
||||
end
|
||||
|
||||
function page:Open()
|
||||
if self.Frame then
|
||||
self.Frame.Visible = true
|
||||
end
|
||||
|
||||
if self._.OnOpen then
|
||||
self._.OnOpen(self)
|
||||
end
|
||||
end
|
||||
|
||||
function page:Close()
|
||||
if self.Frame then
|
||||
self.Frame.Visible = false
|
||||
end
|
||||
|
||||
if self._.OnClose then
|
||||
self._.OnClose(self)
|
||||
end
|
||||
end
|
||||
|
||||
function page:Clean()
|
||||
self:RemoveComponents()
|
||||
self:RemoveButtons()
|
||||
|
||||
self.Container:Remove()
|
||||
end
|
||||
|
||||
function page:Remove()
|
||||
self:Clean()
|
||||
|
||||
if self.Frame then
|
||||
self.Frame:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Helper ]]
|
||||
|
||||
function page:GetManager(): types.Manager
|
||||
return self.Parent.Manager
|
||||
end
|
||||
|
||||
return module
|
228
src/Shared/Modules/Managers/UIManager/Types.lua
Normal file
228
src/Shared/Modules/Managers/UIManager/Types.lua
Normal file
|
@ -0,0 +1,228 @@
|
|||
local replicated = game:GetService("ReplicatedStorage")
|
||||
local trove = require(replicated.Modules.Utility.Trove)
|
||||
|
||||
|
||||
--
|
||||
|
||||
export type ButtonClass = {
|
||||
Parent: Page | Component,
|
||||
Name: string,
|
||||
ButtonGui: GuiButton,
|
||||
|
||||
_: {
|
||||
OnBuild: (self: Button) -> ()?,
|
||||
OnOpen: (self: Button) -> ()?,
|
||||
OnClose: (self: Button) -> ()?,
|
||||
|
||||
OnPressed: (self: Button) -> ()?,
|
||||
OnReleased: (self: Button) -> ()?,
|
||||
OnHovered: (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) -> ()) -> (),
|
||||
OnReleased: (self: Button, callback: (self: Button) -> ()) -> (),
|
||||
OnHovered: (self: Button, callback: (self: Button) -> ()) -> (),
|
||||
OnHoverLeft: (self: Button, callback: (self: Button) -> ()) -> (),
|
||||
|
||||
Pressed: (self: Button) -> (),
|
||||
Released: (self: Button) -> (),
|
||||
Hovered: (self: Button) -> (),
|
||||
HoverLeft: (self: Button) -> (),
|
||||
}
|
||||
|
||||
export type Button = typeof(setmetatable({} :: ButtonClass, {}))
|
||||
|
||||
--
|
||||
|
||||
export type ComponentClass = {
|
||||
Parent: Page,
|
||||
Name: string,
|
||||
Frame: Frame,
|
||||
|
||||
Container: trove.Trove,
|
||||
|
||||
Buttons: {
|
||||
Active: { [string]: Button },
|
||||
Stored: { [string]: Button },
|
||||
},
|
||||
|
||||
BuildButtons: (self: Component) -> (),
|
||||
AddButton: (self: Component, button: Button) -> (),
|
||||
RemoveButtons: (self: Component) -> (),
|
||||
|
||||
_: {
|
||||
OnBuild: (self: Component) -> ()?,
|
||||
OnOpen: (self: Component) -> ()?,
|
||||
OnClose: (self: Component) -> ()?,
|
||||
|
||||
UpdateCallbacks: { [string]: (self: Page, parameters: {}) -> () },
|
||||
},
|
||||
|
||||
OnBuild: (self: Component, 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: {}) -> (),
|
||||
Open: (self: Component) -> (),
|
||||
Close: (self: Component) -> (),
|
||||
|
||||
Clean: (self: Component) -> (),
|
||||
}
|
||||
|
||||
export type Component = typeof(setmetatable({} :: ComponentClass, {}))
|
||||
|
||||
--
|
||||
|
||||
export type PageClass = {
|
||||
Parent: Window,
|
||||
Name: string,
|
||||
Frame: Frame,
|
||||
|
||||
Container: trove.Trove,
|
||||
|
||||
Buttons: {
|
||||
Active: { [string]: Button },
|
||||
Stored: { [string]: Button },
|
||||
},
|
||||
|
||||
BuildButtons: (self: Page) -> (),
|
||||
AddButton: (self: Page, button: Button) -> (),
|
||||
RemoveButtons: (self: Page) -> (),
|
||||
|
||||
Components: {
|
||||
Open: { [string]: Component },
|
||||
Stored: { [string]: Component },
|
||||
},
|
||||
|
||||
BuildComponents: (self: Page) -> (),
|
||||
AddComponent: (self: Page, component: Component) -> (),
|
||||
GetComponent: (self: Page, component: string) -> (),
|
||||
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) -> (),
|
||||
Close: (self: Page) -> (),
|
||||
|
||||
Clean: (self: Page) -> (),
|
||||
Remove: (self: Page) -> (),
|
||||
|
||||
GetManager: (self: Window) -> (Manager),
|
||||
}
|
||||
|
||||
export type Page = typeof(setmetatable({} :: PageClass, {}))
|
||||
|
||||
--
|
||||
|
||||
export type WindowClass = {
|
||||
Manager: Manager,
|
||||
Name: string,
|
||||
ScreenGui: ScreenGui,
|
||||
Pages: {
|
||||
Open: { [string]: Page },
|
||||
Stored: { [string]: Page },
|
||||
},
|
||||
|
||||
BuildPages: (self: Window) -> (),
|
||||
AddPage: (self: Window, page: Page) -> (),
|
||||
GetPage: (self: Page, page: string) -> (),
|
||||
RemovePages: (self: Window) -> (),
|
||||
|
||||
_: {
|
||||
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 Manager = {
|
||||
--[=[
|
||||
Useful packages that may be used globally by all windows, pages, and so forth.
|
||||
]=]
|
||||
Packages: {
|
||||
Trove: typeof(trove),
|
||||
},
|
||||
|
||||
--[=[
|
||||
```Window``` objects that are either ```Stored```, aka cached, or are in an ```Open``` state.
|
||||
]=]
|
||||
Windows: {
|
||||
Open: { [string]: Window },
|
||||
Stored: { [string]: Window },
|
||||
},
|
||||
|
||||
--[=[
|
||||
Locate a ```Window``` object stored under the provided name.
|
||||
|
||||
@param name string
|
||||
|
||||
@return ```Window```
|
||||
]=]
|
||||
GetWindow: (self: Manager, name: string) -> (Window),
|
||||
|
||||
--[=[
|
||||
Build and open a ```Window``` object and log its opened state.
|
||||
|
||||
@param window ```Window```
|
||||
]=]
|
||||
OpenWindow: (self: Manager, window: Window) -> (),
|
||||
|
||||
--[=[
|
||||
Close and remove a ```Window``` object and log its state change.
|
||||
|
||||
@param window ```Window```
|
||||
]=]
|
||||
CloseWindow: (self: Manager, window: Window) -> (),
|
||||
|
||||
--[=[
|
||||
Intended to only be run on the first time that the client boots up. \
|
||||
Receives a table of module scripts with which it will use to build the manager windows.
|
||||
|
||||
@param source { ModuleScript } ```Window``` objects.
|
||||
]=]
|
||||
Build: (self: Manager, source: { ModuleScript }) -> (),
|
||||
|
||||
--[=[
|
||||
Will clean the manager by removing all the open ```Window``` objects.
|
||||
]=]
|
||||
Clean: (self: Manager) -> (),
|
||||
}
|
||||
|
||||
--
|
||||
|
||||
return {}
|
99
src/Shared/Modules/Managers/UIManager/Window.lua
Normal file
99
src/Shared/Modules/Managers/UIManager/Window.lua
Normal file
|
@ -0,0 +1,99 @@
|
|||
local types = require(script.Parent.Types)
|
||||
|
||||
local window: types.WindowClass = {} :: types.WindowClass
|
||||
window["__index"] = window
|
||||
|
||||
local module = {}
|
||||
|
||||
function module.new(name: string): types.Window
|
||||
local self = setmetatable({
|
||||
Name= name,
|
||||
Pages = {
|
||||
Open = {},
|
||||
Stored = {},
|
||||
},
|
||||
|
||||
_ = {},
|
||||
}, window)
|
||||
|
||||
return self
|
||||
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:RemovePages()
|
||||
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) -> ())
|
||||
self._.OnBuild = callback
|
||||
end
|
||||
|
||||
function window:OnOpen(callback: (self: types.Window) -> ())
|
||||
self._.OnOpen = callback
|
||||
end
|
||||
|
||||
function window:OnClose(callback: (self: types.Window) -> ())
|
||||
self._.OnClose = callback
|
||||
end
|
||||
|
||||
function window:Build(manager: types.Manager)
|
||||
self:Clean()
|
||||
|
||||
self.Manager = manager
|
||||
|
||||
if self._.OnBuild then
|
||||
self._.OnBuild(self)
|
||||
end
|
||||
end
|
||||
|
||||
function window:Open()
|
||||
if self.ScreenGui then
|
||||
self.ScreenGui.Enabled = true
|
||||
end
|
||||
|
||||
if self._.OnOpen then
|
||||
self._.OnOpen(self)
|
||||
end
|
||||
end
|
||||
|
||||
function window:Close()
|
||||
if self.ScreenGui then
|
||||
self.ScreenGui.Enabled = false
|
||||
end
|
||||
|
||||
if self._.OnClose then
|
||||
self._.OnClose(self)
|
||||
end
|
||||
end
|
||||
|
||||
function window:Clean()
|
||||
self:RemovePages()
|
||||
end
|
||||
|
||||
function window:Remove()
|
||||
self:Clean()
|
||||
|
||||
if self.ScreenGui then
|
||||
self.ScreenGui:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
return module
|
53
src/Shared/Modules/Managers/UIManager/init.lua
Normal file
53
src/Shared/Modules/Managers/UIManager/init.lua
Normal file
|
@ -0,0 +1,53 @@
|
|||
local replicated = game:GetService("ReplicatedStorage")
|
||||
local trove = require(replicated.Modules.Utility.Trove)
|
||||
local types = require(script.Types)
|
||||
|
||||
local manager: types.Manager = {
|
||||
Packages = {Trove = trove},
|
||||
|
||||
Windows = {
|
||||
Open = {},
|
||||
Stored = {},
|
||||
},
|
||||
} :: types.Manager
|
||||
|
||||
function manager:GetWindow(name: string)
|
||||
return self.Windows.Stored[name]
|
||||
end
|
||||
|
||||
function manager:OpenWindow(window: types.Window)
|
||||
window:Build(self)
|
||||
window:Open()
|
||||
|
||||
self.Windows.Open[window.Name] = window
|
||||
end
|
||||
|
||||
function manager:CloseWindow(window: types.Window)
|
||||
window:Close()
|
||||
window:Remove()
|
||||
|
||||
self.Windows.Open[window.Name] = nil
|
||||
end
|
||||
|
||||
function manager:Build(source: { ModuleScript })
|
||||
self:Clean()
|
||||
|
||||
for _, module: ModuleScript in source do
|
||||
local window: types.Window = require(module) :: types.Window
|
||||
|
||||
self.Windows.Stored[window.Name] = window
|
||||
end
|
||||
end
|
||||
|
||||
function manager:Clean()
|
||||
for _, window in self.Windows.Open do
|
||||
window:Remove()
|
||||
end
|
||||
|
||||
self.Windows = {
|
||||
Open = {},
|
||||
Stored = {},
|
||||
}
|
||||
end
|
||||
|
||||
return manager
|
415
src/Shared/Modules/Utility/Trove.lua
Normal file
415
src/Shared/Modules/Utility/Trove.lua
Normal file
|
@ -0,0 +1,415 @@
|
|||
-- Trove
|
||||
-- Stephen Leitnick
|
||||
-- October 16, 2021
|
||||
|
||||
local FN_MARKER = newproxy()
|
||||
local THREAD_MARKER = newproxy()
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local function GetObjectCleanupFunction(object, cleanupMethod)
|
||||
local t = typeof(object)
|
||||
if t == "function" then
|
||||
return FN_MARKER
|
||||
elseif t == "thread" then
|
||||
return THREAD_MARKER
|
||||
end
|
||||
if cleanupMethod then
|
||||
return cleanupMethod
|
||||
end
|
||||
if t == "Instance" then
|
||||
return "Destroy"
|
||||
elseif t == "RBXScriptConnection" then
|
||||
return "Disconnect"
|
||||
elseif t == "table" then
|
||||
if typeof(object.Destroy) == "function" then
|
||||
return "Destroy"
|
||||
elseif typeof(object.Disconnect) == "function" then
|
||||
return "Disconnect"
|
||||
end
|
||||
end
|
||||
error("Failed to get cleanup function for object " .. t .. ": " .. tostring(object), 3)
|
||||
end
|
||||
|
||||
local function AssertPromiseLike(object)
|
||||
if
|
||||
type(object) ~= "table"
|
||||
or type(object.getStatus) ~= "function"
|
||||
or type(object.finally) ~= "function"
|
||||
or type(object.cancel) ~= "function"
|
||||
then
|
||||
error("Did not receive a Promise as an argument", 3)
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@class Trove
|
||||
A Trove is helpful for tracking any sort of object during
|
||||
runtime that needs to get cleaned up at some point.
|
||||
]=]
|
||||
local Trove = {}
|
||||
Trove.__index = Trove
|
||||
|
||||
--[=[
|
||||
@return Trove
|
||||
Constructs a Trove object.
|
||||
]=]
|
||||
function Trove.new()
|
||||
local self = setmetatable({}, Trove)
|
||||
self._objects = {}
|
||||
self._cleaning = false
|
||||
return self
|
||||
end
|
||||
|
||||
export type Trove = typeof(Trove.new())
|
||||
|
||||
--[=[
|
||||
@return Trove
|
||||
Creates and adds another trove to itself. This is just shorthand
|
||||
for `trove:Construct(Trove)`. This is useful for contexts where
|
||||
the trove object is present, but the class itself isn't.
|
||||
|
||||
:::note
|
||||
This does _not_ clone the trove. In other words, the objects in the
|
||||
trove are not given to the new constructed trove. This is simply to
|
||||
construct a new Trove and add it as an object to track.
|
||||
:::
|
||||
|
||||
```lua
|
||||
local trove = Trove.new()
|
||||
local subTrove = trove:Extend()
|
||||
|
||||
trove:Clean() -- Cleans up the subTrove too
|
||||
```
|
||||
]=]
|
||||
function Trove:Extend()
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:Extend() while cleaning", 2)
|
||||
end
|
||||
return self:Construct(Trove)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Clones the given instance and adds it to the trove. Shorthand for
|
||||
`trove:Add(instance:Clone())`.
|
||||
]=]
|
||||
function Trove:Clone(instance: Instance): Instance
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:Clone() while cleaning", 2)
|
||||
end
|
||||
return self:Add(instance:Clone())
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param class table | (...any) -> any
|
||||
@param ... any
|
||||
@return any
|
||||
Constructs a new object from either the
|
||||
table or function given.
|
||||
|
||||
If a table is given, the table's `new`
|
||||
function will be called with the given
|
||||
arguments.
|
||||
|
||||
If a function is given, the function will
|
||||
be called with the given arguments.
|
||||
|
||||
The result from either of the two options
|
||||
will be added to the trove.
|
||||
|
||||
This is shorthand for `trove:Add(SomeClass.new(...))`
|
||||
and `trove:Add(SomeFunction(...))`.
|
||||
|
||||
```lua
|
||||
local Signal = require(somewhere.Signal)
|
||||
|
||||
-- All of these are identical:
|
||||
local s = trove:Construct(Signal)
|
||||
local s = trove:Construct(Signal.new)
|
||||
local s = trove:Construct(function() return Signal.new() end)
|
||||
local s = trove:Add(Signal.new())
|
||||
|
||||
-- Even Roblox instances can be created:
|
||||
local part = trove:Construct(Instance, "Part")
|
||||
```
|
||||
]=]
|
||||
function Trove:Construct(class, ...)
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:Construct() while cleaning", 2)
|
||||
end
|
||||
local object = nil
|
||||
local t = type(class)
|
||||
if t == "table" then
|
||||
object = class.new(...)
|
||||
elseif t == "function" then
|
||||
object = class(...)
|
||||
end
|
||||
return self:Add(object)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param signal RBXScriptSignal
|
||||
@param fn (...: any) -> ()
|
||||
@return RBXScriptConnection
|
||||
Connects the function to the signal, adds the connection
|
||||
to the trove, and then returns the connection.
|
||||
|
||||
This is shorthand for `trove:Add(signal:Connect(fn))`.
|
||||
|
||||
```lua
|
||||
trove:Connect(workspace.ChildAdded, function(instance)
|
||||
print(instance.Name .. " added to workspace")
|
||||
end)
|
||||
```
|
||||
]=]
|
||||
function Trove:Connect(signal: RBXScriptSignal, fn)
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:Connect() while cleaning", 2)
|
||||
end
|
||||
return self:Add(signal:Connect(fn))
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param signal RBXScriptSignal
|
||||
@param fn (...: any) -> ()
|
||||
@param metatable { [any]: any? }
|
||||
@param ... Passed arguements
|
||||
@return RBXScriptConnection
|
||||
Connects the function to the signal, adds the connection
|
||||
to the trove, and then returns the connection.
|
||||
|
||||
This is shorthand for `trove:Add(signal:Connect(fn))`.
|
||||
|
||||
```lua
|
||||
trove:ConnectMethod(workspace.ChildAdded, function(instance)
|
||||
print(instance.Name .. " added to workspace")
|
||||
end, ...)
|
||||
```
|
||||
]=]
|
||||
function Trove:ConnectMethod(signal: RBXScriptSignal, methodName: string, metatable, parameters: {any})
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:Connect() while cleaning", 2)
|
||||
end
|
||||
|
||||
if not metatable[methodName] then
|
||||
error("Cannot call trove:Connect() because the method does not exist: " .. methodName, 2)
|
||||
end
|
||||
|
||||
return self:Add(signal:Connect(function()
|
||||
metatable[methodName](metatable, table.unpack(parameters))
|
||||
end))
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param name string
|
||||
@param priority number
|
||||
@param fn (dt: number) -> ()
|
||||
Calls `RunService:BindToRenderStep` and registers a function in the
|
||||
trove that will call `RunService:UnbindFromRenderStep` on cleanup.
|
||||
|
||||
```lua
|
||||
trove:BindToRenderStep("Test", Enum.RenderPriority.Last.Value, function(dt)
|
||||
-- Do something
|
||||
end)
|
||||
```
|
||||
]=]
|
||||
function Trove:BindToRenderStep(name: string, priority: number, fn: (dt: number) -> ())
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:BindToRenderStep() while cleaning", 2)
|
||||
end
|
||||
RunService:BindToRenderStep(name, priority, fn)
|
||||
self:Add(function()
|
||||
RunService:UnbindFromRenderStep(name)
|
||||
end)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param promise Promise
|
||||
@return Promise
|
||||
Gives the promise to the trove, which will cancel the promise if the trove is cleaned up or if the promise
|
||||
is removed. The exact promise is returned, thus allowing chaining.
|
||||
|
||||
```lua
|
||||
trove:AddPromise(doSomethingThatReturnsAPromise())
|
||||
:andThen(function()
|
||||
print("Done")
|
||||
end)
|
||||
-- Will cancel the above promise (assuming it didn't resolve immediately)
|
||||
trove:Clean()
|
||||
|
||||
local p = trove:AddPromise(doSomethingThatReturnsAPromise())
|
||||
-- Will also cancel the promise
|
||||
trove:Remove(p)
|
||||
```
|
||||
|
||||
:::caution Promise v4 Only
|
||||
This is only compatible with the [roblox-lua-promise](https://eryn.io/roblox-lua-promise/) library, version 4.
|
||||
:::
|
||||
]=]
|
||||
function Trove:AddPromise(promise)
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:AddPromise() while cleaning", 2)
|
||||
end
|
||||
AssertPromiseLike(promise)
|
||||
if promise:getStatus() == "Started" then
|
||||
promise:finally(function()
|
||||
if self._cleaning then
|
||||
return
|
||||
end
|
||||
self:_findAndRemoveFromObjects(promise, false)
|
||||
end)
|
||||
self:Add(promise, "cancel")
|
||||
end
|
||||
return promise
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param object any -- Object to track
|
||||
@param cleanupMethod string? -- Optional cleanup name override
|
||||
@return object: any
|
||||
Adds an object to the trove. Once the trove is cleaned or
|
||||
destroyed, the object will also be cleaned up.
|
||||
|
||||
The following types are accepted (e.g. `typeof(object)`):
|
||||
|
||||
| Type | Cleanup |
|
||||
| ---- | ------- |
|
||||
| `Instance` | `object:Destroy()` |
|
||||
| `RBXScriptConnection` | `object:Disconnect()` |
|
||||
| `function` | `object()` |
|
||||
| `thread` | `coroutine.close(object)` |
|
||||
| `table` | `object:Destroy()` _or_ `object:Disconnect()` |
|
||||
| `table` with `cleanupMethod` | `object:<cleanupMethod>()` |
|
||||
|
||||
Returns the object added.
|
||||
|
||||
```lua
|
||||
-- Add a part to the trove, then destroy the trove,
|
||||
-- which will also destroy the part:
|
||||
local part = Instance.new("Part")
|
||||
trove:Add(part)
|
||||
trove:Destroy()
|
||||
|
||||
-- Add a function to the trove:
|
||||
trove:Add(function()
|
||||
print("Cleanup!")
|
||||
end)
|
||||
trove:Destroy()
|
||||
|
||||
-- Standard cleanup from table:
|
||||
local tbl = {}
|
||||
function tbl:Destroy()
|
||||
print("Cleanup")
|
||||
end
|
||||
trove:Add(tbl)
|
||||
|
||||
-- Custom cleanup from table:
|
||||
local tbl = {}
|
||||
function tbl:DoSomething()
|
||||
print("Do something on cleanup")
|
||||
end
|
||||
trove:Add(tbl, "DoSomething")
|
||||
```
|
||||
]=]
|
||||
function Trove:Add(object: any, cleanupMethod: string?): any
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:Add() while cleaning", 2)
|
||||
end
|
||||
local cleanup = GetObjectCleanupFunction(object, cleanupMethod)
|
||||
table.insert(self._objects, { object, cleanup })
|
||||
return object
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param object any -- Object to remove
|
||||
Removes the object from the Trove and cleans it up.
|
||||
|
||||
```lua
|
||||
local part = Instance.new("Part")
|
||||
trove:Add(part)
|
||||
trove:Remove(part)
|
||||
```
|
||||
]=]
|
||||
function Trove:Remove(object: any): boolean
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:Remove() while cleaning", 2)
|
||||
end
|
||||
return self:_findAndRemoveFromObjects(object, true)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Cleans up all objects in the trove. This is
|
||||
similar to calling `Remove` on each object
|
||||
within the trove. The ordering of the objects
|
||||
removed is _not_ guaranteed.
|
||||
]=]
|
||||
function Trove:Clean()
|
||||
if self._cleaning then
|
||||
return
|
||||
end
|
||||
self._cleaning = true
|
||||
for _, obj in self._objects do
|
||||
self:_cleanupObject(obj[1], obj[2])
|
||||
end
|
||||
table.clear(self._objects)
|
||||
self._cleaning = false
|
||||
end
|
||||
|
||||
function Trove:_findAndRemoveFromObjects(object: any, cleanup: boolean): boolean
|
||||
local objects = self._objects
|
||||
for i, obj in ipairs(objects) do
|
||||
if obj[1] == object then
|
||||
local n = #objects
|
||||
objects[i] = objects[n]
|
||||
objects[n] = nil
|
||||
if cleanup then
|
||||
self:_cleanupObject(obj[1], obj[2])
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Trove:_cleanupObject(object, cleanupMethod)
|
||||
if cleanupMethod == FN_MARKER then
|
||||
object()
|
||||
elseif cleanupMethod == THREAD_MARKER then
|
||||
coroutine.close(object)
|
||||
else
|
||||
object[cleanupMethod](object)
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@param instance Instance
|
||||
@return RBXScriptConnection
|
||||
Attaches the trove to a Roblox instance. Once this
|
||||
instance is removed from the game (parent or ancestor's
|
||||
parent set to `nil`), the trove will automatically
|
||||
clean up.
|
||||
|
||||
:::caution
|
||||
Will throw an error if `instance` is not a descendant
|
||||
of the game hierarchy.
|
||||
:::
|
||||
]=]
|
||||
function Trove:AttachToInstance(instance: Instance)
|
||||
if self._cleaning then
|
||||
error("Cannot call trove:AttachToInstance() while cleaning", 2)
|
||||
elseif not instance:IsDescendantOf(game) then
|
||||
error("Instance is not a descendant of the game hierarchy", 2)
|
||||
end
|
||||
return self:Connect(instance.Destroying, function()
|
||||
self:Destroy()
|
||||
end)
|
||||
end
|
||||
|
||||
--[=[
|
||||
Alias for `trove:Clean()`.
|
||||
]=]
|
||||
function Trove:Destroy()
|
||||
self:Clean()
|
||||
end
|
||||
|
||||
return Trove
|
Loading…
Reference in a new issue