Files
mtgodot-poc/project/ui/mouse_controller.gd
T

180 lines
6.0 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MouseController —— ClientVS22 mousemodule.py 的全局物品拖放层。
#
# 目标控件通过 register_target() 注册;物品源调用 attach_item() 后,控制器
# 接管鼠标释放,命中目标时调用回调,释放到窗口外 / 右键 / ESC / 失焦则取消。
# 光标状态由 CursorManager 管理,跟随图标不参与世界点击。
extends Node
const CursorManager = preload("res://ui/cursor_manager.gd")
var cursor: Node
var audio: Node
var _overlay: Control
var _icon: TextureRect
var _label: Label
var _attached: Dictionary = {}
var _targets: Array[Dictionary] = []
var _touch_index := -1
var _touch_position := Vector2.ZERO
func setup(parent: Node, cursor_manager: Node = null, audio_node: Node = null) -> void:
cursor = cursor_manager
audio = audio_node
_overlay = Control.new()
_overlay.name = "MouseItemOverlay"
_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
_overlay.z_index = 1000
_overlay.visible = false
_overlay.size = Vector2(72, 72)
parent.add_child(_overlay)
_icon = TextureRect.new()
_icon.position = Vector2(0, 0)
_icon.size = Vector2(48, 48)
_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
_overlay.add_child(_icon)
_label = Label.new()
_label.position = Vector2(2, 48)
_label.size = Vector2(68, 20)
_label.add_theme_font_size_override("font_size", 10)
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
_overlay.add_child(_label)
set_process_input(true)
func attach_item(window: int, cell: int, vnum: int, count: int,
texture: Texture2D = null, source := "inventory", metadata: Dictionary = {},
touch_index := -1, touch_position := Vector2(-1, -1)) -> bool:
if vnum <= 0 or window < 0 or cell < 0:
return false
_attached = {"window": window, "cell": cell, "vnum": vnum,
"count": maxi(1, count), "source": source}
_touch_index = touch_index
if touch_position.x >= 0.0 and touch_position.y >= 0.0:
_touch_position = touch_position
# Keep the instance fields that the reference mouse module exposes to the
# receiving window (anti flags, sockets, attributes, ...), while retaining
# the canonical source coordinates supplied by the caller.
for key in metadata:
if key not in ["window", "cell", "vnum", "count", "source"]:
_attached[key] = metadata[key]
if _icon:
_icon.texture = texture
_icon.visible = texture != null
if _label:
_label.text = "#%d%s" % [vnum, (" ×%d" % count) if count > 1 else ""]
_label.visible = true
if _overlay:
_overlay.visible = true
if cursor and cursor.has_method("set_cursor"):
cursor.set_cursor(CursorManager.ITEM)
_play_ui("pick.wav")
return true
func attach_money(amount: int, source := "inventory") -> bool:
if amount <= 0:
return false
_attached = {"window": -1, "cell": -1, "vnum": -1, "count": amount,
"source": source, "money": true}
_touch_index = -1
_touch_position = Vector2.ZERO
if _icon:
_icon.texture = null
_icon.visible = false
if _label:
_label.text = "金币 ×%d" % amount
_label.visible = true
if _overlay:
_overlay.visible = true
if cursor and cursor.has_method("set_cursor"):
cursor.set_cursor(CursorManager.ITEM)
_play_ui("pick.wav")
return true
func cancel() -> void:
_attached.clear()
_touch_index = -1
_touch_position = Vector2.ZERO
if _overlay:
_overlay.visible = false
if cursor and cursor.has_method("reset"):
cursor.reset()
func is_attached() -> bool:
return not _attached.is_empty()
func attached() -> Dictionary:
return _attached.duplicate(true)
func register_target(control: Control, callback: Callable, owner: Node = null) -> void:
if control == null or not callback.is_valid():
return
_targets.append({"control": control, "callback": callback, "owner": owner})
func unregister_owner(owner: Node) -> void:
for i in range(_targets.size() - 1, -1, -1):
if _targets[i].get("owner", null) == owner:
_targets.remove_at(i)
func _process(_dt: float) -> void:
if _overlay and _overlay.visible:
var position := get_viewport().get_mouse_position()
if _touch_index >= 0:
position = _touch_position
_overlay.global_position = position + Vector2(12, 12)
# 清理已被窗口销毁的目标,防止拖放回调落到旧 UI。
for i in range(_targets.size() - 1, -1, -1):
var c: Control = _targets[i].get("control", null)
if c == null or not is_instance_valid(c):
_targets.remove_at(i)
func _input(event: InputEvent) -> void:
if not is_attached():
return
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
cancel()
get_viewport().set_input_as_handled()
return
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
cancel()
get_viewport().set_input_as_handled()
return
if event is InputEventMouseButton and not event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
_drop_at(event.position)
get_viewport().set_input_as_handled()
return
if event is InputEventScreenDrag and event.index == _touch_index:
_touch_position = event.position
get_viewport().set_input_as_handled()
return
if event is InputEventScreenTouch and not event.pressed and event.index == _touch_index:
_touch_position = event.position
_drop_at(event.position)
get_viewport().set_input_as_handled()
func _drop_at(position: Vector2) -> void:
var payload := attached()
for i in range(_targets.size() - 1, -1, -1):
var c: Control = _targets[i].get("control", null)
if c == null or not is_instance_valid(c):
_targets.remove_at(i)
continue
if not c.visible or not c.get_global_rect().has_point(position):
continue
var cb: Callable = _targets[i].get("callback", Callable())
if cb.is_valid() and bool(cb.call(payload)):
_play_ui("drop.wav")
cancel()
return
# 与参考端释放到无效区域的取消语义一致。
_play_ui("loginfail.wav")
cancel()
func _play_ui(name: String) -> void:
if audio and audio.has_method("play_ui"):
audio.play_ui(name)
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_WINDOW_FOCUS_OUT:
cancel()