Skip to content
Cecil Coupe edited this page Oct 18, 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

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
  def initialize()
  end

  def setup(canvas, attributes)
  end

  def add(canvas, widget, attributes)
  end

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

  def clear()
  end

  def finish()
  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.

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.

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 empty. However, if your layout requires knowing when to compute and move() all the widgets in the layout you would do that here.

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.

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