This is the testing Godot forums! All forum posts unique to this forum will be deleted! Please use the main forums here for any posts you want to keep. All forum rules still apply.

Flex / flow / wrap container layout? Plugin?

adabruadabru Posts: 4Member

The same question was asked on reddit but the thread was archived. I want a container similar to the existing HBoxContainer that wraps the items if their width exceeds the horizontal size limit. The items have variable size.

Maybe it's also a good thing to put into the Asset-Library. At the moment there is only a Circular Container.

Someone who coded this already and willing to share his/her code?

Best Answer

  • adabruadabru Posts: 4
    edited January 2020 Accepted Answer

    Ok, so for now I created a container that wraps horizontally and that exports an angle variable to style some ratio:

    # reference:
    # sciter's flow: https://sciter.com/docs/flex-flow/flex-layout.htm
    # css3 flexbox, short: https://www.w3schools.com/csS/css3_flexbox.asp
    # css3 flexbox, long: https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
    tool
    extends Container
    
    export (float, 0.0, 90.0) var angle = 0.0 setget set_angle
    
    func _notification(what):
      if (what==NOTIFICATION_SORT_CHILDREN):
        var xOff = 0
        var yOff = 0
        var line_height = 0
        for c in get_children():
          if not c is Control:
            continue
          # wrap
          if xOff > 0 and xOff + c.rect_size.x > self.rect_size.x:
            xOff = 0
            yOff += line_height
            line_height = 0
          fit_child_in_rect( c, Rect2( xOff, yOff, c.rect_size.x, c.rect_size.y ) )
          xOff += c.rect_size.x
          line_height = max(line_height, c.rect_size.y)
    
    # set horizontal size so that angle is approximated
    func _get_minimum_size():
      # algorithm
      #  - start with one line
      #  - in each step, shorten by one pixel and shrink to max line-length
      #  - until no shrinking possible anymore
      #
      # a dynamic programming idea would start with something like following table:
      #  1,4 2,4 3,4 4,4
      #  1,3 2,3 3,3
      #  1,2 2,2
      #  1,1
      var variant
      var approximation = 1e6
      var next_variant = _shrink_width(1e6)
      if next_variant.x == 0:
        return Rect2(0, 0, 0, 0)
      var next_approximation = atan(next_variant.y / next_variant.x) / PI * 180
      while abs(angle - next_approximation) < abs(angle - approximation):
        variant = next_variant
        approximation = next_approximation
        next_variant = _shrink_width(variant.x - 1)
        next_approximation = atan(next_variant.y / next_variant.x) / PI * 180
      return variant
    
    func _shrink_width(width):
      var xOff = 0
      var yOff = 0
      var max_width = 0
      var line_height = 0
      for c in get_children():
        if not c is Control:
          continue
        # wrap
        if xOff > 0 and xOff + c.rect_size.x > width:
          xOff = 0
          yOff += line_height
          line_height = 0
        xOff += c.rect_size.x
        max_width = max(max_width, xOff)
        line_height = max(line_height, c.rect_size.y)
      return Vector2(max_width, yOff + line_height)
    
    func set_angle(new_angle):
      angle = new_angle
      minimum_size_changed()
    

    I'm open to feedback and some feature requests. When my colleague and I will be through some iterations and if there is any interest, I will consider adding a plugin. Still I'm also open for your code.

Answers

  • adabruadabru Posts: 4Member
    edited January 2020 Accepted Answer

    Ok, so for now I created a container that wraps horizontally and that exports an angle variable to style some ratio:

    # reference:
    # sciter's flow: https://sciter.com/docs/flex-flow/flex-layout.htm
    # css3 flexbox, short: https://www.w3schools.com/csS/css3_flexbox.asp
    # css3 flexbox, long: https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
    tool
    extends Container
    
    export (float, 0.0, 90.0) var angle = 0.0 setget set_angle
    
    func _notification(what):
      if (what==NOTIFICATION_SORT_CHILDREN):
        var xOff = 0
        var yOff = 0
        var line_height = 0
        for c in get_children():
          if not c is Control:
            continue
          # wrap
          if xOff > 0 and xOff + c.rect_size.x > self.rect_size.x:
            xOff = 0
            yOff += line_height
            line_height = 0
          fit_child_in_rect( c, Rect2( xOff, yOff, c.rect_size.x, c.rect_size.y ) )
          xOff += c.rect_size.x
          line_height = max(line_height, c.rect_size.y)
    
    # set horizontal size so that angle is approximated
    func _get_minimum_size():
      # algorithm
      #  - start with one line
      #  - in each step, shorten by one pixel and shrink to max line-length
      #  - until no shrinking possible anymore
      #
      # a dynamic programming idea would start with something like following table:
      #  1,4 2,4 3,4 4,4
      #  1,3 2,3 3,3
      #  1,2 2,2
      #  1,1
      var variant
      var approximation = 1e6
      var next_variant = _shrink_width(1e6)
      if next_variant.x == 0:
        return Rect2(0, 0, 0, 0)
      var next_approximation = atan(next_variant.y / next_variant.x) / PI * 180
      while abs(angle - next_approximation) < abs(angle - approximation):
        variant = next_variant
        approximation = next_approximation
        next_variant = _shrink_width(variant.x - 1)
        next_approximation = atan(next_variant.y / next_variant.x) / PI * 180
      return variant
    
    func _shrink_width(width):
      var xOff = 0
      var yOff = 0
      var max_width = 0
      var line_height = 0
      for c in get_children():
        if not c is Control:
          continue
        # wrap
        if xOff > 0 and xOff + c.rect_size.x > width:
          xOff = 0
          yOff += line_height
          line_height = 0
        xOff += c.rect_size.x
        max_width = max(max_width, xOff)
        line_height = max(line_height, c.rect_size.y)
      return Vector2(max_width, yOff + line_height)
    
    func set_angle(new_angle):
      angle = new_angle
      minimum_size_changed()
    

    I'm open to feedback and some feature requests. When my colleague and I will be through some iterations and if there is any interest, I will consider adding a plugin. Still I'm also open for your code.

Leave a Comment

Rich Text Editor. To edit a paragraph's style, hit tab to get to the paragraph menu. From there you will be able to pick one style. Nothing defaults to paragraph. An inline formatting menu will show up when you select text. Hit tab to get into that menu. Some elements, such as rich link embeds, images, loading indicators, and error messages may get inserted into the editor. You may navigate to these using the arrow keys inside of the editor and delete them with the delete or backspace key.