Verbeter grafische kwaliteit van kleurplaten (v0.4.67-beta) #5
4 changed files with 144 additions and 28 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.3.71-beta
|
||||
0.3.72-beta
|
||||
|
|
|
|||
|
|
@ -342,7 +342,10 @@
|
|||
.mm-pop .mp-ls[data-ls=dot] i{border-top-style:dotted;}
|
||||
.mm-pop .mp-x{margin-left:auto; font-weight:900; color:var(--red); font-size:12px;}
|
||||
/* physical tools: ruler, set square, magnifier */
|
||||
.dtool{position:absolute; z-index:3; touch-action:none; user-select:none; cursor:grab; left:120px; top:120px;}
|
||||
/* boven de inktlaag (4000): net als een echte lat ligt het gereedschap óp
|
||||
het bord - ook in penmodus te verplaatsen en in te stellen; tekenen
|
||||
begint ernaast en kleeft aan de tekenrand */
|
||||
.dtool{position:absolute; z-index:4200; touch-action:none; user-select:none; cursor:grab; left:120px; top:120px;}
|
||||
.dtool:active{cursor:grabbing;}
|
||||
.dtool .dtool-x{
|
||||
position:absolute; top:-10px; right:-10px; width:24px; height:24px; border-radius:50%;
|
||||
|
|
@ -356,6 +359,11 @@
|
|||
}
|
||||
.dtool-ruler{width:600px; height:76px;}
|
||||
.dtool-geo{width:340px; height:184px;}
|
||||
/* alleen de driehoek zelf is aan te raken - de lege hoeken van de
|
||||
rechthoekige box laten aanrakingen door naar het bord, anders kon er
|
||||
niet vlak naast de schuine randen getekend worden */
|
||||
.dtool-geo{pointer-events:none;}
|
||||
.dtool-geo svg polygon,.dtool-geo .dtool-opts,.dtool-geo .dtool-x,.dtool-geo .dtool-rot{pointer-events:auto;}
|
||||
.dtool-opts{position:absolute; top:-12px; left:8px; display:flex; gap:5px;}
|
||||
.dtool-opts button{
|
||||
width:24px; height:24px; border-radius:50%; border:none; background:var(--surface);
|
||||
|
|
|
|||
|
|
@ -578,6 +578,7 @@ function makeMovable(t){
|
|||
const near = Math.round(a/45)*45;
|
||||
if(Math.abs(a-near) < 4) a = near;
|
||||
ang = a;
|
||||
t._ang = ang; /* voor het kleven van de pen aan de gedraaide rand */
|
||||
t.style.transform = `rotate(${ang}deg)`;
|
||||
const shown = Math.round(((ang % 360) + 360) % 360);
|
||||
badge.textContent = shown + "°";
|
||||
|
|
@ -636,6 +637,8 @@ function toggleDTool(kind){
|
|||
<line x1="0" y1="2" x2="${L}" y2="2" stroke="${ink}" stroke-width="${hc?3:2}"/>
|
||||
<text x="${L/2}" y="68" font-size="12" fill="${ink}" text-anchor="middle" opacity=".65">${cmLen} cm</text>
|
||||
${ticks}</svg>`;
|
||||
/* tekenranden voor het kleven van de pen (bord-lokale coördinaten) */
|
||||
t._edges = [[[0,0],[L,0]], [[0,76],[L,76]]];
|
||||
const lenBtn = t.querySelector(".do-len");
|
||||
lenBtn.textContent = cmLen + " cm";
|
||||
lenBtn.title = T("dtLen");
|
||||
|
|
@ -654,37 +657,76 @@ function toggleDTool(kind){
|
|||
t.style.height = "76px";
|
||||
drawRuler();
|
||||
}else if(kind==="geo"){
|
||||
t._sz = 340; t._hc = false;
|
||||
/* geodriehoek zoals de echte: cm-schaal op de hypotenusa met 0 in het
|
||||
midden, gradenbogen in twee richtingen, en twee niveaus:
|
||||
basis - grote cijfers, alleen de hoofdhoeken (30/45/60/90/…) en
|
||||
hele centimeters, voor jonge leerlingen;
|
||||
volledig - mm-streepjes, graden per 2° met dubbele schaal (links- én
|
||||
rechtsom, zoals op een echte geodriehoek) en evenwijdige
|
||||
hulplijnen per cm om parallelle lijnen te tekenen. */
|
||||
const GEO_CMS = [6, 8, 12];
|
||||
t._cm = 8; t._hc = false; t._full = false;
|
||||
const drawGeo = ()=>{
|
||||
const W = t._sz, H = W/2 + 22, hc = t._hc;
|
||||
const maxCm = Math.max(6, Math.floor((board.clientWidth-40)/50));
|
||||
const cmLen = Math.min(t._cm, maxCm);
|
||||
const W = cmLen*50 + 28, H = W/2 + 22, hc = t._hc, full = t._full;
|
||||
const ink = hc ? "#000" : "#1e4f74";
|
||||
const bg = hc ? "rgba(255,214,0,.9)" : "rgba(185,222,248,.6)";
|
||||
const bg = hc ? "rgba(255,214,0,.9)" : "rgba(185,222,248,.55)";
|
||||
const halo = `stroke="${hc?"#ffd400":"#ffffff"}" stroke-width="3.5" paint-order="stroke" stroke-linejoin="round"`;
|
||||
const ax = W/2, ay = 16, by = H-18; /* apex + baseline */
|
||||
const ax = W/2, ay = 16, by = H-18; /* apex + tekenrand */
|
||||
const half = W/2 - 14;
|
||||
let cmTicks = "";
|
||||
for(let i=25; i<=half-16; i+=25){
|
||||
const cm = i%50===0;
|
||||
/* cm-schaal op de tekenrand, 0 in het midden; volledig ook mm */
|
||||
let cmTicks = `<text x="${ax}" y="${by-18}" font-size="${hc?15:12}" font-weight="bold" fill="${ink}" ${halo} text-anchor="middle">0</text>`;
|
||||
const stepPx = full ? 5 : 25;
|
||||
for(let i=stepPx; i<=half; i+=stepPx){
|
||||
const cm = i%50===0, halfCm = i%25===0;
|
||||
const len = cm ? 13 : halfCm ? 9 : 5;
|
||||
[ax-i, ax+i].forEach(x=>{
|
||||
cmTicks += `<line x1="${x}" y1="${by}" x2="${x}" y2="${by-(cm?13:7)}" stroke="${ink}" stroke-width="${cm?(hc?2.2:1.4):1}"/>`;
|
||||
cmTicks += `<line x1="${x}" y1="${by}" x2="${x}" y2="${by-len}" stroke="${ink}" stroke-width="${cm?(hc?2.2:1.4):1}"/>`;
|
||||
});
|
||||
if(cm){
|
||||
if(cm && i<=half-14){
|
||||
[ax-i, ax+i].forEach(x=>{
|
||||
cmTicks += `<text x="${x}" y="${by-18}" font-size="${hc?15:12}" font-weight="bold" fill="${ink}" ${halo} text-anchor="middle">${i/50}</text>`;
|
||||
});
|
||||
}
|
||||
}
|
||||
/* degree arc with labels INSIDE the arc so they stay readable */
|
||||
/* gradenboog: basis = hoofdhoeken groot; volledig = per 2° met dubbele
|
||||
schaal (buiten linksom, binnen rechtsom) zoals op een echte geodriehoek */
|
||||
let degs = "";
|
||||
const R = half*0.60;
|
||||
for(let d=10; d<180; d+=10){
|
||||
const R = half*0.62;
|
||||
const degStep = full ? 2 : 10;
|
||||
for(let d=degStep; d<180; d+=degStep){
|
||||
const major = full ? d%10===0 : d%30===0 || d===45 || d===135;
|
||||
const rad = d*Math.PI/180;
|
||||
const x1 = ax - Math.cos(rad)*(R-7), y1 = by - Math.sin(rad)*(R-7);
|
||||
const x1 = ax - Math.cos(rad)*(R-(major?10:5)), y1 = by - Math.sin(rad)*(R-(major?10:5));
|
||||
const x2 = ax - Math.cos(rad)*R, y2 = by - Math.sin(rad)*R;
|
||||
degs += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${ink}" stroke-width="${d%30===0?(hc?2.2:1.5):1}"/>`;
|
||||
if(d%30===0 && d>=30 && d<=150){
|
||||
const xt = ax - Math.cos(rad)*(R-22), yt = by - Math.sin(rad)*(R-22);
|
||||
degs += `<text x="${xt}" y="${yt+4}" font-size="${hc?14:11.5}" font-weight="bold" fill="${ink}" ${halo} text-anchor="middle">${d}°</text>`;
|
||||
degs += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${ink}" stroke-width="${major?(hc?2:1.4):0.7}"/>`;
|
||||
}
|
||||
/* 10° en 170° liggen vrijwel op de baseline en zouden de cm-cijfers
|
||||
overlappen - net als op een echte geodriehoek beginnen de labels
|
||||
daar pas een stap hoger (de streepjes staan er wel) */
|
||||
const labels = full
|
||||
? Array.from({length:15}, (_,i)=>(i+2)*10)
|
||||
: [30,45,60,90,120,135,150];
|
||||
labels.forEach(d=>{
|
||||
const rad = d*Math.PI/180;
|
||||
if(full){
|
||||
const xo = ax - Math.cos(rad)*(R-20), yo = by - Math.sin(rad)*(R-20);
|
||||
const xi = ax - Math.cos(rad)*(R-36), yi = by - Math.sin(rad)*(R-36);
|
||||
degs += `<text x="${xo}" y="${yo+3.5}" font-size="10.5" font-weight="bold" fill="${ink}" ${halo} text-anchor="middle">${d}</text>`;
|
||||
degs += `<text x="${xi}" y="${yi+3.5}" font-size="9" fill="${ink}" ${halo} text-anchor="middle" opacity=".75">${180-d}</text>`;
|
||||
}else{
|
||||
const xt = ax - Math.cos(rad)*(R-24), yt = by - Math.sin(rad)*(R-24);
|
||||
degs += `<text x="${xt}" y="${yt+5}" font-size="${hc?16:14}" font-weight="bold" fill="${ink}" ${halo} text-anchor="middle">${d}°</text>`;
|
||||
}
|
||||
});
|
||||
/* volledig: evenwijdige hulplijnen per cm, parallel aan de tekenrand */
|
||||
let parallels = "";
|
||||
if(full){
|
||||
for(let yy=by-50; yy>ay+24; yy-=50){
|
||||
const hw = half * (yy-ay)/(by-ay) - 10;
|
||||
if(hw < 30) break;
|
||||
parallels += `<line x1="${ax-hw}" y1="${yy}" x2="${ax+hw}" y2="${yy}" stroke="${ink}" stroke-width="0.8" opacity=".35"/>`;
|
||||
}
|
||||
}
|
||||
t.style.width = W+"px"; t.style.height = H+"px";
|
||||
|
|
@ -692,19 +734,27 @@ function toggleDTool(kind){
|
|||
<polygon points="14,${by} ${W-14},${by} ${ax},${ay}" fill="${bg}" stroke="${ink}" stroke-width="${hc?3:2}"/>
|
||||
<path d="M ${ax-R} ${by} A ${R} ${R} 0 0 1 ${ax+R} ${by}" fill="none" stroke="${ink}" stroke-width="1.2"/>
|
||||
<line x1="${ax}" y1="${by}" x2="${ax}" y2="${ay+18}" stroke="${ink}" stroke-width="1" stroke-dasharray="4 4"/>
|
||||
<text x="${ax}" y="${by-6}" font-size="${hc?13:10}" font-weight="bold" fill="${ink}" ${halo} text-anchor="middle">0</text>
|
||||
${cmTicks}${degs}</svg>`;
|
||||
<text x="${ax}" y="${by+14}" font-size="${hc?12:10}" font-weight="bold" fill="${ink}" text-anchor="middle" opacity=".7">90°</text>
|
||||
${parallels}${cmTicks}${degs}</svg>`;
|
||||
/* tekenranden voor het kleven van de pen (bord-lokale coördinaten) */
|
||||
t._edges = [[[14,by],[W-14,by]], [[14,by],[ax,ay]], [[W-14,by],[ax,ay]]];
|
||||
const lenBtn = t.querySelector(".do-len");
|
||||
lenBtn.textContent = cmLen + " cm";
|
||||
lenBtn.title = T("dtLen");
|
||||
const modeBtn = t.querySelector(".do-mode");
|
||||
modeBtn.textContent = T(full ? "dtModeFull" : "dtModeBasic");
|
||||
modeBtn.title = T("dtMode");
|
||||
};
|
||||
t.innerHTML = `<div class="dtool-svg"></div>
|
||||
<div class="dtool-opts">
|
||||
<button class="do-len">↔</button>
|
||||
<button class="do-len do-wide"></button>
|
||||
<button class="do-mode do-wide"></button>
|
||||
<button class="do-hc">◐</button>
|
||||
</div>`;
|
||||
const geoLen = t.querySelector(".do-len");
|
||||
geoLen.title = T("dtLen");
|
||||
geoLen.addEventListener("click", ()=>{
|
||||
t._sz = t._sz===340 ? 440 : t._sz===440 ? 280 : 340; drawGeo();
|
||||
t.querySelector(".do-len").addEventListener("click", ()=>{
|
||||
t._cm = GEO_CMS[(GEO_CMS.indexOf(t._cm)+1) % GEO_CMS.length]; drawGeo();
|
||||
});
|
||||
t.querySelector(".do-mode").addEventListener("click", ()=>{ t._full = !t._full; drawGeo(); });
|
||||
const geoHc = t.querySelector(".do-hc");
|
||||
geoHc.title = T("dtContrast");
|
||||
geoHc.addEventListener("click", ()=>{ t._hc = !t._hc; drawGeo(); });
|
||||
|
|
@ -841,6 +891,50 @@ const bPos = ev=>{
|
|||
/* ratio-based: correct under any display zoom */
|
||||
return [(ev.clientX-r.left)*(bInk.width/r.width), (ev.clientY-r.top)*(bInk.height/r.height)];
|
||||
};
|
||||
/* ---- langs de liniaal/geodriehoek tekenen -----------------------------------
|
||||
Elke rand van de liniaal en geodriehoek is een "tekenrand". Begint een haal
|
||||
binnen 16px van zo'n rand, dan wordt die rand voor de hele haal vergrendeld
|
||||
en wordt elk punt erop geprojecteerd: een kaarsrechte lijn, precies zoals
|
||||
tekenen tegen een echte lat. Verder dan ~60px van de rand laat de pen weer
|
||||
los (bewust weggetrokken). Randen draaien mee met het gereedschap. */
|
||||
let bSnap = null;
|
||||
function toolEdges(){
|
||||
const edges = [];
|
||||
["ruler","geo"].forEach(kind=>{
|
||||
const t = DTOOLS[kind];
|
||||
if(!t || !t._edges) return;
|
||||
const rad = (t._ang||0)*Math.PI/180;
|
||||
const cosA = Math.cos(rad), sinA = Math.sin(rad);
|
||||
const cx = t.offsetLeft + t.offsetWidth/2, cy = t.offsetTop + t.offsetHeight/2;
|
||||
const rot = ([x,y])=>{
|
||||
const dx = x - t.offsetWidth/2, dy = y - t.offsetHeight/2;
|
||||
return [cx + dx*cosA - dy*sinA, cy + dx*sinA + dy*cosA];
|
||||
};
|
||||
t._edges.forEach(seg=>edges.push([rot(seg[0]), rot(seg[1])]));
|
||||
});
|
||||
return edges;
|
||||
}
|
||||
function nearestEdge(px, py){
|
||||
let best = null;
|
||||
toolEdges().forEach(([a,b])=>{
|
||||
const ux = b[0]-a[0], uy = b[1]-a[1], len = Math.hypot(ux,uy);
|
||||
if(len < 1) return;
|
||||
const along = Math.max(0, Math.min(len, ((px-a[0])*ux + (py-a[1])*uy)/len));
|
||||
const qx = a[0] + ux/len*along, qy = a[1] + uy/len*along;
|
||||
const d = Math.hypot(px-qx, py-qy);
|
||||
if(!best || d < best.d) best = { d, ax:a[0], ay:a[1], ux:ux/len, uy:uy/len, len };
|
||||
});
|
||||
return best;
|
||||
}
|
||||
function snapPoint(x, y){
|
||||
if(!bSnap) return [x, y];
|
||||
const k = bInk.width / board.clientWidth; /* canvas-px per bord-px */
|
||||
const px = x/k, py = y/k;
|
||||
const along = Math.max(0, Math.min(bSnap.len, ((px-bSnap.ax)*bSnap.ux + (py-bSnap.ay)*bSnap.uy)));
|
||||
const qx = bSnap.ax + bSnap.ux*along, qy = bSnap.ay + bSnap.uy*along;
|
||||
if(Math.hypot(px-qx, py-qy) > 60){ bSnap = null; return [x, y]; }
|
||||
return [qx*k, qy*k];
|
||||
}
|
||||
bInk.addEventListener("pointerdown", e=>{
|
||||
if(e.pointerType==="mouse" && e.button!==0) return;
|
||||
e.preventDefault();
|
||||
|
|
@ -849,13 +943,22 @@ bInk.addEventListener("pointerdown", e=>{
|
|||
bDraw = true;
|
||||
PEN.nib = PEN.size*0.8;
|
||||
[bx,by] = bPos(e);
|
||||
bSnap = null;
|
||||
if(PEN.tool!=="erase" && PEN.tool!=="hand"){
|
||||
const k = bInk.width / board.clientWidth;
|
||||
const hit = nearestEdge(bx/k, by/k);
|
||||
if(hit && hit.d <= 16) bSnap = hit;
|
||||
}
|
||||
[bx,by] = snapPoint(bx, by);
|
||||
bMidX = bMidY = null;
|
||||
bStroke(bx+0.01, by+0.01, e.pressure, e.pointerType);
|
||||
});
|
||||
bInk.addEventListener("pointermove", e=>{
|
||||
if(!bDraw) return;
|
||||
(e.getCoalescedEvents ? e.getCoalescedEvents() : [e]).forEach(ev=>{
|
||||
const [x,y] = bPos(ev); bStroke(x, y, ev.pressure, ev.pointerType);
|
||||
const [rx,ry] = bPos(ev);
|
||||
const [x,y] = snapPoint(rx, ry);
|
||||
bStroke(x, y, ev.pressure, ev.pointerType);
|
||||
});
|
||||
});
|
||||
const bStop = ()=>{
|
||||
|
|
@ -874,6 +977,7 @@ const bStop = ()=>{
|
|||
markBoardChange();
|
||||
}
|
||||
bDraw = false;
|
||||
bSnap = null;
|
||||
bCtx.globalAlpha = 1;
|
||||
if(DTOOLS.lens) DTOOLS.lens.lensDraw();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"use strict";
|
||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
||||
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||
const VERSION = "0.3.71-beta";
|
||||
const VERSION = "0.3.72-beta";
|
||||
(function(){
|
||||
const tag = document.getElementById("verTag");
|
||||
tag.textContent = "v"+VERSION;
|
||||
|
|
@ -244,6 +244,8 @@ const I18N = {
|
|||
parentUnlinkWarn:"Verwijdert alleen de koppeling; het account van je kind blijft bestaan.",
|
||||
parentProgress:"📈 Voortgang", parentProgressEmpty:"Nog geen voortgang.",
|
||||
dtLen:"Lengte", dtContrast:"Hoog contrast",
|
||||
dtMode:"Basis: grote hoofdhoeken en hele centimeters. Volledig: graden per 2° met dubbele schaal, millimeters en evenwijdige hulplijnen.",
|
||||
dtModeBasic:"basis", dtModeFull:"volledig",
|
||||
pupilEmpty:"Je leerkracht heeft nog niets voor je klaargezet.",
|
||||
pupilWatching:"meekijken",
|
||||
amUsers:"Gebruikers", amClasses:"Klassen", amSchools:"Scholen", amOverarching:"Systeem",
|
||||
|
|
@ -515,6 +517,8 @@ const I18N = {
|
|||
parentUnlinkWarn:"Removes only the link; your child's account remains.",
|
||||
parentProgress:"📈 Progress", parentProgressEmpty:"No progress yet.",
|
||||
dtLen:"Length", dtContrast:"High contrast",
|
||||
dtMode:"Basic: big main angles and whole centimetres. Full: degrees every 2° with a double scale, millimetres and parallel guide lines.",
|
||||
dtModeBasic:"basic", dtModeFull:"full",
|
||||
pupilEmpty:"Your teacher hasn't set up anything for you yet.",
|
||||
pupilWatching:"watching",
|
||||
amUsers:"Users", amClasses:"Classes", amSchools:"Schools", amOverarching:"System",
|
||||
|
|
|
|||
Loading…
Reference in a new issue