curve_editor_plugin.cpp 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  1. /**************************************************************************/
  2. /* curve_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 "curve_editor_plugin.h"
  31. #include "canvas_item_editor_plugin.h"
  32. #include "core/input/input.h"
  33. #include "core/math/geometry_2d.h"
  34. #include "core/os/keyboard.h"
  35. #include "editor/editor_interface.h"
  36. #include "editor/editor_node.h"
  37. #include "editor/editor_string_names.h"
  38. #include "editor/editor_undo_redo_manager.h"
  39. #include "editor/gui/editor_spin_slider.h"
  40. #include "editor/settings/editor_settings.h"
  41. #include "editor/themes/editor_scale.h"
  42. #include "scene/gui/flow_container.h"
  43. #include "scene/gui/menu_button.h"
  44. #include "scene/gui/popup_menu.h"
  45. #include "scene/gui/separator.h"
  46. #include "scene/resources/image_texture.h"
  47. CurveEdit::CurveEdit() {
  48. set_focus_mode(FOCUS_ALL);
  49. set_clip_contents(true);
  50. }
  51. void CurveEdit::_bind_methods() {
  52. ClassDB::bind_method(D_METHOD("set_selected_index", "index"), &CurveEdit::set_selected_index);
  53. }
  54. void CurveEdit::set_curve(Ref<Curve> p_curve) {
  55. if (p_curve == curve) {
  56. return;
  57. }
  58. if (curve.is_valid()) {
  59. curve->disconnect_changed(callable_mp(this, &CurveEdit::_curve_changed));
  60. curve->disconnect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
  61. curve->disconnect(Curve::SIGNAL_DOMAIN_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
  62. }
  63. curve = p_curve;
  64. if (curve.is_valid()) {
  65. curve->connect_changed(callable_mp(this, &CurveEdit::_curve_changed));
  66. curve->connect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
  67. curve->connect(Curve::SIGNAL_DOMAIN_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
  68. }
  69. // Note: if you edit a curve, then set another, and try to undo,
  70. // it will normally apply on the previous curve, but you won't see it.
  71. }
  72. Ref<Curve> CurveEdit::get_curve() {
  73. return curve;
  74. }
  75. void CurveEdit::set_snap_enabled(bool p_enabled) {
  76. snap_enabled = p_enabled;
  77. queue_redraw();
  78. if (curve.is_valid()) {
  79. if (snap_enabled) {
  80. curve->set_meta(SNAME("_snap_enabled"), true);
  81. } else {
  82. curve->remove_meta(SNAME("_snap_enabled"));
  83. }
  84. }
  85. }
  86. void CurveEdit::set_snap_count(int p_snap_count) {
  87. snap_count = p_snap_count;
  88. queue_redraw();
  89. if (curve.is_valid()) {
  90. if (snap_count != CurveEditor::DEFAULT_SNAP) {
  91. curve->set_meta(SNAME("_snap_count"), snap_count);
  92. } else {
  93. curve->remove_meta(SNAME("_snap_count"));
  94. }
  95. }
  96. }
  97. Size2 CurveEdit::get_minimum_size() const {
  98. return Vector2(64, MAX(135, get_size().x * ASPECT_RATIO)) * EDSCALE;
  99. }
  100. void CurveEdit::_notification(int p_what) {
  101. switch (p_what) {
  102. case NOTIFICATION_MOUSE_EXIT: {
  103. if (hovered_index != -1 || hovered_tangent_index != TANGENT_NONE) {
  104. hovered_index = -1;
  105. hovered_tangent_index = TANGENT_NONE;
  106. queue_redraw();
  107. }
  108. } break;
  109. case NOTIFICATION_THEME_CHANGED: {
  110. float gizmo_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles");
  111. point_radius = Math::round(BASE_POINT_RADIUS * get_theme_default_base_scale() * gizmo_scale);
  112. hover_radius = Math::round(BASE_HOVER_RADIUS * get_theme_default_base_scale() * gizmo_scale);
  113. tangent_radius = Math::round(BASE_TANGENT_RADIUS * get_theme_default_base_scale() * gizmo_scale);
  114. tangent_hover_radius = Math::round(BASE_TANGENT_HOVER_RADIUS * get_theme_default_base_scale() * gizmo_scale);
  115. tangent_length = Math::round(BASE_TANGENT_LENGTH * get_theme_default_base_scale());
  116. } break;
  117. case NOTIFICATION_ACCESSIBILITY_UPDATE: {
  118. RID ae = get_accessibility_element();
  119. ERR_FAIL_COND(ae.is_null());
  120. //TODO
  121. DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
  122. DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "Curve editor")));
  123. } break;
  124. case NOTIFICATION_DRAW: {
  125. _redraw();
  126. } break;
  127. case NOTIFICATION_VISIBILITY_CHANGED: {
  128. if (!is_visible()) {
  129. grabbing = GRAB_NONE;
  130. }
  131. } break;
  132. }
  133. }
  134. void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
  135. ERR_FAIL_COND(p_event.is_null());
  136. if (curve.is_null()) {
  137. return;
  138. }
  139. Ref<InputEventKey> k = p_event;
  140. if (k.is_valid()) {
  141. // Deleting points or making tangents linear.
  142. if (k->is_pressed() && k->get_keycode() == Key::KEY_DELETE) {
  143. if (selected_tangent_index != TANGENT_NONE) {
  144. toggle_linear(selected_index, selected_tangent_index);
  145. } else if (selected_index != -1) {
  146. if (grabbing == GRAB_ADD) {
  147. curve->remove_point(selected_index); // Point is temporary, so remove directly from curve.
  148. set_selected_index(-1);
  149. } else {
  150. remove_point(selected_index);
  151. }
  152. grabbing = GRAB_NONE;
  153. hovered_index = -1;
  154. hovered_tangent_index = TANGENT_NONE;
  155. }
  156. accept_event();
  157. }
  158. if (k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::ALT) {
  159. queue_redraw(); // Redraw to show the axes or constraints.
  160. }
  161. }
  162. Ref<InputEventMouseButton> mb = p_event;
  163. if (mb.is_valid() && mb->is_pressed()) {
  164. Vector2 mpos = mb->get_position();
  165. if (mb->get_button_index() == MouseButton::RIGHT || mb->get_button_index() == MouseButton::MIDDLE) {
  166. if (mb->get_button_index() == MouseButton::RIGHT && grabbing == GRAB_MOVE) {
  167. // Move a point to its old position.
  168. curve->set_point_value(selected_index, initial_grab_pos.y);
  169. curve->set_point_offset(selected_index, initial_grab_pos.x);
  170. set_selected_index(initial_grab_index);
  171. hovered_index = get_point_at(mpos);
  172. grabbing = GRAB_NONE;
  173. } else {
  174. // Remove a point or make a tangent linear.
  175. selected_tangent_index = get_tangent_at(mpos);
  176. if (selected_tangent_index != TANGENT_NONE) {
  177. toggle_linear(selected_index, selected_tangent_index);
  178. } else {
  179. int point_to_remove = get_point_at(mpos);
  180. if (point_to_remove == -1) {
  181. set_selected_index(-1); // Nothing on the place of the click, just deselect the point.
  182. } else {
  183. if (grabbing == GRAB_ADD) {
  184. curve->remove_point(point_to_remove); // Point is temporary, so remove directly from curve.
  185. set_selected_index(-1);
  186. } else {
  187. remove_point(point_to_remove);
  188. }
  189. hovered_index = get_point_at(mpos);
  190. grabbing = GRAB_NONE;
  191. }
  192. }
  193. }
  194. }
  195. // Selecting or creating points.
  196. if (mb->get_button_index() == MouseButton::LEFT) {
  197. if (grabbing == GRAB_NONE) {
  198. selected_tangent_index = get_tangent_at(mpos);
  199. if (selected_tangent_index == TANGENT_NONE) {
  200. set_selected_index(get_point_at(mpos));
  201. }
  202. queue_redraw();
  203. }
  204. if (selected_index != -1) {
  205. // If an existing point/tangent was grabbed, remember a few things about it.
  206. grabbing = GRAB_MOVE;
  207. initial_grab_pos = curve->get_point_position(selected_index);
  208. initial_grab_index = selected_index;
  209. if (selected_index > 0) {
  210. initial_grab_left_tangent = curve->get_point_left_tangent(selected_index);
  211. }
  212. if (selected_index < curve->get_point_count() - 1) {
  213. initial_grab_right_tangent = curve->get_point_right_tangent(selected_index);
  214. }
  215. } else if (grabbing == GRAB_NONE) {
  216. // Adding a new point. Insert a temporary point for the user to adjust, so it's not in the undo/redo.
  217. Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(curve->get_min_domain(), curve->get_min_value()), Vector2(curve->get_max_domain(), curve->get_max_value()));
  218. if (snap_enabled || mb->is_command_or_control_pressed()) {
  219. new_pos.x = Math::snapped(new_pos.x - curve->get_min_domain(), curve->get_domain_range() / snap_count) + curve->get_min_domain();
  220. new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_value_range() / snap_count) + curve->get_min_value();
  221. }
  222. new_pos.x = get_offset_without_collision(selected_index, new_pos.x, mpos.x >= get_view_pos(new_pos).x);
  223. // Add a temporary point for the user to adjust before adding it permanently.
  224. int new_idx = curve->add_point_no_update(new_pos);
  225. set_selected_index(new_idx);
  226. grabbing = GRAB_ADD;
  227. initial_grab_pos = new_pos;
  228. }
  229. }
  230. }
  231. if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
  232. if (selected_tangent_index != TANGENT_NONE) {
  233. // Finish moving a tangent control.
  234. if (selected_index == 0) {
  235. set_point_right_tangent(selected_index, curve->get_point_right_tangent(selected_index));
  236. } else if (selected_index == curve->get_point_count() - 1) {
  237. set_point_left_tangent(selected_index, curve->get_point_left_tangent(selected_index));
  238. } else {
  239. set_point_tangents(selected_index, curve->get_point_left_tangent(selected_index), curve->get_point_right_tangent(selected_index));
  240. }
  241. grabbing = GRAB_NONE;
  242. } else if (grabbing == GRAB_MOVE) {
  243. // Finish moving a point.
  244. set_point_position(selected_index, curve->get_point_position(selected_index));
  245. grabbing = GRAB_NONE;
  246. } else if (grabbing == GRAB_ADD) {
  247. // Finish inserting a new point. Remove the temporary point and insert a permanent one in its place.
  248. Vector2 new_pos = curve->get_point_position(selected_index);
  249. curve->remove_point(selected_index);
  250. add_point(new_pos);
  251. grabbing = GRAB_NONE;
  252. }
  253. queue_redraw();
  254. }
  255. Ref<InputEventMouseMotion> mm = p_event;
  256. if (mm.is_valid()) {
  257. Vector2 mpos = mm->get_position();
  258. if (grabbing != GRAB_NONE && curve.is_valid()) {
  259. if (selected_index != -1) {
  260. if (selected_tangent_index == TANGENT_NONE) {
  261. // Drag point.
  262. Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(curve->get_min_domain(), curve->get_min_value()), Vector2(curve->get_max_domain(), curve->get_max_value()));
  263. if (snap_enabled || mm->is_command_or_control_pressed()) {
  264. new_pos.x = Math::snapped(new_pos.x - curve->get_min_domain(), curve->get_domain_range() / snap_count) + curve->get_min_domain();
  265. new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_value_range() / snap_count) + curve->get_min_value();
  266. }
  267. // Allow to snap to axes with Shift.
  268. if (mm->is_shift_pressed()) {
  269. Vector2 initial_mpos = get_view_pos(initial_grab_pos);
  270. if (Math::abs(mpos.x - initial_mpos.x) > Math::abs(mpos.y - initial_mpos.y)) {
  271. new_pos.y = initial_grab_pos.y;
  272. } else {
  273. new_pos.x = initial_grab_pos.x;
  274. }
  275. }
  276. // Allow to constraint the point between the adjacent two with Alt.
  277. if (mm->is_alt_pressed()) {
  278. float prev_point_offset = (selected_index > 0) ? (curve->get_point_position(selected_index - 1).x + 0.00001) : curve->get_min_domain();
  279. float next_point_offset = (selected_index < curve->get_point_count() - 1) ? (curve->get_point_position(selected_index + 1).x - 0.00001) : curve->get_max_domain();
  280. new_pos.x = CLAMP(new_pos.x, prev_point_offset, next_point_offset);
  281. }
  282. new_pos.x = get_offset_without_collision(selected_index, new_pos.x, mpos.x >= get_view_pos(new_pos).x);
  283. // The index may change if the point is dragged across another one.
  284. int i = curve->set_point_offset(selected_index, new_pos.x);
  285. hovered_index = i;
  286. set_selected_index(i);
  287. new_pos.y = CLAMP(new_pos.y, curve->get_min_value(), curve->get_max_value());
  288. curve->set_point_value(selected_index, new_pos.y);
  289. } else {
  290. // Drag tangent.
  291. const Vector2 new_pos = curve->get_point_position(selected_index);
  292. const Vector2 control_pos = get_world_pos(mpos);
  293. Vector2 dir = (control_pos - new_pos).normalized();
  294. real_t tangent = dir.y / (dir.x > 0 ? MAX(dir.x, 0.00001) : MIN(dir.x, -0.00001));
  295. // Must keep track of the hovered index as the cursor might move outside of the editor while dragging.
  296. hovered_tangent_index = selected_tangent_index;
  297. // Adjust the tangents.
  298. if (selected_tangent_index == TANGENT_LEFT) {
  299. curve->set_point_left_tangent(selected_index, tangent);
  300. // Align the other tangent if it isn't linear and Shift is not pressed.
  301. // If Shift is pressed at any point, restore the initial angle of the other tangent.
  302. if (selected_index != (curve->get_point_count() - 1) && curve->get_point_right_mode(selected_index) != Curve::TANGENT_LINEAR) {
  303. curve->set_point_right_tangent(selected_index, mm->is_shift_pressed() ? initial_grab_right_tangent : tangent);
  304. }
  305. } else {
  306. curve->set_point_right_tangent(selected_index, tangent);
  307. if (selected_index != 0 && curve->get_point_left_mode(selected_index) != Curve::TANGENT_LINEAR) {
  308. curve->set_point_left_tangent(selected_index, mm->is_shift_pressed() ? initial_grab_left_tangent : tangent);
  309. }
  310. }
  311. }
  312. }
  313. } else {
  314. // Grab mode is GRAB_NONE, so do hovering logic.
  315. hovered_index = get_point_at(mpos);
  316. hovered_tangent_index = get_tangent_at(mpos);
  317. queue_redraw();
  318. }
  319. }
  320. }
  321. void CurveEdit::use_preset(int p_preset_id) {
  322. ERR_FAIL_COND(p_preset_id < 0 || p_preset_id >= PRESET_COUNT);
  323. ERR_FAIL_COND(curve.is_null());
  324. Array previous_data = curve->get_data();
  325. curve->clear_points();
  326. const float min_y = curve->get_min_value();
  327. const float max_y = curve->get_max_value();
  328. const float min_x = curve->get_min_domain();
  329. const float max_x = curve->get_max_domain();
  330. switch (p_preset_id) {
  331. case PRESET_CONSTANT:
  332. curve->add_point(Vector2(min_x, (min_y + max_y) / 2.0));
  333. curve->add_point(Vector2(max_x, (min_y + max_y) / 2.0));
  334. curve->set_point_right_mode(0, Curve::TANGENT_LINEAR);
  335. curve->set_point_left_mode(1, Curve::TANGENT_LINEAR);
  336. break;
  337. case PRESET_LINEAR:
  338. curve->add_point(Vector2(min_x, min_y));
  339. curve->add_point(Vector2(max_x, max_y));
  340. curve->set_point_right_mode(0, Curve::TANGENT_LINEAR);
  341. curve->set_point_left_mode(1, Curve::TANGENT_LINEAR);
  342. break;
  343. case PRESET_EASE_IN:
  344. curve->add_point(Vector2(min_x, min_y));
  345. curve->add_point(Vector2(max_x, max_y), curve->get_value_range() / curve->get_domain_range() * 1.4, 0);
  346. break;
  347. case PRESET_EASE_OUT:
  348. curve->add_point(Vector2(min_x, min_y), 0, curve->get_value_range() / curve->get_domain_range() * 1.4);
  349. curve->add_point(Vector2(max_x, max_y));
  350. break;
  351. case PRESET_SMOOTHSTEP:
  352. curve->add_point(Vector2(min_x, min_y));
  353. curve->add_point(Vector2(max_x, max_y));
  354. break;
  355. default:
  356. break;
  357. }
  358. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  359. undo_redo->create_action(TTR("Load Curve Preset"));
  360. undo_redo->add_do_method(*curve, "_set_data", curve->get_data());
  361. undo_redo->add_do_method(this, "set_selected_index", -1);
  362. undo_redo->add_undo_method(*curve, "_set_data", previous_data);
  363. undo_redo->add_undo_method(this, "set_selected_index", selected_index);
  364. undo_redo->commit_action();
  365. }
  366. void CurveEdit::_curve_changed() {
  367. queue_redraw();
  368. // Point count can change in case of undo.
  369. if (selected_index >= curve->get_point_count()) {
  370. set_selected_index(-1);
  371. }
  372. }
  373. int CurveEdit::get_point_at(const Vector2 &p_pos) const {
  374. if (curve.is_null()) {
  375. return -1;
  376. }
  377. // Use a square-shaped hover region. If hovering multiple points, pick the closer one.
  378. const Rect2 hover_rect = Rect2(p_pos, Vector2(0, 0)).grow(hover_radius);
  379. int closest_idx = -1;
  380. float closest_dist_squared = hover_radius * hover_radius * 2;
  381. for (int i = 0; i < curve->get_point_count(); ++i) {
  382. Vector2 p = get_view_pos(curve->get_point_position(i));
  383. if (hover_rect.has_point(p) && p.distance_squared_to(p_pos) < closest_dist_squared) {
  384. closest_dist_squared = p.distance_squared_to(p_pos);
  385. closest_idx = i;
  386. }
  387. }
  388. return closest_idx;
  389. }
  390. CurveEdit::TangentIndex CurveEdit::get_tangent_at(const Vector2 &p_pos) const {
  391. if (curve.is_null() || selected_index < 0) {
  392. return TANGENT_NONE;
  393. }
  394. const Rect2 hover_rect = Rect2(p_pos, Vector2(0, 0)).grow(tangent_hover_radius);
  395. if (selected_index != 0) {
  396. Vector2 control_pos = get_tangent_view_pos(selected_index, TANGENT_LEFT);
  397. if (hover_rect.has_point(control_pos)) {
  398. return TANGENT_LEFT;
  399. }
  400. }
  401. if (selected_index != curve->get_point_count() - 1) {
  402. Vector2 control_pos = get_tangent_view_pos(selected_index, TANGENT_RIGHT);
  403. if (hover_rect.has_point(control_pos)) {
  404. return TANGENT_RIGHT;
  405. }
  406. }
  407. return TANGENT_NONE;
  408. }
  409. // FIXME: This function should be bounded better.
  410. float CurveEdit::get_offset_without_collision(int p_current_index, float p_offset, bool p_prioritize_right) {
  411. float safe_offset = p_offset;
  412. bool prioritizing_right = p_prioritize_right;
  413. for (int i = 0; i < curve->get_point_count(); i++) {
  414. if (i == p_current_index) {
  415. continue;
  416. }
  417. if (curve->get_point_position(i).x > safe_offset) {
  418. break;
  419. }
  420. if (curve->get_point_position(i).x == safe_offset) {
  421. if (prioritizing_right) {
  422. safe_offset += 0.00001;
  423. if (safe_offset > 1.0) {
  424. safe_offset = 1.0;
  425. prioritizing_right = false;
  426. }
  427. } else {
  428. safe_offset -= 0.00001;
  429. if (safe_offset < 0.0) {
  430. safe_offset = 0.0;
  431. prioritizing_right = true;
  432. }
  433. }
  434. i = -1;
  435. }
  436. }
  437. return safe_offset;
  438. }
  439. void CurveEdit::add_point(const Vector2 &p_pos) {
  440. ERR_FAIL_COND(curve.is_null());
  441. // Add a point to get its index, then remove it immediately. Trick to feed the UndoRedo.
  442. int new_idx = curve->add_point(p_pos);
  443. curve->remove_point(new_idx);
  444. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  445. undo_redo->create_action(TTR("Add Curve Point"));
  446. undo_redo->add_do_method(*curve, "add_point", p_pos);
  447. undo_redo->add_do_method(this, "set_selected_index", new_idx);
  448. undo_redo->add_undo_method(*curve, "remove_point", new_idx);
  449. undo_redo->add_undo_method(this, "set_selected_index", -1);
  450. undo_redo->commit_action();
  451. }
  452. void CurveEdit::remove_point(int p_index) {
  453. ERR_FAIL_COND(curve.is_null());
  454. ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds.");
  455. Curve::Point p = curve->get_point(p_index);
  456. Vector2 old_pos = (grabbing == GRAB_MOVE) ? initial_grab_pos : p.position;
  457. int new_selected_index = selected_index;
  458. // Reselect the old selected point if it's not the deleted one.
  459. if (new_selected_index > p_index) {
  460. new_selected_index -= 1;
  461. } else if (new_selected_index == p_index) {
  462. new_selected_index = -1;
  463. }
  464. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  465. undo_redo->create_action(TTR("Remove Curve Point"));
  466. undo_redo->add_do_method(*curve, "remove_point", p_index);
  467. undo_redo->add_do_method(this, "set_selected_index", new_selected_index);
  468. undo_redo->add_undo_method(*curve, "add_point", old_pos, p.left_tangent, p.right_tangent, p.left_mode, p.right_mode);
  469. undo_redo->add_undo_method(this, "set_selected_index", selected_index);
  470. undo_redo->commit_action();
  471. }
  472. void CurveEdit::set_point_position(int p_index, const Vector2 &p_pos) {
  473. ERR_FAIL_COND(curve.is_null());
  474. ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds.");
  475. if (initial_grab_pos == p_pos) {
  476. return;
  477. }
  478. // Pretend the point started from its old place.
  479. curve->set_point_value(p_index, initial_grab_pos.y);
  480. curve->set_point_offset(p_index, initial_grab_pos.x);
  481. // Note: Changing the offset may modify the order.
  482. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  483. undo_redo->create_action(TTR("Modify Curve Point"));
  484. undo_redo->add_do_method(*curve, "set_point_value", initial_grab_index, p_pos.y);
  485. undo_redo->add_do_method(*curve, "set_point_offset", initial_grab_index, p_pos.x);
  486. undo_redo->add_do_method(this, "set_selected_index", p_index);
  487. undo_redo->add_undo_method(*curve, "set_point_value", p_index, initial_grab_pos.y);
  488. undo_redo->add_undo_method(*curve, "set_point_offset", p_index, initial_grab_pos.x);
  489. undo_redo->add_undo_method(this, "set_selected_index", initial_grab_index);
  490. undo_redo->commit_action();
  491. }
  492. void CurveEdit::set_point_tangents(int p_index, float p_left, float p_right) {
  493. ERR_FAIL_COND(curve.is_null());
  494. ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds.");
  495. if (initial_grab_left_tangent == p_left) {
  496. set_point_right_tangent(p_index, p_right);
  497. return;
  498. } else if (initial_grab_right_tangent == p_right) {
  499. set_point_left_tangent(p_index, p_left);
  500. return;
  501. }
  502. curve->set_point_left_tangent(p_index, initial_grab_left_tangent);
  503. curve->set_point_right_tangent(p_index, initial_grab_right_tangent);
  504. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  505. undo_redo->create_action(TTR("Modify Curve Point's Tangents"));
  506. undo_redo->add_do_method(*curve, "set_point_left_tangent", p_index, p_left);
  507. undo_redo->add_do_method(*curve, "set_point_right_tangent", p_index, p_right);
  508. undo_redo->add_do_method(this, "set_selected_index", p_index);
  509. undo_redo->add_undo_method(*curve, "set_point_left_tangent", p_index, initial_grab_left_tangent);
  510. undo_redo->add_undo_method(*curve, "set_point_right_tangent", p_index, initial_grab_right_tangent);
  511. undo_redo->add_undo_method(this, "set_selected_index", p_index);
  512. undo_redo->commit_action();
  513. }
  514. void CurveEdit::set_point_left_tangent(int p_index, float p_tangent) {
  515. ERR_FAIL_COND(curve.is_null());
  516. ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds.");
  517. if (initial_grab_left_tangent == p_tangent) {
  518. return;
  519. }
  520. curve->set_point_left_tangent(p_index, initial_grab_left_tangent);
  521. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  522. undo_redo->create_action(TTR("Modify Curve Point's Left Tangent"));
  523. undo_redo->add_do_method(*curve, "set_point_left_tangent", p_index, p_tangent);
  524. undo_redo->add_do_method(this, "set_selected_index", p_index);
  525. undo_redo->add_undo_method(*curve, "set_point_left_tangent", p_index, initial_grab_left_tangent);
  526. undo_redo->add_undo_method(this, "set_selected_index", p_index);
  527. undo_redo->commit_action();
  528. }
  529. void CurveEdit::set_point_right_tangent(int p_index, float p_tangent) {
  530. ERR_FAIL_COND(curve.is_null());
  531. ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds.");
  532. if (initial_grab_right_tangent == p_tangent) {
  533. return;
  534. }
  535. curve->set_point_right_tangent(p_index, initial_grab_right_tangent);
  536. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  537. undo_redo->create_action(TTR("Modify Curve Point's Right Tangent"));
  538. undo_redo->add_do_method(*curve, "set_point_right_tangent", p_index, p_tangent);
  539. undo_redo->add_do_method(this, "set_selected_index", p_index);
  540. undo_redo->add_undo_method(*curve, "set_point_right_tangent", p_index, initial_grab_right_tangent);
  541. undo_redo->add_undo_method(this, "set_selected_index", p_index);
  542. undo_redo->commit_action();
  543. }
  544. void CurveEdit::toggle_linear(int p_index, TangentIndex p_tangent) {
  545. ERR_FAIL_COND(curve.is_null());
  546. ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds.");
  547. if (p_tangent == TANGENT_NONE) {
  548. return;
  549. }
  550. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  551. undo_redo->create_action(TTR("Toggle Linear Curve Point's Tangent"));
  552. Curve::TangentMode prev_mode = (p_tangent == TANGENT_LEFT) ? curve->get_point_left_mode(p_index) : curve->get_point_right_mode(p_index);
  553. Curve::TangentMode mode = (prev_mode == Curve::TANGENT_LINEAR) ? Curve::TANGENT_FREE : Curve::TANGENT_LINEAR;
  554. float prev_angle = (p_tangent == TANGENT_LEFT) ? curve->get_point_left_tangent(p_index) : curve->get_point_right_tangent(p_index);
  555. // Add different methods in the UndoRedo based on the tangent passed.
  556. if (p_tangent == TANGENT_LEFT) {
  557. undo_redo->add_do_method(*curve, "set_point_left_mode", p_index, mode);
  558. undo_redo->add_undo_method(*curve, "set_point_left_mode", p_index, prev_mode);
  559. undo_redo->add_undo_method(*curve, "set_point_left_tangent", p_index, prev_angle);
  560. } else {
  561. undo_redo->add_do_method(*curve, "set_point_right_mode", p_index, mode);
  562. undo_redo->add_undo_method(*curve, "set_point_right_mode", p_index, prev_mode);
  563. undo_redo->add_undo_method(*curve, "set_point_right_tangent", p_index, prev_angle);
  564. }
  565. undo_redo->commit_action();
  566. }
  567. void CurveEdit::set_selected_index(int p_index) {
  568. if (p_index != selected_index) {
  569. selected_index = p_index;
  570. queue_redraw();
  571. }
  572. }
  573. void CurveEdit::update_view_transform() {
  574. Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
  575. int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
  576. const real_t margin = font->get_height(font_size) + 2 * EDSCALE;
  577. float min_x = curve.is_valid() ? curve->get_min_domain() : 0.0;
  578. float max_x = curve.is_valid() ? curve->get_max_domain() : 1.0;
  579. float min_y = curve.is_valid() ? curve->get_min_value() : 0.0;
  580. float max_y = curve.is_valid() ? curve->get_max_value() : 1.0;
  581. const Rect2 world_rect = Rect2(min_x, min_y, max_x - min_x, max_y - min_y);
  582. const Size2 view_margin(margin, margin);
  583. const Size2 view_size = get_size() - view_margin * 2;
  584. const Vector2 scale = view_size / world_rect.size;
  585. Transform2D world_trans;
  586. world_trans.translate_local(-world_rect.position - Vector2(0, world_rect.size.y));
  587. world_trans.scale(Vector2(scale.x, -scale.y));
  588. Transform2D view_trans;
  589. view_trans.translate_local(view_margin);
  590. _world_to_view = view_trans * world_trans;
  591. }
  592. Vector2 CurveEdit::get_tangent_view_pos(int p_index, TangentIndex p_tangent) const {
  593. Vector2 dir;
  594. if (p_tangent == TANGENT_LEFT) {
  595. dir = -Vector2(1, curve->get_point_left_tangent(p_index));
  596. } else {
  597. dir = Vector2(1, curve->get_point_right_tangent(p_index));
  598. }
  599. Vector2 point_pos = curve->get_point_position(p_index);
  600. Vector2 point_view_pos = get_view_pos(point_pos);
  601. Vector2 control_view_pos = get_view_pos(point_pos + dir);
  602. Vector2 distance_from_point = tangent_length * (control_view_pos - point_view_pos).normalized();
  603. Vector2 tangent_view_pos = point_view_pos + distance_from_point;
  604. // Since the tangent is long, it might slip outside of the area of the editor for points close to the domain/range boundaries.
  605. // The code below shrinks the tangent control by up to 50% so it always stays inside the editor for points within the bounds.
  606. float fraction_inside = 1.0;
  607. if (distance_from_point.x != 0.0) {
  608. fraction_inside = MIN(fraction_inside, ((distance_from_point.x > 0 ? get_rect().size.x : 0) - point_view_pos.x) / distance_from_point.x);
  609. }
  610. if (distance_from_point.y != 0.0) {
  611. fraction_inside = MIN(fraction_inside, ((distance_from_point.y > 0 ? get_rect().size.y : 0) - point_view_pos.y) / distance_from_point.y);
  612. }
  613. if (fraction_inside < 1.0 && fraction_inside > 0.5) {
  614. tangent_view_pos = point_view_pos + distance_from_point * fraction_inside;
  615. }
  616. return tangent_view_pos;
  617. }
  618. Vector2 CurveEdit::get_view_pos(const Vector2 &p_world_pos) const {
  619. return _world_to_view.xform(p_world_pos);
  620. }
  621. Vector2 CurveEdit::get_world_pos(const Vector2 &p_view_pos) const {
  622. return _world_to_view.affine_inverse().xform(p_view_pos);
  623. }
  624. // Uses non-baked points, but takes advantage of ordered iteration to be faster.
  625. void CurveEdit::plot_curve_accurate(float p_step, const Color &p_line_color, const Color &p_edge_line_color) {
  626. const real_t min_x = curve->get_min_domain();
  627. const real_t max_x = curve->get_max_domain();
  628. if (curve->get_point_count() <= 1) { // Draw single line through entire plot.
  629. real_t y = curve->sample(0);
  630. draw_line(get_view_pos(Vector2(min_x, y)) + Vector2(0.5, 0), get_view_pos(Vector2(max_x, y)) - Vector2(1.5, 0), p_line_color, LINE_WIDTH, true);
  631. return;
  632. }
  633. Vector2 first_point = curve->get_point_position(0);
  634. Vector2 last_point = curve->get_point_position(curve->get_point_count() - 1);
  635. // Transform pixels-per-step into curve domain. Only works for non-rotated transforms.
  636. const float world_step_size = p_step / _world_to_view.get_scale().x;
  637. // Edge lines.
  638. draw_line(get_view_pos(Vector2(min_x, first_point.y)) + Vector2(0.5, 0), get_view_pos(first_point), p_edge_line_color, LINE_WIDTH, true);
  639. draw_line(get_view_pos(last_point), get_view_pos(Vector2(max_x, last_point.y)) - Vector2(1.5, 0), p_edge_line_color, LINE_WIDTH, true);
  640. // Draw section by section, so that we get maximum precision near points.
  641. // It's an accurate representation, but slower than using the baked one.
  642. for (int i = 1; i < curve->get_point_count(); ++i) {
  643. Vector2 a = curve->get_point_position(i - 1);
  644. Vector2 b = curve->get_point_position(i);
  645. Vector2 pos = a;
  646. Vector2 prev_pos = a;
  647. float samples = (b.x - a.x) / world_step_size;
  648. for (int j = 1; j < samples; j++) {
  649. float x = j * world_step_size;
  650. pos.x = a.x + x;
  651. pos.y = curve->sample_local_nocheck(i - 1, x);
  652. draw_line(get_view_pos(prev_pos), get_view_pos(pos), p_line_color, LINE_WIDTH, true);
  653. prev_pos = pos;
  654. }
  655. draw_line(get_view_pos(prev_pos), get_view_pos(b), p_line_color, LINE_WIDTH, true);
  656. }
  657. }
  658. void CurveEdit::_redraw() {
  659. if (curve.is_null()) {
  660. return;
  661. }
  662. update_view_transform();
  663. // Draw background.
  664. Vector2 view_size = get_rect().size;
  665. draw_style_box(get_theme_stylebox(SceneStringName(panel), SNAME("Tree")), Rect2(Point2(), view_size));
  666. // Draw primary grid.
  667. draw_set_transform_matrix(_world_to_view);
  668. Vector2 min_edge = get_world_pos(Vector2(0, view_size.y));
  669. Vector2 max_edge = get_world_pos(Vector2(view_size.x, 0));
  670. const Color grid_color_primary = get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.25);
  671. const Color grid_color = get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.1);
  672. const Vector2i grid_steps = Vector2i(4, 2);
  673. const Vector2 step_size = Vector2(curve->get_domain_range(), curve->get_value_range()) / grid_steps;
  674. draw_line(Vector2(min_edge.x, curve->get_min_value()), Vector2(max_edge.x, curve->get_min_value()), grid_color_primary);
  675. draw_line(Vector2(max_edge.x, curve->get_max_value()), Vector2(min_edge.x, curve->get_max_value()), grid_color_primary);
  676. draw_line(Vector2(curve->get_min_domain(), min_edge.y), Vector2(curve->get_min_domain(), max_edge.y), grid_color_primary);
  677. draw_line(Vector2(curve->get_max_domain(), max_edge.y), Vector2(curve->get_max_domain(), min_edge.y), grid_color_primary);
  678. for (int i = 1; i < grid_steps.x; i++) {
  679. real_t x = curve->get_min_domain() + i * step_size.x;
  680. draw_line(Vector2(x, min_edge.y), Vector2(x, max_edge.y), grid_color);
  681. }
  682. for (int i = 1; i < grid_steps.y; i++) {
  683. real_t y = curve->get_min_value() + i * step_size.y;
  684. draw_line(Vector2(min_edge.x, y), Vector2(max_edge.x, y), grid_color);
  685. }
  686. // Draw number markings.
  687. draw_set_transform_matrix(Transform2D());
  688. Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
  689. int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
  690. float font_height = font->get_height(font_size);
  691. Color text_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
  692. int pad = Math::round(2 * EDSCALE);
  693. for (int i = 0; i <= grid_steps.x; ++i) {
  694. real_t x = curve->get_min_domain() + i * step_size.x;
  695. draw_string(font, get_view_pos(Vector2(x, curve->get_min_value())) + Vector2(pad, font_height - pad), String::num(x, 2), HORIZONTAL_ALIGNMENT_CENTER, -1, font_size, text_color);
  696. }
  697. for (int i = 0; i <= grid_steps.y; ++i) {
  698. real_t y = curve->get_min_value() + i * step_size.y;
  699. draw_string(font, get_view_pos(Vector2(curve->get_min_domain(), y)) + Vector2(pad, -pad), String::num(y, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  700. }
  701. // Draw curve in view coordinates. Curve world-to-view point conversion happens in plot_curve_accurate().
  702. const Color line_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
  703. const Color edge_line_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)) * Color(1, 1, 1, 0.75);
  704. plot_curve_accurate(STEP_SIZE, line_color, edge_line_color);
  705. // Draw points, except for the selected one.
  706. bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
  707. const Color point_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
  708. for (int i = 0; i < curve->get_point_count(); ++i) {
  709. Vector2 pos = get_view_pos(curve->get_point_position(i));
  710. if (selected_index != i) {
  711. draw_rect(Rect2(pos, Vector2(0, 0)).grow(point_radius), point_color);
  712. }
  713. if (hovered_index == i && hovered_tangent_index == TANGENT_NONE) {
  714. draw_rect(Rect2(pos, Vector2(0, 0)).grow(hover_radius - Math::round(3 * EDSCALE)), line_color, false, Math::round(1 * EDSCALE));
  715. }
  716. }
  717. // Draw selected point and its tangents.
  718. if (selected_index >= 0) {
  719. const Vector2 point_pos = curve->get_point_position(selected_index);
  720. const Color selected_point_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
  721. // Draw tangents if not dragging a point, or if holding a point without having moved it yet.
  722. if (grabbing == GRAB_NONE || initial_grab_pos == point_pos || selected_tangent_index != TANGENT_NONE) {
  723. const Color selected_tangent_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)).darkened(0.25);
  724. const Color tangent_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)).darkened(0.25);
  725. if (selected_index != 0) {
  726. Vector2 control_pos = get_tangent_view_pos(selected_index, TANGENT_LEFT);
  727. Color left_tangent_color = (selected_tangent_index == TANGENT_LEFT) ? selected_tangent_color : tangent_color;
  728. draw_line(get_view_pos(point_pos), control_pos, left_tangent_color, 0.5 * EDSCALE, true);
  729. // Square for linear mode, circle otherwise.
  730. if (curve->get_point_left_mode(selected_index) == Curve::TANGENT_FREE) {
  731. draw_circle(control_pos, tangent_radius, left_tangent_color);
  732. } else {
  733. draw_rect(Rect2(control_pos, Vector2(0, 0)).grow(tangent_radius), left_tangent_color);
  734. }
  735. // Hover indicator.
  736. if (hovered_tangent_index == TANGENT_LEFT || (hovered_tangent_index == TANGENT_RIGHT && !shift_pressed && curve->get_point_left_mode(selected_index) != Curve::TANGENT_LINEAR)) {
  737. draw_rect(Rect2(control_pos, Vector2(0, 0)).grow(tangent_hover_radius - Math::round(3 * EDSCALE)), tangent_color, false, Math::round(1 * EDSCALE));
  738. }
  739. }
  740. if (selected_index != curve->get_point_count() - 1) {
  741. Vector2 control_pos = get_tangent_view_pos(selected_index, TANGENT_RIGHT);
  742. Color right_tangent_color = (selected_tangent_index == TANGENT_RIGHT) ? selected_tangent_color : tangent_color;
  743. draw_line(get_view_pos(point_pos), control_pos, right_tangent_color, 0.5 * EDSCALE, true);
  744. // Square for linear mode, circle otherwise.
  745. if (curve->get_point_right_mode(selected_index) == Curve::TANGENT_FREE) {
  746. draw_circle(control_pos, tangent_radius, right_tangent_color);
  747. } else {
  748. draw_rect(Rect2(control_pos, Vector2(0, 0)).grow(tangent_radius), right_tangent_color);
  749. }
  750. // Hover indicator.
  751. if (hovered_tangent_index == TANGENT_RIGHT || (hovered_tangent_index == TANGENT_LEFT && !shift_pressed && curve->get_point_right_mode(selected_index) != Curve::TANGENT_LINEAR)) {
  752. draw_rect(Rect2(control_pos, Vector2(0, 0)).grow(tangent_hover_radius - Math::round(3 * EDSCALE)), tangent_color, false, Math::round(1 * EDSCALE));
  753. }
  754. }
  755. }
  756. draw_rect(Rect2(get_view_pos(point_pos), Vector2(0, 0)).grow(point_radius), selected_point_color);
  757. }
  758. // Draw help text.
  759. if (selected_index > 0 && selected_index < curve->get_point_count() - 1 && selected_tangent_index == TANGENT_NONE && hovered_tangent_index != TANGENT_NONE && !shift_pressed) {
  760. float width = view_size.x - 50 * EDSCALE;
  761. text_color.a *= 0.4;
  762. draw_multiline_string(font, Vector2(25 * EDSCALE, font_height - Math::round(2 * EDSCALE)), TTR("Hold Shift to edit tangents individually"), HORIZONTAL_ALIGNMENT_CENTER, width, font_size, -1, text_color);
  763. } else if (selected_index != -1 && selected_tangent_index == TANGENT_NONE) {
  764. const Vector2 point_pos = curve->get_point_position(selected_index);
  765. float width = view_size.x - 50 * EDSCALE;
  766. text_color.a *= 0.8;
  767. draw_string(font, Vector2(25 * EDSCALE, font_height - Math::round(2 * EDSCALE)), vformat("(%.2f, %.2f)", point_pos.x, point_pos.y), HORIZONTAL_ALIGNMENT_CENTER, width, font_size, text_color);
  768. } else if (selected_index != -1 && selected_tangent_index != TANGENT_NONE) {
  769. float width = view_size.x - 50 * EDSCALE;
  770. text_color.a *= 0.8;
  771. real_t theta = Math::rad_to_deg(Math::atan(selected_tangent_index == TANGENT_LEFT ? -1 * curve->get_point_left_tangent(selected_index) : curve->get_point_right_tangent(selected_index)));
  772. draw_string(font, Vector2(25 * EDSCALE, font_height - Math::round(2 * EDSCALE)), String::num(theta, 1) + String::utf8(" °"), HORIZONTAL_ALIGNMENT_CENTER, width, font_size, text_color);
  773. }
  774. // Draw temporary constraints and snapping axes.
  775. draw_set_transform_matrix(_world_to_view);
  776. if (Input::get_singleton()->is_key_pressed(Key::ALT) && grabbing != GRAB_NONE && selected_tangent_index == TANGENT_NONE) {
  777. float prev_point_offset = (selected_index > 0) ? curve->get_point_position(selected_index - 1).x : curve->get_min_domain();
  778. float next_point_offset = (selected_index < curve->get_point_count() - 1) ? curve->get_point_position(selected_index + 1).x : curve->get_max_domain();
  779. draw_line(Vector2(prev_point_offset, curve->get_min_value()), Vector2(prev_point_offset, curve->get_max_value()), Color(point_color, 0.6));
  780. draw_line(Vector2(next_point_offset, curve->get_min_value()), Vector2(next_point_offset, curve->get_max_value()), Color(point_color, 0.6));
  781. }
  782. if (shift_pressed && grabbing != GRAB_NONE && selected_tangent_index == TANGENT_NONE) {
  783. draw_line(Vector2(initial_grab_pos.x, curve->get_min_value()), Vector2(initial_grab_pos.x, curve->get_max_value()), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)).darkened(0.4));
  784. draw_line(Vector2(curve->get_min_domain(), initial_grab_pos.y), Vector2(curve->get_max_domain(), initial_grab_pos.y), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)).darkened(0.4));
  785. }
  786. }
  787. ///////////////////////
  788. const int CurveEditor::DEFAULT_SNAP = 10;
  789. void CurveEditor::_set_snap_enabled(bool p_enabled) {
  790. curve_editor_rect->set_snap_enabled(p_enabled);
  791. snap_count_edit->set_visible(p_enabled);
  792. }
  793. void CurveEditor::_set_snap_count(int p_snap_count) {
  794. curve_editor_rect->set_snap_count(CLAMP(p_snap_count, 2, 100));
  795. }
  796. void CurveEditor::_on_preset_item_selected(int p_preset_id) {
  797. curve_editor_rect->use_preset(p_preset_id);
  798. }
  799. void CurveEditor::set_curve(const Ref<Curve> &p_curve) {
  800. curve_editor_rect->set_curve(p_curve);
  801. }
  802. void CurveEditor::_notification(int p_what) {
  803. switch (p_what) {
  804. case NOTIFICATION_THEME_CHANGED: {
  805. spacing = Math::round(BASE_SPACING * get_theme_default_base_scale());
  806. snap_button->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
  807. PopupMenu *p = presets_button->get_popup();
  808. p->clear();
  809. p->add_icon_item(get_editor_theme_icon(SNAME("CurveConstant")), TTR("Constant"), CurveEdit::PRESET_CONSTANT);
  810. p->add_icon_item(get_editor_theme_icon(SNAME("CurveLinear")), TTR("Linear"), CurveEdit::PRESET_LINEAR);
  811. p->add_icon_item(get_editor_theme_icon(SNAME("CurveIn")), TTR("Ease In"), CurveEdit::PRESET_EASE_IN);
  812. p->add_icon_item(get_editor_theme_icon(SNAME("CurveOut")), TTR("Ease Out"), CurveEdit::PRESET_EASE_OUT);
  813. p->add_icon_item(get_editor_theme_icon(SNAME("CurveInOut")), TTR("Smoothstep"), CurveEdit::PRESET_SMOOTHSTEP);
  814. } break;
  815. case NOTIFICATION_READY: {
  816. Ref<Curve> curve = curve_editor_rect->get_curve();
  817. if (curve.is_valid()) {
  818. // Set snapping settings based on the curve's meta.
  819. snap_button->set_pressed(curve->get_meta("_snap_enabled", false));
  820. snap_count_edit->set_value(curve->get_meta("_snap_count", DEFAULT_SNAP));
  821. }
  822. } break;
  823. case NOTIFICATION_RESIZED:
  824. curve_editor_rect->update_minimum_size();
  825. break;
  826. }
  827. }
  828. CurveEditor::CurveEditor() {
  829. HFlowContainer *toolbar = memnew(HFlowContainer);
  830. add_child(toolbar);
  831. snap_button = memnew(Button);
  832. snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
  833. snap_button->set_toggle_mode(true);
  834. toolbar->add_child(snap_button);
  835. snap_button->connect(SceneStringName(toggled), callable_mp(this, &CurveEditor::_set_snap_enabled));
  836. toolbar->add_child(memnew(VSeparator));
  837. snap_count_edit = memnew(EditorSpinSlider);
  838. snap_count_edit->set_min(2);
  839. snap_count_edit->set_max(100);
  840. snap_count_edit->set_accessibility_name(TTRC("Snap Step"));
  841. snap_count_edit->set_value(DEFAULT_SNAP);
  842. snap_count_edit->set_custom_minimum_size(Size2(65 * EDSCALE, 0));
  843. toolbar->add_child(snap_count_edit);
  844. snap_count_edit->connect(SceneStringName(value_changed), callable_mp(this, &CurveEditor::_set_snap_count));
  845. presets_button = memnew(MenuButton);
  846. presets_button->set_text(TTR("Presets"));
  847. presets_button->set_switch_on_hover(true);
  848. presets_button->set_h_size_flags(SIZE_EXPAND | SIZE_SHRINK_END);
  849. toolbar->add_child(presets_button);
  850. presets_button->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CurveEditor::_on_preset_item_selected));
  851. curve_editor_rect = memnew(CurveEdit);
  852. add_child(curve_editor_rect);
  853. // Some empty space below. Not a part of the curve editor so it can't draw in it.
  854. Control *empty_space = memnew(Control);
  855. empty_space->set_custom_minimum_size(Vector2(0, spacing));
  856. add_child(empty_space);
  857. set_mouse_filter(MOUSE_FILTER_STOP);
  858. _set_snap_enabled(snap_button->is_pressed());
  859. _set_snap_count(snap_count_edit->get_value());
  860. }
  861. ///////////////////////
  862. bool EditorInspectorPluginCurve::can_handle(Object *p_object) {
  863. return Object::cast_to<Curve>(p_object) != nullptr;
  864. }
  865. void EditorInspectorPluginCurve::parse_begin(Object *p_object) {
  866. Curve *curve = Object::cast_to<Curve>(p_object);
  867. ERR_FAIL_NULL(curve);
  868. Ref<Curve> c(curve);
  869. CurveEditor *editor = memnew(CurveEditor);
  870. editor->set_curve(c);
  871. add_custom_control(editor);
  872. }
  873. CurveEditorPlugin::CurveEditorPlugin() {
  874. Ref<EditorInspectorPluginCurve> plugin;
  875. plugin.instantiate();
  876. add_inspector_plugin(plugin);
  877. EditorInterface::get_singleton()->get_resource_previewer()->add_preview_generator(memnew(CurvePreviewGenerator));
  878. }
  879. ///////////////////////
  880. bool CurvePreviewGenerator::handles(const String &p_type) const {
  881. return p_type == "Curve";
  882. }
  883. Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
  884. Ref<Curve> curve = p_from;
  885. if (curve.is_null()) {
  886. return Ref<Texture2D>();
  887. }
  888. Ref<Image> img_ref;
  889. img_ref.instantiate();
  890. Image &im = **img_ref;
  891. im.initialize_data(p_size.x, p_size.y, false, Image::FORMAT_RGBA8);
  892. Color line_color = EditorInterface::get_singleton()->get_editor_theme()->get_color(SceneStringName(font_color), EditorStringName(Editor));
  893. // Set the first pixel of the thumbnail.
  894. float v = (curve->sample_baked(curve->get_min_domain()) - curve->get_min_value()) / curve->get_value_range();
  895. int y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height() - 1);
  896. im.set_pixel(0, y, line_color);
  897. // Plot a line towards the next point.
  898. int prev_y = y;
  899. for (int x = 1; x < im.get_width(); ++x) {
  900. float t = static_cast<float>(x) / im.get_width() * curve->get_domain_range() + curve->get_min_domain();
  901. v = (curve->sample_baked(t) - curve->get_min_value()) / curve->get_value_range();
  902. y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height() - 1);
  903. Vector<Point2i> points = Geometry2D::bresenham_line(Point2i(x - 1, prev_y), Point2i(x, y));
  904. for (Point2i point : points) {
  905. im.set_pixelv(point, line_color);
  906. }
  907. prev_y = y;
  908. }
  909. return ImageTexture::create_from_image(img_ref);
  910. }