diff --git a/src/css/ingame_hud/dialogs.scss b/src/css/ingame_hud/dialogs.scss index cc742d42..38e359e4 100644 --- a/src/css/ingame_hud/dialogs.scss +++ b/src/css/ingame_hud/dialogs.scss @@ -214,6 +214,53 @@ } } } + + .checkBoxFormElem { + @include S(margin, 10px, 0); + > label { + @include S(margin-right, 10px); + } + display: flex; + align-items: center; + } + + .enum { + @include S(margin, 10px, 0); + display: grid; + grid-template-columns: auto 1fr auto; + @include S(grid-gap, 4px); + @include S(width, 200px); + + > * { + background: #fff; + text-align: center; + pointer-events: all; + cursor: pointer; + @include S(border-radius, $globalBorderRadius); + @include S(padding, 4px); + + transition: background-color 0.12s ease-in-out; + &:hover { + background-color: #fafafa; + } + + @include DarkThemeOverride { + background-color: $darkModeControlsBackground; + color: #ddd; + &:hover { + background-color: darken($darkModeControlsBackground, 2); + } + } + + &.toggle { + @include S(width, 20px); + } + + &.value { + transform: none !important; + } + } + } } > .buttons { diff --git a/src/js/core/modal_dialog_forms.js b/src/js/core/modal_dialog_forms.js index aac81d82..9242c57e 100644 --- a/src/js/core/modal_dialog_forms.js +++ b/src/js/core/modal_dialog_forms.js @@ -1,6 +1,7 @@ import { BaseItem } from "../game/base_item"; import { ClickDetector } from "./click_detector"; import { Signal } from "./signal"; +import { safeModulo } from "./utils"; /* * *************************************************** @@ -235,3 +236,56 @@ export class FormElementItemChooser extends FormElement { focus() {} } + +export class FormElementEnum extends FormElement { + constructor({ id, label = null, options, valueGetter, textGetter }) { + super(id, label); + this.options = options; + this.valueGetter = valueGetter; + this.textGetter = textGetter; + this.index = 0; + + this.element = null; + } + + getHtml() { + return ` +
+ ${this.label ? `` : ""} +
+ +
${this.textGetter(this.options[0])}
+ +
+
+ `; + } + + /** + * @param {HTMLElement} parent + * @param {Array} clickTrackers + */ + bindEvents(parent, clickTrackers) { + this.element = this.getFormElement(parent); + + const children = this.element.children; + for (let i = 0; i < children.length; ++i) { + const child = children[i]; + const detector = new ClickDetector(child, { preventDefault: false }); + clickTrackers.push(detector); + const change = child.classList.contains("prev") ? -1 : 1; + detector.click.add(() => this.toggle(change), this); + } + } + + getValue() { + return this.valueGetter(this.options[this.index]); + } + + toggle(amount) { + this.index = safeModulo(this.index + amount, this.options.length); + this.element.querySelector(".value").innerText = this.textGetter(this.options[this.index]); + } + + focus() {} +} diff --git a/src/js/game/hud/parts/screenshot_exporter.js b/src/js/game/hud/parts/screenshot_exporter.js index 31e90a4f..0057e03c 100644 --- a/src/js/game/hud/parts/screenshot_exporter.js +++ b/src/js/game/hud/parts/screenshot_exporter.js @@ -9,10 +9,38 @@ import { StaticMapEntityComponent } from "../../components/static_map_entity"; import { KEYMAPPINGS } from "../../key_action_mapper"; import { BaseHUDPart } from "../base_hud_part"; import { DialogWithForm } from "../../../core/modal_dialog_elements"; -import { FormElementInput, FormElementCheckbox } from "../../../core/modal_dialog_forms"; +import { FormElementInput, FormElementCheckbox, FormElementEnum } from "../../../core/modal_dialog_forms"; const logger = createLogger("screenshot_exporter"); +/** + * @typedef {{mode: string, resolution?: number}} QualityOptions + */ + +/** + * @type {{id: string, options: QualityOptions}[]} + */ +const screenshotQualities = [ + { + id: "high", + options: { mode: "regular", resolution: 16384 }, + }, + { + id: "medium", + options: { mode: "regular", resolution: 4096 }, + }, + { + id: "low", + options: { mode: "regular", resolution: 1024 }, + }, + { + id: "map", + options: { mode: "map" }, + }, +]; +// @TODO: translation (T.dialogs.exportScreenshotWarning.qualities) +const qualityNames = { high: "High", medium: "Medium", low: "Low", map: "Map" }; + export class HUDScreenshotExporter extends BaseHUDPart { createElements() {} @@ -26,23 +54,24 @@ export class HUDScreenshotExporter extends BaseHUDPart { return; } + const qualityInput = new FormElementEnum({ + id: "screenshotQuality", + options: screenshotQualities, + valueGetter: quality => quality.options, + // @TODO: translation (T.dialogs.exportScreenshotWarning.qualityLabel) + textGetter: quality => "Quality:" + " " + qualityNames[quality.id], + }); const layerInput = new FormElementCheckbox({ id: "screenshotLayer", + // @TODO: translation (T.dialogs.exportScreenshotWarning.descLayer) label: "Include wires layer", defaultValue: this.root.currentLayer === "wires" ? true : false, }); - const qualityInput = new FormElementInput({ - id: "screenshotQuality", - label: "Pixel width per tile", - placeholder: "", - defaultValue: "", - validator: val => !isNaN(val) && parseInt(val) === parseFloat(val) && !isNaN(parseInt(val, 10)), - }); const dialog = new DialogWithForm({ app: this.root.app, title: T.dialogs.exportScreenshotWarning.title, desc: T.dialogs.exportScreenshotWarning.desc, - formElements: [layerInput, qualityInput], + formElements: [qualityInput, layerInput], buttons: ["cancel:good", "ok:bad"], }); @@ -53,7 +82,11 @@ export class HUDScreenshotExporter extends BaseHUDPart { ); } - doExport(wiresLayer, quality) { + /** + * @param {boolean} wiresLayer + * @param {QualityOptions} options + */ + doExport(wiresLayer, options) { logger.log("Starting export ..."); // Find extends @@ -76,12 +109,15 @@ export class HUDScreenshotExporter extends BaseHUDPart { const dimensions = maxChunk.sub(minChunk); logger.log("Dimensions:", dimensions); - let chunkSizePixels = quality * globalConfig.mapChunkSize; const maxDimensions = Math.max(dimensions.x, dimensions.y); - if (maxDimensions > 128) { - chunkSizePixels = Math.max(1, Math.floor(chunkSizePixels * (128 / maxDimensions))); - } + // we want integer pixels per tile + // if resolution too low, we want integer pixels per chunk + const chunkSizePixels = + maxDimensions * globalConfig.mapChunkSize > options.resolution + ? Math.max(1, Math.floor(options.resolution / maxDimensions)) + : Math.floor(options.resolution / (maxDimensions * globalConfig.mapChunkSize)) * + globalConfig.mapChunkSize; logger.log("ChunkSizePixels:", chunkSizePixels); const chunkScale = chunkSizePixels / globalConfig.mapChunkWorldSize;