rect = LayoutTheory.rect row: 0, col: 0, w: 10, h: 2
some_view.frame = CGRectMake rect[:x], rect[:y], rect[:w], rect[:h]
Last active
April 8, 2021 00:21
-
-
Save amirrajan/b007e5df7f61b08b8a2c9e9dcddfa4c3 to your computer and use it in GitHub Desktop.
Layout Theory for RubyMotion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Geometry | |
def left | |
x | |
end | |
def right | |
x + w | |
end | |
def top | |
y | |
end | |
def bottom | |
y + h | |
end | |
def cg_rect | |
CGRectMake(x, y, w, h) | |
end | |
def anchor_rect! h_perc, v_perc | |
self.x += w * h_perc | |
self.y += h * v_perc | |
self | |
end | |
end | |
class Hash | |
include Geometry | |
def x | |
self[:x] || 0 | |
end | |
def x= value | |
self[:x] = value | |
end | |
def y | |
self[:y] || 0 | |
end | |
def y= value | |
self[:y] = value | |
end | |
def w | |
self[:w] || 0 | |
end | |
def h | |
self[:h] || 0 | |
end | |
def to_h | |
Hash.new.merge self | |
end | |
def to_tuple | |
[x, y, w, h] | |
end | |
end | |
class Array | |
include Geometry | |
def x | |
self[0] || 0 | |
end | |
def x= value | |
self[0] = value | |
end | |
def y | |
self[1] || 0 | |
end | |
def y= value | |
self[1] = value | |
end | |
def w | |
self[2] || 0 | |
end | |
def h | |
self[3] || 0 | |
end | |
def merge opts | |
to_h.merge opts | |
end | |
def to_h | |
Hash.new.merge x: x, y: y, w: w, h: h | |
end | |
def to_tuple | |
[x, y, w, h] | |
end | |
def merge opts | |
to_h.merge opts | |
end | |
end | |
module LT | |
class Margin | |
attr_accessor :l, :r, :t, :b | |
def initialize | |
@l = 0 | |
@r = 0 | |
@t = 0 | |
@b = 0 | |
end | |
end | |
class Safe | |
attr_accessor :w, :h, :margin | |
def initialize | |
@w = 0 | |
@h = 0 | |
@margin = Margin.new | |
end | |
end | |
class Grid | |
attr_accessor :w, :h, :margin, :gutter, :col_count, :row_count, :cell_w, :cell_h, :outer_gutter | |
def initialize | |
@w = 0 | |
@h = 0 | |
@gutter = 0 | |
@outer_gutter = 0 | |
@col_count = 0 | |
@row_count = 0 | |
@margin = Margin.new | |
end | |
end | |
class Layout | |
attr_accessor :cell_size, :w, :h, :margin | |
def initialize | |
@margin = Margin.new | |
end | |
end | |
class Device | |
attr_accessor :w, :h, :u, :safe, :grid, :layout, :name, :aspect | |
def initialize | |
@name = "" | |
@w = 0 | |
@h = 0 | |
@u = 0 | |
@safe = Safe.new | |
@grid = Grid.new | |
@layout = Layout.new | |
@aspect = AspectRatio.new | |
end | |
def assert! result, message | |
return if result | |
raise message | |
end | |
def check_math! | |
assert! (@layout.w + @layout.margin.l + @layout.margin.r) == @w, "Math for Width didn't pan out." | |
assert! (@layout.h + @layout.margin.t + @layout.margin.b) == @h, "Math for Height didn't pan out." | |
end | |
end | |
class AspectRatio | |
attr_accessor :w, :h, :u | |
def initialize | |
@w = 0 | |
@h = 0 | |
@u = 0 | |
end | |
end | |
end | |
class LayoutTheory | |
def self.font_relative_scale pt | |
base_line_logical = 14 | |
base_line_actual = font_size_med | |
target_logical = pt | |
target_logical = 1 if target_logical <= 0 | |
(base_line_actual / base_line_logical) * target_logical | |
end | |
def self.font_px_to_pt px | |
(px / 1.33333).floor | |
end | |
def self.font_pt_to_px pt | |
pt * 1.333333 | |
end | |
def self.font_size_cell | |
(cell_height / 1.33333) | |
end | |
def self.font_size_xl | |
font_size_cell | |
end | |
def self.font_size_lg | |
font_size_cell * 0.8 | |
end | |
def self.font_size_med | |
font_size_cell * 0.7 | |
end | |
def self.font_size_sm | |
font_size_cell * 0.6 | |
end | |
def self.font_size | |
font_size_cell * 0.7 | |
end | |
def self.logical_rect | |
@logical_rect ||= [0, 0, UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height] | |
@logical_rect | |
end | |
def self.safe_area | |
[device.safe.margin.l, device.safe.margin.t, device.safe.w, device.safe.h] | |
end | |
def self.layout_rect | |
[device.layout.margin.l, device.layout.margin.t, device.layout.w, device.layout.h] | |
end | |
def self.row_count | |
device.grid.row_count | |
end | |
def self.col_count | |
device.grid.col_count | |
end | |
def self.gutter_height | |
device.grid.gutter | |
end | |
def self.gutter_width | |
device.grid.gutter | |
end | |
def self.cell_height | |
device.layout.cell_size | |
end | |
def self.cell_width | |
device.layout.cell_size | |
end | |
def self.rect_defaults | |
{ | |
row: nil, | |
col: nil, | |
h: 1, | |
w: 1, | |
dx: 0, | |
dy: 0 | |
} | |
end | |
def self.rect opts, &block | |
opts = rect_defaults.merge opts | |
result = safe_area | |
if opts[:row] && opts[:col] && opts[:w] && opts[:h] | |
col = rect_col opts[:col], opts[:w] | |
row = rect_row opts[:row], opts[:h] | |
result = layout_rect.merge x: col.x, | |
y: row.y, | |
w: col.w, | |
h: row.h | |
elsif opts[:row] && !opts[:col] | |
result = rect_row opts[:row], opts[:h] | |
elsif !opts[:row] && opts[:col] | |
result = rect_col opts[:col], opts[:w] | |
else | |
raise "LayoutTheory::rect unable to process opts #{opts}." | |
end | |
if opts[:max_height] && opts[:max_height] >= 0 | |
if result[:h] > opts[:max_height] | |
delta = (result[:h] - opts[:max_height]) * 2 | |
result[:y] += delta | |
result[:h] = opts[:max_height] | |
end | |
end | |
if opts[:max_width] && opts[:max_width] >= 0 | |
if result[:w] > opts[:max_width] | |
delta = (result[:w] - opts[:max_width]) * 2 | |
result[:x] += delta | |
result[:w] = opts[:max_width] | |
end | |
end | |
result[:x] += opts[:dx] | |
result[:y] += opts[:dy] | |
if opts[:include_row_gutter] | |
result[:x] -= device.grid.gutter | |
result[:w] += device.grid.gutter * 2 | |
end | |
if opts[:include_col_gutter] | |
result[:y] -= device.grid.gutter | |
result[:h] += device.grid.gutter * 2 | |
end | |
if block | |
result = block.call result | |
if !result | |
raise "blocks provided to LayoutTheory.rect must return a hash" | |
end | |
end | |
result | |
end | |
def self.rect_center reference, target | |
delta_x = (reference.w - target.w).fdiv 2 | |
delta_y = (reference.h - target.h).fdiv 2 | |
[target.x - delta_x, target.y - delta_y, target.w, target.h] | |
end | |
def self.rect_row index, h | |
row_y = (layout_rect.y) + | |
(device.grid.gutter * index) + | |
(device.layout.cell_size * index) | |
row_h = (device.grid.gutter * (h - 1)) + | |
(device.layout.cell_size * h) | |
layout_rect.merge y: row_y, h: row_h | |
end | |
def self.rect_col index, w | |
col_x = (layout_rect.x) + | |
(device.grid.gutter * index) + | |
(device.layout.cell_size * index) | |
col_w = (device.grid.gutter * (w - 1)) + | |
(device.layout.cell_size * w) | |
layout_rect.merge x: col_x, w: col_w | |
end | |
def self.iPhone11? | |
UIScreen.mainScreen.bounds.size.height == 812 | |
end | |
def self.iPhone11Pro? | |
UIScreen.mainScreen.bounds.size.height == 896 | |
end | |
def self.iPhoneEdgeToEdge? | |
iPhone11Pro? || iPhone11? | |
end | |
def self.iPad? | |
UIScreen.mainScreen.bounds.size.height == 1080 | |
end | |
def self.iPadAir? | |
UIScreen.mainScreen.bounds.size.height == 1112 | |
end | |
def self.iPadPro? | |
UIScreen.mainScreen.bounds.size.height == 1024 | |
end | |
def self.iPadPro12Inch? | |
UIScreen.mainScreen.bounds.size.height == 1366 | |
end | |
def self.iPadPro11Inch? | |
UIScreen.mainScreen.bounds.size.height == 1194 | |
end | |
def self.iPadAir4? | |
UIScreen.mainScreen.bounds.size.height == 1180 | |
end | |
def self.iPhone5? | |
UIScreen.mainScreen.bounds.size.height == 568 | |
end | |
def self.iPhone8Plus? | |
UIScreen.mainScreen.bounds.size.height == 736 | |
end | |
def self.iPhone8? | |
UIScreen.mainScreen.bounds.size.height == 667 | |
end | |
def self.iPadEdgeToEdge? | |
iPadPro12Inch? || iPadPro11Inch? || iPadAir4? | |
end | |
def self.device | |
calc_layout logical_rect.w, logical_rect.h | |
end | |
def self.calc_layout w, h | |
device = LT::Device.new | |
device.aspect.w = w | |
device.aspect.h = h | |
device.aspect.u = (device.aspect.w.fdiv 9).floor | |
device.aspect.u = (device.aspect.h.fdiv 16).floor if device.aspect.u > device.aspect.h | |
device.w = w | |
device.h = h | |
device.name = name | |
device.safe.w = device.aspect.u * 9 | |
device.safe.h = device.aspect.u * 16 | |
device.safe.margin.l = (device.w - device.safe.w).fdiv 2 | |
device.safe.margin.r = (device.w - device.safe.w).fdiv 2 | |
device.safe.margin.t = (device.h - device.safe.h).fdiv 2 | |
device.safe.margin.b = (device.h - device.safe.h).fdiv 2 | |
# default to the smallest device | |
device.grid.outer_gutter = 4 | |
device.grid.gutter = 4 | |
# if the aspect ratio supports a bigger gutter, increase it | |
if (device.grid.outer_gutter / device.w) < 0.0125 | |
device.grid.outer_gutter = 8 | |
device.grid.gutter = 8 | |
end | |
# if the aspect ratio is closer to 4:3, then increase the outer gutter | |
if (device.aspect.w / device.aspect.h) > 0.68 | |
device.grid.outer_gutter = 16 | |
device.grid.gutter = 8 | |
end | |
device.grid.w = device.safe.w - (device.grid.outer_gutter * 2) | |
device.grid.h = device.safe.h - (device.grid.outer_gutter * 2) | |
device.grid.margin.l = (device.w - device.grid.w).fdiv 2 | |
device.grid.margin.r = (device.w - device.grid.w).fdiv 2 | |
device.grid.margin.t = (device.h - device.grid.h).fdiv 2 | |
device.grid.margin.b = (device.h - device.grid.h).fdiv 2 | |
device.grid.col_count = 12 | |
device.grid.row_count = 24 | |
device.grid.cell_w = ((device.aspect.w - (device.grid.outer_gutter * 2)) - ((device.grid.col_count - 1) * device.grid.gutter)).fdiv device.grid.col_count | |
device.grid.cell_h = ((device.aspect.h - (device.grid.outer_gutter * 2)) - ((device.grid.row_count - 1) * device.grid.gutter)).fdiv device.grid.row_count | |
device.layout.cell_size = device.grid.cell_w | |
device.layout.cell_size = device.grid.cell_h if device.grid.cell_h < device.grid.cell_w | |
device.layout.cell_size = device.layout.cell_size.floor | |
device.layout.w = (device.layout.cell_size * device.grid.col_count) + (device.grid.gutter * (device.grid.col_count - 1)) | |
device.layout.h = (device.layout.cell_size * device.grid.row_count) + (device.grid.gutter * (device.grid.row_count - 1)) | |
device.layout.margin.l = (device.w - device.layout.w).fdiv 2 | |
device.layout.margin.r = (device.w - device.layout.w).fdiv 2 | |
device.layout.margin.t = (device.h - device.layout.h).fdiv 2 | |
device.layout.margin.b = (device.h - device.layout.h).fdiv 2 | |
# device.check_math! | |
return device | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment