1 /*
2 ** Surge Synthesizer is Free and Open Source Software
3 **
4 ** Surge is made available under the Gnu General Public License, v3.0
5 ** https://www.gnu.org/licenses/gpl-3.0.en.html
6 **
7 ** Copyright 2004-2020 by various individuals as described by the Git transaction log
8 **
9 ** All source at: https://github.com/surge-synthesizer/surge.git
10 **
11 ** Surge was a commercial product from 2004-2018, with Copyright and ownership
12 ** in that period held by Claes Johanson at Vember Audio. Claes made Surge
13 ** open source in September 2018.
14 */
15 
16 #pragma once
17 #include <string>
18 #include <iostream>
19 #include <unordered_map>
20 #include <unordered_set>
21 #include <memory>
22 #include "SurgeParamConfig.h"
23 #include "SkinColors.h"
24 
25 /*
26  * # SKIN MODEL / SKIN / PARAMETER / SURGEGUIEDITOR COLLABORATION
27  *
28  * This is the developer documentation on the core architecture of the skinning and
29  * UI engine in surge.
30  *
31  * The Surge UI comprises two things
32  * - Items bound to a parameter. Pitch slider, say.
33  * - Control items which do something. Oscillator Selector, say
34  *
35  * Each of these elements has some visual control with a callback
36  * handler to do its action; and each is bound to a well named
37  * control concept or parameter.
38  *
39  * Moreover, we want Surge UI to have a reasonable default compiled in,
40  * and we want that default to be completely over-rideable in our VSTGUI
41  * implementation.
42  *
43  * And finally, we don't want the core of surge to refernce VSTGUI.
44  *
45  * So that gets us the following set of collaborations
46  *
47  * Parameter: A parameter exposed as a DAW parameter with a name, value, type.
48  *            This is a logical concept consumed by the DSP algos and so on
49  * Connector: A connector is the expression of a default layout and control
50  *            type. It has a UID, size, type, and other properties. See
51  *            below for why this is not in src/common/gui. Most importantly
52  *            the parameter is either declared as a global with a NonParameter
53  *            connector type - which means it is an unbound control - or
54  *            it is declared as a global and consumed in the constructor of Parameter
55  * Skin: In src/common/gui. Skin maps a union of the Connectors and an XML document
56  *       to give a runtime representation of the UI. It contains functions like
57  *       "give me a renderable component by id". If the skin document is empty
58  *       there is no difference content wise between a Surge::UI::Skin::Control
59  *        and a Surge::Skin::Connector; but the Connector is just the starting
60  *        point for XML parse so skins can override it
61  * SurgeGUIEditor: As well as callbacks, SGE is now basically a loop over the skin
62  *                 data structure connecting things.
63  *
64  * SO why is this in src/common and not src/common/gui?
65  *
66  * Well the reason is that surge lays out its UI by parameter. Basically it's
67  * a renderer where parameters attach to UI components. That happens at parameter
68  * creation time in SurgePatch (now) and that's a logical place to give something a
69  * UI 'name'.
70  *
71  * This acts as the symbols for the UI names and those symbols carry reasonable
72  * defaults for class and position which the skin engine can override.
73  *
74  * So our two choices were
75  * 1. Have a collection of symbols without an UI information here and augment it
76  *    separately with positions in src/common/gui or
77  * 2. Have the data (but not the renderer) for the layout information in src/common
78  *
79  * I chose #2 mostly because #1 is especially tedious since a collection of parameters
80  * share a skin identity. For instance, all of the oscilator pitch sliders (of which there
81  * are 6 as of 1.8.0) are a single point of skin identity. Thus attaching them to skin
82  * data models in SurgePatch makes sense.
83  *
84  * And I took comfort that this data is completely free of any rendering code. Nothing
85  * here links or requires vstgui etc... This is pure data about naming (via skin ids
86  * and object namespaces) and defaults.
87  */
88 
89 namespace Surge
90 {
91 namespace Skin
92 {
93 
94 struct Component
95 {
96     /*
97      * We could have done something much clevere with templates and per-class enums
98      * but decided not to. Instead here is an enum which is the union of all possible
99      * properites that an item can have.
100      */
101     enum Properties
102     {
103         X = 1001,
104         Y,
105         W,
106         H,
107 
108         BACKGROUND,
109         HOVER_IMAGE,
110         HOVER_ON_IMAGE,
111         IMAGE, // Note for most components "image" binds to "background" but some have it seprate
112 
113         ROWS,
114         COLUMNS,
115         FRAMES,
116         FRAME_OFFSET,
117         DRAGGABLE_HSWITCH,
118 
119         NUMBERFIELD_CONTROLMODE,
120 
121         BACKGROUND_COLOR,
122         FRAME_COLOR,
123 
124         SLIDER_TRAY,
125         HANDLE_IMAGE,
126         HANDLE_HOVER_IMAGE,
127         HANDLE_TEMPOSYNC_IMAGE,
128         HANDLE_TEMPOSYNC_HOVER_IMAGE,
129         HIDE_SLIDER_LABEL,
130 
131         CONTROL_TEXT,
132         FONT_SIZE,
133         FONT_STYLE,
134         TEXT,
135         TEXT_ALIGN,
136         TEXT_ALL_CAPS,
137         TEXT_COLOR,
138         TEXT_HOVER_COLOR,
139         TEXT_HOFFSET,
140         TEXT_VOFFSET,
141 
142         GLYPH_PLACEMENT,
143         GLYPH_W,
144         GLYPH_H,
145         GLPYH_ACTIVE,
146         GLYPH_IMAGE,
147         GLYPH_HOVER_IMAGE
148     };
149 
150     Component() noexcept;
151     explicit Component(const std::string &internalClassname) noexcept;
152     ~Component();
153 
154     struct Payload
155     {
156         unsigned int id = -1;
157         std::unordered_map<Properties, std::vector<std::string>> propertyNamesMap;
158         std::unordered_map<Properties, std::string> propertyDocString;
159         std::unordered_set<Properties> hasPropertySet;
160         std::string internalClassname;
161     };
162     std::shared_ptr<Payload> payload;
163 
164     /*
165      * To allow aliasing and backwards compatability, a property binds to the skin engine
166      * with multiple names for a given class.
167      */
168     Component &withProperty(Properties p, const std::initializer_list<std::string> &names,
169                             const std::string &s = "add doc")
170     {
171         payload->propertyNamesMap[p] = names;
172         payload->propertyDocString[p] = s;
173         payload->hasPropertySet.insert(p);
174         return *this;
175     }
176 
hasPropertyComponent177     bool hasProperty(Properties p)
178     {
179         return payload->hasPropertySet.find(p) != payload->hasPropertySet.end();
180     }
181 
182     bool operator==(const Component &that) const { return payload->id == that.payload->id; }
183     bool operator!=(const Component &that) const { return payload->id != that.payload->id; }
184 
185     static std::vector<int> allComponentIds();
186     static Component componentById(int);
187     static std::string propertyEnumToString(Properties p);
188 };
189 
190 namespace Components
191 {
192 extern Component None, Slider, HSwitch2, Switch, FilterSelector, LFODisplay, OscMenu, FxMenu,
193     NumberField, VuMeter, Custom, Group, Label;
194 }
195 
196 struct Connector
197 {
198     /*
199      * Some UI elements do not bind to a parameter. These special functions
200      * have to identify themselves here so they can be appropriately bound
201      * to actions. These are functional connections not widget types.
202      */
203     enum NonParameterConnection
204     {
205         PARAMETER_CONNECTED = 0,
206         SURGE_MENU,
207         OSCILLATOR_DISPLAY,
208         OSCILLATOR_SELECT,
209         JOG_PATCHCATEGORY,
210         JOG_PATCH,
211         JOG_FX,
212 
213         STORE_PATCH,
214         STORE_PATCH_DIALOG,
215 
216         STATUS_MPE,
217         STATUS_TUNE,
218         STATUS_ZOOM,
219 
220         LFO_LABEL,
221         FXPRESET_LABEL,
222 
223         PATCH_BROWSER,
224         FX_SELECTOR,
225 
226         MAIN_VU_METER,
227 
228         MSEG_EDITOR_WINDOW,
229         MSEG_EDITOR_OPEN,
230 
231         LFO_MENU,
232 
233         N_NONCONNECTED
234     };
235 
236     Connector() noexcept;
237     ~Connector() = default;
238 
239     Connector(const std::string &id, float x, float y) noexcept;
240     Connector(const std::string &id, float x, float y, const Component &c) noexcept;
241     Connector(const std::string &id, float x, float y, float w, float h,
242               const Component &c) noexcept;
243     Connector(const std::string &id, float x, float y, float w, float h, const Component &c,
244               NonParameterConnection n) noexcept;
245     Connector(const std::string &id, float x, float y, NonParameterConnection n) noexcept;
246 
247     static Connector connectorByID(const std::string &id);
248     static Connector connectorByNonParameterConnection(NonParameterConnection n);
249     static std::vector<Connector> connectorsByComponentType(const Component &c);
250 
251     static std::vector<std::string> allConnectorIDs();
252 
withControlStyleConnector253     Connector &withControlStyle(unsigned int flags) noexcept
254     {
255         payload->controlStyleFlags |= flags;
256         return *this;
257     }
258 
asVerticalConnector259     Connector &asVertical() noexcept { return withControlStyle(Surge::ParamConfig::kVertical); }
asHorizontalConnector260     Connector &asHorizontal() noexcept { return withControlStyle(Surge::ParamConfig::kHorizontal); }
asWhiteConnector261     Connector &asWhite() noexcept { return withControlStyle(kWhite); }
262 
263     Connector &asMixerSolo() noexcept;
264     Connector &asMixerMute() noexcept;
265     Connector &asMixerRoute() noexcept;
266 
267     Connector &asJogPlusMinus() noexcept;
268 
withPropertyConnector269     Connector &withProperty(Component::Properties p, std::string v)
270     {
271         payload->properties[p] = v;
272         return *this;
273     }
274 
withPropertyConnector275     Connector &withProperty(Component::Properties p, int v)
276     {
277         return withProperty(p, std::to_string(v));
278     }
279 
withHSwitch2PropertiesConnector280     Connector &withHSwitch2Properties(int bgid, int frames, int rows, int cols)
281     {
282         return withProperty(Component::BACKGROUND, bgid)
283             .withProperty(Component::FRAMES, frames)
284             .withProperty(Component::ROWS, rows)
285             .withProperty(Component::COLUMNS, cols);
286     }
287 
withBackgroundConnector288     Connector &withBackground(int v) { return withProperty(Component::BACKGROUND, v); }
289 
inParentConnector290     Connector &inParent(std::string g)
291     {
292         payload->parentId = g;
293         return *this;
294     }
295 
getPropertyConnector296     std::string getProperty(Component::Properties p)
297     {
298         if (payload->properties.find(p) != payload->properties.end())
299             return payload->properties[p];
300         return "";
301     }
302 
303     struct Payload
304     {
305         std::string id{"unknown"};
306         float posx = -1, posy = -1;
307         float w = -1, h = -1;
308         int controlStyleFlags = 0;
309         Component defaultComponent = Surge::Skin::Components::None;
310         NonParameterConnection nonParamConnection = PARAMETER_CONNECTED;
311         std::string parentId = "";
312         std::unordered_map<Component::Properties, std::string>
313             properties; // since we are base for XML where it's all strings
314     };
315     std::shared_ptr<Payload> payload;
ConnectorConnector316     Connector(std::shared_ptr<Payload> p) : payload(p) {}
317 };
318 
319 namespace AEG
320 {
321 extern Surge::Skin::Connector attack, attack_shape, decay, decay_shape, mode, release,
322     release_shape, sustain;
323 }
324 namespace FEG
325 {
326 extern Surge::Skin::Connector attack, attack_shape, decay, decay_shape, mode, release,
327     release_shape, sustain;
328 }
329 namespace FX
330 {
331 extern Surge::Skin::Connector fx_jog, fxPresetLabel, fx_selector, fx_param_panel;
332 extern Surge::Skin::Connector fx_type, param_1, param_10, param_11, param_12, param_2, param_3,
333     param_4, param_5, param_6, param_7, param_8, param_9;
334 } // namespace FX
335 namespace Filter
336 {
337 extern Surge::Skin::Connector balance, config, cutoff_1, cutoff_2, envmod_1, envmod_2,
338     f2_link_resonance, f2_offset_mode, feedback, highpass, keytrack_1, keytrack_2, resonance_1,
339     resonance_2, subtype_1, subtype_2, type_1, type_2, waveshaper_drive, waveshaper_type;
340 }
341 namespace Global
342 {
343 extern Surge::Skin::Connector active_scene, character, fx1_return, fx2_return, fx_bypass,
344     fx_disable, master_volume, scene_mode;
345 }
346 namespace LFO
347 {
348 extern Surge::Skin::Connector amplitude, attack, decay, deform, delay, hold, phase, rate, release,
349     shape, sustain, trigger_mode, unipolar;
350 extern Surge::Skin::Connector lfo_presets, mseg_editor, lfo_title_label;
351 } // namespace LFO
352 namespace Mixer
353 {
354 extern Surge::Skin::Connector level_noise, level_o1, level_o2, level_o3, level_prefiltergain,
355     level_ring12, level_ring23, mute_noise, mute_o1, mute_o2, mute_o3, mute_ring12, mute_ring23,
356     route_noise, route_o1, route_o2, route_o3, route_ring12, route_ring23, solo_noise, solo_o1,
357     solo_o2, solo_o3, solo_ring12, solo_ring23;
358 }
359 namespace Osc
360 {
361 extern Surge::Skin::Connector osc_display;
362 extern Surge::Skin::Connector osc_select;
363 
364 extern Surge::Skin::Connector keytrack, octave, osc_type, param_1, param_2, param_3, param_4,
365     param_5, param_6, param_7, pitch, retrigger;
366 } // namespace Osc
367 namespace Scene
368 {
369 extern Surge::Skin::Connector polylimit, splitpoint, drift, fmdepth, fmrouting, gain, keytrack_root,
370     noise_color, octave, pan, pbrange_dn, pbrange_up, pitch, playmode, portatime, send_fx_1,
371     send_fx_2, vel_sensitivity, volume, width;
372 }
373 
374 namespace ModSources
375 {
376 // Still have design work to do here
377 }
378 
379 namespace OtherControls
380 {
381 extern Surge::Skin::Connector surge_menu;
382 // extern Surge::Skin::Connector fxSelect;
383 extern Surge::Skin::Connector patch_category_jog, patch_jog, storePatch;
384 
385 extern Surge::Skin::Connector status_mpe, status_zoom, status_tune;
386 
387 extern Surge::Skin::Connector vu_meter;
388 
389 // These active labels are actually controls
390 
391 extern Surge::Skin::Connector patch_browser;
392 
393 extern Surge::Skin::Connector mseg_editor;
394 
395 extern Surge::Skin::Connector store_patch_dialog;
396 
397 // In surge 1.8, the modulation panel is moveable en-masse but individual modulators
398 // are not relocatable. This item gives you the location of the modulators
399 extern Surge::Skin::Connector modulation_panel;
400 }; // namespace OtherControls
401 
402 } // namespace Skin
403 
404 } // namespace Surge
405