Browse Source

shorthands for xywh! initial implementation

dev
Andy Meneely 5 years ago
parent
commit
2969957c5b
  1. 18
      lib/squib/args/box.rb
  2. 14
      lib/squib/args/coords.rb
  3. 7
      lib/squib/args/scale_box.rb
  4. 46
      lib/squib/args/xywh_shorthands.rb
  5. 10
      lib/squib/layout_parser.rb
  6. 21
      samples/shorthands/_shorthands.rb
  7. BIN
      samples/shorthands/shorthand_00_expected.png
  8. 83
      spec/args/box_spec.rb
  9. 23
      spec/args/scale_box_spec.rb

18
lib/squib/args/box.rb

@ -4,17 +4,16 @@ require_relative 'xywh_shorthands'
module Squib::Args
module_function def extract_box(opts, deck, dsl_method_defaults = {})
Box.new(deck, dsl_method_defaults, opts).extract!(opts, deck)
Box.new(deck, dsl_method_defaults).extract!(opts, deck)
end
class Box
include ArgLoader
include XYWHShorthands
def initialize(deck = nil, dsl_method_defaults = {}, opts = {})
def initialize(deck = nil, dsl_method_defaults = {})
@deck = deck
@dsl_method_defaults = dsl_method_defaults
@opts = opts # e.g. value of x can depend on the value of width
end
def self.parameters
@ -32,22 +31,17 @@ module Squib::Args
parameters.keys # all of them
end
def validate_x(arg, i)
apply_x_shorthands(arg, @deck.width)
end
def validate_y(arg,_i)
apply_y_shorthands(arg, @deck.height)
end
def validate_x(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_width(arg, _i)
return arg if @deck.nil?
apply_x_shorthands(arg, @deck.width)
apply_shorthands(arg, @deck, axis: :x)
end
def validate_height(arg, _i)
return arg if @deck.nil?
apply_y_shorthands(arg, @deck.height)
apply_shorthands(arg, @deck, axis: :y)
end
def validate_x_radius(arg, i)

14
lib/squib/args/coords.rb

@ -1,4 +1,5 @@
require_relative 'arg_loader'
require_relative 'xywh_shorthands'
module Squib::Args
module_function def extract_coords(opts, deck)
@ -7,6 +8,7 @@ module Squib::Args
class Coords
include ArgLoader
include XYWHShorthands
def self.parameters
{ x: 0, y: 0,
@ -30,6 +32,18 @@ module Squib::Args
parameters.keys # all of them
end
def validate_x(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_x1(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y1(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_x2(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y2(arg,_i) apply_shorthands(arg, @deck, axis: :y)end
def validate_x3(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y3(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_cx1(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_cy1(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_cx2(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_cy2(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
end
end

7
lib/squib/args/scale_box.rb

@ -1,4 +1,5 @@
require_relative 'arg_loader'
require_relative 'xywh_shorthands'
module Squib::Args
module_function def extract_scale_box(opts, deck)
@ -7,6 +8,7 @@ module Squib::Args
class ScaleBox
include ArgLoader
include XYWHShorthands
def self.parameters
{
@ -23,9 +25,13 @@ module Squib::Args
parameters.keys # all of them
end
def validate_x(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_width(arg, i)
return @deck.width if arg.to_s == 'deck'
return :native if arg.to_s == 'native'
arg = apply_shorthands(arg, @deck, axis: :x)
return arg if arg.respond_to? :to_f
if arg.to_s == 'scale'
raise 'if width is :scale, height must be a number' unless height[i].respond_to? :to_f
@ -37,6 +43,7 @@ module Squib::Args
def validate_height(arg, i)
return @deck.height if arg.to_s == 'deck'
return :native if arg.to_s == 'native'
arg = apply_shorthands(arg, @deck, axis: :y)
return arg if arg.respond_to? :to_f
if arg.to_s == 'scale'
raise 'if height is \'scale\', width must be a number' unless width[i].respond_to? :to_f

46
lib/squib/args/xywh_shorthands.rb

@ -1,30 +1,38 @@
require_relative 'unit_conversion'
module Squib
module Args
module XYWHShorthands
def apply_x_shorthands(arg, deck_width)
arg_s = arg.to_s
case arg_s
when 'middle'
deck_width / 2.0
when 'center'
deck_width / 2.0
when 'deck'
deck_width
else
arg
end
end
def apply_y_shorthands(arg, deck_height)
WIDTH_MINUS_REGEX = /^width\s*\-\s*/
HEIGHT_MINUS_REGEX = /^height\s*\-\s*/
WIDTH_DIV_REGEX = /^width\s*\/\s*/
HEIGHT_DIV_REGEX = /^height\s*\/\s*/
# dimension is usually either deck_width or deck_height
def apply_shorthands(arg, deck, axis: :x)
dimension = (axis == :x) ? deck.width : deck.height
arg_s = arg.to_s
case arg_s
when 'middle'
deck_height / 2.0
dimension / 2.0
when 'center'
deck_height / 2.0
dimension / 2.0
when 'deck'
deck_height
dimension
when WIDTH_MINUS_REGEX # e.g. width - 1.5in
n = arg_s.sub WIDTH_MINUS_REGEX, ''
n = UnitConversion.parse(n)
deck.width - n
when HEIGHT_MINUS_REGEX # e.g. height - 1.5in
n = arg_s.sub HEIGHT_MINUS_REGEX, ''
n = UnitConversion.parse(n)
deck.height - n
when WIDTH_DIV_REGEX # e.g. width / 3
n = (arg_s.sub WIDTH_DIV_REGEX, '').to_f
deck.width / n
when HEIGHT_DIV_REGEX # e.g. height / 3
n = (arg_s.sub HEIGHT_DIV_REGEX, '').to_f
deck.height / n
else
arg
end

10
lib/squib/layout_parser.rb

@ -69,7 +69,7 @@ module Squib
end
def handle_relative_operators(parent_val, child_val)
unless has_digits?(parent_val) && has_digits?(child_val)
if uses_operator?(child_val) && !has_digits?(parent_val) && !has_digits?(child_val)
raise "Layout parse error: can't combine #{parent_val} and #{child_val}"
end
if child_val.to_s.strip.start_with?('+=')
@ -112,8 +112,12 @@ module Squib
# For relative operators, it's difficult for us to handle
# some of the shorthands - so let's just freak out if you're trying to use
# relative operators with words, e.g. "middle += 0.5in"
def has_digits?(str)
str.match? /.*\d.*/
def has_digits?(arg)
/.*\d.*/ === arg.to_s
end
def uses_operator?(arg)
/^[+=|\-=|\/=|*=].*/ === arg.to_s
end
# Does this layout entry have an extends field?

21
samples/shorthands/_shorthands.rb

@ -12,18 +12,29 @@ Squib::Deck.new(width: '0.5in', height: '0.25in') do
rect width: 30, height: 30,
x: :center, y: 'center'
# Applies to shapes
triangle x1: 20, y1: 20,
x2: 60, y2: 20,
x3: :middle, y3: :middle
# We can also do width-, height-, width/, height/
rect x: 20, y: 5, stroke_color: :green,
width: 'width - 0.1in', height: 10
rect x: 10, y: 50, width: 10, height: 'height / 3',
stroke_color: :purple
# Layouts apply this too.
use_layout file: 'shorthands.yml'
rect layout: :example
# The x and y coordinates can also be "centered", assuming the
# HOWEVER! Shorthands don't combine in an "extends" situation,
# e.g. this won't work:
# e.g. this won't work:
# parent:
# x: middle
# child:
# extends: parent
# x: middle
# child:
# extends: parent
# x: += 0.5in
save_png prefix: 'shorthand_'

BIN
samples/shorthands/shorthand_00_expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

83
spec/args/box_spec.rb

@ -2,38 +2,44 @@ 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: [:deck], height: [:deck] } }
let(:deck) { OpenStruct.new(width: 123, height: 456, size: 1, dpi: 300.0) }
let(:expected_defaults) { { x: [0], y: [0], width: [123], height: [456] } }
it 'intitially has no params set' do
box = Squib::Args::Box.new
expect(box).not_to respond_to(:x, :y, :width, :height)
end
it 'extracts the defaults from Box on an empty hash' do
box.load!({})
args = {}
box = Squib::Args.extract_box args, deck
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: [:deck])
args = {x: 4, width: 40}
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(x: [4], width: [40], y: [0], height: [456])
end
it 'extracts the defaults from Box on an empty hash' do
box.load!({ foo: :bar })
args = { foo: :bar }
box = Squib::Args.extract_box args, deck
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) }
let(:args) { { x: [1, 2], y: 3 } }
let(:deck_of_2) { OpenStruct.new(width: 123, height: 456, size: 2) }
let(:box) { Squib::Args.extract_box args, deck_of_2 }
it 'expands box' do
expect(box).to have_attributes({
x: [1, 2],
y: [3, 3],
height: [:deck, :deck],
width: [:deck, :deck]
width: [123, 123],
height: [456, 456],
})
end
@ -41,21 +47,23 @@ describe Squib::Args::Box do
expect(box[0]).to have_attributes({
x: 1,
y: 3,
height: :deck,
width: :deck
width: 123,
height: 456,
})
end
end
context 'layouts' do
let(:layout) do
{ 'attack' => { 'x' => 50 },
'defend' => { 'x' => 60 } }
end
let(:deck_of_2) do
OpenStruct.new(width: 123, height: 456, size: 2, layout: {
'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)
box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes(
x: [50, 60], # set by layout
y: [0, 0], # Box default
@ -64,7 +72,7 @@ describe Squib::Args::Box do
it 'handle single expansion' do
args = { layout: 'attack' }
box.load!(args, expand_by: 2, layout: layout)
box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes(
x: [50, 50], # set by layout
y: [0, 0], # Box default
@ -73,7 +81,7 @@ describe Squib::Args::Box do
it 'handles symbols' do
args = { layout: :attack }
box.load!(args, expand_by: 2, layout: layout)
box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes(
x: [50, 50], # set by layout
y: [0, 0], # Box default
@ -83,7 +91,7 @@ describe Squib::Args::Box do
it 'warns on non-existent layouts' do
args = { layout: :heal }
expect(Squib.logger).to receive(:warn).with('Layout "heal" does not exist in layout file - using default instead').at_least(:once)
box.load!(args, expand_by: 2, layout: layout)
box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes(
x: [0, 0], # Box default
y: [0, 0], # Box default
@ -92,49 +100,58 @@ describe Squib::Args::Box do
end
context 'unit conversion' do
let(:deck_of_2) { OpenStruct.new(width: 123, height: 456, size: 2, dpi: 300) }
it 'converts units on all args' do
args = { x: ['1in', '2in'], y: 300, width: '1in', height: '1in' }
box.load!(args, expand_by: 2)
box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes(
x: [300, 600],
x: [300.0, 600.0],
y: [300, 300],
width: [300, 300],
height: [300, 300],
width: [300.0, 300.0],
height: [300.0, 300.0],
)
end
end
context 'validation' do
let(:deck) { OpenStruct.new(width: 123, height: 456, size: 1) }
it 'replaces with deck width and height' do
args = { width: :deck, height: :deck }
box = Squib::Args::Box.new
box.extract! args, deck
box = Squib::Args.extract_box args, deck
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.extract! args, deck
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(x_radius: [3], y_radius: [3])
end
it 'listens to middle' do
args = { width: :middle, height: 'middle' }
box = Squib::Args::Box.new
box.extract! args, deck
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(width: [61.5], height: [228.0])
end
it 'listens to center' do
args = { width: 'center', height: :center }
box = Squib::Args::Box.new
box.extract! args, deck
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(width: [61.5], height: [228.0])
end
it 'listens to height/2' do
args = { width: 'height / 2', height: :deck }
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(width: [228.0], height: [456])
end
it 'listens to width - 0.5in' do
args = { x: 'width - 0.5in'}
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(x: [ 123 - 150 ])
end
end

23
spec/args/scale_box_spec.rb

@ -2,12 +2,13 @@ require 'spec_helper'
require 'squib/args/scale_box'
describe Squib::Args::ScaleBox do
subject(:box) { Squib::Args::ScaleBox.new }
let(:deck) { OpenStruct.new(width: 123, height: 456, size: 1, dpi: 300.0) }
context 'unit conversion' do
it 'converts units on all args' do
deck_w_2 = OpenStruct.new(width: 123, height: 456, size: 2, dpi: 300.0)
args = { x: ['1in', '2in'], y: 300, width: '1in', height: '1in' }
box.load!(args, expand_by: 2)
box = Squib::Args.extract_scale_box args, deck_w_2
expect(box).to have_attributes(
x: [300, 600],
y: [300, 300],
@ -20,49 +21,47 @@ describe Squib::Args::ScaleBox do
context 'validation' do
it 'replaces with deck width and height' do
args = { width: :deck, height: :deck }
deck = OpenStruct.new(width: 123, height: 456)
box = Squib::Args::Box.new(deck)
box.load!(args, expand_by: 1)
box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [123], height: [456])
end
it 'allows :native' do
args = { width: :native, height: :native }
box.load!(args, expand_by: 1)
box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [:native], height: [:native])
end
it 'allows native to be a string' do
args = { width: 'native' }
box.load!(args, expand_by: 1)
box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [:native], height: [:native])
end
it 'allows :scale on width if height has to_f' do
args = { width: :scale, height: 75 }
box.load!(args, expand_by: 1)
box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [:scale], height: [75])
end
it 'allows :scale on width if height has to_f' do
args = { width: 75, height: :scale }
box.load!(args, expand_by: 1)
box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [75], height: [:scale])
end
it 'disallows both :scale' do
args = { width: :scale, height: :scale }
expect { box.load!(args, expand_by: 1) }.to raise_error('if width is :scale, height must be a number')
expect { Squib::Args.extract_scale_box args, deck }.to raise_error('if width is :scale, height must be a number')
end
it 'disallows non-to_f on width' do
args = { width: :foo }
expect { box.load!(args, expand_by: 1) }.to raise_error('width must be a number, :scale, :native, or :deck')
expect { Squib::Args.extract_scale_box args, deck }.to raise_error('width must be a number, :scale, :native, or :deck')
end
it 'disallows non-to_f on height' do
args = { height: :foo }
expect { box.load!(args, expand_by: 1) }.to raise_error('height must be a number, :scale, :native, or :deck')
expect { Squib::Args.extract_scale_box args, deck }.to raise_error('height must be a number, :scale, :native, or :deck')
end
end

Loading…
Cancel
Save