Class: Shoes::Swt::TextBlock::Fitter

Inherits:
Object
  • Object
show all
Defined in:
shoes-swt/lib/shoes/swt/text_block/fitter.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(text_block, current_position) ⇒ Fitter

Returns a new instance of Fitter


9
10
11
12
13
14
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 9

def initialize(text_block, current_position)
  @text_block = text_block
  @dsl = @text_block.dsl
  @parent = @dsl.parent
  @current_position = current_position
end

Instance Attribute Details

#parentObject (readonly)

Returns the value of attribute parent


7
8
9
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 7

def parent
  @parent
end

Instance Method Details

#available_spaceObject


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 145

def available_space
  width = @dsl.desired_width
  height = next_line_start - @dsl.absolute_top - 1

  if on_new_line?
    height = :unbounded

    # Try to find a parent container we fit in.
    # If that doesn't work, just bail with [0,0] so we don't crash.
    width = width_from_ancestor if width <= 0
    return [0, 0] if width.negative?
  end

  [width, height]
end

#empty_segmentObject


187
188
189
190
191
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 187

def empty_segment
  segment = generate_layout(1, @dsl.text)
  segment.text = ""
  segment
end

#first_element_on_line?Boolean

Returns:

  • (Boolean)

120
121
122
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 120

def first_element_on_line?
  @dsl.left - @dsl.margin_left <= 0
end

#first_height(first_layout, first_text, height) ⇒ Object

If first text is empty, height may be smaller than an actual line in the current font. Take our pre-existing allowed height instead.


212
213
214
215
216
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 212

def first_height(first_layout, first_text, height)
  first_height = first_layout.bounds.height - first_layout.spacing
  first_height = height if first_text.empty? && height != :unbounded
  first_height
end

#fit_as_centered(width, height) ⇒ Object


106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 106

def fit_as_centered(width, height)
  if first_element_on_line?
    segment = CenteredTextSegment.new(@dsl, width)
    [segment.position_at(@dsl.element_left, @dsl.element_top)]
  else
    position_two_segments(
      empty_segment,
      CenteredTextSegment.new(@dsl, @dsl.containing_width),
      "",
      height
    )
  end
end

#fit_as_empty_first_layout(height) ⇒ Object


99
100
101
102
103
104
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 99

def fit_as_empty_first_layout(height)
  return [] if height == :unbounded || height.zero?

  height += ::Shoes::Slot::NEXT_ELEMENT_OFFSET
  generate_two_layouts(empty_segment, "", @dsl.text, height)
end

#fit_as_one_layout(layout) ⇒ Object


80
81
82
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 80

def fit_as_one_layout(layout)
  [layout.position_at(@dsl.element_left, @dsl.element_top)]
end

#fit_as_two_layouts(layout, height, width) ⇒ Object


84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 84

def fit_as_two_layouts(layout, height, width)
  first_text, second_text = split_text(layout, height)

  # Since we regenerate layouts, we must dispose of first try here.
  layout.dispose

  first_layout = generate_layout(width, first_text)

  if second_text.empty?
    fit_as_one_layout(first_layout)
  else
    generate_two_layouts(first_layout, first_text, second_text, height)
  end
end

#fit_into_full_layouts(width, height) ⇒ Object


71
72
73
74
75
76
77
78
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 71

def fit_into_full_layouts(width, height)
  layout = generate_layout(width, @dsl.text)
  if fits_in_one_layout?(layout, height)
    fit_as_one_layout(layout)
  else
    fit_as_two_layouts(layout, height, width)
  end
end

#fit_it_inObject

Fitting text works by using either 1 or 2 layouts

If the text fits in the height and width available, we use one layout.


| button | text layout 1 |


If if the text doesn't fit into that space, then we'll break it into two different layouts.


| button | text layout 1 |


| text layout 2 goes here| | in space |


^

If there wasn't any available space in the first layout (very narrow) then we'll make an empty first layout and flow to the second:


| big big big big button|| < empty layout 1 still present


| text layout 2 goes here| | in space |


^

When flowing, the position for the next element gets set to the end of the text in the second layout (shown as ^ in the diagram).

Stacks properly move to the next whole line as you'd expect.


51
52
53
54
55
56
57
58
59
60
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 51

def fit_it_in
  width, height = available_space
  if @dsl.centered?
    fit_as_centered(width, height)
  elsif no_space_in_first_layout?(width)
    fit_as_empty_first_layout(height)
  else
    fit_into_full_layouts(width, height)
  end
end

#fits_in_one_layout?(layout, height) ⇒ Boolean

Returns:

  • (Boolean)

66
67
68
69
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 66

def fits_in_one_layout?(layout, height)
  return true if height == :unbounded || layout.line_count == 1
  layout.bounds.height <= height
end

#generate_layout(width, text) ⇒ Object


183
184
185
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 183

def generate_layout(width, text)
  TextSegment.new(@dsl, text, width)
end

#generate_second_layout(second_text) ⇒ Object


130
131
132
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 130

def generate_second_layout(second_text)
  generate_layout(@dsl.containing_width, second_text)
end

#generate_two_layouts(first_layout, first_text, second_text, height) ⇒ Object


124
125
126
127
128
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 124

def generate_two_layouts(first_layout, first_text, second_text, height)
  first_layout.fill_background = true
  second_layout = generate_second_layout(second_text)
  position_two_segments(first_layout, second_layout, first_text, height)
end

#next_line_startObject


175
176
177
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 175

def next_line_start
  @current_position.next_line_start
end

#no_space_in_first_layout?(width) ⇒ Boolean

Returns:

  • (Boolean)

62
63
64
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 62

def no_space_in_first_layout?(width)
  width <= 0
end

#on_new_line?Boolean

Returns:

  • (Boolean)

179
180
181
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 179

def on_new_line?
  next_line_start <= @dsl.absolute_top
end

#position_two_segments(first_layout, second_layout, first_text, height) ⇒ Object


134
135
136
137
138
139
140
141
142
143
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 134

def position_two_segments(first_layout, second_layout, first_text, height)
  first_height = first_height(first_layout, first_text, height)

  [
    first_layout.position_at(@dsl.element_left,
                             @dsl.element_top),
    second_layout.position_at(parent.absolute_left + @dsl.margin_left,
                              @dsl.element_top + first_height)
  ]
end

#split_text(layout, height) ⇒ Object

Splits the text into two pieces based on height. Allows one final line to exceed the requested height, which results in smoother flowing text between different sized fonts on a line.


196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 196

def split_text(layout, height)
  ending_offset = 0
  height_so_far = 0

  offsets = layout.line_offsets
  offsets[0...-1].each_with_index do |_, i|
    height_so_far += layout.get_line_bounds(i).height + TextBlock::NEXT_ELEMENT_OFFSET
    ending_offset = offsets[i + 1]

    break if height_so_far >= height
  end
  [layout.text[0...ending_offset], layout.text[ending_offset..-1]]
end

#width_from_ancestorObject

If we're positioned outside our containing width, look up the parent chain until we find a width that accomodates us.


163
164
165
166
167
168
169
170
171
172
173
# File 'shoes-swt/lib/shoes/swt/text_block/fitter.rb', line 163

def width_from_ancestor
  width = -1
  current_ancestor = @dsl.parent
  until width.positive? || current_ancestor.nil?
    width = @dsl.desired_width(current_ancestor.width)

    break unless current_ancestor.respond_to?(:parent)
    current_ancestor = current_ancestor.parent
  end
  width
end