diff --git a/ivette/src/dome/renderer/frame/panel.tsx b/ivette/src/dome/renderer/frame/panel.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e5301212fdac427340f47914e02f6e6bd6c7eecf --- /dev/null +++ b/ivette/src/dome/renderer/frame/panel.tsx @@ -0,0 +1,116 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2024 */ +/* 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). */ +/* */ +/* ************************************************************************ */ + +/** + This package allows us to add a panel inside positioned elements. + + It provides some components to create the panel content: + * ListElement + * Text + * Actions + + @packageDocumentation + @module dome/frame/Panel + */ + +import React from 'react'; +import { classes } from 'dome/misc/utils'; +import { Hbox } from 'dome/layout/boxes'; + + +/* --------------------------------------------------------------------------*/ +/* --- Panel Container */ +/* --------------------------------------------------------------------------*/ +export type PanelPosition = 'top' | 'bottom' | 'left' | 'right'; + +interface PanelProps { + /** Additional class. */ + className?: string; + /** Position to displayed the panel. Default 'tr' */ + position?: PanelPosition; + /** Defaults to `true`. */ + visible?: boolean; + /** Defaults to `true`. */ + display?: boolean; + /** Panel children. */ + children: JSX.Element[]; +} + +export const Panel = (props: PanelProps): JSX.Element => { + const { visible = true, display = true, + className, position = 'right' } = props; + + const classNames = classes( + 'dome-xPanel', + 'dome-xPanel-'+position, + visible ? 'dome-xPanel-open' : 'dome-xPanel-close', + !display && 'dome-control-erased', + className, + ); + + return ( + <div className={classNames}> + {props.children.map((elt, k) => <Hbox key={k}>{elt}</Hbox>)} + </div> + ); +}; + +/* --------------------------------------------------------------------------*/ +/* --- Panel List */ +/* --------------------------------------------------------------------------*/ +export interface ElementProps { + /** Selection state. */ + selected?: boolean; + /** Selection callback. */ + onSelection?: () => void; + /** Item element. */ + children?: JSX.Element; +} + +export function Element(props: ElementProps): JSX.Element { + const { selected = true, onSelection, children } = props; + + const classNames = classes( + 'dome-xPanel-element', + selected ? 'dome-active' : 'dome-inactive', + ); + return ( + <div + className={classNames} + onClick={onSelection} + > + {children} + </div> + ); +} + +interface ListElementProps { + children: JSX.Element[]; +} + +export function ListElement(props: ListElementProps): JSX.Element { + return ( + <div className='dome-xPanel-list'> + {props.children} + </div> + ); +} diff --git a/ivette/src/dome/renderer/frame/style.css b/ivette/src/dome/renderer/frame/style.css index 44f4580e0f4ad8de3985f0b2f90ef70066a8cd6c..c4dd8a880576ffc1ee883344afac7302cc74de82 100644 --- a/ivette/src/dome/renderer/frame/style.css +++ b/ivette/src/dome/renderer/frame/style.css @@ -587,3 +587,107 @@ input:checked + .dome-xSwitch-slider:before { -ms-transform: translateX(13px); transform: translateX(13px); } + +/* -------------------------------------------------------------------------- */ +/* --- Side Panel --- */ +/* -------------------------------------------------------------------------- */ +.dome-xPanel { + position: absolute; + z-index: 50; + background-color: var(--background-intense); + padding: 10px 5px; + transition: transform .5s ease-in-out, opacity .5s; + + button:hover, + button:hover label { + cursor: pointer; + } + + &::-webkit-scrollbar { display: none; } + + &.dome-xPanel-open { + transform: translate(0); + opacity: 1; + } + + &.dome-xPanel-left, + &.dome-xPanel-right { + top: 5px; + padding-top: -5px; + max-height: calc(100% - 10px); + max-width: 350px; + overflow-y: auto; + } + + &.dome-xPanel-left { + border-radius: 0 10px 10px 0; + + &.dome-xPanel-close { + transform: translateX(-350px); + opacity: 0; + } + } + + &.dome-xPanel-right { + right: 0; + border-radius: 10px 0 0 10px; + + &.dome-xPanel-close { + transform: translateX(350px); + opacity: 0; + } + } + + &.dome-xPanel-top, + &.dome-xPanel-bottom { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + width: calc(100% - 10px); + left:5px; + max-height: 200px; + overflow-x: scroll; + + label, + .dome-xPanel-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + } + } + + &.dome-xPanel-top { + overflow-x: auto; + border-radius: 0 0 10px 10px; + + &.dome-xPanel-close { + transform: translateY(-350px); + opacity: 0; + } + } + + &.dome-xPanel-bottom { + bottom: 0; + border-radius: 10px 10px 0 0; + + &.dome-xPanel-close { + transform: translateY(350px); + opacity: 0; + } + } + + .dome-xPanel-list { + .dome-xPanel-element { + margin:5px; + padding: 2px 10px; + background-color: var(--background); + border-radius: 5px; + + &.dome-active { + background-color: var(--background-profound); + } + } + } +} diff --git a/ivette/src/sandbox/panel.tsx b/ivette/src/sandbox/panel.tsx new file mode 100644 index 0000000000000000000000000000000000000000..407bbde1701091ad5ee99319568eb8448419295e --- /dev/null +++ b/ivette/src/sandbox/panel.tsx @@ -0,0 +1,182 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2024 */ +/* 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). */ +/* */ +/* ************************************************************************ */ + +/* -------------------------------------------------------------------------- */ +/* --- Sandbox Testing of Panel --- */ +/* --- Only appears in DEVEL mode. --- */ +/* -------------------------------------------------------------------------- */ + +import React from 'react'; +import * as Dome from 'dome'; +import { IconButton } from 'dome/controls/buttons'; +import { registerSandbox, TitleBar } from 'ivette'; +import { + Panel, ListElement, Element, PanelPosition +} from 'dome/frame/panel'; +import { Button, ButtonGroup } from 'dome/frame/toolbars'; +import { Icon } from 'dome/controls/icons'; +import './style.css'; +import { Label } from 'dome/controls/labels'; + +/* -------------------------------------------------------------------------- */ +/* --- Use Panel --- */ +/* -------------------------------------------------------------------------- */ +function UsePanel(): JSX.Element { + const [position, setPosition] = React.useState<PanelPosition>('right'); + const [visible, flipVisible] = Dome.useFlipState(true); + const [selected, setSelected] = React.useState(1); + const [list, setList] = React.useState({ + flip: true, + list: [ + { id: 1, children: <div>item 1</div> }, + { id: 2, children: <div>item 2</div> }, + { id: 3, children: <div>item 3</div> }, + { id: 4, children: <div>item 4</div> }, + ] + } + ); + + return ( + <> + <TitleBar> + <IconButton + icon='ANGLE.RIGHT' + title="Change position of Panel." + className={'sandbox-panel-button-position-'+position} + onClick={() => setPosition((val) => { + return val === 'right' ? 'bottom' : + val === 'bottom' ? 'left' : + val === 'left' ? 'top' : + 'right'; + }) + } + /> + + <IconButton + icon="SIDEBAR" + title={"show or hide the panel"} + onClick={flipVisible} + /> + </TitleBar> + <div style={{ position: 'relative', height: '100%' }}> + <Panel visible={visible} position={position}> + <Label + label="label" + title="Text Component" + icon="CODE" + > + Content of the Text component. + </Label> + + <Label> + <ButtonGroup> + <Button + label='left' + selected={position === 'left'} + onClick={() => setPosition('left')} /> + <Button + label='top' + selected={position === 'top'} + onClick={() => setPosition('top')} /> + <Button + label='right' + selected={position === 'right'} + onClick={() => setPosition('right')} /> + <Button + label='bottom' + selected={position === 'bottom'} + onClick={() => setPosition('bottom')} /> + </ButtonGroup> + <Button + icon='SIDEBAR' + title="This button is always selected when the panel is visible." + selected={visible} + onClick={() => flipVisible()} /> + </Label> + + <Label + label="List icon" + title="Text Component" + > + <Icon id="WARNING"/> + <Icon id="SETTINGS"/> + <Icon id="RELOAD"/> + <Icon id="DOWNLOAD"/> + <Icon id="LOCK"/> + </Label> + + <Label + label="Add/remove element" + title="Text Component" + > + <Button + icon='PLUS' + title="This button add an element t the end of the list." + onClick={() => setList((elt) => { + const newId = elt.list.length + 1; + const newChild = "item "+newId; + elt.list.push({ + id: newId, children: <div>{newChild}</div> + }); + return { flip: !elt.flip, list: elt.list }; + } + )} + /> + <Button + icon='MINUS' + title="This button remove the last element of the list." + onClick={() => setList((elt) => { + elt.list.pop(); + return { flip: !elt.flip, list: elt.list }; + } + )} + /> + </Label> + <ListElement> + { list.list.map((elt, k) => + <Element + key={k} + selected={selected === elt.id} + onSelection={() => setSelected(elt.id)} + > + {elt.children} + </Element> + ) + } + </ListElement> + </Panel> + </div> + </> + ); +} + +/* -------------------------------------------------------------------------- */ +/* --- Sandbox --- */ +/* -------------------------------------------------------------------------- */ + +registerSandbox({ + id: 'sandbox.panel', + label: 'Panel', + children: <UsePanel />, +}); + +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/sandbox/style.css b/ivette/src/sandbox/style.css new file mode 100644 index 0000000000000000000000000000000000000000..6263e25d48a2fd23a96c0c5432952ade1cc67abf --- /dev/null +++ b/ivette/src/sandbox/style.css @@ -0,0 +1,19 @@ +/* -------------------------------------------------------------------------- */ +/* --- Sandbox Testing CSS --- */ +/* -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- Sandbox Panel --- */ +/* -------------------------------------------------------------------------- */ +.sandbox-panel-button-position-right { + transform: rotate(0); +} +.sandbox-panel-button-position-bottom { + transform: rotate(90deg); +} +.sandbox-panel-button-position-left { + transform: rotate(180deg); +} +.sandbox-panel-button-position-top { + transform: rotate(270deg); +}