/* * DISTRHO Plugin Framework (DPF) * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this * permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "../EventHandlers.hpp" #include "../SubWidget.hpp" START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- struct ButtonEventHandler::PrivateData { ButtonEventHandler* const self; SubWidget* const widget; ButtonEventHandler::Callback* internalCallback; ButtonEventHandler::Callback* userCallback; int button; int state; bool checkable; bool checked; Point lastClickPos; Point lastMotionPos; PrivateData(ButtonEventHandler* const s, SubWidget* const w) : self(s), widget(w), internalCallback(nullptr), userCallback(nullptr), button(-1), state(kButtonStateDefault), checkable(false), checked(false), lastClickPos(0, 0), lastMotionPos(0, 0) {} bool mouseEvent(const Widget::MouseEvent& ev) { lastClickPos = ev.pos; // button was released, handle it now if (button != -1 && ! ev.press) { DISTRHO_SAFE_ASSERT(state & kButtonStateActive); // release button const int button2 = button; button = -1; const int state2 = state; state &= ~kButtonStateActive; self->stateChanged(static_cast(state), static_cast(state2)); widget->repaint(); // cursor was moved outside the button bounds, ignore click if (! widget->contains(ev.pos)) return true; // still on bounds, register click if (checkable) checked = !checked; if (internalCallback != nullptr) internalCallback->buttonClicked(widget, button2); else if (userCallback != nullptr) userCallback->buttonClicked(widget, button2); return true; } // button was pressed, wait for release if (ev.press && widget->contains(ev.pos)) { const int state2 = state; button = static_cast(ev.button); state |= kButtonStateActive; self->stateChanged(static_cast(state), static_cast(state2)); widget->repaint(); return true; } return false; } bool motionEvent(const Widget::MotionEvent& ev) { // keep pressed if (button != -1) { lastMotionPos = ev.pos; return true; } bool ret = false; if (widget->contains(ev.pos)) { // check if entering hover if ((state & kButtonStateHover) == 0x0) { const int state2 = state; state |= kButtonStateHover; ret = widget->contains(lastMotionPos); self->stateChanged(static_cast(state), static_cast(state2)); widget->repaint(); } } else { // check if exiting hover if (state & kButtonStateHover) { const int state2 = state; state &= ~kButtonStateHover; ret = widget->contains(lastMotionPos); self->stateChanged(static_cast(state), static_cast(state2)); widget->repaint(); } } lastMotionPos = ev.pos; return ret; } void setActive(const bool active2, const bool sendCallback) noexcept { const bool active = state & kButtonStateActive; if (active == active2) return; state |= kButtonStateActive; widget->repaint(); if (sendCallback) { if (internalCallback != nullptr) internalCallback->buttonClicked(widget, -1); else if (userCallback != nullptr) userCallback->buttonClicked(widget, -1); } } void setChecked(const bool checked2, const bool sendCallback) noexcept { if (checked == checked2) return; checked = checked2; widget->repaint(); if (sendCallback) { if (internalCallback != nullptr) internalCallback->buttonClicked(widget, -1); else if (userCallback != nullptr) userCallback->buttonClicked(widget, -1); } } DISTRHO_DECLARE_NON_COPYABLE(PrivateData) }; // -------------------------------------------------------------------------------------------------------------------- ButtonEventHandler::ButtonEventHandler(SubWidget* const self) : pData(new PrivateData(this, self)) {} ButtonEventHandler::~ButtonEventHandler() { delete pData; } bool ButtonEventHandler::isActive() noexcept { return pData->state & kButtonStateActive; } void ButtonEventHandler::setActive(const bool active, const bool sendCallback) noexcept { pData->setActive(active, sendCallback); } bool ButtonEventHandler::isChecked() const noexcept { return pData->checked; } void ButtonEventHandler::setChecked(const bool checked, const bool sendCallback) noexcept { pData->setChecked(checked, sendCallback); } bool ButtonEventHandler::isCheckable() const noexcept { return pData->checkable; } void ButtonEventHandler::setCheckable(const bool checkable) noexcept { if (pData->checkable == checkable) return; pData->checkable = checkable; } Point ButtonEventHandler::getLastClickPosition() const noexcept { return pData->lastClickPos; } Point ButtonEventHandler::getLastMotionPosition() const noexcept { return pData->lastMotionPos; } void ButtonEventHandler::setCallback(Callback* const callback) noexcept { pData->userCallback = callback; } bool ButtonEventHandler::mouseEvent(const Widget::MouseEvent& ev) { return pData->mouseEvent(ev); } bool ButtonEventHandler::motionEvent(const Widget::MotionEvent& ev) { return pData->motionEvent(ev); } ButtonEventHandler::State ButtonEventHandler::getState() const noexcept { return static_cast(pData->state); } void ButtonEventHandler::clearState() noexcept { pData->state = kButtonStateDefault; } void ButtonEventHandler::stateChanged(State, State) { } void ButtonEventHandler::setInternalCallback(Callback* const callback) noexcept { pData->internalCallback = callback; } void ButtonEventHandler::triggerUserCallback(SubWidget* const widget, const int button) { if (pData->userCallback != nullptr) pData->userCallback->buttonClicked(widget, button); } // -------------------------------------------------------------------------------------------------------------------- struct KnobEventHandler::PrivateData { KnobEventHandler* const self; SubWidget* const widget; KnobEventHandler::Callback* callback; float accel; float minimum; float maximum; float step; float value; float valueDef; float valueTmp; bool usingDefault; bool usingLog; Orientation orientation; int state; double lastX; double lastY; uint lastClickTime; PrivateData(KnobEventHandler* const s, SubWidget* const w) : self(s), widget(w), callback(nullptr), accel(200.f), minimum(0.f), maximum(1.f), step(0.0f), value(0.5f), valueDef(value), valueTmp(value), usingDefault(false), usingLog(false), orientation(Vertical), state(kKnobStateDefault), lastX(0.0), lastY(0.0), lastClickTime(0) {} PrivateData(KnobEventHandler* const s, SubWidget* const w, PrivateData* const other) : self(s), widget(w), callback(other->callback), accel(other->accel), minimum(other->minimum), maximum(other->maximum), step(other->step), value(other->value), valueDef(other->valueDef), valueTmp(value), usingDefault(other->usingDefault), usingLog(other->usingLog), orientation(other->orientation), state(kKnobStateDefault), lastX(0.0), lastY(0.0), lastClickTime(0) {} void assignFrom(PrivateData* const other) { callback = other->callback; accel = other->accel; minimum = other->minimum; maximum = other->maximum; step = other->step; value = other->value; valueDef = other->valueDef; valueTmp = value; usingDefault = other->usingDefault; usingLog = other->usingLog; orientation = other->orientation; state = kKnobStateDefault; lastX = 0.0; lastY = 0.0; lastClickTime = 0; } inline float logscale(const float v) const { const float b = std::log(maximum/minimum)/(maximum-minimum); const float a = maximum/std::exp(maximum*b); return a * std::exp(b*v); } inline float invlogscale(const float v) const { const float b = std::log(maximum/minimum)/(maximum-minimum); const float a = maximum/std::exp(maximum*b); return std::log(v/a)/b; } bool mouseEvent(const Widget::MouseEvent& ev, const double scaleFactor) { if (ev.button != 1) return false; if (ev.press) { if (! widget->contains(ev.pos)) return false; if ((ev.mod & kModifierShift) != 0 && usingDefault) { setValue(valueDef, true); valueTmp = value; return true; } lastX = ev.pos.getX() / scaleFactor; lastY = ev.pos.getY() / scaleFactor; if (lastClickTime > 0 && ev.time > lastClickTime && ev.time - lastClickTime <= 300) { lastClickTime = 0; if (callback != nullptr) callback->knobDoubleClicked(widget); return true; } lastClickTime = ev.time; state |= kKnobStateDragging; widget->repaint(); if (callback != nullptr) callback->knobDragStarted(widget); return true; } else if (state & kKnobStateDragging) { state &= ~kKnobStateDragging; widget->repaint(); if (callback != nullptr) callback->knobDragFinished(widget); return true; } return false; } bool motionEvent(const Widget::MotionEvent& ev, const double scaleFactor) { if ((state & kKnobStateDragging) == 0x0) return false; float movDiff; switch (orientation) { case Horizontal: movDiff = ev.pos.getX() / scaleFactor - lastX; break; case Vertical: movDiff = lastY - ev.pos.getY() / scaleFactor; break; case Both: { const float movDiffX = ev.pos.getX() / scaleFactor - lastX; const float movDiffY = lastY - ev.pos.getY() / scaleFactor; movDiff = std::abs(movDiffX) > std::abs(movDiffY) ? movDiffX : movDiffY; } break; default: return false; } if (d_isZero(movDiff)) return false; const float divisor = (ev.mod & kModifierControl) ? accel * 10.f : accel; valueTmp += (maximum - minimum) / divisor * movDiff; if (usingLog) valueTmp = logscale(valueTmp); float value2; bool valueChanged = false; if (valueTmp < minimum) { valueTmp = value2 = minimum; valueChanged = true; } else if (valueTmp > maximum) { valueTmp = value2 = maximum; valueChanged = true; } else { if (d_isNotZero(step)) { if (std::abs(valueTmp - value) >= step) { const float rest = std::fmod(valueTmp, step); valueChanged = true; value2 = valueTmp - rest; if (rest < 0 && rest < step * -0.5f) value2 -= step; else if (rest > 0 && rest > step * 0.5f) value2 += step; if (value2 < minimum) value2 = minimum; else if (value2 > maximum) value2 = maximum; } } else { value2 = valueTmp; valueChanged = true; } } if (valueChanged) setValue(value2, true); lastX = ev.pos.getX() / scaleFactor; lastY = ev.pos.getY() / scaleFactor; return true; } bool scrollEvent(const Widget::ScrollEvent& ev) { if (! widget->contains(ev.pos)) return false; const float dir = (ev.delta.getY() > 0.f) ? 1.f : -1.f; const float d = (ev.mod & kModifierControl) ? accel * 10.f : accel; float value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + ((maximum - minimum) / d * 10.f * dir); if (usingLog) value2 = logscale(value2); if (value2 < minimum) { valueTmp = value2 = minimum; } else if (value2 > maximum) { valueTmp = value2 = maximum; } else { valueTmp = value2; if (d_isNotZero(step)) { const float rest = std::fmod(value2, step); value2 = value2 - rest + (rest > step/2.0f ? step : 0.0f); } } setValue(value2, true); return true; } float getNormalizedValue() const noexcept { const float diff = maximum - minimum; return ((usingLog ? invlogscale(value) : value) - minimum) / diff; } void setRange(const float min, const float max) noexcept { DISTRHO_SAFE_ASSERT_RETURN(max > min,); if (value < min) { valueTmp = value = min; widget->repaint(); } else if (value > max) { valueTmp = value = max; widget->repaint(); } minimum = min; maximum = max; } bool setValue(const float value2, const bool sendCallback) { if (d_isEqual(value, value2)) return false; valueTmp = value = value2; widget->repaint(); if (sendCallback && callback != nullptr) { try { callback->knobValueChanged(widget, value); } DISTRHO_SAFE_EXCEPTION("KnobEventHandler::setValue"); } return true; } }; // -------------------------------------------------------------------------------------------------------------------- KnobEventHandler::KnobEventHandler(SubWidget* const self) : pData(new PrivateData(this, self)) {} KnobEventHandler::KnobEventHandler(SubWidget* const self, const KnobEventHandler& other) : pData(new PrivateData(this, self, other.pData)) {} KnobEventHandler& KnobEventHandler::operator=(const KnobEventHandler& other) { pData->assignFrom(other.pData); return *this; } KnobEventHandler::~KnobEventHandler() { delete pData; } bool KnobEventHandler::isInteger() const noexcept { return d_isEqual(pData->step, 1.f); } float KnobEventHandler::getValue() const noexcept { return pData->value; } bool KnobEventHandler::setValue(const float value, const bool sendCallback) noexcept { return pData->setValue(value, sendCallback); } float KnobEventHandler::getNormalizedValue() const noexcept { return pData->getNormalizedValue(); } void KnobEventHandler::setDefault(const float def) noexcept { pData->valueDef = def; pData->usingDefault = true; } void KnobEventHandler::setRange(const float min, const float max) noexcept { pData->setRange(min, max); } void KnobEventHandler::setStep(const float step) noexcept { pData->step = step; } void KnobEventHandler::setUsingLogScale(const bool yesNo) noexcept { pData->usingLog = yesNo; } KnobEventHandler::Orientation KnobEventHandler::getOrientation() const noexcept { return pData->orientation; } void KnobEventHandler::setOrientation(const Orientation orientation) noexcept { pData->orientation = orientation; } void KnobEventHandler::setCallback(Callback* const callback) noexcept { pData->callback = callback; } void KnobEventHandler::setMouseDeceleration(float accel) noexcept { pData->accel = accel; } bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev, const double scaleFactor) { return pData->mouseEvent(ev, scaleFactor); } bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev, const double scaleFactor) { return pData->motionEvent(ev, scaleFactor); } bool KnobEventHandler::scrollEvent(const Widget::ScrollEvent& ev) { return pData->scrollEvent(ev); } KnobEventHandler::State KnobEventHandler::getState() const noexcept { return static_cast(pData->state); } // -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL