GScrollBar.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. #include <LibDraw/CharacterBitmap.h>
  2. #include <LibDraw/GraphicsBitmap.h>
  3. #include <LibDraw/StylePainter.h>
  4. #include <LibGUI/GPainter.h>
  5. #include <LibGUI/GScrollBar.h>
  6. static const char* s_up_arrow_bitmap_data = {
  7. " "
  8. " # "
  9. " ### "
  10. " ##### "
  11. " ####### "
  12. " ### "
  13. " ### "
  14. " ### "
  15. " "
  16. };
  17. static const char* s_down_arrow_bitmap_data = {
  18. " "
  19. " ### "
  20. " ### "
  21. " ### "
  22. " ####### "
  23. " ##### "
  24. " ### "
  25. " # "
  26. " "
  27. };
  28. static const char* s_left_arrow_bitmap_data = {
  29. " "
  30. " # "
  31. " ## "
  32. " ###### "
  33. " ####### "
  34. " ###### "
  35. " ## "
  36. " # "
  37. " "
  38. };
  39. static const char* s_right_arrow_bitmap_data = {
  40. " "
  41. " # "
  42. " ## "
  43. " ###### "
  44. " ####### "
  45. " ###### "
  46. " ## "
  47. " # "
  48. " "
  49. };
  50. static CharacterBitmap* s_up_arrow_bitmap;
  51. static CharacterBitmap* s_down_arrow_bitmap;
  52. static CharacterBitmap* s_left_arrow_bitmap;
  53. static CharacterBitmap* s_right_arrow_bitmap;
  54. GScrollBar::GScrollBar(GWidget* parent)
  55. : GScrollBar(Orientation::Vertical, parent)
  56. {
  57. }
  58. GScrollBar::GScrollBar(Orientation orientation, GWidget* parent)
  59. : GWidget(parent)
  60. , m_orientation(orientation)
  61. {
  62. m_automatic_scrolling_timer = CTimer::construct(this);
  63. if (!s_up_arrow_bitmap)
  64. s_up_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_up_arrow_bitmap_data, 9, 9).leak_ref();
  65. if (!s_down_arrow_bitmap)
  66. s_down_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_down_arrow_bitmap_data, 9, 9).leak_ref();
  67. if (!s_left_arrow_bitmap)
  68. s_left_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_left_arrow_bitmap_data, 9, 9).leak_ref();
  69. if (!s_right_arrow_bitmap)
  70. s_right_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_right_arrow_bitmap_data, 9, 9).leak_ref();
  71. if (m_orientation == Orientation::Vertical) {
  72. set_preferred_size(15, 0);
  73. } else {
  74. set_preferred_size(0, 15);
  75. }
  76. m_automatic_scrolling_timer->set_interval(100);
  77. m_automatic_scrolling_timer->on_timeout = [this] {
  78. on_automatic_scrolling_timer_fired();
  79. };
  80. }
  81. GScrollBar::~GScrollBar()
  82. {
  83. }
  84. void GScrollBar::set_range(int min, int max)
  85. {
  86. ASSERT(min <= max);
  87. if (m_min == min && m_max == max)
  88. return;
  89. m_min = min;
  90. m_max = max;
  91. int old_value = m_value;
  92. if (m_value < m_min)
  93. m_value = m_min;
  94. if (m_value > m_max)
  95. m_value = m_max;
  96. if (on_change && m_value != old_value)
  97. on_change(m_value);
  98. update();
  99. }
  100. void GScrollBar::set_value(int value)
  101. {
  102. if (value < m_min)
  103. value = m_min;
  104. if (value > m_max)
  105. value = m_max;
  106. if (value == m_value)
  107. return;
  108. m_value = value;
  109. if (on_change)
  110. on_change(value);
  111. update();
  112. }
  113. Rect GScrollBar::decrement_button_rect() const
  114. {
  115. return { 0, 0, button_width(), button_height() };
  116. }
  117. Rect GScrollBar::increment_button_rect() const
  118. {
  119. if (orientation() == Orientation::Vertical)
  120. return { 0, height() - button_height(), button_width(), button_height() };
  121. else
  122. return { width() - button_width(), 0, button_width(), button_height() };
  123. }
  124. Rect GScrollBar::decrement_gutter_rect() const
  125. {
  126. if (orientation() == Orientation::Vertical)
  127. return { 0, button_height(), button_width(), scrubber_rect().top() - button_height() };
  128. else
  129. return { button_width(), 0, scrubber_rect().x() - button_width(), button_height() };
  130. }
  131. Rect GScrollBar::increment_gutter_rect() const
  132. {
  133. auto scrubber_rect = this->scrubber_rect();
  134. if (orientation() == Orientation::Vertical)
  135. return { 0, scrubber_rect.bottom() + 1, button_width(), height() - button_height() - scrubber_rect.bottom() - 1 };
  136. else
  137. return { scrubber_rect.right() + 1, 0, width() - button_width() - scrubber_rect.right() - 1, button_width() };
  138. }
  139. int GScrollBar::scrubbable_range_in_pixels() const
  140. {
  141. if (orientation() == Orientation::Vertical)
  142. return height() - button_height() * 2 - scrubber_size();
  143. else
  144. return width() - button_width() * 2 - scrubber_size();
  145. }
  146. bool GScrollBar::has_scrubber() const
  147. {
  148. return m_max != m_min;
  149. }
  150. int GScrollBar::scrubber_size() const
  151. {
  152. int pixel_range = length(orientation()) - button_size() * 2;
  153. int value_range = m_max - m_min;
  154. return ::max(pixel_range - value_range, button_size());
  155. }
  156. Rect GScrollBar::scrubber_rect() const
  157. {
  158. if (!has_scrubber() || length(orientation()) <= (button_size() * 2) + scrubber_size())
  159. return {};
  160. float x_or_y;
  161. if (m_value == m_min)
  162. x_or_y = button_size();
  163. else if (m_value == m_max)
  164. x_or_y = (length(orientation()) - button_size() - scrubber_size()) + 1;
  165. else {
  166. float range_size = m_max - m_min;
  167. float available = scrubbable_range_in_pixels();
  168. float step = available / range_size;
  169. x_or_y = (button_size() + (step * m_value));
  170. }
  171. if (orientation() == Orientation::Vertical)
  172. return { 0, (int)x_or_y, button_width(), scrubber_size() };
  173. else
  174. return { (int)x_or_y, 0, scrubber_size(), button_height() };
  175. }
  176. void GScrollBar::paint_event(GPaintEvent& event)
  177. {
  178. GPainter painter(*this);
  179. painter.add_clip_rect(event.rect());
  180. painter.fill_rect(rect(), Color(SystemColor::Button).lightened());
  181. StylePainter::paint_button(painter, decrement_button_rect(), ButtonStyle::Normal, false, m_hovered_component == Component::DecrementButton);
  182. StylePainter::paint_button(painter, increment_button_rect(), ButtonStyle::Normal, false, m_hovered_component == Component::IncrementButton);
  183. if (length(orientation()) > default_button_size()) {
  184. painter.draw_bitmap(decrement_button_rect().location().translated(3, 3), orientation() == Orientation::Vertical ? *s_up_arrow_bitmap : *s_left_arrow_bitmap, has_scrubber() ? SystemColor::ButtonText : SystemColor::DisabledText);
  185. painter.draw_bitmap(increment_button_rect().location().translated(3, 3), orientation() == Orientation::Vertical ? *s_down_arrow_bitmap : *s_right_arrow_bitmap, has_scrubber() ? SystemColor::ButtonText : SystemColor::DisabledText);
  186. }
  187. if (has_scrubber())
  188. StylePainter::paint_button(painter, scrubber_rect(), ButtonStyle::Normal, false, m_hovered_component == Component::Scrubber || m_scrubber_in_use);
  189. }
  190. void GScrollBar::on_automatic_scrolling_timer_fired()
  191. {
  192. if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Decrement) {
  193. set_value(value() - m_step);
  194. return;
  195. }
  196. if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Increment) {
  197. set_value(value() + m_step);
  198. return;
  199. }
  200. }
  201. void GScrollBar::mousedown_event(GMouseEvent& event)
  202. {
  203. if (event.button() != GMouseButton::Left)
  204. return;
  205. if (decrement_button_rect().contains(event.position())) {
  206. m_automatic_scrolling_direction = AutomaticScrollingDirection::Decrement;
  207. set_automatic_scrolling_active(true);
  208. return;
  209. }
  210. if (increment_button_rect().contains(event.position())) {
  211. m_automatic_scrolling_direction = AutomaticScrollingDirection::Increment;
  212. set_automatic_scrolling_active(true);
  213. return;
  214. }
  215. if (has_scrubber() && scrubber_rect().contains(event.position())) {
  216. m_scrubber_in_use = true;
  217. m_scrubbing = true;
  218. m_scrub_start_value = value();
  219. m_scrub_origin = event.position();
  220. update();
  221. return;
  222. }
  223. if (has_scrubber()) {
  224. float range_size = m_max - m_min;
  225. float available = scrubbable_range_in_pixels();
  226. float x = ::max(0, event.position().x() - button_width() - button_width() / 2);
  227. float y = ::max(0, event.position().y() - button_height() - button_height() / 2);
  228. float rel_x = x / available;
  229. float rel_y = y / available;
  230. if (orientation() == Orientation::Vertical)
  231. set_value(m_min + rel_y * range_size);
  232. else
  233. set_value(m_min + rel_x * range_size);
  234. m_scrubbing = true;
  235. m_scrub_start_value = value();
  236. m_scrub_origin = event.position();
  237. }
  238. }
  239. void GScrollBar::mouseup_event(GMouseEvent& event)
  240. {
  241. if (event.button() != GMouseButton::Left)
  242. return;
  243. m_scrubber_in_use = false;
  244. m_automatic_scrolling_direction = AutomaticScrollingDirection::None;
  245. set_automatic_scrolling_active(false);
  246. if (!m_scrubbing)
  247. return;
  248. m_scrubbing = false;
  249. update();
  250. }
  251. void GScrollBar::mousewheel_event(GMouseEvent& event)
  252. {
  253. if (!is_scrollable())
  254. return;
  255. set_value(value() + event.wheel_delta() * m_step);
  256. GWidget::mousewheel_event(event);
  257. }
  258. void GScrollBar::set_automatic_scrolling_active(bool active)
  259. {
  260. if (active) {
  261. on_automatic_scrolling_timer_fired();
  262. m_automatic_scrolling_timer->start();
  263. } else {
  264. m_automatic_scrolling_timer->stop();
  265. }
  266. }
  267. void GScrollBar::mousemove_event(GMouseEvent& event)
  268. {
  269. auto old_hovered_component = m_hovered_component;
  270. if (scrubber_rect().contains(event.position()))
  271. m_hovered_component = Component::Scrubber;
  272. else if (decrement_button_rect().contains(event.position()))
  273. m_hovered_component = Component::DecrementButton;
  274. else if (increment_button_rect().contains(event.position()))
  275. m_hovered_component = Component::IncrementButton;
  276. else if (rect().contains(event.position()))
  277. m_hovered_component = Component::Gutter;
  278. else
  279. m_hovered_component = Component::Invalid;
  280. if (old_hovered_component != m_hovered_component) {
  281. update();
  282. if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Decrement)
  283. set_automatic_scrolling_active(m_hovered_component == Component::DecrementButton);
  284. else if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Increment)
  285. set_automatic_scrolling_active(m_hovered_component == Component::IncrementButton);
  286. }
  287. if (!m_scrubbing)
  288. return;
  289. float delta = orientation() == Orientation::Vertical ? (event.y() - m_scrub_origin.y()) : (event.x() - m_scrub_origin.x());
  290. float scrubbable_range = scrubbable_range_in_pixels();
  291. float value_steps_per_scrubbed_pixel = (m_max - m_min) / scrubbable_range;
  292. float new_value = m_scrub_start_value + (value_steps_per_scrubbed_pixel * delta);
  293. set_value(new_value);
  294. }
  295. void GScrollBar::leave_event(CEvent&)
  296. {
  297. if (m_hovered_component != Component::Invalid) {
  298. m_hovered_component = Component::Invalid;
  299. update();
  300. }
  301. }
  302. void GScrollBar::change_event(GEvent& event)
  303. {
  304. if (event.type() == GEvent::Type::EnabledChange) {
  305. if (!is_enabled())
  306. m_scrubbing = false;
  307. }
  308. return GWidget::change_event(event);
  309. }