diff --git a/ivette/Makefile.distrib b/ivette/Makefile.distrib index dba6fa37ba930252cee8289297bda4fd21ecfefe..48889dd663ab21c6378160c765143c345856eab6 100644 --- a/ivette/Makefile.distrib +++ b/ivette/Makefile.distrib @@ -112,6 +112,7 @@ DISTRIB_FILES += ivette/src/dome/renderer/layout/boxes.tsx DISTRIB_FILES += ivette/src/dome/renderer/layout/dispatch.tsx DISTRIB_FILES += ivette/src/dome/renderer/layout/forms.tsx DISTRIB_FILES += ivette/src/dome/renderer/layout/grids.js +DISTRIB_FILES += ivette/src/dome/renderer/layout/qsplit.tsx DISTRIB_FILES += ivette/src/dome/renderer/layout/splitters.tsx DISTRIB_FILES += ivette/src/dome/renderer/layout/style.css DISTRIB_FILES += ivette/src/dome/renderer/light.css diff --git a/ivette/headers/header_spec.txt b/ivette/headers/header_spec.txt index be8c7cf2f9ef17c8ccf162b16983bca82a71848d..ece2190612e9d15f7d100ac34108b03ce3ac966f 100644 --- a/ivette/headers/header_spec.txt +++ b/ivette/headers/header_spec.txt @@ -111,6 +111,7 @@ src/dome/renderer/layout/boxes.tsx: CEA_LGPL src/dome/renderer/layout/dispatch.tsx: CEA_LGPL src/dome/renderer/layout/forms.tsx: CEA_LGPL src/dome/renderer/layout/grids.js: CEA_LGPL +src/dome/renderer/layout/qsplit.tsx: CEA_LGPL src/dome/renderer/layout/splitters.tsx: CEA_LGPL src/dome/renderer/layout/style.css: .ignore src/dome/renderer/light.css: .ignore diff --git a/ivette/src/dome/renderer/layout/qsplit.tsx b/ivette/src/dome/renderer/layout/qsplit.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5ef6208c325fbbcc8c46177fa6a16f35325105e7 --- /dev/null +++ b/ivette/src/dome/renderer/layout/qsplit.tsx @@ -0,0 +1,321 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2021 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +// -------------------------------------------------------------------------- +// --- Quarter-based Splitter +// -------------------------------------------------------------------------- + +/** + @packageDocumentation + @module dome/layout/qsplit +*/ + +import * as React from 'react'; +import * as Utils from 'dome/misc/utils'; +import { DraggableCore, DraggableEventHandler } from 'react-draggable'; +import { AutoSizer, Size } from 'react-virtualized'; + +/* -------------------------------------------------------------------------- */ +/* --- Q-Split Properties --- */ +/* -------------------------------------------------------------------------- */ + +export interface QSplitProps { + className?: string; /** Q-Split additional class. */ + style?: React.CSSProperties; /** Q-Split additional style. */ + A?: string; /** Q-Pane to layout in A-quarter. */ + B?: string; /** Q-Pane to layout in B-quarter. */ + C?: string; /** Q-Pane to layout in C-quarter. */ + D?: string; /** Q-Pane to layout in D-quarter. */ + H?: number; /** Horizontal panes ratio (range `0..1`, default `0.5`). */ + V?: number; /** Vertical panes ratio (range `0..1`, default `0.5`). */ + setPosition?: (H: number, V: number) => void; /** Dragging ratios callback. */ + children?: JSX.Element; + /** Q-Split contents. Shall be (possibly packed) Q-Panes. + Other components would be layout as they are in the + positionned `<div/>` of the Q-Split. */ +} + +/* -------------------------------------------------------------------------- */ +/* --- Split Bars --- */ +/* -------------------------------------------------------------------------- */ + +type Dragging = undefined | { + position: number; + anchor: number; + offset: number; +}; + + +interface QSplitterProps { + hsplit: boolean; + style: React.CSSProperties; + dragging: Dragging; + setDragging: (dragging: Dragging) => void; + setPosition: (P: number) => void; +} + +const HANDLE = '.dome-xSplitter-grab'; +const HGRAB = 'dome-xSplitter-grab dome-xSplitter-hgrab'; +const VGRAB = 'dome-xSplitter-grab dome-xSplitter-vgrab'; +const DRAGGING = 'dome-color-dragging'; +const DRAGZONE = 'dome-color-dragzone'; + +function QSplitter(props: QSplitterProps) { + const { hsplit, style, dragging, setDragging, setPosition } = props; + + const onStart: DraggableEventHandler = + (_evt, data) => { + const startPos = hsplit ? data.node.offsetLeft : data.node.offsetTop; + const anchor = hsplit ? data.x : data.y; + setDragging({ position: startPos, offset: anchor, anchor }); + }; + + const onDrag: DraggableEventHandler = + (_evt, data) => { + if (dragging) { + const offset = hsplit ? data.x : data.y; + setDragging({ ...dragging, offset }); + } + }; + + const onStop: DraggableEventHandler = + (evt, _data) => { + if (evt.metaKey || evt.altKey || evt.ctrlKey) { + setPosition(0.5); + } else if (dragging) { + setPosition(dragging.position + dragging.offset - dragging.anchor); + } + setDragging(undefined); + }; + + const dragger = Utils.classes( + hsplit ? HGRAB : VGRAB, + dragging ? DRAGGING : DRAGZONE, + ); + + return ( + <DraggableCore + handle={HANDLE} + onStart={onStart} + onDrag={onDrag} + onStop={onStop} + > + <div + className={hsplit ? 'dome-xQSplitter-h' : 'dome-xQSplitter-v'} + style={style} + > + <div className={dragger} /> + </div> + </DraggableCore> + ); +} + + +/* -------------------------------------------------------------------------- */ +/* --- Q-Split Engine --- */ +/* -------------------------------------------------------------------------- */ + +type QSplitLayout = Map<string, React.CSSProperties>; +const QSplitContext = React.createContext<QSplitLayout>(new Map()); +const NODISPLAY: React.CSSProperties = { display: 'none' }; + +const HSPLIT = ( + left: number, + top: number, + height: number, +) => ({ display: 'block', left, top, height }); + +const VSPLIT = ( + left: number, + top: number, + width: number, +) => ({ display: 'block', left, top, width }); + +const DISPLAY = ( + layout: QSplitLayout, + id: string | undefined, + left: number, + width: number, + top: number, + height: number, +) => { + if (id) layout.set(id, { display: 'block', left, width, top, height }); +}; + +interface QSplitEngineProps extends QSplitProps { size: Size } + +function QSplitEngine(props: QSplitEngineProps) { + const [dragX, setDragX] = React.useState<Dragging>(); + const [dragY, setDragY] = React.useState<Dragging>(); + const layout: QSplitLayout = new Map(); + let hsplit: React.CSSProperties = NODISPLAY; + let vsplit: React.CSSProperties = NODISPLAY; + const { A, B, C, D, H = 0.5, V = 0.5, size, setPosition } = props; + const { width, height } = size; + const setX = React.useCallback((X: number) => { + if (setPosition) setPosition(X / width, V); + }, [setPosition, width, V]); + const setY = React.useCallback((Y: number) => { + if (setPosition) setPosition(H, Y / height); + }, [setPosition, height, H]); + const X = width * H; + const Y = height * V; + const RX = width - X - 1; + const RY = height - Y - 1; + const FULL = A !== undefined && A === D; + const AB = A !== undefined && A === B; + const AC = A !== undefined && A === C; + const BD = B !== undefined && B === D; + const CD = C !== undefined && C === D; + //---------------------------------------- + // [ A ] + //--------------------------------------- + if (FULL) { + DISPLAY(layout, A, 0, width, 0, height); + } + //---------------------------------------- + // [ A - CD ] + //--------------------------------------- + else if (AB && CD) { + vsplit = VSPLIT(0, Y, width); + DISPLAY(layout, A, 0, width, 0, Y); + DISPLAY(layout, C, 0, width, Y + 1, RY); + } + //---------------------------------------- + // [ A | B ] + //--------------------------------------- + else if (AC && BD) { + hsplit = HSPLIT(X, 0, height); + DISPLAY(layout, A, 0, X, 0, height); + DISPLAY(layout, B, X + 1, RX, 0, height); + } + //---------------------------------------- + // [ A – C|D ] + //---------------------------------------- + else if (AB) { + hsplit = HSPLIT(X, 0, height); + vsplit = VSPLIT(X, Y, RX); + DISPLAY(layout, A, 0, width, 0, Y); + DISPLAY(layout, C, 0, X, Y + 1, RY); + DISPLAY(layout, D, X + 1, RX, Y + 1, RY); + } + //---------------------------------------- + // [ A | B-D ] + //---------------------------------------- + else if (AC) { + hsplit = HSPLIT(X, 0, height); + vsplit = VSPLIT(X, Y, RY); + DISPLAY(layout, A, 0, X, 0, height); + DISPLAY(layout, B, X + 1, RX, 0, Y); + DISPLAY(layout, D, X + 1, RX, Y + 1, RY); + } + //---------------------------------------- + // [ A-C | B ] + //---------------------------------------- + else if (BD) { + hsplit = HSPLIT(0, Y, RX); + vsplit = VSPLIT(X, 0, width); + DISPLAY(layout, A, 0, X, 0, Y); + DISPLAY(layout, B, X + 1, RX, 0, height); + DISPLAY(layout, C, 0, X, Y + 1, RY); + } + //---------------------------------------- + // [ A|B - C ] + //---------------------------------------- + else if (CD) { + hsplit = HSPLIT(X, 0, RX); + vsplit = VSPLIT(0, Y, width); + DISPLAY(layout, A, 0, X, 0, Y); + DISPLAY(layout, B, X + 1, RX, 0, Y); + DISPLAY(layout, C, 0, width, Y + 1, RY); + } + //---------------------------------------- + // [ A, B, C, D ] + //---------------------------------------- + else { + hsplit = HSPLIT(X, 0, height); + vsplit = VSPLIT(0, Y, width); + DISPLAY(layout, A, 0, X, 0, Y); + DISPLAY(layout, B, X + 1, RX, 0, Y); + DISPLAY(layout, C, 0, X, Y + 1, RY); + DISPLAY(layout, D, X + 1, RX, Y + 1, RY); + } + //---------------------------------------- + // Rendering + //---------------------------------------- + return ( + <QSplitContext.Provider value={layout}> + <QSplitter + key='SPLIT-H' + hsplit={true} + dragging={dragX} + setDragging={setDragX} + setPosition={setX} + style={hsplit} /> + <QSplitter + key='SPLIT-V' + hsplit={false} + dragging={dragY} + setDragging={setDragY} + setPosition={setY} + style={vsplit} /> + {props.children} + </QSplitContext.Provider> + ); +} + +/* -------------------------------------------------------------------------- */ +/* --- Q-Split --- */ +/* -------------------------------------------------------------------------- */ + +export function QSplit(props: QSplitProps) { + const CONTAINER = Utils.classes('dome-xSplitter-container', props.className); + return ( + <div className={CONTAINER} style={props.style}> + <AutoSizer> + {(size: Size) => ( + <QSplitEngine size={size} {...props} /> + )} + </AutoSizer> + </div> + ); +} + +/* -------------------------------------------------------------------------- */ +/* --- Q-Pane --- */ +/* -------------------------------------------------------------------------- */ + +export interface QPaneProps { + id: string; /** Q-Pane Identifer. */ + className?: string; /** Additional class of the Q-Pane div. */ + style?: React.CSSProperties; /** Additional style of the Q-Pane div. */ + children?: JSX.Element; /** Q-Pane contents. */ +} + +export function QPane(props: QPaneProps) { + const layout = React.useContext(QSplitContext); + const QPANE = Utils.classes('dome-xQPane', props.className); + const QSTYLE = Utils.styles(props.style, layout?.get(props.id) ?? NODISPLAY); + return <div className={QPANE} style={QSTYLE}>{props.children}</div>; +} + +// -------------------------------------------------------------------------- diff --git a/ivette/src/dome/renderer/layout/splitters.tsx b/ivette/src/dome/renderer/layout/splitters.tsx index e56f47dd345caba272617b45e6cb2071ffaf9edc..032766f6f2e931d3f38cafc2b0849289bd38350c 100644 --- a/ivette/src/dome/renderer/layout/splitters.tsx +++ b/ivette/src/dome/renderer/layout/splitters.tsx @@ -274,13 +274,13 @@ function SplitterEngine(props: SplitterEngineProps): JSX.Element { {A} </div> <DraggableCore + key="split" handle={HANDLE} onStart={onStart} onDrag={onDrag} onStop={onStop} > <div - key="split" className={css.split} style={styleR} > diff --git a/ivette/src/dome/renderer/layout/style.css b/ivette/src/dome/renderer/layout/style.css index 4d6125bb7a614057aae47918d3f1ec2fde06cd41..01fade1463e7ca3f948591f35ef512b639a4e73b 100644 --- a/ivette/src/dome/renderer/layout/style.css +++ b/ivette/src/dome/renderer/layout/style.css @@ -191,6 +191,12 @@ background: var(--splitter) ; } +.dome-xQPane +{ + position: absolute; + overflow: hidden; +} + /* -------------------------------------------------------------------------- */ /* --- GridLayout Styles --- */ /* -------------------------------------------------------------------------- */