Skip to content

Instantly share code, notes, and snippets.

@antonhornquist
Last active June 15, 2016 21:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antonhornquist/c1c5326257354c69135061c40c8c243b to your computer and use it in GitHub Desktop.
Save antonhornquist/c1c5326257354c69135061c40c8c243b to your computer and use it in GitHub Desktop.

// Grrr for SuperCollider: an UI library for grid based controllers

// Grrr classes are loosely based on SuperCollider GUI classes. GRControllers are similar to Windows, GRView and its subclasses are similar to View and its subclasses. Like GUI classes a parent is typically passed to a GRView upon instantiation to indicate which controller or view the view will become a child of. Instead of GUI bounds (a Rect supplied to GUI Views) arguments origin (a Point), numCols and numRows designate where on the parent view or controller a Grrr widget is added and how large it is supposed to be.

a=GRScreenGrid.new; // A GRScreenGrid is a virtual grid with configurable size that defaults to 8x8 b=GRButton(a, 0@0, 2, 2); // a button spanning 2x2 keys is placed at top left key on the virtual grid b.action = { |view, value| (value.if("Hello", "Goodbye") + "World").postln }; // pressing the top-leftmost 2x2 grid keys will now post a message to the Post Window and toggle button value which in turn toggles led state

b.value = true; // set value b.value = false;

b.toggleValue; // toggle value, this is the action coupled GRButton presses trigger

b.valueAction = true; // valueAction sets value and triggers action b.valueAction = false;

b.remove; // remove the button from the screen grid

// Enabling and disabling views

b=GRButton(a, 0@0); // a 1x1 (the default size) button placed at top left key b.value = true; b.disable; // button led disappear when a view is disabled and key events are no longer handled b.enable; // enable button again to

b.remove; // remove the button from the screen grid

// Grrr differs from SuperCollider GUI classes in that views in enabled state (the default state) are not allowed to overlap. An error will be thrown for any method invocation that make two or more Grrr Views do so. At any given point in a view or controller only one view may be enabled. As such, there is no z ordering of views. The exception to this is GRContainerView which represents a view that may contain other views.

b=GRButton(a, 0@0, 2, 2); c=GRButton(a, 0@0, 1, 1); // this will throw an error c=GRButton.newDisabled(a, 0@0, 1, 1); // this will work. a disabled view will not handle key events nor output led state c.enable; // this throws an error since b and c overlap b.disable; // if the large button is disabled, c.enable; // it's possible to enable the small button

a.view.removeAllChildren; // removes all of the controllers child views

// GRContainerViews may be used to group other views.

b=GRContainerView(a, 0@0, 4, 4); c=GRMultiButtonView(b, 0@0, 4, 4); c.action = { |view| ("i'm in" + view.getParents).postln }; a.postTree; // postTree prints a textual representation of the view tree to the Post Window

d=GRContainerView(a, 4@0, 4, 4); e=GRContainerView(d, 0@0, 2, 2); f=GRButton(e, 0@0, 2, 2); f.action = { |view| ("i'm in" + view.getParents).postln }; a.postTree;

a.view.removeAllChildren;

// A more convenient way to make pages of widgets is to use GRSwitcher b=GRSwitcher(a, 0@0, 4, 4); // a GRSwitcher for switching between views c=4.collect { GRMultiButtonView(b, 0@0, 4, 4) }; // create four 4x4 views in the GRSwitcher (note: the contained views of a GRSwitcher must be of the same size as the GRSwitcher) b.value; // the GRSwitcher instance value indicates active page b.value=1; // changing value changes active page b.value=2; b.value=3; d=GRHToggle(a, 0@4, 4, 1); // a toggle can be used to switch between views of a GRSwitcher d.action = { |view, value| b.value = value }; // hook up the toggle to the switcher

a.view.removeAllChildren;

// Coupled / decoupled views

// Grrr widgets may be used in a coupled or decoupled fashion. Coupled views automatically updates a widget's value on incoming key events. This is the default, simply because that's how regular GUI views work. Decoupled views do not update a widget's value on incoming key events. A widget's value mostly correspond to the state of the leds of the widget. Decoupled views require a bit more code but is arguably more useful since key presses may be customized and not directly correspond to how a widget normally works.

b=GRHToggle(a, 0@0, 8, 1); // a coupled toggle c=GRHToggle.newDecoupled(a, 0@1, 8, 1); // a decoupled one

s.boot;

b.action = { |view, value| (degree: value, sustain: 0.05).play }; // coupled toggle plays sound

// Widget specific non-led-coupled actions are typically used with decoupled views ( c.togglePressedAction = { |...args| // togglePressedAction is triggered anytime toggle is pressed anywhere c.value = c.numCols.rand; // randomize value on toggle press }; )

// Something a bit more interesting: a sequence of different length triggered from the toggle ( c.togglePressedAction = nil; c.toggleValuePressedAction = { |view, value| // toggleValuePressedAction is triggered anytime a toggle value is pressed var playFunc; playFunc = { |degree| (degree: degree, sustain: 0.05).play }; r.stop; r = Routine({ loop { (value..0).do { |val| 0.1.wait; playFunc.(val); view.value = val; }; }; }).play; }; )

r.stop; // stop the sequence

a.view.removeAllChildren;

// Detached views

// A view is typically created and placed in a container / parent view. When a view has a parent it is considered attached. It's possible to create detached views and attach them at a later stage.

b=GRButton(a, 0@0); b.hasParent; // true c=GRButton.newDetached; c.hasParent; // false c.isDetached; // true a.addChild(c, 1@1); c.hasParent; // true c.isDetached; // false

a.view.removeAllChildren;

// Debugging and Internal Snooping

// Views may be indicated which means its bounds shown with a flash on the controller to which it is attached b=GRButton(a, 1@3); c=GRButton(a, 2@0); b.indicateView; c.indicateView;

a.view.removeAllChildren;

// Using a GRCommon class variable views may automatically be indicated when added/removed to a controller. GRCommon.indicateAddedRemovedAttachedDetached=true;

// Automatic indications are nice hints when prototyping / live coding UI b=GRButton(a, 1@3); c=GRButton(a, 2@0); b.remove; c.remove;

// However, the global should in other cases be set to false (the default) for indications not to happen all the time. GRCommon.indicateAddedRemovedAttachedDetached=false;

// With globals Grrr button and led events may be traced

GRCommon.traceButtonEvents=true; GRCommon.traceButtonEvents=false; // the default

GRCommon.traceLedEvents=true; b=GRButton(a, 1@3); b.value = true; b.value = false; GRCommon.traceLedEvents=false; // the default b.remove

// Textual representations of views

b=GRContainerView(a, 0@0, 4, 4); c=GRMultiButtonView(b, 0@0, 4, 4); a.postTree // post a tree of all contained views

a.plot // plot a view or controller b.plot // plot container view a.plotTree // plot a view or controller and all its contained views recursively

a.view.removeAllChildren;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment