@@ -50,30 +50,7 @@ class p5 {
5050 constructor ( sketch , node ) {
5151 // Apply addon defined decorations
5252 if ( p5 . decorations . size > 0 ) {
53- for ( const [ patternArray , decoration ] of p5 . decorations ) {
54- for ( const member in p5 . prototype ) {
55- // Member must be a function
56- if ( typeof p5 . prototype [ member ] !== 'function' ) continue ;
57-
58- if ( ! patternArray . some ( pattern => {
59- if ( typeof pattern === 'string' ) {
60- return pattern === member ;
61- } else if ( pattern instanceof RegExp ) {
62- return pattern . test ( member ) ;
63- }
64- } ) ) continue ;
65-
66- p5 . prototype [ member ] = decoration ( p5 . prototype [ member ] , {
67- kind : 'method' ,
68- name : member ,
69- access : { } ,
70- static : false ,
71- private : false ,
72- addInitializer ( initializer ) { }
73- } ) ;
74- }
75- }
76-
53+ decorateClass ( p5 , p5 . decorations ) ;
7754 p5 . decorations . clear ( ) ;
7855 }
7956
@@ -165,11 +142,11 @@ class p5 {
165142 }
166143
167144 get pixels ( ) {
168- return this . _renderer . pixels ;
145+ return this . _renderer ? .pixels ;
169146 }
170147
171148 get drawingContext ( ) {
172- return this . _renderer . drawingContext ;
149+ return this . _renderer ? .drawingContext ;
173150 }
174151
175152 static _registeredAddons = new Set ( ) ;
@@ -193,10 +170,20 @@ class p5 {
193170 }
194171
195172 static decorations = new Map ( ) ;
196- static decorateHelper ( pattern , decoration ) {
197- let patternArray = pattern ;
198- if ( ! Array . isArray ( pattern ) ) patternArray = [ pattern ] ;
199- p5 . decorations . set ( patternArray , decoration ) ;
173+ static registerDecorator ( pattern , decoration ) {
174+ if ( typeof pattern === 'string' ) {
175+ const patternStr = pattern ;
176+ pattern = ( { path } ) => patternStr === path ;
177+ } else if (
178+ Array . isArray ( pattern ) &&
179+ pattern . every ( value => typeof value === 'string' )
180+ ) {
181+ const patternArray = pattern ;
182+ pattern = ( { path } ) => patternArray . includes ( path ) ;
183+ } else if ( typeof pattern !== 'function' ) {
184+ throw new Error ( 'Decorator matching pattern must be a function, a string, or an array of strings' ) ;
185+ }
186+ p5 . decorations . set ( pattern , decoration ) ;
200187 }
201188
202189 #customActions = { } ;
@@ -437,6 +424,11 @@ class p5 {
437424 }
438425}
439426
427+ // Attach constants to p5 prototype
428+ for ( const k in constants ) {
429+ p5 . prototype [ k ] = constants [ k ] ;
430+ }
431+
440432// Global helper function for binding properties to window in global mode
441433function createBindGlobal ( instance ) {
442434 return function bindGlobal ( property ) {
@@ -527,9 +519,107 @@ function createBindGlobal(instance) {
527519 } ;
528520}
529521
530- // Attach constants to p5 prototype
531- for ( const k in constants ) {
532- p5 . prototype [ k ] = constants [ k ] ;
522+ // Generic function to decorate classes
523+ function decorateClass ( Target , decorations , path ) {
524+ path ??= Target . name ;
525+ // Static properties
526+ for ( const key in Target ) {
527+ if ( ! key . startsWith ( '_' ) ) {
528+ for ( const [ pattern , decorator ] of decorations ) {
529+ if ( pattern ( { path : `${ path } .${ key } ` } ) ) {
530+ // Check if method or accessor
531+ if ( typeof Target [ key ] === 'function' ) {
532+ const result = decorator ( Target [ key ] , {
533+ kind : 'method' ,
534+ name : key ,
535+ static : true
536+ } ) ;
537+ if ( result ) {
538+ Object . defineProperty ( Target , key , {
539+ enumerable : true ,
540+ writable : true ,
541+ value : result
542+ } ) ;
543+ }
544+ } else {
545+ const result = decorator ( undefined , {
546+ kind : 'field' ,
547+ name : key ,
548+ static : true
549+ } ) ;
550+ if ( result && typeof result === 'function' ) {
551+ Target [ key ] = result ( Target [ key ] ) ;
552+ }
553+ }
554+ }
555+ }
556+
557+ if ( typeof Target [ key ] === 'function' && Target [ key ] . prototype ) {
558+ decorateClass ( Target [ key ] , decorations , `${ path } .${ key } ` ) ;
559+ }
560+ }
561+ }
562+
563+ // Member properties
564+ for ( const member of Object . getOwnPropertyNames ( Target . prototype ) ) {
565+ if ( member !== 'constructor' && ! member . startsWith ( '_' ) ) {
566+ for ( const [ pattern , decorator ] of decorations ) {
567+ if ( pattern ( { path : `${ path } .prototype.${ member } ` } ) ) {
568+ // Check if method or accessor
569+ if ( typeof Target . prototype [ member ] === 'function' ) {
570+ const result = decorator ( Target . prototype [ member ] , {
571+ kind : 'method' ,
572+ name : member ,
573+ static : false
574+ } ) ;
575+ if ( result ) {
576+ Object . defineProperty ( Target . prototype , member , {
577+ enumerable : true ,
578+ writable : true ,
579+ value : result
580+ } ) ;
581+ }
582+ } else {
583+ const descriptor = Object . getOwnPropertyDescriptor (
584+ Target . prototype ,
585+ member
586+ ) ;
587+ if ( descriptor . hasOwnProperty ( 'value' ) ) {
588+ const result = decorator ( undefined , {
589+ kind : 'field' ,
590+ name : member ,
591+ static : false
592+ } ) ;
593+ Object . defineProperty ( Target . prototype , member , {
594+ enumerable : true ,
595+ writable : true ,
596+ value : result && typeof result === 'function' ?
597+ result ( Target . prototype [ member ] ) :
598+ Target . prototype [ member ]
599+ } ) ;
600+ } else {
601+ const { get, set } = descriptor ;
602+ const getterResult = decorator ( get , {
603+ kind : 'getter' ,
604+ name : member ,
605+ static : false
606+ } ) ;
607+ const setterResult = decorator ( set , {
608+ kind : 'setter' ,
609+ name : member ,
610+ static : false
611+ } ) ;
612+ Object . defineProperty ( Target . prototype , member , {
613+ enumerable : true ,
614+ get : getterResult ?? get ,
615+ set : setterResult ?? set
616+ } ) ;
617+ }
618+ }
619+ }
620+ }
621+ }
622+ }
533623}
534624
535625import transform from './transform' ;
0 commit comments