feat: complete mobile UI implementation

This commit is contained in:
shenlei
2026-09-04 19:54:33 +09:00
parent 9a4a044da5
commit b19d5b5dd3
132 changed files with 6643 additions and 180 deletions
+217 -21
View File
@@ -14,6 +14,7 @@
# 辅助页 HIDE_SUPPORT_SKILL_POINT 整页隐藏。升级结果仍由 GC_SKILL_LEVEL 回包驱动。
extends Node
const MobileTouchButton = preload("res://ui/mobile/mobile_touch_button.gd")
const TABS := ["主动", "辅助", "坐骑"]
const MASTER_SUFFIX := ["", " M", " G", " P"] # master_type 0..3
@@ -28,6 +29,11 @@ var ui: CanvasLayer # UiManager
var job := "WARRIOR"
var drag_skill_id := 0
var _tab := 0
var _mobile_mode := false
var _mobile_quickbar: Node
var _mobile_hint: Label
var _mobile_drop_slots: Array[MobileTouchButton] = []
var _mobile_drag_quickbar_slot := -1
var _win: Control
var _rows := {} # skill_id -> {lv, up}
@@ -44,6 +50,15 @@ func set_job(j: String) -> void:
if is_open():
_rebuild()
func set_mobile_mode(enabled: bool) -> void:
_mobile_mode = enabled
if _mobile_mode and _win:
close()
func set_mobile_quickbar(quickbar: Node) -> void:
_mobile_quickbar = quickbar
_refresh_mobile_quickbar_targets()
func is_open() -> bool:
return _win != null and is_instance_valid(_win)
@@ -56,6 +71,8 @@ func close() -> void:
ui.close(_win)
_win = null
_rows.clear()
_mobile_drop_slots.clear()
_mobile_drag_quickbar_slot = -1
func _cat_key() -> String:
match _tab:
@@ -67,9 +84,16 @@ func open() -> void:
if is_open() or table == null:
return
_win = Control.new()
_win.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
_win.position = Vector2(-300, -240)
_win.size = Vector2(290, 470)
_win.name = "MobileSkillWindow" if _mobile_mode else "SkillWindow"
if _mobile_mode:
_win.set_anchors_preset(Control.PRESET_TOP_LEFT)
_win.position = Vector2.ZERO
_win.size = Vector2(760, 340)
_win.set_meta("mobile_title", "技能")
else:
_win.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
_win.position = Vector2(-300, -240)
_win.size = Vector2(290, 470)
_win.set_meta("is_titlebar", true)
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
@@ -80,27 +104,54 @@ func open() -> void:
_win.add_child(panel)
var title := Label.new()
title.text = "技能"
title.position = Vector2(12, 8)
title.position = Vector2(12, 8) if not _mobile_mode else Vector2(14, 48)
title.visible = not _mobile_mode
if _mobile_mode:
title.add_theme_font_size_override("font_size", 15)
_win.add_child(title)
# 分类页签
var tabrow := HBoxContainer.new()
tabrow.name = "tabs"
tabrow.position = Vector2(10, 30)
tabrow.position = Vector2(10, 30) if not _mobile_mode else Vector2(14, 48)
if _mobile_mode:
tabrow.size = Vector2(360, 40)
tabrow.add_theme_constant_override("separation", 6)
_win.add_child(tabrow)
for i in TABS.size():
var b := Button.new()
b.text = TABS[i]
b.toggle_mode = true
b.button_pressed = (i == _tab)
if _mobile_mode:
b.custom_minimum_size = Vector2(110, 38)
b.add_theme_font_size_override("font_size", 12)
var ti: int = i
b.pressed.connect(func(): _switch_tab(ti))
tabrow.add_child(b)
var vb := VBoxContainer.new()
vb.name = "list"
vb.position = Vector2(10, 60)
vb.custom_minimum_size = Vector2(270, 0)
vb.position = Vector2(10, 60) if not _mobile_mode else Vector2(0, 0)
vb.custom_minimum_size = Vector2(270, 0) if not _mobile_mode else Vector2(720, 0)
vb.add_theme_constant_override("separation", 2)
_win.add_child(vb)
if _mobile_mode:
var scroll := ScrollContainer.new()
scroll.name = "SkillScroll"
scroll.position = Vector2(14, 92)
scroll.size = Vector2(732, 214)
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll.mouse_filter = Control.MOUSE_FILTER_STOP
scroll.add_child(vb)
_win.add_child(scroll)
_mobile_hint = Label.new()
_mobile_hint.position = Vector2(14, 310)
_mobile_hint.size = Vector2(720, 22)
_mobile_hint.add_theme_font_size_override("font_size", 10)
_mobile_hint.add_theme_color_override("font_color", Color(0.62, 0.72, 0.84))
_mobile_hint.text = "可用点数与技能等级由服务器实时确认"
_win.add_child(_mobile_hint)
_build_mobile_quickbar_targets()
else:
_win.add_child(vb)
ui.open(_win)
_rebuild()
@@ -113,7 +164,11 @@ func _switch_tab(i: int) -> void:
_rebuild()
func _rebuild() -> void:
var vb: VBoxContainer = _win.get_node("list")
var vb: VBoxContainer = _win.get_node_or_null("list") as VBoxContainer
if vb == null and _mobile_mode:
vb = _win.get_node_or_null("SkillScroll/list") as VBoxContainer
if vb == null:
return
for c in vb.get_children():
c.queue_free()
_rows.clear()
@@ -125,19 +180,39 @@ func _rebuild() -> void:
return
for id in ids:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(268, 26)
var nm := Label.new()
nm.text = table.name_of(id)
nm.custom_minimum_size = Vector2(140, 0)
nm.add_theme_font_size_override("font_size", 12)
row.custom_minimum_size = Vector2(720, 46) if _mobile_mode else Vector2(268, 26)
var nm: Control
if _mobile_mode:
var skill_name := MobileTouchButton.new()
skill_name.setup(table.name_of(id), Color(0.34, 0.58, 0.88, 0.88))
skill_name.set_rect_style()
skill_name.set_gesture_enabled(true)
skill_name.custom_minimum_size = Vector2(300, 42)
skill_name.size = Vector2(300, 42)
var skill_button := skill_name
skill_button.long_pressed.connect(func(): _mobile_begin_skill_drag(id, skill_button))
skill_button.drag_changed.connect(func(_delta: Vector2):
_mobile_update_skill_drag(id, skill_button))
skill_button.drag_released.connect(func(_delta: Vector2):
_mobile_end_skill_drag(id, skill_button))
nm = skill_name
else:
var skill_label := Label.new()
skill_label.text = table.name_of(id)
skill_label.custom_minimum_size = Vector2(140, 0)
skill_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
skill_label.add_theme_font_size_override("font_size", 12)
nm = skill_label
row.add_child(nm)
var lv := Label.new()
lv.name = "lv"
lv.custom_minimum_size = Vector2(58, 0)
lv.add_theme_font_size_override("font_size", 12)
lv.custom_minimum_size = Vector2(100, 42) if _mobile_mode else Vector2(58, 0)
lv.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
lv.add_theme_font_size_override("font_size", 13 if _mobile_mode else 12)
row.add_child(lv)
var tag := Label.new()
tag.custom_minimum_size = Vector2(40, 0)
tag.custom_minimum_size = Vector2(76, 42) if _mobile_mode else Vector2(40, 0)
tag.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
tag.add_theme_font_size_override("font_size", 10)
if table.is_passive(id):
tag.text = "被动"
@@ -150,17 +225,138 @@ func _rebuild() -> void:
if table.can_level_up(id) and not table.is_passive(id):
var up := Button.new()
up.text = ""
up.custom_minimum_size = Vector2(28, 22)
up.custom_minimum_size = Vector2(48, 42) if _mobile_mode else Vector2(28, 22)
if _mobile_mode:
up.add_theme_font_size_override("font_size", 18)
up.pressed.connect(func(): client.skill_up(sid))
row.add_child(up)
_rows[id] = {"lv": lv, "up": up}
else:
_rows[id] = {"lv": lv, "up": null}
nm.gui_input.connect(func(e: InputEvent):
if e is InputEventMouseButton and e.pressed and e.button_index == MOUSE_BUTTON_LEFT:
drag_skill_id = sid)
if not _mobile_mode:
nm.gui_input.connect(func(e: InputEvent):
if e is InputEventMouseButton and e.pressed and e.button_index == MOUSE_BUTTON_LEFT:
drag_skill_id = sid
if _mobile_hint: _mobile_hint.text = "已选择:%s;点击快捷栏按钮自动放入空位" % table.name_of(sid)
nm.accept_event())
if _mobile_mode:
var assign := Button.new()
assign.text = "快捷栏"
assign.custom_minimum_size = Vector2(92, 42)
assign.add_theme_font_size_override("font_size", 11)
assign.pressed.connect(func(): _mobile_assign_skill(sid))
row.add_child(assign)
vb.add_child(row)
refresh()
_refresh_mobile_quickbar_targets()
func _build_mobile_quickbar_targets() -> void:
var title := Label.new()
title.text = "快捷栏"
title.position = Vector2(390, 48)
title.size = Vector2(60, 38)
title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 12)
title.add_theme_color_override("font_color", Color(0.95, 0.82, 0.54))
title.mouse_filter = Control.MOUSE_FILTER_IGNORE
_win.add_child(title)
for i in 8:
var target := MobileTouchButton.new()
target.setup("", Color(0.42, 0.65, 0.92, 0.9))
target.set_rect_style()
target.set_gesture_enabled(true)
target.position = Vector2(448 + i * 38, 48)
target.size = Vector2(34, 38)
target.custom_minimum_size = Vector2(34, 38)
var slot := i
target.pressed.connect(func(): _mobile_drop_on_quickbar(slot))
target.long_pressed.connect(func(): _mobile_begin_quickbar_drag(slot))
target.drag_released.connect(func(_delta: Vector2): _mobile_end_quickbar_drag(slot, target))
_win.add_child(target)
_mobile_drop_slots.append(target)
func _mobile_begin_skill_drag(skill_id: int, _button: MobileTouchButton) -> void:
drag_skill_id = skill_id
if _mobile_hint:
_mobile_hint.text = "拖到上方快捷栏槽位,松手放入"
func _mobile_update_skill_drag(skill_id: int, _button: MobileTouchButton) -> void:
if drag_skill_id == skill_id and _mobile_hint:
_mobile_hint.text = "拖到上方快捷栏槽位,松手放入"
func _mobile_end_skill_drag(skill_id: int, button: MobileTouchButton) -> void:
if drag_skill_id != skill_id:
return
var target := _mobile_drop_slot_at(button.drag_end_position())
if target >= 0 and _mobile_quickbar and _mobile_quickbar.has_method("assign"):
if bool(_mobile_quickbar.assign(target, "skill", skill_id)):
drag_skill_id = 0
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已放入快捷栏第 %d" % (target + 1)
return
if _mobile_hint:
_mobile_hint.text = "已选择:%s;可拖到快捷栏" % table.name_of(skill_id)
func _mobile_drop_on_quickbar(slot: int) -> void:
if _mobile_quickbar == null or not _mobile_quickbar.has_method("assign"):
return
if drag_skill_id != 0:
if bool(_mobile_quickbar.assign(slot, "skill", drag_skill_id)):
drag_skill_id = 0
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已放入快捷栏第 %d" % (slot + 1)
func _mobile_begin_quickbar_drag(slot: int) -> void:
_mobile_drag_quickbar_slot = slot
if _mobile_hint:
_mobile_hint.text = "拖到另一个快捷栏槽位以交换"
func _mobile_end_quickbar_drag(slot: int, button: MobileTouchButton) -> void:
if _mobile_drag_quickbar_slot != slot:
return
var target := _mobile_drop_slot_at(button.drag_end_position())
if target >= 0 and target != slot and _mobile_quickbar \
and _mobile_quickbar.has_method("mobile_swap"):
_mobile_quickbar.mobile_swap(slot, target)
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已交换快捷栏位置"
_mobile_drag_quickbar_slot = -1
func _mobile_drop_slot_at(position: Vector2) -> int:
for i in _mobile_drop_slots.size():
var slot := _mobile_drop_slots[i]
if slot and slot.get_global_rect().has_point(position):
return i
return -1
func _refresh_mobile_quickbar_targets() -> void:
if not _mobile_mode:
return
for i in _mobile_drop_slots.size():
var slot := _mobile_drop_slots[i]
if slot == null or _mobile_quickbar == null:
continue
var state: Dictionary = _mobile_quickbar.mobile_slot_state(i) \
if _mobile_quickbar.has_method("mobile_slot_state") else {}
var label := ""
if state.get("kind", "") == "skill" and table:
label = table.name_of(int(state.get("id", 0))).substr(0, 4)
elif state.get("kind", "") == "item":
label = ""
elif state.get("kind", "") == "emote":
label = ""
slot.set_caption(label)
func _mobile_assign_skill(skill_id: int) -> void:
if _mobile_quickbar == null or not _mobile_quickbar.has_method("assign_mobile"):
if _mobile_hint: _mobile_hint.text = "快捷栏暂不可用"
return
if bool(_mobile_quickbar.assign_mobile("skill", skill_id)):
drag_skill_id = 0
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已放入快捷栏"
else:
if _mobile_hint: _mobile_hint.text = "快捷栏已满,请先移除一个技能"
func refresh() -> void:
if not is_open():