<commit_before>/*************************************************************************/
/*  editor_properties.cpp                                                */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2021 <NAME>, <NAME>.                 */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* 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 "editor_properties.h"

#include "editor/editor_resource_preview.h"
#include "editor/filesystem_dock.h"
#include "editor_node.h"
#include "editor_properties_array_dict.h"
#include "editor_scale.h"
#include "scene/main/viewport.h"

///////////////////// NULL /////////////////////////

void EditorPropertyNil::update_property() {
}

EditorPropertyNil::EditorPropertyNil() {
	Label *label = memnew(Label);
	label->set_text("[null]");
	add_child(label);
}

///////////////////// TEXT /////////////////////////

void EditorPropertyText::_text_entered(const String &p_string) {
	if (updating) {
		return;
	}

	if (text->has_focus()) {
		text->release_focus();
		_text_changed(p_string);
	}
}

void EditorPropertyText::_text_changed(const String &p_string) {
	if (updating) {
		return;
	}

	emit_changed(get_edited_property(), p_string, "", true);
}

void EditorPropertyText::update_property() {
	String s = get_edited_object()->get(get_edited_property());
	updating = true;
	if (text->get_text() != s) {
		text->set_text(s);
	}
	text->set_editable(!is_read_only());
	updating = false;
}

void EditorPropertyText::set_placeholder(const String &p_string) {
	text->set_placeholder(p_string);
}

void EditorPropertyText::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_text_changed", "txt"), &EditorPropertyText::_text_changed);
	ClassDB::bind_method(D_METHOD("_text_entered", "txt"), &EditorPropertyText::_text_entered);
}

EditorPropertyText::EditorPropertyText() {
	text = memnew(LineEdit);
	add_child(text);
	add_focusable(text);
	text->connect("text_changed", this, "_text_changed");
	text->connect("text_entered", this, "_text_entered");

	updating = false;
}

///////////////////// MULTILINE TEXT /////////////////////////

void EditorPropertyMultilineText::_big_text_changed() {
	text->set_text(big_text->get_text());
	emit_changed(get_edited_property(), big_text->get_text(), "", true);
}

void EditorPropertyMultilineText::_text_changed() {
	emit_changed(get_edited_property(), text->get_text(), "", true);
}

void EditorPropertyMultilineText::_open_big_text() {
	if (!big_text_dialog) {
		big_text = memnew(TextEdit);
		big_text->connect("text_changed", this, "_big_text_changed");
		big_text->set_wrap_enabled(true);
		big_text_dialog = memnew(AcceptDialog);
		big_text_dialog->add_child(big_text);
		big_text_dialog->set_title(TTR("Edit Text:"));
		add_child(big_text_dialog);
	}

	big_text_dialog->popup_centered_clamped(Size2(1000, 900) * EDSCALE, 0.8);
	big_text->set_text(text->get_text());
	big_text->grab_focus();
}

void EditorPropertyMultilineText::update_property() {
	String t = get_edited_object()->get(get_edited_property());
	if (text->get_text() != t) {
		text->set_text(t);
		if (big_text && big_text->is_visible_in_tree()) {
			big_text->set_text(t);
		}
	}
}

void EditorPropertyMultilineText::_notification(int p_what) {
	switch (p_what) {
		case NOTIFICATION_THEME_CHANGED:
		case NOTIFICATION_ENTER_TREE: {
			Ref<Texture> df = get_icon("DistractionFree", "EditorIcons");
			open_big_text->set_icon(df);
			Ref<Font> font = get_font("font", "Label");
			text->set_custom_minimum_size(Vector2(0, font->get_height() * 6));

		} break;
	}
}

void EditorPropertyMultilineText::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_text_changed"), &EditorPropertyMultilineText::_text_changed);
	ClassDB::bind_method(D_METHOD("_big_text_changed"), &EditorPropertyMultilineText::_big_text_changed);
	ClassDB::bind_method(D_METHOD("_open_big_text"), &EditorPropertyMultilineText::_open_big_text);
}

EditorPropertyMultilineText::EditorPropertyMultilineText() {
	HBoxContainer *hb = memnew(HBoxContainer);
	add_child(hb);
	set_bottom_editor(hb);
	text = memnew(TextEdit);
	text->connect("text_changed", this, "_text_changed");
	text->set_wrap_enabled(true);
	add_focusable(text);
	hb->add_child(text);
	text->set_h_size_flags(SIZE_EXPAND_FILL);
	open_big_text = memnew(ToolButton);
	open_big_text->connect("pressed", this, "_open_big_text");
	hb->add_child(open_big_text);
	big_text_dialog = nullptr;
	big_text = nullptr;
}

///////////////////// TEXT ENUM /////////////////////////

void EditorPropertyTextEnum::_option_selected(int p_which) {
	emit_changed(get_edited_property(), options->get_item_text(p_which));
}

void EditorPropertyTextEnum::update_property() {
	String which = get_edited_object()->get(get_edited_property());
	for (int i = 0; i < options->get_item_count(); i++) {
		String t = options->get_item_text(i);
		if (t == which) {
			options->select(i);
			return;
		}
	}
}

void EditorPropertyTextEnum::setup(const Vector<String> &p_options) {
	for (int i = 0; i < p_options.size(); i++) {
		options->add_item(p_options[i], i);
	}
}

void EditorPropertyTextEnum::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyTextEnum::_option_selected);
}

EditorPropertyTextEnum::EditorPropertyTextEnum() {
	options = memnew(OptionButton);
	options->set_clip_text(true);
	options->set_flat(true);

	add_child(options);
	add_focusable(options);
	options->connect("item_selected", this, "_option_selected");
}
///////////////////// PATH /////////////////////////

void EditorPropertyPath::_path_selected(const String &p_path) {
	emit_changed(get_edited_property(), p_path);
	update_property();
}
void EditorPropertyPath::_path_pressed() {
	if (!dialog) {
		dialog = memnew(EditorFileDialog);
		dialog->connect("file_selected", this, "_path_selected");
		dialog->connect("dir_selected", this, "_path_selected");
		add_child(dialog);
	}

	String full_path = get_edited_object()->get(get_edited_property());

	dialog->clear_filters();

	if (global) {
		dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
	} else {
		dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
	}

	if (folder) {
		dialog->set_mode(EditorFileDialog::MODE_OPEN_DIR);
		dialog->set_current_dir(full_path);
	} else {
		dialog->set_mode(save_mode ? EditorFileDialog::MODE_SAVE_FILE : EditorFileDialog::MODE_OPEN_FILE);
		for (int i = 0; i < extensions.size(); i++) {
			String e = extensions[i].strip_edges();
			if (e != String()) {
				dialog->add_filter(extensions[i].strip_edges());
			}
		}
		dialog->set_current_path(full_path);
	}

	dialog->popup_centered_ratio();
}

void EditorPropertyPath::update_property() {
	String full_path = get_edited_object()->get(get_edited_property());
	path->set_text(full_path);
	path->set_tooltip(full_path);
}

void EditorPropertyPath::setup(const Vector<String> &p_extensions, bool p_folder, bool p_global) {
	extensions = p_extensions;
	folder = p_folder;
	global = p_global;
}

void EditorPropertyPath::set_save_mode() {
	save_mode = true;
}

void EditorPropertyPath::_notification(int p_what) {
	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
		path_edit->set_icon(get_icon("Folder", "EditorIcons"));
	}
}

void EditorPropertyPath::_path_focus_exited() {
	_path_selected(path->get_text());
}

void EditorPropertyPath::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_path_pressed"), &EditorPropertyPath::_path_pressed);
	ClassDB::bind_method(D_METHOD("_path_selected"), &EditorPropertyPath::_path_selected);
	ClassDB::bind_method(D_METHOD("_path_focus_exited"), &EditorPropertyPath::_path_focus_exited);
}

EditorPropertyPath::EditorPropertyPath() {
	HBoxContainer *path_hb = memnew(HBoxContainer);
	add_child(path_hb);
	path = memnew(LineEdit);
	path_hb->add_child(path);
	path->connect("text_entered", this, "_path_selected");
	path->connect("focus_exited", this, "_path_focus_exited");
	path->set_h_size_flags(SIZE_EXPAND_FILL);

	path_edit = memnew(Button);
	path_edit->set_clip_text(true);
	path_hb->add_child(path_edit);
	add_focusable(path);
	dialog = nullptr;
	path_edit->connect("pressed", this, "_path_pressed");
	folder = false;
	global = false;
	save_mode = false;
}

///////////////////// CLASS NAME /////////////////////////

void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) {
	base_type = p_base_type;
	dialog->set_base_type(base_type);
	selected_type = p_selected_type;
	property->set_text(selected_type);
}

void EditorPropertyClassName::update_property() {
	String s = get_edited_object()->get(get_edited_property());
	property->set_text(s);
	selected_type = s;
}

void EditorPropertyClassName::_property_selected() {
	dialog->popup_create(true);
}

void EditorPropertyClassName::_dialog_created() {
	selected_type = dialog->get_selected_type();
	emit_changed(get_edited_property(), selected_type);
	update_property();
}

void EditorPropertyClassName::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_dialog_created"), &EditorPropertyClassName::_dialog_created);
	ClassDB::bind_method(D_METHOD("_property_selected"), &EditorPropertyClassName::_property_selected);
}

EditorPropertyClassName::EditorPropertyClassName() {
	property = memnew(Button);
	property->set_clip_text(true);
	add_child(property);
	add_focusable(property);
	property->set_text(selected_type);
	property->connect("pressed", this, "_property_selected");
	dialog = memnew(CreateDialog);
	dialog->set_base_type(base_type);
	dialog->connect("create", this, "_dialog_created");
	add_child(dialog);
}

///////////////////// MEMBER /////////////////////////

void EditorPropertyMember::_property_selected(const String &p_selected) {
	emit_changed(get_edited_property(), p_selected);
	update_property();
}

void EditorPropertyMember::_property_select() {
	if (!selector) {
		selector = memnew(PropertySelector);
		selector->connect("selected", this, "_property_selected");
		add_child(selector);
	}

	String current = get_edited_object()->get(get_edited_property());

	if (hint == MEMBER_METHOD_OF_VARIANT_TYPE) {
		Variant::Type type = Variant::NIL;
		for (int i = 0; i < Variant::VARIANT_MAX; i++) {
			if (hint_text == Variant::get_type_name(Variant::Type(i))) {
				type = Variant::Type(i);
			}
		}
		if (type != Variant::NIL) {
			selector->select_method_from_basic_type(type, current);
		}

	} else if (hint == MEMBER_METHOD_OF_BASE_TYPE) {
		selector->select_method_from_base_type(hint_text, current);

	} else if (hint == MEMBER_METHOD_OF_INSTANCE) {
		Object *instance = ObjectDB::get_instance(hint_text.to_int64());
		if (instance) {
			selector->select_method_from_instance(instance, current);
		}

	} else if (hint == MEMBER_METHOD_OF_SCRIPT) {
		Object *obj = ObjectDB::get_instance(hint_text.to_int64());
		if (Object::cast_to<Script>(obj)) {
			selector->select_method_from_script(Object::cast_to<Script>(obj), current);
		}

	} else if (hint == MEMBER_PROPERTY_OF_VARIANT_TYPE) {
		Variant::Type type = Variant::NIL;
		String tname = hint_text;
		if (tname.find(".") != -1) {
			tname = tname.get_slice(".", 0);
		}
		for (int i = 0; i < Variant::VARIANT_MAX; i++) {
			if (tname == Variant::get_type_name(Variant::Type(i))) {
				type = Variant::Type(Variant::Type(i));
			}
		}

		if (type != Variant::NIL) {
			selector->select_property_from_basic_type(type, current);
		}

	} else if (hint == MEMBER_PROPERTY_OF_BASE_TYPE) {
		selector->select_property_from_base_type(hint_text, current);

	} else if (hint == MEMBER_PROPERTY_OF_INSTANCE) {
		Object *instance = ObjectDB::get_instance(hint_text.to_int64());
		if (instance) {
			selector->select_property_from_instance(instance, current);
		}

	} else if (hint == MEMBER_PROPERTY_OF_SCRIPT) {
		Object *obj = ObjectDB::get_instance(hint_text.to_int64());
		if (Object::cast_to<Script>(obj)) {
			selector->select_property_from_script(Object::cast_to<Script>(obj), current);
		}
	}
}

void EditorPropertyMember::setup(Type p_hint, const String &p_hint_text) {
	hint = p_hint;
	hint_text = p_hint_text;
}

void EditorPropertyMember::update_property() {
	String full_path = get_edited_object()->get(get_edited_property());
	property->set_text(full_path);
}

void EditorPropertyMember::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_property_selected"), &EditorPropertyMember::_property_selected);
	ClassDB::bind_method(D_METHOD("_property_select"), &EditorPropertyMember::_property_select);
}

EditorPropertyMember::EditorPropertyMember() {
	selector = nullptr;
	property = memnew(Button);
	property->set_clip_text(true);
	add_child(property);
	add_focusable(property);
	property->connect("pressed", this, "_property_select");
}

///////////////////// CHECK /////////////////////////
void EditorPropertyCheck::_checkbox_pressed() {
	emit_changed(get_edited_property(), checkbox->is_pressed());
}

void EditorPropertyCheck::update_property() {
	bool c = get_edited_object()->get(get_edited_property());
	checkbox->set_pressed(c);
	checkbox->set_disabled(is_read_only());
}

void EditorPropertyCheck::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_checkbox_pressed"), &EditorPropertyCheck::_checkbox_pressed);
}

EditorPropertyCheck::EditorPropertyCheck() {
	checkbox = memnew(CheckBox);
	checkbox->set_text(TTR("On"));
	add_child(checkbox);
	add_focusable(checkbox);
	checkbox->connect("pressed", this, "_checkbox_pressed");
}

///////////////////// ENUM /////////////////////////

void EditorPropertyEnum::_option_selected(int p_which) {
	int64_t val = options->get_item_metadata(p_which);
	emit_changed(get_edited_property(), val);
}

void EditorPropertyEnum::update_property() {
	int64_t which = get_edited_object()->get(get_edited_property());

	for (int i = 0; i < options->get_item_count(); i++) {
		if (which == (int64_t)options->get_item_metadata(i)) {
			options->select(i);
			return;
		}
	}
}

void EditorPropertyEnum::setup(const Vector<String> &p_options) {
	int64_t current_val = 0;
	for (int i = 0; i < p_options.size(); i++) {
		Vector<String> text_split = p_options[i].split(":");
		if (text_split.size() != 1) {
			current_val = text_split[1].to_int64();
		}
		options->add_item(text_split[0]);
		options->set_item_metadata(i, current_val);
		current_val += 1;
	}
}

void EditorPropertyEnum::set_option_button_clip(bool p_enable) {
	options->set_clip_text(p_enable);
}

void EditorPropertyEnum::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyEnum::_option_selected);
}

EditorPropertyEnum::EditorPropertyEnum() {
	options = memnew(OptionButton);
	options->set_clip_text(true);
	options->set_flat(true);
	add_child(options);
	add_focusable(options);
	options->connect("item_selected", this, "_option_selected");
}

///////////////////// FLAGS /////////////////////////

void EditorPropertyFlags::_flag_toggled() {
	uint32_t value = 0;
	for (int i = 0; i < flags.size(); i++) {
		if (flags[i]->is_pressed()) {
			uint32_t val = 1;
			val <<= flag_indices[i];
			value |= val;
		}
	}

	emit_changed(get_edited_property(), value);
}

void EditorPropertyFlags::update_property() {
	uint32_t value = get_edited_object()->get(get_edited_property());

	for (int i = 0; i < flags.size(); i++) {
		uint32_t val = 1;
		val <<= flag_indices[i];
		if (value & val) {
			flags[i]->set_pressed(true);
		} else {
			flags[i]->set_pressed(false);
		}
	}
}

void EditorPropertyFlags::setup(const Vector<String> &p_options) {
	ERR_FAIL_COND(flags.size());

	bool first = true;
	for (int i = 0; i < p_options.size(); i++) {
		String option = p_options[i].strip_edges();
		if (option != "") {
			CheckBox *cb = memnew(CheckBox);
			cb->set_text(option);
			cb->set_clip_text(true);
			cb->connect("pressed", this, "_flag_toggled");
			add_focusable(cb);
			vbox->add_child(cb);
			flags.push_back(cb);
			flag_indices.push_back(i);
			if (first) {
				set_label_reference(cb);
				first = false;
			}
		}
	}
}

void EditorPropertyFlags::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_flag_toggled"), &EditorPropertyFlags::_flag_toggled);
}

EditorPropertyFlags::EditorPropertyFlags() {
	vbox = memnew(VBoxContainer);
	add_child(vbox);
}

///////////////////// LAYERS /////////////////////////

class EditorPropertyLayersGrid : public Control {
	GDCLASS(EditorPropertyLayersGrid, Control);

public:
	uint32_t value;
	Vector<Rect2> flag_rects;
	Vector<String> names;
	Vector<String> tooltips;
	int hovered_index;

	virtual Size2 get_minimum_size() const {
		Ref<Font> font = get_font("font", "Label");
		return Vector2(0, font->get_height() * 2);
	}

	virtual String get_tooltip(const Point2 &p_pos) const {
		for (int i = 0; i < flag_rects.size(); i++) {
			if (i < tooltips.size() && flag_rects[i].has_point(p_pos)) {
				return tooltips[i];
			}
		}
		return String();
	}
	void _gui_input(const Ref<InputEvent> &p_ev) {
		const Ref<InputEventMouseMotion> mm = p_ev;

		if (mm.is_valid()) {
			for (int i = 0; i < flag_rects.size(); i++) {
				if (flag_rects[i].has_point(mm->get_position())) {
					// Used to highlight the hovered flag in the layers grid.
					hovered_index = i;
					update();
					break;
				}
			}
		}

		const Ref<InputEventMouseButton> mb = p_ev;

		if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed() && hovered_index >= 0) {
			// Toggle the flag.
			// We base our choice on the hovered flag, so that it always matches the hovered flag.
			if (value & (1 << hovered_index)) {
				value &= ~(1 << hovered_index);
			} else {
				value |= (1 << hovered_index);
			}

			emit_signal("flag_changed", value);
			update();
		}
	}

	void _notification(int p_what) {
		switch (p_what) {
			case NOTIFICATION_DRAW: {
				Rect2 rect;
				rect.size = get_size();
				flag_rects.clear();

				const int bsize = (rect.size.height * 80 / 100) / 2;
				const int h = bsize * 2 + 1;
				const int vofs = (rect.size.height - h) / 2;

				Color color = get_color("highlight_color", "Editor");
				for (int i = 0; i < 2; i++) {
					Point2 ofs(4, vofs);
					if (i == 1) {
						ofs.y += bsize + 1;
					}

					ofs += rect.position;
					for (int j = 0; j < 10; j++) {
						Point2 o = ofs + Point2(j * (bsize + 1), 0);
						if (j >= 5) {
							o.x += 1;
						}

						const int idx = i * 10 + j;
						const bool on = value & (1 << idx);
						Rect2 rect2 = Rect2(o, Size2(bsize, bsize));

						color.a = on ? 0.6 : 0.2;
						if (idx == hovered_index) {
							// Add visual feedback when hovering a flag.
							color.a += 0.15;
						}

						draw_rect(rect2, color);
						flag_rects.push_back(rect2);
					}
				}
			} break;
			case NOTIFICATION_MOUSE_EXIT: {
				hovered_index = -1;
				update();
			} break;
			default:
				break;
		}
	}

	void set_flag(uint32_t p_flag) {
		value = p_flag;
		update();
	}

	static void _bind_methods() {
		ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input);
		ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag")));
	}

	EditorPropertyLayersGrid() {
		value = 0;
		hovered_index = -1; // Nothing is hovered.
	}
};
void EditorPropertyLayers::_grid_changed(uint32_t p_grid) {
	emit_changed(get_edited_property(), p_grid);
}

void EditorPropertyLayers::update_property() {
	uint32_t value = get_edited_object()->get(get_edited_property());

	grid->set_flag(value);
}

void EditorPropertyLayers::setup(LayerType p_layer_type) {
	String basename;
	switch (p_layer_type) {
		case LAYER_RENDER_2D:
			basename = "layer_names/2d_render";
			break;
		case LAYER_PHYSICS_2D:
			basename = "layer_names/2d_physics";
			break;
		case LAYER_RENDER_3D:
			basename = "layer_names/3d_render";
			break;
		case LAYER_PHYSICS_3D:
			basename = "layer_names/3d_physics";
			break;
	}

	Vector<String> names;
	Vector<String> tooltips;
	for (int i = 0; i < 20; i++) {
		String name;

		if (ProjectSettings::get_singleton()->has_setting(basename + "/layer_" + itos(i + 1))) {
			name = ProjectSettings::get_singleton()->get(basename + "/layer_" + itos(i + 1));
		}

		if (name == "") {
			name = TTR("Layer") + " " + itos(i + 1);
		}

		names.push_back(name);
		tooltips.push_back(name + "\n" + vformat(TTR("Bit %d, value %d"), i, 1 << i));
	}

	grid->names = names;
	grid->tooltips = tooltips;
}

void EditorPropertyLayers::_button_pressed() {
	layers->clear();
	for (int i = 0; i < 20; i++) {
		if (i == 5 || i == 10 || i == 15) {
			layers->add_separator();
		}
		layers->add_check_item(grid->names[i], i);
		int idx = layers->get_item_index(i);
		layers->set_item_checked(idx, grid->value & (1 << i));
	}

	Rect2 gp = button->get_global_rect();
	layers->set_as_minsize();
	Vector2 popup_pos = gp.position - Vector2(layers->get_combined_minimum_size().x, 0);
	layers->set_global_position(popup_pos);
	layers->popup();
}

void EditorPropertyLayers::_menu_pressed(int p_menu) {
	if (grid->value & (1 << p_menu)) {
		grid->value &= ~(1 << p_menu);
	} else {
		grid->value |= (1 << p_menu);
	}
	grid->update();
	layers->set_item_checked(layers->get_item_index(p_menu), grid->value & (1 << p_menu));
	_grid_changed(grid->value);
}

void EditorPropertyLayers::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_grid_changed"), &EditorPropertyLayers::_grid_changed);
	ClassDB::bind_method(D_METHOD("_button_pressed"), &EditorPropertyLayers::_button_pressed);
	ClassDB::bind_method(D_METHOD("_menu_pressed"), &EditorPropertyLayers::_menu_pressed);
}

EditorPropertyLayers::EditorPropertyLayers() {
	HBoxContainer *hb = memnew(HBoxContainer);
	add_child(hb);
	grid = memnew(EditorPropertyLayersGrid);
	grid->connect("flag_changed", this, "_grid_changed");
	grid->set_h_size_flags(SIZE_EXPAND_FILL);
	hb->add_child(grid);
	button = memnew(Button);
	button->set_toggle_mode(true);
	button->set_text("...");
	button->connect("pressed", this, "_button_pressed");
	hb->add_child(button);
	set_bottom_editor(hb);
	layers = memnew(PopupMenu);
	add_child(layers);
	layers->set_hide_on_checkable_item_selection(false);
	layers->connect("id_pressed", this, "_menu_pressed");
	layers->connect("popup_hide", button, "set_pressed", varray(false));
}

///////////////////// INT /////////////////////////

void EditorPropertyInteger::_value_changed(int64_t val) {
	if (setting) {
		return;
	}
	emit_changed(get_edited_property(), val);
}

void EditorPropertyInteger::update_property() {
	int64_t val = get_edited_object()->get(get_edited_property());
	setting = true;
	spin->set_value(val);
	setting = false;
#ifdef DEBUG_ENABLED
	// If spin (currently EditorSplinSlider : Range) is changed so that it can use int64_t, then the below warning wouldn't be a problem.
	if (val != (int64_t)(double)(val)) {
		WARN_PRINT("Cannot reliably represent '" + itos(val) + "' in the inspector, value is too large.");
	}
#endif
}

void EditorPropertyInteger::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyInteger::_value_changed);
}

void EditorPropertyInteger::setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_allow_greater, bool p_allow_lesser) {
	spin->set_min(p_min);
	spin->set_max(p_max);
	spin->set_step(p_step);
	spin->set_allow_greater(p_allow_greater);
	spin->set_allow_lesser(p_allow_lesser);
}

EditorPropertyInteger::EditorPropertyInteger() {
	spin = memnew(EditorSpinSlider);
	spin->set_flat(true);
	add_child(spin);
	add_focusable(spin);
	spin->connect("value_changed", this, "_value_changed");
	setting = false;
}

///////////////////// OBJECT ID /////////////////////////

void EditorPropertyObjectID::_edit_pressed() {
	emit_signal("object_id_selected", get_edited_property(), get_edited_object()->get(get_edited_property()));
}

void EditorPropertyObjectID::update_property() {
	String type = base_type;
	if (type == "") {
		type = "Object";
	}

	ObjectID id = get_edited_object()->get(get_edited_property());
	if (id != 0) {
		edit->set_text(type + " ID: " + itos(id));
		edit->set_disabled(false);
		edit->set_icon(EditorNode::get_singleton()->get_class_icon(type));
	} else {
		edit->set_text(TTR("[Empty]"));
		edit->set_disabled(true);
		edit->set_icon(Ref<Texture>());
	}
}

void EditorPropertyObjectID::setup(const String &p_base_type) {
	base_type = p_base_type;
}

void EditorPropertyObjectID::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_edit_pressed"), &EditorPropertyObjectID::_edit_pressed);
}

EditorPropertyObjectID::EditorPropertyObjectID() {
	edit = memnew(Button);
	add_child(edit);
	add_focusable(edit);
	edit->connect("pressed", this, "_edit_pressed");
}

///////////////////// FLOAT /////////////////////////

void EditorPropertyFloat::_value_changed(double val) {
	if (setting) {
		return;
	}

	emit_changed(get_edited_property(), val);
}

void EditorPropertyFloat::update_property() {
	double val = get_edited_object()->get(get_edited_property());
	setting = true;
	spin->set_value(val);
	setting = false;
}

void EditorPropertyFloat::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyFloat::_value_changed);
}

void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range, bool p_greater, bool p_lesser) {
	spin->set_min(p_min);
	spin->set_max(p_max);
	spin->set_step(p_step);
	spin->set_hide_slider(p_no_slider);
	spin->set_exp_ratio(p_exp_range);
	spin->set_allow_greater(p_greater);
	spin->set_allow_lesser(p_lesser);
}

EditorPropertyFloat::EditorPropertyFloat() {
	spin = memnew(EditorSpinSlider);
	spin->set_flat(true);
	add_child(spin);
	add_focusable(spin);
	spin->connect("value_changed", this, "_value_changed");
	setting = false;
}

///////////////////// EASING /////////////////////////

void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) {
	const Ref<InputEventMouseButton> mb = p_ev;
	if (mb.is_valid()) {
		if (mb->is_doubleclick() && mb->get_button_index() == BUTTON_LEFT) {
			_setup_spin();
		}

		if (mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) {
			preset->set_global_position(easing_draw->get_global_transform().xform(mb->get_position()));
			preset->popup();

			// Ensure the easing doesn't appear as being dragged
			dragging = false;
			easing_draw->update();
		}

		if (mb->get_button_index() == BUTTON_LEFT) {
			dragging = mb->is_pressed();
			// Update to display the correct dragging color
			easing_draw->update();
		}
	}

	const Ref<InputEventMouseMotion> mm = p_ev;

	if (dragging && mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) {
		float rel = mm->get_relative().x;
		if (rel == 0) {
			return;
		}

		if (flip) {
			rel = -rel;
		}

		float val = get_edited_object()->get(get_edited_property());
		bool sg = val < 0;
		val = Math::absf(val);

		val = Math::log(val) / Math::log((float)2.0);
		// Logarithmic space.
		val += rel * 0.05;

		val = Math::pow(2.0f, val);
		if (sg) {
			val = -val;
		}

		// 0 is a singularity, but both positive and negative values
		// are otherwise allowed. Enforce 0+ as workaround.
		if (Math::is_zero_approx(val)) {
			val = 0.00001;
		}

		// Limit to a reasonable value to prevent the curve going into infinity,
		// which can cause crashes and other issues.
		val = CLAMP(val, -1000000, 1000000);

		emit_changed(get_edited_property(), val);
		easing_draw->update();
	}
}

void EditorPropertyEasing::_draw_easing() {
	RID ci = easing_draw->get_canvas_item();

	Size2 s = easing_draw->get_size();

	const int points = 48;

	float prev = 1.0;
	const float exp = get_edited_object()->get(get_edited_property());

	const Ref<Font> f = get_font("font", "Label");
	const Color font_color = get_color("font_color", "Label");
	Color line_color;
	if (dragging) {
		line_color = get_color("accent_color", "Editor");
	} else {
		line_color = get_color("font_color", "Label") * Color(1, 1, 1, 0.9);
	}

	Vector<Point2> lines;
	for (int i = 1; i <= points; i++) {
		float ifl = i / float(points);
		float iflp = (i - 1) / float(points);

		const float h = 1.0 - Math::ease(ifl, exp);

		if (flip) {
			ifl = 1.0 - ifl;
			iflp = 1.0 - iflp;
		}

		lines.push_back(Point2(ifl * s.width, h * s.height));
		lines.push_back(Point2(iflp * s.width, prev * s.height));
		prev = h;
	}

	easing_draw->draw_multiline(lines, line_color, 1.0, true);
	// Draw more decimals for small numbers since higher precision is usually required for fine adjustments.
	int decimals;
	if (Math::abs(exp) < 0.1 - CMP_EPSILON) {
		decimals = 4;
	} else if (Math::abs(exp) < 1 - CMP_EPSILON) {
		decimals = 3;
	} else if (Math::abs(exp) < 10 - CMP_EPSILON) {
		decimals = 2;
	} else {
		decimals = 1;
	}
	f->draw(ci, Point2(10, 10 + f->get_ascent()), rtos(exp).pad_decimals(decimals), font_color);
}

void EditorPropertyEasing::update_property() {
	easing_draw->update();
}

void EditorPropertyEasing::_set_preset(int p_preset) {
	static const float preset_value[EASING_MAX] = { 0.0, 1.0, 2.0, 0.5, -2.0, -0.5 };

	emit_changed(get_edited_property(), preset_value[p_preset]);
	easing_draw->update();
}

void EditorPropertyEasing::_setup_spin() {
	setting = true;
	spin->setup_and_show();
	spin->get_line_edit()->set_text(rtos(get_edited_object()->get(get_edited_property())));
	setting = false;<commit_msg>Make easing properties drawn with anti aliasing<commit_after>/*************************************************************************/
/*  editor_properties.cpp                                                */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2021 <NAME>, <NAME>.                 */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* 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 "editor_properties.h"

#include "editor/editor_resource_preview.h"
#include "editor/filesystem_dock.h"
#include "editor_node.h"
#include "editor_properties_array_dict.h"
#include "editor_scale.h"
#include "scene/main/viewport.h"

///////////////////// NULL /////////////////////////

void EditorPropertyNil::update_property() {
}

EditorPropertyNil::EditorPropertyNil() {
	Label *label = memnew(Label);
	label->set_text("[null]");
	add_child(label);
}

///////////////////// TEXT /////////////////////////

void EditorPropertyText::_text_entered(const String &p_string) {
	if (updating) {
		return;
	}

	if (text->has_focus()) {
		text->release_focus();
		_text_changed(p_string);
	}
}

void EditorPropertyText::_text_changed(const String &p_string) {
	if (updating) {
		return;
	}

	emit_changed(get_edited_property(), p_string, "", true);
}

void EditorPropertyText::update_property() {
	String s = get_edited_object()->get(get_edited_property());
	updating = true;
	if (text->get_text() != s) {
		text->set_text(s);
	}
	text->set_editable(!is_read_only());
	updating = false;
}

void EditorPropertyText::set_placeholder(const String &p_string) {
	text->set_placeholder(p_string);
}

void EditorPropertyText::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_text_changed", "txt"), &EditorPropertyText::_text_changed);
	ClassDB::bind_method(D_METHOD("_text_entered", "txt"), &EditorPropertyText::_text_entered);
}

EditorPropertyText::EditorPropertyText() {
	text = memnew(LineEdit);
	add_child(text);
	add_focusable(text);
	text->connect("text_changed", this, "_text_changed");
	text->connect("text_entered", this, "_text_entered");

	updating = false;
}

///////////////////// MULTILINE TEXT /////////////////////////

void EditorPropertyMultilineText::_big_text_changed() {
	text->set_text(big_text->get_text());
	emit_changed(get_edited_property(), big_text->get_text(), "", true);
}

void EditorPropertyMultilineText::_text_changed() {
	emit_changed(get_edited_property(), text->get_text(), "", true);
}

void EditorPropertyMultilineText::_open_big_text() {
	if (!big_text_dialog) {
		big_text = memnew(TextEdit);
		big_text->connect("text_changed", this, "_big_text_changed");
		big_text->set_wrap_enabled(true);
		big_text_dialog = memnew(AcceptDialog);
		big_text_dialog->add_child(big_text);
		big_text_dialog->set_title(TTR("Edit Text:"));
		add_child(big_text_dialog);
	}

	big_text_dialog->popup_centered_clamped(Size2(1000, 900) * EDSCALE, 0.8);
	big_text->set_text(text->get_text());
	big_text->grab_focus();
}

void EditorPropertyMultilineText::update_property() {
	String t = get_edited_object()->get(get_edited_property());
	if (text->get_text() != t) {
		text->set_text(t);
		if (big_text && big_text->is_visible_in_tree()) {
			big_text->set_text(t);
		}
	}
}

void EditorPropertyMultilineText::_notification(int p_what) {
	switch (p_what) {
		case NOTIFICATION_THEME_CHANGED:
		case NOTIFICATION_ENTER_TREE: {
			Ref<Texture> df = get_icon("DistractionFree", "EditorIcons");
			open_big_text->set_icon(df);
			Ref<Font> font = get_font("font", "Label");
			text->set_custom_minimum_size(Vector2(0, font->get_height() * 6));

		} break;
	}
}

void EditorPropertyMultilineText::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_text_changed"), &EditorPropertyMultilineText::_text_changed);
	ClassDB::bind_method(D_METHOD("_big_text_changed"), &EditorPropertyMultilineText::_big_text_changed);
	ClassDB::bind_method(D_METHOD("_open_big_text"), &EditorPropertyMultilineText::_open_big_text);
}

EditorPropertyMultilineText::EditorPropertyMultilineText() {
	HBoxContainer *hb = memnew(HBoxContainer);
	add_child(hb);
	set_bottom_editor(hb);
	text = memnew(TextEdit);
	text->connect("text_changed", this, "_text_changed");
	text->set_wrap_enabled(true);
	add_focusable(text);
	hb->add_child(text);
	text->set_h_size_flags(SIZE_EXPAND_FILL);
	open_big_text = memnew(ToolButton);
	open_big_text->connect("pressed", this, "_open_big_text");
	hb->add_child(open_big_text);
	big_text_dialog = nullptr;
	big_text = nullptr;
}

///////////////////// TEXT ENUM /////////////////////////

void EditorPropertyTextEnum::_option_selected(int p_which) {
	emit_changed(get_edited_property(), options->get_item_text(p_which));
}

void EditorPropertyTextEnum::update_property() {
	String which = get_edited_object()->get(get_edited_property());
	for (int i = 0; i < options->get_item_count(); i++) {
		String t = options->get_item_text(i);
		if (t == which) {
			options->select(i);
			return;
		}
	}
}

void EditorPropertyTextEnum::setup(const Vector<String> &p_options) {
	for (int i = 0; i < p_options.size(); i++) {
		options->add_item(p_options[i], i);
	}
}

void EditorPropertyTextEnum::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyTextEnum::_option_selected);
}

EditorPropertyTextEnum::EditorPropertyTextEnum() {
	options = memnew(OptionButton);
	options->set_clip_text(true);
	options->set_flat(true);

	add_child(options);
	add_focusable(options);
	options->connect("item_selected", this, "_option_selected");
}
///////////////////// PATH /////////////////////////

void EditorPropertyPath::_path_selected(const String &p_path) {
	emit_changed(get_edited_property(), p_path);
	update_property();
}
void EditorPropertyPath::_path_pressed() {
	if (!dialog) {
		dialog = memnew(EditorFileDialog);
		dialog->connect("file_selected", this, "_path_selected");
		dialog->connect("dir_selected", this, "_path_selected");
		add_child(dialog);
	}

	String full_path = get_edited_object()->get(get_edited_property());

	dialog->clear_filters();

	if (global) {
		dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
	} else {
		dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
	}

	if (folder) {
		dialog->set_mode(EditorFileDialog::MODE_OPEN_DIR);
		dialog->set_current_dir(full_path);
	} else {
		dialog->set_mode(save_mode ? EditorFileDialog::MODE_SAVE_FILE : EditorFileDialog::MODE_OPEN_FILE);
		for (int i = 0; i < extensions.size(); i++) {
			String e = extensions[i].strip_edges();
			if (e != String()) {
				dialog->add_filter(extensions[i].strip_edges());
			}
		}
		dialog->set_current_path(full_path);
	}

	dialog->popup_centered_ratio();
}

void EditorPropertyPath::update_property() {
	String full_path = get_edited_object()->get(get_edited_property());
	path->set_text(full_path);
	path->set_tooltip(full_path);
}

void EditorPropertyPath::setup(const Vector<String> &p_extensions, bool p_folder, bool p_global) {
	extensions = p_extensions;
	folder = p_folder;
	global = p_global;
}

void EditorPropertyPath::set_save_mode() {
	save_mode = true;
}

void EditorPropertyPath::_notification(int p_what) {
	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
		path_edit->set_icon(get_icon("Folder", "EditorIcons"));
	}
}

void EditorPropertyPath::_path_focus_exited() {
	_path_selected(path->get_text());
}

void EditorPropertyPath::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_path_pressed"), &EditorPropertyPath::_path_pressed);
	ClassDB::bind_method(D_METHOD("_path_selected"), &EditorPropertyPath::_path_selected);
	ClassDB::bind_method(D_METHOD("_path_focus_exited"), &EditorPropertyPath::_path_focus_exited);
}

EditorPropertyPath::EditorPropertyPath() {
	HBoxContainer *path_hb = memnew(HBoxContainer);
	add_child(path_hb);
	path = memnew(LineEdit);
	path_hb->add_child(path);
	path->connect("text_entered", this, "_path_selected");
	path->connect("focus_exited", this, "_path_focus_exited");
	path->set_h_size_flags(SIZE_EXPAND_FILL);

	path_edit = memnew(Button);
	path_edit->set_clip_text(true);
	path_hb->add_child(path_edit);
	add_focusable(path);
	dialog = nullptr;
	path_edit->connect("pressed", this, "_path_pressed");
	folder = false;
	global = false;
	save_mode = false;
}

///////////////////// CLASS NAME /////////////////////////

void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) {
	base_type = p_base_type;
	dialog->set_base_type(base_type);
	selected_type = p_selected_type;
	property->set_text(selected_type);
}

void EditorPropertyClassName::update_property() {
	String s = get_edited_object()->get(get_edited_property());
	property->set_text(s);
	selected_type = s;
}

void EditorPropertyClassName::_property_selected() {
	dialog->popup_create(true);
}

void EditorPropertyClassName::_dialog_created() {
	selected_type = dialog->get_selected_type();
	emit_changed(get_edited_property(), selected_type);
	update_property();
}

void EditorPropertyClassName::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_dialog_created"), &EditorPropertyClassName::_dialog_created);
	ClassDB::bind_method(D_METHOD("_property_selected"), &EditorPropertyClassName::_property_selected);
}

EditorPropertyClassName::EditorPropertyClassName() {
	property = memnew(Button);
	property->set_clip_text(true);
	add_child(property);
	add_focusable(property);
	property->set_text(selected_type);
	property->connect("pressed", this, "_property_selected");
	dialog = memnew(CreateDialog);
	dialog->set_base_type(base_type);
	dialog->connect("create", this, "_dialog_created");
	add_child(dialog);
}

///////////////////// MEMBER /////////////////////////

void EditorPropertyMember::_property_selected(const String &p_selected) {
	emit_changed(get_edited_property(), p_selected);
	update_property();
}

void EditorPropertyMember::_property_select() {
	if (!selector) {
		selector = memnew(PropertySelector);
		selector->connect("selected", this, "_property_selected");
		add_child(selector);
	}

	String current = get_edited_object()->get(get_edited_property());

	if (hint == MEMBER_METHOD_OF_VARIANT_TYPE) {
		Variant::Type type = Variant::NIL;
		for (int i = 0; i < Variant::VARIANT_MAX; i++) {
			if (hint_text == Variant::get_type_name(Variant::Type(i))) {
				type = Variant::Type(i);
			}
		}
		if (type != Variant::NIL) {
			selector->select_method_from_basic_type(type, current);
		}

	} else if (hint == MEMBER_METHOD_OF_BASE_TYPE) {
		selector->select_method_from_base_type(hint_text, current);

	} else if (hint == MEMBER_METHOD_OF_INSTANCE) {
		Object *instance = ObjectDB::get_instance(hint_text.to_int64());
		if (instance) {
			selector->select_method_from_instance(instance, current);
		}

	} else if (hint == MEMBER_METHOD_OF_SCRIPT) {
		Object *obj = ObjectDB::get_instance(hint_text.to_int64());
		if (Object::cast_to<Script>(obj)) {
			selector->select_method_from_script(Object::cast_to<Script>(obj), current);
		}

	} else if (hint == MEMBER_PROPERTY_OF_VARIANT_TYPE) {
		Variant::Type type = Variant::NIL;
		String tname = hint_text;
		if (tname.find(".") != -1) {
			tname = tname.get_slice(".", 0);
		}
		for (int i = 0; i < Variant::VARIANT_MAX; i++) {
			if (tname == Variant::get_type_name(Variant::Type(i))) {
				type = Variant::Type(Variant::Type(i));
			}
		}

		if (type != Variant::NIL) {
			selector->select_property_from_basic_type(type, current);
		}

	} else if (hint == MEMBER_PROPERTY_OF_BASE_TYPE) {
		selector->select_property_from_base_type(hint_text, current);

	} else if (hint == MEMBER_PROPERTY_OF_INSTANCE) {
		Object *instance = ObjectDB::get_instance(hint_text.to_int64());
		if (instance) {
			selector->select_property_from_instance(instance, current);
		}

	} else if (hint == MEMBER_PROPERTY_OF_SCRIPT) {
		Object *obj = ObjectDB::get_instance(hint_text.to_int64());
		if (Object::cast_to<Script>(obj)) {
			selector->select_property_from_script(Object::cast_to<Script>(obj), current);
		}
	}
}

void EditorPropertyMember::setup(Type p_hint, const String &p_hint_text) {
	hint = p_hint;
	hint_text = p_hint_text;
}

void EditorPropertyMember::update_property() {
	String full_path = get_edited_object()->get(get_edited_property());
	property->set_text(full_path);
}

void EditorPropertyMember::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_property_selected"), &EditorPropertyMember::_property_selected);
	ClassDB::bind_method(D_METHOD("_property_select"), &EditorPropertyMember::_property_select);
}

EditorPropertyMember::EditorPropertyMember() {
	selector = nullptr;
	property = memnew(Button);
	property->set_clip_text(true);
	add_child(property);
	add_focusable(property);
	property->connect("pressed", this, "_property_select");
}

///////////////////// CHECK /////////////////////////
void EditorPropertyCheck::_checkbox_pressed() {
	emit_changed(get_edited_property(), checkbox->is_pressed());
}

void EditorPropertyCheck::update_property() {
	bool c = get_edited_object()->get(get_edited_property());
	checkbox->set_pressed(c);
	checkbox->set_disabled(is_read_only());
}

void EditorPropertyCheck::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_checkbox_pressed"), &EditorPropertyCheck::_checkbox_pressed);
}

EditorPropertyCheck::EditorPropertyCheck() {
	checkbox = memnew(CheckBox);
	checkbox->set_text(TTR("On"));
	add_child(checkbox);
	add_focusable(checkbox);
	checkbox->connect("pressed", this, "_checkbox_pressed");
}

///////////////////// ENUM /////////////////////////

void EditorPropertyEnum::_option_selected(int p_which) {
	int64_t val = options->get_item_metadata(p_which);
	emit_changed(get_edited_property(), val);
}

void EditorPropertyEnum::update_property() {
	int64_t which = get_edited_object()->get(get_edited_property());

	for (int i = 0; i < options->get_item_count(); i++) {
		if (which == (int64_t)options->get_item_metadata(i)) {
			options->select(i);
			return;
		}
	}
}

void EditorPropertyEnum::setup(const Vector<String> &p_options) {
	int64_t current_val = 0;
	for (int i = 0; i < p_options.size(); i++) {
		Vector<String> text_split = p_options[i].split(":");
		if (text_split.size() != 1) {
			current_val = text_split[1].to_int64();
		}
		options->add_item(text_split[0]);
		options->set_item_metadata(i, current_val);
		current_val += 1;
	}
}

void EditorPropertyEnum::set_option_button_clip(bool p_enable) {
	options->set_clip_text(p_enable);
}

void EditorPropertyEnum::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyEnum::_option_selected);
}

EditorPropertyEnum::EditorPropertyEnum() {
	options = memnew(OptionButton);
	options->set_clip_text(true);
	options->set_flat(true);
	add_child(options);
	add_focusable(options);
	options->connect("item_selected", this, "_option_selected");
}

///////////////////// FLAGS /////////////////////////

void EditorPropertyFlags::_flag_toggled() {
	uint32_t value = 0;
	for (int i = 0; i < flags.size(); i++) {
		if (flags[i]->is_pressed()) {
			uint32_t val = 1;
			val <<= flag_indices[i];
			value |= val;
		}
	}

	emit_changed(get_edited_property(), value);
}

void EditorPropertyFlags::update_property() {
	uint32_t value = get_edited_object()->get(get_edited_property());

	for (int i = 0; i < flags.size(); i++) {
		uint32_t val = 1;
		val <<= flag_indices[i];
		if (value & val) {
			flags[i]->set_pressed(true);
		} else {
			flags[i]->set_pressed(false);
		}
	}
}

void EditorPropertyFlags::setup(const Vector<String> &p_options) {
	ERR_FAIL_COND(flags.size());

	bool first = true;
	for (int i = 0; i < p_options.size(); i++) {
		String option = p_options[i].strip_edges();
		if (option != "") {
			CheckBox *cb = memnew(CheckBox);
			cb->set_text(option);
			cb->set_clip_text(true);
			cb->connect("pressed", this, "_flag_toggled");
			add_focusable(cb);
			vbox->add_child(cb);
			flags.push_back(cb);
			flag_indices.push_back(i);
			if (first) {
				set_label_reference(cb);
				first = false;
			}
		}
	}
}

void EditorPropertyFlags::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_flag_toggled"), &EditorPropertyFlags::_flag_toggled);
}

EditorPropertyFlags::EditorPropertyFlags() {
	vbox = memnew(VBoxContainer);
	add_child(vbox);
}

///////////////////// LAYERS /////////////////////////

class EditorPropertyLayersGrid : public Control {
	GDCLASS(EditorPropertyLayersGrid, Control);

public:
	uint32_t value;
	Vector<Rect2> flag_rects;
	Vector<String> names;
	Vector<String> tooltips;
	int hovered_index;

	virtual Size2 get_minimum_size() const {
		Ref<Font> font = get_font("font", "Label");
		return Vector2(0, font->get_height() * 2);
	}

	virtual String get_tooltip(const Point2 &p_pos) const {
		for (int i = 0; i < flag_rects.size(); i++) {
			if (i < tooltips.size() && flag_rects[i].has_point(p_pos)) {
				return tooltips[i];
			}
		}
		return String();
	}
	void _gui_input(const Ref<InputEvent> &p_ev) {
		const Ref<InputEventMouseMotion> mm = p_ev;

		if (mm.is_valid()) {
			for (int i = 0; i < flag_rects.size(); i++) {
				if (flag_rects[i].has_point(mm->get_position())) {
					// Used to highlight the hovered flag in the layers grid.
					hovered_index = i;
					update();
					break;
				}
			}
		}

		const Ref<InputEventMouseButton> mb = p_ev;

		if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed() && hovered_index >= 0) {
			// Toggle the flag.
			// We base our choice on the hovered flag, so that it always matches the hovered flag.
			if (value & (1 << hovered_index)) {
				value &= ~(1 << hovered_index);
			} else {
				value |= (1 << hovered_index);
			}

			emit_signal("flag_changed", value);
			update();
		}
	}

	void _notification(int p_what) {
		switch (p_what) {
			case NOTIFICATION_DRAW: {
				Rect2 rect;
				rect.size = get_size();
				flag_rects.clear();

				const int bsize = (rect.size.height * 80 / 100) / 2;
				const int h = bsize * 2 + 1;
				const int vofs = (rect.size.height - h) / 2;

				Color color = get_color("highlight_color", "Editor");
				for (int i = 0; i < 2; i++) {
					Point2 ofs(4, vofs);
					if (i == 1) {
						ofs.y += bsize + 1;
					}

					ofs += rect.position;
					for (int j = 0; j < 10; j++) {
						Point2 o = ofs + Point2(j * (bsize + 1), 0);
						if (j >= 5) {
							o.x += 1;
						}

						const int idx = i * 10 + j;
						const bool on = value & (1 << idx);
						Rect2 rect2 = Rect2(o, Size2(bsize, bsize));

						color.a = on ? 0.6 : 0.2;
						if (idx == hovered_index) {
							// Add visual feedback when hovering a flag.
							color.a += 0.15;
						}

						draw_rect(rect2, color);
						flag_rects.push_back(rect2);
					}
				}
			} break;
			case NOTIFICATION_MOUSE_EXIT: {
				hovered_index = -1;
				update();
			} break;
			default:
				break;
		}
	}

	void set_flag(uint32_t p_flag) {
		value = p_flag;
		update();
	}

	static void _bind_methods() {
		ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input);
		ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag")));
	}

	EditorPropertyLayersGrid() {
		value = 0;
		hovered_index = -1; // Nothing is hovered.
	}
};
void EditorPropertyLayers::_grid_changed(uint32_t p_grid) {
	emit_changed(get_edited_property(), p_grid);
}

void EditorPropertyLayers::update_property() {
	uint32_t value = get_edited_object()->get(get_edited_property());

	grid->set_flag(value);
}

void EditorPropertyLayers::setup(LayerType p_layer_type) {
	String basename;
	switch (p_layer_type) {
		case LAYER_RENDER_2D:
			basename = "layer_names/2d_render";
			break;
		case LAYER_PHYSICS_2D:
			basename = "layer_names/2d_physics";
			break;
		case LAYER_RENDER_3D:
			basename = "layer_names/3d_render";
			break;
		case LAYER_PHYSICS_3D:
			basename = "layer_names/3d_physics";
			break;
	}

	Vector<String> names;
	Vector<String> tooltips;
	for (int i = 0; i < 20; i++) {
		String name;

		if (ProjectSettings::get_singleton()->has_setting(basename + "/layer_" + itos(i + 1))) {
			name = ProjectSettings::get_singleton()->get(basename + "/layer_" + itos(i + 1));
		}

		if (name == "") {
			name = TTR("Layer") + " " + itos(i + 1);
		}

		names.push_back(name);
		tooltips.push_back(name + "\n" + vformat(TTR("Bit %d, value %d"), i, 1 << i));
	}

	grid->names = names;
	grid->tooltips = tooltips;
}

void EditorPropertyLayers::_button_pressed() {
	layers->clear();
	for (int i = 0; i < 20; i++) {
		if (i == 5 || i == 10 || i == 15) {
			layers->add_separator();
		}
		layers->add_check_item(grid->names[i], i);
		int idx = layers->get_item_index(i);
		layers->set_item_checked(idx, grid->value & (1 << i));
	}

	Rect2 gp = button->get_global_rect();
	layers->set_as_minsize();
	Vector2 popup_pos = gp.position - Vector2(layers->get_combined_minimum_size().x, 0);
	layers->set_global_position(popup_pos);
	layers->popup();
}

void EditorPropertyLayers::_menu_pressed(int p_menu) {
	if (grid->value & (1 << p_menu)) {
		grid->value &= ~(1 << p_menu);
	} else {
		grid->value |= (1 << p_menu);
	}
	grid->update();
	layers->set_item_checked(layers->get_item_index(p_menu), grid->value & (1 << p_menu));
	_grid_changed(grid->value);
}

void EditorPropertyLayers::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_grid_changed"), &EditorPropertyLayers::_grid_changed);
	ClassDB::bind_method(D_METHOD("_button_pressed"), &EditorPropertyLayers::_button_pressed);
	ClassDB::bind_method(D_METHOD("_menu_pressed"), &EditorPropertyLayers::_menu_pressed);
}

EditorPropertyLayers::EditorPropertyLayers() {
	HBoxContainer *hb = memnew(HBoxContainer);
	add_child(hb);
	grid = memnew(EditorPropertyLayersGrid);
	grid->connect("flag_changed", this, "_grid_changed");
	grid->set_h_size_flags(SIZE_EXPAND_FILL);
	hb->add_child(grid);
	button = memnew(Button);
	button->set_toggle_mode(true);
	button->set_text("...");
	button->connect("pressed", this, "_button_pressed");
	hb->add_child(button);
	set_bottom_editor(hb);
	layers = memnew(PopupMenu);
	add_child(layers);
	layers->set_hide_on_checkable_item_selection(false);
	layers->connect("id_pressed", this, "_menu_pressed");
	layers->connect("popup_hide", button, "set_pressed", varray(false));
}

///////////////////// INT /////////////////////////

void EditorPropertyInteger::_value_changed(int64_t val) {
	if (setting) {
		return;
	}
	emit_changed(get_edited_property(), val);
}

void EditorPropertyInteger::update_property() {
	int64_t val = get_edited_object()->get(get_edited_property());
	setting = true;
	spin->set_value(val);
	setting = false;
#ifdef DEBUG_ENABLED
	// If spin (currently EditorSplinSlider : Range) is changed so that it can use int64_t, then the below warning wouldn't be a problem.
	if (val != (int64_t)(double)(val)) {
		WARN_PRINT("Cannot reliably represent '" + itos(val) + "' in the inspector, value is too large.");
	}
#endif
}

void EditorPropertyInteger::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyInteger::_value_changed);
}

void EditorPropertyInteger::setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_allow_greater, bool p_allow_lesser) {
	spin->set_min(p_min);
	spin->set_max(p_max);
	spin->set_step(p_step);
	spin->set_allow_greater(p_allow_greater);
	spin->set_allow_lesser(p_allow_lesser);
}

EditorPropertyInteger::EditorPropertyInteger() {
	spin = memnew(EditorSpinSlider);
	spin->set_flat(true);
	add_child(spin);
	add_focusable(spin);
	spin->connect("value_changed", this, "_value_changed");
	setting = false;
}

///////////////////// OBJECT ID /////////////////////////

void EditorPropertyObjectID::_edit_pressed() {
	emit_signal("object_id_selected", get_edited_property(), get_edited_object()->get(get_edited_property()));
}

void EditorPropertyObjectID::update_property() {
	String type = base_type;
	if (type == "") {
		type = "Object";
	}

	ObjectID id = get_edited_object()->get(get_edited_property());
	if (id != 0) {
		edit->set_text(type + " ID: " + itos(id));
		edit->set_disabled(false);
		edit->set_icon(EditorNode::get_singleton()->get_class_icon(type));
	} else {
		edit->set_text(TTR("[Empty]"));
		edit->set_disabled(true);
		edit->set_icon(Ref<Texture>());
	}
}

void EditorPropertyObjectID::setup(const String &p_base_type) {
	base_type = p_base_type;
}

void EditorPropertyObjectID::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_edit_pressed"), &EditorPropertyObjectID::_edit_pressed);
}

EditorPropertyObjectID::EditorPropertyObjectID() {
	edit = memnew(Button);
	add_child(edit);
	add_focusable(edit);
	edit->connect("pressed", this, "_edit_pressed");
}

///////////////////// FLOAT /////////////////////////

void EditorPropertyFloat::_value_changed(double val) {
	if (setting) {
		return;
	}

	emit_changed(get_edited_property(), val);
}

void EditorPropertyFloat::update_property() {
	double val = get_edited_object()->get(get_edited_property());
	setting = true;
	spin->set_value(val);
	setting = false;
}

void EditorPropertyFloat::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyFloat::_value_changed);
}

void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range, bool p_greater, bool p_lesser) {
	spin->set_min(p_min);
	spin->set_max(p_max);
	spin->set_step(p_step);
	spin->set_hide_slider(p_no_slider);
	spin->set_exp_ratio(p_exp_range);
	spin->set_allow_greater(p_greater);
	spin->set_allow_lesser(p_lesser);
}

EditorPropertyFloat::EditorPropertyFloat() {
	spin = memnew(EditorSpinSlider);
	spin->set_flat(true);
	add_child(spin);
	add_focusable(spin);
	spin->connect("value_changed", this, "_value_changed");
	setting = false;
}

///////////////////// EASING /////////////////////////

void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) {
	const Ref<InputEventMouseButton> mb = p_ev;
	if (mb.is_valid()) {
		if (mb->is_doubleclick() && mb->get_button_index() == BUTTON_LEFT) {
			_setup_spin();
		}

		if (mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) {
			preset->set_global_position(easing_draw->get_global_transform().xform(mb->get_position()));
			preset->popup();

			// Ensure the easing doesn't appear as being dragged
			dragging = false;
			easing_draw->update();
		}

		if (mb->get_button_index() == BUTTON_LEFT) {
			dragging = mb->is_pressed();
			// Update to display the correct dragging color
			easing_draw->update();
		}
	}

	const Ref<InputEventMouseMotion> mm = p_ev;

	if (dragging && mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) {
		float rel = mm->get_relative().x;
		if (rel == 0) {
			return;
		}

		if (flip) {
			rel = -rel;
		}

		float val = get_edited_object()->get(get_edited_property());
		bool sg = val < 0;
		val = Math::absf(val);

		val = Math::log(val) / Math::log((float)2.0);
		// Logarithmic space.
		val += rel * 0.05;

		val = Math::pow(2.0f, val);
		if (sg) {
			val = -val;
		}

		// 0 is a singularity, but both positive and negative values
		// are otherwise allowed. Enforce 0+ as workaround.
		if (Math::is_zero_approx(val)) {
			val = 0.00001;
		}

		// Limit to a reasonable value to prevent the curve going into infinity,
		// which can cause crashes and other issues.
		val = CLAMP(val, -1000000, 1000000);

		emit_changed(get_edited_property(), val);
		easing_draw->update();
	}
}

void EditorPropertyEasing::_draw_easing() {
	RID ci = easing_draw->get_canvas_item();

	Size2 s = easing_draw->get_size();

	const int point_count = 48;

	const float exp = get_edited_object()->get(get_edited_property());

	const Ref<Font> f = get_font("font", "Label");
	const Color font_color = get_color("font_color", "Label");
	Color line_color;
	if (dragging) {
		line_color = get_color("accent_color", "Editor");
	} else {
		line_color = get_color("font_color", "Label") * Color(1, 1, 1, 0.9);
	}

	Vector<Point2> points;
	for (int i = 0; i <= point_count; i++) {
		float ifl = i / float(point_count);

		const float h = 1.0 - Math::ease(ifl, exp);

		if (flip) {
			ifl = 1.0 - ifl;
		}

		points.push_back(Point2(ifl * s.width, h * s.height));
	}

	easing_draw->draw_polyline(points, line_color, 1.0, true);
	// Draw more decimals for small numbers since higher precision is usually required for fine adjustments.
	int decimals;
	if (Math::abs(exp) < 0.1 - CMP_EPSILON) {
		decimals = 4;
	} else if (Math::abs(exp) < 1 - CMP_EPSILON) {
		decimals = 3;
	} else if (Math::abs(exp) < 10 - CMP_EPSILON) {
		decimals = 2;
	} else {
		decimals = 1;
	}
	f->draw(ci, Point2(10, 10 + f->get_ascent()), rtos(exp).pad_decimals(decimals), font_color);
}

void EditorPropertyEasing::update_property() {
	easing_draw->update();
}

void EditorPropertyEasing::_set_preset(int p_preset) {
	static const float preset_value[EASING_MAX] = { 0.0, 1.0, 2.0, 0.5, -2.0, -0.5 };

	emit_changed(get_edited_property(), preset_value[p_preset]);
	easing_draw->update();
}

void EditorPropertyEasing::_setup_spin() {
	setting = true;
	spin->setup_and_show();
	spin->get_line_edit()->set_text(rtos(get_edited_object()->get(get_edited_property())));
	setting = false;
{'fraction_non_alphanumeric': 0.17565019505851756, 'fraction_numerical': 0.008550065019505853, 'mean_word_length': 4.369730295888976, 'pattern_counts': {'":': 2, '<': 151, '<?xml version=': 0, '>': 497, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4093992', 'n_tokens_mistral': 22521, 'n_tokens_neox': 20804, 'n_words': 3802}