Skip to content

Commit

Permalink
Add tooltips to buttons (rojo-rbx#637)
Browse files Browse the repository at this point in the history
* Add tooltips

* Fix whitespace

* Avoid overloaded word canvas

* Clean render function

* Switch folder to fragment
  • Loading branch information
boatbomber authored Oct 7, 2022
1 parent fda8ccf commit dafe801
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 89 deletions.
1 change: 1 addition & 0 deletions plugin/src/App/Components/BorderedContainer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ local function BorderedContainer(props)
Content = e("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
ZIndex = 2,
}, props[Roact.Children]),

Border = e(SlicedImage, {
Expand Down
5 changes: 5 additions & 0 deletions plugin/src/App/Components/Checkbox.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local Theme = require(Plugin.App.Theme)
local bindingUtil = require(Plugin.App.bindingUtil)

local SlicedImage = require(script.Parent.SlicedImage)
local Tooltip = require(script.Parent.Tooltip)

local e = Roact.createElement

Expand Down Expand Up @@ -52,6 +53,10 @@ function Checkbox:render()

[Roact.Event.Activated] = self.props.onClick,
}, {
StateTip = e(Tooltip.Trigger, {
text = if self.props.active then "Enabled" else "Disabled",
}),

Active = e(SlicedImage, {
slice = Assets.Slices.RoundedBackground,
color = theme.Active.BackgroundColor,
Expand Down
2 changes: 2 additions & 0 deletions plugin/src/App/Components/IconButton.lua
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ function IconButton:render()

BackgroundTransparency = 1,
}),

Children = Roact.createFragment(self.props[Roact.Children]),
})
end

Expand Down
2 changes: 2 additions & 0 deletions plugin/src/App/Components/TextButton.lua
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ function TextButton:render()

zIndex = -2,
}),

Children = Roact.createFragment(self.props[Roact.Children]),
})
end)
end
Expand Down
226 changes: 226 additions & 0 deletions plugin/src/App/Components/Tooltip.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
local TextService = game:GetService("TextService")
local HttpService = game:GetService("HttpService")

local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages

local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme)

local BorderedContainer = require(Plugin.App.Components.BorderedContainer)

local e = Roact.createElement

local DELAY = 0.75 -- How long to hover before a popup is shown (seconds)
local TEXT_PADDING = Vector2.new(8 * 2, 6 * 2) -- Padding for the popup text containers
local TAIL_SIZE = 16 -- Size of the triangle tail piece
local X_OFFSET = 30 -- How far right (from left) the tail will be (assuming enough space)
local Y_OVERLAP = 10 -- Let the triangle tail piece overlap the target a bit to help "connect" it

local TooltipContext = Roact.createContext({})

local function Popup(props)
local textSize = TextService:GetTextSize(
props.Text, 16, Enum.Font.GothamMedium, Vector2.new(math.min(props.parentSize.X, 160), math.huge)
) + TEXT_PADDING

local trigger = props.Trigger:getValue()

local spaceBelow = props.parentSize.Y - (trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y - Y_OVERLAP + TAIL_SIZE)
local spaceAbove = trigger.AbsolutePosition.Y + Y_OVERLAP - TAIL_SIZE

-- If there's not enough space below, and there's more space above, then show the tooltip above the trigger
local displayAbove = spaceBelow < textSize.Y and spaceAbove > spaceBelow

local X = math.clamp(props.Position.X - X_OFFSET, 0, props.parentSize.X - textSize.X)
local Y = 0

if displayAbove then
Y = math.max(trigger.AbsolutePosition.Y - TAIL_SIZE - textSize.Y + Y_OVERLAP, 0)
else
Y = math.min(trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y + TAIL_SIZE - Y_OVERLAP, props.parentSize.Y - textSize.Y)
end

return Theme.with(function(theme)
return e(BorderedContainer, {
position = UDim2.fromOffset(X, Y),
size = UDim2.fromOffset(textSize.X, textSize.Y),
transparency = props.transparency,
}, {
Label = e("TextLabel", {
BackgroundTransparency = 1,
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.new(1, -TEXT_PADDING.X, 1, -TEXT_PADDING.Y),
AnchorPoint = Vector2.new(0.5, 0.5),
Text = props.Text,
TextSize = 16,
Font = Enum.Font.GothamMedium,
TextWrapped = true,
TextXAlignment = Enum.TextXAlignment.Left,
TextColor3 = theme.Button.Bordered.Enabled.TextColor,
TextTransparency = props.transparency,
}),

Tail = e("ImageLabel", {
ZIndex = 100,
Position =
if displayAbove then
UDim2.new(
0, math.clamp(props.Position.X - X, 6, textSize.X-6),
1, -3
)
else
UDim2.new(
0, math.clamp(props.Position.X - X, 6, textSize.X-6),
0, -TAIL_SIZE+3
),
Size = UDim2.fromOffset(TAIL_SIZE, TAIL_SIZE),
AnchorPoint = Vector2.new(0.5, 0),
Rotation = if displayAbove then 180 else 0,
BackgroundTransparency = 1,
Image = "rbxassetid://10983945016",
ImageColor3 = theme.BorderedContainer.BackgroundColor,
ImageTransparency = props.transparency,
}, {
Border = e("ImageLabel", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
Image = "rbxassetid://10983946430",
ImageColor3 = theme.BorderedContainer.BorderColor,
ImageTransparency = props.transparency,
}),
})
})
end)
end

local Provider = Roact.Component:extend("TooltipManager")

function Provider:init()
self:setState({
tips = {},
addTip = function(id: string, data: { Text: string, Position: Vector2, Trigger: any })
self:setState(function(state)
state.tips[id] = data
return state
end)
end,
removeTip = function(id: string)
self:setState(function(state)
state.tips[id] = nil
return state
end)
end,
})
end

function Provider:render()
return Roact.createElement(TooltipContext.Provider, {
value = self.state,
}, self.props[Roact.Children])
end

local Container = Roact.Component:extend("TooltipContainer")

function Container:init()
self:setState({
size = Vector2.new(200, 100),
})
end

function Container:render()
return Roact.createElement(TooltipContext.Consumer, {
render = function(context)
local tips = context.tips
local popups = {}

for key, value in tips do
popups[key] = e(Popup, {
Text = value.Text or "",
Position = value.Position or Vector2.zero,
Trigger = value.Trigger,

parentSize = self.state.size,
})
end

return e("Frame", {
[Roact.Change.AbsoluteSize] = function(rbx)
self:setState({
size = rbx.AbsoluteSize,
})
end,
ZIndex = 100,
BackgroundTransparency = 1,
Size = UDim2.fromScale(1, 1),
}, popups)
end,
})
end

local Trigger = Roact.Component:extend("TooltipTrigger")

function Trigger:init()
self.id = HttpService:GenerateGUID(false)
self.ref = Roact.createRef()
self.mousePos = Vector2.zero

self.destroy = function()
self.props.context.removeTip(self.id)
end
end

function Trigger:willUnmount()
if self.showDelayThread then
task.cancel(self.showDelayThread)
end
if self.destroy then
self.destroy()
end
end

function Trigger:render()
return e("Frame", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
ZIndex = self.props.zIndex or 100,
[Roact.Ref] = self.ref,

[Roact.Event.MouseMoved] = function(_rbx, x, y)
self.mousePos = Vector2.new(x, y)
end,
[Roact.Event.MouseEnter] = function()
self.showDelayThread = task.delay(DELAY, function()
self.props.context.addTip(self.id, {
Text = self.props.text,
Position = self.mousePos,
Trigger = self.ref,
})
end)
end,
[Roact.Event.MouseLeave] = function()
if self.showDelayThread then
task.cancel(self.showDelayThread)
end
self.props.context.removeTip(self.id)
end,
})
end

local function TriggerConsumer(props)
return Roact.createElement(TooltipContext.Consumer, {
render = function(context)
local innerProps = table.clone(props)
innerProps.context = context

return e(Trigger, innerProps)
end,
})
end

return {
Provider = Provider,
Container = Container,
Trigger = TriggerConsumer,
}
5 changes: 5 additions & 0 deletions plugin/src/App/StatusPages/Connected.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local Assets = require(Plugin.Assets)
local Header = require(Plugin.App.Components.Header)
local IconButton = require(Plugin.App.Components.IconButton)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local Tooltip = require(Plugin.App.Components.Tooltip)

local e = Roact.createElement

Expand Down Expand Up @@ -90,6 +91,10 @@ local function ConnectionDetails(props)
anchorPoint = Vector2.new(1, 0.5),

onClick = props.onDisconnect,
}, {
Tip = e(Tooltip.Trigger, {
text = "Disconnect from the Rojo sync server"
}),
}),

Padding = e("UIPadding", {
Expand Down
5 changes: 5 additions & 0 deletions plugin/src/App/StatusPages/Error.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local Theme = require(Plugin.App.Theme)
local TextButton = require(Plugin.App.Components.TextButton)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
local Tooltip = require(Plugin.App.Components.Tooltip)

local e = Roact.createElement

Expand Down Expand Up @@ -123,6 +124,10 @@ function ErrorPage:render()
transparency = self.props.transparency,
layoutOrder = 1,
onClick = self.props.onClose,
}, {
Tip = e(Tooltip.Trigger, {
text = "Dismiss message"
}),
}),

Layout = e("UIListLayout", {
Expand Down
9 changes: 9 additions & 0 deletions plugin/src/App/StatusPages/NotConnected.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local Theme = require(Plugin.App.Theme)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local TextButton = require(Plugin.App.Components.TextButton)
local Header = require(Plugin.App.Components.Header)
local Tooltip = require(Plugin.App.Components.Tooltip)

local PORT_WIDTH = 74
local DIVIDER_WIDTH = 1
Expand Down Expand Up @@ -116,6 +117,10 @@ function NotConnectedPage:render()
transparency = self.props.transparency,
layoutOrder = 1,
onClick = self.props.onNavigateSettings,
}, {
Tip = e(Tooltip.Trigger, {
text = "View and modify plugin settings"
}),
}),

Connect = e(TextButton, {
Expand All @@ -124,6 +129,10 @@ function NotConnectedPage:render()
transparency = self.props.transparency,
layoutOrder = 2,
onClick = self.props.onConnect,
}, {
Tip = e(Tooltip.Trigger, {
text = "Connect to a Rojo sync server"
}),
}),

Layout = e("UIListLayout", {
Expand Down
5 changes: 5 additions & 0 deletions plugin/src/App/StatusPages/Settings/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local Theme = require(Plugin.App.Theme)

local IconButton = require(Plugin.App.Components.IconButton)
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
local Tooltip = require(Plugin.App.Components.Tooltip)
local Setting = require(script.Setting)

local e = Roact.createElement
Expand Down Expand Up @@ -44,6 +45,10 @@ local function Navbar(props)
anchorPoint = Vector2.new(0, 0.5),

onClick = props.onBack,
}, {
Tip = e(Tooltip.Trigger, {
text = "Back"
}),
}),

Text = e("TextLabel", {
Expand Down
Loading

0 comments on commit dafe801

Please sign in to comment.