├── .gitignore ├── addons ├── bounding_box_resize │ ├── BoundingBoxResize.gd │ ├── interface │ │ └── RefreshButton.tscn │ └── plugin.cfg ├── color_palette │ ├── ColorPalettePlugin.gd │ ├── interface │ │ ├── ColorPalette.gd │ │ ├── ColorPalette.tscn │ │ └── color_swatch │ │ │ ├── ColorSwatch.gd │ │ │ └── ColorSwatch.tscn │ ├── palettes │ │ ├── gray.json │ │ └── rgb_cmyk.json │ └── plugin.cfg ├── gdquest_docker │ ├── GDQuestDocker.gd │ ├── interface │ │ ├── Docker.gd │ │ └── Docker.tscn │ └── plugin.cfg └── rich_text_editor │ ├── RichTextEditor.gd │ ├── interface │ ├── Button.tscn │ ├── RichEditorInterface.gd │ └── RichEditorInterface.tscn │ └── plugin.cfg ├── default_env.tres ├── icon.png ├── icon.png.import └── project.godot /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | -------------------------------------------------------------------------------- /addons/bounding_box_resize/BoundingBoxResize.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | # Updates RichTextLabel and TextEdit minimum rect size to fit their content 4 | 5 | onready var _editor: = get_editor_interface() 6 | onready var _selection: = _editor.get_selection() 7 | onready var _interface: Button 8 | 9 | const INTERFACE_SCENE: PackedScene = preload("res://addons/bounding_box_resize/interface/RefreshButton.tscn") 10 | 11 | func _ready() -> void: 12 | _interface = INTERFACE_SCENE.instance() 13 | _interface.visible = false 14 | _interface.connect("pressed", self, "_on_Button_pressed") 15 | 16 | if not _editor.is_plugin_enabled("gdquest_docker"): 17 | add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, _interface) 18 | 19 | _selection.connect("selection_changed", self, "_on_EditorSelection_selection_changed") 20 | 21 | 22 | func _exit_tree(): 23 | if not _editor.is_plugin_enabled("gdquest_docker"): 24 | remove_control_from_container(CONTAINER_CANVAS_EDITOR_MENU, _interface) 25 | 26 | 27 | func _on_EditorSelection_selection_changed() -> void: 28 | var show_interface: bool = true 29 | show_interface = _selection.get_selected_nodes().size() > 0 30 | 31 | for node in _selection.get_selected_nodes(): 32 | if node is RichTextLabel or node is TextEdit: 33 | continue 34 | show_interface = false 35 | break 36 | _interface.visible = show_interface 37 | 38 | 39 | func _on_Button_pressed() -> void: 40 | var nodes: = _selection.get_selected_nodes() 41 | for n in nodes: 42 | fit_rect_vertically(n) 43 | 44 | 45 | func get_plugin_name() -> String: 46 | return "bounding_box_resize" 47 | 48 | 49 | func get_interface() -> Control: 50 | return _interface 51 | 52 | 53 | func fit_rect_vertically(control: Control) -> void: 54 | var content_size: = get_content_size(control) 55 | if content_size.y < control.rect_size.y: 56 | return 57 | 58 | var undo: = get_undo_redo() 59 | undo.create_action("Resize Rect Vertically") 60 | 61 | undo.add_undo_property(control, "rect_min_size", control.rect_min_size) 62 | undo.add_undo_property(control, "rect_size", control.rect_size) 63 | control.rect_min_size.y = content_size.y 64 | undo.add_do_property(control, "rect_min_size", control.rect_min_size) 65 | undo.add_do_property(control, "rect_size", control.rect_size) 66 | 67 | undo.commit_action() 68 | 69 | 70 | # Note that this is generalized because Control is the unique common 71 | # ancestor between RichTextLabel and TextEdit, so to prevent any other 72 | # node to go through this function we must have an assertion on their 73 | # text property 74 | func get_content_size(control: Control) -> Vector2: 75 | if not control.has_method("get_text"): 76 | return control.rect_size 77 | 78 | var font: = get_control_font(control) 79 | var text: String = control.text 80 | 81 | var size: Vector2 = get_size_with_lines(text, font) 82 | var padding: Vector2 = get_padding(control) 83 | size += padding 84 | 85 | return size 86 | 87 | 88 | func get_control_font(control: Control) -> Font: 89 | var font: Font 90 | var custom_font: String = "font" 91 | if control is RichTextLabel: 92 | custom_font = "normal_font" 93 | font = control.get_font(custom_font) 94 | return font 95 | 96 | 97 | func get_size_with_lines(text: String, font: Font) -> Vector2: 98 | var size: = Vector2(0, 0) 99 | var lines: PoolStringArray = text.split("\n") 100 | 101 | size.y = lines.size() * font.get_height() 102 | size.x = get_longest_line_width(lines, font) 103 | 104 | return size 105 | 106 | 107 | func get_longest_line_width(lines: PoolStringArray, font: Font) -> float: 108 | var larger_line_width: = 0.0 109 | for line in lines: 110 | var line_width: = font.get_string_size(line).x 111 | larger_line_width = max(larger_line_width, line_width) 112 | 113 | return larger_line_width 114 | 115 | 116 | # Returns an offset to compensate scrollbars 117 | func get_padding(control: Control) -> Vector2: 118 | var padding: = Vector2(0, 0) 119 | 120 | if control is RichTextLabel: 121 | if control.scroll_active: 122 | padding = Vector2(30, 0) 123 | elif control is TextEdit: 124 | padding = Vector2(30, 30) 125 | 126 | return padding 127 | -------------------------------------------------------------------------------- /addons/bounding_box_resize/interface/RefreshButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [sub_resource type="InputEventKey" id=1] 4 | 5 | control = true 6 | command = true 7 | scancode = 82 8 | 9 | [sub_resource type="ShortCut" id=2] 10 | 11 | shortcut = SubResource( 1 ) 12 | 13 | [node name="Button" type="Button"] 14 | margin_right = 12.0 15 | margin_bottom = 20.0 16 | shortcut = SubResource( 2 ) 17 | text = "Refresh" 18 | 19 | -------------------------------------------------------------------------------- /addons/bounding_box_resize/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Bounding Box Resize" 4 | description="A plugin that automatically resize RichText and TextEdit's bounding box to fits its content." 5 | author="Henrique Campos" 6 | version="0.1.2" 7 | script="BoundingBoxResize.gd" 8 | -------------------------------------------------------------------------------- /addons/color_palette/ColorPalettePlugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | # Enable the use of palettes to set CanvasItem's modulate from an easily accessible dock 4 | onready var _interface: ColorPalette setget , get_interface 5 | 6 | const PATH: = "res://addons/color_palette/palettes/" 7 | const INTERFACE_SCENE: PackedScene = preload("res://addons/color_palette/interface/ColorPalette.tscn") 8 | 9 | var palettes: Dictionary = {} 10 | var _editor: = get_editor_interface() 11 | 12 | func _enter_tree() -> void: 13 | _interface = INTERFACE_SCENE.instance() as ColorPalette 14 | _interface.connect("color_picked", self, "set_canvas_items_modulate") 15 | _interface.connect("palette_selected", self, "update_palette") 16 | 17 | if not _editor.is_plugin_enabled("gdquest_docker"): 18 | add_control_to_dock(DOCK_SLOT_LEFT_BL, _interface) 19 | 20 | load_palettes(PATH) 21 | update_palette(palettes.keys()[0]) 22 | 23 | 24 | func _exit_tree(): 25 | if not _editor.is_plugin_enabled("gdquest_docker"): 26 | remove_control_from_docks(_interface) 27 | 28 | 29 | func get_interface() -> ColorPalette: 30 | return _interface as ColorPalette 31 | 32 | 33 | func get_plugin_name() -> String: 34 | return "color_palette" 35 | 36 | 37 | func load_palettes(palettes_folder) -> void: 38 | var directory: Directory = Directory.new() 39 | 40 | if directory.dir_exists(palettes_folder): 41 | directory.open(palettes_folder) 42 | directory.list_dir_begin(true) 43 | var file_name: String = directory.get_next() 44 | var file: File = File.new() 45 | 46 | while file_name != "": 47 | file.open(palettes_folder + file_name, file.READ) 48 | 49 | var palette: Dictionary = parse_json(file.get_as_text()) 50 | add_palette(palette["id"], palette) 51 | 52 | file_name = directory.get_next() 53 | 54 | 55 | func update_palette(palette_name: String) -> void: 56 | _interface.clear() 57 | 58 | var palette: Dictionary = palettes[palette_name] 59 | _interface.title = palette["name"] 60 | 61 | var colors: Dictionary = palette["colors"] 62 | for color in colors.values(): 63 | _interface.add_swatch(color) 64 | 65 | 66 | func add_palette(unique_name: String, palette: Dictionary) -> void: 67 | assert(not palettes.has(unique_name)) 68 | 69 | palettes[unique_name] = palette 70 | _interface.add_palette(unique_name) 71 | 72 | 73 | func set_canvas_items_modulate(hex_color: String) -> void: 74 | var selection: = get_editor_interface().get_selection() 75 | if selection.get_selected_nodes().size() < 1: 76 | return 77 | 78 | var undo: = get_undo_redo() 79 | undo.create_action("Set Modulate") 80 | 81 | for n in selection.get_selected_nodes(): 82 | if not n.has_method("set_modulate") or n is RichTextLabel: 83 | continue 84 | undo.add_undo_property(n, "modulate", n.modulate) 85 | n.modulate = Color(hex_color) 86 | undo.add_do_property(n, "modulate", n.modulate) 87 | 88 | undo.commit_action() 89 | 90 | -------------------------------------------------------------------------------- /addons/color_palette/interface/ColorPalette.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends MarginContainer 3 | class_name ColorPalette 4 | # An interface to display color palettes as sequence of buttons 5 | 6 | signal palette_selected(palette_name) 7 | signal color_picked(hex_color) 8 | 9 | export (PackedScene) var color_swatch: PackedScene = preload("res://addons/color_palette/interface/color_swatch/ColorSwatch.tscn") 10 | var title: String = "Palette Name" setget set_title 11 | 12 | func set_title(new_title: String) -> void: 13 | title = new_title 14 | var label: = $Column/Label 15 | label.text = title 16 | 17 | 18 | func add_swatch(color_hex: String) -> void: 19 | var new_swatch: ColorSwatch = color_swatch.instance() 20 | var color: = Color(color_hex) 21 | new_swatch.color = color 22 | new_swatch.connect("button_up", self, "_on_Swatch_button_up", [new_swatch.color]) 23 | 24 | var grid: = $Column/Grid 25 | grid.add_child(new_swatch) 26 | 27 | 28 | func add_palette(unique_name: String) -> void: 29 | var option_button: = $Column/OptionButton 30 | option_button.add_item(unique_name) 31 | 32 | 33 | func clear() -> void: 34 | for c in $Column/Grid.get_children(): 35 | c.queue_free() 36 | 37 | 38 | func _on_OptionButton_item_selected(ID: int) -> void: 39 | var option_button = $Column/OptionButton 40 | emit_signal("palette_selected", option_button.get_item_text(ID)) 41 | 42 | 43 | func _on_Swatch_button_up(color: Color) -> void: 44 | var hex_color: = color.to_html() 45 | emit_signal("color_picked", hex_color) 46 | 47 | -------------------------------------------------------------------------------- /addons/color_palette/interface/ColorPalette.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/color_palette/interface/ColorPalette.gd" type="Script" id=1] 4 | 5 | [node name="ColorPalette" type="MarginContainer"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | margin_right = -1497.0 9 | margin_bottom = -598.0 10 | script = ExtResource( 1 ) 11 | 12 | [node name="Column" type="VBoxContainer" parent="."] 13 | margin_right = 423.0 14 | margin_bottom = 482.0 15 | 16 | [node name="Label" type="Label" parent="Column"] 17 | margin_right = 423.0 18 | margin_bottom = 14.0 19 | text = "Palette name" 20 | align = 1 21 | 22 | [node name="Grid" type="GridContainer" parent="Column"] 23 | margin_top = 18.0 24 | margin_right = 423.0 25 | margin_bottom = 458.0 26 | size_flags_horizontal = 3 27 | size_flags_vertical = 3 28 | custom_constants/vseparation = 8 29 | custom_constants/hseparation = 8 30 | columns = 6 31 | 32 | [node name="OptionButton" type="OptionButton" parent="Column"] 33 | margin_top = 462.0 34 | margin_right = 423.0 35 | margin_bottom = 482.0 36 | text = "Palettes" 37 | 38 | [connection signal="item_selected" from="Column/OptionButton" to="." method="_on_OptionButton_item_selected"] 39 | -------------------------------------------------------------------------------- /addons/color_palette/interface/color_swatch/ColorSwatch.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Button 3 | class_name ColorSwatch 4 | 5 | onready var color_rect : ColorRect = $ColorRect 6 | 7 | export var color : = Color('ffffff') setget set_color 8 | 9 | func set_color(value:Color) -> void: 10 | color = value 11 | if not color_rect: 12 | return 13 | color_rect.color = value 14 | 15 | func _ready() -> void: 16 | self.color = color 17 | -------------------------------------------------------------------------------- /addons/color_palette/interface/color_swatch/ColorSwatch.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://src/Components/ColorPalette/ColorSwatch/ColorSwatch.gd" type="Script" id=1] 4 | 5 | [node name="ColorSwatch" type="Button"] 6 | margin_right = 64.0 7 | margin_bottom = 64.0 8 | rect_min_size = Vector2( 32, 32 ) 9 | script = ExtResource( 1 ) 10 | 11 | [node name="ColorRect" type="ColorRect" parent="."] 12 | anchor_right = 1.0 13 | anchor_bottom = 1.0 14 | margin_left = 8.0 15 | margin_top = 8.0 16 | margin_right = -8.0 17 | margin_bottom = -8.0 18 | mouse_filter = 2 19 | 20 | -------------------------------------------------------------------------------- /addons/color_palette/palettes/gray.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "gray_m", 3 | "name" : "Minimal Gray", 4 | "category" : "gray", 5 | "colors" : { 6 | "white" : "ffffff", 7 | "light_gray" : "dddddd", 8 | "mid_gray" : "888888", 9 | "dark_Gray" : "444444", 10 | "black" : "000000", 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /addons/color_palette/palettes/rgb_cmyk.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "rgb_cmyk", 3 | "name" : "RGB & CMYK", 4 | "category" : "basic", 5 | "colors" : { 6 | "01" : "ff0000", 7 | "02" : "00ff00", 8 | "03" : "0000ff", 9 | "04" : "ff00ff", 10 | "05" : "00ffff", 11 | "06" : "ffff00", 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /addons/color_palette/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Color Palette" 4 | description="A widget that loads palettes from json files. Allows the user to easily assign CanvasItems's modulate." 5 | author="Henrique Campos" 6 | version="0.1.1" 7 | script="ColorPalettePlugin.gd" 8 | -------------------------------------------------------------------------------- /addons/gdquest_docker/GDQuestDocker.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | # A container for plugins' widgets that provides an easy access to enable/disable valid plugins 4 | 5 | # # Notes 6 | # --- 7 | # A valid plugin must have the EditorPlugin.get_plugin_name() -> String virtual method implemented 8 | # and also a custom get_interface() -> Control method implemented. 9 | 10 | const INTERFACE_SCENE: PackedScene = preload("res://addons/gdquest_docker/interface/Docker.tscn") 11 | const ADDONS_FOLDER_PATH: String = "res://addons" 12 | 13 | var _interface: Docker = INTERFACE_SCENE.instance() 14 | var _editor: EditorInterface = get_editor_interface() 15 | var _selection: EditorSelection = _editor.get_selection() 16 | var _plugins: Array = [] 17 | 18 | func _enter_tree() -> void: 19 | add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_BR, _interface) 20 | _selection.connect("selection_changed", self, "_on_selection_changed") 21 | 22 | 23 | func _exit_tree() -> void: 24 | remove_control_from_docks(_interface) 25 | 26 | 27 | func _ready(): 28 | load_plugins() 29 | _initialize() 30 | 31 | 32 | func _initialize() -> void: 33 | for plugin in _plugins: 34 | if validate_plugin(plugin): 35 | add_widget(get_plugin(plugin).get_interface()) 36 | else: 37 | remove_plugin_from_list(plugin) 38 | 39 | hook_color_palette() 40 | hook_rich_editor() 41 | 42 | 43 | func _on_selection_changed() -> void: 44 | if not _editor.is_plugin_enabled("rich_text_editor"): 45 | return 46 | var widgets: Control = _interface.get_widget_container() 47 | 48 | if widgets.has_node("RichEditorInterface"): 49 | widgets.get_node("RichEditorInterface").queue_free() 50 | 51 | var plugin: RichTextPlugin = get_plugin("rich_text_editor") as RichTextPlugin 52 | 53 | if RichTextPlugin.is_rich_text_selection(_selection): 54 | plugin.initialize_interface() 55 | 56 | func get_plugin_name() -> String: 57 | return "gdquest_docker" 58 | 59 | 60 | func load_plugins() -> void: 61 | var addons_folder: Directory = Directory.new() 62 | if not addons_folder.dir_exists(ADDONS_FOLDER_PATH): 63 | return 64 | 65 | addons_folder.open(ADDONS_FOLDER_PATH) 66 | addons_folder.list_dir_begin(true) 67 | var addon: String = addons_folder.get_next() 68 | 69 | while not addon == "": 70 | add_plugin_to_list(addon) 71 | addon = addons_folder.get_next() 72 | 73 | 74 | func add_plugin_to_list(plugin_name: String) -> void: 75 | if plugin_name == get_plugin_name(): 76 | return 77 | _plugins.append(plugin_name) 78 | 79 | 80 | func remove_plugin_from_list(plugin_name: String) -> void: 81 | _plugins.erase(plugin_name) 82 | 83 | 84 | func add_widget(widget : Control) -> void: 85 | var container: = _interface.get_widget_container() 86 | container.add_child(widget) 87 | 88 | 89 | func remove_widget(widget : Control) -> void: 90 | var container: = _interface.get_widget_container() 91 | container.remove_child(widget) 92 | 93 | 94 | func validate_plugin(plugin_name: String) -> bool: 95 | #Plugins must be enabled to be validated 96 | var is_enabled: = _editor.is_plugin_enabled(plugin_name) 97 | if not is_enabled: 98 | _editor.set_plugin_enabled(plugin_name, true) 99 | var plugin: = get_plugin(plugin_name) 100 | 101 | var is_valid: bool = false 102 | is_valid = not plugin.get_plugin_name() == "" 103 | is_valid = plugin.has_method("get_interface") 104 | 105 | if not is_enabled and not is_valid: 106 | _editor.set_plugin_enabled(plugin_name, false) 107 | 108 | return is_valid 109 | 110 | 111 | func hook_color_palette() -> void: 112 | if not _editor.is_plugin_enabled("color_palette"): 113 | return 114 | if not _editor.is_plugin_enabled("rich_text_editor"): 115 | return 116 | 117 | var color_palette: ColorPalette = get_plugin("color_palette").get_interface() as ColorPalette 118 | var rich_editor: RichTextPlugin = get_plugin("rich_text_editor") as RichTextPlugin 119 | 120 | if not color_palette.is_connected("color_picked", rich_editor, "_on_ColorPalette_color_picked"): 121 | color_palette.connect("color_picked", rich_editor, "_on_ColorPalette_color_picked") 122 | 123 | 124 | func hook_rich_editor() -> void: 125 | if not _editor.is_plugin_enabled("rich_text_editor"): 126 | return 127 | 128 | var rich_editor: RichTextPlugin = get_plugin("rich_text_editor") as RichTextPlugin 129 | 130 | if not rich_editor.is_connected("text_edit_popped", self, "add_widget"): 131 | rich_editor.connect("text_edit_popped", self, "add_widget") 132 | 133 | 134 | # Currently is not possible to get the plugin's node directly in the Editor, 135 | # i.e. to simply EditorInterface.get_plugin(String plugin_name) -> EditorPlugin 136 | # So these two methods are used as a workaround to find an EditorPlugin's Node 137 | func get_plugins_list() -> Array: 138 | var plugins: Array = [] 139 | 140 | # Since is uncertain what is the actual Node's name in the parent, 141 | # we presume that an EditorPlugin is valid when it implements the 142 | # get_plugin_name virtual method, otherwise we ignore it 143 | for n in get_parent().get_children(): 144 | if not n.has_method("get_plugin_name"): 145 | continue 146 | if n.get_plugin_name() == "": 147 | continue 148 | plugins.append(n) 149 | 150 | return plugins 151 | 152 | 153 | func get_plugin(plugin_name) -> EditorPlugin: 154 | var plugin: = EditorPlugin.new() 155 | for p in get_plugins_list(): 156 | if p.get_plugin_name() == plugin_name: 157 | plugin = p 158 | break 159 | return plugin 160 | -------------------------------------------------------------------------------- /addons/gdquest_docker/interface/Docker.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends MarginContainer 3 | class_name Docker 4 | 5 | onready var _widget_container: VBoxContainer = $Column/WidgetContainer/List 6 | 7 | func get_widget_container() -> VBoxContainer: 8 | return _widget_container as VBoxContainer 9 | 10 | -------------------------------------------------------------------------------- /addons/gdquest_docker/interface/Docker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdquest_docker/interface/Docker.gd" type="Script" id=1] 4 | 5 | [node name="GDQuestDocker" type="MarginContainer"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | size_flags_horizontal = 3 9 | size_flags_vertical = 3 10 | custom_constants/margin_right = 8 11 | custom_constants/margin_top = 8 12 | custom_constants/margin_left = 8 13 | custom_constants/margin_bottom = 8 14 | script = ExtResource( 1 ) 15 | 16 | [node name="Column" type="VBoxContainer" parent="."] 17 | margin_left = 8.0 18 | margin_top = 8.0 19 | margin_right = 1912.0 20 | margin_bottom = 1072.0 21 | 22 | [node name="WidgetContainer" type="ScrollContainer" parent="Column"] 23 | margin_right = 1904.0 24 | margin_bottom = 1064.0 25 | size_flags_horizontal = 3 26 | size_flags_vertical = 3 27 | 28 | [node name="List" type="VBoxContainer" parent="Column/WidgetContainer"] 29 | margin_right = 1904.0 30 | margin_bottom = 1064.0 31 | size_flags_horizontal = 3 32 | size_flags_vertical = 3 33 | 34 | -------------------------------------------------------------------------------- /addons/gdquest_docker/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="GDQuest Docker" 4 | description="A custom docker that contains a set of common use plugins." 5 | author="Henrique Campos" 6 | version="0.2.0" 7 | script="GDQuestDocker.gd" 8 | -------------------------------------------------------------------------------- /addons/rich_text_editor/RichTextEditor.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | class_name RichTextPlugin 4 | 5 | signal text_edit_popped(new_text_edit) 6 | 7 | const BUTTON_SCENE: PackedScene = preload("res://addons/rich_text_editor/interface/Button.tscn") 8 | const INTERFACE_SCENE: PackedScene = preload("res://addons/rich_text_editor/interface/RichEditorInterface.tscn") 9 | 10 | var rich_labels: Array = [] 11 | var text_edit: TextEdit = TextEdit.new() 12 | 13 | var _editor: = get_editor_interface() 14 | var _button: Button = BUTTON_SCENE.instance() 15 | var _selection: = _editor.get_selection() 16 | var _interface: RichEditorInterface 17 | 18 | func _ready() -> void: 19 | _button.hide() 20 | _button.connect("pressed", self, "_on_Button_pressed") 21 | 22 | if not _editor.is_plugin_enabled("gdquest_docker"): 23 | add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, _button) 24 | 25 | _selection.connect("selection_changed", self, "_on_EditorSelection_selection_changed") 26 | 27 | 28 | func _exit_tree() -> void: 29 | if not _editor.is_plugin_enabled("gdquest_docker"): 30 | remove_control_from_container(CONTAINER_CANVAS_EDITOR_MENU, _button) 31 | 32 | 33 | func _on_Button_pressed() -> void: 34 | initialize_interface() 35 | 36 | 37 | func _on_TextEdit_tree_exited() -> void: 38 | _button.disabled = false 39 | 40 | 41 | func _on_TextEdit_text_changed() -> void: 42 | var undo: = get_undo_redo() 43 | 44 | undo.create_action("Set BBCode Text") 45 | for rich_text_label in rich_labels: 46 | rich_text_label.bbcode_enabled = true 47 | undo.add_undo_property(rich_text_label, "bbcode_text", rich_text_label.bbcode_text) 48 | undo.add_do_property(rich_text_label, "bbcode_text", text_edit.text) 49 | rich_text_label.bbcode_text = text_edit.text 50 | 51 | undo.commit_action() 52 | 53 | 54 | func _on_ColorButton_popup_closed() -> void: 55 | colorize_selection(_interface.color_button.color) 56 | 57 | 58 | func _on_EditorSelection_selection_changed() -> void: 59 | var rich_labels_only: = is_rich_text_selection(_selection) 60 | if rich_labels_only: 61 | rich_labels = _selection.get_selected_nodes() 62 | else: 63 | rich_labels.clear() 64 | 65 | _button.visible = rich_labels_only 66 | 67 | 68 | func _on_ColorPalette_color_picked(hex_color: String) -> void: 69 | _interface.color_button.color = Color(hex_color) 70 | colorize_selection(Color(hex_color)) 71 | 72 | 73 | func initialize_interface(): 74 | _interface = INTERFACE_SCENE.instance() 75 | 76 | if not _editor.is_plugin_enabled("gdquest_docker"): 77 | _editor.get_base_control().add_child(_interface) 78 | emit_signal("text_edit_popped", _interface) 79 | 80 | text_edit = _interface.text_edit 81 | 82 | if rich_labels.size() < 2: 83 | var rich_text: RichTextLabel = _editor.get_selection().get_selected_nodes()[0] 84 | text_edit.text = rich_text.text 85 | if rich_text.bbcode_enabled and not rich_text.bbcode_text.empty(): 86 | text_edit.text = rich_text.bbcode_text 87 | 88 | text_edit.connect("text_changed", self, "_on_TextEdit_text_changed") 89 | text_edit.connect("tree_exited", self, "_on_TextEdit_tree_exited") 90 | 91 | _interface.bold_button.connect("pressed", self, "bold_selection") 92 | _interface.italic_button.connect("pressed", self, "italic_selection") 93 | _interface.color_button.connect("popup_closed", self, "_on_ColorButton_popup_closed") 94 | 95 | _button.disabled = true 96 | 97 | 98 | func get_plugin_name() -> String: 99 | return "rich_text_editor" 100 | 101 | 102 | func get_interface() -> Control: 103 | return _button 104 | 105 | 106 | func colorize_selection(color: Color) -> void: 107 | var text: = text_edit.get_selection_text() 108 | text = "%s" + text + "%s" 109 | text = text%["[color=#" + color.to_html() + "]", "[/color]"] 110 | 111 | insert_bbcode_text(text) 112 | 113 | 114 | func bold_selection() -> void: 115 | var text: = text_edit.get_selection_text() 116 | text = "%s" + text + "%s" 117 | text = text%["[b]", "[/b]"] 118 | 119 | insert_bbcode_text(text) 120 | 121 | 122 | func italic_selection() -> void: 123 | var text: = text_edit.get_selection_text() 124 | text = "%s" + text + "%s" 125 | text = text%["[i]", "[/i]"] 126 | 127 | insert_bbcode_text(text) 128 | 129 | 130 | func insert_bbcode_text(new_text: String) -> void: 131 | var undo: = get_undo_redo() 132 | 133 | undo.create_action("Insert BBCode Text") 134 | 135 | undo.add_undo_property(text_edit, "text", text_edit.text) 136 | text_edit.cut() 137 | text_edit.insert_text_at_cursor(new_text) 138 | undo.add_do_property(text_edit, "text", text_edit.text) 139 | undo.commit_action() 140 | 141 | 142 | static func is_rich_text_selection(selection: EditorSelection) -> bool: 143 | var rich_labels_only: bool = true 144 | var nodes: Array = selection.get_selected_nodes() 145 | 146 | rich_labels_only = nodes.size() > 0 147 | 148 | for node in nodes: 149 | if not node is RichTextLabel: 150 | rich_labels_only = false 151 | break 152 | 153 | return rich_labels_only 154 | -------------------------------------------------------------------------------- /addons/rich_text_editor/interface/Button.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [sub_resource type="InputEventKey" id=1] 4 | control = true 5 | command = true 6 | scancode = 69 7 | 8 | [sub_resource type="ShortCut" id=2] 9 | shortcut = SubResource( 1 ) 10 | 11 | [node name="Button" type="Button"] 12 | margin_right = 12.0 13 | margin_bottom = 20.0 14 | shortcut = SubResource( 2 ) 15 | text = "Rich Edit" 16 | 17 | -------------------------------------------------------------------------------- /addons/rich_text_editor/interface/RichEditorInterface.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Control 3 | class_name RichEditorInterface 4 | 5 | onready var bold_button: Button = $VBoxContainer/HBoxContainer/Bold 6 | onready var italic_button: Button = $VBoxContainer/HBoxContainer/Italic 7 | onready var text_edit: TextEdit = $VBoxContainer/TextEdit 8 | onready var color_button: ColorPickerButton = $VBoxContainer/HBoxContainer/Color 9 | -------------------------------------------------------------------------------- /addons/rich_text_editor/interface/RichEditorInterface.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=2] 2 | 3 | [ext_resource path="res://addons/rich_text_editor/interface/RichEditorInterface.gd" type="Script" id=1] 4 | 5 | [sub_resource type="InputEventKey" id=1] 6 | control = true 7 | command = true 8 | pressed = true 9 | scancode = 66 10 | 11 | [sub_resource type="ShortCut" id=2] 12 | shortcut = SubResource( 1 ) 13 | 14 | [sub_resource type="InputEventKey" id=3] 15 | control = true 16 | command = true 17 | pressed = true 18 | scancode = 73 19 | 20 | [sub_resource type="ShortCut" id=4] 21 | shortcut = SubResource( 3 ) 22 | 23 | [sub_resource type="InputEventKey" id=5] 24 | pressed = true 25 | scancode = 16777217 26 | 27 | [sub_resource type="ShortCut" id=6] 28 | shortcut = SubResource( 5 ) 29 | 30 | [node name="RichEditorInterface" type="Panel"] 31 | anchor_right = 1.0 32 | anchor_bottom = 1.0 33 | margin_left = 622.0 34 | margin_top = 188.0 35 | margin_right = -622.0 36 | margin_bottom = -188.0 37 | size_flags_horizontal = 3 38 | size_flags_vertical = 3 39 | script = ExtResource( 1 ) 40 | 41 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 42 | anchor_right = 1.0 43 | anchor_bottom = 1.0 44 | 45 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] 46 | margin_right = 676.0 47 | margin_bottom = 30.0 48 | 49 | [node name="Bold" type="Button" parent="VBoxContainer/HBoxContainer"] 50 | margin_right = 30.0 51 | margin_bottom = 30.0 52 | rect_min_size = Vector2( 30, 0 ) 53 | shortcut = SubResource( 2 ) 54 | text = "B" 55 | 56 | [node name="Italic" type="Button" parent="VBoxContainer/HBoxContainer"] 57 | margin_left = 34.0 58 | margin_right = 64.0 59 | margin_bottom = 30.0 60 | rect_min_size = Vector2( 30, 0 ) 61 | shortcut = SubResource( 4 ) 62 | text = "I" 63 | 64 | [node name="Color" type="ColorPickerButton" parent="VBoxContainer/HBoxContainer"] 65 | margin_left = 68.0 66 | margin_right = 102.0 67 | margin_bottom = 30.0 68 | rect_min_size = Vector2( 34, 30 ) 69 | 70 | [node name="Spacing" type="Control" parent="VBoxContainer/HBoxContainer"] 71 | margin_left = 106.0 72 | margin_right = 652.0 73 | margin_bottom = 30.0 74 | size_flags_horizontal = 3 75 | 76 | [node name="Close" type="Button" parent="VBoxContainer/HBoxContainer"] 77 | margin_left = 656.0 78 | margin_right = 676.0 79 | margin_bottom = 30.0 80 | shortcut = SubResource( 6 ) 81 | text = "X" 82 | 83 | [node name="TextEdit" type="TextEdit" parent="VBoxContainer"] 84 | margin_top = 34.0 85 | margin_right = 676.0 86 | margin_bottom = 704.0 87 | size_flags_vertical = 3 88 | 89 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/Close" to="." method="queue_free"] 90 | -------------------------------------------------------------------------------- /addons/rich_text_editor/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="RichText Editor" 4 | description="A text editor with BBCode user interface to edit RichTextLabels." 5 | author="Henrique Campos" 6 | version="0.1.0" 7 | script="RichTextEditor.gd" 8 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDQuest/godot-text-tools/dd6bd20a7755fde45b0a66fab6796f0bcd984c43/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ { 12 | "base": "MarginContainer", 13 | "class": "ColorPalette", 14 | "language": "GDScript", 15 | "path": "res://addons/color_palette/interface/ColorPalette.gd" 16 | }, { 17 | "base": "Button", 18 | "class": "ColorSwatch", 19 | "language": "GDScript", 20 | "path": "res://addons/color_palette/interface/color_swatch/ColorSwatch.gd" 21 | }, { 22 | "base": "MarginContainer", 23 | "class": "Docker", 24 | "language": "GDScript", 25 | "path": "res://addons/gdquest_docker/interface/Docker.gd" 26 | }, { 27 | "base": "Control", 28 | "class": "RichEditorInterface", 29 | "language": "GDScript", 30 | "path": "res://addons/rich_text_editor/interface/RichEditorInterface.gd" 31 | }, { 32 | "base": "EditorPlugin", 33 | "class": "RichTextPlugin", 34 | "language": "GDScript", 35 | "path": "res://addons/rich_text_editor/RichTextEditor.gd" 36 | } ] 37 | _global_script_class_icons={ 38 | "ColorPalette": "", 39 | "ColorSwatch": "", 40 | "Docker": "", 41 | "RichEditorInterface": "", 42 | "RichTextPlugin": "" 43 | } 44 | 45 | [application] 46 | 47 | config/name="GDQuest Godot text and UI tools" 48 | config/description="A set of plugins to edit text and UI more efficiently with Godot." 49 | config/icon="res://icon.png" 50 | 51 | [display] 52 | 53 | window/size/width=1920 54 | window/size/height=1080 55 | 56 | [editor_plugins] 57 | 58 | enabled=PoolStringArray( "bounding_box_resize", "color_palette", "gdquest_docker", "rich_text_editor" ) 59 | 60 | [rendering] 61 | 62 | environment/default_environment="res://default_env.tres" 63 | --------------------------------------------------------------------------------