Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tooltips to buttons #637

Merged
merged 6 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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