feat: complete mobile UI implementation
This commit is contained in:
+277
-14
@@ -67,6 +67,18 @@ var _drag_from := -1
|
||||
var _combine_from := -1 # Shift 选中的“使用到物品”来源格
|
||||
var _hint: Label
|
||||
var _money_button: BaseButton
|
||||
var _mobile_mode := false
|
||||
var _mobile_selected_ui := -1
|
||||
var _mobile_press_cell := -1
|
||||
var _mobile_touch_index := -1
|
||||
var _mobile_long_press_token := 0
|
||||
var _mobile_detail_dialog: Control
|
||||
var _mobile_dragging := false
|
||||
var _mobile_drag_ui := -1
|
||||
var _mobile_press_position_value := Vector2.ZERO
|
||||
var _mobile_long_press_ready := false
|
||||
const MOBILE_LONG_PRESS_SECONDS := 0.55
|
||||
const MOBILE_DRAG_THRESHOLD := 14.0
|
||||
|
||||
func setup(ui_manager: CanvasLayer, m2client: Node, proto_node: Node, assets: String,
|
||||
il: RefCounted = null) -> void:
|
||||
@@ -82,15 +94,35 @@ func setup(ui_manager: CanvasLayer, m2client: Node, proto_node: Node, assets: St
|
||||
uiscript_dir = assets.path_join("uiscript")
|
||||
if client and client.has_signal("inventory_changed"):
|
||||
client.inventory_changed.connect(_on_inv_changed)
|
||||
set_process_input(true)
|
||||
|
||||
func is_open() -> bool:
|
||||
return not _win.is_empty() and is_instance_valid(_win.get("root"))
|
||||
|
||||
func set_mobile_mode(enabled: bool) -> void:
|
||||
_mobile_mode = enabled
|
||||
mobile_selected_clear()
|
||||
if enabled:
|
||||
_set_hint("点按物品选择;再次点按使用,点另一格移动")
|
||||
|
||||
func mobile_selected() -> int:
|
||||
return _mobile_selected_ui
|
||||
|
||||
func toggle() -> void:
|
||||
if is_open(): close()
|
||||
else: open()
|
||||
|
||||
func close() -> void:
|
||||
if _mobile_detail_dialog and is_instance_valid(_mobile_detail_dialog) and ui:
|
||||
ui.close(_mobile_detail_dialog)
|
||||
_mobile_detail_dialog = null
|
||||
_mobile_long_press_token += 1
|
||||
_mobile_press_cell = -1
|
||||
_mobile_touch_index = -1
|
||||
_mobile_dragging = false
|
||||
_mobile_drag_ui = -1
|
||||
_mobile_press_position_value = Vector2.ZERO
|
||||
_mobile_long_press_ready = false
|
||||
if item_mouse and item_mouse.has_method("unregister_owner"):
|
||||
item_mouse.unregister_owner(self)
|
||||
if item_mouse.has_method("is_attached") and item_mouse.is_attached():
|
||||
@@ -100,6 +132,7 @@ func close() -> void:
|
||||
_win = {}
|
||||
_cells.clear()
|
||||
_combine_from = -1
|
||||
_mobile_selected_ui = -1
|
||||
_hint = null
|
||||
|
||||
func open() -> void:
|
||||
@@ -312,6 +345,22 @@ func _apply_belt_locks(eq: Array) -> void:
|
||||
cell.tooltip_text = "需要腰带等级 %d" % BELT_RULES[local]
|
||||
|
||||
func _on_cell_input(ui_idx: int, e: InputEvent) -> void:
|
||||
if _mobile_mode and e is InputEventScreenTouch:
|
||||
var touch := e as InputEventScreenTouch
|
||||
if touch.pressed:
|
||||
_begin_mobile_cell_press(ui_idx, touch.index, touch.position)
|
||||
elif touch.index == _mobile_touch_index:
|
||||
_finish_mobile_cell_press(ui_idx, touch.index)
|
||||
var cell: Panel = _cells.get(ui_idx, null)
|
||||
if cell:
|
||||
cell.accept_event()
|
||||
return
|
||||
if _mobile_mode and e is InputEventScreenDrag:
|
||||
_handle_mobile_cell_drag(ui_idx, e as InputEventScreenDrag)
|
||||
var drag_cell: Panel = _cells.get(ui_idx, null)
|
||||
if drag_cell:
|
||||
drag_cell.accept_event()
|
||||
return
|
||||
if not (e is InputEventMouseButton):
|
||||
return
|
||||
var cell: Panel = _cells[ui_idx]
|
||||
@@ -361,6 +410,199 @@ func _on_cell_input(ui_idx: int, e: InputEvent) -> void:
|
||||
move_to(_drag_from, over, 1)
|
||||
_drag_from = -1
|
||||
|
||||
func _mobile_cell_tap(ui_idx: int) -> void:
|
||||
var cell: Panel = _cells.get(ui_idx, null)
|
||||
if cell == null or bool(cell.get_meta("locked", false)):
|
||||
_set_hint("该格当前不可用")
|
||||
return
|
||||
var vnum := int(cell.get_meta("vnum", 0))
|
||||
if _mobile_selected_ui < 0:
|
||||
if vnum == 0:
|
||||
_set_hint("这里没有物品")
|
||||
return
|
||||
_mobile_selected_ui = ui_idx
|
||||
_set_hint("已选择 %s;再次点按使用,点另一格移动" % _item_label(ui_idx))
|
||||
return
|
||||
if _mobile_selected_ui == ui_idx:
|
||||
use(ui_idx)
|
||||
mobile_selected_clear()
|
||||
return
|
||||
var from := _mobile_selected_ui
|
||||
mobile_selected_clear()
|
||||
if move_to(from, ui_idx, 1):
|
||||
_set_hint("已发送移动请求")
|
||||
else:
|
||||
_set_hint("物品移动请求发送失败")
|
||||
|
||||
func _begin_mobile_cell_press(ui_idx: int, touch_index: int,
|
||||
position := Vector2(-1, -1)) -> void:
|
||||
var cell: Panel = _cells.get(ui_idx, null)
|
||||
if cell == null or bool(cell.get_meta("locked", false)):
|
||||
return
|
||||
_mobile_press_cell = ui_idx
|
||||
_mobile_touch_index = touch_index
|
||||
_mobile_dragging = false
|
||||
_mobile_drag_ui = -1
|
||||
_mobile_long_press_ready = false
|
||||
var center: Vector2 = _cells[ui_idx].get_global_rect().get_center()
|
||||
_mobile_press_position_value = position if position.x >= 0.0 and position.y >= 0.0 else center
|
||||
_mobile_long_press_token += 1
|
||||
var token := _mobile_long_press_token
|
||||
get_tree().create_timer(MOBILE_LONG_PRESS_SECONDS).timeout.connect(func():
|
||||
if token != _mobile_long_press_token or _mobile_press_cell != ui_idx:
|
||||
return
|
||||
_mobile_long_press_ready = true
|
||||
_show_mobile_detail(ui_idx))
|
||||
|
||||
func _finish_mobile_cell_press(ui_idx: int, touch_index: int) -> void:
|
||||
if touch_index != _mobile_touch_index or ui_idx != _mobile_press_cell:
|
||||
return
|
||||
if _mobile_dragging:
|
||||
_mobile_dragging = false
|
||||
_mobile_drag_ui = -1
|
||||
_mobile_press_cell = -1
|
||||
_mobile_touch_index = -1
|
||||
_mobile_press_position_value = Vector2.ZERO
|
||||
_mobile_long_press_ready = false
|
||||
return
|
||||
_mobile_long_press_token += 1
|
||||
_mobile_touch_index = -1
|
||||
_mobile_press_cell = -1
|
||||
_mobile_long_press_ready = false
|
||||
if _mobile_detail_dialog == null:
|
||||
_mobile_cell_tap(ui_idx)
|
||||
|
||||
func _handle_mobile_cell_drag(ui_idx: int, event: InputEventScreenDrag) -> void:
|
||||
if event.index != _mobile_touch_index or ui_idx != _mobile_press_cell:
|
||||
return
|
||||
var distance := event.position.distance_to(_mobile_press_position())
|
||||
if _mobile_dragging:
|
||||
return
|
||||
# A long press first exposes the item detail, as on the reference tooltip
|
||||
# path. Continuing to drag that same finger converts the gesture into a
|
||||
# real item drag, so a user can move across inventory/equipment targets
|
||||
# without relying on right-click or a tiny desktop drag handle.
|
||||
if not _mobile_long_press_ready or distance < MOBILE_DRAG_THRESHOLD:
|
||||
return
|
||||
_start_mobile_drag(ui_idx, event.index, event.position)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
# Once a long press has opened the detail card, the card can cover the
|
||||
# source cell and steal GUI events. Keep the source touch at the feature
|
||||
# level so dragging out of that card still reaches MouseController.
|
||||
if not _mobile_mode or _mobile_touch_index < 0 or _mobile_press_cell < 0:
|
||||
return
|
||||
if event is InputEventScreenDrag and event.index == _mobile_touch_index:
|
||||
_handle_mobile_cell_drag(_mobile_press_cell, event as InputEventScreenDrag)
|
||||
elif event is InputEventScreenTouch and not event.pressed \
|
||||
and event.index == _mobile_touch_index:
|
||||
_finish_mobile_cell_press(_mobile_press_cell, event.index)
|
||||
|
||||
func _mobile_press_position() -> Vector2:
|
||||
return _mobile_press_position_value
|
||||
|
||||
func _start_mobile_drag(ui_idx: int, touch_index: int, position: Vector2) -> void:
|
||||
var cell: Panel = _cells.get(ui_idx, null)
|
||||
if cell == null or int(cell.get_meta("vnum", 0)) == 0:
|
||||
return
|
||||
if item_mouse == null or not item_mouse.has_method("attach_item"):
|
||||
_set_hint("当前窗口不支持拖放")
|
||||
return
|
||||
var wire := _to_wire(ui_idx)
|
||||
var metadata: Dictionary = client.get_item(wire[0], wire[1]) \
|
||||
if client and client.has_method("get_item") else {}
|
||||
if _mobile_detail_dialog and is_instance_valid(_mobile_detail_dialog):
|
||||
_close_mobile_detail()
|
||||
_mobile_long_press_token += 1
|
||||
_mobile_dragging = true
|
||||
_mobile_drag_ui = ui_idx
|
||||
item_mouse.attach_item(wire[0], wire[1], int(cell.get_meta("vnum", 0)),
|
||||
_item_count(ui_idx), _icon(int(cell.get_meta("vnum", 0))), "inventory",
|
||||
metadata, touch_index, position)
|
||||
_set_hint("拖动中:松手放置到目标格")
|
||||
|
||||
func _show_mobile_detail(ui_idx: int) -> void:
|
||||
var cell: Panel = _cells.get(ui_idx, null)
|
||||
if cell == null or int(cell.get_meta("vnum", 0)) == 0 or ui == null:
|
||||
return
|
||||
if _mobile_detail_dialog and is_instance_valid(_mobile_detail_dialog):
|
||||
ui.close(_mobile_detail_dialog)
|
||||
var vnum := int(cell.get_meta("vnum", 0))
|
||||
var count := int(cell.get_meta("count", 1))
|
||||
var dialog := Panel.new()
|
||||
dialog.name = "MobileItemDetail"
|
||||
dialog.size = Vector2(360, 220)
|
||||
dialog.set_meta("mobile_title", "物品详情")
|
||||
dialog.set_meta("mobile_modal", true)
|
||||
dialog.set_meta("mobile_modal_close", Callable(self, "_close_mobile_detail"))
|
||||
var bg := StyleBoxFlat.new()
|
||||
bg.bg_color = Color(0.04, 0.065, 0.1, 0.98)
|
||||
bg.border_color = Color(0.72, 0.56, 0.25, 0.95)
|
||||
bg.set_border_width_all(1)
|
||||
bg.set_corner_radius_all(8)
|
||||
dialog.add_theme_stylebox_override("panel", bg)
|
||||
var title := Label.new()
|
||||
title.text = _item_label(ui_idx)
|
||||
title.position = Vector2(18, 52)
|
||||
title.size = Vector2(324, 28)
|
||||
title.add_theme_font_size_override("font_size", 15)
|
||||
title.add_theme_color_override("font_color", Color(0.98, 0.84, 0.52))
|
||||
dialog.add_child(title)
|
||||
var detail := Label.new()
|
||||
detail.text = "物品编号:%d\n数量:%d\n\n%s" % [vnum, count, String(cell.tooltip_text)]
|
||||
detail.position = Vector2(18, 84)
|
||||
detail.size = Vector2(324, 70)
|
||||
detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
detail.add_theme_font_size_override("font_size", 10)
|
||||
detail.add_theme_color_override("font_color", Color(0.78, 0.86, 0.96))
|
||||
dialog.add_child(detail)
|
||||
var select := Button.new()
|
||||
select.text = "选择"
|
||||
select.position = Vector2(18, 168)
|
||||
select.size = Vector2(100, 40)
|
||||
select.pressed.connect(func():
|
||||
_close_mobile_detail()
|
||||
_mobile_cell_tap(ui_idx))
|
||||
dialog.add_child(select)
|
||||
var use_button := Button.new()
|
||||
use_button.text = "使用"
|
||||
use_button.position = Vector2(130, 168)
|
||||
use_button.size = Vector2(100, 40)
|
||||
use_button.pressed.connect(func():
|
||||
_close_mobile_detail()
|
||||
use(ui_idx)
|
||||
mobile_selected_clear())
|
||||
dialog.add_child(use_button)
|
||||
var cancel := Button.new()
|
||||
cancel.text = "关闭"
|
||||
cancel.position = Vector2(242, 168)
|
||||
cancel.size = Vector2(100, 40)
|
||||
cancel.pressed.connect(_close_mobile_detail)
|
||||
dialog.add_child(cancel)
|
||||
_mobile_detail_dialog = dialog
|
||||
ui.open(dialog, true)
|
||||
|
||||
func _close_mobile_detail() -> void:
|
||||
var dialog := _mobile_detail_dialog
|
||||
_mobile_detail_dialog = null
|
||||
if dialog and is_instance_valid(dialog) and ui:
|
||||
ui.close(dialog)
|
||||
|
||||
func _item_label(ui_idx: int) -> String:
|
||||
var cell: Panel = _cells.get(ui_idx, null)
|
||||
if cell == null:
|
||||
return "物品"
|
||||
var vnum := int(cell.get_meta("vnum", 0))
|
||||
if proto and proto.has_method("item") and vnum != 0:
|
||||
var data: Dictionary = proto.item(vnum)
|
||||
var name := String(data.get("locale_name", data.get("name", "")))
|
||||
if name != "":
|
||||
return name
|
||||
return "#%d" % vnum
|
||||
|
||||
func mobile_selected_clear() -> void:
|
||||
_mobile_selected_ui = -1
|
||||
|
||||
func _drop_mouse_item(payload: Dictionary, target_ui: int) -> bool:
|
||||
if client == null or payload.is_empty() or target_ui < 0:
|
||||
return false
|
||||
@@ -516,7 +758,9 @@ func _ask_count(title_text: String, max_count: int, on_confirm: Callable) -> voi
|
||||
return
|
||||
var dialog := Panel.new()
|
||||
dialog.position = Vector2(760, 420)
|
||||
dialog.size = Vector2(240, 132)
|
||||
dialog.size = Vector2(360, 210) if _mobile_mode else Vector2(240, 132)
|
||||
if _mobile_mode:
|
||||
dialog.set_meta("mobile_title", title_text)
|
||||
var bg := StyleBoxFlat.new()
|
||||
bg.bg_color = Color(0.05, 0.06, 0.09, 0.97)
|
||||
bg.border_color = Color(0.7, 0.55, 0.22, 0.9)
|
||||
@@ -524,28 +768,47 @@ func _ask_count(title_text: String, max_count: int, on_confirm: Callable) -> voi
|
||||
dialog.add_theme_stylebox_override("panel", bg)
|
||||
var title := Label.new()
|
||||
title.text = title_text
|
||||
title.position = Vector2(12, 10)
|
||||
title.position = Vector2(18, 50) if _mobile_mode else Vector2(12, 10)
|
||||
if _mobile_mode:
|
||||
title.add_theme_font_size_override("font_size", 15)
|
||||
dialog.add_child(title)
|
||||
var spin := SpinBox.new()
|
||||
spin.min_value = 1
|
||||
spin.max_value = max(1, max_count)
|
||||
spin.value = 1
|
||||
spin.step = 1
|
||||
spin.allow_greater = false
|
||||
spin.position = Vector2(12, 42)
|
||||
spin.size = Vector2(216, 28)
|
||||
dialog.add_child(spin)
|
||||
var amount_input: Control
|
||||
if _mobile_mode:
|
||||
var edit := LineEdit.new()
|
||||
edit.name = "CountInput"
|
||||
edit.text = "1"
|
||||
edit.placeholder_text = "1 - %d" % max(1, max_count)
|
||||
edit.max_length = 8
|
||||
edit.virtual_keyboard_type = LineEdit.KEYBOARD_TYPE_NUMBER
|
||||
edit.position = Vector2(18, 88)
|
||||
edit.size = Vector2(324, 40)
|
||||
amount_input = edit
|
||||
dialog.add_child(edit)
|
||||
else:
|
||||
var spin := SpinBox.new()
|
||||
spin.min_value = 1
|
||||
spin.max_value = max(1, max_count)
|
||||
spin.value = 1
|
||||
spin.step = 1
|
||||
spin.allow_greater = false
|
||||
spin.position = Vector2(12, 42)
|
||||
spin.size = Vector2(216, 28)
|
||||
amount_input = spin
|
||||
dialog.add_child(spin)
|
||||
var ok := Button.new()
|
||||
ok.text = "确认"
|
||||
ok.position = Vector2(76, 88)
|
||||
ok.position = Vector2(18, 150) if _mobile_mode else Vector2(76, 88)
|
||||
ok.size = Vector2(154, 44) if _mobile_mode else Vector2(58, 28)
|
||||
ok.pressed.connect(func():
|
||||
var amount := int(spin.value)
|
||||
var amount := int((amount_input as LineEdit).text) if amount_input is LineEdit else int((amount_input as SpinBox).value)
|
||||
amount = clampi(amount, 1, max(1, max_count))
|
||||
ui.close(dialog)
|
||||
on_confirm.call(amount))
|
||||
dialog.add_child(ok)
|
||||
var cancel := Button.new()
|
||||
cancel.text = "取消"
|
||||
cancel.position = Vector2(142, 88)
|
||||
cancel.position = Vector2(188, 150) if _mobile_mode else Vector2(142, 88)
|
||||
cancel.size = Vector2(154, 44) if _mobile_mode else Vector2(58, 28)
|
||||
cancel.pressed.connect(func(): ui.close(dialog))
|
||||
dialog.add_child(cancel)
|
||||
ui.open(dialog, true)
|
||||
|
||||
Reference in New Issue
Block a user