curve_editor_plugin.cpp 28 KB

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