| 1 | // Aseprite UI Library |
| 2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
| 3 | // Copyright (C) 2001-2017 David Capello |
| 4 | // |
| 5 | // This file is released under the terms of the MIT license. |
| 6 | // Read LICENSE.txt for more information. |
| 7 | |
| 8 | #ifdef HAVE_CONFIG_H |
| 9 | #include "config.h" |
| 10 | #endif |
| 11 | |
| 12 | #include "gfx/size.h" |
| 13 | #include "ui/message.h" |
| 14 | #include "ui/paint_event.h" |
| 15 | #include "ui/scroll_bar.h" |
| 16 | #include "ui/theme.h" |
| 17 | |
| 18 | namespace ui { |
| 19 | |
| 20 | using namespace gfx; |
| 21 | |
| 22 | // Internal stuff shared by all scroll-bars (as the user cannot move |
| 23 | // two scroll-bars at the same time). |
| 24 | int ScrollBar::m_wherepos = 0; |
| 25 | int ScrollBar::m_whereclick = 0; |
| 26 | |
| 27 | ScrollBar::ScrollBar(int align, ScrollableViewDelegate* delegate) |
| 28 | : Widget(kViewScrollbarWidget) |
| 29 | , m_delegate(delegate) |
| 30 | , m_thumbStyle(nullptr) |
| 31 | , m_barWidth(0) |
| 32 | , m_pos(0) |
| 33 | , m_size(0) |
| 34 | { |
| 35 | setAlign(align); |
| 36 | initTheme(); |
| 37 | } |
| 38 | |
| 39 | void ScrollBar::setPos(int pos) |
| 40 | { |
| 41 | if (m_pos != pos) { |
| 42 | m_pos = pos; |
| 43 | invalidate(); |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | void ScrollBar::setSize(int size) |
| 48 | { |
| 49 | if (m_size != size) { |
| 50 | m_size = size; |
| 51 | invalidate(); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | void ScrollBar::getScrollBarThemeInfo(int* pos, int* len) |
| 56 | { |
| 57 | getScrollBarInfo(pos, len, nullptr, nullptr); |
| 58 | } |
| 59 | |
| 60 | bool ScrollBar::onProcessMessage(Message* msg) |
| 61 | { |
| 62 | #define MOUSE_IN(x1, y1, x2, y2) \ |
| 63 | ((mousePos.x >= (x1)) && (mousePos.x <= (x2)) && \ |
| 64 | (mousePos.y >= (y1)) && (mousePos.y <= (y2))) |
| 65 | |
| 66 | switch (msg->type()) { |
| 67 | |
| 68 | case kMouseDownMessage: { |
| 69 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
| 70 | int x1, y1, x2, y2; |
| 71 | int u1, v1, u2, v2; |
| 72 | bool ret = false; |
| 73 | int pos, len; |
| 74 | |
| 75 | getScrollBarThemeInfo(&pos, &len); |
| 76 | |
| 77 | m_wherepos = pos; |
| 78 | m_whereclick = (align() & HORIZONTAL) ? |
| 79 | mousePos.x: |
| 80 | mousePos.y; |
| 81 | |
| 82 | x1 = bounds().x; |
| 83 | y1 = bounds().y; |
| 84 | x2 = bounds().x2()-1; |
| 85 | y2 = bounds().y2()-1; |
| 86 | |
| 87 | u1 = x1 + border().left(); |
| 88 | v1 = y1 + border().top(); |
| 89 | u2 = x2 - border().right(); |
| 90 | v2 = y2 - border().bottom(); |
| 91 | |
| 92 | Point scroll = m_delegate->viewScroll(); |
| 93 | |
| 94 | if (align() & HORIZONTAL) { |
| 95 | // in the bar |
| 96 | if (MOUSE_IN(u1+pos, v1, u1+pos+len-1, v2)) { |
| 97 | // capture mouse |
| 98 | } |
| 99 | // left |
| 100 | else if (MOUSE_IN(x1, y1, u1+pos-1, y2)) { |
| 101 | scroll.x -= m_delegate->visibleSize().w/2; |
| 102 | ret = true; |
| 103 | } |
| 104 | // right |
| 105 | else if (MOUSE_IN(u1+pos+len, y1, x2, y2)) { |
| 106 | scroll.x += m_delegate->visibleSize().w/2; |
| 107 | ret = true; |
| 108 | } |
| 109 | } |
| 110 | else { |
| 111 | // in the bar |
| 112 | if (MOUSE_IN(u1, v1+pos, u2, v1+pos+len-1)) { |
| 113 | // capture mouse |
| 114 | } |
| 115 | // left |
| 116 | else if (MOUSE_IN(x1, y1, x2, v1+pos-1)) { |
| 117 | scroll.y -= m_delegate->visibleSize().h/2; |
| 118 | ret = true; |
| 119 | } |
| 120 | // right |
| 121 | else if (MOUSE_IN(x1, v1+pos+len, x2, y2)) { |
| 122 | scroll.y += m_delegate->visibleSize().h/2; |
| 123 | ret = true; |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | if (ret) { |
| 128 | m_delegate->setViewScroll(scroll); |
| 129 | return ret; |
| 130 | } |
| 131 | |
| 132 | setSelected(true); |
| 133 | captureMouse(); |
| 134 | |
| 135 | // Continue to kMouseMoveMessage handler... |
| 136 | [[fallthrough]]; |
| 137 | } |
| 138 | |
| 139 | case kMouseMoveMessage: |
| 140 | if (hasCapture()) { |
| 141 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
| 142 | int pos, len, bar_size, viewport_size; |
| 143 | |
| 144 | getScrollBarInfo(&pos, &len, &bar_size, &viewport_size); |
| 145 | |
| 146 | if (bar_size > len) { |
| 147 | Point scroll = m_delegate->viewScroll(); |
| 148 | |
| 149 | if (align() & HORIZONTAL) { |
| 150 | pos = (m_wherepos + mousePos.x - m_whereclick); |
| 151 | pos = std::clamp(pos, 0, bar_size - len); |
| 152 | |
| 153 | scroll.x = (m_size - viewport_size) * pos / (bar_size - len); |
| 154 | } |
| 155 | else { |
| 156 | pos = (m_wherepos + mousePos.y - m_whereclick); |
| 157 | pos = std::clamp(pos, 0, bar_size - len); |
| 158 | |
| 159 | scroll.y = (m_size - viewport_size) * pos / (bar_size - len); |
| 160 | } |
| 161 | |
| 162 | m_delegate->setViewScroll(scroll); |
| 163 | } |
| 164 | return true; |
| 165 | } |
| 166 | break; |
| 167 | |
| 168 | case kMouseUpMessage: |
| 169 | setSelected(false); |
| 170 | releaseMouse(); |
| 171 | break; |
| 172 | |
| 173 | case kMouseEnterMessage: |
| 174 | case kMouseLeaveMessage: |
| 175 | // TODO add something to avoid this (theme specific stuff) |
| 176 | invalidate(); |
| 177 | break; |
| 178 | } |
| 179 | |
| 180 | return Widget::onProcessMessage(msg); |
| 181 | } |
| 182 | |
| 183 | void ScrollBar::onInitTheme(InitThemeEvent& ev) |
| 184 | { |
| 185 | Widget::onInitTheme(ev); |
| 186 | m_barWidth = theme()->getScrollbarSize(); |
| 187 | } |
| 188 | |
| 189 | void ScrollBar::onPaint(PaintEvent& ev) |
| 190 | { |
| 191 | gfx::Rect thumbBounds = clientBounds(); |
| 192 | if (align() & HORIZONTAL) |
| 193 | getScrollBarThemeInfo(&thumbBounds.x, &thumbBounds.w); |
| 194 | else |
| 195 | getScrollBarThemeInfo(&thumbBounds.y, &thumbBounds.h); |
| 196 | |
| 197 | theme()->paintScrollBar( |
| 198 | ev.graphics(), this, style(), thumbStyle(), |
| 199 | clientBounds(), thumbBounds); |
| 200 | } |
| 201 | |
| 202 | void ScrollBar::getScrollBarInfo(int *_pos, int *_len, int *_bar_size, int *_viewport_size) |
| 203 | { |
| 204 | int bar_size, viewport_size; |
| 205 | int pos, len; |
| 206 | int border_width; |
| 207 | |
| 208 | if (align() & HORIZONTAL) { |
| 209 | bar_size = bounds().w; |
| 210 | viewport_size = m_delegate->visibleSize().w; |
| 211 | border_width = border().height(); |
| 212 | } |
| 213 | else { |
| 214 | bar_size = bounds().h; |
| 215 | viewport_size = m_delegate->visibleSize().h; |
| 216 | border_width = border().width(); |
| 217 | } |
| 218 | |
| 219 | if (m_size <= viewport_size) { |
| 220 | len = bar_size; |
| 221 | pos = 0; |
| 222 | } |
| 223 | else if (m_size > 0) { |
| 224 | len = bar_size * viewport_size / m_size; |
| 225 | len = std::clamp(len, std::min(theme()->getScrollbarSize()*2-border_width, bar_size), bar_size); |
| 226 | pos = (bar_size-len) * m_pos / (m_size-viewport_size); |
| 227 | pos = std::clamp(pos, 0, bar_size-len); |
| 228 | } |
| 229 | else { |
| 230 | len = pos = 0; |
| 231 | } |
| 232 | |
| 233 | if (_pos) *_pos = pos; |
| 234 | if (_len) *_len = len; |
| 235 | if (_bar_size) *_bar_size = bar_size; |
| 236 | if (_viewport_size) *_viewport_size = viewport_size; |
| 237 | } |
| 238 | |
| 239 | } // namespace ui |
| 240 | |