You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
5.8KB

  1. #include <ui/ScrollWidget.hpp>
  2. #include <context.hpp>
  3. namespace rack {
  4. namespace ui {
  5. struct ScrollWidget::Internal {
  6. bool scrolling = false;
  7. };
  8. ScrollWidget::ScrollWidget() {
  9. internal = new Internal;
  10. container = new widget::Widget;
  11. addChild(container);
  12. horizontalScrollbar = new Scrollbar;
  13. horizontalScrollbar->vertical = false;
  14. horizontalScrollbar->hide();
  15. addChild(horizontalScrollbar);
  16. verticalScrollbar = new Scrollbar;
  17. verticalScrollbar->vertical = true;
  18. verticalScrollbar->hide();
  19. addChild(verticalScrollbar);
  20. }
  21. ScrollWidget::~ScrollWidget() {
  22. delete internal;
  23. }
  24. void ScrollWidget::scrollTo(math::Rect r) {
  25. math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos);
  26. offset = offset.clampSafe(bound);
  27. }
  28. math::Rect ScrollWidget::getContainerOffsetBound() {
  29. math::Rect r;
  30. r.pos = containerBox.pos;
  31. r.size = containerBox.size.minus(box.size);
  32. return r;
  33. }
  34. math::Vec ScrollWidget::getHandleOffset() {
  35. return offset.minus(containerBox.pos).div(getContainerOffsetBound().size);
  36. }
  37. math::Vec ScrollWidget::getHandleSize() {
  38. return box.size.div(containerBox.size);
  39. }
  40. bool ScrollWidget::isScrolling() {
  41. return internal->scrolling;
  42. }
  43. void ScrollWidget::draw(const DrawArgs& args) {
  44. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  45. Widget::draw(args);
  46. nvgResetScissor(args.vg);
  47. }
  48. void ScrollWidget::step() {
  49. Widget::step();
  50. // Set containerBox cache
  51. containerBox = container->getVisibleChildrenBoundingBox();
  52. // Clamp scroll offset
  53. math::Rect offsetBounds = getContainerOffsetBound();
  54. offset = offset.clamp(offsetBounds);
  55. // Update the container's position from the offset
  56. container->box.pos = offset.neg().round();
  57. // Make scrollbars visible only if there is a positive range to scroll.
  58. if (hideScrollbars) {
  59. horizontalScrollbar->setVisible(false);
  60. verticalScrollbar->setVisible(false);
  61. }
  62. else {
  63. horizontalScrollbar->setVisible(offsetBounds.size.x > 0.f);
  64. verticalScrollbar->setVisible(offsetBounds.size.y > 0.f);
  65. }
  66. // Reposition and resize scroll bars
  67. math::Vec inner = box.size.minus(math::Vec(verticalScrollbar->box.size.x, horizontalScrollbar->box.size.y));
  68. horizontalScrollbar->box.pos.y = inner.y;
  69. verticalScrollbar->box.pos.x = inner.x;
  70. horizontalScrollbar->box.size.x = verticalScrollbar->isVisible() ? inner.x : box.size.x;
  71. verticalScrollbar->box.size.y = horizontalScrollbar->isVisible() ? inner.y : box.size.y;
  72. }
  73. void ScrollWidget::onHover(const HoverEvent& e) {
  74. OpaqueWidget::onHover(e);
  75. if (!e.mouseDelta.isZero()) {
  76. internal->scrolling = false;
  77. }
  78. }
  79. void ScrollWidget::onButton(const ButtonEvent& e) {
  80. math::Rect offsetBound = getContainerOffsetBound();
  81. // Check if scrollable
  82. if (offsetBound.size.x > 0.f || offsetBound.size.y > 0.f) {
  83. // Handle Alt-click before children, since most widgets consume Alt-click without needing to.
  84. if (e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == GLFW_MOD_ALT) {
  85. e.consume(this);
  86. return;
  87. }
  88. // Might as well handle middle click before children as well.
  89. if (e.button == GLFW_MOUSE_BUTTON_MIDDLE) {
  90. e.consume(this);
  91. return;
  92. }
  93. }
  94. Widget::onButton(e);
  95. }
  96. void ScrollWidget::onDragStart(const DragStartEvent& e) {
  97. e.consume(this);
  98. }
  99. void ScrollWidget::onDragMove(const DragMoveEvent& e) {
  100. math::Vec offsetDelta = e.mouseDelta.div(getAbsoluteZoom());
  101. offset = offset.minus(offsetDelta);
  102. }
  103. void ScrollWidget::onHoverScroll(const HoverScrollEvent& e) {
  104. OpaqueWidget::onHoverScroll(e);
  105. if (e.isConsumed())
  106. return;
  107. int mods = APP->window->getMods();
  108. // Don't scroll when Ctrl is held because this interferes with RackScrollWidget zooming.
  109. if ((mods & RACK_MOD_MASK) & RACK_MOD_CTRL)
  110. return;
  111. // Check if scrollable
  112. math::Rect offsetBound = getContainerOffsetBound();
  113. if (offsetBound.size.x <= 0.f && offsetBound.size.y <= 0.f)
  114. return;
  115. math::Vec scrollDelta = e.scrollDelta;
  116. // Flip coordinates if shift is held
  117. // Mac (or GLFW?) already does this for us.
  118. #if !defined ARCH_MAC
  119. if ((mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT)
  120. scrollDelta = scrollDelta.flip();
  121. #endif
  122. offset = offset.minus(scrollDelta);
  123. e.consume(this);
  124. internal->scrolling = true;
  125. }
  126. void ScrollWidget::onHoverKey(const HoverKeyEvent& e) {
  127. OpaqueWidget::onHoverKey(e);
  128. if (e.isConsumed())
  129. return;
  130. // Check if scrollable
  131. math::Rect offsetBound = getContainerOffsetBound();
  132. if (offsetBound.size.x <= 0.f && offsetBound.size.y <= 0.f)
  133. return;
  134. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  135. if (e.key == GLFW_KEY_PAGE_UP && (e.mods & RACK_MOD_MASK) == 0) {
  136. offset.y -= box.size.y * 0.5;
  137. e.consume(this);
  138. }
  139. if (e.key == GLFW_KEY_PAGE_UP && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  140. offset.x -= box.size.x * 0.5;
  141. e.consume(this);
  142. }
  143. if (e.key == GLFW_KEY_PAGE_DOWN && (e.mods & RACK_MOD_MASK) == 0) {
  144. offset.y += box.size.y * 0.5;
  145. e.consume(this);
  146. }
  147. if (e.key == GLFW_KEY_PAGE_DOWN && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  148. offset.x += box.size.x * 0.5;
  149. e.consume(this);
  150. }
  151. if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) {
  152. math::Rect containerBox = container->getVisibleChildrenBoundingBox();
  153. offset.y = containerBox.getTop();
  154. e.consume(this);
  155. }
  156. if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  157. math::Rect containerBox = container->getVisibleChildrenBoundingBox();
  158. offset.x = containerBox.getLeft();
  159. e.consume(this);
  160. }
  161. if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) {
  162. math::Rect containerBox = container->getVisibleChildrenBoundingBox();
  163. offset.y = containerBox.getBottom();
  164. e.consume(this);
  165. }
  166. if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  167. math::Rect containerBox = container->getVisibleChildrenBoundingBox();
  168. offset.x = containerBox.getRight();
  169. e.consume(this);
  170. }
  171. }
  172. }
  173. } // namespace ui
  174. } // namespace rack