Skip to content

Commit

Permalink
Add built-in GUI to display license notices
Browse files Browse the repository at this point in the history
Press Ctrl/Cmd + Shift + L (`ui_toggle_licenses_dialog` built-in action)
to show/hide the notices dialog.

The dialog can be shown via script using
`SceneTree.licenses_dialog_visible = true|false`.

Co-authored-by: MewPurPur <[email protected]>
  • Loading branch information
Calinou and MewPurPur committed Oct 28, 2024
1 parent a308047 commit 158b224
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 0 deletions.
7 changes: 7 additions & 0 deletions core/input/input_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
{ "ui_filedialog_show_hidden", TTRC("Show Hidden") },
{ "ui_swap_input_direction ", TTRC("Swap Input Direction") },
{ "ui_unicode_start", TTRC("Start Unicode Character Input") },
{ "ui_toggle_licenses_dialog", TTRC("Toggle License Notices") },
{ "", ""}
/* clang-format on */
};
Expand Down Expand Up @@ -785,6 +786,12 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs.push_back(InputEventKey::create_reference(Key::QUOTELEFT | KeyModifierMask::CMD_OR_CTRL));
default_builtin_cache.insert("ui_swap_input_direction", inputs);

// ///// UI Misc Shortcuts /////

inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::L | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT));
default_builtin_cache.insert("ui_toggle_licenses_dialog", inputs);

return default_builtin_cache;
}

Expand Down
4 changes: 4 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,10 @@
Default [InputEventAction] to toggle [i]insert mode[/i] in a text field. While in insert mode, inserting new text overrides the character after the cursor, unless the next character is a new line.
[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
</member>
<member name="input/ui_toggle_licenses_dialog" type="Dictionary" setter="" getter="">
Toggles the built-in dialog which displays Godot's third-party notices. This can also be toggled from a script using [member SceneTree.licenses_dialog_visible].
Since the default shortcut is not usable on mobile platforms, it is recommended to create a button that sets [member SceneTree.licenses_dialog_visible] to [code]true[/code] when pressed in your project's menus. See [url=$DOCS_URL/complying_with_licenses.html]Complying with licenses[/url] in the documentation for more information.
</member>
<member name="input/ui_undo" type="Dictionary" setter="" getter="">
Default [InputEventAction] to undo the most recent action.
[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/SceneTree.xml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@
The root of the scene currently being edited in the editor. This is usually a direct child of [member root].
[b]Note:[/b] This property does nothing in release builds.
</member>
<member name="licenses_dialog_visible" type="bool" setter="set_licenses_dialog_visible" getter="is_licenses_dialog_visible" default="false">
If [code]true[/code], shows the built-in dialog which displays Godot's third-party notices. This dialog can also be toggled by pressing the [member ProjectSettings.input/ui_toggle_licenses_dialog] built-in action. See [url=$DOCS_URL/complying_with_licenses.html]Complying with licenses[/url] in the documentation for more information.
</member>
<member name="multiplayer_poll" type="bool" setter="set_multiplayer_poll_enabled" getter="is_multiplayer_poll_enabled" default="true">
If [code]true[/code] (default value), enables automatic polling of the [MultiplayerAPI] for this SceneTree during [signal process_frame].
If [code]false[/code], you need to manually call [method MultiplayerAPI.poll] to process network packets and deliver RPCs. This allows running RPCs in a different loop (e.g. physics, thread, specific time step) and for manual [Mutex] protection when accessing the [MultiplayerAPI] from threads.
Expand Down
1 change: 1 addition & 0 deletions editor/icons/LicensesDialog.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
146 changes: 146 additions & 0 deletions scene/gui/licenses_dialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**************************************************************************/
/* licenses_dialog.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 "licenses_dialog.h"

#include "box_container.h"
#include "button.h"
#include "core/license.gen.h"
#include "core/string/string_buffer.h"
#include "label.h"
#include "margin_container.h"
#include "panel_container.h"
#include "rich_text_label.h"
#include "scene/main/canvas_layer.h"
#include "scene/resources/style_box_flat.h"

void LicensesDialog::_close_button_pressed() {
SceneTree::get_singleton()->set_licenses_dialog_visible(false);
}

void LicensesDialog::unhandled_key_input(const Ref<InputEvent> &p_event) {
if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
SceneTree::get_singleton()->set_licenses_dialog_visible(false);
Node::get_viewport()->set_input_as_handled();
}
}

LicensesDialog::LicensesDialog() {
// Set on the highest layer, so that nothing else can draw on top.
set_layer(128);

set_process_unhandled_key_input(true);

MarginContainer *margin_container = memnew(MarginContainer);
margin_container->set_anchors_preset(Control::PRESET_FULL_RECT);
const float default_base_scale = margin_container->get_theme_default_base_scale();
const float default_font_size = margin_container->get_theme_default_font_size();
margin_container->add_theme_constant_override("margin_top", Math::round(20 * default_base_scale));
margin_container->add_theme_constant_override("margin_right", Math::round(20 * default_base_scale));
margin_container->add_theme_constant_override("margin_bottom", Math::round(20 * default_base_scale));
margin_container->add_theme_constant_override("margin_left", Math::round(20 * default_base_scale));
add_child(margin_container);

PanelContainer *panel_container = memnew(PanelContainer);
margin_container->add_child(panel_container);

MarginContainer *inner_margin_container = memnew(MarginContainer);
inner_margin_container->add_theme_constant_override("margin_top", Math::round(10 * default_base_scale));
inner_margin_container->add_theme_constant_override("margin_right", Math::round(10 * default_base_scale));
inner_margin_container->add_theme_constant_override("margin_bottom", Math::round(10 * default_base_scale));
inner_margin_container->add_theme_constant_override("margin_left", Math::round(10 * default_base_scale));
panel_container->add_child(inner_margin_container);

VBoxContainer *vbox_container = memnew(VBoxContainer);
vbox_container->add_theme_constant_override("separation", Math::round(10 * default_base_scale));
inner_margin_container->add_child(vbox_container);

Label *title_label = memnew(Label);
title_label->set_text(RTR("Third-party notices"));
title_label->add_theme_font_size_override(SceneStringName(font_size), Math::round(1.333 * default_font_size));
title_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
vbox_container->add_child(title_label);

// Based on `editor_about.cpp` with references to TreeItem removed,
// as we only have the "All Components" view here. A preamble is also added.
StringBuffer<> long_text;
long_text += RTR("This project is powered by Godot Engine, which relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms.") + "\n\n";

long_text += RTR("Components:") + "\n\n";

for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {
const ComponentCopyright &component = COPYRIGHT_INFO[component_index];
const String component_name = String::utf8(component.name);
long_text += "- " + component_name;
for (int part_index = 0; part_index < component.part_count; part_index++) {
const ComponentCopyrightPart &part = component.parts[part_index];
String copyright;
for (int copyright_index = 0; copyright_index < part.copyright_count; copyright_index++) {
copyright += String::utf8("\n \xc2\xa9 ") + String::utf8(part.copyright_statements[copyright_index]);
}
long_text += copyright;
String license = "\n License: " + String::utf8(part.license) + "\n";
long_text += license + "\n\n";
}
}

long_text += RTR("Licenses:") + "\n\n";

for (int i = 0; i < LICENSE_COUNT; i++) {
const String licensename = String::utf8(LICENSE_NAMES[i]);
long_text += "- " + licensename + "\n";
const String licensebody = String::utf8(LICENSE_BODIES[i]);
long_text += " " + licensebody.replace("\n", "\n ") + "\n\n";
}

RichTextLabel *rich_text_label = memnew(RichTextLabel);
rich_text_label->set_text(long_text);
rich_text_label->set_threaded(true);
rich_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
rich_text_label->set_focus_mode(Control::FOCUS_ALL);

// Add a background to the scrollable area with the license text.
Ref<StyleBoxFlat> background;
background.instantiate();
background->set_bg_color(Color(0, 0, 0, 0.25));
background->set_content_margin_all(Math::round(10 * default_base_scale));
rich_text_label->add_theme_style_override(CoreStringName(normal), background);

vbox_container->add_child(rich_text_label);
// Allow for keyboard navigation by grabbing focus immediately on the scrollable control.
callable_mp((Control *)rich_text_label, &Control::grab_focus).call_deferred();

Button *close_button = memnew(Button);
close_button->set_text(RTR("Close"));
close_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
close_button->set_custom_minimum_size(Vector2(100, 40) * default_base_scale);
close_button->connect(SceneStringName(pressed), callable_mp(this, &LicensesDialog::_close_button_pressed));
vbox_container->add_child(close_button);
}
48 changes: 48 additions & 0 deletions scene/gui/licenses_dialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**************************************************************************/
/* licenses_dialog.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. */
/**************************************************************************/

#ifndef LICENSES_DIALOG_H
#define LICENSES_DIALOG_H

#include "scene/main/canvas_layer.h"

class LicensesDialog : public CanvasLayer {
GDCLASS(LicensesDialog, CanvasLayer);

void _close_button_pressed();

protected:
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;

public:
LicensesDialog();
};

#endif // LICENSES_DIALOG_H
35 changes: 35 additions & 0 deletions scene/main/scene_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "scene/animation/tween.h"
#include "scene/debugger/scene_debugger.h"
#include "scene/gui/control.h"
#include "scene/gui/licenses_dialog.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/viewport.h"
#include "scene/resources/environment.h"
Expand Down Expand Up @@ -1649,6 +1650,36 @@ bool SceneTree::is_multiplayer_poll_enabled() const {
return multiplayer_poll;
}

void SceneTree::set_licenses_dialog_visible(bool p_visible) {
if (p_visible) {
if (licenses_dialog == nullptr) {
licenses_dialog = memnew(LicensesDialog);
// Begin name with an underscore to avoid conflict with project nodes.
licenses_dialog->set_name("_LicensesDialog");
get_root()->add_child(licenses_dialog, false, Node::INTERNAL_MODE_BACK);
} else {
ERR_PRINT("Licenses dialog already exists.");
}
} else {
if (licenses_dialog != nullptr) {
// Free when closing to avoid reserving memory during the project's run duration.
licenses_dialog->queue_free();
licenses_dialog = nullptr;
} else {
ERR_PRINT("Couldn't find licenses dialog to hide.");
}
}
}

bool SceneTree::is_licenses_dialog_visible() const {
if (licenses_dialog) {
return licenses_dialog->is_visible();
}

// Licenses dialog isn't created yet. Therefore, it's not visible.
return false;
}

void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_root"), &SceneTree::get_root);
ClassDB::bind_method(D_METHOD("has_group", "name"), &SceneTree::has_group);
Expand Down Expand Up @@ -1723,6 +1754,9 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled);
ClassDB::bind_method(D_METHOD("is_multiplayer_poll_enabled"), &SceneTree::is_multiplayer_poll_enabled);

ClassDB::bind_method(D_METHOD("set_licenses_dialog_visible", "visible"), &SceneTree::set_licenses_dialog_visible);
ClassDB::bind_method(D_METHOD("is_licenses_dialog_visible"), &SceneTree::is_licenses_dialog_visible);

ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_accept_quit"), "set_auto_accept_quit", "is_auto_accept_quit");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "quit_on_go_back"), "set_quit_on_go_back", "is_quit_on_go_back");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_collisions_hint"), "set_debug_collisions_hint", "is_debugging_collisions_hint");
Expand All @@ -1734,6 +1768,7 @@ void SceneTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "licenses_dialog_visible"), "set_licenses_dialog_visible", "is_licenses_dialog_visible");

ADD_SIGNAL(MethodInfo("tree_changed"));
ADD_SIGNAL(MethodInfo("tree_process_mode_changed")); //editor only signal, but due to API hash it can't be removed in run-time
Expand Down
7 changes: 7 additions & 0 deletions scene/main/scene_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Node;
#ifndef _3D_DISABLED
class Node3D;
#endif
class LicensesDialog;
class Window;
class Material;
class Mesh;
Expand Down Expand Up @@ -190,6 +191,9 @@ class SceneTree : public MainLoop {
Node *prev_scene = nullptr;
Node *pending_new_scene = nullptr;

// Initialized lazily and destroyed eagerly to decrease RAM usage, since it contains a lot of text.
LicensesDialog *licenses_dialog = nullptr;

Color debug_collisions_color;
Color debug_collision_contact_color;
Color debug_paths_color;
Expand Down Expand Up @@ -426,6 +430,9 @@ class SceneTree : public MainLoop {
void set_multiplayer_poll_enabled(bool p_enabled);
bool is_multiplayer_poll_enabled() const;

void set_licenses_dialog_visible(bool p_visible);
bool is_licenses_dialog_visible() const;

static void add_idle_callback(IdleCallback p_callback);

void set_disable_node_threading(bool p_disable);
Expand Down
4 changes: 4 additions & 0 deletions scene/main/viewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
}
}

if (!Engine::get_singleton()->is_editor_hint() && !Engine::get_singleton()->is_project_manager_hint() && p_event->is_action_pressed("ui_toggle_licenses_dialog")) {
SceneTree::get_singleton()->set_licenses_dialog_visible(!SceneTree::get_singleton()->is_licenses_dialog_visible());
}

if (gui.key_focus && !gui.key_focus->is_visible_in_tree()) {
gui.key_focus->release_focus();
}
Expand Down

0 comments on commit 158b224

Please sign in to comment.