feat: complete mobile UI implementation
This commit is contained in:
@@ -18,6 +18,7 @@ extends Node
|
||||
const UiScript = preload("res://ui/uiscript.gd")
|
||||
const UiBuild = preload("res://ui/ui_build.gd")
|
||||
const UiAssets = preload("res://ui/ui_assets.gd")
|
||||
const MobileTouchButton = preload("res://ui/mobile/mobile_touch_button.gd")
|
||||
|
||||
# --- EPointTypes(m2dev Packet.h),只列本窗要用的 ---
|
||||
const P_LEVEL := 1
|
||||
@@ -65,6 +66,14 @@ var uiscript_dir := ""
|
||||
|
||||
var _win: Dictionary = {} # { root, nodes }
|
||||
var _state := "STATUS"
|
||||
var _mobile_mode := false
|
||||
var _mobile_root: Control
|
||||
var _mobile_status_page: Control
|
||||
var _mobile_emote_page: Control
|
||||
var _mobile_labels := {}
|
||||
var _mobile_stats := {}
|
||||
var _mobile_equipment_labels: Array[Label] = []
|
||||
var _mobile_hint: Label
|
||||
|
||||
func setup(ui_manager: CanvasLayer, m2client: Node, assets := "") -> void:
|
||||
ui = ui_manager
|
||||
@@ -80,9 +89,19 @@ func setup(ui_manager: CanvasLayer, m2client: Node, assets := "") -> void:
|
||||
if client and client.has_signal("entity_main_set"):
|
||||
client.entity_main_set.connect(func(_v): if is_open(): _refresh())
|
||||
|
||||
func set_mobile_mode(enabled: bool) -> void:
|
||||
if not enabled and _mobile_root != null:
|
||||
var old_mode := _mobile_mode
|
||||
_mobile_mode = true
|
||||
close()
|
||||
_mobile_mode = old_mode
|
||||
_mobile_mode = enabled
|
||||
|
||||
# --- open / close -----------------------------------------------------
|
||||
|
||||
func is_open() -> bool:
|
||||
if _mobile_mode:
|
||||
return _mobile_root != null and is_instance_valid(_mobile_root)
|
||||
return not _win.is_empty() and is_instance_valid(_win.get("root"))
|
||||
|
||||
func toggle() -> void:
|
||||
@@ -90,6 +109,16 @@ func toggle() -> void:
|
||||
else: open()
|
||||
|
||||
func close() -> void:
|
||||
if _mobile_mode:
|
||||
if _mobile_root and is_instance_valid(_mobile_root) and ui:
|
||||
ui.close(_mobile_root)
|
||||
_mobile_root = null
|
||||
_mobile_status_page = null
|
||||
_mobile_emote_page = null
|
||||
_mobile_labels.clear()
|
||||
_mobile_stats.clear()
|
||||
_mobile_equipment_labels.clear()
|
||||
return
|
||||
if is_open():
|
||||
ui.close(_win["root"])
|
||||
_win = {}
|
||||
@@ -97,6 +126,9 @@ func close() -> void:
|
||||
func open(state := "") -> void:
|
||||
if state != "" and state in STATES:
|
||||
_state = state
|
||||
if _mobile_mode:
|
||||
_open_mobile()
|
||||
return
|
||||
if is_open():
|
||||
_set_state(_state)
|
||||
_refresh()
|
||||
@@ -113,6 +145,277 @@ func open(state := "") -> void:
|
||||
_set_state(_state)
|
||||
_refresh()
|
||||
|
||||
func _open_mobile() -> void:
|
||||
if is_open():
|
||||
_mobile_set_state(_state)
|
||||
_refresh_mobile()
|
||||
return
|
||||
_mobile_root = Control.new()
|
||||
_mobile_root.name = "MobileCharacterWindow"
|
||||
_mobile_root.size = Vector2(760, 340)
|
||||
_mobile_root.set_meta("mobile_title", "角色")
|
||||
_mobile_root.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_build_mobile_view()
|
||||
ui.open(_mobile_root)
|
||||
_mobile_set_state(_state)
|
||||
_refresh_mobile()
|
||||
|
||||
func _build_mobile_view() -> void:
|
||||
var panel := Panel.new()
|
||||
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
panel.add_theme_stylebox_override("panel", _mobile_panel_style())
|
||||
_mobile_root.add_child(panel)
|
||||
|
||||
var tabs := HBoxContainer.new()
|
||||
tabs.position = Vector2(14, 48)
|
||||
tabs.size = Vector2(250, 38)
|
||||
tabs.add_theme_constant_override("separation", 6)
|
||||
_mobile_root.add_child(tabs)
|
||||
var status_tab := MobileTouchButton.new()
|
||||
status_tab.setup("属性 / 装备", Color(0.65, 0.78, 0.98, 0.95))
|
||||
status_tab.set_rect_style()
|
||||
status_tab.custom_minimum_size = Vector2(118, 36)
|
||||
status_tab.size = Vector2(118, 36)
|
||||
status_tab.pressed.connect(func(): _mobile_set_state("STATUS"))
|
||||
tabs.add_child(status_tab)
|
||||
var emote_tab := MobileTouchButton.new()
|
||||
emote_tab.setup("表情", Color(0.65, 0.78, 0.98, 0.95))
|
||||
emote_tab.set_rect_style()
|
||||
emote_tab.custom_minimum_size = Vector2(86, 36)
|
||||
emote_tab.size = Vector2(86, 36)
|
||||
emote_tab.pressed.connect(func(): _mobile_set_state("EMOTICON"))
|
||||
tabs.add_child(emote_tab)
|
||||
|
||||
_mobile_status_page = Control.new()
|
||||
_mobile_status_page.name = "StatusPage"
|
||||
_mobile_status_page.position = Vector2(14, 92)
|
||||
_mobile_status_page.size = Vector2(732, 236)
|
||||
_mobile_root.add_child(_mobile_status_page)
|
||||
_build_mobile_profile()
|
||||
_build_mobile_stats()
|
||||
_build_mobile_equipment()
|
||||
|
||||
_mobile_emote_page = Control.new()
|
||||
_mobile_emote_page.name = "EmotePage"
|
||||
_mobile_emote_page.position = Vector2(14, 92)
|
||||
_mobile_emote_page.size = Vector2(732, 236)
|
||||
_mobile_root.add_child(_mobile_emote_page)
|
||||
_build_mobile_emotes()
|
||||
|
||||
func _build_mobile_profile() -> void:
|
||||
var profile := Panel.new()
|
||||
profile.position = Vector2(0, 0)
|
||||
profile.size = Vector2(190, 236)
|
||||
profile.add_theme_stylebox_override("panel", _mobile_panel_style(Color(0.035, 0.055, 0.09, 0.94), Color(0.28, 0.5, 0.78, 0.72)))
|
||||
_mobile_status_page.add_child(profile)
|
||||
var title := Label.new()
|
||||
title.text = "角色信息"
|
||||
title.position = Vector2(12, 10)
|
||||
title.add_theme_font_size_override("font_size", 14)
|
||||
title.add_theme_color_override("font_color", Color(0.95, 0.82, 0.54))
|
||||
profile.add_child(title)
|
||||
for data in [["name", "角色"], ["guild", "公会"], ["level", "等级"], ["exp", "经验"], ["hp", "生命"], ["sp", "法力"]]:
|
||||
var row := HBoxContainer.new()
|
||||
row.position = Vector2(12, 38 + _mobile_labels.size() * 29)
|
||||
row.size = Vector2(166, 24)
|
||||
var caption := Label.new()
|
||||
caption.text = String(data[1])
|
||||
caption.custom_minimum_size = Vector2(44, 22)
|
||||
caption.add_theme_font_size_override("font_size", 11)
|
||||
row.add_child(caption)
|
||||
var value := Label.new()
|
||||
value.name = String(data[0])
|
||||
value.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
value.add_theme_font_size_override("font_size", 11)
|
||||
value.add_theme_color_override("font_color", Color(0.82, 0.9, 1.0))
|
||||
row.add_child(value)
|
||||
profile.add_child(row)
|
||||
_mobile_labels[String(data[0])] = value
|
||||
|
||||
func _build_mobile_stats() -> void:
|
||||
var stats := Panel.new()
|
||||
stats.position = Vector2(200, 0)
|
||||
stats.size = Vector2(270, 236)
|
||||
stats.add_theme_stylebox_override("panel", _mobile_panel_style(Color(0.035, 0.055, 0.09, 0.94), Color(0.28, 0.5, 0.78, 0.72)))
|
||||
_mobile_status_page.add_child(stats)
|
||||
var title := Label.new()
|
||||
title.text = "属性"
|
||||
title.position = Vector2(12, 10)
|
||||
title.add_theme_font_size_override("font_size", 14)
|
||||
title.add_theme_color_override("font_color", Color(0.95, 0.82, 0.54))
|
||||
stats.add_child(title)
|
||||
var points := Label.new()
|
||||
points.name = "stat_points"
|
||||
points.position = Vector2(146, 10)
|
||||
points.size = Vector2(110, 22)
|
||||
points.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
points.add_theme_font_size_override("font_size", 11)
|
||||
points.add_theme_color_override("font_color", Color(0.96, 0.78, 0.35))
|
||||
stats.add_child(points)
|
||||
_mobile_labels["stat_points"] = points
|
||||
for i in 4:
|
||||
var key: String = ["STR", "HTH", "DEX", "INT"][i]
|
||||
var row := HBoxContainer.new()
|
||||
row.position = Vector2(12 + (i % 2) * 128, 44 + (i / 2) * 58)
|
||||
row.size = Vector2(118, 48)
|
||||
var caption := Label.new()
|
||||
caption.text = key
|
||||
caption.custom_minimum_size = Vector2(34, 42)
|
||||
caption.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
caption.add_theme_font_size_override("font_size", 12)
|
||||
row.add_child(caption)
|
||||
var value := Label.new()
|
||||
value.name = "value"
|
||||
value.custom_minimum_size = Vector2(34, 42)
|
||||
value.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
value.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
value.add_theme_font_size_override("font_size", 13)
|
||||
row.add_child(value)
|
||||
var minus := MobileTouchButton.new()
|
||||
minus.setup("-", Color(0.72, 0.74, 0.82, 0.9))
|
||||
minus.set_rect_style()
|
||||
minus.custom_minimum_size = Vector2(24, 42)
|
||||
minus.size = Vector2(24, 42)
|
||||
minus.pressed.connect(func(): _send_stat(MINUS_CMD[key]))
|
||||
row.add_child(minus)
|
||||
var plus := MobileTouchButton.new()
|
||||
plus.setup("+", Color(0.95, 0.72, 0.3, 0.95))
|
||||
plus.set_rect_style()
|
||||
plus.custom_minimum_size = Vector2(24, 42)
|
||||
plus.size = Vector2(24, 42)
|
||||
plus.pressed.connect(func(): _send_stat(PLUS_CMD[key]))
|
||||
row.add_child(plus)
|
||||
stats.add_child(row)
|
||||
_mobile_stats[key] = {"value": value, "minus": minus, "plus": plus}
|
||||
var hint := Label.new()
|
||||
hint.text = "分配属性点会通过服务器确认"
|
||||
hint.position = Vector2(12, 174)
|
||||
hint.size = Vector2(245, 40)
|
||||
hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
hint.add_theme_font_size_override("font_size", 10)
|
||||
hint.add_theme_color_override("font_color", Color(0.62, 0.72, 0.84))
|
||||
stats.add_child(hint)
|
||||
|
||||
func _build_mobile_equipment() -> void:
|
||||
var equipment := Panel.new()
|
||||
equipment.position = Vector2(480, 0)
|
||||
equipment.size = Vector2(252, 236)
|
||||
equipment.add_theme_stylebox_override("panel", _mobile_panel_style(Color(0.035, 0.055, 0.09, 0.94), Color(0.28, 0.5, 0.78, 0.72)))
|
||||
_mobile_status_page.add_child(equipment)
|
||||
var title := Label.new()
|
||||
title.text = "装备"
|
||||
title.position = Vector2(12, 10)
|
||||
title.add_theme_font_size_override("font_size", 14)
|
||||
title.add_theme_color_override("font_color", Color(0.95, 0.82, 0.54))
|
||||
equipment.add_child(title)
|
||||
var grid := GridContainer.new()
|
||||
grid.name = "equipment_grid"
|
||||
grid.columns = 4
|
||||
grid.position = Vector2(10, 38)
|
||||
grid.size = Vector2(232, 188)
|
||||
grid.add_theme_constant_override("h_separation", 5)
|
||||
grid.add_theme_constant_override("v_separation", 5)
|
||||
equipment.add_child(grid)
|
||||
for i in 24:
|
||||
var slot := Panel.new()
|
||||
slot.custom_minimum_size = Vector2(53, 36)
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.08, 0.11, 0.17, 0.95)
|
||||
style.border_color = Color(0.32, 0.45, 0.62, 0.8)
|
||||
style.set_border_width_all(1)
|
||||
style.set_corner_radius_all(4)
|
||||
slot.add_theme_stylebox_override("panel", style)
|
||||
var value := Label.new()
|
||||
value.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
value.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
value.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
value.add_theme_font_size_override("font_size", 9)
|
||||
value.text = str(i + 1)
|
||||
value.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
slot.add_child(value)
|
||||
grid.add_child(slot)
|
||||
_mobile_equipment_labels.append(value)
|
||||
|
||||
func _build_mobile_emotes() -> void:
|
||||
var title := Label.new()
|
||||
title.text = "点击发送表情"
|
||||
title.position = Vector2(8, 8)
|
||||
title.add_theme_font_size_override("font_size", 14)
|
||||
title.add_theme_color_override("font_color", Color(0.95, 0.82, 0.54))
|
||||
_mobile_emote_page.add_child(title)
|
||||
var grid := GridContainer.new()
|
||||
grid.position = Vector2(8, 42)
|
||||
grid.columns = 5
|
||||
grid.add_theme_constant_override("h_separation", 8)
|
||||
grid.add_theme_constant_override("v_separation", 8)
|
||||
_mobile_emote_page.add_child(grid)
|
||||
for i in 9:
|
||||
var button := MobileTouchButton.new()
|
||||
button.setup("表情 %d" % (i + 1), Color(0.45, 0.7, 0.95, 0.92))
|
||||
button.set_rect_style()
|
||||
button.custom_minimum_size = Vector2(112, 52)
|
||||
button.size = Vector2(112, 52)
|
||||
var emote_id := i + 1
|
||||
button.pressed.connect(func():
|
||||
if client and client.has_method("send_emoticon"):
|
||||
client.send_emoticon(emote_id)
|
||||
if _mobile_hint: _mobile_hint.text = "已发送表情 %d" % emote_id)
|
||||
grid.add_child(button)
|
||||
_mobile_hint = Label.new()
|
||||
_mobile_hint.position = Vector2(8, 192)
|
||||
_mobile_hint.size = Vector2(500, 28)
|
||||
_mobile_hint.add_theme_font_size_override("font_size", 11)
|
||||
_mobile_hint.add_theme_color_override("font_color", Color(0.7, 0.8, 0.94))
|
||||
_mobile_emote_page.add_child(_mobile_hint)
|
||||
|
||||
func _mobile_set_state(state: String) -> void:
|
||||
if state in STATES:
|
||||
_state = state
|
||||
if _mobile_status_page:
|
||||
_mobile_status_page.visible = _state != "EMOTICON"
|
||||
if _mobile_emote_page:
|
||||
_mobile_emote_page.visible = _state == "EMOTICON"
|
||||
|
||||
func _refresh_mobile() -> void:
|
||||
if not is_open() or client == null:
|
||||
return
|
||||
var pd: Dictionary = client.get_points() if client.has_method("get_points") else {}
|
||||
var pts: Array = pd.get("points", [])
|
||||
var get := func(i: int) -> int: return int(pts[i]) if i >= 0 and i < pts.size() else 0
|
||||
_mobile_set_label("level", "Lv %d" % int(pd.get("level", get.call(P_LEVEL))))
|
||||
_mobile_set_label("exp", "%d / %d" % [_u32(int(pd.get("exp", get.call(P_EXP)))), _u32(int(pd.get("next_exp", get.call(P_NEXT_EXP))))])
|
||||
_mobile_set_label("hp", "%d / %d" % [int(pd.get("hp", get.call(P_HP))), int(pd.get("max_hp", get.call(P_MAX_HP)))])
|
||||
_mobile_set_label("sp", "%d / %d" % [int(pd.get("sp", get.call(P_SP))), int(pd.get("max_sp", get.call(P_MAX_SP)))])
|
||||
var vid := int(client.get_main_vid()) if client.has_method("get_main_vid") else 0
|
||||
var ent: Dictionary = client.get_entity(vid) if vid != 0 and client.has_method("get_entity") else {}
|
||||
_mobile_set_label("name", String(ent.get("name", "冒险者")))
|
||||
var guild_id := int(ent.get("guild", 0))
|
||||
var guild_name := String(client.get_guild_name(guild_id)) if guild_id != 0 and client.has_method("get_guild_name") else "无"
|
||||
_mobile_set_label("guild", guild_name if guild_name != "" else "无")
|
||||
_mobile_set_label("stat_points", "可用点数:%d" % max(0, get.call(P_STAT)))
|
||||
for key in _mobile_stats:
|
||||
var source: int = int({"STR": P_ST, "HTH": P_HT, "DEX": P_DX, "INT": P_IQ}.get(key, 0))
|
||||
_mobile_stats[key]["value"].text = str(get.call(int(source)))
|
||||
_mobile_stats[key]["plus"].visible = get.call(P_STAT) > 0
|
||||
_mobile_stats[key]["minus"].visible = get.call(P_STAT) > 0
|
||||
var equipment: Array = client.get_equipment() if client.has_method("get_equipment") else []
|
||||
for i in _mobile_equipment_labels.size():
|
||||
var item: Dictionary = equipment[i] if i < equipment.size() else {}
|
||||
var vnum := int(item.get("vnum", 0))
|
||||
_mobile_equipment_labels[i].text = ("#%d" % vnum) if vnum != 0 else str(i + 1)
|
||||
|
||||
func _mobile_set_label(key: String, value: String) -> void:
|
||||
if _mobile_labels.has(key) and is_instance_valid(_mobile_labels[key]):
|
||||
_mobile_labels[key].text = value
|
||||
|
||||
func _mobile_panel_style(bg := Color(0.045, 0.065, 0.1, 0.96), border := Color(0.52, 0.68, 0.9, 0.82)) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = bg
|
||||
style.border_color = border
|
||||
style.set_border_width_all(1)
|
||||
style.set_corner_radius_all(8)
|
||||
return style
|
||||
|
||||
func _node(nm: String) -> Control:
|
||||
if _win.is_empty():
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user