diff --git a/LuaPackages/Packages/_Index/InGameMenuDependencies/lock.toml b/LuaPackages/Packages/_Index/InGameMenuDependencies/lock.toml index fa14534936..9dfdfd6510 100644 --- a/LuaPackages/Packages/_Index/InGameMenuDependencies/lock.toml +++ b/LuaPackages/Packages/_Index/InGameMenuDependencies/lock.toml @@ -10,6 +10,6 @@ dependencies = [ "Roact roblox/roact 1.3.0 url+https://github.com/roblox/roact", "RoactRodux roblox/roact-rodux 0.2.2 url+https://github.com/roblox/roact-rodux", "Rodux roblox/rodux 1.0.0 url+https://github.com/roblox/rodux", - "UIBlox UIBlox a253d523 git+https://github.com/roblox/uiblox#master", + "UIBlox UIBlox d3684f48 git+https://github.com/roblox/uiblox#master", "t roblox/t 1.2.5 url+https://github.com/roblox/t", ] diff --git a/LuaPackages/Packages/_Index/LuaChatDeps/lock.toml b/LuaPackages/Packages/_Index/LuaChatDeps/lock.toml index d9f8d4e18f..a7ee9b3dae 100644 --- a/LuaPackages/Packages/_Index/LuaChatDeps/lock.toml +++ b/LuaPackages/Packages/_Index/LuaChatDeps/lock.toml @@ -7,5 +7,5 @@ dependencies = [ "AssetCard asset-card c7b683fb git+https://github.com/roblox/asset-card#v1.0.2", "InfiniteScroller roblox/infinite-scroller 0.5.6 url+https://github.com/roblox/infinite-scroller", "RoduxNetworking rodux-networking ea19cfe3 git+https://github.rbx.com/roblox/rodux-networking#v1.0.1", - "UIBlox UIBlox a253d523 git+https://github.com/roblox/uiblox#master", + "UIBlox UIBlox d3684f48 git+https://github.com/roblox/uiblox#master", ] diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Button/ActionBar.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Button/ActionBar.lua new file mode 100644 index 0000000000..487e7cc997 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Button/ActionBar.lua @@ -0,0 +1,181 @@ +local Button = script.Parent +local App = Button.Parent +local UIBlox = App.Parent +local Packages = UIBlox.Parent + +local Roact = require(Packages.Roact) +local Cryo = require(Packages.Cryo) +local RoactGamepad = require(Packages.RoactGamepad) +local t = require(Packages.t) + +local FitFrame = require(Packages.FitFrame) +local FitFrameOnAxis = FitFrame.FitFrameOnAxis + +local PrimaryContextualButton = require(Button.PrimaryContextualButton) +local PrimarySystemButton = require(Button.PrimarySystemButton) +local IconButton = require(Button.IconButton) +local withStyle = require(UIBlox.Core.Style.withStyle) +local IconSize = require(App.ImageSet.Enum.IconSize) +local getPageMargin = require(App.Container.getPageMargin) +local UIBloxConfig = require(UIBlox.UIBloxConfig) +local validateButtonProps = require(Button.validateButtonProps) +local validateIconButtonProps = IconButton.validateProps + +local ActionBar = Roact.PureComponent:extend("ActionBar") + +local BUTTON_PADDING = 12 +local BUTTON_HEIGHT = 48 +local ICON_SIZE = 36 + +function ActionBar:init() + self.buttonRefs = RoactGamepad.createRefCache() + + self.state = { + frameWidth = 0 + } + + self.updateFrameSize = function(rbx) + local frameWidth = rbx.AbsoluteSize.X + if frameWidth ~= self.state.frameWidth then + self:setState({ + frameWidth = frameWidth, + }) + end + end +end + +ActionBar.validateProps = t.strictInterface({ + -- buttons: A table of button tables that contain props that PrimaryContextualButton allow. + button = t.optional(t.strictInterface({ + props = validateButtonProps, + })), + + -- icons: A table of button tables that contain props that IconButton allow. + icons = t.optional(t.array(t.strictInterface({ + props = validateIconButtonProps + }))), + + -- Children + [Roact.Children] = t.optional(t.table), + + -- optional parameters for RoactGamepad + NextSelectionLeft = t.optional(t.table), + NextSelectionRight = t.optional(t.table), + NextSelectionUp = t.optional(t.table), + NextSelectionDown = t.optional(t.table), + [Roact.Ref] = t.optional(t.table), +}) + +function ActionBar:render() + + return withStyle(function(stylePalette) + local margin = getPageMargin(self.state.frameWidth) + local contentWidth = self.state.frameWidth - margin * 2 + local iconSize = IconSize.Medium + + local iconNumber = 0 + if self.props.icons and #self.props.icons then + iconNumber = #self.props.icons + end + + local buttonTable = {} + + if iconNumber ~= 0 then + for iconButtonIndex, iconButton in ipairs(self.props.icons) do + local newProps = { + layoutOrder = iconButtonIndex, + iconSize = iconSize, + } + local iconButtonProps = Cryo.Dictionary.join(newProps, iconButton.props) + + if UIBloxConfig.enableExperimentalGamepadSupport then + local gamepadFrameProps = { + Size = UDim2.fromOffset(ICON_SIZE,ICON_SIZE), + BackgroundTransparency = 1, + [Roact.Ref] = self.buttonRefs[iconButtonIndex], + NextSelectionUp = nil, + NextSelectionDown = nil, + NextSelectionLeft = iconButtonIndex > 1 and self.buttonRefs[iconButtonIndex - 1] or nil, + NextSelectionRight = iconButtonIndex < iconNumber and self.buttonRefs[iconButtonIndex + 1] or nil, + inputBindings = { + [Enum.KeyCode.ButtonA] = iconButtonProps.onActivated, + }, + } + + table.insert(buttonTable, Roact.createElement(RoactGamepad.Focusable.Frame, gamepadFrameProps, { + Roact.createElement(IconButton, iconButtonProps) + })) + else + table.insert(buttonTable, Roact.createElement(IconButton, iconButtonProps)) + end + end + end + + if self.props.button then + local button = self.props.button + + local buttonSize = UDim2.fromOffset(contentWidth - iconNumber * (ICON_SIZE + BUTTON_PADDING), BUTTON_HEIGHT) + + local newProps = { + layoutOrder = iconNumber + 1, + size = buttonSize, + } + local buttonProps = Cryo.Dictionary.join(newProps, button.props) + + if UIBloxConfig.enableExperimentalGamepadSupport then + local gamepadFrameProps = { + Size = buttonSize, + BackgroundTransparency = 1, + [Roact.Ref] = self.buttonRefs[iconNumber + 1], + NextSelectionUp = nil, + NextSelectionDown = nil, + NextSelectionLeft = iconNumber and self.buttonRefs[iconNumber] or nil, + NextSelectionRight = nil, + inputBindings = { + [Enum.KeyCode.ButtonA] = buttonProps.onActivated, + }, + } + + table.insert(buttonTable, Roact.createElement(RoactGamepad.Focusable.Frame, gamepadFrameProps, { + Roact.createElement(iconNumber == 0 and PrimarySystemButton or PrimaryContextualButton, buttonProps) + })) + else + table.insert(buttonTable, + Roact.createElement(iconNumber == 0 and PrimarySystemButton or PrimaryContextualButton, buttonProps)) + end + end + + if self.props[Roact.Children] then + buttonTable = self.props[Roact.Children] + end + + return Roact.createElement(UIBloxConfig.enableExperimentalGamepadSupport and + RoactGamepad.Focusable[FitFrameOnAxis] or FitFrameOnAxis, { + BackgroundTransparency = 1, + minimumSize = UDim2.new(1, 0, 0, BUTTON_HEIGHT), + FillDirection = Enum.FillDirection.Horizontal, + HorizontalAlignment = Enum.HorizontalAlignment.Center, + VerticalAlignment = Enum.VerticalAlignment.Center, + Position = UDim2.new(0, 0, 1, -24), + AnchorPoint = Vector2.new(0, 1), + contentPadding = UDim.new(0, BUTTON_PADDING), + [Roact.Ref] = self.props[Roact.Ref], + [Roact.Change.AbsoluteSize] = self.updateFrameSize, + margin = { + left = margin, + right = margin, + top = 0, + bottom = 0 + }, + + NextSelectionLeft = self.props.NextSelectionLeft, + NextSelectionRight = self.props.NextSelectionRight, + NextSelectionUp = self.props.NextSelectionUp, + NextSelectionDown = self.props.NextSelectionDown, + }, + buttonTable + ) + end) +end + +return ActionBar diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Button/ActionBar.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Button/ActionBar.spec.lua new file mode 100644 index 0000000000..4c17b1f1a4 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Button/ActionBar.spec.lua @@ -0,0 +1,120 @@ +return function() + local Button = script.Parent + local App = Button.Parent + local UIBlox = App.Parent + local Packages = UIBlox.Parent + + local Roact = require(Packages.Roact) + local Images = require(App.ImageSet.Images) + + local icon = Images["icons/common/robux_small"] + local mockStyleComponent = require(UIBlox.Utility.mockStyleComponent) + + local ActionBar = require(Button.ActionBar) + + it("should create and destroy ActionBar with one button without errors", function() + local element = mockStyleComponent({ + ActionBar = Roact.createElement(ActionBar, { + button = { + props = { + onActivated = function() end, + text = "Button", + }, + } + }) + }) + local instance = Roact.mount(element) + Roact.unmount(instance) + end) + + it("should create and destroy ActionBar with one button and one icon button without errors", function() + local element = mockStyleComponent({ + ActionBar = Roact.createElement(ActionBar, { + button = { + props = { + onActivated = function() end, + text = "Button", + icon = icon, + }, + }, + icons = { + { + props = { + anchorPoint = Vector2.new(0.5, 0.5), + position = UDim2.fromScale(0.5, 0.5), + icon = icon, + userInteractionEnabled = true, + onActivated = function() + print("Text Button Clicked!") + end, + onStateChanged = function(oldState, newState) + print("state changed \n oldState:", oldState, " newState:", newState) + end + } + } + } + }) + }) + + local instance = Roact.mount(element) + Roact.unmount(instance) + end) + + it("should create and destroy ActionBar with one button and two icon button without errors", function() + local element = mockStyleComponent({ + ActionBar = Roact.createElement(ActionBar, { + button = { + props = { + onActivated = function() end, + text = "Button", + icon = icon, + }, + }, + icons = { + { + props = { + anchorPoint = Vector2.new(0.5, 0.5), + position = UDim2.fromScale(0.5, 0.5), + icon = icon, + userInteractionEnabled = true, + onActivated = function() + print("Text Button Clicked!") + end, + onStateChanged = function(oldState, newState) + print("state changed \n oldState:", oldState, " newState:", newState) + end + } + }, + { + props = { + anchorPoint = Vector2.new(0.5, 0.5), + position = UDim2.fromScale(0.5, 0.5), + icon = icon, + userInteractionEnabled = true, + onActivated = function() + print("Text Button Clicked!") + end, + onStateChanged = function(oldState, newState) + print("state changed \n oldState:", oldState, " newState:", newState) + end + } + } + } + }) + }) + + local instance = Roact.mount(element) + Roact.unmount(instance) + end) + + it("should create and destroy a ActionBar with children without errors", function() + local element = mockStyleComponent({ + ActionBar = Roact.createElement(ActionBar, {}, { + ChildFrame = Roact.createElement("Frame",{}) + }) + }) + + local instance = Roact.mount(element) + Roact.unmount(instance) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.lua index 8eb626da37..324557ecf6 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.lua @@ -18,6 +18,7 @@ local InformativeToast = Roact.PureComponent:extend("InformativeToast") local validateProps = t.strictInterface({ anchorPoint = t.optional(t.Vector2), iconProps = t.optional(validateToastIcon), + iconChildren = t.optional(t.table), layoutOrder = t.optional(t.integer), padding = t.optional(t.numberMin(0)), position = t.optional(t.UDim2), @@ -52,6 +53,7 @@ function InformativeToast:render() }, { ToastFrame = Roact.createElement(ToastFrame, { iconProps = self.props.iconProps, + iconChildren = self.props.iconChildren, padding = self.props.padding, subtitleTextProps = self.props.subtitleTextProps, textFrameSize = self.props.textFrameSize, diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.spec.lua index b20767bde8..61feb930a3 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.spec.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InformativeToast.spec.lua @@ -101,4 +101,25 @@ return function() local instance = Roact.mount(element) Roact.unmount(instance) end) + + it("should create and destroy without errors with composed image", function() + local element = createInformativeToast({ + iconProps = { + Image = Images["icons/status/warning"], + Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE), + }, + iconChildren = { + Child = Roact.createElement("TextLabel"), + }, + titleTextProps = { + colorStyle = TestStyle.Theme.TextEmphasis, + fontStyle = TestStyle.Font.Header2, + Size = UDim2.new(1, -ICON_SIZE, 1, 0), + Text = testText, + }, + }) + + local instance = Roact.mount(element) + Roact.unmount(instance) + end) end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.lua index b7ef442e5c..acbc596a4b 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.lua @@ -30,6 +30,7 @@ local InteractiveToast = Roact.PureComponent:extend("InteractiveToast") local validateProps = t.strictInterface({ anchorPoint = t.optional(t.Vector2), iconProps = t.optional(validateToastIcon), + iconChildren = t.optional(t.table), layoutOrder = t.optional(t.integer), padding = t.optional(t.numberMin(0)), position = t.optional(t.UDim2), @@ -92,6 +93,7 @@ function InteractiveToast:render() }), ToastFrame = Roact.createElement(ToastFrame, { iconProps = self.props.iconProps, + iconChildren = self.props.iconChildren, padding = self.props.padding, subtitleTextProps = self.props.subtitleTextProps, textFrameSize = self.props.textFrameSize, diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.spec.lua index a19d6d83d9..6e342af185 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.spec.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/InteractiveToast.spec.lua @@ -107,6 +107,28 @@ return function() Roact.unmount(instance) end) + it("should create and destroy without errors with composed image", function() + local element = createInteractiveToast({ + textFrameSize = UDim2.new(1, 0, 1, 0), + iconProps = { + Image = Images["icons/status/warning"], + Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE), + }, + iconChildren = { + Child = Roact.createElement("TextLabel"), + }, + titleTextProps = { + colorStyle = TestStyle.Theme.TextEmphasis, + fontStyle = TestStyle.Font.Header2, + Size = UDim2.new(1, -ICON_SIZE, 1, 0), + Text = testText, + }, + }) + + local instance = Roact.mount(element) + Roact.unmount(instance) + end) + it("should create and destroy without errors when pressed", function() local element = createInteractiveToast({ pressed = true, diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/SlideFromTopToast.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/SlideFromTopToast.lua index a10e5d6eb6..be3114715e 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/SlideFromTopToast.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/SlideFromTopToast.lua @@ -40,6 +40,7 @@ local function toastContentEqual(toastContent1, toastContent2) if toastContent1.iconColorStyle ~= toastContent2.iconColorStyle or toastContent1.iconImage ~= toastContent2.iconImage or toastContent1.iconSize ~= toastContent2.iconSize + or toastContent1.iconChildren ~= toastContent2.iconChildren or toastContent1.onActivated ~= toastContent2.onActivated or toastContent1.onDismissed ~= toastContent2.onDismissed or toastContent1.swipeUpDismiss ~= toastContent2.swipeUpDismiss @@ -189,6 +190,7 @@ function SlideFromTopToast:render() iconColorStyle = self.currentToastContent.iconColorStyle, iconImage = self.currentToastContent.iconImage, iconSize = self.currentToastContent.iconSize, + iconChildren = self.currentToastContent.iconChildren, onActivated = onActivated and self.onActivated, onTouchSwipe = swipeUpDismiss and self.onTouchSwipe, renderToast = onActivated and self.renderInteractiveToast or self.renderInformativeToast, diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastContainer.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastContainer.lua index e5fe8c5016..aae8e86baa 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastContainer.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastContainer.lua @@ -3,7 +3,6 @@ local DialogRoot = ToastRoot.Parent local AppRoot = DialogRoot.Parent local UIBloxRoot = AppRoot.Parent local Packages = UIBloxRoot.Parent -local UIBloxConfig = require(UIBloxRoot.UIBloxConfig) local Roact = require(Packages.Roact) local t = require(Packages.t) @@ -22,8 +21,6 @@ local MIN_HEIGHT = 60 local MAX_BOUND = 10000 -local fixToastResizeConfig = UIBloxConfig.fixToastResizeConfig - local function getTextHeight(text, font, fontSize, widthCap) local bounds = Vector2.new(widthCap, MAX_BOUND) local textSize = GetTextSize(text, fontSize, font, bounds) @@ -39,6 +36,7 @@ local validateProps = t.strictInterface({ -- Optional image to be displayed in the toast. iconImage = t.optional(t.union(t.table, t.string)), iconSize = t.optional(t.Vector2), + iconChildren = t.optional(t.table), layoutOrder = t.optional(t.integer), onActivated = t.optional(t.callback), onTouchSwipe = t.optional(t.callback), @@ -95,7 +93,7 @@ function ToastContainer:init() if inputObject.UserInputState == Enum.UserInputState.Begin and (inputObject.UserInputType == Enum.UserInputType.Touch or inputObject.UserInputType == Enum.UserInputType.MouseButton1) then - if not self.state.pressed and (fixToastResizeConfig or self.isMounted) then + if not self.state.pressed then self:setState({ pressed = true, }) @@ -104,7 +102,7 @@ function ToastContainer:init() end self.onButtonInputEnded = function() - if self.state.pressed and (fixToastResizeConfig or self.isMounted) then + if self.state.pressed then self:setState({ pressed = false, }) @@ -152,12 +150,7 @@ function ToastContainer:render() return withStyle(function(stylePalette) local subtitleHeight, titleHeight = self.getTextHeights(stylePalette) - local textFrameHeight - if fixToastResizeConfig then - textFrameHeight = titleHeight + subtitleHeight - else - textFrameHeight = self.state.titleHeight + self.state.subtitleHeight - end + local textFrameHeight = titleHeight + subtitleHeight local size = self.props.size if self.props.fitHeight then @@ -178,40 +171,10 @@ function ToastContainer:render() Size = size, Text = "", [Roact.Change.AbsoluteSize] = function(rbx) - if fixToastResizeConfig and self.state.containerWidth ~= rbx.AbsoluteSize.X then + if self.state.containerWidth ~= rbx.AbsoluteSize.X then self:setState({ containerWidth = rbx.AbsoluteSize.X }) - elseif not fixToastResizeConfig and self.state.containerWidth ~= rbx.AbsoluteSize.X then - local containerWidth = rbx.AbsoluteSize.X - local textFrameWidth = containerWidth - padding*2 - if iconImage then - textFrameWidth = textFrameWidth - iconSize.X - padding - end - - local titleFont = titleStyle.Font - local titleSize = titleStyle.RelativeSize * font.BaseSize - local newTitleHeight = math.max(0, getTextHeight(toastTitle, titleFont, titleSize, textFrameWidth)) - local newSubtitleHeight = 0 - - if toastSubtitle then - local subtitleFont = subtitleStyle.Font - local subtitleSize = subtitleStyle.RelativeSize * font.BaseSize - newSubtitleHeight = math.max(0, getTextHeight(toastSubtitle, subtitleFont, subtitleSize, textFrameWidth)) - end - - -- Wrapped in spawn in order to avoid issues if Roact connects changed signal before the Size - -- prop is set in older versions of Roact (older than 1.0) In 1.0, this is fixed by deferring event - -- handlers and setState calls until after the current update]] - spawn(function() - if self.isMounted then - self:setState({ - containerWidth = containerWidth, - subtitleHeight = newSubtitleHeight, - titleHeight = newTitleHeight, - }) - end - end) end end, [Roact.Event.Activated] = self.props.onActivated, @@ -227,21 +190,21 @@ function ToastContainer:render() Image = iconImage, Size = UDim2.new(0, iconSize.X, 0, iconSize.Y), } or nil, + iconChildren = self.props.iconChildren, padding = padding, pressed = self.props.onActivated and self.state.pressed or nil, pressedScale = self.props.pressedScale, subtitleTextProps = toastSubtitle and { colorStyle = theme.TextEmphasis, fontStyle = subtitleStyle, - Size = fixToastResizeConfig and UDim2.new(1, 0, 0, subtitleHeight) - or UDim2.new(1, 0, 0, self.state.subtitleHeight), + Size = UDim2.new(1, 0, 0, subtitleHeight), Text = toastSubtitle, } or nil, textFrameSize = UDim2.new(1, iconImage and -iconSize.X - padding or 0, 0, textFrameHeight), titleTextProps = { colorStyle = theme.TextEmphasis, fontStyle = titleStyle, - Size = fixToastResizeConfig and UDim2.new(1, 0, 0, titleHeight) or UDim2.new(1, 0, 0, self.state.titleHeight), + Size = UDim2.new(1, 0, 0, titleHeight), Text = toastTitle, }, }), @@ -250,19 +213,9 @@ function ToastContainer:render() end function ToastContainer:didMount() - if fixToastResizeConfig then - self:setState({ - containerWidth = self.containerRef.current and self.containerRef.current.AbsoluteSize.X or 0 - }) - else - self.isMounted = true - end -end - -function ToastContainer:willUnmount() - if not fixToastResizeConfig then - self.isMounted = false - end + self:setState({ + containerWidth = self.containerRef.current and self.containerRef.current.AbsoluteSize.X or 0 + }) end return ToastContainer diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.lua index 55c4de25ee..5d9463b9fc 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.lua @@ -18,6 +18,7 @@ local ToastFrame = Roact.PureComponent:extend("ToastFrame") local validateProps = t.strictInterface({ anchorPoint = t.optional(t.Vector2), iconProps = t.optional(validateToastIcon), + iconChildren = t.optional(t.table), layoutOrder = t.optional(t.integer), padding = t.numberMin(0), position = t.optional(t.UDim2), @@ -63,7 +64,7 @@ function ToastFrame:render() }), ToastIcon = iconProps and Roact.createElement(ToastIcon, Cryo.Dictionary.join(iconProps, { LayoutOrder = 1, - })), + }), self.props.iconChildren), ToastTextFrame = Roact.createElement("Frame", { BackgroundTransparency = 1, LayoutOrder = 2, diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.spec.lua index 5858ce71f0..828da78624 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.spec.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/ToastFrame.spec.lua @@ -83,4 +83,26 @@ return function() local instance = Roact.mount(element) Roact.unmount(instance) end) + + it("should create and destroy without errors with composed image", function() + local element = createToastFrame({ + iconProps = { + colorStyle = TestStyle.Theme.IconEmphasis, + Image = "rbxassetid://4126499279", + Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE), + }, + iconChildren = { + Child = Roact.createElement("TextLabel"), + }, + titleTextProps = { + colorStyle = TestStyle.Theme.TextEmphasis, + fontStyle = TestStyle.Font.Header2, + Size = UDim2.new(1, -ICON_SIZE, 1, 0), + Text = testText, + }, + }) + + local instance = Roact.mount(element) + Roact.unmount(instance) + end) end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/Validator/validateToastContent.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/Validator/validateToastContent.lua index daebcfb0dd..60625123f3 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/Validator/validateToastContent.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Dialog/Toast/Validator/validateToastContent.lua @@ -12,6 +12,7 @@ return t.strictInterface({ -- Optional image to be displayed in the toast. iconImage = t.optional(t.union(t.table, t.string)), iconSize = t.optional(t.Vector2), + iconChildren = t.optional(t.table), onActivated = t.optional(t.callback), onAppeared = t.optional(t.callback), onDismissed = t.optional(t.callback), diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/Enum/Placement.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/Enum/Placement.lua new file mode 100644 index 0000000000..0cf89dc8e7 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/Enum/Placement.lua @@ -0,0 +1,10 @@ +local Core = script.Parent.Parent.Parent +local UIBlox = Core.Parent +local Packages = UIBlox.Parent +local enumerate = require(Packages.enumerate) + +return enumerate(script.Name, { + "Left", + "Bottom", + "Auto", +}) diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/SystemBar.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/SystemBar.lua new file mode 100644 index 0000000000..b292e669ed --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/SystemBar.lua @@ -0,0 +1,308 @@ +local Navigation = script.Parent +local App = Navigation.Parent +local UIBlox = App.Parent +local Packages = UIBlox.Parent + +local Roact = require(Packages.Roact) +local t = require(Packages.t) +local Cryo = require(Packages.Cryo) + +local withStyle = require(UIBlox.Core.Style.withStyle) +local ControlState = require(UIBlox.Core.Control.Enum.ControlState) +local ImageSetComponent = require(UIBlox.Core.ImageSet.ImageSetComponent) +local validateImageSetData = require(UIBlox.Core.ImageSet.Validator.validateImageSetData) +local Badge = require(UIBlox.App.Indicator.Badge) +local IconSize = require(UIBlox.App.ImageSet.Enum.IconSize) +local getIconSize = require(UIBlox.App.ImageSet.getIconSize) + +local InteractableList = require(UIBlox.Core.Control.InteractableList) +local withAnimation = require(UIBlox.Core.Animation.withAnimation) + +local Placement = require(script.Parent.Enum.Placement) + +local SPRING_OPTIONS = { + frequency = 3, +} + +local ICON_SIZE = getIconSize(IconSize.Medium) -- 36 +local ICON_PADDING = 4 + +local ITEM_SIZE_X = ICON_SIZE + 2 * ICON_PADDING +local ITEM_SIZE_Y = ITEM_SIZE_X + +local ICON_SIZE_X = ICON_SIZE +local ICON_SIZE_Y = ICON_SIZE_X +local ICON_HOVER_OFFSET_X = ICON_PADDING +local ICON_HOVER_OFFSET_Y = ICON_HOVER_OFFSET_X +local ICON_TRANSPARENCY = 0 +local ICON_TRANSPARENCY_HOVERED = 0.5 +local BADGE_POSITION_X = 18 +local BADGE_POSITION_Y = -2 + +local MAX_SIZE_PORTRAIT_X = 600 +local TAB_SIZE_PORTRAIT_Y = 48 + +local TAB_SIZE_LANDSCAPE_X = 64 +local TAB_SIZE_LANDSCAPE_Y = TAB_SIZE_LANDSCAPE_X +local TAB_PADDING_LANDSCAPE_Y = 4 +local FIRST_TAB_PADDING_LANDSCAPE_Y = 12 + +local ICON_POSITION_X = (ITEM_SIZE_X - ICON_SIZE_X) / 2 +local ICON_POSITION_Y = (ITEM_SIZE_Y - ICON_SIZE_Y) / 2 +local ICON_POSITION_HOVERED_X = ICON_POSITION_X + ICON_HOVER_OFFSET_X +local ICON_POSITION_HOVERED_Y = ICON_POSITION_Y - ICON_HOVER_OFFSET_Y + +local FIRST_ITEM_PADDING_LANDSCAPE_Y = FIRST_TAB_PADDING_LANDSCAPE_Y + (TAB_SIZE_LANDSCAPE_Y - ITEM_SIZE_Y) / 2 +local ITEM_PADDING_LANDSCAPE_Y = TAB_PADDING_LANDSCAPE_Y + (TAB_SIZE_LANDSCAPE_Y - ITEM_SIZE_Y) + +--[[ + A navigation bar that adapts to orientation, screen resize and can be hidden. + it also notifies on safe area (area outside the navbar) change + safe area can change by resizing the window or hiding the systembar +]] + +local SystemBar = Roact.PureComponent:extend("SystemBar") + +SystemBar.validateProps = t.strictInterface({ + -- list of system bar items + itemList = t.array(t.strictInterface({ + -- icon if the item is currently selected + iconOn = validateImageSetData, + -- icon if the item is not selected + iconOff = validateImageSetData, + -- action when clicking on this item + onActivated = t.callback, + -- number to display as badge next to the icon + badgeValue = t.optional(t.integer), + })), + -- index of the currently selected item + selection = t.optional(t.integer), + -- display style: Left, Bottom, Auto (based on screen size) + placement = t.optional(Placement.isEnumValue), + -- hides the system bar (with animation) when true + hidden = t.optional(t.boolean), + -- function({Position, AbsolutePosition, Size, AbsoluteSize}) called when the safe area is resized + onSafeAreaChanged = t.optional(t.callback), + --- options for the main Frame (contains both systembar and safe area) + size = t.optional(t.UDim2), + position = t.optional(t.UDim2), + layoutOrder = t.optional(t.integer), + -- children are placed in a Frame occupying the safe area + [Roact.Children] = t.optional(t.any), +}) + +SystemBar.defaultProps = { + placement = Placement.Auto, +} + +function SystemBar:isPortrait() + if self.props.placement == Placement.Left then + return false + elseif self.props.placement == Placement.Bottom then + return true + else + return self.state.portrait + end +end + +function SystemBar:renderItem(item, state, selected) + local pressed = state == ControlState.Pressed + local hovered = pressed or state == ControlState.Hover + local hasBadge = item.badgeValue and item.badgeValue > 0 + + local positionX = ICON_POSITION_X + local positionY = ICON_POSITION_Y + if hovered then + if self:isPortrait() then + positionY = ICON_POSITION_HOVERED_Y + else + positionX = ICON_POSITION_HOVERED_X + end + end + return withAnimation({ + positionX = positionX, + positionY = positionY, + }, function(values) + return withStyle(function(stylePalette) + local theme = stylePalette.Theme + return Roact.createElement(ImageSetComponent.Label, { + Position = UDim2.fromOffset( + math.floor(values.positionX + 0.5), + math.floor(values.positionY + 0.5) + ), + Size = UDim2.fromOffset(ICON_SIZE_X, ICON_SIZE_Y), + BackgroundTransparency = 1, + Image = selected and item.iconOn or item.iconOff, + ImageColor3 = theme.IconDefault.Color, + ImageTransparency = pressed and ICON_TRANSPARENCY_HOVERED or ICON_TRANSPARENCY, + }, { + Badge = hasBadge and Roact.createElement(Badge, { + position = UDim2.fromOffset(BADGE_POSITION_X, BADGE_POSITION_Y), + value = item.badgeValue, + }) or nil, + }) + end) + end, SPRING_OPTIONS) +end + +function SystemBar:renderPortrait(frameProps, contents) + return withAnimation({ + offset = self.props.hidden and 0 or -TAB_SIZE_PORTRAIT_Y + }, function(values) + return Roact.createElement("Frame", Cryo.Dictionary.join(frameProps, { + Position = UDim2.new(0, 0, 1, math.floor(values.offset + 0.5)), + Size = UDim2.new(1, 0, 0, TAB_SIZE_PORTRAIT_Y), + ZIndex = 99, + }), { + Layout = Roact.createElement("UIListLayout", { + HorizontalAlignment = Enum.HorizontalAlignment.Center, + }), + InnerFrame = Roact.createElement("Frame", { + Size = UDim2.new(1, 0, 1, 0), + BackgroundTransparency = 1, + }, Cryo.Dictionary.join({ + Constraint = Roact.createElement("UISizeConstraint", { + MaxSize = Vector2.new(MAX_SIZE_PORTRAIT_X, TAB_SIZE_PORTRAIT_Y), + }), + Layout = Roact.createElement("UIListLayout", { + FillDirection = Enum.FillDirection.Horizontal, + HorizontalAlignment = Enum.HorizontalAlignment.Center, + VerticalAlignment = Enum.VerticalAlignment.Center, + Padding = UDim.new(1 / #self.props.itemList, -ITEM_SIZE_X), + }, {}) + }, contents)) + }) + end, SPRING_OPTIONS) +end + +function SystemBar:renderLandscape(frameProps, contents) + return withAnimation({ + offset = self.props.hidden and -TAB_SIZE_LANDSCAPE_X or 0 + }, function(values) + return Roact.createElement("Frame", Cryo.Dictionary.join(frameProps, { + Position = UDim2.new(0, math.floor(values.offset + 0.5), 0, 0), + Size = UDim2.new(0, TAB_SIZE_LANDSCAPE_X, 1, 0), + ZIndex = 99, + }), Cryo.Dictionary.join({ + Padding = Roact.createElement("UIPadding", { + PaddingTop = UDim.new(0, FIRST_ITEM_PADDING_LANDSCAPE_Y), + }), + Layout = Roact.createElement("UIListLayout", { + FillDirection = Enum.FillDirection.Vertical, + HorizontalAlignment = Enum.HorizontalAlignment.Center, + Padding = UDim.new(0, ITEM_PADDING_LANDSCAPE_Y), + }), + }, contents)) + end, SPRING_OPTIONS) +end + +function SystemBar:renderBackground(...) + if self:isPortrait() then + return self:renderPortrait(...) + else + return self:renderLandscape(...) + end +end + +function SystemBar:renderSafeArea() + local position, size + if self.props.hidden then + position = UDim2.new(0, 0, 0, 0) + size = UDim2.new(1, 0, 1, 0) + elseif self:isPortrait() then + position = UDim2.new(0, 0, 0, 0) + size = UDim2.new(1, 0, 1, -TAB_SIZE_PORTRAIT_Y) + else + position = UDim2.new(0, 64, 0, 0) + size = UDim2.new(1, -TAB_SIZE_LANDSCAPE_X, 1, 0) + end + return Roact.createElement("Frame", { + Position = position, + Size = size, + BackgroundTransparency = 1, + [Roact.Change.AbsoluteSize] = self.onSafeAreaEvent, + [Roact.Change.AbsolutePosition] = self.onSafeAreaEvent, + }, self.props[Roact.Children]) +end + +function SystemBar:renderList(items, renderItem) + return withStyle(function(stylePalette) + local theme = stylePalette.Theme + local renderedItems = Cryo.List.map(items, function(item, key) + return renderItem(key) + end) + return Roact.createElement("Frame", { + Position = self.props.position or UDim2.new(0, 0, 0, 0), + Size = self.props.size or UDim2.new(1, 0, 1, 0), + ClipsDescendants = true, + LayoutOrder = self.props.layoutOrder, + [Roact.Change.AbsoluteSize] = function(rbx) + if self.state.portrait and rbx.AbsoluteSize.X > rbx.AbsoluteSize.Y then + self:setState({ + portrait = false, + }) + elseif not self.state.portrait and rbx.AbsoluteSize.X < rbx.AbsoluteSize.Y then + self:setState({ + portrait = true, + }) + end + end + }, { + NavBar = self:renderBackground({ + BackgroundColor3 = theme.BackgroundUIDefault.Color, + BackgroundTransparency = theme.BackgroundUIDefault.Transparency, + BorderSizePixel = 0, + }, renderedItems), + SafeArea = self:renderSafeArea(), + }) + end) +end + +function SystemBar:init() + self:setState({ + portrait = true, + }) + self.onSelectionChanged = function(selection) + if #selection > 0 then + self.props.itemList[selection[1]].onActivated() + end + end + self.onSafeAreaEvent = function(rbx) + if self.props.onSafeAreaChanged then + self.props.onSafeAreaChanged({ + Position = rbx.Position, + AbsolutePosition = rbx.AbsolutePosition, + Size = rbx.Size, + AbsoluteSize = rbx.AbsoluteSize, + }) + end + end +end + +function SystemBar:render() + local itemList = self.props.itemList + local selection = self.props.selection + if selection then + if itemList[selection] == nil then + selection = nil + else + selection = {selection} + end + end + return Roact.createElement(InteractableList, { + itemList = itemList, + selection = selection, + itemSize = UDim2.fromOffset(ITEM_SIZE_X, ITEM_SIZE_Y), + -- since renderList and renderItem depend on state and props, + -- we should always rerender the InteractableList + renderList = function(...) + return self:renderList(...) + end, + renderItem = function(...) + return self:renderItem(...) + end, + onSelectionChanged = self.onSelectionChanged, + }) +end + +return SystemBar diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/SystemBar.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/SystemBar.spec.lua new file mode 100644 index 0000000000..f9593cefa3 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Navigation/SystemBar.spec.lua @@ -0,0 +1,45 @@ +return function() + local Navigation = script.Parent + local App = Navigation.Parent + local UIBloxRoot = App.Parent + local Roact = require(UIBloxRoot.Parent.Roact) + local mockStyleComponent = require(UIBloxRoot.Utility.mockStyleComponent) + local Images = require(App.ImageSet.Images) + + local SystemBar = require(script.Parent.SystemBar) + local Placement = require(script.Parent.Enum.Placement) + + it("should create and destroy with minimum props without errors", function() + local element = mockStyleComponent({ + Roact.createElement(SystemBar, { + itemList = {}, + }), + }) + local instance = Roact.mount(element) + Roact.unmount(instance) + end) + + it("should create and destroy with all props without errors", function() + local element = mockStyleComponent({ + Roact.createElement(SystemBar, { + itemList = {{ + iconOn = Images["icons/actions/favoriteOn"], + iconOff = Images["icons/actions/favoriteOff"], + onActivated = function() end, + badgeValue = 1, + }}, + selection = 1, + placement = Placement.Left, + hidden = false, + onSafeAreaChanged = function() end, + size = UDim2.new(), + position = UDim2.new(), + layoutOrder = 1, + }, { + Contents = Roact.createElement("Frame", {}, {}) + }), + }) + local instance = Roact.mount(element) + Roact.unmount(instance) + end) +end diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/BulletDown.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/BulletDown.lua new file mode 100644 index 0000000000..01d9d3e017 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/BulletDown.lua @@ -0,0 +1,27 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local INSET_ADJUSTMENT = 2 +local ASSET_NAME = "component_assets/bulletDown_17_stroke_3" + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, INSET_ADJUSTMENT * 2, 1, INSET_ADJUSTMENT * 2), + Position = UDim2.fromOffset(-INSET_ADJUSTMENT, -INSET_ADJUSTMENT), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(8, 8, 9, 9), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/BulletUp.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/BulletUp.lua new file mode 100644 index 0000000000..66e8fe9c95 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/BulletUp.lua @@ -0,0 +1,27 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local INSET_ADJUSTMENT = 2 +local ASSET_NAME = "component_assets/bulletUp_17_stroke_3" + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, INSET_ADJUSTMENT * 2, 1, INSET_ADJUSTMENT * 2), + Position = UDim2.fromOffset(-INSET_ADJUSTMENT, -INSET_ADJUSTMENT), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(8, 8, 9, 9), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/InputFields.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/InputFields.lua new file mode 100644 index 0000000000..1334dc9c94 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/InputFields.lua @@ -0,0 +1,25 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local ASSET_NAME = "component_assets/circle_22_stroke_3" + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, 0, 1, 0), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(11, 11, 12, 12), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/LargePill.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/LargePill.lua index dd938b237f..0dc62b72aa 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/LargePill.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/LargePill.lua @@ -2,6 +2,7 @@ local UIBloxRoot = script.Parent.Parent.Parent.Parent local Packages = UIBloxRoot.Parent local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) local Images = require(UIBloxRoot.App.ImageSet.Images) @@ -9,13 +10,16 @@ local Images = require(UIBloxRoot.App.ImageSet.Images) local ASSET_NAME = "component_assets/circle_52_stroke_3" return function(props) - return Roact.createElement(ImageSetComponent.Label, { - Image = Images[ASSET_NAME], - BackgroundTransparency = 1, - Size = UDim2.new(1, 0, 1, 0), - ScaleType = Enum.ScaleType.Slice, - SliceCenter = Rect.new(26, 26, 27, 27), + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, 0, 1, 0), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(26, 26, 27, 27), - [Roact.Ref] = props[Roact.Ref], - }) + [Roact.Ref] = props[Roact.Ref], + }) + end) end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/NavHighlight.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/NavHighlight.lua new file mode 100644 index 0000000000..759775b0fb --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/NavHighlight.lua @@ -0,0 +1,22 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local NAV_HIGHLIGHT_HEIGHT = 3 + +return function(props) + return withStyle(function(style) + return Roact.createElement("Frame", { + AnchorPoint = Vector2.new(0, 1), + Position = UDim2.new(0, 0, 1, -NAV_HIGHLIGHT_HEIGHT), + Size = UDim2.new(1, 0, 0, NAV_HIGHLIGHT_HEIGHT), + BorderSizePixel = 1, + BackgroundColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = style.Theme.SelectionCursor.Transparency, + + [Roact.Ref] = props[Roact.Ref] + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRect.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRect.lua index 65d65b2755..7e4885df50 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRect.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRect.lua @@ -2,6 +2,7 @@ local UIBloxRoot = script.Parent.Parent.Parent.Parent local Packages = UIBloxRoot.Parent local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) local Images = require(UIBloxRoot.App.ImageSet.Images) @@ -10,14 +11,17 @@ local INSET_ADJUSTMENT = 6 local ASSET_NAME = "component_assets/circle_17_stroke_3" return function(props) - return Roact.createElement(ImageSetComponent.Label, { - Image = Images[ASSET_NAME], - BackgroundTransparency = 1, - Size = UDim2.new(1, INSET_ADJUSTMENT * 2, 1, INSET_ADJUSTMENT * 2), - Position = UDim2.new(0, -INSET_ADJUSTMENT, 0, -INSET_ADJUSTMENT), - ScaleType = Enum.ScaleType.Slice, - SliceCenter = Rect.new(8, 8, 9, 9), + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, INSET_ADJUSTMENT * 2, 1, INSET_ADJUSTMENT * 2), + Position = UDim2.new(0, -INSET_ADJUSTMENT, 0, -INSET_ADJUSTMENT), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(8, 8, 9, 9), - [Roact.Ref] = props[Roact.Ref], - }) + [Roact.Ref] = props[Roact.Ref], + }) + end) end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRectNoInset.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRectNoInset.lua index 84da8d7f08..1e7fb7510f 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRectNoInset.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/RoundedRectNoInset.lua @@ -2,6 +2,7 @@ local UIBloxRoot = script.Parent.Parent.Parent.Parent local Packages = UIBloxRoot.Parent local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) local Images = require(UIBloxRoot.App.ImageSet.Images) @@ -9,13 +10,16 @@ local Images = require(UIBloxRoot.App.ImageSet.Images) local ASSET_NAME = "component_assets/circle_17_stroke_3" return function(props) - return Roact.createElement(ImageSetComponent.Label, { - Image = Images[ASSET_NAME], - BackgroundTransparency = 1, - Size = UDim2.new(1, 0, 1, 0), - ScaleType = Enum.ScaleType.Slice, - SliceCenter = Rect.new(8, 8, 9, 9), + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, 0, 1, 0), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(8, 8, 9, 9), - [Roact.Ref] = props[Roact.Ref], - }) + [Roact.Ref] = props[Roact.Ref], + }) + end) end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SelectedKnob.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SelectedKnob.lua new file mode 100644 index 0000000000..2d18e68cbc --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SelectedKnob.lua @@ -0,0 +1,25 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local ASSET_NAME = "component_assets/circle_42_stroke_3" +local ASSET_SIZE = 42 + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.fromOffset(ASSET_SIZE, ASSET_SIZE), + Position = UDim2.new(0.5, -ASSET_SIZE / 2, 0.5, -ASSET_SIZE / 2), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SkinToneCircle.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SkinToneCircle.lua new file mode 100644 index 0000000000..816cfd0b52 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SkinToneCircle.lua @@ -0,0 +1,23 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local ASSET_NAME = "component_assets/circle_69_stroke_3" + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, 0, 1, 0), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SmallPill.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SmallPill.lua index 4905419912..2f5983b540 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SmallPill.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/SmallPill.lua @@ -2,6 +2,7 @@ local UIBloxRoot = script.Parent.Parent.Parent.Parent local Packages = UIBloxRoot.Parent local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) local Images = require(UIBloxRoot.App.ImageSet.Images) @@ -9,13 +10,16 @@ local Images = require(UIBloxRoot.App.ImageSet.Images) local ASSET_NAME = "component_assets/circle_30_stroke_3" return function(props) - return Roact.createElement(ImageSetComponent.Label, { - Image = Images[ASSET_NAME], - BackgroundTransparency = 1, - Size = UDim2.new(1, 0, 1, 0), - ScaleType = Enum.ScaleType.Slice, - SliceCenter = Rect.new(15, 15, 16, 16), + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, 0, 1, 0), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(15, 15, 16, 16), - [Roact.Ref] = props[Roact.Ref], - }) + [Roact.Ref] = props[Roact.Ref], + }) + end) end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/Square.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/Square.lua new file mode 100644 index 0000000000..b793f4f323 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/Square.lua @@ -0,0 +1,27 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local INSET_ADJUSTMENT = 2 +local ASSET_NAME = "component_assets/square_7_stroke_3" + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, INSET_ADJUSTMENT * 2, 1, INSET_ADJUSTMENT * 2), + Position = UDim2.fromOffset(-INSET_ADJUSTMENT, -INSET_ADJUSTMENT), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(3.5, 3.5, 3.5, 3.5), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/Toggle.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/Toggle.lua new file mode 100644 index 0000000000..6f430e8b3d --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/Toggle.lua @@ -0,0 +1,25 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local ASSET_NAME = "component_assets/circle_26_stroke_3" + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.new(1, 0, 1, 0), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = Rect.new(13, 13, 14, 14), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/UnselectedKnob.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/UnselectedKnob.lua new file mode 100644 index 0000000000..7b1656dda0 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/Components/UnselectedKnob.lua @@ -0,0 +1,25 @@ +local UIBloxRoot = script.Parent.Parent.Parent.Parent +local Packages = UIBloxRoot.Parent + +local Roact = require(Packages.Roact) +local withStyle = require(UIBloxRoot.Core.Style.withStyle) + +local ImageSetComponent = require(UIBloxRoot.Core.ImageSet.ImageSetComponent) +local Images = require(UIBloxRoot.App.ImageSet.Images) + +local ASSET_NAME = "component_assets/circle_52_stroke_3" +local ASSET_SIZE = 52 + +return function(props) + return withStyle(function(style) + return Roact.createElement(ImageSetComponent.Label, { + Image = Images[ASSET_NAME], + ImageColor3 = style.Theme.SelectionCursor.Color, + BackgroundTransparency = 1, + Size = UDim2.fromOffset(ASSET_SIZE, ASSET_SIZE), + Position = UDim2.new(0.5, -ASSET_SIZE / 2, 0.5, -ASSET_SIZE / 2), + + [Roact.Ref] = props[Roact.Ref], + }) + end) +end \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/CursorKind.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/CursorKind.lua index 96725d6393..02cd06d5c7 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/CursorKind.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/CursorKind.lua @@ -6,10 +6,24 @@ local RoundedRectCursor = require(script.Parent.Components.RoundedRect) local RoundedRectNoInsetCursor = require(script.Parent.Components.RoundedRectNoInset) local SmallPillCursor = require(script.Parent.Components.SmallPill) local LargePillCursor = require(script.Parent.Components.LargePill) +local SelectedKnobCursor = require(script.Parent.Components.SelectedKnob) +local UnselectedKnobCursor = require(script.Parent.Components.UnselectedKnob) +local NavHighlightCursor = require(script.Parent.Components.NavHighlight) +local SkinToneCircleCursor = require(script.Parent.Components.SkinToneCircle) +local SquareCursor = require(script.Parent.Components.Square) +local ToggleCursor = require(script.Parent.Components.Toggle) +local InputFieldsCursor = require(script.Parent.Components.InputFields) return enumerate(script.Name, { RoundedRect = RoundedRectCursor, RoundedRectNoInset = RoundedRectNoInsetCursor, SmallPill = SmallPillCursor, LargePill = LargePillCursor, + SelectedKnob = SelectedKnobCursor, + UnselectedKnob = UnselectedKnobCursor, + NavHighlight = NavHighlightCursor, + SkinToneCircle = SkinToneCircleCursor, + Square = SquareCursor, + Toggle = ToggleCursor, + InputFields = InputFieldsCursor, }) \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/SelectionCursorProvider.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/SelectionCursorProvider.spec.lua index 4f705cc6d2..d210e76a1a 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/SelectionCursorProvider.spec.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/SelectionImage/SelectionCursorProvider.spec.lua @@ -2,10 +2,13 @@ return function() local Packages = script.Parent.Parent.Parent.Parent local Roact = require(Packages.Roact) + local UIBlox = require(Packages.UIBlox) local SelectionCursorProvider = require(script.Parent.SelectionCursorProvider) local SelectionImageContext = require(script.Parent.SelectionImageContext) local CursorKind = require(script.Parent.CursorKind) + local testStyle = require(Packages.UIBlox.App.Style.Validator.TestStyle) + local StyleProvider = UIBlox.Core.Style.Provider describe("Managed singleton cache of UI elements for use as selection cursors", function() it("should provide a ref that refers to an ImageLabel", function() @@ -19,8 +22,12 @@ return function() }) end - local tree = Roact.mount(Roact.createElement(SelectionCursorProvider, {}, { - RefCapturer = Roact.createElement(CaptureRef) + local tree = Roact.mount(Roact.createElement(StyleProvider, { + style = testStyle, + }, { + SelectionCursorProvider = Roact.createElement(SelectionCursorProvider, {}, { + RefCapturer = Roact.createElement(CaptureRef) + }) })) expect(typeof(ref.getValue)).to.equal("function") @@ -41,13 +48,17 @@ return function() }) end - local tree = Roact.mount(Roact.createElement(SelectionCursorProvider, {}, { - RefCapturer1 = Roact.createElement(CaptureRef, { - key = "ref1", - }), - RefCapturer2 = Roact.createElement(CaptureRef, { - key = "ref2", - }), + local tree = Roact.mount(Roact.createElement(StyleProvider, { + style = testStyle, + }, { + SelectionCursorProvider = Roact.createElement(SelectionCursorProvider, {}, { + RefCapturer1 = Roact.createElement(CaptureRef, { + key = "ref1", + }), + RefCapturer2 = Roact.createElement(CaptureRef, { + key = "ref2", + }), + }) })) expect(capturedRefs.ref1).to.be.ok() diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppOneKnobSlider.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppOneKnobSlider.lua index d62bd17a8c..e0566c81a3 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppOneKnobSlider.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppOneKnobSlider.lua @@ -40,6 +40,10 @@ local function makeAppOneKnobSlider(trackFillThemeKey) position = t.optional(t.UDim2), anchorPoint = t.optional(t.Vector2), layoutOrder = t.optional(t.integer), + + [Roact.Ref] = t.optional(t.table), + NextSelectionUp = t.optional(t.table), + NextSelectionDown = t.optional(t.table), --Internal Only - Don't Pass In style = validateStyle }) @@ -60,7 +64,10 @@ local function makeAppOneKnobSlider(trackFillThemeKey) stepInterval = props.stepInterval, isDisabled = props.isDisabled, onValueChanged = props.onValueChanged, - style = props.style + style = props.style, + [Roact.Ref] = props[Roact.Ref], + NextSelectionUp = props.NextSelectionUp, + NextSelectionDown = props.NextSelectionDown, } if not props.textInputEnabled then diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppSlider.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppSlider.lua index 433ed576de..05aace12f2 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppSlider.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppSlider.lua @@ -170,6 +170,11 @@ local function makeAppSlider(trackFillThemeKey, isTwoKnobs) knobShadowImage = Images["component_assets/dropshadow_28"], knobShadowTransparencyLower = self.knobShadowTransparencyLower, knobShadowTransparencyUpper = self.knobShadowTransparencyUpper, + + [Roact.Ref] = props[Roact.Ref], + NextSelectionUp = props.NextSelectionUp, + NextSelectionDown = props.NextSelectionDown, + focusController = props.focusController, } if isTwoKnobs then diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppTwoKnobSlider.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppTwoKnobSlider.lua index 3b2d28a0cd..541e2b0a69 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppTwoKnobSlider.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Slider/makeAppTwoKnobSlider.lua @@ -41,6 +41,12 @@ local function makeAppTwoKnobSlider(trackFillThemeKey) position = t.optional(t.UDim2), anchorPoint = t.optional(t.Vector2), layoutOrder = t.optional(t.integer), + + [Roact.Ref] = t.optional(t.table), + NextSelectionUp = t.optional(t.table), + NextSelectionDown = t.optional(t.table), + focusController = t.optional(t.table), + --Internal Only - Don't Pass In style = validateStyle }) diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Colors.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Colors.lua index ffe9b59f06..ebf45b5c8b 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Colors.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Colors.lua @@ -18,6 +18,7 @@ local Colors = { Ash = Color3.fromRGB(222, 225, 227), Chalk = Color3.fromRGB(199, 203, 206), Smoke = Color3.fromRGB(96, 97, 98), + XboxBlue = Color3.fromRGB(17, 139, 211), } return Colors \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/DarkTheme.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/DarkTheme.lua index bde1d203e4..459e4c27ea 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/DarkTheme.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/DarkTheme.lua @@ -157,6 +157,11 @@ local theme = { Color = Colors.Flint, Transparency = 0, }, + + SelectionCursor = { + Color = Colors.White, + Transparency = 0, + }, } return theme \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/LightTheme.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/LightTheme.lua index b1b182686f..c3abe88a04 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/LightTheme.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Themes/LightTheme.lua @@ -157,6 +157,11 @@ local theme = { Color = Colors.White, Transparency = 0, }, + + SelectionCursor = { + Color = Colors.XboxBlue, + Transparency = 0, + }, } return theme \ No newline at end of file diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/TestStyle.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/TestStyle.lua index e3ac269dd2..3d4f7bda44 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/TestStyle.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/TestStyle.lua @@ -39,6 +39,7 @@ local testTheme = { Alert = color, Badge = color, BadgeContent = color, + SelectionCursor = color, } local font = { diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/validateTheme.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/validateTheme.lua index e5a74b518f..97ed75e856 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/validateTheme.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Style/Validator/validateTheme.lua @@ -54,6 +54,8 @@ local ThemePalette = t.strictInterface({ Badge = Color, BadgeContent = Color, + + SelectionCursor = Color, }) return ThemePalette diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.lua index be870166d9..ceac18828c 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.lua @@ -19,7 +19,6 @@ local CursorKind = require(App.SelectionImage.CursorKind) local withSelectionCursorProvider = require(App.SelectionImage.withSelectionCursorProvider) local UIBloxConfig = require(UIBlox.UIBloxConfig) -local expandableTextAutomaticResizeConfig = UIBloxConfig.expandableTextAutomaticResizeConfig local DEFAULT_PADDING_TOP = 30 local PADDING_TOP = DEFAULT_PADDING_TOP @@ -58,7 +57,7 @@ local validateProps = t.strictInterface({ Position = t.optional(t.UDim2), compactNumberOfLines = t.optional(t.number), LayoutOrder = t.optional(t.number), - width = expandableTextAutomaticResizeConfig and t.optional(t.UDim) or t.UDim, + width = t.optional(t.UDim), padding = t.optional(t.Vector2), onClick = t.optional(t.callback), @@ -88,9 +87,6 @@ function ExpandableTextArea:init() self.ref = Roact.createRef() self.layoutRef = Roact.createRef() - - -- Remove isMounted once expandableTextAutomaticResizeConfig is removed - self.isMounted = false end function ExpandableTextArea:getRef() @@ -108,7 +104,7 @@ function ExpandableTextArea:applyFit(y) local frame = ref.current local offset = (y + PADDING_TOP + PADDING_BOTTOM) local width = self.props.width - if not expandableTextAutomaticResizeConfig or width then + if width then frame.Size = UDim2.new(width.Scale, width.Offset, 0, offset) else frame.Size = UDim2.new(1, 0, 0, offset) @@ -172,7 +168,7 @@ function ExpandableTextArea:render() BorderSizePixel = 0, LayoutOrder = layoutOrder, Position = position, - Size = (not expandableTextAutomaticResizeConfig or width) and UDim2.new(width.Scale, width.Offset, 0, 0) + Size = width and UDim2.new(width.Scale, width.Offset, 0, 0) or UDim2.new(1, 0, 0, 0), SelectionImageObject = getSelectionCursor(CursorKind.RoundedRect), [Roact.Ref] = ref, @@ -181,19 +177,9 @@ function ExpandableTextArea:render() -- Wrapped in spawn in order to avoid issues if Roact connects changed signal before the Size -- prop is set in older versions of Roact (older than 1.0) In 1.0, this is fixed by deferring event -- handlers and setState calls until after the current update]] - if expandableTextAutomaticResizeConfig then - self:setState({ - frameWidth = rbx.AbsoluteSize.X, - }) - else - spawn(function() - if self.isMounted then - self:setState({ - frameWidth = rbx.AbsoluteSize.X, - }) - end - end) - end + self:setState({ + frameWidth = rbx.AbsoluteSize.X, + }) end end, diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.spec.lua index 7c5238f383..35aadbf6cf 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.spec.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/App/Text/ExpandableTextArea/ExpandableTextArea.spec.lua @@ -9,9 +9,6 @@ return function() local mockStyleComponent = require(UIBlox.Utility.mockStyleComponent) local ExpandableTextArea = require(ExpandableTextAreaFolder.ExpandableTextArea) - local UIBloxConfig = require(UIBlox.UIBloxConfig) - local expandableTextAutomaticResizeConfig = UIBloxConfig.expandableTextAutomaticResizeConfig - local descriptionText = [[ This golden crown was awarded as a prize in the June 2007 Domino Rally Building Contest. Perhaps its most unique characteristic is its ability to inspire viewers with awe @@ -23,7 +20,6 @@ return function() local element = mockStyleComponent({ Image = Roact.createElement(ExpandableTextArea, { Text = descriptionText, - width = not expandableTextAutomaticResizeConfig and UDim.new(0, 200) or nil, }) }) diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Animation/withAnimation.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Animation/withAnimation.lua new file mode 100644 index 0000000000..6877f11cf3 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Animation/withAnimation.lua @@ -0,0 +1,74 @@ +local Animation = script.Parent +local Core = Animation.Parent +local UIBlox = Core.Parent +local Packages = UIBlox.Parent + +local Roact = require(Packages.Roact) +local Otter = require(Packages.Otter) +local t = require(Packages.t) + +--[[ + A convenient animation wrapper + provides the same features as SpringAnimatedItem, with a simpler API + + withAnimation({ + option1 = targetValue, + option2 = target2, + }, function(values) + local currentOption = values.option1 + return Roact.createComponent(.....) + end) +]] + +local AnimatedComponent = Roact.PureComponent:extend("AnimatedComponent") + +AnimatedComponent.validateProps = t.strictInterface({ + -- target values to animate to + values = t.table, + -- function(actualValues: table) called to render components at the current values + render = t.callback, + -- otter spring options + options = t.optional(t.table), +}) + +function AnimatedComponent:init() + self:setState({ + values = self.props.values, + }) + self.motor = Otter.createGroupMotor(self.props.values) + self.motor:onStep(function(values) + self:setState({ + values = values, + }) + end) +end + +function AnimatedComponent:willUpdate(nextProps) + local values = self.props.values + local options = self.props.options + local goals = {} + for key, targetValue in pairs(values) do + goals[key] = Otter.spring(targetValue, options) + end + self.motor:setGoal(goals) +end + +function AnimatedComponent:render() + local values = self.state.values + return self.props.render(values) +end + +function AnimatedComponent:willUnmount() + if self.motor then + self.motor:destroy() + self.motor = nil + end +end + +return function(goals, callback, options) + return Roact.createElement(AnimatedComponent, { + values = goals, + options = options, + render = callback, + }) +end diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Animation/withAnimation.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Animation/withAnimation.spec.lua new file mode 100644 index 0000000000..0946b35c25 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Animation/withAnimation.spec.lua @@ -0,0 +1,32 @@ +return function() + local Navigation = script.Parent + local App = Navigation.Parent + local UIBloxRoot = App.Parent + local Roact = require(UIBloxRoot.Parent.Roact) + + local withAnimation = require(script.Parent.withAnimation) + + local lastValues = nil + local component = function(props) + return withAnimation(props, function(values) + lastValues = values + return Roact.createElement("Frame") + end) + end + + it("should create and destroy without errors", function() + local element = Roact.createElement(component) + local instance = Roact.mount(element) + Roact.unmount(instance) + end) + + it("should mount contents using initial values", function() + lastValues = nil + local element = Roact.createElement(component, { + animatedValue = 5, + }) + local instance = Roact.mount(element) + Roact.unmount(instance) + expect(lastValues.animatedValue).to.equal(5) + end) +end diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/Enum/SelectionMode.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/Enum/SelectionMode.lua new file mode 100644 index 0000000000..e228a8f2b1 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/Enum/SelectionMode.lua @@ -0,0 +1,10 @@ +local Core = script.Parent.Parent.Parent +local UIBlox = Core.Parent +local Packages = UIBlox.Parent +local enumerate = require(Packages.enumerate) + +return enumerate(script.Name, { + "Single", + "Multiple", + "None", +}) diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableList.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableList.lua new file mode 100644 index 0000000000..04d7549de8 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableList.lua @@ -0,0 +1,161 @@ +local Control = script.Parent +local Core = Control.Parent +local UIBlox = Core.Parent +local Packages = UIBlox.Parent + +local Roact = require(Packages.Roact) +local t = require(Packages.t) +local Cryo = require(Packages.Cryo) + +local ControlState = require(UIBlox.Core.Control.Enum.ControlState) + +local InteractableListItem = require(script.Parent.InteractableListItem) +local SelectionMode = require(script.Parent.Enum.SelectionMode) + +local LAYOUT_KEY = "$layout" + +--[[ + Manages selection and control state for a list of items, + useful as a base for components like button lists, tab bars, etc + + expects a list of arbitrary items, and a callback to render an item + each rendered item will be wrapped by a controllable, its state will be passed to the callback + each item has also a selection state, which can be managed by the component or forced through a prop + for managed selection, there are 3 selection modes: single, multiple, or none + a callback can be provided to handle user input selection change events (regardless of selection state) +]] + +local InteractableList = Roact.PureComponent:extend("InteractableList") + +InteractableList.validateProps = t.strictInterface({ + -- list of items to display as interactables, should be a table + itemList = t.table, + -- function(item: any, state: ControlState, selected: boolean) called to render each item + renderItem = t.callback, + -- function(itemList: table, renderItem: function(key)) called to render the list container + renderList = t.optional(t.callback), + -- list of currently selected keys (from itemList), if not provided selection will be managed by component + selection = t.optional(t.table), + -- current selection mode: single, multiple, none + selectionMode = t.optional(SelectionMode.isEnumValue), + -- function(newSelection) + onSelectionChanged = t.optional(t.callback), + --- options for default renderList, passed to a Frame component + size = t.optional(t.UDim2), + position = t.optional(t.UDim2), + layoutOrder = t.optional(t.integer), + padding = t.optional(t.UDim), + fillDirection = t.optional(t.enum(Enum.FillDirection)), + horizontalAlignment = t.optional(t.enum(Enum.HorizontalAlignment)), + verticalAlignment = t.optional(t.enum(Enum.VerticalAlignment)), + sortOrder = t.optional(t.enum(Enum.SortOrder)), + --- options for default controllable + -- container size for each item + itemSize = t.optional(t.UDim2), +}) + +InteractableList.defaultProps = { + renderList = function(items, renderItem, extraProps) + local children = {} + for key in pairs(items) do + children[key] = renderItem(key) + end + return Roact.createElement("Frame", { + Size = extraProps.size, + Position = extraProps.position, + BackgroundTransparency = 1, + BorderSizePixel = 0, + }, Cryo.Dictionary.join({ + [LAYOUT_KEY] = Roact.createElement("UIListLayout", { + Padding = extraProps.padding, + FillDirection = extraProps.fillDirection, + HorizontalAlignment = extraProps.horizontalAlignment, + VerticalAlignment = extraProps.verticalAlignment, + SortOrder = extraProps.sortOrder, + }) + }, children)) + end, + size = UDim2.fromScale(1,1), + itemSize = UDim2.fromScale(1, 1), + selectionMode = SelectionMode.Single, +} + +function InteractableList:init() + local state = { + interactable = {}, + selection = {}, + } + if self.props.selectionMode == SelectionMode.Single then + local firstKey = next(self.props.itemList) + if firstKey ~= nil then + state.selection = { firstKey } + end + end + self:setState(state) + + self.setInteractableState = function(key, newState) + self:setState({ + interactable = Cryo.Dictionary.join(self.state.interactable, { + [key] = newState, + }) + }) + end + self.setSelection = function(newSelection) + self:setState({ + selection = newSelection, + }) + end +end + +function InteractableList:getSelection() + if self.props.selection then + return self.props.selection + end + if self.props.selectionMode == SelectionMode.None then + return {} + end + local selection = Cryo.List.filter(self.state.selection, function(selectedKey) + return self.props.itemList[selectedKey] ~= nil + end) + if self.props.selectionMode == SelectionMode.Single then + local firstKey = selection[#selection] + if firstKey == nil then + firstKey = next(self.props.itemList) + end + if firstKey ~= nil then + return { firstKey } + else + return {} + end + else + return selection + end +end + +function InteractableList:didMount() + if self.props.selection == nil and self.props.onSelectionChanged then + self.props.onSelectionChanged(self:getSelection()) + end +end + +function InteractableList:render() + return self.props.renderList(self.props.itemList, function(key) + local interactableState = self.state.interactable[key] or ControlState.Default + local selection = self:getSelection() + + return Roact.createElement(InteractableListItem, { + key = key, + item = self.props.itemList[key], + interactableState = interactableState, + selection = selection, + renderItem = self.props.renderItem, + itemSize = self.props.itemSize, + selectionMode = self.props.selectionMode, + onSelectionChanged = self.props.onSelectionChanged, + setInteractableState = self.setInteractableState, + setSelection = self.setSelection, + }) + end, self.props) +end + +return InteractableList diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableList.spec.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableList.spec.lua new file mode 100644 index 0000000000..7fde30d220 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableList.spec.lua @@ -0,0 +1,48 @@ +return function() + local Navigation = script.Parent + local App = Navigation.Parent + local UIBloxRoot = App.Parent + local Packages = UIBloxRoot.Parent + local Roact = require(Packages.Roact) + local Cryo = require(Packages.Cryo) + + local InteractableList = require(script.Parent.InteractableList) + local SelectionMode = require(script.Parent.Enum.SelectionMode) + + it("should create and destroy with minimum props without errors", function() + local element = Roact.createElement(InteractableList, { + itemList = {}, + renderItem = function() end, + }) + local instance = Roact.mount(element) + Roact.unmount(instance) + end) + + it("should create and destroy with all props without errors", function() + local element = Roact.createElement(InteractableList, { + itemList = {"one", "two"}, + renderItem = function(item) + return Roact.createElement("TextLabel", { + Text = item, + }) + end, + renderList = function(items, renderItem) + return Roact.createElement("Frame", {}, Cryo.List.map(Cryo.Dictionary.keys(items), renderItem)) + end, + selection = {}, + selectionMode = SelectionMode.None, + onSelectionChanged = function() end, + size = UDim2.new(), + position = UDim2.new(), + layoutOrder = 1, + padding = UDim.new(), + fillDirection = Enum.FillDirection.Horizontal, + horizontalAlignment = Enum.HorizontalAlignment.Center, + verticalAlignment = Enum.VerticalAlignment.Bottom, + sortOrder = Enum.SortOrder.Custom, + itemSize = UDim2.new(), + }) + local instance = Roact.mount(element) + Roact.unmount(instance) + end) +end diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableListItem.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableListItem.lua new file mode 100644 index 0000000000..6bbc4e5cd5 --- /dev/null +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Control/InteractableListItem.lua @@ -0,0 +1,61 @@ + +local Control = script.Parent +local Core = Control.Parent +local UIBlox = Core.Parent +local Packages = UIBlox.Parent + +local Roact = require(Packages.Roact) +local Cryo = require(Packages.Cryo) + +local Interactable = require(UIBlox.Core.Control.Interactable) + +local SelectionMode = require(script.Parent.Enum.SelectionMode) + +--[[ + subcomponent of InteractableList, not intended for use separately + extracted from the main component's code to cache the onStateChanged and onActivated callbacks +]] + +local InteractableListItem = Roact.PureComponent:extend("InteractableListItem") + +function InteractableListItem:init() + self.onStateChanged = function(oldState, newState) + self.props.setInteractableState(self.props.key, newState) + end + self.onActivated = function() + local oldSelection = self.props.selection + local newSelection = { self.props.key } + if self.props.selectionMode == SelectionMode.Multiple then + newSelection = Cryo.List.filter(oldSelection, function(selectedKey) + return selectedKey ~= self.props.key + end) + if #newSelection == #oldSelection then + table.insert(newSelection, self.props.key) + end + end + if self.props.onSelectionChanged then + self.props.onSelectionChanged(newSelection, oldSelection) + end + if self.props.selectionMode ~= SelectionMode.None then + self.props.setSelection(newSelection) + end + end +end + +function InteractableListItem:render() + local selected = Cryo.List.find(self.props.selection, self.props.key) ~= nil + local renderedItem, extraProps = self.props.renderItem(self.props.item, self.props.interactableState, selected) + + return Roact.createElement(Interactable, Cryo.Dictionary.join({ + Size = self.props.itemSize, + BackgroundTransparency = 1, + BorderSizePixel = 0, + }, extraProps or {}, { + onStateChanged = self.onStateChanged, + [Roact.Event.Activated] = self.onActivated, + }), { + [self.props.key] = renderedItem, + }) +end + +return InteractableListItem diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Slider/GenericSlider.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Slider/GenericSlider.lua index 6bcd4afd23..d8577b0072 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Slider/GenericSlider.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Core/Slider/GenericSlider.lua @@ -11,9 +11,19 @@ local Packages = UIBloxRoot.Parent local Roact = require(Packages.Roact) local t = require(Packages.t) +local Gamepad = require(Packages.RoactGamepad) local ImageSetComponent = require(CoreRoot.ImageSet.ImageSetComponent) local lerp = require(UIBloxRoot.Utility.lerp) +local UIBloxConfig = require(UIBloxRoot.UIBloxConfig) +local CursorKind = require(UIBloxRoot.App.SelectionImage.CursorKind) +local withSelectionCursorProvider = require(UIBloxRoot.App.SelectionImage.withSelectionCursorProvider) + +local DPAD_INITIAL_MOVE_INTERVAL = 0.5 +local STICK_INITIAL_MOVE_INTERVAL = 0.2 +local STICK_MOVE_DEADZONE = 0.2 +local DPAD_SPEED = 8 -- In increments per second +local STICK_SPEED = 12 -- In increments per second local GenericSlider = Roact.PureComponent:extend("GenericSlider") @@ -64,6 +74,11 @@ GenericSlider.validateProps = t.strictInterface({ position = t.optional(t.UDim2), anchorPoint = t.optional(t.Vector2), layoutOrder = t.optional(t.integer), + + [Roact.Ref] = t.optional(t.table), + NextSelectionUp = t.optional(t.table), + NextSelectionDown = t.optional(t.table), + focusController = t.optional(t.table), }) GenericSlider.defaultProps = { @@ -72,12 +87,151 @@ GenericSlider.defaultProps = { } function GenericSlider:init() - self.rootRef = Roact.createRef() + self.rootRef = self.props[Roact.Ref] or Roact.createRef() + self.lowerKnobRef = Roact.createRef() + self.upperKnobRef = Roact.createRef() + self.moveDirection = 0 + self.lowerKnobDrag = false self.upperKnobDrag = false + self.totalMoveTime = 0 + self.isFirstMove = true + self.unhandledTime = 0 + + self.state = { + lowerKnobIsSelected = false, + upperKnobIsSelected = false, + } +end + +function GenericSlider:onMoveStep(inputObjects, delta) + if not (self.state.lowerKnobIsSelected or self.state.upperKnobIsSelected) then + return + end + + local stickInput = inputObjects[Enum.KeyCode.Thumbstick1].Position + local usingStick = stickInput.Magnitude > STICK_MOVE_DEADZONE + local increments = 0 + local initialMoveInterval, moveDirection, speed + self.totalMoveTime = self.totalMoveTime + delta + + if usingStick then + moveDirection = stickInput.x > 0 and 1 or -1 + initialMoveInterval = STICK_INITIAL_MOVE_INTERVAL + speed = STICK_SPEED + else + local leftMovement = inputObjects[Enum.KeyCode.DPadLeft].UserInputState == Enum.UserInputState.Begin and -1 or 0 + local rightMovement = inputObjects[Enum.KeyCode.DPadRight].UserInputState == Enum.UserInputState.Begin and 1 or 0 + moveDirection = leftMovement + rightMovement + initialMoveInterval = DPAD_INITIAL_MOVE_INTERVAL + speed = DPAD_SPEED + end + + if moveDirection ~= 0 then + -- Process input for the first button press + if self.isFirstMove then + self.isFirstMove = false + self.totalMoveTime = 0 + self.unhandledTime = 0 + increments = 1 + -- Process input if enough time has passed. + elseif self.totalMoveTime > initialMoveInterval then + -- How much of delta time that was in the first interval + local initialIntervalOverlap = math.max(initialMoveInterval - self.totalMoveTime - delta, 0) + local timeToHandle = delta - initialIntervalOverlap + self.unhandledTime + increments = math.floor(speed * timeToHandle) + + self.unhandledTime = timeToHandle - increments / speed + else + -- Period between first move and subsequent moves + increments = 0 + self.unhandledTime = 0 + end + else + self.totalMoveTime = 0 + self.isFirstMove = true + end + + if increments > 0 then + self:processGamepadInput(moveDirection, increments) + end +end + +function GenericSlider:processGamepadInput(polarity, increments) + if self:hasTwoKnobs() then + self:processTwoKnobGamepadInput(polarity, increments) + else + self:processOneKnobGamepadInput(polarity, increments) + end +end + +function GenericSlider:processTwoKnobGamepadInput(polarity, increments) + local stepInterval = self.props.stepInterval * polarity + local lowerValue = self.props.lowerValue + local upperValue = self.props.upperValue + + --[[ + If the knobs are overlapping, the gamepad may select either knob. + Set the one that should be selected by the direction of the input. + ]] + if lowerValue == upperValue and not self.state.processingGamepad then + if self.state.lowerKnobIsSelected and polarity == 1 and lowerValue ~= self.props.max then + self:setState({ + lowerKnobIsSelected = false, + upperKnobIsSelected = true, + processingGamepad = true, + }) + self.props.focusController.moveFocusTo(self.upperKnobRef) + elseif self.state.upperKnobIsSelected and polarity == -1 and upperValue ~= self.props.min then + self:setState({ + lowerKnobIsSelected = true, + upperKnobIsSelected = false, + processingGamepad = true, + }) + self.props.focusController.moveFocusTo(self.lowerKnobRef) + end + elseif not self.state.processingGamepad then + self:setState({ + processingGamepad = true, + }) + end + + if self.state.lowerKnobIsSelected then + local steppedValue = math.max(math.min(lowerValue + (stepInterval * increments), self.props.max), self.props.min) + if steppedValue <= upperValue then + lowerValue = steppedValue + else + lowerValue = upperValue + end + elseif self.state.upperKnobIsSelected then + local steppedValue = math.max(math.min(upperValue + (stepInterval * increments), self.props.max), self.props.min) + if steppedValue >= lowerValue then + upperValue = steppedValue + else + upperValue = lowerValue + end + end + + if upperValue ~= self.props.upperValue or lowerValue ~= self.props.lowerValue then + self.props.onValueChanged(lowerValue, upperValue) + end +end + +function GenericSlider:processOneKnobGamepadInput(polarity, increments) + local stepInterval = self.props.stepInterval * polarity + local lowerValue = self.props.lowerValue + + if self.state.lowerKnobIsSelected then + lowerValue = math.max(math.min(lowerValue + (stepInterval * increments), self.props.max), self.props.min) + end + + if lowerValue ~= self.props.lowerValue then + self.props.onValueChanged(lowerValue) + end end function GenericSlider:render() + local knobIsSelected = self.state.lowerKnobIsSelected or self.state.upperKnobIsSelected local isTwoKnobs = self:hasTwoKnobs() local fillPercentLower = (self.props.lowerValue - self.props.min) / (self.props.max - self.props.min) local fillPercentUpper = isTwoKnobs and (self.props.upperValue - self.props.min) / (self.props.max - self.props.min) @@ -89,99 +243,171 @@ function GenericSlider:render() local knobPositionUpper = isTwoKnobs and UDim2.new(fillPercentUpper, positionOffsetUpper, 0.5, 0) or nil local fillSize = isTwoKnobs and UDim2.fromScale(fillPercentUpper - fillPercentLower, 1) or UDim2.fromScale(fillPercentLower, 1) + local selectedKnob = self.state.lowerKnobIsSelected or self.state.upperKnobIsSelected - return Roact.createElement(ImageSetComponent.Button, { - BackgroundTransparency = 1, - AnchorPoint = self.props.anchorPoint, - Size = UDim2.new(self.props.width.Scale, self.props.width.Offset, 0, SLIDER_HEIGHT), - LayoutOrder = self.props.layoutOrder, - Position = self.props.position, - [Roact.Event.InputBegan] = function(rbx, inputObject) - if self.props.isDisabled then - return - end + local imageSetComponent = UIBloxConfig.enableExperimentalGamepadSupport and + Gamepad.Focusable[ImageSetComponent.Button] or ImageSetComponent.Button - self:onInputBegan(inputObject, false) - end, - [Roact.Ref] = self.rootRef, - }, { - Track = Roact.createElement(ImageSetComponent.Label, { - AnchorPoint = Vector2.new(0.5, 0.5), - BackgroundTransparency = 1, - ImageColor3 = self.props.trackColor, - ImageTransparency = self.props.trackTransparency, - Image = self.props.trackImage, - Size = UDim2.new(1, 0, 0, 4), - Position = UDim2.fromScale(0.5, 0.5), - ScaleType = Enum.ScaleType.Slice, - SliceCenter = self.props.trackSliceCenter, - }, { - TrackFill = Roact.createElement(ImageSetComponent.Label, { - BackgroundTransparency = 1, - ImageColor3 = self.props.trackFillColor, - ImageTransparency = self.props.trackFillTransparency, - Image = self.props.trackFillImage, - Size = fillSize, - Position = isTwoKnobs and UDim2.new(fillPercentLower, 0, 0, 0) or UDim2.new(0, 0, 0, 0), - ScaleType = Enum.ScaleType.Slice, - SliceCenter = self.props.trackFillSliceCenter, - }) - }), - LowerKnob = Roact.createElement(ImageSetComponent.Button, { - AnchorPoint = Vector2.new(0.5, 0.5), + return withSelectionCursorProvider(function(getSelectionCursor) + return Roact.createElement(imageSetComponent, { BackgroundTransparency = 1, - ImageColor3 = self.props.knobColorLower, - ImageTransparency = self.props.knobTransparency, - Image = self.props.knobImage, - Size = UDim2.fromOffset(KNOB_HEIGHT, KNOB_HEIGHT), - Position = knobPositionLower, - ZIndex = 3, - + AnchorPoint = self.props.anchorPoint, + Size = UDim2.new(self.props.width.Scale, self.props.width.Offset, 0, SLIDER_HEIGHT), + LayoutOrder = self.props.layoutOrder, + Position = self.props.position, [Roact.Event.InputBegan] = function(rbx, inputObject) if self.props.isDisabled then return end - self:onInputBegan(inputObject, true) + self:onInputBegan(inputObject, false) end, - }), - LowerKnobShadow = Roact.createElement(ImageSetComponent.Label, { - AnchorPoint = Vector2.new(0.5, 0.5), - BackgroundTransparency = 1, - ImageTransparency = self.props.knobShadowTransparencyLower, - Image = self.props.knobShadowImage, - Size = UDim2.fromOffset(44, 44), - Position = knobPositionLower, - ZIndex = 2, - }), - UpperKnob = isTwoKnobs and Roact.createElement(ImageSetComponent.Button, { - AnchorPoint = Vector2.new(0.5, 0.5), - BackgroundTransparency = 1, - ImageColor3 = self.props.knobColorUpper, - ImageTransparency = self.props.knobTransparency, - Image = self.props.knobImage, - Size = UDim2.fromOffset(KNOB_HEIGHT, KNOB_HEIGHT), - Position = knobPositionUpper, - ZIndex = 3, - - [Roact.Event.InputBegan] = function(rbx, inputObject) - if self.props.isDisabled then - return + [Roact.Ref] = self.rootRef, + + NextSelectionUp = (not selectedKnob) and self.props.NextSelectionUp or self.rootRef, + NextSelectionDown = (not selectedKnob) and self.props.NextSelectionDown or self.rootRef, + defaultChild = UIBloxConfig.enableExperimentalGamepadSupport and + (self.props.upperValue ~= self.props.min and self.lowerKnobRef or self.upperKnobRef) or nil, + onFocusLost = UIBloxConfig.enableExperimentalGamepadSupport and function() + if self.state.lowerKnobIsSelected or self.state.upperKnobIsSelected then + self:setState({ + lowerKnobIsSelected = false, + upperKnobIsSelected = false, + }) end - - self:onInputBegan(inputObject, true) - end, - }), - UpperKnobShadow = isTwoKnobs and Roact.createElement(ImageSetComponent.Label, { - AnchorPoint = Vector2.new(0.5, 0.5), - BackgroundTransparency = 1, - ImageTransparency = self.props.knobShadowTransparencyUpper, - Image = self.props.knobShadowImage, - Size = UDim2.fromOffset(44, 44), - Position = knobPositionUpper, - ZIndex = 2, - }), - }) + end or nil, + }, { + Track = Roact.createElement(ImageSetComponent.Label, { + AnchorPoint = Vector2.new(0.5, 0.5), + BackgroundTransparency = 1, + ImageColor3 = self.props.trackColor, + ImageTransparency = self.props.trackTransparency, + Image = self.props.trackImage, + Size = UDim2.new(1, 0, 0, 4), + Position = UDim2.fromScale(0.5, 0.5), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = self.props.trackSliceCenter, + }, { + TrackFill = Roact.createElement(ImageSetComponent.Label, { + BackgroundTransparency = 1, + ImageColor3 = self.props.trackFillColor, + ImageTransparency = self.props.trackFillTransparency, + Image = self.props.trackFillImage, + Size = fillSize, + Position = isTwoKnobs and UDim2.new(fillPercentLower, 0, 0, 0) or UDim2.new(0, 0, 0, 0), + ScaleType = Enum.ScaleType.Slice, + SliceCenter = self.props.trackFillSliceCenter, + }) + }), + LowerKnob = Roact.createElement(imageSetComponent, { + AnchorPoint = Vector2.new(0.5, 0.5), + BackgroundTransparency = 1, + ImageColor3 = self.props.knobColorLower, + ImageTransparency = self.props.knobTransparency, + Image = self.props.knobImage, + Size = UDim2.fromOffset(KNOB_HEIGHT, KNOB_HEIGHT), + Position = knobPositionLower, + ZIndex = 3, + inputBindings = UIBloxConfig.enableExperimentalGamepadSupport and { + OnMoveStep = Gamepad.Input.onMoveStep(function(inputObjects, delta) + self:onMoveStep(inputObjects, delta) + end), + SelectLowerKnob = Gamepad.Input.onBegin(Enum.KeyCode.ButtonA, function() + self:setState(function(state) + return { + lowerKnobIsSelected = not state.lowerKnobIsSelected, + processingGamepad = false, + } + end) + end), + UnselectLowerKnob = Gamepad.Input.onBegin(Enum.KeyCode.ButtonB, function() + self:setState({ + lowerKnobIsSelected = false, + processingGamepad = false, + }) + end), + } or nil, + NextSelectionLeft = knobIsSelected and self.lowerKnobRef or nil, + NextSelectionRight = (isTwoKnobs and not knobIsSelected and self.props.upperValue ~= self.props.lowerValue) + and self.upperKnobRef or nil, + NextSelectionUp = knobIsSelected and self.lowerKnobRef or nil, + NextSelectionDown = knobIsSelected and self.lowerKnobRef or nil, + SelectionImageObject = knobIsSelected and getSelectionCursor(CursorKind.SelectedKnob) + or getSelectionCursor(CursorKind.UnselectedKnob), + [Roact.Ref] = self.lowerKnobRef, + [Roact.Event.InputBegan] = function(rbx, inputObject) + if self.props.isDisabled then + return + end + + self:onInputBegan(inputObject, true) + end, + }), + LowerKnobShadow = Roact.createElement(ImageSetComponent.Label, { + AnchorPoint = Vector2.new(0.5, 0.5), + BackgroundTransparency = 1, + ImageTransparency = self.props.knobShadowTransparencyLower, + Image = self.props.knobShadowImage, + Size = UDim2.fromOffset(44, 44), + Position = knobPositionLower, + ZIndex = 2, + }), + UpperKnob = isTwoKnobs and Roact.createElement(imageSetComponent, { + AnchorPoint = Vector2.new(0.5, 0.5), + BackgroundTransparency = 1, + ImageColor3 = self.props.knobColorUpper, + ImageTransparency = self.props.knobTransparency, + Image = self.props.knobImage, + Size = UDim2.fromOffset(KNOB_HEIGHT, KNOB_HEIGHT), + Position = knobPositionUpper, + ZIndex = 3, + + NextSelectionLeft = (isTwoKnobs and not knobIsSelected and self.props.upperValue ~= self.props.lowerValue) + and self.lowerKnobRef or nil, + NextSelectionRight = knobIsSelected and self.upperKnobRef or nil, + NextSelectionUp = knobIsSelected and self.upperKnobRef or nil, + NextSelectionDown = knobIsSelected and self.upperKnobRef or nil, + SelectionImageObject = knobIsSelected and getSelectionCursor(CursorKind.SelectedKnob) + or getSelectionCursor(CursorKind.UnselectedKnob), + [Roact.Ref] = self.upperKnobRef, + [Roact.Event.InputBegan] = function(rbx, inputObject) + if self.props.isDisabled then + return + end + + self:onInputBegan(inputObject, true) + end, + inputBindings = UIBloxConfig.enableExperimentalGamepadSupport and { + OnMoveStep = Gamepad.Input.onMoveStep(function(inputObjects, delta) + self:onMoveStep(inputObjects, delta) + end), + SelectUpperKnob = Gamepad.Input.onBegin(Enum.KeyCode.ButtonA, function() + self:setState(function(state) + return { + upperKnobIsSelected = not state.upperKnobIsSelected, + processingGamepad = false, + } + end) + end), + UnselectUpperKnob = Gamepad.Input.onBegin(Enum.KeyCode.ButtonB, function() + self:setState({ + upperKnobIsSelected = false, + processingGamepad = false, + }) + end), + } or nil, + }), + UpperKnobShadow = isTwoKnobs and Roact.createElement(ImageSetComponent.Label, { + AnchorPoint = Vector2.new(0.5, 0.5), + BackgroundTransparency = 1, + ImageTransparency = self.props.knobShadowTransparencyUpper, + Image = self.props.knobShadowImage, + Size = UDim2.fromOffset(44, 44), + Position = knobPositionUpper, + ZIndex = 2, + }), + }) + end) end function GenericSlider:didMount() diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/TestStyle.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/TestStyle.lua index e3ac269dd2..3d4f7bda44 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/TestStyle.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/TestStyle.lua @@ -39,6 +39,7 @@ local testTheme = { Alert = color, Badge = color, BadgeContent = color, + SelectionCursor = color, } local font = { diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/validateTheme.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/validateTheme.lua index dc01d9e51a..27a9751042 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/validateTheme.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/Style/Validator/validateTheme.lua @@ -51,6 +51,8 @@ local ThemePalette = t.strictInterface({ Badge = Color, BadgeContent = Color, + + SelectionCursor = Color, }) return ThemePalette diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/UIBloxDefaultConfig.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/UIBloxDefaultConfig.lua index 53a4856dfa..dabcd72d05 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/UIBloxDefaultConfig.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/UIBloxDefaultConfig.lua @@ -1,12 +1,4 @@ return { - -- fixToastResizeConfig: fixes bug where Toasts - -- will not resize when text changes. - fixToastResizeConfig = false, - - -- expandableTextAutomaticResizeConfig: refactor of ExpandableTextArea to - -- automatically resize to fit its container. Also removes width prop. - expandableTextAutomaticResizeConfig = false, - -- enableAlertTitleIconConfig: turning this on allows the Alert component to take -- in an optional titleIcon prop, which displays an icon above the Alert's title. enableAlertTitleIconConfig = false, diff --git a/LuaPackages/Packages/_Index/UIBlox/UIBlox/init.lua b/LuaPackages/Packages/_Index/UIBlox/UIBlox/init.lua index 6e3a6e0dfe..149955fb7f 100644 --- a/LuaPackages/Packages/_Index/UIBlox/UIBlox/init.lua +++ b/LuaPackages/Packages/_Index/UIBlox/UIBlox/init.lua @@ -10,6 +10,7 @@ local function initializeLibrary() UIBlox.Core = strict({ Animation = strict({ SpringAnimatedItem = require(script.Utility.SpringAnimatedItem), + withAnimation = require(script.Core.Animation.withAnimation), }), Bar = strict({ @@ -26,9 +27,11 @@ local function initializeLibrary() Control = strict({ Enum = strict({ - ControlState = require(script.Core.Control.Enum.ControlState) + ControlState = require(script.Core.Control.Enum.ControlState), + SelectionMode = require(script.Core.Control.Enum.SelectionMode), }), Interactable = require(script.Core.Control.Interactable), + InteractableList = require(script.Core.Control.InteractableList), }), Style = strict({ @@ -85,6 +88,7 @@ local function initializeLibrary() ButtonStack = require(script.App.Button.ButtonStack), TextButton = require(script.App.Button.TextButton), IconButton = require(script.App.Button.IconButton), + ActionBar = require(script.App.Button.ActionBar) }), Cell = strict({ @@ -210,11 +214,18 @@ local function initializeLibrary() }), + Navigation = strict({ + Enum = strict({ + Placement = require(script.App.Navigation.Enum.Placement), + }), + SystemBar = require(script.App.Navigation.SystemBar), + }), + SelectionImage = strict({ SelectionCursorProvider = require(script.App.SelectionImage.SelectionCursorProvider), CursorKind = require(script.App.SelectionImage.CursorKind), withSelectionCursorProvider = require(script.App.SelectionImage.withSelectionCursorProvider), - }) + }), }) -- DEPRECATED SECTION diff --git a/LuaPackages/Packages/_Index/UIBlox/lock.toml b/LuaPackages/Packages/_Index/UIBlox/lock.toml index 44789dc35c..a0f995d758 100644 --- a/LuaPackages/Packages/_Index/UIBlox/lock.toml +++ b/LuaPackages/Packages/_Index/UIBlox/lock.toml @@ -1,7 +1,7 @@ # Generated by Rotriever. Format subject to change in future releases. name = "UIBlox" version = "0.1.1" -commit = "a253d52373c4ce1611090c04ae2a02994274f1f2" +commit = "d3684f48defd55c133ae606bce5c9ab41bb35bd5" source = "git+https://github.com/roblox/uiblox#master" dependencies = [ "Cryo roblox/cryo 1.0.0 url+https://github.com/roblox/cryo", diff --git a/LuaPackages/Packages/_Index/asset-card/lock.toml b/LuaPackages/Packages/_Index/asset-card/lock.toml index e944af0f45..449cc9964e 100644 --- a/LuaPackages/Packages/_Index/asset-card/lock.toml +++ b/LuaPackages/Packages/_Index/asset-card/lock.toml @@ -6,6 +6,6 @@ source = "git+https://github.com/roblox/asset-card#v1.0.2" dependencies = [ "Roact roblox/roact 1.3.0 url+https://github.com/roblox/roact", "Rodux roblox/rodux 1.0.0 url+https://github.com/roblox/rodux", - "UIBlox UIBlox a253d523 git+https://github.com/roblox/uiblox#master", + "UIBlox UIBlox d3684f48 git+https://github.com/roblox/uiblox#master", "t roblox/t 1.2.5 url+https://github.com/roblox/t", ] diff --git a/LuaPackages/Packages/_Index/roblox_purchase-prompt/lock.toml b/LuaPackages/Packages/_Index/roblox_purchase-prompt/lock.toml index 2a2969a2ff..47aaf01d1c 100644 --- a/LuaPackages/Packages/_Index/roblox_purchase-prompt/lock.toml +++ b/LuaPackages/Packages/_Index/roblox_purchase-prompt/lock.toml @@ -1,7 +1,7 @@ # Generated by Rotriever. Format subject to change in future releases. name = "roblox/purchase-prompt" version = "0.1.6" -commit = "ad4ca756b1cee3e65122ca3459e2ae38668fcd54" +commit = "48fff5233c9850e6edf3744d99c2b5409e05a763" source = "git+https://github.com/roblox/purchasepromptscript-roact#master" dependencies = [ "Cryo roblox/cryo 1.0.0 url+https://github.com/roblox/cryo", @@ -10,6 +10,6 @@ dependencies = [ "Roact roblox/roact 1.3.0 url+https://github.com/roblox/roact", "RoactRodux roblox/roact-rodux 0.2.2 url+https://github.com/roblox/roact-rodux", "Rodux roblox/rodux 1.0.0 url+https://github.com/roblox/rodux", - "UIBlox UIBlox a253d523 git+https://github.com/roblox/uiblox#master", + "UIBlox UIBlox d3684f48 git+https://github.com/roblox/uiblox#master", "t roblox/t 1.2.5 url+https://github.com/roblox/t", ] diff --git a/LuaPackages/Packages/_Index/roblox_purchase-prompt/purchase-prompt/Services/LayoutValues.lua b/LuaPackages/Packages/_Index/roblox_purchase-prompt/purchase-prompt/Services/LayoutValues.lua index ba3da14a2b..6d571cf28b 100644 --- a/LuaPackages/Packages/_Index/roblox_purchase-prompt/purchase-prompt/Services/LayoutValues.lua +++ b/LuaPackages/Packages/_Index/roblox_purchase-prompt/purchase-prompt/Services/LayoutValues.lua @@ -147,9 +147,7 @@ function LayoutValues:generate(isTenFoot) and makeImageData("ui/PurchasePrompt/SingleButtonDown@2x.png", Rect.new(18, 5, 20, 7)) or makeImageData("ui/PurchasePrompt/SingleButtonDown.png", Rect.new(8, 3, 10, 4)) - Image.PremiumIcon = isTenFoot - and makeImageData("ui/PurchasePrompt/premium@2x.png") - or makeImageData("ui/PurchasePrompt/premium.png") + Image.PremiumIcon = makeImageData("ui/PurchasePrompt/Premium.png") if FFlagChinaLicensingApp then Image.RobuxIcon = isTenFoot diff --git a/LuaPackages/Packages/_Index/roblox_testez/lock.toml b/LuaPackages/Packages/_Index/roblox_testez/lock.toml index bc7efec1e9..9bb1e1c96e 100644 --- a/LuaPackages/Packages/_Index/roblox_testez/lock.toml +++ b/LuaPackages/Packages/_Index/roblox_testez/lock.toml @@ -1,5 +1,5 @@ # Generated by Rotriever. Format subject to change in future releases. name = "roblox/testez" -version = "0.3.1" -commit = "42f0b3772c90ed9c0cff187fb56d508aca7b6641" +version = "0.3.3" +commit = "058f3dc7185d77e3e2425cc579a9ff120a6ff127" source = "url+https://github.com/roblox/testez" diff --git a/LuaPackages/Packages/_Index/roblox_testez/testez/Expectation.lua b/LuaPackages/Packages/_Index/roblox_testez/testez/Expectation.lua index 675c33ae36..2b58ff6152 100644 --- a/LuaPackages/Packages/_Index/roblox_testez/testez/Expectation.lua +++ b/LuaPackages/Packages/_Index/roblox_testez/testez/Expectation.lua @@ -230,18 +230,43 @@ function Expectation:near(otherValue, limit) end --[[ - Assert that our functoid expectation value throws an error when called + Assert that our functoid expectation value throws an error when called. + An optional error message can be passed to assert that the error message + contains the given value. ]] -function Expectation:throw() +function Expectation:throw(messageSubstring) local ok, err = pcall(self.value) local result = ok ~= self.successCondition - local message = formatMessage(self.successCondition, - "Expected function to throw an error, but it did not.", - ("Expected function to succeed, but it threw an error: %s"):format( - tostring(err) + if messageSubstring and not ok then + if self.successCondition then + result = err:find(messageSubstring, 1, true) ~= nil + else + result = err:find(messageSubstring, 1, true) == nil + end + end + + local message + + if messageSubstring then + message = formatMessage(self.successCondition, + ("Expected function to throw an error containing %q, but it %s"):format( + messageSubstring, + err and ("threw: %s"):format(err) or "did not throw." + ), + ("Expected function to never throw an error containing %q, but it threw: %s"):format( + messageSubstring, + tostring(err) + ) ) - ) + else + message = formatMessage(self.successCondition, + "Expected function to throw an error, but it did not throw.", + ("Expected function to succeed, but it threw an error: %s"):format( + tostring(err) + ) + ) + end assertLevel(result, message, 3) self:_resetModifiers() diff --git a/LuaPackages/Packages/_Index/roblox_testez/testez/TestRunner.lua b/LuaPackages/Packages/_Index/roblox_testez/testez/TestRunner.lua index c3f4467005..33018d8a57 100644 --- a/LuaPackages/Packages/_Index/roblox_testez/testez/TestRunner.lua +++ b/LuaPackages/Packages/_Index/roblox_testez/testez/TestRunner.lua @@ -134,9 +134,8 @@ function TestRunner.runPlanNode(session, planNode, lifecycleHooks) if not halt then for _, childPlanNode in ipairs(planNode.children) do - session:pushNode(childPlanNode) - if childPlanNode.type == TestEnum.NodeType.It then + session:pushNode(childPlanNode) if session:shouldSkip() then session:setSkipped() else @@ -148,7 +147,9 @@ function TestRunner.runPlanNode(session, planNode, lifecycleHooks) session:setError(errorMessage) end end + session:popNode() elseif childPlanNode.type == TestEnum.NodeType.Describe then + session:pushNode(childPlanNode) TestRunner.runPlanNode(session, childPlanNode, lifecycleHooks) -- Did we have an error trying build a test plan? @@ -158,9 +159,8 @@ function TestRunner.runPlanNode(session, planNode, lifecycleHooks) else session:setStatusFromChildren() end + session:popNode() end - - session:popNode() end end diff --git a/LuaPackages/Packages/_Index/roblox_url-builder/lock.toml b/LuaPackages/Packages/_Index/roblox_url-builder/lock.toml index 27a3d54734..7085c4927f 100644 --- a/LuaPackages/Packages/_Index/roblox_url-builder/lock.toml +++ b/LuaPackages/Packages/_Index/roblox_url-builder/lock.toml @@ -1,7 +1,7 @@ # Generated by Rotriever. Format subject to change in future releases. name = "roblox/url-builder" -version = "1.0.1" -commit = "9ae0b3705f07b75f259ffa19e7e363ef4eb02ebe" +version = "1.0.2" +commit = "5bef2f1ea01ad0fe7c638e349b864c0978cff619" source = "url+https://github.com/roblox/url-builder" dependencies = [ "Cryo roblox/cryo 1.0.0 url+https://github.com/roblox/cryo", diff --git a/LuaPackages/Packages/_Index/roblox_url-builder/url-builder/UrlPatterns/StaticUrlPatterns.lua b/LuaPackages/Packages/_Index/roblox_url-builder/url-builder/UrlPatterns/StaticUrlPatterns.lua index 825c4bb773..165ece0be9 100644 --- a/LuaPackages/Packages/_Index/roblox_url-builder/url-builder/UrlPatterns/StaticUrlPatterns.lua +++ b/LuaPackages/Packages/_Index/roblox_url-builder/url-builder/UrlPatterns/StaticUrlPatterns.lua @@ -16,6 +16,10 @@ return function(UrlBuilder) develop = UrlBuilder.fromString("www:develop/landing"), blog = UrlBuilder.fromString("blog:"), help = UrlBuilder.fromString(isQQ() and "corp:faq" or "www:help"), + email = { + getSetEmail = UrlBuilder.fromString("accountSettings:v1/email"), + sendVerificationEmail = UrlBuilder.fromString("accountSettings:v1/email/verify") + }, about = { us = UrlBuilder.fromString("corp:"), careers = UrlBuilder.fromString(isQQ() and "corp:careers.html" or "corp:careers"),