Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / JITLib / GUI / JITGui.sc
blob645eaefa96a307c38222e8771fdf29ca2489c071
1 /***
2         Abstract superclass for
3         TdefGui,PdefGui,
4         TdefAllGui, PdefAllGui
5         EnvirGui,
7         MonitorGui,
8         NPGui, NdefGui,
9         ProxyMixer (ProxySpaceGui),
10         ParamGui,
11         and possibly others to follow.
13         common traits:
14         an observed object (a proxy, monitor, space, envir, ...)
15         a skipjack for watching it;
16         a getState method for getting all the observed state
17         a prevState instvar for keeping the old state around;
18         a checkUpdate method for comparing old and new state,
19                 and updating the gui elements that have changed;
21         parent, minimum bounds for its numItems, actual bounds;
23         if the object has settings, make an EnvirGui or ParamGui
24         ParamGui can do sliders or knobs ...
26         Simpler ones: EnvirGui, TaskProxyGui;
28 ***/
30 JITGui {
31         var <object, <numItems, <parent, <bounds, <zone, <minSize, <defPos, <skin, <font;
32         var <nameView, <csView;
33         var <prevState, <skipjack, <scroller;
34         var <config, <hasWindow = false;
36         *initClass {
37                 Class.initClassTree(GUI);
39                 GUI.skins.put(\jit, (
40                                 fontSpecs:      ["Helvetica", 12],
41                                 fontColor:      Color.black,
42                                 background:     Color(0.8, 0.85, 0.7, 0.5),
43                                 foreground:     Color.grey(0.95),
44                                 onColor:                Color(0.5, 1, 0.5),
45                                 offColor:               Color.clear,
46                                 hiliteColor:    Color.green(1.0, 0.5),
47                                 gap:                    0 @ 0,
48                                 margin:                 2@2,
49                                 buttonHeight:   18,
50                                 headHeight:     24
52                         )
53                 );
54         }
56         *new { |object, numItems = (0), parent, bounds, makeSkip = true, options = #[]|
57                 ^super.newCopyArgs(nil, numItems, parent, bounds)
58                         .init(makeSkip, options)
59                         .object_(object);
60         }
62         init { |makeSkip, options|
64                 skin = GUI.skins.jit;
65                 font = Font(*skin.fontSpecs);
66                 prevState = ();
68                 // calc bounds - at least minbounds
69                 this.setDefaults(options);
70                 this.calcBounds(options);
72                 if (parent.isNil) { this.makeWindow };
73                 this.makeZone;
75                         // then put all the other gui items on it
76                 this.makeViews(options);
77                         // and start watching
78                 if (makeSkip) { this.makeSkip };
79         }
81         accepts { |obj| ^true   }
83         object_ { |obj|
84                 if (this.accepts(obj)) {
85                         object = obj;
86                         this.checkUpdate;
87                 } {
88                         "% : object % not accepted!".format(this.class, obj).warn;
89                 }
90         }
92         name_ { |name|
93                 if (hasWindow) { parent.name_(this.winName(name)) };
94                 if (nameView.notNil) {
95                         try { nameView.object_(object) };
96                         nameView.string_(name);
97                 };
98         }
99         
100         hasName {
101                 ^nameView.notNil and: { nameView.string.notNil }
102         }
104         getName {       ^try { object.key } ? "_anon_" }
105         winName { |name| ^this.class.name ++ $_ ++ (name ?? { this.getName }) }
107         calcBounds {
108                 var defBounds;
109                 if(bounds.isKindOf(Rect)) {
110                         bounds.setExtent(max(bounds.width, minSize.x), max(bounds.height, minSize.y));
111                         ^this
112                 };
114                 defBounds = Rect.fromPoints(defPos, defPos + minSize + (skin.margin + skin.margin));
115                 if (bounds.isNil) {
116                         bounds = defBounds;
117                         ^this
118                 };
120                 if (bounds.isKindOf(Point)) {
121                         bounds = defBounds.setExtent(max(bounds.x, minSize.x), max(bounds.y, minSize.y));
122                 }
123         }
125         makeWindow {
126                 parent = Window(this.winName, bounds.copy.resizeBy(10, 10)).front;
127                 parent.addFlowLayout;
128                 hasWindow = true;
129         }
131         makeZone {
132                 zone = CompositeView(parent, bounds).background_(Color.white);
133                 zone.addFlowLayout(skin.margin, skin.gap);
134                 zone.resize_(2);
135                 zone.background_(skin.foreground);
136         }
138         moveTo { |h, v|
139                 if (hasWindow) { parent.bounds = parent.bounds.moveTo(h, v); }
140         }
142         close {
143                 if (hasWindow) { parent.close }
144         }
146         makeSkip {
147                 skipjack = SkipJack({
148                 //      try {
149                                 this.checkUpdate
150                 //      } {
151                 //              (this.getName  + "checkUpdate failed.").postln;
152                 //              }
153                         },
154                         0.5,
155                         { parent.isNil or: { parent.isClosed } },
156                         this.getName
157                 );
158         }
161                 // these methods should be overridden in subclasses:
162         setDefaults { |options|
163                 if (parent.isNil) {
164                         defPos = 10@260
165                 } {
166                         defPos = skin.margin;
167                 };
168                 minSize = 250 @ (numItems * skin.buttonHeight + skin.headHeight);
169         //      "minSize: %\n".postf(minSize);
170         }
172         makeViews {
173                 var lineheight = max(
174                         skin.buttonHeight * numItems + skin.headHeight,
175                         zone.bounds.height)  - (skin.margin.y * 2);
177                 nameView = DragBoth(zone, Rect(0,0, 60, lineheight))
178                         .font_(font)
179                         .align_(\center)
180                         .receiveDragHandler_({ arg obj; this.object = View.currentDrag });
182                 csView = EZText(zone,
183                         Rect(0,0, bounds.width - 65, lineheight),
184                         nil, { |ez| object = ez.value; })
185                         .font_(font);
186                 csView.bounds.postln;
187         }
189         getState {
190                 // get all the state I need to know of the object I am watching
191                 ^(object: object)
192         }
194         checkUpdate {
195                 var newState = this.getState;
197                 // compare newState and prevState, update gui items as needed
198                 if (newState == prevState) { ^this };
200                 if (newState[\object] != prevState[\object]) {
201                         this.name_(this.getName);
202                         if (csView.textField.hasFocus.not) { csView.value_(object) };
203                 };
204                 prevState = newState;
205         }
207         makeScroller {
208                 // if numItems is exceeded, shift items along by some number with an EZScroller
209         }