Class: TerminalLayout::RenderObject

Inherits:
Object
  • Object
show all
Includes:
EventEmitter
Defined in:
lib/terminal_layout.rb

Direct Known Subclasses

BlockRenderObject, InlineRenderObject, RenderTree

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from EventEmitter

#_callbacks, #emit, #on, #unsubscribe

Constructor Details

#initialize(box, parent:, content: nil, style: {x:nil, y:nil}, renderer: nil) ⇒ RenderObject

Returns a new instance of RenderObject.



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/terminal_layout.rb', line 35

def initialize(box, parent:, content:nil, style:{x:nil, y:nil}, renderer:nil)
  @box = box
  @content = ANSIString.new(content)
  @children = []
  @parent = parent
  @renderer = renderer
  @style = style
  style[:x] || style[:x] = 0
  style[:y] || style[:y] = 0

  @box.update_computed(style)
end

Instance Attribute Details

#boxObject

Returns the value of attribute box.



33
34
35
# File 'lib/terminal_layout.rb', line 33

def box
  @box
end

#childrenObject

Returns the value of attribute children.



33
34
35
# File 'lib/terminal_layout.rb', line 33

def children
  @children
end

#contentObject

Returns the value of attribute content.



33
34
35
# File 'lib/terminal_layout.rb', line 33

def content
  @content
end

#parentObject

Returns the value of attribute parent.



33
34
35
# File 'lib/terminal_layout.rb', line 33

def parent
  @parent
end

#styleObject

Returns the value of attribute style.



33
34
35
# File 'lib/terminal_layout.rb', line 33

def style
  @style
end

Instance Method Details

#ending_x_for_current_yObject



70
71
72
73
74
75
76
77
# File 'lib/terminal_layout.rb', line 70

def ending_x_for_current_y
  children.map do |child|
    next unless child.float == :right
    next unless child.y && child.y <= @current_y && (child.y + child.height - 1) >= @current_y

    [child.x, width].min
  end.compact.min || self.width || @box.width
end

#heightObject



100
101
102
# File 'lib/terminal_layout.rb', line 100

def height
  style[:height]
end

#inspectObject



104
105
106
# File 'lib/terminal_layout.rb', line 104

def inspect
  to_s
end

#layoutObject



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/terminal_layout.rb', line 139

def layout
  Treefell['render'].puts "layout #{self.inspect}"
  self.children = []
  @current_x = 0
  @current_y = 0
  if @box.display == :block && @box.content.length > 0
    ending_x = ending_x_for_current_y
    available_width = ending_x - @current_x
    new_parent = Box.new(content: nil, style: @box.style.dup.merge(width: available_width))
    inline_box = Box.new(content: @box.content, style: {display: :inline})
    new_parent.children = [inline_box].concat @box.children
    children2crawl = [new_parent]
  else
    children2crawl = @box.children
  end

  children2crawl.each do |cbox|
    Treefell['render'].puts "layout crawling children #{cbox.inspect}"
    if cbox.display == :float
      next if cbox.width.to_i == 0

      render_object = layout_float cbox
      cbox.height = render_object.height

      next if cbox.height.to_i == 0

      self.children << render_object
    elsif cbox.display == :block
      if children.last && children.last.display == :inline && @current_x != 0
        @current_x = 0
        @current_y += 1
      end

      @current_x = starting_x_for_current_y
      available_width = ending_x_for_current_y - @current_x

      if cbox.width && cbox.width > available_width
        @current_y += 1
        @current_x = starting_x_for_current_y
        available_width = ending_x_for_current_y - @current_x
      end

      render_object = render_object_for(cbox, content:nil, style: {
        x: @current_x,
        y: @current_y,
        width: (cbox.width || available_width)
      })
      render_object.layout

      if cbox.height
        render_object.height = cbox.height
      end

      next if [nil, 0].include?(render_object.width) || [nil, 0].include?(render_object.height)

      @current_x = 0
      @current_y += [render_object.height, 1].max

      self.children << render_object
    elsif cbox.display == :inline
      @current_x = starting_x_for_current_y if @current_x == 0
      available_width = ending_x_for_current_y - @current_x

      content_i = 0
      content = ""

      loop do
        partial_content = cbox.content[content_i...(content_i + available_width)]
        chars_needed = partial_content.length
        Treefell['render'].puts "laying out inline #{cbox.name} @current_x=#{@current_x} @current_y=#{@current_y} x=#{x} y=#{y}"
        self.children << render_object_for(
          cbox,
          content:partial_content,
          style: {
            display: :inline,
            x: @current_x,
            y: @current_y,
            width:chars_needed,
            height:1
          }
        )

        content_i += chars_needed

        if chars_needed >= available_width
          @current_y += 1
          @current_x = starting_x_for_current_y
          available_width = ending_x_for_current_y - @current_x
        elsif chars_needed == 0
          break
        else
          @current_x += chars_needed
        end

        break if content_i >= cbox.content.length
      end
    end
  end

  if !height
    if children.length >= 2
      last_child = children.max{ |child| child.y }
      self.height = last_child.y + last_child.height
    elsif children.length == 1
      self.height = self.children.first.height
    else
      self.height = @box.height || 0
    end
  end

  self.children.each do |child|
    child.box.computed[:x] += x
    child.box.computed[:y] += y
  end
  Treefell['render'].puts "laid out box=#{box.name} render-object=#{self.children}"

  self.children
end

#layout_float(fbox) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/terminal_layout.rb', line 258

def layout_float(fbox)
  # only allow the float to be as wide as its parent
  # - first check is the box itself, was it assigned a width?
  if @box.width && fbox.width > width
    fbox.width = width
  end

  if fbox.float == :left
    # if we cannot fit on this line, go to the next
    if @current_x + fbox.width > width
      @current_x = 0
      @current_y += 1
    end

    fbox.x = @current_x
    fbox.y = @current_y

    render_object = render_object_for(fbox, content: fbox.content, style: {height: fbox.height})
    render_object.layout

    @current_x += fbox.width
    return render_object
  elsif fbox.float == :right
    # loop in case there are left floats on the left as we move down rows
    loop do
      starting_x = starting_x_for_current_y
      available_width = ending_x_for_current_y - starting_x

      # if we cannot fit on this line, go to the next
      width_needed = fbox.width
      if width_needed > available_width
        @current_x = 0
        @current_y += 1
      else
        break
      end
    end

    @current_x = ending_x_for_current_y - fbox.width
    fbox.x = @current_x
    fbox.y = @current_y

    render_object = render_object_for(fbox, content: fbox.content, style: {height: fbox.height})
    render_object.layout

    # reset X back to what it should be
    @current_x = starting_x_for_current_y
    return render_object
  end
end

#offsetObject



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/terminal_layout.rb', line 48

def offset
  offset_x = self.x
  offset_y = self.y
  _parent = @parent
  loop do
    break unless _parent
    offset_x += _parent.x
    offset_y += _parent.y
    _parent = _parent.parent
  end
  Position.new(offset_x, offset_y)
end

#positionObject



88
89
90
# File 'lib/terminal_layout.rb', line 88

def position
  Position.new(x, y)
end

#renderObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/terminal_layout.rb', line 116

def render
  # Rather than worry about a 2-dimensional space we're going to cheat
  # and convert everything to a single point.
  result = height.times.map { |n| (" " * width) }.join
  result = ANSIString.new(result)

  if content && content.length > 0
    result[0...content.length] = content.dup.to_s
  end

  children.each do |child|
    rendered_content = child.render

    # Find the single point where this child's content should be placed.
    #  (child.y * width): make sure we take into account the row we're on
    #  plus (child.y): make sure take into account the number of newlines
    x = child.x + (child.y * width)
    result[x...(x+rendered_content.length)] = rendered_content
  end

  result
end

#render_object_for(cbox, content: nil, style: {}) ⇒ Object



309
310
311
312
313
314
315
316
317
318
# File 'lib/terminal_layout.rb', line 309

def render_object_for(cbox, content:nil, style:{})
  case cbox.display
  when :block
    BlockRenderObject.new(cbox, parent: self, content: content, style: {width:@box.width}.merge(style), renderer:@renderer)
  when :inline
    InlineRenderObject.new(cbox, parent: self, content: content, style: style, renderer:@renderer)
  when :float
    FloatRenderObject.new(cbox, parent: self, content: content, style: {x: @current_x, y: @current_y, float: cbox.float}.merge(style), renderer:@renderer)
  end
end

#sizeObject



92
93
94
# File 'lib/terminal_layout.rb', line 92

def size
  Dimension.new(width, height)
end

#starting_x_for_current_yObject



61
62
63
64
65
66
67
68
# File 'lib/terminal_layout.rb', line 61

def starting_x_for_current_y
  children.map do |child|
    next unless child.float == :left || child.display == :inline
    next unless child.y && child.y <= @current_y && (child.y + child.height - 1) >= @current_y

    [child.x + child.width, x].max
  end.compact.max || 0
end

#to_sObject



112
113
114
# File 'lib/terminal_layout.rb', line 112

def to_s
  "<#{self.class.name} position=(#{x},#{y}) dimensions=#{width}x#{height} content=#{content} name=#{@box.name}/>"
end

#to_strObject



108
109
110
# File 'lib/terminal_layout.rb', line 108

def to_str
  to_s
end

#widthObject



96
97
98
# File 'lib/terminal_layout.rb', line 96

def width
  style[:width]
end