@@ -2,7 +2,7 @@ import { createRoot } from 'react-dom/client';
22import styles from './styles.module.css' ;
33import React , { createRef } from 'react' ;
44import { renderImage } from "./Render" ;
5- import { ImageContainer , OnClickHandler } from './ImageContainer' ;
5+ import { ImageContainer , OnClickHandler , OnWheelHandler , OnMouseOverHandler , ImageContainerState , OnKeyHandler , setKeyPressed , listenerState } from './ImageContainer' ;
66import { ToneMapControls } from './ToneMapControls' ;
77import { MethodList } from './MethodList' ;
88import { Tools } from './Tools' ;
@@ -11,9 +11,36 @@ import { ToneMapSettings, ZoomLevel } from './flipviewer';
1111
1212const UPDATE_INTERVAL_MS = 100 ;
1313
14+ // Registry to update flipbooks
15+ export type BookRef = React . RefObject < FlipBook > ;
16+ const registry = new Map < string , Set < BookRef > > ( ) ;
17+
18+ export function getBooks ( id : string ) : BookRef [ ] {
19+ return Array . from ( registry . get ( id ) ?? new Set ( ) ) ;
20+ }
21+
22+ export function registerBook ( id : string , ref : BookRef ) {
23+ if ( ! id ) return ;
24+ const set = registry . get ( id ) ?? new Set < BookRef > ( ) ;
25+ set . add ( ref ) ;
26+ registry . set ( id , set ) ;
27+ }
28+
29+ export function unregisterBook ( id : string , ref : BookRef ) {
30+ const set = registry . get ( id ) ;
31+ if ( ! set ) return ;
32+ set . delete ( ref ) ;
33+ if ( set . size === 0 ) registry . delete ( id ) ;
34+ }
35+
36+ // Keep track of pressed keys
37+ // Idea is to only fire events if state of key changes
38+ const keysPressed = new Set < string > ( ) ;
39+
1440export class ToneMappingImage {
1541 currentTMO : string ;
1642 dirty : boolean ;
43+ isPixelUpdate : boolean ;
1744 canvas : HTMLCanvasElement ;
1845 pixels : Float32Array | ImageData ;
1946
@@ -25,26 +52,52 @@ export class ToneMappingImage {
2552
2653 let hdrImg = this ;
2754 setInterval ( function ( ) {
28- if ( ! hdrImg . dirty ) return ;
29- hdrImg . dirty = false ;
55+ if ( ! hdrImg . dirty || hdrImg . isPixelUpdate )
56+ return ;
3057 renderImage ( hdrImg . canvas , hdrImg . pixels , hdrImg . currentTMO ) ;
58+ hdrImg . dirty = false ;
3159 onAfterRender ( ) ;
3260 } , UPDATE_INTERVAL_MS )
3361 }
3462 apply ( tmo : string ) {
3563 this . currentTMO = tmo ;
3664 this . dirty = true ;
3765 }
66+ setPixels ( p : Float32Array | ImageData ) {
67+ this . isPixelUpdate = true ;
68+ this . pixels = p ;
69+ this . dirty = false ;
70+ renderImage ( this . canvas , this . pixels , this . currentTMO ) ;
71+ this . isPixelUpdate = false ;
72+ }
3873}
3974
75+
4076type SelectUpdateFn = ( groupName : string , newIdx : number ) => void ;
4177var selectUpdateListeners : SelectUpdateFn [ ] = [ ] ;
4278
79+
80+ type TMOUpdateFn = ( groupName : string , newTMOSettings : ToneMapSettings ) => void ;
81+ var tmoUpdateListeners : TMOUpdateFn [ ] = [ ] ;
82+
83+ type imageConStateUpdateFn = ( groupName : string , newImgConState : ImageContainerState ) => void ;
84+ var imgConStateUpdateListeners : imageConStateUpdateFn [ ] = [ ] ;
85+
4386export function SetGroupIndex ( groupName : string , newIdx : number ) {
4487 for ( let fn of selectUpdateListeners )
4588 fn ( groupName , newIdx ) ;
4689}
4790
91+ export function SetGroupTMOSettings ( groupName : string , newTMOSettings : ToneMapSettings ) {
92+ for ( let fn of tmoUpdateListeners )
93+ fn ( groupName , newTMOSettings ) ;
94+ }
95+
96+ export function SetGroupImageContainerSettings ( groupName : string , newImgConState : ImageContainerState ) {
97+ for ( let fn of imgConStateUpdateListeners )
98+ fn ( groupName , newImgConState ) ;
99+ }
100+
48101export interface FlipProps {
49102 names : string [ ] ;
50103 width : number ;
@@ -57,11 +110,15 @@ export interface FlipProps {
57110 initialTMOOverrides : ToneMapSettings [ ] ;
58111 style ?: React . CSSProperties ;
59112 onClick ?: OnClickHandler ;
113+ onWheel ?: OnWheelHandler ;
114+ onMouseOver ?: OnMouseOverHandler ;
115+ onKeyImageContainer ?: OnKeyHandler ;
60116 groupName ?: string ;
61117 hideTools : boolean ;
118+ idStr : string ;
62119}
63120
64- interface FlipState {
121+ export interface FlipState {
65122 selectedIdx : number ;
66123 popupContent ?: React . ReactNode ;
67124 popupDurationMs ?: number ;
@@ -73,8 +130,9 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
73130 imageContainer : React . RefObject < ImageContainer > ;
74131 tools : React . RefObject < Tools > ;
75132
76- constructor ( props : FlipProps ) {
133+ constructor ( props : FlipProps ) {
77134 super ( props ) ;
135+
78136 this . state = {
79137 selectedIdx : 0 ,
80138 hideTools : props . hideTools
@@ -85,10 +143,51 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
85143 this . tools = createRef ( ) ;
86144
87145 this . onKeyDown = this . onKeyDown . bind ( this ) ;
146+ this . onKeyUp = this . onKeyUp . bind ( this ) ;
88147 this . onSelectUpdate = this . onSelectUpdate . bind ( this ) ;
148+ this . onTMOUpdate = this . onTMOUpdate . bind ( this ) ;
149+ }
150+
151+ onKeyUp ( evt : React . KeyboardEvent < HTMLDivElement > ) {
152+ // trigger callbacks
153+ if ( this . props . onKeyImageContainer && keysPressed . has ( evt . key ) ) {
154+ keysPressed . delete ( evt . key ) ;
155+ evt . preventDefault ( ) ;
156+
157+ if ( keysPressed . size == 0 )
158+ this . imageContainer . current . setState ( {
159+ isAnyKeyPressed : false ,
160+ } , ( ) => {
161+ this . imageContainer . current . props . onStateChange ?.( this . imageContainer . current . state ) ; // callback
162+ } ) ;
163+
164+ listenerState . selectedIdx = this . state . selectedIdx ;
165+ listenerState . ID = this . props . idStr ;
166+ listenerState . keysPressed = keysPressed ;
167+
168+ this . props . onKeyImageContainer ( listenerState . mouseX , listenerState . mouseY , listenerState . ID , listenerState . selectedIdx , Array . from ( listenerState . keysPressed ) ) ;
169+ }
89170 }
90171
91172 onKeyDown ( evt : React . KeyboardEvent < HTMLDivElement > ) {
173+ // trigger callbacks
174+ if ( this . props . onKeyImageContainer && ! keysPressed . has ( evt . key ) ) {
175+ keysPressed . add ( evt . key ) ;
176+ evt . preventDefault ( ) ;
177+
178+ this . imageContainer . current . setState ( {
179+ isAnyKeyPressed : true ,
180+ } , ( ) => {
181+ this . imageContainer . current . props . onStateChange ?.( this . imageContainer . current . state ) ; // callback
182+ } ) ;
183+
184+ listenerState . selectedIdx = this . state . selectedIdx ;
185+ listenerState . ID = this . props . idStr ;
186+ listenerState . keysPressed = keysPressed ;
187+
188+ this . props . onKeyImageContainer ( listenerState . mouseX , listenerState . mouseY , listenerState . ID , listenerState . selectedIdx , Array . from ( listenerState . keysPressed ) ) ;
189+ }
190+
92191 let newIdx = this . state . selectedIdx ;
93192 if ( evt . key === "ArrowLeft" || evt . key === "ArrowDown" ) {
94193 newIdx = this . state . selectedIdx - 1 ;
@@ -138,15 +237,23 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
138237 evt . stopPropagation ( ) ;
139238 }
140239
141- if ( evt . key === "r" ) {
240+ if ( evt . ctrlKey && evt . key === 'r' ) {
241+ this . tmoCtrls . current . state . globalSettings . exposure = 0 ;
242+ evt . stopPropagation ( ) ;
243+ evt . preventDefault ( ) ;
244+ }
245+
246+ if ( ! evt . ctrlKey && evt . key === "r" ) {
142247 this . reset ( ) ;
143248 evt . stopPropagation ( ) ;
144249 }
145250
146251 if ( evt . key === "t" ) {
147- this . setState ( { hideTools : ! this . state . hideTools } ) ;
252+ this . setState ( { hideTools : ! this . state . hideTools } ) ;
148253 evt . stopPropagation ( ) ;
149254 }
255+
256+ this . updateTMOSettings ( this . tmoCtrls . current . state . globalSettings ) ;
150257 }
151258
152259 reset ( ) {
@@ -233,7 +340,13 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
233340
234341 updateSelection ( newIdx : number ) {
235342 if ( this . props . groupName ) SetGroupIndex ( this . props . groupName , newIdx ) ;
236- else this . setState ( { selectedIdx : newIdx } ) ;
343+ else this . setState ( { selectedIdx : newIdx } ) ;
344+ }
345+
346+ updateTMOSettings ( newTMOSettings : ToneMapSettings ) {
347+ if ( this . props . groupName ) SetGroupTMOSettings ( this . props . groupName , newTMOSettings ) ;
348+ else this . tmoCtrls . current . applySettings ( newTMOSettings ) ;
349+
237350 }
238351
239352 render ( ) : React . ReactNode {
@@ -242,36 +355,39 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
242355 popup =
243356 < Popup
244357 durationMs = { this . state . popupDurationMs }
245- unmount = { ( ) => this . setState ( { popupContent : null } ) }
358+ unmount = { ( ) => this . setState ( { popupContent : null } ) }
246359 >
247360 { this . state . popupContent }
248361 </ Popup >
249362 }
250363
251364 return (
252- < div className = { styles [ 'flipbook' ] } style = { this . props . style } onKeyDown = { this . onKeyDown } >
253- < div style = { { display : "contents" } } >
365+ < div className = { styles [ 'flipbook' ] } style = { this . props . style } onKeyDown = { this . onKeyDown } onKeyUp = { this . onKeyUp } >
366+ < div style = { { display : "contents" } } >
254367 < MethodList
255368 names = { this . props . names }
256369 selectedIdx = { this . state . selectedIdx }
257370 setSelectedIdx = { this . updateSelection . bind ( this ) }
258371 />
259372 < ImageContainer ref = { this . imageContainer }
260- width = { this . props . width }
261- height = { this . props . height }
373+ width = { this . props . width }
374+ height = { this . props . height }
262375 rawPixels = { this . props . rawPixels }
263376 means = { this . props . means }
264377 toneMappers = { this . props . toneMappers }
265378 selectedIdx = { this . state . selectedIdx }
266379 onZoom = { ( zoom ) => this . tools . current . onZoom ( zoom ) }
267380 onClick = { this . props . onClick }
381+ onWheel = { this . props . onWheel }
382+ onMouseOver = { this . props . onMouseOver }
383+ onStateChange = { ( st ) => this . onImageContainerUpdate ( st ) }
268384 >
269385 { popup }
270386 < button className = { styles . toolsBtn }
271- onClick = { ( ) => this . setState ( { hideTools : ! this . state . hideTools } ) }
272- style = { { position : "absolute" , bottom : 0 , right : 0 } }
387+ onClick = { ( ) => this . setState ( { hideTools : ! this . state . hideTools } ) }
388+ style = { { position : "absolute" , bottom : 0 , right : 0 } }
273389 >
274- { this . state . hideTools ? "Show tools " : "Hide tools " }
390+ { this . state . hideTools ? "Show tools " : "Hide tools " }
275391 < span className = { styles [ 'key' ] } > t</ span >
276392 </ button >
277393 </ ImageContainer >
@@ -298,7 +414,29 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
298414 onSelectUpdate ( groupName : string , newIdx : number ) {
299415 if ( groupName == this . props . groupName ) {
300416 newIdx = Math . min ( this . props . rawPixels . length - 1 , Math . max ( 0 , newIdx ) ) ;
301- this . setState ( { selectedIdx : newIdx } ) ;
417+ this . setState ( { selectedIdx : newIdx } ) ;
418+ }
419+ }
420+
421+ onTMOUpdate ( groupName : string , newTMOSettings : ToneMapSettings ) {
422+ if ( groupName == this . props . groupName ) {
423+ this . tmoCtrls . current . applySettings ( newTMOSettings ) ;
424+ }
425+ }
426+
427+ // is called when onStateIsChanged in ImageContainer is called
428+ // everytime when the ImageContainerState changes (pos, zoom, etc.)
429+ // calls onImageContainerGroupUpdate = ()
430+ onImageContainerUpdate ( newImageContainerState : ImageContainerState ) {
431+ if ( this . props . groupName ) {
432+ SetGroupImageContainerSettings ( this . props . groupName , newImageContainerState ) ;
433+ }
434+ }
435+
436+ // is called when other flipbook's ImageContainerStates changes
437+ onImageContainerGroupUpdate = ( groupName : string , newImageContainerState : ImageContainerState ) => {
438+ if ( groupName === this . props . groupName && this . imageContainer . current ) {
439+ this . imageContainer . current . setState ( newImageContainerState ) ;
302440 }
303441 }
304442
@@ -307,11 +445,19 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
307445 this . imageContainer . current . setZoom ( this . props . initialZoom ) ;
308446
309447 selectUpdateListeners . push ( this . onSelectUpdate ) ;
448+ tmoUpdateListeners . push ( this . onTMOUpdate ) ;
449+ imgConStateUpdateListeners . push ( this . onImageContainerGroupUpdate ) ;
310450 }
311451
312452 componentWillUnmount ( ) : void {
313453 let idx = selectUpdateListeners . findIndex ( v => v === this . onSelectUpdate ) ;
314454 selectUpdateListeners . splice ( idx , 1 ) ;
455+
456+ idx = tmoUpdateListeners . findIndex ( v => v === this . onTMOUpdate ) ;
457+ tmoUpdateListeners . splice ( idx , 1 ) ;
458+
459+ idx = imgConStateUpdateListeners . findIndex ( v => v === this . onImageContainerGroupUpdate ) ;
460+ imgConStateUpdateListeners . splice ( idx , 1 ) ;
315461 }
316462
317463 connect ( other : React . RefObject < FlipBook > ) {
@@ -324,7 +470,7 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
324470 * @param images List of images that are either raw floating point data or HTML image elements
325471 * @returns List of all images with image elements replaced by their image data
326472 */
327- function GetImageData ( images : ( Float32Array | HTMLImageElement ) [ ] ) : ( Float32Array | ImageData ) [ ] {
473+ function GetImageData ( images : ( Float32Array | HTMLImageElement ) [ ] ) : ( Float32Array | ImageData ) [ ] {
328474 let imgData : ( Float32Array | ImageData ) [ ] = [ ]
329475 for ( let i = 0 ; i < images . length ; ++ i ) {
330476 if ( images [ i ] instanceof HTMLImageElement ) {
@@ -381,8 +527,13 @@ export type FlipBookParams = {
381527 initialTMO : ToneMapSettings ,
382528 initialTMOOverrides : ToneMapSettings [ ] ,
383529 onClick ?: OnClickHandler ,
530+ onWheel ?: OnWheelHandler ,
531+ onMouseOver ?: OnMouseOverHandler ,
532+ onKeyImageContainer ?: OnKeyHandler ,
384533 colorTheme ?: string ,
385534 hideTools : boolean ,
535+ containerId : string ,
536+ id : string ,
386537}
387538
388539export function AddFlipBook ( params : FlipBookParams , groupName ?: string ) {
@@ -415,8 +566,10 @@ export function AddFlipBook(params: FlipBookParams, groupName?: string) {
415566 let themeStyle = colorThemes [ params . colorTheme ?? "dark" ] ;
416567
417568 const root = createRoot ( params . parentElement ) ;
569+ const bookRef = createRef < FlipBook > ( ) ;
418570 root . render (
419571 < FlipBook
572+ ref = { bookRef }
420573 names = { params . names }
421574 width = { params . width }
422575 height = { params . height }
@@ -427,15 +580,23 @@ export function AddFlipBook(params: FlipBookParams, groupName?: string) {
427580 initialTMO = { params . initialTMO }
428581 initialTMOOverrides = { params . initialTMOOverrides }
429582 onClick = { params . onClick }
583+ onWheel = { params . onWheel }
584+ onMouseOver = { params . onMouseOver }
585+ onKeyImageContainer = { params . onKeyImageContainer }
430586 style = { themeStyle }
431587 groupName = { groupName }
432588 hideTools = { params . hideTools }
589+ idStr = { params . id }
433590 />
434591 ) ;
435592
593+ if ( params . id )
594+ registerBook ( params . id , bookRef ) ;
595+
436596 new MutationObserver ( _ => {
437597 if ( ! document . body . contains ( params . parentElement ) ) {
598+ unregisterBook ( params . id , bookRef ) ;
438599 root . unmount ( ) ;
439600 }
440- } ) . observe ( document . body , { childList : true , subtree : true } ) ;
601+ } ) . observe ( document . body , { childList : true , subtree : true } ) ;
441602}
0 commit comments