First foray into args class refactoring: rect
So far, I really like this approach. It allows us to push validation logic out of the Card class and into its own set of classes that can be more easily tested. Fixes #75, but we've got a lot more work to do in getting this full scaledev
parent
6c60f21bb0
commit
c1e410fe72
|
|
@ -1,3 +1,6 @@
|
|||
require 'squib/args/box'
|
||||
require 'squib/args/draw'
|
||||
|
||||
module Squib
|
||||
class Deck
|
||||
|
||||
|
|
@ -23,13 +26,12 @@ module Squib
|
|||
# @return [nil] intended to be void
|
||||
# @api public
|
||||
def rect(opts = {})
|
||||
opts = needs(opts, [:range, :x, :y, :width, :height, :rect_radius, :x_radius, :y_radius,
|
||||
opts = needs(opts, [:range, :rect_radius, :x_radius, :y_radius,
|
||||
:fill_color, :stroke_color, :stroke_width, :layout])
|
||||
box = Args::Box.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi)
|
||||
draw = Args::Draw.new.load!(opts, expand_by: size, layout: layout, dpi: dpi)
|
||||
opts[:range].each do |i|
|
||||
@cards[i].rect(opts[:x][i], opts[:y][i], opts[:width][i], opts[:height][i],
|
||||
opts[:x_radius][i], opts[:y_radius][i],
|
||||
opts[:fill_color][i], opts[:stroke_color][i],
|
||||
opts[:stroke_width][i])
|
||||
@cards[i].rect(box[i], draw[i])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
require 'squib/constants'
|
||||
require 'squib/conf'
|
||||
|
||||
module Squib
|
||||
# @api private
|
||||
module Args
|
||||
|
||||
# Intended to be used a a mix-in,
|
||||
# For example use see Box as an example
|
||||
module ArgLoader
|
||||
|
||||
# Main class invoked by the client (i.e. api/ methods)
|
||||
def load!(args, expand_by: 1, layout: {}, dpi: 300)
|
||||
set_attributes(args: args)
|
||||
expand(by: expand_by)
|
||||
layout_args = prep_layout_args(args[:layout], expand_by: expand_by)
|
||||
defaultify(layout_args: layout_args || [], layout: layout)
|
||||
validate
|
||||
convert_units
|
||||
self
|
||||
end
|
||||
|
||||
# Iterate over the args hash and create instance-level attributes for
|
||||
# each parameter
|
||||
# Assumes we have a hash of parameters to their default keys in the class
|
||||
def set_attributes(args: args)
|
||||
attributes = self.class.parameters.keys
|
||||
attributes.each do |p|
|
||||
instance_variable_set "@#{p}", args[p] # often nil, but ok
|
||||
end
|
||||
self.class.class_eval { attr_reader *(attributes) }
|
||||
end
|
||||
|
||||
# Conduct singleton expansion
|
||||
# If expanding-parameter is not already responding to
|
||||
# :each then copy it into an array
|
||||
#
|
||||
# Assumes we have an self.expanding_parameters
|
||||
def expand(by: 1)
|
||||
exp_params = self.class.expanding_parameters
|
||||
exp_params.each do |p|
|
||||
attribute = "@#{p}"
|
||||
arg = instance_variable_get(attribute)
|
||||
unless arg.respond_to? :each
|
||||
instance_variable_set attribute, [arg] * by #expand singleton
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Do singleton expansion on the layout argument as well
|
||||
# Treated differently since layout is not always specified
|
||||
def prep_layout_args(layout_args, expand_by: 1)
|
||||
unless layout_args.respond_to?(:each)
|
||||
layout_args = [layout_args] * expand_by
|
||||
end
|
||||
layout_args || []
|
||||
end
|
||||
|
||||
# Go over each argument and fill it in with layout and defaults wherever nil
|
||||
def defaultify(layout_args: [], layout: {})
|
||||
self.class.parameters.each do |param, default|
|
||||
attribute = "@#{param}"
|
||||
val = instance_variable_get(attribute)
|
||||
if val.respond_to? :each
|
||||
new_val = val.map.with_index do |v, i|
|
||||
v ||= layout[layout_args[i]][param] unless layout_args[i].nil?
|
||||
v ||= default
|
||||
end
|
||||
instance_variable_set(attribute, new_val)
|
||||
else # a non-expanded singleton
|
||||
# TODO handle this case
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# For each parameter/attribute foo we try to invoke a validate_foo
|
||||
def validate
|
||||
self.class.parameters.each do |param, default|
|
||||
method = "validate_#{param}"
|
||||
if self.respond_to? method
|
||||
attribute = "@#{param}"
|
||||
val = instance_variable_get(attribute)
|
||||
if val.respond_to? :each
|
||||
new_val = val.map.with_index{ |v, i| send(method, v, i) }
|
||||
instance_variable_set(attribute, new_val)
|
||||
else
|
||||
instance_variable_set(attribute,send(method, val))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Access an individual arg for a given card
|
||||
# @return an OpenStruct that looks just like the mixed-in class
|
||||
# @api private
|
||||
def [](i)
|
||||
card_arg = OpenStruct.new
|
||||
self.class.expanding_parameters.each do |p|
|
||||
p_val = instance_variable_get("@#{p}")
|
||||
card_arg[p] = p_val[i]
|
||||
end
|
||||
card_arg
|
||||
end
|
||||
|
||||
# Convert units
|
||||
def convert_units(dpi: 300)
|
||||
self.class.params_with_units.each do |p|
|
||||
p_str = "@#{p}"
|
||||
p_val = instance_variable_get(p_str)
|
||||
if p_val.respond_to? :each
|
||||
arr = p_val.map { |x| convert_unit(x, dpi) }
|
||||
instance_variable_set p_str, arr
|
||||
else
|
||||
instance_variable_set p_str, convert_unit(p_val, dpi)
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def convert_unit(arg, dpi)
|
||||
case arg.to_s.rstrip
|
||||
when /in$/ #ends with "in"
|
||||
arg.rstrip[0..-2].to_f * dpi
|
||||
when /cm$/ #ends with "cm"
|
||||
arg.rstrip[0..-2].to_f * dpi * INCHES_IN_CM
|
||||
else
|
||||
arg
|
||||
end
|
||||
end
|
||||
module_function :convert_unit
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
require 'squib/args/arg_loader'
|
||||
|
||||
module Squib
|
||||
# @api private
|
||||
module Args
|
||||
|
||||
class Box
|
||||
include ArgLoader
|
||||
|
||||
def initialize(deck = nil)
|
||||
@deck = deck
|
||||
end
|
||||
|
||||
def self.parameters
|
||||
{ x: 0, y: 0,
|
||||
width: :native, height: :native,
|
||||
radius: nil, x_radius: 0, y_radius: 0
|
||||
}
|
||||
end
|
||||
|
||||
def self.expanding_parameters
|
||||
parameters.keys # all of them
|
||||
end
|
||||
|
||||
def self.params_with_units
|
||||
parameters.keys # all of them
|
||||
end
|
||||
|
||||
def validate_width(arg, _i)
|
||||
return arg if @deck.nil?
|
||||
return @deck.width if arg == :native
|
||||
arg
|
||||
end
|
||||
|
||||
def validate_height(arg, _i)
|
||||
return arg if @deck.nil?
|
||||
return @deck.height if arg == :native
|
||||
arg
|
||||
end
|
||||
|
||||
def validate_x_radius(arg, i)
|
||||
return radius[i] unless radius[i].nil?
|
||||
arg
|
||||
end
|
||||
|
||||
def validate_y_radius(arg, i)
|
||||
return radius[i] unless radius[i].nil?
|
||||
arg
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
require 'squib/args/arg_loader'
|
||||
|
||||
module Squib
|
||||
# @api private
|
||||
module Args
|
||||
|
||||
class Draw
|
||||
include ArgLoader
|
||||
|
||||
def self.parameters
|
||||
{ fill_color: '#0000', stroke_color: :black, stroke_width: 2.0 }
|
||||
end
|
||||
|
||||
def self.expanding_parameters
|
||||
parameters.keys # all of them are expandable
|
||||
end
|
||||
|
||||
def self.params_with_units
|
||||
[:stroke_width]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -35,7 +35,11 @@ module Squib
|
|||
:img_dir, :prefix, :text_hint, :typographer
|
||||
# :nodoc:
|
||||
# @api private
|
||||
attr_reader :layout, :conf
|
||||
attr_reader :layout, :conf, :dpi
|
||||
|
||||
#
|
||||
# deck.size is really just @cards.size
|
||||
def_delegators :cards, :size
|
||||
|
||||
# Squib's constructor that sets the immutable properties.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -4,12 +4,10 @@ module Squib
|
|||
|
||||
# :nodoc:
|
||||
# @api private
|
||||
def rect(x, y, width, height, x_radius, y_radius, fill_color, stroke_color, stroke_width)
|
||||
width = @width if width == :native
|
||||
height = @height if height == :native
|
||||
def rect(box, draw)
|
||||
use_cairo do |cc|
|
||||
cc.rounded_rectangle(x, y, width, height, x_radius, y_radius)
|
||||
cc.fill_n_stroke(fill_color, stroke_color, stroke_width)
|
||||
cc.rounded_rectangle(box.x, box.y, box.width, box.height, box.x_radius, box.y_radius)
|
||||
cc.fill_n_stroke(draw.fill_color, draw.stroke_color, draw.stroke_width)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
require 'spec_helper'
|
||||
require 'squib/args/box'
|
||||
|
||||
describe Squib::Args::Box do
|
||||
subject(:box) { Squib::Args::Box.new }
|
||||
let(:expected_defaults) { {x: [0], y: [0], width: [:native], height: [:native] } }
|
||||
|
||||
it 'intitially has no params set' do
|
||||
expect(box).not_to respond_to(:x, :y, :width, :height)
|
||||
end
|
||||
|
||||
it 'extracts the defaults from Box on an empty hash' do
|
||||
box.load!({})
|
||||
expect(box).to have_attributes(expected_defaults)
|
||||
end
|
||||
|
||||
it 'extracts what is specified and fills in defaults from Box' do
|
||||
box.load!(x: 4, width: 40)
|
||||
expect(box).to have_attributes(x: [4], width: [40], y: [0], height: [:native])
|
||||
end
|
||||
|
||||
it 'extracts the defaults from Box on an empty hash' do
|
||||
box.load!({foo: :bar})
|
||||
expect(box).to have_attributes(expected_defaults)
|
||||
expect(box).not_to respond_to(:foo)
|
||||
end
|
||||
|
||||
context 'single expansion' do
|
||||
let(:args) { {x: [1, 2], y: 3} }
|
||||
before(:each) { box.load!(args, expand_by: 2) }
|
||||
it 'expands box' do
|
||||
expect(box).to have_attributes({
|
||||
x: [1, 2],
|
||||
y: [3, 3],
|
||||
height: [:native, :native],
|
||||
width: [:native, :native]
|
||||
})
|
||||
end
|
||||
|
||||
it 'gives access to each card too' do
|
||||
expect(box[0]).to have_attributes({
|
||||
x: 1,
|
||||
y: 3,
|
||||
height: :native,
|
||||
width: :native
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'layouts' do
|
||||
let(:layout) do
|
||||
{ 'attack' => { x: 50 },
|
||||
'defend' => { x: 60 } }
|
||||
end
|
||||
|
||||
it 'are used when not specified' do
|
||||
args = { layout: ['attack', 'defend'] }
|
||||
box.load!(args, expand_by: 2, layout: layout)
|
||||
expect(box).to have_attributes(
|
||||
x: [50, 60], # set by layout
|
||||
y: [0, 0], # Box default
|
||||
)
|
||||
end
|
||||
|
||||
it 'handle single expansion' do
|
||||
args = { layout: 'attack' }
|
||||
box.load!(args, expand_by: 2, layout: layout)
|
||||
expect(box).to have_attributes(
|
||||
x: [50, 50], # set by layout
|
||||
y: [0, 0], # Box default
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'unit conversion' do
|
||||
|
||||
it 'converts units on all args' do
|
||||
args = {x: ['1in', '2in'], y: 300, width: '1in', height: '1in'}
|
||||
box.load!(args, expand_by: 2)
|
||||
expect(box).to have_attributes(
|
||||
x: [300, 600],
|
||||
y: [300, 300],
|
||||
width: [300, 300],
|
||||
height: [300, 300],
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'validation' do
|
||||
it 'replaces with deck width and height' do
|
||||
args = {width: :native, height: :native}
|
||||
deck = OpenStruct.new(width: 123, height: 456)
|
||||
box = Squib::Args::Box.new(deck)
|
||||
box.load!(args, expand_by: 1)
|
||||
expect(box).to have_attributes(width: [123], height: [456])
|
||||
end
|
||||
|
||||
it 'has radius override x_radius and y_radius' do
|
||||
args = {x_radius: 1, y_radius: 2, radius: 3}
|
||||
box.load!(args, expand_by: 2)
|
||||
expect(box).to have_attributes(x_radius: [3, 3], y_radius: [3, 3])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -32,9 +32,10 @@ describe Squib::Card do
|
|||
expect(cxt).to receive(:restore).once
|
||||
|
||||
card = Squib::Card.new(deck, 100, 150)
|
||||
# rect(x, y, width, height, x_radius, y_radius,
|
||||
# fill_color, stroke_color, stroke_width)
|
||||
card.rect(37, 38, 50, 100, 10, 15, '#fff', '#f00', 2.0)
|
||||
# rect(Args::Box, x_radius, y_radius, Args::Draw)
|
||||
box = OpenStruct.new(x: 37, y: 38, width: 50, height: 100, x_radius: 10, y_radius: 15)
|
||||
draw = OpenStruct.new(fill_color: '#fff', stroke_color: '#f00', stroke_width: 2.0)
|
||||
card.rect(box, draw)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue