uiObject.cpp (18072B)
1 #include "uiObject.h" 2 3 #include <utility> 4 5 #include "editor.h" 6 7 #include "buttonStyle.h" 8 #include "comboboxStyle.h" 9 #include "hyperlinkbuttonStyle.h" 10 #include "labelStyle.h" 11 #include "listBoxStyle.h" 12 #include "rotaryStyle.h" 13 #include "scrollbarStyle.h" 14 #include "textbuttonStyle.h" 15 #include "textEditorStyle.h" 16 #include "treeViewStyle.h" 17 18 #include <cassert> 19 20 #include "button.h" 21 #include "slider.h" 22 23 namespace genericUI 24 { 25 UiObject::UiObject(UiObject* _parent, const juce::var& _json, const bool _isTemplate/* = false*/) : m_isTemplate(_isTemplate), m_parent(_parent) 26 { 27 auto* obj = _json.getDynamicObject(); 28 29 parse(obj); 30 } 31 32 UiObject::~UiObject() 33 { 34 m_juceObjects.clear(); 35 m_style.reset(); 36 } 37 38 void UiObject::createJuceTree(Editor& _editor) 39 { 40 apply(_editor, _editor); 41 42 createChildObjects(_editor, _editor); 43 } 44 45 void UiObject::createChildObjects(Editor& _editor, juce::Component& _parent) const 46 { 47 for (auto& ch : m_children) 48 { 49 auto* obj = ch->createJuceObject(_editor); 50 51 if(!obj) 52 continue; 53 54 _parent.addAndMakeVisible(obj); 55 56 if(ch->m_condition) 57 ch->m_condition->refresh(); 58 59 ch->createChildObjects(_editor, *obj); 60 } 61 } 62 63 void UiObject::createTabGroups(Editor& _editor) 64 { 65 if(m_tabGroup.isValid()) 66 { 67 m_tabGroup.create(_editor); 68 _editor.registerTabGroup(&m_tabGroup); 69 } 70 71 for (const auto& ch : m_children) 72 { 73 ch->createTabGroups(_editor); 74 } 75 } 76 77 void UiObject::createControllerLinks(Editor& _editor) const 78 { 79 for (const auto& link : m_controllerLinks) 80 link->create(_editor); 81 82 for (const auto& ch : m_children) 83 { 84 ch->createControllerLinks(_editor); 85 } 86 } 87 88 void UiObject::registerTemplates(Editor& _editor) const 89 { 90 for(auto& s : m_templates) 91 _editor.registerTemplate(s); 92 93 for (auto& ch : m_children) 94 ch->registerTemplates(_editor); 95 } 96 97 void UiObject::apply(const Editor& _editor, juce::Component& _target) 98 { 99 const auto x = getPropertyInt("x"); 100 const auto y = getPropertyInt("y"); 101 const auto w = getPropertyInt("width"); 102 const auto h = getPropertyInt("height"); 103 104 if(w > 0 && h > 0) 105 { 106 _target.setTopLeftPosition(x, y); 107 _target.setSize(w, h); 108 } 109 else if (!m_isTemplate) 110 { 111 std::stringstream ss; 112 ss << "Size " << w << "x" << h << " for object named " << m_name << " is invalid, each side must be > 0"; 113 throw std::runtime_error(ss.str()); 114 } 115 116 createCondition(_editor, _target); 117 } 118 119 void UiObject::apply(Editor& _editor, juce::Slider& _target) 120 { 121 _target.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); 122 123 apply(_editor, static_cast<juce::Component&>(_target)); 124 125 auto* const s = new RotaryStyle(_editor); 126 127 createStyle(_editor, _target, s); 128 129 const auto sliderStyle = s->getStyle(); 130 131 switch (sliderStyle) 132 { 133 case RotaryStyle::Style::Rotary: 134 _target.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); 135 break; 136 case RotaryStyle::Style::LinearVertical: 137 _target.setSliderStyle(juce::Slider::LinearVertical); 138 break; 139 case RotaryStyle::Style::LinearHorizontal: 140 _target.setSliderStyle(juce::Slider::LinearHorizontal); 141 break; 142 } 143 144 bindParameter(_editor, _target); 145 } 146 147 void UiObject::apply(Editor& _editor, juce::ComboBox& _target) 148 { 149 apply(_editor, static_cast<juce::Component&>(_target)); 150 auto* s = new ComboboxStyle(_editor); 151 createStyle(_editor, _target, s); 152 bindParameter(_editor, _target); 153 s->apply(_target); 154 } 155 156 void UiObject::apply(Editor& _editor, juce::DrawableButton& _target) 157 { 158 apply(_editor, static_cast<juce::Component&>(_target)); 159 auto* s = new ButtonStyle(_editor); 160 createStyle(_editor, _target, s); 161 s->apply(_target); 162 bindParameter(_editor, _target); 163 } 164 165 void UiObject::apply(Editor& _editor, juce::Label& _target) 166 { 167 applyT<juce::Label, LabelStyle>(_editor, _target); 168 bindParameter(_editor, _target); 169 } 170 171 void UiObject::apply(Editor& _editor, juce::ScrollBar& _target) 172 { 173 applyT<juce::ScrollBar, ScrollBarStyle>(_editor, _target); 174 } 175 176 void UiObject::apply(Editor& _editor, juce::TextButton& _target) 177 { 178 applyT<juce::TextButton, TextButtonStyle>(_editor, _target); 179 } 180 181 void UiObject::apply(Editor& _editor, juce::HyperlinkButton& _target) 182 { 183 applyT<juce::HyperlinkButton, HyperlinkButtonStyle>(_editor, _target); 184 } 185 186 void UiObject::apply(Editor& _editor, juce::TreeView& _target) 187 { 188 applyT<juce::TreeView, TreeViewStyle>(_editor, _target); 189 } 190 191 void UiObject::apply(Editor& _editor, juce::ListBox& _target) 192 { 193 applyT<juce::ListBox, ListBoxStyle>(_editor, _target); 194 } 195 196 void UiObject::apply(Editor& _editor, juce::TextEditor& _target) 197 { 198 applyT<juce::TextEditor, TextEditorStyle>(_editor, _target); 199 } 200 201 template <typename TComponent, typename TStyle> void UiObject::applyT(Editor& _editor, TComponent& _target) 202 { 203 apply(_editor, static_cast<juce::Component&>(_target)); 204 205 TStyle* s = nullptr; 206 207 if (!m_style) 208 s = new TStyle(_editor); 209 210 createStyle(_editor, _target, s); 211 212 s = dynamic_cast<TStyle*>(m_style.get()); 213 assert(s); 214 s->apply(_target); 215 } 216 217 void UiObject::collectVariants(std::set<std::string>& _dst, const std::string& _property) const 218 { 219 const std::string res = getProperty(_property); 220 221 if (!res.empty()) 222 _dst.insert(res); 223 224 for (const auto& child : m_children) 225 child->collectVariants(_dst, _property); 226 227 for (const auto& child : m_templates) 228 child->collectVariants(_dst, _property); 229 } 230 231 juce::Component* UiObject::createJuceObject(Editor& _editor) 232 { 233 m_juceObjects.clear(); 234 235 if(hasComponent("rotary")) 236 { 237 createJuceObject<Slider>(_editor); 238 } 239 else if(hasComponent("image")) 240 { 241 // @Juce: Juce does not respect translations on drawables by using setPositionAndSize, even though a Drawable is a component?! Doesn't make sense to me 242 auto* c = createJuceObject<juce::Component>(_editor); 243 auto img = _editor.createImageDrawable(getProperty("texture")); 244 c->addAndMakeVisible(img.get()); 245 m_juceObjects.emplace_back(std::move(img)); 246 } 247 else if(hasComponent("combobox")) 248 { 249 createJuceObject<juce::ComboBox>(_editor); 250 } 251 else if(hasComponent("button")) 252 { 253 createJuceObject<Button<juce::DrawableButton>>(_editor, m_name, juce::DrawableButton::ImageRaw); 254 } 255 else if(hasComponent("hyperlinkbutton")) 256 { 257 createJuceObject<Button<juce::HyperlinkButton>>(_editor); 258 } 259 else if(hasComponent("textbutton")) 260 { 261 createJuceObject<Button<juce::TextButton>>(_editor); 262 } 263 else if(hasComponent("label")) 264 { 265 createJuceObject<juce::Label>(_editor); 266 } 267 else if(hasComponent("root") || hasComponent("component")) 268 { 269 createJuceObject<juce::Component>(_editor); 270 } 271 else 272 { 273 throw std::runtime_error("Failed to determine object type for object named " + m_name); 274 } 275 276 return m_juceObjects.empty() ? nullptr : m_juceObjects.front().get(); 277 } 278 279 int UiObject::getPropertyInt(const std::string& _key, int _default) const 280 { 281 const auto res = getProperty(_key); 282 if (res.empty()) 283 return _default; 284 return strtol(res.c_str(), nullptr, 10); 285 } 286 287 float UiObject::getPropertyFloat(const std::string& _key, float _default) const 288 { 289 const auto res = getProperty(_key); 290 if (res.empty()) 291 return _default; 292 return static_cast<float>(atof(res.c_str())); 293 } 294 295 std::string UiObject::getProperty(const std::string& _key, const std::string& _default) const 296 { 297 for (const auto& properties : m_components) 298 { 299 for(const auto& prop : properties.second) 300 { 301 if (prop.first == _key) 302 return prop.second; 303 } 304 } 305 return _default; 306 } 307 308 size_t UiObject::getConditionCountRecursive() const 309 { 310 size_t count = m_condition ? 1 : 0; 311 312 for (const auto & c : m_children) 313 count += c->getConditionCountRecursive(); 314 315 return count; 316 } 317 318 size_t UiObject::getControllerLinkCountRecursive() const 319 { 320 size_t count = m_controllerLinks.size(); 321 322 for (const auto & c : m_children) 323 count += c->getControllerLinkCountRecursive(); 324 325 return count; 326 } 327 328 void UiObject::setCurrentPart(Editor& _editor, const uint8_t _part) 329 { 330 if(m_condition) 331 m_condition->setCurrentPart(_editor, _part); 332 333 for (const auto& child : m_children) 334 child->setCurrentPart(_editor, _part); 335 } 336 337 void UiObject::updateKeyValueConditions(const std::string& _key, const std::string& _value) const 338 { 339 auto* cond = dynamic_cast<ConditionByKeyValue*>(m_condition.get()); 340 if(cond && cond->getKey() == _key) 341 cond->setValue(_value); 342 343 for (const auto& child : m_children) 344 child->updateKeyValueConditions(_key, _value); 345 } 346 347 void UiObject::createCondition(const Editor& _editor, juce::Component& _target) 348 { 349 if(!hasComponent("condition")) 350 return; 351 352 const auto conditionValues = getProperty("enableOnValues"); 353 354 size_t start = 0; 355 356 std::set<std::string> values; 357 358 for(size_t i=0; i<=conditionValues.size(); ++i) 359 { 360 const auto isEnd = i == conditionValues.size() || conditionValues[i] == ',' || conditionValues[i] == ';'; 361 362 if(!isEnd) 363 continue; 364 365 const auto valueString = conditionValues.substr(start, i - start); 366 367 values.insert(valueString); 368 369 start = i + 1; 370 } 371 372 if(values.empty()) 373 throw std::runtime_error("Condition does not specify any values"); 374 375 const auto paramName = getProperty("enableOnParameter"); 376 377 if(!paramName.empty()) 378 { 379 const auto paramIndex = _editor.getInterface().getParameterIndexByName(paramName); 380 381 if(paramIndex < 0) 382 throw std::runtime_error("Parameter named " + paramName + " not found"); 383 384 const auto v = _editor.getInterface().getParameterValue(paramIndex, 0); 385 386 if(!v) 387 throw std::runtime_error("Parameter named " + paramName + " not found"); 388 389 std::set<uint8_t> valuesInt; 390 391 for (const auto& valueString : values) 392 { 393 const int val = strtol(valueString.c_str(), nullptr, 10); 394 valuesInt.insert(static_cast<uint8_t>(val)); 395 } 396 397 m_condition.reset(new ConditionByParameterValues(_target, v, static_cast<uint32_t>(paramIndex), valuesInt)); 398 } 399 else 400 { 401 auto key = getProperty("enableOnKey"); 402 403 if(key.empty()) 404 throw std::runtime_error("Unknown condition type, neither 'enableOnParameter' nor 'enableOnKey' specified"); 405 406 m_condition.reset(new ConditionByKeyValue(_target, std::move(key), std::move(values))); 407 } 408 } 409 410 juce::DynamicObject& UiObject::applyStyle(juce::DynamicObject& _obj, const std::string& _styleName) 411 { 412 if (m_parent) 413 m_parent->applyStyle(_obj, _styleName); 414 415 const auto it = m_styles.find(_styleName); 416 if (it == m_styles.end()) 417 return _obj; 418 419 const auto& s = it->second; 420 const auto sourceObj = s.getDynamicObject(); 421 422 if (sourceObj) 423 copyPropertiesRecursive(_obj, *sourceObj); 424 425 return _obj; 426 } 427 428 bool UiObject::copyPropertiesRecursive(juce::DynamicObject& _target, const juce::DynamicObject& _source) 429 { 430 bool result = false; 431 432 for (const auto& sourceProp : _source.getProperties()) 433 { 434 const auto& key = sourceProp.name; 435 const auto& val = sourceProp.value; 436 437 if (!_target.hasProperty(key)) 438 { 439 _target.setProperty(key, val); 440 result = true; 441 } 442 else 443 { 444 auto& targetProperty = _target.getProperty(key); 445 446 if (targetProperty.isObject()) 447 { 448 auto targetObj = targetProperty.getDynamicObject(); 449 if (targetObj) 450 { 451 auto sourceObj = val.getDynamicObject(); 452 if (sourceObj) 453 copyPropertiesRecursive(*targetObj, *sourceObj); 454 } 455 } 456 } 457 } 458 return true; 459 } 460 461 bool UiObject::parse(juce::DynamicObject* _obj) 462 { 463 if (!_obj) 464 return false; 465 466 auto props = _obj->getProperties(); 467 468 auto styleName = props["style"].toString().toStdString(); 469 470 std::unique_ptr<juce::DynamicObject> newObj; 471 472 if (!styleName.empty()) 473 { 474 newObj = _obj->clone(); 475 props = applyStyle(*newObj, styleName).getProperties(); 476 } 477 478 for (int i = 0; i < props.size(); ++i) 479 { 480 const auto key = std::string(props.getName(i).toString().toUTF8()); 481 const auto& value = props.getValueAt(i); 482 483 if (key == "name") 484 { 485 m_name = value.toString().toStdString(); 486 } 487 else if(key == "children") 488 { 489 const auto children = value.getArray(); 490 491 if (children) 492 { 493 for (auto&& c : *children) 494 { 495 std::unique_ptr<UiObject> child; 496 child.reset(new UiObject(this, c)); 497 m_children.emplace_back(std::move(child)); 498 } 499 } 500 } 501 else if (key == "templates") 502 { 503 if (const auto children = value.getArray()) 504 { 505 for (const auto& c : *children) 506 m_templates.emplace_back(std::make_shared<UiObject>(this, c, true)); 507 } 508 } 509 else if (key == "styles") 510 { 511 auto obj = value.getDynamicObject(); 512 if (!obj) 513 throw std::runtime_error("styles must be an object"); 514 515 auto p = obj->getProperties(); 516 517 for (const auto& s : p) 518 { 519 const auto name = s.name.toString().toStdString(); 520 521 if (name.empty()) 522 throw std::runtime_error("style needs to have a name"); 523 if (m_styles.find(name) != m_styles.end()) 524 throw std::runtime_error("style with name " + name + " already exists"); 525 526 m_styles.insert({name, s.value}); 527 } 528 529 for (const auto& [name,s] : m_styles) 530 { 531 if (auto* styleObj = s.getDynamicObject()) 532 { 533 auto parentStyleName = styleObj->getProperty("style").toString().toStdString(); 534 535 if (!parentStyleName.empty()) 536 { 537 applyStyle(*styleObj, parentStyleName); 538 } 539 } 540 } 541 } 542 else if(key == "tabgroup") 543 { 544 auto buttons = value["buttons"].getArray(); 545 auto pages = value["pages"].getArray(); 546 auto name = value["name"].toString().toStdString(); 547 548 if(name.empty()) 549 throw std::runtime_error("tab group needs to have a name"); 550 if(buttons == nullptr) 551 throw std::runtime_error("tab group needs to define at least one button but 'buttons' array not found"); 552 if(pages == nullptr) 553 throw std::runtime_error("tab group needs to define at least one page but 'pages' array not found"); 554 555 std::vector<std::string> buttonVec; 556 std::vector<std::string> pagesVec; 557 558 for (const auto& button : *buttons) 559 { 560 const auto b = button.toString().toStdString(); 561 if(b.empty()) 562 throw std::runtime_error("tab group button name must not be empty"); 563 buttonVec.push_back(b); 564 } 565 566 for (const auto& page : *pages) 567 { 568 const auto p = page.toString().toStdString(); 569 if(p.empty()) 570 throw std::runtime_error("tab group page name must not be empty"); 571 pagesVec.push_back(p); 572 } 573 574 if(buttonVec.size() != pagesVec.size()) 575 throw std::runtime_error("tab group page count must match tap group button count"); 576 577 m_tabGroup = TabGroup(name, pagesVec, buttonVec); 578 } 579 else if(key == "controllerlinks") 580 { 581 auto* entries = value.getArray(); 582 583 if(entries && !entries->isEmpty()) 584 { 585 for(auto j=0; j<entries->size(); ++j) 586 { 587 const auto& e = (*entries)[j]; 588 const auto source = e["source"].toString().toStdString(); 589 const auto dest = e["dest"].toString().toStdString(); 590 const auto condition = e["condition"].toString().toStdString(); 591 592 if(source.empty()) 593 throw std::runtime_error("source for controller link needs to have a name"); 594 if(dest.empty()) 595 throw std::runtime_error("destination for controller link needs to have a name"); 596 if(condition.empty()) 597 throw std::runtime_error("condition for controller link needs to have a name"); 598 599 m_controllerLinks.emplace_back(new ControllerLink(source, dest, condition)); 600 } 601 } 602 } 603 else 604 { 605 auto* componentDesc = value.getDynamicObject(); 606 607 if(componentDesc) 608 { 609 const auto& componentProps = componentDesc->getProperties(); 610 611 std::map<std::string, std::string> properties; 612 613 for(int p=0; p<componentProps.size(); ++p) 614 { 615 properties.insert(std::make_pair(componentProps.getName(p).toString().toStdString(), componentProps.getValueAt(p).toString().toStdString())); 616 } 617 618 m_components.insert(std::make_pair(key, properties)); 619 } 620 } 621 } 622 623 return true; 624 } 625 626 void UiObject::readProperties(juce::Component& _target) 627 { 628 const auto it = m_components.find("componentProperties"); 629 if(it == m_components.end()) 630 return; 631 632 const auto props = it->second; 633 634 for (const auto& prop : props) 635 _target.getProperties().set(juce::Identifier(prop.first.c_str()), juce::var(prop.second.c_str())); 636 } 637 638 template <typename T, class... Args> T* UiObject::createJuceObject(Editor& _editor, Args... _args) 639 { 640 T* comp = nullptr; 641 642 comp = _editor.createJuceComponent(comp, *this, _args...); 643 644 if(!comp) 645 comp = new T(_args...); 646 647 return createJuceObject(_editor, comp); 648 } 649 650 template <typename T> T* UiObject::createJuceObject(Editor& _editor, T* _object) 651 { 652 std::unique_ptr<T> c(_object); 653 apply(_editor, *c); 654 auto* comp = c.get(); 655 m_juceObjects.emplace_back(std::move(c)); 656 657 if(!m_name.empty()) 658 _editor.registerComponent(m_name, comp); 659 660 auto* tooltipClient = dynamic_cast<juce::SettableTooltipClient*>(_object); 661 662 if(tooltipClient) 663 { 664 const auto tooltip = getProperty("tooltip"); 665 666 if(!tooltip.empty()) 667 tooltipClient->setTooltip(tooltip); 668 } 669 670 readProperties(*_object); 671 672 return comp; 673 } 674 675 template<typename T> 676 void UiObject::bindParameter(const Editor& _editor, T& _target) const 677 { 678 const std::string param = getProperty("parameter"); 679 680 if(param.empty()) 681 return; 682 683 const auto index = _editor.getInterface().getParameterIndexByName(param); 684 685 if(index < 0) 686 throw std::runtime_error("Parameter named " + param + " not found"); 687 688 _target.getProperties().set("parameter", index); 689 _target.getProperties().set("parametername", juce::String(param)); 690 691 if constexpr(std::is_base_of_v<juce::Button, T>) 692 { 693 auto value = getPropertyInt("value", -1); 694 if(value != -1) 695 _target.getProperties().set("parametervalue", value); 696 auto valueOff = getPropertyInt("valueOff", -1); 697 if(valueOff != -1) 698 _target.getProperties().set("parametervalueoff", valueOff); 699 } 700 701 _editor.getInterface().bindParameter(_target, index); 702 } 703 704 template <typename Target, typename Style> void UiObject::createStyle(Editor& _editor, Target& _target, Style* _style) 705 { 706 if(_style) 707 m_style.reset(_style); 708 m_style->apply(_editor, *this); 709 _target.setLookAndFeel(m_style.get()); 710 } 711 }