From bdcdb39d216fab724a14ce6674d009178562864c Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Mon, 18 May 2026 23:28:29 +0200 Subject: [PATCH] Functional v2 --- Client/WebUI/static/app.js | 69 ++++++++++++++++++++++++++++------- Client/WebUI/static/style.css | 52 +++++++++++--------------- Test/MARTeApp/TestApp.cfg | 2 +- 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/Client/WebUI/static/app.js b/Client/WebUI/static/app.js index 25417ff..c3b2c12 100644 --- a/Client/WebUI/static/app.js +++ b/Client/WebUI/static/app.js @@ -187,7 +187,9 @@ function checkTrigger(sigs) { } } function fireTrigger(t) { - trig.armed=false; trig.collecting=true; trig.trigTime=t; trig.snapshot=null; + // Clear the previous snapshot here (not in trigArm) so the last waveform + // and trigger marker stay visible while the trigger is rearmed and waiting. + trig.snapshot=null; trig.armed=false; trig.collecting=true; trig.trigTime=t; updateTrigStatusBadge('waiting'); setAllCardsCollecting(true); } function finaliseTriggerCapture() { @@ -214,7 +216,10 @@ function finaliseTriggerCapture() { setTimeout(() => { if (trig.enabled && trig.mode==='normal' && !trig.stopped) trigArm(); }, 200); } function trigArm() { - trig.snapshot=null; trig.armed=true; trig.collecting=false; trig.prevVal=null; + // Do NOT clear trig.snapshot here — keep the last waveform and trigger marker + // visible while waiting for the next event. fireTrigger() clears it when a new + // trigger actually fires. + trig.armed=true; trig.collecting=false; trig.prevVal=null; showRearmBtn(false); updateTrigStatusBadge('armed'); cursors.tA = null; cursors.tB = null; updateCursorReadout(); updateCursorBtnVisibility(); @@ -380,6 +385,30 @@ function fmtTrigTick(u, vals) { }); } +// Draw a vertical marker line at x=0 (the trigger event) in trigger mode. +function drawTriggerMarker(u) { + if (!trig.enabled || !trig.snapshot) return; + const { ctx, bbox } = u; + if (!bbox) return; + const x = u.valToPos(0, 'x', true); + if (x < bbox.left || x > bbox.left + bbox.width) return; + const px = Math.round(x); + ctx.save(); + ctx.strokeStyle = 'rgba(203,166,247,0.9)'; + ctx.lineWidth = 1.5; + ctx.setLineDash([]); + ctx.beginPath(); + ctx.moveTo(px, bbox.top); + ctx.lineTo(px, bbox.top + bbox.height); + ctx.stroke(); + // Small "T" label + ctx.fillStyle = 'rgba(203,166,247,0.9)'; + ctx.font = 'bold 10px monospace'; + ctx.textBaseline = 'top'; + ctx.fillText('T', px + 3, bbox.top + 2); + ctx.restore(); +} + // Draw custom cursor A/B lines onto the canvas (called from draw hook) function drawCursorLines(u) { const { ctx, bbox } = u; @@ -436,13 +465,13 @@ function makeUPlotOpts(p, inTrigMode) { series: seriesArr, axes: [ { stroke:'#7f849c', grid:{ stroke:'#313244', width:1 }, ticks:{ stroke:'#313244', width:1 }, - values: xVals, size: 40 }, + values: xVals, size: 36, space: 90 }, { stroke:'#7f849c', grid:{ stroke:'#313244', width:1 }, ticks:{ stroke:'#313244', width:1 }, size: 50 }, ], legend: { show: false }, - padding: [4, 8, 0, 0], + padding: [4, 4, 0, 0], hooks: { - draw: [ u => drawCursorLines(u) ], + draw: [ u => { drawCursorLines(u); drawTriggerMarker(u); } ], // Two-hook zoom detection: setSelect flags that the NEXT setScale is user-initiated. // uPlot fires setSelect → then immediately setScale (when drag.setScale:true). // All programmatic setScale calls happen without a preceding setSelect, so the @@ -989,7 +1018,16 @@ document.getElementById('trig-pre').addEventListener('input', e => { trig.prePercent = parseInt(e.target.value, 10); document.getElementById('trig-pre-val').textContent = trig.prePercent + '%'; }); -document.getElementById('trig-mode').addEventListener('change', e => { trig.mode = e.target.value; }); +document.getElementById('trig-mode').addEventListener('change', e => { + trig.mode = e.target.value; + // If a snapshot already exists the trigger has already fired — update button + // visibility immediately so it matches the newly selected mode. + if (trig.snapshot) { + showRearmBtn(trig.mode === 'single'); + showStopBtn(trig.mode === 'normal'); + updateStopBtn(); + } +}); document.getElementById('btn-trig-rearm').addEventListener('click', () => { if (trig.enabled) trigArm(); }); document.getElementById('btn-trig-stop').addEventListener('click', () => { if (!trig.enabled || trig.mode !== 'normal') return; @@ -1076,7 +1114,7 @@ function layoutPlotCount(cls) { // Build a small SVG grid thumbnail for a given cols×rows layout. function layoutSVG(cols, rows) { - const W = 40, H = 28, GAP = 2, PAD = 2; + const W = 28, H = 20, GAP = 1.5, PAD = 1.5; const cw = (W - PAD * 2 - GAP * (cols - 1)) / cols; const ch = (H - PAD * 2 - GAP * (rows - 1)) / rows; let rects = ''; @@ -1119,12 +1157,17 @@ function applyLayout(cls) { deletePlot(removeId); } - // Add missing plots. + // Add missing plots (DOM only — uPlot created below after layout settles). while (plots.length < needed) addPlot(); - setTimeout(() => plots.forEach(p => { - if (p.uplot) p.uplot.setSize({ width: p.div.clientWidth, height: p.div.clientHeight }); - }), 120); + // Recreate all uPlot instances once the CSS grid has sized the cells. + // This also updates axis visibility (which axes show labels depends on grid position). + requestAnimationFrame(() => { + plots.forEach(p => createUPlot(p)); + setTimeout(() => plots.forEach(p => { + if (p.uplot) p.uplot.setSize({ width: p.div.clientWidth, height: p.div.clientHeight }); + }), 60); + }); } function buildLayoutMenu() { @@ -1218,9 +1261,7 @@ function addPlot() { const plotBody = card.querySelector('#pbody-'+id); const p = { id, traces:[], div: plotBody, needsRedraw:false, xRange:null, uplot:null, ro:null }; plots.push(p); - - // Create the uPlot instance (empty) — deferred so DOM has settled - requestAnimationFrame(() => { createUPlot(p); }); + // uPlot creation is handled by applyLayout (batch, after DOM settles). return id; } diff --git a/Client/WebUI/static/style.css b/Client/WebUI/static/style.css index dd695f8..1acadea 100644 --- a/Client/WebUI/static/style.css +++ b/Client/WebUI/static/style.css @@ -191,8 +191,8 @@ input[type=range].trig-range::-webkit-slider-thumb { #main { flex:1; display:flex; flex-direction:column; overflow:hidden; min-width:0; } /* Layout toggle button in topbar */ #btn-layout { - display:flex; align-items:center; gap:6px; - font-size:12px; padding:4px 10px; + display:flex; align-items:center; gap:4px; + font-size:11px; padding:3px 8px; } #btn-layout svg { flex-shrink:0; } #btn-layout span { flex-shrink:0; } @@ -206,10 +206,10 @@ input[type=range].trig-range::-webkit-slider-thumb { } #layout-menu.open { display:grid; } .layout-menu-item { - display:flex; flex-direction:column; align-items:center; gap:5px; - padding:8px 14px; cursor:pointer; + display:flex; flex-direction:column; align-items:center; gap:3px; + padding:5px 10px; cursor:pointer; background:var(--surface0); border:1px solid var(--surface1); border-radius:6px; - color:var(--subtext1); font-size:11px; font-family:monospace; + color:var(--subtext1); font-size:10px; font-family:monospace; transition:background var(--transition),border-color var(--transition),color var(--transition); } .layout-menu-item:hover { background:var(--surface1); border-color:var(--accent); color:var(--accent); } @@ -217,7 +217,8 @@ input[type=range].trig-range::-webkit-slider-thumb { /* ── Plot grid ────────────────────────────────────────────────── */ #plot-grid { - flex:1; min-height:0; display:grid; gap:8px; padding:8px; overflow:hidden; + flex:1; min-height:0; display:grid; gap:0; padding:0; overflow:hidden; + border-top:1px solid var(--surface0); border-left:1px solid var(--surface0); } #plot-grid.l1x1 { grid-template-columns:1fr; grid-template-rows:1fr; } #plot-grid.l2x1 { grid-template-columns:1fr 1fr; grid-template-rows:1fr; } @@ -232,16 +233,25 @@ input[type=range].trig-range::-webkit-slider-thumb { /* ── Plot card ────────────────────────────────────────────────── */ .plot-card { - background:var(--surface0); border:1px solid var(--surface1); - border-radius:var(--radius); display:flex; flex-direction:column; + background:var(--bg); + border-right:1px solid var(--surface0); border-bottom:1px solid var(--surface0); + border-radius:0; display:flex; flex-direction:column; min-height:0; position:relative; overflow:hidden; - transition:border-color var(--transition); } -.plot-card.drag-over { border-color:var(--accent); box-shadow:0 0 0 2px rgba(137,180,250,0.25); } +.plot-card.drag-over { background:rgba(137,180,250,0.04); box-shadow:inset 0 0 0 2px var(--accent); } +/* Header: hidden by default, slides in on hover */ .plot-card-header { + position:absolute; top:0; left:0; right:0; z-index:5; + background:rgba(17,17,27,0.88); backdrop-filter:blur(6px); + border-bottom:1px solid var(--surface1); display:flex; align-items:center; gap:5px; - padding:4px 8px; border-bottom:1px solid var(--surface1); - flex-shrink:0; min-height:30px; max-height:30px; overflow:hidden; + padding:3px 8px; min-height:26px; overflow:hidden; + opacity:0; transform:translateY(-100%); + transition:opacity 0.12s ease, transform 0.12s ease; + pointer-events:none; +} +.plot-card:hover .plot-card-header { + opacity:1; transform:translateY(0); pointer-events:auto; } .plot-title { font-size:11px; color:var(--subtext1); font-weight:600; @@ -263,29 +273,11 @@ input[type=range].trig-range::-webkit-slider-thumb { transition:color var(--transition); } .sig-badge-x:hover { color:var(--red); } -.plot-icons { display:flex; gap:2px; flex-shrink:0; } -.plot-icon-btn { - background:none; border:none; cursor:pointer; color:var(--subtext0); - font-size:13px; line-height:1; padding:2px 4px; border-radius:4px; - transition:color var(--transition),background var(--transition); -} -.plot-icon-btn:hover { color:var(--accent); background:var(--surface1); } -.plot-icon-btn.danger:hover { color:var(--red); } .plot-body { flex:1; position:relative; min-height:0; overflow:hidden; } .drop-hint { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:var(--overlay0); font-size:13px; pointer-events:none; } -.pause-overlay { - position:absolute; inset:0; display:none; - align-items:center; justify-content:center; pointer-events:none; z-index:10; -} -.plot-card.paused .pause-overlay { display:flex; } -.pause-overlay-text { - background:rgba(49,50,68,0.82); color:var(--yellow); - font-size:11px; font-weight:600; padding:4px 12px; border-radius:20px; - border:1px solid var(--yellow); -} .trig-collect-overlay { position:absolute; inset:0; display:none; align-items:center; justify-content:center; pointer-events:none; z-index:10; diff --git a/Test/MARTeApp/TestApp.cfg b/Test/MARTeApp/TestApp.cfg index f9286bc..8623a1f 100644 --- a/Test/MARTeApp/TestApp.cfg +++ b/Test/MARTeApp/TestApp.cfg @@ -110,7 +110,7 @@ $TestApp = { // ── 1 kHz sine burst – channel 2 (phase-shifted by π/2) ────────────── +SineGAM4 = { Class = SineArrayGAM - Frequency = 1000.0 + Frequency = 10000.0 Amplitude = 0.5 Phase = 1.5708 Offset = 0.0