1import gleam/float2import gleam/int3import gleam/list4import gleeunit/should5import klotski/families6import klotski/layout.{Box, Split, Stack}7import klotski/shading89/// A tall phone, a short one, a phone on its side, a fold, a desktop, and a10/// keypad phone — which is a 4:3 screen the size of a card.11fn boxes() -> List(#(Int, Int)) {12 [13 #(393, 852),14 #(360, 640),15 #(852, 393),16 #(768, 800),17 #(1440, 900),18 #(240, 320),19 ]20}2122/// The composition does not change with the screen. Whatever shape the hardware23/// is, the picture has to hold a board, and every candidate the search rejects is24/// one that could not.25pub fn every_screen_gets_a_board_that_fits_test() {26 boxes()27 |> list.all(fn(shape) {28 let #(w, h) = shape29 let l = layout.choose(Box(0, 0, w, h), 2.0, 3)30 families.all()31 |> list.all(fn(family) {32 let b = layout.board_layout(l, family)33 b.cell >= 1634 && b.plate.x >= 035 && b.plate.y >= l.bar_h36 && b.plate.x + b.plate.w <= l.vw37 && b.plate.y + b.plate.h <= l.vh38 })39 })40 |> should.be_true41}4243/// The cell is in whole shading periods so an ordered screen does not flash when44/// a block slides: a move of one cell changes the phase by the cell's own width,45/// which has to be a whole number of periods or the pattern a block sits on46/// differs before and after.47pub fn a_cell_is_always_whole_periods_test() {48 boxes()49 |> list.all(fn(shape) {50 let #(w, h) = shape51 let l = layout.choose(Box(0, 0, w, h), 2.0, 3)52 families.all()53 |> list.all(fn(family) {54 layout.board_layout(l, family).cell % shading.period == 055 })56 })57 |> should.be_true58}5960/// On a landscape screen the height is all spoken for and a foot of chrome under61/// the board is paid for out of the board, so the same two windows stop stacking62/// and sit side by side. Two windows on a desktop is not a concession to63/// landscape — it is what this system did with a wide screen.64pub fn a_landscape_screen_sets_the_windows_side_by_side_test() {65 layout.choose(Box(0, 0, 852, 393), 3.0, 3).mode |> should.equal(Split)66 layout.choose(Box(0, 0, 393, 852), 3.0, 3).mode |> should.equal(Stack)67}6869/// Split, the two windows are a pair: the panel sits beside the plate rather70/// than under it, and its contents are centred in it as one group.71pub fn the_panel_sits_beside_the_plate_when_split_test() {72 let l = layout.choose(Box(0, 0, 900, 420), 2.0, 3)73 l.mode |> should.equal(Split)74 let b = layout.board_layout(l, families.at(0))75 { b.panel.x > b.plate.x + b.plate.w } |> should.be_true76 b.panel.h |> should.equal(b.plate.h)77 // The gauge is measured off whatever is holding it.78 { b.gauge.x >= b.panel.x } |> should.be_true79 { b.gauge.x + b.gauge.w <= b.panel.x + b.panel.w } |> should.be_true80}8182/// Stacked, the controls sit under the board and the actions row has to land on83/// the screen. A board that fits by two units and posts its buttons off the84/// bottom is the failure the foot's floor exists to prevent.85pub fn the_actions_row_always_lands_on_the_screen_test() {86 boxes()87 |> list.all(fn(shape) {88 let #(w, h) = shape89 let l = layout.choose(Box(0, 0, w, h), 2.0, 3)90 families.all()91 |> list.all(fn(family) {92 layout.board_layout(l, family).panel.actions_y + 34 <= l.vh93 })94 })95 |> should.be_true96}9798/// The column is the design width wherever there is room for it. The app is99/// drawn at one scale and the screen does not get a say in it; what the screen100/// has left over becomes desktop, which is what a Macintosh with a big monitor101/// showed.102pub fn a_wide_screen_gives_its_extra_to_the_desktop_test() {103 let l = layout.choose(Box(0, 0, 1440, 900), 2.0, 3)104 { l.col.w < l.vw } |> should.be_true105 { l.col.w <= 320 } |> should.be_true106 // Centred, so the desktop shows on both sides rather than all on one edge.107 { l.col.x > 0 } |> should.be_true108}109110/// A buffer pixel has to land on a whole number of device pixels or the111/// compositor resamples the one thing that must never be resampled. At a display112/// ratio the density divides, the search should find a scale that lands whole.113pub fn a_crisp_scale_is_taken_where_one_exists_test() {114 let l = layout.choose(Box(0, 0, 393, 852), 2.748, 3)115 let per_buffer_pixel = l.scale *. 2.748 /. 3.0116 let whole = int.to_float(float.round(per_buffer_pixel))117 { float.absolute_value(per_buffer_pixel -. whole) <. 0.001 }118 |> should.be_true119}