How to create "On/Off" Button behavior analogous to a MIDI keyboard?

Hi all,

I need to create a Button that behaves like a key on a MIDI keyboard: it is ON when it is actively being pushed down, and off when released. In other words:

  • It has only two states, “off” and “on”
  • It goes to “on” at mouseDown click, and stays there only as long as user is holding the mouse down
  • Upon release of the mouse (mouse up), button switches back to “off” state.

I was able to create this with the code below, but if feels like cheating:

(
w = Window.new;
b = Button(w, Rect(100, 100, 200, 200))
            .states_([["off", Color.black, Color.white]])
            .mouseDownAction_({
	"ON while mouse is down".postln;
	b.states_([["on", Color.black, Color.red]])
            })
            .action_({ 
	"OFF when mouse is released".postln;
	b.states_([["off", Color.black, Color.white]]);
            });
    w.front;
)

It feels like cheating because the button above has really just one state, and I’m just changing its color and label at the right moment. So I don’t get the benefit of having a button.value of 0 and 1 for the ‘off’ and ‘on’ respectively as it would be the case if I really had two states defined.

Is there a simpler way to achieve this? I couldn’t figure out just by reading the Button help file.

Thanks for any hints!

Bruno

It seems like setting the value to 1 using valueAction inside mouseDownAction would work, then you can use action for the button behaviour:

(
w = Window.new;
b = Button(w, Rect(100, 100, 200, 200))
.states_([["off"], ["on"]])
.mouseDownAction_({ |b|
	b.valueAction = 1;
})
.action_({ |b|
	b.value.postln;
});
w.front;
)
2 Likes

Very cool! I had not thought of that. Thank you!
Bruno

You’re not really cheating. It’s the SC Button that is unusual. In most other GUI toolkits the button states actually refer to the “pressed” and “depressed” states of the GUI visual. In SC the button “states” are logical states that are layered on top of an “ordinary” GUI button which is cycled several times. So you have one logical state per two “physical” (i.e. GUI visual states).

(Actually, to be a bit more accurate, some GUI toolkits have 3 visual states for a button, with “hovered” being the 3rd, but that only makes sense for mouse-like pointers, i.e. not meaningful for touch-screen GUIs.)

This is an example from SC’s own help files. It happens to be in the PopupMenu help, but it also has a button that has exactly the behavior you want and it’s done the obvious way, so it’s an “official cheat”:

(
s.waitForBoot({

 var w, menu, snd, funcs, b;

 w = Window.new.front;

 menu = PopUpMenu(w, Rect(10, 10, 90, 20))
  .items_(["Sine", "Saw" , "Noise" , "Pulse"]);

 funcs = [
  { SinOsc.ar(440, 0, 0.3) },
  { Saw.ar(440, 0.3) },
  { WhiteNoise.ar(0.3) },
  { Pulse.ar(440, 0.2, 0.3) }
 ];

 b = Button(w, Rect(110, 10, 180, 20))
  .states_([["play", Color.black, Color.green]])
  .mouseDownAction_({
    snd = funcs.at(menu.value).play;
   })
  .action_({ arg butt, mod;
    snd.release;
   });

 w.front;

 p = CmdPeriod.add({ b.value_(0) }); // set button to 0 on hitting Cmd-period
 w.onClose_{ snd.release; CmdPeriod.removeAll }; // clean up when window is closed

})
)