extends Node2D signal element_selected(index: int) signal element_property_changed(index: int, position: Vector2, rotation: float, scale: Vector2, skew: float) signal element_action(index: int, action: String, data: Dictionary) signal asset_drop(asset_name: String, position: Vector2) @onready var camera = $Camera2D @onready var root: Sprite2D = $Root @onready var canvas_layer: CanvasLayer = $CanvasLayer @onready var free_transform: Sprite2D = $FreeTransform @onready var xaxis: Line2D = $Xaxis @onready var yaxis: Line2D = $Yaxis @onready var canvas_container: Control = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer @onready var corner: Panel = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer/Corner1 @onready var h_ruler: Panel = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer/HRuler @onready var v_ruler: Panel = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/VRuler @onready var tool_bar_panel: Panel = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer/ToolBarPanel @onready var button_0: Button = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer/ToolBarPanel/VBoxContainer/Panel/HBoxContainer/Button0 @onready var button_1: Button = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer/ToolBarPanel/VBoxContainer/Panel/HBoxContainer/Button1 @onready var button_2: Button = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer/ToolBarPanel/VBoxContainer/Panel/HBoxContainer/Button2 @onready var button_3: Button = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer/ToolBarPanel/VBoxContainer/Panel/HBoxContainer/Button3 @onready var button_4: Button = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer/ToolBarPanel/VBoxContainer/Panel/HBoxContainer/Button4 @onready var asset_listview: Tree = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer2/CanvasContainer/ToolBarPanel/VBoxContainer/AssetListview @onready var v_scroll = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer2/VScrollBar @onready var h_scroll = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer3/HScrollBar @onready var zoom_line_edit: LineEdit = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer3/PanelContainer/HBoxContainer/ZoomLineEdit @onready var mouse_position_label: LineEdit = $CanvasLayer/CanvasRoot/HBoxContainer/VBoxContainer/HBoxContainer3/PanelContainer/HBoxContainer/MousePositionLabel var texture_up: Texture2D = preload("res://Resources/UI/Up.png") var texture_down: Texture2D = preload("res://Resources/UI/Down.png") var texture_delete: Texture2D = preload("res://Resources/UI/Trash.png") # 相机控制参数 var min_zoom: int = 5 var max_zoom: int = 1600 var current_zoom: int = 25 var allow_viewport: Rect2 # 拖动相关变量 var is_ready_dragging = false var is_dragging = false var camera_start_position = Vector2.ZERO var mouse_start_screen_position # element var sprites = {} var sprites_names = [] var current_element: SpriteElement # var bg_buttons:Array[Button] = [] var editable = false var mouse_in_canvas = false # use in all canvas static var bg_color; static var current_bg_button_index = -1; func _ready(): current_element = null init_gui() RenderingServer.set_default_clear_color(Color(0.098, 0.098, 0.098)) set_background(Vector2(2048, 2048), 0) zoom_camera(current_zoom) func _is_visible_in_tree(): var parent_visible = true var p = get_parent() while p != null: if p is Control and p.visible == false: parent_visible = false break p = p.get_parent() return parent_visible func _input(event): if not _is_visible_in_tree(): return if event is InputEventMouseButton: # 滚轮放大 if event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed: _zoom_camera_by_mouse_wheel(true) # 滚轮缩小 elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN and event.pressed: _zoom_camera_by_mouse_wheel(false) # 双击确认变换 elif event.button_index == MOUSE_BUTTON_LEFT and event.double_click: if asset_listview.get_column_at_position(asset_listview.get_local_mouse_position()) == -1: cancle_free_transform() else: get_viewport().set_input_as_handled() # 右键拖拽画布 elif event.button_index == MOUSE_BUTTON_RIGHT: if event.pressed: # 开始拖动 is_dragging = true camera_start_position = camera.position mouse_start_screen_position = get_viewport().get_mouse_position() get_viewport().set_input_as_handled() else: # 结束拖动 is_dragging = false # 空格+左键拖拽画布 elif event.button_index == MOUSE_BUTTON_LEFT and Input.is_key_pressed(KEY_SPACE): # 检查空格键是否同时被按下 if event.pressed: # 开始拖动 is_dragging = true camera_start_position = camera.position mouse_start_screen_position = get_viewport().get_mouse_position() get_viewport().set_input_as_handled() else: # 结束拖动 is_dragging = false # 鼠标拖动 elif event is InputEventMouseMotion: var pos = get_global_mouse_position() mouse_position_label.text = "%d, %d" %[int(pos.x), int(pos.y)] if is_dragging: var current_screen_mouse = get_viewport().get_mouse_position() var screen_offset = mouse_start_screen_position - current_screen_mouse var world_offset = screen_offset / camera.zoom var new_position = camera_start_position + world_offset new_position.x = clamp(new_position.x, allow_viewport.position.x, allow_viewport.end.x) new_position.y = clamp(new_position.y, allow_viewport.position.y, allow_viewport.end.y) camera.position = new_position _update_scrollbars() get_viewport().set_input_as_handled() elif event is InputEventKey: if event.pressed and not event.echo: if event.ctrl_pressed and Input.is_key_pressed(KEY_KP_0): zoom_fit() elif event.ctrl_pressed and Input.is_key_pressed(KEY_KP_1): zoom_100() get_viewport().set_input_as_handled() elif Input.is_key_pressed(KEY_ENTER) or Input.is_key_pressed(KEY_KP_ENTER): cancle_free_transform() func _process(_delta: float) -> void: if not _is_visible_in_tree(): return _update_ruler() var need_update_cursor = false if free_transform.is_dragging == true: need_update_cursor = true elif mouse_in_canvas: var subviewport_container = get_parent().get_parent() if subviewport_container is SubViewportContainer: var pos = subviewport_container.get_local_mouse_position() var rect = tool_bar_panel.get_rect() rect = Rect2(corner.size.x + rect.position.x, corner.size.y, rect.size.x, rect.size.y) if rect.has_point(pos): need_update_cursor = false else: rect = subviewport_container.get_rect() rect = Rect2(corner.size.x, corner.size.y, rect.size.x - corner.size.x * 2, rect.size.y - corner.size.y * 2) if rect.has_point(pos): need_update_cursor = true free_transform.update_free_transform(need_update_cursor) func init_gui(): canvas_container.set_drag_forwarding(Callable(), _on_canvas_can_drop_data, _on_canvas_drop_data) free_transform.value_change.connect(_on_free_transform_value_change) # asset_listview asset_listview.columns = 5 asset_listview.hide_root = true asset_listview.set_column_title(0, "ID") asset_listview.set_column_title(1, "Name") asset_listview.set_column_custom_minimum_width(0, 40) asset_listview.set_column_custom_minimum_width(2, 16) asset_listview.set_column_custom_minimum_width(3, 16) asset_listview.set_column_custom_minimum_width(4, 16) asset_listview.set_column_expand(0, false) asset_listview.set_column_expand(1, true) asset_listview.set_column_expand(2, false) asset_listview.set_column_expand(3, false) asset_listview.set_column_expand(4, false) asset_listview.item_selected.connect(_on_asset_listview_item_selected) asset_listview.nothing_selected.connect(_on_asset_listview_nothing_selected) # bg button bg_buttons.append(button_0) bg_buttons.append(button_1) bg_buttons.append(button_2) bg_buttons.append(button_3) bg_buttons.append(button_4) var button_group = ButtonGroup.new() for i in range(bg_buttons.size()): var btn = bg_buttons[i] btn.button_group = button_group btn.pressed.connect(_on_bg_button_pressed.bind(i)) button_0.button_pressed = true # 连接滚动条信号 v_scroll.value_changed.connect(_on_v_scroll_value_changed) h_scroll.value_changed.connect(_on_h_scroll_value_changed) zoom_line_edit.text_submitted.connect(_on_zoom_line_edit_text_submitted) func _on_bg_button_pressed(index): current_bg_button_index = index bg_buttons[index].button_pressed = true set_background(root.texture.get_size(), index) func _on_v_scroll_value_changed(value): camera.position.y = value _update_scrollbars() func _on_h_scroll_value_changed(value): camera.position.x = value _update_scrollbars() func _on_canvas_can_drop_data(at_position, data): return data is Dictionary and data.get("type") == "asset_panel_drag" func _on_canvas_drop_data(at_position, data): asset_drop.emit(data["data"]["asset_name"], get_global_mouse_position()) func _on_asset_listview_item_selected(): if asset_listview.get_selected() == null: current_element = null free_transform.set_target(null, -1) return var selected_item = asset_listview.get_selected() var index = selected_item.get_index() var column = asset_listview.get_column_at_position(asset_listview.get_local_mouse_position()) match column: 2: element_action.emit(index, "UP", {}) 3: element_action.emit(index, "DOWN", {}) 4: element_action.emit(index, "REMOVE", {}) _: current_element = sprites[int(selected_item.get_text(0))] if selected_item.get_text(1) != "__blank__": free_transform.set_target(current_element, index, editable) else: free_transform.set_target(current_element, index, false) element_selected.emit(index) func _on_asset_listview_nothing_selected(): cancle_free_transform() func _on_zoom_line_edit_text_submitted(new_text: String): var new_zoom = new_text.replace(" ", "").replace("%", "") if new_zoom.is_valid_int(): zoom_camera(int(new_zoom)) elif new_zoom.is_valid_float(): zoom_camera(int(float(new_zoom))) else: zoom_camera(current_zoom) zoom_line_edit.release_focus() func _on_free_transform_value_change(_index: int, _position: Vector2, _rotation: float, _scale: Vector2, _skew: float): var deg = fposmod(rad_to_deg(_rotation), 360) element_property_changed.emit(_index, _position, deg, _scale, _skew) func _zoom_camera_by_mouse_wheel(zoom_up: bool): var zoom_step = 0; if current_zoom < 100: zoom_step = 5 elif current_zoom < 300: zoom_step = 20 elif current_zoom < 500: zoom_step = 25 elif current_zoom < 800: zoom_step = 50 elif current_zoom <= 1600: zoom_step = 100 if zoom_up: zoom_camera(current_zoom + zoom_step) else: zoom_camera(current_zoom - zoom_step) func _update_scrollbars(): var bar_size = 0.4 * (allow_viewport.end.x - allow_viewport.position.x) h_scroll.min_value = allow_viewport.position.x h_scroll.max_value = allow_viewport.end.x + bar_size h_scroll.page = bar_size h_scroll.set_value_no_signal(camera.position.x) bar_size = 0.4 * (allow_viewport.end.y - allow_viewport.position.y) v_scroll.min_value = allow_viewport.position.y v_scroll.max_value = allow_viewport.end.y + bar_size v_scroll.page = bar_size v_scroll.set_value_no_signal(camera.position.y) _update_ruler() func _update_ruler(): var viewport_size = get_viewport().get_visible_rect().size var corner_cover_size = corner.size / camera.zoom var visible_rect = viewport_size / camera.zoom var viewport_rect = Rect2(camera.position - visible_rect / 2 + corner_cover_size, visible_rect - 2.0 * corner_cover_size) var ruler_range = Vector4(viewport_rect.position.x, viewport_rect.position.y, viewport_rect.end.x, viewport_rect.end.y) var root_rect = root.get_rect() var highlight_range = Vector4(root_rect.position.x, root_rect.position.y, root_rect.end.x, root_rect.end.y) var mousr_pos = get_local_mouse_position() h_ruler.material.set_shader_parameter("ruler_size", h_ruler.size) h_ruler.material.set_shader_parameter("ruler_range", ruler_range) h_ruler.material.set_shader_parameter("highlight_range", highlight_range) h_ruler.material.set_shader_parameter("mouse_pos", mousr_pos) v_ruler.material.set_shader_parameter("ruler_size", v_ruler.size) v_ruler.material.set_shader_parameter("ruler_range", ruler_range) v_ruler.material.set_shader_parameter("highlight_range", highlight_range) v_ruler.material.set_shader_parameter("mouse_pos", mousr_pos) func zoom_camera(new_zoom: int): current_zoom = clamp(new_zoom, min_zoom, max_zoom) zoom_line_edit.text = "%d%%" % (current_zoom) var zoom = current_zoom / 100.0 camera.zoom = Vector2(zoom, zoom) free_transform.zoom = zoom var visible_size = get_viewport().get_visible_rect().size / camera.zoom var sprite_size = root.texture.get_size() var corner_cover_size = corner.size / camera.zoom xaxis.width = 2.0 / zoom yaxis.width = 2.0 / zoom allow_viewport.position.x = min(-sprite_size.x, sprite_size.x - visible_size.x) / 2 + corner_cover_size.x allow_viewport.end.x = max(sprite_size.x, -sprite_size.x + visible_size.x) / 2 - corner_cover_size.x allow_viewport.position.y = min(-sprite_size.y, sprite_size.y - visible_size.y) / 2 + corner_cover_size.y allow_viewport.end.y = max(sprite_size.y, -sprite_size.y + visible_size.y) / 2 - corner_cover_size.y _update_scrollbars() set_background(root.texture.get_size()) func zoom_fit(): var visible_size = get_viewport().get_visible_rect().size var sprite_size = root.texture.get_size() var zoom = 0.8 * min(visible_size.x, visible_size.y) / max(sprite_size.x, sprite_size.y) zoom_camera(zoom * 100) camera.position = Vector2.ZERO func zoom_100(): zoom_camera(100) camera.position = Vector2.ZERO func clear_all_sprites() -> void: sprites.clear() sprites_names.clear() for child in root.get_children(): if child is Sprite2D: child.queue_free() asset_listview.clear() asset_listview.create_item() # root free_transform.set_target(null, -1) current_element = null func load_elements(elements_data, selected_index = -1): clear_all_sprites() var selected_item = null asset_listview.set_block_signals(true) for i in range(elements_data.size()): var element = elements_data[i] var sprite = SpriteElement.new() var asset_id = int(element["AssetId"]) sprite.texture = AvatarDollDataMgr.get_asset(asset_id) var asset_name = AvatarDollDataMgr.get_asset_name(asset_id) root.add_child(sprite) sprite.position = Vector2(element["PositionX"], element["PositionY"]) sprite.rotation = deg_to_rad(element["Rotation"]) sprite.scale = Vector2(element["ScaleX"], element["ScaleY"]) sprite.set_shader_param({}) var item = asset_listview.create_item() item.set_cell_mode(2, TreeItem.CELL_MODE_ICON) item.set_cell_mode(3, TreeItem.CELL_MODE_ICON) item.set_cell_mode(4, TreeItem.CELL_MODE_ICON) item.set_text(0, str(i)) item.set_text(1, asset_name) if editable: item.set_icon(2, texture_up) item.set_icon(3, texture_down) item.set_icon(4, texture_delete) if i == selected_index: selected_item = item sprites[i] = sprite sprites_names.append(asset_name) if selected_item: asset_listview.set_selected(selected_item, 0) current_element = sprites[int(selected_item.get_text(0))] if selected_item.get_text(1) != "__blank__": free_transform.set_target(current_element, selected_index, editable) else: free_transform.set_target(current_element, selected_index, false) asset_listview.set_block_signals(false) func update_element(index, element): var sprite = sprites[index] sprite.position = Vector2(element["PositionX"], element["PositionY"]) sprite.rotation = deg_to_rad(element["Rotation"]) sprite.scale = Vector2(element["ScaleX"], element["ScaleY"]) free_transform.position = sprite.position free_transform.rotation = sprite.rotation free_transform.scale = sprite.scale func load_icon(icon_asset_id): clear_all_sprites() var asset_id = int(icon_asset_id) var asset_name = AvatarDollDataMgr.get_asset_name(asset_id) var item = asset_listview.create_item() item.set_cell_mode(2, TreeItem.CELL_MODE_ICON) item.set_cell_mode(3, TreeItem.CELL_MODE_ICON) item.set_cell_mode(4, TreeItem.CELL_MODE_ICON) item.set_text(0, "0") item.set_text(1, asset_name) if editable: item.set_icon(2, texture_up) item.set_icon(3, texture_down) item.set_icon(4, texture_delete) var sprite = SpriteElement.new() sprite.texture = AvatarDollDataMgr.get_asset(asset_id) sprites[0] = sprite root.add_child(sprite) func update_palette(element_index, palettes): if element_index >= 0 and element_index < sprites.size(): var sprite = sprites[element_index] sprite.set_shader_param(palettes) func sync_background(): if current_bg_button_index != -1: bg_buttons[current_bg_button_index].button_pressed = true func set_background(size: Vector2, background_type: int = -1): if root.texture == null or root.texture.get_size() != size: var image = Image.create(int(size.x), int(size.y), false, Image.FORMAT_RGBA8) image.fill(Color(0, 0, 0, 0)) root.texture = ImageTexture.create_from_image(image) if background_type == 0: bg_color = Color.TRANSPARENT elif background_type == 1: bg_color = Color.WHITE elif background_type == 2: bg_color = Color(0.5, 0.5, 0.5, 1.0) elif background_type == 3: bg_color = Color(0.25, 0.25, 0.25, 1.0) elif background_type == 4: bg_color = Color.BLACK root.material.set_shader_parameter("bg_color", bg_color) root.material.set_shader_parameter("zoom", current_zoom / 100.0) func set_editable(_editable: bool): editable = _editable if editable == false: free_transform.set_target(null, -1) func set_selected(index): asset_listview.deselect_all() if index >= 0: current_element = sprites[index] asset_listview.set_selected(asset_listview.get_root().get_child(index), 0) func cancle_free_transform(): asset_listview.deselect_all() free_transform.set_target(null, -1) current_element = null element_selected.emit(-1) func update_mouse_in_canvas(b: bool): mouse_in_canvas = b