From 336bf5269d7bfa3494656daea2d0db65c9674204 Mon Sep 17 00:00:00 2001 From: kobewi Date: Sat, 16 Mar 2024 21:35:49 +0100 Subject: [PATCH] Add credits roll --- editor/credits_roll.cpp | 245 ++++++++++++++++++++++++++++++++++++++++ editor/credits_roll.h | 68 +++++++++++ editor/editor_about.cpp | 37 +++++- editor/editor_about.h | 6 +- 4 files changed, 351 insertions(+), 5 deletions(-) create mode 100644 editor/credits_roll.cpp create mode 100644 editor/credits_roll.h diff --git a/editor/credits_roll.cpp b/editor/credits_roll.cpp new file mode 100644 index 00000000000..265714d3648 --- /dev/null +++ b/editor/credits_roll.cpp @@ -0,0 +1,245 @@ +/**************************************************************************/ +/* credits_roll.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 "credits_roll.h" + +#include "core/authors.gen.h" +#include "core/donors.gen.h" +#include "core/input/input.h" +#include "core/license.gen.h" +#include "core/string/string_builder.h" +#include "editor/editor_string_names.h" +#include "editor/themes/editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/color_rect.h" +#include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" + +Label *CreditsRoll::_create_label(const String &p_with_text, LabelSize p_size) { + Label *label = memnew(Label); + label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + label->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + label->set_text(p_with_text); + + switch (p_size) { + case LabelSize::NORMAL: { + label->add_theme_font_size_override(SceneStringName(font_size), font_size_normal); + } break; + + case LabelSize::HEADER: { + label->add_theme_font_size_override(SceneStringName(font_size), font_size_header); + label->add_theme_font_override(SceneStringName(font), bold_font); + } break; + + case LabelSize::BIG_HEADER: { + label->add_theme_font_size_override(SceneStringName(font_size), font_size_big_header); + label->add_theme_font_override(SceneStringName(font), bold_font); + } break; + } + content->add_child(label); + return label; +} + +void CreditsRoll::_create_nothing(int p_size) { + if (p_size == -1) { + p_size = 30 * EDSCALE; + } + Control *c = memnew(Control); + c->set_custom_minimum_size(Vector2(0, p_size)); + content->add_child(c); +} + +String CreditsRoll::_build_string(const char *const *p_from) const { + StringBuilder sb; + + while (*p_from) { + sb.append(String::utf8(*p_from)); + sb.append("\n"); + p_from++; + } + return sb.as_string(); +} + +void CreditsRoll::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + set_process_internal(false); + mouse_enabled = false; + } + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: { + if (project_manager) { + project_manager->set_text(TTR("Project Manager", "Job Title")); + } + } break; + + case NOTIFICATION_WM_GO_BACK_REQUEST: { + hide(); + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { + const Vector2 pos = content->get_position(); + if (pos.y < -content->get_size().y - 30) { + hide(); + break; + } + + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) { + hide(); + break; + } + + bool lmb = Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT); + if (!mouse_enabled && !lmb) { + // Makes sure that the initial double click does not speed up text. + mouse_enabled = true; + } + + if ((mouse_enabled && lmb) || Input::get_singleton()->is_action_pressed(SNAME("ui_accept"))) { + content->set_position(Vector2(pos.x, pos.y - 2000 * get_process_delta_time())); + } else { + content->set_position(Vector2(pos.x, pos.y - 100 * get_process_delta_time())); + } + } break; + } +} + +void CreditsRoll::roll_credits() { + if (!project_manager) { + font_size_normal = get_theme_font_size("main_size", EditorStringName(EditorFonts)) * 2; + font_size_header = font_size_normal + 10 * EDSCALE; + font_size_big_header = font_size_header + 20 * EDSCALE; + bold_font = get_theme_font("bold", EditorStringName(EditorFonts)); + + { + const Ref logo_texture = get_editor_theme_icon("Logo"); + + TextureRect *logo = memnew(TextureRect); + logo->set_custom_minimum_size(Vector2(0, logo_texture->get_height() * 3)); + logo->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + logo->set_texture(logo_texture); + content->add_child(logo); + } + + _create_label(TTRC("Credits"), LabelSize::BIG_HEADER); + + _create_nothing(); + + _create_label(TTRC("Project Founders"), LabelSize::HEADER); + _create_label(_build_string(AUTHORS_FOUNDERS)); + + _create_nothing(); + + _create_label(TTRC("Lead Developer"), LabelSize::HEADER); + _create_label(_build_string(AUTHORS_LEAD_DEVELOPERS)); + + _create_nothing(); + + project_manager = _create_label(TTR("Project Manager", "Job Title"), LabelSize::HEADER); + _create_label(_build_string(AUTHORS_PROJECT_MANAGERS)); + + _create_nothing(); + + _create_label(TTRC("Developers"), LabelSize::HEADER); + _create_label(_build_string(AUTHORS_DEVELOPERS)); + + _create_nothing(); + + _create_label(TTRC("Patrons"), LabelSize::HEADER); + _create_label(_build_string(DONORS_PATRONS)); + + _create_nothing(); + + _create_label(TTRC("Platinum Sponsors"), LabelSize::HEADER); + _create_label(_build_string(DONORS_SPONSORS_PLATINUM)); + + _create_nothing(); + + _create_label(TTRC("Gold Sponsors"), LabelSize::HEADER); + _create_label(_build_string(DONORS_SPONSORS_GOLD)); + + _create_nothing(); + + _create_label(TTRC("Silver Sponsors"), LabelSize::HEADER); + _create_label(_build_string(DONORS_SPONSORS_SILVER)); + + _create_nothing(); + + _create_label(TTRC("Diamond Members"), LabelSize::HEADER); + _create_label(_build_string(DONORS_MEMBERS_DIAMOND)); + + _create_nothing(); + + _create_label(TTRC("Titanium Members"), LabelSize::HEADER); + _create_label(_build_string(DONORS_MEMBERS_TITANIUM)); + + _create_nothing(); + + _create_label(TTRC("Platinum Members"), LabelSize::HEADER); + _create_label(_build_string(DONORS_MEMBERS_PLATINUM)); + + _create_nothing(); + + _create_label(TTRC("Gold Members"), LabelSize::HEADER); + _create_label(_build_string(DONORS_MEMBERS_GOLD)); + + _create_nothing(); + _create_label(String::utf8(GODOT_LICENSE_TEXT)); + + _create_nothing(400 * EDSCALE); + _create_label(TTRC("Thank you for choosing Godot Engine!"), LabelSize::BIG_HEADER); + } + + Window *root = get_tree()->get_root(); + content->set_position(Vector2(content->get_position().x, root->get_size().y + 30)); + + set_position(root->get_position()); + set_size(root->get_size()); + + popup(); + set_process_internal(true); +} + +CreditsRoll::CreditsRoll() { + set_wrap_controls(false); + + { + ColorRect *background = memnew(ColorRect); + background->set_color(Color(0, 0, 0, 1)); + background->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + add_child(background); + } + + content = memnew(VBoxContainer); + content->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + add_child(content); +} diff --git a/editor/credits_roll.h b/editor/credits_roll.h new file mode 100644 index 00000000000..dbc33715a47 --- /dev/null +++ b/editor/credits_roll.h @@ -0,0 +1,68 @@ +/**************************************************************************/ +/* credits_roll.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/popup.h" + +class Label; +class VBoxContainer; +class Font; + +class CreditsRoll : public Popup { + GDCLASS(CreditsRoll, Popup); + + enum class LabelSize { + NORMAL, + HEADER, + BIG_HEADER, + }; + + int font_size_normal = 0; + int font_size_header = 0; + int font_size_big_header = 0; + Ref bold_font; + + bool mouse_enabled = false; + VBoxContainer *content = nullptr; + Label *project_manager = nullptr; + + Label *_create_label(const String &p_with_text, LabelSize p_size = LabelSize::NORMAL); + void _create_nothing(int p_size = -1); + String _build_string(const char *const *p_from) const; + +protected: + void _notification(int p_what); + +public: + void roll_credits(); + + CreditsRoll(); +}; diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index b1c327fd0fa..6e7be9763b8 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -33,6 +33,8 @@ #include "core/authors.gen.h" #include "core/donors.gen.h" #include "core/license.gen.h" +#include "editor/credits_roll.h" +#include "editor/editor_node.h" #include "editor/editor_string_names.h" #include "editor/gui/editor_version_button.h" #include "editor/themes/editor_scale.h" @@ -80,6 +82,19 @@ void EditorAbout::_license_tree_selected() { _tpl_text->set_text(selected->get_metadata(0)); } +void EditorAbout::_project_manager_clicked() { + if (!EditorNode::get_singleton()) { + // Don't allow in Project Manager. + return; + } + + if (!credits_roll) { + credits_roll = memnew(CreditsRoll); + add_child(credits_roll); + } + credits_roll->roll_credits(); +} + void EditorAbout::_item_with_website_selected(int p_id, ItemList *p_il) { const String website = p_il->get_item_metadata(p_id); if (!website.is_empty()) { @@ -91,7 +106,7 @@ void EditorAbout::_item_list_resized(ItemList *p_il) { p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?. } -ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website) { +ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website, const String &p_easter_egg_section) { ScrollContainer *sc = memnew(ScrollContainer); sc->set_name(p_name); sc->set_v_size_flags(Control::SIZE_EXPAND); @@ -107,9 +122,11 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const Listset_theme_type_variation("HeaderSmall"); - lbl->set_text(*itr); + lbl->set_text(section_name); vbc->add_child(lbl); ItemList *il = memnew(ItemList); @@ -151,6 +168,17 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const Listset_focus_mode(Control::FOCUS_CLICK); + il->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + il->connect("item_activated", callable_mp(this, &EditorAbout::_project_manager_clicked).unbind(1), CONNECT_DEFERRED); + + il->add_theme_style_override("focus", empty_stylebox); + il->add_theme_style_override("selected", empty_stylebox); + } + while (*names_ptr) { il->add_item(String::utf8(*names_ptr++), nullptr, false); } @@ -217,7 +245,8 @@ EditorAbout::EditorAbout() { dev_sections.push_back(TTR("Project Founders")); dev_sections.push_back(TTR("Lead Developer")); // TRANSLATORS: This refers to a job title. - dev_sections.push_back(TTR("Project Manager", "Job Title")); + const String project_manager = TTR("Project Manager", "Job Title"); + dev_sections.push_back(project_manager); dev_sections.push_back(TTR("Developers")); const char *const *dev_src[] = { AUTHORS_FOUNDERS, @@ -225,7 +254,7 @@ EditorAbout::EditorAbout() { AUTHORS_PROJECT_MANAGERS, AUTHORS_DEVELOPERS, }; - tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1)); // First section (Project Founders) is always one column. + tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1, false, project_manager)); // First section (Project Founders) is always one column. // Donors. diff --git a/editor/editor_about.h b/editor/editor_about.h index ac5def663f9..9e10e1d2ea9 100644 --- a/editor/editor_about.h +++ b/editor/editor_about.h @@ -37,6 +37,8 @@ #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" +class CreditsRoll; + /** * NOTE: Do not assume the EditorNode singleton to be available in this class' methods. * EditorAbout is also used from the project manager where EditorNode isn't initialized. @@ -46,15 +48,17 @@ class EditorAbout : public AcceptDialog { private: void _license_tree_selected(); + void _project_manager_clicked(); void _item_with_website_selected(int p_id, ItemList *p_il); void _item_list_resized(ItemList *p_il); - ScrollContainer *_populate_list(const String &p_name, const List &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false); + ScrollContainer *_populate_list(const String &p_name, const List &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false, const String &p_easter_egg_section = String()); Tree *_tpl_tree = nullptr; RichTextLabel *license_text_label = nullptr; RichTextLabel *_tpl_text = nullptr; TextureRect *_logo = nullptr; Vector name_lists; + CreditsRoll *credits_roll = nullptr; protected: void _notification(int p_what);