Skip to main content

GUI Applications with Shoes

    Robert Qualls
    Share

    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: