shoes / shoes3

a tiny graphical app kit for ruby
http://walkabout.mvmanila.com
Other
181 stars 19 forks source link

Provide a per App event catcher #383

Closed ccoupe closed 6 years ago

ccoupe commented 6 years ago

In issue #287 and others, @BackOrder asks for additional control of events delivered to a window (a shoes app) to write a Shoes GUI builder in Shoes. Instead of overriding canvas.click we could create an app.event = {proc} and modify all the shoes_app_event in app.c to to call it, if specified.

It would return true if its handled the event and false if it wants normal Shoes event handling. Or vice versa? Each of the shoesapp could build a hash {event: :click, x: y: key: } and its up to the that overseer method to do the right thing with the hash.

IanTrudel commented 6 years ago

This sounds like a good idea. I would like to suggest a simplification for the API: use event ! It would integrate better in Shoes DSL and still make it distinguishable from other slot events.

Take a look at the following code sample. Notice params could be "x, y", "direction", etc. but the sample below would do fine for a generic event handler. What do you think?

Shoes.app do
   event do |evt, params|
      case evt
         when :click
            para "click: x #{params.first}, y #{params.last}"
         when :wheel
            para "wheel: direction #{params}"
         else
            para "evt #{evt}, params #{params.inspect}"
      end
   end
end
Shoes.app do
   event do |evt, direction|
      if evt.eql? :wheel
         para "wheel is rolling #{direction}"
      end
   end
end
ccoupe commented 6 years ago

One thing I think would be interesting is to control the events of one window (shoes app) from another app - not just interception but feeding events to it . It needs more thought and some extra events like app_loaded and app_closed

IanTrudel commented 6 years ago

The way things work in Shoes is that a block should be enough. Shoes.app can be assigned to a variable and its members are readily available, which would include event. So it would be possible to write something like...

Shoes.app do
   w = Shoes.app {}
   w.event do |evt, p|
      # second window event handler here
   end
end

By the way, I am not against using a proc but hoping to keep the API clean and simple. Shoes API always favoured block instead of proc. Though perhaps you have sample case that would make sense to use a proc instead.

not just interception but feeding events to it

Sounds interesting. Would you care to elaborate on your thoughts?

It needs more thought and some extra events like app_loaded and app_closed

Isn't it like start and finish? aka #25 and #26

ccoupe commented 6 years ago

GUI testing was my thought. Collect the events generated by a human - serialize, Play back as often as you like.

IanTrudel commented 6 years ago

This would be an amazing feature. It would certainly elevate Shoes. I'm liking it. A lot. :)))

ccoupe commented 6 years ago

The event method is more Shoes like. There are issues. In the user supplied event handler above - we need to provide a way to tell shoes to distribute the event to the shoes_canvas methods . An example would help. shoes_app_click(shoes_app *app, ...) would build an 'event' (class or hash) and look in app->event_handler for something to call - the optional handler and pass the arg(s) for the event. That handler has to return a value to the handler invocation from back to shoes_appclick() - pass it on to shoes canvas_clicked or don't pass it and then shoe_app_click() has to return SHOES_OK

Shoes.app do
  def handler(time, evt, hash)
    if evt == :click
      # save click to to yaml
      return true # pass event to shoes
    end
    return false # DISABLE all other events
  end

  para "Collector";
  button "start" do 
    # open yaml file
    @watch = window do 
      para "you are being spied upon"
      edit_line "text entry"
    end
    @watch.events = handler
  end
  button "stop" do
    # close yaml file
    @watch.events = NIL
  end
IanTrudel commented 6 years ago

This approach could even allow multiple event handlers if need to. By the way, it doesn't need to be plural because an event handler only handles one event at a time. I would also suggest to use Shoes approach to remove an event handler using @watch.event.remove such as any other Shoes element would behave.

Also, just to make things clear (as in writing specs), Shoes should assume any handler let events be passed to other handlers by default (return true even when not explicitly written).

ccoupe commented 6 years ago

The other side is replay or sending events to an app. We'll need a new method in app.c, call it 'play_event' for now.

Shoes.app do
  para "tester"
  button "Chose App & yaml" do
    # open yaml
    # load script in new @window
  end
  button "start"
    yaml.each_line do |args|
       # build event
       @window.play_event event
       sleep 0.1
    end
  end
end

This is not well thought out - just illustration.

IanTrudel commented 6 years ago

What do you think about playback instead of play_event ?

It is noteworthy that sleep had some troubles on Windows in the past and may still be in need of fixing.

ccoupe commented 6 years ago

This approach could even allow multiple event handlers if need.

Exactly! A gui builder might want several handlers it can switch to depending on the state of the built app.

Also, just to make things clear (as in writing specs), Shoes should assume any handler let events be passed to other handlers by default (return true even when not explicitly written).

Examples drag in assumptions :-) There is no chain of handlers - there is the default and the one set in app->event_handler. It has to deal with all event types. Writing handlers would be a very difficult task but this proposal is just a cut out in the event flow for clever people to exploit. Even describing it is a problem.

IanTrudel commented 6 years ago

I am getting cold feet with the proc approach after looking at your work in progress. Shoes should really focus on blocks. The good news is that a proc can in fact be passed as block. Here is a sample to illustrate how one would do. So we don't need to sacrifice the simplicity of Shoes for flexibility. We get both by using a block approach.

Shoes.app do
    def event(&block)
        yield if block_given?
    end

    event do
        para "block\n"
    end

    p = proc { para "proc\n" }

    event &p
end
ccoupe commented 6 years ago

Yeah, there is a lot going on and proc may not be correct - 'event do ..end` is essentially a canvas method - I want one app.event method (could be a block, perhaps) - one issue with blocks is returning true so the event cutout will call the canvas functions normally. Probably not a big issue but I'm not writing event handlers in ruby so what do I know?

The big issue with a block is all the C app event functions need to know that there is a cutout to call (or not). I need to find the internal C for obj.respond_to? I haven't found it yet. One way forward would be to have a app.event_handler = t/f (that solves the tricky remove issues so that's good). So

Shoes.app do
   @w = window "controlled" do
   end
   @w.event do |time, event, args_hash| 
      # return true for shoes to process this event normally
      # return false if we don't want normal
   end
   para "Controller" 
   button "start" do @w.event_handler = true end
   button "stop"  do @w.event_handler = false end
end

I'm OK with that two step process. I kind of like it.

I 'm warming up, a lot to having an Event class object being passed to the block/proc

   @w.event do |evt| 
      if evt.type == :keypress
        evt.status = false # ignore keyevent
     if evt,type == :click 
       evt.args["top"] = 200; evt.args["left"]= 300; 
       evt.status = true
     end
  end

That solves the return value issue - when the Event obj is created in C app__code the status field will be set true (pass it on to canvas methods) and the event handler can change change the event. - a lot. In the C code for app_shoes_clicked after calling the handler with the event it can use the status and contents of the Event object sent/returns instead of the args on the initial call. That would allow uber-control of the event system.

IanTrudel commented 6 years ago

RE: return value

blocks can have a return value but it won't use return keyword. It's no different from, say, array methods with blocks (collect, select, etc).

Shoes.app do
    def event(&block)
        retval = yield if block_given?
        para "retval: #{retval}\n"
    end

    event do
        true
    end

    event do
        false
    end
end

RE: app.event_handler = t/f

This is way too C-ish here. Perhaps we should consider supporting event.enable and event.disable (or start/stop in a timer/animate parlance).

Now just remember that it should be modelled based on other Shoes events (click, release, keydown, etc) where they don't really get to be turned on/off. And it seems that remove doesn't exist either. Not sure why I thought there was but we cannot remove those events (we may override them though).

My experience with Shoes events so far is that I always handled the logic by myself. If something had to be momentarily unavailable, I had instance variables to that effect, e.g. colours sample and curve control point sample. So we should keep things in the spirit of Shoes. Or get new shiny feature for all the events. :)

I 'm warming up, a lot to having an Event class object being passed to the block/proc

It would be a good idea but we need to make sure it makes sense. Right now everything is growing in complexity very fast. It's easy to get over engineered. There is actually no return value issue (see above). The evt.args would be a pain to program with compared to block parameters.

However, having an Event class would make it easy to implement things like the hash {event: :click, x: y: key: } you mentioned earlier because we could have an instance of Event do evt.to_yaml.

ccoupe commented 6 years ago

consider supporting event.enable and event.disable

It's not a canvas thing with multiple user handlers for each and every event type and for each slot - that would be over engineering and a lot of code and name pollution . It's an app thing! app.events = t/f works for me because the handler has to deal with all events so plural is appropriate.

It would be a good idea but we need to make sure it makes sense. Right now everything is growing in complexity very fast. It's easy to get over engineered. There is actually no return value issue (see above).

Perhaps a simple request is not so simple? I'm writing the code and I don't want to write any more than I have to considering only one to two people will ever use this feature. But when I do, it ought to be useful for things you and I haven't thought enough about.

The evt.args would be a pain to program with compared to block parameters.

It's only an pseudo code example - I happen to like |time, event, x, y {event specific stuff in hash} but I haven't begun to see whats common and whats to be in the hash. case type is not a big improvement over writing case evt.type - remember no body is going to use it - it's so deep in the wood we probably don't need to document it.

IanTrudel commented 6 years ago

It's an app thing! app.events = t/f works for me because the handler has to deal with all events so plural is appropriate.

We both agree it's on app level. Let's say we would deal with event as any other Shoes events. Most apps that will use a global event handler will use it all the time but let's say you absolutely need a way to turn on and off events. It would look like this:

Shoes.app do
   event do
      if @status
         # perform event selection and action
      end
   end

   button "start" do @status = true end
   button "stop"  do @status = false end

   start { @status = true }
end

RE: events vs event

We both have been around for a long time. Plural form is a rare thing in programming and applies to things like arrays, databases, etc. Events tend to be pooled and sent one at a time. For example, you have GetMessage, TranslateMessage and DispatchMessage in a Windows application. The messages can be just about anything. Still no plural.

Perhaps a simple request is not so simple? I'm writing the code and I don't want to write any more than I have to considering only one to two people will ever use this feature. But when I do, it ought to be useful for things you and I haven't thought enough about.

It ought to be useful. That's why we have a conversation about it!

RE: Event class

There is no compelling reason for an Event class at this time. It creates an additional unnecessary layer of complexity for very little gain.

The mandatory hash in the block might come as a problem because it is not a necessity for most use cases. In fact, we may just have an array instead and do just fine, e.g. |time, evt, x, y| and subsequently do [time, evt, x, y].to_yaml to save for playback.

IanTrudel commented 6 years ago

Normally you are the voice of reason when it comes to Shoes legacy. It kinda feels the other way around right now.

The truth is that a simple event handler would do. It matches Shoes philosophy and how things work in Shoes and I can see all my event dreams be feasible with it. Why do you want to make it so complicated?

IanTrudel commented 6 years ago

Here is something that would embody all the features you want but without the complexity. Perhaps you would better understand what I am talking about if you see the code.

Shoes.app do
   event do |e, t, args|
      case e
      when :click
         x, y = args
         para "click at #{t} with coordinates #{x}@#{y}\n"
      when :wheel
         para "wheel direction #{args}\n" 
         app.event.next # prevent slot wheel events and move to next event
      when :motion
         left, top = args
         para "motion at #{left}@#{top}\n"
      else
         # saving for playback
         [e, t, args].to_yaml
         # ...
      end if @status
   end

   button "start" do @status = true end
   button "stop"  do @status = false end

   button "playback" do
      # load some yaml file
      yaml.each_line do |e, t, args|
         # time can be passed to replicate the execution timeline
         # ...or use a number such as 0.1 for equivalent to sleep 0.1
         app.event.playback e, t, args
      end
   end

   button "clear" do
      handler = proc { |e, t, args|
         para "proc: #{e}, #{t}, #{args.inspect}\n"
         app.event.next
      }

      event &handler
   end

   start { @status = true }
end
dredknight commented 6 years ago

code seems legit I like it. Simple and useful.

IanTrudel commented 6 years ago

A small revision for the suggested API. You could name app.events with an s if it is generally considered and behaving like a pool of events, only and only one instance per window and is preferably an internal class (cannot be instantiated). event block should however remain without an s. Making it a class attached to app (or window or so) will allow us to prevent polluting app.* namespace and make it easy to grow the event API as we go.

ccoupe commented 6 years ago

Wow. Lots to ideas to process - I should just wait another day and maybe the code will appear!

Shoes.app do
   event do |e, t, args|
      case e
      when :click
         x, y = args
         para "click at #{t} with coordinates #{x}@#{y}\n"
   ....
      end if @status
   end
   button "start" do @status = true end
   button "stop"  do @status = false end

the C code in app.c has no way to know that you have chosen @status as your variable. It has no way to know that you've have an event block in your code. Look at app.c - its not big or tricky - the event handlers are all together. It't can assume you have because that would be all existing scripts - it has to be told you have one.

I don't want to write an Events class unless I have to but there maybe some compelling reasons like getters and setters for all the different evt types - for example keypress doesn't have x, and y.. if event args was an array like some examples above then you have know (document) what the array contents are for each eventype.

app.events.playback event

would only work if app.events was an obj that respnded to 'playback_events' so app.events can't be an array. It could be object of class EventManager (or some other name) but would require you write app.event = EventManager.new() and assign your block to some method of that object which is less shoes like than what I have know. However, it has some benefits - an initializer so It can inform shoea_app_clicked ... that there code to call into and since its an object of Shoes we can depend on the method name and data defined in there. Also a place to keep the list of 'events'

Yes, it just grew again and it's not shoes like.

IanTrudel commented 6 years ago

Wow. Lots to ideas to process - I should just wait another day and maybe the code will appear!

Are we grumpy again? Please, bear with me. I'm doing my best so we have a good understanding of the problem and its solutions.

RE: @status

The block is called from C but its execution is done by the Ruby interpreter. Basically, we don't need to worry about it. This technique has proven to be working over and over. This is what I used in colour and curve control point samples, Numinoes and several other Shoes programs. It's a not an issue. It's already working.

To make sure you totally understand: C side only needs to prepare what it will send to the block (evt, time, parameters) and then call the Ruby function to execute the block. Ruby execute the block and has access to all relevant global/class/instance variables.

would only work if app.events was an obj that respnded to 'playback_events' so app.events can't be an array. It could be object of class EventManager (or some other name) but would require you write app.event = EventManager.new() and assign your block to some method of that object which is less shoes like than what I have know.

Actually, we can subclass Array but it's not a necessity. I am suggesting a coherent API here but we can work out some details. EventManager would do. The user doesn't have direct access to EventManager (similar to Canvas). Shoes will internally instantiate it into app.events when a new Shoes.app/Window/Dialog is created.

How does it sound?

ccoupe commented 6 years ago

Lots of good ideas but C Ruby api and the existing code base (fricking macros will be the death of us all) will determine what is possible. It's always confusing at the C level with the junction/mixin of app vs canvas .

To make sure you totally understand: C side only needs to prepare what it will send to the block (evt, time, parameters) and then call the Ruby function to execute the block. Ruby execute the block and has access to all relevant global/class/instance variables.

If only that were true. - event is not like click or keypress or a widget. Your canvas event {|args| block} preference is really annoying at the C level. Not sure why but it is. Deep in the woods. I'll figure it out.

IanTrudel commented 6 years ago

RE: Macroland and mixins

We both brought significative improvements to Shoes internals but there are still legacy code haunting us. We might open an issue related to this.

Macroland would need improvements and simplification, or a whole new approach. For example, macros for native widgets are using C convention but we would be able to completely move shoes/types/* to Ruby if Ruby convention would used instead. Shoes widgets are written in C but they are in fact Ruby calls. We are shortchanging ourselves here.

If only that were true. - event is not like click or keypress or a widget. Your canvas event {|args| block} preference is really annoying at the C level. Not sure why but it is. Deep in the woods. I'll figure it out.

I am not sure where the hurdles are. My latest suggestions are based on already existing things in Shoes. Some of the proof-of-concepts I wrote before included extending DSL with blocks.

It should be noted it's not necessary to mixin when it comes to app.events. Only instantiating EventManager in Shoes.app and write an app.events method that always return the said EventManager instance. That should do the trick.

By the way, I thought about how to return an array and we should just settle for app.events.to_a as it is the convention in Ruby.

ccoupe commented 6 years ago

[Updated] Finally, some success!

Shoes.app do
  event do |evt, args|
    $stderr.puts "event handler called #{evt} #{args}"
    true
  end
  app.events = true
end

produces event handler called click [1, 306, 301] on the terminal. and it goes on to call the real click code in C. More good news - we don't need the app.events = true. Bad news is that you can't put an event block in window 2 from window 1 which reduces the utility. It may be possible to do that with the older app.event_handler = proc {}

IanTrudel commented 6 years ago

This is very good news!

More good news - we don't need the app.events = true.

Tell me something I don't know! haha

Bad news is that you can't put an event block in window 2 from window 1 which reduces the utility. It may be possible to do that with the older app.event_handler = proc {}

It's not really a new problem. The window is only available at runtime and thus you cannot attach a new event until start {} begins. app.event_handler wouldn't work either (sic!).

Shoes.app do
   @w = window {}
   start do
      @w.click do
         @w.para "click"
      end
   end
end
IanTrudel commented 6 years ago

And if you want to statically define it...

Shoes.app do
   window do
      click do
         para "click"
      end
   end
end
ccoupe commented 6 years ago

if utility includes gui testing or event recording of unmodified shoes scripts then we need the feature. There is still much to do it's not trapping the click on a button for example - not calling shoes_button_send_click so that some unused code in the refactor.

IanTrudel commented 6 years ago

We had heated conversations about how difficult it would be to ensure Shoes elements are readily available. I think it's possible without that much work and you think otherwise - our usual dynamics. :)

So it won't be strange for Shoes users to use it. They should be declaring the event within the Window declaration in a typical scenario. Sometimes it may be necessary to declaring it at runtime and thus one would use the start block as it is customary.

Don't get me wrong. I would love to have it exactly as you suggest. Just saying here that the usage is acceptable because it's always been that way.

Typical scenario:

Shoes.app do
   window do
      event do |e, t, params|
         para "#{e}, #{t}, #{params}"
      end
   end
end

Adding event at runtime:

Shoes.app do
   @w = window {}
   start do
      @w.event do |e, t, params|
         @w.para "#{e}, #{t}, #{params}"
      end
   end
end
ccoupe commented 6 years ago

our usual dynamics. :)

This works too - tested. We can have both. we do have both.

Shoes.app do  
  stack do
    flow do 
      button "click here" do
        $stderr.puts "button clicked"
      end
    end
  end
  app.event = proc do |evt, args|
    $stderr.puts "event handler2 called #{evt} #{args}"
    true
  end
end

When I click on the middle of the screen and then on the button we get this on the terminal

set app event handler
have event_handler, invoking...
event handler2 called click [1, 215, 194]
button click seeks permission
button: don't have event - but should
button clicked

Sadly, button's presses don't go the shoes_app_click -> shoes_canvas_send_click path and as example shows we can't (yet) find the 'event' block on the first canvas from nested slots. We just need to climb up via parent until we find it in shoes_control_send() . It's also clear that many widgets chose to report via shoes_control_send().

It also brings the question - for a captured button press what are the args to the event handler? #384 related. :click, [self_of_control] ? x,y don't exist at this level.

IanTrudel commented 6 years ago

This works too - tested. We can have both. we do have both.

Sorry? The code you have shown is for the current app rather than another window. Did you test for another window? That was your initial concern.

It also brings the question - for a captured button press what are the args to the event handler?

You mean the global event handler? The point of the global event handler is to be a meta event handler and not a replacement for existing Shoes handlers. It shouldn't care about the button at all because the button has its own handler.

Glad to see progress. It's exciting!

ccoupe commented 6 years ago

If it works in one app it will work on another app.(window). If you want to use this feature to intercept clicks (for example) anywhere then we need a way to know if the click is actually a button press -- two different things.

You may consider that a bug in Shoes but it's a deep and everywhere bug and appears to be toolkit or maintainer based do 'what works'. From this perspective its a bug/misfeature.

IanTrudel commented 6 years ago

If it works in one app it will work on another app.(window).

I guess I will see later on.

If you want to use this feature to intercept clicks (for example) anywhere then we need a way to know if the click is actually a button press -- two different things.

Why? The click event doesn't know about it. The button block handles the button press. So why the meta click has to know whether a button is pressed or not?

We could perhaps need to know if there is a widget where the mouse is clicked. Typical GUI programming normally requires one to search through a window controls list with given information (such as position) and then will return a reference to the control.

You may consider that a bug in Shoes but it's a deep and everywhere bug and appears to be toolkit or maintainer based do 'what works'. From this perspective its a bug/misfeature.

We got a lot of those in Shoes.

ccoupe commented 6 years ago

Typical GUI programming normally requires one to search through a window controls list with given information (such as position) and then will return a reference to the control.

we are not typical

IanTrudel commented 6 years ago

we are not typical

Not an excuse to bastardize Shoes.

We will be stuck to deal with a useless value in the parameters, that might often be nil or something, if you force this into the global click event because most case scenarios will not need to know this information. Global event handler does not and should not replace local events. How would the code look like anyway? Is there any way to be optional?

You might have the GUI builder in mind but it would be the GUI builder responsibility to find out which widget is clicked on. I would rather we improve traversing features: contents, parent, perhaps adding some find element, etc. Then a GUI builder would rely on traversing features.

ccoupe commented 6 years ago

Slightly better event1.rb

Shoes.app do
  event do |evt,args|
    case evt
    when :click 
      $stderr.puts "event handler called: #{evt} #{args}"
      return false
    else
      return true
    end
  end
  stack do
    flow do 
      button "click here" do
        $stderr.puts "button clicked"
      end
      click do
         puts "flow click"
      end 
    end
  end
  click do
    puts "app click" 
  end
end

line 6, return false turns of all click handler and the button press, as currently written. return true allows all of them to be processes it that's where the mouse is. The button click reports as event handler called: click [(Shoes::Types::Button)] - not particularly useful.

Global event handler does not and should not replace local events. How would the code look like anyway? Is there any way to be optional?

You seem to have slightly different definition of 'global event handler' I suspect you just want to collect the lexical event code in one block so it easier to write? You can do that as proposed - return true and everything gets it's events. Or you could deny keyboard events but keep the clicks flowing. Depends on what you want to do.

I do want to allow grabbing events, modifying events and creating events and that's a lot more work because it's a confusing mess in there.

IanTrudel commented 6 years ago

I think that I don't fully understand what you are doing and why. The relationship with the new event system, slot events and widget events are somehow becoming unclear now.

My objections are mostly focused on something that will fit in Shoes ecosystem. Sometimes it seems to me that you don't use Shoes that much because the new APIs are always terrible. Though the C code you produce is always good.

We don't always see eye-to-eye but we do find compromises on most things. API changes is however something you never agree to. So we must make sure that we introduce a very good API.

RE: returning true/false

This is obviously work in progress. While it was my initial suggestion, I'd like to outline that some methods may return true/false and cause mayhem when a return is not explicitly specified. Thus I corrected it with the subsequent suggestion of app.events.next. This method would, say, allow to change flags in the EventManager for which Shoes will take the appropriate action.

The button click reports as event handler called: click [(Shoes::Types::Button)] - not particularly useful.

What is reported when clicking anywhere where there is no widgets?

I do want to allow grabbing events, modifying events and creating events and that's a lot more work because it's a confusing mess in there.

This is a very powerful idea. I'm agreeing with you but just don't like your approach. We need to make sure to keep things simple, coherent and consistent.

ccoupe commented 6 years ago

What is reported when clicking anywhere where there is no widgets?

event handler called: click [1, 253, 241]

This is a very powerful idea. I'm agreeing with you but just don't like your approach. We need to make sure to keep things simple, coherent and consistent.

You know, you could answer some of your questions by compiling Shoes and running the test scripts faster than asking me on github? Perhaps you could write the Ruby level specs for EventManager and Event classes and such, maybe the code too and I'll move on to other branches and bugs.

IanTrudel commented 6 years ago

event handler called: click [(Shoes::Types::Button)] event handler called: click [1, 253, 241]

Are we basically loosing button id and coordinates when one click on a widget?

Perhaps you could write the Ruby level specs for EventManager and Event classes and such, maybe the code too and I'll move on to other branches and bugs.

A little difficult to do out of the blue because it has to be tied up with Shoes internal event mechanism. It's also likely to be written on the C side of Shoes. It would be relatively simple (see below an outline).

class EventManager
   attr_accessor :filename
   attr_accessor :logging

   def initialize
      @events = []
      @logging = true
      @filename = "shoes-event-#{Time.now.strftime("%Y%m%d-%H%M%S")}.log"
   end

   def to_a
      @events
   end

   def to_yaml
      @events.to_yaml
   end

   # Shoes event loop log event to EventManager
   def record(event, time, parameters)
      @events << [event, time, parameters]
      open(@filename, "a") { |f| f.puts(@events.last.to_yaml) } if @logging
   end

   def playback(event, time, parameters)
      shoes_send_event(event, time, parameters) ### C call
   end

   # Tell Shoes to stop dispatching this event
   # and wait for next event
   def next
      shoes_cancel_dispatch_event() ### C call
   end
end
ccoupe commented 6 years ago

Are we basically loosing button id and coordinates when one click on a widget?

Correct - it's that gray hole in the 'click' space when using shoes_control_send() to report widget activity. Osx uses it a lot for (s_change) often that gtk and thats a little worrisome. At that level, widgets don't report their outer most window co-ords and I don't know if there is a way to get that easily from the tool kits. Clicks on images, svg and some other visual things go through a much different path where shoes uses the outermost x,y and finds the proper 'widget' by crawling down the slots/canvas tree.

   # Tell Shoes to stop dispatching this event
   # and wait for next event
   def next
      shoes_cancel_dispatch_event() ### C call
   end

Sadly, that belongs in the Events class - the arg to event do |eventobj| method - the simple event_name, other_args [] I have now is not ruby or shoes like. Needs a class.

I also don't want to pollute the Shoes/Ruby name space with the Event Class or EventManager Class names. Those name could easily clash with other gems or something the user would like to use. consider this:

Assume that we can create meaningful Shoes events from gtk/cocoa. Assume

Shoes_EventManager obviously manages objects of Shoes_Events class. There is only one one Shoes_EventManager obj for all windows/app so the triggering 'app' is a member var in the Shoes_Event object. It's started at shoes startup - normal coders don't know it's there. New GUI events are appended to the events queue (an []?) in Shoes_EventManager , The users 'event do' method will get the first event, given a change to say yay/nay and /or modify the event. If they don't have an event do set (99% of user scripts won't) it's just push/pop and pass it on to the existing shoes widgets, pseudo widgets and any of click/keypress methods - just like normal nobody needs to know anything about Shoes_EventManager or Shoes_Event

Shoes_Events objects are the harder thing to define and code. We know from the description above that an object of Shoes_Events needs a member for the event type (click, keypress...) and it needs a field/member/var for the 'app' that created it and it needs a var for 'what do do with it` - pass it on so shoes can use it or subsume it, do thing more with it. Call the field/var 'use_event' or 'status' or whatever you want. it's a boolean. True means Shoes should do the regular thing (or you've modified the Shoes_Event and want to use the modification.

What else is in a Shoe_Event object - click needs outer most x,y,and button (and shift_key and meta-press if we can get them, keypress need the keycode (and the meta keys flags) What does a button click report? A combo_box selection? I don't know!

About those Assumptions While I expect I can get the top_level x,y for a widget "action" it's not a given for all widgets and all toolkits (gtk/cocoa) and all for all events . It could be a large amount of C/Obj-C work and all for naught if it can't do what the user want. I'm well aware that some folks want to know what is available before they decide what's important. Considering the amount of C/Obj->C work to answer that , I'm going to need a spec target(s)

ccoupe commented 6 years ago

It would be worth learning what what java/jruby reports in the svt::event class. http://www.informit.com/articles/article.aspx?p=354574&seqNum=3

ccoupe commented 6 years ago

For the folks that want to know what events Shoes could be reported in a Shoes_event, we have NSEvent and Gtk Many common things and far more things than Shoes uses now. Find the ones you like.

IanTrudel commented 6 years ago

Correct - it's that gray hole in the 'click' space when using shoes_control_send() to report widget activity.

This is something I am concerned about. I like the feature but not with this approach. One of my complains about slot event click is that we don't get the proper coordinates when clicking over a widget! Your current approach also creates an inconsistency where you may be returned coordinates or a Shoes widget and thus leaving additional burden to the Shoes users.

Osx uses it a lot for (s_change) often that gtk and thats a little worrisome.

There is no doubt there are some parts in Shoes that are rotting and downright abusive that went unchecked for years.

Sadly, that belongs in the Events class - the arg to event do |eventobj| method - the simple event_name, other_args [] I have now is not ruby or shoes like. Needs a class.

I also don't want to pollute the Shoes/Ruby name space with the Event Class or EventManager Class names. Those name could easily clash with other gems or something the user would like to use. consider this:

We have more flexibility on this considering this is an internal class. Even an anonymous class could do. The pseudo Ruby class is just for your consideration, an inspiration. So what you deem appropriate should be alright as long as the Shoes API is acceptable.

RE: NSEvent and GTK Event

Excellent resources. We should however first focus on the core event system we want to put in place and make sure we can extend it in a coherent and consistent manner. Then we can grow the API as time goes. Still, it won't hurt to get inspired by both event structures.

ccoupe commented 6 years ago

This is something I am concerned about. I like the feature but not with this approach. One of my complains about slot event click is that we don't get the proper coordinates when clicking over a widget! Your current approach also creates an inconsistency where you may be returned coordinates or a Shoes widget and thus leaving additional burden to the Shoes users.

This one of the critical assumptions that I'm working on. Shoes knows where the widget is (ie shoes_place) so we don't have to work with gtk/cocoa, hopefully. In this approach you'd get the start x,y darkspace (widget) but in top level co-ords. The 'puts' in the example may be confusing re: widgets - that value is the button object (self) so it can respond to many methods. Is that good enough?

I'm going to write the Shoes_Event. It's still exploratory so don't expect perfection. We need several getters and one setter so it can't be hidden/anonymous.

Shoes.app do
  @b1 = button "one"
  @b2 = button "two"
  event do |evt|
    if evt.type == :click && evt.object == @b2
       evt.accept = false # no clicks for you, b2
    else
      evt.accept = true
    end
  end
end
IanTrudel commented 6 years ago

The evt as presented in the code snippet is a huge departure of everything else in Shoes. A considerable complexity not seen in a typical Shoes application. It may be the easiest approach to implement but it's changing the paradigm of Shoes. I'm not keen on it and continue to think we can use the current DSL to reach our objectives.

Exploring is fine. We may learn what we need to learn to find a viable solution.

ccoupe commented 6 years ago

Design your dsl for events, my friend. There is nothing Shoes-like or trivial about poking your head into an event stream and picking winners and losers. Experts only territory. If you can make it simple, please do so.

IanTrudel commented 6 years ago

Design your dsl for events, my friend. There is nothing Shoes-like or trivial about poking your head into an event stream and picking winners and losers. Experts only territory. If you can make it simple, please do so.

Well I did submit to you some ideas and code samples. This is work in progress on both sides. However, I totally agree with you that there is nothing easy about this project. It does feel easier to use a data structure approach but this ain't no Pascal programming club here!

Let's keep talking and going back and forth. It might help when we have something substantial to play with and get a feel of it. Eventually one of us or both of us will figure out. We made a lot of progress already.

ccoupe commented 6 years ago

Pascal? - those good old days on the Apple II! If you think about it , Shoes/Ruby blocks modify objects in or out their block scope all the time. Would naming event do..end to event! do ...end appease you? It's more pure to Ruby.

This works

Shoes.app do
  event do |evt|
    case evt.type
    when :click 
      $stderr.puts "event handler called: #{evt.type} #{evt.button}, #{evt.x} #{evt.y}"
      evt.accept = @ck1.checked?
      if evt.object == @btn
        $stderr.puts "have widget #{evt.object}"
      end
    else
      evt.accept = true
    end
  end  

  stack do
    para "Click test 1"
    flow do 
      @ck1 = check checked: true; para "pass clicks to Shoes"
    end
    flow do 
      @btn = button "click here" do
        $stderr.puts "button clicked"
      end
      click do
         puts "flow click"
      end 
    end
  end
  click do
    puts "app click" 
  end
end

Is an if - else chain with mentally conflicting states and names confusing? Yes it is. I said it before, It's not something I would want to code. It's not easy. Maybe you could use a state machine gem with it's own DSL. Won't be shoes-like but this feature never was.

IanTrudel commented 6 years ago

Pascal? - those good old days on the Apple II!

I also have fond memories of Pascal programming. Been using Turbo Pascal for years.

RE: DSL

All the new major features (SVG, Plot, Event) suffer from being poorly designed API. You are not entirely to blame because Shoes comes with a shallow DSL and the features work with multiple calls where most things in Shoes don't (para, button, etc). These features are great and hopefully you don't take often to my comment. The problem really is about how to use them.

I had previously experimented in implementing a better DSL for Shoes and did some test on GtkNotebook. Many of the things I come up with don't make it up to here, for various reasons, as I mentioned before. Notebook might not make it here today but it seems appropriate to talk about my DSL experiments.

The basic idea is simply to provide methods within a block in the DSL.

Notebook example

notebook do
   page do
      ...
   end
   page do
      ...
   end
end

Here is a proof of concept for Event. We could could have Shoes.app event return an instance of Event or something, so there won't be a need for e.event do ... end but simply event do ... end instead.

class Event
   def event(&block)
      @self_before_instance_eval = eval("self", block.binding)
      instance_eval(&block)
   end

   def method_missing(method, *args, &block)
      @self_before_instance_eval.send(method, *args, &block)
   end

   def accept(yn)
      puts yn
   end

   def object
      "button"
   end
end

e = Event.new

e.event do
   accept true
   puts object
end

We can probably come up with a module DSL to use into anything we need a DSL with inner method support.

This approach for DSL will greatly improve Shoes because it gives us more flexibility in complex cases such as SVG, Plot, Event, Notebook, etc. It is also well within Shoes way of doing things. One could only suspect this approach was not well known in early days of Shoes.

IanTrudel commented 6 years ago

A clarification: I still suggest we keep block paramaters in event do |evt, ...| ... end in addition to the DSL extention.

ccoupe commented 6 years ago

How much of that has to be done in the Ruby-C api? How much is Shoes? How much is Ruby? We are currently limited to using the Shoes DSL (and those FUNC_M... macros) to define anything in C to Shoes. That's a huge constraint in flexibility along with the problem that I'm just keeping shoes 3 alive - just one C coder and there is some serious code slinging ahead for the Ruby 2.4 work and the gtk3 issues. Building a new Shoes from this code base is fun to think about but it's unlikely.

The more important questions are What do you need to write your GUI builder?" What was the stumbling block? Is the proposed solution good enough?