aboutsummaryrefslogtreecommitdiffstats
path: root/root
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2019-07-12 15:19:55 +0000
committers-ol <s-ol@users.noreply.github.com>2019-07-12 15:19:55 +0000
commitf0b8e51fb9cac674d6f892db116e9d37524f6b16 (patch)
tree589a0be3c16e452d12fe167a181f58c96f9571c0 /root
parentfix sassc mismatch (diff)
downloadmmm-f0b8e51fb9cac674d6f892db116e9d37524f6b16.tar.gz
mmm-f0b8e51fb9cac674d6f892db116e9d37524f6b16.zip
add aspect ratios
Diffstat (limited to 'root')
-rw-r--r--root/blog/aspect_ratios/date: time$iso8601-date1
-rw-r--r--root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon92
-rw-r--r--root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon17
-rw-r--r--root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon36
-rw-r--r--root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon48
-rw-r--r--root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon30
-rw-r--r--root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon57
-rw-r--r--root/blog/aspect_ratios/text$markdown.md77
-rw-r--r--root/blog/aspect_ratios/title: text$plain1
9 files changed, 359 insertions, 0 deletions
diff --git a/root/blog/aspect_ratios/date: time$iso8601-date b/root/blog/aspect_ratios/date: time$iso8601-date
new file mode 100644
index 0000000..1f5943f
--- /dev/null
+++ b/root/blog/aspect_ratios/date: time$iso8601-date
@@ -0,0 +1 @@
+2019-07-12
diff --git a/root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon b/root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon
new file mode 100644
index 0000000..d348f4b
--- /dev/null
+++ b/root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon
@@ -0,0 +1,92 @@
+assert MODE == 'CLIENT', '[nossr]'
+
+import CanvasApp from require 'mmm.canvasapp'
+import div from require 'mmm.dom'
+
+class UIDemo extends CanvasApp
+ width: nil
+ height: nil
+ new: () =>
+ super!
+
+ @canvas.width = nil
+ @canvas.height = nil
+ @canvas.style.width = '100%'
+ @canvas.style.height = '100%'
+ @canvas.style.border = '2px solid var(--gray-dark)'
+
+ @node = div @canvas, style: {
+ position: 'relative'
+ resize: 'horizontal'
+ overflow: 'hidden'
+
+ width: '576px'
+ height: '324px'
+ minWidth: '324px'
+ maxWidth: '742px'
+
+ margin: 'auto'
+ padding: '10px'
+ boxSizing: 'border-box'
+ }
+
+ -- match size of current parent element and return it (for interactive resizing in the demo)
+ updateSize: =>
+ { :clientWidth, :clientHeight } = @canvas.parentElement
+ @canvas.width, @canvas.height = clientWidth, clientHeight
+ @canvas.width, @canvas.height
+
+ -- set up a coordinate system such that the virtual viewport
+ -- of size (w, h) is centered on (0,0) and fills the canvas
+ -- returns remaining margins on the two axes
+ fit: (w, h) =>
+ width, height = @updateSize!
+ @ctx\translate width/2, height/2
+
+ -- maximum scale without cropping either axis
+ scale = math.min (width/w), (height/h)
+ @ctx\scale scale, scale
+
+ -- calculate remaining space on x/y axis
+ rx = (width/scale) - w
+ ry = (height/scale) - h
+
+ -- margins are half of the remaining space
+ rx / 2, ry / 2
+
+ strokeRect: (cx, cy, w, h) =>
+ lw = @ctx.lineWidth / 2
+ @ctx\strokeRect cx - w/2 + lw, cy - h/2 + lw,
+ w - 2*lw, h - 2*lw,
+
+class Box
+ new: (@cx, @cy, @w, @h) =>
+
+ rect: =>
+ @cx, @cy, @w, @h
+
+class Example extends UIDemo
+ click: =>
+ if @naive
+ @naive = false
+ else
+ if @show_boxes
+ @naive = true
+ @show_boxes = not @show_boxes
+
+ text: (box, text, align='center', my=.5) =>
+ mx = .1
+ @ctx.textAlign = align
+
+ if align == 'left'
+ @ctx\fillText text, box.cx + mx - box.w/2, box.cy + my
+ else if align == 'center'
+ @ctx\fillText text, box.cx, box.cy + my
+ if align == 'right'
+ @ctx\fillText text, box.cx - mx + box.w/2, box.cy + my
+
+{
+ :UIDemo
+ :Example
+ :Box
+}
diff --git a/root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon
new file mode 100644
index 0000000..499f9aa
--- /dev/null
+++ b/root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon
@@ -0,0 +1,17 @@
+=>
+ assert MODE == 'CLIENT', '[nossr]'
+
+ import UIDemo from @get '_base: table'
+
+ class FitDemo extends UIDemo
+ draw: =>
+ @fit 16, 9
+ @ctx.fillStyle = 'red'
+ @ctx\fillRect -8, -4.5, 16, 9
+
+ @ctx.fillStyle = 'black'
+ @ctx.font = '6px Arial'
+ @ctx.textAlign = 'center'
+ @ctx\fillText '16:9', 0, 2
+
+ FitDemo!
diff --git a/root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon
new file mode 100644
index 0000000..38aeac1
--- /dev/null
+++ b/root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon
@@ -0,0 +1,36 @@
+=>
+ assert MODE == 'CLIENT', '[nossr]'
+
+ import UIDemo from @get '_base: table'
+
+ arr = (args) ->
+ with js.new js.global.Array
+ for i in *args
+ \push i
+
+ class PerforateDemo extends UIDemo
+ draw: =>
+ @fit 16, 9
+
+ @ctx.lineWidth = 0.15
+ @ctx.strokeStyle = 'green'
+ @strokeRect 0, -3.5, 16, 2
+ @strokeRect 0, 3.5, 16, 2
+
+ @ctx\setLineDash arr { 0.4 }
+ @ctx\beginPath!
+ @ctx\moveTo 0, -4.5
+ @ctx\lineTo 0, -2.5
+ @ctx\moveTo 0, 2.5
+ @ctx\lineTo 0, 4.5
+ @ctx\stroke!
+ @ctx\setLineDash arr {}
+
+ @ctx.strokeStyle = 'blue'
+ @strokeRect 0, 0, 16, 5
+
+ @ctx.font = '4px Arial'
+ @ctx.textAlign = 'center'
+ @ctx\fillText '16:5', 0, 1.5
+
+ PerforateDemo!
diff --git a/root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon
new file mode 100644
index 0000000..2017e20
--- /dev/null
+++ b/root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon
@@ -0,0 +1,48 @@
+=>
+ assert MODE == 'CLIENT', '[nossr]'
+
+ import Box, Example from @get '_base: table'
+
+ class Sidebar extends Example
+ draw: =>
+ margin_x, margin_y = @fit 16, 9
+ if @naive
+ margin_x, margin_y = 0, 0
+
+ @ctx.font = '1.5px Arial'
+ sidebar = Box -7 - margin_x, -1, 2, 7
+ @text sidebar, 'A', 'center', -1.8
+ @text sidebar, 'B', 'center', -0.3
+ @text sidebar, 'C', 'center', 1.2
+ @text sidebar, 'D', 'center', 2.7
+
+ bottom_l = Box -4 - margin_x, 3.5 + margin_y, 8, 2
+ bottom_r = Box 4 + margin_x, 3.5 + margin_y, 8, 2
+
+ @text bottom_l, 'levelname', 'left'
+ @text bottom_r, 'info a b c', 'right'
+
+ main = Box 1, -1, 14, 7
+ @ctx.lineWidth = 0.1
+ @ctx.strokeStyle = 'black'
+ @ctx\beginPath!
+ for x=-5.5, 7.5
+ @ctx\moveTo x, -4.5
+ @ctx\lineTo x, 2.5
+
+ for y=-4, 2
+ @ctx\moveTo -6, y
+ @ctx\lineTo 8, y
+ @ctx\stroke!
+
+ if @show_boxes
+ @ctx.lineWidth = 0.1
+ @ctx.strokeStyle = 'green'
+ @strokeRect sidebar\rect!
+ @strokeRect bottom_l\rect!
+ @strokeRect bottom_r\rect!
+
+ @ctx.strokeStyle = 'blue'
+ @strokeRect main\rect!
+
+ Sidebar!
diff --git a/root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon
new file mode 100644
index 0000000..f108e0f
--- /dev/null
+++ b/root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon
@@ -0,0 +1,30 @@
+=>
+ assert MODE == 'CLIENT', '[nossr]'
+
+ import UIDemo from @get '_base: table'
+
+ arr = (args) ->
+ with js.new js.global.Array
+ for i in *args
+ \push i
+
+ class TearDemo extends UIDemo
+ draw: =>
+ margin_x, margin_y = @fit 16, 9
+
+ @ctx.lineWidth = 0.15
+ @ctx.strokeStyle = 'green'
+ @strokeRect -4 - margin_x, -3.5 - margin_y, 8, 2
+ @strokeRect 4 + margin_x, -3.5 - margin_y, 8, 2
+
+ @strokeRect -4 - margin_x, 3.5 + margin_y, 8, 2
+ @strokeRect 4 + margin_x, 3.5 + margin_y, 8, 2
+
+ @ctx.strokeStyle = 'blue'
+ @strokeRect 0, 0, 16, 5
+
+ @ctx.font = '4px Arial'
+ @ctx.textAlign = 'center'
+ @ctx\fillText '16:5', 0, 1.5
+
+ TearDemo!
diff --git a/root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon
new file mode 100644
index 0000000..dae4dfb
--- /dev/null
+++ b/root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon
@@ -0,0 +1,57 @@
+=>
+ assert MODE == 'CLIENT', '[nossr]'
+
+ import Box, Example from @get '_base: table'
+
+ class VTK extends Example
+ draw: =>
+ margin_x, margin_y = @fit 16, 9
+ if @naive
+ margin_x, margin_y = 0, 0
+
+ levelname = Box -4 - margin_x, -3.5 - margin_y, 8, 2
+ settings = Box 4 + margin_x, -3.5 - margin_y, 8, 2
+ infobar = Box -4 - margin_x, 3.5 + margin_y, 8, 2
+ exit = Box 4 + margin_x, 3.5 + margin_y, 8, 2
+
+ main = Box 0, 0, 16, 5
+
+ @ctx.font = '1.5px Arial'
+ @text levelname, 'levelname', 'left'
+ @text infobar, 'info a b c', 'left'
+ @text settings, 'settings', 'right'
+ @text exit, 'exit', 'right'
+
+ @ctx.lineWidth = 0.2
+ @ctx.strokeStyle = 'black'
+ @ctx\beginPath!
+ @ctx\moveTo -8 - margin_x, -2.5 - margin_y
+ @ctx\lineTo 8 + margin_x, -2.5 - margin_y
+ @ctx\moveTo -8 - margin_x, 2.5 + margin_y
+ @ctx\lineTo 8 + margin_x, 2.5 + margin_y
+ @ctx\stroke!
+
+ @ctx.lineWidth = 0.1
+ @ctx.strokeStyle = 'gray'
+ @ctx\beginPath!
+ for x=-7.5, 7.5
+ @ctx\moveTo x, -2.5
+ @ctx\lineTo x, 2.5
+
+ for y=-2, 2
+ @ctx\moveTo -8, y
+ @ctx\lineTo 8, y
+ @ctx\stroke!
+
+ if @show_boxes
+ @ctx.lineWidth = 0.1
+ @ctx.strokeStyle = 'green'
+ @strokeRect levelname\rect!
+ @strokeRect settings\rect!
+ @strokeRect infobar\rect!
+ @strokeRect exit\rect!
+
+ @ctx.strokeStyle = 'blue'
+ @strokeRect main\rect!
+
+ VTK!
diff --git a/root/blog/aspect_ratios/text$markdown.md b/root/blog/aspect_ratios/text$markdown.md
new file mode 100644
index 0000000..dea0249
--- /dev/null
+++ b/root/blog/aspect_ratios/text$markdown.md
@@ -0,0 +1,77 @@
+Dealing with different screen sizes and formats can be annoying.
+On desktop PCs, 16:9 is the most common aspect ratio these days, but depending on where you are rendering your content,
+it might still be a different format, for example when a task bar, broswer navigation bar or similar is slicing away some of your screen real-estate.
+For mobile games the situation is a bit worse, because there are simply more different aspect ratios out there
+and different systems may require extra space for on-screen navigation keys, statusbars etc.
+
+In this post I want to present a simple technique for layouting **simple UIs for games** that can work across a range of similar screens.
+If you are looking for a way to design a UI-heavy app, or something that needs to work across very different environments (like phones, tablets and desktop PCs), this is probably not what you are looking for.
+In any case, if you just want to take a quick look you can jump down to the [examples](#examples) at the bottom of the page as well.
+
+## step one: `fit`
+The most trivial solution is to simply choose the aspect ratio you would like to work with,
+and then fit a rectangle with that ratio into whichever space is available.
+Depending on the screen, this may either be a perfect fit, leave space on the horizontal axis, or leave space on the vertical axis:
+(drag the lower right corner to see this approach react to different screen sizes)
+
+<mmm-embed path="interactive" facet="fit" nolink></mmm-embed>
+
+This is pretty trivial to accomplish in code.
+You can calculate the scales in relation to your reference grid on the x and y axis separately, and simply use the one with a lower value.
+Establishing a reference grid and knowing the scale to translate between it and the physical screen sizes will also help
+designing the layout in general, as we will see later.
+Here is a simple example in JS:
+
+```js
+// measured from somewhere
+const width = 2000;
+const height = 1080;
+
+const sx = width / 16;
+const sy = height / 9;
+
+const scale = Math.min(sx, sy);
+const offset_x = width * (1 - scale);
+const offset_y = height * (1 - scale);
+
+// the biggest 16:9 rectangle you can fit is (scale*width, scale*height) large
+// and it's top-left corner is at (offset_x/2, offset_y/2)
+```
+
+The problem with this is that it simply doesn't look very good. When the ratio is a perfect match there is of course no problem,
+but otherwise the empty space makes the screen look empty (especially if there are system UI elements next to it).
+
+## step two: `perforate`
+So how can this be imroved? We would like to use the unused space on the x or y axis, but we can't just scale everything up,
+or we would start cropping important pieces of UI. Stretching the game to fill the screen also doesn't work for obvious reasons.
+
+To proceed, the UI has to be 'perforated' into different sections that are independent from each other.
+This is where establishing a reference grid becomes useful to orient ourselves in the layout.
+In my example I am splitting the screen into a main content section that is 16:5 units large, as well as a top and bottom bar.
+The two bars are also split in half vertically in the middle, for reasons that we will see in the third step.
+
+<mmm-embed path="interactive" facet="perforate" nolink></mmm-embed>
+
+It's imortant to note that how you divide the screen up depends completely on your game/interface of course.
+The layout I am using here is just an example; at the end of this post you can find another one with a different layout as well.
+
+## step three: `tear`
+
+Now that the sections are defined, in the last step we can 'tear' the sections apart and decide how they should react to the
+left-over space calculated in step one individiually.
+In my layout, I stretch the top and bottom bars to fill the screen completely horizontally.
+By dividing the bars into separate sections in the last step, I know how much space is guaranteed to be available on each side.
+If there is room left on the vertical axis, I move the bars out from the center to give the content some visual space:
+
+<mmm-embed path="interactive" facet="tear" nolink></mmm-embed>
+
+Once again, how you make the pieces behave depends a lot on what elements your UI has in the first place, and how you want it to look and feel.
+
+# examples
+
+Finally, here are two examples with a bit more visual coherence to show how this actually ends up working.
+You can click on these to cycle between the normal view, showing the frames used to subdivide the canvas, and viewing `fit` only for comparison.
+
+<mmm-embed path="interactive" facet="vtk" nolink></mmm-embed>
+<mmm-embed path="interactive" facet="sidebar" nolink></mmm-embed>
+
diff --git a/root/blog/aspect_ratios/title: text$plain b/root/blog/aspect_ratios/title: text$plain
new file mode 100644
index 0000000..464c7ef
--- /dev/null
+++ b/root/blog/aspect_ratios/title: text$plain
@@ -0,0 +1 @@
+Aspect-ratio independent UIs