Skip to content

Commit

Permalink
fix: word wrap remove text on line feed
Browse files Browse the repository at this point in the history
feat: string arrays for text
fix: border visible when border size > 0 (default color white)
feat: disable enforce aspectRatio for content
  • Loading branch information
bbohlender committed Feb 27, 2024
1 parent 07dc938 commit 55277bf
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 70 deletions.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ Build performant 3D user interfaces for Three.js using @react-three/fiber and yo

TODO Release

- drag/click threshold
- add shadcn components
- cli for kits
- add apfel components
- Content "measureContent" flag => allow disabling content measuring and scaling
- support for visibility="hidden"
- input
- decrease clipping rect when scrollbar present
- feat: nesting inside non root/container components (e.g. image)
- feat: support more characters for different languages
- fix: always loading normal font
- fix: scrollbar border radius to high (happens with very long panels)
- feat: drag/click threshold
- feat: cli for kits
- feat: add apfel components
- feat: Content "measureContent" flag => allow disabling content measuring and scaling
- feat: support for visibility="hidden"
- feat: input
- fix: decrease clipping rect when scrollbar present

TODO Later

Expand Down
File renamed without changes.
28 changes: 28 additions & 0 deletions docs/getting-started/first-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Image from '@theme/IdealImage';
import { CodesandboxEmbed } from '../CodesandboxEmbed.tsx'

# First Layout

At first, we will create 3 containers. One container is the root node with a size of 2 by 1 three.js untits, expressed by `Root`. The `Root` has a horizontal (row) flex-direction, while the children expressed by `Container` equally fill its width with a margin between them.

<CodesandboxEmbed path="koestlich-first-layout-owgw9d"/>

<Image img={require('@site/static/images/first-layout.png')}/>

```tsx
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import { Root, Container } from "@react-three/uikit";

export default function App() {
return (
<Canvas>
<OrbitControls />
<Root backgroundColor="red" sizeX={2} sizeY={1} flexDirection="row">
<Container flexGrow={1} margin={48} backgroundColor="green" />
<Container flexGrow={1} margin={48} backgroundColor="blue" />
</Root>
</Canvas>
);
}
```
23 changes: 15 additions & 8 deletions docs/getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,21 @@ createRoot(document.getElementById('root')).render(

The tutorials expect some level of familarity with react, threejs, and @react-three/fiber.

1. Build your [First Layout]()
2. Learn about the [Available Components and Their Properties]()
3. Get inspired by our [Examples]()
1. Build your [First Layout](./first-layout.md)
2. Learn about the [Available Components and Their Properties](./components-and-properties.md)
3. Get inspired by our [Examples](./examples.md)
4. Learn more about
- Using [Custom Materials]()
- Using [Custom Fonts]()
- Creating [Responsivene User Interfaces]()
- [Scrolling]()
- [Sizing]()
- Using [Custom Materials](../tutorials/custom-materials.mdx)
- Using [Custom Fonts](../tutorials/fonts.mdx)
- Creating [Responsivene User Interfaces](../tutorials/responsive.mdx)
- [Scrolling](../tutorials/scroll.mdx)
- [Sizing](../tutorials/sizing.mdx
)
5. Learn about [Common Pitfalls]() and how to [Optimize Performance]()

## Migration guides

- from [Koestlich](../migration/from-koestlich.mdx)
- from HTML/CSS
- from Tailwind

12 changes: 11 additions & 1 deletion examples/uikit/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default function App() {
cursor="pointer"
>
{t}
more
</Text>
<Container
onHoverChange={(hover) => (x.value = hover ? 'yellow' : undefined)}
Expand All @@ -70,7 +71,16 @@ export default function App() {
<CustomContainer transformRotateZ={45} height={200} width={4}>
<meshPhongMaterial depthWrite={false} transparent color="green" />
</CustomContainer>
<Content height={200} hover={{ height: 300 }} depthAlign="front" onSizeChange={(w, h) => console.log(w, h)}>
<Content
height={200}
width={200}
hover={{ height: 300 }}
transformScaleZ={0.05}
depthAlign="back"
onSizeChange={(w, h) => console.log(w, h)}
keepAspectRatio={false}
borderRight={100}
>
<Box>
<meshPhongMaterial depthWrite={false} transparent />
</Box>
Expand Down
3 changes: 0 additions & 3 deletions examples/uikit/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
include: ['@react-three/uikit-lucide', '@react-three/uikit'],
},
resolve: {
alias: [
{ find: '@', replacement: path.resolve(__dirname, '../../packages/kits/default') },
Expand Down
72 changes: 47 additions & 25 deletions packages/uikit/src/components/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '../properties/utils.js'
import { alignmentZMap, fitNormalizedContentInside, useRootGroupRef, useSignalEffect } from '../utils.js'
import { Box3, Group, Mesh, Vector3 } from 'three'
import { effect, Signal, signal } from '@preact/signals-core'
import { computed, effect, Signal, signal } from '@preact/signals-core'
import { useApplyHoverProperties } from '../hover.js'
import {
ComponentInternals,
Expand Down Expand Up @@ -55,6 +55,7 @@ export const Content = forwardRef<
children?: ReactNode
zIndexOffset?: ZIndexOffset
backgroundMaterialClass?: MaterialClass
keepAspectRatio?: boolean
} & ContentProperties &
EventHandlers &
LayoutListeners &
Expand Down Expand Up @@ -86,7 +87,7 @@ export const Content = forwardRef<
const innerGroupRef = useRef<Group>(null)
const rootGroupRef = useRootGroupRef()
const orderInfo = useOrderInfo(ElementType.Object, undefined, backgroundOrderInfo)
const aspectRatio = useNormalizedContent(
const size = useNormalizedContent(
collection,
innerGroupRef,
rootGroupRef,
Expand All @@ -99,26 +100,47 @@ export const Content = forwardRef<
useApplyProperties(collection, properties)
useApplyResponsiveProperties(collection, properties)
const hoverHandlers = useApplyHoverProperties(collection, properties)
writeCollection(collection, 'aspectRatio', aspectRatio)
const aspectRatio = useMemo(
() =>
computed(() => {
const [x, y] = size.value
return x / y
}),
[size],
)
if ((properties.keepAspectRatio ?? true) === true) {
writeCollection(collection, 'aspectRatio', aspectRatio)
}
finalizeCollection(collection)

const outerGroupRef = useRef<Group>(null)
useEffect(
() =>
effect(() => {
const [offsetX, offsetY, scale] = fitNormalizedContentInside(
node.size,
node.paddingInset,
node.borderInset,
node.pixelSize,
aspectRatio.value ?? 1,
)
const [width, height] = node.size.value
const [pTop, pRight, pBottom, pLeft] = node.paddingInset.value
const [bTop, bRight, bBottom, bLeft] = node.borderInset.value
const topInset = pTop + bTop
const rightInset = pRight + bRight
const bottomInset = pBottom + bBottom
const leftInset = pLeft + bLeft

const innerWidth = width - leftInset - rightInset
const innerHeight = height - topInset - bottomInset

const { pixelSize } = node

const { current } = outerGroupRef
current?.position.set(offsetX, offsetY, 0)
current?.scale.setScalar(scale)
current?.position.set((leftInset - rightInset) * 0.5 * pixelSize, (bottomInset - topInset) * 0.5 * pixelSize, 0)
const [, y, z] = size.value
current?.scale.set(
innerWidth * pixelSize,
innerHeight * pixelSize,
properties.keepAspectRatio ? (innerHeight * pixelSize * z) / y : z,
)
current?.updateMatrix()
}),
[node, aspectRatio],
[node, properties.keepAspectRatio, size],
)

const interactionPanel = useInteractionPanel(node.size, node, backgroundOrderInfo, rootGroupRef)
Expand Down Expand Up @@ -151,8 +173,8 @@ function useNormalizedContent(
rootCameraDistance: CameraDistanceRef,
parentClippingRect: Signal<ClippingRect | undefined> | undefined,
orderInfo: OrderInfo,
): Signal<number | undefined> {
const aspectRatio = useMemo(() => signal<number | undefined>(undefined), [])
): Signal<Vector3> {
const sizeSignal = useMemo(() => signal<Vector3>(new Vector3(1, 1, 1)), [])
const clippingPlanes = useGlobalClippingPlanes(parentClippingRect, rootGroupRef)
const getPropertySignal = useGetBatchedProperties<DepthAlignProperties>(collection, propertyKeys)
useEffect(() => {
Expand All @@ -171,24 +193,24 @@ function useNormalizedContent(
const parent = group.parent
parent?.remove(group)
box3Helper.setFromObject(group)
const vector = new Vector3()
box3Helper.getSize(vector)
const scale = 1 / vector.y
const depth = vector.z
aspectRatio.value = vector.x / vector.y
group.scale.set(1, 1, 1).multiplyScalar(scale)
const size = new Vector3()
const center = new Vector3()
box3Helper.getSize(size)
const depth = size.z
sizeSignal.value = size
group.scale.set(1, 1, 1).divide(size)
if (parent != null) {
parent.add(group)
}
box3Helper.getCenter(vector)
box3Helper.getCenter(center)
return effect(() => {
group.position.copy(vector).negate()
group.position.copy(center).negate()
group.position.z -= alignmentZMap[getPropertySignal.value('depthAlign') ?? 'back'] * depth
group.position.multiplyScalar(scale)
group.position.divide(size)
group.updateMatrix()
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getPropertySignal, rootCameraDistance, clippingPlanes, rootGroupRef])

return aspectRatio
return sizeSignal
}
2 changes: 1 addition & 1 deletion packages/uikit/src/components/text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export type TextProperties = WithConditionals<
export const Text = forwardRef<
ComponentInternals,
{
children: string | Signal<string>
children: string | Signal<string> | Array<string | Signal<string>>
backgroundMaterialClass?: MaterialClass
zIndexOffset?: ZIndexOffset
} & TextProperties &
Expand Down
8 changes: 4 additions & 4 deletions packages/uikit/src/panel/panel-material.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,12 @@ export function compilePanelMaterial(parameters: WebGLProgramParametersWithUnifo
if(backgroundColor.r < 0.0 && backgroundOpacity >= 0.0) {
backgroundColor = vec3(1.0);
}
if(backgroundOpacity < 0.0 && backgroundColor.r >= 0.0) {
backgroundOpacity = 1.0;
if(backgroundOpacity < 0.0) {
backgroundOpacity = backgroundColor.r >= 0.0 ? 1.0 : 0.0;
}
if(backgroundOpacity <= 0.0) {
discard;
if(backgroundOpacity < 0.0) {
backgroundOpacity = 0.0;
}
diffuseColor.rgb = mix(borderColor, diffuseColor.rgb * backgroundColor, transition);
Expand Down
15 changes: 8 additions & 7 deletions packages/uikit/src/text/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export type InstancedTextProperties = TextAlignProperties &

export function useInstancedText(
collection: ManagerCollection,
text: string | Signal<string>,
text: string | Signal<string> | Array<string | Signal<string>>,
matrix: Signal<Matrix4>,
node: FlexNode,
isHidden: Signal<boolean> | undefined,
Expand All @@ -123,7 +123,8 @@ export function useInstancedText(
) {
const getGroup = useContext(InstancedGlyphContext)
const fontSignal = useFont(collection)
const textSignal = useMemo(() => signal<string | Signal<string> | undefined>(undefined), [])
// eslint-disable-next-line react-hooks/exhaustive-deps
const textSignal = useMemo(() => signal<string | Signal<string> | Array<string | Signal<string>>>(text), [])
textSignal.value = text
const propertiesRef = useRef<GlyphLayoutProperties | undefined>(undefined)

Expand Down Expand Up @@ -229,7 +230,7 @@ function getWeightNumber(value: string): number {
export function useMeasureFunc(
collection: ManagerCollection,
fontSignal: Signal<Font | undefined>,
textSignal: Signal<string | Signal<string> | undefined>,
textSignal: Signal<string | Signal<string> | Array<Signal<string> | string>>,
propertiesRef: MutableRefObject<GlyphLayoutProperties | undefined>,
) {
const getGlyphProperties = useGetBatchedProperties<GlyphLayoutProperties>(collection, glyphPropertyKeys)
Expand All @@ -240,10 +241,10 @@ export function useMeasureFunc(
if (font == null) {
return undefined
}
const text = readReactive(textSignal.value)
if (text == null) {
return undefined
}
const textSignalValue = textSignal.value
const text = Array.isArray(textSignalValue)
? textSignalValue.map((t) => readReactive(t)).join('')
: readReactive(textSignalValue)
const letterSpacing = getGlyphProperties.value('letterSpacing') ?? 0
const lineHeight = getGlyphProperties.value('lineHeight') ?? 1.2
const fontSize = getGlyphProperties.value('fontSize') ?? 16
Expand Down
16 changes: 6 additions & 10 deletions packages/uikit/src/text/render/instanced-glyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,14 @@ export class InstancedGlyph {
instanceRGBA.needsUpdate = true
}

updateTransformation(x: number, y: number, fontSize: number): void {
if (this.x === x && this.y === y && this.fontSize === fontSize) {
updateGlyphAndTransformation(glyphInfo: GlyphInfo, x: number, y: number, fontSize: number): void {
if (this.glyphInfo === this.glyphInfo && this.x === x && this.y === y && this.fontSize === fontSize) {
return
}
if (this.glyphInfo != glyphInfo) {
this.glyphInfo = glyphInfo
this.writeUV()
}
this.x = x
this.y = y
this.fontSize = fontSize
Expand All @@ -113,14 +117,6 @@ export class InstancedGlyph {
this.writeUpdatedMatrix()
}

updateGlyphInfo(glyphInfo: GlyphInfo): void {
if (this.glyphInfo === glyphInfo) {
return
}
this.glyphInfo = glyphInfo
this.writeUV()
}

private writeUV(): void {
if (this.index == null || this.glyphInfo == null) {
return
Expand Down
4 changes: 2 additions & 2 deletions packages/uikit/src/text/render/instanced-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,12 @@ export class InstancedText {
this.parentClippingRect?.peek(),
)
}
glyph.updateTransformation(
glyph.updateGlyphAndTransformation(
glyphInfo,
pixelSize * (x + getGlyphOffsetX(font, fontSize, glyphInfo, prevGlyphId)),
-pixelSize * (y + getGlyphOffsetY(fontSize, lineHeight, glyphInfo)),
pixelSize * fontSize,
)
glyph.updateGlyphInfo(glyphInfo)
glyph.show()

++glyphIndex
Expand Down
5 changes: 4 additions & 1 deletion packages/uikit/src/text/wrapper/word-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export const WordWrapper: GlyphWrapper = ({ text, fontSize, font, letterSpacing
continue
}

if (textIndex < text.length && text[textIndex] != ' ') {
const newChar = text[textIndex]

if (newChar != ' ' && newChar != '\n' && textIndex < text.length) {
//no reason to advance the save point
continue
}

Expand Down

0 comments on commit 55277bf

Please sign in to comment.