1/**************************************************************************/
2/* editor_settings.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 "editor_settings.h"
32
33#include "core/config/project_settings.h"
34#include "core/input/input_event.h"
35#include "core/input/input_map.h"
36#include "core/input/shortcut.h"
37#include "core/io/certs_compressed.gen.h"
38#include "core/io/dir_access.h"
39#include "core/io/file_access.h"
40#include "core/io/ip.h"
41#include "core/io/resource_loader.h"
42#include "core/io/resource_saver.h"
43#include "core/object/class_db.h"
44#include "core/os/keyboard.h"
45#include "core/os/os.h"
46#include "core/string/translation.h"
47#include "core/version.h"
48#include "editor/editor_node.h"
49#include "editor/editor_paths.h"
50#include "editor/editor_translation.h"
51#include "scene/main/node.h"
52#include "scene/main/scene_tree.h"
53#include "scene/main/window.h"
54
55// PRIVATE METHODS
56
57Ref<EditorSettings> EditorSettings::singleton = nullptr;
58
59// Properties
60
61bool EditorSettings::_set(const StringName &p_name, const Variant &p_value) {
62 _THREAD_SAFE_METHOD_
63
64 bool changed = _set_only(p_name, p_value);
65 if (changed) {
66 changed_settings.insert(p_name);
67 emit_signal(SNAME("settings_changed"));
68 }
69 return true;
70}
71
72bool EditorSettings::_set_only(const StringName &p_name, const Variant &p_value) {
73 _THREAD_SAFE_METHOD_
74
75 if (p_name == "shortcuts") {
76 Array arr = p_value;
77 for (int i = 0; i < arr.size(); i++) {
78 Dictionary dict = arr[i];
79 String shortcut_name = dict["name"];
80
81 Array shortcut_events = dict["shortcuts"];
82
83 Ref<Shortcut> sc;
84 sc.instantiate();
85 sc->set_events(shortcut_events);
86 add_shortcut(shortcut_name, sc);
87 }
88
89 return false;
90 } else if (p_name == "builtin_action_overrides") {
91 Array actions_arr = p_value;
92 for (int i = 0; i < actions_arr.size(); i++) {
93 Dictionary action_dict = actions_arr[i];
94
95 String action_name = action_dict["name"];
96 Array events = action_dict["events"];
97
98 InputMap *im = InputMap::get_singleton();
99 im->action_erase_events(action_name);
100
101 builtin_action_overrides[action_name].clear();
102 for (int ev_idx = 0; ev_idx < events.size(); ev_idx++) {
103 im->action_add_event(action_name, events[ev_idx]);
104 builtin_action_overrides[action_name].push_back(events[ev_idx]);
105 }
106 }
107 return false;
108 }
109
110 bool changed = false;
111
112 if (p_value.get_type() == Variant::NIL) {
113 if (props.has(p_name)) {
114 props.erase(p_name);
115 changed = true;
116 }
117 } else {
118 if (props.has(p_name)) {
119 if (p_value != props[p_name].variant) {
120 props[p_name].variant = p_value;
121 changed = true;
122 }
123 } else {
124 props[p_name] = VariantContainer(p_value, last_order++);
125 changed = true;
126 }
127
128 if (save_changed_setting) {
129 if (!props[p_name].save) {
130 props[p_name].save = true;
131 changed = true;
132 }
133 }
134 }
135
136 return changed;
137}
138
139bool EditorSettings::_get(const StringName &p_name, Variant &r_ret) const {
140 _THREAD_SAFE_METHOD_
141
142 if (p_name == "shortcuts") {
143 Array save_array;
144 const HashMap<String, List<Ref<InputEvent>>> &builtin_list = InputMap::get_singleton()->get_builtins();
145 for (const KeyValue<String, Ref<Shortcut>> &shortcut_definition : shortcuts) {
146 Ref<Shortcut> sc = shortcut_definition.value;
147
148 if (builtin_list.has(shortcut_definition.key)) {
149 // This shortcut was auto-generated from built in actions: don't save.
150 // If the builtin is overridden, it will be saved in the "builtin_action_overrides" section below.
151 continue;
152 }
153
154 Array shortcut_events = sc->get_events();
155
156 Dictionary dict;
157 dict["name"] = shortcut_definition.key;
158 dict["shortcuts"] = shortcut_events;
159
160 if (!sc->has_meta("original")) {
161 // Getting the meta when it doesn't exist will return an empty array. If the 'shortcut_events' have been cleared,
162 // we still want save the shortcut in this case so that shortcuts that the user has customized are not reset,
163 // even if the 'original' has not been populated yet. This can happen when calling save() from the Project Manager.
164 save_array.push_back(dict);
165 continue;
166 }
167
168 Array original_events = sc->get_meta("original");
169
170 bool is_same = Shortcut::is_event_array_equal(original_events, shortcut_events);
171 if (is_same) {
172 continue; // Not changed from default; don't save.
173 }
174
175 save_array.push_back(dict);
176 }
177 r_ret = save_array;
178 return true;
179 } else if (p_name == "builtin_action_overrides") {
180 Array actions_arr;
181 for (const KeyValue<String, List<Ref<InputEvent>>> &action_override : builtin_action_overrides) {
182 List<Ref<InputEvent>> events = action_override.value;
183
184 Dictionary action_dict;
185 action_dict["name"] = action_override.key;
186
187 // Convert the list to an array, and only keep key events as this is for the editor.
188 Array events_arr;
189 for (const Ref<InputEvent> &ie : events) {
190 Ref<InputEventKey> iek = ie;
191 if (iek.is_valid()) {
192 events_arr.append(iek);
193 }
194 }
195
196 Array defaults_arr;
197 List<Ref<InputEvent>> defaults = InputMap::get_singleton()->get_builtins()[action_override.key];
198 for (const Ref<InputEvent> &default_input_event : defaults) {
199 if (default_input_event.is_valid()) {
200 defaults_arr.append(default_input_event);
201 }
202 }
203
204 bool same = Shortcut::is_event_array_equal(events_arr, defaults_arr);
205
206 // Don't save if same as default.
207 if (same) {
208 continue;
209 }
210
211 action_dict["events"] = events_arr;
212 actions_arr.push_back(action_dict);
213 }
214
215 r_ret = actions_arr;
216 return true;
217 }
218
219 const VariantContainer *v = props.getptr(p_name);
220 if (!v) {
221 WARN_PRINT("EditorSettings::_get - Property not found: " + String(p_name));
222 return false;
223 }
224 r_ret = v->variant;
225 return true;
226}
227
228void EditorSettings::_initial_set(const StringName &p_name, const Variant &p_value) {
229 set(p_name, p_value);
230 props[p_name].initial = p_value;
231 props[p_name].has_default_value = true;
232}
233
234struct _EVCSort {
235 String name;
236 Variant::Type type = Variant::Type::NIL;
237 int order = 0;
238 bool save = false;
239 bool restart_if_changed = false;
240
241 bool operator<(const _EVCSort &p_vcs) const { return order < p_vcs.order; }
242};
243
244void EditorSettings::_get_property_list(List<PropertyInfo> *p_list) const {
245 _THREAD_SAFE_METHOD_
246
247 RBSet<_EVCSort> vclist;
248
249 for (const KeyValue<String, VariantContainer> &E : props) {
250 const VariantContainer *v = &E.value;
251
252 if (v->hide_from_editor) {
253 continue;
254 }
255
256 _EVCSort vc;
257 vc.name = E.key;
258 vc.order = v->order;
259 vc.type = v->variant.get_type();
260 vc.save = v->save;
261 /*if (vc.save) { this should be implemented, but lets do after 3.1 is out.
262 if (v->initial.get_type() != Variant::NIL && v->initial == v->variant) {
263 vc.save = false;
264 }
265 }*/
266 vc.restart_if_changed = v->restart_if_changed;
267
268 vclist.insert(vc);
269 }
270
271 for (const _EVCSort &E : vclist) {
272 uint32_t pusage = PROPERTY_USAGE_NONE;
273 if (E.save || !optimize_save) {
274 pusage |= PROPERTY_USAGE_STORAGE;
275 }
276
277 if (!E.name.begins_with("_") && !E.name.begins_with("projects/")) {
278 pusage |= PROPERTY_USAGE_EDITOR;
279 } else {
280 pusage |= PROPERTY_USAGE_STORAGE; //hiddens must always be saved
281 }
282
283 PropertyInfo pi(E.type, E.name);
284 pi.usage = pusage;
285 if (hints.has(E.name)) {
286 pi = hints[E.name];
287 }
288
289 if (E.restart_if_changed) {
290 pi.usage |= PROPERTY_USAGE_RESTART_IF_CHANGED;
291 }
292
293 p_list->push_back(pi);
294 }
295
296 p_list->push_back(PropertyInfo(Variant::ARRAY, "shortcuts", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); //do not edit
297 p_list->push_back(PropertyInfo(Variant::ARRAY, "builtin_action_overrides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
298}
299
300void EditorSettings::_add_property_info_bind(const Dictionary &p_info) {
301 ERR_FAIL_COND(!p_info.has("name"));
302 ERR_FAIL_COND(!p_info.has("type"));
303
304 PropertyInfo pinfo;
305 pinfo.name = p_info["name"];
306 ERR_FAIL_COND(!props.has(pinfo.name));
307 pinfo.type = Variant::Type(p_info["type"].operator int());
308 ERR_FAIL_INDEX(pinfo.type, Variant::VARIANT_MAX);
309
310 if (p_info.has("hint")) {
311 pinfo.hint = PropertyHint(p_info["hint"].operator int());
312 }
313 if (p_info.has("hint_string")) {
314 pinfo.hint_string = p_info["hint_string"];
315 }
316
317 add_property_hint(pinfo);
318}
319
320// Default configs
321bool EditorSettings::has_default_value(const String &p_setting) const {
322 _THREAD_SAFE_METHOD_
323
324 if (!props.has(p_setting)) {
325 return false;
326 }
327 return props[p_setting].has_default_value;
328}
329
330void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
331 _THREAD_SAFE_METHOD_
332// Sets up the editor setting with a default value and hint PropertyInfo.
333#define EDITOR_SETTING(m_type, m_property_hint, m_name, m_default_value, m_hint_string) \
334 _initial_set(m_name, m_default_value); \
335 hints[m_name] = PropertyInfo(m_type, m_name, m_property_hint, m_hint_string);
336
337#define EDITOR_SETTING_USAGE(m_type, m_property_hint, m_name, m_default_value, m_hint_string, m_usage) \
338 _initial_set(m_name, m_default_value); \
339 hints[m_name] = PropertyInfo(m_type, m_name, m_property_hint, m_hint_string, m_usage);
340
341 /* Languages */
342
343 {
344 String lang_hint = "en";
345 String host_lang = OS::get_singleton()->get_locale();
346
347 // Skip locales if Text server lack required features.
348 Vector<String> locales_to_skip;
349 if (!TS->has_feature(TextServer::FEATURE_BIDI_LAYOUT) || !TS->has_feature(TextServer::FEATURE_SHAPING)) {
350 locales_to_skip.push_back("ar"); // Arabic
351 locales_to_skip.push_back("fa"); // Persian
352 locales_to_skip.push_back("ur"); // Urdu
353 }
354 if (!TS->has_feature(TextServer::FEATURE_BIDI_LAYOUT)) {
355 locales_to_skip.push_back("he"); // Hebrew
356 }
357 if (!TS->has_feature(TextServer::FEATURE_SHAPING)) {
358 locales_to_skip.push_back("bn"); // Bengali
359 locales_to_skip.push_back("hi"); // Hindi
360 locales_to_skip.push_back("ml"); // Malayalam
361 locales_to_skip.push_back("si"); // Sinhala
362 locales_to_skip.push_back("ta"); // Tamil
363 locales_to_skip.push_back("te"); // Telugu
364 }
365
366 if (!locales_to_skip.is_empty()) {
367 WARN_PRINT("Some locales are not properly supported by selected Text Server and are disabled.");
368 }
369
370 String best;
371 int best_score = 0;
372 for (const String &locale : get_editor_locales()) {
373 // Skip locales which we can't render properly (see above comment).
374 // Test against language code without regional variants (e.g. ur_PK).
375 String lang_code = locale.get_slice("_", 0);
376 if (locales_to_skip.has(lang_code)) {
377 continue;
378 }
379
380 lang_hint += ",";
381 lang_hint += locale;
382
383 int score = TranslationServer::get_singleton()->compare_locales(host_lang, locale);
384 if (score > 0 && score >= best_score) {
385 best = locale;
386 best_score = score;
387 }
388 }
389 if (best_score == 0) {
390 best = "en";
391 }
392
393 EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_ENUM, "interface/editor/editor_language", best, lang_hint, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
394 }
395
396 /* Interface */
397
398 // Editor
399 // Display what the Auto display scale setting effectively corresponds to.
400 const String display_scale_hint_string = vformat("Auto (%d%%),75%%,100%%,125%%,150%%,175%%,200%%,Custom", Math::round(get_auto_display_scale() * 100));
401 EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/display_scale", 0, display_scale_hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
402
403 String ed_screen_hints = "Screen With Mouse Pointer:-4,Screen With Keyboard Focus:-3,Primary Screen:-2"; // Note: Main Window Screen:-1 is not used for the main window.
404 for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) {
405 ed_screen_hints += ",Screen " + itos(i + 1) + ":" + itos(i);
406 }
407 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/editor_screen", -2, ed_screen_hints)
408 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/project_manager_screen", -2, ed_screen_hints)
409
410 _initial_set("interface/editor/debug/enable_pseudolocalization", false);
411 set_restart_if_changed("interface/editor/debug/enable_pseudolocalization", true);
412 // Use pseudolocalization in editor.
413 EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/use_embedded_menu", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
414 EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/expand_to_title", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
415
416 EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/editor/custom_display_scale", 1.0, "0.5,3,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
417 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/main_font_size", 14, "8,48,1")
418 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/code_font_size", 14, "8,48,1")
419 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/code_font_contextual_ligatures", 1, "Enabled,Disable Contextual Alternates (Coding Ligatures),Use Custom OpenType Feature Set")
420 _initial_set("interface/editor/code_font_custom_opentype_features", "");
421 _initial_set("interface/editor/code_font_custom_variations", "");
422 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_antialiasing", 1, "None,Grayscale,LCD Subpixel")
423#ifdef MACOS_ENABLED
424 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_hinting", 0, "Auto (None),None,Light,Normal")
425#else
426 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_hinting", 0, "Auto (Light),None,Light,Normal")
427#endif
428 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_subpixel_positioning", 1, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel")
429
430 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm")
431 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font_bold", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm")
432 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/code_font", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm")
433 EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/editor/low_processor_mode_sleep_usec", 6900, "1,100000,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
434 // Default unfocused usec sleep is for 10 FPS. Allow an unfocused FPS limit
435 // as low as 1 FPS for those who really need low power usage (but don't need
436 // to preview particles or shaders while the editor is unfocused). With very
437 // low FPS limits, the editor can take a small while to become usable after
438 // being focused again, so this should be used at the user's discretion.
439 EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/editor/unfocused_low_processor_mode_sleep_usec", 100000, "1,1000000,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
440 _initial_set("interface/editor/separate_distraction_mode", false);
441 _initial_set("interface/editor/automatically_open_screenshots", true);
442 EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/single_window_mode", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
443 _initial_set("interface/editor/mouse_extra_buttons_navigate_history", true);
444 _initial_set("interface/editor/save_each_scene_on_quit", true); // Regression
445 EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/accept_dialog_cancel_ok_buttons", 0,
446 vformat("Auto (%s),Cancel First,OK First", DisplayServer::get_singleton()->get_swap_cancel_ok() ? "OK First" : "Cancel First"),
447 PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
448#ifdef DEV_ENABLED
449 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/show_internal_errors_in_toast_notifications", 0, "Auto (Enabled),Enabled,Disabled")
450#else
451 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/show_internal_errors_in_toast_notifications", 0, "Auto (Disabled),Enabled,Disabled")
452#endif
453
454 // Inspector
455 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1")
456 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/inspector/show_low_level_opentype_features", false, "")
457 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/inspector/float_drag_speed", 5.0, "0.1,100,0.01")
458
459 // Theme
460 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_ENUM, "interface/theme/preset", "Default", "Default,Breeze Dark,Godot 2,Gray,Light,Solarized (Dark),Solarized (Light),Black (OLED),Custom")
461 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/theme/icon_and_font_color", 0, "Auto,Dark,Light")
462 EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "interface/theme/base_color", Color(0.2, 0.23, 0.31), "")
463 EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "interface/theme/accent_color", Color(0.41, 0.61, 0.91), "")
464 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/theme/contrast", 0.3, "-1,1,0.01")
465 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/theme/draw_extra_borders", false, "")
466 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/theme/icon_saturation", 1.0, "0,2,0.01")
467 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/theme/relationship_line_opacity", 0.1, "0.00,1,0.01")
468 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/theme/border_size", 0, "0,2,1")
469 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/theme/corner_radius", 3, "0,6,1")
470 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/theme/additional_spacing", 0.0, "0,5,0.1")
471 EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/theme/custom_theme", "", "*.res,*.tres,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
472
473 // Touchscreen
474 bool has_touchscreen_ui = DisplayServer::get_singleton()->is_touchscreen_available();
475 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/increase_scrollbar_touch_area", has_touchscreen_ui, "")
476 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_long_press_as_right_click", has_touchscreen_ui, "")
477 set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true);
478 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_pan_and_scale_gestures", has_touchscreen_ui, "")
479 set_restart_if_changed("interface/touchscreen/enable_pan_and_scale_gestures", true);
480 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/touchscreen/scale_gizmo_handles", has_touchscreen_ui ? 3 : 1, "1,5,1")
481
482 // Scene tabs
483 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/scene_tabs/display_close_button", 1, "Never,If Tab Active,Always"); // TabBar::CloseButtonDisplayPolicy
484 _initial_set("interface/scene_tabs/show_thumbnail_on_hover", true);
485 EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "interface/scene_tabs/maximum_width", 350, "0,9999,1", PROPERTY_USAGE_DEFAULT)
486 _initial_set("interface/scene_tabs/show_script_button", false);
487
488 // Multi Window
489 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/enable", true, "");
490 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/restore_windows_on_load", true, "");
491 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/maximize_window", false, "");
492 set_restart_if_changed("interface/multi_window/enable", true);
493
494 /* Filesystem */
495
496 // External Programs
497 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/raster_image_editor", "", "")
498 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/vector_image_editor", "", "")
499 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/audio_editor", "", "")
500 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/3d_model_editor", "", "")
501
502 // Directories
503 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/directories/autoscan_project_path", "", "")
504 const String fs_dir_default_project_path = OS::get_singleton()->has_environment("HOME") ? OS::get_singleton()->get_environment("HOME") : OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DOCUMENTS);
505 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/directories/default_project_path", fs_dir_default_project_path, "")
506
507 // On save
508 _initial_set("filesystem/on_save/compress_binary_resources", true);
509 _initial_set("filesystem/on_save/safe_save_on_backup_then_rename", true);
510
511 // File dialog
512 _initial_set("filesystem/file_dialog/show_hidden_files", false);
513 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "filesystem/file_dialog/display_mode", 0, "Thumbnails,List")
514 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16")
515
516 // Import (for glft module)
517 EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/import/blender/blender3_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
518 EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_port", 6011, "0,65535,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
519 EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_server_uptime", 5, "0,300,1,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
520 EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
521
522 /* Docks */
523
524 // SceneTree
525 _initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false);
526 _initial_set("docks/scene_tree/auto_expand_to_selected", true);
527
528 // FileSystem
529 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16")
530 _initial_set("docks/filesystem/always_show_folders", true);
531 _initial_set("docks/filesystem/textfile_extensions", "txt,md,cfg,ini,log,json,yml,yaml,toml");
532
533 // Property editor
534 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "docks/property_editor/auto_refresh_interval", 0.2, "0.01,1,0.001"); // Update 5 times per second by default.
535 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "docks/property_editor/subresource_hue_tint", 0.75, "0,1,0.01")
536
537 /* Text editor */
538
539 // Theme
540 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_ENUM, "text_editor/theme/color_theme", "Default", "Default,Godot 2,Custom")
541
542 // Theme: Highlighting
543 _load_godot2_text_editor_theme();
544
545 // Appearance
546 // Appearance: Caret
547 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/appearance/caret/type", 0, "Line,Block")
548 _initial_set("text_editor/appearance/caret/caret_blink", true);
549 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "text_editor/appearance/caret/caret_blink_interval", 0.5, "0.1,10,0.01")
550 _initial_set("text_editor/appearance/caret/highlight_current_line", true);
551 _initial_set("text_editor/appearance/caret/highlight_all_occurrences", true);
552
553 // Appearance: Guidelines
554 _initial_set("text_editor/appearance/guidelines/show_line_length_guidelines", true);
555 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/appearance/guidelines/line_length_guideline_soft_column", 80, "20,160,1")
556 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/appearance/guidelines/line_length_guideline_hard_column", 100, "20,160,1")
557
558 // Appearance: Gutters
559 _initial_set("text_editor/appearance/gutters/show_line_numbers", true);
560 _initial_set("text_editor/appearance/gutters/line_numbers_zero_padded", false);
561 _initial_set("text_editor/appearance/gutters/highlight_type_safe_lines", true);
562 _initial_set("text_editor/appearance/gutters/show_info_gutter", true);
563
564 // Appearance: Minimap
565 _initial_set("text_editor/appearance/minimap/show_minimap", true);
566 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/appearance/minimap/minimap_width", 80, "50,250,1")
567
568 // Appearance: Lines
569 _initial_set("text_editor/appearance/lines/code_folding", true);
570 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/appearance/lines/word_wrap", 0, "None,Boundary")
571 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/appearance/lines/autowrap_mode", 3, "Arbitrary:1,Word:2,Word (Smart):3")
572
573 // Appearance: Whitespace
574 _initial_set("text_editor/appearance/whitespace/draw_tabs", true);
575 _initial_set("text_editor/appearance/whitespace/draw_spaces", false);
576 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/appearance/whitespace/line_spacing", 4, "0,50,1")
577
578 // Behavior
579 // Behavior: Navigation
580 _initial_set("text_editor/behavior/navigation/move_caret_on_right_click", true);
581 _initial_set("text_editor/behavior/navigation/scroll_past_end_of_file", false);
582 _initial_set("text_editor/behavior/navigation/smooth_scrolling", true);
583 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/behavior/navigation/v_scroll_speed", 80, "1,10000,1")
584 _initial_set("text_editor/behavior/navigation/drag_and_drop_selection", true);
585 _initial_set("text_editor/behavior/navigation/stay_in_script_editor_on_node_selected", true);
586
587 // Behavior: Indent
588 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/behavior/indent/type", 0, "Tabs,Spaces")
589 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/behavior/indent/size", 4, "1,64,1") // size of 0 crashes.
590 _initial_set("text_editor/behavior/indent/auto_indent", true);
591
592 // Behavior: Files
593 _initial_set("text_editor/behavior/files/trim_trailing_whitespace_on_save", false);
594 _initial_set("text_editor/behavior/files/autosave_interval_secs", 0);
595 _initial_set("text_editor/behavior/files/restore_scripts_on_load", true);
596 _initial_set("text_editor/behavior/files/convert_indent_on_save", true);
597 _initial_set("text_editor/behavior/files/auto_reload_scripts_on_external_change", false);
598
599 // Script list
600 _initial_set("text_editor/script_list/show_members_overview", true);
601 _initial_set("text_editor/script_list/sort_members_outline_alphabetically", false);
602
603 // Completion
604 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "text_editor/completion/idle_parse_delay", 2.0, "0.1,10,0.01")
605 _initial_set("text_editor/completion/auto_brace_complete", true);
606 _initial_set("text_editor/completion/code_complete_enabled", true);
607 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "text_editor/completion/code_complete_delay", 0.3, "0.01,5,0.01,or_greater")
608 _initial_set("text_editor/completion/put_callhint_tooltip_below_current_line", true);
609 _initial_set("text_editor/completion/complete_file_paths", true);
610 _initial_set("text_editor/completion/add_type_hints", false);
611 _initial_set("text_editor/completion/use_single_quotes", false);
612 _initial_set("text_editor/completion/colorize_suggestions", true);
613
614 // Help
615 _initial_set("text_editor/help/show_help_index", true);
616 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/help/help_font_size", 16, "8,48,1")
617 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/help/help_source_font_size", 15, "8,48,1")
618 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "text_editor/help/help_title_font_size", 23, "8,64,1")
619 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/help/class_reference_examples", 0, "GDScript,C#,GDScript and C#")
620
621 /* Editors */
622
623 // GridMap
624 _initial_set("editors/grid_map/pick_distance", 5000.0);
625
626 // 3D
627 EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d/primary_grid_color", Color(0.56, 0.56, 0.56, 0.5), "")
628 EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d/secondary_grid_color", Color(0.38, 0.38, 0.38, 0.5), "")
629
630 // Use a similar color to the 2D editor selection.
631 EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d/selection_box_color", Color(1.0, 0.5, 0), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
632 _initial_set("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
633 _initial_set("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1));
634 _initial_set("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
635
636 // If a line is a multiple of this, it uses the primary grid color.
637 // Use a power of 2 value by default as it's more common to use powers of 2 in level design.
638 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "editors/3d/primary_grid_steps", 8, "1,100,1")
639 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "editors/3d/grid_size", 200, "1,2000,1")
640 // Higher values produce graphical artifacts when far away unless View Z-Far
641 // is increased significantly more than it really should need to be.
642 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "editors/3d/grid_division_level_max", 2, "-1,3,1")
643 // Lower values produce graphical artifacts regardless of view clipping planes, so limit to -2 as a lower bound.
644 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "editors/3d/grid_division_level_min", 0, "-2,2,1")
645 // -0.2 seems like a sensible default. -1.0 gives Blender-like behavior, 0.5 gives huge grids.
646 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/grid_division_level_bias", -0.2, "-1.0,0.5,0.1")
647
648 _initial_set("editors/3d/grid_xz_plane", true);
649 _initial_set("editors/3d/grid_xy_plane", false);
650 _initial_set("editors/3d/grid_yz_plane", false);
651
652 // Use a lower default FOV for the 3D camera compared to the
653 // Camera3D node as the 3D viewport doesn't span the whole screen.
654 // This means it's technically viewed from a further distance, which warrants a narrower FOV.
655 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/default_fov", 70.0, "1,179,0.1,degrees")
656 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/default_z_near", 0.05, "0.01,10,0.01,or_greater,suffix:m")
657 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/default_z_far", 4000.0, "0.1,4000,0.1,or_greater,suffix:m")
658
659 // 3D: Navigation
660 _initial_set("editors/3d/navigation/invert_x_axis", false);
661 _initial_set("editors/3d/navigation/invert_y_axis", false);
662 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/navigation_scheme", 0, "Godot,Maya,Modo")
663 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_style", 0, "Vertical,Horizontal")
664
665 _initial_set("editors/3d/navigation/emulate_numpad", false);
666 _initial_set("editors/3d/navigation/emulate_3_button_mouse", false);
667 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/orbit_modifier", 0, "None,Shift,Alt,Meta,Ctrl")
668 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/pan_modifier", 1, "None,Shift,Alt,Meta,Ctrl")
669 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_modifier", 4, "None,Shift,Alt,Meta,Ctrl")
670 _initial_set("editors/3d/navigation/warped_mouse_panning", true);
671
672 // 3D: Navigation feel
673 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/orbit_sensitivity", 0.25, "0.01,2,0.001")
674 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/orbit_inertia", 0.0, "0,1,0.001")
675 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/translation_inertia", 0.05, "0,1,0.001")
676 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/zoom_inertia", 0.05, "0,1,0.001")
677
678 // 3D: Freelook
679 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/freelook/freelook_navigation_scheme", 0, "Default,Partially Axis-Locked (id Tech),Fully Axis-Locked (Minecraft)")
680 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/freelook/freelook_sensitivity", 0.25, "0.01,2,0.001")
681 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/freelook/freelook_inertia", 0.0, "0,1,0.001")
682 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/freelook/freelook_base_speed", 5.0, "0,10,0.01")
683 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/freelook/freelook_activation_modifier", 0, "None,Shift,Alt,Meta,Ctrl")
684 _initial_set("editors/3d/freelook/freelook_speed_zoom_link", false);
685
686 // 2D
687 _initial_set("editors/2d/grid_color", Color(1.0, 1.0, 1.0, 0.07));
688 _initial_set("editors/2d/guides_color", Color(0.6, 0.0, 0.8));
689 _initial_set("editors/2d/smart_snapping_line_color", Color(0.9, 0.1, 0.1));
690 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/2d/bone_width", 5.0, "0.01,20,0.01,or_greater")
691 _initial_set("editors/2d/bone_color1", Color(1.0, 1.0, 1.0, 0.7));
692 _initial_set("editors/2d/bone_color2", Color(0.6, 0.6, 0.6, 0.7));
693 _initial_set("editors/2d/bone_selected_color", Color(0.9, 0.45, 0.45, 0.7));
694 _initial_set("editors/2d/bone_ik_color", Color(0.9, 0.9, 0.45, 0.7));
695 _initial_set("editors/2d/bone_outline_color", Color(0.35, 0.35, 0.35, 0.5));
696 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/2d/bone_outline_size", 2.0, "0.01,8,0.01,or_greater")
697 _initial_set("editors/2d/viewport_border_color", Color(0.4, 0.4, 1.0, 0.4));
698 _initial_set("editors/2d/use_integer_zoom_by_default", false);
699
700 // Panning
701 // Enum should be in sync with ControlScheme in ViewPanner.
702 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/panning/2d_editor_panning_scheme", 0, "Scroll Zooms,Scroll Pans");
703 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/panning/sub_editors_panning_scheme", 0, "Scroll Zooms,Scroll Pans");
704 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/panning/animation_editors_panning_scheme", 1, "Scroll Zooms,Scroll Pans");
705 _initial_set("editors/panning/simple_panning", false);
706 _initial_set("editors/panning/warped_mouse_panning", true);
707 _initial_set("editors/panning/2d_editor_pan_speed", 20);
708
709 // Tiles editor
710 _initial_set("editors/tiles_editor/display_grid", true);
711 _initial_set("editors/tiles_editor/grid_color", Color(1.0, 0.5, 0.2, 0.5));
712
713 // Polygon editor
714 _initial_set("editors/polygon_editor/point_grab_radius", has_touchscreen_ui ? 32 : 8);
715 _initial_set("editors/polygon_editor/show_previous_outline", true);
716
717 // Animation
718 _initial_set("editors/animation/autorename_animation_tracks", true);
719 _initial_set("editors/animation/default_create_bezier_tracks", false);
720 _initial_set("editors/animation/default_create_reset_tracks", true);
721 _initial_set("editors/animation/onion_layers_past_color", Color(1, 0, 0));
722 _initial_set("editors/animation/onion_layers_future_color", Color(0, 1, 0));
723
724 // Shader editor
725 _initial_set("editors/shader_editor/behavior/files/restore_shaders_on_load", true);
726
727 // Visual editors
728 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/visual_editors/minimap_opacity", 0.85, "0.0,1.0,0.01")
729 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/visual_editors/lines_curvature", 0.5, "0.0,1.0,0.01")
730 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "editors/visual_editors/visual_shader/port_preview_size", 160, "100,400,0.01")
731
732 /* Run */
733
734 // Window placement
735#ifndef ANDROID_ENABLED
736 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/rect", 1, "Top Left,Centered,Custom Position,Force Maximized,Force Fullscreen")
737 // Keep the enum values in sync with the `DisplayServer::SCREEN_` enum.
738 String screen_hints = "Same as Editor:-5,Previous Screen:-4,Next Screen:-3,Primary Screen:-2"; // Note: Main Window Screen:-1 is not used for the main window.
739 for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) {
740 screen_hints += ",Screen " + itos(i + 1) + ":" + itos(i);
741 }
742 _initial_set("run/window_placement/rect_custom_position", Vector2());
743 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/screen", -5, screen_hints)
744#endif
745 // Should match the ANDROID_WINDOW_* constants in 'platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt'
746 String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2";
747 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints)
748
749 // Auto save
750 _initial_set("run/auto_save/save_before_running", true);
751
752 // Output
753 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "run/output/font_size", 13, "8,48,1")
754 _initial_set("run/output/always_clear_output_on_play", true);
755 _initial_set("run/output/always_open_output_on_play", true);
756 _initial_set("run/output/always_close_output_on_stop", false);
757
758 /* Network */
759
760 // Debug
761 _initial_set("network/debug/remote_host", "127.0.0.1"); // Hints provided in setup_network
762
763 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "network/debug/remote_port", 6007, "1,65535,1")
764
765 // SSL
766 EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "network/tls/editor_tls_certificates", _SYSTEM_CERTS_PATH, "*.crt,*.pem", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
767
768 // Debugger/profiler
769 EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_remote_scene_tree", false, "")
770 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_history_size", 3600, "60,10000,1")
771 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_max_functions", 64, "16,512,1")
772 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "debugger/remote_scene_tree_refresh_interval", 1.0, "0.1,10,0.01,or_greater")
773 EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "debugger/remote_inspect_refresh_interval", 0.2, "0.02,10,0.01,or_greater")
774
775 // HTTP Proxy
776 _initial_set("network/http_proxy/host", "");
777 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "network/http_proxy/port", 8080, "1,65535,1")
778
779 /* Extra config */
780
781 // TRANSLATORS: Project Manager here refers to the tool used to create/manage Godot projects.
782 EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "project_manager/sorting_order", 0, "Last Edited,Name,Path")
783
784#if defined(WEB_ENABLED)
785 // Web platform only supports `gl_compatibility`.
786 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_ENUM, "project_manager/default_renderer", "gl_compatibility", "forward_plus,mobile,gl_compatibility")
787#elif defined(ANDROID_ENABLED)
788 // Use more suitable rendering method by default.
789 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_ENUM, "project_manager/default_renderer", "mobile", "forward_plus,mobile,gl_compatibility")
790#else
791 EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_ENUM, "project_manager/default_renderer", "forward_plus", "forward_plus,mobile,gl_compatibility")
792#endif
793
794 if (p_extra_config.is_valid()) {
795 if (p_extra_config->has_section("init_projects") && p_extra_config->has_section_key("init_projects", "list")) {
796 Vector<String> list = p_extra_config->get_value("init_projects", "list");
797 for (int i = 0; i < list.size(); i++) {
798 String proj_name = list[i].replace("/", "::");
799 set("projects/" + proj_name, list[i]);
800 }
801 }
802
803 if (p_extra_config->has_section("presets")) {
804 List<String> keys;
805 p_extra_config->get_section_keys("presets", &keys);
806
807 for (const String &key : keys) {
808 Variant val = p_extra_config->get_value("presets", key);
809 set(key, val);
810 }
811 }
812 }
813}
814
815void EditorSettings::_load_godot2_text_editor_theme() {
816 // Godot 2 is only a dark theme; it doesn't have a light theme counterpart.
817 _initial_set("text_editor/theme/highlighting/symbol_color", Color(0.73, 0.87, 1.0));
818 _initial_set("text_editor/theme/highlighting/keyword_color", Color(1.0, 1.0, 0.7));
819 _initial_set("text_editor/theme/highlighting/control_flow_keyword_color", Color(1.0, 0.85, 0.7));
820 _initial_set("text_editor/theme/highlighting/base_type_color", Color(0.64, 1.0, 0.83));
821 _initial_set("text_editor/theme/highlighting/engine_type_color", Color(0.51, 0.83, 1.0));
822 _initial_set("text_editor/theme/highlighting/user_type_color", Color(0.42, 0.67, 0.93));
823 _initial_set("text_editor/theme/highlighting/comment_color", Color(0.4, 0.4, 0.4));
824 _initial_set("text_editor/theme/highlighting/string_color", Color(0.94, 0.43, 0.75));
825 _initial_set("text_editor/theme/highlighting/background_color", Color(0.13, 0.12, 0.15));
826 _initial_set("text_editor/theme/highlighting/completion_background_color", Color(0.17, 0.16, 0.2));
827 _initial_set("text_editor/theme/highlighting/completion_selected_color", Color(0.26, 0.26, 0.27));
828 _initial_set("text_editor/theme/highlighting/completion_existing_color", Color(0.87, 0.87, 0.87, 0.13));
829 _initial_set("text_editor/theme/highlighting/completion_scroll_color", Color(1, 1, 1, 0.29));
830 _initial_set("text_editor/theme/highlighting/completion_scroll_hovered_color", Color(1, 1, 1, 0.4));
831 _initial_set("text_editor/theme/highlighting/completion_font_color", Color(0.67, 0.67, 0.67));
832 _initial_set("text_editor/theme/highlighting/text_color", Color(0.67, 0.67, 0.67));
833 _initial_set("text_editor/theme/highlighting/line_number_color", Color(0.67, 0.67, 0.67, 0.4));
834 _initial_set("text_editor/theme/highlighting/safe_line_number_color", Color(0.67, 0.78, 0.67, 0.6));
835 _initial_set("text_editor/theme/highlighting/caret_color", Color(0.67, 0.67, 0.67));
836 _initial_set("text_editor/theme/highlighting/caret_background_color", Color(0, 0, 0));
837 _initial_set("text_editor/theme/highlighting/text_selected_color", Color(0, 0, 0, 0));
838 _initial_set("text_editor/theme/highlighting/selection_color", Color(0.41, 0.61, 0.91, 0.35));
839 _initial_set("text_editor/theme/highlighting/brace_mismatch_color", Color(1, 0.2, 0.2));
840 _initial_set("text_editor/theme/highlighting/current_line_color", Color(0.3, 0.5, 0.8, 0.15));
841 _initial_set("text_editor/theme/highlighting/line_length_guideline_color", Color(0.3, 0.5, 0.8, 0.1));
842 _initial_set("text_editor/theme/highlighting/word_highlighted_color", Color(0.8, 0.9, 0.9, 0.15));
843 _initial_set("text_editor/theme/highlighting/number_color", Color(0.92, 0.58, 0.2));
844 _initial_set("text_editor/theme/highlighting/function_color", Color(0.4, 0.64, 0.81));
845 _initial_set("text_editor/theme/highlighting/member_variable_color", Color(0.9, 0.31, 0.35));
846 _initial_set("text_editor/theme/highlighting/mark_color", Color(1.0, 0.4, 0.4, 0.4));
847 _initial_set("text_editor/theme/highlighting/bookmark_color", Color(0.08, 0.49, 0.98));
848 _initial_set("text_editor/theme/highlighting/breakpoint_color", Color(0.9, 0.29, 0.3));
849 _initial_set("text_editor/theme/highlighting/executing_line_color", Color(0.98, 0.89, 0.27));
850 _initial_set("text_editor/theme/highlighting/code_folding_color", Color(0.8, 0.8, 0.8, 0.8));
851 _initial_set("text_editor/theme/highlighting/folded_code_region_color", Color(0.68, 0.46, 0.77, 0.2));
852 _initial_set("text_editor/theme/highlighting/search_result_color", Color(0.05, 0.25, 0.05, 1));
853 _initial_set("text_editor/theme/highlighting/search_result_border_color", Color(0.41, 0.61, 0.91, 0.38));
854}
855
856bool EditorSettings::_save_text_editor_theme(String p_file) {
857 String theme_section = "color_theme";
858 Ref<ConfigFile> cf = memnew(ConfigFile); // hex is better?
859
860 List<String> keys;
861
862 for (const KeyValue<String, VariantContainer> &E : props) {
863 keys.push_back(E.key);
864 }
865
866 keys.sort();
867
868 for (const String &key : keys) {
869 if (key.begins_with("text_editor/theme/highlighting/") && key.find("color") >= 0) {
870 cf->set_value(theme_section, key.replace("text_editor/theme/highlighting/", ""), ((Color)props[key].variant).to_html());
871 }
872 }
873
874 Error err = cf->save(p_file);
875
876 return err == OK;
877}
878
879bool EditorSettings::_is_default_text_editor_theme(String p_theme_name) {
880 return p_theme_name == "default" || p_theme_name == "godot 2" || p_theme_name == "custom";
881}
882
883// PUBLIC METHODS
884
885EditorSettings *EditorSettings::get_singleton() {
886 return singleton.ptr();
887}
888
889void EditorSettings::create() {
890 // IMPORTANT: create() *must* create a valid EditorSettings singleton,
891 // as the rest of the engine code will assume it. As such, it should never
892 // return (incl. via ERR_FAIL) without initializing the singleton member.
893
894 if (singleton.ptr()) {
895 ERR_PRINT("Can't recreate EditorSettings as it already exists.");
896 return;
897 }
898
899 String config_file_path;
900 Ref<ConfigFile> extra_config = memnew(ConfigFile);
901
902 if (!EditorPaths::get_singleton()) {
903 ERR_PRINT("Bug (please report): EditorPaths haven't been initialized, EditorSettings cannot be created properly.");
904 goto fail;
905 }
906
907 if (EditorPaths::get_singleton()->is_self_contained()) {
908 Error err = extra_config->load(EditorPaths::get_singleton()->get_self_contained_file());
909 if (err != OK) {
910 ERR_PRINT("Can't load extra config from path: " + EditorPaths::get_singleton()->get_self_contained_file());
911 }
912 }
913
914 if (EditorPaths::get_singleton()->are_paths_valid()) {
915 // Validate editor config file.
916 Ref<DirAccess> dir = DirAccess::open(EditorPaths::get_singleton()->get_config_dir());
917 String config_file_name = "editor_settings-" + itos(VERSION_MAJOR) + ".tres";
918 config_file_path = EditorPaths::get_singleton()->get_config_dir().path_join(config_file_name);
919 if (!dir->file_exists(config_file_name)) {
920 goto fail;
921 }
922
923 singleton = ResourceLoader::load(config_file_path, "EditorSettings");
924
925 if (singleton.is_null()) {
926 ERR_PRINT("Could not load editor settings from path: " + config_file_path);
927 goto fail;
928 }
929
930 singleton->save_changed_setting = true;
931
932 print_verbose("EditorSettings: Load OK!");
933
934 singleton->setup_language();
935 singleton->setup_network();
936 singleton->load_favorites_and_recent_dirs();
937 singleton->list_text_editor_themes();
938
939 return;
940 }
941
942fail:
943 // patch init projects
944 String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
945
946 if (extra_config->has_section("init_projects")) {
947 Vector<String> list = extra_config->get_value("init_projects", "list");
948 for (int i = 0; i < list.size(); i++) {
949 list.write[i] = exe_path.path_join(list[i]);
950 }
951 extra_config->set_value("init_projects", "list", list);
952 }
953
954 singleton = Ref<EditorSettings>(memnew(EditorSettings));
955 singleton->set_path(config_file_path, true);
956 singleton->save_changed_setting = true;
957 singleton->_load_defaults(extra_config);
958 singleton->setup_language();
959 singleton->setup_network();
960 singleton->list_text_editor_themes();
961}
962
963void EditorSettings::setup_language() {
964 TranslationServer::get_singleton()->set_editor_pseudolocalization(get("interface/editor/debug/enable_pseudolocalization"));
965 String lang = get("interface/editor/editor_language");
966 if (lang == "en") {
967 return; // Default, nothing to do.
968 }
969 // Load editor translation for configured/detected locale.
970 load_editor_translations(lang);
971 load_property_translations(lang);
972
973 // Load class reference translation.
974 load_doc_translations(lang);
975}
976
977void EditorSettings::setup_network() {
978 List<IPAddress> local_ip;
979 IP::get_singleton()->get_local_addresses(&local_ip);
980 String hint;
981 String current = has_setting("network/debug/remote_host") ? get("network/debug/remote_host") : "";
982 String selected = "127.0.0.1";
983
984 // Check that current remote_host is a valid interface address and populate hints.
985 for (const IPAddress &ip : local_ip) {
986 // link-local IPv6 addresses don't work, skipping them
987 if (String(ip).begins_with("fe80:0:0:0:")) { // fe80::/64
988 continue;
989 }
990 // Same goes for IPv4 link-local (APIPA) addresses.
991 if (String(ip).begins_with("169.254.")) { // 169.254.0.0/16
992 continue;
993 }
994 // Select current IP (found)
995 if (ip == current) {
996 selected = ip;
997 }
998 if (!hint.is_empty()) {
999 hint += ",";
1000 }
1001 hint += ip;
1002 }
1003
1004 // Add hints with valid IP addresses to remote_host property.
1005 add_property_hint(PropertyInfo(Variant::STRING, "network/debug/remote_host", PROPERTY_HINT_ENUM, hint));
1006 // Fix potentially invalid remote_host due to network change.
1007 set("network/debug/remote_host", selected);
1008}
1009
1010void EditorSettings::save() {
1011 //_THREAD_SAFE_METHOD_
1012
1013 if (!singleton.ptr()) {
1014 return;
1015 }
1016
1017 Error err = ResourceSaver::save(singleton);
1018
1019 if (err != OK) {
1020 ERR_PRINT("Error saving editor settings to " + singleton->get_path());
1021 } else {
1022 singleton->changed_settings.clear();
1023 print_verbose("EditorSettings: Save OK!");
1024 }
1025}
1026
1027PackedStringArray EditorSettings::get_changed_settings() const {
1028 PackedStringArray arr;
1029 for (const String &setting : changed_settings) {
1030 arr.push_back(setting);
1031 }
1032
1033 return arr;
1034}
1035
1036bool EditorSettings::check_changed_settings_in_group(const String &p_setting_prefix) const {
1037 for (const String &setting : changed_settings) {
1038 if (setting.begins_with(p_setting_prefix)) {
1039 return true;
1040 }
1041 }
1042
1043 return false;
1044}
1045
1046void EditorSettings::mark_setting_changed(const String &p_setting) {
1047 changed_settings.insert(p_setting);
1048}
1049
1050void EditorSettings::destroy() {
1051 if (!singleton.ptr()) {
1052 return;
1053 }
1054 save();
1055 singleton = Ref<EditorSettings>();
1056}
1057
1058void EditorSettings::set_optimize_save(bool p_optimize) {
1059 optimize_save = p_optimize;
1060}
1061
1062// Properties
1063
1064void EditorSettings::set_setting(const String &p_setting, const Variant &p_value) {
1065 _THREAD_SAFE_METHOD_
1066 set(p_setting, p_value);
1067}
1068
1069Variant EditorSettings::get_setting(const String &p_setting) const {
1070 _THREAD_SAFE_METHOD_
1071 return get(p_setting);
1072}
1073
1074bool EditorSettings::has_setting(const String &p_setting) const {
1075 _THREAD_SAFE_METHOD_
1076
1077 return props.has(p_setting);
1078}
1079
1080void EditorSettings::erase(const String &p_setting) {
1081 _THREAD_SAFE_METHOD_
1082
1083 props.erase(p_setting);
1084}
1085
1086void EditorSettings::raise_order(const String &p_setting) {
1087 _THREAD_SAFE_METHOD_
1088
1089 ERR_FAIL_COND(!props.has(p_setting));
1090 props[p_setting].order = ++last_order;
1091}
1092
1093void EditorSettings::set_restart_if_changed(const StringName &p_setting, bool p_restart) {
1094 _THREAD_SAFE_METHOD_
1095
1096 if (!props.has(p_setting)) {
1097 return;
1098 }
1099 props[p_setting].restart_if_changed = p_restart;
1100}
1101
1102void EditorSettings::set_initial_value(const StringName &p_setting, const Variant &p_value, bool p_update_current) {
1103 _THREAD_SAFE_METHOD_
1104
1105 if (!props.has(p_setting)) {
1106 return;
1107 }
1108 props[p_setting].initial = p_value;
1109 props[p_setting].has_default_value = true;
1110 if (p_update_current) {
1111 set(p_setting, p_value);
1112 }
1113}
1114
1115Variant _EDITOR_DEF(const String &p_setting, const Variant &p_default, bool p_restart_if_changed) {
1116 ERR_FAIL_NULL_V_MSG(EditorSettings::get_singleton(), p_default, "EditorSettings not instantiated yet.");
1117
1118 Variant ret = p_default;
1119 if (EditorSettings::get_singleton()->has_setting(p_setting)) {
1120 ret = EDITOR_GET(p_setting);
1121 } else {
1122 EditorSettings::get_singleton()->set_manually(p_setting, p_default);
1123 EditorSettings::get_singleton()->set_restart_if_changed(p_setting, p_restart_if_changed);
1124 }
1125
1126 if (!EditorSettings::get_singleton()->has_default_value(p_setting)) {
1127 EditorSettings::get_singleton()->set_initial_value(p_setting, p_default);
1128 }
1129
1130 return ret;
1131}
1132
1133Variant _EDITOR_GET(const String &p_setting) {
1134 ERR_FAIL_COND_V(!EditorSettings::get_singleton() || !EditorSettings::get_singleton()->has_setting(p_setting), Variant());
1135 return EditorSettings::get_singleton()->get(p_setting);
1136}
1137
1138bool EditorSettings::_property_can_revert(const StringName &p_name) const {
1139 if (!props.has(p_name)) {
1140 return false;
1141 }
1142
1143 if (!props[p_name].has_default_value) {
1144 return false;
1145 }
1146
1147 return props[p_name].initial != props[p_name].variant;
1148}
1149
1150bool EditorSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
1151 if (!props.has(p_name) || !props[p_name].has_default_value) {
1152 return false;
1153 }
1154
1155 r_property = props[p_name].initial;
1156 return true;
1157}
1158
1159void EditorSettings::add_property_hint(const PropertyInfo &p_hint) {
1160 _THREAD_SAFE_METHOD_
1161
1162 hints[p_hint.name] = p_hint;
1163}
1164
1165// Metadata
1166
1167void EditorSettings::set_project_metadata(const String &p_section, const String &p_key, Variant p_data) {
1168 Ref<ConfigFile> cf = memnew(ConfigFile);
1169 String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("project_metadata.cfg");
1170 Error err;
1171 err = cf->load(path);
1172 ERR_FAIL_COND_MSG(err != OK && err != ERR_FILE_NOT_FOUND, "Cannot load editor settings from file '" + path + "'.");
1173 cf->set_value(p_section, p_key, p_data);
1174 err = cf->save(path);
1175 ERR_FAIL_COND_MSG(err != OK, "Cannot save editor settings to file '" + path + "'.");
1176}
1177
1178Variant EditorSettings::get_project_metadata(const String &p_section, const String &p_key, Variant p_default) const {
1179 Ref<ConfigFile> cf = memnew(ConfigFile);
1180 String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("project_metadata.cfg");
1181 Error err = cf->load(path);
1182 if (err != OK) {
1183 return p_default;
1184 }
1185 return cf->get_value(p_section, p_key, p_default);
1186}
1187
1188void EditorSettings::set_favorites(const Vector<String> &p_favorites) {
1189 favorites = p_favorites;
1190 String favorites_file;
1191 if (Engine::get_singleton()->is_project_manager_hint()) {
1192 favorites_file = EditorPaths::get_singleton()->get_config_dir().path_join("favorite_dirs");
1193 } else {
1194 favorites_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites");
1195 }
1196 Ref<FileAccess> f = FileAccess::open(favorites_file, FileAccess::WRITE);
1197 if (f.is_valid()) {
1198 for (int i = 0; i < favorites.size(); i++) {
1199 f->store_line(favorites[i]);
1200 }
1201 }
1202}
1203
1204Vector<String> EditorSettings::get_favorites() const {
1205 return favorites;
1206}
1207
1208void EditorSettings::set_recent_dirs(const Vector<String> &p_recent_dirs) {
1209 recent_dirs = p_recent_dirs;
1210 String recent_dirs_file;
1211 if (Engine::get_singleton()->is_project_manager_hint()) {
1212 recent_dirs_file = EditorPaths::get_singleton()->get_config_dir().path_join("recent_dirs");
1213 } else {
1214 recent_dirs_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("recent_dirs");
1215 }
1216 Ref<FileAccess> f = FileAccess::open(recent_dirs_file, FileAccess::WRITE);
1217 if (f.is_valid()) {
1218 for (int i = 0; i < recent_dirs.size(); i++) {
1219 f->store_line(recent_dirs[i]);
1220 }
1221 }
1222}
1223
1224Vector<String> EditorSettings::get_recent_dirs() const {
1225 return recent_dirs;
1226}
1227
1228void EditorSettings::load_favorites_and_recent_dirs() {
1229 String favorites_file;
1230 String recent_dirs_file;
1231 if (Engine::get_singleton()->is_project_manager_hint()) {
1232 favorites_file = EditorPaths::get_singleton()->get_config_dir().path_join("favorite_dirs");
1233 recent_dirs_file = EditorPaths::get_singleton()->get_config_dir().path_join("recent_dirs");
1234 } else {
1235 favorites_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites");
1236 recent_dirs_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("recent_dirs");
1237 }
1238 Ref<FileAccess> f = FileAccess::open(favorites_file, FileAccess::READ);
1239 if (f.is_valid()) {
1240 String line = f->get_line().strip_edges();
1241 while (!line.is_empty()) {
1242 favorites.push_back(line);
1243 line = f->get_line().strip_edges();
1244 }
1245 }
1246
1247 f = FileAccess::open(recent_dirs_file, FileAccess::READ);
1248 if (f.is_valid()) {
1249 String line = f->get_line().strip_edges();
1250 while (!line.is_empty()) {
1251 recent_dirs.push_back(line);
1252 line = f->get_line().strip_edges();
1253 }
1254 }
1255}
1256
1257bool EditorSettings::is_dark_theme() {
1258 int AUTO_COLOR = 0;
1259 int LIGHT_COLOR = 2;
1260 Color base_color = get("interface/theme/base_color");
1261 int icon_font_color_setting = get("interface/theme/icon_and_font_color");
1262 return (icon_font_color_setting == AUTO_COLOR && base_color.get_luminance() < 0.5) || icon_font_color_setting == LIGHT_COLOR;
1263}
1264
1265void EditorSettings::list_text_editor_themes() {
1266 String themes = "Default,Godot 2,Custom";
1267
1268 Ref<DirAccess> d = DirAccess::open(EditorPaths::get_singleton()->get_text_editor_themes_dir());
1269 if (d.is_valid()) {
1270 List<String> custom_themes;
1271 d->list_dir_begin();
1272 String file = d->get_next();
1273 while (!file.is_empty()) {
1274 if (file.get_extension() == "tet" && !_is_default_text_editor_theme(file.get_basename().to_lower())) {
1275 custom_themes.push_back(file.get_basename());
1276 }
1277 file = d->get_next();
1278 }
1279 d->list_dir_end();
1280
1281 custom_themes.sort();
1282 for (const String &E : custom_themes) {
1283 themes += "," + E;
1284 }
1285 }
1286 add_property_hint(PropertyInfo(Variant::STRING, "text_editor/theme/color_theme", PROPERTY_HINT_ENUM, themes));
1287}
1288
1289void EditorSettings::load_text_editor_theme() {
1290 String p_file = get("text_editor/theme/color_theme");
1291
1292 if (_is_default_text_editor_theme(p_file.get_file().to_lower())) {
1293 if (p_file == "Godot 2") {
1294 _load_godot2_text_editor_theme();
1295 }
1296 return; // sorry for "Settings changed" console spam
1297 }
1298
1299 String theme_path = EditorPaths::get_singleton()->get_text_editor_themes_dir().path_join(p_file + ".tet");
1300
1301 Ref<ConfigFile> cf = memnew(ConfigFile);
1302 Error err = cf->load(theme_path);
1303
1304 if (err != OK) {
1305 return;
1306 }
1307
1308 List<String> keys;
1309 cf->get_section_keys("color_theme", &keys);
1310
1311 for (const String &key : keys) {
1312 String val = cf->get_value("color_theme", key);
1313
1314 // don't load if it's not already there!
1315 if (has_setting("text_editor/theme/highlighting/" + key)) {
1316 // make sure it is actually a color
1317 if (val.is_valid_html_color() && key.find("color") >= 0) {
1318 props["text_editor/theme/highlighting/" + key].variant = Color::html(val); // change manually to prevent "Settings changed" console spam
1319 }
1320 }
1321 }
1322 emit_signal(SNAME("settings_changed"));
1323 // if it doesn't load just use what is currently loaded
1324}
1325
1326bool EditorSettings::import_text_editor_theme(String p_file) {
1327 if (!p_file.ends_with(".tet")) {
1328 return false;
1329 } else {
1330 if (p_file.get_file().to_lower() == "default.tet") {
1331 return false;
1332 }
1333
1334 Ref<DirAccess> d = DirAccess::open(EditorPaths::get_singleton()->get_text_editor_themes_dir());
1335 if (d.is_valid()) {
1336 d->copy(p_file, EditorPaths::get_singleton()->get_text_editor_themes_dir().path_join(p_file.get_file()));
1337 return true;
1338 }
1339 }
1340 return false;
1341}
1342
1343bool EditorSettings::save_text_editor_theme() {
1344 String p_file = get("text_editor/theme/color_theme");
1345
1346 if (_is_default_text_editor_theme(p_file.get_file().to_lower())) {
1347 return false;
1348 }
1349 String theme_path = EditorPaths::get_singleton()->get_text_editor_themes_dir().path_join(p_file + ".tet");
1350 return _save_text_editor_theme(theme_path);
1351}
1352
1353bool EditorSettings::save_text_editor_theme_as(String p_file) {
1354 if (!p_file.ends_with(".tet")) {
1355 p_file += ".tet";
1356 }
1357
1358 if (_is_default_text_editor_theme(p_file.get_file().to_lower().trim_suffix(".tet"))) {
1359 return false;
1360 }
1361 if (_save_text_editor_theme(p_file)) {
1362 // switch to theme is saved in the theme directory
1363 list_text_editor_themes();
1364 String theme_name = p_file.substr(0, p_file.length() - 4).get_file();
1365
1366 if (p_file.get_base_dir() == EditorPaths::get_singleton()->get_text_editor_themes_dir()) {
1367 _initial_set("text_editor/theme/color_theme", theme_name);
1368 load_text_editor_theme();
1369 }
1370 return true;
1371 }
1372 return false;
1373}
1374
1375bool EditorSettings::is_default_text_editor_theme() {
1376 String p_file = get("text_editor/theme/color_theme");
1377 return _is_default_text_editor_theme(p_file.get_file().to_lower());
1378}
1379
1380Vector<String> EditorSettings::get_script_templates(const String &p_extension, const String &p_custom_path) {
1381 Vector<String> templates;
1382 String template_dir = EditorPaths::get_singleton()->get_script_templates_dir();
1383 if (!p_custom_path.is_empty()) {
1384 template_dir = p_custom_path;
1385 }
1386 Ref<DirAccess> d = DirAccess::open(template_dir);
1387 if (d.is_valid()) {
1388 d->list_dir_begin();
1389 String file = d->get_next();
1390 while (!file.is_empty()) {
1391 if (file.get_extension() == p_extension) {
1392 templates.push_back(file.get_basename());
1393 }
1394 file = d->get_next();
1395 }
1396 d->list_dir_end();
1397 }
1398 return templates;
1399}
1400
1401String EditorSettings::get_editor_layouts_config() const {
1402 return EditorPaths::get_singleton()->get_config_dir().path_join("editor_layouts.cfg");
1403}
1404
1405float EditorSettings::get_auto_display_scale() const {
1406#if defined(MACOS_ENABLED) || defined(ANDROID_ENABLED)
1407 return DisplayServer::get_singleton()->screen_get_max_scale();
1408#else
1409 const int screen = DisplayServer::get_singleton()->window_get_current_screen();
1410
1411 if (DisplayServer::get_singleton()->screen_get_size(screen) == Vector2i()) {
1412 // Invalid screen size, skip.
1413 return 1.0;
1414 }
1415
1416 // Use the smallest dimension to use a correct display scale on portrait displays.
1417 const int smallest_dimension = MIN(DisplayServer::get_singleton()->screen_get_size(screen).x, DisplayServer::get_singleton()->screen_get_size(screen).y);
1418 if (DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && smallest_dimension >= 1400) {
1419 // hiDPI display.
1420 return 2.0;
1421 } else if (smallest_dimension >= 1700) {
1422 // Likely a hiDPI display, but we aren't certain due to the returned DPI.
1423 // Use an intermediate scale to handle this situation.
1424 return 1.5;
1425 } else if (smallest_dimension <= 800) {
1426 // Small loDPI display. Use a smaller display scale so that editor elements fit more easily.
1427 // Icons won't look great, but this is better than having editor elements overflow from its window.
1428 return 0.75;
1429 }
1430 return 1.0;
1431#endif
1432}
1433
1434// Shortcuts
1435
1436void EditorSettings::_add_shortcut_default(const String &p_name, const Ref<Shortcut> &p_shortcut) {
1437 shortcuts[p_name] = p_shortcut;
1438}
1439
1440void EditorSettings::add_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut) {
1441 shortcuts[p_name] = p_shortcut;
1442 shortcuts[p_name]->set_meta("customized", true);
1443}
1444
1445bool EditorSettings::is_shortcut(const String &p_name, const Ref<InputEvent> &p_event) const {
1446 HashMap<String, Ref<Shortcut>>::ConstIterator E = shortcuts.find(p_name);
1447 ERR_FAIL_COND_V_MSG(!E, false, "Unknown Shortcut: " + p_name + ".");
1448
1449 return E->value->matches_event(p_event);
1450}
1451
1452Ref<Shortcut> EditorSettings::get_shortcut(const String &p_name) const {
1453 HashMap<String, Ref<Shortcut>>::ConstIterator SC = shortcuts.find(p_name);
1454 if (SC) {
1455 return SC->value;
1456 }
1457
1458 // If no shortcut with the provided name is found in the list, check the built-in shortcuts.
1459 // Use the first item in the action list for the shortcut event, since a shortcut can only have 1 linked event.
1460
1461 Ref<Shortcut> sc;
1462 HashMap<String, List<Ref<InputEvent>>>::ConstIterator builtin_override = builtin_action_overrides.find(p_name);
1463 if (builtin_override) {
1464 sc.instantiate();
1465 sc->set_events_list(&builtin_override->value);
1466 sc->set_name(InputMap::get_singleton()->get_builtin_display_name(p_name));
1467 }
1468
1469 // If there was no override, check the default builtins to see if it has an InputEvent for the provided name.
1470 if (sc.is_null()) {
1471 HashMap<String, List<Ref<InputEvent>>>::ConstIterator builtin_default = InputMap::get_singleton()->get_builtins_with_feature_overrides_applied().find(p_name);
1472 if (builtin_default) {
1473 sc.instantiate();
1474 sc->set_events_list(&builtin_default->value);
1475 sc->set_name(InputMap::get_singleton()->get_builtin_display_name(p_name));
1476 }
1477 }
1478
1479 if (sc.is_valid()) {
1480 // Add the shortcut to the list.
1481 shortcuts[p_name] = sc;
1482 return sc;
1483 }
1484
1485 return Ref<Shortcut>();
1486}
1487
1488void EditorSettings::get_shortcut_list(List<String> *r_shortcuts) {
1489 for (const KeyValue<String, Ref<Shortcut>> &E : shortcuts) {
1490 r_shortcuts->push_back(E.key);
1491 }
1492}
1493
1494Ref<Shortcut> ED_GET_SHORTCUT(const String &p_path) {
1495 ERR_FAIL_NULL_V_MSG(EditorSettings::get_singleton(), nullptr, "EditorSettings not instantiated yet.");
1496
1497 Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path);
1498
1499 ERR_FAIL_COND_V_MSG(!sc.is_valid(), sc, "Used ED_GET_SHORTCUT with invalid shortcut: " + p_path + ".");
1500
1501 return sc;
1502}
1503
1504void ED_SHORTCUT_OVERRIDE(const String &p_path, const String &p_feature, Key p_keycode, bool p_physical) {
1505 ERR_FAIL_NULL_MSG(EditorSettings::get_singleton(), "EditorSettings not instantiated yet.");
1506
1507 Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path);
1508 ERR_FAIL_COND_MSG(!sc.is_valid(), "Used ED_SHORTCUT_OVERRIDE with invalid shortcut: " + p_path + ".");
1509
1510 PackedInt32Array arr;
1511 arr.push_back((int32_t)p_keycode);
1512
1513 ED_SHORTCUT_OVERRIDE_ARRAY(p_path, p_feature, arr, p_physical);
1514}
1515
1516void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, const PackedInt32Array &p_keycodes, bool p_physical) {
1517 ERR_FAIL_NULL_MSG(EditorSettings::get_singleton(), "EditorSettings not instantiated yet.");
1518
1519 Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path);
1520 ERR_FAIL_COND_MSG(!sc.is_valid(), "Used ED_SHORTCUT_OVERRIDE_ARRAY with invalid shortcut: " + p_path + ".");
1521
1522 // Only add the override if the OS supports the provided feature.
1523 if (!OS::get_singleton()->has_feature(p_feature)) {
1524 if (!(p_feature == "macos" && (OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")))) {
1525 return;
1526 }
1527 }
1528
1529 Array events;
1530
1531 for (int i = 0; i < p_keycodes.size(); i++) {
1532 Key keycode = (Key)p_keycodes[i];
1533
1534 if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
1535 // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS
1536 if (keycode == Key::KEY_DELETE) {
1537 keycode = KeyModifierMask::META | Key::BACKSPACE;
1538 }
1539 }
1540
1541 Ref<InputEventKey> ie;
1542 if (keycode != Key::NONE) {
1543 ie = InputEventKey::create_reference(keycode, p_physical);
1544 events.push_back(ie);
1545 }
1546 }
1547
1548 // Override the existing shortcut only if it wasn't customized by the user.
1549 if (!sc->has_meta("customized")) {
1550 sc->set_events(events);
1551 }
1552
1553 sc->set_meta("original", events.duplicate(true));
1554}
1555
1556Ref<Shortcut> ED_SHORTCUT(const String &p_path, const String &p_name, Key p_keycode, bool p_physical) {
1557 PackedInt32Array arr;
1558 arr.push_back((int32_t)p_keycode);
1559 return ED_SHORTCUT_ARRAY(p_path, p_name, arr, p_physical);
1560}
1561
1562Ref<Shortcut> ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, bool p_physical) {
1563 Array events;
1564
1565 for (int i = 0; i < p_keycodes.size(); i++) {
1566 Key keycode = (Key)p_keycodes[i];
1567
1568 if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
1569 // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS
1570 if (keycode == Key::KEY_DELETE) {
1571 keycode = KeyModifierMask::META | Key::BACKSPACE;
1572 }
1573 }
1574
1575 Ref<InputEventKey> ie;
1576 if (keycode != Key::NONE) {
1577 ie = InputEventKey::create_reference(keycode, p_physical);
1578 events.push_back(ie);
1579 }
1580 }
1581
1582 if (!EditorSettings::get_singleton()) {
1583 Ref<Shortcut> sc;
1584 sc.instantiate();
1585 sc->set_name(p_name);
1586 sc->set_events(events);
1587 sc->set_meta("original", events.duplicate(true));
1588 return sc;
1589 }
1590
1591 Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_path);
1592 if (sc.is_valid()) {
1593 sc->set_name(p_name); //keep name (the ones that come from disk have no name)
1594 sc->set_meta("original", events.duplicate(true)); //to compare against changes
1595 return sc;
1596 }
1597
1598 sc.instantiate();
1599 sc->set_name(p_name);
1600 sc->set_events(events);
1601 sc->set_meta("original", events.duplicate(true)); //to compare against changes
1602 EditorSettings::get_singleton()->_add_shortcut_default(p_path, sc);
1603
1604 return sc;
1605}
1606
1607void EditorSettings::set_builtin_action_override(const String &p_name, const TypedArray<InputEvent> &p_events) {
1608 List<Ref<InputEvent>> event_list;
1609
1610 // Override the whole list, since events may have their order changed or be added, removed or edited.
1611 InputMap::get_singleton()->action_erase_events(p_name);
1612 for (int i = 0; i < p_events.size(); i++) {
1613 event_list.push_back(p_events[i]);
1614 InputMap::get_singleton()->action_add_event(p_name, p_events[i]);
1615 }
1616
1617 // Check if the provided event array is same as built-in. If it is, it does not need to be added to the overrides.
1618 // Note that event order must also be the same.
1619 bool same_as_builtin = true;
1620 HashMap<String, List<Ref<InputEvent>>>::ConstIterator builtin_default = InputMap::get_singleton()->get_builtins_with_feature_overrides_applied().find(p_name);
1621 if (builtin_default) {
1622 const List<Ref<InputEvent>> &builtin_events = builtin_default->value;
1623
1624 // In the editor we only care about key events.
1625 List<Ref<InputEventKey>> builtin_key_events;
1626 for (Ref<InputEventKey> iek : builtin_events) {
1627 if (iek.is_valid()) {
1628 builtin_key_events.push_back(iek);
1629 }
1630 }
1631
1632 if (p_events.size() == builtin_key_events.size()) {
1633 int event_idx = 0;
1634
1635 // Check equality of each event.
1636 for (const Ref<InputEventKey> &E : builtin_key_events) {
1637 if (!E->is_match(p_events[event_idx])) {
1638 same_as_builtin = false;
1639 break;
1640 }
1641 event_idx++;
1642 }
1643 } else {
1644 same_as_builtin = false;
1645 }
1646 }
1647
1648 if (same_as_builtin && builtin_action_overrides.has(p_name)) {
1649 builtin_action_overrides.erase(p_name);
1650 } else {
1651 builtin_action_overrides[p_name] = event_list;
1652 }
1653
1654 // Update the shortcut (if it is used somewhere in the editor) to be the first event of the new list.
1655 if (shortcuts.has(p_name)) {
1656 shortcuts[p_name]->set_events_list(&event_list);
1657 }
1658}
1659
1660const Array EditorSettings::get_builtin_action_overrides(const String &p_name) const {
1661 HashMap<String, List<Ref<InputEvent>>>::ConstIterator AO = builtin_action_overrides.find(p_name);
1662 if (AO) {
1663 Array event_array;
1664
1665 List<Ref<InputEvent>> events_list = AO->value;
1666 for (const Ref<InputEvent> &E : events_list) {
1667 event_array.push_back(E);
1668 }
1669 return event_array;
1670 }
1671
1672 return Array();
1673}
1674
1675void EditorSettings::notify_changes() {
1676 _THREAD_SAFE_METHOD_
1677
1678 SceneTree *sml = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop());
1679
1680 if (!sml) {
1681 return;
1682 }
1683
1684 Node *root = sml->get_root()->get_child(0);
1685
1686 if (!root) {
1687 return;
1688 }
1689 root->propagate_notification(NOTIFICATION_EDITOR_SETTINGS_CHANGED);
1690}
1691
1692void EditorSettings::_bind_methods() {
1693 ClassDB::bind_method(D_METHOD("has_setting", "name"), &EditorSettings::has_setting);
1694 ClassDB::bind_method(D_METHOD("set_setting", "name", "value"), &EditorSettings::set_setting);
1695 ClassDB::bind_method(D_METHOD("get_setting", "name"), &EditorSettings::get_setting);
1696 ClassDB::bind_method(D_METHOD("erase", "property"), &EditorSettings::erase);
1697 ClassDB::bind_method(D_METHOD("set_initial_value", "name", "value", "update_current"), &EditorSettings::set_initial_value);
1698 ClassDB::bind_method(D_METHOD("add_property_info", "info"), &EditorSettings::_add_property_info_bind);
1699
1700 ClassDB::bind_method(D_METHOD("set_project_metadata", "section", "key", "data"), &EditorSettings::set_project_metadata);
1701 ClassDB::bind_method(D_METHOD("get_project_metadata", "section", "key", "default"), &EditorSettings::get_project_metadata, DEFVAL(Variant()));
1702
1703 ClassDB::bind_method(D_METHOD("set_favorites", "dirs"), &EditorSettings::set_favorites);
1704 ClassDB::bind_method(D_METHOD("get_favorites"), &EditorSettings::get_favorites);
1705 ClassDB::bind_method(D_METHOD("set_recent_dirs", "dirs"), &EditorSettings::set_recent_dirs);
1706 ClassDB::bind_method(D_METHOD("get_recent_dirs"), &EditorSettings::get_recent_dirs);
1707
1708 ClassDB::bind_method(D_METHOD("set_builtin_action_override", "name", "actions_list"), &EditorSettings::set_builtin_action_override);
1709
1710 ClassDB::bind_method(D_METHOD("check_changed_settings_in_group", "setting_prefix"), &EditorSettings::check_changed_settings_in_group);
1711 ClassDB::bind_method(D_METHOD("get_changed_settings"), &EditorSettings::get_changed_settings);
1712 ClassDB::bind_method(D_METHOD("mark_setting_changed", "setting"), &EditorSettings::mark_setting_changed);
1713
1714 ADD_SIGNAL(MethodInfo("settings_changed"));
1715
1716 BIND_CONSTANT(NOTIFICATION_EDITOR_SETTINGS_CHANGED);
1717}
1718
1719EditorSettings::EditorSettings() {
1720 last_order = 0;
1721
1722 _load_defaults();
1723}
1724
1725EditorSettings::~EditorSettings() {
1726}
1727