path_2d_editor_plugin.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. /**************************************************************************/
  2. /* path_2d_editor_plugin.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "path_2d_editor_plugin.h"
  31. #include "canvas_item_editor_plugin.h"
  32. #include "core/os/keyboard.h"
  33. #include "editor/editor_node.h"
  34. #include "editor/editor_settings.h"
  35. #include "editor/editor_undo_redo_manager.h"
  36. #include "editor/themes/editor_scale.h"
  37. #include "scene/gui/dialogs.h"
  38. #include "scene/gui/menu_button.h"
  39. void Path2DEditor::_notification(int p_what) {
  40. switch (p_what) {
  41. case NOTIFICATION_THEME_CHANGED: {
  42. curve_edit->set_button_icon(get_editor_theme_icon(SNAME("CurveEdit")));
  43. curve_edit_curve->set_button_icon(get_editor_theme_icon(SNAME("CurveCurve")));
  44. curve_create->set_button_icon(get_editor_theme_icon(SNAME("CurveCreate")));
  45. curve_del->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete")));
  46. curve_close->set_button_icon(get_editor_theme_icon(SNAME("CurveClose")));
  47. curve_clear_points->set_button_icon(get_editor_theme_icon(SNAME("Clear")));
  48. create_curve_button->set_button_icon(get_editor_theme_icon(SNAME("Curve2D")));
  49. } break;
  50. }
  51. }
  52. void Path2DEditor::_node_removed(Node *p_node) {
  53. if (p_node == node) {
  54. node = nullptr;
  55. hide();
  56. }
  57. }
  58. bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
  59. if (!node) {
  60. return false;
  61. }
  62. if (!node->is_visible_in_tree()) {
  63. return false;
  64. }
  65. Viewport *vp = node->get_viewport();
  66. if (vp && !vp->is_visible_subviewport()) {
  67. return false;
  68. }
  69. if (node->get_curve().is_null()) {
  70. return false;
  71. }
  72. real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
  73. Ref<InputEventMouseButton> mb = p_event;
  74. if (mb.is_valid()) {
  75. Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
  76. Vector2 gpoint = mb->get_position();
  77. Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
  78. cpoint = node->to_local(node->get_viewport()->get_popup_base_transform().affine_inverse().xform(cpoint));
  79. if (mb->is_pressed() && action == ACTION_NONE) {
  80. Ref<Curve2D> curve = node->get_curve();
  81. original_mouse_pos = gpoint;
  82. for (int i = 0; i < curve->get_point_count(); i++) {
  83. real_t dist_to_p = gpoint.distance_to(xform.xform(curve->get_point_position(i)));
  84. real_t dist_to_p_out = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_out(i)));
  85. real_t dist_to_p_in = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_in(i)));
  86. // Check for point movement start (for point + in/out controls).
  87. if (mb->get_button_index() == MouseButton::LEFT) {
  88. if (mode == MODE_EDIT && !mb->is_shift_pressed() && dist_to_p < grab_threshold) {
  89. // Points can only be moved in edit mode.
  90. action = ACTION_MOVING_POINT;
  91. action_point = i;
  92. moving_from = curve->get_point_position(i);
  93. moving_screen_from = gpoint;
  94. return true;
  95. } else if (mode == MODE_EDIT || mode == MODE_EDIT_CURVE) {
  96. // In/out controls can be moved in multiple modes.
  97. if (dist_to_p_out < grab_threshold && i < (curve->get_point_count() - 1)) {
  98. action = ACTION_MOVING_OUT;
  99. action_point = i;
  100. moving_from = curve->get_point_out(i);
  101. moving_screen_from = gpoint;
  102. orig_in_length = curve->get_point_in(action_point).length();
  103. return true;
  104. } else if (dist_to_p_in < grab_threshold && i > 0) {
  105. action = ACTION_MOVING_IN;
  106. action_point = i;
  107. moving_from = curve->get_point_in(i);
  108. moving_screen_from = gpoint;
  109. orig_out_length = curve->get_point_out(action_point).length();
  110. return true;
  111. }
  112. }
  113. }
  114. // Check for point deletion.
  115. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  116. if ((mb->get_button_index() == MouseButton::RIGHT && (mode == MODE_EDIT || mode == MODE_CREATE)) || (mb->get_button_index() == MouseButton::LEFT && mode == MODE_DELETE)) {
  117. if (dist_to_p < grab_threshold) {
  118. undo_redo->create_action(TTR("Remove Point from Curve"));
  119. undo_redo->add_do_method(curve.ptr(), "remove_point", i);
  120. undo_redo->add_undo_method(curve.ptr(), "add_point", curve->get_point_position(i), curve->get_point_in(i), curve->get_point_out(i), i);
  121. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  122. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  123. undo_redo->commit_action();
  124. return true;
  125. } else if (dist_to_p_out < grab_threshold) {
  126. undo_redo->create_action(TTR("Remove Out-Control from Curve"));
  127. undo_redo->add_do_method(curve.ptr(), "set_point_out", i, Vector2());
  128. undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i));
  129. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  130. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  131. undo_redo->commit_action();
  132. return true;
  133. } else if (dist_to_p_in < grab_threshold) {
  134. undo_redo->create_action(TTR("Remove In-Control from Curve"));
  135. undo_redo->add_do_method(curve.ptr(), "set_point_in", i, Vector2());
  136. undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i));
  137. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  138. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  139. undo_redo->commit_action();
  140. return true;
  141. }
  142. }
  143. }
  144. }
  145. if (action != ACTION_NONE && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
  146. _cancel_current_action();
  147. return true;
  148. }
  149. // Check for point creation.
  150. if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && ((mb->is_command_or_control_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) {
  151. Ref<Curve2D> curve = node->get_curve();
  152. curve->add_point(cpoint);
  153. moving_from = cpoint;
  154. action = ACTION_MOVING_NEW_POINT;
  155. action_point = curve->get_point_count() - 1;
  156. moving_from = curve->get_point_position(action_point);
  157. moving_screen_from = gpoint;
  158. canvas_item_editor->update_viewport();
  159. return true;
  160. }
  161. // Check for segment split.
  162. if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && mode == MODE_EDIT && on_edge) {
  163. Vector2 gpoint2 = mb->get_position();
  164. Ref<Curve2D> curve = node->get_curve();
  165. int insertion_point = -1;
  166. float mbLength = curve->get_closest_offset(xform.affine_inverse().xform(gpoint2));
  167. int len = curve->get_point_count();
  168. for (int i = 0; i < len - 1; i++) {
  169. float compareLength = curve->get_closest_offset(curve->get_point_position(i + 1));
  170. if (mbLength >= curve->get_closest_offset(curve->get_point_position(i)) && mbLength <= compareLength) {
  171. insertion_point = i;
  172. }
  173. }
  174. if (insertion_point == -1) {
  175. insertion_point = curve->get_point_count() - 2;
  176. }
  177. const Vector2 new_point = xform.affine_inverse().xform(gpoint2);
  178. curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
  179. action = ACTION_MOVING_NEW_POINT_FROM_SPLIT;
  180. action_point = insertion_point + 1;
  181. moving_from = curve->get_point_position(action_point);
  182. moving_screen_from = gpoint2;
  183. canvas_item_editor->update_viewport();
  184. on_edge = false;
  185. return true;
  186. }
  187. // Check for point movement completion.
  188. if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && action != ACTION_NONE) {
  189. Ref<Curve2D> curve = node->get_curve();
  190. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  191. Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from);
  192. switch (action) {
  193. case ACTION_NONE:
  194. // N/A, handled in above condition.
  195. break;
  196. case ACTION_MOVING_POINT:
  197. if (original_mouse_pos != gpoint) {
  198. undo_redo->create_action(TTR("Move Point in Curve"));
  199. undo_redo->add_undo_method(curve.ptr(), "set_point_position", action_point, moving_from);
  200. undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
  201. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  202. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  203. undo_redo->commit_action(false);
  204. }
  205. break;
  206. case ACTION_MOVING_NEW_POINT: {
  207. undo_redo->create_action(TTR("Add Point to Curve"));
  208. undo_redo->add_do_method(curve.ptr(), "add_point", cpoint);
  209. undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
  210. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  211. undo_redo->add_undo_method(curve.ptr(), "remove_point", curve->get_point_count() - 1);
  212. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  213. undo_redo->commit_action(false);
  214. } break;
  215. case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
  216. undo_redo->create_action(TTR("Split Curve"));
  217. undo_redo->add_do_method(curve.ptr(), "add_point", Vector2(), Vector2(), Vector2(), action_point);
  218. undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
  219. undo_redo->add_undo_method(curve.ptr(), "remove_point", action_point);
  220. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  221. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  222. undo_redo->commit_action(false);
  223. } break;
  224. case ACTION_MOVING_IN: {
  225. if (original_mouse_pos != gpoint) {
  226. undo_redo->create_action(TTR("Move In-Control in Curve"));
  227. undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, new_pos);
  228. undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, moving_from);
  229. if (mirror_handle_angle) {
  230. undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length));
  231. undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));
  232. }
  233. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  234. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  235. undo_redo->commit_action();
  236. }
  237. } break;
  238. case ACTION_MOVING_OUT: {
  239. if (original_mouse_pos != gpoint) {
  240. undo_redo->create_action(TTR("Move Out-Control in Curve"));
  241. undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, new_pos);
  242. undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, moving_from);
  243. if (mirror_handle_angle) {
  244. undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length));
  245. undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_in_length));
  246. }
  247. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  248. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  249. undo_redo->commit_action();
  250. }
  251. } break;
  252. }
  253. action = ACTION_NONE;
  254. return true;
  255. }
  256. }
  257. Ref<InputEventMouseMotion> mm = p_event;
  258. if (mm.is_valid()) {
  259. if (action == ACTION_NONE && mode == MODE_EDIT) {
  260. // Handle Edge Follow
  261. bool old_edge = on_edge;
  262. Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
  263. Vector2 gpoint = mm->get_position();
  264. Ref<Curve2D> curve = node->get_curve();
  265. if (curve.is_null()) {
  266. return true;
  267. }
  268. if (curve->get_point_count() < 2) {
  269. return true;
  270. }
  271. // Find edge
  272. edge_point = xform.xform(curve->get_closest_point(xform.affine_inverse().xform(mm->get_position())));
  273. on_edge = false;
  274. if (edge_point.distance_to(gpoint) <= grab_threshold) {
  275. on_edge = true;
  276. }
  277. // However, if near a control point or its in-out handles then not on edge
  278. int len = curve->get_point_count();
  279. for (int i = 0; i < len; i++) {
  280. Vector2 pp = curve->get_point_position(i);
  281. Vector2 p = xform.xform(pp);
  282. if (p.distance_to(gpoint) <= grab_threshold) {
  283. on_edge = false;
  284. break;
  285. }
  286. p = xform.xform(pp + curve->get_point_in(i));
  287. if (p.distance_to(gpoint) <= grab_threshold) {
  288. on_edge = false;
  289. break;
  290. }
  291. p = xform.xform(pp + curve->get_point_out(i));
  292. if (p.distance_to(gpoint) <= grab_threshold) {
  293. on_edge = false;
  294. break;
  295. }
  296. }
  297. if (on_edge || old_edge != on_edge) {
  298. canvas_item_editor->update_viewport();
  299. return true;
  300. }
  301. }
  302. if (action != ACTION_NONE) {
  303. // Handle point/control movement.
  304. Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
  305. Vector2 gpoint = mm->get_position();
  306. Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
  307. cpoint = node->to_local(node->get_viewport()->get_popup_base_transform().affine_inverse().xform(cpoint));
  308. Ref<Curve2D> curve = node->get_curve();
  309. Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from);
  310. switch (action) {
  311. case ACTION_NONE:
  312. // N/A, handled in above condition.
  313. break;
  314. case ACTION_MOVING_POINT:
  315. case ACTION_MOVING_NEW_POINT:
  316. case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
  317. curve->set_point_position(action_point, cpoint);
  318. } break;
  319. case ACTION_MOVING_IN: {
  320. curve->set_point_in(action_point, new_pos);
  321. if (mirror_handle_angle) {
  322. curve->set_point_out(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length));
  323. }
  324. } break;
  325. case ACTION_MOVING_OUT: {
  326. curve->set_point_out(action_point, new_pos);
  327. if (mirror_handle_angle) {
  328. curve->set_point_in(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length));
  329. }
  330. } break;
  331. }
  332. canvas_item_editor->update_viewport();
  333. return true;
  334. }
  335. }
  336. return false;
  337. }
  338. void Path2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
  339. if (!node || !node->is_visible_in_tree() || node->get_curve().is_null()) {
  340. return;
  341. }
  342. Viewport *vp = node->get_viewport();
  343. if (vp && !vp->is_visible_subviewport()) {
  344. return;
  345. }
  346. Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
  347. const Ref<Texture2D> path_sharp_handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
  348. const Ref<Texture2D> path_smooth_handle = get_editor_theme_icon(SNAME("EditorPathSmoothHandle"));
  349. // Both handle icons must be of the same size
  350. const Size2 handle_size = path_sharp_handle->get_size();
  351. const Ref<Texture2D> curve_handle = get_editor_theme_icon(SNAME("EditorCurveHandle"));
  352. const Size2 curve_handle_size = curve_handle->get_size();
  353. Ref<Curve2D> curve = node->get_curve();
  354. int len = curve->get_point_count();
  355. Control *vpc = canvas_item_editor->get_viewport_control();
  356. for (int i = 0; i < len; i++) {
  357. Vector2 point = xform.xform(curve->get_point_position(i));
  358. // Determines the point icon to be used
  359. bool smooth = false;
  360. if (i < len - 1) {
  361. Vector2 point_out = xform.xform(curve->get_point_position(i) + curve->get_point_out(i));
  362. if (point != point_out) {
  363. smooth = true;
  364. // Draw the line with a dark and light color to be visible on all backgrounds
  365. vpc->draw_line(point, point_out, Color(0, 0, 0, 0.5), Math::round(EDSCALE));
  366. vpc->draw_line(point, point_out, Color(1, 1, 1, 0.5), Math::round(EDSCALE));
  367. vpc->draw_texture_rect(curve_handle, Rect2(point_out - curve_handle_size * 0.5, curve_handle_size), false, Color(1, 1, 1, 0.75));
  368. }
  369. }
  370. if (i > 0) {
  371. Vector2 point_in = xform.xform(curve->get_point_position(i) + curve->get_point_in(i));
  372. if (point != point_in) {
  373. smooth = true;
  374. // Draw the line with a dark and light color to be visible on all backgrounds
  375. vpc->draw_line(point, point_in, Color(0, 0, 0, 0.5), Math::round(EDSCALE));
  376. vpc->draw_line(point, point_in, Color(1, 1, 1, 0.5), Math::round(EDSCALE));
  377. vpc->draw_texture_rect(curve_handle, Rect2(point_in - curve_handle_size * 0.5, curve_handle_size), false, Color(1, 1, 1, 0.75));
  378. }
  379. }
  380. vpc->draw_texture_rect(
  381. smooth ? path_smooth_handle : path_sharp_handle,
  382. Rect2(point - handle_size * 0.5, handle_size),
  383. false);
  384. }
  385. if (on_edge) {
  386. Ref<Texture2D> add_handle = get_editor_theme_icon(SNAME("EditorHandleAdd"));
  387. p_overlay->draw_texture(add_handle, edge_point - add_handle->get_size() * 0.5);
  388. }
  389. }
  390. void Path2DEditor::_node_visibility_changed() {
  391. if (!node) {
  392. return;
  393. }
  394. canvas_item_editor->update_viewport();
  395. _update_toolbar();
  396. }
  397. void Path2DEditor::_update_toolbar() {
  398. if (!node) {
  399. return;
  400. }
  401. bool has_curve = node->get_curve().is_valid();
  402. toolbar->set_visible(has_curve);
  403. create_curve_button->set_visible(!has_curve);
  404. }
  405. void Path2DEditor::edit(Node *p_path2d) {
  406. if (!canvas_item_editor) {
  407. canvas_item_editor = CanvasItemEditor::get_singleton();
  408. }
  409. if (action != ACTION_NONE) {
  410. _cancel_current_action();
  411. }
  412. if (p_path2d) {
  413. node = Object::cast_to<Path2D>(p_path2d);
  414. _update_toolbar();
  415. if (!node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed))) {
  416. node->connect(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed));
  417. }
  418. } else {
  419. // The node may have been deleted at this point.
  420. if (node && node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed))) {
  421. node->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed));
  422. }
  423. node = nullptr;
  424. }
  425. canvas_item_editor->update_viewport();
  426. }
  427. void Path2DEditor::_bind_methods() {
  428. ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar);
  429. ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points);
  430. ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points);
  431. }
  432. void Path2DEditor::_mode_selected(int p_mode) {
  433. if (p_mode == MODE_CREATE) {
  434. curve_create->set_pressed(true);
  435. curve_edit->set_pressed(false);
  436. curve_edit_curve->set_pressed(false);
  437. curve_del->set_pressed(false);
  438. } else if (p_mode == MODE_EDIT) {
  439. curve_create->set_pressed(false);
  440. curve_edit->set_pressed(true);
  441. curve_edit_curve->set_pressed(false);
  442. curve_del->set_pressed(false);
  443. } else if (p_mode == MODE_EDIT_CURVE) {
  444. curve_create->set_pressed(false);
  445. curve_edit->set_pressed(false);
  446. curve_edit_curve->set_pressed(true);
  447. curve_del->set_pressed(false);
  448. } else if (p_mode == MODE_DELETE) {
  449. curve_create->set_pressed(false);
  450. curve_edit->set_pressed(false);
  451. curve_edit_curve->set_pressed(false);
  452. curve_del->set_pressed(true);
  453. } else if (p_mode == MODE_CLOSE) {
  454. if (node->get_curve().is_null()) {
  455. return;
  456. }
  457. if (node->get_curve()->get_point_count() < 3) {
  458. return;
  459. }
  460. Vector2 begin = node->get_curve()->get_point_position(0);
  461. Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1);
  462. if (begin.is_equal_approx(end)) {
  463. return;
  464. }
  465. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  466. undo_redo->create_action(TTR("Close the Curve"));
  467. undo_redo->add_do_method(node->get_curve().ptr(), "add_point", begin);
  468. undo_redo->add_undo_method(node->get_curve().ptr(), "remove_point", node->get_curve()->get_point_count());
  469. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  470. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  471. undo_redo->commit_action();
  472. return;
  473. } else if (p_mode == MODE_CLEAR_POINTS) {
  474. if (node->get_curve().is_null()) {
  475. return;
  476. }
  477. if (node->get_curve()->get_point_count() == 0) {
  478. return;
  479. }
  480. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  481. PackedVector2Array points = node->get_curve()->get_points().duplicate();
  482. undo_redo->create_action(TTR("Clear Curve Points"), UndoRedo::MERGE_DISABLE, node);
  483. undo_redo->add_do_method(this, "_clear_curve_points", node);
  484. undo_redo->add_undo_method(this, "_restore_curve_points", node, points);
  485. undo_redo->add_do_method(canvas_item_editor, "update_viewport");
  486. undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
  487. undo_redo->commit_action();
  488. return;
  489. }
  490. mode = Mode(p_mode);
  491. }
  492. void Path2DEditor::_handle_option_pressed(int p_option) {
  493. PopupMenu *pm;
  494. pm = handle_menu->get_popup();
  495. switch (p_option) {
  496. case HANDLE_OPTION_ANGLE: {
  497. bool is_checked = pm->is_item_checked(HANDLE_OPTION_ANGLE);
  498. mirror_handle_angle = !is_checked;
  499. pm->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
  500. pm->set_item_disabled(HANDLE_OPTION_LENGTH, !mirror_handle_angle);
  501. } break;
  502. case HANDLE_OPTION_LENGTH: {
  503. bool is_checked = pm->is_item_checked(HANDLE_OPTION_LENGTH);
  504. mirror_handle_length = !is_checked;
  505. pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
  506. } break;
  507. }
  508. }
  509. void Path2DEditor::_cancel_current_action() {
  510. ERR_FAIL_NULL(node);
  511. Ref<Curve2D> curve = node->get_curve();
  512. ERR_FAIL_COND(curve.is_null());
  513. switch (action) {
  514. case ACTION_MOVING_POINT: {
  515. curve->set_point_position(action_point, moving_from);
  516. } break;
  517. case ACTION_MOVING_NEW_POINT: {
  518. curve->remove_point(curve->get_point_count() - 1);
  519. } break;
  520. case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
  521. curve->remove_point(action_point);
  522. } break;
  523. case ACTION_MOVING_IN: {
  524. curve->set_point_in(action_point, moving_from);
  525. curve->set_point_out(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));
  526. } break;
  527. case ACTION_MOVING_OUT: {
  528. curve->set_point_out(action_point, moving_from);
  529. curve->set_point_in(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_in_length));
  530. } break;
  531. default: {
  532. }
  533. }
  534. canvas_item_editor->update_viewport();
  535. action = ACTION_NONE;
  536. }
  537. void Path2DEditor::_create_curve() {
  538. ERR_FAIL_NULL(node);
  539. Ref<Curve2D> new_curve;
  540. new_curve.instantiate();
  541. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  542. undo_redo->create_action(TTR("Create Curve in Path2D"));
  543. undo_redo->add_do_property(node, "curve", new_curve);
  544. undo_redo->add_undo_property(node, "curve", Ref<Curve2D>());
  545. undo_redo->add_do_method(this, "_update_toolbar");
  546. undo_redo->add_undo_method(this, "_update_toolbar");
  547. undo_redo->commit_action();
  548. }
  549. void Path2DEditor::_confirm_clear_points() {
  550. if (!node || node->get_curve().is_null()) {
  551. return;
  552. }
  553. if (node->get_curve()->get_point_count() == 0) {
  554. return;
  555. }
  556. clear_points_dialog->reset_size();
  557. clear_points_dialog->popup_centered();
  558. }
  559. void Path2DEditor::_clear_curve_points(Path2D *p_path2d) {
  560. if (!p_path2d || p_path2d->get_curve().is_null()) {
  561. return;
  562. }
  563. Ref<Curve2D> curve = p_path2d->get_curve();
  564. if (curve->get_point_count() == 0) {
  565. return;
  566. }
  567. curve->clear_points();
  568. if (node == p_path2d) {
  569. _mode_selected(MODE_CREATE);
  570. }
  571. }
  572. void Path2DEditor::_restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points) {
  573. if (!p_path2d || p_path2d->get_curve().is_null()) {
  574. return;
  575. }
  576. Ref<Curve2D> curve = p_path2d->get_curve();
  577. if (curve->get_point_count() > 0) {
  578. curve->clear_points();
  579. }
  580. for (int i = 0; i < p_points.size(); i += 3) {
  581. curve->add_point(p_points[i + 2], p_points[i], p_points[i + 1]); // The Curve2D::points pattern is [point_in, point_out, point_position].
  582. }
  583. if (node == p_path2d) {
  584. _mode_selected(MODE_EDIT);
  585. }
  586. }
  587. Path2DEditor::Path2DEditor() {
  588. toolbar = memnew(HBoxContainer);
  589. curve_edit = memnew(Button);
  590. curve_edit->set_theme_type_variation(SceneStringName(FlatButton));
  591. curve_edit->set_toggle_mode(true);
  592. curve_edit->set_pressed(true);
  593. curve_edit->set_focus_mode(Control::FOCUS_NONE);
  594. curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point"));
  595. curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT));
  596. toolbar->add_child(curve_edit);
  597. curve_edit_curve = memnew(Button);
  598. curve_edit_curve->set_theme_type_variation(SceneStringName(FlatButton));
  599. curve_edit_curve->set_toggle_mode(true);
  600. curve_edit_curve->set_focus_mode(Control::FOCUS_NONE);
  601. curve_edit_curve->set_tooltip_text(TTR("Select Control Points (Shift+Drag)"));
  602. curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE));
  603. toolbar->add_child(curve_edit_curve);
  604. curve_create = memnew(Button);
  605. curve_create->set_theme_type_variation(SceneStringName(FlatButton));
  606. curve_create->set_toggle_mode(true);
  607. curve_create->set_focus_mode(Control::FOCUS_NONE);
  608. curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Right Click: Delete Point"));
  609. curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE));
  610. toolbar->add_child(curve_create);
  611. curve_del = memnew(Button);
  612. curve_del->set_theme_type_variation(SceneStringName(FlatButton));
  613. curve_del->set_toggle_mode(true);
  614. curve_del->set_focus_mode(Control::FOCUS_NONE);
  615. curve_del->set_tooltip_text(TTR("Delete Point"));
  616. curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE));
  617. toolbar->add_child(curve_del);
  618. curve_close = memnew(Button);
  619. curve_close->set_theme_type_variation(SceneStringName(FlatButton));
  620. curve_close->set_focus_mode(Control::FOCUS_NONE);
  621. curve_close->set_tooltip_text(TTR("Close Curve"));
  622. curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE));
  623. toolbar->add_child(curve_close);
  624. curve_clear_points = memnew(Button);
  625. curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton));
  626. curve_clear_points->set_focus_mode(Control::FOCUS_NONE);
  627. curve_clear_points->set_tooltip_text(TTR("Clear Points"));
  628. curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_confirm_clear_points));
  629. toolbar->add_child(curve_clear_points);
  630. clear_points_dialog = memnew(ConfirmationDialog);
  631. clear_points_dialog->set_title(TTR("Please Confirm..."));
  632. clear_points_dialog->set_text(TTR("Remove all curve points?"));
  633. clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLEAR_POINTS));
  634. toolbar->add_child(clear_points_dialog);
  635. handle_menu = memnew(MenuButton);
  636. handle_menu->set_flat(false);
  637. handle_menu->set_theme_type_variation("FlatMenuButton");
  638. handle_menu->set_text(TTR("Options"));
  639. toolbar->add_child(handle_menu);
  640. PopupMenu *menu = handle_menu->get_popup();
  641. menu->add_check_item(TTR("Mirror Handle Angles"));
  642. menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
  643. menu->add_check_item(TTR("Mirror Handle Lengths"));
  644. menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
  645. menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed));
  646. add_child(toolbar);
  647. create_curve_button = memnew(Button);
  648. create_curve_button->set_text(TTR("Create Curve"));
  649. create_curve_button->hide();
  650. add_child(create_curve_button);
  651. create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_create_curve));
  652. }
  653. void Path2DEditorPlugin::edit(Object *p_object) {
  654. path2d_editor->edit(Object::cast_to<Node>(p_object));
  655. }
  656. bool Path2DEditorPlugin::handles(Object *p_object) const {
  657. return p_object->is_class("Path2D");
  658. }
  659. void Path2DEditorPlugin::make_visible(bool p_visible) {
  660. if (p_visible) {
  661. path2d_editor->show();
  662. } else {
  663. path2d_editor->hide();
  664. path2d_editor->edit(nullptr);
  665. }
  666. }
  667. Path2DEditorPlugin::Path2DEditorPlugin() {
  668. path2d_editor = memnew(Path2DEditor);
  669. CanvasItemEditor::get_singleton()->add_control_to_menu_panel(path2d_editor);
  670. path2d_editor->hide();
  671. }
  672. Path2DEditorPlugin::~Path2DEditorPlugin() {
  673. }