Merge pull request #1318 from Mnem42/main

zoom.js ui improvements
This commit is contained in:
slweeb 2025-12-07 14:06:47 -05:00 committed by GitHub
commit ddbdfbe07e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 264 additions and 253 deletions

View File

@ -1,289 +1,300 @@
// IIFE because paranoid const zoom_levels = [
(() => { 0.5,
const zoom_levels = [ 1,
0.5, 2,
1, 3,
2, 6,
3, 12
6, ]
12 window.zoom_data_div = null
window.zoom_level = 1
window.zoom_panning = [0,0]
function handle_zoom(direction){
switch (direction){
case "in":
if (!(zoom_level+1 in zoom_levels)) { break; }
window.zoom_level += 1
break;
case "out":
if (!(zoom_level-1 in zoom_levels)) { break; }
window.zoom_level -= 1
break;
}
rescale()
}
function handle_pan(direction, speed){
switch (direction){
case "right":
zoom_panning[0] -= speed
break;
case "left":
zoom_panning[0] += speed
break;
case "up":
zoom_panning[1] += speed
break;
case "down":
zoom_panning[1] -= speed
break;
}
rescale()
}
function gen_button(row, col, html, click, nopos, id){
const elem = document.createElement("button")
if (!nopos){
elem.style.gridColumn = row
elem.style.gridRow = col
}
if (id) { elem.id = id }
// Table for the data-pos to assign (row major). If null, don't add.
const data_pos_map = [
["tl", null, "tr"],
[null, null, null],
["bl", null, "br"]
] ]
window.zoom_data_div = null
window.zoom_level = 1 elem.innerHTML = html
window.zoom_panning = [0,0] elem.onclick = click
function handle_zoom(direction){ if (data_pos_map[row-1][col-1] !== null) {
switch (direction){ elem.dataset.pos = data_pos_map[row-1][col-1]
case "in":
if (!(zoom_level+1 in zoom_levels)) { break; }
window.zoom_level += 1
break;
case "out":
if (!(zoom_level-1 in zoom_levels)) { break; }
window.zoom_level -= 1
break;
}
rescale()
} }
function handle_pan(direction, speed){ return elem
switch (direction){ }
case "right":
zoom_panning[0] -= speed function add_css(){
break; const CSS = `
case "left": #zm_data_div { margin-bottom: 10px; }
zoom_panning[0] += speed #canvasDiv { overflow: hidden }
break;
case "up": @media(pointer=coarse){
zoom_panning[1] += speed #zm_floater_container#zm_floater_container {
break; width: 40%;
case "down": height: auto;
zoom_panning[1] -= speed }
break; #zm_floater_container:has(#zm_collapse[data-collapsed="true"]){
width: calc(40% / 3);
} }
rescale()
} }
function gen_button(row, col, html, click, nopos, id){ @media(pointer:coarse) and (orientation:landscape){
const elem = document.createElement("button") #zm_floater_container#zm_floater_container {
width: auto;
top: 5px;
if (!nopos){
elem.style.gridColumn = row
elem.style.gridRow = col
} }
if (id) { elem.id = id } #zm_floater_container:has(#zm_collapse[data-collapsed="true"]){
width: calc(40% / 3);
// Table for the data-pos to assign (row major). If null, don't add.
const data_pos_map = [
["tl", null, "tr"],
[null, null, null],
["bl", null, "br"]
]
elem.innerHTML = html
elem.onclick = click
if (data_pos_map[row-1][col-1] !== null) {
elem.dataset.pos = data_pos_map[row-1][col-1]
} }
return elem
} }
function add_css(){ #zm_floater_container {
const FLOATER_CSS = ` position: absolute;
#zm_data_div { display: grid;
margin-bottom: 10px;
right: 5px;
bottom: 5px;
height: 100px;
aspect-ratio: 1;
max-width: 200px;
max-height: 200px;
border: 2px solid white;
background-color: black;
font-size: 120%;
button { text-align: center; border: 0px solid white }
button:where([data-pos="tl"]) { border-width: 0px 2px 2px 0px };
button:where([data-pos="tr"]) { border-width: 2px 2px 0px 0px };
button:where([data-pos="bl"]) { border-width: 0px 0px 2px 2px };
button:where([data-pos="br"]) { border-width: 2px 0px 0px 2px };
}
#zm_floater_container:has(#zm_collapse[data-collapsed="true"]) {
button:not(#zm_collapse) {
display: none
} }
#canvasDiv {
overflow: hidden
}
#zm_floater_container {
position: absolute;
display: grid;
right: 5px;
bottom: 5px;
height: 24%;
aspect-ratio: 1;
border: 2px solid white;
background-color: black;
font-size: 1.2em;
button { text-align: center; border: 0px solid white }
button:where([data-pos="tl"]) { border-width: 0px 2px 2px 0px };
button:where([data-pos="tr"]) { border-width: 2px 2px 0px 0px };
button:where([data-pos="bl"]) { border-width: 0px 0px 2px 2px };
button:where([data-pos="br"]) { border-width: 2px 0px 0px 2px };
}
#zm_floater_container:has(#zm_collapse[data-collapsed="true"]) {
height: calc(8% - 1px);
button:not(#zm_collapse) {
display: none
}
}
.zm_corner {
border: 2px solid white;
}
#zm_collapse {
grid-row: 3;
grid-column: 3;
}
#zm_collapse[data-collapsed="true"] {
grid-row: 1;
grid-column: 1;
border-width: 0px;
}
`
const style_div = document.createElement("style")
style_div.innerHTML = FLOATER_CSS
document.head.appendChild(style_div)
} }
function add_zoom_floaters(){ .zm_corner { border: 2px solid white; }
const container = document.createElement("div")
container.id = "zm_floater_container"
// Pan mode selector (C: Coarse F: Fine) #zm_collapse {
const pan_mode_sel = gen_button( grid-row: 3;
1,3, "C", grid-column: 3;
}
#zm_collapse[data-collapsed="true"] {
grid-row: 1;
grid-column: 1;
border-width: 0px;
}
`
const style_div = document.createElement("style")
style_div.innerHTML = CSS
document.head.appendChild(style_div)
}
function add_zoom_floaters(){
const container = document.createElement("div")
container.id = "zm_floater_container"
// Pan mode selector (C: Coarse F: Fine)
const pan_mode_sel = gen_button(
1,3, "C",
(evt) => {
evt.target.dataset.mode = evt.target.dataset.mode == "F" ? "C" : "F"
evt.target.innerText = evt.target.dataset.mode
},
false,
"zm_panmode_sel"
)
const speed = () =>
(window.zoom_level > 3 ? 5 : 10) * // More granular at higher zoom levels
(pan_mode_sel.dataset.mode == "F" ? 0.25 : 1) // Increase granularity in fine mode
container.append(
// Direction buttons
gen_button(2,1, "↑", () => handle_pan("up" ,speed())),
gen_button(1,2, "←", () => handle_pan("left" ,speed())),
gen_button(3,2, "→", () => handle_pan("right" ,speed())),
gen_button(2,3, "↓", () => handle_pan("down" ,speed())),
// Zoom buttons
gen_button(1,1, "+", () => handle_zoom("in")),
gen_button(3,1, "-", () => handle_zoom("out")),
// Collapse button
gen_button(
3,3, "#",
(evt) => { (evt) => {
evt.target.dataset.mode = evt.target.dataset.mode == "F" ? "C" : "F" evt.target.dataset.collapsed = evt.target.dataset.collapsed == "true"
evt.target.innerText = evt.target.dataset.mode ? "false"
: "true"
}, },
false, true,
"zm_panmode_sel" "zm_collapse"
) ),
pan_mode_sel
)
const speed = () => const canvas_div = document.getElementById("canvasDiv")
(window.zoom_level > 3 ? 5 : 10) * // More granular at higher zoom levels canvas_div.appendChild(container)
(pan_mode_sel.dataset.mode == "F" ? 0.25 : 1) // Increase granularity in fine mode }
container.append( function rescale(){
// Direction buttons log_info()
gen_button(2,1, "↑", () => handle_pan("up" ,speed())),
gen_button(1,2, "←", () => handle_pan("left" ,speed())),
gen_button(3,2, "→", () => handle_pan("right" ,speed())),
gen_button(2,3, "↓", () => handle_pan("down" ,speed())),
// Zoom buttons const scale = zoom_levels[zoom_level]
gen_button(1,1, "+", () => handle_zoom("in")), const x = zoom_panning[0] * (pixelSize * scale)
gen_button(3,1, "-", () => handle_zoom("out")), const y = zoom_panning[1] * (pixelSize * scale)
// Collapse button gameCanvas.style.transform = `translate(${x}px,${y}px) scale(${scale})`
gen_button( }
3,3, "#",
(evt) => {
evt.target.dataset.collapsed = evt.target.dataset.collapsed == "true"
? "false"
: "true"
},
true,
"zm_collapse"
),
pan_mode_sel
)
const canvas_div = document.getElementById("canvasDiv") function log_info(){
canvas_div.appendChild(container) // Values are negated to make them more intuitive
const x_pan = (-zoom_panning[0]).toString().padEnd(4)
const y_pan = (-zoom_panning[1]).toString().padEnd(4)
if (zoom_data_div === null){ return; }
zoom_data_div.innerText = ""
zoom_data_div.innerText += `Scale: ${zoom_levels[zoom_level]}x\n`
zoom_data_div.innerText += `Pan : ${x_pan}, ${y_pan}`
}
function patch_keybinds(){
// Be more granular at higher zoom levels
const speed_a = () => zoom_level > 3 ? 5 : 10
const speed_b = () => zoom_level > 3 ? 10 : 20
keybinds["9"] = () => handle_zoom("in")
keybinds["0"] = () => handle_zoom("out")
keybinds["w"] = () => handle_pan("up", speed_a())
keybinds["a"] = () => handle_pan("left", speed_a())
keybinds["s"] = () => handle_pan("down", speed_a())
keybinds["d"] = () => handle_pan("right", speed_a())
keybinds["W"] = () => handle_pan("up", speed_b())
keybinds["A"] = () => handle_pan("left", speed_b())
keybinds["S"] = () => handle_pan("down", speed_b())
keybinds["D"] = () => handle_pan("right", speed_b())
}
function patch_ui(){
add_css()
add_zoom_floaters()
zoom_data_div = document.createElement("div")
zoom_data_div.id = "zm_data_div"
document.getElementById("logDiv").prepend(zoom_data_div)
const controls_table = document.getElementById("controlsTable").lastElementChild
controls_table.insertAdjacentHTML("beforeBegin",`
<tr>
<td>Zoom in/out</td>
<td>
<kbd>9</kbd>/
<kbd>0</kbd>
</td>
</tr>
<tr>
<td>Pan</td>
<td>
<kbd>W</kbd>
<kbd>A</kbd>
<kbd>S</kbd>
<kbd>D</kbd>
</td>
</tr>
<tr>
<td>Pan (fast)</td>
<td>
<kbd>Shift</kbd> +
<kbd>W</kbd>
<kbd>A</kbd>
<kbd>S</kbd>
<kbd>D</kbd>
</td>
</tr>
`)
}
// Redefine to give correct numbers when zoomed
window.getMousePos = (canvas, evt) => {
if (evt.touches) {
evt.preventDefault();
evt = evt.touches[0];
isMobile = true;
} }
const rect = canvas.getBoundingClientRect();
function rescale(){ let x = (evt.clientX - rect.left) / zoom_levels[zoom_level];
log_info() let y = (evt.clientY - rect.top) / zoom_levels[zoom_level];
const scale = zoom_levels[zoom_level] x = Math.floor((x / canvas.clientWidth) * (width+1));
const x = zoom_panning[0] * (pixelSize * scale) y = Math.floor((y / canvas.clientHeight) * (height+1));
const y = zoom_panning[1] * (pixelSize * scale)
gameCanvas.style.transform = `translate(${x}px,${y}px) scale(${scale})` return {x:x, y:y};
} }
function log_info(){ runAfterReset(() => {
// Values are negated to make them more intuitive window.zoom_level = 1
const x_pan = (-zoom_panning[0]).toString().padEnd(4) rescale()
const y_pan = (-zoom_panning[1]).toString().padEnd(4) })
if (zoom_data_div === null){ return; } runAfterLoad(() => {
patch_keybinds()
zoom_data_div.innerText = "" patch_ui()
zoom_data_div.innerText += `Scale: ${zoom_levels[zoom_level]}x\n` })
zoom_data_div.innerText += `Pan : ${x_pan}, ${y_pan}`
}
function patch_keybinds(){
// Be more granular at higher zoom levels
const speed_a = () => zoom_level > 3 ? 5 : 10
const speed_b = () => zoom_level > 3 ? 10 : 20
keybinds["9"] = () => handle_zoom("in")
keybinds["0"] = () => handle_zoom("out")
keybinds["w"] = () => handle_pan("up", speed_a())
keybinds["a"] = () => handle_pan("left", speed_a())
keybinds["s"] = () => handle_pan("down", speed_a())
keybinds["d"] = () => handle_pan("right", speed_a())
keybinds["W"] = () => handle_pan("up", speed_b())
keybinds["A"] = () => handle_pan("left", speed_b())
keybinds["S"] = () => handle_pan("down", speed_b())
keybinds["D"] = () => handle_pan("right", speed_b())
}
function patch_ui(){
add_css()
add_zoom_floaters()
zoom_data_div = document.createElement("div")
zoom_data_div.id = "zm_data_div"
document.getElementById("logDiv").prepend(zoom_data_div)
const controls_table = document.getElementById("controlsTable").lastElementChild
controls_table.insertAdjacentHTML("beforeBegin",`
<tr>
<td>Zoom in/out</td>
<td>
<kbd>9</kbd>/
<kbd>0</kbd>
</td>
</tr>
<tr>
<td>Pan</td>
<td>
<kbd>W</kbd>
<kbd>A</kbd>
<kbd>S</kbd>
<kbd>D</kbd>
</td>
</tr>
<tr>
<td>Pan (fast)</td>
<td>
<kbd>Shift</kbd> +
<kbd>W</kbd>
<kbd>A</kbd>
<kbd>S</kbd>
<kbd>D</kbd>
</td>
</tr>
`)
}
// Redefine to give correct numbers when zoomed
window.getMousePos = (canvas, evt) => {
if (evt.touches) {
evt.preventDefault();
evt = evt.touches[0];
isMobile = true;
}
const rect = canvas.getBoundingClientRect();
let x = (evt.clientX - rect.left) / zoom_levels[zoom_level];
let y = (evt.clientY - rect.top) / zoom_levels[zoom_level];
x = Math.floor((x / canvas.clientWidth) * (width+1));
y = Math.floor((y / canvas.clientHeight) * (height+1));
return {x:x, y:y};
}
runAfterReset(() => {
window.zoom_level = 1
rescale()
})
runAfterLoad(() => {
patch_keybinds()
patch_ui()
})
})()