Skip to content
Cecil Coupe edited this page Dec 18, 2017 · 6 revisions

Event Streams

Think of events as a stream of user clicks, mouse moves, keypress and such.Shoes just passes them on to your click block or the keypress block, if you have them. Dont't have those 'event handlers' defined in your script? You won't be notified. But, those events are seen by Shoes and it knows your script did not ask for click events (for example) to be sent to it. How does Shoes know that? You did't set a click handler (or keypress handler).

So, you have a stream of events (clicks, mouse moves, keypress). If your script asks to be notified of those events then your click or keypress block is called, buttons get pressed, characters enter edit_lines and you have Shoes working normally.

What if you wanted to spy on all events, or prevent or modify events before the normal processing. Why would you want to do that? Perhaps you want to create a GUI builder app - you don't all clicks going to the being-built layout. Or you could write a program to capture all the events as you use a Shoes app and then later play them back - for testing or presentations?

Let me be very clear: Only Shoes Experts will survive poking inside the event stream!

Shoes 3.3.5 introduced the ShoesEvent class and the 'event' block

Shoes.app do
   button "one" do
     puts "button one"
   end
   click do |button,x,y,mods|
     puts "click at #{x},#{y} for button #{button} with modifiers #{mods}"
   end
   
   # Get's gnarly here
   event do |evt|
     evt.accept = true
   end
end

That gnarly event block will be called before the button block gets run and before that click block is called. As the writer of the event block, you control whether to pass the event on (accept it) for normal processing or don't pass it on (evt.accept = false).

If you don't have an event block everything behaves as it always did. Once you add the event block you will play in a different sandbox and some of the rules are strict - you will always do an evt.accept = t/f Failure to do so will just confuse you. I use evt as the block arg so it doesn't confuse anybody reading this. event is the Shoes level method. evt is the block argument and is an object of Class ShoesEvent.

If you have that event block in your script, Shoes will start creating ShoeEvents objects for it to deal with and you have to deal with them all - accept = t/f. You might imagine thats there's a lot of object creation and garbage collect is going to run sooner and more often. Correct. This is not something for the normal script writer to use.

Setting an event block

You can set an event block into a different window. It's not intuitive but you can do it and you should consider using it in your design.

ShoesEvent class

It's a C struct with Ruby getters and setters methods for each of the fields in the struct. You probably won't be creating these object in your code. Shoes does it and gives them to your event block

accept

You've already seen that there is the accept field and I've warned you to alway set it before leaving the event block. Yes, the event is created with a default of true, pass it it on, but you should be explicit.

type

This where the fun begins. This is a Ruby symbol, not a string. Although it prints like it was a string, it's not a string. Not all possible events are currently passed to an event block. Tests/events/event6.rb is an example

Shoes.app do

  event do |evt|
    # do not trigger new events here unless you can handle them recursively
    # which is harder than you think. 
    case evt.type
    when :click 
      $stderr.puts "click handler called: #{evt.type} #{evt.button}, #{evt.x} #{evt.y} #{evt.modifiers}"
      evt.accept = true
    when :keypress
      $stderr.puts "keypress: #{evt.key}"
      evt.accept = true
    when :keydown
      $stderr.puts "keydown for #{evt.key}"
      evt.accept = $ck.checked? 
    when :keyup
      $stderr.puts "keyup for #{evt.key}"
      evt.accept = $ck.checked? 
    when :motion
      evt.accept = false
    when :release
      evt.accept = false
    when :wheel 
      $stderr.puts "wheel handler called: #{evt.type} #{evt.button}, #{evt.x} #{evt.y} #{evt.modifiers}"
      evt.accept = true
    else
      puts "Other: #{evt.type.inspect}"
      evt.accept = true
    end
  end  

  stack do
    para "Key Tests"
    flow do
      $ck = check checked: true; para "Enable up/down"
    end
    @eb = edit_box width: 500, height: 350
  end
  keypress do |key|
    @eb.append "press: #{key}\n"
  end
  keyup do |key| 
    @eb.append "up: #{key}\n"
  end
  keydown do |key| 
    @eb.append "down: #{key}\n"
  end
  wheel do |d, x, y, mods|
    @eb.append "wheel dir: #{d} at #{x},#{y}, with #{mods}\n"
  end
end

Other events maybe included over time but the above sample shows that not a fields are valid for all events. They won't crash anything but they aren't useful. Keypress for example does report x,y and pressing on a button won't tell you which button was pressed. Slippery rules, all different.

Native Button events are not :clicks, they are :btn_activate when sent to the event block/handler.

object Read/Only

This gets set for native widgets like a button, checkbox or radio. x and y are valid as is height and width. but keep reading. You will have to keep track of the widget in the window, Shoes just notifies you that the use clicked this one. For non native widgets like Image, Svg, and Plot your event block can get modifiers like 'shift' and 'control'

Object is also valid if you click in a textblock (para, title, inscription...) and you can use the x,y,w,h. Remember that checkboxes are usually two things in a flow slot, the box and the text are two different Shoes objects.

x Read/Only

Most mouse related events like click have a valid x. This is the x of the widget in window co-ordinate. Not relative to the slot. For native widgets this will be left most position of the widget and not where the mouse was within the button. For non native widgets (Image, Svg, Plot) it might be the real mouse location.

y Read/Only

Most mouse related events like click have a valid y. This is the y of the widget in window co-ordinate. Not relative to the slot. For native widgets this will be left most position of the widget and not where the mouse was within the button. For non native widgets (Image, Svg, Plot) it might be the real mouse location.

width Read/Only

Clicks on a Widget (native or non-native) will report a valid width for the widget.

height Read/Only

Clicks on a Widget (native or non-native) will report a valid height for the widget.

button Read/Only

The button that was clicked. Highly platform and hardware dependent. Don't make assumptions about any number > 3. In wheel created events the button is actually the direction of the scroll. 1 for scrolling up and 0 for scrolling down. This may be changeable in the OS and Theme so you should discount it's usefulness.

key

This is only valid for keyup, keydown, and keypress events and they don't report the same. Press ^R and key down and key up will provide 'r' and keypress will provide 'control_r'. I'm certain that depression is ahead for anyone trying to 'front run' that mess in an event block. Use Keypress if you have to

key =

Currently you can change the key before the event is passed to normal Shoes handling. Likely to be not allowed in the future.

modifiers Read/Only

Many Mouse based events handlers like click, release, motion and wheel handlers have a new modifiers argument available in Shoes 3.3.5 (native widgets don't). So, the event block and the ShoesEvent object passed to it needs to provide them. There is only 3 possible: 'control, shift and control_shift, 4 states if you count nil.

Check the source

Tests/events/*.rb is the real source of what event handlers can do. There is capture/replay that works with samples/simple/chipmunk.rb as a proof of concept for GUI testing There is an undocumented and unfinished app.replay_event hash method.

Clone this wiki locally