Merge pull request #48589 from akien-mga/3.x-cherrypicks

This commit is contained in:
Rémi Verschelde 2021-05-09 18:54:49 +02:00 committed by GitHub
commit 8bd6cb0ec7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 178 additions and 465 deletions

View file

@ -1028,6 +1028,8 @@ ProjectSettings::ProjectSettings() {
}
extensions.push_back("shader");
GLOBAL_DEF("editor/main_run_args", "");
GLOBAL_DEF("editor/search_in_file_extensions", extensions);
custom_prop_info["editor/search_in_file_extensions"] = PropertyInfo(Variant::POOL_STRING_ARRAY, "editor/search_in_file_extensions");

View file

@ -5,6 +5,7 @@
</brief_description>
<description>
Plays an audio stream non-positionally.
To play audio positionally, use [AudioStreamPlayer2D] or [AudioStreamPlayer3D] instead of [AudioStreamPlayer].
</description>
<tutorials>
<link title="Audio streams">https://docs.godotengine.org/en/3.3/tutorials/audio/audio_streams.html</link>

View file

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AudioStreamPlayer2D" inherits="Node2D" version="3.4">
<brief_description>
Plays audio in 2D.
Plays positional sound in 2D space.
</brief_description>
<description>
Plays audio that dampens with distance from screen center.
See also [AudioStreamPlayer] to play a sound non-positionally.
[b]Note:[/b] Hiding an [AudioStreamPlayer2D] node does not disable its audio output. To temporarily disable an [AudioStreamPlayer2D]'s audio output, set [member volume_db] to a very low value like [code]-100[/code] (which isn't audible to human hearing).
</description>
<tutorials>
<link>https://docs.godotengine.org/en/3.3/tutorials/audio/audio_streams.html</link>

View file

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AudioStreamPlayer3D" inherits="Spatial" version="3.4">
<brief_description>
Plays 3D sound in 3D space.
Plays positional sound in 3D space.
</brief_description>
<description>
Plays a sound effect with directed sound effects, dampens with distance if needed, generates effect of hearable position in space. For greater realism, a low-pass filter is automatically applied to distant sounds. This can be disabled by setting [member attenuation_filter_cutoff_hz] to [code]20500[/code].
By default, audio is heard from the camera position. This can be changed by adding a [Listener] node to the scene and enabling it by calling [method Listener.make_current] on it.
See also [AudioStreamPlayer] to play a sound non-positionally.
[b]Note:[/b] Hiding an [AudioStreamPlayer3D] node does not disable its audio output. To temporarily disable an [AudioStreamPlayer3D]'s audio output, set [member unit_db] to a very low value like [code]-100[/code] (which isn't audible to human hearing).
</description>
<tutorials>
<link>https://docs.godotengine.org/en/3.3/tutorials/audio/audio_streams.html</link>

View file

@ -28,6 +28,15 @@
<members>
<member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" override="true" default="true" />
</members>
<signals>
<signal name="pressed">
<argument index="0" name="button" type="Object">
</argument>
<description>
Emitted when one of the buttons of the group is pressed.
</description>
</signal>
</signals>
<constants>
</constants>
</class>

View file

@ -157,7 +157,7 @@
Returns the command-line arguments passed to the engine.
Command-line arguments can be written in any form, including both [code]--key value[/code] and [code]--key=value[/code] forms so they can be properly parsed, as long as custom command-line arguments do not conflict with engine arguments.
You can also incorporate environment variables using the [method get_environment] method.
You can set [code]editor/main_run_args[/code] in the Project Settings to define command-line arguments to be passed by the editor when running the project.
You can set [member ProjectSettings.editor/main_run_args] to define command-line arguments to be passed by the editor when running the project.
Here's a minimal example on how to parse command-line arguments into a dictionary using the [code]--key=value[/code] form for arguments:
[codeblock]
var arguments = {}
@ -530,6 +530,7 @@
</return>
<description>
Returns a string that is unique to the device.
[b]Note:[/b] This string may change without notice if the user reinstalls/upgrades their operating system or changes their hardware. This means it should generally not be used to encrypt persistent data as the data saved prior to an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by [method get_unique_id] for security purposes.
[b]Note:[/b] Returns an empty string on HTML5 and UWP, as this method isn't implemented on those platforms yet.
</description>
</method>

View file

@ -514,6 +514,14 @@
If [code]Use Vsync[/code] is enabled and this setting is [code]true[/code], enables vertical synchronization via the operating system's window compositor when in windowed mode and the compositor is enabled. This will prevent stutter in certain situations. (Windows only.)
[b]Note:[/b] This option is experimental and meant to alleviate stutter experienced by some users. However, some users have experienced a Vsync framerate halving (e.g. from 60 FPS to 30 FPS) when using it.
</member>
<member name="editor/main_run_args" type="String" setter="" getter="" default="&quot;&quot;">
The command-line arguments to append to Godot's own command line when running the project. This doesn't affect the editor itself.
It is possible to make another executable run Godot by using the [code]%command%[/code] placeholder. The placeholder will be replaced with Godot's own command line. Program-specific arguments should be placed [i]before[/i] the placeholder, whereas Godot-specific arguments should be placed [i]after[/i] the placeholder.
For example, this can be used to force the project to run on the dedicated GPU in a NVIDIA Optimus system on Linux:
[codeblock]
prime-run %command%
[/codeblock]
</member>
<member name="editor/script_templates_search_path" type="String" setter="" getter="" default="&quot;res://script_templates&quot;">
Search path for project-specific script templates. Godot will search for script templates both in the editor-specific path and in this project-specific path.
</member>

View file

@ -2352,15 +2352,24 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
Node *scene = editor_data.get_edited_scene_root(scene_idx);
if (!scene) {
int saved = _save_external_resources();
String err_text;
if (saved > 0) {
err_text = vformat(TTR("Saved %s modified resource(s)."), itos(saved));
} else {
err_text = TTR("A root node is required to save the scene.");
if (p_option == FILE_SAVE_SCENE) {
// Pressing Ctrl + S saves the current script if a scene is currently open, but it won't if the scene has no root node.
// Work around this by explicitly saving the script in this case (similar to pressing Ctrl + Alt + S).
ScriptEditor::get_singleton()->save_current_script();
}
const int saved = _save_external_resources();
if (saved > 0) {
show_accept(
vformat(TTR("The current scene has no root node, but %d modified external resource(s) were saved anyway."), saved),
TTR("OK"));
} else if (p_option == FILE_SAVE_AS_SCENE) {
// Don't show this dialog when pressing Ctrl + S to avoid interfering with script saving.
show_accept(
TTR("A root node is required to save the scene. You can add a root node using the Scene tree dock."),
TTR("OK"));
}
show_accept(err_text, TTR("OK"));
break;
}
@ -5842,8 +5851,6 @@ EditorNode::EditorNode() {
register_exporters();
GLOBAL_DEF("editor/main_run_args", "");
ClassDB::set_class_enabled("RootMotionView", true);
//defs here, use EDITOR_GET in logic

View file

@ -2922,6 +2922,8 @@ EditorPropertyResource::EditorPropertyResource() {
preview->set_margin(MARGIN_TOP, 1);
preview->set_margin(MARGIN_BOTTOM, -1);
preview->set_margin(MARGIN_RIGHT, -1);
// This is required to draw the focus outline in front of the preview, rather than behind.
preview->set_draw_behind_parent(true);
assign->add_child(preview);
assign->connect("gui_input", this, "_button_input");

View file

@ -186,15 +186,50 @@ Error EditorRun::run(const String &p_scene, const String &p_custom_args, const L
args.push_back(p_scene);
}
String exec = OS::get_singleton()->get_executable_path();
if (p_custom_args != "") {
Vector<String> cargs = p_custom_args.split(" ", false);
for (int i = 0; i < cargs.size(); i++) {
args.push_back(cargs[i].replace(" ", "%20"));
// Allow the user to specify a command to run, similar to Steam's launch options.
// In this case, Godot will no longer be run directly; it's up to the underlying command
// to run it. For instance, this can be used on Linux to force a running project
// to use Optimus using `prime-run` or similar.
// Example: `prime-run %command% --time-scale 0.5`
const int placeholder_pos = p_custom_args.find("%command%");
Vector<String> custom_args;
if (placeholder_pos != -1) {
// Prepend executable-specific custom arguments.
// If nothing is placed before `%command%`, behave as if no placeholder was specified.
Vector<String> exec_args = p_custom_args.substr(0, placeholder_pos).split(" ", false);
if (exec_args.size() >= 1) {
exec = exec_args[0];
exec_args.remove(0);
// Append the Godot executable name before we append executable arguments
// (since the order is reversed when using `push_front()`).
args.push_front(OS::get_singleton()->get_executable_path());
}
for (int i = exec_args.size() - 1; i >= 0; i--) {
// Iterate backwards as we're pushing items in the reverse order.
args.push_front(exec_args[i].replace(" ", "%20"));
}
// Append Godot-specific custom arguments.
custom_args = p_custom_args.substr(placeholder_pos + String("%command%").size()).split(" ", false);
for (int i = 0; i < custom_args.size(); i++) {
args.push_back(custom_args[i].replace(" ", "%20"));
}
} else {
// Append Godot-specific custom arguments.
custom_args = p_custom_args.split(" ", false);
for (int i = 0; i < custom_args.size(); i++) {
args.push_back(custom_args[i].replace(" ", "%20"));
}
}
}
String exec = OS::get_singleton()->get_executable_path();
printf("Running: %ls", exec.c_str());
for (List<String>::Element *E = args.front(); E; E = E->next()) {
printf(" %ls", E->get().c_str());

View file

@ -386,7 +386,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("interface/theme/additional_spacing", 0);
hints["interface/theme/additional_spacing"] = PropertyInfo(Variant::REAL, "interface/theme/additional_spacing", PROPERTY_HINT_RANGE, "0,5,0.1", PROPERTY_USAGE_DEFAULT);
_initial_set("interface/theme/custom_theme", "");
hints["interface/theme/custom_theme"] = PropertyInfo(Variant::STRING, "interface/theme/custom_theme", PROPERTY_HINT_GLOBAL_FILE, "*.res,*.tres,*.theme", PROPERTY_USAGE_DEFAULT);
hints["interface/theme/custom_theme"] = PropertyInfo(Variant::STRING, "interface/theme/custom_theme", PROPERTY_HINT_GLOBAL_FILE, "*.res,*.tres,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
// Scene tabs
_initial_set("interface/scene_tabs/show_thumbnail_on_hover", true);

View file

@ -31,6 +31,7 @@
#include "audio_stream_editor_plugin.h"
#include "core/io/resource_loader.h"
#include "core/os/keyboard.h"
#include "core/project_settings.h"
#include "editor/audio_stream_preview.h"
#include "editor/editor_scale.h"
@ -144,23 +145,26 @@ void AudioStreamEditor::_draw_indicator() {
Rect2 rect = _preview->get_rect();
float len = stream->get_length();
float ofs_x = _current / len * rect.size.width;
_indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), get_color("accent_color", "Editor"), 1);
const Color color = get_color("accent_color", "Editor");
_indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), color, Math::round(2 * EDSCALE));
_indicator->draw_texture(
get_icon("TimelineIndicator", "EditorIcons"),
Point2(ofs_x - get_icon("TimelineIndicator", "EditorIcons")->get_width() * 0.5, 0),
color);
_current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /");
}
void AudioStreamEditor::_on_input_indicator(Ref<InputEvent> p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) {
if (mb->is_pressed()) {
_seek_to(mb->get_position().x);
}
_dragging = mb->is_pressed();
}
Ref<InputEventMouseMotion> mm = p_event;
const Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (_dragging) {
_seek_to(mm->get_position().x);
@ -234,6 +238,7 @@ AudioStreamEditor::AudioStreamEditor() {
hbox->add_child(_play_button);
_play_button->set_focus_mode(Control::FOCUS_NONE);
_play_button->connect("pressed", this, "_play");
_play_button->set_shortcut(ED_SHORTCUT("inspector/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), KEY_SPACE));
_stop_button = memnew(ToolButton);
hbox->add_child(_stop_button);

View file

@ -551,7 +551,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
if (p_save) {
// Do not try to save internal scripts
if (!script.is_valid() || !(script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1)) {
_menu_option(FILE_SAVE);
save_current_script();
}
}
@ -1089,33 +1089,7 @@ void ScriptEditor::_menu_option(int p_option) {
if (current) {
switch (p_option) {
case FILE_SAVE: {
if (_test_script_times_on_disk()) {
return;
}
if (trim_trailing_whitespace_on_save) {
current->trim_trailing_whitespace();
}
current->insert_final_newline();
if (convert_indent_on_save) {
if (use_space_indentation) {
current->convert_indent_to_spaces();
} else {
current->convert_indent_to_tabs();
}
}
RES resource = current->get_edited_resource();
Ref<TextFile> text_file = resource;
if (text_file != nullptr) {
current->apply_code();
_save_text_file(text_file, text_file->get_path());
break;
}
editor->save_resource(resource);
save_current_script();
} break;
case FILE_SAVE_AS: {
if (trim_trailing_whitespace_on_save) {
@ -2175,6 +2149,39 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
return true;
}
void ScriptEditor::save_current_script() {
ScriptEditorBase *current = _get_current_editor();
if (_test_script_times_on_disk()) {
return;
}
if (trim_trailing_whitespace_on_save) {
current->trim_trailing_whitespace();
}
current->insert_final_newline();
if (convert_indent_on_save) {
if (use_space_indentation) {
current->convert_indent_to_spaces();
} else {
current->convert_indent_to_tabs();
}
}
RES resource = current->get_edited_resource();
Ref<TextFile> text_file = resource;
if (text_file != nullptr) {
current->apply_code();
_save_text_file(text_file, text_file->get_path());
return;
}
editor->save_resource(resource);
}
void ScriptEditor::save_all_scripts() {
for (int i = 0; i < tab_container->get_child_count(); i++) {
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
@ -2284,6 +2291,9 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const
script_list->select(script_list->find_metadata(i));
// Save the current script so the changes can be picked up by an external editor.
save_current_script();
break;
}
}

View file

@ -427,6 +427,7 @@ public:
void get_breakpoints(List<String> *p_breakpoints);
void save_current_script();
void save_all_scripts();
void set_window_layout(Ref<ConfigFile> p_layout);

View file

@ -2586,14 +2586,16 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
}
}
if (profile_allow_script_editing) {
if (profile_allow_editing) {
menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/cut_node"), TOOL_CUT);
menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/copy_node"), TOOL_COPY);
if (selection.size() == 1 && !node_clipboard.empty()) {
menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE);
}
menu->add_separator();
}
if (profile_allow_script_editing) {
bool add_separator = false;
if (full_selection.size() == 1) {
@ -2621,7 +2623,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
}
}
if (add_separator) {
if (add_separator && profile_allow_editing) {
menu->add_separator();
}
}
@ -3050,7 +3052,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
ED_SHORTCUT("scene_tree/rename", TTR("Rename"), KEY_F2);
ED_SHORTCUT("scene_tree/batch_rename", TTR("Batch Rename"), KEY_MASK_SHIFT | KEY_F2);
ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node"), KEY_MASK_CMD | KEY_A);
ED_SHORTCUT("scene_tree/instance_scene", TTR("Instance Child Scene"));
ED_SHORTCUT("scene_tree/instance_scene", TTR("Instance Child Scene"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_A);
ED_SHORTCUT("scene_tree/expand_collapse_all", TTR("Expand/Collapse All"));
ED_SHORTCUT("scene_tree/cut_node", TTR("Cut"), KEY_MASK_CMD | KEY_X);
ED_SHORTCUT("scene_tree/copy_node", TTR("Copy"), KEY_MASK_CMD | KEY_C);

View file

@ -1,393 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<link id='-gd-engine-icon' rel='icon' type='image/png' href='favicon.png' />
<title>$GODOT_PROJECT_NAME</title>
<style type="text/css">
body {
margin: 0;
border: 0 none;
padding: 0;
text-align: center;
background-color: #222226;
font-family: 'Noto Sans', Arial, sans-serif;
}
/* Godot Engine default theme style
* ================================ */
.godot {
color: #e0e0e0;
background-color: #3b3943;
background-image: linear-gradient(to bottom, #403e48, #35333c);
border: 1px solid #45434e;
box-shadow: 0 0 1px 1px #2f2d35;
}
button.godot {
font-family: 'Droid Sans', Arial, sans-serif; /* override user agent style */
padding: 1px 5px;
background-color: #37353f;
background-image: linear-gradient(to bottom, #413e49, #3a3842);
border: 1px solid #514f5d;
border-radius: 1px;
box-shadow: 0 0 1px 1px #2a2930;
}
button.godot:hover {
color: #f0f0f0;
background-color: #44414e;
background-image: linear-gradient(to bottom, #494652, #423f4c);
border: 1px solid #5a5667;
box-shadow: 0 0 1px 1px #26252b;
}
button.godot:active {
color: #fff;
background-color: #3e3b46;
background-image: linear-gradient(to bottom, #36343d, #413e49);
border: 1px solid #4f4c59;
box-shadow: 0 0 1px 1px #26252b;
}
button.godot:disabled {
color: rgba(230, 230, 230, 0.2);
background-color: #3d3d3d;
background-image: linear-gradient(to bottom, #434343, #393939);
border: 1px solid #474747;
box-shadow: 0 0 1px 1px #2d2b33;
}
/* Canvas / wrapper
* ================ */
#container {
display: inline-block; /* scale with canvas */
vertical-align: top; /* prevent extra height */
position: relative; /* root for absolutely positioned overlay */
margin: 0;
border: 0 none;
padding: 0;
background-color: #0c0c0c;
}
#canvas {
display: block;
margin: 0 auto;
color: white;
}
#canvas:focus {
outline: none;
}
/* Status display
* ============== */
#status {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
/* don't consume click events - make children visible explicitly */
visibility: hidden;
}
#status-progress {
width: 244px;
height: 7px;
background-color: #38363A;
border: 1px solid #444246;
padding: 1px;
box-shadow: 0 0 2px 1px #1B1C22;
border-radius: 2px;
visibility: visible;
}
#status-progress-inner {
height: 100%;
width: 0;
box-sizing: border-box;
transition: width 0.5s linear;
background-color: #202020;
border: 1px solid #222223;
box-shadow: 0 0 1px 1px #27282E;
border-radius: 3px;
}
#status-indeterminate {
visibility: visible;
position: relative;
}
#status-indeterminate > div {
width: 3px;
height: 0;
border-style: solid;
border-width: 6px 2px 0 2px;
border-color: #2b2b2b transparent transparent transparent;
transform-origin: center 14px;
position: absolute;
}
#status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); }
#status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); }
#status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); }
#status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); }
#status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); }
#status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); }
#status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); }
#status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); }
#status-notice {
margin: 0 100px;
line-height: 1.3;
visibility: visible;
padding: 4px 6px;
visibility: visible;
}
/* Debug output
* ============ */
#output-panel {
display: none;
max-width: 700px;
font-size: small;
margin: 6px auto 0;
padding: 0 4px 4px;
text-align: left;
line-height: 2.2;
}
#output-header {
display: flex;
justify-content: space-between;
align-items: center;
}
#output-container {
padding: 6px;
background-color: #2c2a32;
box-shadow: inset 0 0 1px 1px #232127;
color: #bbb;
}
#output-scroll {
line-height: 1;
height: 12em;
overflow-y: scroll;
white-space: pre-wrap;
font-size: small;
font-family: "Lucida Console", Monaco, monospace;
}
</style>
$GODOT_HEAD_INCLUDE
</head>
<body>
<div id="container">
<canvas id="canvas" width="640" height="480">
HTML5 canvas appears to be unsupported in the current browser.<br />
Please try updating or use a different browser.
</canvas>
<div id="status">
<div id='status-progress' style='display: none;' oncontextmenu="event.preventDefault();"><div id ='status-progress-inner'></div></div>
<div id='status-indeterminate' style='display: none;' oncontextmenu="event.preventDefault();">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div id="status-notice" class="godot" style='display: none;'></div>
</div>
</div>
<div id="output-panel" class="godot">
<div id="output-header">
Output:
<button id='output-clear' class='godot' type='button' autocomplete='off'>Clear</button>
</div>
<div id="output-container"><div id="output-scroll"></div></div>
</div>
<script type="text/javascript" src="$GODOT_BASENAME.js"></script>
<script type="text/javascript">//<![CDATA[
var engine = new Engine;
(function() {
const EXECUTABLE_NAME = '$GODOT_BASENAME';
const MAIN_PACK = '$GODOT_BASENAME.pck';
const DEBUG_ENABLED = $GODOT_DEBUG_ENABLED;
const INDETERMINATE_STATUS_STEP_MS = 100;
var container = document.getElementById('container');
var canvas = document.getElementById('canvas');
var statusProgress = document.getElementById('status-progress');
var statusProgressInner = document.getElementById('status-progress-inner');
var statusIndeterminate = document.getElementById('status-indeterminate');
var statusNotice = document.getElementById('status-notice');
var initializing = true;
var statusMode = 'hidden';
var indeterminiateStatusAnimationId = 0;
function setStatusMode(mode) {
if (statusMode === mode || !initializing)
return;
[statusProgress, statusIndeterminate, statusNotice].forEach(elem => {
elem.style.display = 'none';
});
if (indeterminiateStatusAnimationId !== 0) {
cancelAnimationFrame(indeterminiateStatusAnimationId);
indeterminiateStatusAnimationId = 0;
}
switch (mode) {
case 'progress':
statusProgress.style.display = 'block';
break;
case 'indeterminate':
statusIndeterminate.style.display = 'block';
indeterminiateStatusAnimationId = requestAnimationFrame(animateStatusIndeterminate);
break;
case 'notice':
statusNotice.style.display = 'block';
break;
case 'hidden':
break;
default:
throw new Error("Invalid status mode");
}
statusMode = mode;
}
function animateStatusIndeterminate(ms) {
var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8);
if (statusIndeterminate.children[i].style.borderTopColor == '') {
Array.prototype.slice.call(statusIndeterminate.children).forEach(child => {
child.style.borderTopColor = '';
});
statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf';
}
requestAnimationFrame(animateStatusIndeterminate);
}
function setStatusNotice(text) {
while (statusNotice.lastChild) {
statusNotice.removeChild(statusNotice.lastChild);
}
var lines = text.split('\n');
lines.forEach((line, index) => {
statusNotice.appendChild(document.createTextNode(line));
statusNotice.appendChild(document.createElement('br'));
});
};
engine.setProgressFunc((current, total) => {
if (total > 0) {
statusProgressInner.style.width = current/total * 100 + '%';
setStatusMode('progress');
if (current === total) {
// wait for progress bar animation
setTimeout(() => {
setStatusMode('indeterminate');
}, 500);
}
} else {
setStatusMode('indeterminate');
}
});
if (DEBUG_ENABLED) {
var outputRoot = document.getElementById("output-panel");
var outputScroll = document.getElementById("output-scroll");
var OUTPUT_MSG_COUNT_MAX = 400;
document.getElementById('output-clear').addEventListener('click', () => {
while (outputScroll.firstChild) {
outputScroll.firstChild.remove();
}
});
outputRoot.style.display = 'block';
function print(text) {
while (outputScroll.childElementCount >= OUTPUT_MSG_COUNT_MAX) {
outputScroll.firstChild.remove();
}
var msg = document.createElement("div");
if (String.prototype.trim.call(text).startsWith("**ERROR**")) {
msg.style.color = "#d44";
} else if (String.prototype.trim.call(text).startsWith("**WARNING**")) {
msg.style.color = "#ccc000";
} else if (String.prototype.trim.call(text).startsWith("**SCRIPT ERROR**")) {
msg.style.color = "#c6d";
}
msg.textContent = text;
var scrollToBottom = outputScroll.scrollHeight - (outputScroll.clientHeight + outputScroll.scrollTop) < 10;
outputScroll.appendChild(msg);
if (scrollToBottom) {
outputScroll.scrollTop = outputScroll.scrollHeight;
}
};
function printError(text) {
if (!String.prototype.trim.call(text).startsWith('**ERROR**: ')) {
text = '**ERROR**: ' + text;
}
print(text);
}
engine.setStdoutFunc(text => {
print(text);
console.log(text);
});
engine.setStderrFunc(text => {
printError(text);
console.warn(text);
});
}
function displayFailureNotice(err) {
var msg = err.message || err;
if (DEBUG_ENABLED) {
printError(msg);
}
console.error(msg);
setStatusNotice(msg);
setStatusMode('notice');
initializing = false;
};
if (!Engine.isWebGLAvailable()) {
displayFailureNotice("WebGL not available");
} else {
setStatusMode('indeterminate');
engine.setCanvas(canvas);
engine.startGame(EXECUTABLE_NAME, MAIN_PACK).then(() => {
setStatusMode('hidden');
initializing = false;
}, displayFailureNotice);
}
})();
//]]></script>
</body>
</html>

View file

@ -34,7 +34,8 @@ Error make_request(Method p_method, const String &p_url, const Vector<String> &p
static void _parse_headers(int p_len, const char **p_headers, void *p_ref);
int js_id = 0;
int read_limit = 4096;
// 64 KiB by default (favors fast download speeds at the cost of memory usage).
int read_limit = 65536;
Status status = STATUS_DISCONNECTED;
String host;

View file

@ -65,6 +65,11 @@ void main_loop_callback() {
int target_fps = Engine::get_singleton()->get_target_fps();
if (target_fps > 0) {
if (current_ticks - target_ticks > 1000000) {
// When the window loses focus, we stop getting updates and accumulate delay.
// For this reason, if the difference is too big, we reset target ticks to the current ticks.
target_ticks = current_ticks;
}
target_ticks += (uint64_t)(1000000 / target_fps);
}
if (os->main_loop_iterate()) {

View file

@ -1535,6 +1535,12 @@ Vector2 TileMap::map_to_world(const Vector2 &p_pos, bool p_ignore_ofs) const {
Vector2 TileMap::world_to_map(const Vector2 &p_pos) const {
Vector2 ret = get_cell_transform().affine_inverse().xform(p_pos);
// Account for precision errors on the border (GH-23250).
// 0.00005 is 5*CMP_EPSILON, results would start being unpredictable if
// cell size is > 15,000, but we can hardly have more precision anyway with
// floating point.
ret += Vector2(0.00005, 0.00005);
switch (half_offset) {
case HALF_OFFSET_X: {
if (int(floor(ret.y)) & 1) {
@ -1561,11 +1567,6 @@ Vector2 TileMap::world_to_map(const Vector2 &p_pos) const {
}
}
// Account for precision errors on the border (GH-23250).
// 0.00005 is 5*CMP_EPSILON, results would start being unpredictable if
// cell size is > 15,000, but we can hardly have more precision anyway with
// floating point.
ret += Vector2(0.00005, 0.00005);
return ret.floor();
}

View file

@ -599,9 +599,9 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) {
Skeleton *sk = Object::cast_to<Skeleton>(spatial);
track_xform->skeleton = sk;
int bone_idx = sk->find_bone(path.get_subname(0));
if (bone_idx != -1) {
track_xform->skeleton = sk;
track_xform->bone_idx = bone_idx;
}
}
@ -1207,7 +1207,7 @@ void AnimationTree::_process_graph(float p_delta) {
} else if (t->skeleton && t->bone_idx >= 0) {
t->skeleton->set_bone_pose(t->bone_idx, xform);
} else {
} else if (!t->skeleton) {
t->spatial->set_transform(xform);
}

View file

@ -155,6 +155,9 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
}
status.pressed = !status.pressed;
_unpress_group();
if (button_group.is_valid()) {
button_group->emit_signal("pressed", this);
}
_toggled(status.pressed);
_pressed();
}
@ -220,6 +223,9 @@ void BaseButton::set_pressed(bool p_pressed) {
if (p_pressed) {
_unpress_group();
if (button_group.is_valid()) {
button_group->emit_signal("pressed", this);
}
}
_toggled(status.pressed);
@ -480,6 +486,7 @@ BaseButton *ButtonGroup::get_pressed_button() {
void ButtonGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pressed_button"), &ButtonGroup::get_pressed_button);
ClassDB::bind_method(D_METHOD("get_buttons"), &ButtonGroup::_get_buttons);
ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button")));
}
ButtonGroup::ButtonGroup() {

View file

@ -648,7 +648,7 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
}
Control::CursorShape LineEdit::get_cursor_shape(const Point2 &p_pos) const {
if (!text.empty() && is_editable() && _is_over_clear_button(p_pos)) {
if ((!text.empty() && is_editable() && _is_over_clear_button(p_pos)) || (!is_editable() && (!is_selecting_enabled() || text.empty()))) {
return CURSOR_ARROW;
}
return Control::get_cursor_shape(p_pos);

View file

@ -323,6 +323,7 @@ void TabContainer::_notification(int p_what) {
Vector<int> tab_widths;
for (int i = first_tab_cache; i < tabs.size(); i++) {
if (get_tab_hidden(i)) {
tab_widths.push_back(0);
continue;
}
int tab_width = _get_tab_width(i);

View file

@ -4811,10 +4811,11 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
return CURSOR_POINTING_HAND;
}
int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width;
if ((completion_active && completion_rect.has_point(p_pos))) {
if ((completion_active && completion_rect.has_point(p_pos)) || (is_readonly() && (!is_selecting_enabled() || text.size() == 0))) {
return CURSOR_ARROW;
}
int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width;
if (p_pos.x < gutter) {
int row, col;
_get_mouse_pos(p_pos, row, col);

View file

@ -2872,18 +2872,12 @@ void Tree::_notification(int p_what) {
RID ci = get_canvas_item();
Ref<StyleBox> bg = cache.bg;
Ref<StyleBox> bg_focus = get_stylebox("bg_focus");
Point2 draw_ofs;
draw_ofs += bg->get_offset();
Size2 draw_size = get_size() - bg->get_minimum_size();
bg->draw(ci, Rect2(Point2(), get_size()));
if (has_focus()) {
VisualServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
bg_focus->draw(ci, Rect2(Point2(), get_size()));
VisualServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
}
int tbh = _get_title_button_height();
@ -2908,6 +2902,15 @@ void Tree::_notification(int p_what) {
f->draw_halign(ci, tbrect.position + Point2i(sb->get_offset().x, (tbrect.size.height - f->get_height()) / 2 + f->get_ascent()), HALIGN_CENTER, clip_w, columns[i].title, cache.title_button_color);
}
}
// Draw the background focus outline last, so that it is drawn in front of the section headings.
// Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling.
if (has_focus()) {
VisualServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
const Ref<StyleBox> bg_focus = get_stylebox("bg_focus");
bg_focus->draw(ci, Rect2(Point2(), get_size()));
VisualServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
}
}
if (p_what == NOTIFICATION_THEME_CHANGED) {