Skip to content

Commit

Permalink
Save color palette as resources to reuse later
Browse files Browse the repository at this point in the history
The lack of a palette library slows down development time because swatches contain many colors that may not match the mood an artist is trying to achieve. This can hinder their workflow as they search for the right color within a large set of mostly irrelevant options.
  • Loading branch information
nongvantinh committed May 18, 2024
1 parent 557f63d commit 1b024dd
Show file tree
Hide file tree
Showing 13 changed files with 369 additions and 8 deletions.
16 changes: 16 additions & 0 deletions doc/classes/ColorPalette.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ColorPalette" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A resource class for managing a palette of colors.
</brief_description>
<description>
The ColorPalette class is designed to store and manage a collection of colors. This class is useful in scenarios where a predefined set of colors is required, such as for creating themes, designing user interfaces, or managing game assets.
</description>
<tutorials>
</tutorials>
<members>
<member name="colors" type="PackedColorArray" setter="set_colors" getter="get_colors" default="PackedColorArray()">
A PackedColorArray containing the colors in the palette. This array holds the color data that can be accessed and manipulated through the set_colors and get_colors methods. By default, it initializes as an empty array.
</member>
</members>
</class>
15 changes: 15 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2636,6 +2636,11 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
quick_open->popup_dialog("Script", true);
quick_open->set_title(TTR("Quick Open Script..."));

} break;
case FILE_QUICK_OPEN_PALETTE: {
quick_open->popup_dialog("ColorPalette", true);
quick_open->set_title(TTR("Quick Open Color Palette..."));

} break;
case FILE_OPEN_PREV: {
if (previous_scenes.is_empty()) {
Expand Down Expand Up @@ -3384,6 +3389,10 @@ void EditorNode::_update_file_menu_closed() {
file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false);
}

void EditorNode::_palette_quick_open_dialog() {
_menu_option_confirm(FILE_QUICK_OPEN_PALETTE, false);
}

VBoxContainer *EditorNode::get_main_screen_control() {
return main_screen_vbox;
}
Expand Down Expand Up @@ -3885,6 +3894,10 @@ void EditorNode::setup_color_picker(ColorPicker *p_picker) {

p_picker->set_color_mode((ColorPicker::ColorModeType)default_color_mode);
p_picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape);

p_picker->set_quick_open_callback(callable_mp(this, &EditorNode::_palette_quick_open_dialog));
p_picker->set_palette_saved_callback(callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::update_file));
palette_file_selected_callback = callable_mp(p_picker, &ColorPicker::_palette_file_selected);
}

bool EditorNode::is_scene_open(const String &p_path) {
Expand Down Expand Up @@ -4401,6 +4414,8 @@ void EditorNode::_quick_opened() {
const String &res_path = files[i];
if (open_scene_dialog || ClassDB::is_parent_class(ResourceLoader::get_resource_type(res_path), "PackedScene")) {
open_request(res_path);
} else if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(res_path), "ColorPalette")) {
palette_file_selected_callback.call_deferred(files[i]);
} else {
load_resource(res_path);
}
Expand Down
3 changes: 3 additions & 0 deletions editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class EditorNode : public Node {
FILE_QUICK_OPEN,
FILE_QUICK_OPEN_SCENE,
FILE_QUICK_OPEN_SCRIPT,
FILE_QUICK_OPEN_PALETTE,
FILE_OPEN_PREV,
FILE_CLOSE,
FILE_CLOSE_OTHERS,
Expand Down Expand Up @@ -423,6 +424,7 @@ class EditorNode : public Node {
Timer *editor_layout_save_delay_timer = nullptr;
Timer *scan_changes_timer = nullptr;
Button *distraction_free = nullptr;
Callable palette_file_selected_callback;

EditorBottomPanel *bottom_panel = nullptr;

Expand Down Expand Up @@ -539,6 +541,7 @@ class EditorNode : public Node {
void _export_as_menu_option(int p_idx);
void _update_file_menu_opened();
void _update_file_menu_closed();
void _palette_quick_open_dialog();

void _remove_plugin_from_enabled(const String &p_name);
void _plugin_over_edit(EditorPlugin *p_plugin, Object *p_object);
Expand Down
214 changes: 206 additions & 8 deletions scene/gui/color_picker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "scene/gui/color_mode.h"
#include "scene/gui/file_dialog.h"
#include "scene/gui/margin_container.h"
#include "scene/resources/color_palette.h"
#include "scene/resources/image_texture.h"
#include "scene/resources/style_box_flat.h"
#include "scene/resources/style_box_texture.h"
#include "scene/scene_string_names.h"
#include "scene/theme/theme_db.h"
#include "servers/display_server.h"
#include "thirdparty/misc/ok_color.h"
Expand Down Expand Up @@ -75,6 +78,7 @@ void ColorPicker::_notification(int p_what) {
_update_drop_down_arrow(btn_preset->is_pressed(), btn_preset);
_update_drop_down_arrow(btn_recent_preset->is_pressed(), btn_recent_preset);
btn_add_preset->set_icon(theme_cache.add_preset);
menu_btn->set_icon(get_theme_icon(SNAME("menu_highlight"), SNAME("TabContainer")));

btn_pick->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0));
btn_shape->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0));
Expand Down Expand Up @@ -476,6 +480,15 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) {
_update_presets();
_update_recent_presets();
}

void ColorPicker::set_quick_open_callback(const Callable &p_file_selected) {
quick_open_callback = p_file_selected;
}

void ColorPicker::set_palette_saved_callback(const Callable &p_palette_saved) {
palette_saved_callback = p_palette_saved;
}

#endif

HSlider *ColorPicker::get_slider(int p_idx) {
Expand Down Expand Up @@ -656,16 +669,32 @@ void ColorPicker::_update_presets() {

#ifdef TOOLS_ENABLED
if (editor_settings) {
// Rebuild swatch color buttons, keeping the add-preset button in the first position.
for (int i = 1; i < preset_container->get_child_count(); i++) {
preset_container->get_child(i)->queue_free();
}
for (const Color &preset : preset_cache) {
_add_preset_button(preset_size, preset);
palette_name->set_text(editor_settings->call(SNAME("get_project_metadata"), "color_picker", "palette_name", String()));
bool palette_edited = editor_settings->call(SNAME("get_project_metadata"), "color_picker", "palette_edited", false);
if (!palette_name->get_text().is_empty()) {
if (btn_preset->is_pressed() && preset_cache.size()) {
palette_name->show();
}

if (palette_edited) {
palette_name->set_text(vformat("%s*", palette_name->get_text().replace("*", "")));
palette_name->set_tooltip_text(ETR("The changes to this palette have not been saved to a file."));
}
}
_notification(NOTIFICATION_VISIBILITY_CHANGED);
}
#endif

// Rebuild swatch color buttons, keeping the add-preset button in the first position.
for (int i = 1; i < preset_container->get_child_count(); i++) {
preset_container->get_child(i)->queue_free();
}

presets = preset_cache;
for (const Color &preset : preset_cache) {
_add_preset_button(preset_size, preset);
}

_notification(NOTIFICATION_VISIBILITY_CHANGED);
}

void ColorPicker::_update_recent_presets() {
Expand Down Expand Up @@ -774,13 +803,88 @@ void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) {
btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new));
}

void ColorPicker::_load_palette() {
List<String> extensions;
ResourceLoader::get_recognized_extensions_for_type("ColorPalette", &extensions);

file_dialog->set_title(RTR("Load Color Palette"));
file_dialog->clear_filters();
for (const String &K : extensions) {
file_dialog->add_filter("*." + K);
}

file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE);
file_dialog->set_current_file("");
file_dialog->popup_centered_ratio();
}

void ColorPicker::_save_palette() {
List<String> extensions;
ResourceLoader::get_recognized_extensions_for_type("ColorPalette", &extensions);

file_dialog->set_title(RTR("Save Color Palette"));
file_dialog->clear_filters();
for (const String &K : extensions) {
file_dialog->add_filter("*." + K);
}

file_dialog->set_file_mode(FileDialog::FILE_MODE_SAVE_FILE);
file_dialog->set_current_file("new_palette.tres");
file_dialog->popup_centered_ratio();
}

void ColorPicker::_palette_file_selected(const String &p_path) {
switch (file_dialog->get_file_mode()) {
case FileDialog::FileMode::FILE_MODE_OPEN_FILE: {
Ref<ColorPalette> palette = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE);
ERR_FAIL_COND_MSG(palette.is_null(), vformat("Cannot open color palette file for reading at: %s", p_path));
preset_cache.clear();
presets.clear();

PackedColorArray saved_presets = palette->get_colors();
for (const Color &saved_preset : saved_presets) {
preset_cache.push_back(saved_preset);
presets.push_back(saved_preset);
}
} break;
case FileDialog::FileMode::FILE_MODE_SAVE_FILE: {
ColorPalette *palette = memnew(ColorPalette);
palette->set_colors(get_presets());
Error error = ResourceSaver::save(palette, p_path);
ERR_FAIL_COND_MSG(error != Error::OK, vformat("Cannot open color palette file for writing at: %s", p_path));
if (palette_saved_callback.is_valid()) {
palette_saved_callback.call_deferred(p_path);
}
} break;
default:
break;
}

palette_name->set_text(p_path.get_file().get_basename());
palette_name->set_tooltip_text("");
btn_preset->set_pressed(true);

#ifdef TOOLS_ENABLED
if (editor_settings) {
editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_name", palette_name->get_text());
editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_edited", false);
}
#endif
_update_presets();
}

void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) {
if (p_is_btn_pressed) {
p_preset_container->show();
} else {
p_preset_container->hide();
}
_update_drop_down_arrow(p_is_btn_pressed, p_btn_preset);

palette_name->hide();
if (btn_preset->is_pressed() && !palette_name->get_text().is_empty()) {
palette_name->show();
}
}

void ColorPicker::_update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset) {
Expand Down Expand Up @@ -857,10 +961,14 @@ void ColorPicker::add_preset(const Color &p_color) {
_add_preset_button(_get_preset_size(), p_color);
}

palette_name->set_text(vformat("%s*", palette_name->get_text().replace("*", "")));
palette_name->set_tooltip_text(ETR("The changes to this palette have not been saved to a file."));

#ifdef TOOLS_ENABLED
if (editor_settings) {
PackedColorArray arr_to_save = get_presets();
editor_settings->call(SNAME("set_project_metadata"), "color_picker", "presets", arr_to_save);
editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_edited", true);
}
#endif
}
Expand Down Expand Up @@ -901,12 +1009,24 @@ void ColorPicker::erase_preset(const Color &p_color) {
}
}

palette_name->set_text(vformat("%s*", palette_name->get_text().replace("*", "")));
palette_name->set_tooltip_text(ETR("The changes to this palette have not been saved to a file."));

#ifdef TOOLS_ENABLED
if (editor_settings) {
PackedColorArray arr_to_save = get_presets();
editor_settings->call(SNAME("set_project_metadata"), "color_picker", "presets", arr_to_save);
editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_edited", true);
if (preset_cache.is_empty()) {
palette_name->hide();
editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_name", "");
}
}
#endif
if (preset_cache.is_empty()) {
palette_name->set_text("");
palette_name->hide();
}
}
}

Expand Down Expand Up @@ -1531,6 +1651,56 @@ void ColorPicker::_pick_finished() {
picker_window->hide();
}

void ColorPicker::_update_menu_items() {
options_menu->clear();
options_menu->reset_size();

if (preset_cache.size()) {
options_menu->add_icon_item(get_theme_icon(SNAME("save"), SNAME("FileDialog")), RTR("Save"), MENU_SAVE);
options_menu->set_item_tooltip(-1, ETR("Save the current color palette to reuse later."));
}
options_menu->add_icon_item(get_theme_icon(SNAME("load"), SNAME("FileDialog")), RTR("Load"), MENU_LOAD);
options_menu->set_item_tooltip(-1, ETR("Load existing color palette."));

if (Engine::get_singleton()->is_editor_hint()) {
options_menu->add_icon_item(get_theme_icon(SNAME("load"), SNAME("FileDialog")), RTR("Quick Load"), MENU_QUICKLOAD);
options_menu->set_item_tooltip(-1, ETR("Load existing color palette."));
}
}

void ColorPicker::_update_menu() {
_update_menu_items();
Rect2 gt = menu_btn->get_screen_rect();
menu_btn->reset_size();
int min_size = menu_btn->get_minimum_size().width;
Vector2 popup_pos = gt.get_end() - Vector2(min_size, 0);
options_menu->set_position(popup_pos);
options_menu->popup();
}

void ColorPicker::_options_menu_cbk(int p_which) {
switch (p_which) {
case MENU_SAVE:
_save_palette();
break;
case MENU_LOAD:
_load_palette();
break;

#ifdef TOOLS_ENABLED
case MENU_QUICKLOAD:
if (quick_open_callback.is_valid()) {
file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE);
quick_open_callback.call_deferred();
}
break;
#endif // TOOLS_ENABLED

default:
break;
}
}

void ColorPicker::_pick_button_pressed_legacy() {
if (!is_inside_tree()) {
return;
Expand Down Expand Up @@ -1811,6 +1981,12 @@ void ColorPicker::_bind_methods() {
}

ColorPicker::ColorPicker() {
file_dialog = memnew(FileDialog);
add_child(file_dialog, false, INTERNAL_MODE_FRONT);
file_dialog->connect("file_selected", callable_mp(this, &ColorPicker::_palette_file_selected));
file_dialog->set_current_dir(Engine::get_singleton()->is_editor_hint() ? "res://" : "user://");
file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM);

internal_margin = memnew(MarginContainer);
add_child(internal_margin, false, INTERNAL_MODE_FRONT);

Expand Down Expand Up @@ -1993,14 +2169,36 @@ ColorPicker::ColorPicker() {

preset_group.instantiate();

HBoxContainer *palette_box = memnew(HBoxContainer);
palette_box->set_h_size_flags(SIZE_EXPAND_FILL);
real_vbox->add_child(palette_box);

btn_preset = memnew(Button);
btn_preset->set_text("Swatches");
btn_preset->set_flat(true);
btn_preset->set_toggle_mode(true);
btn_preset->set_focus_mode(FOCUS_NONE);
btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container));
real_vbox->add_child(btn_preset);
palette_box->add_child(btn_preset);

HBoxContainer *padding_box = memnew(HBoxContainer);
padding_box->set_h_size_flags(SIZE_EXPAND_FILL);
palette_box->add_child(padding_box);

menu_btn = memnew(Button);
menu_btn->set_flat(true);
menu_btn->set_tooltip_text(ETR("Show all options available."));
menu_btn->set_focus_mode(FOCUS_NONE);
menu_btn->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_update_menu));
palette_box->add_child(menu_btn);
options_menu = memnew(PopupMenu);
add_child(options_menu);
options_menu->connect("id_pressed", callable_mp(this, &ColorPicker::_options_menu_cbk));

palette_name = memnew(Label);
palette_name->hide();
real_vbox->add_child(palette_name);

real_vbox->add_child(preset_container);

Expand Down

0 comments on commit 1b024dd

Please sign in to comment.