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