mirror of
https://github.com/alesimula/Murine-launcher.git
synced 2026-10-28 09:43:42 +00:00
WEBSITE - WIP - icon shapes - manual position coordinates + angle center mode
This commit is contained in:
1 parent
b0a8363ac2
commit
a172b9e8f4
3 files changed
+59
-13
No files matched your search
+1
-1
@@ -5,7 +5,7 @@ description: >-
|
||||
url: "https://alesimula.github.io"
|
||||
baseurl: ""
|
||||
|
||||
version: "1.0.25" #Website version - increase to invalidate cached resources
|
||||
version: "1.0.26" #Website version - increase to invalidate cached resources
|
||||
github_repo: "alesimula/Murine-launcher"
|
||||
icon_url: "https://raw.githubusercontent.com/alesimula/Murine-launcher/refs/heads/main/image/app_icon/icon_browser_fullsize.svg"
|
||||
icon_freeform_url: "https://raw.githubusercontent.com/alesimula/Murine-launcher/refs/heads/main/image/app_icon/icon_freeform.svg"
|
||||
|
||||
@@ -282,9 +282,9 @@
|
||||
});
|
||||
return { x: x / (3 * a), y: y / (3 * a) };
|
||||
}
|
||||
// The point a shape rotates and centres around, as picked in the Move pill: the middle of its box, or its
|
||||
// centre of mass. Only the box one moves as the shape turns.
|
||||
let pivotMode = 'box';
|
||||
// The point a shape rotates and centres around, as picked in the Move pill: the middle of its box, one of its
|
||||
// corners, or its centre of mass. Only the centre of mass stays put as the shape turns.
|
||||
let pivotMode = 'box', pivotCorner = 0; // 'box', 'mass' or 'corner'; the corner as in boxCorners()
|
||||
// Measured from the outline at 6 decimals: at 2, turning a shape and turning it back would pivot around
|
||||
// two slightly different points and not land exactly where it started.
|
||||
const pivot = (sh) => {
|
||||
@@ -292,7 +292,7 @@
|
||||
try {
|
||||
if (pivotMode === 'mass') return centroid(sh);
|
||||
const b = measure(shapePath(sh));
|
||||
return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
||||
return pivotMode === 'corner' ? boxCorners(b)[pivotCorner] : { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
||||
} finally { digits = 2; }
|
||||
};
|
||||
// Turns a shape by deg (clockwise) around its pivot; a stretch turns with it. rot is the running total the
|
||||
@@ -337,6 +337,7 @@
|
||||
const STORE = 'murine-shape-editor';
|
||||
let shapes, active = 0, mode = 'edit', sel = -1, drag = null, sliding = false;
|
||||
let turnTyping = false; // the rotation pill shows a number field instead of the slider
|
||||
let pillView = 'pos'; // which half of the Move pill is open: the centre point's position, or 'rot'ation
|
||||
let pickedCorner = -1; // Move mode: the selected resize corner (0 top left, clockwise), -1 for none
|
||||
let pts = null, fixed = false; // shortcuts into shapes[active]: its points, or whether its outline is fixed
|
||||
let synced = '';
|
||||
@@ -588,6 +589,10 @@
|
||||
$('se-unstretch').hidden = !shapes[active].st;
|
||||
for (const b of $('se-mirror').querySelectorAll('[data-mirror]')) b.setAttribute('aria-pressed', b.dataset.mirror === shapes[active].mirror);
|
||||
for (const b of $('se-mirror').querySelectorAll('[data-pivot]')) b.setAttribute('aria-pressed', b.dataset.pivot === pivotMode);
|
||||
const [cx, cy] = [[6, 6], [18, 6], [18, 18], [6, 18]][pivotCorner];
|
||||
$('se-corner-dot').setAttribute('cx', cx); $('se-corner-dot').setAttribute('cy', cy);
|
||||
for (const b of $('se-corner-pick').children) b.setAttribute('aria-pressed', +b.dataset.corner === pivotCorner);
|
||||
if (!moving) $('se-corner-pick').hidden = true;
|
||||
if (moving) { const c = pivot(shapes[active]); $('se-pivot-mark').setAttribute('transform', `translate(${c.x} ${c.y})`); }
|
||||
// The stretch marks: in Move mode on the pivot cross, past the end of its arms; in Edit mode from the
|
||||
// centre of mass, around a dot.
|
||||
@@ -614,8 +619,12 @@
|
||||
if (moving && !at) {
|
||||
const rot = shapes[active].rot || 0;
|
||||
$('se-turn').value = rot; $('se-turn-val').value = Math.round(rot) % 360 + '°'; $('se-turn-num').value = fmt(r2(rot) % 360);
|
||||
$('se-turn').hidden = $('se-turn-val').hidden = turnTyping; $('se-turn-type').hidden = !turnTyping;
|
||||
$('se-turn-mode').setAttribute('aria-pressed', turnTyping);
|
||||
const rotOpen = pillView === 'rot';
|
||||
$('se-turn').hidden = $('se-turn-val').hidden = !rotOpen || turnTyping; $('se-turn-type').hidden = !rotOpen || !turnTyping;
|
||||
$('se-turn-mode').setAttribute('aria-pressed', rotOpen);
|
||||
$('se-pos-mode').setAttribute('aria-pressed', !rotOpen);
|
||||
$('se-pos-fields').hidden = rotOpen;
|
||||
if (!rotOpen) { const c = pivot(shapes[active]); $('se-px').value = fmt(c.x); $('se-py').value = fmt(c.y); }
|
||||
}
|
||||
if (at) {
|
||||
$('se-x').value = fmt(at.x); $('se-y').value = fmt(at.y);
|
||||
@@ -914,9 +923,15 @@
|
||||
sync(); render();
|
||||
};
|
||||
$('se-mirror').addEventListener('click', (e) => {
|
||||
const p = e.target.closest('[data-pivot]');
|
||||
if (p) { pivotMode = p.dataset.pivot; render(); }
|
||||
const p = e.target.closest('[data-pivot]'), c = e.target.closest('[data-corner]');
|
||||
// The corner mode opens a small picker of the four corners above it (and again when tapped again).
|
||||
if (c) { pivotCorner = +c.dataset.corner; $('se-corner-pick').hidden = true; render(); }
|
||||
else if (p) {
|
||||
$('se-corner-pick').hidden = p.dataset.pivot !== 'corner' || (pivotMode === 'corner' && !$('se-corner-pick').hidden);
|
||||
pivotMode = p.dataset.pivot; render();
|
||||
}
|
||||
});
|
||||
document.addEventListener('pointerdown', (e) => { if (!e.target.closest('.se-corner-wrap')) $('se-corner-pick').hidden = true; });
|
||||
$('se-mirror').addEventListener('click', (e) => {
|
||||
const b = e.target.closest('[data-mirror]');
|
||||
if (!b || b.dataset.mirror === shapes[active].mirror) return;
|
||||
@@ -969,7 +984,7 @@
|
||||
let ghostTimer = 0;
|
||||
const ghost = (c) => {
|
||||
clearTimeout(ghostTimer);
|
||||
if (pivotMode !== 'box') return;
|
||||
if (pivotMode === 'mass') return;
|
||||
$('se-pivot-ghost').setAttribute('transform', `translate(${c.x} ${c.y})`);
|
||||
$('se-pivot-ghost').classList.remove('off');
|
||||
};
|
||||
@@ -981,11 +996,23 @@
|
||||
});
|
||||
['change', 'blur'].forEach((t) => $('se-turn').addEventListener(t, () => { turnFrom = null; ghostOff(); }));
|
||||
// The icon switches between the slider and typing the angle; any typed angle is wrapped into 0-359.
|
||||
// The rotation icon opens the rotation half of the pill; tapped again, it switches slider and typing.
|
||||
$('se-turn-mode').onclick = () => {
|
||||
turnTyping = !turnTyping;
|
||||
if (pillView !== 'rot') pillView = 'rot'; else turnTyping = !turnTyping;
|
||||
render();
|
||||
if (turnTyping) $('se-turn-num').select();
|
||||
};
|
||||
$('se-pos-mode').onclick = () => { pillView = 'pos'; render(); };
|
||||
// Typing a position moves the shape so its centre point (as picked in the bottom pill) lands there.
|
||||
['x', 'y'].forEach((k) => $('se-p' + k).addEventListener('change', (e) => {
|
||||
const v = clamp(+e.target.value, view.lo, view.hi), sh = shapes[active];
|
||||
if (e.target.value === '' || !Number.isFinite(v)) return render(); // puts the current value back
|
||||
const d = v - pivot(sh)[k];
|
||||
if (Math.abs(d) < 1e-9) return render();
|
||||
checkpoint();
|
||||
shapes[active] = transform(sh, 1, 0, 0, k === 'x' ? d : 0, k === 'y' ? d : 0);
|
||||
sync(); render();
|
||||
}));
|
||||
$('se-turn-num').addEventListener('change', (e) => {
|
||||
const v = +e.target.value, sh = shapes[active];
|
||||
if (e.target.value === '' || !Number.isFinite(v)) return render(); // puts the current value back
|
||||
|
||||
+21
-2
@@ -92,6 +92,9 @@ noindex: true # and out of search engines
|
||||
background: var(--md-surface-container-low); box-shadow: var(--md-shadow); font-size: .82rem; }
|
||||
.se-pill:has(#se-remove[hidden]) { padding-right: 8px; }
|
||||
#se-rot-pill { padding: 3px 10px 3px 3px; }
|
||||
#se-rot-pill:has(#se-turn-mode:last-of-type[aria-pressed="false"]) { padding-right: 3px; }
|
||||
.se-pill-part { display: flex; align-items: center; gap: 8px; }
|
||||
.se-pill svg { width: 20px; height: 20px; }
|
||||
.se-pill[hidden], .se-pill [hidden] { display: none; }
|
||||
.se-pill label { display: flex; align-items: center; gap: 4px; color: var(--md-on-surface-variant); }
|
||||
.se-pill input { width: 4.2em; height: 28px; padding: 0 6px; font: inherit; color: var(--md-on-surface);
|
||||
@@ -148,6 +151,11 @@ noindex: true # and out of search engines
|
||||
.se-mirror button { width: 34px; height: 34px; border: 0; background: transparent; }
|
||||
.se-mirror button[aria-pressed="true"] { background: var(--md-primary-container); color: var(--md-on-primary-container); }
|
||||
.se-mirror svg { width: 20px; height: 20px; }
|
||||
.se-corner-wrap { position: relative; display: flex; }
|
||||
.se-corner-pick { position: absolute; left: 50%; bottom: calc(100% + 10px); transform: translateX(-50%); display: grid;
|
||||
grid-template-columns: 34px 34px; gap: 2px; padding: 3px; border-radius: 18px; border: 1px solid var(--md-outline-variant);
|
||||
background: var(--md-surface-container-low); box-shadow: var(--md-shadow); }
|
||||
.se-corner-pick[hidden] { display: none; }
|
||||
.se-pivot { display: flex; gap: 2px; padding-left: 5px; margin-left: 3px; border-left: 1px solid var(--md-outline-variant); }
|
||||
.se-pivot-ghost { pointer-events: none; fill: none; stroke: var(--md-primary); stroke-width: .55; stroke-linecap: round; opacity: .8; transition: opacity .6s; }
|
||||
.se-pivot-ghost.off { opacity: 0; }
|
||||
@@ -265,11 +273,22 @@ noindex: true # and out of search engines
|
||||
<button type="button" data-mirror="both" title="Mirror both ways" aria-label="Mirror both ways"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M10 2v16M2 10h16" stroke="currentColor" stroke-width="1.2" stroke-dasharray="2 1.6" fill="none"/><path d="M2 2h5l-5 5zM18 2h-5l5 5zM2 18h5l-5-5zM18 18h-5l5-5z"/></svg></button>
|
||||
<span class="se-pivot" role="group" aria-label="Centre point, for rotating and centring">
|
||||
<button type="button" data-pivot="box" title="Rotate and centre around the middle of the box" aria-label="Middle of the box"><svg viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="12" cy="12" r="2.4" fill="currentColor"/></svg></button>
|
||||
<span class="se-corner-wrap">
|
||||
<button type="button" data-pivot="corner" title="Rotate and centre around a corner of the box" aria-label="A corner of the box"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle id="se-corner-dot" cx="6" cy="6" r="2.6" fill="currentColor"/></svg></button>
|
||||
<span id="se-corner-pick" class="se-corner-pick" role="group" aria-label="Which corner" hidden>
|
||||
<button type="button" data-corner="0" aria-label="Top left corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="6" cy="6" r="2.6" fill="currentColor"/></svg></button><button type="button" data-corner="1" aria-label="Top right corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="18" cy="6" r="2.6" fill="currentColor"/></svg></button><button type="button" data-corner="3" aria-label="Bottom left corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="6" cy="18" r="2.6" fill="currentColor"/></svg></button><button type="button" data-corner="2" aria-label="Bottom right corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="18" cy="18" r="2.6" fill="currentColor"/></svg></button>
|
||||
</span>
|
||||
</span>
|
||||
<button type="button" data-pivot="mass" title="Rotate and centre around the centre of mass" aria-label="Centre of mass"><svg viewBox="0 0 24 24"><path d="M12 3.5 21 19.5H3Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><circle cx="12" cy="14.2" r="1.6" fill="currentColor"/><path d="M12 10.6v1.6M12 16.2v1.6M8.4 14.2h1.6M14 14.2h1.6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
||||
</span>
|
||||
</div>
|
||||
<div id="se-rot-pill" class="se-pill" role="group" aria-label="Rotation" hidden>
|
||||
<button id="se-turn-mode" type="button" aria-pressed="false" title="Type the angle" aria-label="Type the angle"><span class="material-symbols-outlined" aria-hidden="true"></span></button>
|
||||
<div id="se-rot-pill" class="se-pill" role="group" aria-label="Position and rotation" hidden>
|
||||
<button id="se-pos-mode" type="button" aria-pressed="true" title="Position of the centre point" aria-label="Position"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="4.5"/><path d="M12 2.5v4M12 17.5v4M2.5 12h4M17.5 12h4"/><circle cx="12" cy="12" r="1" fill="currentColor" stroke="none"/></svg></button>
|
||||
<span id="se-pos-fields" class="se-pill-part">
|
||||
<label>X <input id="se-px" type="number" step="0.5" inputmode="decimal" aria-label="Centre point X"></label>
|
||||
<label>Y <input id="se-py" type="number" step="0.5" inputmode="decimal" aria-label="Centre point Y"></label>
|
||||
</span>
|
||||
<button id="se-turn-mode" type="button" aria-pressed="false" title="Rotation (tap again to type the angle)" aria-label="Rotation"><span class="material-symbols-outlined" aria-hidden="true"></span></button>
|
||||
<input id="se-turn" type="range" min="0" max="359" step="1" aria-label="Rotation in degrees, clockwise"><output id="se-turn-val" for="se-turn"></output>
|
||||
<label id="se-turn-type" hidden><input id="se-turn-num" type="number" step="1" aria-label="Rotation in degrees, clockwise">°</label>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user