Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to Gradient2D Editor #70940

Merged
merged 1 commit into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 153 additions & 102 deletions editor/plugins/gradient_texture_2d_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,145 +39,191 @@
#include "scene/gui/flow_container.h"
#include "scene/gui/separator.h"

Point2 GradientTexture2DEditorRect::_get_handle_position(const Handle p_handle) {
void GradientTexture2DEdit::_on_mouse_exited() {
if (hovered != HANDLE_NONE) {
hovered = HANDLE_NONE;
queue_redraw();
}
}

Point2 GradientTexture2DEdit::_get_handle_pos(const Handle p_handle) {
// Get the handle's mouse position in pixels relative to offset.
return (p_handle == HANDLE_FILL_FROM ? texture->get_fill_from() : texture->get_fill_to()).clamp(Vector2(), Vector2(1, 1)) * size;
return (p_handle == HANDLE_FROM ? texture->get_fill_from() : texture->get_fill_to()).clamp(Vector2(), Vector2(1, 1)) * size;
}

void GradientTexture2DEditorRect::_update_fill_position() {
if (handle == HANDLE_NONE) {
return;
GradientTexture2DEdit::Handle GradientTexture2DEdit::get_handle_at(const Vector2 &p_pos) {
Point2 from_pos = _get_handle_pos(HANDLE_FROM);
Point2 to_pos = _get_handle_pos(HANDLE_TO);
// If both handles are at the position, grab the one that's closer.
if (p_pos.distance_squared_to(from_pos) < p_pos.distance_squared_to(to_pos)) {
return Rect2(from_pos.round() - handle_size / 2, handle_size).has_point(p_pos) ? HANDLE_FROM : HANDLE_NONE;
} else {
return Rect2(to_pos.round() - handle_size / 2, handle_size).has_point(p_pos) ? HANDLE_TO : HANDLE_NONE;
}
}

// Update the texture's fill_from/fill_to property based on mouse input.
Vector2 percent = ((get_local_mouse_position() - offset) / size).clamp(Vector2(), Vector2(1, 1));
if (snap_enabled) {
percent = (percent - Vector2(0.5, 0.5)).snapped(Vector2(snap_size, snap_size)) + Vector2(0.5, 0.5);
void GradientTexture2DEdit::set_fill_pos(const Vector2 &p_pos) {
if (p_pos.is_equal_approx(initial_grab_pos)) {
return;
}

String property_name = handle == HANDLE_FILL_FROM ? "fill_from" : "fill_to";

const StringName property_name = (grabbed == HANDLE_FROM) ? "fill_from" : "fill_to";
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(vformat(TTR("Set %s"), property_name), UndoRedo::MERGE_ENDS);
undo_redo->add_do_property(texture.ptr(), property_name, percent);
undo_redo->add_undo_property(texture.ptr(), property_name, handle == HANDLE_FILL_FROM ? texture->get_fill_from() : texture->get_fill_to());
undo_redo->create_action(TTR("Move GradientTexture2D Fill Point"));
undo_redo->add_do_property(texture.ptr(), property_name, p_pos);
undo_redo->add_undo_property(texture.ptr(), property_name, initial_grab_pos);
undo_redo->commit_action();
}

void GradientTexture2DEditorRect::gui_input(const Ref<InputEvent> &p_event) {
// Grab/release handle.
void GradientTexture2DEdit::gui_input(const Ref<InputEvent> &p_event) {
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
Point2 mouse_position = mb->get_position() - offset;
if (Rect2(_get_handle_position(HANDLE_FILL_FROM).round() - handle_size / 2, handle_size).has_point(mouse_position)) {
handle = HANDLE_FILL_FROM;
} else if (Rect2(_get_handle_position(HANDLE_FILL_TO).round() - handle_size / 2, handle_size).has_point(mouse_position)) {
handle = HANDLE_FILL_TO;
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
grabbed = get_handle_at(mb->get_position() - offset);

if (grabbed != HANDLE_NONE) {
initial_grab_pos = _get_handle_pos(grabbed) / size;
queue_redraw();
}
} else {
handle = HANDLE_NONE;
// Release the handle.
if (grabbed != HANDLE_NONE) {
set_fill_pos(_get_handle_pos(grabbed) / size);
grabbed = HANDLE_NONE;
queue_redraw();
}
}
} else {
_update_fill_position();
handle = HANDLE_NONE;
}

if (grabbed != HANDLE_NONE && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
texture->set((grabbed == HANDLE_FROM) ? SNAME("fill_from") : SNAME("fill_to"), initial_grab_pos);
grabbed = HANDLE_NONE;
queue_redraw();
}
}

// Move handle.
const Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
_update_fill_position();
Vector2 mpos = mm->get_position() - offset;

Handle handle_at_mpos = get_handle_at(mpos);
if (hovered != handle_at_mpos) {
hovered = handle_at_mpos;
queue_redraw();
}

if (grabbed == HANDLE_NONE) {
return;
}

Vector2 new_pos = (mpos / size).clamp(Vector2(0, 0), Vector2(1, 1));
if (snap_enabled || mm->is_ctrl_pressed()) {
new_pos = new_pos.snapped(Vector2(1.0 / snap_count, 1.0 / snap_count));
}

// Allow to snap to an axis with Shift.
if (mm->is_shift_pressed()) {
Vector2 initial_mpos = initial_grab_pos * size;
if (Math::abs(mpos.x - initial_mpos.x) > Math::abs(mpos.y - initial_mpos.y)) {
new_pos.y = initial_grab_pos.y;
} else {
new_pos.x = initial_grab_pos.x;
}
}
// Do it directly from the texture so there's no undo/redo until the handle is released.
texture->set((grabbed == HANDLE_FROM) ? SNAME("fill_from") : SNAME("fill_to"), new_pos);
}
}

void GradientTexture2DEditorRect::set_texture(Ref<GradientTexture2D> &p_texture) {
void GradientTexture2DEdit::set_texture(Ref<GradientTexture2D> &p_texture) {
texture = p_texture;
texture->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
}

void GradientTexture2DEditorRect::set_snap_enabled(bool p_snap_enabled) {
void GradientTexture2DEdit::set_snap_enabled(bool p_snap_enabled) {
snap_enabled = p_snap_enabled;
queue_redraw();
if (texture.is_valid()) {
if (snap_enabled) {
texture->set_meta(SNAME("_snap_enabled"), true);
} else {
texture->remove_meta(SNAME("_snap_enabled"));
}
}
}

void GradientTexture2DEditorRect::set_snap_size(float p_snap_size) {
snap_size = p_snap_size;
void GradientTexture2DEdit::set_snap_count(int p_snap_count) {
snap_count = p_snap_count;
queue_redraw();
if (texture.is_valid()) {
if (snap_count != GradientTexture2DEditor::DEFAULT_SNAP) {
texture->set_meta(SNAME("_snap_count"), snap_count);
} else {
texture->remove_meta(SNAME("_snap_count"));
}
}
}

void GradientTexture2DEditorRect::_notification(int p_what) {
void GradientTexture2DEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
connect("mouse_exited", callable_mp(this, &GradientTexture2DEdit::_on_mouse_exited));
[[fallthrough]];
case NOTIFICATION_THEME_CHANGED: {
checkerboard->set_texture(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")));
} break;

case NOTIFICATION_DRAW: {
if (texture.is_null()) {
return;
}

const Ref<Texture2D> fill_from_icon = get_theme_icon(SNAME("EditorPathSmoothHandle"), SNAME("EditorIcons"));
const Ref<Texture2D> fill_to_icon = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons"));
handle_size = fill_from_icon->get_size();

Size2 rect_size = get_size();

// Get the size and position to draw the texture and handles at.
// Subtract handle sizes so they stay inside the preview, but keep the texture's aspect ratio.
Size2 available_size = rect_size - handle_size;
Size2 ratio = available_size / texture->get_size();
size = MIN(ratio.x, ratio.y) * texture->get_size();
offset = ((rect_size - size) / 2).round();

checkerboard->set_rect(Rect2(offset, size));

draw_set_transform(offset);
draw_texture_rect(texture, Rect2(Point2(), size));

// Draw grid snap lines.
if (snap_enabled) {
const Color primary_line_color = Color(0.5, 0.5, 0.5, 0.9);
const Color line_color = Color(0.5, 0.5, 0.5, 0.5);
_draw();
} break;
}
}

// Draw border and centered axis lines.
draw_rect(Rect2(Point2(), size), primary_line_color, false);
draw_line(Point2(size.width / 2, 0), Point2(size.width / 2, size.height), primary_line_color);
draw_line(Point2(0, size.height / 2), Point2(size.width, size.height / 2), primary_line_color);
void GradientTexture2DEdit::_draw() {
if (texture.is_null()) {
return;
}

// Draw vertical lines.
int prev_idx = 0;
for (int x = 0; x < size.width; x++) {
int idx = int((x / size.width - 0.5) / snap_size);
const Ref<Texture2D> fill_from_icon = get_theme_icon(SNAME("EditorPathSmoothHandle"), SNAME("EditorIcons"));
const Ref<Texture2D> fill_to_icon = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons"));
handle_size = fill_from_icon->get_size();

if (x > 0 && prev_idx != idx) {
draw_line(Point2(x, 0), Point2(x, size.height), line_color);
}
Size2 rect_size = get_size();

prev_idx = idx;
}
// Get the size and position to draw the texture and handles at.
// Subtract handle sizes so they stay inside the preview, but keep the texture's aspect ratio.
Size2 available_size = rect_size - handle_size;
Size2 ratio = available_size / texture->get_size();
size = MIN(ratio.x, ratio.y) * texture->get_size();
offset = ((rect_size - size) / 2).round();

// Draw horizontal lines.
prev_idx = 0;
for (int y = 0; y < size.height; y++) {
int idx = int((y / size.height - 0.5) / snap_size);
checkerboard->set_rect(Rect2(offset, size));

if (y > 0 && prev_idx != idx) {
draw_line(Point2(0, y), Point2(size.width, y), line_color);
}
draw_set_transform(offset);
draw_texture_rect(texture, Rect2(Point2(), size));

prev_idx = idx;
}
}
// Draw grid snap lines.
if (snap_enabled || (Input::get_singleton()->is_key_pressed(Key::CTRL) && grabbed != HANDLE_NONE)) {
const Color line_color = Color(0.5, 0.5, 0.5, 0.5);

// Draw handles.
draw_texture(fill_from_icon, (_get_handle_position(HANDLE_FILL_FROM) - handle_size / 2).round());
draw_texture(fill_to_icon, (_get_handle_position(HANDLE_FILL_TO) - handle_size / 2).round());
} break;
for (int idx = 0; idx < snap_count + 1; idx++) {
float x = float(idx * size.width) / snap_count;
float y = float(idx * size.height) / snap_count;
draw_line(Point2(x, 0), Point2(x, size.height), line_color);
draw_line(Point2(0, y), Point2(size.width, y), line_color);
}
}

// Draw handles.
const Color focus_modulate = Color(0.5, 1, 2);
bool modulate_handle_from = grabbed == HANDLE_FROM || (grabbed != HANDLE_FROM && hovered == HANDLE_FROM);
bool modulate_handle_to = grabbed == HANDLE_TO || (grabbed != HANDLE_TO && hovered == HANDLE_TO);
draw_texture(fill_from_icon, (_get_handle_pos(HANDLE_FROM) - handle_size / 2).round(), modulate_handle_from ? focus_modulate : Color(1, 1, 1));
draw_texture(fill_to_icon, (_get_handle_pos(HANDLE_TO) - handle_size / 2).round(), modulate_handle_to ? focus_modulate : Color(1, 1, 1));
}

GradientTexture2DEditorRect::GradientTexture2DEditorRect() {
GradientTexture2DEdit::GradientTexture2DEdit() {
checkerboard = memnew(TextureRect);
checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE);
checkerboard->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
Expand All @@ -189,6 +235,8 @@ GradientTexture2DEditorRect::GradientTexture2DEditorRect() {

///////////////////////

const int GradientTexture2DEditor::DEFAULT_SNAP = 10;

void GradientTexture2DEditor::_reverse_button_pressed() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Swap GradientTexture2D Fill Points"));
Expand All @@ -201,12 +249,11 @@ void GradientTexture2DEditor::_reverse_button_pressed() {

void GradientTexture2DEditor::_set_snap_enabled(bool p_enabled) {
texture_editor_rect->set_snap_enabled(p_enabled);

snap_size_edit->set_visible(p_enabled);
snap_count_edit->set_visible(p_enabled);
}

void GradientTexture2DEditor::_set_snap_size(float p_snap_size) {
texture_editor_rect->set_snap_size(MAX(p_snap_size, 0.01));
void GradientTexture2DEditor::_set_snap_count(int p_snap_count) {
texture_editor_rect->set_snap_count(p_snap_count);
}

void GradientTexture2DEditor::set_texture(Ref<GradientTexture2D> &p_texture) {
Expand All @@ -221,6 +268,11 @@ void GradientTexture2DEditor::_notification(int p_what) {
reverse_button->set_icon(get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons")));
snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons")));
} break;
case NOTIFICATION_READY: {
// Set snapping settings based on the texture's meta.
snap_button->set_pressed(texture->get_meta("_snap_enabled", false));
snap_count_edit->set_value(texture->get_meta("_snap_count", DEFAULT_SNAP));
} break;
}
}

Expand All @@ -241,21 +293,20 @@ GradientTexture2DEditor::GradientTexture2DEditor() {
toolbar->add_child(snap_button);
snap_button->connect("toggled", callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled));

snap_size_edit = memnew(EditorSpinSlider);
snap_size_edit->set_min(0.01);
snap_size_edit->set_max(0.5);
snap_size_edit->set_step(0.01);
snap_size_edit->set_value(0.1);
snap_size_edit->set_custom_minimum_size(Size2(65 * EDSCALE, 0));
toolbar->add_child(snap_size_edit);
snap_size_edit->connect("value_changed", callable_mp(this, &GradientTexture2DEditor::_set_snap_size));
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, &GradientTexture2DEditor::_set_snap_count));

texture_editor_rect = memnew(GradientTexture2DEditorRect);
texture_editor_rect = memnew(GradientTexture2DEdit);
add_child(texture_editor_rect);

set_mouse_filter(MOUSE_FILTER_STOP);
_set_snap_enabled(snap_button->is_pressed());
_set_snap_size(snap_size_edit->get_value());
_set_snap_count(snap_count_edit->get_value());
}

///////////////////////
Expand Down
Loading