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