From c8c08eed4815fe208d229887570f12c58a5052e0 Mon Sep 17 00:00:00 2001 From: 5o1 Date: Sat, 18 Jul 2026 02:45:57 +0800 Subject: [PATCH] Add text scale control to webview --- src/editor/webview/assets/drawing.js | 77 ++++++++++++++++++++----- src/editor/webview/assets/kspace.js | 10 ++-- src/editor/webview/assets/state.js | 55 ++++++++++++++++-- src/editor/webview/assets/styles.css | 38 ++++++------ src/editor/webview/assets/template.html | 1 + 5 files changed, 137 insertions(+), 44 deletions(-) diff --git a/src/editor/webview/assets/drawing.js b/src/editor/webview/assets/drawing.js index 455f55b..ebc2416 100644 --- a/src/editor/webview/assets/drawing.js +++ b/src/editor/webview/assets/drawing.js @@ -28,7 +28,7 @@ function drawCursorOverlay(){ var color=getComputedStyle(document.body).getPropertyValue('--cr').trim(); moctx.strokeStyle=color;moctx.lineWidth=0.8;moctx.setLineDash([4,3]); moctx.beginPath();moctx.moveTo(cx,M.t);moctx.lineTo(cx,h-M.b);moctx.stroke();moctx.setLineDash([]); - moctx.fillStyle=color;moctx.font='10px monospace';moctx.textAlign='center'; + moctx.fillStyle=color;moctx.font=fontSpec(10,'monospace');moctx.textAlign='center'; var label=fmtT(timeConv(cursorT)),tw=moctx.measureText(label).width; moctx.fillText(label,Math.max(M.l+tw/2+4,Math.min(cx,w-M.r-tw/2-4)),M.t-1); } @@ -45,7 +45,7 @@ function drawBlockBounds(w,h,vs,ve,s){ for(var i=range.start;iw-M.r)continue; ctx.beginPath();ctx.moveTo(x,M.t);ctx.lineTo(x,h-M.b);ctx.stroke(); - ctx.fillStyle=s.getPropertyValue('--ax').trim();ctx.font='9px monospace';ctx.textAlign='center'; + ctx.fillStyle=s.getPropertyValue('--ax').trim();ctx.font=fontSpec(9,'monospace');ctx.textAlign='center'; ctx.fillText('#'+BL[i].i,x,M.t-2); } ctx.setLineDash([]); @@ -68,6 +68,53 @@ function channelRange(ci){ return 1; } function fmtPhase(v){if(Math.abs(v-6.28318)<1e-3)return'2\u03c0';if(Math.abs(v-3.14159)<1e-3)return'\u03c0';if(v>=1)return v.toFixed(2);if(v>=0.01)return v.toFixed(3);return v.toExponential(1);} +function axisTickLabels(ci){ + if(ci===0)return [fmtAmp(channelRange(0))+'Hz','0']; + if(ci===1){var ph=channelRange(1);return [fmtPhase(ph),fmtPhase(ph/2),'0'];} + if(ci>=2&&ci<=4)return ['\u00b1'+fmtAmp(gradConv(channelRange(ci)))+gradUnitStr(),'0']; + if(ci===5)return ['on']; + if(ci===6)return ['ch']; + if(ci===7)return [fmtAmp(channelRange(7))+'%','0']; + if(ci>=8&&ci<=10)return ['\u00b1'+fmtAmp(channelRange(ci))+'s/m','0']; + return []; +} +function axisTickLabelOffset(){return Math.round(14*textScaleFactor());} +function axisUnitLabelOffset(){return Math.round(4*textScaleFactor());} +function canvasTextWidth(font,text){ + ctx.save();ctx.font=font; + var width=ctx.measureText(String(text||'')).width; + ctx.restore();return width; +} +function canvasTextDescent(font,text){ + ctx.save();ctx.font=font; + var metrics=ctx.measureText(String(text||'0123456789')); + ctx.restore(); + return metrics.actualBoundingBoxDescent||Math.max(2,scaledPx(2)); +} +function maxCanvasTextWidth(font,labels){ + var max=0; + for(var i=0;i1){ + var neededBottom=Math.ceil(axisTickLabelOffset()+canvasTextDescent(axFont,'000.000')+3); + M.b=Math.max(M.b,neededBottom); + } +} function rowClip(vi,ch,fn){ var w=mc.width/(window.devicePixelRatio||1),top=M.t+vi*ch,bottom=M.t+(vi+1)*ch,rw=Math.max(0,w-M.l-M.r); if(rw<=0||bottom<=top)return; @@ -78,16 +125,16 @@ function rowClip(vi,ch,fn){ /* ── Axes (X time + Y per channel) ────────────────────────────────────── */ function drawAxes(w,h,vs,ve,s){ var narrow=(typeof layoutMode!=='undefined'&&layoutMode==='vertical')||w<600; - var axFont=narrow?'9px monospace':'10px monospace'; - var chFont=narrow?'bold 14px monospace':'bold 18px monospace'; + var axFont=fontSpec(narrow?9:10,'monospace'); + var chFont=fontSpec(narrow?14:18,'monospace','bold'); // X-axis ctx.fillStyle=s.getPropertyValue('--lb').trim();ctx.font=axFont;ctx.textAlign='center'; ctx.strokeStyle=s.getPropertyValue('--ax').trim();ctx.lineWidth=1; ctx.beginPath();ctx.moveTo(M.l,h-M.b);ctx.lineTo(w-M.r,h-M.b);ctx.stroke(); var st=nice((ve-vs)/8),t=Math.floor(vs/st)*st; - while(t<=ve){ctx.fillText(fmtT(timeConv(t)),t2x(t),h-M.b+14);t+=st;} + while(t<=ve){ctx.fillText(fmtT(timeConv(t)),t2x(t),h-M.b+axisTickLabelOffset());t+=st;} ctx.textAlign='right'; - ctx.fillText('time ('+timeUnitStr()+')',w-4,h-M.b+4); + ctx.fillText('time ('+timeUnitStr()+')',w-4,h-M.b+axisUnitLabelOffset()); // Y-axes — one per visible channel var vc=visChannels(),ch=cH(); @@ -105,13 +152,13 @@ function drawAxes(w,h,vs,ve,s){ ctx.fillStyle=s.getPropertyValue('--lb').trim();ctx.font=axFont; var lblX=narrow?4:M.l-12; ctx.textAlign=narrow?'left':'right'; - if(ci===0){ctx.fillText(fmtAmp(channelRange(0))+'Hz',lblX,M.t+vi*ch+12);ctx.fillText('0',lblX,y0+ch/2-2);} - else if(ci===1){var ph=channelRange(1);ctx.fillText(fmtPhase(ph),lblX,M.t+vi*ch+12);ctx.fillText(fmtPhase(ph/2),lblX,y0+4);ctx.fillText('0',lblX,y0+ch/2-2);} - else if(ci>=2&&ci<=4){var d=gradConv(channelRange(ci));ctx.fillText('\u00b1'+fmtAmp(d)+gradUnitStr(),lblX,M.t+vi*ch+12);ctx.fillText('0',lblX,y0+4);} - else if(ci===5){ctx.fillText('on',lblX,M.t+vi*ch+12);} - else if(ci===6){ctx.fillText('ch',lblX,M.t+vi*ch+12);} - else if(ci===7){ctx.fillText(fmtAmp(channelRange(7))+'%',lblX,M.t+vi*ch+12);ctx.fillText('0',lblX,M.t+(vi+1)*ch-4);} - else if(ci>=8&&ci<=10){ctx.fillText('\u00b1'+fmtAmp(channelRange(ci))+'s/m',lblX,M.t+vi*ch+12);ctx.fillText('0',lblX,y0+4);} + var tickLabels=axisTickLabels(ci); + if(ci===0){ctx.fillText(tickLabels[0],lblX,M.t+vi*ch+12);ctx.fillText(tickLabels[1],lblX,y0+ch/2-2);} + else if(ci===1){ctx.fillText(tickLabels[0],lblX,M.t+vi*ch+12);ctx.fillText(tickLabels[1],lblX,y0+4);ctx.fillText(tickLabels[2],lblX,y0+ch/2-2);} + else if(ci>=2&&ci<=4){ctx.fillText(tickLabels[0],lblX,M.t+vi*ch+12);ctx.fillText(tickLabels[1],lblX,y0+4);} + else if(ci===5||ci===6){ctx.fillText(tickLabels[0],lblX,M.t+vi*ch+12);} + else if(ci===7){ctx.fillText(tickLabels[0],lblX,M.t+vi*ch+12);ctx.fillText(tickLabels[1],lblX,M.t+(vi+1)*ch-4);} + else if(ci>=8&&ci<=10){ctx.fillText(tickLabels[0],lblX,M.t+vi*ch+12);ctx.fillText(tickLabels[1],lblX,y0+4);} // Small tick marks ctx.strokeStyle=s.getPropertyValue('--ax').trim();ctx.lineWidth=0.5; @@ -481,7 +528,7 @@ function drawAdcBlocks(start,end,vi,ch,colors,vs,ve){ if(x1-x0>30)labels.push({text:adc.n+'pts',x:(x0+x1)/2}); } if(hasRect){ctx.fillStyle=colors.adf;ctx.fill();ctx.strokeStyle=colors.adc;ctx.lineWidth=1;ctx.stroke();} - ctx.fillStyle=colors.fg;ctx.font='9px monospace';ctx.textAlign='center'; + ctx.fillStyle=colors.fg;ctx.font=fontSpec(9,'monospace');ctx.textAlign='center'; for(var li=0;li1)info+=seqTiming.trCount+' TRs'; } if(info){ - mmCtx.fillStyle='rgba(0,0,0,0.35)';mmCtx.font='9px monospace';mmCtx.textAlign='left'; + mmCtx.fillStyle='rgba(0,0,0,0.35)';mmCtx.font=fontSpec(9,'monospace');mmCtx.textAlign='left'; mmCtx.fillText(info,5,H-3); mmCtx.fillStyle=s.getPropertyValue('--fg').trim();mmCtx.globalAlpha=0.9; mmCtx.fillText(info,4,H-4); @@ -664,11 +709,11 @@ function rs(){ moc.width=r.width*dpr;moc.height=r.height*dpr; moc.style.width=r.width+'px';moc.style.height=r.height+'px'; moctx.setTransform(dpr,0,0,dpr,0,0); - // Adaptive margins for small/narrow screens + // Keep legacy margins by default; expand only when scaled axis labels need room. if(layoutMode==='vertical'||r.width<600){ - M.l=60;M.r=3;M.t=5;M.b=18; + M.l=60;M.r=3;M.t=5;M.b=18;if(typeof applyAdaptiveCanvasMargins==='function')applyAdaptiveCanvasMargins(r.width,true); }else{ - M.l=92;M.r=4;M.t=8;M.b=22; + M.l=92;M.r=4;M.t=8;M.b=22;if(typeof applyAdaptiveCanvasMargins==='function')applyAdaptiveCanvasMargins(r.width,false); } // Resize minimap canvas too var mr=document.getElementById('mmap').getBoundingClientRect(); diff --git a/src/editor/webview/assets/styles.css b/src/editor/webview/assets/styles.css index d8ba7e6..cd106ea 100644 --- a/src/editor/webview/assets/styles.css +++ b/src/editor/webview/assets/styles.css @@ -15,7 +15,7 @@ body.theme-githublight{--bg:#ffffff;--fg:#24292f;--gr:#d0d7de;--rf:#cf222e;--rff body.theme-github{--bg:#0d1117;--fg:#c9d1d9;--gr:#21262d;--rf:#ff7b72;--rff:rgba(255,123,114,.08);--gx:#7ee787;--gy:#79c0ff;--gz:#ffa657;--adc:#a5d6ff;--adf:rgba(165,214,255,.12);--tr:#d2a8ff;--ax:#30363d;--lb:#8b949e;--cr:#ff7b72;--bd:#21262d;--trbg:#161b22} /* ── Layout ── */ *{margin:0;padding:0;box-sizing:border-box} -body{font:13px -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--fg);overflow:hidden;height:100vh;user-select:none;display:flex;flex-direction:column} +body{--font-body:13px;font:var(--font-body) -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--fg);overflow:hidden;height:100vh;user-select:none;display:flex;flex-direction:column} #main{display:flex;flex:1;overflow:hidden} #left{display:flex;flex-direction:column;flex:1;overflow:hidden;min-width:0} #right{width:0;overflow:hidden;position:relative;border-left:1px solid var(--gr);transition:width .25s} @@ -26,28 +26,28 @@ body{font:13px -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background #kg{z-index:0;pointer-events:none} #kc{z-index:1;pointer-events:auto;background:transparent} #rc{position:absolute;top:4px;left:4px;z-index:10;display:flex;flex-direction:column;gap:2px} -#rc button{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:2px 6px;border-radius:3px;cursor:pointer;font-size:10px} +#rc button{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:2px 6px;border-radius:3px;cursor:pointer;font-size:.769em} #rc button:hover{opacity:.75} -#tb{display:flex;align-items:center;gap:6px;padding:5px 10px;background:var(--bg);border-bottom:1px solid var(--gr);font-size:11px;flex-shrink:0;flex-wrap:wrap} -#tb button{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:2px 7px;border-radius:3px;cursor:pointer;font-size:11px} +#tb{display:flex;align-items:center;gap:6px;padding:5px 10px;background:var(--bg);border-bottom:1px solid var(--gr);font-size:.846em;flex-shrink:0;flex-wrap:wrap} +#tb button{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:2px 7px;border-radius:3px;cursor:pointer;font-size:1em} #tb button:hover{opacity:.75} #tb button:disabled{opacity:.45;cursor:not-allowed} -#tb select{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:2px 4px;border-radius:3px;font-size:11px;cursor:pointer} +#tb select{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:2px 4px;border-radius:3px;font-size:1em;cursor:pointer} #tb .sep{width:1px;height:16px;background:var(--ax);margin:0 3px} -#tb .lg{display:flex;gap:7px;row-gap:2px;align-items:center;font-size:11px;flex-wrap:wrap} +#tb .lg{display:flex;gap:7px;row-gap:2px;align-items:center;font-size:1em;flex-wrap:wrap} #tb .li{cursor:pointer;display:flex;align-items:center;gap:3px;padding:1px 4px;border-radius:3px;opacity:1;transition:opacity .15s} #tb .li.off{opacity:.30} #tb .ld{width:10px;height:10px;border-radius:2px;border:1px solid rgba(0,0,0,.15)} -#tb .cur{font:11px monospace;color:var(--cr);margin-left:auto;min-width:300px;text-align:right;white-space:nowrap} -#tb .ulbl{font-size:10px;color:var(--lb);margin:0 1px} -#viewerNotice{display:none;flex-shrink:0;padding:5px 10px;border-bottom:1px solid #b8860b;background:rgba(218,165,32,.16);color:var(--fg);font-size:11px;line-height:1.35;white-space:normal} -#viewerNotice .notice-action{margin-left:8px;background:var(--gr);border:1px solid #b8860b;color:var(--fg);padding:2px 7px;border-radius:3px;cursor:pointer;font-size:11px;white-space:nowrap} +#tb .cur{font:1em monospace;color:var(--cr);margin-left:auto;min-width:300px;text-align:right;white-space:nowrap} +#tb .ulbl{font-size:.909em;color:var(--lb);margin:0 1px} +#viewerNotice{display:none;flex-shrink:0;padding:5px 10px;border-bottom:1px solid #b8860b;background:rgba(218,165,32,.16);color:var(--fg);font-size:.846em;line-height:1.35;white-space:normal} +#viewerNotice .notice-action{margin-left:8px;background:var(--gr);border:1px solid #b8860b;color:var(--fg);padding:2px 7px;border-radius:3px;cursor:pointer;font-size:1em;white-space:nowrap} #kspaceSafetyOverlay{position:fixed;inset:0;background:rgba(0,0,0,.48);z-index:540;display:flex;align-items:center;justify-content:center;padding:16px;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)} #kspaceSafetyCard{width:min(520px,100%);background:var(--bg);border:1px solid #b8860b;border-radius:10px;padding:18px 20px;box-shadow:0 10px 36px rgba(0,0,0,.3)} -#kspaceSafetyTitle{font-weight:700;font-size:15px;margin-bottom:9px;color:var(--fg)} -#kspaceSafetyText{font-size:12px;line-height:1.5;color:var(--fg);white-space:pre-line} +#kspaceSafetyTitle{font-weight:700;font-size:1.154em;margin-bottom:9px;color:var(--fg)} +#kspaceSafetyText{font-size:.923em;line-height:1.5;color:var(--fg);white-space:pre-line} #kspaceSafetyActions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;flex-wrap:wrap} -#kspaceSafetyActions button{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:7px 11px;border-radius:5px;cursor:pointer;font-size:12px} +#kspaceSafetyActions button{background:var(--gr);border:1px solid var(--ax);color:var(--fg);padding:7px 11px;border-radius:5px;cursor:pointer;font-size:.923em} #kspaceSafetyActions #kspaceSafetyProceed{background:#9b1c1c;border-color:#c43b3b;color:#fff} #kspaceSafetyActions button:disabled{opacity:.5;cursor:not-allowed} #cc{flex:1;overflow:hidden;position:relative;cursor:crosshair} @@ -62,15 +62,15 @@ canvas{display:block} #pcard{background:var(--bg);border:1px solid var(--gr);border-radius:10px;padding:24px 32px;min-width:280px;max-width:380px;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.18)} #pspinner{width:32px;height:32px;border:3px solid var(--gr);border-top-color:var(--adc);border-radius:50%;animation:spin .7s linear infinite;margin:0 auto 12px} @keyframes spin{to{transform:rotate(360deg)}} -#ptext{font-size:12px;color:var(--fg);margin-bottom:10px;line-height:1.4} +#ptext{font-size:.923em;color:var(--fg);margin-bottom:10px;line-height:1.4} #pbar2{height:5px;background:var(--gr);border-radius:3px;overflow:hidden;margin:0 0 6px} #pfill2{height:100%;width:0%;background:var(--adc);border-radius:3px;transition:width .4s ease} -#ppct{font-size:11px;color:var(--lb);font-family:monospace} -#tt{position:absolute;pointer-events:none;background:rgba(0,0,0,.88);color:#fff;padding:5px 9px;border-radius:4px;font:11px monospace;white-space:pre;display:none;z-index:100;line-height:1.35;max-width:340px} +#ppct{font-size:.846em;color:var(--lb);font-family:monospace} +#tt{position:absolute;pointer-events:none;background:rgba(0,0,0,.88);color:#fff;padding:5px 9px;border-radius:4px;font:.846em monospace;white-space:pre;display:none;z-index:100;line-height:1.35;max-width:340px} /* ── Touch & mobile ── */ #mc,#kc,#mmc{touch-action:none} body{overscroll-behavior:none} -#menuBtn{display:none;background:var(--gr);border:1px solid var(--ax);color:var(--fg);font-size:14px;line-height:1;padding:2px 5px;border-radius:3px;cursor:pointer;min-width:26px;min-height:26px} +#menuBtn{display:none;background:var(--gr);border:1px solid var(--ax);color:var(--fg);font-size:1.077em;line-height:1;padding:2px 5px;border-radius:3px;cursor:pointer;min-width:26px;min-height:26px} #tbMore{display:flex;align-items:center;gap:6px;flex-wrap:wrap} /* ── Vertical (portrait) layout ── */ body.layout-vertical #main{flex-direction:column} @@ -79,12 +79,12 @@ body.layout-vertical #right{width:100%!important;height:0;border-left:none;borde body.layout-vertical #right.open{height:300px;width:100%!important} body.layout-vertical #right .handle{left:0;right:0;top:0;width:100%;height:5px;cursor:row-resize;bottom:auto} body.layout-vertical #rc{flex-direction:row;gap:4px;top:6px;left:4px} -body.layout-vertical #rc button{padding:4px 8px;font-size:10px} +body.layout-vertical #rc button{padding:4px 8px;font-size:.769em} /* ── Narrow / touch-device toolbar ── */ @media(max-width:768px),(pointer:coarse){ #tb{padding:4px 6px;gap:4px;position:relative} #tb .sep{margin:0 2px} - #tb .cur{min-width:0;font-size:10px;flex-basis:100%;text-align:left;margin-left:0;order:99} + #tb .cur{min-width:0;font-size:.909em;flex-basis:100%;text-align:left;margin-left:0;order:99} #menuBtn{display:inline-block} #zi,#zo,#zf,#zr{display:none} #zr+.sep{display:none} diff --git a/src/editor/webview/assets/template.html b/src/editor/webview/assets/template.html index d444020..bc7947f 100644 --- a/src/editor/webview/assets/template.html +++ b/src/editor/webview/assets/template.html @@ -13,6 +13,7 @@
Time: Grad: +Text: