extends Control ## 画脸面板:把 FaceRig 的笔画层显示出来,并把鼠标坐标换算成画布坐标喂给画笔。 ## ## 面板自己不画任何东西 —— 笔画直接落在 FaceRig 的画笔上,所以关掉面板后角色脸上立刻就是刚画的。 ## 打开时拍一张快照,点「取消」还原;点「完成」才写进存档。 signal finished signal cancelled ## 画布在屏幕上的显示边长。真实画布是 rig.canvas_size 见方,坐标按比例换算 const VIEW_SIZE := 420 @onready var _canvas_rect: TextureRect = %StrokeRect @onready var _preset_rect: TextureRect = %PresetRect @onready var _color_button: ColorPickerButton = %ColorButton @onready var _width_slider: HSlider = %WidthSlider @onready var _width_label: Label = %WidthLabel @onready var _eraser_toggle: CheckButton = %EraserToggle @onready var _name_edit: LineEdit = %NameEdit var _rig: FaceRig ## 打开面板时的笔画层快照,取消时还原 var _snapshot: PackedByteArray ## 打开面板时选中的表情 ID,取消时还原 var _expression_before: String = "" ## 左键是不是按着 —— 拖拽判定用这个,不看 motion 事件里的 button_mask var _drawing := false func _ready() -> void: visible = false _canvas_rect.custom_minimum_size = Vector2(VIEW_SIZE, VIEW_SIZE) _canvas_rect.gui_input.connect(_on_canvas_input) _color_button.color_changed.connect(_on_color_changed) _width_slider.value_changed.connect(_on_width_changed) _eraser_toggle.toggled.connect(_on_eraser_toggled) %ClearButton.pressed.connect(_on_clear) %DoneButton.pressed.connect(_on_done) %CancelButton.pressed.connect(_on_cancel) _on_width_changed(_width_slider.value) # ---------------- 对外接口 ---------------- ## 打开面板并绑定到 FaceRig。start_blank = true 时先快照再清空,取消可完整还原。 func open(rig: FaceRig, start_blank: bool = false) -> void: if rig == null or rig.painter == null: push_warning("画脸面板没拿到可用的 FaceRig") return _rig = rig _snapshot = rig.painter.snapshot() _expression_before = rig.selected_expression_id if start_blank: rig.select_blank(true) _canvas_rect.texture = rig.painter.texture %SkinRect.color = rig.skin_color _preset_rect.texture = rig.selected_expression_texture _name_edit.text = "" rig.painter.brush_color = _color_button.color rig.painter.brush_width = _width_slider.value rig.painter.erasing = _eraser_toggle.button_pressed visible = true # ---------------- 画布输入 ---------------- ## 屏幕坐标 → 画布坐标。显示尺寸和画布尺寸不一致时按比例缩 func _canvas_pos(event: InputEventMouse) -> Vector2: var scale := float(_rig.painter.canvas_size) / maxf(1.0, _canvas_rect.size.x) return event.position * scale func _on_canvas_input(event: InputEvent) -> void: if _rig == null: return if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: # 自己记「按没按下」,不靠 motion 事件里的 button_mask —— 那个字段不总是可靠 _drawing = event.pressed if event.pressed: _rig.painter.begin_stroke(_canvas_pos(event)) else: _rig.painter.end_stroke() elif event is InputEventMouseMotion and _drawing: _rig.painter.extend_stroke(_canvas_pos(event)) # ---------------- 工具条 ---------------- func _on_color_changed(c: Color) -> void: if _rig != null: _rig.painter.brush_color = c # 选了颜色就顺手退出橡皮 —— 不然「我选了红色怎么还是擦」 _eraser_toggle.set_pressed_no_signal(false) if _rig != null: _rig.painter.erasing = false func _on_width_changed(w: float) -> void: if _rig != null: _rig.painter.brush_width = w _width_label.text = "%d px" % int(w) func _on_eraser_toggled(on: bool) -> void: if _rig != null: _rig.painter.erasing = on func _on_clear() -> void: if _rig != null: _rig.painter.clear() func _on_done() -> void: if _rig == null: return var saved := _rig.save_drawing_as_expression(_name_edit.text) if saved.is_empty(): push_warning("表情保存失败,请检查本地磁盘空间和写入权限") return visible = false finished.emit() func _on_cancel() -> void: if _rig != null: if not _expression_before.is_empty(): _rig.select_expression(_expression_before, false) else: _rig.select_blank(false) if _snapshot.size() > 0: _rig.painter.restore(_snapshot) visible = false cancelled.emit()