From 81c7f97d72b3a5bf95d8cfe1e5c7daa71ba82f5d Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Tue, 3 Nov 2020 13:50:34 -0700 Subject: [PATCH] Improve behavior on mobile - Hide the key hints in portrait mode - Make auto-scaling more robust; it now handles when the player root is wider than the actual play area, it better understands the inventory behavior in portrait mode, and it recognizes when it needs to shrink; with these changes, the game actually fills the screen on both Firefox and Chrome on my phone! - Replace the text buttons with SVG icons - Add a little more contrast to button edges - Fix alignment of the heart/time/score counters in portrait mode - Detect movement based on where the touch is relative to the level viewport, not the entire play area (oof) --- index.html | 26 +++++++++++++++++++------- js/main.js | 54 ++++++++++++++++++++++++++++++++++++++---------------- style.css | 38 ++++++++++++++++++++++++++++++++------ 3 files changed, 89 insertions(+), 29 deletions(-) diff --git a/index.html b/index.html index 04e9bd8..c829e7e 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@ - +
@@ -34,9 +34,13 @@

Level 1 — Key Pyramid

@@ -117,10 +121,18 @@
- - - - + + + +
diff --git a/js/main.js b/js/main.js index 2314f8f..7b3dc6c 100644 --- a/js/main.js +++ b/js/main.js @@ -528,7 +528,7 @@ class Player extends PrimaryView { // Figure out where these touches are, relative to the game area // TODO allow starting a level without moving? - let rect = touch_target.getBoundingClientRect(); + let rect = this.level_el.getBoundingClientRect(); for (let touch of ev.changedTouches) { // Normalize touch coordinates to [-1, 1] let rx = (touch.clientX - rect.left) / rect.width * 2 - 1; @@ -1243,10 +1243,13 @@ class Player extends PrimaryView { adjust_scale() { // TODO make this optional let style = window.getComputedStyle(this.root); + // If we're not visible, no layout information is available and this is impossible + if (style['display'] === 'none') + return; + let is_portrait = !! style.getPropertyValue('--is-portrait'); // The base size is the size of the canvas, i.e. the viewport size times the tile size -- // but note that we have 2x4 extra tiles for the inventory depending on layout - // TODO if there's ever a portrait view for phones, this will need adjusting let base_x, base_y; if (is_portrait) { base_x = this.conductor.tileset.size_x * this.renderer.viewport_size_x; @@ -1256,24 +1259,43 @@ class Player extends PrimaryView { base_x = this.conductor.tileset.size_x * (this.renderer.viewport_size_x + 4); base_y = this.conductor.tileset.size_y * this.renderer.viewport_size_y; } - // The main UI is centered in a flex item with auto margins, so the extra space available is - // the size of those margins (which naturally discounts the size of the buttons and music - // title and whatnot, so those automatically reserve their own space) - if (style['display'] === 'none') { - // the computed margins can be 'auto' in this case - return; + // Unfortunately, finding the available space is a little tricky. The container is a CSS + // flex item, and the flex cell doesn't correspond directly to any element, so there's no + // way for us to query its size directly. We also have various stuff up top and down below + // that shouldn't count as available space. So instead we take a rough guess by adding up: + // - the space currently taken up by the canvas + let avail_x = this.renderer.canvas.offsetWidth; + let avail_y = this.renderer.canvas.offsetHeight; + // - the space currently taken up by the inventory, depending on orientation + if (is_portrait) { + avail_y += this.inventory_el.offsetHeight; } - let extra_x = parseFloat(style['margin-left']) + parseFloat(style['margin-right']); - let extra_y = parseFloat(style['margin-top']) + parseFloat(style['margin-bottom']); - // The total available space, then, is the current size of the canvas (and inventory, when - // appropriate) plus the size of the margins - let total_x = extra_x + this.renderer.canvas.offsetWidth + this.inventory_el.offsetWidth; - let total_y = extra_y + this.renderer.canvas.offsetHeight; + else { + avail_x += this.inventory_el.offsetWidth; + } + // - the difference between the size of the play area and the size of our root (which will + // add in any gap around the player, e.g. if the controls stretch the root to be wider) + let root_rect = this.root.getBoundingClientRect(); + let player_rect = this.root.querySelector('.-main-area').getBoundingClientRect(); + avail_x += root_rect.width - player_rect.width; + avail_y += root_rect.height - player_rect.height; + // - the margins around our root, which consume all the extra space + let margin_x = parseFloat(style['margin-left']) + parseFloat(style['margin-right']); + let margin_y = parseFloat(style['margin-top']) + parseFloat(style['margin-bottom']); + avail_x += margin_x; + avail_y += margin_y; + // If those margins are zero, by the way, we risk being too big for the viewport already, + // and we need to subtract any extra scroll on the body + if (margin_x === 0 || margin_y === 0) { + avail_x -= document.body.scrollWidth - document.body.clientWidth; + avail_y -= document.body.scrollHeight - document.body.clientHeight; + } + let dpr = window.devicePixelRatio || 1.0; // Divide to find the biggest scale that still fits. But don't exceed 90% of the available // space, or it'll feel cramped (except on small screens, where being too small HURTS). - let maxfrac = total_x < 800 ? 1 : 0.9; - let scale = Math.floor(maxfrac * dpr * Math.min(total_x / base_x, total_y / base_y)); + let maxfrac = is_portrait ? 1 : 0.9; + let scale = Math.floor(maxfrac * dpr * Math.min(avail_x / base_x, avail_y / base_y)); if (scale <= 1) { scale = 1; } diff --git a/style.css b/style.css index 9bdeb48..16fcd32 100644 --- a/style.css +++ b/style.css @@ -37,7 +37,8 @@ button { font-family: inherit; color: white; background: var(--button-bg-color); - border: none; + border: 1px solid hsl(225, 10%, 15%); + box-shadow: 0 1px 0 hsl(225, 10%, 10%); border-radius: 0.25em; text-transform: lowercase; cursor: pointer; @@ -45,6 +46,10 @@ button { button:hover { background: var(--button-bg-hover-color); } +button:active { + box-shadow: none; + transform: translateY(1px); +} button:disabled { color: #606060; background: #202020; @@ -110,6 +115,15 @@ a:active { color: hsl(0, 50%, 60%); } +svg.svg-icon { + width: 1em; + height: 1em; + vertical-align: middle; + + stroke: none; + fill: currentColor; +} + /* Overlay styling */ .overlay { display: flex; @@ -269,6 +283,13 @@ label.option .option-label { /* TODO */ } +@media (max-width: 800px) { + .dialog { + max-width: 90%; + max-height: 90%; + } +} + /* Bits and pieces */ img.compat-icon { margin: 0 0.25em 0.125em; @@ -687,7 +708,7 @@ dl.score-chart .-sum { background-size: calc(var(--tile-width) * var(--scale)) calc(var(--tile-height) * var(--scale)); width: calc(4 * var(--tile-width) * var(--scale)); - min-height: calc(2 * var(--tile-height) * var(--scale)); + height: calc(2 * var(--tile-height) * var(--scale)); } #player .inventory img { width: calc(var(--tile-width) * var(--scale)); @@ -738,6 +759,7 @@ dl.score-chart .-sum { .play-controls, .demo-controls { display: flex; + align-items: center; gap: 0.25em; } .play-controls { @@ -802,10 +824,10 @@ main.--has-demo .demo-controls { /* Rearrange the grid to be vertical */ grid: "level level" - "chips inventory" - "time inventory" - "bonus inventory" - / min-content min-content + "chips inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + "time inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + "bonus inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + / 1fr min-content ; row-gap: 0.5em; column-gap: 1em; @@ -823,6 +845,10 @@ main.--has-demo .demo-controls { z-index: 1; font-size: calc(var(--tile-height) * var(--scale) / 2.5); } + #player .controls .keyhint { + /* Hide key hints, they take up surprisingly a lot of space */ + display: none; + } #player-music { /* Stack the title/artist on the volume, since they don't fit well side by side */ font-size: 0.875em;