diff --git a/FlipViewer/package.json b/FlipViewer/package.json index 1b36250..b259bb6 100644 --- a/FlipViewer/package.json +++ b/FlipViewer/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "build": "npx webpack --mode production" + "build": "npx webpack --mode production", + "build-dev": "npx webpack --mode development" }, "author": "Pascal Grittmann", "license": "MIT", diff --git a/FlipViewer/src/FlipBook.tsx b/FlipViewer/src/FlipBook.tsx index 18ee920..25a728e 100644 --- a/FlipViewer/src/FlipBook.tsx +++ b/FlipViewer/src/FlipBook.tsx @@ -2,7 +2,7 @@ import { createRoot } from 'react-dom/client'; import styles from './styles.module.css'; import React, { createRef } from 'react'; import { renderImage } from "./Render"; -import { ImageContainer, OnClickHandler } from './ImageContainer'; +import { ImageContainer, OnClickHandler, OnWheelHandler, OnMouseOverHandler, ImageContainerState, OnKeyHandler, setKeyPressed } from './ImageContainer'; import { ToneMapControls } from './ToneMapControls'; import { MethodList } from './MethodList'; import { Tools } from './Tools'; @@ -11,9 +11,36 @@ import { ToneMapSettings, ZoomLevel } from './flipviewer'; const UPDATE_INTERVAL_MS = 100; +// Registry to update flipbooks +export type BookRef = React.RefObject; +const registry = new Map>(); + +export function getBooks(id: string): BookRef[] { + return Array.from(registry.get(id) ?? new Set()); +} + +export function registerBook(id: string, ref: BookRef) { + if (!id) return; + const set = registry.get(id) ?? new Set(); + set.add(ref); + registry.set(id, set); +} + +export function unregisterBook(id: string, ref: BookRef) { + const set = registry.get(id); + if (!set) return; + set.delete(ref); + if (set.size === 0) registry.delete(id); +} + +// to get only key presses and not is down state +// Idea is to only fire events if state of key changes +const keyPressed = new Set(); + export class ToneMappingImage { currentTMO: string; dirty: boolean; + isPixelUpdate: boolean; canvas: HTMLCanvasElement; pixels: Float32Array | ImageData; @@ -25,9 +52,10 @@ export class ToneMappingImage { let hdrImg = this; setInterval(function() { - if (!hdrImg.dirty) return; - hdrImg.dirty = false; + if (!hdrImg.dirty || hdrImg.isPixelUpdate) + return; renderImage(hdrImg.canvas, hdrImg.pixels, hdrImg.currentTMO); + hdrImg.dirty = false; onAfterRender(); }, UPDATE_INTERVAL_MS) } @@ -35,16 +63,46 @@ export class ToneMappingImage { this.currentTMO = tmo; this.dirty = true; } + setPixels(p: Float32Array | ImageData) { + this.isPixelUpdate = true; + this.pixels = p; + this.dirty = false; + renderImage(this.canvas, this.pixels, this.currentTMO); + this.isPixelUpdate = false; + } } + type SelectUpdateFn = (groupName: string, newIdx: number) => void; var selectUpdateListeners: SelectUpdateFn[] = []; + +type TMOUpdateFn = (groupName: string, newTMOSettings: ToneMapSettings) => void; +var tmoUpdateListeners: TMOUpdateFn[] = []; + +type imageConStateUpdateFn = (groupName: string, newImgConState: ImageContainerState) => void; +var imgConStateUpdateListeners: imageConStateUpdateFn[] = []; + export function SetGroupIndex(groupName: string, newIdx: number) { for (let fn of selectUpdateListeners) fn(groupName, newIdx); } + +export function SetGroupTMOSettings(groupName: string, newTMOSettings: ToneMapSettings) +{ + for (let fn of tmoUpdateListeners) + fn(groupName, newTMOSettings); +} + + +export function SetGroupImageContainerSettings(groupName: string, newImgConState: ImageContainerState) +{ + for (let fn of imgConStateUpdateListeners) + fn(groupName, newImgConState); +} + + export interface FlipProps { names: string[]; width: number; @@ -57,11 +115,15 @@ export interface FlipProps { initialTMOOverrides: ToneMapSettings[]; style?: React.CSSProperties; onClick?: OnClickHandler; + onWheel?: OnWheelHandler; + onMouseOver?: OnMouseOverHandler; + onKeyImageContainer?: OnKeyHandler; groupName?: string; hideTools: boolean; + idStr: string; } -interface FlipState { +export interface FlipState { selectedIdx: number; popupContent?: React.ReactNode; popupDurationMs?: number; @@ -75,6 +137,7 @@ export class FlipBook extends React.Component { constructor(props : FlipProps) { super(props); + this.state = { selectedIdx: 0, hideTools: props.hideTools @@ -85,10 +148,35 @@ export class FlipBook extends React.Component { this.tools = createRef(); this.onKeyDown = this.onKeyDown.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); this.onSelectUpdate = this.onSelectUpdate.bind(this); + this.onTMOUpdate = this.onTMOUpdate.bind(this); + } + + onKeyUp(evt: React.KeyboardEvent) { + // trigger callbacks + if(this.props.onKeyImageContainer && keyPressed.has(evt.key)) + { + keyPressed.delete(evt.key); + evt.preventDefault(); + + if(keyPressed.size == 0) + setKeyPressed(false); + + this.props.onKeyImageContainer(this.state.selectedIdx, this.props.idStr, evt.key, false); + } } onKeyDown(evt: React.KeyboardEvent) { + // trigger callbacks + if(this.props.onKeyImageContainer && !keyPressed.has(evt.key)) + { + keyPressed.add(evt.key); + evt.preventDefault(); + setKeyPressed(true); + this.props.onKeyImageContainer(this.state.selectedIdx, this.props.idStr, evt.key, true); + } + let newIdx = this.state.selectedIdx; if (evt.key === "ArrowLeft" || evt.key === "ArrowDown") { newIdx = this.state.selectedIdx - 1; @@ -138,7 +226,13 @@ export class FlipBook extends React.Component { evt.stopPropagation(); } - if (evt.key === "r") { + if (evt.ctrlKey && evt.key === 'r') { + this.tmoCtrls.current.state.globalSettings.exposure = 0; + evt.stopPropagation(); + evt.preventDefault(); + } + + if (!evt.ctrlKey && evt.key === "r") { this.reset(); evt.stopPropagation(); } @@ -147,6 +241,9 @@ export class FlipBook extends React.Component { this.setState({hideTools: !this.state.hideTools}); evt.stopPropagation(); } + + + this.updateTMOSettings(this.tmoCtrls.current.state.globalSettings); } reset() { @@ -236,6 +333,13 @@ export class FlipBook extends React.Component { else this.setState({selectedIdx: newIdx}); } + + updateTMOSettings(newTMOSettings: ToneMapSettings){ + if (this.props.groupName) SetGroupTMOSettings(this.props.groupName, newTMOSettings); + else this.tmoCtrls.current.applySettings(newTMOSettings); + + } + render(): React.ReactNode { let popup = null; if (this.state.popupContent) { @@ -249,7 +353,7 @@ export class FlipBook extends React.Component { } return ( -
+
{ selectedIdx={this.state.selectedIdx} onZoom={(zoom) => this.tools.current.onZoom(zoom)} onClick={this.props.onClick} + onWheel={this.props.onWheel} + onMouseOver={this.props.onMouseOver} + onStateChange={(st) => this.onImageContainerUpdate(st)} > {popup}