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