GScrollBar.cpp 10 KB

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