Initial Project Upload

This commit is contained in:
Schimmel 2025-02-25 22:07:11 +01:00
commit 37f8656620
482 changed files with 28013 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

4
source/.editorconfig Normal file
View File

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Patou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,114 @@
# BBCodeEdit (for script editor[^editor_only])
![Addon's icon](/icon.svg)
A Godot addon that brings BBCode completion and QOL tools to the script editor
in order to help format documentation comments.
(May be extended to any CodeEdit in the future.[^editor_only])
# Showcase
There are **shortcuts** to easily toggle some formattings:
![Using the keyboard to toggle bold, italic, underline, striketrough](/addons/bbcode_edit.editor/.assets_for_readme/shortcuts.gif)
**Completion** for formatting tags, with some special completions implemented for specific tags:
![Advanced completion for color tag](/addons/bbcode_edit.editor/.assets_for_readme/color_completion.gif)
**Documentation references** are completed:
![Reference completion](/addons/bbcode_edit.editor/.assets_for_readme/reference_completion.gif)
Some useful **snippets** are included:
![A "Note" snippet, with the same formatting as the one used in the official documentation](/addons/bbcode_edit.editor/.assets_for_readme/snippet.gif)
# Features / Roadmap
*Checked items are the implemented ones, unchecked are the ones in development.[^editor_only]*
- [ ] Code completion for:
- [x] Most used BBCode tags
- [ ] All BBCode tags[^editor_only] (See [Godot Reference](https://docs.godotengine.org/en/4.3/tutorials/ui/bbcode_in_richtextlabel.html#reference))
- [x] Documentation comments:
- [x] formatting tags
- [x] referencing tag
- [x] `@` tags:
- [x] deprecated
- [x] experimental
- [x] tutorial
- [x] Snippets:
- [x] **Note:**, **Warning:**...
- [ ] Classify tags accepted in documentation comments according to [Godot Reference](https://docs.godotengine.org/en/4.3/tutorials/scripting/gdscript/gdscript_documentation_comments.html#bbcode-and-class-reference)
- [ ] Advanced completions:
- [x] Color:
- [x] Named colors
- [x] Hexadecimal color preview (**Note: ** If it starts with a digit, `0x` will be prefixed temporarily because Godot cancels int completion)
- [x] Color picker
- [ ] URL of files? (I don't know if file URLs work)
- [x] Documentation comments' references: (NB: [Inner classes](https://docs.godotengine.org/en/4.3/tutorials/scripting/gdscript/gdscript_basics.html#inner-classes) won't be properly proposed in completions)
- [x] Classes
- [x] Parameters
- [x] Members (aka. Properties)
- [x] Methods (aka. Functions)
- [x] Constants
- [x] Signals
- [x] Enums
- [ ] Other references, like annotations or operators, are not implemented because they are rarely used.
- [ ] ~~BBCode preview (through [SyntaxHighlighter](https://docs.godotengine.org/en/4.3/classes/class_syntaxhighlighter.html)?)~~\
~~Edit: Won't work because GDSCriptSyntaxHighlighter can't be extended\
BBCode spellcheck/semi-preview (through `_draw()` ?)~~\
Edit 2: Won't implement, because there is easier:
- [ ] Add a shortcut to open:
- [x] Current file documentation (May have a problem if pressed before the editor checks unsaved status of the file)
- [ ] Preview of the selected text (or autodetect start and end if no selection)
- [ ] Add shortcuts for:
*You can rebind them in `Project → Project settings → Input Map`. If you just enabled the addon, they may appear here only after a restart of the editor. You will have to restart the editor for any change to take effect.*
- [x] **bold** (`alt + B`)
- [x] *italic* (`alt + I`)
- [x] ~~striketrough~~ (`alt + C`, but if you had unbound `alt + S` from `open shader editor`, it will be `alt + S`)
- [x] <u>underline</u> (`alt + U`)
- [ ] Wrap in any tag?
- [ ] Add an external CodeEdit in the editor to write bbcode, because completion inside strings is a nightmare due to builtin behaviors.[^editor_only]
## Installation
You can download the addon:
- On GitHub: `Code``Download ZIP`.
- Through the editor: `AssetLib` → Search for "BBCodeEdit"
*By default, this readme is included, along with it's illustrations. If you don't want them,
do not download `addons/bbcode_edit.editor/README.md` nor `addons/bbcode_edit.editor/.assets_for_readme/*`*
To edit shortcuts:
- *If they don't show up* in the input map GUI: first restart the editor
- modify them in the input map GUI
- **restart** the editor to update the input map.
You can also exclude `*.editor/*` or `bbcode_edit.editor/` from your export presets,
because this addon is (for now[^editor_only]) script-editor-only.
## Godot version
Godot 4.3 (May work with previous 4.x versions)
# Development Status
Development halted. But the addon is really handy as-is.
[^editor_only]: **Note:** All non-script-editor-related features are on hold for now
because I don't have the time nor the need to implement them.
This would also require refactoring `bbcode_edit.gd` into two separate classes.
One with all [RichTextLabel's tags](https://docs.godotengine.org/en/4.3/tutorials/ui/bbcode_in_richtextlabel.html#reference)
and one with [documentation comments' tags](https://docs.godotengine.org/en/4.3/tutorials/scripting/gdscript/gdscript_documentation_comments.html#bbcode-and-class-reference).
If you really need BBCode completion in a CodeEdit (eg. for an inspector plugin, or for any code edit within an exported project),
I *may* give it a try, or you could contribute this refactor (this repo is under the MIT Licence).

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#e0e0e0"><path d="M456-96q-29.7 0-50.85-21.15Q384-138.3 384-168v-167H264q-29.7 0-50.85-21.15Q192-377.3 192-407v-265q0-61 42-102.5T336-816h432v409q0 29.7-21.5 50.85Q725-335 696-335H576v167q0 29.7-21.5 50.85Q533-96 504-96h-48ZM264-552h432v-192h-48v144h-72v-144h-48v73h-72v-73H336q-29.7 0-50.85 20.5Q264-703 264-672v120Zm0 145h432v-73H264v73Zm0 0v-73 73Z"/></svg>

After

Width:  |  Height:  |  Size: 458 B

View File

@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bpwcspqlqm78h"
path="res://.godot/imported/bbcode_completion_icon.svg-e050b1e9fb923c5a17b0f52532042234.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/bbcode_edit.editor/bbcode_completion_icon.svg"
dest_files=["res://.godot/imported/bbcode_completion_icon.svg-e050b1e9fb923c5a17b0f52532042234.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@ -0,0 +1,891 @@
@tool
extends CodeEdit
enum CompletionKind {
IGNORE,
FORMATTING,
COLOR,
COMMAND,
CLASS_REFERENCE,
REFERENCE_START,
REFERENCE_END,
REFERENCING_TAG,
ANNOTATION,
}
const Completions = preload("res://addons/bbcode_edit.editor/completions_db/completions.gd")
const Scraper = preload("res://addons/bbcode_edit.editor/editor_interface_scraper.gd")
const ACTION_TOGGLE_BOLD = &"bbcode_edit/toggle_bold"
const ACTION_TOGGLE_ITALIC = &"bbcode_edit/toggle_italic"
const ACTION_TOGGLE_UNDERLINE = &"bbcode_edit/toggle_underline"
const ACTION_TOGGLE_STRIKE = &"bbcode_edit/toggle_strike"
const TOGGLING_ACTIONS = {
ACTION_TOGGLE_BOLD: "b",
ACTION_TOGGLE_ITALIC: "i",
ACTION_TOGGLE_UNDERLINE: "u",
ACTION_TOGGLE_STRIKE: "s",
}
const BBCODE_COMPLETION_ICON = preload("res://addons/bbcode_edit.editor/bbcode_completion_icon.svg")
const COLOR_PICKER_CONTAINER_PATH = ^"_BBCodeEditColorPicker"
const COLOR_PICKER_PATH = ^"_BBCodeEditColorPicker/ColorPicker"
const MALFORMED = "MALFORMED"
const COMMAND_PREFIX_CHAR = "\u0001"
const ORDER_PREFIX = "\ufffe"
const CLASS_REFERENCE_PREFIX_CHAR = "\uffff"
const REFERENCE_START_SUFFIX_CHAR = "\uffff"
const REFERENCE_END_SUFFIX_CHAR = "\ufffe"
const ANNOTATION_SUFFIX_CHAR = "\ufff0"
const _COMMAND_COLOR_PICKER = "color_picker"
const CLASS_DOC_ENDERS: Array[String] = [
"##",
"signal",
"enum",
"const",
"@export",
"var",
"@onready",
"static",
"func",
"class ",
]
static var REGEX_PARENTHESES = RegEx.create_from_string(r"\(([^)]+)\)")
func _init() -> void:
set_process_input(true)
if has_meta(&"initialized"):
return
code_completion_requested.connect(add_completion_options)
text_changed.connect(_on_text_changed)
code_completion_prefixes += ["["] # Use assignation because append don't work
set_meta(&"initialized", true)
func add_completion_options() -> void:
#print("Code completion options requested")
var line_i: int = get_caret_line()
var line: String = get_line(line_i)
var column_i: int = get_caret_column()
var comment_i: int = is_in_comment(line_i, column_i)
if comment_i == -1 or get_delimiter_start_key(comment_i) != "##":
if line[column_i-1] == "[":
#print_rich("[color=red]Emergency cancel[/color]")
cancel_code_completion()
#print_rich("[color=red]not in bbcode completion[/color]")
return
#print_rich("[color=red]in bbcode completion[/color]")
var to_test: String = trim_doc_comment_start(line.left(column_i))
var line_only: String = to_test
if line_only[0] == "@" and not (
line_only.begins_with("@tutorial: ")
or line_only.begins_with("@deprecated: ")
or line_only.begins_with("@experimental: ")
):
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"@deprecated" + ANNOTATION_SUFFIX_CHAR,
"deprecated\n## ",
get_theme_color(&"font-color"),
Scraper.get_icon(&"StatusError"),
)
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"@deprecated: Some explaination" + ANNOTATION_SUFFIX_CHAR,
"deprecated: ",
get_theme_color(&"font-color"),
Scraper.get_icon(&"StatusError"),
)
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"@experimental" + ANNOTATION_SUFFIX_CHAR,
"experimental\n## ",
get_theme_color(&"font-color"),
Scraper.get_icon(&"NodeWarning"),
)
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"@experimental: Some explaination" + ANNOTATION_SUFFIX_CHAR,
"experimental: ",
get_theme_color(&"font-color"),
Scraper.get_icon(&"NodeWarning"),
)
var class_comment_end_line: int = 0
while not _is_line_class_doc_ender(get_line(class_comment_end_line)):
class_comment_end_line += 1
while get_line(class_comment_end_line).begins_with("##"):
class_comment_end_line += 1
if _is_line_class_doc_ender(get_line(class_comment_end_line)):
class_comment_end_line = -1
if line_i < class_comment_end_line:
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"@tutorial: https://example.com" + ANNOTATION_SUFFIX_CHAR,
"tutorial: https://",
get_theme_color(&"font-color"),
Scraper.get_icon(&"ExternalLink"),
)
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"@tutorial(Title): https://example.com" + ANNOTATION_SUFFIX_CHAR,
"tutorial(|): https://",
get_theme_color(&"font-color"),
Scraper.get_icon(&"ExternalLink"),
)
update_code_completion_options(true)
return
var prev_line_i: int = line_i - 1
var prev_line: String = get_line(prev_line_i).strip_edges(true, false)
while prev_line.begins_with("##"):
to_test = prev_line.trim_prefix("##").strip_edges() + " " + to_test
prev_line_i -= 1
prev_line = get_line(prev_line_i).strip_edges(true, false)
to_test = to_test.split("]")[-1]#.split("=")[-1]
#print_rich("to_test:[color=magenta][code] ", to_test)
if "[" not in to_test:
#print("No BRACKET")
update_code_completion_options(true)
return
var describes_i: int = line_i
while is_in_comment(describes_i) != -1:
describes_i += 1
var describes: String = get_line(describes_i)
if check_parameter_completions(to_test, describes_i, describes):
return
var font_color: Color = get_theme_color(&"font_color")
if line_only[0] == "[":
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"Note:",
"b]Note:[/b] ",
font_color,
Scraper.get_icon(&"TextMesh"),
)
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"Warning:",
"b]Warning:[/b] ",
font_color,
Scraper.get_icon(&"TextMesh"),
)
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"Example:",
"b]Example:[/b] ",
font_color,
Scraper.get_icon(&"TextMesh"),
)
# TODO only propose valid tags
var completions: Array[String] = (
Completions.TAGS_UNIVERSAL
+ Completions.TAGS_DOC_COMMENT_FORMATTING
# TODO MAYBE: I have to refactor everything related to tag availability.
#+ Completions.TAGS_RICH_TEXT_LABEL
)
var displays: Array[String] = []
displays.assign(completions.map(_bracket))
#print("First completion is: ", completions[0])
for i in completions.size():
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
displays[i].replace("|", ""),
completions[i],
font_color,
BBCODE_COMPLETION_ICON,
)
var reference_completions: Array[String] = Completions.TAGS_DOC_COMMENT_REFERENCE
var reference_displays: Array[String] = []
for completion in reference_completions:
reference_displays.append(_bracket(completion.trim_suffix("|") + "Class.name"))
var reference_icon: Texture2D = Scraper.get_reference_icon()
for i in reference_completions.size():
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
reference_displays[i].replace("|", ""),
reference_completions[i],
font_color,
reference_icon,
)
if describes.begins_with("func "):
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
"[param name]",
"param |",
font_color,
reference_icon,
)
var class_completions := Completions.get_class_completions()
for i in len(class_completions.names):
var name_: String = class_completions.names[i]
add_code_completion_option(
CodeEdit.KIND_CLASS,
CLASS_REFERENCE_PREFIX_CHAR + "[" + name_ + "]",
name_ + "||",
font_color,
class_completions.icons[i],
)
update_code_completion_options(true) # NEEDED so that `[` triggers popup
func _is_line_class_doc_ender(line: String) -> bool:
for doc_ender in CLASS_DOC_ENDERS:
if line.begins_with(doc_ender):
return true
return false
func _bracket(string: String) -> String:
return "[" + string + "]"
func trim_doc_comment_start(line: String) -> String:
return line.strip_edges(true, false).trim_prefix("##").strip_edges(true, false)
func check_parameter_completions(to_test: String, describes_i: int, describes: String) -> bool:
to_test = to_test.split("[")[-1]
var parts: PackedStringArray = to_test.split(" ", false)
var parameters: PackedStringArray = PackedStringArray()
var values: PackedStringArray = PackedStringArray()
for part in parts:
# TODO MAYBE impleement sub parameter handling ? (e.g. [font otv="wght=200,wdth=400"])
var split: PackedStringArray = part.split("=", 1)
parameters.append(split[0])
values.append(split[1] if split.size() == 2 else MALFORMED)
#print_rich("Parameters:[color=magenta] ", parameters)
#print_rich("Values:[color=magenta] ", values)
if parameters.is_empty():
return false
if parameters.size() == 1 and values[0] != "MALFORMED":
var value: String = values[0]
match parameters[0]:
"color":
if value.begins_with(HEX_PREFIX) and value.substr(HEX_PREFIX.length()).is_valid_html_color():
add_hex_color(value.substr(HEX_PREFIX.length()), true)
elif value.is_valid_html_color():
if value.is_valid_int():
insert_text(HEX_PREFIX, get_caret_line(), get_caret_column()-value.length())
request_code_completion.call_deferred(true)
add_hex_color(value)
add_color_completions(value.length())
return true
match parameters[0]:
"param":
if not describes.begins_with("func "):
return false
if ")" not in describes:
var next_line_i: int = describes_i + 1
var next_line: String = get_line(next_line_i)
while ")" not in next_line:
describes += next_line
next_line_i += 1
next_line = get_line(next_line_i)
describes += next_line
#print_rich("Describes: [color=purple][code]", describes)
for part in (
REGEX_PARENTHESES.search(describes).get_string().trim_prefix("(").trim_suffix(")").split(",")
):
var param_parts := part.split(":", true, 1)
var parameter: String = param_parts[0].strip_edges()
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
parameter + REFERENCE_END_SUFFIX_CHAR,
parameter + "||",
get_theme_color(&"font_color"),
Scraper.get_icon(&"Variant")
if param_parts.size() == 1 else
Scraper.try_get_icon(param_parts[1].split("=", true, 1)[0].strip_edges(), &"Variant")
)
update_code_completion_options(true)
return true
"member":
if parameters.size() >= 2:
var path: PackedStringArray = parameters[1].split(".")
if path.size() >= 2:
if ClassDB.class_exists(path[0]):
add_member_completion_from_class_name(path[0])
else:
for other_class_ in ProjectSettings.get_global_class_list():
if other_class_["class"] == path[0]:
add_member_completion_from_script(load(other_class_["path"]))
break
update_code_completion_options(true)
return true
add_member_completion_from_script(EditorInterface.get_script_editor().get_current_script())
add_classes_completion()
return true
"method":
if parameters.size() >= 2:
var path: PackedStringArray = parameters[1].split(".")
if path.size() >= 2:
if ClassDB.class_exists(path[0]):
add_method_completion_from_class_name(path[0])
else:
for other_class_ in ProjectSettings.get_global_class_list():
if other_class_["class"] == path[0]:
add_method_completion_from_script(load(other_class_["path"]))
break
update_code_completion_options(true)
return true
add_method_completion_from_script(EditorInterface.get_script_editor().get_current_script())
add_classes_completion()
return true
"constant":
if parameters.size() >= 2:
var path: PackedStringArray = parameters[1].split(".")
if path.size() >= 2:
if ClassDB.class_exists(path[0]):
add_constant_completion_from_class_name(path[0])
else:
for other_class_ in ProjectSettings.get_global_class_list():
if other_class_["class"] == path[0]:
add_constant_completion_from_script(load(other_class_["path"]))
break
update_code_completion_options(true)
return true
add_constant_completion_from_script(EditorInterface.get_script_editor().get_current_script())
add_classes_completion()
return true
"signal":
if parameters.size() >= 2:
var path: PackedStringArray = parameters[1].split(".")
if path.size() >= 2:
if ClassDB.class_exists(path[0]):
add_signal_completion_from_class_name(path[0])
else:
for other_class_ in ProjectSettings.get_global_class_list():
if other_class_["class"] == path[0]:
add_signal_completion_from_script(load(other_class_["path"]))
break
update_code_completion_options(true)
return true
add_signal_completion_from_script(EditorInterface.get_script_editor().get_current_script())
add_classes_completion()
return true
"enum":
if parameters.size() >= 2:
var path: PackedStringArray = parameters[1].split(".")
if path.size() >= 2:
if ClassDB.class_exists(path[0]):
add_enum_completion_from_class_name(path[0])
else:
for other_class_ in ProjectSettings.get_global_class_list():
if other_class_["class"] == path[0]:
add_enum_completion_from_script(load(other_class_["path"]))
break
update_code_completion_options(true)
return true
add_enum_completion_from_script(EditorInterface.get_script_editor().get_current_script())
add_classes_completion()
return true
return false
func substr_clamped_start(string: String, from: int, len: int) -> String:
if from < 0:
if len != -1:
len += from
if len < 0:
len = 0
from = 0
return string.substr(from, len)
const HEX_PREFIX = "0x"
func add_hex_color(hex: String, include_prefix: bool = false) -> void:
print_rich("Valid color: ", hex, " [color=", hex, "]██████")
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
HEX_PREFIX + hex + " ",
HEX_PREFIX + hex if include_prefix else hex,
get_theme_color(&"font_color"),
Scraper.get_color_icon(),
Color.html(hex),
)
func add_color_completions(chars_typed: int) -> void:
var icon = Scraper.get_color_icon()
for color in Completions.COLORS:
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
color,
color,
get_theme_color(&"font_color"),
icon,
Color.from_string(color, Color.RED),
)
add_code_completion_option(
CodeEdit.KIND_PLAIN_TEXT,
COMMAND_PREFIX_CHAR + "Bring color picker",
_COMMAND_COLOR_PICKER + "," + str(chars_typed),
get_theme_color(&"font_color"),
EditorInterface.get_base_control().get_theme_icon("ColorPicker", "EditorIcons"),
)
update_code_completion_options(true)
## Add completion for classes WITH SUBSCRIPT (aka it will add a dot at the end)
func add_classes_completion() -> void:
var class_completions := Completions.get_class_completions()
for i in len(class_completions.names):
var name_: String = class_completions.names[i]
add_code_completion_option(
CodeEdit.KIND_CLASS,
ORDER_PREFIX + name_ + "." + REFERENCE_START_SUFFIX_CHAR,
name_ + ".",
get_theme_color(&"font_color"),
class_completions.icons[i],
)
update_code_completion_options(true)
func add_member_completion_from_script(class_: Script) -> void:
add_members(class_.get_script_property_list())
# Don't show inherited things, because the reference won't work.
#add_member_completion_from_class_name(class_.get_instance_base_type())
func add_member_completion_from_class_name(class_: StringName) -> void:
add_members(ClassDB.class_get_property_list(class_, true))
func add_members(members: Array[Dictionary]) -> void:
for member in members:
if member["usage"] & (
PROPERTY_USAGE_INTERNAL
| PROPERTY_USAGE_CATEGORY
| PROPERTY_USAGE_GROUP
| PROPERTY_USAGE_SUBGROUP
):
continue
add_code_completion_option(
CodeEdit.KIND_MEMBER,
member["name"] + REFERENCE_END_SUFFIX_CHAR,
member["name"] + "||",
get_theme_color(&"font-color"),
get_icon_for_member(member),
)
func get_icon_for_member(member: Dictionary, fallback: StringName = &"MemberProperty") -> Texture2D:
if member["type"] == TYPE_OBJECT:
return Scraper.get_class_icon(member["class_name"], fallback)
return Scraper.get_builtin_type_icon(member["type"], fallback)
func add_method_completion_from_script(class_: Script) -> void:
add_methods(class_.get_method_list())
# Don't show inherited things, because the reference won't work.
#add_method_completion_from_class_name(class_.get_instance_base_type())
func add_method_completion_from_class_name(class_: StringName) -> void:
add_methods(ClassDB.class_get_method_list(class_, true))
func add_methods(methods: Array[Dictionary]) -> void:
for method in methods:
add_code_completion_option(
CodeEdit.KIND_FUNCTION,
method["name"] + REFERENCE_END_SUFFIX_CHAR,
method["name"] + "||",
get_theme_color(&"font-color"),
get_icon_for_method(method),
)
func get_icon_for_method(method: Dictionary) -> Texture2D:
var returned: Dictionary = method["return"]
if returned["type"] == TYPE_NIL:
return Scraper.get_icon(&"MemberMethod")
return get_icon_for_member(returned, &"Function")
func add_constant_completion_from_script(class_: Script) -> void:
add_constants(class_.get_script_constant_map())
# Don't show inherited things, because the reference won't work.
#add_constant_completion_from_class_name(class_.get_instance_base_type())
func add_constant_completion_from_class_name(class_: StringName) -> void:
for constant_name in ClassDB.class_get_integer_constant_list(class_, true):
add_constants({constant_name: ClassDB.class_get_integer_constant(class_, constant_name)})
func add_constants(constants: Dictionary) -> void:
for constant in constants:
var value: Variant = constants[constant]
var type: int = typeof(value)
if type == TYPE_OBJECT and value is Script and value.resource_path.is_empty():
# Inner classes don't work in class
continue
var display_text: String = constant
if type != TYPE_COLOR:
var repr: String = var_to_str(value)
if repr.is_empty():
if type == TYPE_OBJECT:
repr = value.to_string()
else:
repr = str(repr)
if repr.length() > 32:
repr = repr.left(32) + "..."
display_text += " (" + repr + ")"
display_text += REFERENCE_END_SUFFIX_CHAR
add_code_completion_option(
CodeEdit.KIND_CONSTANT,
display_text,
constant + "||",
get_theme_color(&"font-color"),
Scraper.get_type_icon(value, &"MemberConstant"),
value
)
func add_signal_completion_from_script(class_: Script) -> void:
add_signals(class_.get_script_signal_list())
# Don't show inherited things, because the reference won't work.
#add_signal_completion_from_class_name(class_.get_instance_base_type())
func add_signal_completion_from_class_name(class_: StringName) -> void:
add_signals(ClassDB.class_get_signal_list(class_, true))
func add_signals(signals: Array[Dictionary]) -> void:
var icon: Texture2D = Scraper.get_icon(&"MemberSignal")
for signal_ in signals:
add_code_completion_option(
CodeEdit.KIND_SIGNAL,
signal_["name"] + REFERENCE_END_SUFFIX_CHAR,
signal_["name"] + "||",
get_theme_color(&"font-color"),
icon,
)
func add_enum_completion_from_script(class_: Script) -> void:
var map := class_.get_script_constant_map()
var probable_enums: PackedStringArray
for constant in map:
if typeof(map[constant]) == TYPE_DICTIONARY:
var candidate: Dictionary = map[constant]
if candidate.values().all(_is_int):
probable_enums.append(constant)
if probable_enums:
add_enums(probable_enums)
# Don't show inherited things, because the reference won't work.
#add_enum_completion_from_class_name(class_.get_instance_base_type())
static func _is_int(value: Variant) -> bool:
return typeof(value) == TYPE_INT
func add_enum_completion_from_class_name(class_: StringName) -> void:
add_enums(ClassDB.class_get_enum_list(class_, true))
func add_enums(enums: PackedStringArray) -> void:
var icon: Texture2D = Scraper.get_icon(&"Enum")
for enum_ in enums:
add_code_completion_option(
CodeEdit.KIND_ENUM,
enum_ + REFERENCE_END_SUFFIX_CHAR,
enum_ + "||",
get_theme_color(&"font-color"),
icon,
)
func toggle_tag(tag: String) -> void:
var prefix: String = "[" + tag + "]"
var prefix_len: int = prefix.length()
var suffix: String = "[/" + tag + "]"
var suffix_len: int = suffix.length()
var main_selection_from_column: int = get_selection_from_column()
var main_selection_from_line: int = get_selection_from_line()
var main_selection_to_column: int = get_selection_to_column()
var main_selection_to_line: int = get_selection_to_line()
var main_selection_end_line: String = get_line(main_selection_to_line)
if (
main_selection_from_column > prefix_len
and get_line(main_selection_from_line).substr(
main_selection_from_column - prefix_len,
prefix_len
) == prefix
and main_selection_to_column <= main_selection_end_line.length() - suffix_len
and main_selection_end_line.substr(
main_selection_to_column,
suffix_len
) == suffix
):
begin_complex_operation()
begin_multicaret_edit()
for caret in get_caret_count():
if multicaret_edit_ignore_caret(caret):
continue
var initial_text: String = get_selected_text(caret)
var initial_start_column: int = get_selection_from_column(caret)
var initial_end_column: int = get_selection_to_column(caret)
select(
get_selection_from_line(caret),
initial_start_column - prefix_len,
get_selection_to_line(caret),
initial_end_column + suffix_len,
caret
)
insert_text_at_caret(initial_text, caret)
select(
get_selection_from_line(caret),
initial_start_column - prefix_len,
get_selection_to_line(caret),
initial_end_column - prefix_len,
caret
)
end_multicaret_edit()
end_complex_operation()
return
begin_complex_operation()
begin_multicaret_edit()
for caret in get_caret_count():
if multicaret_edit_ignore_caret(caret):
continue
var initial_start_column: int = get_selection_from_column(caret)
var initial_end_column: int = get_selection_to_column(caret)
insert_text_at_caret(prefix + get_selected_text(caret) + suffix, caret)
select(
get_selection_from_line(caret),
initial_start_column + prefix_len,
get_selection_to_line(caret),
initial_end_column + prefix_len,
caret
)
end_multicaret_edit()
end_complex_operation()
func _confirm_code_completion(replace: bool = false) -> void:
var selected_completion: Dictionary = get_code_completion_option(get_code_completion_selected_index())
var display_text: String = selected_completion["display_text"]
var prefix: String = display_text[0]
if prefix == COMMAND_PREFIX_CHAR:
var parts: PackedStringArray = selected_completion["insert_text"].split(",")
var chars_to_remove: int = int(parts[1]) if parts.size() >= 2 else 0
if chars_to_remove:
for caret in get_caret_count():
var line_i: int = get_caret_line(caret)
var line: String = get_line(line_i)
var column_i: int = get_caret_column(caret)
set_line(line_i, line.left(column_i - chars_to_remove) + line.substr(column_i))
set_caret_column(column_i - chars_to_remove, false, caret)
match parts[0]:
_COMMAND_COLOR_PICKER:
if not has_node(^"BBCODE_EDIT_COLOR_PICKER"):
add_child(preload("res://addons/bbcode_edit.editor/color_picker.tscn").instantiate())
var container: PopupPanel = get_node(COLOR_PICKER_CONTAINER_PATH)
var picker: ColorPicker = get_node(COLOR_PICKER_PATH)
container.position = Vector2(get_pos_at_line_column(get_caret_line(), get_caret_column())) + global_position + Vector2(0, get_line_height())
container.add_theme_stylebox_override(&"panel", EditorInterface.get_base_control().get_theme_stylebox(&"Content", &"EditorStyles"))
picker.color_changed.connect(_on_color_picker_color_changed)
cancel_code_completion()
return
var icon: Texture2D = selected_completion["icon"]
var suffix: String = display_text[-1]
var kind: CompletionKind = (
CompletionKind.FORMATTING if icon == BBCODE_COMPLETION_ICON else
CompletionKind.COLOR if icon == Scraper.get_color_icon() else
CompletionKind.CLASS_REFERENCE if prefix == CLASS_REFERENCE_PREFIX_CHAR else
CompletionKind.REFERENCE_START if suffix == REFERENCE_START_SUFFIX_CHAR else
CompletionKind.REFERENCE_END if suffix == REFERENCE_END_SUFFIX_CHAR else
CompletionKind.ANNOTATION if suffix == ANNOTATION_SUFFIX_CHAR else
CompletionKind.REFERENCING_TAG if icon == Scraper.get_reference_icon() else
0
)
#print_rich("Kind is [color=red][code]", CompletionKind.find_key(kind))
begin_complex_operation()
# Don't use the following code, it's a dev crime.
# Oops, I just did...
# This code block allows to call the code that is meant to be executed
# when the virtual method isn't implemented.
var script: GDScript = get_script()
set_script(null)
super.confirm_code_completion(replace)
set_script(script)
if (
kind == CompletionKind.FORMATTING
or kind == CompletionKind.CLASS_REFERENCE
or kind == CompletionKind.REFERENCING_TAG
):
for caret in get_caret_count():
var line: String = get_line(get_caret_line(caret)) + " " # Add space so that column is in range
var column: int = get_caret_column(caret)
if not line[column] == "]":
insert_text_at_caret("]", caret)
# Replace caret at it's previous column
set_caret_column(column, false, caret)
if kind == CompletionKind.COLOR:
var line_i: int = get_caret_line()
var line: String = get_line(line_i)
var column: int = get_caret_column()
var color_start: int = column - selected_completion["display_text"].length() + 1
if line.substr(color_start).begins_with(HEX_PREFIX):
set_line(
line_i,
line.left(color_start) + line.substr(color_start + HEX_PREFIX.length())
)
set_caret_column(column - HEX_PREFIX.length())
for caret in get_caret_count():
set_caret_column(get_caret_column(caret) + 1, false, caret)
elif kind:
for caret in get_caret_count():
var line_i: int = get_caret_line(caret)
var line: String = get_line(line_i)
var first_pipe: int = line.find("|")
if first_pipe == -1: # Just in case
continue
var pipe_end: int = first_pipe + 1
while pipe_end < line.length() and line[pipe_end] == "|":
pipe_end += 1
set_line(line_i, line.left(first_pipe) + line.substr(pipe_end))
set_caret_column(pipe_end-1, false, caret)
end_complex_operation()
if kind == CompletionKind.REFERENCING_TAG:
var prefixes := code_completion_prefixes
code_completion_prefixes += [" "]
request_code_completion()
code_completion_prefixes = prefixes
elif kind and kind != CompletionKind.ANNOTATION:
request_code_completion()
func _gui_input(event: InputEvent) -> void:
if not event.is_pressed() or event.is_echo():
return
if has_node(COLOR_PICKER_CONTAINER_PATH):
if event is InputEventKey or event is InputEventMouseButton:
get_node(COLOR_PICKER_CONTAINER_PATH).free()
for action in TOGGLING_ACTIONS:
if is_action(event, action):
toggle_tag(TOGGLING_ACTIONS[action])
func is_action(event: InputEvent, action: StringName) -> bool:
return InputMap.has_action(action) and event.is_action(action, true)
func _on_text_changed() -> void:
var line_i: int = get_caret_line()
var column_i: int = get_caret_column()
var line: String = get_line(get_caret_line())
if (
is_in_comment(line_i, column_i) == -1
and is_in_string(line_i, column_i) == -1
and line
and line[column_i-1] == "["
):
cancel_code_completion() # Prevent completing when typing array fast
func _on_color_picker_color_changed(color: Color) -> void:
var hex: String = color.to_html(color.a8 != 255)
for caret in get_caret_count():
var line_i: int = get_caret_line(caret)
var line: String = get_line(line_i)
var column_i: int = get_caret_column(caret)
if line[column_i] != "]" and line[column_i] != " ":
var to_scan: String = line.substr(column_i)
var end: int = to_scan.find("]")
if end == -1:
end = to_scan.find(" ")
else:
var other: int = to_scan.find(" ")
if other != -1 and other < end:
end = other
set_line(line_i, line.left(column_i) + to_scan.substr(end))
insert_text_at_caret(hex, caret)
set_caret_column(column_i, false, caret)

View File

@ -0,0 +1 @@
uid://3wc61yw7stvq

View File

@ -0,0 +1,218 @@
@tool
extends EditorPlugin
const BBCodeEdit: GDScript = preload("res://addons/bbcode_edit.editor/bbcode_edit.gd")
const Scraper = preload("res://addons/bbcode_edit.editor/editor_interface_scraper.gd")
const ADDON_NAME = "BBCode Editor"
const ACTION_OPEN_DOC = &"bbcode_edit/editor/open_current_file_documentation"
const ACTION_SETTINGS: Array[StringName] = [
"input/" + ACTION_OPEN_DOC,
"input/" + BBCodeEdit.ACTION_TOGGLE_BOLD,
"input/" + BBCodeEdit.ACTION_TOGGLE_ITALIC,
"input/" + BBCodeEdit.ACTION_TOGGLE_UNDERLINE,
"input/" + BBCodeEdit.ACTION_TOGGLE_STRIKE,
]
func _enable_plugin() -> void:
print("Enabling ", ADDON_NAME)
add_keybinds()
_on_editor_startup.call_deferred()
print("Enabled ", ADDON_NAME)
func _disable_plugin() -> void:
print("Disabling ", ADDON_NAME)
for editor in EditorInterface.get_script_editor().get_open_script_editors():
editor.get_base_editor().set_script(null)
remove_keybinds()
for setting in ACTION_SETTINGS:
InputMap.erase_action(setting.substr(6))
print("Disabled ", ADDON_NAME)
func _enter_tree() -> void:
if not EditorInterface.has_meta(&"bbcode_edit_saved_once"):
EditorInterface.set_meta(&"bbcode_edit_saved_once", PackedStringArray())
_on_editor_startup.call_deferred()
var started_up: bool = false
func _on_editor_startup() -> void:
if started_up:
return
started_up = true
# TODO check if InputMap.load_from_project_settings() is better
for setting in ACTION_SETTINGS:
if !ProjectSettings.has_setting(setting):
continue
var action_dict: Dictionary = ProjectSettings.get_setting(setting)
var action_name: StringName = setting.substr(6)
InputMap.add_action(action_name, action_dict["deadzone"])
for event in action_dict["events"]:
InputMap.action_add_event(action_name, event)
EditorInterface.get_script_editor().editor_script_changed.connect(check_current.unbind(1))
check_current.call_deferred()
func add_keybinds() -> void:
var toggle_bold := InputEventKey.new()
toggle_bold.alt_pressed = true
toggle_bold.keycode = 66
ProjectSettings.set_setting(
&"input/bbcode_edit/toggle_bold",
{
"deadzone": 0.5,
"events": [toggle_bold],
}
)
var toggle_italic := InputEventKey.new()
toggle_italic.alt_pressed = true
toggle_italic.keycode = 73
ProjectSettings.set_setting(
&"input/bbcode_edit/toggle_italic",
{
"deadzone": 0.5,
"events": [toggle_italic],
}
)
var toggle_underline := InputEventKey.new()
toggle_underline.alt_pressed = true
toggle_underline.keycode = 85
ProjectSettings.set_setting(
&"input/bbcode_edit/toggle_underline",
{
"deadzone": 0.5,
"events": [toggle_underline],
}
)
var toggle_strike := InputEventKey.new()
toggle_strike.alt_pressed = true
toggle_strike.keycode = _get_striketrough_keycode()
ProjectSettings.set_setting(
&"input/bbcode_edit/toggle_strike",
{
"deadzone": 0.5,
"events": [toggle_strike],
}
)
if Engine.is_editor_hint():
add_editor_keybinds()
ProjectSettings.save()
print_rich("[color=orange]If you don't see the keybinds in the InputMap, please reload the Project.[/color]")
func _get_striketrough_keycode() -> int:
var editor_shortcuts: Variant = EditorInterface.get_editor_settings().get(&"shortcuts")
if editor_shortcuts == null:
return KEY_C
for shortcut: Dictionary in editor_shortcuts:
if shortcut["name"] == "bottom_panels/toggle_shader_editor_bottom_panel":
var input_events: Array = shortcut.get("shortcuts")
for input_event: InputEvent in input_events:
if input_event.keycode == KEY_S:
return KEY_C
return KEY_S
return KEY_C
func add_editor_keybinds() -> void:
var open_current_file_documentation := InputEventKey.new()
open_current_file_documentation.shift_pressed = true
open_current_file_documentation.keycode = 4194332
ProjectSettings.set_setting(
"input/" + ACTION_OPEN_DOC,
{
"deadzone": 0.5,
"events": [open_current_file_documentation],
}
)
# NB: Initial values DON'T work, see [url]https://github.com/godotengine/godot/issues/56598[/url]
#ProjectSettings.set_initial_value(&"input/bbcode_edit/editor/open_current_file_documentation", open_current_file_documentation.duplicate())
func remove_keybinds() -> void:
ProjectSettings.set_setting("input/" + BBCodeEdit.ACTION_TOGGLE_BOLD, null)
ProjectSettings.set_setting("input/" + BBCodeEdit.ACTION_TOGGLE_ITALIC, null)
ProjectSettings.set_setting("input/" + BBCodeEdit.ACTION_TOGGLE_UNDERLINE, null)
ProjectSettings.set_setting("input/" + BBCodeEdit.ACTION_TOGGLE_STRIKE, null)
# This calls ProjectSettings.save(), so please call it last
remove_editor_keybinds()
func remove_editor_keybinds() -> void:
ProjectSettings.set_setting("input/" + ACTION_OPEN_DOC, null)
ProjectSettings.save()
func check_current() -> void:
var current_editor := EditorInterface.get_script_editor().get_current_editor()
if current_editor == null:
return
check_bbcode_pretendant(current_editor.get_base_editor())
func check_bbcode_pretendant(pretendant: Control) -> void:
if pretendant is CodeEdit and not pretendant.has_meta(&"BBCode_utilities"):
add_bbcode_handling(pretendant)
func add_bbcode_handling(code_edit: CodeEdit) -> void:
# TODO MAYBE implement automatic script inheritence if script is already overriden by another addon
code_edit.set_meta(&"never_changed", true)
code_edit.set_script(BBCodeEdit)
## [b]WARING:[/b] not fully implemented for non-current script
func open_doc(script: Script, code_edit: CodeEdit = null) -> void:
var class_name_: String = script.get_global_name()
if class_name_ == "":
class_name_ = '"' + script.resource_path.trim_prefix("res://") + '"'
var bbcode_edit_saved_once: PackedStringArray = EditorInterface.get_meta(&"bbcode_edit_saved_once", PackedStringArray())
if code_edit and class_name_ not in bbcode_edit_saved_once:
bbcode_edit_saved_once.append(class_name_)
print_rich("[color=orange]The script never changed since startup: brute-forcing documentation generation. (See [url=https://github.com/godotengine/godot/pull/95821]godot#95821[/url])[/color]")
code_edit.text = code_edit.text
EditorInterface.save_all_scenes()
elif Scraper.is_current_script_unsaved():
# TODO ↑ Fix this for non-current script
print_rich("[color=orange]Saving to make godot generate documentation.[/color]")
EditorInterface.save_all_scenes()
elif Scraper.is_current_script_unsaved():
print_rich("[color=orange]Saving to make godot generate documentation.[/color]")
EditorInterface.save_all_scenes()
EditorInterface.get_script_editor().get_current_editor().go_to_help.emit.call_deferred("class_name:"+class_name_)
func _unhandled_input(event: InputEvent) -> void:
if (
InputMap.has_action(ACTION_OPEN_DOC)
and InputMap.event_is_action(event, ACTION_OPEN_DOC, true)
):
# TODO find a workaround for the appearance delay of (*) to check unsaved status.
var current_editor := EditorInterface.get_script_editor().get_current_editor()
if current_editor == null:
return
var code_edit := current_editor.get_base_editor()
if code_edit is CodeEdit:
open_doc(EditorInterface.get_script_editor().get_current_script(), code_edit)

View File

@ -0,0 +1 @@
uid://cvo3g4cvmjypn

View File

@ -0,0 +1,14 @@
[gd_scene format=3 uid="uid://ofreh5nfb6pa"]
[node name="_BBCodeEditColorPicker" type="PopupPanel"]
size = Vector2i(306, 584)
visible = true
[node name="ColorPicker" type="ColorPicker" parent="."]
offset_left = 4.0
offset_top = 4.0
offset_right = 302.0
offset_bottom = 580.0
color = Color(0.83233, 0, 0.367475, 1)
deferred_mode = true
picker_shape = 3

View File

@ -0,0 +1,937 @@
AESContext
AStar2D
AStar3D
AStarGrid2D
AcceptDialog
AnimatableBody2D
AnimatableBody3D
AnimatedSprite2D
AnimatedSprite3D
AnimatedTexture
Animation
AnimationLibrary
AnimationMixer
AnimationNode
AnimationNodeAdd2
AnimationNodeAdd3
AnimationNodeAnimation
AnimationNodeBlend2
AnimationNodeBlend3
AnimationNodeBlendSpace1D
AnimationNodeBlendSpace2D
AnimationNodeBlendTree
AnimationNodeOneShot
AnimationNodeOutput
AnimationNodeStateMachine
AnimationNodeStateMachinePlayback
AnimationNodeStateMachineTransition
AnimationNodeSub2
AnimationNodeSync
AnimationNodeTimeScale
AnimationNodeTimeSeek
AnimationNodeTransition
AnimationPlayer
AnimationRootNode
AnimationTree
Area2D
Area3D
ArrayMesh
ArrayOccluder3D
AspectRatioContainer
AtlasTexture
AudioBusLayout
AudioEffect
AudioEffectAmplify
AudioEffectBandLimitFilter
AudioEffectBandPassFilter
AudioEffectCapture
AudioEffectChorus
AudioEffectCompressor
AudioEffectDelay
AudioEffectDistortion
AudioEffectEQ
AudioEffectEQ10
AudioEffectEQ21
AudioEffectEQ6
AudioEffectFilter
AudioEffectHardLimiter
AudioEffectHighPassFilter
AudioEffectHighShelfFilter
AudioEffectInstance
AudioEffectLimiter
AudioEffectLowPassFilter
AudioEffectLowShelfFilter
AudioEffectNotchFilter
AudioEffectPanner
AudioEffectPhaser
AudioEffectPitchShift
AudioEffectRecord
AudioEffectReverb
AudioEffectSpectrumAnalyzer
AudioEffectSpectrumAnalyzerInstance
AudioEffectStereoEnhance
AudioListener2D
AudioListener3D
AudioSample
AudioSamplePlayback
AudioServer
AudioStream
AudioStreamGenerator
AudioStreamGeneratorPlayback
AudioStreamInteractive
AudioStreamMP3
AudioStreamMicrophone
AudioStreamOggVorbis
AudioStreamPlayback
AudioStreamPlaybackInteractive
AudioStreamPlaybackOggVorbis
AudioStreamPlaybackPlaylist
AudioStreamPlaybackPolyphonic
AudioStreamPlaybackResampled
AudioStreamPlaybackSynchronized
AudioStreamPlayer
AudioStreamPlayer2D
AudioStreamPlayer3D
AudioStreamPlaylist
AudioStreamPolyphonic
AudioStreamRandomizer
AudioStreamSynchronized
AudioStreamWAV
BackBufferCopy
BaseButton
BaseMaterial3D
BitMap
Bone2D
BoneAttachment3D
BoneMap
BoxContainer
BoxMesh
BoxOccluder3D
BoxShape3D
Button
ButtonGroup
CPUParticles2D
CPUParticles3D
CSGBox3D
CSGCombiner3D
CSGCylinder3D
CSGMesh3D
CSGPolygon3D
CSGPrimitive3D
CSGShape3D
CSGSphere3D
CSGTorus3D
CallbackTweener
Camera2D
Camera3D
CameraAttributes
CameraAttributesPhysical
CameraAttributesPractical
CameraFeed
CameraServer
CameraTexture
CanvasGroup
CanvasItem
CanvasItemMaterial
CanvasLayer
CanvasModulate
CanvasTexture
CapsuleMesh
CapsuleShape2D
CapsuleShape3D
CenterContainer
CharFXTransform
CharacterBody2D
CharacterBody3D
CheckBox
CheckButton
CircleShape2D
ClassDB
CodeEdit
CodeHighlighter
CollisionObject2D
CollisionObject3D
CollisionPolygon2D
CollisionPolygon3D
CollisionShape2D
CollisionShape3D
ColorPicker
ColorPickerButton
ColorRect
Compositor
CompositorEffect
CompressedCubemap
CompressedCubemapArray
CompressedTexture2D
CompressedTexture2DArray
CompressedTexture3D
CompressedTextureLayered
ConcavePolygonShape2D
ConcavePolygonShape3D
ConeTwistJoint3D
ConfigFile
ConfirmationDialog
Container
Control
ConvexPolygonShape2D
ConvexPolygonShape3D
Crypto
CryptoKey
Cubemap
CubemapArray
Curve
Curve2D
Curve3D
CurveTexture
CurveXYZTexture
CylinderMesh
CylinderShape3D
DTLSServer
DampedSpringJoint2D
Decal
DirAccess
DirectionalLight2D
DirectionalLight3D
DisplayServer
ENetConnection
ENetMultiplayerPeer
ENetPacketPeer
EditorCommandPalette
EditorDebuggerPlugin
EditorDebuggerSession
EditorExportPlatform
EditorExportPlatformAndroid
EditorExportPlatformIOS
EditorExportPlatformLinuxBSD
EditorExportPlatformMacOS
EditorExportPlatformPC
EditorExportPlatformWeb
EditorExportPlatformWindows
EditorExportPlugin
EditorFeatureProfile
EditorFileDialog
EditorFileSystem
EditorFileSystemDirectory
EditorFileSystemImportFormatSupportQuery
EditorImportPlugin
EditorInspector
EditorInspectorPlugin
EditorInterface
EditorNode3DGizmo
EditorNode3DGizmoPlugin
EditorPaths
EditorPlugin
EditorProperty
EditorResourceConversionPlugin
EditorResourcePicker
EditorResourcePreview
EditorResourcePreviewGenerator
EditorResourceTooltipPlugin
EditorSceneFormatImporter
EditorSceneFormatImporterBlend
EditorSceneFormatImporterFBX2GLTF
EditorSceneFormatImporterGLTF
EditorSceneFormatImporterUFBX
EditorScenePostImport
EditorScenePostImportPlugin
EditorScript
EditorScriptPicker
EditorSelection
EditorSettings
EditorSpinSlider
EditorSyntaxHighlighter
EditorTranslationParserPlugin
EditorUndoRedoManager
EditorVCSInterface
EncodedObjectAsID
Engine
EngineDebugger
EngineProfiler
Environment
Expression
FBXDocument
FBXState
FastNoiseLite
FileAccess
FileDialog
FileSystemDock
FlowContainer
FogMaterial
FogVolume
Font
FontFile
FontVariation
FramebufferCacheRD
GDExtension
GDExtensionManager
GDScript
GDScriptEditorTranslationParserPlugin
GDScriptNativeClass
GLTFAccessor
GLTFAnimation
GLTFBufferView
GLTFCamera
GLTFDocument
GLTFDocumentExtension
GLTFDocumentExtensionConvertImporterMesh
GLTFDocumentExtensionPhysics
GLTFDocumentExtensionTextureKTX
GLTFDocumentExtensionTextureWebP
GLTFLight
GLTFMesh
GLTFNode
GLTFPhysicsBody
GLTFPhysicsShape
GLTFSkeleton
GLTFSkin
GLTFSpecGloss
GLTFState
GLTFTexture
GLTFTextureSampler
GPUParticles2D
GPUParticles3D
GPUParticlesAttractor3D
GPUParticlesAttractorBox3D
GPUParticlesAttractorSphere3D
GPUParticlesAttractorVectorField3D
GPUParticlesCollision3D
GPUParticlesCollisionBox3D
GPUParticlesCollisionHeightField3D
GPUParticlesCollisionSDF3D
GPUParticlesCollisionSphere3D
Generic6DOFJoint3D
Geometry2D
Geometry3D
GeometryInstance3D
GodotNavigationServer2D
GodotPhysicsServer2D
GodotPhysicsServer3D
Gradient
GradientTexture1D
GradientTexture2D
GraphEdit
GraphElement
GraphFrame
GraphNode
GridContainer
GridMap
GrooveJoint2D
HBoxContainer
HFlowContainer
HMACContext
HScrollBar
HSeparator
HSlider
HSplitContainer
HTTPClient
HTTPRequest
HashingContext
HeightMapShape3D
HingeJoint3D
IP
IPUnix
Image
ImageFormatLoader
ImageFormatLoaderExtension
ImageTexture
ImageTexture3D
ImageTextureLayered
ImmediateMesh
ImporterMesh
ImporterMeshInstance3D
Input
InputEvent
InputEventAction
InputEventFromWindow
InputEventGesture
InputEventJoypadButton
InputEventJoypadMotion
InputEventKey
InputEventMIDI
InputEventMagnifyGesture
InputEventMouse
InputEventMouseButton
InputEventMouseMotion
InputEventPanGesture
InputEventScreenDrag
InputEventScreenTouch
InputEventShortcut
InputEventWithModifiers
InputMap
InstancePlaceholder
IntervalTweener
ItemList
JNISingleton
JSON
JSONRPC
JavaClass
JavaClassWrapper
JavaScriptBridge
JavaScriptObject
Joint2D
Joint3D
KinematicCollision2D
KinematicCollision3D
Label
Label3D
LabelSettings
Light2D
Light3D
LightOccluder2D
LightmapGI
LightmapGIData
LightmapProbe
Lightmapper
LightmapperRD
Line2D
LineEdit
LinkButton
MainLoop
MarginContainer
Marker2D
Marker3D
Marshalls
Material
MenuBar
MenuButton
Mesh
MeshConvexDecompositionSettings
MeshDataTool
MeshInstance2D
MeshInstance3D
MeshLibrary
MeshTexture
MethodTweener
MissingNode
MissingResource
MobileVRInterface
MovieWriter
MovieWriterMJPEG
MovieWriterPNGWAV
MultiMesh
MultiMeshInstance2D
MultiMeshInstance3D
MultiplayerAPI
MultiplayerAPIExtension
MultiplayerPeer
MultiplayerPeerExtension
MultiplayerSpawner
MultiplayerSynchronizer
Mutex
NativeMenu
NativeMenuWindows
NavigationAgent2D
NavigationAgent3D
NavigationLink2D
NavigationLink3D
NavigationMesh
NavigationMeshGenerator
NavigationMeshSourceGeometryData2D
NavigationMeshSourceGeometryData3D
NavigationObstacle2D
NavigationObstacle3D
NavigationPathQueryParameters2D
NavigationPathQueryParameters3D
NavigationPathQueryResult2D
NavigationPathQueryResult3D
NavigationPolygon
NavigationRegion2D
NavigationRegion3D
NavigationServer2D
NavigationServer3D
NinePatchRect
Node
Node2D
Node3D
Node3DGizmo
Noise
NoiseTexture2D
NoiseTexture3D
ORMMaterial3D
OS
Object
Occluder3D
OccluderInstance3D
OccluderPolygon2D
OfflineMultiplayerPeer
OggPacketSequence
OggPacketSequencePlayback
OmniLight3D
OpenXRAPIExtension
OpenXRAction
OpenXRActionMap
OpenXRActionSet
OpenXRCompositionLayer
OpenXRCompositionLayerCylinder
OpenXRCompositionLayerEquirect
OpenXRCompositionLayerQuad
OpenXRExtensionWrapperExtension
OpenXRHand
OpenXRIPBinding
OpenXRInteractionProfile
OpenXRInteractionProfileMetadata
OpenXRInterface
OptimizedTranslation
OptionButton
PCKPacker
PackedDataContainer
PackedDataContainerRef
PackedScene
PacketPeer
PacketPeerDTLS
PacketPeerExtension
PacketPeerStream
PacketPeerUDP
Panel
PanelContainer
PanoramaSkyMaterial
Parallax2D
ParallaxBackground
ParallaxLayer
ParticleProcessMaterial
Path2D
Path3D
PathFollow2D
PathFollow3D
Performance
PhysicalBone2D
PhysicalBone3D
PhysicalBoneSimulator3D
PhysicalSkyMaterial
PhysicsBody2D
PhysicsBody3D
PhysicsDirectBodyState2D
PhysicsDirectBodyState2DExtension
PhysicsDirectBodyState3D
PhysicsDirectBodyState3DExtension
PhysicsDirectSpaceState2D
PhysicsDirectSpaceState2DExtension
PhysicsDirectSpaceState3D
PhysicsDirectSpaceState3DExtension
PhysicsMaterial
PhysicsPointQueryParameters2D
PhysicsPointQueryParameters3D
PhysicsRayQueryParameters2D
PhysicsRayQueryParameters3D
PhysicsServer2D
PhysicsServer2DExtension
PhysicsServer2DManager
PhysicsServer3D
PhysicsServer3DExtension
PhysicsServer3DManager
PhysicsServer3DRenderingServerHandler
PhysicsShapeQueryParameters2D
PhysicsShapeQueryParameters3D
PhysicsTestMotionParameters2D
PhysicsTestMotionParameters3D
PhysicsTestMotionResult2D
PhysicsTestMotionResult3D
PinJoint2D
PinJoint3D
PlaceholderCubemap
PlaceholderCubemapArray
PlaceholderMaterial
PlaceholderMesh
PlaceholderTexture2D
PlaceholderTexture2DArray
PlaceholderTexture3D
PlaceholderTextureLayered
PlaneMesh
PointLight2D
PointMesh
Polygon2D
PolygonOccluder3D
PolygonPathFinder
Popup
PopupMenu
PopupPanel
PortableCompressedTexture2D
PrimitiveMesh
PrismMesh
ProceduralSkyMaterial
ProgressBar
ProjectSettings
PropertyTweener
QuadMesh
QuadOccluder3D
RDAttachmentFormat
RDFramebufferPass
RDPipelineColorBlendState
RDPipelineColorBlendStateAttachment
RDPipelineDepthStencilState
RDPipelineMultisampleState
RDPipelineRasterizationState
RDPipelineSpecializationConstant
RDSamplerState
RDShaderFile
RDShaderSPIRV
RDShaderSource
RDTextureFormat
RDTextureView
RDUniform
RDVertexAttribute
RandomNumberGenerator
Range
RayCast2D
RayCast3D
RectangleShape2D
RefCounted
ReferenceRect
ReflectionProbe
RegEx
RegExMatch
RemoteTransform2D
RemoteTransform3D
RenderData
RenderDataExtension
RenderDataRD
RenderSceneBuffers
RenderSceneBuffersConfiguration
RenderSceneBuffersExtension
RenderSceneBuffersRD
RenderSceneData
RenderSceneDataExtension
RenderSceneDataRD
RenderingDevice
RenderingServer
Resource
ResourceFormatImporterSaver
ResourceFormatLoader
ResourceFormatSaver
ResourceImporter
ResourceImporterBMFont
ResourceImporterBitMap
ResourceImporterCSVTranslation
ResourceImporterDynamicFont
ResourceImporterImage
ResourceImporterImageFont
ResourceImporterLayeredTexture
ResourceImporterMP3
ResourceImporterOBJ
ResourceImporterOggVorbis
ResourceImporterScene
ResourceImporterShaderFile
ResourceImporterTexture
ResourceImporterTextureAtlas
ResourceImporterWAV
ResourceLoader
ResourcePreloader
ResourceSaver
ResourceUID
RibbonTrailMesh
RichTextEffect
RichTextLabel
RigidBody2D
RigidBody3D
RootMotionView
SceneCacheInterface
SceneMultiplayer
SceneRPCInterface
SceneReplicationConfig
SceneReplicationInterface
SceneState
SceneTree
SceneTreeTimer
Script
ScriptCreateDialog
ScriptEditor
ScriptEditorBase
ScriptExtension
ScriptLanguage
ScriptLanguageExtension
ScrollBar
ScrollContainer
SegmentShape2D
Semaphore
SeparationRayShape2D
SeparationRayShape3D
Separator
Shader
ShaderGlobalsOverride
ShaderInclude
ShaderMaterial
Shape2D
Shape3D
ShapeCast2D
ShapeCast3D
Shortcut
Skeleton2D
Skeleton3D
SkeletonIK3D
SkeletonModification2D
SkeletonModification2DCCDIK
SkeletonModification2DFABRIK
SkeletonModification2DJiggle
SkeletonModification2DLookAt
SkeletonModification2DPhysicalBones
SkeletonModification2DStackHolder
SkeletonModification2DTwoBoneIK
SkeletonModificationStack2D
SkeletonModifier3D
SkeletonProfile
SkeletonProfileHumanoid
Skin
SkinReference
Sky
Slider
SliderJoint3D
SoftBody3D
SphereMesh
SphereOccluder3D
SphereShape3D
SpinBox
SplitContainer
SpotLight3D
SpringArm3D
Sprite2D
Sprite3D
SpriteBase3D
SpriteFrames
StandardMaterial3D
StaticBody2D
StaticBody3D
StatusIndicator
StreamPeer
StreamPeerBuffer
StreamPeerExtension
StreamPeerGZIP
StreamPeerTCP
StreamPeerTLS
StyleBox
StyleBoxEmpty
StyleBoxFlat
StyleBoxLine
StyleBoxTexture
SubViewport
SubViewportContainer
SurfaceTool
SyntaxHighlighter
SystemFont
TCPServer
TLSOptions
TabBar
TabContainer
TextEdit
TextLine
TextMesh
TextParagraph
TextServer
TextServerAdvanced
TextServerDummy
TextServerExtension
TextServerManager
Texture
Texture2D
Texture2DArray
Texture2DArrayRD
Texture2DRD
Texture3D
Texture3DRD
TextureButton
TextureCubemapArrayRD
TextureCubemapRD
TextureLayered
TextureLayeredRD
TextureProgressBar
TextureRect
Theme
ThemeContext
ThemeDB
Thread
TileData
TileMap
TileMapLayer
TileMapPattern
TileSet
TileSetAtlasSource
TileSetScenesCollectionSource
TileSetSource
Time
Timer
TorusMesh
TouchScreenButton
Translation
TranslationServer
Tree
TreeItem
TriangleMesh
TubeTrailMesh
Tween
Tweener
UDPServer
UPNP
UPNPDevice
UndoRedo
UniformSetCacheRD
VBoxContainer
VFlowContainer
VScrollBar
VSeparator
VSlider
VSplitContainer
VehicleBody3D
VehicleWheel3D
VideoStream
VideoStreamPlayback
VideoStreamPlayer
VideoStreamTheora
Viewport
ViewportTexture
VisibleOnScreenEnabler2D
VisibleOnScreenEnabler3D
VisibleOnScreenNotifier2D
VisibleOnScreenNotifier3D
VisualInstance3D
VisualShader
VisualShaderNode
VisualShaderNodeBillboard
VisualShaderNodeBooleanConstant
VisualShaderNodeBooleanParameter
VisualShaderNodeClamp
VisualShaderNodeColorConstant
VisualShaderNodeColorFunc
VisualShaderNodeColorOp
VisualShaderNodeColorParameter
VisualShaderNodeComment
VisualShaderNodeCompare
VisualShaderNodeConstant
VisualShaderNodeCubemap
VisualShaderNodeCubemapParameter
VisualShaderNodeCurveTexture
VisualShaderNodeCurveXYZTexture
VisualShaderNodeCustom
VisualShaderNodeDerivativeFunc
VisualShaderNodeDeterminant
VisualShaderNodeDistanceFade
VisualShaderNodeDotProduct
VisualShaderNodeExpression
VisualShaderNodeFaceForward
VisualShaderNodeFloatConstant
VisualShaderNodeFloatFunc
VisualShaderNodeFloatOp
VisualShaderNodeFloatParameter
VisualShaderNodeFrame
VisualShaderNodeFresnel
VisualShaderNodeGlobalExpression
VisualShaderNodeGroupBase
VisualShaderNodeIf
VisualShaderNodeInput
VisualShaderNodeIntConstant
VisualShaderNodeIntFunc
VisualShaderNodeIntOp
VisualShaderNodeIntParameter
VisualShaderNodeIs
VisualShaderNodeLinearSceneDepth
VisualShaderNodeMix
VisualShaderNodeMultiplyAdd
VisualShaderNodeOuterProduct
VisualShaderNodeOutput
VisualShaderNodeParameter
VisualShaderNodeParameterRef
VisualShaderNodeParticleAccelerator
VisualShaderNodeParticleBoxEmitter
VisualShaderNodeParticleConeVelocity
VisualShaderNodeParticleEmit
VisualShaderNodeParticleEmitter
VisualShaderNodeParticleMeshEmitter
VisualShaderNodeParticleMultiplyByAxisAngle
VisualShaderNodeParticleOutput
VisualShaderNodeParticleRandomness
VisualShaderNodeParticleRingEmitter
VisualShaderNodeParticleSphereEmitter
VisualShaderNodeProximityFade
VisualShaderNodeRandomRange
VisualShaderNodeRemap
VisualShaderNodeReroute
VisualShaderNodeResizableBase
VisualShaderNodeRotationByAxis
VisualShaderNodeSDFRaymarch
VisualShaderNodeSDFToScreenUV
VisualShaderNodeSample3D
VisualShaderNodeScreenNormalWorldSpace
VisualShaderNodeScreenUVToSDF
VisualShaderNodeSmoothStep
VisualShaderNodeStep
VisualShaderNodeSwitch
VisualShaderNodeTexture
VisualShaderNodeTexture2DArray
VisualShaderNodeTexture2DArrayParameter
VisualShaderNodeTexture2DParameter
VisualShaderNodeTexture3D
VisualShaderNodeTexture3DParameter
VisualShaderNodeTextureParameter
VisualShaderNodeTextureParameterTriplanar
VisualShaderNodeTextureSDF
VisualShaderNodeTextureSDFNormal
VisualShaderNodeTransformCompose
VisualShaderNodeTransformConstant
VisualShaderNodeTransformDecompose
VisualShaderNodeTransformFunc
VisualShaderNodeTransformOp
VisualShaderNodeTransformParameter
VisualShaderNodeTransformVecMult
VisualShaderNodeUIntConstant
VisualShaderNodeUIntFunc
VisualShaderNodeUIntOp
VisualShaderNodeUIntParameter
VisualShaderNodeUVFunc
VisualShaderNodeUVPolarCoord
VisualShaderNodeVarying
VisualShaderNodeVaryingGetter
VisualShaderNodeVaryingSetter
VisualShaderNodeVec2Constant
VisualShaderNodeVec2Parameter
VisualShaderNodeVec3Constant
VisualShaderNodeVec3Parameter
VisualShaderNodeVec4Constant
VisualShaderNodeVec4Parameter
VisualShaderNodeVectorBase
VisualShaderNodeVectorCompose
VisualShaderNodeVectorDecompose
VisualShaderNodeVectorDistance
VisualShaderNodeVectorFunc
VisualShaderNodeVectorLen
VisualShaderNodeVectorOp
VisualShaderNodeVectorRefract
VisualShaderNodeWorldPositionFromDepth
VoxelGI
VoxelGIData
WeakRef
WebRTCDataChannel
WebRTCDataChannelExtension
WebRTCMultiplayerPeer
WebRTCPeerConnection
WebRTCPeerConnectionExtension
WebSocketMultiplayerPeer
WebSocketPeer
WebXRInterface
Window
WorkerThreadPool
World2D
World3D
WorldBoundaryShape2D
WorldBoundaryShape3D
WorldEnvironment
X509Certificate
XMLParser
XRAnchor3D
XRBodyModifier3D
XRBodyTracker
XRCamera3D
XRController3D
XRControllerTracker
XRFaceModifier3D
XRFaceTracker
XRHandModifier3D
XRHandTracker
XRInterface
XRInterfaceExtension
XRNode3D
XROrigin3D
XRPose
XRPositionalTracker
XRServer
XRTracker
XRVRS
ZIPPacker
ZIPReader

View File

@ -0,0 +1,265 @@
extends Object
const Scraper = preload("res://addons/bbcode_edit.editor/editor_interface_scraper.gd")
const PATH_BUILTIN_COMPLETIONS = "res://addons/bbcode_edit.editor/completions_db/builtin_classes.txt"
# TODO add all tags and classify them between Documentation Only, Documentation Forbidden, Universal
const TAGS_UNIVERSAL: Array[String] = [
"b]|[/b",
"u]|[/u",
"i]|[/i",
"s]|[/s",
"code]|[/code",
"color=|][/color",
"lb||",
"rb||",
"font=|][/font",
"img]res://|[/img",
"img width=| height=]res://[/img",
"url]|[/url",
"url=https://|][/url",
"center]|[/center",
]
const TAGS_DOC_COMMENT_REFERENCE: Array[String] = [
"annotation |",
"constant |",
"enum |",
"member |",
"method |",
"constructor |",
"operator |",
"signal |",
"theme_item |",
]
const TAGS_DOC_COMMENT_FORMATTING: Array[String] = [
"codeblock]|[/codeblock",
"br||",
"kbd]|[/kbd",
]
# TODO add all tags
const TAGS_RICH_TEXT_LABEL: Array[String] = [
# TODO complete with all options
"font name=| size=][/font]",
'url={"|": }][/url',
]
const COLORS: Array[StringName] = [
"alice_blue",
"antique_white",
"aqua",
"aquamarine",
"azure",
"beige",
"bisque",
"black",
"blanched_almond",
"blue",
"blue_violet",
"brown",
"burlywood",
"cadet_blue",
"chartreuse",
"chocolate",
"coral",
"cornflower_blue",
"cornsilk",
"crimson",
"cyan",
"dark_blue",
"dark_cyan",
"dark_goldenrod",
"dark_gray",
"dark_green",
"dark_khaki",
"dark_magenta",
"dark_olive_green",
"dark_orange",
"dark_orchid",
"dark_red",
"dark_salmon",
"dark_sea_green",
"dark_slate_blue",
"dark_slate_gray",
"dark_turquoise",
"dark_violet",
"deep_pink",
"deep_sky_blue",
"dim_gray",
"dodger_blue",
"firebrick",
"floral_white",
"forest_green",
"fuchsia",
"gainsboro",
"ghost_white",
"gold",
"goldenrod",
"gray",
"green",
"green_yellow",
"honeydew",
"hot_pink",
"indian_red",
"indigo",
"ivory",
"khaki",
"lavender",
"lavender_blush",
"lawn_green",
"lemon_chiffon",
"light_blue",
"light_coral",
"light_cyan",
"light_goldenrod",
"light_gray",
"light_green",
"light_pink",
"light_salmon",
"light_sea_green",
"light_sky_blue",
"light_slate_gray",
"light_steel_blue",
"light_yellow",
"lime",
"lime_green",
"linen",
"magenta",
"maroon",
"medium_aquamarine",
"medium_blue",
"medium_orchid",
"medium_purple",
"medium_sea_green",
"medium_slate_blue",
"medium_spring_green",
"medium_turquoise",
"medium_violet_red",
"midnight_blue",
"mint_cream",
"misty_rose",
"moccasin",
"navajo_white",
"navy_blue",
"old_lace",
"olive",
"olive_drab",
"orange",
"orange_red",
"orchid",
"pale_goldenrod",
"pale_green",
"pale_turquoise",
"pale_violet_red",
"papaya_whip",
"peach_puff",
"peru",
"pink",
"plum",
"powder_blue",
"purple",
"rebecca_purple",
"red",
"rosy_brown",
"royal_blue",
"saddle_brown",
"salmon",
"sandy_brown",
"sea_green",
"seashell",
"sienna",
"silver",
"sky_blue",
"slate_blue",
"slate_gray",
"snow",
"spring_green",
"steel_blue",
"tan",
"teal",
"thistle",
"tomato",
"transparent",
"turquoise",
"violet",
"web_gray",
"web_green",
"web_maroon",
"web_purple",
"wheat",
"white",
"white_smoke",
"yellow",
"yellow_green",
]
static var _BUILTIN_CLASSES = PackedStringArray()
static func get_builtin_classes() -> PackedStringArray:
if not _BUILTIN_CLASSES:
var file: FileAccess = FileAccess.open(PATH_BUILTIN_COMPLETIONS, FileAccess.READ)
if FileAccess.get_open_error():
push_error(
"Failed to open "
+ PATH_BUILTIN_COMPLETIONS
+ ", error is:"
+ error_string(FileAccess.get_open_error())
)
_BUILTIN_CLASSES = file.get_as_text().split("\n")
return _BUILTIN_CLASSES
static var icon_cache: Dictionary = {}
static func get_class_completions() -> ClassCompletions:
var class_names: PackedStringArray = get_builtin_classes().duplicate()
var icons: Array[Texture2D] = []
for class_name_ in class_names:
icons.append(Scraper.get_builtin_class_icon(class_name_))
var classes: Array[Dictionary] = ProjectSettings.get_global_class_list()
var class_to_icon: Dictionary = {}
var class_to_base: Dictionary = {}
for class_ in classes:
var class_name_: String = class_["class"]
class_names.append(class_name_)
var icon_path: String = class_.get("icon", "")
if icon_path:
icons.append(load(icon_path))
elif class_name_ in icon_cache:
icons.append(icon_cache[class_name_])
else:
if class_to_base.is_empty():
for class__ in classes:
if class__["icon"]:
class_to_icon[class__["class"]] = class__["icon"]
class_to_base[class__["class"]] = class__["base"]
var icon_class: String = class_name_
while icon_class in class_to_base:
icon_class = class_to_base[icon_class]
if icon_class in class_to_icon:
icon_cache[class_name_] = load(class_to_icon[icon_class])
icons.append(icon_cache[class_name_])
break
if len(icons) != len(class_names):
icon_cache[class_name_] = Scraper.get_builtin_class_icon(icon_class)
icons.append(icon_cache[class_name_])
return ClassCompletions.new(
class_names,
icons,
)
class ClassCompletions:
var names: PackedStringArray
var icons: Array[Texture2D]
func _init(_names: PackedStringArray, _icons: Array[Texture2D]) -> void:
names = _names
icons = _icons

View File

@ -0,0 +1 @@
uid://cwk6mlavaqf8c

View File

@ -0,0 +1,23 @@
extends Node
## This has to be a scene.
## (In an EditorScript, editor specifc classes would polute the result)
const Completions = preload("res://addons/bbcode_edit.editor/completions_db/completions.gd")
func _ready() -> void:
print("Fetching classes...")
var file: FileAccess = FileAccess.open(Completions.PATH_BUILTIN_COMPLETIONS, FileAccess.WRITE)
if FileAccess.get_open_error():
push_error(
"Failed to open "
+ Completions.PATH_BUILTIN_COMPLETIONS
+ ", error is:"
+ error_string(FileAccess.get_open_error())
)
return
file.store_string("\n".join(ClassDB.get_class_list()))
print_rich("[color=web_green]Classes successfuly pasted to clipboard")
get_tree().quit()

View File

@ -0,0 +1 @@
uid://bd4tjvv6hc3wg

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://blcco0wsttyck"]
[ext_resource type="Script" path="res://addons/bbcode_edit.editor/completions_db/fetch_builtin_classes.gd" id="1_tk0nj"]
[node name="FetchClassDB" type="Node"]
script = ExtResource("1_tk0nj")

View File

@ -0,0 +1,126 @@
extends Object
## This singleton has utility methods to scrap the Editor's interface
static func get_icon(icon: StringName) -> Texture2D:
return EditorInterface.get_base_control().get_theme_icon(icon, &"EditorIcons")
static func get_color_icon() -> Texture2D:
return get_icon(&"Color")
static func get_reference_icon() -> Texture2D:
return get_icon(&"Help")
static func try_get_icon(icon: StringName, fallback: StringName) -> Texture2D:
var result: Texture2D = get_icon(icon)
if result == get_icon(&"za86e81czxe1s89az6ee7s1"): # Random
return get_icon(fallback)
return result
static func get_builtin_class_icon(class_name_: StringName) -> Texture2D:
var result: Texture2D = get_icon(class_name_)
var file_broken: Texture2D = get_icon(&"za86e81czxe1s89az6ee7s1") # Random
while result == file_broken and class_name_ != &"":
class_name_ = ClassDB.get_parent_class(class_name_)
result = get_icon(class_name_)
return result
static func get_class_icon(name: StringName, fallback: StringName) -> Texture2D:
if ClassDB.class_exists(name):
return get_builtin_class_icon(name)
var base_name = name
var global_class_list := ProjectSettings.get_global_class_list()
var found: bool = true
while found:
found = false
for class_ in global_class_list:
if class_["class"] == name:
if class_["icon"]:
return load(class_["icon"])
else:
name = class_["base"]
if ClassDB.class_exists(name):
return get_builtin_class_icon(name)
found = true
break
# This can happen for type union (ex: CanvasItemMaterial,ShaderMaterial)
return get_icon(fallback)
## [b]Note:[/b] Return the Variant icon for [constant @GlobalScope.TYPE_NIL].
static func get_builtin_type_icon(type: Variant.Type, fallback: StringName) -> Texture2D:
if type == TYPE_NIL:
return get_icon(&"Variant")
if 0 <= type and type < TYPE_MAX:
return get_icon(type_string(type))
return get_icon(fallback)
static func get_type_icon(value: Variant, fallback: StringName) -> Texture2D:
var type: int = typeof(value)
if type == TYPE_OBJECT:
if value is Script:
var to_check: Script = value
while true:
if to_check.get_global_name():
return get_class_icon(to_check.get_global_name(), fallback)
# TODO MAYBE Read first line for @icon
if to_check.get_base_script():
to_check = to_check.get_base_script()
else:
return get_builtin_class_icon(to_check.get_instance_base_type())
var script: Script = value.get_script()
if script:
var search_for: Script = script
while true:
for class_ in ProjectSettings.get_global_class_list():
if class_["path"] == search_for.resource_path:
return get_class_icon(class_["class"], fallback)
if search_for.get_base_script():
search_for = search_for.get_base_script()
else:
return get_builtin_class_icon(search_for.get_instance_base_type())
return get_builtin_class_icon(value.get_class())
return get_builtin_type_icon(type, fallback)
## Scrap the Editor tree to find if it's unsaved.
static func is_current_script_unsaved() -> bool:
# Reference path: $"../../../../../../@VSplitContainer@9820/@VBoxContainer@9821/@ItemList@9824"
var current_editor := EditorInterface.get_script_editor().get_current_editor().get_base_editor()
if current_editor is not CodeEdit:
return false
var pointer: Node = current_editor.get_node(^"../../../../../..")
if pointer == null:
return false
for node_type: String in ["VSplitContainer", "VBoxContainer", "ItemList"]:
pointer = _fetch_node(pointer, node_type)
if pointer == null:
return false
var item_list: ItemList = pointer
return item_list.get_item_text(item_list.get_selected_items()[0]).ends_with("(*)")
static func _fetch_node(parent: Node, type: String) -> Node:
type = "@" + type
for child in parent.get_children():
if child.name.begins_with(type):
return child
return null

View File

@ -0,0 +1 @@
uid://dfbwqmen4hcc4

View File

@ -0,0 +1,7 @@
[plugin]
name="BBCodeEdit (for script editor)"
description="A Godot addon that brings BBCode completion and QOL tools to the script editor in order to help formatting documentation comments."
author="Patou"
version="1.0.1"
script="bbcode_edit_main.gd"

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 DmitriySalnikov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the Software), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, andor sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,162 @@
![icon](/images/icon_3d_128.png)
# Debug drawing utility for Godot
This is an add-on for debug drawing in 3D and for some 2D overlays, which is written in `C++` and can be used with `GDScript` or `C#`.
Based on my previous addon, which was developed [only for C#](https://github.com/DmitriySalnikov/godot_debug_draw_cs), and which was inspired by [Zylann's GDScript addon](https://github.com/Zylann/godot_debug_draw)
## [Documentation](https://dd3d.dmitriysalnikov.ru/docs/)
## [Godot 3 version](https://github.com/DmitriySalnikov/godot_debug_draw_3d/tree/godot_3)
## Support me
Your support adds motivation to develop my public projects.
<a href="https://boosty.to/dmitriysalnikov/donate"><img src="/docs/images/boosty.png" alt="Boosty" width=150px/></a>
<img src="/docs/images/USDT-TRC20.png" alt="USDT-TRC20" width=150px/>
<b>USDT-TRC20 TEw934PrsffHsAn5M63SoHYRuZo984EF6v</b>
## Features
3D:
* Arrow
* Billboard opaque square
* Box
* Camera Frustum
* Cylinder
* Gizmo
* Grid
* Line
* Line Path
* Line with Arrow
* Plane
* Points
* Position 3D (3 crossing axes)
* Sphere
2D:
* **[Work in progress]**
Overlay:
* Text (with grouping and coloring)
* FPS Graph
* Custom Graphs
Precompiled for:
* Windows
* Linux (built on Ubuntu 20.04)
* macOS (10.14+)
* Android (5.0+)
* iOS
* Web (Firefox not supported)
This addon supports working with several World3D and different Viewports.
There is also a no depth test mode and other settings that can be changed for each instance.
This library supports double-precision builds, for more information, [see the documentation](https://dd3d.dmitriysalnikov.ru/docs/1.4.5/md_docs_2DoublePrecision.html).
## [Interactive Web Demo](https://dd3d.dmitriysalnikov.ru/demo/)
[![screenshot_web](/images/screenshot_web.png)](https://dd3d.dmitriysalnikov.ru/demo/)
> [!WARNING]
>
> * Firefox most likely can't run this demo
## Download
To download, use the [Godot Asset Library](https://godotengine.org/asset-library/asset/1766) or use one of the stable versions from the [GitHub Releases](https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases) page.
For versions prior to `1.4.5`, just download one of the `source codes` in the assets. For newer versions, download `debug-draw-3d_[version].zip`.
### Installation
* Close editor
* Copy `addons/debug_draw_3d` to your `addons` folder, create it if the folder doesn't exist
* Launch editor
## Examples
More examples can be found in the `examples_dd3d/` folder.
Simple test:
```gdscript
func _process(delta: float) -> void:
var _time = Time.get_ticks_msec() / 1000.0
var box_pos = Vector3(0, sin(_time * 4), 0)
var line_begin = Vector3(-1, sin(_time * 4), 0)
var line_end = Vector3(1, cos(_time * 4), 0)
DebugDraw3D.draw_box(box_pos, Vector3(1, 2, 1), Color(0, 1, 0))
DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0))
DebugDraw2D.set_text("Time", _time)
DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn())
DebugDraw2D.set_text("FPS", Engine.get_frames_per_second())
DebugDraw2D.set_text("delta", delta)
```
![screenshot_1](/images/screenshot_1.png)
An example of using scoped configs:
```gdscript
@tool
extends Node3D
func _ready():
# Set the base scoped_config.
# Each frame will be reset to these scoped values.
DebugDraw3D.scoped_config().set_thickness(0.1).set_center_brightness(0.6)
func _process(delta):
# Draw using the base scoped config.
DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE * 2, Color.CORNFLOWER_BLUE)
if true:
# Create a scoped config that will exist until exiting this if.
var _s = DebugDraw3D.new_scoped_config().set_thickness(0).set_center_brightness(0.1)
# Draw with a thickness of 0
DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE, Color.RED)
# If necessary, the values inside this scope can be changed
# even before each call to draw_*.
_s.set_thickness(0.05)
DebugDraw3D.draw_box(Vector3(1,0,1), Quaternion.IDENTITY, Vector3.ONE * 1, Color.BLUE_VIOLET)
```
![screenshot_5](/images/screenshot_5.png)
> [!TIP]
>
> If you want to use a non-standard Viewport for rendering a 3d scene, then do not forget to specify it in the scoped config!
## API
This project has a separate [documentation](https://dd3d.dmitriysalnikov.ru/docs/) page.
Also, a list of all functions is available in the documentation inside the editor (see `DebugDraw3D` and `DebugDraw2D`).
![screenshot_4](/images/screenshot_4.png)
## Known issues and limitations
The text in the keys and values of a text group cannot contain multi-line strings.
The entire text overlay can only be placed in one corner, unlike `DataGraphs`.
[Frustum of Camera3D does not take into account the window size from ProjectSettings](https://github.com/godotengine/godot/issues/70362).
## More screenshots
`DebugDrawDemoScene.tscn` in editor
![screenshot_2](/images/screenshot_2.png)
`DebugDrawDemoScene.tscn` in play mode
![screenshot_3](/images/screenshot_3.png)

View File

@ -0,0 +1,153 @@
[configuration]
entry_symbol = "debug_draw_3d_library_init"
compatibility_minimum = "4.1.4"
reloadable = false
[dependencies]
; example.x86_64 = { "relative or absolute path to the dependency" : "the path relative to the exported project", }
; -------------------------------------
; debug
macos = { }
windows.x86_64 = { }
linux.x86_64 = { }
; by default godot is using threads
web.wasm32.nothreads = {}
web.wasm32 = {}
android.arm32 = { }
android.arm64 = { }
android.x86_32 = { }
android.x86_64 = { }
ios = {}
; -------------------------------------
; release no debug draw
macos.template_release = { }
windows.template_release.x86_64 = { }
linux.template_release.x86_64 = { }
web.template_release.wasm32.nothreads = { }
web.template_release.wasm32 = { }
android.template_release.arm32 = { }
android.template_release.arm64 = { }
android.template_release.x86_32 = { }
android.template_release.x86_64 = { }
ios.template_release = {}
; -------------------------------------
; release forced debug draw
macos.template_release.forced_dd3d = { }
windows.template_release.x86_64.forced_dd3d = { }
linux.template_release.x86_64.forced_dd3d = { }
web.template_release.wasm32.nothreads.forced_dd3d = { }
web.template_release.wasm32.forced_dd3d = { }
ios.template_release.forced_dd3d = {}
[libraries]
; -------------------------------------
; debug
macos = "libs/libdd3d.macos.editor.universal.framework"
windows.x86_64 = "libs/libdd3d.windows.editor.x86_64.dll"
linux.x86_64 = "libs/libdd3d.linux.editor.x86_64.so"
web.wasm32.nothreads = "libs/libdd3d.web.template_debug.wasm32.wasm"
web.wasm32 = "libs/libdd3d.web.template_debug.wasm32.threads.wasm"
android.arm32 = "libs/libdd3d.android.template_debug.arm32.so"
android.arm64 = "libs/libdd3d.android.template_debug.arm64.so"
android.x86_32 = "libs/libdd3d.android.template_debug.x86_32.so"
android.x86_64 = "libs/libdd3d.android.template_debug.x86_64.so"
ios = "libs/libdd3d.ios.template_debug.universal.dylib"
; -------------------------------------
; release no debug draw
macos.template_release = "libs/libdd3d.macos.template_release.universal.framework"
windows.template_release.x86_64 = "libs/libdd3d.windows.template_release.x86_64.dll"
linux.template_release.x86_64 = "libs/libdd3d.linux.template_release.x86_64.so"
web.template_release.wasm32.nothreads = "libs/libdd3d.web.template_release.wasm32.wasm"
web.template_release.wasm32 = "libs/libdd3d.web.template_release.wasm32.threads.wasm"
android.template_release.arm32 = "libs/libdd3d.android.template_release.arm32.so"
android.template_release.arm64 = "libs/libdd3d.android.template_release.arm64.so"
android.template_release.x86_32 = "libs/libdd3d.android.template_release.x86_32.so"
android.template_release.x86_64 = "libs/libdd3d.android.template_release.x86_64.so"
ios.template_release = "libs/libdd3d.ios.template_release.universal.dylib"
; -------------------------------------
; release forced debug draw
macos.template_release.forced_dd3d = "libs/libdd3d.macos.template_release.universal.enabled.framework"
windows.template_release.x86_64.forced_dd3d = "libs/libdd3d.windows.template_release.x86_64.enabled.dll"
linux.template_release.x86_64.forced_dd3d = "libs/libdd3d.linux.template_release.x86_64.enabled.so"
web.template_release.wasm32.nothreads.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.enabled.wasm"
web.template_release.wasm32.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm"
ios.template_release.forced_dd3d = "libs/libdd3d.ios.template_release.universal.enabled.dylib"
; -------------------------------------
; DOUBLE PRECISION
; -------------------------------------
; -------------------------------------
; debug
macos.double = "libs/libdd3d.macos.editor.universal.double.framework"
windows.x86_64.double = "libs/libdd3d.windows.editor.x86_64.double.dll"
linux.x86_64.double = "libs/libdd3d.linux.editor.x86_64.double.so"
web.wasm32.nothreads.double = "libs/libdd3d.web.template_debug.wasm32.double.wasm"
web.wasm32.double = "libs/libdd3d.web.template_debug.wasm32.threads.double.wasm"
android.arm32.double = "libs/libdd3d.android.template_debug.arm32.double.so"
android.arm64.double = "libs/libdd3d.android.template_debug.arm64.double.so"
android.x86_32.double = "libs/libdd3d.android.template_debug.x86_32.double.so"
android.x86_64.double = "libs/libdd3d.android.template_debug.x86_64.double.so"
ios.double = "libs/libdd3d.ios.template_debug.universal.dylib"
; -------------------------------------
; release no debug draw
macos.template_release.double = "libs/libdd3d.macos.template_release.universal.double.framework"
windows.template_release.x86_64.double = "libs/libdd3d.windows.template_release.x86_64.double.dll"
linux.template_release.x86_64.double = "libs/libdd3d.linux.template_release.x86_64.double.so"
web.template_release.wasm32.nothreads.double = "libs/libdd3d.web.template_release.wasm32.double.wasm"
web.template_release.wasm32.double = "libs/libdd3d.web.template_release.wasm32.threads.double.wasm"
android.template_release.arm32.double = "libs/libdd3d.android.template_release.arm32.double.so"
android.template_release.arm64.double = "libs/libdd3d.android.template_release.arm64.double.so"
android.template_release.x86_32.double = "libs/libdd3d.android.template_release.x86_32.double.so"
android.template_release.x86_64.double = "libs/libdd3d.android.template_release.x86_64.double.so"
ios.template_release.double = "libs/libdd3d.ios.template_release.universal.double.dylib"
; -------------------------------------
; release forced debug draw
macos.template_release.forced_dd3d.double = "libs/libdd3d.macos.template_release.universal.enabled.double.framework"
windows.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.windows.template_release.x86_64.enabled.double.dll"
linux.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.linux.template_release.x86_64.enabled.double.so"
web.template_release.wasm32.nothreads.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.enabled.double.wasm"
web.template_release.wasm32.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.threads.enabled.double.wasm"
ios.template_release.forced_dd3d.double = "libs/libdd3d.ios.template_release.universal.enabled.double.dylib"

View File

@ -0,0 +1 @@
uid://c0q15xk1bg0rd

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>libdd3d.macos.editor.universal.dylib</string>
<key>CFBundleName</key>
<string>Debug Draw 3D</string>
<key>CFBundleDisplayName</key>
<string>Debug Draw 3D</string>
<key>CFBundleIdentifier</key>
<string>ru.dmitriysalnikov.dd3d</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) Dmitriy Salnikov.</string>
<key>CFBundleVersion</key>
<string>1.4.5</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>DTPlatformName</key>
<string>macosx</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
</dict>
</plist>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>libdd3d.macos.template_release.universal.enabled.dylib</string>
<key>CFBundleName</key>
<string>Debug Draw 3D</string>
<key>CFBundleDisplayName</key>
<string>Debug Draw 3D</string>
<key>CFBundleIdentifier</key>
<string>ru.dmitriysalnikov.dd3d</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) Dmitriy Salnikov.</string>
<key>CFBundleVersion</key>
<string>1.4.5</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>DTPlatformName</key>
<string>macosx</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
</dict>
</plist>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>libdd3d.macos.template_release.universal.dylib</string>
<key>CFBundleName</key>
<string>Debug Draw 3D</string>
<key>CFBundleDisplayName</key>
<string>Debug Draw 3D</string>
<key>CFBundleIdentifier</key>
<string>ru.dmitriysalnikov.dd3d</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) Dmitriy Salnikov.</string>
<key>CFBundleVersion</key>
<string>1.4.5</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>DTPlatformName</key>
<string>macosx</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
</dict>
</plist>

View File

@ -0,0 +1,88 @@
extends CanvasLayer
enum ErrTypes { NORMAL, NORMAL_RICH, WARNING, ERROR }
const DEFAULT_DURATION: float = 5.0
@export_range(0, 1000, 1) var log_limit: int = 120
var msg_list: PackedStringArray = []
var msg_uid: PackedInt32Array = []
@onready var log_label: RichTextLabel = $LogLabel
func _ready() -> void:
if OS.has_feature("standalone"):
hide()
func print_msg(text: String, duration: float = DEFAULT_DURATION, type: ErrTypes = ErrTypes.NORMAL, color := Color.AQUA) -> void:
if not OS.is_debug_build():
return
if msg_list.size() + 1 > log_limit:
if msg_list.is_empty(): # Don't do anything if the [param log_limit] is set to 0.
return
else: # Remove the first messages until we have a free slot.
msg_list.remove_at(0)
msg_uid.remove_at(0)
print_msg(text, duration, type, color)
return
var prefix: String = ""
match type:
ErrTypes.NORMAL_RICH:
print_rich(text)
ErrTypes.WARNING:
push_warning(text)
prefix = str("[color=orange][b]WARNING: [/b][/color]")
ErrTypes.ERROR:
push_error(text)
printerr(text)
prefix = str("[color=red][b]ERROR: [/b][/color]")
# print messages into the log first before stoppingQ.
if OS.has_feature("standalone"):
return
text = str("%s[color=%s]%s[/color]" %[prefix, color.to_html(), text])
msg_list.append(text)
var timer := Timer.new()
timer.one_shot = true
msg_uid.append(absi(timer.get_instance_id()))
timer.timeout.connect(func() -> void:
var uid: int = msg_uid.find(absi(timer.get_instance_id()))
if uid >= 0:
msg_list.remove_at(uid)
msg_uid.remove_at(uid)
_update_hud()
timer.queue_free()
)
add_child(timer)
timer.start(duration if duration >= 0.0 else DEFAULT_DURATION)
_update_hud()
func clear_log() -> void:
msg_list.clear()
msg_uid.clear()
_update_hud()
# Remove the timers.
for i: Node in get_children():
if not i == log_label:
i.queue_free()
func _update_hud() -> void:
if not self.is_node_ready():
await self.ready
var text: String = "\n".join(msg_list) if not msg_list.is_empty() else ""
log_label.set_text(text)
if msg_list.is_empty():
log_label.clear()

View File

@ -0,0 +1 @@
uid://b5b7ohheyhxr0

View File

@ -0,0 +1,31 @@
[gd_scene load_steps=2 format=3 uid="uid://cpfunq8fyixco"]
[ext_resource type="Script" uid="uid://b5b7ohheyhxr0" path="res://addons/logger/logger.gd" id="1_thwo5"]
[node name="Logger" type="CanvasLayer"]
process_mode = 3
layer = 128
follow_viewport_enabled = true
script = ExtResource("1_thwo5")
log_limit = 30
[node name="LogLabel" type="RichTextLabel" parent="."]
modulate = Color(1, 1, 1, 0.498039)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 2
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
theme_override_font_sizes/bold_italics_font_size = 12
theme_override_font_sizes/italics_font_size = 12
theme_override_font_sizes/mono_font_size = 12
theme_override_font_sizes/normal_font_size = 12
theme_override_font_sizes/bold_font_size = 12
bbcode_enabled = true
fit_content = true
scroll_active = false
drag_and_drop_selection_enabled = false

View File

@ -0,0 +1,7 @@
[plugin]
name="On-Screen Logger - Pixelation Games"
description="A logger that automatically displays the information on the game screen for a set or given amount of time"
author="SchimmelSpreu83"
version="0.5.0"
script="plugin.gd"

View File

@ -0,0 +1,10 @@
@tool
extends EditorPlugin
func _enable_plugin() -> void:
add_autoload_singleton("Logger", "res://addons/logger/logger.tscn")
func _disable_plugin() -> void:
remove_autoload_singleton("Logger")

View File

@ -0,0 +1 @@
uid://p6f8m3u0e8du

View File

@ -0,0 +1 @@
3f4fa8737ead98dec6e3c296fe1fac9bc9c397c1

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2023 Feo (k2kra) Wu
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1 @@
This repository is a mirror that tracks the latest version of [PankuConsole](https://github.com/Ark2000/PankuConsole), so you can add it as a submodule in you addons folder.

View File

@ -0,0 +1,35 @@
extends VBoxContainer
# it is like, an infinite scroll game.
# specifically, the first buffer will be cleared and sent to the last
# when the last buffer is full.
# with buffers, we can constanly output lots of fancy stuff while keeping a smooth experience.
const BUFFER_MAX_PARAGRAPHS = 64
const BUFFERS = 4
var cur_label_idx:int = 0
func add_text(text:String):
var cur_label:RichTextLabel = get_child(cur_label_idx)
cur_label.text += text
if cur_label.get_paragraph_count() > BUFFER_MAX_PARAGRAPHS:
cur_label_idx += 1
if cur_label_idx == BUFFERS:
cur_label_idx = BUFFERS - 1
var first_label:RichTextLabel = get_child(0)
first_label.text = ""
move_child(first_label, BUFFERS - 1)
func _ready():
set("theme_override_constants/separation", 0)
for child in get_children():
child.queue_free()
for i in range(BUFFERS):
var new_buffer:RichTextLabel = RichTextLabel.new()
new_buffer.fit_content = true
new_buffer.bbcode_enabled = true
new_buffer.selection_enabled = true
add_child(new_buffer)

View File

@ -0,0 +1 @@
uid://dqbh0mtduoa3k

View File

@ -0,0 +1,37 @@
class_name PankuConfig
const USER_CONFIG_FILE_PATH = "user://panku_config.cfg"
# load config from file, always return a dictionary
static func _get_config(file_path:String) -> Dictionary:
if FileAccess.file_exists(file_path):
var file = FileAccess.open(file_path, FileAccess.READ)
var content := file.get_as_text()
var config:Dictionary = str_to_var(content)
if config: return config
return {}
# save user config to file
static func set_config(config:Dictionary):
var file = FileAccess.open(USER_CONFIG_FILE_PATH, FileAccess.WRITE)
var content = var_to_str(config)
file.store_string(content)
# get config, if user config exists, return user config, otherwise return default config configured by plugin user
static func get_config() -> Dictionary:
var user_config:Dictionary = _get_config(USER_CONFIG_FILE_PATH)
if not user_config.is_empty():
return user_config
# if no user config, return default config, which is read-only
if PankuConsolePlugin.is_custom_default_config_exists():
return _get_config(PankuConsolePlugin.get_custom_default_config_path())
return _get_config(PankuConsolePlugin.INITIAL_DEFAULT_CONFIG_FILE_PATH)
static func get_value(key:String, default:Variant) -> Variant:
return get_config().get(key, default)
static func set_value(key:String, val:Variant) -> void:
var config = _get_config(USER_CONFIG_FILE_PATH)
config[key] = val
set_config(config)

View File

@ -0,0 +1 @@
uid://bgc3h0yuqrdnt

View File

@ -0,0 +1,276 @@
class_name PankuGDExprEnv
const type_names = {
TYPE_NIL: "null",
TYPE_BOOL: "bool",
TYPE_INT: "int",
TYPE_FLOAT: "float",
TYPE_STRING: "String",
TYPE_VECTOR2: "Vector2",
TYPE_VECTOR2I: "Vector2i",
TYPE_RECT2: "Rect2",
TYPE_RECT2I: "Rect2i",
TYPE_VECTOR3: "Vector3",
TYPE_VECTOR3I: "Vector3i",
TYPE_TRANSFORM2D: "Transform2D",
TYPE_VECTOR4: "Vector4",
TYPE_VECTOR4I: "Vector4i",
TYPE_PLANE: "Plane",
TYPE_QUATERNION: "Quaternion",
TYPE_AABB: "AABB",
TYPE_BASIS: "Basis",
TYPE_TRANSFORM3D: "Transform3D",
TYPE_PROJECTION: "Projection",
TYPE_COLOR: "Color",
TYPE_STRING_NAME: "StringName",
TYPE_NODE_PATH: "NodePath",
TYPE_RID: "RID",
TYPE_OBJECT: "Object",
TYPE_CALLABLE: "Callable",
TYPE_SIGNAL: "Signal",
TYPE_DICTIONARY: "Dictionary",
TYPE_ARRAY: "Array",
TYPE_PACKED_BYTE_ARRAY: "PackedByteArray",
TYPE_PACKED_INT32_ARRAY: "PackedInt32Array",
TYPE_PACKED_INT64_ARRAY: "PackedInt64Array",
TYPE_PACKED_FLOAT32_ARRAY: "PackedFloat32Array",
TYPE_PACKED_FLOAT64_ARRAY: "PackedFloat64Array",
TYPE_PACKED_STRING_ARRAY: "PackedStringArray",
TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array",
TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array",
TYPE_PACKED_COLOR_ARRAY: "PackedColorArray",
}
var _envs = {}
var _envs_info = {}
var _expression = Expression.new()
var _base_instance:Object
func set_base_instance(base_instance:Object):
_base_instance = base_instance
#add info of base instance
var env_info = extract_info_from_script(_base_instance.get_script())
for k in env_info: _envs_info[k] = env_info[k]
func get_base_instance():
return _base_instance
## Register an environment that run expressions.
## [br][code]env_name[/code]: the name of the environment
## [br][code]env[/code]: The base instance that runs the expressions. For exmaple your player node.
func register_env(env_name:String, env:Object):
_envs[env_name] = env
# output("[color=green][Info][/color] [b]%s[/b] env loaded!"%env_name)
if env is Node:
env.tree_exiting.connect(
func(): remove_env(env_name)
)
if env.get_script():
var env_info = extract_info_from_script(env.get_script())
for k in env_info:
var keyword = "%s.%s" % [env_name, k]
_envs_info[keyword] = env_info[k]
## Return the environment object or [code]null[/code] by its name.
func get_env(env_name:String) -> Node:
return _envs.get(env_name)
## Remove the environment named [code]env_name[/code]
func remove_env(env_name:String):
if _envs.has(env_name):
_envs.erase(env_name)
for k in _envs_info.keys():
if k.begins_with(env_name + "."):
_envs_info.erase(k)
#Execute an expression in a preset environment.
func execute(exp:String) -> Dictionary:
return execute_exp(exp, _expression, _base_instance, _envs)
# TODO: not used
func get_available_export_objs() -> Array:
var result = []
for obj_name in _envs:
var obj = _envs[obj_name]
if !obj.get_script():
continue
var export_properties = get_export_properties_from_script(obj.get_script())
if export_properties.is_empty():
continue
result.push_back(obj_name)
return result
func get_help_info(k:String) -> String:
return _envs_info[k]["help"]
#TODO: refactor all those mess
func parse_exp(exp:String, allow_empty:=false):
var result:Array
var empty_flag = allow_empty and exp.is_empty()
if empty_flag:
result = _envs_info.keys()
else:
result = search_and_sort_and_highlight(exp, _envs_info.keys())
var hints_bbcode = []
var hints_value = []
for r in result:
var keyword:String
var bbcode_main:String
if empty_flag:
keyword = r
bbcode_main = r
else:
keyword = r["keyword"]
bbcode_main = r["bbcode"]
var bbcode_postfix = _envs_info[keyword]["bbcode_postfix"]
var keyword_type = _envs_info[keyword]["type"]
hints_value.push_back(keyword)
hints_bbcode.push_back(bbcode_main + bbcode_postfix)
return {
"hints_bbcode": hints_bbcode,
"hints_value": hints_value
}
static func search_and_sort_and_highlight(s:String, li:Array):
s = s.lstrip(" ").rstrip(" ")
var matched = []
if s == "": return matched
for k in li:
var start = k.find(s)
if start >= 0:
var similarity = 1.0 * s.length() / k.length()
matched.append({
"keyword": k,
"similarity": similarity,
"start": start,
"bbcode": ""
})
matched.sort_custom(
func(k1, k2):
if k1["start"] != k2["start"]:
return k1["start"] > k2["start"]
else:
return k1["similarity"] < k2["similarity"]
)
var line_format = "%s[color=green][b]%s[/b][/color]%s"
for m in matched:
var p = ["", "", ""]
if m["start"] < 0:
p[0] = m["keyword"]
else:
p[0] = m["keyword"].substr(0, m["start"])
p[1] = s
p[2] = m["keyword"].substr(m["start"] + s.length(), -1)
m["bbcode"] = line_format % p
return matched
static func extract_info_from_script(script:Script):
var result = {}
var methods = []
var properties = []
var constants = []
var constants_bbcode_postfix = {}
for m in script.get_script_method_list():
if m["name"] != "" and m["name"].is_valid_identifier() and !m["name"].begins_with("_"):
var args = []
for a in m["args"]:
args.push_back("[color=cyan]%s[/color][color=gray]:[/color][color=orange]%s[/color]"%[a["name"], type_names[a["type"]]])
result[m["name"]] = {
"type": "method",
"bbcode_postfix": "(%s)"%("[color=gray], [/color]".join(PackedStringArray(args)))
}
for p in script.get_script_property_list():
if p["name"] != "" and !p["name"].begins_with("_") and p["name"].is_valid_identifier():
result[p["name"]] = {
"type": "property",
"bbcode_postfix":"[color=gray]:[/color][color=orange]%s[/color]"%type_names[p["type"]]
}
var constant_map = script.get_script_constant_map()
var help_info = {}
for c in constant_map:
if !c.begins_with("_"):
result[c] = {
"type": "constant",
"bbcode_postfix":"[color=gray]:[/color][color=orange]%s[/color]"%type_names[typeof(constant_map[c])]
}
elif c.begins_with("_HELP_") and c.length() > 6 and typeof(constant_map[c]) == TYPE_STRING:
var key = c.lstrip("_HELP_")
help_info[key] = constant_map[c]
for k in result:
if help_info.has(k):
result[k]["help"] = help_info[k]
else:
result[k]["help"] = "No help information provided."
#keyword -> {type, bbcode_postfix, help}
return result
static func execute_exp(exp_str:String, expression:Expression, base_instance:Object, env:Dictionary):
var failed := false
var result = null
var error = expression.parse(exp_str, env.keys())
if error != OK:
failed = true
result = expression.get_error_text()
else:
result = expression.execute(env.values(), base_instance, true)
if expression.has_execute_failed():
failed = true
result = expression.get_error_text()
return {
"failed": failed,
"result": result
}
static func get_export_properties_from_script(script:Script):
var result = []
var data = script.get_script_property_list()
for d in data:
if !(d.usage == PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE):
continue
result.append(d)
return result
static func generate_help_text_from_script(script:Script):
var result = ["[color=cyan][b]User script defined identifiers[/b][/color]: "]
var env_info = extract_info_from_script(script)
var keys = env_info.keys()
keys.sort()
for k in keys:
result.push_back("%s - [i]%s[/i]"%[k + env_info[k]["bbcode_postfix"], env_info[k]["help"]])
return "\n".join(PackedStringArray(result))
#returns a string containing all public script properties of an object
#please BE AWARE when using this function on an object with custom getters.
static func get_object_outline(obj:Object) -> String:
var result := PackedStringArray()
if obj == null: return "null"
var script = obj.get_script()
if script == null:
return "this object has no script attached."
var properties = script.get_script_property_list()
for p in properties:
if p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE == 0:
continue
if p.name.begins_with("_"):
continue
result.append("%s: %s" % [p.name, str(obj.get(p.name))])
if result.is_empty():
return "this object has no public script variables."
return "\n".join(result)

View File

@ -0,0 +1 @@
uid://cv7potpg6dris

View File

@ -0,0 +1,9 @@
extends Panel
func hey_i_am_here():
modulate.a = 0.0
var t = create_tween()
t.set_speed_scale(1.0 / Engine.time_scale)
for i in range(2):
t.tween_property(self, "modulate:a", 0.3, 0.1)
t.tween_property(self, "modulate:a", 0.0, 0.1)

View File

@ -0,0 +1 @@
uid://djg7feld7wktp

View File

@ -0,0 +1,300 @@
class_name PankuLynxWindow extends ColorRect
#Do not connect the button node directly, use these signals to detect click event.
signal title_btn_clicked
signal window_closed
const lynx_window_shader_material:ShaderMaterial = preload("./lynx_window_shader_material.tres")
@export var _window_title_container:HBoxContainer
@export var _title_btn:PankuButton
@export var _close_btn:PankuButton
@export var _options_btn:PankuButton
@export var _resize_btn:Button
@export var _shadow_focus:Panel
@export var _shadow:NinePatchRect
@export var _container:Panel
@export var _pop_btn:PankuButton
@export var no_resize := false
@export var no_resize_x := false
@export var no_resize_y := false
@export var no_move := false
@export var no_snap := false
@export var no_title := false:
set(v):
no_title = v
_window_title_container.visible = !v
@export var queue_free_on_close := true
@export var flicker := true
var transform_interp_speed := 40.0
var bounds_interp_speed := 50.0
var anim_interp_speed := 10.0
var _is_dragging := false
var _drag_start_position:Vector2
var _drag_start_position_global:Vector2
var _is_resizing := false
var _resize_start_position:Vector2
var _os_window:Window
var _content:Control
var _size_before_folded:Vector2
var _folded:bool = false
var _size_animation:bool = false
var _target_size:Vector2
func add_options_button(callback:Callable):
_options_btn.show()
_options_btn.pressed.connect(callback)
func get_layout_position(layout:Control.LayoutPreset) -> Vector2:
var window_rect = get_rect()
var screen_rect = get_viewport_rect()
var new_position = Vector2.ZERO
var end_position = screen_rect.size - window_rect.size
var center_position = end_position / 2
if layout == PRESET_TOP_LEFT:
pass
elif layout == PRESET_CENTER_TOP:
new_position.x = center_position.x
elif layout == PRESET_TOP_RIGHT:
new_position.x = end_position.x
elif layout == PRESET_CENTER_LEFT:
new_position.y = center_position.y
elif layout == PRESET_CENTER:
new_position = center_position
elif layout == PRESET_CENTER_RIGHT:
new_position.x = end_position.x
new_position.y = center_position.y
elif layout == PRESET_BOTTOM_LEFT:
new_position.y = end_position.y
elif layout == PRESET_CENTER_BOTTOM:
new_position.x = center_position.x
new_position.y = end_position.y
elif layout == PRESET_BOTTOM_RIGHT:
new_position = end_position
return new_position
func get_content():
return _content
func set_content(node:Control):
_content = node
if _os_window and _os_window.visible:
if _os_window.get_child_count() > 0:
push_error("Error: error in set_content")
return
_os_window.add_child(node)
return
if _container.get_child_count() > 0:
push_error("Error: error in set_content.")
return
_container.add_child(node)
func highlight(v:bool):
_shadow_focus.visible = v
func _init_os_window():
_os_window = Window.new()
var color_rect = ColorRect.new()
color_rect.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_os_window.add_child(color_rect)
get_tree().root.add_child(_os_window)
#destructor
tree_exiting.connect(
func():
_os_window.queue_free()
)
#switch back to embed window when os window close requested
_os_window.close_requested.connect(
func():
_os_window.remove_child(_content)
_os_window.hide()
set_content(_content)
show()
)
if get_parent().has_method("get_os_window_bg_color"):
color_rect.color = get_parent().get_os_window_bg_color()
func switch_to_os_window():
if _content == null:
push_error("Error: No content. ")
return
if _os_window == null:
_init_os_window()
_container.remove_child(_content)
_os_window.add_child(_content)
_os_window.size = size
_os_window.title = _title_btn.text
_os_window.position = Vector2(DisplayServer.window_get_position(0)) + position
_content.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_os_window.show()
hide()
func show_window():
if _os_window and _os_window.visible:
return
show()
move_to_front()
modulate.a = 0.0
create_tween().tween_property(self, "modulate:a", 1.0, 0.2)
func hide_window():
if _os_window and _os_window.visible:
_os_window.close_requested.emit()
hide()
func toggle_window_visibility():
if _os_window.visible or visible:
hide_window()
else:
show_window()
func set_window_visibility(b:bool):
if b: show_window()
else: hide_window()
func get_window_visibility() -> bool:
return visible or _os_window.visible
func set_window_title_text(text:String):
if _os_window and _os_window.visible:
_os_window.title = text
else:
_title_btn.text = " " + text
func get_normal_window_size():
if _folded: return _size_before_folded
return size
func get_title_bar_height():
return _window_title_container.size.y
func _ready():
custom_minimum_size = _window_title_container.get_minimum_size()
_options_btn.visible = false
_title_btn.button_down.connect(
func():
_is_dragging = true
_drag_start_position = get_local_mouse_position()
_drag_start_position_global = get_global_mouse_position()
)
_title_btn.button_up.connect(
func():
_is_dragging = false
)
_resize_btn.button_down.connect(
func():
_is_resizing = true
_resize_start_position = _resize_btn.get_local_mouse_position()
)
_resize_btn.button_up.connect(
func():
_is_resizing = false
)
_close_btn.pressed.connect(
func():
window_closed.emit()
if queue_free_on_close:
queue_free()
else:
hide()
)
_title_btn.button.gui_input.connect(
func(e):
if e is InputEventMouseButton and !e.pressed:
if e.button_index != MOUSE_BUTTON_NONE:
if (get_global_mouse_position() - _drag_start_position_global).length_squared() < 4:
title_btn_clicked.emit()
)
visibility_changed.connect(
func():
if is_visible_in_tree() and flicker:
$Border.hey_i_am_here()
)
if flicker:
$Border.hey_i_am_here()
_pop_btn.pressed.connect(switch_to_os_window)
if _container.get_child_count() > 0:
_content = _container.get_child(0)
if get_parent().has_method("get_enable_os_popup_btns"):
_pop_btn.visible = get_parent().get_enable_os_popup_btns()
# feature: foldable window
title_btn_clicked.connect(
func():
if _folded:
_target_size = _size_before_folded
else:
if !_size_animation:
_size_before_folded = size
_target_size = _window_title_container.size
_size_animation = true
_folded = !_folded
_resize_btn.visible = !_folded
)
func _input(e):
#release focus when you click outside of the window
if is_visible:
if e is InputEventMouseButton and e.pressed:
if !get_global_rect().has_point(get_global_mouse_position()):
var f = get_viewport().gui_get_focus_owner()
if f and is_ancestor_of(f):
f.release_focus()
if e is InputEventKey and e.keycode == KEY_ESCAPE and e.pressed and get_global_rect().has_point(get_global_mouse_position()):
window_closed.emit()
if queue_free_on_close:
queue_free()
else:
hide()
func _process(delta: float) -> void:
if !no_move and _is_dragging:
var tp := position + get_local_mouse_position() - _drag_start_position
position = interp(position, tp, transform_interp_speed, delta)
elif !no_resize and _is_resizing:
var ts := size + _resize_btn.get_local_mouse_position() - _resize_start_position
ts.x = min(ts.x, get_viewport_rect().size.x)
ts.y = min(ts.y, get_viewport_rect().size.y)
if !no_resize_x:
size.x = interp(size.x, ts.x, transform_interp_speed, delta)
if !no_resize_y:
size.y = interp(size.y, ts.y, transform_interp_speed, delta)
elif !no_snap:
var window_rect := get_rect()
var screen_rect := get_viewport_rect()
var target_position := window_rect.position
var target_size := window_rect.size.clamp(Vector2.ZERO, screen_rect.size)
if window_rect.position.y < 0:
target_position.y = 0
if window_rect.end.y > screen_rect.end.y:
target_position.y = screen_rect.end.y - window_rect.size.y
if window_rect.end.y > screen_rect.end.y + window_rect.size.y / 2:
target_position.y = screen_rect.end.y - get_title_bar_height()
if window_rect.position.x < 0:
target_position.x = 0
if window_rect.end.x > screen_rect.end.x:
target_position.x = screen_rect.end.x - window_rect.size.x
var current_position = window_rect.position
current_position = interp(current_position, target_position, bounds_interp_speed, delta)
size = interp(size, target_size, bounds_interp_speed, delta)
position = current_position
if _size_animation:
if _target_size.is_equal_approx(size):
_size_animation = false
size = interp(size, _target_size, anim_interp_speed, delta)
# Framerate-independent interpolation.
func interp(from, to, lambda: float, delta: float):
return lerp(from, to, 1.0 - exp(-lambda * delta))

View File

@ -0,0 +1 @@
uid://ctfiphl1luka1

View File

@ -0,0 +1,196 @@
[gd_scene load_steps=19 format=3 uid="uid://s88loppa6gja"]
[ext_resource type="Material" uid="uid://dyipeqsa8lcpc" path="res://addons/panku_console/common/lynx_window2/lynx_window_shader_material.tres" id="1_tvp6i"]
[ext_resource type="Script" path="res://addons/panku_console/common/lynx_window2/lynx_window_2.gd" id="2_1ul5o"]
[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="2_3fhqk"]
[ext_resource type="Texture2D" uid="uid://dosm26riekruh" path="res://addons/panku_console/res/icons2/menu.svg" id="4_4dlyn"]
[ext_resource type="PackedScene" uid="uid://drn5t13m088fb" path="res://addons/panku_console/common/panku_button.tscn" id="4_dnesi"]
[ext_resource type="Texture2D" uid="uid://gav3m4qtvgje" path="res://addons/panku_console/res/icons2/pop-out-svgrepo-com.svg" id="4_im81u"]
[ext_resource type="Texture2D" uid="uid://8g5afcuanbl6" path="res://addons/panku_console/res/icons2/close.svg" id="5_l4qpm"]
[ext_resource type="Texture2D" uid="uid://dvr12fl5prm78" path="res://addons/panku_console/res/effect/square_shadow.png" id="6_mfp1h"]
[ext_resource type="Texture2D" uid="uid://ciu5jiw4xmkq0" path="res://addons/panku_console/res/icons2/resize-svgrepo-com.svg" id="7_duwqn"]
[ext_resource type="Script" path="res://addons/panku_console/common/lynx_window2/border.gd" id="8_gj3ji"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hv45g"]
draw_center = false
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(1, 1, 1, 0.25098)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6i67d"]
content_margin_left = 8.0
content_margin_top = 8.0
content_margin_right = 8.0
content_margin_bottom = 8.0
draw_center = false
border_width_left = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(1, 1, 1, 0.25098)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lct0k"]
draw_center = false
shadow_color = Color(0, 0, 0, 0.0627451)
shadow_size = 16
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_5muk4"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_r0x7y"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_p7tml"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_p3y6j"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uldta"]
draw_center = false
border_width_left = 4
border_width_top = 4
border_width_right = 4
border_width_bottom = 4
border_color = Color(1, 1, 1, 1)
[node name="LynxWindow2" type="ColorRect" node_paths=PackedStringArray("_window_title_container", "_title_btn", "_close_btn", "_options_btn", "_resize_btn", "_shadow_focus", "_shadow", "_container", "_pop_btn")]
material = ExtResource("1_tvp6i")
clip_contents = true
offset_right = 413.0
offset_bottom = 305.0
theme = ExtResource("2_3fhqk")
script = ExtResource("2_1ul5o")
_window_title_container = NodePath("VBoxContainer/Up")
_title_btn = NodePath("VBoxContainer/Up/TitleButton")
_close_btn = NodePath("VBoxContainer/Up/CloseButton")
_options_btn = NodePath("VBoxContainer/Up/MenuButton")
_resize_btn = NodePath("Button")
_shadow_focus = NodePath("Shadow2")
_shadow = NodePath("Shadow")
_container = NodePath("VBoxContainer/Down")
_pop_btn = NodePath("VBoxContainer/Up/PopupButton")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Up" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="TitleButton" parent="VBoxContainer/Up" instance=ExtResource("4_dnesi")]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
[node name="TextureRect" parent="VBoxContainer/Up/TitleButton/HBoxContainer" index="0"]
texture = null
[node name="Label" parent="VBoxContainer/Up/TitleButton/HBoxContainer" index="1"]
text = "Window Title"
[node name="PopupButton" parent="VBoxContainer/Up" instance=ExtResource("4_dnesi")]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
[node name="TextureRect" parent="VBoxContainer/Up/PopupButton/HBoxContainer" index="0"]
texture = ExtResource("4_im81u")
[node name="Label" parent="VBoxContainer/Up/PopupButton/HBoxContainer" index="1"]
visible = false
[node name="MenuButton" parent="VBoxContainer/Up" instance=ExtResource("4_dnesi")]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
[node name="TextureRect" parent="VBoxContainer/Up/MenuButton/HBoxContainer" index="0"]
texture = ExtResource("4_4dlyn")
[node name="Label" parent="VBoxContainer/Up/MenuButton/HBoxContainer" index="1"]
visible = false
[node name="CloseButton" parent="VBoxContainer/Up" instance=ExtResource("4_dnesi")]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
[node name="TextureRect" parent="VBoxContainer/Up/CloseButton/HBoxContainer" index="0"]
texture = ExtResource("5_l4qpm")
[node name="Label" parent="VBoxContainer/Up/CloseButton/HBoxContainer" index="1"]
visible = false
[node name="Down" type="Panel" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_6i67d")
[node name="Shadow" type="NinePatchRect" parent="."]
self_modulate = Color(1, 1, 1, 0.501961)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -64.0
offset_top = -79.0
offset_right = 63.0
offset_bottom = 47.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("6_mfp1h")
draw_center = false
region_rect = Rect2(0, 0, 512, 512)
patch_margin_left = 64
patch_margin_top = 80
patch_margin_right = 64
patch_margin_bottom = 48
[node name="Shadow2" type="Panel" parent="."]
visible = false
layout_mode = 2
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_lct0k")
[node name="Button" type="Button" parent="."]
self_modulate = Color(1, 1, 1, 0.501961)
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -12.0
offset_top = -12.0
grow_horizontal = 0
grow_vertical = 0
mouse_default_cursor_shape = 12
theme_override_styles/normal = SubResource("StyleBoxEmpty_5muk4")
theme_override_styles/hover = SubResource("StyleBoxEmpty_r0x7y")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_p7tml")
theme_override_styles/focus = SubResource("StyleBoxEmpty_p3y6j")
icon = ExtResource("7_duwqn")
flat = true
expand_icon = true
[node name="Border" type="Panel" parent="."]
modulate = Color(1, 1, 1, 0)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_uldta")
script = ExtResource("8_gj3ji")
[editable path="VBoxContainer/Up/TitleButton"]
[editable path="VBoxContainer/Up/PopupButton"]
[editable path="VBoxContainer/Up/MenuButton"]
[editable path="VBoxContainer/Up/CloseButton"]

View File

@ -0,0 +1,8 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://dyipeqsa8lcpc"]
[ext_resource type="Shader" path="res://addons/panku_console/res/shader/simple_fast_blur.gdshader" id="1_3h55m"]
[resource]
shader = ExtResource("1_3h55m")
shader_parameter/lod = 4.0
shader_parameter/modulate = Color(0, 0, 0, 0.12549)

View File

@ -0,0 +1,80 @@
#A simple control node managing its child windows
class_name PankuLynxWindowsManager extends Control
const CFG_ENABLE_OS_WINDOW = "enable_os_window"
const CFG_OS_WINDOW_BGCOLOR = "os_window_bg_color"
@onready var console:PankuConsole = get_node(PankuConsole.SingletonPath)
var os_popup_btn_enabled:bool
var os_window_bg_color:Color
func _ready():
load_data()
func _input(e):
if e is InputEventMouseButton and e.pressed:
var flag = true
#traverse child windows in reverse order, use double shadow to highlight current active window.
for i in range(get_child_count() - 1, -1, -1):
var w:Control = get_child(i)
if w.visible and w.get_global_rect().has_point(get_global_mouse_position()):
var forefront = get_child(get_child_count() - 1)
if forefront.has_method("highlight"): forefront.highlight(false)
w.move_to_front()
forefront = get_child(get_child_count() - 1)
if forefront.has_method("highlight"): forefront.highlight(true)
flag = false
break
if flag and get_child_count() > 0:
var forefront = get_child(get_child_count() - 1)
if forefront.has_method("highlight"): forefront.highlight(false)
func create_window(content:Control) -> PankuLynxWindow:
var new_window:PankuLynxWindow = preload("lynx_window_2.tscn").instantiate()
content.anchors_preset = Control.PRESET_FULL_RECT
new_window.set_content(content)
add_child(new_window)
new_window.show_window()
return new_window
func enable_os_popup_btns(b:bool):
#note that this may affect your project
get_viewport().gui_embed_subwindows = !b
os_popup_btn_enabled = b
for w in get_children():
#maybe there's a better way to get node type
if !w.has_method("switch_to_os_window"):
continue
w._pop_btn.visible = b
func get_enable_os_popup_btns() -> bool:
return os_popup_btn_enabled
func set_os_window_bg_color(c:Color):
os_window_bg_color = c
for w in get_children():
#maybe there's a better way to get node type
if !w.has_method("switch_to_os_window"):
continue
if w._os_window != null:
w._os_window.get_child(0).color = c
func get_os_window_bg_color() -> Color:
return os_window_bg_color
func save_data():
var cfg = PankuConfig.get_config()
cfg[CFG_ENABLE_OS_WINDOW] = os_popup_btn_enabled
cfg[CFG_OS_WINDOW_BGCOLOR] = os_window_bg_color
PankuConfig.set_config(cfg)
func load_data():
var cfg = PankuConfig.get_config()
enable_os_popup_btns(cfg.get(CFG_ENABLE_OS_WINDOW, false))
set_os_window_bg_color(cfg.get(CFG_OS_WINDOW_BGCOLOR, Color("#2b2e32")))
func _notification(what):
#quit event
if what == NOTIFICATION_WM_CLOSE_REQUEST:
save_data()

View File

@ -0,0 +1 @@
uid://ciferd4gytj41

View File

@ -0,0 +1,54 @@
class_name PankuModuleManager
var _modules:Array[PankuModule]
var _modules_table:Dictionary
var _core:PankuConsole
func init_manager(_core:PankuConsole, _modules:Array[PankuModule]):
self._modules = _modules
self._core = _core
load_modules()
func load_modules():
# The extra tree structure is purely used for avoiding using RefCounted which may cause uncessary leaked instance warnings.
var manager_node:Node = Node.new()
manager_node.name = "_Modules_"
_core.add_child(manager_node)
for _m in _modules:
var module:PankuModule = _m
_modules_table[module.get_module_name()] = module
module.name = module.get_module_name()
manager_node.add_child(module)
for _m in _modules:
var module:PankuModule = _m
module.core = _core
module._init_module()
#print("[info] %s module loaded!" % module.get_module_name())
func update_modules(delta:float):
for _m in _modules:
var module:PankuModule = _m
module.update_module(delta)
func get_module(module_name:String):
return _modules_table[module_name]
func has_module(module_name:String):
return _modules_table.has(module_name)
func get_module_option_objects():
var objects = []
for _m in _modules:
var module:PankuModule = _m
if module._opt != null:
objects.append(module._opt)
return objects
func quit_modules():
for _m in _modules:
var module:PankuModule = _m
module.quit_module()

View File

@ -0,0 +1 @@
uid://ciurr4mc7r71x

View File

@ -0,0 +1,11 @@
class_name ModuleOptions extends Resource
var _module:PankuModule
var _loaded := false
#FIXME: Tricky part of saving data, needs to be reworked
func update_setting(key: String, value: Variant):
self.set(key, value)
if _loaded and _module:
_module.save_module_data(key, value)

View File

@ -0,0 +1 @@
uid://c43wmdfwptw3v

View File

@ -0,0 +1,39 @@
class_name PankuButton extends Control
signal pressed
signal button_down
signal button_up
@export
var button:Button
@export
var trect:TextureRect
@export
var label:Label
var icon:
set(v):
trect.texture = v
get:
return trect.texture
var text:
set(v):
label.text = v
get:
return label.text
func _ready():
button.pressed.connect(
func():
pressed.emit()
)
button.button_down.connect(
func(): button_down.emit()
)
button.button_up.connect(
func(): button_up.emit()
)

View File

@ -0,0 +1 @@
uid://0ydmyeffilu7

View File

@ -0,0 +1,42 @@
[gd_scene load_steps=5 format=3 uid="uid://drn5t13m088fb"]
[ext_resource type="Script" path="res://addons/panku_console/common/panku_button.gd" id="1_7kf5f"]
[ext_resource type="Texture2D" uid="uid://dchvk7qgfe37m" path="res://addons/panku_console/res/icons2/fold-svgrepo-com.svg" id="2_su653"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_v3kpx"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cwnaw"]
content_margin_top = 4.0
content_margin_bottom = 4.0
[node name="PankuButton" type="PanelContainer" node_paths=PackedStringArray("button", "trect", "label")]
editor_description = "Godot's Button can't handle scaling icons properly as descripted in https://github.com/godotengine/godot-proposals/issues/660, so I have to make a new one."
self_modulate = Color(1, 1, 1, 0)
offset_right = 112.0
offset_bottom = 31.0
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxEmpty_v3kpx")
script = ExtResource("1_7kf5f")
button = NodePath("Button")
trect = NodePath("HBoxContainer/TextureRect")
label = NodePath("HBoxContainer/Label")
[node name="Button" type="Button" parent="."]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
mouse_filter = 2
theme_override_constants/separation = 0
[node name="TextureRect" type="TextureRect" parent="HBoxContainer"]
layout_mode = 2
mouse_filter = 2
texture = ExtResource("2_su653")
expand_mode = 2
[node name="Label" type="Label" parent="HBoxContainer"]
layout_mode = 2
size_flags_vertical = 1
theme_override_styles/normal = SubResource("StyleBoxEmpty_cwnaw")
vertical_alignment = 1

View File

@ -0,0 +1,101 @@
class_name PankuModule extends Node
# extends Node: A hacky way to avoid cyclic RefCounted verbose warnings which is uncessary to worry about.
var core:PankuConsole
var _env:RefCounted = null
var _opt:ModuleOptions = null
# dir name of the module
func get_module_name() -> String:
return get_script().resource_path.get_base_dir().get_file()
# called when the module is loaded
func init_module():
pass
# called when the module is unloaded (quit program)
func quit_module():
if _opt:
_opt._loaded = false
# called at the start of each physics frame
func update_module(delta:float):
pass
func save_module_data(key:String, value:Variant):
var cfg:Dictionary = PankuConfig.get_config()
var module_name:String = get_module_name()
if !cfg.has(module_name):
cfg[module_name] = {}
cfg[module_name][key] = value
PankuConfig.set_config(cfg)
func load_module_data(key:String, default_value:Variant = null) -> Variant:
var cfg:Dictionary = PankuConfig.get_config()
var module_name:String = get_module_name()
var module_data = cfg.get(module_name, {})
return module_data.get(key, default_value)
func has_module_data(key:String) -> bool:
var cfg:Dictionary = PankuConfig.get_config()
var module_name:String = get_module_name()
var module_data = cfg.get(module_name, {})
return module_data.has(key)
func load_window_data(window:PankuLynxWindow):
window.position = load_module_data("window_position", window.get_layout_position([
Control.PRESET_TOP_LEFT,
Control.PRESET_CENTER_TOP,
Control.PRESET_TOP_RIGHT,
Control.PRESET_CENTER_LEFT,
Control.PRESET_CENTER,
Control.PRESET_CENTER_RIGHT,
Control.PRESET_BOTTOM_LEFT,
Control.PRESET_CENTER_BOTTOM,
Control.PRESET_BOTTOM_RIGHT,
][randi()%9]))
window.size = load_module_data("window_size", window.get_normal_window_size())
window.set_window_visibility(load_module_data("window_visibility", false))
func save_window_data(window:PankuLynxWindow):
_save_window_geometry(window)
save_module_data("window_visibility", window.visible)
func _save_window_geometry(window:PankuLynxWindow):
save_module_data("window_position", window.position)
save_module_data("window_size", window.get_normal_window_size())
# Add hook to window to auto save its geometry on close.
func add_auto_save_hook(window: PankuLynxWindow) -> void:
# Here some global settings check can be implemented,
# if we decide to make "save on close" feature optional
window.window_closed.connect(_save_window_geometry.bind(window))
func get_module_env() -> RefCounted:
return _env
func get_module_opt() -> ModuleOptions:
return _opt
func _init_module():
var module_script_dir:String = get_script().resource_path.get_base_dir()
var env_script_path = module_script_dir + "/env.gd"
var opt_script_path = module_script_dir + "/opt.gd"
if FileAccess.file_exists(env_script_path):
_env = load(env_script_path).new()
_env._module = self
core.gd_exprenv.register_env(get_module_name(), _env)
if FileAccess.file_exists(opt_script_path):
#print(opt_script_path)
_opt = load(opt_script_path).new() as ModuleOptions
_opt._module = self
init_module()
if _opt:
_opt._loaded = true

View File

@ -0,0 +1 @@
uid://d21arokusrmjd

View File

@ -0,0 +1,23 @@
var _core:PankuConsole
const _HELP_help := "List all environment variables."
var help:String:
get:
var result = ["Registered objects:\n"]
var colors = ["#7c3f58", "#eb6b6f", "#f9a875", "#fff6d3"]
var i = 0
for k in _core.gd_exprenv._envs:
var c = colors[i%4]
i = i + 1
result.push_back("[b][color=%s]%s[/color][/b] "%[c, k])
result.push_back("\n")
result.push_back("You can type [b]helpe(object)[/b] to get more information.")
return "".join(PackedStringArray(result))
const _HELP_helpe := "Provide detailed information about one specific environment variable."
func helpe(obj:Object) -> String:
if !obj:
return "Invalid!"
if !obj.get_script():
return "It has no attached script!"
return PankuGDExprEnv.generate_help_text_from_script(obj.get_script())

View File

@ -0,0 +1 @@
uid://cw0ur3eb0henf

View File

@ -0,0 +1,48 @@
extends PanelContainer
@export var clip_container:Control
@export var scrollbar:VScrollBar
@export var follow_content:bool = true
@onready var content:Control = clip_container.get_child(0)
var scroll_progress:float = 0.0
var prev_content_size_y:float = 0.0
func init_progressbar() -> void:
scrollbar.min_value = 0.0
scrollbar.allow_greater = true
scrollbar.allow_lesser = true
scrollbar.value = 0.0
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.is_pressed():
var step:float = clip_container.size.y / 8.0
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
scrollbar.value -= step
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
scrollbar.value += step
func _process(delta: float) -> void:
content.size = Vector2(clip_container.size.x, 0)
content.position = Vector2.ZERO
scrollbar.max_value = content.size.y
var scrollbar_value_max = max(0, scrollbar.max_value - clip_container.size.y)
scrollbar.value = lerp(scrollbar.value, clampf(scrollbar.value, 0.0, scrollbar_value_max), 0.2)
scrollbar.page = clip_container.size.y
scrollbar.visible = content.size.y > clip_container.size.y
scroll_progress = lerp(scroll_progress, scrollbar.value, 0.2)
content.position.y = - scroll_progress
if !follow_content: return
if prev_content_size_y != content.size.y:
var should_follow:bool = (scrollbar.value + scrollbar.page) / prev_content_size_y > 0.99
prev_content_size_y = content.size.y
if should_follow:
scrollbar.value = scrollbar.max_value - scrollbar.page
func _ready() -> void:
init_progressbar()

View File

@ -0,0 +1 @@
uid://rd44n33h004h

View File

@ -0,0 +1,30 @@
[gd_scene load_steps=3 format=3 uid="uid://dyq4rjkkjs55d"]
[ext_resource type="Script" path="res://addons/panku_console/common/smooth_scroll/smooth_scroll.gd" id="1_ma8ku"]
[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_pa7xs"]
[node name="SmoothScrollContainer" type="PanelContainer" node_paths=PackedStringArray("clip_container", "scrollbar")]
self_modulate = Color(1, 1, 1, 0)
clip_contents = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_pa7xs")
script = ExtResource("1_ma8ku")
clip_container = NodePath("HBoxContainer/Control")
scrollbar = NodePath("HBoxContainer/VScrollBar")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Control" type="Control" parent="HBoxContainer"]
clip_contents = true
layout_mode = 2
size_flags_horizontal = 3
[node name="VScrollBar" type="VScrollBar" parent="HBoxContainer"]
layout_mode = 2
page = 20.0
value = 80.0

Some files were not shown because too many files have changed in this diff Show More