diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 65cdb6d9..bd9a38a8 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3,8 +3,23 @@ include("cmake/Vstgui.cmake") set(EDITOR_RESOURCES logo.png + logo_text.png + logo_text@2x.png + icon_white.png + icon_white@2x.png + knob48.png + knob48@2x.png + Fonts/fluentui-system-regular-20.ttf PARENT_SCOPE) +function(copy_editor_resources SOURCE_DIR DESTINATION_DIR) + foreach(res ${EDITOR_RESOURCES}) + get_filename_component(_dir "${res}" DIRECTORY) + file(MAKE_DIRECTORY "${DESTINATION_DIR}/${_dir}") + file(COPY "${SOURCE_DIR}/${res}" DESTINATION "${DESTINATION_DIR}/${_dir}") + endforeach() +endfunction() + # editor add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL src/editor/EditIds.h @@ -14,8 +29,53 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL src/editor/EditorController.h src/editor/GUIComponents.h src/editor/GUIComponents.cpp + src/editor/NativeHelpers.h + src/editor/NativeHelpers.cpp + src/editor/layout/main.hpp src/editor/utility/vstgui_after.h src/editor/utility/vstgui_before.h) target_include_directories(sfizz_editor PUBLIC "src") target_link_libraries(sfizz_editor PRIVATE sfizz-vstgui) target_link_libraries(sfizz_editor PUBLIC absl::strings) +if(APPLE) + find_library(APPLE_APPKIT_LIBRARY "AppKit") + find_library(APPLE_CORESERVICES_LIBRARY "CoreServices") + find_library(APPLE_FOUNDATION_LIBRARY "Foundation") + target_sources(sfizz_editor PRIVATE + src/editor/NativeHelpers.mm) + target_link_libraries(sfizz_editor PRIVATE + "${APPLE_APPKIT_LIBRARY}" + "${APPLE_CORESERVICES_LIBRARY}" + "${APPLE_FOUNDATION_LIBRARY}") + target_compile_options(sfizz_editor PRIVATE "-fobjc-arc") +endif() + +# dependencies +if(WIN32) + # +elseif(APPLE) + # +else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(sfizz-gio "gio-2.0" REQUIRED) + target_include_directories(sfizz_editor PRIVATE ${sfizz-gio_INCLUDE_DIRS}) + target_link_libraries(sfizz_editor PRIVATE ${sfizz-gio_LIBRARIES}) +endif() +target_include_directories(sfizz_editor PRIVATE "../src/external") # ghc::filesystem + +# layout tool +if(NOT CMAKE_CROSSCOMPILING) + add_executable(layout-maker + "tools/layout-maker/sources/layout.h" + "tools/layout-maker/sources/reader.cpp" + "tools/layout-maker/sources/reader.h" + "tools/layout-maker/sources/main.cpp") + target_link_libraries(layout-maker PRIVATE absl::strings) + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/editor/layout/main.hpp" + COMMAND "${CMAKE_CURRENT_BINARY_DIR}/layout-maker" + "${CMAKE_CURRENT_SOURCE_DIR}/layout/main.fl" + > "${CMAKE_CURRENT_SOURCE_DIR}/src/editor/layout/main.hpp" + DEPENDS layout-maker "${CMAKE_CURRENT_SOURCE_DIR}/layout/main.fl") +endif() diff --git a/editor/cmake/Vstgui.cmake b/editor/cmake/Vstgui.cmake index 2cac1f17..25f185bb 100644 --- a/editor/cmake/Vstgui.cmake +++ b/editor/cmake/Vstgui.cmake @@ -211,6 +211,8 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") # higher C++ requirement on Windows set_property(TARGET sfizz-vstgui PROPERTY CXX_STANDARD 14) + # Windows 10 RS2 DDI for custom fonts + target_compile_definitions(sfizz-vstgui PRIVATE "NTDDI_VERSION=0x0A000003") endif() if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/editor/layout/main.fl b/editor/layout/main.fl new file mode 100644 index 00000000..1d27e987 --- /dev/null +++ b/editor/layout/main.fl @@ -0,0 +1,344 @@ +# data file for the Fltk User Interface Designer (fluid) +version 1.0305 +header_name {.h} +code_name {.cxx} +widget_class mainView {open + xywh {410 523 800 475} type Double + class LogicalGroup visible +} { + Fl_Group {} { + comment {theme=darkTheme} open + xywh {0 0 800 110} + class LogicalGroup + } { + Fl_Group {} {open + xywh {5 4 100 101} box ROUNDED_BOX align 0 + class RoundedGroup + } { + Fl_Box {} { + comment {tag=kTagFirstChangePanel+kPanelGeneral} + image {../resources/icon_white.png} xywh {7 6 96 96} + class SfizzMainButton + } + } + Fl_Group {} {open + xywh {110 5 380 100} box ROUNDED_BOX + class RoundedGroup + } { + Fl_Box {} { + label {File:} + xywh {125 15 40 25} labelsize 16 + class Label + } + Fl_Box {} { + label {KS:} + xywh {125 45 40 25} labelsize 16 + class Label + } + Fl_Box {} { + label {Separator 1} + xywh {120 40 355 5} box BORDER_BOX labeltype NO_LABEL + class HLine + } + Fl_Box {} { + label {Separator 2} + xywh {120 70 355 5} box BORDER_BOX labeltype NO_LABEL + class HLine + } + Fl_Box sfzFileLabel_ { + label {DefaultInstrument.sfz} + xywh {190 15 230 25} labelsize 20 + class Label + } + Fl_Box {} { + label {Key switch} + xywh {190 45 230 25} labelsize 20 + class Label + } + Fl_Box {} { + label {Voices:} + xywh {120 75 60 25} labelsize 12 align 24 + class Label + } + Fl_Button {} { + comment {tag=kTagLoadSfzFile} + xywh {425 15 25 25} labelsize 24 + class LoadFileButton + } + Fl_Button {} { + comment {tag=kTagEditSfzFile} + xywh {450 15 25 25} labelsize 24 + class EditFileButton + } + Fl_Box infoVoicesLabel_ { + xywh {185 75 50 25} labelsize 12 align 16 + class Label + } + Fl_Box {} { + label {Max:} + xywh {240 75 60 25} labelsize 12 align 24 + class Label + } + Fl_Box numVoicesLabel_ { + xywh {305 75 50 25} labelsize 12 align 16 + class Label + } + Fl_Box {} { + label {Memory:} + xywh {360 75 60 25} labelsize 12 align 24 + class Label + } + Fl_Box memoryLabel_ { + xywh {425 75 50 25} labelsize 12 align 16 + class Label + } + } + Fl_Group {} {open + xywh {495 5 100 100} box ROUNDED_BOX + class RoundedGroup + } { + Fl_Light_Button {} { + label SETUP + comment {tag=kTagFirstChangePanel+kPanelSettings} + xywh {510 42 70 25} + class LightButton + } + Fl_Light_Button {} { + label CC + comment {tag=kTagFirstChangePanel+kPanelControls} + xywh {510 15 70 25} + class LightButton + } + Fl_Light_Button {} { + label INFO + comment {tag=kTagFirstChangePanel+kPanelInfo} + xywh {510 69 70 25} + class LightButton + } + } + Fl_Group {} {open + xywh {600 5 195 100} box ROUNDED_BOX + class RoundedGroup + } { + Fl_Dial {} { + xywh {615 20 48 48} value 0.5 hide + class Knob48 + } + Fl_Box {} { + label Center + xywh {610 70 60 25} labelsize 12 hide + class ValueLabel + } + Fl_Dial volumeSlider_ { + comment {tag=kTagSetVolume} + xywh {680 20 48 48} value 0.5 + class Knob48 + } + Fl_Box volumeLabel_ { + label {0.0 dB} + xywh {675 70 60 25} labelsize 12 + class ValueLabel + } + Fl_Box {} { + xywh {745 20 35 70} box BORDER_BOX + class VMeter + } + } + } + Fl_Group {subPanels_[kPanelGeneral]} { + xywh {5 110 790 285} hide + class LogicalGroup + } { + Fl_Group {} {open + xywh {5 110 120 285} box ROUNDED_BOX + class RoundedGroup + } { + Fl_Box {} { + label {Curves:} + xywh {15 120 60 25} labelsize 12 align 20 + class Label + } + Fl_Box {} { + label {Masters:} + xywh {15 145 60 25} labelsize 12 align 20 + class Label + } + Fl_Box {} { + label {Groups:} + xywh {15 170 60 25} labelsize 12 align 20 + class Label + } + Fl_Box {} { + label {Regions:} + xywh {15 195 60 25} labelsize 12 align 20 + class Label + } + Fl_Box {} { + label {Samples:} + xywh {15 220 60 25} labelsize 12 align 20 + class Label + } + Fl_Box infoCurvesLabel_ { + label 0 + xywh {75 120 40 25} labelsize 12 align 16 + class Label + } + Fl_Box infoMastersLabel_ { + label 0 + xywh {75 145 40 25} labelsize 12 align 16 + class Label + } + Fl_Box infoGroupsLabel_ { + label 0 + xywh {75 170 40 25} labelsize 12 align 16 + class Label + } + Fl_Box infoRegionsLabel_ { + label 0 + xywh {75 195 40 25} labelsize 12 align 16 + class Label + } + Fl_Box infoSamplesLabel_ { + label 0 + xywh {75 220 40 25} labelsize 12 align 16 + class Label + } + } + Fl_Group {} {open + xywh {130 110 665 280} + class LogicalGroup + } { + Fl_Box {} { + image {../resources/logo_text.png} xywh {260 125 400 250} + class SfizzLargePicture + } + } + } + Fl_Group {subPanels_[kPanelControls]} { + xywh {5 110 790 285} hide + class LogicalGroup + } { + Fl_Group {} {open + xywh {5 110 790 285} box ROUNDED_BOX + class RoundedGroup + } { + Fl_Box {} { + label {Controls not available} + xywh {5 110 790 285} labelsize 40 + class Label + } + } + } + Fl_Group {subPanels_[kPanelSettings]} {open + xywh {5 110 790 285} + class LogicalGroup + } { + Fl_Group {} { + label Engine open selected + xywh {260 125 280 110} box ROUNDED_BOX labelsize 12 align 17 + class TitleGroup + } { + Fl_Spinner numVoicesSlider_ { + comment {tag=kTagSetNumVoices} + xywh {285 185 60 25} labelsize 12 textsize 12 + class ValueMenu + } + Fl_Box {} { + label Polyphony + xywh {275 145 80 25} labelsize 12 + class ValueLabel + } + Fl_Spinner oversamplingSlider_ { + comment {tag=kTagSetOversampling} + xywh {370 185 60 25} labelsize 12 textsize 12 + class ValueMenu + } + Fl_Box {} { + label Oversampling + xywh {360 145 80 25} labelsize 12 + class ValueLabel + } + Fl_Box {} { + label {Preload size} + xywh {445 145 80 25} labelsize 12 + class ValueLabel + } + Fl_Spinner preloadSizeSlider_ { + comment {tag=kTagSetPreloadSize} + xywh {455 185 60 25} labelsize 12 textsize 12 + class ValueMenu + } + } + Fl_Group {} { + label Tuning open + xywh {205 260 390 120} box ROUNDED_BOX labelsize 12 align 17 + class TitleGroup + } { + Fl_Box {} { + label {Root key} + xywh {330 280 80 25} labelsize 12 + class ValueLabel + } + Fl_Spinner tuningFrequencySlider_ { + comment {tag=kTagSetTuningFrequency} + xywh {425 320 60 25} labelsize 12 textsize 12 + class ValueMenu + } + Fl_Box {} { + label Frequency + xywh {415 280 80 25} labelsize 12 + class ValueLabel + } + Fl_Dial stretchedTuningSlider_ { + comment {tag=kTagSetStretchedTuning} + xywh {515 305 48 48} value 0.5 + class Knob48 + } + Fl_Box {} { + label Stretch + xywh {500 280 80 25} labelsize 12 + class ValueLabel + } + Fl_Box {} { + label {Scala file} + xywh {225 280 100 25} labelsize 12 + class ValueLabel + } + Fl_Button scalaFileButton_ { + label DefaultScale + comment {tag=kTagLoadScalaFile} + xywh {225 320 100 25} labelsize 12 + class ValueButton + } + Fl_Spinner scalaRootKeySlider_ { + comment {tag=kTagSetScalaRootKey} + xywh {340 320 35 25} labelsize 12 textsize 12 + class ValueMenu + } + Fl_Spinner scalaRootOctaveSlider_ { + comment {tag=kTagSetScalaRootKey} + xywh {375 320 30 25} labelsize 12 textsize 12 + class ValueMenu + } + } + } + Fl_Group {subPanels_[kPanelInfo]} { + xywh {5 110 790 285} hide + class LogicalGroup + } { + Fl_Group {} {open + xywh {5 110 790 285} box ROUNDED_BOX + class RoundedGroup + } { + Fl_Box {} { + label {Informative text goes here} + xywh {5 110 790 285} labelsize 40 + class Label + } + } + } + Fl_Box {} { + xywh {5 400 790 70} + class Piano + } +} diff --git a/editor/resources/Fonts/fluentui-system-regular-20.ttf b/editor/resources/Fonts/fluentui-system-regular-20.ttf new file mode 100644 index 00000000..4e838582 Binary files /dev/null and b/editor/resources/Fonts/fluentui-system-regular-20.ttf differ diff --git a/editor/resources/icon_white.png b/editor/resources/icon_white.png new file mode 100644 index 00000000..d63b6f3d Binary files /dev/null and b/editor/resources/icon_white.png differ diff --git a/editor/resources/icon_white.svg b/editor/resources/icon_white.svg new file mode 100644 index 00000000..4021b787 --- /dev/null +++ b/editor/resources/icon_white.svg @@ -0,0 +1,253 @@ + + + sfizz logo + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + sfizz logo + 2020-05-16 + + + Tobiasz 'unfa' Karoń + + + + + CC-0 + + + + + + + + + + + + + + + diff --git a/editor/resources/icon_white@2x.png b/editor/resources/icon_white@2x.png new file mode 100644 index 00000000..e5bc80a2 Binary files /dev/null and b/editor/resources/icon_white@2x.png differ diff --git a/editor/resources/knob.knob b/editor/resources/knob.knob new file mode 100644 index 00000000..093bd66c Binary files /dev/null and b/editor/resources/knob.knob differ diff --git a/editor/resources/knob48.png b/editor/resources/knob48.png new file mode 100644 index 00000000..aa5c8eb3 Binary files /dev/null and b/editor/resources/knob48.png differ diff --git a/editor/resources/knob48@2x.png b/editor/resources/knob48@2x.png new file mode 100644 index 00000000..2f8d49e6 Binary files /dev/null and b/editor/resources/knob48@2x.png differ diff --git a/editor/resources/logo_text.png b/editor/resources/logo_text.png new file mode 100644 index 00000000..d683e44e Binary files /dev/null and b/editor/resources/logo_text.png differ diff --git a/editor/resources/logo_text.svg b/editor/resources/logo_text.svg new file mode 100644 index 00000000..a62481c8 --- /dev/null +++ b/editor/resources/logo_text.svg @@ -0,0 +1,252 @@ + + + sfizz logo + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + sfizz logo + 2020-05-16 + + + Tobiasz 'unfa' Karoń + + + + + CC-0 + + + + + + + + + + + + + + + diff --git a/editor/resources/logo_text@2x.png b/editor/resources/logo_text@2x.png new file mode 100644 index 00000000..c3e28c63 Binary files /dev/null and b/editor/resources/logo_text@2x.png differ diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index d50aa5b4..8e3089a8 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -8,7 +8,10 @@ #include "EditorController.h" #include "EditIds.h" #include "GUIComponents.h" +#include "NativeHelpers.h" #include +#include +#include #include #include @@ -18,19 +21,20 @@ using namespace VSTGUI; -const int Editor::viewWidth { 482 }; -const int Editor::viewHeight { 225 }; +const int Editor::viewWidth { 800 }; +const int Editor::viewHeight { 475 }; struct Editor::Impl : EditorController::Receiver, IControlListener { EditorController* ctrl_ = nullptr; CFrame* frame_ = nullptr; - SharedPointer view_; + SharedPointer mainView_; + + std::string currentSfzFile_; enum { kPanelGeneral, - // kPanelControls, + kPanelControls, kPanelSettings, - kPanelTuning, kPanelInfo, kNumPanels, }; @@ -40,6 +44,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { enum { kTagLoadSfzFile, + kTagEditSfzFile, kTagSetVolume, kTagSetNumVoices, kTagSetOversampling, @@ -54,19 +59,21 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { CTextLabel* sfzFileLabel_ = nullptr; CTextLabel* scalaFileLabel_ = nullptr; - CSliderBase *volumeSlider_ = nullptr; + CTextButton* scalaFileButton_ = nullptr; + CControl *volumeSlider_ = nullptr; CTextLabel* volumeLabel_ = nullptr; - CSliderBase *numVoicesSlider_ = nullptr; + SValueMenu *numVoicesSlider_ = nullptr; CTextLabel* numVoicesLabel_ = nullptr; - CSliderBase *oversamplingSlider_ = nullptr; + SValueMenu *oversamplingSlider_ = nullptr; CTextLabel* oversamplingLabel_ = nullptr; - CSliderBase *preloadSizeSlider_ = nullptr; + SValueMenu *preloadSizeSlider_ = nullptr; CTextLabel* preloadSizeLabel_ = nullptr; - CSliderBase *scalaRootKeySlider_ = nullptr; + SValueMenu *scalaRootKeySlider_ = nullptr; + SValueMenu *scalaRootOctaveSlider_ = nullptr; CTextLabel* scalaRootKeyLabel_ = nullptr; - CSliderBase *tuningFrequencySlider_ = nullptr; + SValueMenu *tuningFrequencySlider_ = nullptr; CTextLabel* tuningFrequencyLabel_ = nullptr; - CSliderBase *stretchedTuningSlider_ = nullptr; + CControl *stretchedTuningSlider_ = nullptr; CTextLabel* stretchedTuningLabel_ = nullptr; CTextLabel* infoCurvesLabel_ = nullptr; @@ -76,6 +83,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { CTextLabel* infoSamplesLabel_ = nullptr; CTextLabel* infoVoicesLabel_ = nullptr; + CTextLabel* memoryLabel_ = nullptr; + void uiReceiveValue(EditId id, const EditValue& v) override; void createFrameContents(); @@ -90,11 +99,16 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { } void chooseSfzFile(); + void changeSfzFile(const std::string& filePath); void chooseScalaFile(); + void changeScalaFile(const std::string& filePath); + + static absl::string_view simplifiedFileName(absl::string_view path, absl::string_view removedSuffix, absl::string_view ifEmpty); void updateSfzFileLabel(const std::string& filePath); void updateScalaFileLabel(const std::string& filePath); - static void updateLabelWithFileName(CTextLabel* label, const std::string& filePath); + static void updateLabelWithFileName(CTextLabel* label, const std::string& filePath, absl::string_view removedSuffix); + static void updateButtonWithFileName(CTextButton* button, const std::string& filePath, absl::string_view removedSuffix); void updateVolumeLabel(float volume); void updateNumVoicesLabel(int numVoices); void updateOversamplingLabel(int oversamplingLog2); @@ -142,7 +156,7 @@ void Editor::open(CFrame& frame) Impl& impl = *impl_; impl.frame_ = &frame; - frame.addView(impl.view_.get()); + frame.addView(impl.mainView_.get()); } void Editor::close() @@ -150,7 +164,7 @@ void Editor::close() Impl& impl = *impl_; if (impl.frame_) { - impl.frame_->removeView(impl.view_.get(), false); + impl.frame_->removeView(impl.mainView_.get(), false); impl.frame_ = nullptr; } } @@ -161,22 +175,27 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) case EditId::SfzFile: { const std::string& value = v.to_string(); + currentSfzFile_ = value; updateSfzFileLabel(value); } break; case EditId::Volume: { const float value = v.to_float(); - if (volumeSlider_) + if (volumeSlider_) { volumeSlider_->setValue(value); + volumeSlider_->setDirty(); + } updateVolumeLabel(value); } break; case EditId::Polyphony: { const int value = static_cast(v.to_float()); - if (numVoicesSlider_) + if (numVoicesSlider_) { numVoicesSlider_->setValue(value); + numVoicesSlider_->setDirty(); + } updateNumVoicesLabel(value); } break; @@ -188,16 +207,20 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) for (int f = value; f > 1; f /= 2) ++log2Value; - if (oversamplingSlider_) + if (oversamplingSlider_) { oversamplingSlider_->setValue(log2Value); + oversamplingSlider_->setDirty(); + } updateOversamplingLabel(log2Value); } break; case EditId::PreloadSize: { const int value = static_cast(v.to_float()); - if (preloadSizeSlider_) + if (preloadSizeSlider_) { preloadSizeSlider_->setValue(value); + preloadSizeSlider_->setDirty(); + } updatePreloadSizeLabel(value); } break; @@ -209,25 +232,35 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) break; case EditId::ScalaRootKey: { - const int value = static_cast(v.to_float()); - if (scalaRootKeySlider_) - scalaRootKeySlider_->setValue(value); + const int value = std::max(0, static_cast(v.to_float())); + if (scalaRootKeySlider_) { + scalaRootKeySlider_->setValue(value % 12); + scalaRootKeySlider_->setDirty(); + } + if (scalaRootOctaveSlider_) { + scalaRootOctaveSlider_->setValue(value / 12); + scalaRootOctaveSlider_->setDirty(); + } updateScalaRootKeyLabel(value); } break; case EditId::TuningFrequency: { const float value = v.to_float(); - if (tuningFrequencySlider_) + if (tuningFrequencySlider_) { tuningFrequencySlider_->setValue(value); + tuningFrequencySlider_->setDirty(); + } updateTuningFrequencyLabel(value); } break; case EditId::StretchTuning: { const float value = v.to_float(); - if (stretchedTuningSlider_) + if (stretchedTuningSlider_) { stretchedTuningSlider_->setValue(value); + stretchedTuningSlider_->setDirty(); + } updateStretchedTuningLabel(value); } break; @@ -284,405 +317,319 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) void Editor::Impl::createFrameContents() { - const CRect bounds { 0.0, 0.0, static_cast(viewWidth), static_cast(viewHeight) }; - CViewContainer* view = new CViewContainer(bounds); - view_ = owned(view); + CViewContainer* mainView; - view->setBackgroundColor(CColor(0xff, 0xff, 0xff)); + SharedPointer iconWhite = owned(new CBitmap("icon_white.png")); + SharedPointer knob48 = owned(new CBitmap("knob48.png")); + SharedPointer logoText = owned(new CBitmap("logo_text.png")); - SharedPointer logo = owned(new CBitmap("logo.png")); + { + const CColor frameBackground = { 0xd3, 0xd7, 0xcf }; - CRect bottomRow = bounds; - bottomRow.top = bottomRow.bottom - 30; + struct Theme { + CColor boxBackground; + CColor text; + CColor titleBoxText; + CColor titleBoxBackground; + CColor icon; + CColor valueText; + CColor valueBackground; + }; - CRect topRow = bounds; - topRow.bottom = topRow.top + 30; + Theme lightTheme; + lightTheme.boxBackground = { 0xba, 0xbd, 0xb6 }; + lightTheme.text = { 0x00, 0x00, 0x00 }; + lightTheme.titleBoxText = { 0xff, 0xff, 0xff }; + lightTheme.titleBoxBackground = { 0x2e, 0x34, 0x36 }; + lightTheme.icon = lightTheme.text; + lightTheme.valueText = { 0xff, 0xff, 0xff }; + lightTheme.valueBackground = { 0x2e, 0x34, 0x36 }; + Theme darkTheme; + darkTheme.boxBackground = { 0x2e, 0x34, 0x36 }; + darkTheme.text = { 0xff, 0xff, 0xff }; + darkTheme.titleBoxText = { 0x00, 0x00, 0x00 }; + darkTheme.titleBoxBackground = { 0xba, 0xbd, 0xb6 }; + darkTheme.icon = darkTheme.text; + darkTheme.valueText = { 0x2e, 0x34, 0x36 }; + darkTheme.valueBackground = { 0xff, 0xff, 0xff }; + Theme& defaultTheme = lightTheme; + Theme* theme = &defaultTheme; + auto enterTheme = [&theme](Theme& t) { theme = &t; }; + + typedef CViewContainer LogicalGroup; + typedef SBoxContainer RoundedGroup; + typedef STitleContainer TitleGroup; + typedef CKickButton SfizzMainButton; + typedef CTextLabel Label; + typedef CViewContainer HLine; + typedef CTextButton LightButton; + typedef CAnimKnob Knob48; + typedef CTextLabel ValueLabel; + typedef CViewContainer VMeter; + typedef CView SfizzLargePicture; + typedef SValueMenu ValueMenu; +#if 0 + typedef CTextButton Button; +#endif + typedef CTextButton ValueButton; + typedef CTextButton LoadFileButton; + typedef CTextButton EditFileButton; + typedef SPiano Piano; + + auto createLogicalGroup = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + CViewContainer* container = new CViewContainer(bounds); + container->setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00)); + return container; + }; + auto createRoundedGroup = [&theme](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + auto* box = new SBoxContainer(bounds); + box->setCornerRadius(10.0); + box->setBackgroundColor(theme->boxBackground); + return box; + }; + auto createTitleGroup = [&theme](const CRect& bounds, int, const char* label, CHoriTxtAlign, int fontsize) { + auto* box = new STitleContainer(bounds, label); + box->setCornerRadius(10.0); + box->setBackgroundColor(theme->boxBackground); + box->setTitleFontColor(theme->titleBoxText); + box->setTitleBackgroundColor(theme->titleBoxBackground); + auto font = owned(new CFontDesc(*box->getTitleFont())); + font->setSize(fontsize); + box->setTitleFont(font); + return box; + }; + auto createSfizzMainButton = [this, &iconWhite](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int) { + return new CKickButton(bounds, this, tag, iconWhite); + }; + auto createLabel = [&theme](const CRect& bounds, int, const char* label, CHoriTxtAlign align, int fontsize) { + CTextLabel* lbl = new CTextLabel(bounds, label); + lbl->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + lbl->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + lbl->setFontColor(theme->text); + lbl->setHoriAlign(align); + auto font = owned(new CFontDesc(*lbl->getFont())); + font->setSize(fontsize); + lbl->setFont(font); + return lbl; + }; + auto createHLine = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + int y = static_cast(0.5 * (bounds.top + bounds.bottom)); + CRect lineBounds(bounds.left, y, bounds.right, y + 1); + CViewContainer* hline = new CViewContainer(lineBounds); + hline->setBackgroundColor(CColor(0xff, 0xff, 0xff, 0xff)); + return hline; + }; + auto createLightButton = [this](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int) { + CTextButton* button = new CTextButton(bounds, this, tag, label); + button->setTextAlignment(align); + return button; + }; + auto createKnob48 = [this, &knob48](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int) { + return new CAnimKnob(bounds, this, tag, 31, 48, knob48); + }; + auto createValueLabel = [&theme](const CRect& bounds, int, const char* label, CHoriTxtAlign align, int fontsize) { + CTextLabel* lbl = new CTextLabel(bounds, label); + lbl->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + lbl->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + lbl->setFontColor(theme->text); + lbl->setHoriAlign(align); + auto font = owned(new CFontDesc(*lbl->getFont())); + font->setSize(fontsize); + lbl->setFont(font); + return lbl; + }; + auto createVMeter = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + // TODO the volume meter... + CViewContainer* container = new CViewContainer(bounds); + container->setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00)); + return container; + }; + auto createSfizzLargePicture = [&logoText](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + CView* picture = new CView(bounds); + picture->setBackground(logoText); + return picture; + }; +#if 0 + auto createButton = [this](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int fontsize) { + CTextButton* button = new CTextButton(bounds, this, tag, label); + auto font = owned(new CFontDesc(*button->getFont())); + font->setSize(fontsize); + button->setFont(font); + button->setTextAlignment(align); + return button; + }; +#endif + auto createValueButton = [this, &theme](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int fontsize) { + CTextButton* button = new CTextButton(bounds, this, tag, label); + auto font = owned(new CFontDesc(*button->getFont())); + font->setSize(fontsize); + button->setFont(font); + button->setTextAlignment(align); + button->setTextColor(theme->valueText); + button->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + SharedPointer gradient = owned(CGradient::create(0.0, 1.0, theme->valueBackground, theme->valueBackground)); + button->setGradient(gradient); + button->setGradientHighlighted(gradient); + return button; + }; + auto createValueMenu = [this, &theme](const CRect& bounds, int tag, const char*, CHoriTxtAlign align, int fontsize) { + SValueMenu* vm = new SValueMenu(bounds, this, tag); + vm->setHoriAlign(align); + auto font = owned(new CFontDesc(*vm->getFont())); + font->setSize(fontsize); + vm->setFont(font); + vm->setFontColor(theme->valueText); + vm->setBackColor(theme->valueBackground); + vm->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + vm->setStyle(CParamDisplay::kRoundRectStyle); + vm->setRoundRectRadius(5.0); + return vm; + }; + auto createGlyphButton = [this, &theme](UTF8StringPtr glyph, const CRect& bounds, int tag, int fontsize) { + CTextButton* btn = new CTextButton(bounds, this, tag, glyph); + btn->setFont(new CFontDesc("Fluent System Regular W20", fontsize)); + btn->setTextColor(theme->icon); + btn->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + btn->setGradient(nullptr); + btn->setGradientHighlighted(nullptr); + return btn; + }; + auto createLoadFileButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) { + return createGlyphButton("\ue142", bounds, tag, fontsize); + }; + auto createEditFileButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) { + return createGlyphButton("\ue148", bounds, tag, fontsize); + }; + auto createPiano = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + SPiano* piano = new SPiano(bounds); + return piano; + }; + + #include "layout/main.hpp" + + mainView->setBackgroundColor(frameBackground); + + mainView_ = owned(mainView); + } + + /// + SharedPointer fileDropTarget = owned(new SFileDropTarget); + + fileDropTarget->setFileDropFunction([this](const std::string& file) { + changeSfzFile(file); + }); + + mainView_->setDropTarget(fileDropTarget); + + /// + adjustMinMaxToEditRange(volumeSlider_, EditId::Volume); + adjustMinMaxToEditRange(numVoicesSlider_, EditId::Polyphony); + adjustMinMaxToEditRange(oversamplingSlider_, EditId::Oversampling); + adjustMinMaxToEditRange(preloadSizeSlider_, EditId::PreloadSize); + if (scalaRootKeySlider_) { + scalaRootKeySlider_->setMin(0.0); + scalaRootKeySlider_->setMax(11.0); + scalaRootKeySlider_->setDefaultValue( + static_cast(EditRange::get(EditId::ScalaRootKey).def) % 12); + } + if (scalaRootOctaveSlider_) { + scalaRootOctaveSlider_->setMin(0.0); + scalaRootOctaveSlider_->setMax(10.0); + scalaRootOctaveSlider_->setDefaultValue( + static_cast(EditRange::get(EditId::ScalaRootKey).def) / 12); + } + adjustMinMaxToEditRange(tuningFrequencySlider_, EditId::TuningFrequency); + adjustMinMaxToEditRange(stretchedTuningSlider_, EditId::StretchTuning); + + for (int value : {1, 2, 4, 8, 16, 32, 64, 96, 128, 160, 192, 224, 256}) + numVoicesSlider_->addEntry(std::to_string(value), value); + numVoicesSlider_->setValueToStringFunction2( + [](float value, std::string& result, CParamDisplay*) -> bool + { + result = std::to_string(static_cast(value)); + return true; + }); + + for (int log2value = 0; log2value <= 3; ++log2value) { + int value = 1 << log2value; + oversamplingSlider_->addEntry(std::to_string(value) + "x", log2value); + } + oversamplingSlider_->setValueToStringFunction2( + [](float value, std::string& result, CParamDisplay*) -> bool + { + result = std::to_string(1 << static_cast(value)) + "x"; + return true; + }); + + for (int log2value = 10; log2value <= 16; ++log2value) { + int value = 1 << log2value; + char text[256]; + sprintf(text, "%d kB", value / 1024); + text[sizeof(text) - 1] = '\0'; + preloadSizeSlider_->addEntry(text, value); + } + preloadSizeSlider_->setValueToStringFunction2( + [](float value, std::string& result, CParamDisplay*) -> bool + { + result = std::to_string(static_cast(std::round(value * (1.0 / 1024)))) + " kB"; + return true; + }); + + static const std::pair tuningFrequencies[] = { + {380.0f, "English pitchpipe 380 (1720)"}, + {409.0f, "Handel fork 409 (1780)"}, + {415.0f, "Baroque 415"}, + {422.5f, "Handel fork 422.5 (1740)"}, + {423.2f, "Dresden opera 423.2 (1815)"}, + {435.0f, "French Law 435 (1859)"}, + {439.0f, "British Phil 439 (1896)"}, + {440.0f, "International 440"}, + {442.0f, "European 442"}, + {445.0f, "Germany, China 445"}, + {451.0f, "La Scala in Milan 451 (18th)"}, + }; + + for (std::pair value : tuningFrequencies) + tuningFrequencySlider_->addEntry(value.second, value.first); + tuningFrequencySlider_->setValueToStringFunction( + [](float value, char result[256], CParamDisplay*) -> bool + { + sprintf(result, "%.1f Hz", value); + return true; + }); + + static const char* notesInOctave[12] = { + "C", "C#", "D", "D#", "E", + "F", "F#", "G", "G#", "A", "A#", "B", + }; + for (int note = 0; note < 12; ++note) + scalaRootKeySlider_->addEntry(notesInOctave[note], note); + for (int octave = 0; octave <= 10; ++octave) + scalaRootOctaveSlider_->addEntry(std::to_string(octave - 1), octave); + scalaRootKeySlider_->setValueToStringFunction2( + [](float value, std::string& result, CParamDisplay*) -> bool + { + result = notesInOctave[std::max(0, static_cast(value)) % 12]; + return true; + }); + scalaRootOctaveSlider_->setValueToStringFunction2( + [](float value, std::string& result, CParamDisplay*) -> bool + { + result = std::to_string(static_cast(value) - 1); + return true; + }); + + /// CViewContainer* panel; activePanel_ = 0; - CRect topLeftLabelBox = topRow; - topLeftLabelBox.right -= 20 * kNumPanels; - - // general panel - { - panel = new CViewContainer(bounds); - view->addView(panel); - panel->setTransparency(true); - - CKickButton* sfizzButton = new CKickButton(bounds, this, kTagLoadSfzFile, logo); - panel->addView(sfizzButton); - - CTextLabel* topLeftLabel = new CTextLabel(topLeftLabelBox, "No file loaded"); - topLeftLabel->setFontColor(CColor(0x00, 0x00, 0x00)); - topLeftLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - panel->addView(topLeftLabel); - sfzFileLabel_ = topLeftLabel; - - subPanels_[kPanelGeneral] = panel; - } - - // settings panel - { - panel = new CViewContainer(bounds); - view->addView(panel); - panel->setTransparency(true); - - CTextLabel* topLeftLabel = new CTextLabel(topLeftLabelBox, "Settings"); - topLeftLabel->setFontColor(CColor(0x00, 0x00, 0x00)); - topLeftLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - panel->addView(topLeftLabel); - - CRect row = topRow; - row.top += 45.0; - row.bottom += 45.0; - row.left += 20.0; - row.right -= 20.0; - - static const CCoord interRow = 35.0; - static const CCoord interColumn = 20.0; - static const int numColumns = 3; - - auto nthColumn = [&row](int colIndex) -> CRect { - CRect div = row; - CCoord columnWidth = (div.right - div.left + interColumn) / numColumns - interColumn; - div.left = div.left + colIndex * (columnWidth + interColumn); - div.right = div.left + columnWidth; - return div; - }; - - CTextLabel* label; - SimpleSlider* slider; - - label = new CTextLabel(nthColumn(0), "Volume"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - slider = new SimpleSlider(nthColumn(1), this, kTagSetVolume); - panel->addView(slider); - adjustMinMaxToEditRange(slider, EditId::Volume); - volumeSlider_ = slider; - label = new CTextLabel(nthColumn(2), ""); - volumeLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Polyphony"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - slider = new SimpleSlider(nthColumn(1), this, kTagSetNumVoices); - panel->addView(slider); - adjustMinMaxToEditRange(slider, EditId::Polyphony); - numVoicesSlider_ = slider; - label = new CTextLabel(nthColumn(2), ""); - numVoicesLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Oversampling"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - slider = new SimpleSlider(nthColumn(1), this, kTagSetOversampling); - panel->addView(slider); - adjustMinMaxToEditRange(slider, EditId::Oversampling); - oversamplingSlider_ = slider; - label = new CTextLabel(nthColumn(2), ""); - oversamplingLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Preload size"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - slider = new SimpleSlider(nthColumn(1), this, kTagSetPreloadSize); - panel->addView(slider); - adjustMinMaxToEditRange(slider, EditId::PreloadSize); - preloadSizeSlider_ = slider; - label = new CTextLabel(nthColumn(2), ""); - preloadSizeLabel_ = label; - panel->addView(label); - - subPanels_[kPanelSettings] = panel; - } - - // tuning panel - { - panel = new CViewContainer(bounds); - view->addView(panel); - panel->setTransparency(true); - - CTextLabel* topLeftLabel = new CTextLabel(topLeftLabelBox, "Tuning"); - topLeftLabel->setFontColor(CColor(0x00, 0x00, 0x00)); - topLeftLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - panel->addView(topLeftLabel); - - CRect row = topRow; - row.top += 45.0; - row.bottom += 45.0; - row.left += 20.0; - row.right -= 20.0; - - static const CCoord interRow = 35.0; - static const CCoord interColumn = 20.0; - static const int numColumns = 3; - - auto nthColumn = [&row](int colIndex) -> CRect { - CRect div = row; - CCoord columnWidth = (div.right - div.left + interColumn) / numColumns - interColumn; - div.left = div.left + colIndex * (columnWidth + interColumn); - div.right = div.left + columnWidth; - return div; - }; - - CTextLabel* label; - SimpleSlider* slider; - CTextButton* textbutton; - - label = new CTextLabel(nthColumn(0), "Scala file"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - textbutton = new CTextButton(nthColumn(1), this, kTagLoadScalaFile, "Choose"); - panel->addView(textbutton); - label = new CTextLabel(nthColumn(2), ""); - scalaFileLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Scala root key"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - slider = new SimpleSlider(nthColumn(1), this, kTagSetScalaRootKey); - panel->addView(slider); - adjustMinMaxToEditRange(slider, EditId::ScalaRootKey); - scalaRootKeySlider_ = slider; - label = new CTextLabel(nthColumn(2), ""); - scalaRootKeyLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Tuning frequency"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - slider = new SimpleSlider(nthColumn(1), this, kTagSetTuningFrequency); - panel->addView(slider); - adjustMinMaxToEditRange(slider, EditId::TuningFrequency); - tuningFrequencySlider_ = slider; - label = new CTextLabel(nthColumn(2), ""); - tuningFrequencyLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Stretched tuning"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - slider = new SimpleSlider(nthColumn(1), this, kTagSetStretchedTuning); - panel->addView(slider); - adjustMinMaxToEditRange(slider, EditId::StretchTuning); - stretchedTuningSlider_ = slider; - label = new CTextLabel(nthColumn(2), ""); - stretchedTuningLabel_ = label; - panel->addView(label); - - subPanels_[kPanelTuning] = panel; - } - - // info panel - { - panel = new CViewContainer(bounds); - view->addView(panel); - panel->setTransparency(true); - - CTextLabel* topLeftLabel = new CTextLabel(topLeftLabelBox, "Information"); - topLeftLabel->setFontColor(CColor(0x00, 0x00, 0x00)); - topLeftLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - panel->addView(topLeftLabel); - - CRect row = topRow; - row.top += 45.0; - row.bottom += 45.0; - row.left += 20.0; - row.right -= 20.0; - - static const CCoord interRow = 20.0; - static const CCoord interColumn = 20.0; - static const int numColumns = 3; - - auto nthColumn = [&row](int colIndex) -> CRect { - CRect div = row; - CCoord columnWidth = (div.right - div.left + interColumn) / numColumns - interColumn; - div.left = div.left + colIndex * (columnWidth + interColumn); - div.right = div.left + columnWidth; - return div; - }; - - CTextLabel* label; - - label = new CTextLabel(nthColumn(0), "Curves"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - label = new CTextLabel(nthColumn(1), ""); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - infoCurvesLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Masters"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - label = new CTextLabel(nthColumn(1), ""); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - infoMastersLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Groups"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - label = new CTextLabel(nthColumn(1), ""); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - infoGroupsLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Regions"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - label = new CTextLabel(nthColumn(1), ""); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - infoRegionsLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Samples"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - label = new CTextLabel(nthColumn(1), ""); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - infoSamplesLabel_ = label; - panel->addView(label); - - row.top += interRow; - row.bottom += interRow; - - label = new CTextLabel(nthColumn(0), "Voices"); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - panel->addView(label); - label = new CTextLabel(nthColumn(1), ""); - label->setFontColor(CColor(0x00, 0x00, 0x00)); - label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - label->setHoriAlign(kLeftText); - infoVoicesLabel_ = label; - panel->addView(label); - - subPanels_[kPanelInfo] = panel; - } - // all panels for (unsigned currentPanel = 0; currentPanel < kNumPanels; ++currentPanel) { panel = subPanels_[currentPanel]; - CTextLabel* descLabel = new CTextLabel( - bottomRow, "Paul Ferrand and the SFZ Tools work group"); - descLabel->setFontColor(CColor(0x00, 0x00, 0x00)); - descLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - panel->addView(descLabel); - - for (unsigned i = 0; i < kNumPanels; ++i) { - CRect btnRect = topRow; - btnRect.left = topRow.right - (kNumPanels - i) * 50; - btnRect.right = btnRect.left + 50; - - const char *text; - switch (i) { - case kPanelGeneral: text = "File"; break; - case kPanelSettings: text = "Setup"; break; - case kPanelTuning: text = "Tuning"; break; - case kPanelInfo: text = "Info"; break; - default: text = "?"; break; - } - - CTextButton* changePanelButton = new CTextButton(btnRect, this, kTagFirstChangePanel + i, text); - panel->addView(changePanelButton); - - changePanelButton->setRoundRadius(0.0); - } + if (!panel) + continue; panel->setVisible(currentPanel == activePanel_); } @@ -697,14 +644,18 @@ void Editor::Impl::chooseSfzFile() if (fs->runModal()) { UTF8StringPtr file = fs->getSelectedFile(0); - if (file) { - std::string str(file); - ctrl_->uiSendValue(EditId::SfzFile, str); - updateSfzFileLabel(str); - } + if (file) + changeSfzFile(file); } } +void Editor::Impl::changeSfzFile(const std::string& filePath) +{ + ctrl_->uiSendValue(EditId::SfzFile, filePath); + currentSfzFile_ = filePath; + updateSfzFileLabel(filePath); +} + void Editor::Impl::chooseScalaFile() { SharedPointer fs = owned(CNewFileSelector::create(frame_)); @@ -714,42 +665,64 @@ void Editor::Impl::chooseScalaFile() if (fs->runModal()) { UTF8StringPtr file = fs->getSelectedFile(0); - if (file) { - std::string str(file); - ctrl_->uiSendValue(EditId::ScalaFile, str); - updateScalaFileLabel(str); - } + if (file) + changeScalaFile(file); } } +void Editor::Impl::changeScalaFile(const std::string& filePath) +{ + ctrl_->uiSendValue(EditId::ScalaFile, filePath); + updateScalaFileLabel(filePath); +} + +absl::string_view Editor::Impl::simplifiedFileName(absl::string_view path, absl::string_view removedSuffix, absl::string_view ifEmpty) +{ + if (path.empty()) + return ifEmpty; + +#if defined (_WIN32) + size_t pos = path.find_last_of("/\\"); +#else + size_t pos = path.rfind('/'); +#endif + path = (pos != path.npos) ? path.substr(pos + 1) : path; + + if (!removedSuffix.empty() && absl::EndsWithIgnoreCase(path, removedSuffix)) + path.remove_suffix(removedSuffix.size()); + + return path; +} + void Editor::Impl::updateSfzFileLabel(const std::string& filePath) { - updateLabelWithFileName(sfzFileLabel_, filePath); + updateLabelWithFileName(sfzFileLabel_, filePath, ".sfz"); } void Editor::Impl::updateScalaFileLabel(const std::string& filePath) { - updateLabelWithFileName(scalaFileLabel_, filePath); + updateLabelWithFileName(scalaFileLabel_, filePath, ".scl"); + updateButtonWithFileName(scalaFileButton_, filePath, ".scl"); } -void Editor::Impl::updateLabelWithFileName(CTextLabel* label, const std::string& filePath) +void Editor::Impl::updateLabelWithFileName(CTextLabel* label, const std::string& filePath, absl::string_view removedSuffix) { if (!label) return; - std::string fileName; - if (filePath.empty()) - fileName = ""; - else { -#if defined (_WIN32) - size_t pos = filePath.find_last_of("/\\"); -#else - size_t pos = filePath.rfind('/'); -#endif - fileName = (pos != filePath.npos) ? - filePath.substr(pos + 1) : filePath; - } + std::string fileName = std::string(simplifiedFileName(filePath, removedSuffix, "")); label->setText(fileName.c_str()); + label->setDirty(); +} + +void Editor::Impl::updateButtonWithFileName(CTextButton* button, const std::string& filePath, absl::string_view removedSuffix) +{ + if (!button) + return; + + std::string fileName = std::string(simplifiedFileName(filePath, removedSuffix, "")); + button->setTitle(fileName.c_str()); + button->setDirty(); } void Editor::Impl::updateVolumeLabel(float volume) @@ -762,6 +735,7 @@ void Editor::Impl::updateVolumeLabel(float volume) sprintf(text, "%.1f dB", volume); text[sizeof(text) - 1] = '\0'; label->setText(text); + label->setDirty(); } void Editor::Impl::updateNumVoicesLabel(int numVoices) @@ -774,6 +748,7 @@ void Editor::Impl::updateNumVoicesLabel(int numVoices) sprintf(text, "%d", numVoices); text[sizeof(text) - 1] = '\0'; label->setText(text); + label->setDirty(); } void Editor::Impl::updateOversamplingLabel(int oversamplingLog2) @@ -786,6 +761,7 @@ void Editor::Impl::updateOversamplingLabel(int oversamplingLog2) sprintf(text, "%dx", 1 << oversamplingLog2); text[sizeof(text) - 1] = '\0'; label->setText(text); + label->setDirty(); } void Editor::Impl::updatePreloadSizeLabel(int preloadSize) @@ -795,9 +771,10 @@ void Editor::Impl::updatePreloadSizeLabel(int preloadSize) return; char text[64]; - sprintf(text, "%.1f kB", preloadSize * (1.0 / 1024)); + sprintf(text, "%d kB", static_cast(std::round(preloadSize * (1.0 / 1024)))); text[sizeof(text) - 1] = '\0'; label->setText(text); + label->setDirty(); } void Editor::Impl::updateScalaRootKeyLabel(int rootKey) @@ -826,6 +803,7 @@ void Editor::Impl::updateScalaRootKeyLabel(int rootKey) }; label->setText(noteName(rootKey)); + label->setDirty(); } void Editor::Impl::updateTuningFrequencyLabel(float tuningFrequency) @@ -838,6 +816,7 @@ void Editor::Impl::updateTuningFrequencyLabel(float tuningFrequency) sprintf(text, "%.1f", tuningFrequency); text[sizeof(text) - 1] = '\0'; label->setText(text); + label->setDirty(); } void Editor::Impl::updateStretchedTuningLabel(float stretchedTuning) @@ -850,6 +829,7 @@ void Editor::Impl::updateStretchedTuningLabel(float stretchedTuning) sprintf(text, "%.3f", stretchedTuning); text[sizeof(text) - 1] = '\0'; label->setText(text); + label->setDirty(); } void Editor::Impl::setActivePanel(unsigned panelId) @@ -857,9 +837,11 @@ void Editor::Impl::setActivePanel(unsigned panelId) panelId = std::max(0, std::min(kNumPanels - 1, static_cast(panelId))); if (activePanel_ != panelId) { - subPanels_[activePanel_]->setVisible(false); + if (subPanels_[activePanel_]) + subPanels_[activePanel_]->setVisible(false); + if (subPanels_[panelId]) + subPanels_[panelId]->setVisible(true); activePanel_ = panelId; - subPanels_[panelId]->setVisible(true); } } @@ -893,6 +875,14 @@ void Editor::Impl::valueChanged(CControl* ctl) Call::later([this]() { chooseSfzFile(); }); break; + case kTagEditSfzFile: + if (value != 1) + break; + + if (!currentSfzFile_.empty()) + openFileInExternalEditor(currentSfzFile_.c_str()); + break; + case kTagLoadScalaFile: if (value != 1) break; @@ -921,8 +911,15 @@ void Editor::Impl::valueChanged(CControl* ctl) break; case kTagSetScalaRootKey: - ctrl.uiSendValue(EditId::ScalaRootKey, value); - updateScalaRootKeyLabel(static_cast(value)); + { + if (scalaRootKeySlider_ && scalaRootOctaveSlider_) { + int key = static_cast(scalaRootKeySlider_->getValue()); + int octave = static_cast(scalaRootOctaveSlider_->getValue()); + int midiKey = key + 12 * octave; + ctrl.uiSendValue(EditId::ScalaRootKey, midiKey); + updateScalaRootKeyLabel(midiKey); + } + } break; case kTagSetTuningFrequency: diff --git a/editor/src/editor/GUIComponents.cpp b/editor/src/editor/GUIComponents.cpp index a85bf64b..c4e6f958 100644 --- a/editor/src/editor/GUIComponents.cpp +++ b/editor/src/editor/GUIComponents.cpp @@ -5,34 +5,422 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "GUIComponents.h" +#include #include "utility/vstgui_before.h" #include "vstgui/lib/cdrawcontext.h" +#include "vstgui/lib/cgraphicspath.h" +#include "vstgui/lib/cframe.h" #include "utility/vstgui_after.h" -SimpleSlider::SimpleSlider(const CRect& bounds, IControlListener* listener, int32_t tag) - : CSliderBase(bounds, listener, tag) +/// +SBoxContainer::SBoxContainer(const CRect& size) + : CViewContainer(size) { - setStyle(kHorizontal|kLeft); - - CPoint offsetHandle(2.0, 2.0); - setOffsetHandle(offsetHandle); - - CCoord handleSize = 20.0; - setHandleSizePrivate(handleSize, bounds.bottom - bounds.top - 2 * offsetHandle.y); - setHandleRangePrivate(bounds.right - bounds.left - handleSize - 2 * offsetHandle.x); + CViewContainer::setBackgroundColor(CColor(0, 0, 0, 0)); } -void SimpleSlider::draw(CDrawContext* dc) +void SBoxContainer::setCornerRadius(CCoord radius) +{ + cornerRadius_ = radius; + setDirty(); +} + +void SBoxContainer::setBackgroundColor(const CColor& color) +{ + backgroundColor_ = color; + setDirty(); +} + +CColor SBoxContainer::getBackgroundColor() const +{ + return backgroundColor_; +} + +void SBoxContainer::drawRect(CDrawContext* dc, const CRect& updateRect) { CRect bounds = getViewSize(); - CRect handle = calculateHandleRect(getValueNormalized()); dc->setDrawMode(kAntiAliasing); - dc->setFrameColor(_frame); - dc->drawRect(bounds, kDrawStroked); + SharedPointer path = owned(dc->createGraphicsPath()); + path->addRoundRect(bounds, cornerRadius_); - dc->setFillColor(_fill); - dc->drawRect(handle, kDrawFilled); + dc->setFillColor(backgroundColor_); + dc->drawGraphicsPath(path.get(), CDrawContext::kPathFilled); + + CViewContainer::drawRect(dc, updateRect); +} + +/// +STitleContainer::STitleContainer(const CRect& size, UTF8StringPtr text) + : SBoxContainer(size), text_(text ? text : ""), titleFont_(kNormalFont) +{ +} + +void STitleContainer::setTitleFont(CFontRef font) +{ + titleFont_ = font; + setDirty(); +} + +void STitleContainer::setTitleFontColor(CColor color) +{ + titleFontColor_ = color; + setDirty(); +} + +void STitleContainer::setTitleBackgroundColor(CColor color) +{ + titleBackgroundColor_ = color; + setDirty(); +} + +void STitleContainer::drawRect(CDrawContext* dc, const CRect& updateRect) +{ + SBoxContainer::drawRect(dc, updateRect); + + CRect bounds = getViewSize(); + CCoord cornerRadius = cornerRadius_; + + dc->setDrawMode(kAntiAliasing); + + CCoord fontHeight = titleFont_->getSize(); + CCoord titleHeight = fontHeight + 8.0; + + CRect titleBounds = bounds; + titleBounds.bottom = titleBounds.top + titleHeight; + + SharedPointer path = owned(dc->createGraphicsPath()); + path->beginSubpath(titleBounds.getBottomRight()); + path->addLine(titleBounds.getBottomLeft()); + path->addArc(CRect(titleBounds.left, titleBounds.top, titleBounds.left + 2.0 * cornerRadius, titleBounds.top + 2.0 * cornerRadius), 180., 270., true); + path->addArc(CRect(titleBounds.right - 2.0 * cornerRadius, titleBounds.top, titleBounds.right, titleBounds.top + 2.0 * cornerRadius), 270., 360., true); + path->closeSubpath(); + + dc->setFillColor(titleBackgroundColor_); + dc->drawGraphicsPath(path, CDrawContext::kPathFilled); + + dc->setFont(titleFont_); + dc->setFontColor(titleFontColor_); + dc->drawString(text_.c_str(), titleBounds, kCenterText); +} + +/// +void SFileDropTarget::setFileDropFunction(FileDropFunction f) +{ + dropFunction_ = std::move(f); +} + +DragOperation SFileDropTarget::onDragEnter(DragEventData data) +{ + op_ = isFileDrop(data.drag) ? + DragOperation::Copy : DragOperation::None; + return op_; +} + +DragOperation SFileDropTarget::onDragMove(DragEventData data) +{ + (void)data; + return op_; +} + +void SFileDropTarget::onDragLeave(DragEventData data) +{ + (void)data; + op_ = DragOperation::None; +} + +bool SFileDropTarget::onDrop(DragEventData data) +{ + if (op_ != DragOperation::Copy || !isFileDrop(data.drag)) + return false; + + IDataPackage::Type type; + const void* bytes; + uint32_t size = data.drag->getData(0, bytes, type); + std::string path(reinterpret_cast(bytes), size); + + if (dropFunction_) + dropFunction_(path); + + return true; +} + +bool SFileDropTarget::isFileDrop(IDataPackage* package) +{ + return package->getCount() == 1 && + package->getDataType(0) == IDataPackage::kFilePath; +} + +/// +SPiano::SPiano(const CRect& bounds) + : CView(bounds), font_(kNormalFont) +{ +} + +void SPiano::setFont(CFontRef font) +{ + font_ = font; + setDirty(); +} + +void SPiano::clearKeyRanges() +{ + keyInRange_.reset(); +} + +void SPiano::addKeyRange(int start, int end) +{ + start = std::min(127, std::max(0, start)); + end = std::min(127, std::max(0, end)); + + for (int x = start; x <= end; ++x) + keyInRange_.set(x); +} + +CCoord SPiano::getKeyWidth() +{ + return 6.0; +} + +CCoord SPiano::getKeySwitchesHeight() +{ + return 20.0; +} + +CCoord SPiano::getKeyRangesHeight() +{ + return 11.0; +} + +CCoord SPiano::getKeysHeight() const +{ + return getHeight() - + (getKeySwitchesHeight() + getKeyRangesHeight() + getOctavesHeight()); +} + +CCoord SPiano::getOctavesHeight() const +{ + return font_->getSize(); +} + +void SPiano::getZoneDimensions( + CRect* pKeySwitches, + CRect* pKeyboard, + CRect* pKeyRanges, + CRect* pOctaves) +{ + CRect bounds = getViewSize(); + + CRect keySwitches(bounds); + keySwitches.setHeight(getKeySwitchesHeight()); + + CRect keyboard(bounds); + keyboard.top = keySwitches.bottom; + keyboard.setHeight(getKeysHeight()); + + CRect keyRanges(bounds); + keyRanges.top = keyboard.bottom; + keyRanges.setHeight(getKeyRangesHeight()); + + CRect octaves(bounds); + octaves.top = keyRanges.bottom; + octaves.setHeight(getOctavesHeight()); + + // apply some paddings + keySwitches.extend(-2.0, -2.0); + keyboard.extend(-2.0, -2.0); + keyRanges.extend(-2.0, -4.0); + octaves.extend(-2.0, -2.0); + + // offsets for centered keyboard + CCoord keyWidth = getKeyWidth(); + CCoord offset = std::round((keyboard.getWidth() - (128.0 * keyWidth)) * 0.5); + if (offset > 0) { + keySwitches.extend(-offset, 0.0); + keyboard.extend(-offset, 0.0); + keyRanges.extend(-offset, 0.0); + octaves.extend(-offset, 0.0); + } + + // + if (pKeySwitches) + *pKeySwitches = keySwitches; + if (pKeyboard) + *pKeyboard = keyboard; + if (pKeyRanges) + *pKeyRanges = keyRanges; + if (pOctaves) + *pOctaves = octaves; +} + +void SPiano::draw(CDrawContext* dc) +{ + CRect bounds = getViewSize(); + + dc->setDrawMode(kAntiAliasing); + + SharedPointer path; + + path = owned(dc->createGraphicsPath()); + path->addRoundRect(bounds, 5.0); + dc->setFillColor(CColor(0xca, 0xca, 0xca)); + dc->drawGraphicsPath(path, CDrawContext::kPathFilled); + + // + CRect rectKeySwitches; + CRect rectKeyboard; + CRect rectKeyRanges; + CRect rectOctaves; + getZoneDimensions(&rectKeySwitches, &rectKeyboard, &rectKeyRanges, &rectOctaves); + + // + path = owned(dc->createGraphicsPath()); + path->addRoundRect(rectKeyboard, 1.0); + dc->setFillColor(CColor(0xff, 0xff, 0xff)); + dc->drawGraphicsPath(path, CDrawContext::kPathFilled); + + CCoord keyWidth = getKeyWidth(); + for (int key = 0; key < 128; ++key) { + CCoord keyX = rectKeyboard.left + key * keyWidth; + int key12 = key % 12; + if (key12 == 1 || key12 == 3 || + key12 == 6 || key12 == 8 || key12 == 10) + { + CRect blackRect(keyX, rectKeyboard.top + 2, keyX + keyWidth, rectKeyboard.bottom - 2); + path = owned(dc->createGraphicsPath()); + path->addRoundRect(blackRect, 1.0); + dc->setFillColor(CColor(0x02, 0x02, 0x02)); + dc->drawGraphicsPath(path, CDrawContext::kPathFilled); + } + if (key != 0 && key12 == 0) { + dc->setLineWidth(1.5); + dc->setFrameColor(CColor(0x63, 0x63, 0x63)); + dc->drawLine(CPoint(keyX, rectKeyboard.top), CPoint(keyX, rectKeyboard.bottom)); + } + if (key12 == 5) { + CCoord pad = rectKeyboard.getHeight() * 0.4; + dc->setLineWidth(1.0); + dc->setFrameColor(CColor(0x63, 0x63, 0x63)); + dc->drawLine(CPoint(keyX, rectKeyboard.top + pad), CPoint(keyX, rectKeyboard.bottom - pad)); + } + } + + // + + for (int rangeStart = 0; rangeStart < 128;) + { + if (!keyInRange_[rangeStart]) { + ++rangeStart; + } + else { + int rangeEnd = rangeStart; + while (rangeEnd + 1 < 128 && keyInRange_[rangeEnd + 1]) + ++rangeEnd; + + CCoord rangeStartX = rectKeyRanges.left + rangeStart * keyWidth; + CCoord rangeEndX = rectKeyRanges.left + (rangeEnd + 1.0) * keyWidth; + CRect rectRange(rangeStartX, rectKeyRanges.top, rangeEndX, rectKeyRanges.bottom); + + path = owned(dc->createGraphicsPath()); + path->addRoundRect(rectRange, 2.0); + dc->setFillColor(CColor(0x0f, 0x0f, 0x0f)); + dc->drawGraphicsPath(path, CDrawContext::kPathFilled); + + rangeStart = rangeEnd + 1; + } + } + + // + + for (int key = 0; key < 128; ++key) { + CCoord keyX = rectOctaves.left + key * keyWidth; + int key12 = key % 12; + if (key12 == 0) { + CRect textRect(keyX, rectOctaves.top, keyX + 12 * keyWidth, rectOctaves.bottom); + dc->setFont(font_); + dc->setFontColor(CColor(0x63, 0x63, 0x63)); + dc->drawString(std::to_string(key / 12 - 1).c_str(), textRect, kLeftText); + } + } + + // +} + +/// +SValueMenu::SValueMenu(const CRect& bounds, IControlListener* listener, int32_t tag) + : CParamDisplay(bounds), menuListener_(owned(new MenuListener(*this))) +{ + setListener(listener); + setTag(tag); +} + +CMenuItem* SValueMenu::addEntry(CMenuItem* item, float value, int32_t index) +{ + if (index < 0 || index > getNbEntries()) { + menuItems_.emplace_back(owned(item)); + menuItemValues_.emplace_back(value); + } + else + { + menuItems_.insert(menuItems_.begin() + index, owned(item)); + menuItemValues_.insert(menuItemValues_.begin() + index, value); + } + return item; +} + +CMenuItem* SValueMenu::addEntry(const UTF8String& title, float value, int32_t index, int32_t itemFlags) +{ + if (title == "-") + return addSeparator(index); + CMenuItem* item = new CMenuItem(title, nullptr, 0, nullptr, itemFlags); + return addEntry(item, value, index); +} + +CMenuItem* SValueMenu::addSeparator(int32_t index) +{ + CMenuItem* item = new CMenuItem("", nullptr, 0, nullptr, CMenuItem::kSeparator); + return addEntry(item, 0.0f, index); +} + +int32_t SValueMenu::getNbEntries() const +{ + return static_cast(menuItems_.size()); +} + +CMouseEventResult SValueMenu::onMouseDown(CPoint& where, const CButtonState& buttons) +{ + (void)where; + + if (buttons & (kLButton|kRButton|kApple)) { + CFrame* frame = getFrame(); + CRect bounds = getViewSize(); + + CPoint frameWhere = bounds.getBottomLeft(); + this->localToFrame(frameWhere); + + auto self = shared(this); + frame->doAfterEventProcessing([self, frameWhere]() { + if (CFrame* frame = self->getFrame()) { + SharedPointer menu = owned(new COptionMenu(CRect(), self->menuListener_, -1, nullptr, nullptr, COptionMenu::kPopupStyle)); + for (const SharedPointer& item : self->menuItems_) { + menu->addEntry(item); + item->remember(); // above call does not increment refcount + } + menu->popup(frame, frameWhere + CPoint(0.0, 1.0)); + } + }); + return kMouseDownEventHandledButDontNeedMovedOrUpEvents; + } + + return kMouseEventNotHandled; +} + +void SValueMenu::onItemClicked(int32_t index) +{ + float oldValue = getValue(); + setValue(menuItemValues_[index]); + if (getValue() != oldValue) + valueChanged(); } diff --git a/editor/src/editor/GUIComponents.h b/editor/src/editor/GUIComponents.h index c76c2e44..8c295f5e 100644 --- a/editor/src/editor/GUIComponents.h +++ b/editor/src/editor/GUIComponents.h @@ -5,22 +5,144 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once +#include #include "utility/vstgui_before.h" #include "vstgui/lib/controls/cslider.h" +#include "vstgui/lib/controls/cknob.h" +#include "vstgui/lib/controls/ctextlabel.h" +#include "vstgui/lib/controls/coptionmenu.h" +#include "vstgui/lib/cviewcontainer.h" #include "vstgui/lib/ccolor.h" +#include "vstgui/lib/dragging.h" #include "utility/vstgui_after.h" using namespace VSTGUI; -class SimpleSlider : public CSliderBase { +/// +class SBoxContainer : public CViewContainer { public: - SimpleSlider(const CRect& bounds, IControlListener* listener, int32_t tag); - void draw(CDrawContext* dc) override; + explicit SBoxContainer(const CRect& size); + virtual ~SBoxContainer() {} + void setCornerRadius(CCoord radius); + void setBackgroundColor(const CColor& color) override; + CColor getBackgroundColor() const override; - CLASS_METHODS(SimpleSlider, CSliderBase) +protected: + void drawRect(CDrawContext* dc, const CRect& updateRect) override; + +protected: + CCoord cornerRadius_ = 0.0; + CColor backgroundColor_; +}; + +/// +class STitleContainer : public SBoxContainer { +public: + explicit STitleContainer(const CRect& size, UTF8StringPtr text = nullptr); + ~STitleContainer() {} + + void setTitleFont(CFontRef font); + CFontRef getTitleFont() { return titleFont_; } + + void setTitleFontColor(CColor color); + CColor getTitleFontColor() const { return titleFontColor_; } + void setTitleBackgroundColor(CColor color); + CColor getTitleBackgroundColor() const { return titleBackgroundColor_; } + +protected: + void drawRect(CDrawContext* dc, const CRect& updateRect) override; private: - CColor _frame = CColor(0x00, 0x00, 0x00); - CColor _fill = CColor(0x00, 0x00, 0x00); + std::string text_; + CColor titleFontColor_; + CColor titleBackgroundColor_; + SharedPointer titleFont_; +}; + +/// +class SFileDropTarget : public IDropTarget, + public NonAtomicReferenceCounted { +public: + typedef std::function FileDropFunction; + void setFileDropFunction(FileDropFunction f); + +protected: + DragOperation onDragEnter(DragEventData data) override; + DragOperation onDragMove(DragEventData data) override; + void onDragLeave(DragEventData data) override; + bool onDrop(DragEventData data) override; + +private: + static bool isFileDrop(IDataPackage* package); + +private: + DragOperation op_ = DragOperation::None; + FileDropFunction dropFunction_; +}; + +/// +class SPiano : public CView { +public: + explicit SPiano(const CRect& bounds); + CFontRef getFont() const { return font_; } + void setFont(CFontRef font); + + void clearKeyRanges(); + void addKeyRange(int start, int end); + +protected: + static CCoord getKeyWidth(); + static CCoord getKeySwitchesHeight(); + static CCoord getKeyRangesHeight(); + CCoord getKeysHeight() const; + CCoord getOctavesHeight() const; + + void getZoneDimensions( + CRect* pKeySwitches, + CRect* pKeyboard, + CRect* pKeyRanges, + CRect* pOctaves); + + void draw(CDrawContext* dc) override; + +private: + SharedPointer font_; + std::bitset<128> keyInRange_; +}; + +/// +class SValueMenu : public CParamDisplay { +public: + explicit SValueMenu(const CRect& bounds, IControlListener* listener, int32_t tag); + CMenuItem* addEntry(CMenuItem* item, float value, int32_t index = -1); + CMenuItem* addEntry(const UTF8String& title, float value, int32_t index = -1, int32_t itemFlags = CMenuItem::kNoFlags); + CMenuItem* addSeparator(int32_t index = -1); + int32_t getNbEntries() const; + +protected: + CMouseEventResult onMouseDown(CPoint& where, const CButtonState& buttons); + +private: + class MenuListener; + + // + void onItemClicked(int32_t index); + + // + CMenuItemList menuItems_; + std::vector menuItemValues_; + SharedPointer menuListener_; + + // + class MenuListener : public IControlListener, public NonAtomicReferenceCounted { + public: + explicit MenuListener(SValueMenu& menu) : menu_(menu) {} + void valueChanged(CControl* control) override + { + menu_.onItemClicked(static_cast(control->getValue())); + } + private: + SValueMenu& menu_; + }; }; diff --git a/editor/src/editor/NativeHelpers.cpp b/editor/src/editor/NativeHelpers.cpp new file mode 100644 index 00000000..ab760d73 --- /dev/null +++ b/editor/src/editor/NativeHelpers.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "NativeHelpers.h" + +#if defined(_WIN32) +#include "ghc/fs_std.hpp" +#include +#include + +bool openFileInExternalEditor(const char *filename) +{ + std::wstring path = fs::u8path(filename).wstring(); + + SHELLEXECUTEINFOW info; + memset(&info, 0, sizeof(info)); + + info.cbSize = sizeof(info); + info.fMask = SEE_MASK_CLASSNAME; + info.lpVerb = L"open"; + info.lpFile = path.c_str(); + info.lpClass = L"txtfile"; + info.nShow = SW_SHOW; + + return ShellExecuteExW(&info); +} +#elif defined(__APPLE__) + // implemented in NativeHelpers.mm +#else +#include + +bool openFileInExternalEditor(const char *filename) +{ + GAppInfo* appinfo = g_app_info_get_default_for_type("text/plain", FALSE); + if (!appinfo) + return 1; + + GList* files = nullptr; + GFile* file = g_file_new_for_path(filename); + files = g_list_append(files, file); + gboolean success = g_app_info_launch(appinfo, files, nullptr, nullptr); + g_object_unref(file); + g_list_free(files); + g_object_unref(appinfo); + return success == TRUE; +} +#endif diff --git a/editor/src/editor/NativeHelpers.h b/editor/src/editor/NativeHelpers.h new file mode 100644 index 00000000..b9c29bb6 --- /dev/null +++ b/editor/src/editor/NativeHelpers.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once + +bool openFileInExternalEditor(const char *filename); diff --git a/editor/src/editor/NativeHelpers.mm b/editor/src/editor/NativeHelpers.mm new file mode 100644 index 00000000..9cb8cd28 --- /dev/null +++ b/editor/src/editor/NativeHelpers.mm @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "NativeHelpers.h" + +#if defined(__APPLE__) +#import +#import +#import + +bool openFileInExternalEditor(const char *fileNameUTF8) +{ + BOOL wasOpened = NO; + + NSURL* applicationURL = (__bridge_transfer NSURL*)LSCopyDefaultApplicationURLForContentType( + kUTTypePlainText, kLSRolesEditor, nil); + if (!applicationURL) + return false; + if ([applicationURL isFileURL]) { + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + NSString* fileName = [NSString stringWithUTF8String:fileNameUTF8]; + wasOpened = [workspace openFile:fileName withApplication:[applicationURL path]]; + } + + return wasOpened == YES; +} +#endif diff --git a/editor/src/editor/layout/main.hpp b/editor/src/editor/layout/main.hpp new file mode 100644 index 00000000..808e5a10 --- /dev/null +++ b/editor/src/editor/layout/main.hpp @@ -0,0 +1,167 @@ +/* This file is generated by the layout maker tool. */ +LogicalGroup* const view__0 = createLogicalGroup(CRect(0, 0, 800, 475), -1, "", kCenterText, 14); +mainView = view__0; +enterTheme(darkTheme); +LogicalGroup* const view__1 = createLogicalGroup(CRect(0, 0, 800, 110), -1, "", kCenterText, 14); +view__0->addView(view__1); +RoundedGroup* const view__2 = createRoundedGroup(CRect(5, 4, 105, 105), -1, "", kCenterText, 14); +view__1->addView(view__2); +SfizzMainButton* const view__3 = createSfizzMainButton(CRect(2, 2, 98, 98), kTagFirstChangePanel+kPanelGeneral, "", kCenterText, 14); +view__2->addView(view__3); +RoundedGroup* const view__4 = createRoundedGroup(CRect(110, 5, 490, 105), -1, "", kCenterText, 14); +view__1->addView(view__4); +Label* const view__5 = createLabel(CRect(15, 10, 55, 35), -1, "File:", kCenterText, 16); +view__4->addView(view__5); +Label* const view__6 = createLabel(CRect(15, 40, 55, 65), -1, "KS:", kCenterText, 16); +view__4->addView(view__6); +HLine* const view__7 = createHLine(CRect(10, 35, 365, 40), -1, "", kCenterText, 14); +view__4->addView(view__7); +HLine* const view__8 = createHLine(CRect(10, 65, 365, 70), -1, "", kCenterText, 14); +view__4->addView(view__8); +Label* const view__9 = createLabel(CRect(80, 10, 310, 35), -1, "DefaultInstrument.sfz", kCenterText, 20); +sfzFileLabel_ = view__9; +view__4->addView(view__9); +Label* const view__10 = createLabel(CRect(80, 40, 310, 65), -1, "Key switch", kCenterText, 20); +view__4->addView(view__10); +Label* const view__11 = createLabel(CRect(10, 70, 70, 95), -1, "Voices:", kRightText, 12); +view__4->addView(view__11); +LoadFileButton* const view__12 = createLoadFileButton(CRect(315, 10, 340, 35), kTagLoadSfzFile, "", kCenterText, 24); +view__4->addView(view__12); +EditFileButton* const view__13 = createEditFileButton(CRect(340, 10, 365, 35), kTagEditSfzFile, "", kCenterText, 24); +view__4->addView(view__13); +Label* const view__14 = createLabel(CRect(75, 70, 125, 95), -1, "", kCenterText, 12); +infoVoicesLabel_ = view__14; +view__4->addView(view__14); +Label* const view__15 = createLabel(CRect(130, 70, 190, 95), -1, "Max:", kRightText, 12); +view__4->addView(view__15); +Label* const view__16 = createLabel(CRect(195, 70, 245, 95), -1, "", kCenterText, 12); +numVoicesLabel_ = view__16; +view__4->addView(view__16); +Label* const view__17 = createLabel(CRect(250, 70, 310, 95), -1, "Memory:", kRightText, 12); +view__4->addView(view__17); +Label* const view__18 = createLabel(CRect(315, 70, 365, 95), -1, "", kCenterText, 12); +memoryLabel_ = view__18; +view__4->addView(view__18); +RoundedGroup* const view__19 = createRoundedGroup(CRect(495, 5, 595, 105), -1, "", kCenterText, 14); +view__1->addView(view__19); +LightButton* const view__20 = createLightButton(CRect(15, 37, 85, 62), kTagFirstChangePanel+kPanelSettings, "SETUP", kCenterText, 14); +view__19->addView(view__20); +LightButton* const view__21 = createLightButton(CRect(15, 10, 85, 35), kTagFirstChangePanel+kPanelControls, "CC", kCenterText, 14); +view__19->addView(view__21); +LightButton* const view__22 = createLightButton(CRect(15, 64, 85, 89), kTagFirstChangePanel+kPanelInfo, "INFO", kCenterText, 14); +view__19->addView(view__22); +RoundedGroup* const view__23 = createRoundedGroup(CRect(600, 5, 795, 105), -1, "", kCenterText, 14); +view__1->addView(view__23); +Knob48* const view__24 = createKnob48(CRect(15, 15, 63, 63), -1, "", kCenterText, 14); +view__23->addView(view__24); +view__24->setVisible(false); +ValueLabel* const view__25 = createValueLabel(CRect(10, 65, 70, 90), -1, "Center", kCenterText, 12); +view__23->addView(view__25); +view__25->setVisible(false); +Knob48* const view__26 = createKnob48(CRect(80, 15, 128, 63), kTagSetVolume, "", kCenterText, 14); +volumeSlider_ = view__26; +view__23->addView(view__26); +ValueLabel* const view__27 = createValueLabel(CRect(75, 65, 135, 90), -1, "0.0 dB", kCenterText, 12); +volumeLabel_ = view__27; +view__23->addView(view__27); +VMeter* const view__28 = createVMeter(CRect(145, 15, 180, 85), -1, "", kCenterText, 14); +view__23->addView(view__28); +enterTheme(defaultTheme); +LogicalGroup* const view__29 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelGeneral] = view__29; +view__0->addView(view__29); +view__29->setVisible(false); +RoundedGroup* const view__30 = createRoundedGroup(CRect(0, 0, 120, 285), -1, "", kCenterText, 14); +view__29->addView(view__30); +Label* const view__31 = createLabel(CRect(10, 10, 70, 35), -1, "Curves:", kLeftText, 12); +view__30->addView(view__31); +Label* const view__32 = createLabel(CRect(10, 35, 70, 60), -1, "Masters:", kLeftText, 12); +view__30->addView(view__32); +Label* const view__33 = createLabel(CRect(10, 60, 70, 85), -1, "Groups:", kLeftText, 12); +view__30->addView(view__33); +Label* const view__34 = createLabel(CRect(10, 85, 70, 110), -1, "Regions:", kLeftText, 12); +view__30->addView(view__34); +Label* const view__35 = createLabel(CRect(10, 110, 70, 135), -1, "Samples:", kLeftText, 12); +view__30->addView(view__35); +Label* const view__36 = createLabel(CRect(70, 10, 110, 35), -1, "0", kCenterText, 12); +infoCurvesLabel_ = view__36; +view__30->addView(view__36); +Label* const view__37 = createLabel(CRect(70, 35, 110, 60), -1, "0", kCenterText, 12); +infoMastersLabel_ = view__37; +view__30->addView(view__37); +Label* const view__38 = createLabel(CRect(70, 60, 110, 85), -1, "0", kCenterText, 12); +infoGroupsLabel_ = view__38; +view__30->addView(view__38); +Label* const view__39 = createLabel(CRect(70, 85, 110, 110), -1, "0", kCenterText, 12); +infoRegionsLabel_ = view__39; +view__30->addView(view__39); +Label* const view__40 = createLabel(CRect(70, 110, 110, 135), -1, "0", kCenterText, 12); +infoSamplesLabel_ = view__40; +view__30->addView(view__40); +LogicalGroup* const view__41 = createLogicalGroup(CRect(125, 0, 790, 280), -1, "", kCenterText, 14); +view__29->addView(view__41); +SfizzLargePicture* const view__42 = createSfizzLargePicture(CRect(130, 15, 530, 265), -1, "", kCenterText, 14); +view__41->addView(view__42); +LogicalGroup* const view__43 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelControls] = view__43; +view__0->addView(view__43); +view__43->setVisible(false); +RoundedGroup* const view__44 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14); +view__43->addView(view__44); +Label* const view__45 = createLabel(CRect(0, 0, 790, 285), -1, "Controls not available", kCenterText, 40); +view__44->addView(view__45); +LogicalGroup* const view__46 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelSettings] = view__46; +view__0->addView(view__46); +TitleGroup* const view__47 = createTitleGroup(CRect(255, 15, 535, 125), -1, "Engine", kCenterText, 12); +view__46->addView(view__47); +ValueMenu* const view__48 = createValueMenu(CRect(25, 60, 85, 85), kTagSetNumVoices, "", kCenterText, 12); +numVoicesSlider_ = view__48; +view__47->addView(view__48); +ValueLabel* const view__49 = createValueLabel(CRect(15, 20, 95, 45), -1, "Polyphony", kCenterText, 12); +view__47->addView(view__49); +ValueMenu* const view__50 = createValueMenu(CRect(110, 60, 170, 85), kTagSetOversampling, "", kCenterText, 12); +oversamplingSlider_ = view__50; +view__47->addView(view__50); +ValueLabel* const view__51 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oversampling", kCenterText, 12); +view__47->addView(view__51); +ValueLabel* const view__52 = createValueLabel(CRect(185, 20, 265, 45), -1, "Preload size", kCenterText, 12); +view__47->addView(view__52); +ValueMenu* const view__53 = createValueMenu(CRect(195, 60, 255, 85), kTagSetPreloadSize, "", kCenterText, 12); +preloadSizeSlider_ = view__53; +view__47->addView(view__53); +TitleGroup* const view__54 = createTitleGroup(CRect(200, 150, 590, 270), -1, "Tuning", kCenterText, 12); +view__46->addView(view__54); +ValueLabel* const view__55 = createValueLabel(CRect(125, 20, 205, 45), -1, "Root key", kCenterText, 12); +view__54->addView(view__55); +ValueMenu* const view__56 = createValueMenu(CRect(220, 60, 280, 85), kTagSetTuningFrequency, "", kCenterText, 12); +tuningFrequencySlider_ = view__56; +view__54->addView(view__56); +ValueLabel* const view__57 = createValueLabel(CRect(210, 20, 290, 45), -1, "Frequency", kCenterText, 12); +view__54->addView(view__57); +Knob48* const view__58 = createKnob48(CRect(310, 45, 358, 93), kTagSetStretchedTuning, "", kCenterText, 14); +stretchedTuningSlider_ = view__58; +view__54->addView(view__58); +ValueLabel* const view__59 = createValueLabel(CRect(295, 20, 375, 45), -1, "Stretch", kCenterText, 12); +view__54->addView(view__59); +ValueLabel* const view__60 = createValueLabel(CRect(20, 20, 120, 45), -1, "Scala file", kCenterText, 12); +view__54->addView(view__60); +ValueButton* const view__61 = createValueButton(CRect(20, 60, 120, 85), kTagLoadScalaFile, "DefaultScale", kCenterText, 12); +scalaFileButton_ = view__61; +view__54->addView(view__61); +ValueMenu* const view__62 = createValueMenu(CRect(135, 60, 170, 85), kTagSetScalaRootKey, "", kCenterText, 12); +scalaRootKeySlider_ = view__62; +view__54->addView(view__62); +ValueMenu* const view__63 = createValueMenu(CRect(170, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12); +scalaRootOctaveSlider_ = view__63; +view__54->addView(view__63); +LogicalGroup* const view__64 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelInfo] = view__64; +view__0->addView(view__64); +view__64->setVisible(false); +RoundedGroup* const view__65 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14); +view__64->addView(view__65); +Label* const view__66 = createLabel(CRect(0, 0, 790, 285), -1, "Informative text goes here", kCenterText, 40); +view__65->addView(view__66); +Piano* const view__67 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 14); +view__0->addView(view__67); diff --git a/editor/tools/layout-maker/LICENSE b/editor/tools/layout-maker/LICENSE new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/editor/tools/layout-maker/LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/editor/tools/layout-maker/README b/editor/tools/layout-maker/README new file mode 100644 index 00000000..e8402cc3 --- /dev/null +++ b/editor/tools/layout-maker/README @@ -0,0 +1,2 @@ +This purpose of this tool is to accept UI designs make with Fluid, the FLTK +design editor, and convert these designs to music plugin interfaces. diff --git a/editor/tools/layout-maker/sources/layout.h b/editor/tools/layout-maker/sources/layout.h new file mode 100644 index 00000000..35a1978e --- /dev/null +++ b/editor/tools/layout-maker/sources/layout.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include + +struct LayoutImage { + std::string filepath; + int x = 0; + int y = 0; + int w = 0; + int h = 0; +}; + +struct LayoutItem { + std::string id; + std::string classname; + std::string label; + int x = 0; + int y = 0; + int w = 0; + int h = 0; + std::string box; + std::string down_box; + int labelfont = 0; + int labelsize = 14; + std::string labeltype; + int textsize = 14; + int align = 0; + double value = 0; + double minimum = 0; + double maximum = 0; + double step = 0; + std::string type; + std::string callback; + LayoutImage image; + bool hidden = false; + std::string comment; + std::vector items; +}; + +struct Layout { + std::vector items; +}; diff --git a/editor/tools/layout-maker/sources/main.cpp b/editor/tools/layout-maker/sources/main.cpp new file mode 100644 index 00000000..06e5283b --- /dev/null +++ b/editor/tools/layout-maker/sources/main.cpp @@ -0,0 +1,136 @@ +#include "layout.h" +#include "reader.h" +#include +#include +#include +#include + +/// +typedef std::unordered_map Metadata; + +static Metadata metadata_from_comment(absl::string_view comment) +{ + Metadata md; + + while (!comment.empty()) { + absl::string_view line; + + size_t pos = comment.find_first_of("\r\n"); + if (pos != comment.npos) { + line = comment.substr(0, pos); + comment.remove_prefix(pos + 1); + } + else { + line = comment; + comment = {}; + } + + line = absl::StripAsciiWhitespace(line); + if (line.empty() || line[0] == '#') + continue; + + std::string key, value; + pos = line.find_first_of('='); + if (pos != comment.npos) { + key = std::string(line.substr(0, pos)); + value = std::string(line.substr(pos + 1)); + } + else + key = std::string(line); + + md.emplace(std::move(key), std::move(value)); + } + + return md; +} + +/// +static void codegen_item(int& idCounter, int parentId, int parentX, int parentY, const LayoutItem& item, absl::string_view oldTheme) +{ + const Metadata md = metadata_from_comment(item.comment); + + absl::string_view tag = "-1"; + absl::string_view newTheme; + + Metadata::const_iterator it; + it = md.find("tag"); + if (it != md.end()) + tag = it->second; + it = md.find("theme"); + if (it != md.end()) + newTheme = it->second; + + absl::string_view currentTheme = newTheme.empty() ? oldTheme : newTheme; + + int id = idCounter++; + int myX = item.x; + int myY = item.y; + if (parentId == -1) { + myX = 0; + myY = 0; + } + int relX = myX - parentX; + int relY = myY - parentY; + + //std::cout << "// Begin " << id << " " << item.classname << " {" << item.label << "}" << "\n"; + + if (!newTheme.empty()) + std::cout << "enterTheme(" << newTheme << ");\n"; + + absl::string_view label; + if (!item.label.empty() && item.labeltype != "NO_LABEL") + label = item.label; + + absl::string_view align = "kCenterText"; + if (item.align & 4) + align = "kLeftText"; + else if (item.align & 8) + align = "kRightText"; + + std::cout << item.classname << "* const view__" << id << " = create" << item.classname << "(CRect(" << relX << ", " << relY << ", " << (relX + item.w) << ", " << (relY + item.h) << "), " << tag << ", \"" << label << "\", " << align << ", " << item.labelsize << ");\n"; + + if (!item.id.empty()) + std::cout << item.id << " = view__" << id << ";\n"; + + if (parentId != -1) + std::cout << "view__" << parentId << "->addView(view__" << id << ");\n"; + + if (item.hidden) + std::cout << "view__" << id << "->setVisible(false);\n"; + + for (const LayoutItem& subItem : item.items) + codegen_item(idCounter, id, myX, myY, subItem, currentTheme); + + if (!newTheme.empty()) + std::cout << "enterTheme(" << oldTheme << ");\n"; + + //std::cout << "// End " << id << " " << item.classname << " {" << item.label << "}" << "\n"; +} + +static void codegen_layout(const LayoutItem& item) +{ + int idCounter = 0; + codegen_item(idCounter, -1, 0, 0, item, "defaultTheme"); +} + +/// +int main(int argc, char *argv[]) +{ + if (argc != 2) { + std::cerr << "Please indicate a fluid design file.\n"; + return 1; + } + + Layout layout = read_file_layout(argv[1]); + + if (layout.items.size() != 1) { + std::cerr << "There must be exactly 1 top level component."; + return 1; + } + + std::cout << "/* This file is generated by the layout maker tool. */\n"; + + codegen_layout(layout.items[0]); + + return 0; +} diff --git a/editor/tools/layout-maker/sources/reader.cpp b/editor/tools/layout-maker/sources/reader.cpp new file mode 100644 index 00000000..788f93d7 --- /dev/null +++ b/editor/tools/layout-maker/sources/reader.cpp @@ -0,0 +1,359 @@ +#include "reader.h" +#include +#include +#include + +typedef std::vector TokenList; +static bool read_file_tokens(const char *filename, TokenList &tokens); +static Layout read_tokens_layout(TokenList::iterator &tok_it, TokenList::iterator tok_end); + +Layout read_file_layout(const char *filename) +{ + std::vector tokens; + if (!read_file_tokens(filename, tokens)) + throw std::runtime_error("Cannot read fluid design file."); + + TokenList::iterator tok_it = tokens.begin(); + TokenList::iterator tok_end = tokens.end(); + return read_tokens_layout(tok_it, tok_end); +} + +static std::string consume_next_token(TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + if (tok_it == tok_end) + throw file_format_error("Premature end of tokens"); + return *tok_it++; +} + +static bool try_consume_next_token(const char *text, TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + if (tok_it == tok_end) + return false; + + if (*tok_it != text) + return false; + + ++tok_it; + return true; +} + +static void ensure_next_token(const char *text, TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + std::string tok = consume_next_token(tok_it, tok_end); + if (tok != text) + throw file_format_error("Unexpected token: " + tok); +} + +static std::string consume_enclosed_string(TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + ensure_next_token("{", tok_it, tok_end); + unsigned depth = 1; + + std::string text; + for (;;) { + std::string part = consume_next_token(tok_it, tok_end); + if (part == "}") { + if (--depth == 0) + return text; + } + else if (part == "{") + ++depth; + if (!text.empty()) + text.push_back(' '); + text.append(part); + } + + return text; +} + +static std::string consume_any_string(TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + if (tok_it != tok_end && *tok_it == "{") + return consume_enclosed_string(tok_it, tok_end); + else + return consume_next_token(tok_it, tok_end); +} + +static int consume_int_token(TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + std::string text = consume_next_token(tok_it, tok_end); + return std::stoi(text); +} + +static int consume_real_token(TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + std::string text = consume_next_token(tok_it, tok_end); + return std::stod(text); +} + +static void consume_image_properties(LayoutImage &image, TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + for (bool have = true; have;) { + if (try_consume_next_token("xywh", tok_it, tok_end)) { + ensure_next_token("{", tok_it, tok_end); + image.x = consume_int_token(tok_it, tok_end); + image.y = consume_int_token(tok_it, tok_end); + image.w = consume_int_token(tok_it, tok_end); + image.h = consume_int_token(tok_it, tok_end); + ensure_next_token("}", tok_it, tok_end); + } + else + have = false; + } +} + +// static void consume_layout_item_properties(LayoutItem &item, TokenList::iterator &tok_it, TokenList::iterator tok_end) +// { +// ensure_next_token("{", tok_it, tok_end); +// for (std::string text; (text = consume_next_token(tok_it, tok_end)) != "}";) { +// if (text == "open" || text == "selected") +// ; // skip +// else if (text == "label") +// item.label = consume_any_string(tok_it, tok_end); +// else if (text == "xywh") { +// ensure_next_token("{", tok_it, tok_end); +// item.x = consume_int_token(tok_it, tok_end); +// item.y = consume_int_token(tok_it, tok_end); +// item.w = consume_int_token(tok_it, tok_end); +// item.h = consume_int_token(tok_it, tok_end); +// ensure_next_token("}", tok_it, tok_end); +// } +// else if (text == "box") +// item.box = consume_next_token(tok_it, tok_end); +// else if (text == "labelfont") +// item.labelfont = consume_int_token(tok_it, tok_end); +// else if (text == "labelsize") +// item.labelsize = consume_int_token(tok_it, tok_end); +// else if (text == "labeltype") +// item.labeltype = consume_any_string(tok_it, tok_end); +// else if (text == "align") +// item.align = consume_int_token(tok_it, tok_end); +// else if (text == "type") +// item.type = consume_any_string(tok_it, tok_end); +// else if (text == "callback") +// item.callback = consume_any_string(tok_it, tok_end); +// else if (text == "class") +// item.classname = consume_any_string(tok_it, tok_end); +// else if (text == "minimum") +// item.minimum = consume_real_token(tok_it, tok_end); +// else if (text == "maximum") +// item.maximum = consume_real_token(tok_it, tok_end); +// else if (text == "step") +// item.step = consume_real_token(tok_it, tok_end); +// else if (text == "image") { +// item.image.filepath = consume_any_string(tok_it, tok_end); +// consume_image_properties(item.image, tok_it, tok_end); +// } +// } +// } + +static void consume_layout_item_properties(LayoutItem &item, TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + ensure_next_token("{", tok_it, tok_end); + for (bool have = true; have;) { + if (try_consume_next_token("open", tok_it, tok_end)) + ; // skip + else if (try_consume_next_token("selected", tok_it, tok_end)) + ; // skip + else if (try_consume_next_token("label", tok_it, tok_end)) + item.label = consume_any_string(tok_it, tok_end); + else if (try_consume_next_token("xywh", tok_it, tok_end)) { + ensure_next_token("{", tok_it, tok_end); + item.x = consume_int_token(tok_it, tok_end); + item.y = consume_int_token(tok_it, tok_end); + item.w = consume_int_token(tok_it, tok_end); + item.h = consume_int_token(tok_it, tok_end); + ensure_next_token("}", tok_it, tok_end); + } + else if (try_consume_next_token("box", tok_it, tok_end)) + item.box = consume_next_token(tok_it, tok_end); + else if (try_consume_next_token("down_box", tok_it, tok_end)) + item.down_box = consume_next_token(tok_it, tok_end); + else if (try_consume_next_token("labelfont", tok_it, tok_end)) + item.labelfont = consume_int_token(tok_it, tok_end); + else if (try_consume_next_token("labelsize", tok_it, tok_end)) + item.labelsize = consume_int_token(tok_it, tok_end); + else if (try_consume_next_token("labeltype", tok_it, tok_end)) + item.labeltype = consume_any_string(tok_it, tok_end); + else if (try_consume_next_token("textsize", tok_it, tok_end)) + item.textsize = consume_int_token(tok_it, tok_end); + else if (try_consume_next_token("align", tok_it, tok_end)) + item.align = consume_int_token(tok_it, tok_end); + else if (try_consume_next_token("type", tok_it, tok_end)) + item.type = consume_any_string(tok_it, tok_end); + else if (try_consume_next_token("callback", tok_it, tok_end)) + item.callback = consume_any_string(tok_it, tok_end); + else if (try_consume_next_token("class", tok_it, tok_end)) + item.classname = consume_any_string(tok_it, tok_end); + else if (try_consume_next_token("value", tok_it, tok_end)) + item.value = consume_real_token(tok_it, tok_end); + else if (try_consume_next_token("minimum", tok_it, tok_end)) + item.minimum = consume_real_token(tok_it, tok_end); + else if (try_consume_next_token("maximum", tok_it, tok_end)) + item.maximum = consume_real_token(tok_it, tok_end); + else if (try_consume_next_token("step", tok_it, tok_end)) + item.step = consume_real_token(tok_it, tok_end); + else if (try_consume_next_token("image", tok_it, tok_end)) + item.image.filepath = consume_any_string(tok_it, tok_end); + else if (try_consume_next_token("hide", tok_it, tok_end)) + item.hidden = true; + else if (try_consume_next_token("visible", tok_it, tok_end)) + /* skip */; + else if (try_consume_next_token("comment", tok_it, tok_end)) + item.comment = consume_any_string(tok_it, tok_end); + else + have = false; + } + ensure_next_token("}", tok_it, tok_end); +} + +static LayoutItem consume_layout_item(const std::string &classname, TokenList::iterator &tok_it, TokenList::iterator tok_end, bool anonymous = false) +{ + LayoutItem item; + item.classname = classname; + if (!anonymous) + item.id = consume_any_string(tok_it, tok_end); + consume_layout_item_properties(item, tok_it, tok_end); + if (tok_it != tok_end && *tok_it == "{") { + consume_next_token(tok_it, tok_end); + for (std::string text; (text = consume_next_token(tok_it, tok_end)) != "}";) { + if (text == "decl") { + consume_any_string(tok_it, tok_end); + consume_any_string(tok_it, tok_end); + } + else if (text == "Function") { + consume_any_string(tok_it, tok_end); + consume_any_string(tok_it, tok_end); + consume_any_string(tok_it, tok_end); + } + else + item.items.push_back(consume_layout_item(text, tok_it, tok_end)); + } + } + return item; +} + +static Layout read_tokens_layout(TokenList::iterator &tok_it, TokenList::iterator tok_end) +{ + Layout layout; + + std::string version_name; + std::string header_name; + std::string code_name; + + while (tok_it != tok_end) { + std::string key = consume_next_token(tok_it, tok_end); + + if (key == "version") + version_name = consume_next_token(tok_it, tok_end); + else if (key == "header_name") { + ensure_next_token("{", tok_it, tok_end); + header_name = consume_next_token(tok_it, tok_end); + ensure_next_token("}", tok_it, tok_end); + } + else if (key == "code_name") { + ensure_next_token("{", tok_it, tok_end); + code_name = consume_next_token(tok_it, tok_end); + ensure_next_token("}", tok_it, tok_end); + } + else if (key == "decl") { + consume_any_string(tok_it, tok_end); + consume_any_string(tok_it, tok_end); + } + else if (key == "widget_class") { + key = consume_next_token(tok_it, tok_end); + layout.items.push_back(consume_layout_item(key, tok_it, tok_end, true)); + layout.items.back().id = key; + } + else + layout.items.push_back(consume_layout_item(key, tok_it, tok_end)); + } + + return layout; +} + +/// +class tokenizer { +public: + tokenizer( + absl::string_view text, + absl::string_view dropped_delims, + absl::string_view kept_delims); + + absl::string_view next(); + +private: + absl::string_view text_; + absl::string_view dropped_delims_; + absl::string_view kept_delims_; +}; + +tokenizer::tokenizer( + absl::string_view text, + absl::string_view dropped_delims, + absl::string_view kept_delims) + : text_(text), dropped_delims_(dropped_delims), kept_delims_(kept_delims) +{ +} + +absl::string_view tokenizer::next() +{ + auto is_dropped = [this](char c) -> bool { + return dropped_delims_.find(c) != dropped_delims_.npos; + }; + auto is_kept = [this](char c) -> bool { + return kept_delims_.find(c) != kept_delims_.npos; + }; + auto is_delim = [this](char c) -> bool { + return dropped_delims_.find(c) != dropped_delims_.npos || + kept_delims_.find(c) != kept_delims_.npos; + }; + + absl::string_view text = text_; + + while (!text.empty() && is_dropped(text[0])) + text.remove_prefix(1); + + if (text.empty()) + return {}; + + size_t pos; + { + auto it = std::find_if(text.begin(), text.end(), is_delim); + if (it == text.end()) + pos = text.size(); + else { + pos = std::distance(text.begin(), it); + pos += is_kept(text[0]); + } + } + + absl::string_view token = text.substr(0, pos); + text_ = text.substr(pos); + return token; +} + +/// +static bool read_file_tokens(const char *filename, TokenList &tokens) +{ + std::ifstream stream(filename); + std::string line; + + std::string text; + while (std::getline(stream, line)) { + if (!line.empty() && line[0] != '#') { + text.append(line); + text.push_back('\n'); + } + } + + if (stream.bad()) + return false; + + tokenizer tok(text, " \t\r\n", "{}"); + absl::string_view token; + while (!(token = tok.next()).empty()) + tokens.emplace_back(token); + + return !stream.bad(); +} diff --git a/editor/tools/layout-maker/sources/reader.h b/editor/tools/layout-maker/sources/reader.h new file mode 100644 index 00000000..589bd307 --- /dev/null +++ b/editor/tools/layout-maker/sources/reader.h @@ -0,0 +1,13 @@ +#pragma once +#include "layout.h" +#include +#include + +Layout read_file_layout(const char *filename); + +/// +struct file_format_error : public std::runtime_error { +public: + explicit file_format_error(const std::string &reason = "Format error") + : runtime_error(reason) {} +}; diff --git a/lv2/CMakeLists.txt b/lv2/CMakeLists.txt index 3322d3d1..a888ca81 100644 --- a/lv2/CMakeLists.txt +++ b/lv2/CMakeLists.txt @@ -98,10 +98,9 @@ endforeach() if (SFIZZ_LV2_UI) execute_process ( COMMAND "${CMAKE_COMMAND}" -E make_directory "${PROJECT_BINARY_DIR}/Contents/Resources") - foreach(res ${EDITOR_RESOURCES}) - file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/../editor/resources/${res}" - DESTINATION "${PROJECT_BINARY_DIR}/Contents/Resources") - endforeach() + copy_editor_resources( + "${CMAKE_CURRENT_SOURCE_DIR}/../editor/resources" + "${PROJECT_BINARY_DIR}/Contents/Resources") endif() # Installation diff --git a/scripts/innosetup.iss.in b/scripts/innosetup.iss.in index b287de6a..7c845a4b 100644 --- a/scripts/innosetup.iss.in +++ b/scripts/innosetup.iss.in @@ -50,9 +50,7 @@ Name: "vst3"; Description: "VST3 plugin"; Types: full custom; [Files] Source: "sfizz.lv2\Contents\Binary\sfizz.dll"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Binary"; Flags: ignoreversion Source: "sfizz.lv2\Contents\Binary\sfizz_ui.dll"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Binary"; Flags: ignoreversion -Source: "sfizz.lv2\Contents\Resources\DefaultInstrument.sfz"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Resources" -Source: "sfizz.lv2\Contents\Resources\DefaultScale.scl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Resources" -Source: "sfizz.lv2\Contents\Resources\logo.png"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Resources" +Source: "sfizz.lv2\Contents\Resources\*"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Resources" Source: "sfizz.lv2\manifest.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2" Source: "sfizz.lv2\sfizz.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2" Source: "sfizz.lv2\sfizz_ui.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2" @@ -60,7 +58,7 @@ Source: "sfizz.lv2\lgpl-3.0.txt"; Components: main; DestDir: "{app}" Source: "sfizz.lv2\LICENSE.md"; Components: main; DestDir: "{app}" Source: "sfizz.vst3\desktop.ini"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3" Source: "sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win\sfizz.vst3"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win"; Flags: ignoreversion -Source: "sfizz.vst3\Contents\Resources\logo.png"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\Resources" +Source: "sfizz.vst3\Contents\Resources\*"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\Resources" Source: "sfizz.vst3\Plugin.ico"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3" Source: "sfizz.vst3\gpl-3.0.txt"; Components: main; DestDir: "{app}" ;Source: "setup\vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index c4b3538e..bbd2ff2e 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -68,10 +68,9 @@ endif() # Create the bundle (see "VST 3 Locations / Format") execute_process ( COMMAND "${CMAKE_COMMAND}" -E make_directory "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") -foreach(res ${EDITOR_RESOURCES}) - file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/../editor/resources/${res}" - DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") -endforeach() +copy_editor_resources( + "${CMAKE_CURRENT_SOURCE_DIR}/../editor/resources" + "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") if(WIN32) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES SUFFIX ".vst3"