MoveTool.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022-2023, 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()->relative_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. VERIFY(m_editor);
  166. GUI::Painter painter(*m_editor);
  167. painter.add_clip_rect(event.rect());
  168. auto content_rect = m_scaling ? m_new_layer_rect : m_editor->active_layer()->relative_rect();
  169. auto rect_in_editor = m_editor->content_to_frame_rect(content_rect).to_rounded<int>();
  170. if (m_scaling && (!m_cached_preview_bitmap.is_null() || !update_cached_preview_bitmap(layer).is_error())) {
  171. Gfx::PainterStateSaver saver(painter);
  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. painter.draw_rect_with_thickness(rect_in_editor, Color::Black, 3);
  176. painter.draw_rect_with_thickness(rect_in_editor, Color::White, 1);
  177. auto resize_anchors = resize_anchor_rects(rect_in_editor);
  178. for (auto const& resize_anchor_rect : resize_anchors) {
  179. painter.draw_rect_with_thickness(resize_anchor_rect, Color::Black, 3);
  180. painter.draw_rect_with_thickness(resize_anchor_rect, Color::White, 1);
  181. }
  182. }
  183. Gfx::IntRect MoveTool::resize_anchor_rect_from_position(Gfx::IntPoint position)
  184. {
  185. constexpr int resize_anchor_size = 20;
  186. auto resize_anchor_rect_top_left = position.translated(-resize_anchor_size / 2);
  187. return Gfx::IntRect(resize_anchor_rect_top_left, Gfx::IntSize(resize_anchor_size, resize_anchor_size));
  188. }
  189. Array<Gfx::IntRect, 4> MoveTool::resize_anchor_rects(Gfx::IntRect layer_rect_in_frame_coordinates)
  190. {
  191. return Array {
  192. resize_anchor_rect_from_position(layer_rect_in_frame_coordinates.top_left()),
  193. resize_anchor_rect_from_position(layer_rect_in_frame_coordinates.top_right().translated(1, 0)),
  194. resize_anchor_rect_from_position(layer_rect_in_frame_coordinates.bottom_left().translated(0, 1)),
  195. resize_anchor_rect_from_position(layer_rect_in_frame_coordinates.bottom_right().translated(1))
  196. };
  197. }
  198. ErrorOr<void> MoveTool::update_cached_preview_bitmap(Layer const* layer)
  199. {
  200. auto editor_rect_size = m_editor->frame_inner_rect().size();
  201. auto const& source_bitmap = layer->content_bitmap();
  202. auto preview_bitmap_size = editor_rect_size.contains(source_bitmap.size()) ? source_bitmap.size() : editor_rect_size;
  203. m_cached_preview_bitmap = TRY(Gfx::Bitmap::create(source_bitmap.format(), preview_bitmap_size));
  204. GUI::Painter preview_painter(*m_cached_preview_bitmap);
  205. preview_painter.draw_scaled_bitmap(m_cached_preview_bitmap->rect(), source_bitmap, source_bitmap.rect(), 0.8f, Gfx::Painter::ScalingMode::BilinearBlend);
  206. Gfx::ContrastFilter preview_filter(0.5f);
  207. preview_filter.apply(*m_cached_preview_bitmap, m_cached_preview_bitmap->rect(), *m_cached_preview_bitmap, m_cached_preview_bitmap->rect());
  208. return {};
  209. }
  210. Optional<ResizeAnchorLocation const> MoveTool::resize_anchor_location_from_cursor_position(Layer const* layer, MouseEvent& event)
  211. {
  212. auto cursor_within_resize_anchor_rect = [&](Gfx::IntPoint layer_position_in_frame_coordinates) {
  213. auto resize_anchor_rect = resize_anchor_rect_from_position(layer_position_in_frame_coordinates);
  214. return resize_anchor_rect.contains(event.raw_event().position());
  215. };
  216. auto layer_rect = m_editor->content_to_frame_rect(layer->relative_rect()).to_rounded<int>();
  217. if (cursor_within_resize_anchor_rect(layer_rect.top_left()))
  218. return ResizeAnchorLocation::TopLeft;
  219. if (cursor_within_resize_anchor_rect(layer_rect.top_right().translated(1, 0)))
  220. return ResizeAnchorLocation::TopRight;
  221. if (cursor_within_resize_anchor_rect(layer_rect.bottom_left().translated(0, 1)))
  222. return ResizeAnchorLocation::BottomLeft;
  223. if (cursor_within_resize_anchor_rect(layer_rect.bottom_right().translated(1)))
  224. return ResizeAnchorLocation::BottomRight;
  225. return {};
  226. }
  227. Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap>> MoveTool::cursor()
  228. {
  229. if (m_resize_anchor_location.has_value()) {
  230. switch (m_resize_anchor_location.value()) {
  231. case ResizeAnchorLocation::TopLeft:
  232. case ResizeAnchorLocation::BottomRight:
  233. return Gfx::StandardCursor::ResizeDiagonalTLBR;
  234. case ResizeAnchorLocation::BottomLeft:
  235. case ResizeAnchorLocation::TopRight:
  236. return Gfx::StandardCursor::ResizeDiagonalBLTR;
  237. }
  238. }
  239. return Gfx::StandardCursor::Move;
  240. }
  241. GUI::Widget* MoveTool::get_properties_widget()
  242. {
  243. if (!m_properties_widget) {
  244. m_properties_widget = GUI::Widget::construct();
  245. m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
  246. auto& selection_mode_container = m_properties_widget->add<GUI::Widget>();
  247. selection_mode_container.set_layout<GUI::HorizontalBoxLayout>();
  248. selection_mode_container.set_fixed_height(46);
  249. auto& selection_mode_label = selection_mode_container.add<GUI::Label>("Selection Mode:");
  250. selection_mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  251. selection_mode_label.set_fixed_size(80, 40);
  252. auto& mode_radio_container = selection_mode_container.add<GUI::Widget>();
  253. mode_radio_container.set_layout<GUI::VerticalBoxLayout>();
  254. m_selection_mode_foreground = mode_radio_container.add<GUI::RadioButton>("Foreground");
  255. m_selection_mode_active = mode_radio_container.add<GUI::RadioButton>("Active Layer");
  256. m_selection_mode_foreground->on_checked = [&](bool) {
  257. m_layer_selection_mode = LayerSelectionMode::ForegroundLayer;
  258. };
  259. m_selection_mode_active->on_checked = [&](bool) {
  260. m_layer_selection_mode = LayerSelectionMode::ActiveLayer;
  261. };
  262. m_selection_mode_foreground->set_checked(true);
  263. }
  264. return m_properties_widget.ptr();
  265. }
  266. void MoveTool::toggle_selection_mode()
  267. {
  268. if (m_selection_mode_foreground->is_checked())
  269. m_selection_mode_active->set_checked(true);
  270. else
  271. m_selection_mode_foreground->set_checked(true);
  272. }
  273. }