Skip to content

Commit

Permalink
Release v0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
boatbomber committed Mar 5, 2024
1 parent 052a40c commit fd69680
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 61 deletions.
59 changes: 51 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Via [wally](https://wally.run):

```toml
[dependencies]
GlassmorphicUI = "boatbomber/glassmorphicui@0.1.1"
GlassmorphicUI = "boatbomber/glassmorphicui@0.2.0"
```

Alternatively, grab the `.rbxm` standalone model from the latest [release.](https://github.com/boatbomber/GlassmorphicUI/releases/latest)
Expand All @@ -30,11 +30,54 @@ Compatible with UICorners and all other ImageLabel properties.
```lua
local GlassmorphicUI = require(Path.To.GlassmorphicUI)

local blurryWindow = GlassmorphicUI.new()
blurryWindow.BackgroundTransparency = 0.5
blurryWindow.BackgroundColor3 = Color3.fromRGB(7, 48, 84)
blurryWindow.Size = UDim2.fromScale(0.3, 0.3)
blurryWindow.Position = UDim2.fromScale(0.5, 0.5)
blurryWindow.AnchorPoint = Vector2.new(0.5, 0.5)
blurryWindow.Parent = ScreenGui
local glassyimage = GlassmorphicUI.new()
glassyimage.BackgroundTransparency = 0.5
glassyimage.BackgroundColor3 = Color3.fromRGB(7, 48, 84)
glassyimage.Size = UDim2.fromScale(0.3, 0.3)
glassyimage.Position = UDim2.fromScale(0.5, 0.5)
glassyimage.AnchorPoint = Vector2.new(0.5, 0.5)
glassyimage.Parent = ScreenGui
```

```Lua
function GlassmorphicUI.applyGlassToImageLabel(ImageLabel: ImageLabel): ()
```

Takes an existing ImageLabel and applies the glassmorphic effect to it.
Useful for integrating GlassmorphicUI with existing UI systems.

```lua
local GlassmorphicUI = require(Path.To.GlassmorphicUI)

local glassyimage = Instance.new("ImageLabel")
glassyimage.BackgroundTransparency = 0.5
glassyimage.BackgroundColor3 = Color3.fromRGB(7, 48, 84)
glassyimage.Size = UDim2.fromScale(0.3, 0.3)
glassyimage.Position = UDim2.fromScale(0.5, 0.5)
glassyimage.AnchorPoint = Vector2.new(0.5, 0.5)
glassyimage.Parent = ScreenGui

GlassmorphicUI.applyGlassToImageLabel(glassyimage)
```


```Lua
function GlassmorphicUI.addGlassBackground(GuiObject: GuiObject): ImageLabel
```

Takes an existing GuiObject (such as a Frame) and parents a glassy ImageLabel inside it.
The ImageLabel will have a very low ZIndex as to appear as the background of the GuiObject.
The GuiObject will be forced to have a BackgroundTransparency of 1, otherwise the effect would just show your GuiObject's background behind the glass.
Useful for integrating GlassmorphicUI with existing UI systems.

```lua
local GlassmorphicUI = require(Path.To.GlassmorphicUI)

local frame = Instance.new("Frame")
frame.Size = UDim2.fromScale(0.2, 0.2)
frame.Parent = script.Parent

local glassyimage = GlassmorphicUI.addGlassBackground(frame)
glassyimage.BackgroundTransparency = 0.5
glassyimage.BackgroundColor3 = Color3.fromRGB(7, 48, 84)
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@boatbomber/glassmorphicui",
"version": "0.1.1",
"version": "0.2.0",
"license": "MPL 2.0",
"repository": {
"type": "git",
Expand Down
154 changes: 103 additions & 51 deletions src/init.lua
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
--!strict
--!native
--!optimize 2

local RunService = game:GetService("RunService")

local EditableImageBlur, PixelColorApproximation
if script:FindFirstChild("Packages") then
-- Built as a stand-alone model
EditableImageBlur = require(script.Packages.EditableImageBlur)
PixelColorApproximation = require(script.Packages.PixelColorApproximation)
elseif
script.Parent:FindFirstChild("EditableImageBlur") and script.Parent:FindFirstChild("PixelColorApproximation")
local Packages = script:FindFirstChild("Packages") or script.Parent
local EditableImageBlurModule = Packages:FindFirstChild("EditableImageBlur")
local PixelColorApproximationModule = Packages:FindFirstChild("PixelColorApproximation")

if
EditableImageBlurModule
and EditableImageBlurModule:IsA("ModuleScript")
and PixelColorApproximationModule
and PixelColorApproximationModule:IsA("ModuleScript")
then
-- Installed via wally
EditableImageBlur = require(script.Parent.EditableImageBlur)
PixelColorApproximation = require(script.Parent.PixelColorApproximation)
else
EditableImageBlur = require(EditableImageBlurModule)
PixelColorApproximation = require(PixelColorApproximationModule)
end

if not EditableImageBlur or not PixelColorApproximation then
error("Could not find required packages")
end

Expand All @@ -38,7 +46,7 @@ GlassmorphicUI._glassObjects = {} :: { GlassObject }
GlassmorphicUI._glassObjectUpdateIndex = 1

GlassmorphicUI.MAX_AXIS_SAMPLING_RES = 39
GlassmorphicUI.UPDATE_TIME_BUDGET = 0.002
GlassmorphicUI.UPDATE_TIME_BUDGET = 0.0025
GlassmorphicUI.RADIUS = 5
GlassmorphicUI.TEMPORAL_SMOOTHING = 0.75

Expand All @@ -51,6 +59,36 @@ function GlassmorphicUI.new(): ImageLabel
Window.BackgroundTransparency = 0.8
Window.Name = "GlassmorphicUI"

return GlassmorphicUI._setupGlassWindow(Window)
end

function GlassmorphicUI.applyGlassToImageLabel(ImageLabel: ImageLabel)
if typeof(ImageLabel) == "Instance" and ImageLabel:IsA("ImageLabel") then
GlassmorphicUI._setupGlassWindow(ImageLabel)
end
end

function GlassmorphicUI.addGlassBackground(GuiObject: GuiObject): ImageLabel
if typeof(GuiObject) ~= "Instance" or not GuiObject:IsA("GuiObject") then
error("Expected GuiObject, got " .. typeof(GuiObject))
end

-- Ensure the glass isn't obstructed by the object
GuiObject.BackgroundTransparency = 1
GuiObject:GetPropertyChangedSignal("BackgroundTransparency"):Connect(function()
GuiObject.BackgroundTransparency = 1
end)

local glassBackground = GlassmorphicUI.new()
glassBackground.Size = UDim2.fromScale(1, 1)
glassBackground.Position = UDim2.fromScale(0, 0)
glassBackground.ZIndex = -999999
glassBackground.Parent = GuiObject

return glassBackground
end

function GlassmorphicUI._setupGlassWindow(Window: ImageLabel)
local EditableImage = Instance.new("EditableImage")
EditableImage.Parent = Window

Expand All @@ -74,7 +112,7 @@ function GlassmorphicUI.new(): ImageLabel
},
}

GlassmorphicUI:watchProperties(glassObject)
GlassmorphicUI._watchProperties(glassObject)

Window.Destroying:Connect(function()
local index = table.find(GlassmorphicUI._glassObjects, glassObject)
Expand All @@ -83,54 +121,61 @@ function GlassmorphicUI.new(): ImageLabel
end
end)

local initializeConnection
initializeConnection = Window.AncestryChanged:Connect(function()
if not Window:IsDescendantOf(game) then
return
end
if not Window:IsDescendantOf(game) then
local initializeConnection
initializeConnection = Window.AncestryChanged:Connect(function()
if not Window:IsDescendantOf(game) then
return
end

initializeConnection:Disconnect()
initializeConnection:Disconnect()

-- Wait for window properties to load
while Window.AbsoluteSize.X == 0 do
task.wait()
end
-- Wait for window properties to load
while Window.AbsoluteSize.X == 0 or Window.AbsoluteSize.Y == 0 do
task.wait()
end

GlassmorphicUI:updateWindowColor(glassObject)
GlassmorphicUI:updateWindowPosition(glassObject)
GlassmorphicUI:updateWindowSize(glassObject)
GlassmorphicUI._updateWindowColor(glassObject)
GlassmorphicUI._updateWindowPosition(glassObject)
GlassmorphicUI._updateWindowSize(glassObject)

table.insert(GlassmorphicUI._glassObjects, glassObject)
end)
else
GlassmorphicUI._updateWindowColor(glassObject)
GlassmorphicUI._updateWindowPosition(glassObject)
GlassmorphicUI._updateWindowSize(glassObject)
table.insert(GlassmorphicUI._glassObjects, glassObject)
end)
end

return Window
end

function GlassmorphicUI:watchProperties(glassObject: GlassObject)
function GlassmorphicUI._watchProperties(glassObject: GlassObject)
local Window = glassObject.Window

Window:GetPropertyChangedSignal("AbsolutePosition"):Connect(function()
GlassmorphicUI:updateWindowPosition(glassObject)
GlassmorphicUI._updateWindowPosition(glassObject)
end)
Window:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
GlassmorphicUI:updateWindowSize(glassObject)
GlassmorphicUI._updateWindowSize(glassObject)
end)
Window:GetPropertyChangedSignal("BackgroundColor3"):Connect(function()
GlassmorphicUI:updateWindowColor(glassObject)
GlassmorphicUI._updateWindowColor(glassObject)
end)
Window:GetPropertyChangedSignal("BackgroundTransparency"):Connect(function()
GlassmorphicUI:updateWindowColor(glassObject)
GlassmorphicUI._updateWindowColor(glassObject)
end)
end

function GlassmorphicUI:updateWindowPosition(glassObject: GlassObject)
function GlassmorphicUI._updateWindowPosition(glassObject: GlassObject)
local Window = glassObject.Window
local absolutePosition = Window.AbsolutePosition
glassObject.WindowPositionX = absolutePosition.X
glassObject.WindowPositionY = absolutePosition.Y
end

function GlassmorphicUI:updateWindowColor(glassObject: GlassObject)
function GlassmorphicUI._updateWindowColor(glassObject: GlassObject)
local Window = glassObject.Window
local windowAlpha = 1 - Window.BackgroundTransparency
local windowColor = Window.BackgroundColor3
Expand All @@ -140,7 +185,7 @@ function GlassmorphicUI:updateWindowColor(glassObject: GlassObject)
glassObject.WindowColor[4] = windowAlpha
end

function GlassmorphicUI:updateWindowSize(glassObject: GlassObject)
function GlassmorphicUI._updateWindowSize(glassObject: GlassObject)
local Window = glassObject.Window

local absoluteSize = Window.AbsoluteSize
Expand All @@ -154,7 +199,7 @@ function GlassmorphicUI:updateWindowSize(glassObject: GlassObject)
glassObject.WindowSizeY = windowSizeY

local maxAxis = math.max(windowSizeX, windowSizeY)
local samplerSize = maxAxis / math.min(self.MAX_AXIS_SAMPLING_RES, maxAxis)
local samplerSize = maxAxis / math.min(GlassmorphicUI.MAX_AXIS_SAMPLING_RES, maxAxis)

local resolutionX, resolutionY = windowSizeX // samplerSize, windowSizeY // samplerSize
local inverseResX, inverseResY = 1 / resolutionX, 1 / resolutionY
Expand Down Expand Up @@ -191,7 +236,7 @@ function GlassmorphicUI:updateWindowSize(glassObject: GlassObject)
end
end

function GlassmorphicUI:processNextPixel(glassObject: GlassObject)
function GlassmorphicUI._processNextPixel(glassObject: GlassObject)
local Window = glassObject.Window
if (not Window) or (not Window:IsDescendantOf(game)) then
return
Expand Down Expand Up @@ -241,9 +286,9 @@ function GlassmorphicUI:processNextPixel(glassObject: GlassObject)
color[3] = (1 - windowAlpha) * color[3] + windowAlpha * WindowColor[3]

local prevR, prevG, prevB = Pixels[PixelIndex], Pixels[PixelIndex + 1], Pixels[PixelIndex + 2]
Pixels[PixelIndex] = prevR + (color[1] - prevR) * self.TEMPORAL_SMOOTHING
Pixels[PixelIndex + 1] = prevG + (color[2] - prevG) * self.TEMPORAL_SMOOTHING
Pixels[PixelIndex + 2] = prevB + (color[3] - prevB) * self.TEMPORAL_SMOOTHING
Pixels[PixelIndex] = prevR + (color[1] - prevR) * GlassmorphicUI.TEMPORAL_SMOOTHING
Pixels[PixelIndex + 1] = prevG + (color[2] - prevG) * GlassmorphicUI.TEMPORAL_SMOOTHING
Pixels[PixelIndex + 2] = prevB + (color[3] - prevB) * GlassmorphicUI.TEMPORAL_SMOOTHING
--Pixels[pixelIndex + 3] = color[4]

PixelIndex += 8
Expand All @@ -255,41 +300,48 @@ function GlassmorphicUI:processNextPixel(glassObject: GlassObject)
glassObject.PixelIndex = PixelIndex
end

function GlassmorphicUI:update()
local totalGlassObjects = #self._glassObjects
function GlassmorphicUI._update()
local totalGlassObjects = #GlassmorphicUI._glassObjects
if totalGlassObjects == 0 then
return
end

local estimatedBlurTime = (totalGlassObjects * 27e-5)
local allottedPixelProcessingTime = GlassmorphicUI.UPDATE_TIME_BUDGET - estimatedBlurTime

local startClock = os.clock()

-- Process pixels until time is up
while os.clock() - startClock < self.UPDATE_TIME_BUDGET do
local glassObject = self._glassObjects[self._glassObjectUpdateIndex]
while os.clock() - startClock < allottedPixelProcessingTime do
local glassObject = GlassmorphicUI._glassObjects[GlassmorphicUI._glassObjectUpdateIndex]
if glassObject then
self:processNextPixel(glassObject)
GlassmorphicUI._processNextPixel(glassObject)
end
self._glassObjectUpdateIndex += 1
GlassmorphicUI._glassObjectUpdateIndex += 1

if self._glassObjectUpdateIndex > totalGlassObjects then
self._glassObjectUpdateIndex = 1
if GlassmorphicUI._glassObjectUpdateIndex > totalGlassObjects then
GlassmorphicUI._glassObjectUpdateIndex = 1
end
end

-- Apply the pixels to the images with blur
for _, glassObject in self._glassObjects do
for _, glassObject in GlassmorphicUI._glassObjects do
EditableImageBlur({
image = glassObject.EditableImage,
pixelData = glassObject.Pixels,
blurRadius = self.RADIUS,
blurRadius = GlassmorphicUI.RADIUS,
downscaleFactor = 1,
skipAlpha = true,
})
end
end

RunService.Heartbeat:Connect(function()
GlassmorphicUI:update()
GlassmorphicUI._update()
end)

return GlassmorphicUI
return table.freeze({
new = GlassmorphicUI.new,
applyGlassToImageLabel = GlassmorphicUI.applyGlassToImageLabel,
addGlassBackground = GlassmorphicUI.addGlassBackground,
})
2 changes: 1 addition & 1 deletion wally.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "boatbomber/glassmorphicui"
description = "Glassmorphic UI in Roblox"
version = "0.1.1"
version = "0.2.0"
license = "MPL 2.0"
authors = ["boatbomber (https://boatbomber.com)"]
registry = "https://github.com/upliftgames/wally-index"
Expand Down

0 comments on commit fd69680

Please sign in to comment.