AbstractZoomPanWidget.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /*
  2. * Copyright (c) 2022, Mustafa Quraish <mustafa@serenityos.org>
  3. * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "AbstractZoomPanWidget.h"
  8. namespace GUI {
  9. constexpr float wheel_zoom_factor = 8.0f;
  10. void AbstractZoomPanWidget::set_scale(float new_scale)
  11. {
  12. if (m_original_rect.is_null())
  13. return;
  14. m_scale = clamp(new_scale, m_min_scale, m_max_scale);
  15. m_content_rect.set_size({
  16. m_original_rect.width() * m_scale,
  17. m_original_rect.height() * m_scale,
  18. });
  19. if (on_scale_change)
  20. on_scale_change(m_scale);
  21. relayout();
  22. }
  23. void AbstractZoomPanWidget::scale_by(float delta)
  24. {
  25. float new_scale = m_scale * AK::exp2(delta);
  26. set_scale(new_scale);
  27. }
  28. void AbstractZoomPanWidget::scale_centered(float new_scale, Gfx::IntPoint const& center)
  29. {
  30. if (m_original_rect.is_null())
  31. return;
  32. new_scale = clamp(new_scale, m_min_scale, m_max_scale);
  33. if (new_scale == m_scale)
  34. return;
  35. Gfx::FloatPoint focus_point {
  36. center.x() - width() / 2.0f,
  37. center.y() - height() / 2.0f,
  38. };
  39. m_origin = (m_origin + focus_point) * (new_scale / m_scale) - focus_point;
  40. set_scale(new_scale);
  41. }
  42. void AbstractZoomPanWidget::start_panning(Gfx::IntPoint const& position)
  43. {
  44. m_saved_cursor = override_cursor();
  45. set_override_cursor(Gfx::StandardCursor::Drag);
  46. m_pan_start = m_origin;
  47. m_pan_mouse_pos = position;
  48. m_is_panning = true;
  49. }
  50. void AbstractZoomPanWidget::stop_panning()
  51. {
  52. m_is_panning = false;
  53. set_override_cursor(m_saved_cursor);
  54. }
  55. void AbstractZoomPanWidget::pan_to(Gfx::IntPoint const& position)
  56. {
  57. // NOTE: `position` here (and `m_pan_mouse_pos`) are both in frame coordinates, not
  58. // content coordinates, by design. The derived class should not have to keep track of
  59. // the (zoomed) content coordinates itself, but just pass along the mouse position.
  60. auto delta = position - m_pan_mouse_pos;
  61. m_origin = m_pan_start.translated(-delta.x(), -delta.y());
  62. relayout();
  63. }
  64. Gfx::FloatPoint AbstractZoomPanWidget::frame_to_content_position(Gfx::IntPoint const& frame_position) const
  65. {
  66. return {
  67. (static_cast<float>(frame_position.x()) - m_content_rect.x()) / m_scale,
  68. (static_cast<float>(frame_position.y()) - m_content_rect.y()) / m_scale,
  69. };
  70. }
  71. Gfx::FloatRect AbstractZoomPanWidget::frame_to_content_rect(Gfx::IntRect const& frame_rect) const
  72. {
  73. Gfx::FloatRect content_rect;
  74. content_rect.set_location(frame_to_content_position(frame_rect.location()));
  75. content_rect.set_size({
  76. frame_rect.width() / m_scale,
  77. frame_rect.height() / m_scale,
  78. });
  79. return content_rect;
  80. }
  81. Gfx::FloatPoint AbstractZoomPanWidget::content_to_frame_position(Gfx::IntPoint const& content_position) const
  82. {
  83. return {
  84. m_content_rect.x() + content_position.x() * m_scale,
  85. m_content_rect.y() + content_position.y() * m_scale,
  86. };
  87. }
  88. Gfx::FloatRect AbstractZoomPanWidget::content_to_frame_rect(Gfx::IntRect const& content_rect) const
  89. {
  90. Gfx::FloatRect frame_rect;
  91. frame_rect.set_location(content_to_frame_position(content_rect.location()));
  92. frame_rect.set_size({
  93. content_rect.width() * m_scale,
  94. content_rect.height() * m_scale,
  95. });
  96. return frame_rect;
  97. }
  98. void AbstractZoomPanWidget::mousewheel_event(GUI::MouseEvent& event)
  99. {
  100. float new_scale = scale() / AK::exp2(event.wheel_delta_y() / wheel_zoom_factor);
  101. scale_centered(new_scale, event.position());
  102. }
  103. void AbstractZoomPanWidget::mousedown_event(GUI::MouseEvent& event)
  104. {
  105. if (!m_is_panning && event.button() == GUI::MouseButton::Middle) {
  106. start_panning(event.position());
  107. event.accept();
  108. return;
  109. }
  110. }
  111. void AbstractZoomPanWidget::resize_event(GUI::ResizeEvent& event)
  112. {
  113. relayout();
  114. GUI::Widget::resize_event(event);
  115. }
  116. void AbstractZoomPanWidget::mousemove_event(GUI::MouseEvent& event)
  117. {
  118. if (!m_is_panning)
  119. return;
  120. pan_to(event.position());
  121. event.accept();
  122. }
  123. void AbstractZoomPanWidget::mouseup_event(GUI::MouseEvent& event)
  124. {
  125. if (m_is_panning && event.button() == GUI::MouseButton::Middle) {
  126. stop_panning();
  127. event.accept();
  128. return;
  129. }
  130. }
  131. void AbstractZoomPanWidget::relayout()
  132. {
  133. if (m_original_rect.is_null())
  134. return;
  135. m_content_rect.set_location({
  136. (width() / 2) - (m_content_rect.width() / 2) - m_origin.x(),
  137. (height() / 2) - (m_content_rect.height() / 2) - m_origin.y(),
  138. });
  139. handle_relayout(m_content_rect);
  140. }
  141. void AbstractZoomPanWidget::reset_view()
  142. {
  143. m_origin = { 0, 0 };
  144. set_scale(1.0f);
  145. }
  146. void AbstractZoomPanWidget::set_content_rect(Gfx::IntRect const& content_rect)
  147. {
  148. m_content_rect = enclosing_int_rect(content_to_frame_rect(content_rect));
  149. update();
  150. }
  151. void AbstractZoomPanWidget::set_scale_bounds(float min_scale, float max_scale)
  152. {
  153. m_min_scale = min_scale;
  154. m_max_scale = max_scale;
  155. }
  156. void AbstractZoomPanWidget::fit_content_to_rect(Gfx::IntRect const& viewport_rect, FitType type)
  157. {
  158. float const border_ratio = 0.95f;
  159. auto image_size = m_original_rect.size();
  160. auto height_ratio = floorf(border_ratio * viewport_rect.height()) / image_size.height();
  161. auto width_ratio = floorf(border_ratio * viewport_rect.width()) / image_size.width();
  162. float new_scale = 1.0f;
  163. switch (type) {
  164. case FitType::Width:
  165. new_scale = width_ratio;
  166. break;
  167. case FitType::Height:
  168. new_scale = height_ratio;
  169. break;
  170. case FitType::Both:
  171. new_scale = min(height_ratio, width_ratio);
  172. break;
  173. }
  174. auto const& offset = rect().center() - viewport_rect.center();
  175. set_origin({ offset.x(), offset.y() });
  176. set_scale(new_scale);
  177. }
  178. }