diff --git a/ivette/src/dome/renderer/frame/sidebars.tsx b/ivette/src/dome/renderer/frame/sidebars.tsx index 88901d06f2dd2d5b32c5ab561f944eb99991d2f0..209503ba5b425d0b8f78cd622e0a2d8acb2b1a2e 100644 --- a/ivette/src/dome/renderer/frame/sidebars.tsx +++ b/ivette/src/dome/renderer/frame/sidebars.tsx @@ -34,6 +34,8 @@ import { useFlipSettings } from 'dome'; import { Badge } from 'dome/controls/icons'; import { Label } from 'dome/controls/labels'; import { classes } from 'dome/misc/utils'; +import { Hbox, Hfill } from 'dome/layout/boxes'; +import { IconButton, IconButtonProps } from 'dome/controls/buttons'; import './style.css'; @@ -70,12 +72,12 @@ export function SideBar(props: SideBarProps): JSX.Element { export type BadgeElt = undefined | null | string | number | React.ReactNode; export type Badges = BadgeElt | BadgeElt[]; -const makeBadgeElt = (elt: BadgeElt): React.ReactNode => { +const makeBadgeElt = (elt: BadgeElt, index: number): React.ReactNode => { if (elt === undefined || elt === null) return null; switch (typeof (elt)) { case 'number': case 'string': - return <Badge value={elt} />; + return <Badge value={elt} key={`item#${index}`} />; default: return elt; } @@ -84,27 +86,9 @@ const makeBadgeElt = (elt: BadgeElt): React.ReactNode => { const makeBadge = (elt: Badges): React.ReactNode => { if (Array.isArray(elt)) return elt.map(makeBadgeElt); - return makeBadgeElt(elt); + return makeBadgeElt(elt, 0); }; -// -------------------------------------------------------------------------- -// --- SideBar Section Hide/Show Button -// -------------------------------------------------------------------------- - -interface HideShowProps { - onClick: () => void; - visible: boolean; -} - -const HideShow = (props: HideShowProps): JSX.Element => ( - <label - className="dome-xSideBarSection-hideshow dome-text-label" - onClick={props.onClick} - > - {props.visible ? 'Hide' : 'Show'} - </label> -); - // -------------------------------------------------------------------------- // --- SideBar Section // -------------------------------------------------------------------------- @@ -126,10 +110,12 @@ export interface SectionProps { disabled?: boolean; /** Badge summary (only visible when folded). */ summary?: Badges; - /** Right-click callback. */ - onContextMenu?: () => void; + /** Additional controls, (only visible when unfolded). */ + rightButtonProps?: IconButtonProps; /** Section contents. */ children?: React.ReactNode; + /** Additionnal CSS class. */ + className?: string; } /** @@ -143,32 +129,34 @@ export interface SectionProps { Sections with no items are not displayed. */ export function Section(props: SectionProps): JSX.Element | null { - - const [state, flipState] = useFlipSettings( - props.settings, - props.defaultUnfold, - ); + const { settings, defaultUnfold, unfold } = props; + const [state, flipState] = useFlipSettings(settings, defaultUnfold); + const icon = state ? 'TRIANGLE.DOWN' : 'TRIANGLE.RIGHT'; const { enabled = true, disabled = false, children } = props; - if (disabled || !enabled || React.Children.count(children) === 0) - return null; - const { unfold } = props; - const foldable = unfold === undefined; + if (disabled || !enabled || React.Children.count(children) === 0) return null; + const visible = unfold ?? state; const maxHeight = visible ? 'max-content' : 0; + const { rightButtonProps: iconProps } = props; + const className = `dome-xSideBarSection-filterButton ${iconProps?.className}`; + const rightButton = + iconProps ? <IconButton {...iconProps} className={className}/> : undefined; return ( - <div className="dome-xSideBarSection"> - <div - className="dome-xSideBarSection-title dome-color-frame" - title={props.title} - onContextMenu={props.onContextMenu} - > - <Label label={props.label} /> - {!visible && makeBadge(props.summary)} - {foldable && <HideShow visible={visible} onClick={flipState} />} - </div> - <div className="dome-xSideBarSection-content" style={{ maxHeight }}> + <div className={`dome-xSideBarSection ${props.className}`}> + <Hbox> + <Label + className='dome-xSideBarSection-title dome-color-frame' + title={props.title} + label={props.label} + icon={icon} + onClick={flipState} + /> + <Hfill /> + {visible ? rightButton : makeBadge(props.summary)} + </Hbox> + <div className='dome-xSideBarSection-content' style={{ maxHeight }}> {children} </div> </div> diff --git a/ivette/src/dome/renderer/frame/style.css b/ivette/src/dome/renderer/frame/style.css index 69a6511a02a360c4fb9b3d259e716a37491e8f6c..266bed0e505b00875e050e9834c43bf5ada4b9d7 100644 --- a/ivette/src/dome/renderer/frame/style.css +++ b/ivette/src/dome/renderer/frame/style.css @@ -105,12 +105,13 @@ .dome-xSideBarSection { display: block ; padding: 0px ; + padding-top: 3px; } .dome-xSideBarSection-title { position: sticky ; top: 0px ; - padding-top: 3px ; + padding-top: 0px; padding-bottom: 0px ; padding-left: 3px ; padding-right: 2px ; @@ -130,6 +131,11 @@ flex: 0 0 ; } +.dome-xSideBarSection-filterButton { + margin: 0px; + padding: 0px; +} + .dome-xSideBarSection-hideshow { flex: 0 0 ; margin: 1px ; @@ -149,6 +155,23 @@ transition: max-height 250ms ease-in-out ; } +.dome-xSideBarSection-content > .dome-xBoxButton { + margin-left: 20px; + margin-right: 10px; + margin-top: 7px; + color: var(--info-text); + fill: var(--info-text); + display: flex; +} + +.dome-xSideBarSection-info { + color: var(--info-text-discrete); + font-style: italic; + padding-left: 20px; + padding-right: 10px; + display: block; +} + /* -------------------------------------------------------------------------- */ /* --- SideBar Items --- */ /* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/kernel/Globals.tsx b/ivette/src/frama-c/kernel/Globals.tsx index 5faef62df82f6f847ae62dadbbad2552aa8d9f9f..de8ef1cabd5c4f9c7d89c0d5c80fef5a340c64a1 100644 --- a/ivette/src/frama-c/kernel/Globals.tsx +++ b/ivette/src/frama-c/kernel/Globals.tsx @@ -29,6 +29,7 @@ import * as Dome from 'dome'; import { classes } from 'dome/misc/utils'; import { alpha } from 'dome/data/compare'; import { Section, Item } from 'dome/frame/sidebars'; +import { Button } from 'dome/controls/buttons'; import * as Ivette from 'ivette'; import * as States from 'frama-c/states'; @@ -180,21 +181,48 @@ export default function Globals(): JSX.Element { const nTotal = fcts.length; const nFilter = filtered.length; const title = `Functions ${nFilter} / ${nTotal}`; + + const filterButtonProps = { + icon: 'TUNINGS', + title: `Functions filtering options (${nFilter} / ${nTotal})`, + onClick: onContextMenu, + }; + + const filteredFunctions = + filtered.map((fct) => ( + <FctItem + key={fct.name} + fct={fct} + current={current} + onSelection={onSelection} + /> + )); + + const noFunction = + <div className='dome-xSideBarSection-content'> + <label className='dome-xSideBarSection-info'> + {'There is no function to display.'} + </label> + </div>; + + const allFiltered = + <div className='dome-xSideBarSection-content'> + <label className='dome-xSideBarSection-info'> + {'All functions are filtered. Try adjusting function filters.'} + </label> + <Button {...filterButtonProps} label='Functions filters' /> + </div>; + return ( <Section label="Functions" title={title} - onContextMenu={onContextMenu} defaultUnfold + rightButtonProps={filterButtonProps} + summary={[nFilter]} + className='globals-function-section' > - {filtered.map((fct) => ( - <FctItem - key={fct.name} - fct={fct} - current={current} - onSelection={onSelection} - /> - ))} + {nFilter > 0 ? filteredFunctions : nTotal > 0 ? allFiltered : noFunction} </Section> ); diff --git a/ivette/src/frama-c/kernel/style.css b/ivette/src/frama-c/kernel/style.css index 1a6ef94f45373e357608c88fe24c0c57637b441c..be30b0d1581daf1edff87853cd07e314138b2694 100644 --- a/ivette/src/frama-c/kernel/style.css +++ b/ivette/src/frama-c/kernel/style.css @@ -49,6 +49,10 @@ color: #aaa; } +.globals-function-section { + padding-right: 3px; +} + /* -------------------------------------------------------------------------- */ /* --- Globals --- */ /* -------------------------------------------------------------------------- */