1ForeverHD / TopbarPlus

Construct dynamic and intuitive topbar icons. Enhance the appearance and behaviour of these icons with features such as themes, dropdowns and menus.
https://devforum.roblox.com/t/topbarplus/1017485
Mozilla Public License 2.0
100 stars 129 forks source link

Wrapping Roblox Gui objects. #18

Closed noah-peeters closed 3 years ago

noah-peeters commented 3 years ago

It is currently not possible to wrap a Roblox UI object like a Frame, inside of an Icon. If I want to create a timer that has nice effects, using this module: https://devforum.roblox.com/t/numberspinner-module/1105961. I'm forced to use the Frame that it returns. I would like the frame to behave like an icon (one that is not clickable). This means having the same theme as other icons, adjustable position from the Topbar.

Some ideas that comes to mind:

1ForeverHD commented 3 years ago

Had a few hours to spare so I've introduced some new methods and modified internals to achieve the following:

Icon.new()
    :setName("CashSpinnerIcon")
    :setRight()
    :lock()
    :setSize(100, 32)
    :set("iconLabelSize", UDim2.new(1,0,1,0))
    :give(function(icon)
        -- This creates the icon spinner and parents it to the iconButton
        local labelSpinner = NumberSpinner.new()
        labelSpinner.Name = "LabelSpinner"
        labelSpinner.Decimals = 3
        labelSpinner.Duration = 0.25
        labelSpinner.Parent = icon:getInstance("iconButton")

        -- This creates a fake iconLabel which updates the property of all descendant spinner TextLabels when indexed
        local textLabel = {}
        setmetatable(textLabel, {__newindex = function(_, index, value)
            for _, textLabel in pairs(labelSpinner.Frame:GetDescendants()) do
                if textLabel:IsA("TextLabel") then
                    textLabel[index] = value
                end
            end
        end})

        -- This overrides existing instances and settings so that they update the spinners properties (instead of the old textlabel)
        local iconButton = icon:getInstance("iconButton")
        iconButton.ZIndex = 0
        icon:setInstance("iconLabel", textLabel, true) -- Passing ``true`` as the third argument destroys the original instance
        icon:modifySetting("iconText", {instanceNames = {}}) -- We do this to prevent text being modified within the metatable above
        icon:setInstance("iconLabelSpinner", labelSpinner.Frame)
        local settingsToConvert = {"iconLabelVisible", "iconLabelAnchorPoint", "iconLabelPosition", "iconLabelSize"}
        for _, settingName in pairs(settingsToConvert) do
            icon:modifySetting(settingName, {instanceNames = {"iconLabelSpinner"}})
        end

        -- This applies all the values we just updated
        icon:_updateAll()

        -- Finally, set your desired text
        coroutine.wrap(function()
            while wait(0.5) do
                labelSpinner.Value = math.random(100000)/1000
            end
        end)()
    end)

How is this? It's a little more code than I'd like necessary although essential to ensure the spinner integrates seamlessly with themes and other features and internals.

You can test this at the playground: https://www.roblox.com/games/6199274521/TopbarPlus-Playground?refPageId=4e9c29a8-6c23-4d42-bb34-41c7ea120389

If this works well I can then deploy the changes.

noah-peeters commented 3 years ago

That's awesome! Great work on implementing this so fast!

I would like if some stuff was abstracted away more. Like this code:

local textLabel = {}
setmetatable(textLabel, {__newindex = function(_, index, value)
    for _, textLabel in pairs(labelSpinner.Frame:GetDescendants()) do
        if textLabel:IsA("TextLabel") then
            textLabel[index] = value
        end
    end
end})

I would prefer if there was a function, that would search automatically for the Frame's children.

Same goes for the settingsConversion. Could there be a way to make it automatic?

Thanks for implementing this.

1ForeverHD commented 3 years ago

Sure thing, this utilises a convertLabelToNumberSpinner method which abstracts most of the above:

Icon.new()
    :setName("CashSpinnerIcon")
    :setRight()
    :lock()
    :setSize(100, 32)
    :give(function(icon)
        local labelSpinner = NumberSpinner.new()
        icon:convertLabelToNumberSpinner(labelSpinner)
        labelSpinner.Name = "LabelSpinner"
        labelSpinner.Decimals = 3
        labelSpinner.Duration = 0.25
        coroutine.wrap(function()
            while wait(0.5) do
                labelSpinner.Value = math.random(100000)/1000
            end
        end)()
    end)

You can test that at the playground and if it works well I can officially deploy and release.

noah-peeters commented 3 years ago

That is really awesome! This is exactly what I was looking for, thanks.

1ForeverHD commented 3 years ago

No problem and cheers for the feature suggestion, this has been officially released now in v2.5.1: https://devforum.roblox.com/t/topbarplus-v2-construct-intuitive-topbar-icons/1017485/92