andymeneely / squib

A Ruby DSL for prototyping card games.
http://squib.rocks
MIT License
917 stars 67 forks source link

Relative text positions based on previous text height #330

Closed bbugh closed 3 years ago

bbugh commented 3 years ago

Hi 👋

I have a use case where I need to write out a table-ish data list. Unfortunately, a few of the entries can be taller than one line, so I'm using relative positioning to output each value, but I can't find any way to increment the y based on height.

I'm currently double-printing to get the height from the return value of text, but I can't find any way to access the row index so I can use the height

%i[col1, col2, col3].each_with_index do |column, index|
  # Pre-print in order to calculate the height of the text
  info = text str: data[column], layout: 'label', ellipsize: false

  # This is the problem - seems like no way to get height⁉️ value for the current "row"
  # I can get it from the "colum
  height = info[ROW_INDEX⁉️][:height]

  y_pos = y_pos + (LINE_PADDING * 2) + height⁉️

  # draw alternating backgrounds
  if index.even?
    rect x: '4.25in', y: y_pos - LINE_PADDING, width: '3.75in', height: height⁉️ + (LINE_PADDING * 2), fill_color: '#F5F3F4', stroke_width: 0
  end

  # draw the real text
  text layout: "stat_text", y: y_pos, ellipsize: false, str: data[column], height: ⁉️
end

I've dug through the issues and documentation for quite a while and can't find any way to do this. I see #201 and #295, but really I just need a way to set the height based on the current "row". Does this exist?

andymeneely commented 3 years ago

The info here refers to the index of the card - card 0, card 1, etc. So that might be different based on how the text on each card was rendered. So, your y_pos should probably be handled as an array. To get all the heights for all the cards, you can do something like this:

# renamed info to extents
extents = text str: data[column], layout: 'label', ellipsize: false
heights = extents.map { |e| e[:height] }
# you can modify the map here to modify each height as necessary to get your y_pos array

I think that will get you where you need... but, that being said, here are some thoughts:

bbugh commented 3 years ago

Thanks!

I ended up pre-measuring all of the possible text, getting the heights, and then transposing the big array into the appropriate format.

COLUMNS.each do |column|
  measured_text = text str: data[column], layout: "text_value", ellipsize: false
  column_heights[column] = measured_text.map { |x| x[:height] }
end

card_column_heights = column_heights.values.transpose

positions = card_column_heights.map do |card|
  next_y_pos = 0
  positions = card.map.with_index do |cur_height, i|
    y_pos = i == 0 ? START_Y_POS : next_y_pos
    height = cur_height + (LINE_PADDING * 2)
    next_y_pos = y_pos + cur_height + (LINE_PADDING * 2)
    { y_pos: y_pos, height: height }
  end
end

column_y_positions = positions.transpose.map { |list| list.map { |x| x[:y_pos] } }
column_heights = positions.transpose.map { |list| list.map { |x| x[:height] } }

COLUMNS.each_with_index do |column, column_index|
  if column_index.even?
    rect x: '4.25in', y: column_y_positions[column_index], width: '3.75in', height: column_heights[column_index], fill_color: '#F5F3F4', stroke_width: 0
  end

  text str: column.titleize, layout: "text_label", y: column_y_positions[column_index].map { |x| x + LINE_PADDING }
  text str: data[column], layout: "text_value", y: column_y_positions[column_index].map { |x| x + LINE_PADDING }, ellipsize: false
end

It's a little messy and can possibly be simplified, but it works fast and exactly as desired.

If you need to show things in a table, resizing rows based on previous rows might be confusing to players

I agree, but we're making flashcards for memorization!