diff --git a/mods/zoom.js b/mods/zoom.js index da7a037b..2a781625 100644 --- a/mods/zoom.js +++ b/mods/zoom.js @@ -1,322 +1,567 @@ -const zoom_levels = [ - 0.5, - 1, - 2, - 3, - 6, - 12 -] -window.zoom_data_div = null -window.zoom_level = 1 -window.zoom_panning = [0,0] - -let colour_setting; - -dependOn("betterSettings.js", () => { - const settings_tab = new SettingsTab("zoom.js"); - colour_setting = new Setting( - "Canvas background", - "canvas_bkg", - settingType.COLOR, - false, - defaultValue="#252525" - ); - - settings_tab.registerSettings(undefined, colour_setting) - settingsManager.registerTab(settings_tab) -}) - -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 +// zoom.js +"use strict"; +(() => { + // src/custom_setting_types.ts + var def_classes = () => { + class Numlist2 extends Setting { + step; + input_container = null; + push_btn = null; + pop_btn = null; + constructor(name, storage_name, default_values = [], desc = "", step = 1, custom_validator = () => true, disabled = false) { + super( + name, + storage_name, + [5, 0], + disabled, + default_values ?? [], + desc, + custom_validator + ); + this.step = step; + } + #new_input(value, i) { + const elem = document.createElement("input"); + elem.type = "number"; + elem.value = value.toString(); + elem.step = this.step.toString(); + elem.onchange = (ev) => { + const parsed = Number.parseFloat(ev.target.value); + if (!Number.isNaN(parsed)) { + this.value[i] = parsed; + this.set(this.value); + } + }; + return elem; + } + #push_pop_btns() { + this.push_btn = document.createElement("button"); + this.push_btn.style.color = "#0F0"; + this.push_btn.innerText = "+"; + this.pop_btn = document.createElement("button"); + this.pop_btn.style.color = "#F00"; + this.pop_btn.innerText = "-"; + this.push_btn.onclick = () => { + this.value.push(1); + this.input_container.append(this.#new_input(1, this.value.length)); + }; + this.pop_btn.onclick = () => { + this.value.pop(); + if (this.input_container.lastChild) { + this.input_container.removeChild(this.input_container.lastChild); + } + }; + return [this.push_btn, this.pop_btn]; + } + disable() { + this.push_btn?.setAttribute("disabled", "true"); + this.pop_btn?.setAttribute("disabled", "true"); + } + enable() { + this.push_btn?.removeAttribute("disabled"); + this.pop_btn?.removeAttribute("disabled"); + } + build() { + const value = this.get(); + const container = document.createElement("span"); + container.classList.add("setting-span", "zm_nml_setting"); + const l_container = document.createElement("span"); + const label = document.createElement("span"); + label.innerText = this.name; + const btn_container = document.createElement("span"); + btn_container.classList.add("zm_nml_btn_container"); + btn_container.append(...this.#push_pop_btns()); + l_container.append(label, document.createElement("br"), btn_container); + this.input_container = document.createElement("span"); + this.input_container.classList.add("zm_nml_icontainer"); + const elems = []; + value.forEach((x, i) => { + elems.push(this.#new_input(x, i)); + }); + this.input_container.append(...elems); + container.append(l_container, this.input_container); + return container; + } } - 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"] - ] - - 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] + class MultiSetting extends Setting { + settings; + elements = []; + multi_input_name; + rows = []; + constructor(name, storage_name, extra_opts, ...settings) { + super( + name, + storage_name, + [255], + extra_opts.disabled, + extra_opts.default_value ?? 0, + extra_opts.desc, + void 0 + ); + this.settings = settings; + this.multi_input_name = crypto.randomUUID(); + } + build() { + const container = document.createElement("span"); + this.settings.forEach((setting, i) => { + const row_container = document.createElement("div"); + row_container.classList.add("zm_ms_row"); + this.rows.push(row_container); + const select_btn = document.createElement("button"); + select_btn.classList.add("zm_ms_selbtn"); + select_btn.innerText = "#"; + const built_item = setting.build(); + built_item.classList.add("zm_ms_item"); + built_item.dataset.index = i.toString(); + row_container.dataset.current = i == this.value ? "true" : "false"; + select_btn.onclick = () => { + this.set(i); + setting.enable(); + for (const setting2 of this.settings) setting2.disable(); + for (const row of this.rows) { + row.dataset.current = "false"; + row.querySelectorAll(".zm_ms_item input").forEach((x) => x.setAttribute("disabled", "true")); + } + built_item.querySelectorAll("input").forEach((x) => x.removeAttribute("disabled")); + row_container.dataset.current = "true"; + }; + row_container.append(select_btn, built_item); + container.appendChild(row_container); + }); + return container; + } } - - return elem -} - -function add_css(){ - const CSS = ` - #zm_data_div { margin-bottom: 10px } - #canvasDiv { overflow: hidden; background-color: var(--opac-85) } - - @media(pointer=coarse){ - #zm_floater_container#zm_floater_container { - width: 40%; - height: auto; + class SettingGroup extends Setting { + settings; + constructor(settings) { + super( + "", + "", + [2763], + false + ); + this.settings = settings; + } + enable() { + for (const x of Object.values(this.settings)) { + x.enable(); } - #zm_floater_container:has(#zm_collapse[data-collapsed="true"]){ - width: calc(40% / 3); + } + disable() { + for (const x of Object.values(this.settings)) { + x.disable(); } - } - - @media(pointer:coarse) and (orientation:landscape){ - #zm_floater_container#zm_floater_container { - width: auto; - top: 5px; - } - #zm_floater_container:has(#zm_collapse[data-collapsed="true"]){ - width: calc(40% / 3); + } + build() { + const container = document.createElement("div"); + for (const x of Object.values(this.settings)) { + container.appendChild(x.build()); } + return container; + } + get() { + return this.settings; + } + // Override these so the defaults don't do anything + set() { + } + update() { + } + onUpdate() { + } } + return { + Numlist: Numlist2, + MultiSetting, + SettingGroup + }; + }; - #colorSelector { z-index: 1; right: 5px } - #zm_floater_container { - position: absolute; - display: grid; - - 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"]) { - height: 50px; - - button:not(#zm_collapse) { - display: none; - } - } - #canvasDiv:has(#colorSelector[style *= "block"]) #zm_floater_container { - bottom: 50px; - } - - .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 = 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 - }, + // src/custom_settings.ts + var CustomSettingsManager = class { + canvas_bkg; + zoom; + unl_zoom; + show_floater; + fpan_speed; + cpan_speed; + upan_speed; + constructor(on_edit) { + const { Numlist: Numlist2, MultiSetting, SettingGroup } = def_classes(); + const settings_tab = new SettingsTab("zoom.js"); + const validator = () => { + on_edit.cb(this); + return true; + }; + this.canvas_bkg = new Setting( + "Canvas background", + "canvas_bkg", + settingType.COLOR, 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.target.dataset.collapsed = evt.target.dataset.collapsed == "true" - ? "false" - : "true" - }, - true, - "zm_collapse" + "#252525", + "The colour for the area around the canvas", + validator + ); + this.cpan_speed = new Setting( + "Coarse pan speed", + "cpan_speed", + settingType.NUMBER, + false, + 10, + "The default pan speed", + validator + ); + this.fpan_speed = new Setting( + "Fine pan speed", + "fpan_speed", + settingType.NUMBER, + false, + 3, + "The pan speed when holding shift (F in the floater)", + validator + ); + this.upan_speed = new Setting( + "Ultrafine pan speed", + "upan_speed", + settingType.NUMBER, + false, + 1, + "The pan speed when holding alt (U in the floater)", + validator + ); + this.show_floater = new Setting( + "Show floater", + "show_floater", + settingType.BOOLEAN, + false, + true, + "Whether to show the floater or not", + validator + ); + const zoom_levels = new Numlist2( + "Zoom levels", + "zoom_levels", + [0.5, 1, 2, 3, 6, 12], + "Zoom levels", + 1, + validator + ); + this.unl_zoom = new SettingGroup({ + speed: new Setting( + "Zoom speed", + "unl_zoom_speed", + settingType.NUMBER, + false, + 2, + "The zoom magnitude (as the multiplier to the zoom level every time zoom is used)", + validator ), - pan_mode_sel - ) - - const canvas_div = document.getElementById("canvasDiv") - canvas_div.style.backgroundColor = colour_setting?.value ?? "#252525" - canvas_div.appendChild(container) -} - -function rescale(){ - log_info() - - const scale = zoom_levels[zoom_level] - const x = zoom_panning[0] * (pixelSize * scale) - const y = zoom_panning[1] * (pixelSize * scale) - - gameCanvas.style.transform = `translate(${x}px, ${y}px) translateX(-50%) scale(${scale})` -} - -function log_info(){ - // 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",` - - Zoom in/out - - 9/ - 0 - - - - Pan - - W - A - S - D - - - - Pan (fast) - - Shift + - W - A - S - D - - - `) -} - -// Redefine to give correct numbers when zoomed -window.getMousePos = (canvas, evt) => { - if (evt.touches) { - evt.preventDefault(); - evt = evt.touches[0]; - isMobile = true; + min: new Setting( + "Zoom limit (min)", + "unl_zlim_min", + settingType.NUMBER, + false, + 0.25, + "The lower zoom limit (reducing may lead to rounding error coming back from very low levels)", + validator + ), + max: new Setting( + "Zoom limit (max)", + "unl_zlim_max", + settingType.NUMBER, + false, + 25, + "The upper zoom limit (reducing may lead to rounding error coming back from very high levels)", + validator + ) + }); + this.zoom = new MultiSetting( + "Zoom mode", + "zoom_mode", + {}, + zoom_levels, + this.unl_zoom + ); + settings_tab.registerSettings( + void 0, + this.canvas_bkg, + this.show_floater, + this.zoom + ); + settings_tab.registerSettings( + "Panning", + this.cpan_speed, + this.fpan_speed, + this.upan_speed + ); + settingsManager.registerTab(settings_tab); } - const rect = canvas.getBoundingClientRect(); + }; - let x = (evt.clientX - rect.left) / zoom_levels[zoom_level]; - let y = (evt.clientY - rect.top) / zoom_levels[zoom_level]; + // src/handler.ts + var Handler = class { + settings; + patcher; + zoom_panning = [0, 0]; + zoom_level; + constructor(settings, patcher) { + this.settings = settings; + this.patcher = patcher; + this.zoom_level = 1; + this.patch_keybinds(); + this.patch_floater(); + window.getMousePos = (canvas2, evt) => { + if (evt.touches) { + evt.preventDefault(); + evt = evt.touches[0]; + isMobile = true; + } + const rect = canvas2.getBoundingClientRect(); + const clx = evt.clientX; + const cly = evt.clientY; + let x = (clx - rect.left) / this.scale(); + let y = (cly - rect.top) / this.scale(); + x = Math.floor(x / canvas2.clientWidth * (width + 1)); + y = Math.floor(y / canvas2.clientHeight * (height + 1)); + return { x, y }; + }; + runAfterReset(() => { + this.zoom_level = 1; + this.zoom_panning = [0, 0]; + this.rescale(); + }); + } + handle_zoom(direction) { + if (this.settings.zoom.value == 0) { + switch (direction) { + case "in": + if (!(this.zoom_level + 1 in this.settings.zoom.settings[0].value)) { + break; + } + this.zoom_level += 1; + break; + case "out": + if (!(this.zoom_level - 1 in this.settings.zoom.settings[0].value)) { + break; + } + this.zoom_level -= 1; + break; + } + } else { + const settings = this.settings.zoom.settings[1].settings; + const speed = settings.speed.value; + const min = settings.min.value; + const max = settings.max.value; + switch (direction) { + case "in": + if (this.zoom_level * speed > max) break; + this.zoom_level *= speed; + break; + case "out": + if (this.zoom_level / speed < min) break; + this.zoom_level /= speed; + break; + } + this.zoom_level = Number(this.zoom_level.toPrecision(3)); + } + this.rescale(); + } + handle_pan(direction, speed) { + switch (direction) { + case "right": + this.zoom_panning[0] -= speed; + break; + case "left": + this.zoom_panning[0] += speed; + break; + case "up": + this.zoom_panning[1] += speed; + break; + case "down": + this.zoom_panning[1] -= speed; + break; + } + this.rescale(); + } + scale() { + return this.settings.zoom.value == 0 ? this.settings.zoom.settings[0].value[this.zoom_level] : this.zoom_level; + } + rescale() { + this.log_info(); + const x = this.zoom_panning[0] * (pixelSize * this.scale()); + const y = this.zoom_panning[1] * (pixelSize * this.scale()); + canvas.style.transform = `translate(${x}px, ${y}px) translateX(-50%) scale(${this.scale()})`; + } + log_info() { + const x_pan = (-this.zoom_panning[0]).toString().padEnd(4); + const y_pan = (-this.zoom_panning[1]).toString().padEnd(4); + this.patcher.zoom_data_div.innerText = ""; + this.patcher.zoom_data_div.innerText += `Scale: ${this.scale()}x +`; + this.patcher.zoom_data_div.innerText += `Pan : ${x_pan}, ${y_pan}`; + } + patch_keybinds() { + keybinds["9"] = () => this.handle_zoom("in"); + keybinds["0"] = () => this.handle_zoom("out"); + keybinds["w"] = (ev) => this.handle_pan("up", ev.altKey ? this.settings.upan_speed.value : this.settings.cpan_speed.value); + keybinds["a"] = (ev) => this.handle_pan("left", ev.altKey ? this.settings.upan_speed.value : this.settings.cpan_speed.value); + keybinds["s"] = (ev) => this.handle_pan("down", ev.altKey ? this.settings.upan_speed.value : this.settings.cpan_speed.value); + keybinds["d"] = (ev) => this.handle_pan("right", ev.altKey ? this.settings.upan_speed.value : this.settings.cpan_speed.value); + keybinds["W"] = () => this.handle_pan("up", this.settings.fpan_speed.value); + keybinds["A"] = () => this.handle_pan("left", this.settings.fpan_speed.value); + keybinds["S"] = () => this.handle_pan("down", this.settings.fpan_speed.value); + keybinds["D"] = () => this.handle_pan("right", this.settings.fpan_speed.value); + } + speed() { + switch (this.patcher.panmode_sel.innerText) { + case "C": + return this.settings.cpan_speed.value; + case "F": + return this.settings.fpan_speed.value; + case "U": + return this.settings.upan_speed.value; + default: + return 0; + } + } + patch_floater() { + document.getElementById("zm_floater_u").onclick = () => this.handle_pan("up", this.speed()); + document.getElementById("zm_floater_d").onclick = () => this.handle_pan("down", this.speed()); + document.getElementById("zm_floater_l").onclick = () => this.handle_pan("left", this.speed()); + document.getElementById("zm_floater_r").onclick = () => this.handle_pan("right", this.speed()); + } + }; - x = Math.floor((x / canvas.clientWidth) * (width+1)); - y = Math.floor((y / canvas.clientHeight) * (height+1)); + // assets/ctrl_info.html + var ctrl_info_default = "\r\n Zoom in/out\r\n \r\n 9/\r\n 0\r\n \r\n\r\n\r\n Pan\r\n \r\n W\r\n A\r\n S\r\n D\r\n \r\n\r\n\r\n Pan (fast)\r\n \r\n Shift + \r\n W\r\n A\r\n S\r\n D\r\n \r\n"; - return {x:x, y:y}; + // assets/floater.html + var floater_default = '
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n
'; + + // assets/main.css + var main_default = '#zm_data_div { margin-bottom: 10px }\r\n#canvasDiv { overflow: hidden; background-color: var(--opac-85) }\r\n\r\n@media(pointer=coarse){\r\n#zm_floater_container#zm_floater_container { \r\n width: 40%;\r\n height: auto;\r\n}\r\n#zm_floater_container:has(#zm_collapse[data-collapsed="true"]){\r\n width: calc(40% / 3);\r\n}\r\n}\r\n\r\n@media(pointer:coarse) and (orientation:landscape){\r\n#zm_floater_container#zm_floater_container {\r\n width: auto;\r\n top: 5px;\r\n}\r\n#zm_floater_container:has(#zm_collapse[data-collapsed="true"]){\r\n width: calc(40% / 3);\r\n}\r\n}\r\n\r\n#colorSelector { z-index: 1; right: 5px }\r\n#zm_floater_container {\r\nposition: absolute;\r\ndisplay: grid;\r\n\r\nright: 5px;\r\nbottom: 5px;\r\nheight: 100px;\r\naspect-ratio: 1;\r\n\r\nmax-width: 200px;\r\nmax-height: 200px;\r\n\r\nborder: 2px solid white;\r\nbackground-color: black;\r\nfont-size: 120%;\r\n\r\nbutton { text-align: center; border: 0px solid white }\r\n\r\nbutton:where([data-pos="tl"]) { border-width: 0px 2px 2px 0px };\r\nbutton:where([data-pos="tr"]) { border-width: 2px 2px 0px 0px };\r\nbutton:where([data-pos="bl"]) { border-width: 0px 0px 2px 2px };\r\nbutton:where([data-pos="br"]) { border-width: 2px 0px 0px 2px };\r\n}\r\n#zm_floater_container:has(#zm_collapse[data-collapsed="true"]) {\r\nheight: 50px;\r\n\r\nbutton:not(#zm_collapse) {\r\n display: none;\r\n}\r\n}\r\n#canvasDiv:has(#colorSelector[style *= "block"]) #zm_floater_container {\r\nbottom: 50px;\r\n}\r\n\r\n.zm_corner { border: 2px solid white; }\r\n\r\n#zm_collapse {\r\ngrid-row: 3;\r\ngrid-column: 3;\r\n}\r\n#zm_collapse[data-collapsed="true"] {\r\ngrid-row: 1;\r\ngrid-column: 1;\r\nborder-width: 0px;\r\n}'; + + // assets/nlist_spinner.png + var nlist_spinner_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAYCAYAAADH2bwQAAAAcklEQVQokcWSXQrAIAyDv8oOoPe/49wJzB7coIg/jA2WPhQ1TdOiATsThNmjJ8QV4XjdokIkRHqiEAF2NAgoSw8GlCuD3K3zYEzgFdSQBbA15LaYQN1i9lU+3y16CgDqjSl/MKBoMIk5DyNk46sf9SfhBITwI86iGhy9AAAAAElFTkSuQmCC"; + + // assets/numlist.css.ts + var CSS = ` +#settingsMenu .zm_nml_btn_container button { + font-size: 2em; + padding: 0px; + margin: 0px; } -runAfterReset(() => { - window.zoom_level = 1 - rescale() -}) +#settingsMenu .zm_nml_icontainer { + align-self: center +} -runAfterLoad(() => { - patch_keybinds() - patch_ui() -}) +#settingsMenu .zm_nml_setting { + display: grid; + grid-template-columns: 7em 1fr; +} + +#settingsMenu .zm_nml_setting span { + input { + width: 2em; + margin-right: 4px; + margin-bottom: 4px; + } + + input:focus { + outline: none; + box-shadow: none; + border-color: white; + } + + /* Sorry, firefox users */ + input::-webkit-inner-spin-button, + input::-webkit-outer-spin-button { + -webkit-appearance: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 0.75em; + + background: #000 url(${nlist_spinner_default}) no-repeat center center; + background-size: 100%; + border-left: 2px solid var(--theme); + + image-rendering: pixelated; + opacity: 0.8; + } + + input::-webkit-inner-spin-button:hover, + input::-webkit-outer-spin-button:active { + border-left: 2px solid white; + opacity: 1; + } +} +`; + var numlist_css_default = CSS; + + // assets/multisetting.css + var multisetting_default = '.zm_ms_row {\r\n display: grid;\r\n grid-template-columns: 2.2em 1fr; \r\n}\r\n\r\n.zm_ms_row[data-current="false"] {\r\n .zm_ms_selbtn { color: transparent }\r\n}\r\n\r\n.zm_ms_selbtn.zm_ms_selbtn:not(#_) {\r\n height: 100%;\r\n width: calc(100% - 10px);\r\n\r\n margin-right: 2px;\r\n padding: 0px;\r\n\r\n border: 2px solid var(--theme);\r\n font-size: 1.5em;\r\n}'; + + // src/patcher.ts + var Patcher = class { + zoom_data_div; + floater_div; + canvas_div; + settings; + panmode_sel; + constructor(settings) { + this.settings = settings; + const style_div = document.createElement("style"); + style_div.innerHTML = main_default; + document.head.appendChild(style_div); + dependOn("betterSettings.js", () => { + const style_div2 = document.createElement("style"); + style_div2.innerHTML = numlist_css_default + multisetting_default; + document.head.appendChild(style_div2); + }); + this.canvas_div = document.getElementById("canvasDiv"); + this.canvas_div.insertAdjacentHTML("beforeend", floater_default); + this.floater_div = document.getElementById("zm_floater_container"); + this.panmode_sel = document.getElementById("zm_panmode_sel"); + this.panmode_sel.onclick = () => { + switch (this.panmode_sel.innerText) { + case "C": + this.panmode_sel.innerText = "F"; + break; + case "F": + this.panmode_sel.innerText = "U"; + break; + case "U": + this.panmode_sel.innerText = "C"; + break; + } + }; + this.zoom_data_div = document.createElement("div"); + this.zoom_data_div.id = "zm_data_div"; + document.getElementById("logDiv")?.prepend(this.zoom_data_div); + document.getElementById("controlsTable")?.lastElementChild?.insertAdjacentHTML("beforebegin", ctrl_info_default); + this.update_from_settings(); + runAfterLoad(() => { + const cb = this.update_from_settings.bind(this); + for (const elem of document.querySelectorAll("#betterSettings\\/div\\/zoom\\.js span.setting-span input")) { + elem.addEventListener(elem.classList.contains("toggleInput") ? "click" : "change", cb); + } + }); + } + update_from_settings() { + this.floater_div.style.display = this.settings.show_floater.value ? "grid" : "none"; + this.canvas_div.style.backgroundColor = this.settings.canvas_bkg.value ?? "#252525"; + } + }; + + // src/main.ts + dependOn("betterSettings.js", () => { + const on_change = { cb: () => { + } }; + const settings_manager = new CustomSettingsManager(on_change); + const patcher = new Patcher(settings_manager); + const handler = new Handler(settings_manager, patcher); + }, true); +})(); diff --git a/mods/zoom_legacy.js b/mods/zoom_legacy.js new file mode 100644 index 00000000..28016110 --- /dev/null +++ b/mods/zoom_legacy.js @@ -0,0 +1,322 @@ +const zoom_levels = [ + 0.5, + 1, + 2, + 3, + 6, + 12 +] +window.zoom_data_div = null +window.zoom_level = 1 +window.zoom_panning = [0,0] + +let colour_setting; + +dependOn("betterSettings.js", () => { + const settings_tab = new SettingsTab("zoom.js"); + colour_setting = new Setting( + "Canvas background", + "canvas_bkg", + settingType.COLOR, + false, + defaultValue="#252525" + ); + + settings_tab.registerSettings(undefined, colour_setting) + settingsManager.registerTab(settings_tab) +}) + +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"] + ] + + 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(){ + const CSS = ` + #zm_data_div { margin-bottom: 10px } + #canvasDiv { overflow: hidden; background-color: var(--opac-85) } + + @media(pointer=coarse){ + #zm_floater_container#zm_floater_container { + width: 40%; + height: auto; + } + #zm_floater_container:has(#zm_collapse[data-collapsed="true"]){ + width: calc(40% / 3); + } + } + + @media(pointer:coarse) and (orientation:landscape){ + #zm_floater_container#zm_floater_container { + width: auto; + top: 5px; + } + #zm_floater_container:has(#zm_collapse[data-collapsed="true"]){ + width: calc(40% / 3); + } + } + + #colorSelector { z-index: 1; right: 5px } + #zm_floater_container { + position: absolute; + display: grid; + + 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"]) { + height: 50px; + + button:not(#zm_collapse) { + display: none; + } + } + #canvasDiv:has(#colorSelector[style *= "block"]) #zm_floater_container { + bottom: 50px; + } + + .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 = 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.target.dataset.collapsed = evt.target.dataset.collapsed == "true" + ? "false" + : "true" + }, + true, + "zm_collapse" + ), + pan_mode_sel + ) + + const canvas_div = document.getElementById("canvasDiv") + canvas_div.style.backgroundColor = colour_setting?.value ?? "#252525" + canvas_div.appendChild(container) +} + +function rescale(){ + log_info() + + const scale = zoom_levels[zoom_level] + const x = zoom_panning[0] * (pixelSize * scale) + const y = zoom_panning[1] * (pixelSize * scale) + + gameCanvas.style.transform = `translate(${x}px, ${y}px) translateX(-50%) scale(${scale})` +} + +function log_info(){ + // 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",` + + Zoom in/out + + 9/ + 0 + + + + Pan + + W + A + S + D + + + + Pan (fast) + + Shift + + W + A + S + D + + + `) +} + +// 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() +}) \ No newline at end of file