|
32 | 32 | #include <QTabWidget>
|
33 | 33 | #include <QTextEdit>
|
34 | 34 | #include <QToolTip>
|
| 35 | +#include <QVBoxLayout> |
35 | 36 |
|
36 | 37 | // QT's extension foreach defines a foreach macro which interferes
|
37 | 38 | // with an OSL internal foreach method. So we will undefine it here
|
@@ -351,6 +352,241 @@ class OSLToyRenderView final : public QLabel {
|
351 | 352 | #endif
|
352 | 353 | };
|
353 | 354 |
|
| 355 | + |
| 356 | + |
| 357 | +class OSLToySearchPathLine final : public QLineEdit { |
| 358 | + // Q_OBJECT |
| 359 | +public: |
| 360 | + explicit OSLToySearchPathLine(OSLToySearchPathEditor* editor, int index); |
| 361 | + |
| 362 | + bool previouslyHadContent() const { return m_previouslyHadContent; } |
| 363 | + |
| 364 | + void setPreviouslyHadContent(bool value) { m_previouslyHadContent = value; } |
| 365 | + |
| 366 | + int getIndex() const { return m_index; } |
| 367 | + |
| 368 | + QSize sizeHint() const override; |
| 369 | + |
| 370 | +private: |
| 371 | + static QColor getColor(int index) |
| 372 | + { |
| 373 | + if (index % 2) |
| 374 | + return QColor(0xFFE0F0FF); // light blue |
| 375 | + else |
| 376 | + return Qt::white; |
| 377 | + } |
| 378 | + |
| 379 | + bool m_previouslyHadContent = false; |
| 380 | + int m_index; |
| 381 | + OSLToySearchPathEditor* m_editor = nullptr; |
| 382 | +}; |
| 383 | + |
| 384 | + |
| 385 | + |
| 386 | +// More generically, this is a popup window with a list (that grows as needed) of editable text items. |
| 387 | +class OSLToySearchPathEditor final : public QWidget { |
| 388 | + using UpdatePathListAction |
| 389 | + = std::function<void(const std::vector<std::string>&)>; |
| 390 | + |
| 391 | +public: |
| 392 | + OSLToySearchPathEditor(QWidget* parent, |
| 393 | + UpdatePathListAction updatePathsAction) |
| 394 | + : QWidget(parent, static_cast<Qt::WindowFlags>( |
| 395 | + Qt::Tool | Qt::WindowStaysOnTopHint)) |
| 396 | + , m_lines() |
| 397 | + , m_updateAction(updatePathsAction) |
| 398 | + { |
| 399 | + window()->setWindowTitle(tr("#include Search Path List")); |
| 400 | + |
| 401 | + int thisWidth = parent->width(); // / 3; |
| 402 | + int thisHeight = parent->height(); // / 4; |
| 403 | + resize(thisWidth, thisHeight); |
| 404 | + setFixedSize(size()); |
| 405 | + |
| 406 | + class MyScrollArea : public QScrollArea { |
| 407 | + QWidget* m_parent; |
| 408 | + |
| 409 | + public: |
| 410 | + explicit MyScrollArea(QWidget* parent) |
| 411 | + : QScrollArea(parent), m_parent(parent) |
| 412 | + { |
| 413 | + } |
| 414 | + |
| 415 | + QSize sizeHint() const override { return m_parent->size(); } |
| 416 | + }; |
| 417 | + |
| 418 | + class MyFrame : public QFrame { |
| 419 | + QWidget* m_parent; |
| 420 | + |
| 421 | + public: |
| 422 | + explicit MyFrame(QWidget* parent) : QFrame(parent), m_parent(parent) |
| 423 | + { |
| 424 | + } |
| 425 | + |
| 426 | + QSize sizeHint() const override { return m_parent->size(); } |
| 427 | + }; |
| 428 | + |
| 429 | + auto scroll_area = new MyScrollArea(this); |
| 430 | + scroll_area->setWidgetResizable(true); |
| 431 | + auto frame = new MyFrame(scroll_area); |
| 432 | + auto layout = new QVBoxLayout(); |
| 433 | + layout->setSpacing(0); |
| 434 | + frame->setLayout(layout); |
| 435 | + frame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); |
| 436 | + scroll_area->setWidget(frame); |
| 437 | + scroll_area->show(); |
| 438 | + m_layout = layout; |
| 439 | + m_scrollArea = scroll_area; |
| 440 | + } |
| 441 | + |
| 442 | + void set_path_list(const std::vector<std::string>& paths) |
| 443 | + { |
| 444 | + while (!m_lines.empty()) |
| 445 | + pop_line(); |
| 446 | + m_maxIndexWithContent |
| 447 | + = (int)paths.size() |
| 448 | + - 1; // ok that this is -1 if the paths are empty |
| 449 | + auto initialLineCount = required_lines(); |
| 450 | + m_lines.reserve(initialLineCount); |
| 451 | + while (m_lines.size() < initialLineCount) |
| 452 | + push_line(); |
| 453 | + for (size_t i = 0; i < paths.size(); ++i) |
| 454 | + m_lines[i]->setText(QString::fromStdString(paths[i])); |
| 455 | + update_path_list(); |
| 456 | + } |
| 457 | + |
| 458 | + void observe_changed_text() |
| 459 | + { |
| 460 | + // Only listen to signals from OSLToySearchPathLine objects |
| 461 | + if (auto changedLine = dynamic_cast<OSLToySearchPathLine*>(sender())) { |
| 462 | + bool isNowEmpty = changedLine->text().isEmpty(); |
| 463 | + if (changedLine->previouslyHadContent() && isNowEmpty) { |
| 464 | + if (changedLine->getIndex() == m_maxIndexWithContent) { |
| 465 | + // Find the next max index with content, or -1 if none. |
| 466 | + do { |
| 467 | + --m_maxIndexWithContent; |
| 468 | + } while (m_maxIndexWithContent >= 0 |
| 469 | + && !m_lines[m_maxIndexWithContent] |
| 470 | + ->previouslyHadContent()); |
| 471 | + |
| 472 | + shrink_as_needed(); |
| 473 | + } |
| 474 | + } else if (!changedLine->previouslyHadContent() && !isNowEmpty) { |
| 475 | + if (changedLine->getIndex() > m_maxIndexWithContent) { |
| 476 | + m_maxIndexWithContent = changedLine->getIndex(); |
| 477 | + grow_as_needed(); |
| 478 | + } |
| 479 | + } |
| 480 | + |
| 481 | + changedLine->setPreviouslyHadContent(!isNowEmpty); |
| 482 | + } |
| 483 | + } |
| 484 | + |
| 485 | +protected: |
| 486 | + void closeEvent(QCloseEvent* ev) override |
| 487 | + { |
| 488 | + // On close, collate the list of search paths, and if there has been any change, update. |
| 489 | + bool has_updated = false; |
| 490 | + for (auto line : m_lines) { |
| 491 | + if (line->isModified()) { |
| 492 | + if (!has_updated) { |
| 493 | + update_path_list(); |
| 494 | + has_updated = true; |
| 495 | + } |
| 496 | + line->setModified(false); |
| 497 | + } |
| 498 | + } |
| 499 | + |
| 500 | + ev->accept(); |
| 501 | + } |
| 502 | + |
| 503 | +private: |
| 504 | + void push_line() |
| 505 | + { |
| 506 | + auto l = new OSLToySearchPathLine(this, (int)m_lines.size()); |
| 507 | + m_layout->addWidget(l); |
| 508 | + m_lines.push_back(l); |
| 509 | + } |
| 510 | + |
| 511 | + void pop_line() |
| 512 | + { |
| 513 | + auto line = m_lines.back(); |
| 514 | + m_lines.pop_back(); |
| 515 | + m_layout->removeWidget(line); |
| 516 | + } |
| 517 | + |
| 518 | + void update_path_list() |
| 519 | + { |
| 520 | + std::vector<std::string> path_list; |
| 521 | + for (auto line : m_lines) { |
| 522 | + auto&& text = line->text(); |
| 523 | + if (!text.isEmpty()) |
| 524 | + path_list.push_back(text.toStdString()); |
| 525 | + } |
| 526 | + m_updateAction(path_list); |
| 527 | + } |
| 528 | + |
| 529 | + size_t required_lines() const |
| 530 | + { |
| 531 | + return static_cast<size_t>( |
| 532 | + (std::max)(m_minLineCount, |
| 533 | + m_maxIndexWithContent + m_guaranteedEmptyLineCount + 1)); |
| 534 | + } |
| 535 | + |
| 536 | + void grow_as_needed() |
| 537 | + { |
| 538 | + auto newReqLines = required_lines(); |
| 539 | + while (m_lines.size() < newReqLines) { |
| 540 | + push_line(); |
| 541 | + } |
| 542 | + } |
| 543 | + |
| 544 | + void shrink_as_needed() |
| 545 | + { |
| 546 | + auto newReqLines = required_lines(); |
| 547 | + while (m_lines.size() > newReqLines) { |
| 548 | + pop_line(); |
| 549 | + } |
| 550 | + } |
| 551 | + |
| 552 | + int m_minLineCount = 12; |
| 553 | + int m_guaranteedEmptyLineCount = 5; |
| 554 | + int m_maxIndexWithContent = -1; |
| 555 | + std::vector<OSLToySearchPathLine*> m_lines; |
| 556 | + QLayout* m_layout = nullptr; |
| 557 | + QScrollArea* m_scrollArea = nullptr; |
| 558 | + UpdatePathListAction m_updateAction; |
| 559 | +}; |
| 560 | + |
| 561 | + |
| 562 | + |
| 563 | +OSLToySearchPathLine::OSLToySearchPathLine(OSLToySearchPathEditor* editor, |
| 564 | + int index) |
| 565 | + : QLineEdit(), m_index(index), m_editor(editor) |
| 566 | +{ |
| 567 | + setFrame(true); |
| 568 | + |
| 569 | + auto p = this->palette(); |
| 570 | + p.setColor(QPalette::Base, getColor(index)); |
| 571 | + setPalette(p); |
| 572 | + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); |
| 573 | + QObject::connect(this, &OSLToySearchPathLine::editingFinished, editor, |
| 574 | + &OSLToySearchPathEditor::observe_changed_text); |
| 575 | + |
| 576 | + // Maybe add a QCompleter that completes known paths |
| 577 | + show(); |
| 578 | +} |
| 579 | + |
| 580 | + |
| 581 | + |
| 582 | +QSize |
| 583 | +OSLToySearchPathLine::sizeHint() const |
| 584 | +{ |
| 585 | + return QSize(m_editor->width() - 4, 10); |
| 586 | +} |
| 587 | + |
| 588 | + |
| 589 | + |
354 | 590 | void
|
355 | 591 | #if OSL_QT_MAJOR < 6
|
356 | 592 | Magnifier::enterEvent(QEvent* event)
|
@@ -402,9 +638,6 @@ OSLToyMainWindow::OSLToyMainWindow(OSLToyRenderer* rend, int xr, int yr)
|
402 | 638 |
|
403 | 639 | setWindowTitle(tr("OSL Toy"));
|
404 | 640 |
|
405 |
| - // Set size of the window |
406 |
| - // setFixedSize(100, 50); |
407 |
| - |
408 | 641 | createActions();
|
409 | 642 | createMenus();
|
410 | 643 | createStatusBar();
|
@@ -458,6 +691,11 @@ OSLToyMainWindow::OSLToyMainWindow(OSLToyRenderer* rend, int xr, int yr)
|
458 | 691 | &OSLToyMainWindow::restart_time);
|
459 | 692 | control_area_layout->addWidget(restartButton);
|
460 | 693 |
|
| 694 | + searchPathEditor |
| 695 | + = new OSLToySearchPathEditor(this, [this](auto&& paths) mutable { |
| 696 | + update_include_search_paths(paths); |
| 697 | + }); |
| 698 | + |
461 | 699 | auto editorarea = new QWidget;
|
462 | 700 | QFontMetrics fontmetrics(CodeEditor::fixedFont());
|
463 | 701 | #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
|
@@ -517,6 +755,9 @@ OSLToyMainWindow::createActions()
|
517 | 755 | &OSLToyMainWindow::recompile_shaders);
|
518 | 756 | add_action("Enter Full Screen", "", "",
|
519 | 757 | &OSLToyMainWindow::action_fullscreen);
|
| 758 | + add_action("search-path-popup", "Edit #include search paths...", |
| 759 | + "Shift-Ctrl+P", |
| 760 | + &OSLToyMainWindow::action_open_search_path_popup); |
520 | 761 | }
|
521 | 762 |
|
522 | 763 |
|
@@ -553,6 +794,7 @@ OSLToyMainWindow::createMenus()
|
553 | 794 |
|
554 | 795 | toolsMenu = new QMenu(tr("&Tools"), this);
|
555 | 796 | toolsMenu->addAction(actions["Recompile shaders"]);
|
| 797 | + toolsMenu->addAction(actions["search-path-popup"]); |
556 | 798 | menuBar()->addMenu(toolsMenu);
|
557 | 799 |
|
558 | 800 | helpMenu = new QMenu(tr("&Help"), this);
|
@@ -711,6 +953,23 @@ OSLToyMainWindow::open_file(const std::string& filename)
|
711 | 953 | }
|
712 | 954 |
|
713 | 955 |
|
| 956 | +void |
| 957 | +OSLToyMainWindow::set_include_search_paths(const std::vector<std::string>& paths) |
| 958 | +{ |
| 959 | + searchPathEditor->set_path_list(paths); |
| 960 | +} |
| 961 | + |
| 962 | +void |
| 963 | +OSLToyMainWindow::update_include_search_paths( |
| 964 | + const std::vector<std::string>& paths) |
| 965 | +{ |
| 966 | + m_include_search_paths = paths; |
| 967 | + m_should_regenerate_compile_options = true; |
| 968 | + |
| 969 | + // Open question: Do we want to force a recompile whenever the list is updated? |
| 970 | + // For now, I'm defaulting to no, but this is just a guess. |
| 971 | +} |
| 972 | + |
714 | 973 |
|
715 | 974 | void
|
716 | 975 | OSLToyMainWindow::action_saveas()
|
@@ -758,6 +1017,17 @@ OSLToyMainWindow::action_save()
|
758 | 1017 |
|
759 | 1018 |
|
760 | 1019 |
|
| 1020 | +void |
| 1021 | +OSLToyMainWindow::action_open_search_path_popup() |
| 1022 | +{ |
| 1023 | + auto centeredXPos = x() + (width() - searchPathEditor->width()) / 2; |
| 1024 | + auto centeredYPos = y() + (height() - searchPathEditor->height()) / 2; |
| 1025 | + searchPathEditor->move(centeredXPos, centeredYPos); |
| 1026 | + searchPathEditor->show(); |
| 1027 | +} |
| 1028 | + |
| 1029 | + |
| 1030 | + |
761 | 1031 | // Separate thread pool just for the async render kickoff triggers, but use
|
762 | 1032 | // the default pool for the workers.
|
763 | 1033 | static OIIO::thread_pool trigger_pool;
|
@@ -836,6 +1106,24 @@ class MyOSLCErrorHandler final : public OIIO::ErrorHandler {
|
836 | 1106 | };
|
837 | 1107 |
|
838 | 1108 |
|
| 1109 | +void |
| 1110 | +OSLToyMainWindow::regenerate_compile_options() |
| 1111 | +{ |
| 1112 | + // Right now, the only option we consider is include search path (-I) |
| 1113 | + |
| 1114 | + // Annoyingly, oslcomp only supports -I flags without any seperator between |
| 1115 | + // the -I and the path itself, but OIIO::ArgParse does not support parsing |
| 1116 | + // arguments in this manner. Oy vey. |
| 1117 | + |
| 1118 | + m_compile_options.clear(); |
| 1119 | + |
| 1120 | + for (auto&& path : m_include_search_paths) |
| 1121 | + m_compile_options.push_back(std::string("-I").append(path)); |
| 1122 | + |
| 1123 | + |
| 1124 | + m_should_regenerate_compile_options = false; |
| 1125 | +} |
| 1126 | + |
839 | 1127 |
|
840 | 1128 | void
|
841 | 1129 | OSLToyMainWindow::recompile_shaders()
|
@@ -863,11 +1151,19 @@ OSLToyMainWindow::recompile_shaders()
|
863 | 1151 | MyOSLCErrorHandler errhandler(this);
|
864 | 1152 | OSLCompiler oslcomp(&errhandler);
|
865 | 1153 | std::string osooutput;
|
866 |
| - std::vector<std::string> options; |
867 |
| - ok = oslcomp.compile_buffer(source, osooutput, options, "", |
868 |
| - briefname); |
869 |
| - set_error_message(tab, |
870 |
| - OIIO::Strutil::join(errhandler.errors, "\n")); |
| 1154 | + |
| 1155 | + if (m_should_regenerate_compile_options) |
| 1156 | + regenerate_compile_options(); |
| 1157 | + |
| 1158 | + ok = oslcomp.compile_buffer(source, osooutput, m_compile_options, |
| 1159 | + "", briefname); |
| 1160 | + |
| 1161 | + auto error_message = OIIO::Strutil::fmt::format( |
| 1162 | + "{}\n\nCompiled {} with options: {}", |
| 1163 | + OIIO::Strutil::join(errhandler.errors, "\n"), briefname, |
| 1164 | + OIIO::Strutil::join(m_compile_options, " ")); |
| 1165 | + set_error_message(tab, error_message); |
| 1166 | + |
871 | 1167 | if (ok) {
|
872 | 1168 | // std::cout << osooutput << "\n";
|
873 | 1169 | ok = shadingsys()->LoadMemoryCompiledShader(briefname,
|
|
0 commit comments