Skip to content

ShoesSlotsSelf.md

Noah Gibbs edited this page Oct 10, 2023 · 9 revisions

Shoes, Slots and Self

In Shoes, there is a single Shoes::App object and it's the "self" everywhere. For instance:

def printish(msg)
  $stderr.puts msg
  File.open("/tmp/shoesy_stuff.txt", "a") do |f|
    f.write(msg + "\n")
  end
end

Shoes.app do
  printish "Self top: #{self.inspect}" # It's an instance of Shoes::App, yes
  stack do
    printish "Self in stack: #{self.inspect}" # Yup, here too
  end
  button("Clickity") do
    alert("You clicked me!")
    printish "Self in button handler: #{self.inspect}" # And here too
  end
end

Here's what I see from Shoes3 in that logfile:

Self top: (Shoes::Types::App "Shoes")
Self in stack: (Shoes::Types::App "Shoes")
Self in button handler: (Shoes::Types::App "Shoes")

Note: as of this writing in June 2023, Scarpe does not do this. Scarpe has several selves. This is a problem with some examples, and an incompatibility.

You can find other types of objects. For instance, the object returned by "button" there inspects as "(Shoes::Types::Button)". And you need an object that knows which button it belongs to so it can be used to change button text, set up new click handlers and so on.

Based on both Shoes-Core and Shoes3, I'll say: when a new slot object is created, it "pushes" itself onto a stack of slots. And then newly-created widgets can be added to the current "uppermost" slot, such as the stack you're currently creating.

Pushing Sounds Fragile

You might reasonably say, wait, if a slot just pushes itself and first and pops itself after, can we add anything to it after its block is done? In Shoes3 yeah, definitely.

Here, look:

Shoes.app do
  para "Top"
  stack do
    title "In stack!"
    @f = flow do
      button "Clickity" do
        para "Wherity?"
        @f.para "Addity"
      end
      para "Hrm"
      puts "Flow: " + @f.inspect
    end
  end
end

In the button handler for "Clickity", notice that we add two paras. One goes after the Clickity-button in the flow and adds sidewise. But the other ("Wherity?") shows up at the bottom, after the stacks and flows. So just like we can save a button to mess with it later, we can save a reference to a slot (stack, flow, etc) to mess with it later. But that block still runs with "self" being your app. But your app remembers the current slot. To mess with a slot after its block is finished, you save a reference to it. Just like to mess with a button or para after you make it, save a reference to it.

This is a little confusing because:

  • when you call "button" in the block of a slot, the button shows up in that slot
  • calling "button" on the object returned by that slot will make the button show up in that slot
  • but when you call "button" inside that block, you're calling it on your Shoes::App, not the slot's own object

Your Inner App

Because Shoes::App is so tied through the API, the various Shoes apps all seem to distinguish between Shoes::App-the-DSL-object and the internal app. Though they all do it in slightly different ways. Shoes4 uses Shoes::App/Shoes::InternalApp. Shoes3 uses Shoes::App/Shoes::Canvas. Scarpe uses App/DocumentRoot.

This makes some sense. Since Scarpe::App is a DSL and interface object, some of the heavy lifting is likely to be better done elsewhere.

Widgets

So if that's Shoes::App and various Shoes slots, how do Widgets work? A slot (e.g. a Shoes::Flow) inherites from Shoes::Widget. But you can also create a widget outside Shoes::App. How does that work?

Here, let's try this in Shoes3:

File.unlink("/tmp/shoesy_stuff.txt") rescue nil
def printish(msg)
  $stderr.puts msg
  File.open("/tmp/shoesy_stuff.txt", "a") do |f|
    f.write(msg + "\n")
  end
end

class Twinsies < Shoes::Widget
  def initialize(text) # In Shoes4 this changes to initialize_widget
    printish "Whoah, where are we? #{self.inspect} #{self.class} #{self.class.ancestors}"
    para text
    para em(text)
  end
end

Shoes.app do
  twinsies "Hello!"
end

This shows us that when we call para there, we're in a Twinsies object instance, of class Twinsies, and that inherits from Shoes::Widget, Shoes::Canvas, Shoes::Basic and more. But basically, it seems to be a Shoes slot (remember a Widget acts like a Flow for its contents), and the self is the slot object (Twinsies), not the Shoes::App.

Which would imply that you cannot use your instance variables from your Shoes.app in widgets. They get their own instance variables and can't share them.

Slots and Children

What counts as a slot? Can only slots have children?

Slots and GTK+/Cairo

It looks a lot like a "slot" -- which can include, but is not limited to, stacks and flows -- is mostly a GTK+/Cairo-level abstraction. As a result, a "slot" is mostly a coordinate system for child widgets. This is part of why having a "shape" makes sense. You often want a sub-coordinate-system for multiple art widgets together. It just also happens to be squashed together with CSG union shapes.