curve_editor_plugin.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. /*************************************************************************/
  2. /* curve_editor_plugin.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
  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/os/keyboard.h"
  35. #include "editor/editor_node.h"
  36. #include "editor/editor_scale.h"
  37. #include "editor/editor_settings.h"
  38. CurveEditor::CurveEditor() {
  39. _selected_point = -1;
  40. _hover_point = -1;
  41. _selected_tangent = TANGENT_NONE;
  42. _hover_radius = 6;
  43. _tangents_length = 40;
  44. _dragging = false;
  45. _has_undo_data = false;
  46. set_focus_mode(FOCUS_ALL);
  47. set_clip_contents(true);
  48. _context_menu = memnew(PopupMenu);
  49. _context_menu->connect("id_pressed", callable_mp(this, &CurveEditor::on_context_menu_item_selected));
  50. add_child(_context_menu);
  51. _presets_menu = memnew(PopupMenu);
  52. _presets_menu->set_name("_presets_menu");
  53. _presets_menu->add_item(TTR("Flat 0"), PRESET_FLAT0);
  54. _presets_menu->add_item(TTR("Flat 1"), PRESET_FLAT1);
  55. _presets_menu->add_item(TTR("Linear"), PRESET_LINEAR);
  56. _presets_menu->add_item(TTR("Ease In"), PRESET_EASE_IN);
  57. _presets_menu->add_item(TTR("Ease Out"), PRESET_EASE_OUT);
  58. _presets_menu->add_item(TTR("Smoothstep"), PRESET_SMOOTHSTEP);
  59. _presets_menu->connect("id_pressed", callable_mp(this, &CurveEditor::on_preset_item_selected));
  60. _context_menu->add_child(_presets_menu);
  61. }
  62. void CurveEditor::set_curve(Ref<Curve> curve) {
  63. if (curve == _curve_ref) {
  64. return;
  65. }
  66. if (_curve_ref.is_valid()) {
  67. _curve_ref->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveEditor::_curve_changed));
  68. _curve_ref->disconnect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEditor::_curve_changed));
  69. }
  70. _curve_ref = curve;
  71. if (_curve_ref.is_valid()) {
  72. _curve_ref->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveEditor::_curve_changed));
  73. _curve_ref->connect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEditor::_curve_changed));
  74. }
  75. _selected_point = -1;
  76. _hover_point = -1;
  77. _selected_tangent = TANGENT_NONE;
  78. queue_redraw();
  79. // Note: if you edit a curve, then set another, and try to undo,
  80. // it will normally apply on the previous curve, but you won't see it
  81. }
  82. Size2 CurveEditor::get_minimum_size() const {
  83. return Vector2(64, 150) * EDSCALE;
  84. }
  85. void CurveEditor::_notification(int p_what) {
  86. switch (p_what) {
  87. case NOTIFICATION_DRAW: {
  88. _draw();
  89. } break;
  90. }
  91. }
  92. void CurveEditor::gui_input(const Ref<InputEvent> &p_event) {
  93. Ref<InputEventMouseButton> mb_ref = p_event;
  94. if (mb_ref.is_valid()) {
  95. const InputEventMouseButton &mb = **mb_ref;
  96. if (mb.is_pressed() && !_dragging) {
  97. Vector2 mpos = mb.get_position();
  98. _selected_tangent = get_tangent_at(mpos);
  99. if (_selected_tangent == TANGENT_NONE) {
  100. set_selected_point(get_point_at(mpos));
  101. }
  102. switch (mb.get_button_index()) {
  103. case MouseButton::RIGHT:
  104. _context_click_pos = mpos;
  105. open_context_menu(get_screen_position() + mpos);
  106. break;
  107. case MouseButton::MIDDLE:
  108. remove_point(_hover_point);
  109. break;
  110. case MouseButton::LEFT:
  111. _dragging = true;
  112. break;
  113. default:
  114. break;
  115. }
  116. }
  117. if (!mb.is_pressed() && _dragging && mb.get_button_index() == MouseButton::LEFT) {
  118. _dragging = false;
  119. if (_has_undo_data) {
  120. Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo();
  121. ur->create_action(_selected_tangent == TANGENT_NONE ? TTR("Modify Curve Point") : TTR("Modify Curve Tangent"));
  122. ur->add_do_method(*_curve_ref, "_set_data", _curve_ref->get_data());
  123. ur->add_undo_method(*_curve_ref, "_set_data", _undo_data);
  124. // Note: this will trigger one more "changed" signal even if nothing changes,
  125. // but it's ok since it would have fired every frame during the drag anyways
  126. ur->commit_action();
  127. _has_undo_data = false;
  128. }
  129. }
  130. }
  131. Ref<InputEventMouseMotion> mm_ref = p_event;
  132. if (mm_ref.is_valid()) {
  133. const InputEventMouseMotion &mm = **mm_ref;
  134. Vector2 mpos = mm.get_position();
  135. if (_dragging && _curve_ref.is_valid()) {
  136. if (_selected_point != -1) {
  137. Curve &curve = **_curve_ref;
  138. if (!_has_undo_data) {
  139. // Save full curve state before dragging points,
  140. // because this operation can modify their order
  141. _undo_data = curve.get_data();
  142. _has_undo_data = true;
  143. }
  144. const float curve_amplitude = curve.get_max_value() - curve.get_min_value();
  145. // Snap to "round" coordinates when holding Ctrl.
  146. // Be more precise when holding Shift as well.
  147. float snap_threshold;
  148. if (mm.is_ctrl_pressed()) {
  149. snap_threshold = mm.is_shift_pressed() ? 0.025 : 0.1;
  150. } else {
  151. snap_threshold = 0.0;
  152. }
  153. if (_selected_tangent == TANGENT_NONE) {
  154. // Drag point
  155. Vector2 point_pos = get_world_pos(mpos).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude));
  156. int i = curve.set_point_offset(_selected_point, point_pos.x);
  157. // The index may change if the point is dragged across another one
  158. set_hover_point_index(i);
  159. set_selected_point(i);
  160. // This is to prevent the user from losing a point out of view.
  161. if (point_pos.y < curve.get_min_value()) {
  162. point_pos.y = curve.get_min_value();
  163. } else if (point_pos.y > curve.get_max_value()) {
  164. point_pos.y = curve.get_max_value();
  165. }
  166. curve.set_point_value(_selected_point, point_pos.y);
  167. } else {
  168. // Drag tangent
  169. const Vector2 point_pos = curve.get_point_position(_selected_point);
  170. const Vector2 control_pos = get_world_pos(mpos).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude));
  171. Vector2 dir = (control_pos - point_pos).normalized();
  172. real_t tangent;
  173. if (!Math::is_zero_approx(dir.x)) {
  174. tangent = dir.y / dir.x;
  175. } else {
  176. tangent = 9999 * (dir.y >= 0 ? 1 : -1);
  177. }
  178. bool link = !Input::get_singleton()->is_key_pressed(Key::SHIFT);
  179. if (_selected_tangent == TANGENT_LEFT) {
  180. curve.set_point_left_tangent(_selected_point, tangent);
  181. // Note: if a tangent is set to linear, it shouldn't be linked to the other
  182. if (link && _selected_point != (curve.get_point_count() - 1) && curve.get_point_right_mode(_selected_point) != Curve::TANGENT_LINEAR) {
  183. curve.set_point_right_tangent(_selected_point, tangent);
  184. }
  185. } else {
  186. curve.set_point_right_tangent(_selected_point, tangent);
  187. if (link && _selected_point != 0 && curve.get_point_left_mode(_selected_point) != Curve::TANGENT_LINEAR) {
  188. curve.set_point_left_tangent(_selected_point, tangent);
  189. }
  190. }
  191. }
  192. }
  193. } else {
  194. set_hover_point_index(get_point_at(mpos));
  195. }
  196. }
  197. Ref<InputEventKey> key_ref = p_event;
  198. if (key_ref.is_valid()) {
  199. const InputEventKey &key = **key_ref;
  200. if (key.is_pressed() && _selected_point != -1) {
  201. if (key.get_keycode() == Key::KEY_DELETE) {
  202. remove_point(_selected_point);
  203. }
  204. }
  205. }
  206. }
  207. void CurveEditor::on_preset_item_selected(int preset_id) {
  208. ERR_FAIL_COND(preset_id < 0 || preset_id >= PRESET_COUNT);
  209. ERR_FAIL_COND(_curve_ref.is_null());
  210. Curve &curve = **_curve_ref;
  211. Array previous_data = curve.get_data();
  212. curve.clear_points();
  213. switch (preset_id) {
  214. case PRESET_FLAT0:
  215. curve.add_point(Vector2(0, 0));
  216. curve.add_point(Vector2(1, 0));
  217. curve.set_point_right_mode(0, Curve::TANGENT_LINEAR);
  218. curve.set_point_left_mode(1, Curve::TANGENT_LINEAR);
  219. break;
  220. case PRESET_FLAT1:
  221. curve.add_point(Vector2(0, 1));
  222. curve.add_point(Vector2(1, 1));
  223. curve.set_point_right_mode(0, Curve::TANGENT_LINEAR);
  224. curve.set_point_left_mode(1, Curve::TANGENT_LINEAR);
  225. break;
  226. case PRESET_LINEAR:
  227. curve.add_point(Vector2(0, 0));
  228. curve.add_point(Vector2(1, 1));
  229. curve.set_point_right_mode(0, Curve::TANGENT_LINEAR);
  230. curve.set_point_left_mode(1, Curve::TANGENT_LINEAR);
  231. break;
  232. case PRESET_EASE_IN:
  233. curve.add_point(Vector2(0, 0));
  234. curve.add_point(Vector2(1, 1), (curve.get_max_value() - curve.get_min_value()) * 1.4, 0);
  235. break;
  236. case PRESET_EASE_OUT:
  237. curve.add_point(Vector2(0, 0), 0, (curve.get_max_value() - curve.get_min_value()) * 1.4);
  238. curve.add_point(Vector2(1, 1));
  239. break;
  240. case PRESET_SMOOTHSTEP:
  241. curve.add_point(Vector2(0, 0));
  242. curve.add_point(Vector2(1, 1));
  243. break;
  244. default:
  245. break;
  246. }
  247. Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo();
  248. ur->create_action(TTR("Load Curve Preset"));
  249. ur->add_do_method(&curve, "_set_data", curve.get_data());
  250. ur->add_undo_method(&curve, "_set_data", previous_data);
  251. ur->commit_action();
  252. }
  253. void CurveEditor::_curve_changed() {
  254. queue_redraw();
  255. // Point count can change in case of undo
  256. if (_selected_point >= _curve_ref->get_point_count()) {
  257. set_selected_point(-1);
  258. }
  259. }
  260. void CurveEditor::on_context_menu_item_selected(int action_id) {
  261. switch (action_id) {
  262. case CONTEXT_ADD_POINT:
  263. add_point(_context_click_pos);
  264. break;
  265. case CONTEXT_REMOVE_POINT:
  266. remove_point(_selected_point);
  267. break;
  268. case CONTEXT_LINEAR:
  269. toggle_linear();
  270. break;
  271. case CONTEXT_LEFT_LINEAR:
  272. toggle_linear(TANGENT_LEFT);
  273. break;
  274. case CONTEXT_RIGHT_LINEAR:
  275. toggle_linear(TANGENT_RIGHT);
  276. break;
  277. }
  278. }
  279. void CurveEditor::open_context_menu(Vector2 pos) {
  280. _context_menu->set_position(pos);
  281. _context_menu->clear();
  282. if (_curve_ref.is_valid()) {
  283. _context_menu->add_item(TTR("Add Point"), CONTEXT_ADD_POINT);
  284. if (_selected_point >= 0) {
  285. _context_menu->add_item(TTR("Remove Point"), CONTEXT_REMOVE_POINT);
  286. if (_selected_tangent != TANGENT_NONE) {
  287. _context_menu->add_separator();
  288. _context_menu->add_check_item(TTR("Linear"), CONTEXT_LINEAR);
  289. bool is_linear = _selected_tangent == TANGENT_LEFT
  290. ? _curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR
  291. : _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR;
  292. _context_menu->set_item_checked(_context_menu->get_item_index(CONTEXT_LINEAR), is_linear);
  293. } else {
  294. if (_selected_point > 0 || _selected_point + 1 < _curve_ref->get_point_count()) {
  295. _context_menu->add_separator();
  296. }
  297. if (_selected_point > 0) {
  298. _context_menu->add_check_item(TTR("Left Linear"), CONTEXT_LEFT_LINEAR);
  299. _context_menu->set_item_checked(_context_menu->get_item_index(CONTEXT_LEFT_LINEAR),
  300. _curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR);
  301. }
  302. if (_selected_point + 1 < _curve_ref->get_point_count()) {
  303. _context_menu->add_check_item(TTR("Right Linear"), CONTEXT_RIGHT_LINEAR);
  304. _context_menu->set_item_checked(_context_menu->get_item_index(CONTEXT_RIGHT_LINEAR),
  305. _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR);
  306. }
  307. }
  308. }
  309. _context_menu->add_separator();
  310. }
  311. _context_menu->add_submenu_item(TTR("Load Preset"), _presets_menu->get_name());
  312. _context_menu->reset_size();
  313. _context_menu->popup();
  314. }
  315. int CurveEditor::get_point_at(Vector2 pos) const {
  316. if (_curve_ref.is_null()) {
  317. return -1;
  318. }
  319. const Curve &curve = **_curve_ref;
  320. const float true_hover_radius = Math::round(_hover_radius * EDSCALE);
  321. const float r = true_hover_radius * true_hover_radius;
  322. for (int i = 0; i < curve.get_point_count(); ++i) {
  323. Vector2 p = get_view_pos(curve.get_point_position(i));
  324. if (p.distance_squared_to(pos) <= r) {
  325. return i;
  326. }
  327. }
  328. return -1;
  329. }
  330. CurveEditor::TangentIndex CurveEditor::get_tangent_at(Vector2 pos) const {
  331. if (_curve_ref.is_null() || _selected_point < 0) {
  332. return TANGENT_NONE;
  333. }
  334. if (_selected_point != 0) {
  335. Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_LEFT);
  336. if (control_pos.distance_to(pos) < _hover_radius) {
  337. return TANGENT_LEFT;
  338. }
  339. }
  340. if (_selected_point != _curve_ref->get_point_count() - 1) {
  341. Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_RIGHT);
  342. if (control_pos.distance_to(pos) < _hover_radius) {
  343. return TANGENT_RIGHT;
  344. }
  345. }
  346. return TANGENT_NONE;
  347. }
  348. void CurveEditor::add_point(Vector2 pos) {
  349. ERR_FAIL_COND(_curve_ref.is_null());
  350. Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo();
  351. ur->create_action(TTR("Remove Curve Point"));
  352. Vector2 point_pos = get_world_pos(pos);
  353. if (point_pos.y < 0.0) {
  354. point_pos.y = 0.0;
  355. } else if (point_pos.y > 1.0) {
  356. point_pos.y = 1.0;
  357. }
  358. // Small trick to get the point index to feed the undo method
  359. int i = _curve_ref->add_point(point_pos);
  360. _curve_ref->remove_point(i);
  361. ur->add_do_method(*_curve_ref, "add_point", point_pos);
  362. ur->add_undo_method(*_curve_ref, "remove_point", i);
  363. ur->commit_action();
  364. }
  365. void CurveEditor::remove_point(int index) {
  366. ERR_FAIL_COND(_curve_ref.is_null());
  367. Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo();
  368. ur->create_action(TTR("Remove Curve Point"));
  369. Curve::Point p = _curve_ref->get_point(index);
  370. ur->add_do_method(*_curve_ref, "remove_point", index);
  371. ur->add_undo_method(*_curve_ref, "add_point", p.position, p.left_tangent, p.right_tangent, p.left_mode, p.right_mode);
  372. if (index == _selected_point) {
  373. set_selected_point(-1);
  374. }
  375. if (index == _hover_point) {
  376. set_hover_point_index(-1);
  377. }
  378. ur->commit_action();
  379. }
  380. void CurveEditor::toggle_linear(TangentIndex tangent) {
  381. ERR_FAIL_COND(_curve_ref.is_null());
  382. Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo();
  383. ur->create_action(TTR("Toggle Curve Linear Tangent"));
  384. if (tangent == TANGENT_NONE) {
  385. tangent = _selected_tangent;
  386. }
  387. if (tangent == TANGENT_LEFT) {
  388. bool is_linear = _curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR;
  389. Curve::TangentMode prev_mode = _curve_ref->get_point_left_mode(_selected_point);
  390. Curve::TangentMode mode = is_linear ? Curve::TANGENT_FREE : Curve::TANGENT_LINEAR;
  391. ur->add_do_method(*_curve_ref, "set_point_left_mode", _selected_point, mode);
  392. ur->add_undo_method(*_curve_ref, "set_point_left_mode", _selected_point, prev_mode);
  393. } else {
  394. bool is_linear = _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR;
  395. Curve::TangentMode prev_mode = _curve_ref->get_point_right_mode(_selected_point);
  396. Curve::TangentMode mode = is_linear ? Curve::TANGENT_FREE : Curve::TANGENT_LINEAR;
  397. ur->add_do_method(*_curve_ref, "set_point_right_mode", _selected_point, mode);
  398. ur->add_undo_method(*_curve_ref, "set_point_right_mode", _selected_point, prev_mode);
  399. }
  400. ur->commit_action();
  401. }
  402. void CurveEditor::set_selected_point(int index) {
  403. if (index != _selected_point) {
  404. _selected_point = index;
  405. queue_redraw();
  406. }
  407. }
  408. void CurveEditor::set_hover_point_index(int index) {
  409. if (index != _hover_point) {
  410. _hover_point = index;
  411. queue_redraw();
  412. }
  413. }
  414. void CurveEditor::update_view_transform() {
  415. Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
  416. int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
  417. const real_t margin = font->get_height(font_size) + 2 * EDSCALE;
  418. float min_y = 0;
  419. float max_y = 1;
  420. if (_curve_ref.is_valid()) {
  421. min_y = _curve_ref->get_min_value();
  422. max_y = _curve_ref->get_max_value();
  423. }
  424. const Rect2 world_rect = Rect2(Curve::MIN_X, min_y, Curve::MAX_X, max_y - min_y);
  425. const Size2 view_margin(margin, margin);
  426. const Size2 view_size = get_size() - view_margin * 2;
  427. const Vector2 scale = view_size / world_rect.size;
  428. Transform2D world_trans;
  429. world_trans.translate_local(-world_rect.position - Vector2(0, world_rect.size.y));
  430. world_trans.scale(Vector2(scale.x, -scale.y));
  431. Transform2D view_trans;
  432. view_trans.translate_local(view_margin);
  433. _world_to_view = view_trans * world_trans;
  434. }
  435. Vector2 CurveEditor::get_tangent_view_pos(int i, TangentIndex tangent) const {
  436. Vector2 dir;
  437. if (tangent == TANGENT_LEFT) {
  438. dir = -Vector2(1, _curve_ref->get_point_left_tangent(i));
  439. } else {
  440. dir = Vector2(1, _curve_ref->get_point_right_tangent(i));
  441. }
  442. Vector2 point_pos = get_view_pos(_curve_ref->get_point_position(i));
  443. Vector2 control_pos = get_view_pos(_curve_ref->get_point_position(i) + dir);
  444. return point_pos + Math::round(_tangents_length * EDSCALE) * (control_pos - point_pos).normalized();
  445. }
  446. Vector2 CurveEditor::get_view_pos(Vector2 world_pos) const {
  447. return _world_to_view.xform(world_pos);
  448. }
  449. Vector2 CurveEditor::get_world_pos(Vector2 view_pos) const {
  450. return _world_to_view.affine_inverse().xform(view_pos);
  451. }
  452. // Uses non-baked points, but takes advantage of ordered iteration to be faster
  453. template <typename T>
  454. static void plot_curve_accurate(const Curve &curve, float step, T plot_func) {
  455. if (curve.get_point_count() <= 1) {
  456. // Not enough points to make a curve, so it's just a straight line
  457. float y = curve.sample(0);
  458. plot_func(Vector2(0, y), Vector2(1.f, y), true);
  459. } else {
  460. Vector2 first_point = curve.get_point_position(0);
  461. Vector2 last_point = curve.get_point_position(curve.get_point_count() - 1);
  462. // Edge lines
  463. plot_func(Vector2(0, first_point.y), first_point, false);
  464. plot_func(Vector2(Curve::MAX_X, last_point.y), last_point, false);
  465. // Draw section by section, so that we get maximum precision near points.
  466. // It's an accurate representation, but slower than using the baked one.
  467. for (int i = 1; i < curve.get_point_count(); ++i) {
  468. Vector2 a = curve.get_point_position(i - 1);
  469. Vector2 b = curve.get_point_position(i);
  470. Vector2 pos = a;
  471. Vector2 prev_pos = a;
  472. float len = b.x - a.x;
  473. for (float x = step; x < len; x += step) {
  474. pos.x = a.x + x;
  475. pos.y = curve.sample_local_nocheck(i - 1, x);
  476. plot_func(prev_pos, pos, true);
  477. prev_pos = pos;
  478. }
  479. plot_func(prev_pos, b, true);
  480. }
  481. }
  482. }
  483. struct CanvasItemPlotCurve {
  484. CanvasItem &ci;
  485. Color color1;
  486. Color color2;
  487. CanvasItemPlotCurve(CanvasItem &p_ci, Color p_color1, Color p_color2) :
  488. ci(p_ci),
  489. color1(p_color1),
  490. color2(p_color2) {}
  491. void operator()(Vector2 pos0, Vector2 pos1, bool in_definition) {
  492. // FIXME: Using a line width greater than 1 breaks curve rendering
  493. ci.draw_line(pos0, pos1, in_definition ? color1 : color2, 1);
  494. }
  495. };
  496. void CurveEditor::_draw() {
  497. if (_curve_ref.is_null()) {
  498. return;
  499. }
  500. Curve &curve = **_curve_ref;
  501. update_view_transform();
  502. // Background
  503. Vector2 view_size = get_rect().size;
  504. draw_style_box(get_theme_stylebox(SNAME("bg"), SNAME("Tree")), Rect2(Point2(), view_size));
  505. // Grid
  506. draw_set_transform_matrix(_world_to_view);
  507. Vector2 min_edge = get_world_pos(Vector2(0, view_size.y));
  508. Vector2 max_edge = get_world_pos(Vector2(view_size.x, 0));
  509. const Color grid_color0 = get_theme_color(SNAME("mono_color"), SNAME("Editor")) * Color(1, 1, 1, 0.15);
  510. const Color grid_color1 = get_theme_color(SNAME("mono_color"), SNAME("Editor")) * Color(1, 1, 1, 0.07);
  511. draw_line(Vector2(min_edge.x, curve.get_min_value()), Vector2(max_edge.x, curve.get_min_value()), grid_color0);
  512. draw_line(Vector2(max_edge.x, curve.get_max_value()), Vector2(min_edge.x, curve.get_max_value()), grid_color0);
  513. draw_line(Vector2(0, min_edge.y), Vector2(0, max_edge.y), grid_color0);
  514. draw_line(Vector2(1, max_edge.y), Vector2(1, min_edge.y), grid_color0);
  515. float curve_height = (curve.get_max_value() - curve.get_min_value());
  516. const Vector2 grid_step(0.25, 0.5 * curve_height);
  517. for (real_t x = 0; x < 1.0; x += grid_step.x) {
  518. draw_line(Vector2(x, min_edge.y), Vector2(x, max_edge.y), grid_color1);
  519. }
  520. for (real_t y = curve.get_min_value(); y < curve.get_max_value(); y += grid_step.y) {
  521. draw_line(Vector2(min_edge.x, y), Vector2(max_edge.x, y), grid_color1);
  522. }
  523. // Markings
  524. draw_set_transform_matrix(Transform2D());
  525. Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
  526. int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
  527. float font_height = font->get_height(font_size);
  528. Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
  529. {
  530. // X axis
  531. float y = curve.get_min_value();
  532. Vector2 off(0, font_height - 1);
  533. draw_string(font, get_view_pos(Vector2(0, y)) + off, "0.0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  534. draw_string(font, get_view_pos(Vector2(0.25, y)) + off, "0.25", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  535. draw_string(font, get_view_pos(Vector2(0.5, y)) + off, "0.5", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  536. draw_string(font, get_view_pos(Vector2(0.75, y)) + off, "0.75", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  537. draw_string(font, get_view_pos(Vector2(1, y)) + off, "1.0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  538. }
  539. {
  540. // Y axis
  541. float m0 = curve.get_min_value();
  542. float m1 = 0.5 * (curve.get_min_value() + curve.get_max_value());
  543. float m2 = curve.get_max_value();
  544. Vector2 off(1, -1);
  545. draw_string(font, get_view_pos(Vector2(0, m0)) + off, String::num(m0, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  546. draw_string(font, get_view_pos(Vector2(0, m1)) + off, String::num(m1, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  547. draw_string(font, get_view_pos(Vector2(0, m2)) + off, String::num(m2, 3), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
  548. }
  549. // Draw tangents for current point
  550. if (_selected_point >= 0) {
  551. const Color tangent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
  552. int i = _selected_point;
  553. Vector2 pos = curve.get_point_position(i);
  554. if (i != 0) {
  555. Vector2 control_pos = get_tangent_view_pos(i, TANGENT_LEFT);
  556. draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE));
  557. draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color);
  558. }
  559. if (i != curve.get_point_count() - 1) {
  560. Vector2 control_pos = get_tangent_view_pos(i, TANGENT_RIGHT);
  561. draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE));
  562. draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color);
  563. }
  564. }
  565. // Draw lines
  566. draw_set_transform_matrix(_world_to_view);
  567. const Color line_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
  568. const Color edge_line_color = get_theme_color(SNAME("highlight_color"), SNAME("Editor"));
  569. CanvasItemPlotCurve plot_func(*this, line_color, edge_line_color);
  570. plot_curve_accurate(curve, 4.f / view_size.x, plot_func);
  571. // Draw points
  572. draw_set_transform_matrix(Transform2D());
  573. const Color point_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
  574. const Color selected_point_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
  575. for (int i = 0; i < curve.get_point_count(); ++i) {
  576. Vector2 pos = curve.get_point_position(i);
  577. draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(3 * EDSCALE)), i == _selected_point ? selected_point_color : point_color);
  578. // TODO Circles are prettier. Needs a fix! Or a texture
  579. //draw_circle(pos, 2, point_color);
  580. }
  581. // Hover
  582. if (_hover_point != -1) {
  583. const Color hover_color = line_color;
  584. Vector2 pos = curve.get_point_position(_hover_point);
  585. draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(_hover_radius * EDSCALE)), hover_color, false, Math::round(EDSCALE));
  586. }
  587. // Help text
  588. float width = view_size.x - 60 * EDSCALE;
  589. if (_selected_point > 0 && _selected_point + 1 < curve.get_point_count()) {
  590. text_color.a *= 0.4;
  591. draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Hold Shift to edit tangents individually"), HORIZONTAL_ALIGNMENT_LEFT, width, -1, font_size, text_color);
  592. } else if (curve.get_point_count() == 0) {
  593. text_color.a *= 0.4;
  594. draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Right click to add point"), HORIZONTAL_ALIGNMENT_LEFT, width, -1, font_size, text_color);
  595. }
  596. }
  597. //---------------
  598. bool EditorInspectorPluginCurve::can_handle(Object *p_object) {
  599. return Object::cast_to<Curve>(p_object) != nullptr;
  600. }
  601. void EditorInspectorPluginCurve::parse_begin(Object *p_object) {
  602. Curve *curve = Object::cast_to<Curve>(p_object);
  603. ERR_FAIL_COND(!curve);
  604. Ref<Curve> c(curve);
  605. CurveEditor *editor = memnew(CurveEditor);
  606. editor->set_curve(curve);
  607. add_custom_control(editor);
  608. }
  609. CurveEditorPlugin::CurveEditorPlugin() {
  610. Ref<EditorInspectorPluginCurve> curve_plugin;
  611. curve_plugin.instantiate();
  612. EditorInspector::add_inspector_plugin(curve_plugin);
  613. get_editor_interface()->get_resource_previewer()->add_preview_generator(memnew(CurvePreviewGenerator));
  614. }
  615. //-----------------------------------
  616. // Preview generator
  617. bool CurvePreviewGenerator::handles(const String &p_type) const {
  618. return p_type == "Curve";
  619. }
  620. Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size) const {
  621. Ref<Curve> curve_ref = p_from;
  622. ERR_FAIL_COND_V_MSG(curve_ref.is_null(), Ref<Texture2D>(), "It's not a reference to a valid Resource object.");
  623. Curve &curve = **curve_ref;
  624. // FIXME: Should be ported to use p_size as done in b2633a97
  625. int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
  626. thumbnail_size *= EDSCALE;
  627. Ref<Image> img_ref;
  628. img_ref.instantiate();
  629. Image &im = **img_ref;
  630. im.create(thumbnail_size, thumbnail_size / 2, false, Image::FORMAT_RGBA8);
  631. Color bg_color(0.1, 0.1, 0.1, 1.0);
  632. im.fill(bg_color);
  633. Color line_color(0.8, 0.8, 0.8, 1.0);
  634. float range_y = curve.get_max_value() - curve.get_min_value();
  635. int prev_y = 0;
  636. for (int x = 0; x < im.get_width(); ++x) {
  637. float t = static_cast<float>(x) / im.get_width();
  638. float v = (curve.sample_baked(t) - curve.get_min_value()) / range_y;
  639. int y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height());
  640. // Plot point
  641. if (y >= 0 && y < im.get_height()) {
  642. im.set_pixel(x, y, line_color);
  643. }
  644. // Plot vertical line to fix discontinuity (not 100% correct but enough for a preview)
  645. if (x != 0 && Math::abs(y - prev_y) > 1) {
  646. int y0, y1;
  647. if (y < prev_y) {
  648. y0 = y;
  649. y1 = prev_y;
  650. } else {
  651. y0 = prev_y;
  652. y1 = y;
  653. }
  654. for (int ly = y0; ly < y1; ++ly) {
  655. im.set_pixel(x, ly, line_color);
  656. }
  657. }
  658. prev_y = y;
  659. }
  660. return ImageTexture::create_from_image(img_ref);
  661. }