Skip to content
Cecil Coupe edited this page Oct 20, 2018 · 14 revisions

This is an idea for Shoes 3.3.8 or beyond.

We define a new Shoes slot like 'thing' called 'layout' so we have flow, stack and layout. layout has many of the methods and style settings that flow does but it adds a couple of new ones.

class MyLayout
...
end
Shoes.app do
  layout manager: MyLayout.new(), width: 400, height: 300 do
    button "One"
    para "Two"
  end
end

Currently, Shoes does not compute the size of the widgets inside the layout and has no way for you to pass that information to Shoes. Therefore, unlike flows and stacks, user layouts must specify height and width. They can be resized after creation.

Layout Protocol

The manager: hash argument should include an object. Shoes will call methods of that object/class at certain points. In computer terminology that means there is a protocol to follow. What the methods do depends on what the author wants. Shoes just requires they exist with the proper arguments and return the correct value.

class MyLayout
  attr_accessor :canvas
  def initialize()
  end

  def setup(canvas, attributes)
    @canvas = canvas
  end

  def add(canvas, widget, attributes)
  end

  def remove(canvas, widget, position)
    return true
  end

  def clear()
  end

  def size(canvas, pass)
  end

  def finish()
    @canvas.show
  end
end

initialize(args...)

This is the standard Ruby initialize for your class. Arguments can be what ever you like.

setup(canvas, attributes)

This is called after the layout slot's canvas has been created. The attributes argument is the styles hash. Note that hash keys are symbols. :width and :height for example. Be cautious. There may or may not be the key you want and the value may not be what you expect. The canvas width is often 0 (zero) heights are often 0 (zero)

It is highly recommended to keep a copy of the canvas argument. The finish() callback will thank you.

add(canvas, widget, attributes)

This method is called after the widget has been added to the layout canvas/slot contents array. Attributes that the user supplied are available. Note: you can send in an style argument that Shoes doesn't define but you can use, for example,

  @ml = layout manager: MyLayout.new() do
    button "Press Me:, name: 'widget-1'
    para "User Info Demo", name: 'widget-2'
  end

:name is not a Shoes style but in the add method you can get it name = attributes && attributes[:name]. This is a feature. As a layout author, you can use this to establish a protocol with your layout user.

At some point, either here in add() or in finialize() you need to widget.move x,y to the proper place in your layout. Until then, Shoes thinks this is flow slot. It's the move that tells Shoes not to manage it and just leave it where it is. We depend this feature.

remove(canvas, widget, position)

This is called before the widget is deleted from the canvas/slot at the given position. The position is probably the numeric position in the contents array to the canvas. You can return nil if you don't want it deleted. That's not user friendly but it's your layout. Explicitly, return true is recommended.

clear()

This method is called when all contents have gone away - when the slot/canvas has been deleted. An orderly Shoes quit will call this. You probably don't want to do anything here, but if you opened a SQL data base in initialize() or setup() you'd want to close it here.

size(canvas, pass)

This method can be called for several situations. It will be called after Shoes has added the layout canvas and the layout {block} has run and widgets added by the block. This is when pass == 0. Odds are, there is nothing you want to do for pass == 0

Pass == 1 or higher is called when Shoes wants to paint - which is frequently. To avoid performance problems Shoes attempts to only call when the size changes. For many layout situations this might be the correct time to do an internal call to finish() for drawing contents in the new locations.

finish()

This will be called when the layout user issues a 'finish' method call. If you do all the layout in add()/remove() then this method can be short. However, if your layout requires knowing when to compute and move() all the widgets in the layout you would do that here. See the size method and setup methods. They often need to work together

Note the layout is hidden until you call @canvas.show. It

DO NOT ITERATE ATTRIBUTES

DO NOT iterate over the attributes/style hash. It has keys that will crash Shoes it you try. Even inspect can crash Shoes.

Default Layout.

At some point Shoes may include an internal layout manager when you don't provide your own layout manager class. That day is not here yet.

Previous Thoughts

One way to approach the whole layout issue would be to have something like the user defined Shoes Widget only for slots. class MyLayout < Shoes::Slot You would get all the slot methods add,clear,replace,hide to intercept if needed as well as contents array. The class author would need to supply a method for shoes_place_decide() to call for (re)sizing.

From the user perspective, and borrowing from Android constraint layout

require `shoes/contraints`
Shoes.app do
  stack do
    para "Demo"
    @cl = constraint_layout width: 300, height: 200 do
       b1 = button "one", width: 40
       b2 = button "two", width: 80, height: 30
       self.topleftpos b1,20,10
       self.radius b1,b2,120
    end
    para "after"
  end
end

shoes/constraints:

class constraint_layout << Shoes::Slot
  attr_accessor :width, :height, :contraints
  def initialize(hashargs)
    @constraints = {}
  end
  
  def topleftpos (widget,top,left)
    # initial position
    @constraints[widget] = [:fixed, top, left]
    widget.move_to(top,left)
  end

  def radius(wid1, wid2, degrees)
    # initial position
    @contraints[wid1] = [:radial ,wid2, degree]
    ntop,nleft = compute_radial(wid1, degrees)
    wid2.move_to(ntop,nleft)
  end
  
  # called from inside Shoes:
  def resize(new_x, new_y, new_w, new_h)
    recompute_all_xy(new_x, new_y, new_w, new_h)
    @constraints.each do |wid, ary|
      case ary[0]
      when :fixed
        x = @widgets[wid].x
        y = @widgets[wid].y
        wid.move_to(x,y)
      when :radial 
    end
  end
end

Of course that example is woefully wrong and incomplete - fixing it is not the question. The question is this API sufficient for writing Layouts?

References

AutoLayout

  1. Gtk emeus
  2. Apple VFS

iOS AutoLayout Videos

  1. Part 1
  2. Part 2

Menu

In This Section:

Clone this wiki locally