Proper UI Component handling?

Guys, I need help with something.

Here’s my current file structure:

  • ReplicatedStorage
    • Library
      • Interface
        • Components
          • Buttons
            • MenuButton
          • Panels
            • Episodes
        • Screens
  • StarterPlayer
    • StarterPlayerScripts
      • UISetup

I’m handling my UI components using Fusion. I have a template menu button and have an Episodes panel. I handle my all my UI components using the UISetup script in StarterPlayerScripts. I loop over the Panels folder and parent the component the module script returns and parent them to the menu screen.

I can’t think of a way to create the MenuButton in such a way where it will have a system of binding a panel. That’s the basic idea.

Right now, here’s my code to handle the ui components (Component.luau):

No need to tell me that the current code doesn’t work, it’s still wip

--!strict
-- // Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- // Folders
local Packages = ReplicatedStorage.Packages

-- // Dependencies
local Fusion = require(Packages.Fusion)
local Library = require(ReplicatedStorage.Library)
local Messages = Library.Config.Messages

-- // Variables
local CS = {} -- Component System

-- // Modification
-- // Functions
function CS.Build(
    class: "Panel" | "Button",
    states: (Value: any) -> { [any]: Fusion.StateObject<any> },
    component: (fusion: any, states: {}) -> Instance
): {
    Class: "Button" | "Panel",
    States: {},
    Component: Instance,
    Binded: {}?,
}
    if class == "Panel" then
        print("what did i do")
    elseif class == "Button" then
        local BindedComponent

        function Bind(class: { [any]: any })
            if typeof(class) == "table" and class.Class == "Panel" then
                BindedComponent = class
            end
        end

        return {
            Class = "Button",
            Component = component(Fusion, states(Fusion.Value)) :: Instance,
            Bind = Bind,
            Binded = BindedComponent,
        }
    else
        Messages.Warn("InvalidClass", "Button, Panel")
        return nil
    end
end

-- // Event Listeners
-- // Returning
return CS
5 Likes

Would be good if anybody could help…

2 Likes

Your actual problem isn’t really clear. No offense. Does your code error or do you want advice on better project structure?

1 Like

Ah, sorry for the lack of clarification. Basically, I don’t need any advice on how I’m gonna organize the files, I already have it written down. The main problem is the code. How exactly I’m going to create a system where I can bind a frame/panel to a button, create states etc. I’m currently unsure of how to organize the code.

Here’s how my files are organized:
image

3 Likes

Oh, gotcha. I never personally worked with Fusion. Could you give an example/specific use case of what you want to achieve, just a rough idea of what you want. I think that will really clear things up. I’m not sure if you want to achieve something like shared state since you mentioned binding frames/creating states.

2 Likes

Sure thing! Before working on the custom Components system (the code I sent above), I was doing it script-by-script. Here’s what I mean. Under the Buttons folder, I have a MenuButton module script:

-- // Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- // Dependencies
local Library = require(ReplicatedStorage.Library)
local Fusion = require(ReplicatedStorage.Packages.Fusion)

-- // Variables
local New = Fusion.New
local Children = Fusion.Children
local Value = Fusion.Value

-- // Functions
local function CreateButton(props: props)
	local States = {}

	local Button = New("TextButton")({
		Name = props.Name or "MenuButton",
		FontFace = Font.fromEnum(Enum.Font.SourceSans),
		Text = "",
		TextColor3 = Color3.fromRGB(0, 0, 0),
		TextSize = 14,
		AnchorPoint = Vector2.new(0.5, 0.5),
		BackgroundColor3 = Color3.fromRGB(255, 255, 255),
		BorderColor3 = Color3.fromRGB(0, 0, 0),
		BorderSizePixel = 0,
		Position = UDim2.fromScale(0.35, 0.5),
		Size = UDim2.fromScale(1, 0.09),
		LayoutOrder = props.Order or 1,
		...
	})

	return {
		States = States,
		Component = Button,
	}
end

-- // Types
type props = {
	Name: string?,
	Order: number?,
}

-- // Returning
return function(props: props)
	local Button = CreateButton(props)
	local Panel

	local function Bind(panel)
		if typeof(panel) == "function" then
			Panel = panel()
		end
	end

	local function Open(boolean: boolean)
		Panel.States.Open:set(boolean)
		print(Panel.States.Open:get())
	end

	return {
		States = Button.States,
		Component = Button.Component,
		Bind = Bind,
		Open = Open,
	}
end

And a panel code:

-- // Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

-- // Dependencies
local Library = require(ReplicatedStorage.Library)
local Fusion = require(ReplicatedStorage.Packages.Fusion)

-- // Variables
local New = Fusion.New
local Children = Fusion.Children
local Value = Fusion.Value
local Spring = Fusion.Spring
local Observer = Fusion.Observer

-- local Info = TweenInfo.new(0.25, Enum.EasingStyle.Exponential, Enum.EasingDirection.InOut)

-- // Functions
return function()
	local States = {
		Open = Value(false),
		Opacity = Value(1),
		Position = Value(UDim2.fromScale(0.75, 0.5)),
	}

	local Component = New("CanvasGroup")({
		Name = "EpisodePanel",
		GroupTransparency = Spring(States.Opacity, 20, 0.5),
		AnchorPoint = Vector2.new(0.5, 0.5),
		BackgroundTransparency = 1,
		BorderSizePixel = 0,
		Position = Spring(States.Position, 20, 0.5),
		Size = UDim2.fromScale(0.6, 0.7),
		...
	})

	Observer(States.Open):onChange(function()
		local IsOpened = States.Open:get()

		if IsOpened == true then
			States.Opacity:set(0)
			States.Position:set(UDim2.fromScale(0.5, 0.5))
		elseif IsOpened == false then
			States.Opacity:set(1)
			States.Position:set(UDim2.fromScale(0.75, 0.5))
		end
	end)

	return {
		Component = Component,
		States = States,
	}
end

Oh, I see. So, if I understand correctly, you want to bind the Panel component to the Button component. Are you looking for shared state or some kind of inheritance approach?

Trying out some inheritance approach since I’m defining each panel, but for menu button I’m just creating the template. I have a UISetup code where I actually initialize everything.

So menu button is just a combination of Panel state/functionality on regular button component. If that’s the case then I would just create an entire separate component if it’s going to used across your UI. I’m really assuming a lot here, like aforementioned I never personally used Fusion, so please correct me if I’m mistaken.

It’s completely fine! I’m not asking for code. I just need some suggestions on it.

Also yeah, I am creating a separate component for each of the items like a menu button, a settings panel and such. Is there any way you could just give me some advices to make my Component code better (the first code I sent)?

Alright, since we’ve established menu button and other soon to be made components are derived from Button and Panel, you could make another component specifically as a base for things like menu button and such, if you know what I mean. So instead of building either Panel or Button you would build the base and define the presets [state in this case], I’m sorry if this makes no sense but I’m attempting to give solutions besides my limited knowledge with Fusion.

Your solution isn’t bad. And it doesn’t have to be specifically within fusion though! I just need the idea, and the coding’s up to me! This might go a liiiiittle complex, but I’m sure it’d be good. I’ll let you know if I have any progress.

Thanks for the advice!

No problem, I really hope you find a clean/clear solution to your problem. Have a wonderful day :smile:!