diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-13 19:44:45 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-13 19:44:45 -0500 |
| commit | 6c0d305687ef5b96928cdc7578e712a9dd0a2d8c (patch) | |
| tree | 02c2c853ea603a46ef2e0214cb8a315bd930ae7d /scripts/theme-studio/theme-studio.html | |
| parent | 47c1df50046d5fe6dcf7b210dcb49142964387e2 (diff) | |
| download | dotemacs-6c0d305687ef5b96928cdc7578e712a9dd0a2d8c.tar.gz dotemacs-6c0d305687ef5b96928cdc7578e712a9dd0a2d8c.zip | |
Add theme studio box color controls
Diffstat (limited to 'scripts/theme-studio/theme-studio.html')
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 51 |
1 files changed, 36 insertions, 15 deletions
diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 4c67faa1..909f39b0 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -25,6 +25,9 @@ .cstep.locked .cdd{cursor:default;opacity:.85;box-shadow:inset 0 0 0 2px #e8bd3088} .lockbtn{background:none;border:none;cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;opacity:.5;filter:grayscale(1)} .lockbtn.on{opacity:1;filter:none} + .boxctl{display:flex;align-items:center;gap:5px} + .boxctl .cdd{width:108px} + .boxctl .cstepbtn{width:18px} .legctl{margin:0 0 8px;display:flex;gap:8px;align-items:center} .cat{color:#b4b1a2} .ex{font-size:17px} .sbtn{width:26px;height:24px;border:1px solid #3a3a3a;border-radius:3px;background:#eaeaea;color:#111;cursor:pointer;font-size:15px;margin-right:2px;padding:0} @@ -1321,22 +1324,28 @@ function udeco(o){return `font-weight:${o.bold?'bold':'normal'};font-style:${o.i // A face's :box, rendered as an inset box-shadow (no layout shift). Returns the // box-shadow VALUE (or '' for no box). 'line' is a flat border in the box color // (or the face's own color when unset); 'released'/'pressed' are the 3D button -// styles Emacs draws, derived from the background so they read on any color. +// styles Emacs draws, derived from explicit box color when set, otherwise the +// background so they read on any color. function boxCss(b,bg){if(!b||!b.style)return '';const w=b.width||1; if(b.style==='released'||b.style==='pressed'){ - // Emacs derives the 3D edges from the face's background (reliefColors, - // ported from xterm.c); the translucent pair is only the no-bg fallback. - const r=bg?reliefColors(bg):{hl:null,sh:null}; + // Emacs derives the 3D edges from a base color (reliefColors, ported from + // xterm.c); the translucent pair is only the no-color fallback. + const r=(b.color||bg)?reliefColors(b.color||bg):{hl:null,sh:null}; const hl=r.hl||'#ffffff33',sh=r.sh||'#00000066'; const [a,z]=b.style==='released'?[hl,sh]:[sh,hl]; return `inset ${w}px ${w}px 0 ${a},inset -${w}px -${w}px 0 ${z}`;} return `inset 0 0 0 ${w}px ${b.color||'currentColor'}`;} -// The per-row box control: none / line / raised / pressed. get()/set() read and -// write the face's box object (null = no box). -function mkBoxSelect(get,set){const s=document.createElement('select');s.className='chip';s.style.cssText='width:84px;font:10pt monospace'; +// The per-row box control: none / line / raised / pressed plus optional line +// color. get()/set() read and write the face's box object (null = no box). +function mkBoxControl(get,set){const wrap=document.createElement('div');wrap.className='boxctl'; + const s=document.createElement('select');s.className='chip';s.style.cssText='width:84px;font:10pt monospace'; [['','no box'],['line','line'],['released','raised'],['pressed','pressed']].forEach(([v,l])=>{const o=document.createElement('option');o.value=v;o.textContent=l;s.appendChild(o);}); - const cur=get();s.value=cur&&cur.style?cur.style:''; - s.onchange=()=>set(s.value?{style:s.value,width:1,color:null}:null);return s;} + const dd=mkColorDropdown(ddList((get()&&get().color)||''),(get()&&get().color)||'',h=>{const cur=get();if(!cur)return;set(Object.assign({},cur,{color:h||null}));}); + function paint(){const cur=get();s.value=cur&&cur.style?cur.style:'';dd.setValue(cur&&cur.color?cur.color:''); + const off=!cur||!cur.style||wrap.dataset.locked==='1';dd.dataset.locked=off?'1':'';dd.classList.toggle('locked',off);if(dd.syncLocked)dd.syncLocked();} + s.onchange=()=>{const cur=get();set(s.value?{style:s.value,width:cur&&cur.width||1,color:cur&&cur.color||null}:null);paint();}; + wrap.syncLocked=()=>{const locked=wrap.dataset.locked==='1';s.disabled=locked;paint();}; + wrap.append(s,dd);paint();return wrap;} function flashRow(tr){if(!tr)return;tr.scrollIntoView({block:'center',behavior:'smooth'});tr.classList.remove('flash');void tr.offsetWidth;tr.classList.add('flash');} function flashEl(el){if(!el)return;el.scrollIntoView({block:'nearest',inline:'nearest',behavior:'smooth'});el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');} // Flash every matching element but scroll only the first into view, so a face @@ -1452,9 +1461,9 @@ function buildPkgTable(){ const ci=document.createElement('td');const isel=document.createElement('select');isel.className='chip';isel.style.cssText='width:150px;font:10pt monospace';inh.forEach(o=>{const op=document.createElement('option');op.value=o;op.textContent=o||'— none —';isel.appendChild(op);});isel.value=f.inherit||'';isel.onchange=()=>{f.inherit=isel.value||null;f.source='user';pkgChanged();};ci.appendChild(isel); const ch=document.createElement('td');const hin=document.createElement('input');hin.type='number';hin.min='0.8';hin.max='2.5';hin.step='0.05';hin.value=f.height||1;hin.className='hstep';hin.onchange=()=>{f.height=parseFloat(hin.value)||1;f.source='user';pkgChanged();};ch.appendChild(hin); const cc=document.createElement('td');cc.style.fontSize='10pt';cc.style.whiteSpace='nowrap';const efg=effFg(pkgEffFg(app,face)),ebg=effBg(pkgEffBg(app,face)),r=contrast(efg,ebg);cc.innerHTML=crHtml(r); - const cx=document.createElement('td');const boxSel=mkBoxSelect(()=>f.box,b=>{f.box=b;f.source='user';pkgChanged();});cx.appendChild(boxSel); + const cx=document.createElement('td');const boxCtl=mkBoxControl(()=>f.box,b=>{f.box=b;f.source='user';pkgChanged();});cx.appendChild(boxCtl); const cr=document.createElement('td');const rb=document.createElement('button');rb.className='sbtn';rb.textContent='↺';rb.title='reset to default';rb.onclick=()=>{PKGMAP[app][face]=seedFace(def);pkgChanged();};cr.appendChild(rb); - const cL=mkLockCell('pkg:'+app+':'+face,[fgd,bgd,...pkBtns,isel,hin,boxSel,rb]); + const cL=mkLockCell('pkg:'+app+':'+face,[fgd,bgd,...pkBtns,isel,hin,boxCtl,rb]); tr.append(c0,cL,cf,cb,cw,cc,ci,ch,cx,cr);tb.appendChild(tr); } applyTableSort('pkgbody'); @@ -1799,8 +1808,8 @@ function buildUITable(){ stBtns.forEach(b=>cS.appendChild(b)); const cC=document.createElement('td');cC.id='uicr-'+face;cC.style.whiteSpace='nowrap';cC.style.fontSize='10pt'; const cP=document.createElement('td');cP.className='ex';cP.id='uiprev-'+face;cP.textContent=ex;cP.style.padding='4px 10px';cP.style.borderRadius='4px'; - const cX=document.createElement('td');const boxSel=mkBoxSelect(()=>UIMAP[face].box,b=>{UIMAP[face].box=b;paintUI(face);buildMockFrame();});cX.appendChild(boxSel); - const cL=mkLockCell('ui:'+face,[fgSel,bgSel,...stBtns,boxSel]); + const cX=document.createElement('td');const boxCtl=mkBoxControl(()=>UIMAP[face].box,b=>{UIMAP[face].box=b;paintUI(face);buildMockFrame();});cX.appendChild(boxCtl); + const cL=mkLockCell('ui:'+face,[fgSel,bgSel,...stBtns,boxCtl]); tr.appendChild(c0);tr.appendChild(cL);tr.appendChild(cF);tr.appendChild(cB);tr.appendChild(cS);tr.appendChild(cC);tr.appendChild(cP);tr.appendChild(cX);tb.appendChild(tr);paintUI(face); } applyTableSort('uibody'); @@ -2072,7 +2081,7 @@ if(location.hash==='#contrasttest'){let ok=true;const notes=[];const A=(c,n)=>{i // highlight and shadow from the face's effective bg per Emacs's relief // algorithm, and pressed draws the shadow edge first. if(location.hash==='#beveltest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; - const saveUI=JSON.parse(JSON.stringify(UIMAP)); + const saveUI=JSON.parse(JSON.stringify(UIMAP)),saveP=PALETTE.slice(),savePK=JSON.parse(JSON.stringify(PKGMAP)); UIMAP['mode-line']={fg:'#d8dee9',bg:'#30343c',bold:false,italic:false,underline:false,strike:false,box:{style:'released',width:1,color:null}}; buildUITable(); const pv=document.getElementById('uiprev-mode-line'); @@ -2084,7 +2093,19 @@ if(location.hash==='#beveltest'){let ok=true;const notes=[];const A=(c,n)=>{if(! A(bs2&&bs2.includes('rgb(15, 17, 22)')&&bs2.includes('rgb(113, 118, 127)')&&bs2.indexOf('rgb(15, 17, 22)')<bs2.indexOf('rgb(113, 118, 127)'),'pressed swaps the pair (shadow edge first): '+bs2); UIMAP['mode-line'].box={style:'line',width:1,color:'#ff0000'};paintUI('mode-line'); A(pv&&pv.style.boxShadow.includes('rgb(255, 0, 0)'),'line style keeps its explicit color: '+(pv&&pv.style.boxShadow)); - for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveUI);buildUITable(); + UIMAP['mode-line'].box={style:'released',width:1,color:'#ff0000'};paintUI('mode-line'); + const bs3=pv&&pv.style.boxShadow; + A(bs3&&bs3.includes('rgb(255, 42, 42)')&&bs3.includes('rgb(143, 0, 0)'),'released style derives relief from explicit box color: '+bs3); + PALETTE=[['#ff0000','red','red'],['#30343c','slate','slate']]; + buildUITable(); + const mlrow=document.querySelector('#uibody tr[data-face="mode-line"]'),boxCell=mlrow&&mlrow.cells[7],boxSel=boxCell&&boxCell.querySelector('select'),boxDd=boxCell&&boxCell.querySelector('.cdd'); + if(boxSel&&boxDd){boxSel.value='line';boxSel.dispatchEvent(new Event('change',{bubbles:true}));boxDd.click();const redRow=[...document.querySelectorAll('.cddpop .cddrow')].find(r=>r.textContent.includes('red'));if(redRow)redRow.click();} + A(UIMAP['mode-line'].box&&UIMAP['mode-line'].box.color==='#ff0000','UI box color dropdown writes box.color'); + const app=curApp(),face=APPS[app].faces[0][0];PKGMAP[app][face].box={style:'line',width:1,color:null};buildPkgTable(); + const prow=document.querySelector('#pkgbody tr[data-face="'+face+'"]'),pbox=prow&&prow.cells[8],pdd=pbox&&pbox.querySelector('.cdd'); + if(pdd){pdd.click();const redRow=[...document.querySelectorAll('.cddpop .cddrow')].find(r=>r.textContent.includes('red'));if(redRow)redRow.click();} + A(PKGMAP[app][face].box&&PKGMAP[app][face].box.color==='#ff0000','package box color dropdown writes box.color'); + PALETTE=saveP;PKGMAP=savePK;for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveUI);buildUITable();buildPkgTable(); document.title='BEVELTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='beveltest';d.textContent='BEVELTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} // Preview-link gate (open with #previewlinktest): known bespoke-preview face |
