Browse Source

Merge branch 'master' of https://github.com/VCVRack/Rack

tags/v0.5.0
Andrew Belt 7 years ago
parent
commit
205c04d4fe
41 changed files with 800 additions and 403 deletions
  1. +9
    -7
      Makefile
  2. +1
    -1
      ext/osdialog
  3. +29
    -28
      include/app.hpp
  4. +15
    -4
      include/components.hpp
  5. +4
    -4
      include/engine.hpp
  6. +107
    -0
      include/events.hpp
  7. +77
    -86
      include/widgets.hpp
  8. BIN
      installer-banner.bmp
  9. +88
    -0
      installer.nsi
  10. +71
    -0
      res/ComponentLibrary/LEDBezel.svg
  11. +7
    -6
      src/app/Knob.cpp
  12. +2
    -2
      src/app/LightWidget.cpp
  13. +36
    -24
      src/app/ModuleWidget.cpp
  14. +5
    -3
      src/app/ParamWidget.cpp
  15. +6
    -6
      src/app/PluginManagerWidget.cpp
  16. +16
    -13
      src/app/Port.cpp
  17. +24
    -8
      src/app/RackScene.cpp
  18. +16
    -10
      src/app/RackWidget.cpp
  19. +2
    -2
      src/app/SVGKnob.cpp
  20. +2
    -2
      src/app/SVGSlider.cpp
  21. +2
    -2
      src/app/SVGSwitch.cpp
  22. +11
    -11
      src/app/Toolbar.cpp
  23. +13
    -3
      src/asset.cpp
  24. +6
    -6
      src/core/AudioInterface.cpp
  25. +8
    -7
      src/core/Blank.cpp
  26. +2
    -2
      src/engine.cpp
  27. +89
    -23
      src/gui.cpp
  28. +9
    -3
      src/main.cpp
  29. +8
    -7
      src/widgets/Button.cpp
  30. +1
    -1
      src/widgets/FramebufferWidget.cpp
  31. +4
    -4
      src/widgets/Menu.cpp
  32. +5
    -4
      src/widgets/MenuItem.cpp
  33. +11
    -7
      src/widgets/MenuOverlay.cpp
  34. +4
    -2
      src/widgets/QuantityWidget.cpp
  35. +6
    -5
      src/widgets/RadioButton.cpp
  36. +5
    -5
      src/widgets/ScrollBar.cpp
  37. +3
    -3
      src/widgets/ScrollWidget.cpp
  38. +12
    -8
      src/widgets/Slider.cpp
  39. +16
    -17
      src/widgets/TextField.cpp
  40. +39
    -65
      src/widgets/Widget.cpp
  41. +29
    -12
      src/widgets/ZoomWidget.cpp

+ 9
- 7
Makefile View File

@@ -116,6 +116,9 @@ ifeq ($(ARCH), mac)

mkdir -p $(BUNDLE)/Contents/Resources/plugins
cp -R plugins/Fundamental/dist/Fundamental $(BUNDLE)/Contents/Resources/plugins
# Make DMG image
cd dist && ln -s /Applications Applications
cd dist && hdiutil create -srcfolder . -volname Rack -ov -format UDZO Rack-$(VERSION)-$(ARCH).dmg
endif
ifeq ($(ARCH), win)
mkdir -p dist/Rack
@@ -135,6 +138,11 @@ ifeq ($(ARCH), win)
cp dep/bin/portaudio_x64.dll dist/Rack/
mkdir -p dist/Rack/plugins
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
# Make ZIP
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
# Make NSIS installer
makensis installer.nsi
mv Rack-setup.exe dist/Rack-$(VERSION)-$(ARCH).exe
endif
ifeq ($(ARCH), lin)
mkdir -p dist/Rack
@@ -149,16 +157,10 @@ ifeq ($(ARCH), lin)
cp dep/lib/libportaudio.so.2 dist/Rack/
cp dep/lib/librtmidi.so.4 dist/Rack/
mkdir -p dist/Rack/plugins
# Make ZIP
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
endif

ifeq ($(ARCH), mac)
cd dist && ln -s /Applications Applications
cd dist && hdiutil create -srcfolder . -volname Rack -ov -format UDZO Rack-$(VERSION)-$(ARCH).dmg
else
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
endif


# Plugin helpers



+ 1
- 1
ext/osdialog

@@ -1 +1 @@
Subproject commit 4dd22f56d6b733c8de13d6e8b12f13390aa5782e
Subproject commit 015d020615e8169d2f227ad385c5f2aa1e091fd1

+ 29
- 28
include/app.hpp View File

@@ -67,12 +67,12 @@ struct ModuleWidget : OpaqueWidget {
void draw(NVGcontext *vg) override;

Vec dragPos;
Widget *onMouseMove(Vec pos, Vec mouseRel) override;
Widget *onHoverKey(Vec pos, int key) override;
void onDragStart() override;
void onDragMove(Vec mouseRel) override;
void onDragEnd() override;
void onMouseDownOpaque(int button) override;
void onMouseDown(EventMouseDown &e) override;
void onMouseMove(EventMouseMove &e) override;
void onHoverKey(EventHoverKey &e) override;
void onDragStart(EventDragStart &e) override;
void onDragEnd(EventDragEnd &e) override;
void onDragMove(EventDragMove &e) override;
};

struct ValueLight;
@@ -144,9 +144,9 @@ struct RackWidget : OpaqueWidget {
void step() override;
void draw(NVGcontext *vg) override;

Widget *onMouseMove(Vec pos, Vec mouseRel) override;
void onMouseDownOpaque(int button) override;
void onZoom() override;
void onMouseMove(EventMouseMove &e) override;
void onMouseDown(EventMouseDown &e) override;
void onZoom(EventZoom &e) override;
};

struct RackRail : TransparentWidget {
@@ -184,8 +184,8 @@ struct ParamWidget : OpaqueWidget, QuantityWidget {
json_t *toJson();
void fromJson(json_t *rootJ);
virtual void randomize();
void onMouseDownOpaque(int button) override;
void onChange() override;
void onMouseDown(EventMouseDown &e) override;
void onChange(EventChange &e) override;
};

/** Implements vertical dragging behavior for ParamWidgets */
@@ -193,11 +193,11 @@ struct Knob : ParamWidget {
/** Snap to nearest integer while dragging */
bool snap = false;
float dragValue;
void onDragStart() override;
void onDragMove(Vec mouseRel) override;
void onDragEnd() override;
void onDragStart(EventDragStart &e) override;
void onDragMove(EventDragMove &e) override;
void onDragEnd(EventDragEnd &e) override;
/** Tell engine to smoothly vary this parameter */
void onChange() override;
void onChange(EventChange &e) override;
};

struct SpriteKnob : virtual Knob, SpriteWidget {
@@ -216,7 +216,7 @@ struct SVGKnob : virtual Knob, FramebufferWidget {
SVGKnob();
void setSVG(std::shared_ptr<SVG> svg);
void step() override;
void onChange() override;
void onChange(EventChange &e) override;
};

struct SVGSlider : Knob, FramebufferWidget {
@@ -228,7 +228,7 @@ struct SVGSlider : Knob, FramebufferWidget {

SVGSlider();
void step() override;
void onChange() override;
void onChange(EventChange &e) override;
};

struct Switch : ParamWidget {
@@ -243,12 +243,12 @@ struct SVGSwitch : virtual Switch, FramebufferWidget {
/** Adds an SVG file to represent the next switch position */
void addFrame(std::shared_ptr<SVG> svg);
void step() override;
void onChange() override;
void onChange(EventChange &e) override;
};

/** A switch that cycles through each mechanical position */
struct ToggleSwitch : virtual Switch {
void onDragStart() override {
void onDragStart(EventDragStart &e) override {
// Cycle through values
// e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3.
if (value >= maxValue)
@@ -262,10 +262,10 @@ struct ToggleSwitch : virtual Switch {
struct MomentarySwitch : virtual Switch {
/** Don't randomize state */
void randomize() override {}
void onDragStart() override {
void onDragStart(EventDragStart &e) override {
setValue(maxValue);
}
void onDragEnd() override {
void onDragEnd(EventDragEnd &e) override {
setValue(minValue);
}
};
@@ -286,12 +286,12 @@ struct Port : OpaqueWidget {

~Port();
void draw(NVGcontext *vg) override;
void onMouseDownOpaque(int button) override;
void onDragEnd() override;
void onDragStart() override;
void onDragDrop(Widget *origin) override;
void onDragEnter(Widget *origin) override;
void onDragLeave(Widget *origin) override;
void onMouseDown(EventMouseDown &e) override;
void onDragStart(EventDragStart &e) override;
void onDragEnd(EventDragEnd &e) override;
void onDragDrop(EventDragDrop &e) override;
void onDragEnter(EventDragEnter &e) override;
void onDragLeave(EventDragEnter &e) override;
};

struct SVGPort : Port, FramebufferWidget {
@@ -365,7 +365,8 @@ struct RackScene : Scene {
RackScene();
void step() override;
void draw(NVGcontext *vg) override;
Widget *onHoverKey(Vec pos, int key) override;
void onHoverKey(EventHoverKey &e) override;
void onPathDrop(EventPathDrop &e) override;
};

////////////////////


+ 15
- 4
include/components.hpp View File

@@ -409,34 +409,39 @@ struct RedGreenBlueLight : ColorLightWidget {
};


/** Based on the size of 5mm LEDs */
template <typename BASE>
struct LargeLight : BASE {
LargeLight() {
this->box.size = Vec(20, 20);
this->box.size = mm2px(Vec(5.179, 5.179));
}
};

/** Based on the size of 3mm LEDs */
template <typename BASE>
struct MediumLight : BASE {
MediumLight() {
this->box.size = Vec(12, 12);
this->box.size = mm2px(Vec(3.176, 3.176));
}
};

/** Based on the size of 2mm LEDs */
template <typename BASE>
struct SmallLight : BASE {
SmallLight() {
this->box.size = Vec(8, 8);
this->box.size = mm2px(Vec(2.176, 2.176));
}
};

/** Based on the size of 1mm LEDs */
template <typename BASE>
struct TinyLight : BASE {
TinyLight() {
this->box.size = Vec(5, 5);
this->box.size = mm2px(Vec(1.088, 1.088));
}
};


////////////////////
// Switches and Buttons
////////////////////
@@ -497,6 +502,12 @@ struct PB61303 : SVGSwitch, MomentarySwitch {
}
};

struct LEDBezel : SVGSwitch, MomentarySwitch {
LEDBezel() {
addFrame(SVG::load(assetGlobal("res/ComponentLibrary/LEDBezel.svg")));
}
};


////////////////////
// Misc


+ 4
- 4
include/engine.hpp View File

@@ -63,15 +63,15 @@ struct Module {
virtual void step() {}
virtual void onSampleRateChange() {}

/** Override these to store extra internal data in the "data" property */
virtual json_t *toJson() { return NULL; }
virtual void fromJson(json_t *root) {}

/** Override these to implement spacial behavior when user clicks Initialize and Randomize */
virtual void reset() {}
virtual void randomize() {}
/** Deprecated */
virtual void initialize() final {}

/** Override these to store extra internal data in the "data" property */
virtual json_t *toJson() { return NULL; }
virtual void fromJson(json_t *root) {}
};

struct Wire {


+ 107
- 0
include/events.hpp View File

@@ -0,0 +1,107 @@
#pragma once
#include <list>

#include "math.hpp"


namespace rack {


struct Widget;


struct Event {
/** Set this to true to signal that no other widgets should receive the event */
bool consumed = false;
};

struct EventPosition : Event {
Vec pos;
};

///////////

struct EventMouseDown : EventPosition {
int button;
/** The widget which responded to the click. Set it to `this` if consumed. */
Widget *target = NULL;
};

struct EventMouseUp : EventPosition {
int button;
Widget *target = NULL;
};

struct EventMouseMove : EventPosition {
Vec mouseRel;
Widget *target = NULL;
};

struct EventHoverKey : EventPosition {
int key;
Widget *target = NULL;
};

struct EventMouseEnter : Event {
};

struct EventMouseLeave : Event {
};

struct EventFocus : Event {
};

struct EventDefocus : Event {
};

struct EventText : Event {
int codepoint;
};

struct EventKey : Event {
int key;
};

struct EventScroll : EventPosition {
Vec scrollRel;
};

/////////////

struct EventDragStart : Event {
};

struct EventDragEnd : Event {
};

struct EventDragMove : Event {
Vec mouseRel;
};

struct EventDragEnter : Event {
Widget *origin = NULL;
};

struct EventDragLeave : Event {
Widget *origin = NULL;
};

struct EventDragDrop : Event {
Widget *origin = NULL;
};

struct EventPathDrop : EventPosition {
std::list<std::string> paths;
};

struct EventAction : Event {
};

struct EventChange : Event {
};

struct EventZoom : Event {
};


} // namespace rack

+ 77
- 86
include/widgets.hpp View File

@@ -8,6 +8,7 @@

#include "math.hpp"
#include "util.hpp"
#include "events.hpp"


#define SVG_DPI 75.0
@@ -122,35 +123,36 @@ struct Widget {
Return `this` to accept the event.
Return NULL to reject the event and pass it to the widget behind this one.
*/
virtual Widget *onMouseDown(Vec pos, int button);
virtual Widget *onMouseUp(Vec pos, int button);
virtual void onMouseDown(EventMouseDown &e);
virtual void onMouseUp(EventMouseUp &e);
/** Called on every frame, even if mouseRel = Vec(0, 0) */
virtual Widget *onMouseMove(Vec pos, Vec mouseRel);
virtual Widget *onHoverKey(Vec pos, int key);
virtual void onMouseMove(EventMouseMove &e);
virtual void onHoverKey(EventHoverKey &e);
/** Called when this widget begins responding to `onMouseMove` events */
virtual void onMouseEnter() {}
virtual void onMouseEnter(EventMouseEnter &e) {}
/** Called when another widget begins responding to `onMouseMove` events */
virtual void onMouseLeave() {}
virtual bool onFocus() {return false;}
virtual void onDefocus() {}
virtual bool onFocusText(int codepoint) {return false;}
virtual bool onFocusKey(int key) {return false;}
virtual Widget *onScroll(Vec pos, Vec scrollRel);
virtual void onMouseLeave(EventMouseLeave &e) {}
virtual void onFocus(EventFocus &e) {}
virtual void onDefocus(EventDefocus &e) {}
virtual void onText(EventText &e) {}
virtual void onKey(EventKey &e) {}
virtual void onScroll(EventScroll &e);

/** Called when a widget responds to `onMouseDown` for a left button press */
virtual void onDragStart() {}
virtual void onDragStart(EventDragStart &e) {}
/** Called when the left button is released and this widget is being dragged */
virtual void onDragEnd() {}
virtual void onDragEnd(EventDragEnd &e) {}
/** Called when a widget responds to `onMouseMove` and is being dragged */
virtual void onDragMove(Vec mouseRel) {}
virtual void onDragMove(EventDragMove &e) {}
/** Called when a widget responds to `onMouseUp` for a left button release and a widget is being dragged */
virtual void onDragEnter(Widget *origin) {}
virtual void onDragLeave(Widget *origin) {}
virtual void onDragDrop(Widget *origin) {}

virtual void onAction() {}
virtual void onChange() {}
virtual void onZoom();
virtual void onDragEnter(EventDragEnter &e) {}
virtual void onDragLeave(EventDragEnter &e) {}
virtual void onDragDrop(EventDragDrop &e) {}
virtual void onPathDrop(EventPathDrop &e);

virtual void onAction(EventAction &e) {}
virtual void onChange(EventChange &e) {}
virtual void onZoom(EventZoom &e);
};

struct TransformWidget : Widget {
@@ -170,11 +172,11 @@ struct ZoomWidget : Widget {
Rect getViewport(Rect r) override;
void setZoom(float zoom);
void draw(NVGcontext *vg) override;
Widget *onMouseDown(Vec pos, int button) override;
Widget *onMouseUp(Vec pos, int button) override;
Widget *onMouseMove(Vec pos, Vec mouseRel) override;
Widget *onHoverKey(Vec pos, int key) override;
Widget *onScroll(Vec pos, Vec scrollRel) override;
void onMouseDown(EventMouseDown &e) override;
void onMouseUp(EventMouseUp &e) override;
void onMouseMove(EventMouseMove &e) override;
void onHoverKey(EventHoverKey &e) override;
void onScroll(EventScroll &e) override;
};

////////////////////
@@ -183,47 +185,36 @@ struct ZoomWidget : Widget {

/** Widget that does not respond to events */
struct TransparentWidget : virtual Widget {
Widget *onMouseDown(Vec pos, int button) override {return NULL;}
Widget *onMouseUp(Vec pos, int button) override {return NULL;}
Widget *onMouseMove(Vec pos, Vec mouseRel) override {return NULL;}
Widget *onScroll(Vec pos, Vec scrollRel) override {return NULL;}
void onMouseDown(EventMouseDown &e) override {}
void onMouseUp(EventMouseUp &e) override {}
void onMouseMove(EventMouseMove &e) override {}
void onScroll(EventScroll &e) override {}
};

/** Widget that automatically responds to all mouse events but gives a chance for children to respond instead */
struct OpaqueWidget : virtual Widget {
Widget *onMouseDown(Vec pos, int button) override {
Widget *w = Widget::onMouseDown(pos, button);
if (w) return w;
onMouseDownOpaque(button);
return this;
void onMouseDown(EventMouseDown &e) override {
Widget::onMouseDown(e);
if (!e.target)
e.target = this;
e.consumed = true;
}
Widget *onMouseUp(Vec pos, int button) override {
Widget *w = Widget::onMouseUp(pos, button);
if (w) return w;
onMouseUpOpaque(button);
return this;
void onMouseUp(EventMouseUp &e) override {
Widget::onMouseUp(e);
if (!e.target)
e.target = this;
e.consumed = true;
}
Widget *onMouseMove(Vec pos, Vec mouseRel) override {
Widget *w = Widget::onMouseMove(pos, mouseRel);
if (w) return w;
onMouseMoveOpaque(mouseRel);
return this;
void onMouseMove(EventMouseMove &e) override {
Widget::onMouseMove(e);
if (!e.target)
e.target = this;
e.consumed = true;
}
Widget *onScroll(Vec pos, Vec scrollRel) override {
Widget *w = Widget::onScroll(pos, scrollRel);
if (w) return w;
if (onScrollOpaque(scrollRel))
return this;
return NULL;
void onScroll(EventScroll &e) override {
Widget::onScroll(e);
e.consumed = true;
}

/** "High level" events called by the above lower level events.
Use these if you don't care about the clicked position.
*/
virtual void onMouseDownOpaque(int button) {}
virtual void onMouseUpOpaque(int button) {}
virtual void onMouseMoveOpaque(Vec mouseRel) {}
virtual bool onScrollOpaque(Vec scrollRel) {return false;}
};

struct SpriteWidget : virtual Widget {
@@ -264,7 +255,7 @@ struct FramebufferWidget : virtual Widget {
~FramebufferWidget();
void draw(NVGcontext *vg) override;
int getImageHandle();
void onZoom() override;
void onZoom(EventZoom &e) override;
};

struct QuantityWidget : virtual Widget {
@@ -302,9 +293,9 @@ struct Label : Widget {

// Deletes itself from parent when clicked
struct MenuOverlay : OpaqueWidget {
void onDragDrop(Widget *origin) override;
bool onScrollOpaque(Vec scrollRel) override {return true;}
Widget *onHoverKey(Vec pos, int key) override;
void onDragDrop(EventDragDrop &e) override;
void onScroll(EventScroll &e) override;
void onHoverKey(EventHoverKey &e) override;
};

struct MenuEntry;
@@ -324,7 +315,7 @@ struct Menu : OpaqueWidget {
void setChildMenu(Menu *menu);
void step() override;
void draw(NVGcontext *vg) override;
bool onScrollOpaque(Vec scrollRel) override;
void onScroll(EventScroll &e) override;
};

struct MenuEntry : OpaqueWidget {
@@ -343,8 +334,8 @@ struct MenuLabel : MenuEntry {
struct MenuItem : MenuEntry {
void draw(NVGcontext *vg) override;
virtual Menu *createChildMenu() {return NULL;}
void onMouseEnter() override;
void onDragDrop(Widget *origin) override;
void onMouseEnter(EventMouseEnter &e) override;
void onDragDrop(EventDragDrop &e) override;
};

struct Button : OpaqueWidget {
@@ -355,11 +346,11 @@ struct Button : OpaqueWidget {
box.size.y = BND_WIDGET_HEIGHT;
}
void draw(NVGcontext *vg) override;
void onMouseEnter() override;
void onMouseLeave() override;
void onDragStart() override;
void onDragEnd() override;
void onDragDrop(Widget *origin) override;
void onMouseEnter(EventMouseEnter &e) override;
void onMouseLeave(EventMouseLeave &e) override;
void onDragStart(EventDragStart &e) override;
void onDragEnd(EventDragEnd &e) override;
void onDragDrop(EventDragDrop &e) override;
};

struct ChoiceButton : Button {
@@ -373,9 +364,9 @@ struct RadioButton : OpaqueWidget, QuantityWidget {
box.size.y = BND_WIDGET_HEIGHT;
}
void draw(NVGcontext *vg) override;
void onMouseEnter() override;
void onMouseLeave() override;
void onDragDrop(Widget *origin) override;
void onMouseEnter(EventMouseEnter &e) override;
void onMouseLeave(EventMouseLeave &e) override;
void onDragDrop(EventDragDrop &e) override;
};

struct Slider : OpaqueWidget, QuantityWidget {
@@ -385,10 +376,10 @@ struct Slider : OpaqueWidget, QuantityWidget {
box.size.y = BND_WIDGET_HEIGHT;
}
void draw(NVGcontext *vg) override;
void onDragStart() override;
void onDragMove(Vec mouseRel) override;
void onDragEnd() override;
void onMouseDownOpaque(int button) override;
void onDragStart(EventDragStart &e) override;
void onDragMove(EventDragMove &e) override;
void onDragEnd(EventDragEnd &e) override;
void onMouseDown(EventMouseDown &e) override;
};

/** Parent must be a ScrollWidget */
@@ -400,9 +391,9 @@ struct ScrollBar : OpaqueWidget {
box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT);
}
void draw(NVGcontext *vg) override;
void onDragStart() override;
void onDragMove(Vec mouseRel) override;
void onDragEnd() override;
void onDragStart(EventDragStart &e) override;
void onDragMove(EventDragMove &e) override;
void onDragEnd(EventDragEnd &e) override;
};

/** Handles a container with ScrollBar */
@@ -414,7 +405,7 @@ struct ScrollWidget : OpaqueWidget {

ScrollWidget();
void step() override;
bool onScrollOpaque(Vec scrollRel) override;
void onScroll(EventScroll &e) override;
};

struct TextField : OpaqueWidget {
@@ -428,10 +419,10 @@ struct TextField : OpaqueWidget {
box.size.y = BND_WIDGET_HEIGHT;
}
void draw(NVGcontext *vg) override;
Widget *onMouseDown(Vec pos, int button) override;
bool onFocusText(int codepoint) override;
bool onFocusKey(int key) override;
bool onFocus() override;
void onMouseDown(EventMouseDown &e) override;
void onFocus(EventFocus &e) override;
void onText(EventText &e) override;
void onKey(EventKey &e) override;
void insertText(std::string newText);
virtual void onTextChange() {}
};


BIN
installer-banner.bmp View File

Before After

+ 88
- 0
installer.nsi View File

@@ -0,0 +1,88 @@
!include "MUI2.nsh"
Name "VCV Rack"
OutFile "Rack-setup.exe"
SetCompressor "bzip2"
CRCCheck On
;Default installation folder
InstallDir "$PROGRAMFILES\VCV"
;Get installation folder from registry if available
InstallDirRegKey HKCU "Software\VCV Rack" ""
;Request application privileges for Windows Vista
RequestExecutionLevel admin
!define MUI_ICON "icon.ico"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "installer-banner.bmp" ; 150x57
; !define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" ; 164x314
; !define MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" ; 164x314
!define MUI_COMPONENTSPAGE_NODESC
; Pages
; !insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
;second directory selection
; Var DbInstDir
; !define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install the database."
; !define MUI_DIRECTORYPAGE_TEXT_TOP "The installer will install the database(s) in the following folder. To install in a differenct folder, click Browse and select another folder. Click Next to continue."
; !define MUI_DIRECTORYPAGE_VARIABLE $DbInstDir ; <= the other directory will be stored into that variable
; !insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Section "!VCV Rack" VCVRACK
SetOutPath "$INSTDIR"
File /r "dist\Rack"
;Store installation folder
WriteRegStr HKCU "Software\VCV Rack" "" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCV Rack" "DisplayName" "VCV Rack"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCV Rack" "UninstallString" "$\"$INSTDIR\UninstallRack.exe$\""
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCV Rack" "QuietUninstallString" "$\"$INSTDIR\UninstallRack.exe$\" /S"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCV Rack" "InstallLocation" "$\"$INSTDIR$\""
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCV Rack" "Publisher" "VCV"
;Create uninstaller
WriteUninstaller "$INSTDIR\UninstallRack.exe"
;Create shortcuts
CreateDirectory "$SMPROGRAMS"
; Set working directory of shortcut
SetOutPath "$INSTDIR\Rack"
CreateShortcut "$SMPROGRAMS\VCV Rack.lnk" "$INSTDIR\Rack\Rack.exe"
SectionEnd
; Section "VST Plugin" VST
; SectionEnd
Section "Uninstall"
RMDir /r "$INSTDIR\Rack"
Delete "$INSTDIR\UninstallRack.exe"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\VCV Rack.lnk"
DeleteRegKey /ifempty HKCU "Software\VCV Rack"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCV Rack"
SectionEnd

+ 71
- 0
res/ComponentLibrary/LEDBezel.svg View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="7.5001564mm"
height="7.5000095mm"
viewBox="0 0 7.5001564 7.5000095"
version="1.1"
id="svg64581"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="LEDBezel.svg">
<defs
id="defs64575" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="-100.74496"
inkscape:cy="9.8683694"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="2560"
inkscape:window-height="1422"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="0" />
<metadata
id="metadata64578">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-94.523732,-120.89285)">
<path
inkscape:connector-curvature="0"
id="path9729"
d="m 94.750061,123.36034 c -0.706931,1.94578 0.297674,4.09829 2.240704,4.8066 1.947157,0.70556 4.098285,-0.29626 4.805225,-2.24208 0.70968,-1.94578 -0.29355,-4.09688 -2.239332,-4.80522 -1.945781,-0.70693 -4.096914,0.29355 -4.806597,2.2407"
style="fill:#2b2b2b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" />
<path
inkscape:connector-curvature="0"
id="path64013"
d="m 99.30033,121.82382 c -1.557161,-0.56497 -3.279704,0.23703 -3.846089,1.79285 -0.565009,1.55716 0.237031,3.27833 1.794192,3.84334 1.557197,0.56776 3.276987,-0.23428 3.844747,-1.79144 0.56498,-1.55582 -0.23565,-3.27837 -1.79285,-3.84475"
style="clip-rule:nonzero;fill:#5a5a5a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" />
</g>
</svg>

+ 7
- 6
src/app/Knob.cpp View File

@@ -10,29 +10,30 @@ namespace rack {
#define KNOB_SENSITIVITY 0.0015


void Knob::onDragStart() {
void Knob::onDragStart(EventDragStart &e) {
guiCursorLock();
dragValue = value;
randomizable = false;
}

void Knob::onDragMove(Vec mouseRel) {
void Knob::onDragMove(EventDragMove &e) {
// Drag slower if Mod
float delta = KNOB_SENSITIVITY * (maxValue - minValue) * -e.mouseRel.y;
if (guiIsModPressed())
mouseRel = mouseRel.mult(1/16.0);
dragValue += KNOB_SENSITIVITY * (maxValue - minValue) * -mouseRel.y;
delta /= 16.0;
dragValue += delta;
if (snap)
setValue(roundf(dragValue));
else
setValue(dragValue);
}

void Knob::onDragEnd() {
void Knob::onDragEnd(EventDragEnd &e) {
guiCursorUnlock();
randomizable = true;
}

void Knob::onChange() {
void Knob::onChange(EventChange &e) {
if (!module)
return;



+ 2
- 2
src/app/LightWidget.cpp View File

@@ -6,7 +6,7 @@ namespace rack {

void LightWidget::draw(NVGcontext *vg) {
float radius = box.size.x / 2.0;
float oradius = radius + 20.0;
float oradius = radius + 15.0;

color.r = clampf(color.r, 0.0, 1.0);
color.g = clampf(color.g, 0.0, 1.0);
@@ -36,7 +36,7 @@ void LightWidget::draw(NVGcontext *vg) {
nvgRect(vg, radius - oradius, radius - oradius, 2*oradius, 2*oradius);
NVGpaint paint;
NVGcolor icol = color;
icol.a *= 0.15;
icol.a *= 0.10;
NVGcolor ocol = color;
ocol.a = 0.0;
paint = nvgRadialGradient(vg, radius, radius, radius, oradius, icol, ocol);


+ 36
- 24
src/app/ModuleWidget.cpp View File

@@ -165,7 +165,22 @@ void ModuleWidget::draw(NVGcontext *vg) {
nvgResetScissor(vg);
}

Widget *ModuleWidget::onMouseMove(Vec pos, Vec mouseRel) {
void ModuleWidget::onMouseDown(EventMouseDown &e) {
Widget::onMouseDown(e);
if (e.consumed)
return;

if (e.button == 1) {
createContextMenu();
}
e.consumed = true;
e.target = this;
}

void ModuleWidget::onMouseMove(EventMouseMove &e) {
OpaqueWidget::onMouseMove(e);

// Don't delete the ModuleWidget if a TextField is focused
if (!gFocusedWidget) {
// Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget.
if (glfwGetKey(gWindow, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_BACKSPACE) == GLFW_PRESS) {
@@ -174,82 +189,85 @@ Widget *ModuleWidget::onMouseMove(Vec pos, Vec mouseRel) {
this->finalizeEvents();
delete this;
// Kinda sketchy because events will be passed further down the tree
return NULL;
return;
}
}
}
return OpaqueWidget::onMouseMove(pos, mouseRel);
}

Widget *ModuleWidget::onHoverKey(Vec pos, int key) {
switch (key) {
void ModuleWidget::onHoverKey(EventHoverKey &e) {
switch (e.key) {
case GLFW_KEY_I:
if (guiIsModPressed() && !guiIsShiftPressed()) {
reset();
return this;
e.consumed = true;
return;
}
break;
case GLFW_KEY_R:
if (guiIsModPressed() && !guiIsShiftPressed()) {
randomize();
return this;
e.consumed = true;
return;
}
break;
case GLFW_KEY_D:
if (guiIsModPressed() && !guiIsShiftPressed()) {
gRackWidget->cloneModule(this);
return this;
e.consumed = true;
return;
}
break;
}

return Widget::onHoverKey(pos, key);
Widget::onHoverKey(e);
}

void ModuleWidget::onDragStart() {
void ModuleWidget::onDragStart(EventDragStart &e) {
dragPos = gRackWidget->lastMousePos.minus(box.pos);
}

void ModuleWidget::onDragMove(Vec mouseRel) {
void ModuleWidget::onDragEnd(EventDragEnd &e) {
}

void ModuleWidget::onDragMove(EventDragMove &e) {
Rect newBox = box;
newBox.pos = gRackWidget->lastMousePos.minus(dragPos);
gRackWidget->requestModuleBoxNearest(this, newBox);
}

void ModuleWidget::onDragEnd() {
}

struct DisconnectMenuItem : MenuItem {
ModuleWidget *moduleWidget;
void onAction() override {
void onAction(EventAction &e) override {
moduleWidget->disconnect();
}
};

struct ResetMenuItem : MenuItem {
ModuleWidget *moduleWidget;
void onAction() override {
void onAction(EventAction &e) override {
moduleWidget->reset();
}
};

struct RandomizeMenuItem : MenuItem {
ModuleWidget *moduleWidget;
void onAction() override {
void onAction(EventAction &e) override {
moduleWidget->randomize();
}
};

struct CloneMenuItem : MenuItem {
ModuleWidget *moduleWidget;
void onAction() override {
void onAction(EventAction &e) override {
gRackWidget->cloneModule(moduleWidget);
}
};

struct DeleteMenuItem : MenuItem {
ModuleWidget *moduleWidget;
void onAction() override {
void onAction(EventAction &e) override {
gRackWidget->deleteModule(moduleWidget);
moduleWidget->finalizeEvents();
delete moduleWidget;
@@ -295,11 +313,5 @@ Menu *ModuleWidget::createContextMenu() {
return menu;
}

void ModuleWidget::onMouseDownOpaque(int button) {
if (button == 1) {
createContextMenu();
}
}


} // namespace rack

+ 5
- 3
src/app/ParamWidget.cpp View File

@@ -19,13 +19,15 @@ void ParamWidget::randomize() {
setValue(rescalef(randomf(), 0.0, 1.0, minValue, maxValue));
}

void ParamWidget::onMouseDownOpaque(int button) {
if (button == 1) {
void ParamWidget::onMouseDown(EventMouseDown &e) {
if (e.button == 1) {
setValue(defaultValue);
}
e.consumed = true;
e.target = this;
}

void ParamWidget::onChange() {
void ParamWidget::onChange(EventChange &e) {
if (!module)
return;



+ 6
- 6
src/app/PluginManagerWidget.cpp View File

@@ -15,7 +15,7 @@ PluginManagerWidget::PluginManagerWidget() {
Vec pos = Vec(0, 0);

struct RegisterButton : Button {
void onAction() override {
void onAction(EventAction &e) override {
std::thread t(openBrowser, "https://vcvrack.com/");
t.detach();
}
@@ -46,7 +46,7 @@ PluginManagerWidget::PluginManagerWidget() {
struct LogInButton : Button {
TextField *emailField;
TextField *passwordField;
void onAction() override {
void onAction(EventAction &e) override {
std::thread t(pluginLogIn, emailField->text, passwordField->text);
t.detach();
passwordField->text = "";
@@ -79,7 +79,7 @@ PluginManagerWidget::PluginManagerWidget() {
Vec pos = Vec(0, 0);

struct ManageButton : Button {
void onAction() override {
void onAction(EventAction &e) override {
std::thread t(openBrowser, "https://vcvrack.com/");
t.detach();
}
@@ -92,7 +92,7 @@ PluginManagerWidget::PluginManagerWidget() {
pos.x += manageButton->box.size.x;

struct RefreshButton : Button {
void onAction() override {
void onAction(EventAction &e) override {
std::thread t(pluginRefresh);
t.detach();
}
@@ -106,7 +106,7 @@ PluginManagerWidget::PluginManagerWidget() {
pos.x += refreshButton->box.size.x;

struct LogOutButton : Button {
void onAction() override {
void onAction(EventAction &e) override {
pluginLogOut();
}
};
@@ -142,7 +142,7 @@ PluginManagerWidget::PluginManagerWidget() {
pos.x += downloadProgress->box.size.x;

// struct CancelButton : Button {
// void onAction() override {
// void onAction(EventAction &e) override {
// pluginCancelDownload();
// }
// };


+ 16
- 13
src/app/Port.cpp View File

@@ -17,23 +17,20 @@ void Port::draw(NVGcontext *vg) {
}
}

void Port::onMouseDownOpaque(int button) {
if (button == 1) {
void Port::onMouseDown(EventMouseDown &e) {
if (e.button == 1) {
gRackWidget->wireContainer->removeTopWire(this);

// HACK
// Update hovered*Port of active wire if applicable
onDragEnter(NULL);
EventDragEnter e;
onDragEnter(e);
}
e.consumed = true;
e.target = this;
}

void Port::onDragEnd() {
// FIXME
// If the source Port is deleted, this will be called, removing the cable
gRackWidget->wireContainer->commitActiveWire();
}

void Port::onDragStart() {
void Port::onDragStart(EventDragStart &e) {
// Try to grab wire on top of stack
WireWidget *wire = gRackWidget->wireContainer->getTopWire(this);
if (guiIsModPressed()) {
@@ -62,10 +59,16 @@ void Port::onDragStart() {
gRackWidget->wireContainer->setActiveWire(wire);
}

void Port::onDragDrop(Widget *origin) {
void Port::onDragEnd(EventDragEnd &e) {
// FIXME
// If the source Port is deleted, this will be called, removing the cable
gRackWidget->wireContainer->commitActiveWire();
}

void Port::onDragDrop(EventDragDrop &e) {
}

void Port::onDragEnter(Widget *origin) {
void Port::onDragEnter(EventDragEnter &e) {
// Reject ports if this is an input port and something is already plugged into it
if (type == INPUT) {
WireWidget *topWire = gRackWidget->wireContainer->getTopWire(this);
@@ -82,7 +85,7 @@ void Port::onDragEnter(Widget *origin) {
}
}

void Port::onDragLeave(Widget *origin) {
void Port::onDragLeave(EventDragEnter &e) {
WireWidget *activeWire = gRackWidget->wireContainer->activeWire;
if (activeWire) {
if (type == INPUT)


+ 24
- 8
src/app/RackScene.cpp View File

@@ -82,39 +82,55 @@ void RackScene::draw(NVGcontext *vg) {
Scene::draw(vg);
}

Widget *RackScene::onHoverKey(Vec pos, int key) {
switch (key) {
void RackScene::onHoverKey(EventHoverKey &e) {
switch (e.key) {
case GLFW_KEY_N:
if (guiIsModPressed() && !guiIsShiftPressed()) {
gRackWidget->reset();
return this;
e.consumed = true;
return;
}
break;
case GLFW_KEY_Q:
if (guiIsModPressed() && !guiIsShiftPressed()) {
guiClose();
return this;
e.consumed = true;
return;
}
break;
case GLFW_KEY_O:
if (guiIsModPressed() && !guiIsShiftPressed()) {
gRackWidget->openDialog();
return this;
e.consumed = true;
return;
}
break;
case GLFW_KEY_S:
if (guiIsModPressed() && !guiIsShiftPressed()) {
gRackWidget->saveDialog();
return this;
e.consumed = true;
return;
}
if (guiIsModPressed() && guiIsShiftPressed()) {
gRackWidget->saveAsDialog();
return this;
e.consumed = true;
return;
}
break;
}

return Widget::onHoverKey(pos, key);
Widget::onHoverKey(e);
}


void RackScene::onPathDrop(EventPathDrop &e) {
if (e.paths.size() >= 1) {
const std::string& firstPath = e.paths.front();
if (extractExtension(firstPath) == "vcv") {
gRackWidget->loadPatch(firstPath);
e.consumed = true;
}
}
}




+ 16
- 10
src/app/RackWidget.cpp View File

@@ -375,7 +375,7 @@ void RackWidget::draw(NVGcontext *vg) {
struct AddModuleMenuItem : MenuItem {
Model *model;
Vec modulePos;
void onAction() override {
void onAction(EventAction &e) override {
ModuleWidget *moduleWidget = model->createModuleWidget();
gRackWidget->moduleContainer->addChild(moduleWidget);
// Move module nearest to the mouse position
@@ -388,7 +388,7 @@ struct AddModuleMenuItem : MenuItem {

struct UrlItem : MenuItem {
std::string url;
void onAction() override {
void onAction(EventAction &e) override {
std::thread t(openBrowser, url);
t.detach();
}
@@ -487,13 +487,17 @@ struct SearchModuleField : TextField {
}
};

Widget *RackWidget::onMouseMove(Vec pos, Vec mouseRel) {
lastMousePos = pos;
return OpaqueWidget::onMouseMove(pos, mouseRel);
void RackWidget::onMouseMove(EventMouseMove &e) {
OpaqueWidget::onMouseMove(e);
lastMousePos = e.pos;
}

void RackWidget::onMouseDownOpaque(int button) {
if (button == 1) {
void RackWidget::onMouseDown(EventMouseDown &e) {
Widget::onMouseDown(e);
if (e.consumed)
return;

if (e.button == 1) {
Menu *menu = gScene->createMenu();

menu->pushChild(construct<MenuLabel>(&MenuLabel::text, "Add module"));
@@ -519,15 +523,17 @@ void RackWidget::onMouseDownOpaque(int button) {
AddManufacturerMenuItem *item = new AddManufacturerMenuItem();
item->text = manufacturerName;
item->manufacturerName = manufacturerName;
item->modulePos = lastMousePos;
item->modulePos = e.pos;
menu->pushChild(item);
}
}
e.consumed = true;
e.target = this;
}

void RackWidget::onZoom() {
void RackWidget::onZoom(EventZoom &e) {
rails->box.size = Vec();
Widget::onZoom();
Widget::onZoom(e);
}




+ 2
- 2
src/app/SVGKnob.cpp View File

@@ -35,9 +35,9 @@ void SVGKnob::step() {
FramebufferWidget::step();
}

void SVGKnob::onChange() {
void SVGKnob::onChange(EventChange &e) {
dirty = true;
Knob::onChange();
Knob::onChange(e);
}




+ 2
- 2
src/app/SVGSlider.cpp View File

@@ -21,9 +21,9 @@ void SVGSlider::step() {
FramebufferWidget::step();
}

void SVGSlider::onChange() {
void SVGSlider::onChange(EventChange &e) {
dirty = true;
ParamWidget::onChange();
ParamWidget::onChange(e);
}




+ 2
- 2
src/app/SVGSwitch.cpp View File

@@ -22,12 +22,12 @@ void SVGSwitch::step() {
FramebufferWidget::step();
}

void SVGSwitch::onChange() {
void SVGSwitch::onChange(EventChange &e) {
assert(frames.size() > 0);
int index = clampi((int) roundf(value), 0, frames.size() - 1);
sw->setSVG(frames[index]);
dirty = true;
Switch::onChange();
Switch::onChange(e);
}




+ 11
- 11
src/app/Toolbar.cpp View File

@@ -7,37 +7,37 @@ namespace rack {


struct NewItem : MenuItem {
void onAction() override {
void onAction(EventAction &e) override {
gRackWidget->reset();
}
};

struct OpenItem : MenuItem {
void onAction() override {
void onAction(EventAction &e) override {
gRackWidget->openDialog();
}
};

struct SaveItem : MenuItem {
void onAction() override {
void onAction(EventAction &e) override {
gRackWidget->saveDialog();
}
};

struct SaveAsItem : MenuItem {
void onAction() override {
void onAction(EventAction &e) override {
gRackWidget->saveAsDialog();
}
};

struct QuitItem : MenuItem {
void onAction() override {
void onAction(EventAction &e) override {
guiClose();
}
};

struct FileChoice : ChoiceButton {
void onAction() override {
void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y));
menu->box.size.x = box.size.x;
@@ -54,21 +54,21 @@ struct FileChoice : ChoiceButton {


struct PauseItem : MenuItem {
void onAction() override {
void onAction(EventAction &e) override {
gPaused = !gPaused;
}
};

struct SampleRateItem : MenuItem {
float sampleRate;
void onAction() override {
void onAction(EventAction &e) override {
engineSetSampleRate(sampleRate);
gPaused = false;
}
};

struct SampleRateChoice : ChoiceButton {
void onAction() override {
void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y));
menu->box.size.x = box.size.x;
@@ -149,8 +149,8 @@ Toolbar::Toolbar() {
xPos += margin;
{
struct ZoomSlider : Slider {
void onAction() override {
Slider::onAction();
void onAction(EventAction &e) override {
Slider::onAction(e);
gRackScene->zoomWidget->setZoom(value / 100.0);
}
};


+ 13
- 3
src/asset.cpp View File

@@ -9,6 +9,11 @@
#include <pwd.h>
#endif

#if ARCH_WIN
#include <Windows.h>
#include <Shlobj.h>
#endif


namespace rack {

@@ -76,9 +81,14 @@ std::string assetLocal(std::string filename) {
path += "/" + filename;
#endif
#if ARCH_WIN
// TODO
// Use ~/My Documents/Rack or something
path = "./" + filename;
// Get My Documents folder
char buf[MAX_PATH];
HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, buf);
assert(result == S_OK);
path = buf;
path += "/Rack";
CreateDirectory(path.c_str(), NULL);
path += "/" + filename;
#endif
#if ARCH_LIN
// TODO


+ 6
- 6
src/core/AudioInterface.cpp View File

@@ -326,14 +326,14 @@ void AudioInterface::closeDevice() {
struct AudioItem : MenuItem {
AudioInterface *audioInterface;
int deviceId;
void onAction() override {
void onAction(EventAction &e) override {
audioInterface->setDeviceId(deviceId);
}
};

struct AudioChoice : ChoiceButton {
AudioInterface *audioInterface;
void onAction() override {
void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
menu->box.size.x = box.size.x;
@@ -364,14 +364,14 @@ struct AudioChoice : ChoiceButton {
struct SampleRateItem : MenuItem {
AudioInterface *audioInterface;
float sampleRate;
void onAction() override {
void onAction(EventAction &e) override {
audioInterface->setSampleRate(sampleRate);
}
};

struct SampleRateChoice : ChoiceButton {
AudioInterface *audioInterface;
void onAction() override {
void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
menu->box.size.x = box.size.x;
@@ -395,14 +395,14 @@ struct SampleRateChoice : ChoiceButton {
struct BlockSizeItem : MenuItem {
AudioInterface *audioInterface;
int blockSize;
void onAction() override {
void onAction(EventAction &e) override {
audioInterface->setBlockSize(blockSize);
}
};

struct BlockSizeChoice : ChoiceButton {
AudioInterface *audioInterface;
void onAction() override {
void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
menu->box.size.x = box.size.x;


+ 8
- 7
src/core/Blank.cpp View File

@@ -10,20 +10,21 @@ struct ModuleResizeHandle : Widget {
ModuleResizeHandle() {
box.size = Vec(RACK_GRID_WIDTH * 1, RACK_GRID_HEIGHT);
}
Widget *onMouseDown(Vec pos, int button) override {
if (button == 0)
return this;
return NULL;
void onMouseDown(EventMouseDown &e) override {
if (e.button == 0) {
e.consumed = true;
e.target = this;
}
}
void onDragStart() override {
void onDragStart(EventDragStart &e) override {
assert(parent);
originalWidth = parent->box.size.x;
totalX = 0.0;
}
void onDragMove(Vec mouseRel) override {
void onDragMove(EventDragMove &e) override {
ModuleWidget *m = dynamic_cast<ModuleWidget*>(parent);
assert(m);
totalX += mouseRel.x;
totalX += e.mouseRel.x;
float targetWidth = originalWidth;
if (right)
targetWidth += totalX;


+ 2
- 2
src/engine.cpp View File

@@ -41,8 +41,8 @@ float Light::getBrightness() {
void Light::setBrightnessSmooth(float brightness) {
float v = brightness * brightness;
if (v < value) {
// Fade out light with lambda = 3 * framerate
value += (v - value) * sampleTime * (60.0 * 3.0);
// Fade out light with lambda = 2 * framerate
value += (v - value) * sampleTime * (60.0 * 2.0);
}
else {
// Immediately illuminate light


+ 89
- 23
src/gui.cpp View File

@@ -54,25 +54,36 @@ void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
#endif

if (action == GLFW_PRESS) {
Widget *w = NULL;
// onMouseDown
Widget *w = gScene->onMouseDown(gMousePos, button);
{
EventMouseDown e;
e.pos = gMousePos;
e.button = button;
gScene->onMouseDown(e);
w = e.target;
}

if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (w) {
// onDragStart
w->onDragStart();
EventDragStart e;
w->onDragStart(e);
}
gDraggedWidget = w;

if (w != gFocusedWidget) {
if (gFocusedWidget) {
// onDefocus
w->onDefocus();
EventDefocus e;
w->onDefocus(e);
}
gFocusedWidget = NULL;
if (w) {
// onFocus
if (w->onFocus()) {
EventFocus e;
w->onFocus(e);
if (e.consumed) {
gFocusedWidget = w;
}
}
@@ -81,17 +92,27 @@ void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
}
else if (action == GLFW_RELEASE) {
// onMouseUp
Widget *w = gScene->onMouseUp(gMousePos, button);
Widget *w = NULL;
{
EventMouseUp e;
e.pos = gMousePos;
e.button = button;
gScene->onMouseUp(e);
w = e.target;
}

if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (gDraggedWidget) {
// onDragDrop
w->onDragDrop(gDraggedWidget);
EventDragDrop e;
e.origin = gDraggedWidget;
w->onDragDrop(e);
}
// gDraggedWidget might have been set to null in the last event, recheck here
if (gDraggedWidget) {
// onDragEnd
gDraggedWidget->onDragEnd();
EventDragEnd e;
gDraggedWidget->onDragEnd(e);
}
gDraggedWidget = NULL;
gDragHoveredWidget = NULL;
@@ -141,19 +162,32 @@ void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {

gMousePos = mousePos;

Widget *hovered = NULL;
// onMouseMove
Widget *hovered = gScene->onMouseMove(gMousePos, mouseRel);
{
EventMouseMove e;
e.pos = mousePos;
e.mouseRel = mouseRel;
gScene->onMouseMove(e);
hovered = e.target;
}

if (gDraggedWidget) {
// onDragMove
gDraggedWidget->onDragMove(mouseRel);
EventDragMove e;
e.mouseRel = mouseRel;
gDraggedWidget->onDragMove(e);

if (hovered != gDragHoveredWidget) {
if (gDragHoveredWidget) {
gDragHoveredWidget->onDragLeave(gDraggedWidget);
EventDragEnter e;
e.origin = gDraggedWidget;
gDragHoveredWidget->onDragLeave(e);
}
if (hovered) {
hovered->onDragEnter(gDraggedWidget);
EventDragEnter e;
e.origin = gDraggedWidget;
hovered->onDragEnter(e);
}
gDragHoveredWidget = hovered;
}
@@ -162,11 +196,13 @@ void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
if (hovered != gHoveredWidget) {
if (gHoveredWidget) {
// onMouseLeave
gHoveredWidget->onMouseLeave();
EventMouseLeave e;
gHoveredWidget->onMouseLeave(e);
}
if (hovered) {
// onMouseEnter
hovered->onMouseEnter();
// onMouseEnter
EventMouseEnter e;
hovered->onMouseEnter(e);
}
gHoveredWidget = hovered;
}
@@ -174,14 +210,19 @@ void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
if (glfwGetMouseButton(gWindow, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) {
// TODO
// Define a new global called gScrollWidget, which remembers the widget where middle-click was first pressed
gScene->onScroll(mousePos, mouseRel);
EventScroll e;
e.pos = mousePos;
e.scrollRel = mouseRel;
gScene->onScroll(e);
}
}

void cursorEnterCallback(GLFWwindow* window, int entered) {
if (!entered) {
if (gHoveredWidget) {
gHoveredWidget->onMouseLeave();
// onMouseLeave
EventMouseLeave e;
gHoveredWidget->onMouseLeave(e);
}
gHoveredWidget = NULL;
}
@@ -194,23 +235,47 @@ void scrollCallback(GLFWwindow *window, double x, double y) {
scrollRel = Vec(y, x);
#endif
// onScroll
gScene->onScroll(gMousePos, scrollRel.mult(50.0));
EventScroll e;
e.pos = gMousePos;
e.scrollRel = scrollRel.mult(50.0);
gScene->onScroll(e);
}

void charCallback(GLFWwindow *window, unsigned int codepoint) {
if (gFocusedWidget) {
gFocusedWidget->onFocusText(codepoint);
// onText
EventText e;
e.codepoint = codepoint;
gFocusedWidget->onText(e);
}
}

void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
// onFocusKey
if (gFocusedWidget && gFocusedWidget->onFocusKey(key))
return;
if (gFocusedWidget) {
// onKey
EventKey e;
e.key = key;
gFocusedWidget->onKey(e);
if (e.consumed)
return;
}
// onHoverKey
gScene->onHoverKey(gMousePos, key);
EventHoverKey e;
e.pos = gMousePos;
e.key = key;
gScene->onHoverKey(e);
}
}

void dropCallback(GLFWwindow *window, int count, const char **paths) {
// onPathDrop
EventPathDrop e;
e.pos = gMousePos;
for (int i = 0; i < count; i++) {
e.paths.push_back(paths[i]);
}
gScene->onPathDrop(e);
}

void errorCallback(int error, const char *description) {
@@ -272,6 +337,7 @@ void guiInit() {
glfwSetScrollCallback(gWindow, scrollCallback);
glfwSetCharCallback(gWindow, charCallback);
glfwSetKeyCallback(gWindow, keyCallback);
glfwSetDropCallback(gWindow, dropCallback);

// Set up GLEW
glewExperimental = GL_TRUE;


+ 9
- 3
src/main.cpp View File

@@ -10,9 +10,15 @@
using namespace rack;

int main(int argc, char* argv[]) {
char *cwd = getcwd(NULL, 0);
printf("Current working directory is %s\n", cwd);
free(cwd);
{
char *cwd = getcwd(NULL, 0);
printf("Current working directory: %s\n", cwd);
free(cwd);
std::string globalDir = assetGlobal("");
std::string localDir = assetLocal("");
printf("Global directory: %s\n", globalDir.c_str());
printf("Local directory: %s\n", localDir.c_str());
}

pluginInit();
engineInit();


+ 8
- 7
src/widgets/Button.cpp View File

@@ -7,25 +7,26 @@ void Button::draw(NVGcontext *vg) {
bndToolButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str());
}

void Button::onMouseEnter() {
void Button::onMouseEnter(EventMouseEnter &e) {
state = BND_HOVER;
}

void Button::onMouseLeave() {
void Button::onMouseLeave(EventMouseLeave &e) {
state = BND_DEFAULT;
}

void Button::onDragStart() {
void Button::onDragStart(EventDragStart &e) {
state = BND_ACTIVE;
}

void Button::onDragEnd() {
void Button::onDragEnd(EventDragEnd &e) {
state = BND_HOVER;
}

void Button::onDragDrop(Widget *origin) {
if (origin == this) {
onAction();
void Button::onDragDrop(EventDragDrop &e) {
if (e.origin == this) {
EventAction eAction;
onAction(eAction);
}
}



+ 1
- 1
src/widgets/FramebufferWidget.cpp View File

@@ -117,7 +117,7 @@ int FramebufferWidget::getImageHandle() {
return internal->fb->image;
}

void FramebufferWidget::onZoom() {
void FramebufferWidget::onZoom(EventZoom &e) {
dirty = true;
}



+ 4
- 4
src/widgets/Menu.cpp View File

@@ -52,12 +52,12 @@ void Menu::draw(NVGcontext *vg) {
}


bool Menu::onScrollOpaque(Vec scrollRel) {
void Menu::onScroll(EventScroll &e) {
if (!parent)
return true;
return;
if (!parent->box.contains(box))
box.pos = box.pos.plus(scrollRel);
return true;
box.pos = box.pos.plus(e.scrollRel);
e.consumed = true;
}




+ 5
- 4
src/widgets/MenuItem.cpp View File

@@ -22,7 +22,7 @@ void MenuItem::draw(NVGcontext *vg) {
bndIconLabelValue(vg, x, 0.0, box.size.x, box.size.y, -1, rightColor, BND_LEFT, BND_LABEL_FONT_SIZE, rightText.c_str(), NULL);
}

void MenuItem::onMouseEnter() {
void MenuItem::onMouseEnter(EventMouseEnter &e) {
Menu *parentMenu = dynamic_cast<Menu*>(parent);
if (!parentMenu)
return;
@@ -38,11 +38,12 @@ void MenuItem::onMouseEnter() {
parentMenu->setChildMenu(childMenu);
}

void MenuItem::onDragDrop(Widget *origin) {
if (origin != this)
void MenuItem::onDragDrop(EventDragDrop &e) {
if (e.origin != this)
return;

onAction();
EventAction eAction;
onAction(eAction);
// deletes `this`
gScene->setOverlay(NULL);
}


+ 11
- 7
src/widgets/MenuOverlay.cpp View File

@@ -3,18 +3,22 @@

namespace rack {

void MenuOverlay::onDragDrop(Widget *origin) {
if (origin == this) {
void MenuOverlay::onDragDrop(EventDragDrop &e) {
if (e.origin == this) {
// deletes `this`
gScene->setOverlay(NULL);
}
}

Widget *MenuOverlay::onHoverKey(Vec pos, int key) {
Widget *w = Widget::onHoverKey(pos, key);
if (w) return w;
// Steal all keys
return this;
void MenuOverlay::onScroll(EventScroll &e) {
// Don't recurse children, consume the event
e.consumed = true;
}

void MenuOverlay::onHoverKey(EventHoverKey &e) {
// Recurse children but consume the event
Widget::onHoverKey(e);
e.consumed = true;
}




+ 4
- 2
src/widgets/QuantityWidget.cpp View File

@@ -4,12 +4,14 @@
namespace rack {

QuantityWidget::QuantityWidget() {
onChange();
EventChange e;
onChange(e);
}

void QuantityWidget::setValue(float value) {
this->value = clampf(value, fminf(minValue, maxValue), fmaxf(minValue, maxValue));
onChange();
EventChange e;
onChange(e);
}

void QuantityWidget::setLimits(float minValue, float maxValue) {


+ 6
- 5
src/widgets/RadioButton.cpp View File

@@ -7,22 +7,23 @@ void RadioButton::draw(NVGcontext *vg) {
bndRadioButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, value == 0.0 ? state : BND_ACTIVE, -1, label.c_str());
}

void RadioButton::onMouseEnter() {
void RadioButton::onMouseEnter(EventMouseEnter &e) {
state = BND_HOVER;
}

void RadioButton::onMouseLeave() {
void RadioButton::onMouseLeave(EventMouseLeave &e) {
state = BND_DEFAULT;
}

void RadioButton::onDragDrop(Widget *origin) {
if (origin == this) {
void RadioButton::onDragDrop(EventDragDrop &e) {
if (e.origin == this) {
if (value == 0.0)
value = 1.0;
else
value = 0.0;

onAction();
EventAction eAction;
onAction(eAction);
}
}



+ 5
- 5
src/widgets/ScrollBar.cpp View File

@@ -19,21 +19,21 @@ void ScrollBar::draw(NVGcontext *vg) {
bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size);
}

void ScrollBar::onDragStart() {
void ScrollBar::onDragStart(EventDragStart &e) {
state = BND_ACTIVE;
guiCursorLock();
}

void ScrollBar::onDragMove(Vec mouseRel) {
void ScrollBar::onDragMove(EventDragMove &e) {
ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent);
assert(scrollWidget);
if (orientation == HORIZONTAL)
scrollWidget->offset.x += mouseRel.x;
scrollWidget->offset.x += e.mouseRel.x;
else
scrollWidget->offset.y += mouseRel.y;
scrollWidget->offset.y += e.mouseRel.y;
}

void ScrollBar::onDragEnd() {
void ScrollBar::onDragEnd(EventDragEnd &e) {
state = BND_DEFAULT;
guiCursorUnlock();
}


+ 3
- 3
src/widgets/ScrollWidget.cpp View File

@@ -58,9 +58,9 @@ void ScrollWidget::step() {
Widget::step();
}

bool ScrollWidget::onScrollOpaque(Vec scrollRel) {
offset = offset.minus(scrollRel);
return true;
void ScrollWidget::onScroll(EventScroll &e) {
offset = offset.minus(e.scrollRel);
e.consumed = true;
}




+ 12
- 8
src/widgets/Slider.cpp View File

@@ -11,26 +11,30 @@ void Slider::draw(NVGcontext *vg) {
bndSlider(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, getText().c_str(), NULL);
}

void Slider::onDragStart() {
void Slider::onDragStart(EventDragStart &e) {
state = BND_ACTIVE;
guiCursorLock();
}

void Slider::onDragMove(Vec mouseRel) {
setValue(value + SLIDER_SENSITIVITY * (maxValue - minValue) * mouseRel.x);
void Slider::onDragMove(EventDragMove &e) {
setValue(value + SLIDER_SENSITIVITY * (maxValue - minValue) * e.mouseRel.x);
}

void Slider::onDragEnd() {
void Slider::onDragEnd(EventDragEnd &e) {
state = BND_DEFAULT;
guiCursorUnlock();
onAction();
EventAction eAction;
onAction(eAction);
}

void Slider::onMouseDownOpaque(int button) {
if (button == 1) {
void Slider::onMouseDown(EventMouseDown &e) {
if (e.button == 1) {
setValue(defaultValue);
onAction();
EventAction eAction;
onAction(eAction);
}
e.consumed = true;
e.target = this;
}




+ 16
- 17
src/widgets/TextField.cpp View File

@@ -24,21 +24,25 @@ void TextField::draw(NVGcontext *vg) {
}
}

Widget *TextField::onMouseDown(Vec pos, int button) {
end = begin = bndTextFieldTextPosition(gVg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), pos.x, pos.y);
return OpaqueWidget::onMouseDown(pos, button);
void TextField::onMouseDown(EventMouseDown &e) {
end = begin = bndTextFieldTextPosition(gVg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), e.pos.x, e.pos.y);
OpaqueWidget::onMouseDown(e);
}

void TextField::onFocus(EventFocus &e) {
begin = 0;
end = text.size();
e.consumed = true;
}

bool TextField::onFocusText(int codepoint) {
char c = codepoint;
std::string newText(1, c);
void TextField::onText(EventText &e) {
std::string newText(1, (char) e.codepoint);
insertText(newText);
return true;
e.consumed = true;
}

bool TextField::onFocusKey(int key) {
switch (key) {
void TextField::onKey(EventKey &e) {
switch (e.key) {
case GLFW_KEY_BACKSPACE:
if (begin < end) {
text.erase(begin, end - begin);
@@ -107,20 +111,15 @@ bool TextField::onFocusKey(int key) {
insertText("\n");
}
else {
onAction();
EventAction e;
onAction(e);
}
break;
}

begin = mini(maxi(begin, 0), text.size());
end = mini(maxi(end, 0), text.size());
return true;
}

bool TextField::onFocus() {
begin = 0;
end = text.size();
return true;
e.consumed = true;
}

void TextField::insertText(std::string newText) {


+ 39
- 65
src/widgets/Widget.cpp View File

@@ -78,18 +78,21 @@ void Widget::clearChildren() {
void Widget::finalizeEvents() {
// Stop dragging and hovering this widget
if (gHoveredWidget == this) {
gHoveredWidget->onMouseLeave();
EventMouseLeave e;
gHoveredWidget->onMouseLeave(e);
gHoveredWidget = NULL;
}
if (gDraggedWidget == this) {
gDraggedWidget->onDragEnd();
EventDragEnd e;
gDraggedWidget->onDragEnd(e);
gDraggedWidget = NULL;
}
if (gDragHoveredWidget == this) {
gDragHoveredWidget = NULL;
}
if (gFocusedWidget == this) {
gFocusedWidget->onDefocus();
EventDefocus e;
gFocusedWidget->onDefocus(e);
gFocusedWidget = NULL;
}
for (Widget *child : children) {
@@ -114,80 +117,51 @@ void Widget::draw(NVGcontext *vg) {
}
}

Widget *Widget::onMouseDown(Vec pos, int button) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (!child->visible)
continue;
if (child->box.contains(pos)) {
Widget *w = child->onMouseDown(pos.minus(child->box.pos), button);
if (w)
return w;
}
}
return NULL;
#define RECURSE_EVENT_POSITION(_method) { \
Vec pos = e.pos; \
for (auto it = children.rbegin(); it != children.rend(); it++) { \
Widget *child = *it; \
if (!child->visible) \
continue; \
if (child->box.contains(pos)) { \
e.pos = pos.minus(child->box.pos); \
child->_method(e); \
if (e.consumed) \
break; \
} \
} \
e.pos = pos; \
}

Widget *Widget::onMouseUp(Vec pos, int button) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (!child->visible)
continue;
if (child->box.contains(pos)) {
Widget *w = child->onMouseUp(pos.minus(child->box.pos), button);
if (w)
return w;
}
}
return NULL;

void Widget::onMouseDown(EventMouseDown &e) {
RECURSE_EVENT_POSITION(onMouseDown);
}

Widget *Widget::onMouseMove(Vec pos, Vec mouseRel) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (!child->visible)
continue;
if (child->box.contains(pos)) {
Widget *w = child->onMouseMove(pos.minus(child->box.pos), mouseRel);
if (w)
return w;
}
}
return NULL;
void Widget::onMouseUp(EventMouseUp &e) {
RECURSE_EVENT_POSITION(onMouseUp);
}

Widget *Widget::onHoverKey(Vec pos, int key) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (!child->visible)
continue;
if (child->box.contains(pos)) {
Widget *w = child->onHoverKey(pos.minus(child->box.pos), key);
if (w)
return w;
}
}
return NULL;
void Widget::onMouseMove(EventMouseMove &e) {
RECURSE_EVENT_POSITION(onMouseMove);
}

Widget *Widget::onScroll(Vec pos, Vec scrollRel) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (!child->visible)
continue;
if (child->box.contains(pos)) {
Widget *w = child->onScroll(pos.minus(child->box.pos), scrollRel);
if (w)
return w;
}
}
return NULL;
void Widget::onHoverKey(EventHoverKey &e) {
RECURSE_EVENT_POSITION(onHoverKey);
}

void Widget::onScroll(EventScroll &e) {
RECURSE_EVENT_POSITION(onScroll);
}

void Widget::onPathDrop(EventPathDrop &e) {
RECURSE_EVENT_POSITION(onPathDrop);
}

void Widget::onZoom() {
void Widget::onZoom(EventZoom &e) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
child->onZoom();
child->onZoom(e);
}
}



+ 29
- 12
src/widgets/ZoomWidget.cpp View File

@@ -18,8 +18,10 @@ Rect ZoomWidget::getViewport(Rect r) {
}

void ZoomWidget::setZoom(float zoom) {
if (zoom != this->zoom)
onZoom();
if (zoom != this->zoom) {
EventZoom e;
onZoom(e);
}
this->zoom = zoom;
}

@@ -28,24 +30,39 @@ void ZoomWidget::draw(NVGcontext *vg) {
Widget::draw(vg);
}

Widget *ZoomWidget::onMouseDown(Vec pos, int button) {
return Widget::onMouseDown(pos.div(zoom), button);
void ZoomWidget::onMouseDown(EventMouseDown &e) {
Vec pos = e.pos;
e.pos = e.pos.div(zoom);
Widget::onMouseDown(e);
e.pos = pos;
}

Widget *ZoomWidget::onMouseUp(Vec pos, int button) {
return Widget::onMouseUp(pos.div(zoom), button);
void ZoomWidget::onMouseUp(EventMouseUp &e) {
Vec pos = e.pos;
e.pos = e.pos.div(zoom);
Widget::onMouseUp(e);
e.pos = pos;
}

Widget *ZoomWidget::onMouseMove(Vec pos, Vec mouseRel) {
return Widget::onMouseMove(pos.div(zoom), mouseRel);
void ZoomWidget::onMouseMove(EventMouseMove &e) {
Vec pos = e.pos;
e.pos = e.pos.div(zoom);
Widget::onMouseMove(e);
e.pos = pos;
}

Widget *ZoomWidget::onHoverKey(Vec pos, int key) {
return Widget::onHoverKey(pos.div(zoom), key);
void ZoomWidget::onHoverKey(EventHoverKey &e) {
Vec pos = e.pos;
e.pos = e.pos.div(zoom);
Widget::onHoverKey(e);
e.pos = pos;
}

Widget *ZoomWidget::onScroll(Vec pos, Vec scrollRel) {
return Widget::onScroll(pos.div(zoom), scrollRel);
void ZoomWidget::onScroll(EventScroll &e) {
Vec pos = e.pos;
e.pos = e.pos.div(zoom);
Widget::onScroll(e);
e.pos = pos;
}




Loading…
Cancel
Save