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 16, 2024
1 parent 557f63d commit b45baac
Show file tree
Hide file tree
Showing 11 changed files with 370 additions and 8 deletions.
13 changes: 13 additions & 0 deletions doc/classes/ColorPalette.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?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>
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<members>
<member name="colors" type="PackedColorArray" setter="set_colors" getter="get_colors" default="PackedColorArray()">
</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 = Callable();

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
213 changes: 205 additions & 8 deletions scene/gui/color_picker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
#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"
Expand Down Expand Up @@ -75,6 +77,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 +479,15 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) {
_update_presets();
_update_recent_presets();
}

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

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

#endif

HSlider *ColorPicker::get_slider(int p_idx) {
Expand Down Expand Up @@ -656,16 +668,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 +802,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 +960,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 +1008,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 +1650,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 +1980,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 +2168,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("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 b45baac

Please sign in to comment.