diff --git a/editor/plugins/gradient_editor.cpp b/editor/plugins/gradient_editor.cpp deleted file mode 100644 index bcc7d2a0046..00000000000 --- a/editor/plugins/gradient_editor.cpp +++ /dev/null @@ -1,476 +0,0 @@ -/**************************************************************************/ -/* gradient_editor.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "gradient_editor.h" - -#include "core/os/keyboard.h" -#include "editor/editor_node.h" -#include "editor/editor_scale.h" -#include "editor/editor_string_names.h" -#include "editor/editor_undo_redo_manager.h" -#include "scene/resources/gradient_texture.h" - -void GradientEditor::set_gradient(const Ref &p_gradient) { - gradient = p_gradient; - connect("ramp_changed", callable_mp(this, &GradientEditor::_ramp_changed)); - gradient->connect_changed(callable_mp(this, &GradientEditor::_gradient_changed)); - set_points(gradient->get_points()); - set_interpolation_mode(gradient->get_interpolation_mode()); - set_interpolation_color_space(gradient->get_interpolation_color_space()); -} - -void GradientEditor::reverse_gradient() { - gradient->reverse(); - set_points(gradient->get_points()); - emit_signal(SNAME("ramp_changed")); - queue_redraw(); -} - -int GradientEditor::_get_point_from_pos(int x) { - int result = -1; - int total_w = get_size().width - get_size().height - draw_spacing - handle_width; - float min_distance = 1e20; - for (int i = 0; i < points.size(); i++) { - // Check if we clicked at point. - float distance = ABS(x - points[i].offset * total_w); - float min = handle_width * 0.85; // Allow the mouse to be more than half a handle width away for ease of grabbing. - if (distance <= min && distance < min_distance) { - result = i; - min_distance = distance; - } - } - return result; -} - -void GradientEditor::_show_color_picker() { - if (grabbed == -1) { - return; - } - picker->set_pick_color(points[grabbed].color); - Size2 minsize = popup->get_contents_minimum_size(); - bool show_above = false; - if (get_global_position().y + get_size().y + minsize.y > get_viewport_rect().size.y) { - show_above = true; - } - if (show_above) { - popup->set_position(get_screen_position() - Vector2(0, minsize.y)); - } else { - popup->set_position(get_screen_position() + Vector2(0, get_size().y)); - } - popup->popup(); -} - -void GradientEditor::_gradient_changed() { - if (editing) { - return; - } - - editing = true; - Vector grad_points = gradient->get_points(); - set_points(grad_points); - set_interpolation_mode(gradient->get_interpolation_mode()); - set_interpolation_color_space(gradient->get_interpolation_color_space()); - queue_redraw(); - editing = false; -} - -void GradientEditor::_ramp_changed() { - editing = true; - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Gradient Edited"), UndoRedo::MERGE_ENDS); - undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets()); - undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors()); - undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode()); - undo_redo->add_do_method(gradient.ptr(), "set_interpolation_color_space", get_interpolation_color_space()); - undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets()); - undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors()); - undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode()); - undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_color_space", gradient->get_interpolation_color_space()); - undo_redo->commit_action(); - editing = false; -} - -void GradientEditor::_color_changed(const Color &p_color) { - if (grabbed == -1) { - return; - } - points.write[grabbed].color = p_color; - queue_redraw(); - emit_signal(SNAME("ramp_changed")); -} - -void GradientEditor::set_ramp(const Vector &p_offsets, const Vector &p_colors) { - ERR_FAIL_COND(p_offsets.size() != p_colors.size()); - points.clear(); - for (int i = 0; i < p_offsets.size(); i++) { - Gradient::Point p; - p.offset = p_offsets[i]; - p.color = p_colors[i]; - points.push_back(p); - } - - points.sort(); - queue_redraw(); -} - -Vector GradientEditor::get_offsets() const { - Vector ret; - for (int i = 0; i < points.size(); i++) { - ret.push_back(points[i].offset); - } - return ret; -} - -Vector GradientEditor::get_colors() const { - Vector ret; - for (int i = 0; i < points.size(); i++) { - ret.push_back(points[i].color); - } - return ret; -} - -void GradientEditor::set_points(Vector &p_points) { - if (points.size() != p_points.size()) { - grabbed = -1; - } - points.clear(); - points = p_points; - points.sort(); -} - -Vector &GradientEditor::get_points() { - return points; -} - -void GradientEditor::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { - interpolation_mode = p_interp_mode; -} - -Gradient::InterpolationMode GradientEditor::get_interpolation_mode() { - return interpolation_mode; -} - -void GradientEditor::set_interpolation_color_space(Gradient::ColorSpace p_color_space) { - interpolation_color_space = p_color_space; -} - -Gradient::ColorSpace GradientEditor::get_interpolation_color_space() { - return interpolation_color_space; -} - -ColorPicker *GradientEditor::get_picker() { - return picker; -} - -PopupPanel *GradientEditor::get_popup() { - return popup; -} - -Size2 GradientEditor::get_minimum_size() const { - return Size2(0, 60) * EDSCALE; -} - -void GradientEditor::gui_input(const Ref &p_event) { - ERR_FAIL_COND(p_event.is_null()); - - Ref k = p_event; - - if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && grabbed != -1) { - points.remove_at(grabbed); - grabbed = -1; - grabbing = false; - queue_redraw(); - emit_signal(SNAME("ramp_changed")); - accept_event(); - } - - Ref mb = p_event; - - if (mb.is_valid() && mb->is_pressed()) { - float adjusted_mb_x = mb->get_position().x - handle_width / 2; - - // Delete point on right click. - if (mb->get_button_index() == MouseButton::RIGHT) { - grabbed = _get_point_from_pos(adjusted_mb_x); - if (grabbed != -1) { - points.remove_at(grabbed); - grabbed = -1; - grabbing = false; - queue_redraw(); - emit_signal(SNAME("ramp_changed")); - accept_event(); - } - } - - // Hold Alt key to duplicate selected color. - if (mb->get_button_index() == MouseButton::LEFT && mb->is_alt_pressed()) { - grabbed = _get_point_from_pos(adjusted_mb_x); - - if (grabbed != -1) { - int total_w = get_size().width - get_size().height - draw_spacing - handle_width; - Gradient::Point new_point = points[grabbed]; - new_point.offset = CLAMP(adjusted_mb_x / float(total_w), 0, 1); - points.push_back(new_point); - points.sort(); - for (int i = 0; i < points.size(); ++i) { - if (points[i].offset == new_point.offset) { - grabbed = i; - break; - } - } - - emit_signal(SNAME("ramp_changed")); - queue_redraw(); - } - } - - // Select. - if (mb->get_button_index() == MouseButton::LEFT) { - queue_redraw(); - int total_w = get_size().width - get_size().height - draw_spacing - handle_width; - - // Check if color selector was clicked or ramp was double-clicked. - if (adjusted_mb_x > total_w + draw_spacing) { - if (!mb->is_double_click()) { - _show_color_picker(); - } - return; - } else if (mb->is_double_click()) { - grabbed = _get_point_from_pos(adjusted_mb_x); - _show_color_picker(); - accept_event(); - return; - } - - grabbing = true; - grabbed = _get_point_from_pos(adjusted_mb_x); - - // Grab or select. - if (grabbed != -1) { - return; - } - - // Insert point. - Gradient::Point new_point; - new_point.offset = CLAMP(adjusted_mb_x / float(total_w), 0, 1); - new_point.color = gradient->get_color_at_offset(new_point.offset); - - points.push_back(new_point); - points.sort(); - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == new_point.offset) { - grabbed = i; - break; - } - } - - emit_signal(SNAME("ramp_changed")); - } - } - - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { - if (grabbing) { - grabbing = false; - emit_signal(SNAME("ramp_changed")); - } - queue_redraw(); - } - - Ref mm = p_event; - - if (mm.is_valid() && grabbing) { - float adjusted_mm_x = mm->get_position().x - handle_width / 2; - int total_w = get_size().width - get_size().height - draw_spacing - handle_width; - float newofs = CLAMP(adjusted_mm_x / float(total_w), 0, 1); - - // Snap to "round" coordinates if holding Ctrl. - // Be more precise if holding Shift as well. - if (mm->is_command_or_control_pressed()) { - newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1); - } else if (mm->is_shift_pressed()) { - // Snap to nearest point if holding just Shift. - const float snap_threshold = 0.03; - float smallest_ofs = snap_threshold; - bool found = false; - int nearest_point = 0; - for (int i = 0; i < points.size(); ++i) { - if (i != grabbed) { - float temp_ofs = ABS(points[i].offset - newofs); - if (temp_ofs < smallest_ofs) { - smallest_ofs = temp_ofs; - nearest_point = i; - if (found) { - break; - } - found = true; - } - } - } - if (found) { - if (points[nearest_point].offset < newofs) { - newofs = points[nearest_point].offset + 0.00001; - } else { - newofs = points[nearest_point].offset - 0.00001; - } - newofs = CLAMP(newofs, 0, 1); - } - } - - bool valid = true; - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == newofs && i != grabbed) { - valid = false; - break; - } - } - - if (!valid || grabbed == -1) { - return; - } - points.write[grabbed].offset = newofs; - - points.sort(); - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == newofs) { - grabbed = i; - break; - } - } - - emit_signal(SNAME("ramp_changed")); - - queue_redraw(); - } -} - -void GradientEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - if (!picker->is_connected("color_changed", callable_mp(this, &GradientEditor::_color_changed))) { - picker->connect("color_changed", callable_mp(this, &GradientEditor::_color_changed)); - } - [[fallthrough]]; - } - case NOTIFICATION_THEME_CHANGED: { - draw_spacing = BASE_SPACING * get_theme_default_base_scale(); - handle_width = BASE_HANDLE_WIDTH * get_theme_default_base_scale(); - } break; - - case NOTIFICATION_DRAW: { - int w = get_size().x; - int h = get_size().y; - - if (w == 0 || h == 0) { - return; // Safety check. We have division by 'h'. And in any case there is nothing to draw with such size. - } - - int total_w = get_size().width - get_size().height - draw_spacing - handle_width; - - // Draw checker pattern for ramp. - draw_texture_rect(get_editor_theme_icon(SNAME("GuiMiniCheckerboard")), Rect2(handle_width / 2, 0, total_w, h), true); - - // Draw color ramp. - gradient_cache->set_points(points); - gradient_cache->set_interpolation_mode(interpolation_mode); - gradient_cache->set_interpolation_color_space(interpolation_color_space); - preview_texture->set_gradient(gradient_cache); - draw_texture_rect(preview_texture, Rect2(handle_width / 2, 0, total_w, h)); - - // Draw borders around color ramp if in focus. - if (has_focus()) { - draw_rect(Rect2(handle_width / 2, 0, total_w, h), Color(1, 1, 1, 0.9), false, 1); - } - - // Draw point markers. - for (int i = 0; i < points.size(); i++) { - Color col = points[i].color.get_v() > 0.5 ? Color(0, 0, 0) : Color(1, 1, 1); - col.a = 0.9; - - draw_line(Vector2(points[i].offset * total_w + handle_width / 2, 0), Vector2(points[i].offset * total_w + handle_width / 2, h / 2), col); - Rect2 rect = Rect2(points[i].offset * total_w, h / 2, handle_width, h / 2); - draw_rect(rect, points[i].color, true); - draw_rect(rect, col, false, 1); - if (grabbed == i) { - const Color focus_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); - rect = rect.grow(-1); - if (has_focus()) { - draw_rect(rect, focus_color, false, 1); - } else { - draw_rect(rect, focus_color.darkened(0.4), false, 1); - } - - rect = rect.grow(-1); - draw_rect(rect, col, false, 1); - } - } - - // Draw "button" for color selector. - int button_offset = total_w + handle_width + draw_spacing; - draw_texture_rect(get_editor_theme_icon(SNAME("GuiMiniCheckerboard")), Rect2(button_offset, 0, h, h), true); - if (grabbed != -1) { - // Draw with selection color. - draw_rect(Rect2(button_offset, 0, h, h), points[grabbed].color); - } else { - // If no color selected draw gray color with 'X' on top. - draw_rect(Rect2(button_offset, 0, h, h), Color(0.5, 0.5, 0.5, 1)); - draw_line(Vector2(button_offset, 0), Vector2(button_offset + h, h), Color(1, 1, 1, 0.6)); - draw_line(Vector2(button_offset, h), Vector2(button_offset + h, 0), Color(1, 1, 1, 0.6)); - } - } break; - - case NOTIFICATION_VISIBILITY_CHANGED: { - if (!is_visible()) { - grabbing = false; - } - } break; - } -} - -void GradientEditor::_bind_methods() { - ADD_SIGNAL(MethodInfo("ramp_changed")); -} - -GradientEditor::GradientEditor() { - set_focus_mode(FOCUS_ALL); - - popup = memnew(PopupPanel); - picker = memnew(ColorPicker); - popup->add_child(picker); - popup->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(GradientEditor::get_picker())); - - gradient_cache.instantiate(); - preview_texture.instantiate(); - - preview_texture->set_width(1024); - add_child(popup, false, INTERNAL_MODE_FRONT); -} - -GradientEditor::~GradientEditor() { -} diff --git a/editor/plugins/gradient_editor.h b/editor/plugins/gradient_editor.h deleted file mode 100644 index 9ff39b2213c..00000000000 --- a/editor/plugins/gradient_editor.h +++ /dev/null @@ -1,102 +0,0 @@ -/**************************************************************************/ -/* gradient_editor.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef GRADIENT_EDITOR_H -#define GRADIENT_EDITOR_H - -#include "scene/gui/color_picker.h" -#include "scene/gui/popup.h" -#include "scene/resources/gradient.h" - -class GradientTexture1D; - -class GradientEditor : public Control { - GDCLASS(GradientEditor, Control); - - PopupPanel *popup = nullptr; - ColorPicker *picker = nullptr; - - bool grabbing = false; - int grabbed = -1; - Vector points; - Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR; - Gradient::ColorSpace interpolation_color_space = Gradient::GRADIENT_COLOR_SPACE_SRGB; - - bool editing = false; - Ref gradient; - Ref gradient_cache; - Ref preview_texture; - - // Make sure to use the scaled value below. - const int BASE_SPACING = 3; - const int BASE_HANDLE_WIDTH = 8; - - int draw_spacing = BASE_SPACING; - int handle_width = BASE_HANDLE_WIDTH; - - void _gradient_changed(); - void _ramp_changed(); - void _color_changed(const Color &p_color); - - int _get_point_from_pos(int x); - void _show_color_picker(); - -protected: - virtual void gui_input(const Ref &p_event) override; - void _notification(int p_what); - static void _bind_methods(); - -public: - void set_gradient(const Ref &p_gradient); - void reverse_gradient(); - - void set_ramp(const Vector &p_offsets, const Vector &p_colors); - - Vector get_offsets() const; - Vector get_colors() const; - void set_points(Vector &p_points); - Vector &get_points(); - - void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode); - Gradient::InterpolationMode get_interpolation_mode(); - - void set_interpolation_color_space(Gradient::ColorSpace p_color_space); - Gradient::ColorSpace get_interpolation_color_space(); - - ColorPicker *get_picker(); - PopupPanel *get_popup(); - - virtual Size2 get_minimum_size() const override; - - GradientEditor(); - virtual ~GradientEditor(); -}; - -#endif // GRADIENT_EDITOR_H diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index c85e19fda6e..128b52e7ec6 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -31,27 +31,624 @@ #include "gradient_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "core/os/keyboard.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_spin_slider.h" #include "node_3d_editor_plugin.h" +#include "scene/gui/color_picker.h" +#include "scene/gui/flow_container.h" +#include "scene/gui/popup.h" +#include "scene/gui/separator.h" +#include "scene/resources/gradient_texture.h" -void GradientReverseButton::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_DRAW: { - Ref icon = get_editor_theme_icon(SNAME("ReverseGradient")); - if (is_pressed()) { - draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height()), false, get_theme_color(SNAME("icon_pressed_color"), SNAME("Button"))); +int GradientEdit::_get_point_at(int p_xpos) const { + int result = -1; + int total_w = _get_gradient_rect_width(); + float min_distance = handle_width * 0.8; // Allow the cursor to be more than half a handle width away for ease of use. + for (int i = 0; i < gradient->get_point_count(); i++) { + // Ignore points outside of [0, 1]. + if (gradient->get_offset(i) < 0) { + continue; + } else if (gradient->get_offset(i) > 1) { + break; + } + // Check if we clicked at point. + float distance = ABS(p_xpos - gradient->get_offset(i) * total_w); + if (distance < min_distance) { + result = i; + min_distance = distance; + } + } + return result; +} + +int GradientEdit::_predict_insertion_index(float p_offset) { + int result = 0; + while (result < gradient->get_point_count() && gradient->get_offset(result) < p_offset) { + result++; + } + return result; +} + +int GradientEdit::_get_gradient_rect_width() const { + return get_size().width - get_size().height - draw_spacing - handle_width; +} + +void GradientEdit::_show_color_picker() { + if (selected_index == -1) { + return; + } + + picker->set_pick_color(gradient->get_color(selected_index)); + Size2 minsize = popup->get_contents_minimum_size(); + float viewport_height = get_viewport_rect().size.y; + + // Determine in which direction to show the popup. By default popup below. + // But if the popup doesn't fit below and the Gradient Editor is in the bottom half of the viewport, show above. + bool show_above = get_global_position().y + get_size().y + minsize.y > viewport_height && get_global_position().y * 2 + get_size().y > viewport_height; + + float v_offset = show_above ? -minsize.y : get_size().y; + popup->set_position(get_screen_position() + Vector2(0, v_offset)); + popup->popup(); +} + +void GradientEdit::_color_changed(const Color &p_color) { + set_color(selected_index, p_color); +} + +void GradientEdit::set_gradient(const Ref &p_gradient) { + gradient = p_gradient; + gradient->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); +} + +const Ref &GradientEdit::get_gradient() const { + return gradient; +} + +void GradientEdit::add_point(float p_offset, const Color &p_color) { + int new_idx = _predict_insertion_index(p_offset); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Gradient Point")); + undo_redo->add_do_method(*gradient, "add_point", p_offset, p_color); + undo_redo->add_do_method(this, "set_selected_index", new_idx); + undo_redo->add_undo_method(*gradient, "remove_point", new_idx); + undo_redo->add_undo_method(this, "set_selected_index", -1); + undo_redo->commit_action(); +} + +void GradientEdit::remove_point(int p_index) { + ERR_FAIL_INDEX_MSG(p_index, gradient->get_point_count(), "Gradient point is out of bounds."); + + if (gradient->get_point_count() <= 1) { + return; + } + + // If the point is removed while it's being moved, remember its old offset. + float old_offset = (grabbing == GRAB_MOVE) ? pre_grab_offset : gradient->get_offset(p_index); + Color old_color = gradient->get_color(p_index); + + int new_selected_index = selected_index; + // Reselect the old selected point if it's not the deleted one. + if (new_selected_index > p_index) { + new_selected_index -= 1; + } else if (new_selected_index == p_index) { + new_selected_index = -1; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Remove Gradient Point")); + undo_redo->add_do_method(*gradient, "remove_point", p_index); + undo_redo->add_do_method(this, "set_selected_index", new_selected_index); + undo_redo->add_undo_method(*gradient, "add_point", old_offset, old_color); + undo_redo->add_undo_method(this, "set_selected_index", selected_index); + undo_redo->commit_action(); +} + +void GradientEdit::set_offset(int p_index, float p_offset) { + ERR_FAIL_INDEX_MSG(p_index, gradient->get_point_count(), "Gradient point is out of bounds."); + + // Use pre_grab_offset to determine things for the undo/redo. + if (Math::is_equal_approx(pre_grab_offset, p_offset)) { + return; + } + + int new_idx = _predict_insertion_index(p_offset); + + gradient->set_offset(p_index, pre_grab_offset); // Pretend the point started from its old place. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Move Gradient Point")); + undo_redo->add_do_method(*gradient, "set_offset", pre_grab_index, p_offset); + undo_redo->add_do_method(this, "set_selected_index", new_idx); + undo_redo->add_undo_method(*gradient, "set_offset", new_idx, pre_grab_offset); + undo_redo->add_undo_method(this, "set_selected_index", pre_grab_index); + undo_redo->commit_action(); + queue_redraw(); +} + +void GradientEdit::set_color(int p_index, const Color &p_color) { + ERR_FAIL_INDEX_MSG(p_index, gradient->get_point_count(), "Gradient point is out of bounds."); + + Color old_color = gradient->get_color(p_index); + if (old_color == p_color) { + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Recolor Gradient Point"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(*gradient, "set_color", p_index, p_color); + undo_redo->add_undo_method(*gradient, "set_color", p_index, old_color); + undo_redo->commit_action(); + queue_redraw(); +} + +void GradientEdit::reverse_gradient() { + int new_selected_idx = (selected_index == -1) ? -1 : (gradient->get_point_count() - selected_index - 1); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Reverse Gradient"), UndoRedo::MERGE_DISABLE, *gradient); + undo_redo->add_do_method(*gradient, "reverse"); + undo_redo->add_do_method(this, "set_selected_index", new_selected_idx); + undo_redo->add_undo_method(*gradient, "reverse"); + undo_redo->add_undo_method(this, "set_selected_index", selected_index); + undo_redo->commit_action(); +} + +void GradientEdit::set_selected_index(int p_index) { + selected_index = p_index; + queue_redraw(); +} + +void GradientEdit::set_snap_enabled(bool p_enabled) { + snap_enabled = p_enabled; + queue_redraw(); + if (gradient.is_valid()) { + if (snap_enabled) { + gradient->set_meta(SNAME("_snap_enabled"), true); + } else { + gradient->remove_meta(SNAME("_snap_enabled")); + } + } +} + +void GradientEdit::set_snap_count(int p_count) { + snap_count = p_count; + queue_redraw(); + if (gradient.is_valid()) { + if (snap_count != GradientEditor::DEFAULT_SNAP) { + gradient->set_meta(SNAME("_snap_count"), snap_count); + } else { + gradient->remove_meta(SNAME("_snap_count")); + } + } +} + +ColorPicker *GradientEdit::get_picker() const { + return picker; +} + +PopupPanel *GradientEdit::get_popup() const { + return popup; +} + +void GradientEdit::gui_input(const Ref &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + Ref k = p_event; + + if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && selected_index != -1) { + if (grabbing == GRAB_ADD) { + gradient->remove_point(selected_index); // Point is temporary, so remove directly from gradient. + set_selected_index(-1); + } else { + remove_point(selected_index); + } + grabbing = GRAB_NONE; + hovered_index = -1; + accept_event(); + } + + Ref mb = p_event; + + if (mb.is_valid() && mb->is_pressed()) { + float adjusted_mb_x = mb->get_position().x - handle_width / 2; + bool should_snap = snap_enabled || mb->is_ctrl_pressed(); + + // Delete point or move it to old position on middle or right click. + if (mb->get_button_index() == MouseButton::RIGHT || mb->get_button_index() == MouseButton::MIDDLE) { + if (grabbing == GRAB_MOVE && mb->get_button_index() == MouseButton::RIGHT) { + gradient->set_offset(selected_index, pre_grab_offset); + set_selected_index(pre_grab_index); } else { - draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height())); + int point_to_remove = _get_point_at(adjusted_mb_x); + if (point_to_remove == -1) { + set_selected_index(-1); // Nothing on the place of the click, just deselect any handle. + } else { + if (grabbing == GRAB_ADD) { + gradient->remove_point(point_to_remove); // Point is temporary, so remove directly from gradient. + set_selected_index(-1); + } else { + remove_point(point_to_remove); + } + hovered_index = -1; + } + } + grabbing = GRAB_NONE; + accept_event(); + } + + // Select point. + if (mb->get_button_index() == MouseButton::LEFT) { + int total_w = _get_gradient_rect_width(); + + // Check if color picker was clicked or gradient was double-clicked. + if (adjusted_mb_x > total_w + draw_spacing) { + if (!mb->is_double_click()) { + _show_color_picker(); + } + accept_event(); + return; + } else if (mb->is_double_click()) { + set_selected_index(_get_point_at(adjusted_mb_x)); + _show_color_picker(); + accept_event(); + return; + } + + if (grabbing == GRAB_NONE) { + set_selected_index(_get_point_at(adjusted_mb_x)); + } + + if (selected_index != -1 && !mb->is_alt_pressed()) { + // An existing point was grabbed. + grabbing = GRAB_MOVE; + pre_grab_offset = gradient->get_offset(selected_index); + pre_grab_index = selected_index; + } else if (grabbing == GRAB_NONE) { + // Adding a new point. Insert a temporary point for the user to adjust, so it's not in the undo/redo. + float new_offset = CLAMP(adjusted_mb_x / float(total_w), 0, 1); + if (should_snap) { + new_offset = Math::snapped(new_offset, 1.0 / snap_count); + } + + for (int i = 0; i < gradient->get_point_count(); i++) { + if (gradient->get_offset(i) == new_offset) { + // If another point with the same offset is found, then + // tweak it if Alt was pressed, otherwise something has gone wrong, so stop the operation. + if (mb->is_alt_pressed()) { + new_offset = MIN(gradient->get_offset(i) + 0.00001, 1); + } else { + return; + } + } + } + + Color new_color = gradient->get_color_at_offset(new_offset); + if (mb->is_alt_pressed()) { + // Alt + Click on a point duplicates it. So copy its color. + int point_to_copy = _get_point_at(adjusted_mb_x); + if (point_to_copy != -1) { + new_color = gradient->get_color(point_to_copy); + } + } + // Add a temporary point for the user to adjust before adding it permanently. + gradient->add_point(new_offset, new_color); + set_selected_index(_predict_insertion_index(new_offset)); + grabbing = GRAB_ADD; + } + } + } + + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { + if (grabbing == GRAB_MOVE) { + // Finish moving a point. + set_offset(selected_index, gradient->get_offset(selected_index)); + grabbing = GRAB_NONE; + } else if (grabbing == GRAB_ADD) { + // Finish inserting a new point. Remove the temporary point and insert the permanent one in its place. + float new_offset = gradient->get_offset(selected_index); + Color new_color = gradient->get_color(selected_index); + gradient->remove_point(selected_index); + add_point(new_offset, new_color); + grabbing = GRAB_NONE; + } + } + + Ref mm = p_event; + + if (mm.is_valid()) { + int total_w = _get_gradient_rect_width(); + float adjusted_mm_x = mm->get_position().x - handle_width / 2; + bool should_snap = snap_enabled || mm->is_ctrl_pressed(); + + // Hovering logic. + if (grabbing == GRAB_NONE) { + int nearest_point = _get_point_at(adjusted_mm_x); + if (hovered_index != nearest_point) { + hovered_index = nearest_point; + queue_redraw(); + } + return; + } else { + hovered_index = -1; + } + + // Grabbing logic. + float new_offset = CLAMP(adjusted_mm_x / float(total_w), 0, 1); + + // Give the ability to snap right next to a point when using Shift. + if (mm->is_shift_pressed()) { + float smallest_offset = should_snap ? (0.5 / snap_count) : 0.01; + int nearest_idx = -1; + // Only check the two adjacent points to find which one is the nearest. + if (selected_index > 0) { + float temp_offset = ABS(gradient->get_offset(selected_index - 1) - new_offset); + if (temp_offset < smallest_offset) { + smallest_offset = temp_offset; + nearest_idx = selected_index - 1; + } + } + if (selected_index < gradient->get_point_count() - 1) { + float temp_offset = ABS(gradient->get_offset(selected_index + 1) - new_offset); + if (temp_offset < smallest_offset) { + smallest_offset = temp_offset; + nearest_idx = selected_index + 1; + } + } + if (nearest_idx != -1) { + // Snap to the point with a slight adjustment to the left or right. + float adjustment = gradient->get_offset(nearest_idx) < new_offset ? 0.00001 : -0.00001; + new_offset = CLAMP(gradient->get_offset(nearest_idx) + adjustment, 0, 1); + } else if (should_snap) { + new_offset = Math::snapped(new_offset, 1.0 / snap_count); + } + } else if (should_snap) { + // Shift is not pressed, so snap fully without adjustments. + new_offset = Math::snapped(new_offset, 1.0 / snap_count); + } + + // Don't move the point if its new offset would be the same as another point's. + for (int i = 0; i < gradient->get_point_count(); i++) { + if (gradient->get_offset(i) == new_offset && i != selected_index) { + return; + } + } + + if (selected_index == -1) { + return; + } + + // We want to only save this action for undo/redo when released, so don't use set_offset() yet. + gradient->set_offset(selected_index, new_offset); + + // Update selected_index after the gradient updates its indices, so you keep holding the same color. + for (int i = 0; i < gradient->get_point_count(); i++) { + if (gradient->get_offset(i) == new_offset) { + set_selected_index(i); + break; + } + } + } +} + +void GradientEdit::_redraw() { + int w = get_size().x; + int h = get_size().y - draw_spacing; // A bit of spacing below the gradient too. + + if (w == 0 || h == 0) { + return; // Safety check as there is nothing to draw with such size. + } + + int total_w = _get_gradient_rect_width(); + int half_handle_width = handle_width * 0.5; + + // Draw gradient. + draw_texture_rect(get_editor_theme_icon(SNAME("GuiMiniCheckerboard")), Rect2(half_handle_width, 0, total_w, h), true); + preview_texture->set_gradient(gradient); + draw_texture_rect(preview_texture, Rect2(half_handle_width, 0, total_w, h)); + + // Draw vertical snap lines. + if (snap_enabled || (Input::get_singleton()->is_key_pressed(Key::CTRL) && grabbing != GRAB_NONE)) { + const Color line_color = Color(0.5, 0.5, 0.5, 0.5); + for (int idx = 1; idx < snap_count; idx++) { + float offset_x = idx * total_w / (float)snap_count + half_handle_width; + draw_line(Point2(offset_x, 0), Point2(offset_x, h), line_color); + } + } + + // Draw handles. + for (int i = 0; i < gradient->get_point_count(); i++) { + // Only draw handles for points in [0, 1]. If there are points before or after, draw a little indicator. + if (gradient->get_offset(i) < 0.0) { + continue; + } else if (gradient->get_offset(i) > 1.0) { + break; + } + // White or black handle color, to contrast with the selected color's brightness. + // Also consider the fact that the color may be translucent. + // The checkerboard pattern in the background has an average luminance of 0.75. + Color inside_col = gradient->get_color(i); + Color border_col = Math::lerp(0.75f, inside_col.get_luminance(), inside_col.a) > 0.455 ? Color(0, 0, 0) : Color(1, 1, 1); + + int handle_thickness = MAX(1, Math::round(EDSCALE)); + float handle_x_pos = gradient->get_offset(i) * total_w + half_handle_width; + float handle_start_x = handle_x_pos - half_handle_width; + Rect2 rect = Rect2(handle_start_x, h / 2, handle_width, h / 2); + + if (inside_col.a < 1) { + // If the color is translucent, draw a little opaque rectangle at the bottom to more easily see it. + draw_texture_rect(get_editor_theme_icon(SNAME("GuiMiniCheckerboard")), rect, true); + draw_rect(rect, inside_col, true); + Color inside_col_opaque = inside_col; + inside_col_opaque.a = 1.0; + draw_rect(Rect2(handle_start_x + handle_thickness / 2.0, h * 0.9 - handle_thickness / 2.0, handle_width - handle_thickness, h * 0.1), inside_col_opaque, true); + } else { + draw_rect(rect, inside_col, true); + } + + if (selected_index == i) { + // Handle is selected. + draw_rect(rect, border_col, false, handle_thickness); + draw_line(Vector2(handle_x_pos, 0), Vector2(handle_x_pos, h / 2 - handle_thickness), border_col, handle_thickness); + if (inside_col.a < 1) { + draw_line(Vector2(handle_start_x + handle_thickness / 2.0, h * 0.9 - handle_thickness), Vector2(handle_start_x + handle_width - handle_thickness / 2.0, h * 0.9 - handle_thickness), border_col, handle_thickness); + } + rect = rect.grow(-handle_thickness); + const Color focus_col = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); + draw_rect(rect, has_focus() ? focus_col : focus_col.darkened(0.4), false, handle_thickness); + rect = rect.grow(-handle_thickness); + draw_rect(rect, border_col, false, handle_thickness); + } else { + // Handle isn't selected. + border_col.a = 0.9; + draw_rect(rect, border_col, false, handle_thickness); + draw_line(Vector2(handle_x_pos, 0), Vector2(handle_x_pos, h / 2 - handle_thickness), border_col, handle_thickness); + if (inside_col.a < 1) { + draw_line(Vector2(handle_start_x + handle_thickness / 2.0, h * 0.9 - handle_thickness), Vector2(handle_start_x + handle_width - handle_thickness / 2.0, h * 0.9 - handle_thickness), border_col, handle_thickness); + } + if (hovered_index == i) { + // Draw a subtle translucent rect inside the handle if it's being hovered. + rect = rect.grow(-handle_thickness); + border_col.a = 0.54; + draw_rect(rect, border_col, false, handle_thickness); + } + } + } + + // Draw "button" for color selector. + int button_offset = total_w + handle_width + draw_spacing; + if (selected_index != -1) { + Color grabbed_col = gradient->get_color(selected_index); + if (grabbed_col.a < 1) { + draw_texture_rect(get_editor_theme_icon(SNAME("GuiMiniCheckerboard")), Rect2(button_offset, 0, h, h), true); + } + draw_rect(Rect2(button_offset, 0, h, h), grabbed_col); + if (grabbed_col.r > 1 || grabbed_col.g > 1 || grabbed_col.b > 1) { + // Draw an indicator to denote that the currently selected color is "overbright". + draw_texture(get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), Point2(button_offset, 0)); + } + } else { + // If no color is selected, draw grey color with 'X' on top. + draw_rect(Rect2(button_offset, 0, h, h), Color(0.5, 0.5, 0.5, 1)); + draw_line(Vector2(button_offset, 0), Vector2(button_offset + h, h), Color(0.8, 0.8, 0.8)); + draw_line(Vector2(button_offset, h), Vector2(button_offset + h, 0), Color(0.8, 0.8, 0.8)); + } +} + +void GradientEdit::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + draw_spacing = BASE_SPACING * get_theme_default_base_scale(); + handle_width = BASE_HANDLE_WIDTH * get_theme_default_base_scale(); + } break; + case NOTIFICATION_DRAW: { + _redraw(); + } break; + case NOTIFICATION_MOUSE_EXIT: { + if (hovered_index != -1) { + hovered_index = -1; + queue_redraw(); + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + grabbing = GRAB_NONE; } } break; } } -Size2 GradientReverseButton::get_minimum_size() const { - return (get_editor_theme_icon(SNAME("ReverseGradient"))->get_size() + Size2(margin * 2, margin * 2)); +void GradientEdit::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_selected_index", "index"), &GradientEdit::set_selected_index); +} + +GradientEdit::GradientEdit() { + set_focus_mode(FOCUS_ALL); + set_custom_minimum_size(Size2(0, 60) * EDSCALE); + + picker = memnew(ColorPicker); + int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); + picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); + picker->connect("color_changed", callable_mp(this, &GradientEdit::_color_changed)); + + popup = memnew(PopupPanel); + popup->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(picker)); + + add_child(popup, false, INTERNAL_MODE_FRONT); + popup->add_child(picker); + + preview_texture.instantiate(); + preview_texture->set_width(1024); +} + +/////////////////////// + +const int GradientEditor::DEFAULT_SNAP = 10; + +void GradientEditor::_set_snap_enabled(bool p_enabled) { + gradient_editor_rect->set_snap_enabled(p_enabled); + snap_count_edit->set_visible(p_enabled); +} + +void GradientEditor::_set_snap_count(int p_count) { + gradient_editor_rect->set_snap_count(CLAMP(p_count, 2, 100)); +} + +void GradientEditor::set_gradient(const Ref &p_gradient) { + gradient_editor_rect->set_gradient(p_gradient); +} + +void GradientEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + reverse_button->set_icon(get_editor_theme_icon(SNAME("ReverseGradient"))); + snap_button->set_icon(get_editor_theme_icon(SNAME("SnapGrid"))); + } break; + case NOTIFICATION_READY: { + Ref gradient = gradient_editor_rect->get_gradient(); + if (gradient.is_valid()) { + // Set snapping settings based on the gradient's meta. + snap_button->set_pressed(gradient->get_meta("_snap_enabled", false)); + snap_count_edit->set_value(gradient->get_meta("_snap_count", DEFAULT_SNAP)); + } + } break; + } +} + +GradientEditor::GradientEditor() { + HFlowContainer *toolbar = memnew(HFlowContainer); + add_child(toolbar); + + reverse_button = memnew(Button); + reverse_button->set_tooltip_text(TTR("Reverse/Mirror Gradient")); + toolbar->add_child(reverse_button); + + toolbar->add_child(memnew(VSeparator)); + + snap_button = memnew(Button); + snap_button->set_tooltip_text(TTR("Toggle Grid Snap")); + snap_button->set_toggle_mode(true); + toolbar->add_child(snap_button); + snap_button->connect("toggled", callable_mp(this, &GradientEditor::_set_snap_enabled)); + + snap_count_edit = memnew(EditorSpinSlider); + snap_count_edit->set_min(2); + snap_count_edit->set_max(100); + snap_count_edit->set_value(DEFAULT_SNAP); + snap_count_edit->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); + toolbar->add_child(snap_count_edit); + snap_count_edit->connect("value_changed", callable_mp(this, &GradientEditor::_set_snap_count)); + + gradient_editor_rect = memnew(GradientEdit); + add_child(gradient_editor_rect); + reverse_button->connect("pressed", callable_mp(gradient_editor_rect, &GradientEdit::reverse_gradient)); + + set_mouse_filter(MOUSE_FILTER_STOP); + _set_snap_enabled(snap_button->is_pressed()); + _set_snap_count(snap_count_edit->get_value()); } /////////////////////// @@ -62,29 +659,15 @@ bool EditorInspectorPluginGradient::can_handle(Object *p_object) { void EditorInspectorPluginGradient::parse_begin(Object *p_object) { Gradient *gradient = Object::cast_to(p_object); + ERR_FAIL_COND(!gradient); Ref g(gradient); - editor = memnew(GradientEditor); + GradientEditor *editor = memnew(GradientEditor); editor->set_gradient(g); add_custom_control(editor); - - int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); - editor->get_picker()->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); - - reverse_btn = memnew(GradientReverseButton); - - gradient_tools_hbox = memnew(HBoxContainer); - gradient_tools_hbox->add_child(reverse_btn); - - add_custom_control(gradient_tools_hbox); - - reverse_btn->connect("pressed", callable_mp(this, &EditorInspectorPluginGradient::_reverse_button_pressed)); - reverse_btn->set_tooltip_text(TTR("Reverse/mirror gradient.")); } -void EditorInspectorPluginGradient::_reverse_button_pressed() { - editor->reverse_gradient(); -} +/////////////////////// GradientEditorPlugin::GradientEditorPlugin() { Ref plugin; diff --git a/editor/plugins/gradient_editor_plugin.h b/editor/plugins/gradient_editor_plugin.h index f6908f2cbd8..06d79d55abc 100644 --- a/editor/plugins/gradient_editor_plugin.h +++ b/editor/plugins/gradient_editor_plugin.h @@ -33,26 +33,102 @@ #include "editor/editor_inspector.h" #include "editor/editor_plugin.h" -#include "gradient_editor.h" -class GradientReverseButton : public BaseButton { - GDCLASS(GradientReverseButton, BaseButton); +class EditorSpinSlider; +class ColorPicker; +class PopupPanel; +class GradientTexture1D; - int margin = 2; +class GradientEdit : public Control { + GDCLASS(GradientEdit, Control); + Ref gradient; + Ref preview_texture; + + PopupPanel *popup = nullptr; + ColorPicker *picker = nullptr; + + bool snap_enabled = false; + int snap_count = 10; + + enum GrabMode { + GRAB_NONE, + GRAB_ADD, + GRAB_MOVE + }; + + GrabMode grabbing = GRAB_NONE; + float pre_grab_offset = 0.5; + int pre_grab_index = -1; + int selected_index = -1; + int hovered_index = -1; + + // Make sure to use the scaled values below. + const int BASE_SPACING = 4; + const int BASE_HANDLE_WIDTH = 8; + + int draw_spacing = BASE_SPACING; + int handle_width = BASE_HANDLE_WIDTH; + + int _get_gradient_rect_width() const; + + void _color_changed(const Color &p_color); + void _redraw(); + + int _get_point_at(int p_xpos) const; + int _predict_insertion_index(float p_offset); + void _show_color_picker(); + +protected: + virtual void gui_input(const Ref &p_event) override; void _notification(int p_what); - virtual Size2 get_minimum_size() const override; + static void _bind_methods(); + +public: + void set_gradient(const Ref &p_gradient); + const Ref &get_gradient() const; + + ColorPicker *get_picker() const; + PopupPanel *get_popup() const; + + void set_selected_index(int p_index); + + void add_point(float p_offset, const Color &p_color); + void remove_point(int p_index); + void set_offset(int p_index, float p_offset); + void set_color(int p_index, const Color &p_color); + void reverse_gradient(); + + void set_snap_enabled(bool p_enabled); + void set_snap_count(int p_count); + + GradientEdit(); +}; + +class GradientEditor : public VBoxContainer { + GDCLASS(GradientEditor, VBoxContainer); + + Button *reverse_button = nullptr; + Button *snap_button = nullptr; + EditorSpinSlider *snap_count_edit = nullptr; + GradientEdit *gradient_editor_rect = nullptr; + + void _set_snap_enabled(bool p_enabled); + void _set_snap_count(int p_count); + +protected: + void _notification(int p_what); + +public: + static const int DEFAULT_SNAP; + void set_gradient(const Ref &p_gradient); + + GradientEditor(); }; class EditorInspectorPluginGradient : public EditorInspectorPlugin { GDCLASS(EditorInspectorPluginGradient, EditorInspectorPlugin); - GradientEditor *editor = nullptr; - HBoxContainer *gradient_tools_hbox = nullptr; - GradientReverseButton *reverse_btn = nullptr; - - void _reverse_button_pressed(); - public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h index b88399117d2..0c996e2de9c 100644 --- a/scene/resources/gradient.h +++ b/scene/resources/gradient.h @@ -55,8 +55,8 @@ public: struct Point { float offset = 0.0; Color color; - bool operator<(const Point &p_ponit) const { - return offset < p_ponit.offset; + bool operator<(const Point &p_point) const { + return offset < p_point.offset; } };