Initial commit: Fenix 8 V3 data watch face
- Restore watchface-kit design (500x500, scaled per device) - 7 themes auto-generated from tokens via gen_themes.py -> Themes.mc - Four-segment gradient progress ring (22+21 ticks, 4 anchor dots) - Center time, date band w/ Bluetooth icon, top battery + dual complications, bottom distance/steps - 17 devices compiled clean (SDK 9.1.0) - Includes browser/JS preview (preview.html) + 7-theme SVG sheet
This commit is contained in:
322
preview.html
Normal file
322
preview.html
Normal file
@@ -0,0 +1,322 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Fenix 8 V3 表盘运行预览</title>
|
||||
<style>
|
||||
:root{ --bg:#0a0a0a; --panel:#141413; --ink:#e9e6e0; --dim:#8d8a84; --line:#26261f; --amber:#FEAA00; }
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.6 -apple-system,"PingFang SC","Microsoft YaHei",Helvetica,Arial,sans-serif}
|
||||
.wrap{max-width:1080px;margin:0 auto;padding:32px 20px 64px}
|
||||
h1{font-size:22px;margin:0 0 4px}
|
||||
.lede{color:var(--dim);margin:0 0 24px}
|
||||
.stage{display:flex;gap:28px;flex-wrap:wrap;align-items:flex-start}
|
||||
.facebox{background:#000;border-radius:14px;padding:14px;box-shadow:0 8px 30px #0008}
|
||||
canvas{display:block;background:#000;border-radius:50%}
|
||||
canvas.square{border-radius:12px}
|
||||
.controls{display:flex;flex-direction:column;gap:18px;min-width:240px;flex:1}
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 16px}
|
||||
.card h3{margin:0 0 10px;font-size:13px;color:var(--dim);font-weight:500}
|
||||
.themes{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
|
||||
.themes button{cursor:pointer;border:1px solid var(--line);background:#1c1c19;color:var(--ink);border-radius:8px;padding:8px 4px;font-size:12px;transition:.15s}
|
||||
.themes button:hover{border-color:var(--amber)}
|
||||
.themes button.active{background:var(--amber);color:#000;border-color:var(--amber);font-weight:600}
|
||||
.row{display:flex;align-items:center;gap:10px;margin:8px 0}
|
||||
.row label{flex:1;color:var(--dim)}
|
||||
input[type=range]{width:130px}
|
||||
.chk{display:flex;align-items:center;gap:8px;color:var(--dim)}
|
||||
.val{font:12px ui-monospace,Menlo,monospace;color:var(--amber);min-width:42px;text-align:right}
|
||||
.gallery{margin-top:34px}
|
||||
.gallery h2{font-size:16px;border-bottom:1px solid var(--line);padding-bottom:8px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:18px;margin-top:18px}
|
||||
figure{margin:0;text-align:center}
|
||||
figure canvas{margin:0 auto;border-radius:50%;background:#000}
|
||||
figcaption{color:var(--dim);font-size:12px;margin-top:8px}
|
||||
.note{color:var(--dim);font-size:12px;margin-top:6px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>Fenix 8 V3 · 运行效果预览</h1>
|
||||
<p class="lede">网页版 1:1 还原 <code>source/Fenix8V3View.mc</code> 的 <code>onUpdate</code> 绘制逻辑:同坐标、同主题色、同指标填充。下方可切换 7 套主题、调指标进度、套圆形遮罩。</p>
|
||||
|
||||
<div class="stage">
|
||||
<div class="facebox"><canvas id="face" width="500" height="500"></canvas></div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="card">
|
||||
<h3>主题 Theme</h3>
|
||||
<div class="themes" id="themes"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>指标进度(驱动进度环)</h3>
|
||||
<div class="row"><label>步数 Steps</label><input type="range" id="m0" min="0" max="100" value="84"><span class="val" id="v0"></span></div>
|
||||
<div class="row"><label>卡路里 Calories</label><input type="range" id="m1" min="0" max="100" value="64"><span class="val" id="v1"></span></div>
|
||||
<div class="row"><label>距离 Distance</label><input type="range" id="m2" min="0" max="100" value="65"><span class="val" id="v2"></span></div>
|
||||
<div class="row"><label>楼层 Floors</label><input type="range" id="m3" min="0" max="100" value="80"><span class="val" id="v3"></span></div>
|
||||
<div class="row"><label>动动条 Move Bar</label><input type="range" id="m4" min="0" max="100" value="40"><span class="val" id="v4"></span></div>
|
||||
<div class="note">默认值对应设计稿:顶右=步数 · 顶左=卡路里 · 底右=动动条 · 底左=楼层</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>显示 / 外观</h3>
|
||||
<div class="row"><label>电量 Battery</label><input type="range" id="bat" min="0" max="100" value="78"><span class="val" id="vbat"></span></div>
|
||||
<div class="row"><label>天气温度 °C</label><input type="range" id="temp" min="-10" max="45" value="23"><span class="val" id="vtemp"></span></div>
|
||||
<div class="chk"><input type="checkbox" id="round" checked><label for="round">圆形设备遮罩(多数 Fenix/Epix 为圆屏)</label></div>
|
||||
<div class="chk"><input type="checkbox" id="live" checked><label for="live">时间实时走动</label></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery">
|
||||
<h2>七套主题一览(实时当前时间)</h2>
|
||||
<div class="grid" id="gallery"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ---- 主题数据(与 source/Themes.mc 一致,由 watchface-tokens.json 生成)----
|
||||
const THEMES = {
|
||||
"01":{name:"Ember",ringTop:[[8,0xFFF203],[15,0xFDDB0D],[25,0xFFC203],[35,0xFFA00A],[45,0xFF8705],[55,0xFF610C],[65,0xFF4904],[75,0xFF2602],[86,0xFF2604]],
|
||||
ringBottom:[[7,0xAE1706],[14,0xB32504],[21,0xC03B03],[28,0xCD6206],[35,0xD57805],[42,0xDE9208],[47,0xC98D00]],
|
||||
tickOff:0x545655,anchorDot:0x525551,bandFill:0xFEAA00,bandText:0x000002,accent:0xFCA901,textPrimary:0xFAFAFA,colon:0x525551,hours:0xFBFBFB,minutes:0xFCA901},
|
||||
"02":{name:"Aurora",ringTop:[[8,0xA5FA11],[15,0x9EFA23],[25,0x97F84B],[35,0x8CF965],[45,0x89F87F],[55,0x74FB9F],[65,0x6DFCC6],[75,0x64FCDF],[86,0x57FDFF]],
|
||||
ringBottom:[[7,0x55FDFF],[14,0x64FCDF],[21,0x6FFCC6],[28,0x74FBAE],[35,0x7EFA85],[42,0x88F96D],[47,0x81E654]],
|
||||
tickOff:0x545655,anchorDot:0x0F3C82,bandFill:0x525551,bandText:0xFFFFFF,accent:0xABA9AC,textPrimary:0xFAFAFA,colon:0x525551,hours:0xFBFBFB,minutes:0xAAF701},
|
||||
"03":{name:"Brass",ringTop:[[8,0xB4814F],[15,0xB88954],[25,0xC5975C],[35,0xC59E65],[45,0xCBA775],[55,0xD7AE7F],[65,0xDEBC84],[75,0xE7C48D],[86,0xF0D097]],
|
||||
ringBottom:[[7,0xB97E4A],[14,0xB68954],[21,0xBD925E],[28,0xC69B67],[35,0xCEA26C],[42,0xD9AB74],[47,0xC59E70]],
|
||||
tickOff:0x3A2815,anchorDot:0x62441E,bandFill:0x392810,bandText:0xFFFFFF,accent:0xB37C4A,textPrimary:0xEBCB93,colon:0x525551,hours:0xFBFBFB,minutes:0xECCC93},
|
||||
"04":{name:"Voltage",ringTop:[[8,0x5CAFF0],[15,0x6EB6E0],[25,0x7FBEC5],[35,0x96CA9D],[45,0xAAD87F],[55,0xBFDF66],[65,0xD6EA40],[75,0xE8F227],[86,0xFFFB0A]],
|
||||
ringBottom:[[7,0xF7FA11],[14,0xE9F225],[21,0xD6EB3F],[28,0xC0DE65],[35,0xADD880],[42,0xA0CF98],[47,0x88B98E]],
|
||||
tickOff:0x545655,anchorDot:0x53AAFF,bandFill:0x53AAFF,bandText:0x000000,accent:0xABA9AC,textPrimary:0x51A7FA,colon:0x525551,hours:0xFBFBFB,minutes:0x52A8FD},
|
||||
"05":{name:"Reef",ringTop:[[8,0xF87E0D],[15,0xE78E25],[25,0xCE9B4D],[35,0xBDAC65],[45,0xA5BE88],[55,0x95CA9E],[65,0x7EDEC7],[75,0x6DEBE1],[86,0x57FDFF]],
|
||||
ringBottom:[[7,0xFF7905],[14,0xE78B29],[21,0xD59C3E],[28,0xBEAC63],[35,0xADB77F],[42,0xA0C796],[47,0x88B98E]],
|
||||
tickOff:0x545655,anchorDot:0xFE7900,bandFill:0x52FDFF,bandText:0x000000,accent:0xFCA901,textPrimary:0xFAFAFA,colon:0x525551,hours:0xFBFBFB,minutes:0xFC7801},
|
||||
"06":{name:"Acid",ringTop:[[8,0x9CCC14],[15,0xA9CE0C],[25,0xA4D610],[35,0xACDB08],[45,0xB4E107],[55,0xB5E603],[65,0xBFEE04],[75,0xBDF108],[86,0xC7F808]],
|
||||
ringBottom:[[7,0xC7FA02],[14,0xBEF20A],[21,0xC0ED04],[28,0xB4E905],[35,0xB4E107],[42,0xADDD0C],[47,0x9BC603]],
|
||||
tickOff:0x545655,anchorDot:0xF58909,bandFill:0x525551,bandText:0x000000,accent:0xC4F801,textPrimary:0xFAFAFA,colon:0x525551,hours:0xDBF731,minutes:0xC4F900},
|
||||
"07":{name:"Kelp",ringTop:[[8,0xEF8917],[15,0xD8901C],[25,0xBD9A34],[35,0xAD9E3C],[45,0x96A54E],[55,0x7DAE5D],[65,0x61B474],[75,0x4EBB84],[86,0x36C294]],
|
||||
ringBottom:[[7,0xCE520F],[14,0xB1631D],[21,0xA26A2F],[28,0x9D763E],[35,0x85874B],[42,0x76915D],[47,0x5E8651]],
|
||||
tickOff:0x545655,anchorDot:0xF58909,bandFill:0x525551,bandText:0x000000,accent:0x31C192,textPrimary:0xFAFAFA,colon:0x525551,hours:0xF28708,minutes:0xF4880A}
|
||||
};
|
||||
const ORDER = ["01","02","03","04","05","06","07"];
|
||||
|
||||
// ---- 颜色工具 ----
|
||||
const toHex = c => '#' + (c & 0xffffff).toString(16).padStart(6,'0');
|
||||
function lerpColor(c1,c2,f){
|
||||
const r1=(c1>>16)&255,g1=(c1>>8)&255,b1=c1&255;
|
||||
const r2=(c2>>16)&255,g2=(c2>>8)&255,b2=c2&255;
|
||||
return (Math.round(r1+(r2-r1)*f)<<16)|(Math.round(g1+(g2-g1)*f)<<8)|Math.round(b1+(b2-b1)*f);
|
||||
}
|
||||
function lerpStops(stops,t){
|
||||
const n=stops.length; if(!n) return 0x888888;
|
||||
const first=stops[0], last=stops[n-1];
|
||||
if(t<=first[0]) return first[1];
|
||||
if(t>=last[0]) return last[1];
|
||||
for(let i=0;i<n-1;i++){const a=stops[i],b=stops[i+1];
|
||||
if(t>=a[0]&&t<=b[0]) return lerpColor(a[1],b[1],(t-a[0])/(b[0]-a[0]));}
|
||||
return last[1];
|
||||
}
|
||||
|
||||
// ---- 几何常量(设计稿 500 基准)----
|
||||
const TOP=[], BOT=[];
|
||||
for(let a=2;a<=86;a+=4) TOP.push(a);
|
||||
for(let a=6;a<=66;a+=3) BOT.push(a);
|
||||
const DOW=["SUN","MON","TUE","WED","THU","FRI","SAT"];
|
||||
const MON=["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"];
|
||||
|
||||
// ---- 绘制原语(与 Monkey C 对应)----
|
||||
function drawTick(ctx,cx,cy,rIn,rOut,w,ang,col,mirror){
|
||||
const eff=mirror?-ang:ang, rad=eff*Math.PI/180, s=Math.sin(rad), c=Math.cos(rad);
|
||||
ctx.strokeStyle=toHex(col); ctx.lineWidth=w; ctx.lineCap='round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx+s*rIn, cy-c*rIn); ctx.lineTo(cx+s*rOut, cy-c*rOut); ctx.stroke();
|
||||
}
|
||||
function drawArcTop(ctx,cx,cy,rIn,rOut,w,angles,stops,off,mirror,lit,anchorOuter){
|
||||
const n=angles.length;
|
||||
for(let i=0;i<n;i++){const a=angles[i];
|
||||
const on=anchorOuter?(i>=n-lit):(i<lit);
|
||||
drawTick(ctx,cx,cy,rIn,rOut,w,a,on?lerpStops(stops,a):off,mirror);}
|
||||
}
|
||||
function drawArcBottom(ctx,cx,cy,rIn,rOut,w,botAngles,stops,off,mirror,lit){
|
||||
const n=botAngles.length;
|
||||
for(let i=0;i<n;i++){const a=botAngles[i];
|
||||
const ang=180-a;
|
||||
drawTick(ctx,cx,cy,rIn,rOut,w,ang,i<lit?lerpStops(stops,a):off,mirror);}
|
||||
}
|
||||
function drawRing(ctx,cx,cy,s,theme,pTR,pTL,pBR,pBL){
|
||||
const rOut=249.5*s, rIn=231.5*s, tickOff=theme.tickOff;
|
||||
const topStops=theme.ringTop, botStops=theme.ringBottom;
|
||||
const topW=13*s, botW=9*s;
|
||||
const litTR=Math.round(pTR*22), litTL=Math.round(pTL*22), litBR=Math.round(pBR*21), litBL=Math.round(pBL*21);
|
||||
drawArcTop(ctx,cx,cy,rIn,rOut,topW,TOP,topStops,tickOff,false,litTR,true);
|
||||
drawArcTop(ctx,cx,cy,rIn,rOut,topW,TOP,topStops,tickOff,true ,litTL,true);
|
||||
drawArcBottom(ctx,cx,cy,rIn,rOut,botW,BOT,botStops,tickOff,true ,litBL);
|
||||
drawArcBottom(ctx,cx,cy,rIn,rOut,botW,BOT,botStops,tickOff,false,litBR);
|
||||
// 锚点圆点
|
||||
const dotR=9.5*s, dotRad=240.5*s, dotCol=theme.anchorDot;
|
||||
ctx.fillStyle=toHex(dotCol);
|
||||
[0,93.3,180,266.7].forEach(da=>{const rad=da*Math.PI/180;
|
||||
ctx.beginPath(); ctx.arc(cx+Math.sin(rad)*dotRad, cy-Math.cos(rad)*dotRad, dotR, 0, 7); ctx.fill();});
|
||||
}
|
||||
function drawBattery(ctx,cx,s,theme,pct){
|
||||
const y=53.5*s, gw=30*s, gh=16*s, gx=cx-52*s, gy=y-gh/2;
|
||||
ctx.strokeStyle=toHex(theme.accent); ctx.lineWidth=2;
|
||||
ctx.strokeRect(gx,gy,gw,gh);
|
||||
ctx.fillStyle=toHex(theme.accent);
|
||||
ctx.fillRect(gx+gw, gy+gh*0.28, 3*s, gh*0.44);
|
||||
ctx.fillStyle=toHex(theme.accent);
|
||||
ctx.fillRect(gx+2, gy+2, (gw-4)*(pct/100), gh-4);
|
||||
ctx.fillStyle=toHex(theme.textPrimary);
|
||||
ctx.font=`${Math.round(16*s)}px -apple-system,Helvetica,Arial`;
|
||||
ctx.textAlign='left'; ctx.textBaseline='middle';
|
||||
ctx.fillText(Math.round(pct)+'%', cx+16*s, y);
|
||||
}
|
||||
function drawComplicationIcon(ctx,x,y,r,col,typeIdx){
|
||||
ctx.strokeStyle=toHex(col); ctx.fillStyle=toHex(col); ctx.lineWidth=2;
|
||||
if(typeIdx===0){ // sun
|
||||
ctx.beginPath(); ctx.arc(x,y,r*0.45,0,7); ctx.fill();
|
||||
for(let i=0;i<8;i++){const rad=i*45*Math.PI/180;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x+Math.sin(rad)*r*0.6, y-Math.cos(rad)*r*0.6);
|
||||
ctx.lineTo(x+Math.sin(rad)*r, y-Math.cos(rad)*r); ctx.stroke();}
|
||||
} else if(typeIdx===1){ // flame
|
||||
ctx.beginPath(); ctx.moveTo(x,y-r); ctx.lineTo(x+r*0.8,y+r*0.6); ctx.lineTo(x-r*0.8,y+r*0.6); ctx.closePath(); ctx.fill();
|
||||
} else if(typeIdx===2){ // steps
|
||||
ctx.beginPath(); ctx.moveTo(x-r*0.5,y-r*0.3); ctx.lineTo(x+r*0.5,y-r*0.3);
|
||||
ctx.moveTo(x-r*0.3,y); ctx.lineTo(x+r*0.6,y); ctx.stroke();
|
||||
} else if(typeIdx===3){ // distance dot
|
||||
ctx.beginPath(); ctx.arc(x,y,r*0.5,0,7); ctx.fill();
|
||||
} else if(typeIdx===4){ // floors chevron
|
||||
ctx.beginPath(); ctx.moveTo(x,y-r); ctx.lineTo(x+r*0.7,y+r*0.2); ctx.lineTo(x-r*0.7,y+r*0.2); ctx.closePath(); ctx.fill();
|
||||
} else { // battery
|
||||
ctx.strokeRect(x-r*0.6,y-r*0.4,r*1.2,r*0.8);
|
||||
}
|
||||
}
|
||||
function drawTopComplication(ctx,cx,cy,s,theme,typeIdx,isLeft,valStr){
|
||||
const sideX=(isLeft?156:343)*s, iconY=90*s, valY=130.5*s;
|
||||
ctx.fillStyle=toHex(theme.textPrimary);
|
||||
ctx.font=`${Math.round(22*s)}px -apple-system,Helvetica,Arial`;
|
||||
ctx.textAlign='center'; ctx.textBaseline='middle';
|
||||
ctx.fillText(valStr, sideX, valY);
|
||||
drawComplicationIcon(ctx, sideX, iconY, 13*s, theme.accent, typeIdx);
|
||||
}
|
||||
function drawBluetooth(ctx,x,y,r,col){
|
||||
ctx.strokeStyle=toHex(col); ctx.lineWidth=3;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x,y-r); ctx.lineTo(x,y+r);
|
||||
ctx.moveTo(x,y-r); ctx.lineTo(x+r*0.7,y-r*0.3);
|
||||
ctx.lineTo(x,y); ctx.moveTo(x,y); ctx.lineTo(x+r*0.7,y+r*0.3);
|
||||
ctx.lineTo(x,y+r); ctx.stroke();
|
||||
}
|
||||
function drawDateBand(ctx,w,cx,s,theme,gi){
|
||||
const by=286.5*s, bh=44*s, yc=308.5*s;
|
||||
ctx.fillStyle=toHex(theme.bandFill); ctx.fillRect(0,by,w,bh);
|
||||
const txt=toHex(theme.bandText);
|
||||
drawBluetooth(ctx,36.5*s,yc,10*s,theme.bandText);
|
||||
const dow=DOW[gi.day_of_week-1], md=MON[gi.month-1]+' '+gi.day;
|
||||
ctx.fillStyle=txt; ctx.font=`${Math.round(16*s)}px -apple-system,Helvetica,Arial`;
|
||||
ctx.textBaseline='middle';
|
||||
ctx.textAlign='center'; ctx.fillText(dow,177.5*s,yc);
|
||||
ctx.fillText(md,297.5*s,yc);
|
||||
ctx.textAlign='right'; ctx.fillText(gi.hour>=12?'PM':'AM',465*s,yc);
|
||||
}
|
||||
function drawTime(ctx,cx,s,theme,gi){
|
||||
let hh=gi.hour; if(hh===0)hh=12; else if(hh>12)hh=hh-12;
|
||||
const hStr=''+hh, mStr=('0'+gi.min).slice(-2), ty=220*s;
|
||||
ctx.font=`700 ${Math.round(66*s)}px -apple-system,Helvetica,Arial`;
|
||||
ctx.textBaseline='middle';
|
||||
ctx.fillStyle=toHex(theme.hours); ctx.textAlign='right'; ctx.fillText(hStr,211*s,ty);
|
||||
ctx.fillStyle=toHex(theme.minutes); ctx.textAlign='left'; ctx.fillText(mStr,272*s,ty);
|
||||
ctx.fillStyle=toHex(theme.colon);
|
||||
ctx.fillRect(238*s,184*s,24*s,20*s);
|
||||
ctx.fillRect(238*s,235*s,24*s,20*s);
|
||||
}
|
||||
function drawBottom(ctx,cx,s,theme,dist,steps){
|
||||
const valY=366*s, labY=410.5*s, leftX=234*s, rightX=266*s;
|
||||
ctx.textBaseline='middle';
|
||||
ctx.fillStyle=toHex(theme.textPrimary); ctx.font=`${Math.round(22*s)}px -apple-system,Helvetica,Arial`;
|
||||
ctx.textAlign='right'; ctx.fillText(dist,leftX,valY);
|
||||
ctx.fillStyle=toHex(theme.accent); ctx.font=`${Math.round(16*s)}px -apple-system,Helvetica,Arial`;
|
||||
ctx.fillText('DST',leftX,labY);
|
||||
ctx.fillStyle=toHex(theme.textPrimary); ctx.font=`${Math.round(22*s)}px -apple-system,Helvetica,Arial`;
|
||||
ctx.textAlign='left'; ctx.fillText(steps,rightX,valY);
|
||||
ctx.fillStyle=toHex(theme.accent);
|
||||
ctx.fillText('STEP',rightX,labY);
|
||||
}
|
||||
|
||||
// ---- 主渲染 ----
|
||||
function renderWatch(ctx, W, themeId, data, round){
|
||||
const cx=W/2, cy=W/2, s=W/500;
|
||||
ctx.clearRect(0,0,W,W);
|
||||
ctx.fillStyle='#000'; ctx.fillRect(0,0,W,W);
|
||||
if(round){ ctx.save(); ctx.beginPath(); ctx.arc(cx,cy,W/2-2,0,7); ctx.clip(); }
|
||||
const theme=THEMES[themeId];
|
||||
const p=[data.m[0],data.m[1],data.m[2],data.m[3],data.m[4]];
|
||||
const idx={TR:0,TL:1,BR:4,BL:3}; // 默认设置
|
||||
drawRing(ctx,cx,cy,s,theme, p[idx.TR],p[idx.TL],p[idx.BR],p[idx.BL]);
|
||||
drawBattery(ctx,cx,s,theme,data.battery);
|
||||
drawTopComplication(ctx,cx,cy,s,theme,1,true, Math.round(data.m[1]*2000).toLocaleString()); // 左上=卡路里
|
||||
drawTopComplication(ctx,cx,cy,s,theme,0,false, data.temp+'°'); // 右上=天气温度
|
||||
const gi=data.time;
|
||||
drawTime(ctx,cx,s,theme,gi);
|
||||
drawDateBand(ctx,W,cx,s,theme,gi);
|
||||
drawBottom(ctx,cx,s,theme, (data.m[2]*10).toFixed(1), Math.round(data.m[0]*10000).toLocaleString());
|
||||
if(round) ctx.restore();
|
||||
}
|
||||
|
||||
// ---- 状态 ----
|
||||
const state={ themeId:'01', round:true, live:true,
|
||||
m:[0.84,0.64,0.65,0.80,0.40], battery:78, temp:23 };
|
||||
function nowGI(){const d=new Date();return {hour:d.getHours(),min:d.getMinutes(),day:d.getDate(),month:d.getMonth()+1,day_of_week:(d.getDay()+1)%7||7};}
|
||||
|
||||
const face=document.getElementById('face'), fctx=face.getContext('2d');
|
||||
function drawMain(){
|
||||
const gi=state.live?nowGI():{hour:10,min:8,day:9,month:9,day_of_week:3};
|
||||
renderWatch(fctx,500,state.themeId,{m:state.m,battery:state.battery,temp:state.temp,time:gi},state.round);
|
||||
}
|
||||
|
||||
// 主题按钮
|
||||
const themesBox=document.getElementById('themes');
|
||||
ORDER.forEach((id,i)=>{
|
||||
const b=document.createElement('button'); b.textContent=(i+1)+' '+THEMES[id].name;
|
||||
b.onclick=()=>{state.themeId=id; [...themesBox.children].forEach(c=>c.classList.remove('active')); b.classList.add('active'); drawMain(); drawGallery();};
|
||||
themesBox.appendChild(b);
|
||||
});
|
||||
themesBox.children[0].classList.add('active');
|
||||
|
||||
// 滑块
|
||||
[0,1,2,3,4].forEach(i=>{
|
||||
const el=document.getElementById('m'+i), v=document.getElementById('v'+i);
|
||||
const upd=()=>{state.m[i]=el.value/100; v.textContent=el.value+'%'; drawMain();};
|
||||
el.oninput=upd; v.textContent=el.value+'%';
|
||||
});
|
||||
const bat=document.getElementById('bat'), vbat=document.getElementById('vbat');
|
||||
bat.oninput=()=>{state.battery=+bat.value; vbat.textContent=bat.value+'%'; drawMain();}; vbat.textContent=bat.value+'%';
|
||||
const temp=document.getElementById('temp'), vtemp=document.getElementById('vtemp');
|
||||
temp.oninput=()=>{state.temp=+temp.value; vtemp.textContent=temp.value+'°'; drawMain();}; vtemp.textContent=temp.value+'°';
|
||||
document.getElementById('round').onchange=e=>{state.round=e.target.checked; drawMain();};
|
||||
document.getElementById('live').onchange=e=>{state.live=e.target.checked; drawMain();};
|
||||
|
||||
// 画廊
|
||||
const gal=document.getElementById('gallery');
|
||||
ORDER.forEach(id=>{
|
||||
const fig=document.createElement('figure');
|
||||
const c=document.createElement('canvas'); c.width=150; c.height=150;
|
||||
const cap=document.createElement('figcaption'); cap.textContent=(ORDER.indexOf(id)+1)+' · '+THEMES[id].name;
|
||||
fig.appendChild(c); fig.appendChild(cap); gal.appendChild(fig);
|
||||
});
|
||||
function drawGallery(){
|
||||
[...gal.children].forEach((fig,i)=>{
|
||||
const c=fig.querySelector('canvas'), x=c.getContext('2d');
|
||||
renderWatch(x,150,ORDER[i],{m:state.m,battery:state.battery,temp:state.temp,time:nowGI()},true);
|
||||
});
|
||||
}
|
||||
|
||||
drawMain(); drawGallery();
|
||||
setInterval(()=>{ if(state.live) drawMain(); drawGallery(); }, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user