Created
June 29, 2023 03:38
-
-
Save littensy/d5ae1123a37daaffa6198f4e92ddf956 to your computer and use it in GitHub Desktop.
A bunch of components I like to use often
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useBindingListener, useCamera } from "@rbxts/pretty-roact-hooks"; | |
import Roact from "@rbxts/roact"; | |
import { useState } from "@rbxts/roact-hooked"; | |
interface BackgroundBlurProps { | |
blurSize?: number | Roact.Binding<number>; | |
} | |
/** | |
* Wraps a BlurEffect | |
*/ | |
export function BackgroundBlur({ blurSize }: BackgroundBlurProps) { | |
const camera = useCamera(); | |
const [visible, setVisible] = useState(false); | |
useBindingListener(blurSize, (size = 0) => { | |
setVisible(size > 0); | |
}); | |
return <Roact.Portal target={camera}>{visible && <blureffect Size={blurSize} />}</Roact.Portal>; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { mapBinding, useBindingState } from "@rbxts/pretty-roact-hooks"; | |
import Roact from "@rbxts/roact"; | |
import { useEffect, useMemo, useState } from "@rbxts/roact-hooked"; | |
import { CanvasGroup, CanvasGroupProps } from "./canvas-group"; | |
import { Frame } from "./frame"; | |
import { Group } from "./group"; | |
type CanvasOrFrameProps = CanvasGroupProps & { | |
ref?: Roact.RefPropertyOrFunction<Frame | CanvasGroup>; | |
event?: Roact.JsxInstanceEvents<Frame | CanvasGroup>; | |
change?: Roact.JsxInstanceChangeEvents<Frame | CanvasGroup>; | |
directChildren?: Roact.Element; | |
}; | |
const EPSILON = 0.03; | |
/** | |
* Render a CanvasGroup when animating GroupTransparency, render | |
* a Frame when it's zero or close to zero. Great for transitions | |
* that don't need to permanently use a CanvasGroup. | |
*/ | |
export function CanvasOrFrame(props: CanvasOrFrameProps) { | |
const propsWithoutChildren = { | |
...props, | |
[Roact.Children]: undefined, | |
}; | |
const isCanvasBinding = useMemo(() => { | |
return mapBinding(props.groupTransparency, (t = 0) => t > EPSILON); | |
}, [props.groupTransparency]); | |
const isCanvas = useBindingState(isCanvasBinding); | |
const [canvas, canvasRef] = useState<CanvasGroup>(); | |
const [frame, frameRef] = useState<Frame>(); | |
const portalTarget = useMemo(() => { | |
// Hack to avoid changing the 'target' prop of Portal, since | |
// that forces children to re-mount, which is bad | |
const frame = new Instance("Frame"); | |
frame.Name = "smart-canvas-target"; | |
frame.Size = UDim2.fromScale(1, 1); | |
frame.BackgroundTransparency = 1; | |
return frame; | |
}, []); | |
useEffect(() => { | |
portalTarget.Parent = isCanvas ? canvas : frame; | |
}, [isCanvas, canvas, frame]); | |
useEffect(() => { | |
return () => { | |
portalTarget.Destroy(); | |
}; | |
}, []); | |
return ( | |
<> | |
<Roact.Portal Key="portal" target={portalTarget}> | |
{props[Roact.Children]} | |
</Roact.Portal> | |
<Group> | |
<CanvasGroup {...propsWithoutChildren} visible={isCanvas} ref={canvasRef}> | |
{props.directChildren} | |
</CanvasGroup> | |
<Frame {...propsWithoutChildren} visible={!isCanvas} ref={frameRef}> | |
{props.directChildren} | |
</Frame> | |
</Group> | |
</> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Roact from "@rbxts/roact"; | |
import { useEffect, useState } from "@rbxts/roact-hooked"; | |
import { setTimeout } from "@rbxts/set-timeout"; | |
interface DelayRenderProps extends Roact.PropsWithChildren { | |
shouldRender: boolean; | |
mountDelay?: number; | |
unmountDelay?: number; | |
} | |
/** | |
* Delay the mount or unmount of a component with a prop | |
*/ | |
export function DelayRender({ | |
shouldRender, | |
mountDelay = 0, | |
unmountDelay = 0, | |
[Roact.Children]: children, | |
}: DelayRenderProps) { | |
const [render, setRender] = useState(mountDelay === 0 ? shouldRender : false); | |
useEffect(() => { | |
if (shouldRender) { | |
return setTimeout(() => setRender(true), mountDelay); | |
} else { | |
return setTimeout(() => setRender(false), unmountDelay); | |
} | |
}, [shouldRender]); | |
return <>{render && children}</>; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useComposedRef, useDeferState } from "@rbxts/pretty-roact-hooks"; | |
import Roact from "@rbxts/roact"; | |
import { useEffect, useState } from "@rbxts/roact-hooked"; | |
import { Group } from "./group"; | |
interface RenderInViewProps extends Roact.PropsWithChildren { | |
ref?: (rbx?: Frame) => void; | |
change?: Roact.JsxInstanceChangeEvents<Frame>; | |
event?: Roact.JsxInstanceEvents<Frame>; | |
container?: GuiObject; | |
containerMargin?: Vector2; | |
size?: UDim2 | Roact.Binding<UDim2>; | |
position?: UDim2 | Roact.Binding<UDim2>; | |
anchorPoint?: Vector2 | Roact.Binding<Vector2>; | |
zIndex?: number | Roact.Binding<number>; | |
layoutOrder?: number | Roact.Binding<number>; | |
} | |
/** | |
* Unmount the children if this invisible container frame is | |
* outside of the `container` object's bounds plus the margin | |
*/ | |
export function RenderInView({ | |
ref, | |
change = {}, | |
event = {}, | |
container, | |
containerMargin = Vector2.zero, | |
size, | |
position, | |
anchorPoint, | |
zIndex, | |
layoutOrder, | |
[Roact.Children]: children, | |
}: RenderInViewProps) { | |
const [frame, setFrame] = useState<Frame>(); | |
const [shouldRender, setShouldRender] = useDeferState(false); | |
useEffect(() => { | |
if (!frame || !container) return; | |
// Set shouldRender to 'true' if any part of the frame is inside the container | |
const updateShouldRender = () => { | |
const framePosition = frame.AbsolutePosition; | |
const frameSize = frame.AbsoluteSize; | |
const containerPosition = container.AbsolutePosition.sub(containerMargin.div(2)); | |
const containerSize = container.AbsoluteSize.add(containerMargin); | |
const inFrame = | |
framePosition.X + frameSize.X > containerPosition.X && | |
framePosition.X < containerPosition.X + containerSize.X && | |
framePosition.Y + frameSize.Y > containerPosition.Y && | |
framePosition.Y < containerPosition.Y + containerSize.Y; | |
setShouldRender(inFrame); | |
}; | |
const connections = [ | |
frame.GetPropertyChangedSignal("AbsolutePosition").Connect(updateShouldRender), | |
frame.GetPropertyChangedSignal("AbsoluteSize").Connect(updateShouldRender), | |
container.GetPropertyChangedSignal("AbsolutePosition").Connect(updateShouldRender), | |
container.GetPropertyChangedSignal("AbsoluteSize").Connect(updateShouldRender), | |
]; | |
updateShouldRender(); | |
return () => { | |
for (const connection of connections) { | |
connection.Disconnect(); | |
} | |
}; | |
}, [frame, container, containerMargin]); | |
return ( | |
<Group | |
ref={useComposedRef(setFrame, ref)} | |
change={change} | |
event={event} | |
size={size} | |
position={position} | |
anchorPoint={anchorPoint} | |
zIndex={zIndex} | |
layoutOrder={layoutOrder} | |
> | |
{shouldRender && children} | |
</Group> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Roact from "@rbxts/roact"; | |
import { IS_EDIT } from "shared/utils/constants"; | |
import { Group } from "./group"; | |
interface RootProps extends Roact.PropsWithChildren { | |
displayOrder?: number; | |
} | |
/** | |
* Create a ScreenGui if this is a live game, otherwise render | |
* an invisible container frame | |
* `IS_EDIT = RunService.IsStudio() && !RunService.IsRunning()` | |
*/ | |
export function Root({ displayOrder, [Roact.Children]: children }: RootProps) { | |
return IS_EDIT ? ( | |
<Group zIndex={displayOrder}>{children}</Group> | |
) : ( | |
<screengui ResetOnSpawn={false} DisplayOrder={displayOrder} IgnoreGuiInset ZIndexBehavior="Sibling"> | |
{children} | |
</screengui> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Linear, Spring, useMotor, useProperty } from "@rbxts/pretty-roact-hooks"; | |
import Roact from "@rbxts/roact"; | |
import { useEffect } from "@rbxts/roact-hooked"; | |
import { setInterval } from "@rbxts/set-timeout"; | |
import { Dictionary } from "@rbxts/sift"; | |
import { useRem } from "client/app/hooks"; | |
import { Text, TextProps } from "./text"; | |
interface TextTruncatedProps extends TextProps {} | |
const GRADIENT = new NumberSequence([ | |
new NumberSequenceKeypoint(0, 0), | |
new NumberSequenceKeypoint(0.75, 0), | |
new NumberSequenceKeypoint(1, 1), | |
]); | |
/** | |
* Blurs out the right-edge of a text label and adds automatic | |
* scrolling to view the entire string, like how some music apps | |
* scroll the title of a song if it's too long | |
*/ | |
export function TextTruncated(props: TextTruncatedProps) { | |
const textProps = Dictionary.removeKeys(props, Roact.Children); | |
const rem = useRem(); | |
const [bounds = Vector2.one, size = Vector2.one, change] = useProperty("TextBounds", "AbsoluteSize"); | |
const [offset, setOffset] = useMotor(0); | |
const distance = size.X - bounds.X - 2 * rem; | |
useEffect(() => { | |
setOffset(new Spring(0)); | |
if (bounds.X < size.X + rem) { | |
return; | |
} | |
let toggle = false; | |
return setInterval(() => { | |
toggle = !toggle; | |
if (toggle) { | |
setOffset(new Linear(distance, { velocity: 5 * rem })); | |
} else { | |
setOffset(new Linear(0, { velocity: 5 * rem })); | |
} | |
}, 5); | |
}, [bounds, size, rem]); | |
return ( | |
<Text {...textProps} clipsDescendants change={{ ...props.change, ...change }}> | |
<uigradient Key="truncate-gradient" Transparency={GRADIENT} /> | |
<uipadding Key="truncate-offset" PaddingLeft={offset.map((x) => new UDim(0, math.round(x)))} /> | |
{props[Roact.Children]} | |
</Text> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Roact from "@rbxts/roact"; | |
import { useState } from "@rbxts/roact-hooked"; | |
import { FrameProps } from "./frame"; | |
interface ViewportFrameProps extends FrameProps<ViewportFrame> { | |
camera?: CFrame | Roact.Binding<CFrame>; | |
fieldOfView?: number | Roact.Binding<number>; | |
ambient?: Color3 | Roact.Binding<Color3>; | |
lightColor?: Color3 | Roact.Binding<Color3>; | |
lightDirection?: Vector3 | Roact.Binding<Vector3>; | |
imageColor?: Color3 | Roact.Binding<Color3>; | |
imageTransparency?: number | Roact.Binding<number>; | |
useWorldModel?: boolean; | |
} | |
/** | |
* Make working with viewport frames a bit easier | |
*/ | |
export function ViewportFrame(props: ViewportFrameProps) { | |
const [currentCamera, setCurrentCamera] = useState<Camera>(); | |
return ( | |
<viewportframe | |
Ref={props.ref} | |
CurrentCamera={currentCamera} | |
Ambient={props.ambient} | |
LightColor={props.lightColor} | |
LightDirection={props.lightDirection} | |
ImageColor3={props.imageColor} | |
ImageTransparency={props.imageTransparency} | |
Size={props.size} | |
Position={props.position} | |
AnchorPoint={props.anchorPoint} | |
BackgroundColor3={props.backgroundColor} | |
BackgroundTransparency={props.backgroundTransparency} | |
ClipsDescendants={props.clipsDescendants} | |
Visible={props.visible} | |
ZIndex={props.zIndex} | |
LayoutOrder={props.layoutOrder} | |
BorderSizePixel={0} | |
Event={props.event || {}} | |
Change={props.change || {}} | |
> | |
<camera Ref={setCurrentCamera} CFrame={props.camera} FieldOfView={props.fieldOfView} /> | |
{props.cornerRadius && <uicorner CornerRadius={props.cornerRadius} />} | |
{props.useWorldModel ? <worldmodel>{props[Roact.Children]}</worldmodel> : <>{props[Roact.Children]}</>} | |
</viewportframe> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment