diff --git a/README.md b/README.md
index f2903c0b..6d116bfc 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/docs/getting-started/basic-elements.md b/docs/getting-started/components-and-properties.md
similarity index 100%
rename from docs/getting-started/basic-elements.md
rename to docs/getting-started/components-and-properties.md
diff --git a/docs/getting-started/first-layout.md b/docs/getting-started/first-layout.md
index e69de29b..f17662a5 100644
--- a/docs/getting-started/first-layout.md
+++ b/docs/getting-started/first-layout.md
@@ -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.
+
+
+
+
+
+```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 (
+
+ );
+}
+```
diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md
index ec8f5ffa..bfd44ae2 100644
--- a/docs/getting-started/introduction.md
+++ b/docs/getting-started/introduction.md
@@ -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
+
diff --git a/examples/uikit/src/App.tsx b/examples/uikit/src/App.tsx
index cb62ee28..80313eb5 100644
--- a/examples/uikit/src/App.tsx
+++ b/examples/uikit/src/App.tsx
@@ -56,6 +56,7 @@ export default function App() {
cursor="pointer"
>
{t}
+ more
(x.value = hover ? 'yellow' : undefined)}
@@ -70,7 +71,16 @@ export default function App() {
- console.log(w, h)}>
+ console.log(w, h)}
+ keepAspectRatio={false}
+ borderRight={100}
+ >
diff --git a/examples/uikit/vite.config.ts b/examples/uikit/vite.config.ts
index 5eb10be6..9a823cca 100644
--- a/examples/uikit/vite.config.ts
+++ b/examples/uikit/vite.config.ts
@@ -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') },
diff --git a/packages/uikit/src/components/content.tsx b/packages/uikit/src/components/content.tsx
index 98572b53..0f669ea0 100644
--- a/packages/uikit/src/components/content.tsx
+++ b/packages/uikit/src/components/content.tsx
@@ -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,
@@ -55,6 +55,7 @@ export const Content = forwardRef<
children?: ReactNode
zIndexOffset?: ZIndexOffset
backgroundMaterialClass?: MaterialClass
+ keepAspectRatio?: boolean
} & ContentProperties &
EventHandlers &
LayoutListeners &
@@ -86,7 +87,7 @@ export const Content = forwardRef<
const innerGroupRef = useRef(null)
const rootGroupRef = useRootGroupRef()
const orderInfo = useOrderInfo(ElementType.Object, undefined, backgroundOrderInfo)
- const aspectRatio = useNormalizedContent(
+ const size = useNormalizedContent(
collection,
innerGroupRef,
rootGroupRef,
@@ -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(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)
@@ -151,8 +173,8 @@ function useNormalizedContent(
rootCameraDistance: CameraDistanceRef,
parentClippingRect: Signal | undefined,
orderInfo: OrderInfo,
-): Signal {
- const aspectRatio = useMemo(() => signal(undefined), [])
+): Signal {
+ const sizeSignal = useMemo(() => signal(new Vector3(1, 1, 1)), [])
const clippingPlanes = useGlobalClippingPlanes(parentClippingRect, rootGroupRef)
const getPropertySignal = useGetBatchedProperties(collection, propertyKeys)
useEffect(() => {
@@ -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
}
diff --git a/packages/uikit/src/components/text.tsx b/packages/uikit/src/components/text.tsx
index f4065520..d07fa922 100644
--- a/packages/uikit/src/components/text.tsx
+++ b/packages/uikit/src/components/text.tsx
@@ -42,7 +42,7 @@ export type TextProperties = WithConditionals<
export const Text = forwardRef<
ComponentInternals,
{
- children: string | Signal
+ children: string | Signal | Array>
backgroundMaterialClass?: MaterialClass
zIndexOffset?: ZIndexOffset
} & TextProperties &
diff --git a/packages/uikit/src/panel/panel-material.ts b/packages/uikit/src/panel/panel-material.ts
index fbbbdb38..fccb7f1d 100644
--- a/packages/uikit/src/panel/panel-material.ts
+++ b/packages/uikit/src/panel/panel-material.ts
@@ -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);
diff --git a/packages/uikit/src/text/react.tsx b/packages/uikit/src/text/react.tsx
index 2e553a4e..21eb57ee 100644
--- a/packages/uikit/src/text/react.tsx
+++ b/packages/uikit/src/text/react.tsx
@@ -114,7 +114,7 @@ export type InstancedTextProperties = TextAlignProperties &
export function useInstancedText(
collection: ManagerCollection,
- text: string | Signal,
+ text: string | Signal | Array>,
matrix: Signal,
node: FlexNode,
isHidden: Signal | undefined,
@@ -123,7 +123,8 @@ export function useInstancedText(
) {
const getGroup = useContext(InstancedGlyphContext)
const fontSignal = useFont(collection)
- const textSignal = useMemo(() => signal | undefined>(undefined), [])
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const textSignal = useMemo(() => signal | Array>>(text), [])
textSignal.value = text
const propertiesRef = useRef(undefined)
@@ -229,7 +230,7 @@ function getWeightNumber(value: string): number {
export function useMeasureFunc(
collection: ManagerCollection,
fontSignal: Signal,
- textSignal: Signal | undefined>,
+ textSignal: Signal | Array | string>>,
propertiesRef: MutableRefObject,
) {
const getGlyphProperties = useGetBatchedProperties(collection, glyphPropertyKeys)
@@ -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
diff --git a/packages/uikit/src/text/render/instanced-glyph.ts b/packages/uikit/src/text/render/instanced-glyph.ts
index bd1cb24a..3566316c 100644
--- a/packages/uikit/src/text/render/instanced-glyph.ts
+++ b/packages/uikit/src/text/render/instanced-glyph.ts
@@ -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
@@ -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
diff --git a/packages/uikit/src/text/render/instanced-text.ts b/packages/uikit/src/text/render/instanced-text.ts
index e79ae36a..41acb083 100644
--- a/packages/uikit/src/text/render/instanced-text.ts
+++ b/packages/uikit/src/text/render/instanced-text.ts
@@ -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
diff --git a/packages/uikit/src/text/wrapper/word-wrapper.ts b/packages/uikit/src/text/wrapper/word-wrapper.ts
index 255d585b..1d428989 100644
--- a/packages/uikit/src/text/wrapper/word-wrapper.ts
+++ b/packages/uikit/src/text/wrapper/word-wrapper.ts
@@ -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
}