From 28aa48152294a5d635f6ffde0a92565d696b5f4b Mon Sep 17 00:00:00 2001 From: David Hares Date: Mon, 6 Oct 2025 21:46:54 +0200 Subject: [PATCH] template changes --- FlipViewer/src/FlipBook.tsx | 187 ++++++++++++++++++++++++++++- FlipViewer/src/ImageContainer.tsx | 104 +++++++++++++--- FlipViewer/src/Popup.tsx | 2 +- FlipViewer/src/ToneMapControls.tsx | 4 + FlipViewer/src/flipviewer.ts | 49 +++++++- SimpleImageIO/FlipBook.cs | 139 ++++++++++++++++++++- SimpleImageIO/Image.cs | 5 + 7 files changed, 461 insertions(+), 29 deletions(-) diff --git a/FlipViewer/src/FlipBook.tsx b/FlipViewer/src/FlipBook.tsx index 18ee920..b24928d 100644 --- a/FlipViewer/src/FlipBook.tsx +++ b/FlipViewer/src/FlipBook.tsx @@ -2,14 +2,46 @@ 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 } from './ImageContainer'; import { ToneMapControls } from './ToneMapControls'; import { MethodList } from './MethodList'; import { Tools } from './Tools'; import { Popup } from './Popup'; import { ToneMapSettings, ZoomLevel } from './flipviewer'; -const UPDATE_INTERVAL_MS = 100; +const UPDATE_INTERVAL_MS = 500; + +// Registry to update flipbooks ------------- +export type BookRef = React.RefObject; +const registry = new Map>(); + +export function keyToString(key: number[]){ + if(key) + return key.join(','); + else + return ""; +} + +export function getBooks(key: number[]): BookRef[] { + return Array.from(registry.get(keyToString(key)) ?? new Set()); +} +export function registerBook(key: number[], ref: BookRef) { + if (!key) return; + const set = registry.get(keyToString(key)) ?? new Set(); + set.add(ref); + registry.set(keyToString(key), set); +} +export function unregisterBook(key: number[], ref: BookRef) { + const set = registry.get(keyToString(key)); + if (!set) return; + set.delete(ref); + if (set.size === 0) registry.delete(keyToString(key)); +} +// --------------------------------------------------------------- + +// 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; @@ -25,7 +57,8 @@ export class ToneMappingImage { let hdrImg = this; setInterval(function() { - if (!hdrImg.dirty) return; + if (!hdrImg.dirty) + return; hdrImg.dirty = false; renderImage(hdrImg.canvas, hdrImg.pixels, hdrImg.currentTMO); onAfterRender(); @@ -35,16 +68,43 @@ export class ToneMappingImage { this.currentTMO = tmo; this.dirty = true; } + setPixels(p: Float32Array | ImageData) { + this.pixels = p; + this.dirty = true; + } } + 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 +117,16 @@ export interface FlipProps { initialTMOOverrides: ToneMapSettings[]; style?: React.CSSProperties; onClick?: OnClickHandler; + onWheel?: OnWheelHandler; + onMouseOver?: OnMouseOverHandler; + onKeyIC?: OnKeyHandler; + // onKeyUpIC?: OnKeyUpHandler; groupName?: string; hideTools: boolean; + keyStr: string; } -interface FlipState { +export interface FlipState { selectedIdx: number; popupContent?: React.ReactNode; popupDurationMs?: number; @@ -75,6 +140,7 @@ export class FlipBook extends React.Component { constructor(props : FlipProps) { super(props); + this.state = { selectedIdx: 0, hideTools: props.hideTools @@ -85,10 +151,30 @@ 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) { + // c# listener + if(this.props.onKeyIC && keyPressed.has(evt.key) && (evt.key === "Alt" || evt.key === "Control")) + { + keyPressed.delete(evt.key); + evt.preventDefault(); + this.props.onKeyIC(this.state.selectedIdx, this.props.keyStr, evt.key, false); + } } onKeyDown(evt: React.KeyboardEvent) { + // c# listener + if(this.props.onKeyIC && !keyPressed.has(evt.key) && (evt.key === "Alt" || evt.key === "Control")) + { + keyPressed.add(evt.key); + evt.preventDefault(); + this.props.onKeyIC(this.state.selectedIdx, this.props.keyStr, evt.key, true); + } + let newIdx = this.state.selectedIdx; if (evt.key === "ArrowLeft" || evt.key === "ArrowDown") { newIdx = this.state.selectedIdx - 1; @@ -138,7 +224,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 +239,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 +331,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 +351,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}