diff --git a/addons/ColorPreview/ColorPreview.gd b/addons/ColorPreview/ColorPreview.gd index 9942eb9..097612f 100644 --- a/addons/ColorPreview/ColorPreview.gd +++ b/addons/ColorPreview/ColorPreview.gd @@ -2,28 +2,64 @@ extends EditorPlugin var editor: Node = null -var editors = [] -var gutter_position = 0 -var gutter_name = "color_preview" -var gutter_texture = preload("res://addons/ColorPreview/Preview.png") +var editors: Array = [] +var current_textedit: TextEdit +var hovering_line = null +var gutter_position: int = 0 +var preview_gutter_name: String = "color_preview" +var picker_popup: Popup func _enter_tree() -> void: - var editor = get_editor_interface() - var script_editor = editor.get_script_editor() - script_editor.connect("editor_script_changed", editor_script_changed) - get_all_text_editors(script_editor) + initialize_picker() + initialize_gutter() func _exit_tree() -> void: - var editor = get_editor_interface() - var script_editor = editor.get_script_editor() + exit_picker() + exit_gutter() + + +func initialize_picker() -> void: + if not picker_popup: + picker_popup = preload("res://addons/ColorPreview/picker.tscn").instantiate() + picker_popup.connect("popup_hide", on_picker_popup_close) + picker_popup.hide() + + var picker = picker_popup.get_node("ColorPicker") + if not picker.is_connected("color_changed", picker_color_changed): + picker.connect("color_changed", picker_color_changed) + + var editor := get_editor_interface() + if not picker_popup.is_inside_tree() or not editor.has_node(picker_popup.get_path()): + editor.add_child(picker_popup) + + +func exit_picker() -> void: + if picker_popup: + picker_popup.queue_free() + + +func initialize_gutter() -> void: + var script_editor := get_editor_interface().get_script_editor() + if not script_editor.is_connected("editor_script_changed", editor_script_changed): + script_editor.connect("editor_script_changed", editor_script_changed) + get_all_text_editors(script_editor) + for child in editors: + if child and is_instance_valid(child) and child is TextEdit: + add_color_gutter(child) + + +func exit_gutter() -> void: + var script_editor := get_editor_interface().get_script_editor() get_all_text_editors(script_editor) for child in editors: if child is TextEdit: remove_color_gutter(child) +### ### ### GETTING AND KEEPING EDITORS ### ### ### + func get_all_text_editors(parent : Node) -> void: for child in parent.get_children(): if child.get_child_count(): @@ -39,117 +75,218 @@ func get_all_text_editors(parent : Node) -> void: if child.is_connected("caret_changed", caret_changed): child.disconnect("caret_changed", caret_changed) child.connect("caret_changed", caret_changed, [child]) - + func caret_changed(textedit: TextEdit) -> void: - var editor = get_editor_interface() - # just in case the editor instance changes - if not editors.has(textedit): - editors.clear() - get_all_text_editors(editor.get_script_editor()) - preview_colors(textedit) + handle_change(textedit) func text_changed(textedit : TextEdit) -> void: + handle_change(textedit) + + +func handle_change(textedit : TextEdit) -> void: + current_textedit = textedit + if not current_textedit.is_connected("gui_input", textedit_clicked): + current_textedit.connect("gui_input", textedit_clicked, [textedit]) + var editor = get_editor_interface() if not editors.has(textedit): editors.clear() get_all_text_editors(editor.get_script_editor()) - preview_colors(textedit) + get_colors_from_textedit(textedit) func editor_script_changed(script: Script) -> void: + initialize_picker() + initialize_gutter() + if current_textedit: + if current_textedit.is_connected("gui_input", textedit_clicked): + current_textedit.disconnect("gui_input", textedit_clicked) + current_textedit = null var editor = get_editor_interface() var script_editor = editor.get_script_editor() editors.clear() get_all_text_editors(script_editor) - -func add_color_gutter(textedit: TextEdit) -> void: - var has_preview := false - for gutter in textedit.get_gutter_count() - 1: - if textedit.get_gutter_name(gutter) == gutter_name: - has_preview = true - if not has_preview: - textedit.add_gutter(gutter_position) - textedit.set_gutter_type(gutter_position, TextEdit.GUTTER_TYPE_ICON) - textedit.set_gutter_name(gutter_position, gutter_name) - textedit.set_gutter_width(gutter_position, 35) +### ### ### HANDLING GUTTERS ### ### ### -# custom gutter with color picker? not figured out yet -# textedit.set_gutter_clickable(gutter_position, true) -# textedit.set_gutter_type(gutter_position, TextEdit.GUTTER_TYPE_CUSTOM) -# textedit.set_gutter_custom_draw(gutter_position, custom_draw) -# textedit.set_gutter_draw(gutter_position, true) +func has_color_preview_gutter(textedit: TextEdit) -> bool: + for gutter_index in textedit.get_gutter_count() - 1: + if gutter_index < 0: + continue + if textedit.get_gutter_name(gutter_index) == preview_gutter_name: + return true + return false -#func custom_draw(line: int, gutter: int, area: Rect2): -# var picker = preload("res://addons/ColorPreview/Picker.tscn") -# picker.instantiate() + +func add_color_gutter(textedit: TextEdit) -> void: + if has_color_preview_gutter(textedit): + return + current_textedit = textedit + textedit.add_gutter(gutter_position) + textedit.set_gutter_width(gutter_position, 35) + textedit.set_gutter_name(gutter_position, preview_gutter_name) + textedit.set_gutter_type(gutter_position, TextEdit.GUTTER_TYPE_CUSTOM) + textedit.set_gutter_custom_draw(gutter_position, gutter_draw_color_preview) func remove_color_gutter(textedit: TextEdit) -> void: for gutter_index in textedit.get_gutter_count() - 1: - if textedit.get_gutter_name(gutter_index) == "color_preview": + if gutter_index < 0: + continue + var name = textedit.get_gutter_name(gutter_index) + if name == preview_gutter_name: textedit.remove_gutter(gutter_position) -func preview_colors(textedit: TextEdit) -> void: - var all_lines = textedit.text.split("\n") - var has_color = false +func gutter_draw_color_preview(line: int, gutter: int, area: Rect2) -> void: + if not current_textedit or line > current_textedit.get_line_count() - 1: + return + + var size: int + var offset := Vector2.ZERO + # centering the preview square in the line because perfectionism + if area.size.x < area.size.y: + size = area.size.x + offset = Vector2(0, (area.size.y - area.size.x) /2) + else: + size = area.size.y + offset = Vector2((area.size.x - area.size.y) /2, 0) + var icon_region := Rect2(area.position + offset, Vector2(size, size)) + # spacing the squares so they don't merge + var padding = size / 6 + icon_region.position += Vector2(padding, padding) + icon_region.size -= Vector2(padding, padding) * 2 + + var mouse_pos := current_textedit.get_local_mouse_pos() + var hovering := area.has_point(mouse_pos) + var line_color = get_line_color(current_textedit, line) + + # black is falsey, comparing to null allows us to preview it + if line_color != null: + current_textedit.draw_rect(icon_region, line_color) + current_textedit.set_line_gutter_clickable(line, gutter_position, true) + + if hovering: + hovering_line = line + else: + if hovering: + hovering_line = null + + # not captured by the hovering check above + # if left or right of gutter, forget the line + if mouse_pos.x > area.end.x or mouse_pos.x < area.position.x: + # in case the picker is open, remember the line even when moving away + if not picker_popup or not picker_popup.visible: + hovering_line = null + + +func get_colors_from_textedit(textedit: TextEdit) -> void: + var all_lines = textedit.text.split("\n") + var has_color := false + for line_index in len(all_lines): var color = color_from_string(all_lines[line_index]) if color != null: has_color = true - add_color_gutter(textedit) - textedit.set_line_gutter_icon(line_index, gutter_position, gutter_texture) - textedit.set_line_gutter_item_color(line_index, gutter_position, color) + set_line_color(textedit, line_index, color) + textedit.set_gutter_draw(gutter_position, true) else: - textedit.set_line_gutter_icon(line_index, gutter_position, null) - + set_line_color(textedit, line_index) + if not has_color: - remove_color_gutter(textedit) + textedit.set_gutter_draw(gutter_position, false) + +func set_line_color(textedit: TextEdit, line: int, color = null) -> void: + textedit.set_line_gutter_metadata(line, gutter_position, color) -func color_from_string(string: String): + +func get_line_color(textedit: TextEdit, line: int): + var meta = textedit.get_line_gutter_metadata(line, gutter_position) + return meta if meta != null else null + + +### ### ### COLOR PICKER ### ### ### + +func textedit_clicked(event: InputEvent, textedit: TextEdit) -> void: + if hovering_line != null: + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: + var color = get_line_color(textedit, hovering_line) + if color != null: + picker_popup.get_node("ColorPicker").color = color + picker_popup.position = event.global_position + picker_popup.popup() + + +func picker_color_changed(new_color: Color) -> void: + if hovering_line and current_textedit: + set_line_color(current_textedit, hovering_line, new_color) + + +func on_picker_popup_close() -> void: + if not hovering_line: + return + var text := current_textedit.get_line(hovering_line) + var color_match : RegExMatch = match_color_in_string(text) + var new_color = get_line_color(current_textedit, hovering_line) + text = text.replace(color_match.get_string(), "Color" + str(new_color)) + current_textedit.set_line(hovering_line, text) + + +### ### ### COLOR INTERPRETATION ### ### ### + +func color_from_string(string: String): # Color or null var color_match = match_color_in_string(string) if !color_match: return null return color_from_regex_match(color_match) -func color_from_regex_match(regex_match: RegExMatch): +func color_from_regex_match(regex_match: RegExMatch): # Color or null var color_const = regex_match.get_string("const") - if color_const and Color.find_named_color(color_const) != -1: + if color_const != null and Color.find_named_color(color_const) != -1: return Color(color_const) var params = regex_match.get_string("params") if not "," in params: var color = color_from_string(params) - if color: + if color != null: return color color = named_or_hex_color(params) - if color: + if color != null: return color var parameters = params.split(",") match len(parameters): 2: var color = color_from_string(parameters[0]) - if color: + if color != null: return Color(color, parameters[1].to_float()) color = named_or_hex_color(parameters[0]) - if color: + if color != null: return Color(color , parameters[1].to_float()) 3: - return Color(parameters[0].to_float(), parameters[1].to_float(), parameters[2].to_float()) + return Color( + parameters[0].to_float(), + parameters[1].to_float(), + parameters[2].to_float(), + ) 4: - return Color(parameters[0].to_float(), parameters[1].to_float(), parameters[2].to_float(), parameters[3].to_float()) + return Color( + parameters[0].to_float(), + parameters[1].to_float(), + parameters[2].to_float(), + parameters[3].to_float(), + ) return null -func named_or_hex_color(string: String): +func named_or_hex_color(string: String): # Color or null string = string.trim_prefix("\"").trim_prefix("\'").trim_suffix("\"").trim_suffix("\'") if string.is_valid_html_color() or Color.find_named_color(string) != -1: return Color(string) @@ -160,7 +297,7 @@ func match_color_in_string(string: String) -> RegExMatch: var re = RegEx.new() re.compile("Color\\((?(?R)*.*?)\\)") var color = re.search(string) - if color: + if color != null: return color re.compile("Color\\.(?[A-Z_]+)\\b") return re.search(string) diff --git a/addons/ColorPreview/Picker.tscn b/addons/ColorPreview/Picker.tscn index 2298f09..e25ab35 100644 --- a/addons/ColorPreview/Picker.tscn +++ b/addons/ColorPreview/Picker.tscn @@ -1,7 +1,17 @@ -[gd_scene format=3 uid="uid://busnw0ulacpl8"] +[gd_scene format=3 uid="uid://bd8slp7oko0my"] -[node name="Picker" type="ColorPickerButton"] -offset_right = 30.0 -offset_bottom = 31.0 -theme_override_colors/font_outline_color = Color(1, 1, 1, 1) -theme_override_colors/font_color = Color(1, 1, 1, 1) +[node name="Popup" type="Popup"] +size = Vector2i(465, 775) +min_size = Vector2i(465, 775) +content_scale_mode = 1 +content_scale_aspect = 1 +content_scale_factor = 1.5 + +[node name="ColorPicker" type="ColorPicker" parent="."] +offset_left = 9.0 +offset_top = 9.0 +offset_right = 299.0 +offset_bottom = 506.0 +__meta__ = { +"_edit_use_custom_anchors": false +} diff --git a/addons/ColorPreview/Preview.png b/addons/ColorPreview/Preview.png deleted file mode 100644 index 33fdd8f..0000000 Binary files a/addons/ColorPreview/Preview.png and /dev/null differ diff --git a/addons/ColorPreview/Preview.png.import b/addons/ColorPreview/Preview.png.import deleted file mode 100644 index 072a773..0000000 --- a/addons/ColorPreview/Preview.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="StreamTexture2D" -uid="uid://dt4wh3tc2hog1" -path="res://.godot/imported/Preview.png-3765252390ba5620fbce9250a386553a.stex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://addons/ColorPreview/Preview.png" -dest_files=["res://.godot/imported/Preview.png-3765252390ba5620fbce9250a386553a.stex"] - -[params] - -compress/mode=0 -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/bptc_ldr=0 -compress/normal_map=2 -compress/channel_pack=0 -compress/streamed=false -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/size_limit=0 -detect_3d/compress_to=1 diff --git a/addons/ColorPreview/plugin.cfg b/addons/ColorPreview/plugin.cfg index cbb7e98..2d7c8de 100644 --- a/addons/ColorPreview/plugin.cfg +++ b/addons/ColorPreview/plugin.cfg @@ -1,7 +1,7 @@ [plugin] name="ColorPreview" -description="" +description="An Editor Plugin which lets you preview and adjust colors from code directly next to them" author="Ste" -version="1.0" +version="2.0" script="ColorPreview.gd" diff --git a/colors.png b/colors.png index 7f0a96b..85a9158 100644 Binary files a/colors.png and b/colors.png differ diff --git a/colors_demo.gd b/colors_demo.gd index d2cc81b..acc6f17 100644 --- a/colors_demo.gd +++ b/colors_demo.gd @@ -1,6 +1,7 @@ extends Node + func show_off_all_the_pretty_colors(): Color("#ffbb00") # hex colors Color("#9f4", 0.8) # short hex, with alpha @@ -10,7 +11,12 @@ func show_off_all_the_pretty_colors(): Color(Color.DEEP_SKY_BLUE, 0.6) # and alpha Color(1, 0.2, 0.5) # rgb Color(1, 0.2, 0.5, 0.6) # rgba - Color(Color("medium spring green")) # nesting + Color(Color("medium spring green")) # nesting Color(Color("#ff0000"), 0.5) # with alpha - Color(Color(Color(Color.DARK_ORANGE, 0.7))) # even this here + Color(Color(Color(Color.DARK_ORANGE, 0.8))) # even this here + + + Color.DARK_TURQUOISE # click the preview + Color(0.9727, 0.019, 0.4229, 0.9255) # to get a color picker + Color(0.9297, 0.852, 0.0254, 1) # and change colors directly