Layouts are Squib’s Best Feature

Working with tons of options to a method can be tiresome. Ideally everything in a game prototype should be data-driven, easily changed, and your Ruby code should readable without being littered with magic numbers.

For this, most Squib methods have a layout option. Layouts are a way of setting default values for any parameter given to the method. They let you group things logically, manipulate options, and use built-in stylings.

Think of layouts and DSL calls like CSS and HTML: you can always specify style in your logic (e.g. directly in an HTML tag), but a cleaner approach is to group your styles together in a separate sheet and work on them separately.

To use a layout, set the layout: option on Deck.new to point to a YAML file. Any command that allows a layout option can be set with a Ruby symbol or string, and the command will then load the specified options. The individual command can also override these options.

For example, instead of this:

# deck.rb
Squib::Deck.new do
  rect x: 75, y: 75, width: 675, height: 975
end

You can put your logic in the layout file and reference them:

# custom-layout.yml
bleed:
  x: 75
  y: 75
  width: 975
  height: 675

Then your script looks like this:

# deck.rb
Squib::Deck.new(layout: 'custom-layout.yml') do
  rect layout: 'bleed'
end

The goal is to make your Ruby code separate the data decisions from logic. For the above example, you are separating the decision to draw rectangle around the “bleed” area, and then your YAML file is defining specifically what “bleed” actually means. (Who is going to remember that x: 75 means “bleed area”??) This process of separating logic from data makes your code more readable, changeable, and maintainable.

Warning

YAML is very finnicky about not allowing tab characters. Use two spaces for indentation instead. If you get a Psych syntax error, this is likely the culprit. Indendation is also strongly enforced in Yaml too. See the Yaml docs for more info.

Order of Precedence for Options

Layouts will override Squib’s system defaults, but are overriden by anything specified in the command itself. Thus, the order of precedence looks like this:

  1. Use what the DSL method specified, e.g. rect x: 25
  2. If anything was not yet specified, use what was given in a layout (if a layout was specified in the command and the file was given to the Deck). e.g. rect layout: :bleed
  3. If still anything was not yet specified, use what was given in Squib’s defaults as defined in the DSL Reference.

For example, back to our example:

# custom-layout.yml
bleed:
  x: 0.25in
  y: 0.25in
  width: 2.5in
  height: 3.5in

(Note that this example makes use of Unit Conversion)

Combined with this script:

# deck.rb
Squib::Deck.new(layout: 'custom-layout.yml') do
  rect layout: 'bleed', x: 50
end

The options that go into rect will be:

  • x will be 50 because it’s specified in the DSL method and overrides the layout
  • y, width, and height were specified in the layout file, so their values are used
  • The rect’s stroke_color (and others options like it) was never specified anywhere, so the default for rect is used - as discussed in Parameters are Optional.

Note

Defaults are not global for the name of the option - they are specific to the method itself. For example, the default fill_color for rect is '#0000' but for showcase it’s :white.

Note

Layouts work with all options (for DSL methods that support layouts), so you can use options like file or font or whatever is needed.

Warning

If you provide an option in the Yaml file that is not supported by the DSL method, the DSL method will simply ignore it. Same behavior as described in Parameters are Optional.

When Layouts Are Similar, Use extends

Using layouts are a great way of keeping your Ruby code clean and concise. But those layout Yaml files can get pretty long. If you have a bunch of icons along the top of a card, for example, you’re specifying the same y option over and over again. This is annoyingly verbose, and what if you want to move all those icons downward at once?

Squib provides a way of reusing layouts with the special extends` key. When defining an `extends key, we can merge in another key and modify its data coming in if we want to. This allows us to do things like place text next to an icon and be able to move them with each other. Like this:

# If we change attack, we move defend too!
attack:
  x: 100
  y: 100
defend:
  extends: attack
  x: 150
  #defend now is {:x => 150, :y => 100}

Over time, using extends saves you a lot of space in your Yaml files while also giving more structure and readability to the file itself.

You can also modify data as they get passed through extends:

# If we change attack, we move defend too!
attack:
  x: 100
defend:
  extends: attack
  x: += 50
  #defend now is {:x => 150, :y => 100}
The following operators are supported within evaluating extends
  • += will add the giuven number to the inherited number
  • -= will subtract the given number from the inherited number
  • *= will multiply the inherited number by the given number
  • /= will divide the inherited number by the given number

+= and -= also support Unit Conversion

From a design point of view, you can also extract out a base design and have your other layouts extend from them:

top_icons:
  y: 100
  font: Arial 36
attack:
  extends: top_icon
  x: 25
defend:
  extends: top_icon
  x: 50
health:
  extends: top_icon
  x: 75
# ...and so on

Note

Those fluent in Yaml may notice that extends key is similar to Yaml’s merge keys. Technically, you can use these together - but I just recommend sticking with extends since it does what merge keys do and more. If you do choose to use both extends and Yaml merge keys, the Yaml merge keys are processed first (upon Yaml parsing), then extends (after parsing).

Yes, extends is Multi-Generational

As you might expect, extends can be composed multiple times:

socrates:
  x: 100
plato:
  extends: socrates
  x: += 10    # evaluates to 110
aristotle:
  extends: plato
  x: "*= 2"     # evaluates to 220, note that YAML requires quotes here

Yes, extends has Multiple Inheritance

If you want to extend multiple parents, it looks like this:

socrates:
  x: 100
plato:
  y: 200
aristotle:
  extends:
    - socrates
    - plato
  x: += 50    # evaluates to 250 from plato

If multiple keys override the same keys in a parent, the later (“younger”) child in the extends list takes precedent. Like this:

socrates:
  x: 100
plato:
  x: 200
aristotle:
  extends:
    - plato    # note the order here
    - socrates
  x: += 50     # evaluates to 150 from socrates

Multiple Layout Files get Merged

Squib also supports the combination of multiple layout files. If you provide an Array of files then Squib will merge them sequentially. Colliding keys will be completely re-defined by the later file. The extends key is processed after each file, but can be used across files. Here’s an example:

# load order: a.yml, b.yml

##############
# file a.yml #
##############
grandparent:
  x: 100
parent_a:
  extends: grandparent
  x: += 10   # evaluates to 110
parent_b:
  extends: grandparent
  x: += 20   # evaluates to 120

##############
# file b.yml #
##############
child_a:
  extends: parent_a  # i.e. extends a layout in a separate file
  x: += 3    # evaluates to 113 (i.e 110 + 3)
parent_b:    # redefined
  extends: grandparent
  x: += 30   # evaluates to 130 (i.e. 100 + 30)
child_b:
  extends: parent_b
  x: += 3    # evaluates to 133 (i.e. 130 + 3)
This can be helpful for:
  • Creating a base layout for structure, and one for full color for easier color/black-and-white switching
  • Sharing base layouts with other designers

Squib Comes with Built-In Layouts

Why mess with x-y coordinates when you’re first prototyping your game? Just use a built-in layout to get your game to the table as quickly as possible.

If your layout file is not found in the current directory, Squib will search for its own set of layout files. The latest the development version of these can be found on GitHub.

Contributions in this area are particularly welcome!!

The following depictions of the layouts are generated with this script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
require 'squib'

# This sample demonstrates the built-in layouts for Squib.
# Each card demonstrates a different built-in layout.
Squib::Deck.new(layout: 'fantasy.yml') do
  background color: 'white'

  set font: 'Times New Roman,Serif 10.5'
  hint text: '#333' # show extents of text boxes to demo the layout

  text str: 'fantasy.yml', layout: :title
  text str: 'ur',          layout: :upper_right
  text str: 'art',         layout: :art
  text str: 'type',        layout: :type
  text str: 'tr',          layout: :type_right
  text str: 'description', layout: :description
  text str: 'lr',          layout: :lower_right
  text str: 'll',          layout: :lower_left
  text str: 'credits',     layout: :copy

  rect layout: :safe
  rect layout: :cut
  save_png prefix: 'layouts_builtin_fantasy_'
end

Squib::Deck.new(layout: 'economy.yml') do
  background color: 'white'

  set font: 'Times New Roman,Serif 10.5'
  hint text: '#333' # show extents of text boxes to demo the layout

  text str: 'economy.yml', layout: :title
  text str: 'art',         layout: :art
  text str: 'description', layout: :description
  text str: 'type',        layout: :type
  text str: 'lr',          layout: :lower_right
  text str: 'll',          layout: :lower_left
  text str: 'credits',     layout: :copy

  rect layout: :safe
  rect layout: :cut
  save_png prefix: 'layouts_builtin_economy_'
end

Squib::Deck.new(layout: 'hand.yml') do
  background color: 'white'
  %w(title bonus1 bonus2 bonus3 bonus4 bonus5 description
     snark art).each do |icon|
    text str: icon.capitalize, layout: icon,
         hint: :red, valign: 'middle', align: 'center'
  end
  save_png prefix: 'layouts_builtin_hand_'
end

Squib::Deck.new(layout: 'playing-card.yml') do
  background color: 'white'
  text str: "A\u2660", layout: :bonus_ul, font: 'Sans bold 33', hint: :red
  text str: "A\u2660", layout: :bonus_lr, font: 'Sans bold 33', hint: :red
  text str: 'artwork here', layout: :art, hint: :red
  save_png prefix: 'layouts_builtin_playing_card_'
end

Squib::Deck.new(layout: 'tuck_box.yml', width: 2325, height: 1950) do
  background color: 'white'
  rect layout: :top_rect
  rect layout: :bottom_rect
  rect layout: :right_rect
  rect layout: :left_rect
  rect layout: :back_rect
  rect layout: :front_rect
  curve layout: :front_curve

  save_png prefix: 'layouts_builtin_tuck_box_'
end

Squib::Deck.new(layout: 'party.yml') do
  background color: 'white'
  # hint text: :black # uncomment to see the text box boundaries

  rect layout: :title_box,
       fill_color: :deep_sky_blue, stroke_width: 0
  text str: "A SILLY NAME",  layout: :title
  text str: '✔',             layout: :type_icon
  text str: 'TYPE',          layout: :type
  text str: 'A Silly Name',  layout: :title_middle
  rect fill_color: :black,   layout: :middle_rect

  str = 'Rule or story text. Be funny ha ha ha this is a party.'
  text str: str, layout: :rule_top
  text str: str, layout: :rule_bottom

  text str: 'Tiny text', layout: :copyright

  rect layout: :safe
  rect layout: :cut
  save_png prefix: 'layouts_builtin_party_'
end

See Layouts in Action

This sample, which lives here, demonstrates many different ways of using and combining layouts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# encoding: utf-8
require 'squib'
require 'pp'

Squib::Deck.new(layout: 'custom-layout.yml') do
  background color: :white
  hint text: :cyan

  # Layouts are YAML files that specify any option as a default
  rect layout: :frame

  # You can also override a given layout entry in the command
  circle layout: :frame, x: 50, y: 50, radius: 25

  # Lots of commands have the :layout option
  text str: 'The Title', layout: :title

  # Layouts also support YAML merge keys toreuse settings
  svg file: 'spanner.svg',     layout: :icon_left
  png file: 'shiny-purse.png', layout: :icon_middle
  svg file: 'spanner.svg',     layout: :icon_right

  # Squib has its own, richer merge key: "extends"
  rect fill_color: :black, layout: :bonus
  rect fill_color: :white, layout: :bonus_inner
  text str: 'Extends!',    layout: :bonus_text

  # Strings can also be used to specify a layout (e.g. from a data file)
  text str: 'subtitle', layout: 'subtitle'

  # For debugging purposes, you can always print out the loaded layout
  # require 'pp'
  # pp layout

  save_png prefix: 'layout_'
end

Squib::Deck.new(layout: ['custom-layout.yml', 'custom-layout2.yml']) do
  text str: 'The Title',       layout: :title       # from custom-layout.yml
  text str: 'The Subtitle',    layout: :subtitle    # redefined in custom-layout2.yml
  text str: 'The Description', layout: :description # from custom-layout2.yml
  save_png prefix: 'layout2_'
end

# Built-in layouts are easy to use and extend
Squib::Deck.new(layout: 'playing-card.yml') do
  text str: "A\u2660",      layout: :bonus_ul, font: 'Sans bold 33', hint: :red
  text str: "A\u2660",      layout: :bonus_lr, font: 'Sans bold 33', hint: :red
  text str: 'artwork here', layout: :art, hint: :red
  save_png prefix: 'layout_builtin_playing_card_'
end

# Built-in layouts are easy to use and extend
Squib::Deck.new(layout: 'hand.yml') do
  %w(title bonus1 bonus2 bonus3 bonus4 bonus5
    description snark art).each do |icon|
    text str: icon.capitalize, layout: icon,
         hint: :red, valign: 'middle', align: 'center'
  end
  save_png prefix: 'layout_builtin_hand_'
end

# Layouts can also be specified in their own DSL method call
# Each layout call will progressively be merged with the priors
Squib::Deck.new do
  use_layout file: 'custom-layout.yml'
  use_layout file: 'custom-layout2.yml'
  text str: 'The Title',   layout: :title # from custom-layout.yml
  text str: 'The Subtitle', layout: :subtitle # redefined in custom-layout2.yml
  save_png prefix: 'layout3_'
end

This is custom-layout.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Used in the layouts.rb sample in conjunction with custom-layout2.yml
frame:
  x: 38
  y: 38
  width: 750
  height: 1050
  radius: 25
title:
  x: 125
  y: 50
  width: 625
  height: 100
  align: center #strings also work for most options
  valign: !ruby/symbol middle #yaml also support symbols, see http://www.yaml.org/YAML_for_ruby.html#symbols
subtitle:
  x: 150
  y: 150
  width: 575
  height: 60
  align: center 
  valign: middle
icon:
  width: 125
  height: 125
  y: 250
icon_left:
  extends: icon
  x: 150
icon_middle:
  extends: icon
  x: 350
  y: 400  #overrides the y inherited from icon
icon_right:
  extends: icon
  x: 550

# Squib also supports its own merging-and-modify feature
# Called "extends"
# Any layout can extend another layout, so long as it's not a circle
# Order doesn't matter since it's done after YAML procesing
# And, if the entry overrides
bonus: #becomes our bonus rectangle
  x: 250
  y: 600
  width:  300
  height: 200
  radius: 32
bonus_inner:
  extends: bonus
  x: += 10 # i.e. 260
  y: += 10 # i.e. 610
  width: -= 20  # i.e. 180
  height: -= 20 # i.e. 180
  radius: -= 8
bonus_text:
  extends: bonus_inner
  x: +=10
  y: +=10
  width: -= 20
  height: -= 20

This is custom-layout2.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Used in the layouts.rb sample in conjunction with custom-layout.yml
#
# When used with custom-layout.yml loaded first, this layout overrides subtitle
subtitle:
  x: 150
  y: 150
  width: 575
  height: 60
  align: left 
  valign: middle
  font_size: 8
# This one is completely new
description: 
  extends: subtitle
  y: += 350