diff --git a/tests/Demo.cpp b/tests/Demo.cpp index 41759d242..fc96cc278 100644 --- a/tests/Demo.cpp +++ b/tests/Demo.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho + * 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 @@ -23,6 +23,7 @@ #ifdef DGL_OPENGL #include "widgets/ExampleTextWidget.hpp" #endif +#include "widgets/ResizeHandle.hpp" #include "demo_res/DemoArtwork.cpp" #include "images_res/CatPics.cpp" @@ -250,155 +251,6 @@ class LeftSideWidget : public SubWidget #endif }; -// -------------------------------------------------------------------------------------------------------------------- -// Resize handle widget - -class ResizeHandle : public TopLevelWidget -{ - Rectangle area; - Line l1, l2, l3; - uint baseSize; - - // event handling state - bool resizing; - Point lastResizePoint; - Size resizingSize; - -public: - explicit ResizeHandle(TopLevelWidget* const tlw) - : TopLevelWidget(tlw->getWindow()), - baseSize(16), - resizing(false) - { - resetArea(); - } - - explicit ResizeHandle(Window& window) - : TopLevelWidget(window), - baseSize(16), - resizing(false) - { - resetArea(); - } - - void setBaseSize(const uint size) - { - baseSize = std::max(16u, size); - resetArea(); - } - -protected: - void onDisplay() override - { - const GraphicsContext& context(getGraphicsContext()); - const double offset = getScaleFactor(); - - // draw white lines, 1px wide - Color(1.0f, 1.0f, 1.0f).setFor(context); - l1.draw(context, 1); - l2.draw(context, 1); - l3.draw(context, 1); - - // draw black lines, offset by 1px and 2px wide - Color(0.0f, 0.0f, 0.0f).setFor(context); - Line l1b(l1), l2b(l2), l3b(l3); - l1b.moveBy(offset, offset); - l2b.moveBy(offset, offset); - l3b.moveBy(offset, offset); - l1b.draw(context, 1); - l2b.draw(context, 1); - l3b.draw(context, 1); - } - - bool onMouse(const MouseEvent& ev) override - { - if (ev.button != 1) - return false; - - if (ev.press && area.contains(ev.pos)) - { - resizing = true; - resizingSize = Size(getWidth(), getHeight()); - lastResizePoint = ev.pos; - return true; - } - - if (resizing && ! ev.press) - { - resizing = false; - return true; - } - - return false; - } - - bool onMotion(const MotionEvent& ev) override - { - if (! resizing) - return false; - - const Size offset(ev.pos.getX() - lastResizePoint.getX(), - ev.pos.getY() - lastResizePoint.getY()); - - resizingSize += offset; - lastResizePoint = ev.pos; - - // TODO min width, min height - const uint minWidth = 16; - const uint minHeight = 16; - - if (resizingSize.getWidth() < minWidth) - resizingSize.setWidth(minWidth); - if (resizingSize.getHeight() < minHeight) - resizingSize.setHeight(minHeight); - - setSize(resizingSize.getWidth(), resizingSize.getHeight()); - return true; - } - - void onResize(const ResizeEvent& ev) override - { - TopLevelWidget::onResize(ev); - resetArea(); - } - -private: - void resetArea() - { - const double scaleFactor = getScaleFactor(); - const uint margin = 0.0 * scaleFactor; - const uint size = baseSize * scaleFactor; - - area = Rectangle(getWidth() - size - margin, - getHeight() - size - margin, - size, size); - - recreateLines(area.getX(), area.getY(), size); - } - - void recreateLines(const uint x, const uint y, const uint size) - { - uint linesize = size; - uint offset = 0; - - // 1st line, full diagonal size - l1.setStartPos(x + size, y); - l1.setEndPos(x, y + size); - - // 2nd line, bit more to the right and down, cropped - offset += size / 3; - linesize -= size / 3; - l2.setStartPos(x + linesize + offset, y + offset); - l2.setEndPos(x + offset, y + linesize + offset); - - // 3rd line, even more right and down - offset += size / 3; - linesize -= size / 3; - l3.setStartPos(x + linesize + offset, y + offset); - l3.setEndPos(x + offset, y + linesize + offset); - } -}; - // -------------------------------------------------------------------------------------------------------------------- // Main Demo Window, having a left-side tab-like widget and main area for current widget @@ -535,6 +387,7 @@ void createAndShowExampleWidgetStandaloneWindow(Application& app) { ExampleWidgetStandaloneWindow swin(app); const double scaleFactor = swin.getScaleFactor(); + swin.setGeometryConstraints(128 * scaleFactor, 128 * scaleFactor); swin.setResizable(true); swin.setSize(600 * scaleFactor, 500 * scaleFactor); swin.setTitle(ExampleWidgetStandaloneWindow::kExampleWidgetName); diff --git a/tests/widgets/ResizeHandle.hpp b/tests/widgets/ResizeHandle.hpp new file mode 100644 index 000000000..99a164a15 --- /dev/null +++ b/tests/widgets/ResizeHandle.hpp @@ -0,0 +1,207 @@ +/* + * Resize handle for DPF + * Copyright (C) 2021-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. + */ + +#pragma once + +#include "../../dgl/Color.hpp" +#include "../../dgl/TopLevelWidget.hpp" + +START_NAMESPACE_DGL + +/** Resize handle for DPF windows, will sit on bottom-right. */ +class ResizeHandle : public TopLevelWidget +{ +public: + /** Constructor for placing this handle on top of a window. */ + explicit ResizeHandle(Window& window) + : TopLevelWidget(window), + handleSize(16), + hasCursor(false), + isResizing(false) + { + resetArea(); + } + + /** Overloaded constructor, will fetch the window from an existing top-level widget. */ + explicit ResizeHandle(TopLevelWidget* const tlw) + : TopLevelWidget(tlw->getWindow()), + handleSize(16), + hasCursor(false), + isResizing(false) + { + resetArea(); + } + + /** Set the handle size, minimum 16. + * Scale factor is automatically applied on top of this size as needed */ + void setHandleSize(const uint size) + { + handleSize = std::max(16u, size); + resetArea(); + } + +protected: + void onDisplay() override + { + // TODO implement gl3 stuff in DPF +#ifndef DGL_USE_OPENGL3 + const GraphicsContext& context(getGraphicsContext()); + const double lineWidth = 1.0 * getScaleFactor(); + + #if defined(DGL_OPENGL) && !defined(DGL_USE_OPENGL3) + glMatrixMode(GL_MODELVIEW); + #endif + + // draw white lines, 1px wide + Color(1.0f, 1.0f, 1.0f).setFor(context); + l1.draw(context, lineWidth); + l2.draw(context, lineWidth); + l3.draw(context, lineWidth); + + // draw black lines, offset by 1px and 1px wide + Color(0.0f, 0.0f, 0.0f).setFor(context); + Line l1b(l1), l2b(l2), l3b(l3); + l1b.moveBy(lineWidth, lineWidth); + l2b.moveBy(lineWidth, lineWidth); + l3b.moveBy(lineWidth, lineWidth); + l1b.draw(context, lineWidth); + l2b.draw(context, lineWidth); + l3b.draw(context, lineWidth); +#endif + } + + bool onMouse(const MouseEvent& ev) override + { + if (ev.button != 1) + return false; + + if (ev.press && area.contains(ev.pos)) + { + isResizing = true; + resizingSize = Size(getWidth(), getHeight()); + lastResizePoint = ev.pos; + return true; + } + + if (isResizing && ! ev.press) + { + isResizing = false; + recheckCursor(ev.pos); + return true; + } + + return false; + } + + bool onMotion(const MotionEvent& ev) override + { + if (! isResizing) + { + recheckCursor(ev.pos); + return false; + } + + const Size offset(ev.pos.getX() - lastResizePoint.getX(), + ev.pos.getY() - lastResizePoint.getY()); + + resizingSize += offset; + lastResizePoint = ev.pos; + + // TODO keepAspectRatio + bool keepAspectRatio; + const Size minSize(getWindow().getGeometryConstraints(keepAspectRatio)); + const uint minWidth = minSize.getWidth(); + const uint minHeight = minSize.getHeight(); + + if (resizingSize.getWidth() < minWidth) + resizingSize.setWidth(minWidth); + if (resizingSize.getWidth() > 16384) + resizingSize.setWidth(16384); + if (resizingSize.getHeight() < minHeight) + resizingSize.setHeight(minHeight); + if (resizingSize.getHeight() > 16384) + resizingSize.setHeight(16384); + + setSize(resizingSize.getWidth(), resizingSize.getHeight()); + return true; + } + + void onResize(const ResizeEvent& ev) override + { + TopLevelWidget::onResize(ev); + resetArea(); + } + +private: + Rectangle area; + Line l1, l2, l3; + uint handleSize; + + // event handling state + bool hasCursor, isResizing; + Point lastResizePoint; + Size resizingSize; + + void recheckCursor(const Point& pos) + { + const bool shouldHaveCursor = area.contains(pos); + + if (shouldHaveCursor == hasCursor) + return; + + hasCursor = shouldHaveCursor; + setCursor(shouldHaveCursor ? kMouseCursorDiagonal : kMouseCursorArrow); + } + + void resetArea() + { + const double scaleFactor = getScaleFactor(); + const uint margin = 0.0 * scaleFactor; + const uint size = handleSize * scaleFactor; + + area = Rectangle(getWidth() - size - margin, + getHeight() - size - margin, + size, size); + + recreateLines(area.getX(), area.getY(), size); + } + + void recreateLines(const uint x, const uint y, const uint size) + { + uint linesize = size; + uint offset = 0; + + // 1st line, full diagonal size + l1.setStartPos(x + size, y); + l1.setEndPos(x, y + size); + + // 2nd line, bit more to the right and down, cropped + offset += size / 3; + linesize -= size / 3; + l2.setStartPos(x + linesize + offset, y + offset); + l2.setEndPos(x + offset, y + linesize + offset); + + // 3rd line, even more right and down + offset += size / 3; + linesize -= size / 3; + l3.setStartPos(x + linesize + offset, y + offset); + l3.setEndPos(x + offset, y + linesize + offset); + } + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ResizeHandle) +}; + +END_NAMESPACE_DGL