Skip to content
2 changes: 1 addition & 1 deletion src/Elements/Container.lua
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ return function(Icon)
end
screenGuiCenter.Name = "TopbarCentered"
screenGuiCenter.DisplayOrder = Icon.baseDisplayOrder
screenGuiCenter.ScreenInsets = Enum.ScreenInsets.None
screenGuiCenter.ScreenInsets = Enum.ScreenInsets.TopbarSafeInsets
Icon.baseDisplayOrderChanged:Connect(function()
screenGuiCenter.DisplayOrder = Icon.baseDisplayOrder
end)
Expand Down
20 changes: 18 additions & 2 deletions src/Elements/Dropdown.lua
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ return function(icon)
end
totalHeight += height
end
-- FIX: Include UIListLayout padding in height calculation
-- Previously, the dropdown height didn't account for spacing between icons
-- causing a slight visual mismatch when MaxIcons was set
local listPadding = dropdownList.Padding.Offset
local visibleIconCount = math.min(maxIconsRoundedUp, #children)
if visibleIconCount > 1 then
totalHeight += listPadding * (visibleIconCount - 1)
end

-- Add container padding
totalHeight += dropdownPadding.PaddingTop.Offset + dropdownPadding.PaddingBottom.Offset
return totalHeight
end
Expand Down Expand Up @@ -274,10 +284,16 @@ return function(icon)
childIcon:getInstance("ClickRegion").NextSelectionUp = nextSelection
end
end
-- FIX: Include UIListLayout padding in height calculation
-- Previously, the dropdown height didn't account for spacing between icons
-- causing a slight visual mismatch when MaxIcons was set
local listPadding = dropdownList.Padding.Offset
local visibleIconCount = math.min(maxIconsRoundedUp, #orderedInstances)
if visibleIconCount > 1 then
totalHeight += listPadding * (visibleIconCount - 1)
end
totalHeight += dropdownPadding.PaddingTop.Offset + dropdownPadding.PaddingBottom.Offset

dropdownScroller.Size = UDim2.fromOffset(0, totalHeight)

end

dropdownJanitor:add(dropdownScroller:GetPropertyChangedSignal("AbsoluteCanvasSize"):Connect(updateMaxIconsListener))
Expand Down
87 changes: 87 additions & 0 deletions src/Features/Overflow.lua
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,93 @@ function Overflow.updateBoundary(alignment)
overflowIcon:select()
end

-- Restore relocated center icons when space is available
if not isCentral then
-- Only restore if both overflow menus are completely inactive
local leftOverflow = overflowIcons["Left"]
local rightOverflow = overflowIcons["Right"]
local leftIsInactive = not leftOverflow or not leftOverflow.isEnabled
local rightIsInactive = not rightOverflow or not rightOverflow.isEnabled
local bothSidesStable = leftIsInactive and rightIsInactive

if not joinOverflow and bothSidesStable then
local relocatedIcons = {}
for _, icon in pairs(ourOrderedIcons) do
if icon.hasRelocatedInOverflow and not icon.parentIconUID then
table.insert(relocatedIcons, icon)
end
end

if #relocatedIcons > 0 then
table.sort(relocatedIcons, function(a, b)
return (a.widget.LayoutOrder or 0) < (b.widget.LayoutOrder or 0)
end)

for _, icon in ipairs(relocatedIcons) do
local centerOrderedIcons = Overflow.getAvailableIcons("Center")
local centerHolder = holders["Center"]
local centerHolderXPos = centerHolder.AbsolutePosition.X
local centerHolderXSize = centerHolder.AbsoluteSize.X
local centerUIList = centerHolder.UIListLayout
local centerPadding = centerUIList.Padding.Offset

local totalCenterWidth = 0
for _, centerIcon in pairs(centerOrderedIcons) do
totalCenterWidth += Overflow.getWidth(centerIcon) + centerPadding
end
totalCenterWidth += Overflow.getWidth(icon) + centerPadding

local leftOrderedIcons = Overflow.getAvailableIcons("Left")
local rightOrderedIcons = Overflow.getAvailableIcons("Right")

local leftBoundary = centerHolderXPos
local rightBoundary = centerHolderXPos + centerHolderXSize

-- Calculate boundaries excluding relocated icons
if #leftOrderedIcons > 0 then
local leftRealXPositions = Overflow.getRealXPositions("Left", leftOrderedIcons)
for _, leftIcon in pairs(leftOrderedIcons) do
if not leftIcon.hasRelocatedInOverflow then
local leftIconX = leftRealXPositions[leftIcon.UID]
local leftIconWidth = Overflow.getWidth(leftIcon)
leftBoundary = math.max(leftBoundary, leftIconX + leftIconWidth + BOUNDARY_GAP)
end
end
end

if #rightOrderedIcons > 0 then
local rightRealXPositions = Overflow.getRealXPositions("Right", rightOrderedIcons)
for _, rightIcon in pairs(rightOrderedIcons) do
if not rightIcon.hasRelocatedInOverflow then
local rightIconX = rightRealXPositions[rightIcon.UID]
rightBoundary = math.min(rightBoundary, rightIconX - BOUNDARY_GAP)
end
end
end

-- Safety margin to prevent oscillation
local SAFETY_MARGIN = BOUNDARY_GAP * 2.5
local availableCenterWidth = rightBoundary - leftBoundary - SAFETY_MARGIN

if availableCenterWidth > 0 and totalCenterWidth <= availableCenterWidth then
icon:align("Center")
icon.hasRelocatedInOverflow = nil

Overflow.updateAvailableIcons("Center")
Overflow.updateAvailableIcons(alignment)

task.spawn(function()
task.wait(0.2)
Overflow.updateBoundary("Left")
Overflow.updateBoundary("Right")
end)

break
end
end
end
end
end
end


Expand Down
17 changes: 17 additions & 0 deletions src/Types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,23 @@ type Methods = {
return nil :: any
end
),
bindSignal: typeof(
--[[
Connects an <code>RBXScriptSignal</code> (such as <code>Touched</code> or <code>Changed</code>) to the icon.
<code>callback</code> is called with arguments <code>(self, ...)</code> when the signal fires.
]]
function(self: Icon, signal: RBXScriptSignal, callback: (...any) -> ()): Icon
return nil :: any
end
),
unbindSignal: typeof(
--[[
Disconnects a signal previously bound with <code>:bindSignal()</code>.
]]
function(self: Icon, signal: RBXScriptSignal): Icon
return nil :: any
end
),
bindToggleKey: typeof(
--[[
Binds a keycode which toggles the icon when pressed.
Expand Down
22 changes: 22 additions & 0 deletions src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ function Icon.new()
self.dropdownIcons = {}
self.childIconsDict = {}
self.creationTime = os.clock()
self.bindedSignalEvents = {}

-- Widget is the new name for an icon
local widget = janitor:add(require(elements.Widget)(self, Icon))
Expand Down Expand Up @@ -924,6 +925,27 @@ function Icon:unbindEvent(iconEventName)
return self
end

function Icon:bindSignal(signal, signalFunction)
assert(typeof(signal) == "RBXScriptSignal", "argument[1] must be a valid RBXScriptSignal!")
assert(typeof(signalFunction) == "function", "argument[2] must be a function!")

local connection = signal:Connect(function(...)
signalFunction(self, ...)
end)
self.bindSignalEvents[signal] = connection
return self
end

function Icon:unbindSignal(signal)
local connection = self.bindSignalEvents[signal]
if connection then
connection:Disconnect()
self.bindSignalEvents[signal] = nil
end

return self
end

function Icon:bindToggleKey(keyCodeEnum)
assert(typeof(keyCodeEnum) == "EnumItem", "argument[1] must be a KeyCode EnumItem!")
self.bindedToggleKeys[keyCodeEnum] = true
Expand Down