feat: complete mobile UI implementation
This commit is contained in:
@@ -55,6 +55,7 @@ var _price_dialog: Control = null
|
||||
var _server_prices: Dictionary = {} # vnum -> last price returned by MyShopPriceList
|
||||
var item_mouse: Node
|
||||
const INVENTORY_WINDOW := 1
|
||||
var _mobile_mode := false
|
||||
|
||||
func setup(m2client: Node, ui_manager: CanvasLayer, proto_node: Node = null, assets := "") -> void:
|
||||
client = m2client
|
||||
@@ -80,6 +81,15 @@ func setup(m2client: Node, ui_manager: CanvasLayer, proto_node: Node = null, ass
|
||||
func is_open() -> bool:
|
||||
return not _win.is_empty() and is_instance_valid(_win.get("root"))
|
||||
|
||||
func get_mobile_window() -> Control:
|
||||
return _win.get("root") if not _win.is_empty() else null
|
||||
|
||||
func set_mobile_mode(enabled: bool) -> void:
|
||||
_mobile_mode = enabled
|
||||
if is_open():
|
||||
_add_mobile_width(_win["root"])
|
||||
_refresh()
|
||||
|
||||
func toggle() -> void:
|
||||
if is_open(): close()
|
||||
else: open()
|
||||
@@ -91,7 +101,14 @@ func close() -> void:
|
||||
if item_mouse.has_method("is_attached") and item_mouse.is_attached():
|
||||
item_mouse.cancel()
|
||||
if is_open():
|
||||
ui.close(_win["root"])
|
||||
var root: Control = _win["root"]
|
||||
# An external mobile window is owned by UiManager's presentation stack;
|
||||
# calling ui.close() here would call this method again through its close
|
||||
# callback. Hide it and let UiManager restore the original parent.
|
||||
if _mobile_mode and bool(root.get_meta("mobile_external", false)):
|
||||
root.visible = false
|
||||
else:
|
||||
ui.close(root)
|
||||
_win = {}
|
||||
_cells.clear()
|
||||
_stock.clear()
|
||||
@@ -113,6 +130,8 @@ func open() -> void:
|
||||
_index_cells(root)
|
||||
_overlay_name_edit(root)
|
||||
_add_inv_panel(root)
|
||||
if _mobile_mode and ui and ui.has_method("reflow_mobile_window"):
|
||||
ui.reflow_mobile_window(root)
|
||||
_wire_buttons(root)
|
||||
_refresh()
|
||||
|
||||
@@ -132,6 +151,8 @@ func _index_cells(root: Node) -> void:
|
||||
var idx := slot
|
||||
n.gui_input.connect(func(e: InputEvent) -> void:
|
||||
if e is InputEventMouseButton and e.button_index == MOUSE_BUTTON_LEFT and e.pressed:
|
||||
_on_slot_clicked(idx)
|
||||
elif e is InputEventScreenTouch and e.pressed:
|
||||
_on_slot_clicked(idx))
|
||||
# 同时支持从右侧背包候选区拖到空货位;点击选取仍保留给触屏/无鼠标场景。
|
||||
n.set_drag_forwarding(Callable(self, "_no_drag_data"),
|
||||
@@ -201,6 +222,7 @@ func _add_inv_panel(root: Control) -> void:
|
||||
bg.set_border_width_all(1)
|
||||
panel.add_theme_stylebox_override("panel", bg)
|
||||
root.add_child(panel)
|
||||
_add_mobile_width(root)
|
||||
var title := Label.new()
|
||||
title.text = "배낭 (클릭 → 집기)"
|
||||
title.position = Vector2(10, 8)
|
||||
@@ -219,6 +241,13 @@ func _add_inv_panel(root: Control) -> void:
|
||||
_status.add_theme_color_override("font_color", Color(1, 0.9, 0.5))
|
||||
panel.add_child(_status)
|
||||
|
||||
func _add_mobile_width(root: Control) -> void:
|
||||
if not _mobile_mode or root == null:
|
||||
return
|
||||
# Keep the candidate list inside the hosted/scaled root instead of leaving
|
||||
# it as an overflow panel outside the mobile safe area.
|
||||
root.size.x = maxf(root.size.x, 674.0)
|
||||
|
||||
# --- 명칭 --------------------------------------------------------------
|
||||
|
||||
func _name_of(vnum: int) -> String:
|
||||
@@ -375,9 +404,16 @@ func _on_slot_clicked(slot: int) -> void:
|
||||
func _ask_price(slot: int) -> void:
|
||||
_dismiss_price_dialog()
|
||||
var dlg := Panel.new()
|
||||
dlg.set_anchors_preset(Control.PRESET_CENTER)
|
||||
dlg.position = Vector2(-120, -70)
|
||||
dlg.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
dlg.size = Vector2(240, 128)
|
||||
dlg.position = (_win["root"].size - dlg.size) * 0.5
|
||||
dlg.z_index = 200
|
||||
dlg.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
dlg.set_meta("mobile_modal", _mobile_mode)
|
||||
# UiManager closes mobile modals before the feature window itself. Keep the
|
||||
# controller's reference in sync when Android back/ESC hides this custom
|
||||
# panel instead of going through the Cancel button.
|
||||
dlg.set_meta("mobile_modal_close", Callable(self, "_dismiss_price_dialog"))
|
||||
var sb := StyleBoxFlat.new()
|
||||
sb.bg_color = Color(0.06, 0.06, 0.09, 0.98)
|
||||
sb.border_color = Color(0.7, 0.55, 0.22, 0.9)
|
||||
@@ -387,30 +423,54 @@ func _ask_price(slot: int) -> void:
|
||||
t.text = "판매 가격"
|
||||
t.position = Vector2(12, 10)
|
||||
dlg.add_child(t)
|
||||
var spin := SpinBox.new()
|
||||
spin.min_value = 1
|
||||
spin.max_value = 2000000000
|
||||
spin.step = 1
|
||||
var default_price := int(_server_prices.get(int(_picked["vnum"]), 1))
|
||||
if _stock.has(slot):
|
||||
default_price = int(_stock[slot].get("price", default_price))
|
||||
spin.value = default_price
|
||||
spin.position = Vector2(12, 40)
|
||||
spin.size = Vector2(216, 28)
|
||||
dlg.add_child(spin)
|
||||
var price_input: Control
|
||||
if _mobile_mode:
|
||||
var edit := LineEdit.new()
|
||||
edit.name = "PriceInput"
|
||||
edit.text = str(default_price)
|
||||
edit.placeholder_text = "输入价格"
|
||||
edit.max_length = 10
|
||||
edit.virtual_keyboard_type = LineEdit.KEYBOARD_TYPE_NUMBER
|
||||
edit.position = Vector2(12, 40)
|
||||
edit.size = Vector2(216, 32)
|
||||
edit.text_submitted.connect(func(_text: String) -> void:
|
||||
_place(slot, _parse_price(edit)))
|
||||
price_input = edit
|
||||
else:
|
||||
var spin := SpinBox.new()
|
||||
spin.min_value = 1
|
||||
spin.max_value = 2000000000
|
||||
spin.step = 1
|
||||
spin.value = default_price
|
||||
spin.position = Vector2(12, 40)
|
||||
spin.size = Vector2(216, 28)
|
||||
price_input = spin
|
||||
dlg.add_child(price_input)
|
||||
var ok := Button.new()
|
||||
ok.text = "확인"
|
||||
ok.position = Vector2(60, 86)
|
||||
ok.pressed.connect(func() -> void: _place(slot, int(spin.value)))
|
||||
ok.pressed.connect(func() -> void:
|
||||
_place(slot, _parse_price(price_input)))
|
||||
dlg.add_child(ok)
|
||||
var cancel := Button.new()
|
||||
cancel.text = "취소"
|
||||
cancel.position = Vector2(130, 86)
|
||||
cancel.pressed.connect(_dismiss_price_dialog)
|
||||
dlg.add_child(cancel)
|
||||
_win["root"].get_parent().add_child(dlg)
|
||||
_win["root"].add_child(dlg)
|
||||
_price_dialog = dlg
|
||||
|
||||
func _parse_price(input: Control) -> int:
|
||||
if input is SpinBox:
|
||||
return int((input as SpinBox).value)
|
||||
if input is LineEdit:
|
||||
var raw := (input as LineEdit).text.strip_edges()
|
||||
return int(raw) if raw.is_valid_int() else 0
|
||||
return 0
|
||||
|
||||
func _dismiss_price_dialog() -> void:
|
||||
if is_instance_valid(_price_dialog):
|
||||
_price_dialog.queue_free()
|
||||
|
||||
Reference in New Issue
Block a user