Skip to content

Instantly share code, notes, and snippets.

@amirrajan
Last active April 8, 2021 00:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save amirrajan/b007e5df7f61b08b8a2c9e9dcddfa4c3 to your computer and use it in GitHub Desktop.
Save amirrajan/b007e5df7f61b08b8a2c9e9dcddfa4c3 to your computer and use it in GitHub Desktop.
Layout Theory for RubyMotion

Usage:

rect = LayoutTheory.rect row: 0, col: 0, w: 10, h: 2
some_view.frame = CGRectMake rect[:x], rect[:y], rect[:w], rect[:h]
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