MoveTool.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "MoveTool.h"
  8. #include "../Image.h"
  9. #include "../ImageEditor.h"
  10. #include "../Layer.h"
  11. #include <AK/String.h>
  12. #include <LibGUI/Action.h>
  13. #include <LibGUI/BoxLayout.h>
  14. #include <LibGUI/Label.h>
  15. #include <LibGUI/Menu.h>
  16. #include <LibGUI/MessageBox.h>
  17. #include <LibGUI/Painter.h>
  18. #include <LibGUI/Window.h>
  19. #include <LibGfx/Filters/ContrastFilter.h>
  20. namespace PixelPaint {
  21. void MoveTool::on_mousedown(Layer* layer, MouseEvent& event)
  22. {
  23. if (event.image_event().button() == GUI::MouseButton::Secondary) {
  24. m_editor->start_panning(event.raw_event().position());
  25. return;
  26. }
  27. if (!layer)
  28. return;
  29. auto& layer_event = event.layer_event();
  30. auto& image_event = event.image_event();
  31. if (layer_event.button() != GUI::MouseButton::Primary)
  32. return;
  33. if (!layer->rect().contains(layer_event.position()) && !m_resize_anchor_location.has_value())
  34. return;
  35. m_scaling = m_resize_anchor_location.has_value();
  36. m_layer_being_moved = *layer;
  37. m_event_origin = image_event.position();
  38. m_layer_origin = layer->location();
  39. m_new_layer_rect = m_editor->active_layer()->rect();
  40. }
  41. void MoveTool::on_mousemove(Layer* layer, MouseEvent& event)
  42. {
  43. if (m_editor->is_panning()) {
  44. m_editor->pan_to(event.raw_event().position());
  45. return;
  46. }
  47. if (!layer)
  48. return;
  49. if (!m_scaling) {
  50. auto current_resize_anchor_location = resize_anchor_location_from_cursor_position(layer, event);
  51. if (m_resize_anchor_location != current_resize_anchor_location) {
  52. m_resize_anchor_location = current_resize_anchor_location;
  53. m_editor->update_tool_cursor();
  54. }
  55. }
  56. if (!m_layer_being_moved)
  57. return;
  58. auto cursor_position = event.image_event().position();
  59. auto delta = cursor_position - m_event_origin;
  60. auto rect_being_moved = m_layer_being_moved->relative_rect();
  61. Gfx::IntPoint scaling_origin;
  62. Gfx::IntPoint opposite_corner;
  63. if (m_scaling) {
  64. VERIFY(m_resize_anchor_location.has_value());
  65. switch (m_resize_anchor_location.value()) {
  66. case ResizeAnchorLocation::TopLeft:
  67. scaling_origin = rect_being_moved.top_left();
  68. opposite_corner = rect_being_moved.bottom_right().translated(1, 1);
  69. break;
  70. case ResizeAnchorLocation::BottomRight:
  71. scaling_origin = rect_being_moved.bottom_right().translated(1, 1);
  72. opposite_corner = rect_being_moved.top_left();
  73. break;
  74. case ResizeAnchorLocation::BottomLeft:
  75. scaling_origin = rect_being_moved.bottom_left().translated(0, 1);
  76. opposite_corner = rect_being_moved.top_right().translated(1, 0);
  77. break;
  78. case ResizeAnchorLocation::TopRight:
  79. scaling_origin = rect_being_moved.top_right().translated(1, 0);
  80. opposite_corner = rect_being_moved.bottom_left().translated(0, 1);
  81. break;
  82. }
  83. scaling_origin.translate_by(delta);
  84. if (m_keep_aspect_ratio) {
  85. auto aspect_ratio = m_layer_being_moved->size().aspect_ratio();
  86. scaling_origin = opposite_corner.end_point_for_aspect_ratio(scaling_origin, aspect_ratio);
  87. }
  88. auto scaled_rect = Gfx::IntRect::from_two_points(scaling_origin, opposite_corner);
  89. if (!scaled_rect.is_empty())
  90. m_new_layer_rect = scaled_rect;
  91. } else {
  92. m_layer_being_moved->set_location(m_layer_origin.translated(delta));
  93. }
  94. m_editor->update();
  95. }
  96. void MoveTool::on_mouseup(Layer* layer, MouseEvent& event)
  97. {
  98. if (event.image_event().button() == GUI::MouseButton::Secondary) {
  99. m_editor->stop_panning();
  100. m_editor->set_override_cursor(cursor());
  101. return;
  102. }
  103. if (!layer)
  104. return;
  105. auto& layer_event = event.layer_event();
  106. if (layer_event.button() != GUI::MouseButton::Primary)
  107. return;
  108. if (m_scaling) {
  109. auto resized_or_error = m_editor->active_layer()->resize(m_new_layer_rect, Gfx::Painter::ScalingMode::BilinearBlend);
  110. if (resized_or_error.is_error())
  111. GUI::MessageBox::show_error(m_editor->window(), MUST(String::formatted("Failed to resize layer: {}", resized_or_error.error().string_literal())));
  112. else
  113. m_editor->layers_did_change();
  114. }
  115. m_scaling = false;
  116. m_layer_being_moved = nullptr;
  117. m_cached_preview_bitmap = nullptr;
  118. m_editor->update_tool_cursor();
  119. m_editor->did_complete_action(tool_name());
  120. }
  121. bool MoveTool::on_keydown(GUI::KeyEvent& event)
  122. {
  123. if (event.key() == Key_Shift)
  124. m_keep_aspect_ratio = true;
  125. if (event.key() == Key_Alt)
  126. toggle_selection_mode();
  127. if (m_scaling)
  128. return true;
  129. if (!(event.modifiers() == Mod_None || event.modifiers() == Mod_Shift))
  130. return false;
  131. auto* layer = m_editor->active_layer();
  132. if (!layer)
  133. return false;
  134. auto new_location = layer->location();
  135. auto speed = event.shift() ? 10 : 1;
  136. switch (event.key()) {
  137. case Key_Up:
  138. new_location.translate_by(0, -speed);
  139. break;
  140. case Key_Down:
  141. new_location.translate_by(0, speed);
  142. break;
  143. case Key_Left:
  144. new_location.translate_by(-speed, 0);
  145. break;
  146. case Key_Right:
  147. new_location.translate_by(speed, 0);
  148. break;
  149. default:
  150. return false;
  151. }
  152. layer->set_location(new_location);
  153. m_editor->layers_did_change();
  154. return true;
  155. }
  156. void MoveTool::on_keyup(GUI::KeyEvent& event)
  157. {
  158. if (event.key() == Key_Shift)
  159. m_keep_aspect_ratio = false;
  160. if (event.key() == Key_Alt)
  161. toggle_selection_mode();
  162. }
  163. void MoveTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event)
  164. {
  165. if (layer != m_layer_being_moved.ptr() || !m_scaling)
  166. return;
  167. GUI::Painter painter(*m_editor);
  168. painter.add_clip_rect(event.rect());
  169. auto rect_in_editor = m_editor->content_to_frame_rect(m_new_layer_rect).to_rounded<int>();
  170. painter.draw_rect(rect_in_editor, Color::Black);
  171. if (!m_cached_preview_bitmap.is_null() || !update_cached_preview_bitmap(layer).is_error()) {
  172. painter.add_clip_rect(m_editor->content_rect());
  173. painter.draw_scaled_bitmap(rect_in_editor, *m_cached_preview_bitmap, m_cached_preview_bitmap->rect(), 1.0f, Gfx::Painter::ScalingMode::BilinearBlend);
  174. }
  175. }
  176. ErrorOr<void> MoveTool::update_cached_preview_bitmap(Layer const* layer)
  177. {
  178. auto editor_rect_size = m_editor->frame_inner_rect().size();
  179. auto const& source_bitmap = layer->content_bitmap();
  180. auto preview_bitmap_size = editor_rect_size.contains(source_bitmap.size()) ? source_bitmap.size() : editor_rect_size;
  181. m_cached_preview_bitmap = TRY(Gfx::Bitmap::try_create(source_bitmap.format(), preview_bitmap_size));
  182. GUI::Painter preview_painter(*m_cached_preview_bitmap);
  183. preview_painter.draw_scaled_bitmap(m_cached_preview_bitmap->rect(), source_bitmap, source_bitmap.rect(), 0.8f, Gfx::Painter::ScalingMode::BilinearBlend);
  184. Gfx::ContrastFilter preview_filter(0.5f);
  185. preview_filter.apply(*m_cached_preview_bitmap, m_cached_preview_bitmap->rect(), *m_cached_preview_bitmap, m_cached_preview_bitmap->rect());
  186. return {};
  187. }
  188. Optional<ResizeAnchorLocation const> MoveTool::resize_anchor_location_from_cursor_position(Layer const* layer, MouseEvent& event)
  189. {
  190. auto cursor_within_resize_anchor_rect = [&](Gfx::IntPoint layer_position) {
  191. constexpr int sensitivity = 20;
  192. auto resize_anchor_rect_center = m_editor->content_to_frame_position(layer_position).to_rounded<int>();
  193. auto resize_anchor_rect_top_left = resize_anchor_rect_center.translated(-sensitivity / 2);
  194. auto resize_anchor_rect = Gfx::IntRect(resize_anchor_rect_top_left, Gfx::IntSize(sensitivity, sensitivity));
  195. return resize_anchor_rect.contains(event.raw_event().position());
  196. };
  197. auto layer_rect = layer->relative_rect();
  198. if (cursor_within_resize_anchor_rect(layer_rect.top_left()))
  199. return ResizeAnchorLocation::TopLeft;
  200. if (cursor_within_resize_anchor_rect(layer_rect.top_right().translated(1, 0)))
  201. return ResizeAnchorLocation::TopRight;
  202. if (cursor_within_resize_anchor_rect(layer_rect.bottom_left().translated(0, 1)))
  203. return ResizeAnchorLocation::BottomLeft;
  204. if (cursor_within_resize_anchor_rect(layer_rect.bottom_right().translated(1)))
  205. return ResizeAnchorLocation::BottomRight;
  206. return {};
  207. }
  208. Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap>> MoveTool::cursor()
  209. {
  210. if (m_resize_anchor_location.has_value()) {
  211. switch (m_resize_anchor_location.value()) {
  212. case ResizeAnchorLocation::TopLeft:
  213. case ResizeAnchorLocation::BottomRight:
  214. return Gfx::StandardCursor::ResizeDiagonalTLBR;
  215. case ResizeAnchorLocation::BottomLeft:
  216. case ResizeAnchorLocation::TopRight:
  217. return Gfx::StandardCursor::ResizeDiagonalBLTR;
  218. }
  219. }
  220. return Gfx::StandardCursor::Move;
  221. }
  222. GUI::Widget* MoveTool::get_properties_widget()
  223. {
  224. if (!m_properties_widget) {
  225. m_properties_widget = GUI::Widget::construct();
  226. m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
  227. auto& selection_mode_container = m_properties_widget->add<GUI::Widget>();
  228. selection_mode_container.set_layout<GUI::HorizontalBoxLayout>();
  229. selection_mode_container.set_fixed_height(46);
  230. auto& selection_mode_label = selection_mode_container.add<GUI::Label>("Selection Mode:");
  231. selection_mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  232. selection_mode_label.set_fixed_size(80, 40);
  233. auto& mode_radio_container = selection_mode_container.add<GUI::Widget>();
  234. mode_radio_container.set_layout<GUI::VerticalBoxLayout>();
  235. m_selection_mode_foreground = mode_radio_container.add<GUI::RadioButton>("Foreground");
  236. m_selection_mode_active = mode_radio_container.add<GUI::RadioButton>("Active Layer");
  237. m_selection_mode_foreground->on_checked = [&](bool) {
  238. m_layer_selection_mode = LayerSelectionMode::ForegroundLayer;
  239. };
  240. m_selection_mode_active->on_checked = [&](bool) {
  241. m_layer_selection_mode = LayerSelectionMode::ActiveLayer;
  242. };
  243. m_selection_mode_foreground->set_checked(true);
  244. }
  245. return m_properties_widget.ptr();
  246. }
  247. void MoveTool::toggle_selection_mode()
  248. {
  249. if (m_selection_mode_foreground->is_checked())
  250. m_selection_mode_active->set_checked(true);
  251. else
  252. m_selection_mode_foreground->set_checked(true);
  253. }
  254. }