initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
editor/scene/texture/SCsub
Normal file
6
editor/scene/texture/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
122
editor/scene/texture/bit_map_editor_plugin.cpp
Normal file
122
editor/scene/texture/bit_map_editor_plugin.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/**************************************************************************/
|
||||
/* bit_map_editor_plugin.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 "bit_map_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/aspect_ratio_container.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
|
||||
void BitMapEditor::setup(const Ref<BitMap> &p_bitmap) {
|
||||
Ref<ImageTexture> bitmap_texture = ImageTexture::create_from_image(p_bitmap->convert_to_image());
|
||||
texture_rect->set_texture(bitmap_texture);
|
||||
if (bitmap_texture.is_valid()) {
|
||||
centering_container->set_custom_minimum_size(Size2(0, 250) * EDSCALE);
|
||||
centering_container->set_ratio(bitmap_texture->get_size().aspect());
|
||||
outline_overlay->connect(SceneStringName(draw), callable_mp(this, &BitMapEditor::_draw_outline));
|
||||
}
|
||||
size_label->set_text(vformat(U"%s×%s", p_bitmap->get_size().width, p_bitmap->get_size().height));
|
||||
}
|
||||
|
||||
void BitMapEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
cached_outline_color = get_theme_color(SNAME("extra_border_color_1"), EditorStringName(Editor));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void BitMapEditor::_draw_outline() {
|
||||
const float outline_width = Math::round(EDSCALE);
|
||||
const Rect2 outline_rect = Rect2(Vector2(), texture_rect->get_size()).grow(outline_width * 0.5);
|
||||
outline_overlay->draw_rect(outline_rect, cached_outline_color, false, outline_width);
|
||||
}
|
||||
|
||||
BitMapEditor::BitMapEditor() {
|
||||
MarginContainer *margin_container = memnew(MarginContainer);
|
||||
const float outline_width = Math::round(EDSCALE);
|
||||
margin_container->add_theme_constant_override("margin_right", outline_width);
|
||||
margin_container->add_theme_constant_override("margin_top", outline_width);
|
||||
margin_container->add_theme_constant_override("margin_left", outline_width);
|
||||
margin_container->add_theme_constant_override("margin_bottom", outline_width);
|
||||
add_child(margin_container);
|
||||
|
||||
centering_container = memnew(AspectRatioContainer);
|
||||
margin_container->add_child(centering_container);
|
||||
|
||||
texture_rect = memnew(TextureRect);
|
||||
texture_rect->set_texture_filter(TEXTURE_FILTER_NEAREST);
|
||||
texture_rect->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
centering_container->add_child(texture_rect);
|
||||
|
||||
outline_overlay = memnew(Control);
|
||||
centering_container->add_child(outline_overlay);
|
||||
|
||||
size_label = memnew(Label);
|
||||
size_label->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
size_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
|
||||
add_child(size_label);
|
||||
|
||||
// Reduce extra padding on top and bottom of size label.
|
||||
Ref<StyleBoxEmpty> stylebox;
|
||||
stylebox.instantiate();
|
||||
stylebox->set_content_margin(SIDE_RIGHT, 4 * EDSCALE);
|
||||
size_label->add_theme_style_override(CoreStringName(normal), stylebox);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
bool EditorInspectorPluginBitMap::can_handle(Object *p_object) {
|
||||
return Object::cast_to<BitMap>(p_object) != nullptr;
|
||||
}
|
||||
|
||||
void EditorInspectorPluginBitMap::parse_begin(Object *p_object) {
|
||||
BitMap *bitmap = Object::cast_to<BitMap>(p_object);
|
||||
if (!bitmap) {
|
||||
return;
|
||||
}
|
||||
Ref<BitMap> bm(bitmap);
|
||||
|
||||
BitMapEditor *editor = memnew(BitMapEditor);
|
||||
editor->setup(bm);
|
||||
add_custom_control(editor);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
BitMapEditorPlugin::BitMapEditorPlugin() {
|
||||
Ref<EditorInspectorPluginBitMap> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
75
editor/scene/texture/bit_map_editor_plugin.h
Normal file
75
editor/scene/texture/bit_map_editor_plugin.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/**************************************************************************/
|
||||
/* bit_map_editor_plugin.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/resources/bit_map.h"
|
||||
|
||||
class AspectRatioContainer;
|
||||
class TextureRect;
|
||||
|
||||
class BitMapEditor : public VBoxContainer {
|
||||
GDCLASS(BitMapEditor, VBoxContainer);
|
||||
|
||||
private:
|
||||
AspectRatioContainer *centering_container = nullptr;
|
||||
Control *outline_overlay = nullptr;
|
||||
TextureRect *texture_rect = nullptr;
|
||||
Label *size_label = nullptr;
|
||||
|
||||
Color cached_outline_color;
|
||||
|
||||
void _draw_outline();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void setup(const Ref<BitMap> &p_bitmap);
|
||||
|
||||
BitMapEditor();
|
||||
};
|
||||
|
||||
class EditorInspectorPluginBitMap : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginBitMap, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class BitMapEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(BitMapEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
BitMapEditorPlugin();
|
||||
};
|
148
editor/scene/texture/color_channel_selector.cpp
Normal file
148
editor/scene/texture/color_channel_selector.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
/**************************************************************************/
|
||||
/* color_channel_selector.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 "color_channel_selector.h"
|
||||
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/resources/style_box_flat.h"
|
||||
|
||||
ColorChannelSelector::ColorChannelSelector() {
|
||||
toggle_button = memnew(Button);
|
||||
toggle_button->set_flat(true);
|
||||
toggle_button->set_toggle_mode(true);
|
||||
toggle_button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_toggled));
|
||||
toggle_button->set_tooltip_text(TTRC("Toggle color channel preview selection."));
|
||||
toggle_button->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);
|
||||
toggle_button->set_theme_type_variation("PreviewLightButton");
|
||||
add_child(toggle_button);
|
||||
|
||||
panel = memnew(PanelContainer);
|
||||
panel->hide();
|
||||
|
||||
HBoxContainer *container = memnew(HBoxContainer);
|
||||
container->add_theme_constant_override("separation", 0);
|
||||
|
||||
create_button(0, "R", container);
|
||||
create_button(1, "G", container);
|
||||
create_button(2, "B", container);
|
||||
create_button(3, "A", container);
|
||||
|
||||
// Use a bit of transparency to be less distracting.
|
||||
set_modulate(Color(1, 1, 1, 0.7));
|
||||
|
||||
panel->add_child(container);
|
||||
|
||||
add_child(panel);
|
||||
}
|
||||
|
||||
void ColorChannelSelector::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_THEME_CHANGED) {
|
||||
// PanelContainer's background is invisible in the editor. We need a background.
|
||||
// And we need this in turn because buttons don't look good without background (for example, hover is transparent).
|
||||
Ref<StyleBox> bg_style = get_theme_stylebox(SceneStringName(panel), "TabContainer");
|
||||
ERR_FAIL_COND(bg_style.is_null());
|
||||
bg_style = bg_style->duplicate();
|
||||
// The default content margin makes the widget become a bit too large. It should be like mini-toolbar.
|
||||
bg_style->set_content_margin(SIDE_LEFT, 1.0f * EDSCALE);
|
||||
bg_style->set_content_margin(SIDE_RIGHT, 1.0f * EDSCALE);
|
||||
bg_style->set_content_margin(SIDE_TOP, 1.0f * EDSCALE);
|
||||
bg_style->set_content_margin(SIDE_BOTTOM, 1.0f * EDSCALE);
|
||||
panel->add_theme_style_override(SceneStringName(panel), bg_style);
|
||||
|
||||
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("TexturePreviewChannels"));
|
||||
toggle_button->set_button_icon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorChannelSelector::set_available_channels_mask(uint32_t p_mask) {
|
||||
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
|
||||
const bool available = (p_mask & (1u << i)) != 0;
|
||||
Button *button = channel_buttons[i];
|
||||
button->set_visible(available);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorChannelSelector::on_channel_button_toggled(bool p_unused_pressed) {
|
||||
emit_signal("selected_channels_changed");
|
||||
}
|
||||
|
||||
uint32_t ColorChannelSelector::get_selected_channels_mask() const {
|
||||
uint32_t mask = 0;
|
||||
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
|
||||
Button *button = channel_buttons[i];
|
||||
if (button->is_visible() && channel_buttons[i]->is_pressed()) {
|
||||
mask |= (1 << i);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Helper
|
||||
Vector4 ColorChannelSelector::get_selected_channel_factors() const {
|
||||
Vector4 channel_factors;
|
||||
const uint32_t mask = get_selected_channels_mask();
|
||||
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
|
||||
if ((mask & (1 << i)) != 0) {
|
||||
channel_factors[i] = 1;
|
||||
}
|
||||
}
|
||||
return channel_factors;
|
||||
}
|
||||
|
||||
void ColorChannelSelector::create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent) {
|
||||
ERR_FAIL_COND(p_channel_index >= CHANNEL_COUNT);
|
||||
ERR_FAIL_COND(channel_buttons[p_channel_index] != nullptr);
|
||||
Button *button = memnew(Button);
|
||||
button->set_text(p_text);
|
||||
button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
button->set_toggle_mode(true);
|
||||
button->set_pressed(true);
|
||||
|
||||
// Don't show focus, it stands out too much and remains visible which can be confusing.
|
||||
button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
|
||||
|
||||
// Make it look similar to toolbar buttons.
|
||||
button->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
|
||||
button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_channel_button_toggled));
|
||||
p_parent->add_child(button);
|
||||
channel_buttons[p_channel_index] = button;
|
||||
}
|
||||
|
||||
void ColorChannelSelector::on_toggled(bool p_pressed) {
|
||||
panel->set_visible(p_pressed);
|
||||
}
|
||||
|
||||
void ColorChannelSelector::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("selected_channels_changed"));
|
||||
}
|
62
editor/scene/texture/color_channel_selector.h
Normal file
62
editor/scene/texture/color_channel_selector.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/**************************************************************************/
|
||||
/* color_channel_selector.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
|
||||
class PanelContainer;
|
||||
class Button;
|
||||
|
||||
class ColorChannelSelector : public HBoxContainer {
|
||||
GDCLASS(ColorChannelSelector, HBoxContainer);
|
||||
|
||||
static const unsigned int CHANNEL_COUNT = 4;
|
||||
|
||||
public:
|
||||
ColorChannelSelector();
|
||||
|
||||
void set_available_channels_mask(uint32_t p_mask);
|
||||
uint32_t get_selected_channels_mask() const;
|
||||
Vector4 get_selected_channel_factors() const;
|
||||
|
||||
private:
|
||||
void _notification(int p_what);
|
||||
|
||||
void on_channel_button_toggled(bool p_unused_pressed);
|
||||
void create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent);
|
||||
void on_toggled(bool p_pressed);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
Button *channel_buttons[CHANNEL_COUNT] = {};
|
||||
PanelContainer *panel = nullptr;
|
||||
Button *toggle_button = nullptr;
|
||||
};
|
333
editor/scene/texture/gradient_texture_2d_editor_plugin.cpp
Normal file
333
editor/scene/texture/gradient_texture_2d_editor_plugin.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
/**************************************************************************/
|
||||
/* gradient_texture_2d_editor_plugin.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_texture_2d_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/gui/editor_spin_slider.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/flow_container.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/resources/gradient_texture.h"
|
||||
|
||||
Point2 GradientTexture2DEdit::_get_handle_pos(const Handle p_handle) {
|
||||
// Get the handle's mouse position in pixels relative to offset.
|
||||
return (p_handle == HANDLE_FROM ? texture->get_fill_from() : texture->get_fill_to()).clampf(0, 1) * size;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void GradientTexture2DEdit::set_fill_pos(const Vector2 &p_pos) {
|
||||
if (p_pos.is_equal_approx(initial_grab_pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const StringName property_name = (grabbed == HANDLE_FROM) ? "fill_from" : "fill_to";
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
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 GradientTexture2DEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
const Ref<InputEventMouseButton> mb = p_event;
|
||||
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 {
|
||||
// Release the handle.
|
||||
if (grabbed != HANDLE_NONE) {
|
||||
set_fill_pos(_get_handle_pos(grabbed) / size);
|
||||
grabbed = HANDLE_NONE;
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
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).clampf(0, 1);
|
||||
if (snap_enabled || mm->is_command_or_control_pressed()) {
|
||||
new_pos = new_pos.snappedf(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 GradientTexture2DEdit::set_texture(Ref<GradientTexture2D> &p_texture) {
|
||||
texture = p_texture;
|
||||
texture->connect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
|
||||
}
|
||||
|
||||
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 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 GradientTexture2DEdit::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_MOUSE_EXIT: {
|
||||
if (hovered != HANDLE_NONE) {
|
||||
hovered = HANDLE_NONE;
|
||||
queue_redraw();
|
||||
}
|
||||
} break;
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
checkerboard->set_texture(get_editor_theme_icon(SNAME("GuiMiniCheckerboard")));
|
||||
} break;
|
||||
case NOTIFICATION_DRAW: {
|
||||
_draw();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void GradientTexture2DEdit::_draw() {
|
||||
if (texture.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Ref<Texture2D> fill_from_icon = get_editor_theme_icon(SNAME("EditorPathSmoothHandle"));
|
||||
const Ref<Texture2D> fill_to_icon = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
|
||||
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 || (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && grabbed != HANDLE_NONE)) {
|
||||
const Color line_color = Color(0.5, 0.5, 0.5, 0.5);
|
||||
|
||||
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.4, 1, 1);
|
||||
bool modulate_handle_from = grabbed == HANDLE_FROM || hovered == HANDLE_FROM;
|
||||
bool modulate_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));
|
||||
}
|
||||
|
||||
GradientTexture2DEdit::GradientTexture2DEdit() {
|
||||
checkerboard = memnew(TextureRect);
|
||||
checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE);
|
||||
checkerboard->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
checkerboard->set_draw_behind_parent(true);
|
||||
add_child(checkerboard, false, INTERNAL_MODE_FRONT);
|
||||
|
||||
set_custom_minimum_size(Size2(0, 250 * EDSCALE));
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
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"));
|
||||
undo_redo->add_do_property(texture.ptr(), "fill_from", texture->get_fill_to());
|
||||
undo_redo->add_do_property(texture.ptr(), "fill_to", texture->get_fill_from());
|
||||
undo_redo->add_undo_property(texture.ptr(), "fill_from", texture->get_fill_from());
|
||||
undo_redo->add_undo_property(texture.ptr(), "fill_to", texture->get_fill_to());
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void GradientTexture2DEditor::_set_snap_enabled(bool p_enabled) {
|
||||
texture_editor_rect->set_snap_enabled(p_enabled);
|
||||
snap_count_edit->set_visible(p_enabled);
|
||||
}
|
||||
|
||||
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) {
|
||||
texture = p_texture;
|
||||
texture_editor_rect->set_texture(p_texture);
|
||||
}
|
||||
|
||||
void GradientTexture2DEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
reverse_button->set_button_icon(get_editor_theme_icon(SNAME("ReverseGradient")));
|
||||
snap_button->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
|
||||
} break;
|
||||
case NOTIFICATION_READY: {
|
||||
if (texture.is_valid()) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
GradientTexture2DEditor::GradientTexture2DEditor() {
|
||||
HFlowContainer *toolbar = memnew(HFlowContainer);
|
||||
add_child(toolbar);
|
||||
|
||||
reverse_button = memnew(Button);
|
||||
reverse_button->set_tooltip_text(TTR("Swap Gradient Fill Points"));
|
||||
toolbar->add_child(reverse_button);
|
||||
reverse_button->connect(SceneStringName(pressed), callable_mp(this, &GradientTexture2DEditor::_reverse_button_pressed));
|
||||
|
||||
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(SceneStringName(toggled), callable_mp(this, &GradientTexture2DEditor::_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_accessibility_name(TTRC("Grid Step"));
|
||||
snap_count_edit->set_custom_minimum_size(Size2(65 * EDSCALE, 0));
|
||||
toolbar->add_child(snap_count_edit);
|
||||
snap_count_edit->connect(SceneStringName(value_changed), callable_mp(this, &GradientTexture2DEditor::_set_snap_count));
|
||||
|
||||
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_count(snap_count_edit->get_value());
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
bool EditorInspectorPluginGradientTexture2D::can_handle(Object *p_object) {
|
||||
return Object::cast_to<GradientTexture2D>(p_object) != nullptr;
|
||||
}
|
||||
|
||||
void EditorInspectorPluginGradientTexture2D::parse_begin(Object *p_object) {
|
||||
GradientTexture2D *texture = Object::cast_to<GradientTexture2D>(p_object);
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
Ref<GradientTexture2D> t(texture);
|
||||
|
||||
GradientTexture2DEditor *editor = memnew(GradientTexture2DEditor);
|
||||
editor->set_texture(t);
|
||||
add_custom_control(editor);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
GradientTexture2DEditorPlugin::GradientTexture2DEditorPlugin() {
|
||||
Ref<EditorInspectorPluginGradientTexture2D> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
119
editor/scene/texture/gradient_texture_2d_editor_plugin.h
Normal file
119
editor/scene/texture/gradient_texture_2d_editor_plugin.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/**************************************************************************/
|
||||
/* gradient_texture_2d_editor_plugin.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
|
||||
class Button;
|
||||
class EditorSpinSlider;
|
||||
class GradientTexture2D;
|
||||
|
||||
class GradientTexture2DEdit : public Control {
|
||||
GDCLASS(GradientTexture2DEdit, Control);
|
||||
|
||||
enum Handle {
|
||||
HANDLE_NONE,
|
||||
HANDLE_FROM,
|
||||
HANDLE_TO
|
||||
};
|
||||
|
||||
Ref<GradientTexture2D> texture;
|
||||
bool snap_enabled = false;
|
||||
int snap_count = 0;
|
||||
|
||||
TextureRect *checkerboard = nullptr;
|
||||
|
||||
Handle hovered = HANDLE_NONE;
|
||||
Handle grabbed = HANDLE_NONE;
|
||||
Point2 initial_grab_pos;
|
||||
|
||||
Size2 handle_size;
|
||||
Point2 offset;
|
||||
Size2 size;
|
||||
|
||||
Point2 _get_handle_pos(const Handle p_handle);
|
||||
Handle get_handle_at(const Vector2 &p_pos);
|
||||
void set_fill_pos(const Vector2 &p_pos);
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
void _draw();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void set_texture(Ref<GradientTexture2D> &p_texture);
|
||||
void set_snap_enabled(bool p_snap_enabled);
|
||||
void set_snap_count(int p_snap_count);
|
||||
|
||||
GradientTexture2DEdit();
|
||||
};
|
||||
|
||||
class GradientTexture2DEditor : public VBoxContainer {
|
||||
GDCLASS(GradientTexture2DEditor, VBoxContainer);
|
||||
|
||||
Ref<GradientTexture2D> texture;
|
||||
|
||||
Button *reverse_button = nullptr;
|
||||
Button *snap_button = nullptr;
|
||||
EditorSpinSlider *snap_count_edit = nullptr;
|
||||
GradientTexture2DEdit *texture_editor_rect = nullptr;
|
||||
|
||||
void _reverse_button_pressed();
|
||||
void _set_snap_enabled(bool p_enabled);
|
||||
void _set_snap_count(int p_snap_count);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static const int DEFAULT_SNAP;
|
||||
void set_texture(Ref<GradientTexture2D> &p_texture);
|
||||
|
||||
GradientTexture2DEditor();
|
||||
};
|
||||
|
||||
class EditorInspectorPluginGradientTexture2D : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginGradientTexture2D, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class GradientTexture2DEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(GradientTexture2DEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
GradientTexture2DEditorPlugin();
|
||||
};
|
313
editor/scene/texture/texture_3d_editor_plugin.cpp
Normal file
313
editor/scene/texture/texture_3d_editor_plugin.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
/**************************************************************************/
|
||||
/* texture_3d_editor_plugin.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 "texture_3d_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/scene/texture/color_channel_selector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/label.h"
|
||||
|
||||
// Shader sources.
|
||||
|
||||
constexpr const char *texture_3d_shader = R"(
|
||||
// Texture3DEditor preview shader.
|
||||
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform sampler3D tex;
|
||||
uniform float layer;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = textureLod(tex, vec3(UV, layer), 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
void Texture3DEditor::_texture_rect_draw() {
|
||||
texture_rect->draw_rect(Rect2(Point2(), texture_rect->get_size()), Color(1, 1, 1, 1));
|
||||
}
|
||||
|
||||
void Texture3DEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_RESIZED: {
|
||||
_texture_rect_update_area();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_DRAW: {
|
||||
Ref<Texture2D> checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
|
||||
draw_texture_rect(checkerboard, texture_rect->get_rect(), true);
|
||||
_draw_outline();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (info) {
|
||||
Ref<Font> metadata_label_font = get_theme_font(SNAME("expression"), EditorStringName(EditorFonts));
|
||||
info->add_theme_font_override(SceneStringName(font), metadata_label_font);
|
||||
}
|
||||
theme_cache.outline_color = get_theme_color(SNAME("extra_border_color_1"), EditorStringName(Editor));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void Texture3DEditor::_texture_changed() {
|
||||
if (!is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setting = true;
|
||||
_update_gui();
|
||||
setting = false;
|
||||
|
||||
_update_material(true);
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void Texture3DEditor::_update_material(bool p_texture_changed) {
|
||||
material->set_shader_parameter("layer", (layer->get_value() + 0.5) / texture->get_depth());
|
||||
|
||||
if (p_texture_changed) {
|
||||
material->set_shader_parameter("tex", texture->get_rid());
|
||||
}
|
||||
|
||||
material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors());
|
||||
}
|
||||
|
||||
void Texture3DEditor::_draw_outline() {
|
||||
const float outline_width = Math::round(EDSCALE);
|
||||
const Rect2 outline_rect = texture_rect->get_rect().grow(outline_width * 0.5);
|
||||
draw_rect(outline_rect, theme_cache.outline_color, false, outline_width);
|
||||
}
|
||||
|
||||
void Texture3DEditor::_make_shaders() {
|
||||
shader.instantiate();
|
||||
shader->set_code(texture_3d_shader);
|
||||
|
||||
material.instantiate();
|
||||
material->set_shader(shader);
|
||||
}
|
||||
|
||||
void Texture3DEditor::_texture_rect_update_area() {
|
||||
Size2 size = get_size();
|
||||
int tex_width = texture->get_width() * size.height / texture->get_height();
|
||||
int tex_height = size.height;
|
||||
|
||||
if (tex_width > size.width) {
|
||||
tex_width = size.width;
|
||||
tex_height = texture->get_height() * tex_width / texture->get_width();
|
||||
}
|
||||
|
||||
// Prevent the texture from being unpreviewable after the rescale, so that we can still see something
|
||||
if (tex_height <= 0) {
|
||||
tex_height = 1;
|
||||
}
|
||||
if (tex_width <= 0) {
|
||||
tex_width = 1;
|
||||
}
|
||||
|
||||
int ofs_x = (size.width - tex_width) / 2;
|
||||
int ofs_y = (size.height - tex_height) / 2;
|
||||
|
||||
texture_rect->set_position(Vector2(ofs_x, ofs_y - Math::round(EDSCALE)));
|
||||
texture_rect->set_size(Vector2(tex_width, tex_height));
|
||||
}
|
||||
|
||||
void Texture3DEditor::_update_gui() {
|
||||
if (texture.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_texture_rect_update_area();
|
||||
|
||||
layer->set_max(texture->get_depth() - 1);
|
||||
|
||||
const Image::Format format = texture->get_format();
|
||||
const String format_name = Image::get_format_name(format);
|
||||
|
||||
if (texture->has_mipmaps()) {
|
||||
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format);
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_depth();
|
||||
|
||||
info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_depth(),
|
||||
format_name,
|
||||
mip_count,
|
||||
String::humanize_size(memory)));
|
||||
|
||||
} else {
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_depth();
|
||||
|
||||
info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_depth(),
|
||||
format_name,
|
||||
String::humanize_size(memory)));
|
||||
}
|
||||
|
||||
const uint32_t components_mask = Image::get_format_component_mask(format);
|
||||
if (is_power_of_2(components_mask)) {
|
||||
// Only one channel available, no point in showing a channel selector.
|
||||
channel_selector->hide();
|
||||
} else {
|
||||
channel_selector->show();
|
||||
channel_selector->set_available_channels_mask(components_mask);
|
||||
}
|
||||
}
|
||||
|
||||
void Texture3DEditor::on_selected_channels_changed() {
|
||||
_update_material(false);
|
||||
}
|
||||
|
||||
void Texture3DEditor::edit(Ref<Texture3D> p_texture) {
|
||||
if (texture.is_valid()) {
|
||||
texture->disconnect_changed(callable_mp(this, &Texture3DEditor::_texture_changed));
|
||||
}
|
||||
|
||||
texture = p_texture;
|
||||
|
||||
if (texture.is_valid()) {
|
||||
if (shader.is_null()) {
|
||||
_make_shaders();
|
||||
}
|
||||
|
||||
texture->connect_changed(callable_mp(this, &Texture3DEditor::_texture_changed));
|
||||
texture_rect->set_material(material);
|
||||
|
||||
setting = true;
|
||||
layer->set_value(0);
|
||||
layer->show();
|
||||
_update_gui();
|
||||
setting = false;
|
||||
|
||||
_update_material(true);
|
||||
queue_redraw();
|
||||
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
Texture3DEditor::Texture3DEditor() {
|
||||
set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
|
||||
set_custom_minimum_size(Size2(1, 256.0) * EDSCALE);
|
||||
|
||||
texture_rect = memnew(Control);
|
||||
texture_rect->set_mouse_filter(MOUSE_FILTER_IGNORE);
|
||||
texture_rect->connect(SceneStringName(draw), callable_mp(this, &Texture3DEditor::_texture_rect_draw));
|
||||
|
||||
add_child(texture_rect);
|
||||
|
||||
layer = memnew(SpinBox);
|
||||
layer->set_step(1);
|
||||
layer->set_max(100);
|
||||
|
||||
layer->set_modulate(Color(1, 1, 1, 0.8));
|
||||
layer->set_h_grow_direction(GROW_DIRECTION_BEGIN);
|
||||
layer->set_anchor(SIDE_RIGHT, 1);
|
||||
layer->set_anchor(SIDE_LEFT, 1);
|
||||
layer->connect(SceneStringName(value_changed), callable_mp(this, &Texture3DEditor::_layer_changed));
|
||||
|
||||
add_child(layer);
|
||||
|
||||
channel_selector = memnew(ColorChannelSelector);
|
||||
channel_selector->connect("selected_channels_changed", callable_mp(this, &Texture3DEditor::on_selected_channels_changed));
|
||||
channel_selector->set_anchors_preset(Control::PRESET_TOP_LEFT);
|
||||
add_child(channel_selector);
|
||||
|
||||
info = memnew(Label);
|
||||
info->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
|
||||
info->add_theme_color_override("font_shadow_color", Color(0, 0, 0));
|
||||
info->add_theme_font_size_override(SceneStringName(font_size), 14 * EDSCALE);
|
||||
info->add_theme_color_override("font_outline_color", Color(0, 0, 0));
|
||||
info->add_theme_constant_override("outline_size", 8 * EDSCALE);
|
||||
|
||||
info->set_h_grow_direction(GROW_DIRECTION_BEGIN);
|
||||
info->set_v_grow_direction(GROW_DIRECTION_BEGIN);
|
||||
info->set_h_size_flags(Control::SIZE_SHRINK_END);
|
||||
info->set_v_size_flags(Control::SIZE_SHRINK_END);
|
||||
info->set_anchor(SIDE_RIGHT, 1);
|
||||
info->set_anchor(SIDE_LEFT, 1);
|
||||
info->set_anchor(SIDE_BOTTOM, 1);
|
||||
info->set_anchor(SIDE_TOP, 1);
|
||||
|
||||
add_child(info);
|
||||
}
|
||||
|
||||
Texture3DEditor::~Texture3DEditor() {
|
||||
if (texture.is_valid()) {
|
||||
texture->disconnect_changed(callable_mp(this, &Texture3DEditor::_texture_changed));
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorInspectorPlugin3DTexture::can_handle(Object *p_object) {
|
||||
return Object::cast_to<Texture3D>(p_object) != nullptr;
|
||||
}
|
||||
|
||||
void EditorInspectorPlugin3DTexture::parse_begin(Object *p_object) {
|
||||
Texture3D *texture = Object::cast_to<Texture3D>(p_object);
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
Ref<Texture3D> m(texture);
|
||||
|
||||
Texture3DEditor *editor = memnew(Texture3DEditor);
|
||||
editor->edit(m);
|
||||
add_custom_control(editor);
|
||||
}
|
||||
|
||||
Texture3DEditorPlugin::Texture3DEditorPlugin() {
|
||||
Ref<EditorInspectorPlugin3DTexture> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
106
editor/scene/texture/texture_3d_editor_plugin.h
Normal file
106
editor/scene/texture/texture_3d_editor_plugin.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/**************************************************************************/
|
||||
/* texture_3d_editor_plugin.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
#include "scene/resources/shader.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
class ColorChannelSelector;
|
||||
|
||||
class Texture3DEditor : public Control {
|
||||
GDCLASS(Texture3DEditor, Control);
|
||||
|
||||
struct ThemeCache {
|
||||
Color outline_color;
|
||||
} theme_cache;
|
||||
|
||||
SpinBox *layer = nullptr;
|
||||
Label *info = nullptr;
|
||||
Ref<Texture3D> texture;
|
||||
|
||||
Ref<Shader> shader;
|
||||
Ref<ShaderMaterial> material;
|
||||
|
||||
Control *texture_rect = nullptr;
|
||||
|
||||
ColorChannelSelector *channel_selector = nullptr;
|
||||
|
||||
bool setting = false;
|
||||
|
||||
void _draw_outline();
|
||||
|
||||
void _make_shaders();
|
||||
|
||||
void _layer_changed(double) {
|
||||
if (!setting) {
|
||||
_update_material(false);
|
||||
}
|
||||
}
|
||||
|
||||
void _texture_changed();
|
||||
|
||||
void _texture_rect_update_area();
|
||||
void _texture_rect_draw();
|
||||
|
||||
void _update_material(bool p_texture_changed);
|
||||
void _update_gui();
|
||||
|
||||
void on_selected_channels_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void edit(Ref<Texture3D> p_texture);
|
||||
|
||||
Texture3DEditor();
|
||||
~Texture3DEditor();
|
||||
};
|
||||
|
||||
class EditorInspectorPlugin3DTexture : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPlugin3DTexture, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class Texture3DEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(Texture3DEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "Texture3D"; }
|
||||
|
||||
Texture3DEditorPlugin();
|
||||
};
|
328
editor/scene/texture/texture_editor_plugin.cpp
Normal file
328
editor/scene/texture/texture_editor_plugin.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
/**************************************************************************/
|
||||
/* texture_editor_plugin.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 "texture_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/scene/texture/color_channel_selector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/aspect_ratio_container.h"
|
||||
#include "scene/gui/color_rect.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/resources/animated_texture.h"
|
||||
#include "scene/resources/atlas_texture.h"
|
||||
#include "scene/resources/compressed_texture.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
#include "scene/resources/portable_compressed_texture.h"
|
||||
#include "scene/resources/style_box_flat.h"
|
||||
|
||||
constexpr const char *texture_2d_shader = R"(
|
||||
shader_type canvas_item;
|
||||
render_mode blend_mix;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = filter_preview_colors(texture(TEXTURE, UV), u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
TextureRect *TexturePreview::get_texture_display() {
|
||||
return texture_display;
|
||||
}
|
||||
|
||||
void TexturePreview::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (!is_inside_tree()) {
|
||||
// TODO: This is a workaround because `NOTIFICATION_THEME_CHANGED`
|
||||
// is getting called for some reason when the `TexturePreview` is
|
||||
// getting destroyed, which causes `get_theme_font()` to return `nullptr`.
|
||||
// See https://github.com/godotengine/godot/issues/50743.
|
||||
break;
|
||||
}
|
||||
|
||||
if (metadata_label) {
|
||||
Ref<Font> metadata_label_font = get_theme_font(SNAME("expression"), EditorStringName(EditorFonts));
|
||||
metadata_label->add_theme_font_override(SceneStringName(font), metadata_label_font);
|
||||
}
|
||||
|
||||
bg_rect->set_color(get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor)));
|
||||
checkerboard->set_texture(get_editor_theme_icon(SNAME("Checkerboard")));
|
||||
theme_cache.outline_color = get_theme_color(SNAME("extra_border_color_1"), EditorStringName(Editor));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void TexturePreview::_draw_outline() {
|
||||
const float outline_width = Math::round(EDSCALE);
|
||||
const Rect2 outline_rect = Rect2(Vector2(), outline_overlay->get_size()).grow(outline_width * 0.5);
|
||||
outline_overlay->draw_rect(outline_rect, theme_cache.outline_color, false, outline_width);
|
||||
}
|
||||
|
||||
void TexturePreview::_update_texture_display_ratio() {
|
||||
if (texture_display->get_texture().is_valid()) {
|
||||
centering_container->set_ratio(texture_display->get_texture()->get_size().aspect());
|
||||
}
|
||||
}
|
||||
|
||||
static Image::Format get_texture_2d_format(const Ref<Texture2D> &p_texture) {
|
||||
const Ref<ImageTexture> image_texture = p_texture;
|
||||
if (image_texture.is_valid()) {
|
||||
return image_texture->get_format();
|
||||
}
|
||||
|
||||
const Ref<CompressedTexture2D> compressed_texture = p_texture;
|
||||
if (compressed_texture.is_valid()) {
|
||||
return compressed_texture->get_format();
|
||||
}
|
||||
|
||||
const Ref<PortableCompressedTexture2D> portable_compressed_texture = p_texture;
|
||||
if (portable_compressed_texture.is_valid()) {
|
||||
return portable_compressed_texture->get_format();
|
||||
}
|
||||
|
||||
// AtlasTexture?
|
||||
|
||||
// Unknown
|
||||
return Image::FORMAT_MAX;
|
||||
}
|
||||
|
||||
static int get_texture_mipmaps_count(const Ref<Texture2D> &p_texture) {
|
||||
ERR_FAIL_COND_V(p_texture.is_null(), -1);
|
||||
|
||||
// We are having to download the image only to get its mipmaps count. It would be nice if we didn't have to.
|
||||
Ref<Image> image;
|
||||
Ref<AtlasTexture> at = p_texture;
|
||||
if (at.is_valid()) {
|
||||
// The AtlasTexture tries to obtain the region from the atlas as an image,
|
||||
// which will fail if it is a compressed format.
|
||||
Ref<Texture2D> atlas = at->get_atlas();
|
||||
if (atlas.is_valid()) {
|
||||
image = atlas->get_image();
|
||||
}
|
||||
} else {
|
||||
image = p_texture->get_image();
|
||||
}
|
||||
|
||||
if (image.is_valid()) {
|
||||
return image->get_mipmap_count();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TexturePreview::_update_metadata_label_text() {
|
||||
const Ref<Texture2D> texture = texture_display->get_texture();
|
||||
ERR_FAIL_COND(texture.is_null());
|
||||
|
||||
const Image::Format format = get_texture_2d_format(texture.ptr());
|
||||
|
||||
const String format_name = format != Image::FORMAT_MAX ? Image::get_format_name(format) : texture->get_class();
|
||||
|
||||
const Vector2i resolution = texture->get_size();
|
||||
const int mipmaps = get_texture_mipmaps_count(texture);
|
||||
|
||||
if (format != Image::FORMAT_MAX) {
|
||||
// Avoid signed integer overflow that could occur with huge texture sizes by casting everything to uint64_t.
|
||||
uint64_t memory = uint64_t(resolution.x) * uint64_t(resolution.y) * uint64_t(Image::get_format_pixel_size(format));
|
||||
// Handle VRAM-compressed formats that are stored with 4 bpp.
|
||||
memory >>= Image::get_format_pixel_rshift(format);
|
||||
|
||||
float mipmaps_multiplier = 1.0;
|
||||
float mipmap_increase = 0.25;
|
||||
for (int i = 0; i < mipmaps; i++) {
|
||||
// Each mip adds 25% memory usage of the previous one.
|
||||
// With a complete mipmap chain, memory usage increases by ~33%.
|
||||
mipmaps_multiplier += mipmap_increase;
|
||||
mipmap_increase *= 0.25;
|
||||
}
|
||||
memory *= mipmaps_multiplier;
|
||||
|
||||
if (mipmaps >= 1) {
|
||||
metadata_label->set_text(
|
||||
vformat(String::utf8("%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format_name,
|
||||
mipmaps,
|
||||
String::humanize_size(memory)));
|
||||
} else {
|
||||
// "No Mipmaps" is easier to distinguish than "0 Mipmaps",
|
||||
// especially since 0, 6, and 8 look quite close with the default code font.
|
||||
metadata_label->set_text(
|
||||
vformat(String::utf8("%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format_name,
|
||||
String::humanize_size(memory)));
|
||||
}
|
||||
} else {
|
||||
metadata_label->set_text(
|
||||
vformat(String::utf8("%d×%d %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format_name));
|
||||
}
|
||||
}
|
||||
|
||||
void TexturePreview::on_selected_channels_changed() {
|
||||
material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors());
|
||||
}
|
||||
|
||||
TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
|
||||
set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE);
|
||||
|
||||
bg_rect = memnew(ColorRect);
|
||||
|
||||
add_child(bg_rect);
|
||||
|
||||
margin_container = memnew(MarginContainer);
|
||||
const float outline_width = Math::round(EDSCALE);
|
||||
margin_container->add_theme_constant_override("margin_right", outline_width);
|
||||
margin_container->add_theme_constant_override("margin_top", outline_width);
|
||||
margin_container->add_theme_constant_override("margin_left", outline_width);
|
||||
margin_container->add_theme_constant_override("margin_bottom", outline_width);
|
||||
add_child(margin_container);
|
||||
|
||||
centering_container = memnew(AspectRatioContainer);
|
||||
margin_container->add_child(centering_container);
|
||||
|
||||
checkerboard = memnew(TextureRect);
|
||||
checkerboard->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE);
|
||||
checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED);
|
||||
centering_container->add_child(checkerboard);
|
||||
|
||||
{
|
||||
Ref<Shader> shader;
|
||||
shader.instantiate();
|
||||
shader->set_code(texture_2d_shader);
|
||||
|
||||
material.instantiate();
|
||||
material->set_shader(shader);
|
||||
material->set_shader_parameter("u_channel_factors", Vector4(1, 1, 1, 1));
|
||||
}
|
||||
|
||||
texture_display = memnew(TextureRect);
|
||||
texture_display->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
|
||||
texture_display->set_texture(p_texture);
|
||||
texture_display->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
texture_display->set_material(material);
|
||||
centering_container->add_child(texture_display);
|
||||
|
||||
// Creating a separate control so it is not affected by the filtering shader.
|
||||
outline_overlay = memnew(Control);
|
||||
centering_container->add_child(outline_overlay);
|
||||
|
||||
outline_overlay->connect(SceneStringName(draw), callable_mp(this, &TexturePreview::_draw_outline));
|
||||
|
||||
if (p_texture.is_valid()) {
|
||||
_update_texture_display_ratio();
|
||||
p_texture->connect_changed(callable_mp(this, &TexturePreview::_update_texture_display_ratio));
|
||||
}
|
||||
|
||||
// Null can be passed by `Camera3DPreview` (which immediately after sets a texture anyways).
|
||||
const Image::Format format = p_texture.is_valid() ? get_texture_2d_format(p_texture.ptr()) : Image::FORMAT_MAX;
|
||||
const uint32_t components_mask = format != Image::FORMAT_MAX ? Image::get_format_component_mask(format) : 0xf;
|
||||
|
||||
// Add color channel selector at the bottom left if more than 1 channel is available.
|
||||
if (p_show_metadata && !is_power_of_2(components_mask)) {
|
||||
channel_selector = memnew(ColorChannelSelector);
|
||||
channel_selector->connect("selected_channels_changed", callable_mp(this, &TexturePreview::on_selected_channels_changed));
|
||||
channel_selector->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);
|
||||
channel_selector->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);
|
||||
channel_selector->set_available_channels_mask(components_mask);
|
||||
add_child(channel_selector);
|
||||
}
|
||||
|
||||
if (p_show_metadata) {
|
||||
metadata_label = memnew(Label);
|
||||
metadata_label->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
|
||||
if (p_texture.is_valid()) {
|
||||
_update_metadata_label_text();
|
||||
p_texture->connect_changed(callable_mp(this, &TexturePreview::_update_metadata_label_text));
|
||||
}
|
||||
|
||||
// It's okay that these colors are static since the grid color is static too.
|
||||
metadata_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
|
||||
metadata_label->add_theme_color_override("font_shadow_color", Color(0, 0, 0));
|
||||
|
||||
metadata_label->add_theme_font_size_override(SceneStringName(font_size), 14 * EDSCALE);
|
||||
metadata_label->add_theme_color_override("font_outline_color", Color(0, 0, 0));
|
||||
metadata_label->add_theme_constant_override("outline_size", 8 * EDSCALE);
|
||||
|
||||
metadata_label->set_h_size_flags(Control::SIZE_SHRINK_END);
|
||||
metadata_label->set_v_size_flags(Control::SIZE_SHRINK_END);
|
||||
|
||||
add_child(metadata_label);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorInspectorPluginTexture::can_handle(Object *p_object) {
|
||||
return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<CompressedTexture2D>(p_object) != nullptr || Object::cast_to<PortableCompressedTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr || Object::cast_to<Image>(p_object) != nullptr;
|
||||
}
|
||||
|
||||
void EditorInspectorPluginTexture::parse_begin(Object *p_object) {
|
||||
Ref<Texture> texture(Object::cast_to<Texture>(p_object));
|
||||
if (texture.is_null()) {
|
||||
Ref<Image> image(Object::cast_to<Image>(p_object));
|
||||
texture = ImageTexture::create_from_image(image);
|
||||
|
||||
ERR_FAIL_COND_MSG(texture.is_null(), "Failed to create the texture from an invalid image.");
|
||||
}
|
||||
|
||||
add_custom_control(memnew(TexturePreview(texture, true)));
|
||||
}
|
||||
|
||||
TextureEditorPlugin::TextureEditorPlugin() {
|
||||
Ref<EditorInspectorPluginTexture> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
93
editor/scene/texture/texture_editor_plugin.h
Normal file
93
editor/scene/texture/texture_editor_plugin.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/**************************************************************************/
|
||||
/* texture_editor_plugin.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
class AspectRatioContainer;
|
||||
class ColorRect;
|
||||
class TextureRect;
|
||||
class ShaderMaterial;
|
||||
class ColorChannelSelector;
|
||||
|
||||
class TexturePreview : public MarginContainer {
|
||||
GDCLASS(TexturePreview, MarginContainer);
|
||||
|
||||
private:
|
||||
struct ThemeCache {
|
||||
Color outline_color;
|
||||
} theme_cache;
|
||||
|
||||
TextureRect *texture_display = nullptr;
|
||||
|
||||
MarginContainer *margin_container = nullptr;
|
||||
Control *outline_overlay = nullptr;
|
||||
AspectRatioContainer *centering_container = nullptr;
|
||||
ColorRect *bg_rect = nullptr;
|
||||
TextureRect *checkerboard = nullptr;
|
||||
Label *metadata_label = nullptr;
|
||||
Ref<ShaderMaterial> material;
|
||||
|
||||
ColorChannelSelector *channel_selector = nullptr;
|
||||
|
||||
void _draw_outline();
|
||||
void _update_metadata_label_text();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
void _update_texture_display_ratio();
|
||||
|
||||
void on_selected_channels_changed();
|
||||
|
||||
public:
|
||||
TextureRect *get_texture_display();
|
||||
TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata);
|
||||
};
|
||||
|
||||
class EditorInspectorPluginTexture : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginTexture, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class TextureEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(TextureEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "Texture2D"; }
|
||||
|
||||
TextureEditorPlugin();
|
||||
};
|
445
editor/scene/texture/texture_layered_editor_plugin.cpp
Normal file
445
editor/scene/texture/texture_layered_editor_plugin.cpp
Normal file
@@ -0,0 +1,445 @@
|
||||
/**************************************************************************/
|
||||
/* texture_layered_editor_plugin.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 "texture_layered_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/scene/texture/color_channel_selector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/label.h"
|
||||
|
||||
// Shader sources.
|
||||
|
||||
constexpr const char *array_2d_shader = R"(
|
||||
// TextureLayeredEditor preview shader (2D array).
|
||||
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform sampler2DArray tex;
|
||||
uniform float layer;
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = textureLod(tex, vec3(UV, layer), 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr const char *cubemap_shader = R"(
|
||||
// TextureLayeredEditor preview shader (cubemap).
|
||||
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform samplerCube tex;
|
||||
uniform vec3 normal;
|
||||
uniform mat3 rot;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z));
|
||||
COLOR = textureLod(tex, n, 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr const char *cubemap_array_shader = R"(
|
||||
// TextureLayeredEditor preview shader (cubemap array).
|
||||
|
||||
shader_type canvas_item;
|
||||
uniform samplerCubeArray tex;
|
||||
uniform vec3 normal;
|
||||
uniform mat3 rot;
|
||||
uniform float layer;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z));
|
||||
COLOR = textureLod(tex, vec4(n, layer), 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
void TextureLayeredEditor::gui_input(const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND(p_event.is_null());
|
||||
|
||||
Ref<InputEventMouseMotion> mm = p_event;
|
||||
if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
|
||||
y_rot += -mm->get_relative().x * 0.01;
|
||||
x_rot += -mm->get_relative().y * 0.01;
|
||||
|
||||
_update_material(false);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_texture_rect_draw() {
|
||||
texture_rect->draw_rect(Rect2(Point2(), texture_rect->get_size()), Color(1, 1, 1, 1));
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_update_gui() {
|
||||
if (texture.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_texture_rect_update_area();
|
||||
|
||||
const Image::Format format = texture->get_format();
|
||||
const String format_name = Image::get_format_name(format);
|
||||
String texture_info;
|
||||
|
||||
switch (texture->get_layered_type()) {
|
||||
case TextureLayered::LAYERED_TYPE_2D_ARRAY: {
|
||||
layer->set_max(texture->get_layers() - 1);
|
||||
|
||||
texture_info = vformat(String::utf8("%d×%d (×%d) %s\n"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_layers(),
|
||||
format_name);
|
||||
|
||||
} break;
|
||||
case TextureLayered::LAYERED_TYPE_CUBEMAP: {
|
||||
layer->hide();
|
||||
|
||||
texture_info = vformat(String::utf8("%d×%d %s\n"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format_name);
|
||||
|
||||
} break;
|
||||
case TextureLayered::LAYERED_TYPE_CUBEMAP_ARRAY: {
|
||||
layer->set_max(texture->get_layers() / 6 - 1);
|
||||
|
||||
texture_info = vformat(String::utf8("%d×%d (×%d) %s\n"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_layers() / 6,
|
||||
format_name);
|
||||
|
||||
} break;
|
||||
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
if (texture->has_mipmaps()) {
|
||||
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format);
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_layers();
|
||||
|
||||
texture_info += vformat(TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
mip_count,
|
||||
String::humanize_size(memory));
|
||||
|
||||
} else {
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_layers();
|
||||
|
||||
texture_info += vformat(TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
String::humanize_size(memory));
|
||||
}
|
||||
|
||||
info->set_text(texture_info);
|
||||
|
||||
const uint32_t components_mask = Image::get_format_component_mask(format);
|
||||
if (is_power_of_2(components_mask)) {
|
||||
// Only one channel available, no point in showing a channel selector.
|
||||
channel_selector->hide();
|
||||
} else {
|
||||
channel_selector->show();
|
||||
channel_selector->set_available_channels_mask(components_mask);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_RESIZED: {
|
||||
_texture_rect_update_area();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_DRAW: {
|
||||
Ref<Texture2D> checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
|
||||
draw_texture_rect(checkerboard, texture_rect->get_rect(), true);
|
||||
_draw_outline();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (info) {
|
||||
Ref<Font> metadata_label_font = get_theme_font(SNAME("expression"), EditorStringName(EditorFonts));
|
||||
info->add_theme_font_override(SceneStringName(font), metadata_label_font);
|
||||
}
|
||||
theme_cache.outline_color = get_theme_color(SNAME("extra_border_color_1"), EditorStringName(Editor));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_texture_changed() {
|
||||
if (!is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setting = true;
|
||||
_update_gui();
|
||||
setting = false;
|
||||
|
||||
_update_material(true);
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_update_material(bool p_texture_changed) {
|
||||
materials[0]->set_shader_parameter("layer", layer->get_value());
|
||||
materials[2]->set_shader_parameter("layer", layer->get_value());
|
||||
|
||||
Vector3 v(1, 1, 1);
|
||||
v.normalize();
|
||||
|
||||
Basis b;
|
||||
b.rotate(Vector3(1, 0, 0), x_rot);
|
||||
b.rotate(Vector3(0, 1, 0), y_rot);
|
||||
|
||||
materials[1]->set_shader_parameter("normal", v);
|
||||
materials[1]->set_shader_parameter("rot", b);
|
||||
materials[2]->set_shader_parameter("normal", v);
|
||||
materials[2]->set_shader_parameter("rot", b);
|
||||
|
||||
if (p_texture_changed) {
|
||||
materials[texture->get_layered_type()]->set_shader_parameter("tex", texture->get_rid());
|
||||
}
|
||||
|
||||
const Vector4 channel_factors = channel_selector->get_selected_channel_factors();
|
||||
for (unsigned int i = 0; i < 3; ++i) {
|
||||
materials[i]->set_shader_parameter("u_channel_factors", channel_factors);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::on_selected_channels_changed() {
|
||||
_update_material(false);
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_draw_outline() {
|
||||
const float outline_width = Math::round(EDSCALE);
|
||||
const Rect2 outline_rect = texture_rect->get_rect().grow(outline_width * 0.5);
|
||||
draw_rect(outline_rect, theme_cache.outline_color, false, outline_width);
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_make_shaders() {
|
||||
shaders[0].instantiate();
|
||||
shaders[0]->set_code(array_2d_shader);
|
||||
|
||||
shaders[1].instantiate();
|
||||
shaders[1]->set_code(cubemap_shader);
|
||||
|
||||
shaders[2].instantiate();
|
||||
shaders[2]->set_code(cubemap_array_shader);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
materials[i].instantiate();
|
||||
materials[i]->set_shader(shaders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_texture_rect_update_area() {
|
||||
Size2 size = get_size();
|
||||
int tex_width = texture->get_width() * size.height / texture->get_height();
|
||||
int tex_height = size.height;
|
||||
|
||||
if (tex_width > size.width) {
|
||||
tex_width = size.width;
|
||||
tex_height = texture->get_height() * tex_width / texture->get_width();
|
||||
}
|
||||
|
||||
// Prevent the texture from being unpreviewable after the rescale, so that we can still see something
|
||||
if (tex_height <= 0) {
|
||||
tex_height = 1;
|
||||
}
|
||||
if (tex_width <= 0) {
|
||||
tex_width = 1;
|
||||
}
|
||||
|
||||
int ofs_x = (size.width - tex_width) / 2;
|
||||
int ofs_y = (size.height - tex_height) / 2;
|
||||
|
||||
texture_rect->set_position(Vector2(ofs_x, ofs_y - Math::round(EDSCALE)));
|
||||
texture_rect->set_size(Vector2(tex_width, tex_height));
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::edit(Ref<TextureLayered> p_texture) {
|
||||
if (texture.is_valid()) {
|
||||
texture->disconnect_changed(callable_mp(this, &TextureLayeredEditor::_texture_changed));
|
||||
}
|
||||
|
||||
texture = p_texture;
|
||||
|
||||
if (texture.is_valid()) {
|
||||
if (shaders[0].is_null()) {
|
||||
_make_shaders();
|
||||
}
|
||||
|
||||
texture->connect_changed(callable_mp(this, &TextureLayeredEditor::_texture_changed));
|
||||
texture_rect->set_material(materials[texture->get_layered_type()]);
|
||||
|
||||
setting = true;
|
||||
layer->set_value(0);
|
||||
layer->show();
|
||||
_update_gui();
|
||||
setting = false;
|
||||
|
||||
x_rot = 0;
|
||||
y_rot = 0;
|
||||
|
||||
_update_material(true);
|
||||
queue_redraw();
|
||||
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
TextureLayeredEditor::TextureLayeredEditor() {
|
||||
set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
|
||||
set_custom_minimum_size(Size2(0, 256.0) * EDSCALE);
|
||||
|
||||
texture_rect = memnew(Control);
|
||||
texture_rect->set_mouse_filter(MOUSE_FILTER_IGNORE);
|
||||
texture_rect->connect(SceneStringName(draw), callable_mp(this, &TextureLayeredEditor::_texture_rect_draw));
|
||||
|
||||
add_child(texture_rect);
|
||||
|
||||
layer = memnew(SpinBox);
|
||||
layer->set_step(1);
|
||||
layer->set_max(100);
|
||||
|
||||
layer->set_modulate(Color(1, 1, 1, 0.8));
|
||||
layer->set_h_grow_direction(GROW_DIRECTION_BEGIN);
|
||||
layer->set_anchor(SIDE_RIGHT, 1);
|
||||
layer->set_anchor(SIDE_LEFT, 1);
|
||||
layer->connect(SceneStringName(value_changed), callable_mp(this, &TextureLayeredEditor::_layer_changed));
|
||||
|
||||
add_child(layer);
|
||||
|
||||
channel_selector = memnew(ColorChannelSelector);
|
||||
channel_selector->connect("selected_channels_changed", callable_mp(this, &TextureLayeredEditor::on_selected_channels_changed));
|
||||
channel_selector->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT);
|
||||
add_child(channel_selector);
|
||||
|
||||
info = memnew(Label);
|
||||
info->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
|
||||
info->add_theme_color_override("font_shadow_color", Color(0, 0, 0));
|
||||
info->add_theme_font_size_override(SceneStringName(font_size), 14 * EDSCALE);
|
||||
info->add_theme_color_override("font_outline_color", Color(0, 0, 0));
|
||||
info->add_theme_constant_override("outline_size", 8 * EDSCALE);
|
||||
|
||||
info->set_h_grow_direction(GROW_DIRECTION_BEGIN);
|
||||
info->set_v_grow_direction(GROW_DIRECTION_BEGIN);
|
||||
info->set_h_size_flags(Control::SIZE_SHRINK_END);
|
||||
info->set_v_size_flags(Control::SIZE_SHRINK_END);
|
||||
info->set_anchor(SIDE_RIGHT, 1);
|
||||
info->set_anchor(SIDE_LEFT, 1);
|
||||
info->set_anchor(SIDE_BOTTOM, 1);
|
||||
info->set_anchor(SIDE_TOP, 1);
|
||||
|
||||
add_child(info);
|
||||
}
|
||||
|
||||
bool EditorInspectorPluginLayeredTexture::can_handle(Object *p_object) {
|
||||
return Object::cast_to<TextureLayered>(p_object) != nullptr;
|
||||
}
|
||||
|
||||
void EditorInspectorPluginLayeredTexture::parse_begin(Object *p_object) {
|
||||
TextureLayered *texture = Object::cast_to<TextureLayered>(p_object);
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
Ref<TextureLayered> m(texture);
|
||||
|
||||
TextureLayeredEditor *editor = memnew(TextureLayeredEditor);
|
||||
editor->edit(m);
|
||||
add_custom_control(editor);
|
||||
}
|
||||
|
||||
TextureLayeredEditorPlugin::TextureLayeredEditorPlugin() {
|
||||
Ref<EditorInspectorPluginLayeredTexture> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
108
editor/scene/texture/texture_layered_editor_plugin.h
Normal file
108
editor/scene/texture/texture_layered_editor_plugin.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/**************************************************************************/
|
||||
/* texture_layered_editor_plugin.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
#include "scene/resources/shader.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
class ColorChannelSelector;
|
||||
|
||||
class TextureLayeredEditor : public Control {
|
||||
GDCLASS(TextureLayeredEditor, Control);
|
||||
|
||||
struct ThemeCache {
|
||||
Color outline_color;
|
||||
} theme_cache;
|
||||
|
||||
SpinBox *layer = nullptr;
|
||||
Label *info = nullptr;
|
||||
Ref<TextureLayered> texture;
|
||||
|
||||
Ref<Shader> shaders[3];
|
||||
Ref<ShaderMaterial> materials[3];
|
||||
|
||||
float x_rot = 0;
|
||||
float y_rot = 0;
|
||||
Control *texture_rect = nullptr;
|
||||
|
||||
bool setting = false;
|
||||
|
||||
ColorChannelSelector *channel_selector = nullptr;
|
||||
|
||||
void _draw_outline();
|
||||
|
||||
void _make_shaders();
|
||||
void _update_material(bool p_texture_changed);
|
||||
|
||||
void _layer_changed(double) {
|
||||
if (!setting) {
|
||||
_update_material(false);
|
||||
}
|
||||
}
|
||||
|
||||
void _texture_changed();
|
||||
|
||||
void _texture_rect_update_area();
|
||||
void _texture_rect_draw();
|
||||
|
||||
void _update_gui();
|
||||
|
||||
void on_selected_channels_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
public:
|
||||
void edit(Ref<TextureLayered> p_texture);
|
||||
|
||||
TextureLayeredEditor();
|
||||
};
|
||||
|
||||
class EditorInspectorPluginLayeredTexture : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginLayeredTexture, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class TextureLayeredEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(TextureLayeredEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "TextureLayered"; }
|
||||
|
||||
TextureLayeredEditorPlugin();
|
||||
};
|
1317
editor/scene/texture/texture_region_editor_plugin.cpp
Normal file
1317
editor/scene/texture/texture_region_editor_plugin.cpp
Normal file
File diff suppressed because it is too large
Load Diff
181
editor/scene/texture/texture_region_editor_plugin.h
Normal file
181
editor/scene/texture/texture_region_editor_plugin.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/**************************************************************************/
|
||||
/* texture_region_editor_plugin.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class AtlasTexture;
|
||||
class NinePatchRect;
|
||||
class OptionButton;
|
||||
class PanelContainer;
|
||||
class Sprite2D;
|
||||
class Sprite3D;
|
||||
class StyleBoxTexture;
|
||||
class ViewPanner;
|
||||
|
||||
class TextureRegionEditor : public AcceptDialog {
|
||||
GDCLASS(TextureRegionEditor, AcceptDialog);
|
||||
|
||||
enum SnapMode {
|
||||
SNAP_NONE,
|
||||
SNAP_PIXEL,
|
||||
SNAP_GRID,
|
||||
SNAP_AUTOSLICE
|
||||
};
|
||||
|
||||
friend class TextureRegionEditorPlugin;
|
||||
OptionButton *snap_mode_button = nullptr;
|
||||
Button *zoom_in = nullptr;
|
||||
Button *zoom_reset = nullptr;
|
||||
Button *zoom_out = nullptr;
|
||||
HBoxContainer *hb_grid = nullptr; //For showing/hiding the grid controls when changing the SnapMode
|
||||
SpinBox *sb_step_y = nullptr;
|
||||
SpinBox *sb_step_x = nullptr;
|
||||
SpinBox *sb_off_y = nullptr;
|
||||
SpinBox *sb_off_x = nullptr;
|
||||
SpinBox *sb_sep_y = nullptr;
|
||||
SpinBox *sb_sep_x = nullptr;
|
||||
|
||||
PanelContainer *texture_preview = nullptr;
|
||||
Panel *texture_overlay = nullptr;
|
||||
|
||||
VScrollBar *vscroll = nullptr;
|
||||
HScrollBar *hscroll = nullptr;
|
||||
|
||||
Vector2 draw_ofs;
|
||||
float draw_zoom = 1.0;
|
||||
float min_draw_zoom = 1.0;
|
||||
float max_draw_zoom = 1.0;
|
||||
bool updating_scroll = false;
|
||||
|
||||
SnapMode snap_mode = SNAP_NONE;
|
||||
Vector2 snap_offset;
|
||||
Vector2 snap_step;
|
||||
Vector2 snap_separation;
|
||||
|
||||
Sprite2D *node_sprite_2d = nullptr;
|
||||
Sprite3D *node_sprite_3d = nullptr;
|
||||
NinePatchRect *node_ninepatch = nullptr;
|
||||
Ref<StyleBoxTexture> res_stylebox;
|
||||
Ref<AtlasTexture> res_atlas_texture;
|
||||
|
||||
Rect2 rect;
|
||||
Rect2 rect_prev;
|
||||
float prev_margin = 0.0f;
|
||||
int edited_margin = -1;
|
||||
HashMap<RID, List<Rect2>> cache_map;
|
||||
List<Rect2> autoslice_cache;
|
||||
bool autoslice_is_dirty = true;
|
||||
|
||||
bool drag = false;
|
||||
bool creating = false;
|
||||
Vector2 drag_from;
|
||||
int drag_index = -1;
|
||||
bool request_center = false;
|
||||
|
||||
Ref<ViewPanner> panner;
|
||||
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
|
||||
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
|
||||
void _scroll_changed(float);
|
||||
Transform2D _get_offset_transform() const;
|
||||
|
||||
void _set_snap_mode(int p_mode);
|
||||
void _set_snap_off_x(float p_val);
|
||||
void _set_snap_off_y(float p_val);
|
||||
void _set_snap_step_x(float p_val);
|
||||
void _set_snap_step_y(float p_val);
|
||||
void _set_snap_sep_x(float p_val);
|
||||
void _set_snap_sep_y(float p_val);
|
||||
|
||||
void _zoom_on_position(float p_zoom, Point2 p_position = Point2());
|
||||
void _zoom_in();
|
||||
void _zoom_reset();
|
||||
void _zoom_out();
|
||||
|
||||
void _apply_rect(const Rect2 &p_rect);
|
||||
void _update_rect();
|
||||
void _update_autoslice();
|
||||
|
||||
Ref<Texture2D> _get_edited_object_texture() const;
|
||||
Rect2 _get_edited_object_region() const;
|
||||
void _texture_changed();
|
||||
void _node_removed(Node *p_node);
|
||||
|
||||
void _edit_region();
|
||||
void _clear_edited_object();
|
||||
|
||||
void _draw_margin_line(Vector2 p_from, Vector2 p_to);
|
||||
|
||||
void _set_grid_parameters_clamping(bool p_enabled);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
|
||||
static void _bind_methods();
|
||||
|
||||
void _texture_preview_draw();
|
||||
void _texture_overlay_draw();
|
||||
void _texture_overlay_input(const Ref<InputEvent> &p_input);
|
||||
|
||||
Vector2 snap_point(Vector2 p_target) const;
|
||||
|
||||
public:
|
||||
void edit(Object *p_obj);
|
||||
|
||||
TextureRegionEditor();
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
class EditorInspectorPluginTextureRegion : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginTextureRegion, EditorInspectorPlugin);
|
||||
|
||||
TextureRegionEditor *texture_region_editor = nullptr;
|
||||
|
||||
void _region_edit(Object *p_object);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) override;
|
||||
|
||||
EditorInspectorPluginTextureRegion();
|
||||
};
|
||||
|
||||
class TextureRegionEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(TextureRegionEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "TextureRegion"; }
|
||||
|
||||
TextureRegionEditorPlugin();
|
||||
};
|
Reference in New Issue
Block a user