Skip to content

Commit daa6198

Browse files
CalinouMewPurPur
andcommitted
Add built-in GUI to display license notices
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]>
1 parent a308047 commit daa6198

File tree

9 files changed

+259
-0
lines changed

9 files changed

+259
-0
lines changed

core/input/input_map.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
400400
{ "ui_filedialog_show_hidden", TTRC("Show Hidden") },
401401
{ "ui_swap_input_direction ", TTRC("Swap Input Direction") },
402402
{ "ui_unicode_start", TTRC("Start Unicode Character Input") },
403+
{ "ui_toggle_licenses_dialog", TTRC("Toggle License Notices") },
403404
{ "", ""}
404405
/* clang-format on */
405406
};
@@ -785,6 +786,12 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
785786
inputs.push_back(InputEventKey::create_reference(Key::QUOTELEFT | KeyModifierMask::CMD_OR_CTRL));
786787
default_builtin_cache.insert("ui_swap_input_direction", inputs);
787788

789+
// ///// UI Misc Shortcuts /////
790+
791+
inputs = List<Ref<InputEvent>>();
792+
inputs.push_back(InputEventKey::create_reference(Key::L | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT));
793+
default_builtin_cache.insert("ui_toggle_licenses_dialog", inputs);
794+
788795
return default_builtin_cache;
789796
}
790797

doc/classes/ProjectSettings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,10 @@
13881388
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.
13891389
[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.
13901390
</member>
1391+
<member name="input/ui_toggle_licenses_dialog" type="Dictionary" setter="" getter="">
1392+
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].
1393+
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 menu. See [url=$DOCS_URL/complying_with_licenses.html]Complying with licenses[/url] in the documentation for more information.
1394+
</member>
13911395
<member name="input/ui_undo" type="Dictionary" setter="" getter="">
13921396
Default [InputEventAction] to undo the most recent action.
13931397
[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.

doc/classes/SceneTree.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@
255255
The root of the scene currently being edited in the editor. This is usually a direct child of [member root].
256256
[b]Note:[/b] This property does nothing in release builds.
257257
</member>
258+
<member name="licenses_dialog_visible" type="bool" setter="set_licenses_dialog_visible" getter="is_licenses_dialog_visible" default="false">
259+
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.
260+
</member>
258261
<member name="multiplayer_poll" type="bool" setter="set_multiplayer_poll_enabled" getter="is_multiplayer_poll_enabled" default="true">
259262
If [code]true[/code] (default value), enables automatic polling of the [MultiplayerAPI] for this SceneTree during [signal process_frame].
260263
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.

editor/icons/LicensesDialog.svg

Lines changed: 1 addition & 0 deletions
Loading

scene/gui/licenses_dialog.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/**************************************************************************/
2+
/* licenses_dialog.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "licenses_dialog.h"
32+
33+
#include "core/license.gen.h"
34+
#include "core/string/string_buffer.h"
35+
#include "scene/gui/box_container.h"
36+
#include "scene/gui/button.h"
37+
#include "scene/gui/label.h"
38+
#include "scene/gui/margin_container.h"
39+
#include "scene/gui/panel_container.h"
40+
#include "scene/gui/rich_text_label.h"
41+
#include "scene/main/canvas_item.h"
42+
#include "scene/resources/style_box_flat.h"
43+
44+
void LicensesDialog::_close_button_pressed() {
45+
SceneTree::get_singleton()->set_licenses_dialog_visible(false);
46+
}
47+
48+
void LicensesDialog::unhandled_key_input(const Ref<InputEvent> &p_event) {
49+
if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
50+
SceneTree::get_singleton()->set_licenses_dialog_visible(false);
51+
Node::get_viewport()->set_input_as_handled();
52+
}
53+
}
54+
55+
LicensesDialog::LicensesDialog() {
56+
// Set on the highest layer, so that nothing else can draw on top.
57+
set_layer(128);
58+
59+
// Keep UI interactions functional even if the game is paused.
60+
set_process_mode(Node::PROCESS_MODE_ALWAYS);
61+
62+
set_process_unhandled_key_input(true);
63+
64+
MarginContainer *margin_container = memnew(MarginContainer);
65+
margin_container->set_anchors_preset(Control::PRESET_FULL_RECT);
66+
const float default_base_scale = margin_container->get_theme_default_base_scale();
67+
const float default_font_size = margin_container->get_theme_default_font_size();
68+
margin_container->add_theme_constant_override("margin_top", Math::round(20 * default_base_scale));
69+
margin_container->add_theme_constant_override("margin_right", Math::round(20 * default_base_scale));
70+
margin_container->add_theme_constant_override("margin_bottom", Math::round(20 * default_base_scale));
71+
margin_container->add_theme_constant_override("margin_left", Math::round(20 * default_base_scale));
72+
add_child(margin_container);
73+
74+
PanelContainer *panel_container = memnew(PanelContainer);
75+
margin_container->add_child(panel_container);
76+
77+
MarginContainer *inner_margin_container = memnew(MarginContainer);
78+
inner_margin_container->add_theme_constant_override("margin_top", Math::round(10 * default_base_scale));
79+
inner_margin_container->add_theme_constant_override("margin_right", Math::round(10 * default_base_scale));
80+
inner_margin_container->add_theme_constant_override("margin_bottom", Math::round(10 * default_base_scale));
81+
inner_margin_container->add_theme_constant_override("margin_left", Math::round(10 * default_base_scale));
82+
panel_container->add_child(inner_margin_container);
83+
84+
VBoxContainer *vbox_container = memnew(VBoxContainer);
85+
vbox_container->add_theme_constant_override("separation", Math::round(10 * default_base_scale));
86+
inner_margin_container->add_child(vbox_container);
87+
88+
Label *title_label = memnew(Label);
89+
title_label->set_text(RTR("Third-party notices"));
90+
title_label->add_theme_font_size_override(SceneStringName(font_size), Math::round(1.333 * default_font_size));
91+
title_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
92+
vbox_container->add_child(title_label);
93+
94+
// Based on `editor_about.cpp` with references to TreeItem removed,
95+
// as we only have the "All Components" view here. A preamble is also added.
96+
StringBuffer<> long_text;
97+
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";
98+
99+
long_text += RTR("Components:") + "\n\n";
100+
101+
for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {
102+
const ComponentCopyright &component = COPYRIGHT_INFO[component_index];
103+
const String component_name = String::utf8(component.name);
104+
long_text += "- " + component_name;
105+
for (int part_index = 0; part_index < component.part_count; part_index++) {
106+
const ComponentCopyrightPart &part = component.parts[part_index];
107+
String copyright;
108+
for (int copyright_index = 0; copyright_index < part.copyright_count; copyright_index++) {
109+
copyright += String::utf8("\n \xc2\xa9 ") + String::utf8(part.copyright_statements[copyright_index]);
110+
}
111+
long_text += copyright;
112+
String license = "\n License: " + String::utf8(part.license) + "\n";
113+
long_text += license + "\n\n";
114+
}
115+
}
116+
117+
long_text += RTR("Licenses:") + "\n\n";
118+
119+
for (int i = 0; i < LICENSE_COUNT; i++) {
120+
const String licensename = String::utf8(LICENSE_NAMES[i]);
121+
long_text += "- " + licensename + "\n";
122+
const String licensebody = String::utf8(LICENSE_BODIES[i]);
123+
long_text += " " + licensebody.replace("\n", "\n ") + "\n\n";
124+
}
125+
126+
RichTextLabel *rich_text_label = memnew(RichTextLabel);
127+
rich_text_label->set_text(long_text);
128+
rich_text_label->set_threaded(true);
129+
rich_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
130+
rich_text_label->set_focus_mode(Control::FOCUS_ALL);
131+
rich_text_label->add_theme_font_size_override("normal_font_size", Math::round(0.75 * default_font_size));
132+
133+
// Add a background to the scrollable area with the license text.
134+
Ref<StyleBoxFlat> background;
135+
background.instantiate();
136+
background->set_bg_color(Color(0, 0, 0, 0.5));
137+
background->set_content_margin_all(Math::round(10 * default_base_scale));
138+
rich_text_label->add_theme_style_override(CoreStringName(normal), background);
139+
140+
vbox_container->add_child(rich_text_label);
141+
// Allow for keyboard navigation by grabbing focus immediately on the scrollable control.
142+
callable_mp((Control *)rich_text_label, &Control::grab_focus).call_deferred();
143+
144+
Button *close_button = memnew(Button);
145+
close_button->set_text(RTR("Close"));
146+
close_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
147+
close_button->set_custom_minimum_size(Vector2(100, 40) * default_base_scale);
148+
close_button->connect(SceneStringName(pressed), callable_mp(this, &LicensesDialog::_close_button_pressed));
149+
vbox_container->add_child(close_button);
150+
}

scene/gui/licenses_dialog.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**************************************************************************/
2+
/* licenses_dialog.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#ifndef LICENSES_DIALOG_H
32+
#define LICENSES_DIALOG_H
33+
34+
#include "scene/main/canvas_layer.h"
35+
36+
class LicensesDialog : public CanvasLayer {
37+
GDCLASS(LicensesDialog, CanvasLayer);
38+
39+
void _close_button_pressed();
40+
41+
protected:
42+
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
43+
44+
public:
45+
LicensesDialog();
46+
};
47+
48+
#endif // LICENSES_DIALOG_H

scene/main/scene_tree.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "scene/animation/tween.h"
4747
#include "scene/debugger/scene_debugger.h"
4848
#include "scene/gui/control.h"
49+
#include "scene/gui/licenses_dialog.h"
4950
#include "scene/main/multiplayer_api.h"
5051
#include "scene/main/viewport.h"
5152
#include "scene/resources/environment.h"
@@ -1649,6 +1650,36 @@ bool SceneTree::is_multiplayer_poll_enabled() const {
16491650
return multiplayer_poll;
16501651
}
16511652

1653+
void SceneTree::set_licenses_dialog_visible(bool p_visible) {
1654+
if (p_visible) {
1655+
if (licenses_dialog == nullptr) {
1656+
licenses_dialog = memnew(LicensesDialog);
1657+
// Begin name with an underscore to avoid conflict with project nodes.
1658+
licenses_dialog->set_name("_LicensesDialog");
1659+
get_root()->add_child(licenses_dialog, false, Node::INTERNAL_MODE_BACK);
1660+
} else {
1661+
ERR_PRINT("Licenses dialog already exists.");
1662+
}
1663+
} else {
1664+
if (licenses_dialog != nullptr) {
1665+
// Free when closing to avoid reserving memory during the project's run duration.
1666+
licenses_dialog->queue_free();
1667+
licenses_dialog = nullptr;
1668+
} else {
1669+
ERR_PRINT("Couldn't find licenses dialog to hide.");
1670+
}
1671+
}
1672+
}
1673+
1674+
bool SceneTree::is_licenses_dialog_visible() const {
1675+
if (licenses_dialog) {
1676+
return licenses_dialog->is_visible();
1677+
}
1678+
1679+
// Licenses dialog isn't created yet. Therefore, it's not visible.
1680+
return false;
1681+
}
1682+
16521683
void SceneTree::_bind_methods() {
16531684
ClassDB::bind_method(D_METHOD("get_root"), &SceneTree::get_root);
16541685
ClassDB::bind_method(D_METHOD("has_group", "name"), &SceneTree::has_group);
@@ -1723,6 +1754,9 @@ void SceneTree::_bind_methods() {
17231754
ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled);
17241755
ClassDB::bind_method(D_METHOD("is_multiplayer_poll_enabled"), &SceneTree::is_multiplayer_poll_enabled);
17251756

1757+
ClassDB::bind_method(D_METHOD("set_licenses_dialog_visible", "visible"), &SceneTree::set_licenses_dialog_visible);
1758+
ClassDB::bind_method(D_METHOD("is_licenses_dialog_visible"), &SceneTree::is_licenses_dialog_visible);
1759+
17261760
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_accept_quit"), "set_auto_accept_quit", "is_auto_accept_quit");
17271761
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "quit_on_go_back"), "set_quit_on_go_back", "is_quit_on_go_back");
17281762
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_collisions_hint"), "set_debug_collisions_hint", "is_debugging_collisions_hint");
@@ -1734,6 +1768,7 @@ void SceneTree::_bind_methods() {
17341768
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root");
17351769
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled");
17361770
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled");
1771+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "licenses_dialog_visible"), "set_licenses_dialog_visible", "is_licenses_dialog_visible");
17371772

17381773
ADD_SIGNAL(MethodInfo("tree_changed"));
17391774
ADD_SIGNAL(MethodInfo("tree_process_mode_changed")); //editor only signal, but due to API hash it can't be removed in run-time

scene/main/scene_tree.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Node;
4444
#ifndef _3D_DISABLED
4545
class Node3D;
4646
#endif
47+
class LicensesDialog;
4748
class Window;
4849
class Material;
4950
class Mesh;
@@ -190,6 +191,9 @@ class SceneTree : public MainLoop {
190191
Node *prev_scene = nullptr;
191192
Node *pending_new_scene = nullptr;
192193

194+
// Initialized lazily and destroyed eagerly to decrease RAM usage, since it contains a lot of text.
195+
LicensesDialog *licenses_dialog = nullptr;
196+
193197
Color debug_collisions_color;
194198
Color debug_collision_contact_color;
195199
Color debug_paths_color;
@@ -426,6 +430,9 @@ class SceneTree : public MainLoop {
426430
void set_multiplayer_poll_enabled(bool p_enabled);
427431
bool is_multiplayer_poll_enabled() const;
428432

433+
void set_licenses_dialog_visible(bool p_visible);
434+
bool is_licenses_dialog_visible() const;
435+
429436
static void add_idle_callback(IdleCallback p_callback);
430437

431438
void set_disable_node_threading(bool p_disable);

scene/main/viewport.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
21062106
}
21072107
}
21082108

2109+
if (!Engine::get_singleton()->is_editor_hint() && !Engine::get_singleton()->is_project_manager_hint() && p_event->is_action_pressed("ui_toggle_licenses_dialog")) {
2110+
SceneTree::get_singleton()->set_licenses_dialog_visible(!SceneTree::get_singleton()->is_licenses_dialog_visible());
2111+
}
2112+
21092113
if (gui.key_focus && !gui.key_focus->is_visible_in_tree()) {
21102114
gui.key_focus->release_focus();
21112115
}

0 commit comments

Comments
 (0)