GUI Applications with Shoes

shoes-icon

If you’ve been coding in Ruby for any length of time, you’ve probably at least heard of Shoes. Shoes is a DSL / GUI toolkit originally developed by the famed _why The Lucky Stiff. It’s also the basis for the Hackety Hack program that helps users learn Ruby and GUI programming.

You can find the official website here.

A simple app in Shoes looks like this:

# hello_world.rb
require 'green_shoes'

Shoes.app do
  para "Hello World"
end

Shoes make creating GUIs pretty darn easy. This tutorial covers the fundamentals and shows some examples of things that can be created with it.

The code in this tutorial is available on github.

Putting Your Shoes On

One of the confusing things about Shoes is that it comes in a few flavors.

  • Red Shoes – the original Shoes platform
  • Blue Shoes – QT implementation
  • Purple Shoes – JRuby/SWT implementation
  • Green Shoes – pure Ruby/GTK2 implementation
  • Shoes4 – the next version of Shoes

As of this writing, if you install the shoes gem and try to use it, you will get the following message:

Sorry, this gem currently does nothing. Team Shoes is working on Gemifying Shoes, and this is just a placeholder until then.

The Shoes github project has not been updated in quite a while, so it looks like Team Shoes decided gemifying Shoes wasn’t at the top of their priority list. Part of the reason is likely to be the fact that Shoes is not just a GUI library, it’s an app platform. It even comes with its own Ruby interpreter.

blue_shoes looks like it never made it out of the early stages of development. Looking through the code, a lot of the methods are unimplemented, so you probably want to stay away from this one.

purple_shoes was created by the same person as green\_shoes. It’s probably the second best alternative to Red Shoes. I tested several of the cross-platform examples, and they seemed to work. However, it has not been updated in a couple of years, so your mileage may vary. You will need JRuby if you want to try purple_shoes.

green_shoes is a GTK2 flavor of Shoes. It seems to be the most recently updated and most implemented alternative to Red Shoes. Isn’t exactly the same, but it’s close enough if you just want to get a taste without installing Red Shoes.

Green Shoes has a page on the differences between Red Shoes and Green Shoes. In addition, you will notice a lot of “Note: Green Shoes doesn’t support…” in the manual. If you are are serious about using Shoes, you will likely want to see you can get Red Shoes to work, or plan on heavily modifying Green Shoes.

Team Shoes currently appears to be working on a rewrite called Shoes4. The code is available on github. Unfortunately, It doesn’t look like it’s ready for use quite yet.

Getting Started

I had trouble getting the Red Shoes platform installed, so I will be using the Green Shoes gem in this tutorial. Since Green Shoes uses GTK2, make sure you have the GTK2 libraries installed before trying to run anything.

First, go ahead and grab the green_shoes gem:

gem install green_shoes

The structure of a Shoes app looks like this:

Shoes.app do
  element/slot(args) do
    element/slot(args) do
      ...etc
    end
  end  
end

In the example at the beginning of the article, “para” created a paragraph element with the content “Hello World.”

# hello_world.rb
require 'green_shoes'

Shoes.app do
  para "Hello World"
end

In the Shoes DSL, GUI elements are created in place using element creation methods. This is in contrast to the standard practice of instantiating windows, instantiating layouts, instantiating widgets, adding the widgets to the layouts, and adding the layouts to the windows. Shoes does this underneath the hood.

It’s possible to store elements in variables so that they can be referenced elsewhere in the app.

Shoes.app do
  @paragraph = para "This is a paragraph."
  @button = button("change it") do
    @paragraph.text = "Now it says something else."
  end
end

The variables in this tutorial are all instance variables (prefixed with ‘@’) to avoid confusing them with Shoes DSL methods.

Stacks and Flows

Shoes organizes its elements in two ways it calls slots.

  • stacks – vertical
  • flows – horizontal
# stacks_flows.rb

require 'green_shoes'

Shoes.app do

  stack do
    para "stack item 1", width: 100
    button "stack item 2"  
  end

  flow do
    para "flow item 1", width: 100
    button "flow item 2"
  end
end

Something worth pointing out is that, in Green Shoes, TextBlocks (para, caption, etc) take up the full width unless a width is specified. This is true even if a TextBlock is in a flow.

Stacks and flows can have a margin set to separate the elements.

# margins.rb
require 'green_shoes'

Shoes.app do

  caption "No margin set:"

  stack do
    para "stack item 1", width: 100
    button "stack item 2" 
  end

  flow do
    para "flow item 1", width: 100
    button "flow item 2"
  end

  caption "Margin: 10"

  stack(margin: 10) do
    para "stack item 1", width: 100
    button "stack item 2" 
  end

  flow(margin: 10) do
    para "flow item 1", width: 100
    button "flow item 2"
  end
end

Sometimes the desired layout is achieved by putting a flow inside of a stack and vice versa.

# flow_inside_stack.rb
require 'green_shoes'

Shoes.app do

  stack do
    para "stack item 1", width: 100

    flow do
      para "flow item 1", width: 100
      button "flow item 2"
    end

    button "stack item 2" 
  end
end

Responding to User Interaction

As expected in Ruby, Shoes uses blocks for handling events like button clicks or text changes.

# event_handling.rb
# Interactive Shoes elements take an event handler as a block
require 'green_shoes'

Shoes.app do
  stack(margin: 10) do
    @edit_box = edit_box do |e|
      @copy_box.text = @edit_box.text
    end  

    @copy_box = para "what you type in the edit box goes here"

    @button = button("obfuscate") do
      ob_bytes = @copy_box.text.each_byte.map { |b| b+1 }
      @copy_box.text = ob_bytes.map { |b| b.chr}.join
    end
  end
end

Making a YARV Instruction Viewer

Shoes’ basic text box is created with #edit_box. You can read or set EditBox#text. Here is an app that takes Ruby code and shows the virtual machine instructions associated with it. Note: This example will not work if you use Purple Shoes.

# yarv_viewer.rb
# Show YARV instructions for given Ruby code
require 'green_shoes'

Shoes.app(width: 900, height: 550) do
  stack(margin: 10) do
    flow(margin: 10) do
      caption "Ruby Code", width: 400
      caption "YARV Instructions", width: 400
    end
    flow(margin: 10) do
      @code = edit_box(width: 400, height: 400)
      @yarv = edit_box(width: 400, height: 400) 
    end
    flow(margin: 10) do
      @compile_button = button("compile") do
        @yarv.text = RubyVM::InstructionSequence.compile(@code.text).disasm
      end
    end
  end
end

Using Shoes’ Progress Bar

Shoes provides a barebones progress bar. It has the value #fraction which ranges from 0.0 to 1.0. One of the things that separates Progress from the other elements is that it appears to need to be placed manually, pixel-wise, rather than simply fitting in a slot.

# downloader.rb
# Basic downloader with progress-bar
require 'green_shoes'
require 'open-uri'

Shoes.app(width: 350) do
  title "Downloader"
  stack(margin: 10) do

    # user input
    flow do
      @url_line = edit_line
      @download_button = button("download") do
        url = @url_line.text 
        uri = URI.parse(url)
        filename = File.basename(uri.path)

        Thread.new do
          open(filename, 'wb') do |f|
            f.write(open(url, 
              :content_length_proc => @content_length_proc,
              :progress_proc => @progress_proc
            ).read)
          end
        end
      end
    end

    # labels
    caption "File Size:" 
    @expected_bytes = para ""
    caption "Downloaded:"
    @current_bytes = para ""
    caption "Progress:"
    @progress_bar = progress left: 10, top: 300

    # open-uri event handlers
    @content_length_proc = lambda do |content_length|
        @content_length = content_length
        @expected_bytes.text = content_length
    end

    @progress_proc = lambda do |bytes|
      @current_bytes.text = bytes
      @progress_bar.fraction = bytes / @content_length.round(1)
    end
  end
end

Ruby’s #open method accepts a hash that can specify two event handlers that can be used to determine progress.

  1. :content_length_proc – provides access to the size of the file being downloaded
  2. :progress_proc – provides access to the number of bytes downloaded so far

These event handlers (lambdas, in this case) are used to set @progress_bar.fraction.

Wrapping CLI Tools

One possible use for Shoes is creating simple wrappers for command-line tools like ImageMagick’s convert.

# image_converter.rb
# Basic image converter using ImageMagick
require 'green_shoes'
require 'gtk2'

Shoes.app do
  title "--Image Converter--"

  caption "Image Path:"
  @path_line = edit_line

  button("select image") do
    dialog = Gtk::FileChooserDialog.new(
            "Select Image",
            nil,
            Gtk::FileChooser::ACTION_OPEN,
            nil,
            [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
            [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT])

    if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
      @path_line.text = dialog.filename
    end
    dialog.destroy
  end

  caption "Choose a new image format:"
  list_box items: ["jpg", "png", "gif"] do |list|
    @conversion_type = list.text
  end

  button("convert") do
    @file_path = @path_line.text
    @filename = File.basename(@file_path, '.*')
    @conversion_path = "#{File.dirname(@file_path)}/#{@filename}.#{@conversion_type}"
    if @file_path != @conversion_path
      Thread.new do
        @status.text = "converting..."
        system("convert #{@file_path} #{@conversion_path}")
        @status.text = ""
      end
    else
      alert("The image is already in that format")
    end
  end
  @status = para ""
end

Although Shoes doesn’t provide particular essentials, it’s sometimes possible to take them from other libraries. In this example, I have borrowed GTK’s FileChooserDialog to make it easier for the user to specify images. This isn’t very clean, but green_shoes uses gtk2 anyway, so in this case it’s not so bad. Note: This will not work with Purple Shoes.

Drawing and Animation

Shoes takes inspiration from animation toolkits like Processing in its drawing and animation facilities.

# drawing.rb
# Show how to draw basic shapes in Shoes
require 'green_shoes'

Shoes.app do
  background darkgray
  fill darkblue
  arrow 10, 10, 100
  fill red
  rect 10, 100, 100
  fill yellow
  oval 10, 200, 50
  strokewidth(2)
  line 10, 300, 100, 300
end

One of the unexpected things about Shoes is that colors can be specified without using symbols or strings. For example, you would use darkblue and not :darkblue. It’s also possible to specify colors using #rgb and hexadecimal color code strings like ‘#FF1B77′.

Shoes provides the #animate method for moving elements over time. It takes one argument which is the frames per second of the animation. You’re not going to get Processing -or certainly not Actionscript- levels of control, but it’s enough to play around.

# animation.rb
# Shows how to animate a bouncing ball
require 'green_shoes'

Shoes.app do
  fill blue
  @gravity = 0.05
  @velocity = 0.0
  @ball_radius = 60
  @ball = oval(top: 10, 
              left: width / 2 - @ball_radius, 
              radius: @ball_radius)
  button("drop ball") do
    animate(60) do
      @velocity += @gravity
      @ball.top += @velocity 
      if @ball.top > height - @ball_radius * 2
        @ball.top = height - @ball_radius * 2
        @velocity = @velocity * -0.8
      end
    end
  end
end

If you have never written any animation code before, the position set after the boundary check at the end might not be intuitive. Essentially, once the “ball” crosses the boundary (the “ground”), the condition is true. Even after the velocity is reversed, the condition is still true, so the velocity will simply be reversed again…and again, causing the ball to jitter around the boundary
until it runs out of energy. The edge of the ball is set at the boundary to prevent this from happening and to maintain the illusion that the ball is bouncing against it. A seemingly obvious solution to this problem would be to use the == operator instead, but it is extremely unlikely that the edge of the ball is going to be exactly on the boundary on any given frame.

Conclusion

Shoes brings some good ideas and is perfect for small, toy projects. Unfortunately, if you want to do anything serious with it, some DIY will likely be involved for a few reasons:

  • Development efforts have been split between various flavors, and there isn’t a Red Shoes gem at this time.
  • Shoes was never intended to be a complete replacement for a standard GUI library. It was mainly created as an educational tool.
  • At least with Green Shoes, error reporting seems broken. No matter what happened, I only ever saw a generic GLib error. Debugging won’t be easy.

Shoes has a lot of features that were not covered in this tutorial. If you would like to learn more, be sure to check out the following resources:

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Imran Latif

    Awesome article. Explains different flavors of Shoes in a clear way. You have explained basic usage of Shoes in a great way.