11'use client' ;
22
33import { BackButton } from '@/components/back-button' ;
4- import { cleanUpLevaParams } from '@/helpers/clean-up-leva-params' ;
5- import { toHsla } from '@/helpers/to-hsla' ;
6- import { usePresetHighlight } from '@/helpers/use-preset-highlight' ;
7- import { setParamsSafe , useResetLevaParams } from '@/helpers/use-reset-leva-params' ;
4+ import { createNumberedObject } from '@/helpers/create-numbered-object' ;
5+ import { getValuesSortedByKey } from '@/helpers/get-values-sorted-by-key' ;
86import { type ShaderFit , ShaderFitOptions , tartanMeta } from '@paper-design/shaders' ;
97import { Tartan , tartanPresets } from '@paper-design/shaders-react' ;
10- import { button , folder , useControls } from 'leva' ;
8+ import { button , folder , levaStore , useControls } from 'leva' ;
9+ import type { Schema } from 'leva/dist/declarations/src/types' ;
1110import Link from 'next/link' ;
11+ import { useEffect } from 'react' ;
1212
13- /**
14- * You can copy/paste this example to use Tartan in your app
15- */
16- const TartanExample = ( ) => {
17- return < Tartan style = { { position : 'fixed' , width : '100%' , height : '100%' } } /> ;
18- } ;
13+ const defaults = tartanPresets [ 0 ] . params ;
1914
2015/**
2116 * This example has controls added so you can play with settings in the example app
2217 */
23-
24- const defaults = tartanPresets [ 0 ] . params ;
25-
2618const TartanWithControls = ( ) => {
27- const [ { count : stripeCount } , setStripeCount ] = useControls ( ( ) => ( {
19+ // Presets
20+ useControls ( {
21+ Presets : folder (
22+ Object . fromEntries (
23+ tartanPresets . map ( ( { name, params : { worldWidth, worldHeight, ...preset } } ) => [
24+ name ,
25+ button ( ( ) => {
26+ const { stripeColors, stripeWidths, ...presetParams } = preset ;
27+ setParams ( presetParams ) ;
28+ setColors (
29+ createNumberedObject ( 'color' , tartanMeta . maxStripeCount , ( i ) => stripeColors [ i % stripeColors . length ] )
30+ ) ;
31+ setWidths (
32+ createNumberedObject ( 'width' , tartanMeta . maxStripeCount , ( i ) => stripeWidths [ i % stripeWidths . length ] )
33+ ) ;
34+ } ) ,
35+ ] )
36+ ) ,
37+ {
38+ order : - 1 ,
39+ collapsed : false ,
40+ }
41+ ) ,
42+ } ) ;
43+
44+ // Scalar parameters
45+ const [ params , setParams ] = useControls ( ( ) => ( {
46+ Parameters : folder (
47+ {
48+ weaveSize : {
49+ value : defaults . weaveSize ,
50+ min : 1.0 ,
51+ max : 10.0 ,
52+ step : 0.25 ,
53+ order : 0 ,
54+ } ,
55+ weaveStrength : {
56+ value : defaults . weaveStrength ,
57+ min : 0.0 ,
58+ max : 1.0 ,
59+ step : 0.05 ,
60+ order : 1 ,
61+ } ,
62+ } ,
63+ {
64+ order : 0 ,
65+ collapsed : false ,
66+ }
67+ ) ,
2868 Stripes : folder (
2969 {
30- count : {
31- value : defaults . stripeColors . length ,
70+ stripeCount : {
71+ value : defaults . stripeCount ,
3272 min : 2 ,
3373 max : tartanMeta . maxStripeCount ,
3474 step : 1 ,
3575 order : 0 ,
76+ label : 'count' ,
3677 } ,
3778 } ,
38- { order : 1 }
79+ {
80+ order : 1 ,
81+ collapsed : false ,
82+ }
83+ ) ,
84+ Transform : folder (
85+ {
86+ scale : { value : defaults . scale , min : 0.01 , max : 4 , order : 400 } ,
87+ rotation : { value : defaults . rotation , min : 0 , max : 360 , order : 401 } ,
88+ offsetX : { value : defaults . offsetX , min : - 1 , max : 1 , order : 402 } ,
89+ offsetY : { value : defaults . offsetY , min : - 1 , max : 1 , order : 403 } ,
90+ } ,
91+ {
92+ order : 2 ,
93+ collapsed : false ,
94+ }
95+ ) ,
96+ Fit : folder (
97+ {
98+ fit : { value : defaults . fit , options : Object . keys ( ShaderFitOptions ) as ShaderFit [ ] , order : 404 } ,
99+ worldWidth : { value : 1000 , min : 0 , max : 5120 , order : 405 } ,
100+ worldHeight : { value : 500 , min : 0 , max : 5120 , order : 406 } ,
101+ originX : { value : defaults . originX , min : 0 , max : 1 , order : 407 } ,
102+ originY : { value : defaults . originY , min : 0 , max : 1 , order : 408 } ,
103+ } ,
104+ {
105+ order : 3 ,
106+ collapsed : true ,
107+ }
39108 ) ,
40109 } ) ) ;
41110
42- const [ colors , setColors ] = useControls ( ( ) => {
43- const stripe : Record < string , { value : string ; [ key : string ] : unknown } > = { } ;
44-
45- for ( let i = 0 ; i < stripeCount ; i ++ ) {
46- stripe [ `color${ i + 1 } ` ] = {
47- value : defaults . stripeColors [ i ] ? toHsla ( defaults . stripeColors [ i ] ) : `hsla(${ ( 40 * i ) % 360 } , 60%, 50%, 1)` ,
48- order : 1 + i * 2 ,
49- } ;
50- }
51-
52- return {
53- Stripes : folder ( stripe ) ,
54- } ;
55- } , [ stripeCount ] ) ;
56-
57- const [ widths , setWidths ] = useControls ( ( ) => {
58- const stripe : Record < string , { value : number ; [ key : string ] : unknown } > = { } ;
59-
60- for ( let i = 0 ; i < stripeCount ; i ++ ) {
61- stripe [ `width${ i + 1 } ` ] = {
62- value : defaults . stripeWidths [ i ] ,
63- min : 1 ,
64- max : 400 ,
65- step : 1 ,
66- order : 1 + i * 2 + 1 ,
67- } ;
68- }
69-
70- return {
71- Stripes : folder ( stripe ) ,
72- } ;
73- } , [ stripeCount ] ) ;
111+ // Stripe colors
112+ const [ colors , setColors ] = useControls (
113+ ( ) => ( {
114+ Stripes : folder ( {
115+ ...createNumberedObject (
116+ 'color' ,
117+ tartanMeta . maxStripeCount ,
118+ ( i ) =>
119+ ( {
120+ label : `color${ i + 1 } ` ,
121+ order : i * 2 + 1 ,
122+ render : ( ) => params . stripeCount > i ,
123+ value : defaults . stripeColors [ i % defaults . stripeColors . length ] ,
124+ } ) satisfies Schema [ string ]
125+ ) ,
126+ } ) ,
127+ } ) ,
128+ [ params . stripeCount ]
129+ ) ;
74130
75- const [ params , setParams ] = useControls ( ( ) => {
76- return {
77- Parameters : folder (
78- {
79- weaveSize : {
80- value : defaults . weaveSize ,
81- min : 1.0 ,
82- max : 10.0 ,
83- step : 0.25 ,
84- order : 0 ,
85- } ,
86- weaveStrength : {
87- value : defaults . weaveStrength ,
88- min : 0.0 ,
89- max : 1.0 ,
90- step : 0.05 ,
91- order : 1 ,
92- } ,
93- } ,
94- {
95- order : 0 ,
96- collapsed : false ,
97- }
98- ) ,
99- Transform : folder (
100- {
101- scale : { value : defaults . scale , min : 0.01 , max : 4 , order : 400 } ,
102- rotation : { value : defaults . rotation , min : 0 , max : 360 , order : 401 } ,
103- offsetX : { value : defaults . offsetX , min : - 1 , max : 1 , order : 402 } ,
104- offsetY : { value : defaults . offsetY , min : - 1 , max : 1 , order : 403 } ,
105- } ,
106- {
107- order : 2 ,
108- collapsed : false ,
109- }
110- ) ,
111- Fit : folder (
112- {
113- fit : { value : defaults . fit , options : Object . keys ( ShaderFitOptions ) as ShaderFit [ ] , order : 404 } ,
114- worldWidth : { value : 1000 , min : 0 , max : 5120 , order : 405 } ,
115- worldHeight : { value : 500 , min : 0 , max : 5120 , order : 406 } ,
116- originX : { value : defaults . originX , min : 0 , max : 1 , order : 407 } ,
117- originY : { value : defaults . originY , min : 0 , max : 1 , order : 408 } ,
118- } ,
119- {
120- order : 3 ,
121- collapsed : true ,
122- }
123- ) ,
124- } ;
125- } ) ;
131+ // Stripe widths
132+ const [ widths , setWidths ] = useControls (
133+ ( ) => ( {
134+ Stripes : folder ( {
135+ ...createNumberedObject (
136+ 'width' ,
137+ tartanMeta . maxStripeCount ,
138+ ( i ) =>
139+ ( {
140+ label : `width${ i + 1 } ` ,
141+ max : 100 ,
142+ min : 1 ,
143+ order : i * 2 + 2 ,
144+ render : ( ) => params . stripeCount > i ,
145+ step : 1 ,
146+ value : defaults . stripeWidths [ i % defaults . stripeWidths . length ] ,
147+ } ) satisfies Schema [ string ]
148+ ) ,
149+ } ) ,
150+ } ) ,
151+ [ params . stripeCount ]
152+ ) ;
126153
127- useControls ( ( ) => {
128- const presets = Object . fromEntries (
129- tartanPresets . map ( ( { name, params : { worldWidth, worldHeight, ...preset } } ) => [
130- name ,
131- button ( ( ) => {
132- const { stripeColors, stripeWidths, ...presetParams } = preset ;
133- setStripeCount ( { count : stripeColors . length } ) ;
134- setColors (
135- Object . fromEntries ( stripeColors . map ( ( value , i ) => [ `color${ i + 1 } ` , toHsla ( value ) ] ) ) as unknown as Record <
136- string ,
137- { value : string ; [ key : string ] : unknown }
138- >
139- ) ;
140- setWidths ( Object . fromEntries ( stripeWidths . map ( ( value , i ) => [ `width${ i + 1 } ` , value ] ) ) ) ;
141- setParamsSafe ( params , setParams , presetParams ) ;
142- } ) ,
143- ] )
144- ) ;
145- return {
146- Presets : folder ( presets , { order : - 1 } ) ,
154+ // Clear the Leva store when the component unmounts.
155+ useEffect ( ( ) => {
156+ return ( ) => {
157+ levaStore . dispose ( ) ;
147158 } ;
148- } ) ;
149-
150- // Reset to defaults on mount, so that Leva doesn't show values from other
151- // shaders when navigating (if two shaders have a color1 param for example)
152- useResetLevaParams ( params , setParams , defaults ) ;
153- usePresetHighlight ( tartanPresets , params ) ;
154- cleanUpLevaParams ( params ) ;
159+ } , [ ] ) ;
155160
156161 return (
157162 < >
@@ -160,8 +165,8 @@ const TartanWithControls = () => {
160165 </ Link >
161166 < Tartan
162167 { ...params }
163- stripeColors = { Object . values ( colors ) as unknown as Array < string > }
164- stripeWidths = { [ ... Object . values ( widths ) , ... Array ( 9 - stripeCount ) . fill ( 0 ) ] }
168+ stripeColors = { getValuesSortedByKey ( colors ) }
169+ stripeWidths = { getValuesSortedByKey ( widths ) }
165170 className = "fixed size-full"
166171 />
167172 </ >
0 commit comments