From f826c0e3cc62b6faeb73d9fc462be084559f736f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Thu, 17 Dec 2020 16:56:08 +0100
Subject: [PATCH] [ivette/eva] zoom cell

---
 ivette/src/frama-c/eva/Values.tsx | 60 +++++++++++++++++++++++++------
 ivette/src/frama-c/eva/layout.ts  | 13 ++++---
 ivette/src/frama-c/eva/probes.ts  |  9 +++++
 ivette/src/frama-c/eva/style.css  |  1 +
 4 files changed, 67 insertions(+), 16 deletions(-)

diff --git a/ivette/src/frama-c/eva/Values.tsx b/ivette/src/frama-c/eva/Values.tsx
index 9bcc4f76602..510d9ee8986 100644
--- a/ivette/src/frama-c/eva/Values.tsx
+++ b/ivette/src/frama-c/eva/Values.tsx
@@ -50,29 +50,39 @@ function ProbeEditor() {
   const label = probe?.label;
   const code = probe?.code;
   const rank = probe?.rank;
-  const byCS = probe?.byCallstacks;
   const stmt = rank ? `@S${rank}` : undefined;
+  const byCS = probe?.byCallstacks;
   const stacks = model.getStacks(probe).length;
+  const stackable = byCS || stacks > 1;
   const { cols, rows } = sizeof(code);
   const width = WSIZER.dimension(cols) + 4;
   const height = HSIZER.dimension(rows) + 3;
   const visible = probe ? !!code : model.getRowCount() > 0;
+  const zoomed = probe?.zoomed;
+  const zoomable = probe?.zoomable;
   const visibility = visible ? 'visible' : 'hidden';
   return (
     <Hpack style={{ visibility }} className="eva-probe">
       <Label className="eva-probe-label">{label && `${label}:`}</Label>
-      <div style={{ width, height }} className="eva-probe-code">
+      <div style={{ minWidth: width, height }} className="eva-probe-code">
         <SizedArea cols={cols} rows={rows}>{code}</SizedArea>
       </div>
       <Code className="eva-probe-stmt">{stmt}</Code>
       <IconButton
         className="eva-probe-button"
-        visible={byCS || stacks > 0}
+        display={stackable}
         selected={byCS}
         icon="ITEMS.LIST"
         title={`Details by callstack (${stacks})`}
         onClick={() => { if (probe) probe.setByCallstacks(!byCS); }}
       />
+      <IconButton
+        className="eva-probe-button"
+        display={zoomable}
+        selected={zoomed}
+        icon="SEARCH"
+        onClick={() => { if (probe) probe.setZoomed(!zoomed); }}
+      />
       <IconButton
         className="eva-probe-button"
         kind={transient ? 'selected' : 'warning'}
@@ -94,7 +104,7 @@ interface TableCellProps {
   row: Row;
 }
 
-const CELLPADDING = 4;
+const CELLPADDING = 12;
 
 function TableCell(props: TableCellProps) {
   const model = useModel();
@@ -118,8 +128,10 @@ function TableCell(props: TableCellProps) {
         const atpoint = rank && (
           <span className="eva-probe-stmt">@S{rank}</span>
         );
+        const text =
+          label ? <span className="dome-text-label">{label}</span> : code;
         contents = (
-          <span className="dome-text-label">{label ?? code}{atpoint}</span>
+          <span className="dome-text-code">{text}{atpoint}</span>
         );
       }
       break;
@@ -158,11 +170,15 @@ function TableCell(props: TableCellProps) {
       setSelection({ location });
     }
   };
+  const onDoubleClick = () => {
+    if (probe && probe.zoomable) probe.setZoomed(!probe.zoomed);
+  };
   return (
     <div
       className={className}
       style={style}
       onClick={onClick}
+      onDoubleClick={onDoubleClick}
     >
       {contents}
     </div>
@@ -190,6 +206,7 @@ function TableRow(props: TableRowProps) {
       {sk === undefined ? '#' : `${1 + sk}`}
     </div>
   );
+  const style: React.CSSProperties = { left: row.stacks ? 0 : 12 };
   const contents = probes.map((probe) => (
     <TableCell
       key={probe.marker}
@@ -199,7 +216,7 @@ function TableRow(props: TableRowProps) {
   ));
   return (
     <Hpack className={className} style={props.style}>
-      <div className="eva-row">
+      <div style={style} className="eva-row">
         {header}
         {contents}
       </div>
@@ -217,9 +234,13 @@ interface Dimension {
   height: number;
 }
 
-function ValuesPanel(props: Dimension) {
+interface ValuesPanelProps extends Dimension {
+  zoom: number;
+}
+
+function ValuesPanel(props: ValuesPanelProps) {
   const model = useModel();
-  const { width, height } = props;
+  const { zoom, width, height } = props;
   // --- reset line cache
   const listRef = React.useRef<VariableSizeList>(null);
   Dome.useEvent(model.laidout, () => {
@@ -239,7 +260,7 @@ function ValuesPanel(props: Dimension) {
   const [selection] = States.useSelection();
   React.useEffect(() => {
     const target = Ast.jMarker(selection?.current?.marker);
-    model.setLayout({ margin, target });
+    model.setLayout({ zoom, margin, target });
   });
   // --- render list
   return (
@@ -263,14 +284,31 @@ function ValuesPanel(props: Dimension) {
 // --------------------------------------------------------------------------
 
 function ValuesComponent() {
+  const [zoom, setZoom] = Dome.useNumberSettings('eva-zoom-factor', 0);
   return (
     <>
-      <TitleBar />
+      <TitleBar>
+        <IconButton
+          enabled={zoom > 0}
+          icon="ZOOM.OUT"
+          onClick={() => setZoom(zoom - 1)}
+        />
+        <IconButton
+          enabled={zoom < 20}
+          icon="ZOOM.IN"
+          onClick={() => setZoom(zoom + 1)}
+        />
+      </TitleBar>
       <Vfill>
         <ProbeEditor />
         <Vfill>
           <AutoSizer>
-            {(dim: Dimension) => <ValuesPanel {...dim} />}
+            {(dim: Dimension) => (
+              <ValuesPanel
+                zoom={zoom}
+                {...dim}
+              />
+            )}
           </AutoSizer>
         </Vfill>
       </Vfill>
diff --git a/ivette/src/frama-c/eva/layout.ts b/ivette/src/frama-c/eva/layout.ts
index 92b6a71667e..d31e9c7f53a 100644
--- a/ivette/src/frama-c/eva/layout.ts
+++ b/ivette/src/frama-c/eva/layout.ts
@@ -5,7 +5,7 @@
 import { callstack } from 'frama-c/api/plugins/eva/values';
 import { Probe } from './probes';
 import { StacksCache } from './stacks';
-import { Size, EMPTY, addH, ValueCache } from './cells';
+import { Size, EMPTY, leq, addH, ValueCache } from './cells';
 
 export interface LayoutProps {
   zoom?: number;
@@ -63,16 +63,19 @@ export class LayoutEngine {
   private buffer: Probe[] = [];
   private rows: Row[] = [];
 
-  crop(s: Size): Size {
+  crop(zoomed: boolean, s: Size): Size {
+    const cols = zoomed ? s.cols : Math.min(s.cols, this.hcrop);
+    const rows = zoomed ? s.rows : Math.min(s.rows, this.vcrop);
     return {
-      cols: Math.max(HCROP, Math.min(s.cols, this.hcrop)),
-      rows: Math.max(VCROP, Math.min(s.rows, this.vcrop)),
+      cols: Math.max(HCROP, cols),
+      rows: Math.max(VCROP, rows),
     };
   }
 
   push(p: Probe) {
     const probeSize = this.values.getProbeSize(p.marker);
-    const s = this.crop(probeSize);
+    const s = this.crop(p.zoomed, probeSize);
+    p.zoomable = p.zoomed || !leq(probeSize, s);
     p.minCols = s.cols;
     p.maxCols = Math.max(p.minCols, probeSize.cols);
     const stmt = p.byCallstacks ? p.stmt : undefined;
diff --git a/ivette/src/frama-c/eva/probes.ts b/ivette/src/frama-c/eva/probes.ts
index 75f326d4bb5..bd8a561d090 100644
--- a/ivette/src/frama-c/eva/probes.ts
+++ b/ivette/src/frama-c/eva/probes.ts
@@ -54,6 +54,8 @@ export class Probe {
   minCols: number = LabelSize;
   maxCols: number = LabelSize;
   byCallstacks = false;
+  zoomed = false;
+  zoomable = false;
 
   constructor(state: StateCallbacks, marker: Ast.marker) {
     this.marker = marker;
@@ -106,6 +108,13 @@ export class Probe {
     }
   }
 
+  setZoomed(zoomed: boolean) {
+    if (zoomed !== this.zoomed) {
+      this.zoomed = zoomed;
+      this.state.forceLayout();
+    }
+  }
+
   // --------------------------------------------------------------------------
   // --- Ordering
   // --------------------------------------------------------------------------
diff --git a/ivette/src/frama-c/eva/style.css b/ivette/src/frama-c/eva/style.css
index dea6379e551..738cfe2dc9a 100644
--- a/ivette/src/frama-c/eva/style.css
+++ b/ivette/src/frama-c/eva/style.css
@@ -57,6 +57,7 @@
 
 .eva-row {
     display: flex;
+    position: relative;
     flex: 0 1 auto;
     height: 100%;
     border-bottom: thin solid black;
-- 
GitLab