curve_editor_plugin.cpp 43 KB

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