From b115fc8434592388875d65d860fe59de3f27e46d Mon Sep 17 00:00:00 2001
From: Maxime Jacquemin <maxime2.jacquemin@gmail.com>
Date: Mon, 28 Mar 2022 12:00:56 +0200
Subject: [PATCH] [ivette] Horizontal scroll on ctrl+scroll

---
 ivette/src/frama-c/plugins/eva/style.css      | 31 +++++++++------
 ivette/src/frama-c/plugins/eva/valuetable.tsx | 38 +++++++++++++------
 2 files changed, 46 insertions(+), 23 deletions(-)

diff --git a/ivette/src/frama-c/plugins/eva/style.css b/ivette/src/frama-c/plugins/eva/style.css
index 9e2bcec67e9..e9748dac2d9 100644
--- a/ivette/src/frama-c/plugins/eva/style.css
+++ b/ivette/src/frama-c/plugins/eva/style.css
@@ -134,7 +134,7 @@
 /* -------------------------------------------------------------------------- */
 
 .eva-table {
-  border-left: 0px;
+  border: 0px;
   border-spacing: 0px;
 }
 
@@ -145,7 +145,7 @@
 }
 
 .eva-table tr th {
-  border-left: thin solid var(--border);
+  border-right: thin solid var(--border);
   border-top: thin solid var(--border);
   border-bottom: thin solid var(--border);
   height: 22px;
@@ -153,10 +153,6 @@
   max-height: 22px;
 }
 
-.eva-table tr th:last-child {
-  border-right: thin solid var(--border);
-}
-
 .eva-table tr:nth-child(2n) {
   background-color: var(--background-alterning-odd);
 }
@@ -169,10 +165,6 @@
   border-bottom: thin solid var(--border);
 }
 
-.eva-table tr td:last-child {
-  border-right: thin solid var(--border);
-}
-
 .eva-table-container {
   overflow: hidden;
   position: relative;
@@ -230,6 +222,7 @@
   text-align: center;
   color: var(--info-text);
   border-left: thin solid var(--border);
+  border-right: thin solid var(--border);
   border-bottom: thin solid var(--border);
   padding: 2px;
 }
@@ -246,7 +239,7 @@ tr:first-of-type > .eva-table-callsite-box {
   position: relative;
   border: 0px;
   padding: 2px 3px 2px 3px;
-  border-left: thin solid var(--border);
+  border-right: thin solid var(--border);
   border-bottom: thin solid var(--border);
   min-width: 144px;
   font-family: Andale Mono, monospace;
@@ -275,6 +268,22 @@ tr:first-of-type > .eva-table-callsite-box {
   z-index: +1;
 }
 
+.eva-table-callsite-box.eva-table-descr-sticky {
+  left: 0px;
+  z-index: +2;
+}
+
+.eva-table-callsite-box.eva-table-header-sticky {
+  left: 0px;
+  z-index: +2;
+}
+
+.eva-table-value-sticky {
+  position: sticky;
+  left: 0px;
+  z-index: +1;
+}
+
 .eva-table-values-alarms {
   min-width: 130px;
 }
diff --git a/ivette/src/frama-c/plugins/eva/valuetable.tsx b/ivette/src/frama-c/plugins/eva/valuetable.tsx
index 48dd72a5638..1a48e691f95 100644
--- a/ivette/src/frama-c/plugins/eva/valuetable.tsx
+++ b/ivette/src/frama-c/plugins/eva/valuetable.tsx
@@ -104,22 +104,25 @@ function makeStackTitle(calls: Callsite[]): string {
 
 async function CallsiteCell(props: CallsiteCellProps): Promise<JSX.Element> {
   const { callstack, index, getCallsites, selectedClass = '' } = props;
-  const stickyHd = callstack === 'Header' ? 'eva-table-header-sticky' : '';
-  const stickyDc = callstack === 'Descr' ? 'eva-table-descr-sticky' : '';
-  const sticky = classes(stickyHd, stickyDc);
-  const cl = classes('eva-table-callsite-box', selectedClass, sticky);
-  function Res(props: { text: string, title: string }): JSX.Element {
-    return <td className={cl} title={props.title}>{props.text}</td>;
-  }
+  const baseClasses = classes('eva-table-callsite-box', selectedClass);
   switch (callstack) {
-    case 'Header': return <Res text='#' title='Corresponding callstack'/>;
-    case 'Descr': return <Res text='D' title='Column description'/>;
+    case 'Header': {
+      const cls = classes(baseClasses, 'eva-table-header-sticky');
+      const title = 'Corresponding callstack';
+      return <td className={cls} title={title}>{'#'}</td>;
+    }
+    case 'Descr': {
+      const cls = classes(baseClasses, 'eva-table-descr-sticky');
+      const title = 'Column description';
+      return <td className={cls} title={title}>{'D'}</td>;
+    }
     default: {
+      const cls = classes(baseClasses, 'eva-table-value-sticky');
       const callsites = await getCallsites(callstack);
       const isSummary = callstack === 'Summary';
       const infos = isSummary ? 'Summary' : makeStackTitle(callsites);
       const text = isSummary ? '∑' : (index ? index.toString() : '0');
-      return <Res text={text} title={infos}/>;
+      return <td className={cls} title={infos}>{text}</td>;
     }
   }
 }
@@ -579,7 +582,8 @@ async function FunctionSection(props: FunctionProps): Promise<JSX.Element> {
     const call = await CallsiteCell({ index, callstack, ...callProps });
     const values = await Promise.all(builders.map((b) => b(callstack)));
     return (
-      <tr key={callstack} onClick={onClick(callstack)}>{call}
+      <tr key={callstack} onClick={onClick(callstack)}>
+        {call}
         {React.Children.toArray(values)}
       </tr>
     );
@@ -598,6 +602,12 @@ async function FunctionSection(props: FunctionProps): Promise<JSX.Element> {
     }
   };
 
+  const onWheel = (event: React.WheelEvent): void => {
+    const tgt = event.currentTarget;
+    const left = event.deltaY * tgt.scrollWidth / tgt.scrollHeight;
+    if (event.ctrlKey) tgt.scrollLeft += left;
+  };
+
   return (
     <>
       <Hpack className="eva-function">
@@ -623,7 +633,11 @@ async function FunctionSection(props: FunctionProps): Promise<JSX.Element> {
           onClick={close}
         />
       </Hpack>
-      <div onScroll={onScroll} className='eva-table-container'>
+      <div
+        onWheel={onWheel}
+        onScroll={onScroll}
+        className='eva-table-container'
+      >
         <table className='eva-table' style={{ display: displayTable }}>
           <tbody>
             <tr>
-- 
GitLab