/* * 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 "NanoVG.hpp" START_NAMESPACE_DGL /** Resize handle for DPF windows, will sit on bottom-right. */ class ResizeHandle : public NanoTopLevelWidget { public: /** Overloaded constructor, will fetch the window from an existing top-level widget. */ explicit ResizeHandle(TopLevelWidget* const tlw) : NanoTopLevelWidget(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 onNanoDisplay() override { const double lineWidth = 1.0 * getScaleFactor(); strokeWidth(lineWidth); // draw white lines, 1px wide strokeColor(Color(1.0f, 1.0f, 1.0f)); drawLine(l1); drawLine(l2); drawLine(l3); // draw black lines, offset by 1px and 1px wide strokeColor(Color(0.0f, 0.0f, 0.0f)); Line l1b(l1), l2b(l2), l3b(l3); l1b.moveBy(lineWidth, lineWidth); l2b.moveBy(lineWidth, lineWidth); l3b.moveBy(lineWidth, lineWidth); drawLine(l1b); drawLine(l2b); drawLine(l3b); } void drawLine(const Line& line) { beginPath(); moveTo(line.getStartPos().getX(), line.getStartPos().getY()); lineTo(line.getEndPos().getX(), line.getEndPos().getY()); stroke(); } 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 = 1.5 * 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