class_name ColorGradientEditor extends Control # Handle width in pixels const HANDLE_WIDTH : int = 8 ## Edited Gradient resource @export var gradient : Gradient : set(value): if gradient != null: gradient.changed.disconnect(_update_viewer) gradient = value gradient.changed.connect(_update_viewer) if gradient_texture: gradient_texture.gradient = gradient @onready var gradient_texture : GradientTexture1D @onready var gradient_viewer : TextureRect = $%GradientViewer as TextureRect @onready var color_picker : ColorPickerButton = $%ColorPickerButton as ColorPickerButton @onready var cues : ColorRect = $%Cues as ColorRect @onready var handles : Array[Rect2] = [] @onready var dragged : int = -1 @onready var selected : int = -1 func _ready() -> void: gradient_texture = GradientTexture1D.new() gradient_texture.gradient = gradient gradient_texture.changed.connect(gradient_viewer.queue_redraw) gradient_texture.changed.connect(cues.queue_redraw) gradient_viewer.texture = gradient_texture gradient_viewer.resized.connect(_resize_color_picker) cues.draw.connect(_draw_cues) cues.gui_input.connect(_cues_input) color_picker.color_changed.connect(_change_selected_color) ## Update color for the selected control point func _change_selected_color(color : Color) -> void: if selected >= 0: gradient.set_color(selected, color) ## Input event management on handles func _cues_input(event: InputEvent) -> void: var mb : InputEventMouseButton = event as InputEventMouseButton if mb: if mb.button_index == MOUSE_BUTTON_LEFT: if mb.pressed: # Is there an handle at mouse cursor ? dragged = _get_point_at(mb.position) if dragged < 0: var offset : float = mb.position.x / cues.size.x var color : Color = gradient.sample(offset) gradient.add_point(offset, color) color_picker.color = color cues.queue_redraw() await cues.draw selected = _get_point_at(mb.position) else: color_picker.color = gradient.get_color(dragged) selected = dragged else: dragged = -1 accept_event(); return if mb.button_index == MOUSE_BUTTON_MIDDLE and mb.pressed and gradient.get_point_count() > 1: var sel : int = _get_point_at(mb.position) if sel >= 0: gradient.remove_point(sel) accept_event(); return if dragged >= 0: var mm : InputEventMouseMotion = event as InputEventMouseMotion if not mm: return var ratio : float = clampf(mm.position.x / cues.size.x, 0., 1.) var previous_ratio : float = gradient.get_offset(dragged) var new_idx : int = _get_last_handle(ratio) if previous_ratio <= ratio else _get_first_handle(ratio) if new_idx != dragged: var sw_col : Color = gradient.get_color(new_idx) var sw_ratio : float = gradient.get_offset(new_idx) var cur_col : Color = gradient.get_color(dragged) gradient.set_offset(dragged, sw_ratio) gradient.set_color(new_idx, cur_col) gradient.set_color(dragged, sw_col) dragged = new_idx selected = new_idx else: gradient.set_offset(dragged, ratio) accept_event() func _get_last_handle(end : float) -> int: var current : int = dragged var count : int = gradient.get_point_count() while current < count and gradient.get_offset(current) <= end: current += 1 return current - 1 if current != dragged else dragged func _get_first_handle(start : float) -> int: var current : int = dragged while current >= 0 and gradient.get_offset(current) >= start: current -= 1 return current + 1 ## Return the handle id (and thus, the point id) at the local mouse position func _get_point_at(pos : Vector2) -> int: for i : int in range(handles.size()): if handles[i].has_point(pos): return i return -1 ## Resize the color picker according to the height of the neighbor gradient viewer func _resize_color_picker() -> void: var s : float = gradient_viewer.size.y color_picker.custom_minimum_size = Vector2(s, s) reset_size() ## Update the gradient viewer TextureRect func _update_viewer() -> void: if not gradient_viewer: return gradient_viewer.queue_redraw() cues.queue_redraw() ## Redraw the handles/cues func _draw_cues() -> void: handles.clear() var sz : Vector2 = cues.size cues.draw_rect(Rect2(Vector2.ZERO, sz), Color.TRANSPARENT) if not gradient: return var cue_box_height : float = sz.y / 3. var count : int = gradient.get_point_count() for i : int in range(count): var offset : float = gradient.get_offset(i) * sz.x var color : Color = gradient.get_color(i) var cue_border_color : Color = Color.WHITE if color.ok_hsl_l < 0.5 else Color.BLACK cues.draw_line(Vector2(offset, 0), Vector2(offset, cue_box_height * 2.), cue_border_color, 2) # Create the handles while we're at it. var start_handle : float = max(0, offset - (HANDLE_WIDTH / 2.)) var end_handle : float = start_handle + HANDLE_WIDTH if end_handle > sz.y: # Unless we use a VERY small area (< 8 pixels large), handle shall be drawn propertly start_handle = end_handle - HANDLE_WIDTH var handle_rect : Rect2 = Rect2(Vector2(start_handle, cue_box_height * 2.), Vector2(HANDLE_WIDTH, cue_box_height)) handles.append(handle_rect) cues.draw_rect(handle_rect, color) cues.draw_rect(handle_rect, cue_border_color, false, 2)
or share this direct link: