Files
rmq/motion/ruby_motion_query/rect.rb
Mark Rickert 6ab0ba7823 Supports setting relative frames when a previous element does not exist
below_prev & above_prev should set set their frames relative to y=0 and right_of_prev & left_of_prev should use x=0 as their starting point when no previous element exists.
2014-12-09 13:43:08 -07:00

577 lines
19 KiB
Ruby

module RubyMotionQuery
class RMQ
RECT_WHITELIST = [:grid, :g, :l, :left, :x, :fl, :top, :t, :y, :w, :width, :h, :height, :below_prev, :bp, :below_previous, :left_of_prev, :lop, :left_of_previous, :r, :right, :b, :bottom, :from_right, :fr, :from_bottom, :fb, :centered, :padding, :p, :above_prev, :ap, :above_previous, :rop, :right_of_prev, :right_of_previous, :left_of_prev, :lop, :left_of_previous]
# @return RubyMotionQuery::Rect or array of RubyMotionQuery::Rect
#
# @example
# left = rmq(my_view).frame.left
def frame(params = nil)
if params
frame = params
self
else
if selected.length == 1
Rect.frame_for_view(selected.first)
else
selected.map{|s| Rect.frame_for_view(s)}
end
end
end
# Same as layout:
# rmq(my_view).layout(l: 10, t: 20, w: 100, h: 150)
#
# Always applied in this order, regardless of the hash order:
# grid
# l, t, w, h
# prev, next
# from_right, from_bottom
# right, bottom
# left and right applied together (will change width)
# top and bottom applied together (will change height)
# centered
# padding
#
# @example
# rmq(my_view).frame = :full
# rmq(my_view).frame = {l: 10, t: 20, w: 100, h: 150}
# rmq(my_view).frame = {t: 20, h: 150, l: 10, w: 100}
# rmq(my_view).frame = {l: 10, t: 20}
# rmq(my_view).frame = {h: 20}
# rmq(my_view).frame = {l: :prev, t: 20, w: 100, h: 150}
# rmq(my_view).frame = {l: 10, below_prev: 10, w: prev, h: 150}
# rmq(my_view).frame = {left: 10, top: 20, width: 100, height: 150}
# rmq(my_view).frame = {l: 10, t: 10, fr: 10, fb: 10}
# rmq(my_view).frame = {width: 50, height: 20, centered: :both}
# rmq(my_view).frame = "a1:b5"
# rmq(my_view, my_other_view).frame = {grid: "b2", w: 100, h: 200}
# rmq(my_view, my_other_view).frame = {g: "b2", w: 100, h: 200}
#
# @example with padding
# rmq(my_view).frame = {grid: "b2:d14", padding: 5}
# rmq(my_view).frame = {grid: "b2:d14", padding: {l: 5, t: 0, r: 5, b:0}}
def frame=(value)
selected.each do |view|
RubyMotionQuery::Rect.update_view_frame(view, value)
end
end
def bounds
if selected.length == 1
RubyMotionQuery::Rect.bounds_for_view(selected.first)
else
selected.map{|s| Rect.bounds_for_view(s)}
end
end
def bounds=(value)
selected.each do |view|
RubyMotionQuery::Rect.bounds_for_view(view).update(value, self.grid).apply_to_bounds
end
end
end
# RMQ Rect
#
# *******************---*******---*************************** value options
# * | | * -------------
# * | | * Integer
# * | | * signed Integer
# * top | * Float
# * | | * String
# * | | *
# * --- | * also
# * ***************|***** --- * -----------------------
# * * view | * | * :full
# * * | * | * :right_of_prev (:rop)
# * * bottom * | * :left_of_prev (:lop)
# * * | * | * :below_prev (:bp)
# *|--- left ---|* | * | * :above_prev (:ap)
# * * | * height * :grid (:g)
# * * | * | * :padding (:p)
# * * | * | * int or hash: l,t,b,r
# *|-------------------- right -+---|* | *
# * * | * | * abbreviations
# * * | * |--+--from_right----|* -----------------------
# * * --- * | * :l, :t, :w, :h
# * ***************---*** --- * :r, :b
# * | * :fr, fb
# * |------ width - + -| *
# * | * :centered options
# * | * -----------------------
# * from_bottom * :horizontal
# * | * :vertical
# * | * :both
# * --- *
# ***********************************************************
#
class Rect
attr_reader :view
class << self
def update_view_frame(view, params)
view.frame = object_to_cg_rect(params, view, view.frame, view.rmq.grid)
end
def update_view_bounds(view, params)
view.frame = object_to_cg_rect(params, view, view.bounds, view.rmq.grid)
end
# Previous sibling
def previous_view(view)
if view
pv = view.rmq.prev.get
if RubyMotionQuery::RMQ.is_blank?(pv)
nil
else
pv
end
end
end
def frame_for_view(view)
new(view.frame, view)
end
def bounds_for_view(view)
new(view.bounds, view)
end
# Used internally, don't use this
#
# In singleton for performance # TODO, test if this is necessary
def object_to_cg_rect(o, view = nil, existing_rect = nil, grid = nil)
if o.is_a?(Hash)
a = rect_hash_to_rect_array(view, existing_rect, o, grid)
CGRectMake(a[0], a[1], a[2], a[3])
elsif o == :full
if view && (sv = view.superview)
sv.bounds
else
rmq.root_view.bounds
end
elsif o.is_a?(RubyMotionQuery::Rect)
o.to_cgrect
elsif grid && o.is_a?(String)
a = rect_hash_to_rect_array(view, existing_rect, {grid: o}, grid)
CGRectMake(a[0], a[1], a[2], a[3])
elsif o.is_a?(Array)
case o.length
when 4
CGRectMake(o[0], o[1], o[2], o[3])
when 2
o
end
else
o # CGRect, etc
end
end
# Used internally, don't use this
#
# In singleton for performance # TODO, test if this is necessary
def rect_hash_to_rect_array(view, existing_rect, params, grid = nil)
# TODO check if this is performant, it should be
if (sv = view.superview) && (vc = view.rmq_data.view_controller)
not_in_root_view = !(vc.view == sv)
end
# performant warn if hash has keys that are not whitelisted
unknown_keys = params.keys - RMQ::RECT_WHITELIST
unless unknown_keys.empty?
puts "\n[RMQ ERROR] rect keys #{unknown_keys} don't exist. Verify your hash for #{view.class.name} uses approved keys - #{RMQ::RECT_WHITELIST}\n\n"
end
# Grid
if grid
if params_g = params[:grid] || params[:g]
params.delete(:grid)
params.delete(:g)
grid_h = grid[params_g]
params = grid_h.merge(params)
end
end
params_l = params[:l] || params[:left] || params[:x] || params[:fl]
params_t = params[:t] || params[:top] || params[:y]
params_w = params[:w] || params[:width]
params_h = params[:h] || params[:height]
below_prev = params[:below_prev] || params[:bp] || params[:below_previous]
above_prev = params[:above_prev] || params[:ap] || params[:above_previous]
right_of_prev = params[:right_of_prev] || params[:rop] || params[:right_of_previous]
left_of_prev = params[:left_of_prev] || params[:lop] || params[:left_of_previous]
l = params_l || existing_rect.origin.x
t = params_t || existing_rect.origin.y
w = params_w || existing_rect.size.width
h = params_h || existing_rect.size.height
r = params[:r] || params[:right]
b = params[:b] || params[:bottom]
fr = params[:from_right] || params[:fr]
fb = params[:from_bottom] || params[:fb]
centered = params[:centered]
# Previous
if prev_view = previous_view(view)
if params_g && (prev_sv = prev_view.superview)
previous_root_view_point = vc.view.convertPoint(prev_view.origin, fromView: prev_sv)
if below_prev
t = params_t = previous_root_view_point.y + prev_view.frame.size.height + below_prev
elsif above_prev
t = params_t = previous_root_view_point.y - above_prev - h
end
if right_of_prev
l = params_l = previous_root_view_point.x + prev_view.frame.size.width + right_of_prev
elsif left_of_prev
l = params_l = previous_root_view_point.x - left_of_prev - w
end
else
if below_prev
t = prev_view.frame.origin.y + prev_view.frame.size.height + below_prev
elsif above_prev
t = prev_view.frame.origin.y - above_prev - h
end
if right_of_prev
l = prev_view.frame.origin.x + prev_view.frame.size.width + right_of_prev
elsif left_of_prev
l = prev_view.frame.origin.x - left_of_prev - w
end
end
else
if below_prev
t = below_prev
elsif above_prev
t = above_prev - h
end
if right_of_prev
l = right_of_prev
elsif left_of_prev
l = left_of_prev - w
end
end
if sv
if (fr || fb || centered) # Needs size
# Horrible horrible hack, TODO fix. This is here because
# the root_view's height isn't changed until after viewDidLoad when
# vc.edgesForExtendedLayout = UIRectEdgeNone.
# Not sure how often people use UIRectEdgeNone as I never do,
# perhaps an edge case that should be isolated in some wayo
# I hate to have to check and calc this every time
if vc && !not_in_root_view && (vc.edgesForExtendedLayout == UIRectEdgeNone)
sv_size = CGSizeMake(sv.bounds.size.width, rmq.device.screen_height - 64)
else
sv_size = sv.bounds.size
end
end
end
# From right, from_bottom
if (fr || fb) && sv
if fr
if params_l || left_of_prev || right_of_prev
w = sv_size.width - l - fr
else
l = sv_size.width - w - fr
end
end
if fb
if params_t || below_prev || above_prev
h = sv_size.height - t - fb
else
t = sv_size.height - h - fb
end
end
end
# Right and bottom
if r && !fr && !params_l
l = r - w
end
if b && !fb && !params_t
t = b - h
end
# Left and right applied together
if params_l && r && !params_w
w = r - l
end
# Top and bottom applied together
if params_t && b && !params_h
h = b - t
end
# Centered, :horizontal, :vertical, :both
if sv && centered
case centered
when :horizontal
l = (sv_size.width / 2) - (w / 2)
when :vertical
t = (sv_size.height / 2) - (h / 2)
when :both
l = (sv_size.width / 2) - (w / 2)
t = (sv_size.height / 2) - (h / 2)
end
end
if padding = params[:padding] || params[:p]
if padding.is_a?(Hash)
padding_l = padding[:l] || padding[:left] || 0
padding_t = padding[:t] || padding[:top] || 0
padding_r = padding[:r] || padding[:right] || 0
padding_b = padding[:b] || padding[:bottom] || 0
l += padding_l
t += padding_t
w -= (padding_l + padding_r)
h -= (padding_t + padding_b)
else
l += padding
t += padding
w -= (padding * 2)
h -= (padding * 2)
end
end
if params_g && not_in_root_view # Change to root_view_space
point = CGPointMake(l,t)
root_view_point = vc.view.convertPoint(point, toView: sv)
l = root_view_point.x
t = root_view_point.y
end
[l,t,w,h]
end
end # << self
def initialize(params, view = nil, grid = nil)
@left = @top = @width = @height = 0
@view = view
update params, grid
end
def update(params, grid = nil)
# Doing all of the updates to the Rect in singleton for performance.
# It would be better to be done inside an actual Rect instance, but that
# would require creating a lot of temporary objects.
# TODO performance and see if there is any real loss bringing
# object_to_cg_rect into Rect instance
#
# If we did it that way, then we'd create a new instance, then appy the
# rect instance to the frame or bounds, like so:
# Rect.new(params, view, grid).apply_to_frame
cg_rect = RubyMotionQuery::Rect.object_to_cg_rect(params, @view, self.to_cgrect, grid)
@left = cg_rect.origin.x
@top = cg_rect.origin.y
@width = cg_rect.size.width
@height = cg_rect.size.height
end
def apply_to_frame
@view.frame = to_cgrect if @view
end
def apply_to_bounds
@view.bounds = to_cgrect if @view
end
def left
@left
end
alias :l :left
alias :x :left
def right
@left + @width
end
alias :r :right
def from_right
if @view && (sv = @view.superview)
sv.size.width - right
end
end
alias :fr :from_right
def top
@top
end
alias :t :top
alias :y :top
def bottom
@top + @height
end
alias :b :bottom
def from_bottom
if @view && (sv = @view.superview)
sv.bounds.size.height - bottom
end
end
alias :fb :from_bottom
def width
@width
end
alias :w :width
def height
@height
end
alias :h :height
def z_order
if @view && (sv = @view.superview)
sv.subviews.to_a.index(@view) # is there a better way??
end
end
def origin
to_cgpoint
end
def size
to_cgsize
end
def point_in_root_view
if @view && (sv = @view.superview) && (vc = view.rmq.view_controller)
vc.view.convertPoint(CGPointMake(@left,@top), fromView: sv)
end
end
def rect_in_root_view
if @view && (sv = @view.superview) && (vc = view.rmq.view_controller)
point = vc.view.convertPoint(CGPointMake(@left,@top), fromView: sv)
RubyMotionQuery::Rect.new({l: point.x, t: point.y, w: @view.size.width, h: @view.size.height}, @view)
end
end
def left_in_root_view
if point = point_in_root_view
point.x
end
end
def top_in_root_view
if point = point_in_root_view
point.y
end
end
# TODO add center
def z_position
if @view
@view.layer.zPosition
end
end
def to_cgpoint
CGPointMake(@left, @top)
end
def to_cgsize
CGSizeMake(@width, @height)
end
def to_cgrect
CGRectMake(@left, @top, @width, @height)
end
def to_a
[@left, @top, @width, @height]
end
def to_h
{left: @left, top: @top, width: @width, height: @height}
end
def inspect
format = '#0.#'
s = "Rect {l: #{RMQ.format.numeric(left, format)}"
s << ", t: #{RMQ.format.numeric(top, format)}"
s << ", w: #{RMQ.format.numeric(width, format)}"
s << ", h: #{RMQ.format.numeric(height, format)}}"
s
end
def log
def i_f_to_s(int_or_float)
if int_or_float % 1 == 0
int_or_float.to_i.to_s
else
int_or_float.to_s
end
end
l = i_f_to_s(left.round(2)).ljust(5)
t = i_f_to_s(top.round(2)).rjust(5)
w = i_f_to_s(width.round(2)).ljust(5)
h = i_f_to_s(height.round(2)).ljust(5)
b = i_f_to_s(bottom.round(2)).rjust(5)
r = i_f_to_s(right.round(2)).ljust(5)
fr = i_f_to_s(from_right.round(2)).ljust(5)
fb = i_f_to_s(from_bottom.round(2)).rjust(5)
ww = i_f_to_s(rmq.app.window.size.width.round(2))
wh = i_f_to_s(rmq.app.window.size.height.round(2))
if @view && (sv = @view.superview)
sw = i_f_to_s(sv.bounds.size.width.round(2))
sh = i_f_to_s(sv.bounds.size.height.round(2))
end
out = %(
*****************---*******---**************************
* | | * window
* #{ t} top | * {w: #{ww}, h: #{wh}}
* | | *
* --- | * superview
* ***************|***** --- * {w: #{sw}, h: #{sh}}
* * | * | *
* * | * | *
* * #{ b} bottom * | * view
* #{ l} * | * | * {l: #{l.strip}, t: #{t.strip},
*|-- left --|* | * | * w: #{w.strip}, h: #{h.strip}}
* * | * height #{ h} *
* * | * | * z_order: #{z_order}
* * #{ r} | * | * z_position: #{z_position}
*|------------------ right -+---|* | *
* * | * | #{fr} * Location in root view
* * | * |--+--from_right---|* {l: #{i_f_to_s(left_in_root_view)}, t: #{i_f_to_s(top_in_root_view)},
* * --- * | * w: #{w.strip}, h: #{h.strip}}
* ***************---*** --- *
* | *
* |------ width - + --| *
* #{ w} | *
* | *
* | *
* #{fb} from_bottom *
* | *
* --- *
********************************************************
)
puts out
end
end
end