Functional v2

This commit is contained in:
Martino Ferrari
2026-05-18 23:28:29 +02:00
parent f06b948c0f
commit bdcdb39d21
3 changed files with 78 additions and 45 deletions
+55 -14
View File
@@ -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;
}
+22 -30
View File
@@ -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;
+1 -1
View File
@@ -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