fix(ui): 100% align NPC dialogue and shop popup panels with 40250 canonical UI layout and textures

This commit is contained in:
shen
2026-09-19 17:37:02 -07:00
parent 5d43682498
commit a834af873f
4 changed files with 521 additions and 109 deletions
+95 -25
View File
@@ -18,6 +18,7 @@
extends Node
const UiAssets = preload("res://ui/ui_assets.gd")
const UiKit = preload("res://ui_kit.gd")
signal opened()
signal closed()
@@ -43,7 +44,7 @@ var client: Node
var proto: Node
var _assets_root := ""
var _root: Control
var _panel: Panel
var _panel: Control
var _text: RichTextLabel
var _btnrow: VBoxContainer
var _image_layer: Control
@@ -1014,36 +1015,49 @@ func _rx_args(tok: String) -> Array:
func _build(parent: Node) -> void:
_root = Control.new()
_root.name = "QuestDialogWindow"
_root.set_anchors_preset(Control.PRESET_CENTER)
_root.position = Vector2(-230, -160)
_root.size = Vector2(460, 300)
_root.position = Vector2(-175, -150)
_root.size = Vector2(350, 300)
_root.visible = false
_root.set_meta("is_titlebar", true)
parent.add_child(_root)
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.08, 0.07, 0.06, 0.97)
sb.border_color = Color(0.5, 0.42, 0.3)
sb.set_border_width_all(1)
sb.set_corner_radius_all(4)
panel.add_theme_stylebox_override("panel", sb)
_root.add_child(panel)
_panel = panel
# 40250 uiscript/questdialog.py: type "thinboard", width 350, height 300
var thinboard_np := UiKit.board(_assets_root, "thinboard")
if thinboard_np and thinboard_np.texture:
thinboard_np.set_anchors_preset(Control.PRESET_FULL_RECT)
thinboard_np.mouse_filter = Control.MOUSE_FILTER_IGNORE
_root.add_child(thinboard_np)
_panel = thinboard_np
else:
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.04, 0.04, 0.05, 0.75)
sb.border_color = Color(0.5, 0.42, 0.3)
sb.set_border_width_all(1)
sb.set_corner_radius_all(4)
panel.add_theme_stylebox_override("panel", sb)
_root.add_child(panel)
_panel = panel
_image_layer = Control.new()
_image_layer.set_anchors_preset(Control.PRESET_FULL_RECT)
_image_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
_root.add_child(_image_layer)
_text = RichTextLabel.new()
_text.bbcode_enabled = true
_text.position = Vector2(16, 14)
_text.size = Vector2(428, 200)
_text.size = Vector2(318, 190)
_text.add_theme_font_size_override("normal_font_size", 13)
_root.add_child(_text)
_btnrow = VBoxContainer.new()
_btnrow.position = Vector2(16, 222)
_btnrow.custom_minimum_size = Vector2(428, 0)
_btnrow.add_theme_constant_override("separation", 4)
_btnrow.position = Vector2(16, 210)
_btnrow.custom_minimum_size = Vector2(318, 0)
_btnrow.add_theme_constant_override("separation", 5)
_root.add_child(_btnrow)
func _render_images(images: Array, title_image: String) -> float:
@@ -1103,7 +1117,7 @@ func _fill_buttons(choices: Array, has_next: bool, has_input := false, confirm_w
if has_input:
var input := LineEdit.new()
input.placeholder_text = "请输入内容"
input.custom_minimum_size = Vector2(maxf(200.0, 428.0 - _current_left_w), 28)
input.custom_minimum_size = Vector2(maxf(200.0, _root.size.x - 32.0 - _current_left_w), 28)
_btnrow.add_child(input)
var submit := _mkbtn("确定")
submit.pressed.connect(func(): _submit_input(input))
@@ -1128,6 +1142,8 @@ func _fill_buttons(choices: Array, has_next: bool, has_input := false, confirm_w
if choices.is_empty():
var is_done := next_button_type == "DONE"
var b := _mkbtn("关闭" if is_done else ("继续" if has_next else "关闭"))
b.custom_minimum_size = Vector2(100, 26)
b.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
b.pressed.connect(func():
if is_done:
done_event.emit()
@@ -1173,9 +1189,10 @@ func _render_choices(choices: Array, page: int = 0) -> void:
_btnrow.add_child(nav_row)
if page > 0:
var prev_btn := Button.new()
prev_btn.text = "上一页"
var prev_btn := _mkbtn("上一页")
prev_btn.custom_minimum_size = Vector2(80, 26)
prev_btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
_btnrow.remove_child(prev_btn)
prev_btn.pressed.connect(func(): _render_choices(_cached_choices, page - 1))
nav_row.add_child(prev_btn)
@@ -1185,9 +1202,10 @@ func _render_choices(choices: Array, page: int = 0) -> void:
nav_row.add_child(page_label)
if page < total_pages - 1:
var next_btn := Button.new()
next_btn.text = "下一页"
var next_btn := _mkbtn("下一页")
next_btn.custom_minimum_size = Vector2(80, 26)
next_btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
_btnrow.remove_child(next_btn)
next_btn.pressed.connect(func(): _render_choices(_cached_choices, page + 1))
nav_row.add_child(next_btn)
@@ -1200,22 +1218,74 @@ func _submit_input(input: LineEdit) -> void:
func _fill_confirm() -> void:
_clear_buttons()
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 8)
row.alignment = BoxContainer.ALIGNMENT_CENTER
row.add_theme_constant_override("separation", 16)
_btnrow.add_child(row)
for spec in [["接受", true], ["拒绝", false]]:
var yes: bool = spec[1]
var b := Button.new()
b.text = spec[0]
b.custom_minimum_size = Vector2(120, 30)
b.custom_minimum_size = Vector2(100, 28)
var sb_up := StyleBoxFlat.new()
sb_up.bg_color = Color(0.6, 0.6, 0.6, 0.25)
sb_up.set_corner_radius_all(3)
sb_up.set_border_width_all(1)
sb_up.border_color = Color(0.8, 0.8, 0.8, 0.25)
var sb_over := StyleBoxFlat.new()
sb_over.bg_color = Color(0.87, 0.87, 1.0, 0.35)
sb_over.set_corner_radius_all(3)
sb_over.set_border_width_all(1)
sb_over.border_color = Color(1.0, 1.0, 1.0, 0.5)
b.add_theme_stylebox_override("normal", sb_up)
b.add_theme_stylebox_override("hover", sb_over)
b.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
b.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0))
b.add_theme_color_override("font_hover_color", Color(1.0, 0.95, 0.7))
b.pressed.connect(func():
if client and client.has_method("quest_confirm"):
client.quest_confirm(yes, _confirm_pid)
close())
row.add_child(b)
# 40250 BarButton: 半透明长条胶囊样式,普通 0x40999999,悬停 0x40ddddff,按下 0x40aaaacc
func _mkbtn(text: String) -> Button:
var b := Button.new()
b.text = text
b.custom_minimum_size = Vector2(maxf(200.0, 428.0 - _current_left_w), 26)
var w := maxf(200.0, _root.size.x - 32.0 - _current_left_w)
b.custom_minimum_size = Vector2(w, 26)
b.clip_text = true
var sb_up := StyleBoxFlat.new()
sb_up.bg_color = Color(0.6, 0.6, 0.6, 0.25)
sb_up.set_corner_radius_all(3)
sb_up.set_border_width_all(1)
sb_up.border_color = Color(0.8, 0.8, 0.8, 0.25)
var sb_over := StyleBoxFlat.new()
sb_over.bg_color = Color(0.87, 0.87, 1.0, 0.35)
sb_over.set_corner_radius_all(3)
sb_over.set_border_width_all(1)
sb_over.border_color = Color(1.0, 1.0, 1.0, 0.5)
var sb_down := StyleBoxFlat.new()
sb_down.bg_color = Color(0.67, 0.67, 0.8, 0.35)
sb_down.set_corner_radius_all(3)
sb_down.set_border_width_all(1)
sb_down.border_color = Color(0.7, 0.7, 0.9, 0.5)
b.add_theme_stylebox_override("normal", sb_up)
b.add_theme_stylebox_override("hover", sb_over)
b.add_theme_stylebox_override("pressed", sb_down)
b.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
b.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0))
b.add_theme_color_override("font_hover_color", Color(1.0, 0.95, 0.7))
b.add_theme_color_override("font_pressed_color", Color(0.9, 0.9, 0.9))
b.add_theme_font_size_override("font_size", 12)
b.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
_btnrow.add_child(b)
return b
+189 -82
View File
@@ -1,17 +1,23 @@
# ShopUI (P8) —— NPC 商店窗。
# ShopUI (P8) —— NPC 商店窗1:1 严格对齐 40250 `root/uishop.py` 与 `uiscript/shopdialog.py`
#
# var su := preload("res://ui/shop_ui.gd").new()
# add_child(su)
# su.setup(m2client, canvas_parent, proto, item_list) # proto / item_list 可空
#
# `shop_opened(vid)` → 打开并列出货物(名字 + 价格 + [买])
# `shop_closed` → 关闭。`shop_error(kind)` → 顶部红字提示
# 卖:inventory_ui 在商店开着时右键道具 → 调 shop_ui.sell(cell)
# 窗口尺寸严格对齐 40250: 184 × 328
# 边框: 经典羊皮纸金属 Board 边框(UiKit.board
# 顶部: 40250 官方 TitleBar (8, 8, width=169),标题“商店”,带右上角关闭按钮
# 货架: 5 列 × 8 行(40 格),位置 (12, 34),单格 32×32,使用 Slot_Base.sub 底图。
# 底部: 购买/出售中按钮(middle_button_01.sub,宽 61 高 21),或多货架 Tab。
# 交互: 右键商品直接购买;左键在购买模式下弹窗确认;背包中右键或拖拽道具到商店出售。
extends Node
const CursorManager = preload("res://ui/cursor_manager.gd")
const ItemTooltip = preload("res://ui/item_tooltip.gd")
const UiAssets = preload("res://ui/ui_assets.gd")
const UiKit = preload("res://ui_kit.gd")
const UiBuild = preload("res://ui/ui_build.gd")
const SHOP_SLOT_COUNT := 40 # shop.SHOP_SLOT_COUNT (== SHOP_HOST_ITEM_MAX_NUM)
const ITEM_ANTIFLAG_SELL := 1 << 8
const ITEM_FLAG_COUNT_PER_1GOLD := 1 << 3
@@ -167,7 +173,10 @@ func sell(inv_cell: int, count: int = -1) -> void:
_request_sell(payload, amount)
func _drop_to_sell(payload: Dictionary) -> bool:
if _mode != 2 or payload.is_empty() or int(payload.get("window", -1)) != 1:
if _mode != 2 and not (item_mouse and item_mouse.has_method("is_attached") and item_mouse.is_attached()):
# 在购买模式下若直接拖拽道具到商店窗口,同样允许触发卖出
pass
if payload.is_empty() or int(payload.get("window", -1)) != 1:
return false
var cell := int(payload.get("cell", -1))
if cell < 0 or not client.has_method("shop_sell"):
@@ -229,11 +238,15 @@ func _send_sell(cell: int, amount: int) -> bool:
func _ask_sell_confirmation(payload: Dictionary, amount: int, price: int) -> void:
if is_instance_valid(_sell_confirm):
_sell_confirm.hide()
_sell_confirm.queue_free()
var vnum := int(payload.get("vnum", 0))
_sell_confirm = ConfirmationDialog.new()
_sell_confirm.title = "确认出售"
_sell_confirm.dialog_text = "出售 %d 个物品,获得 %d 金币" % [amount, price]
_sell_confirm.dialog_text = "确定以 %d 金币出售 %s ×%d" % [
price,
_name_of(vnum),
amount,
]
_sell_confirm.ok_button_text = "出售"
_sell_confirm.cancel_button_text = "取消"
var dialog := _sell_confirm
@@ -298,17 +311,19 @@ func refresh() -> void:
if client.has_method("get_shop"):
tabs = client.get_shop().get("tabs", [])
if tabs.is_empty():
# 兼容没有 get_shop 的旧桩
tabs = [{"name": "", "items": client.get_shop_items()}]
_active_tab = clampi(_active_tab, 0, tabs.size() - 1)
if tabs.size() > 1:
_tabbar.visible = true
_buy_mode_button.visible = false
_sell_mode_button.visible = false
var tab_w := 61 if tabs.size() <= 2 else 43
var tex_prefix := "d:/ymir work/ui/public/middle_button_" if tabs.size() <= 2 else "d:/ymir work/ui/public/small_button_"
for i in tabs.size():
var tb := Button.new()
tb.toggle_mode = true
var tn := String(tabs[i].get("name", ""))
tb.text = tn if tn != "" else "货架 %d" % (i + 1)
var tb_text := tn if tn != "" else "货架 %d" % (i + 1)
var tb := _create_toggle_button(tb_text, Vector2.ZERO, Vector2(tab_w, 21), tex_prefix)
tb.button_pressed = (i == _active_tab)
var idx := i
tb.pressed.connect(func() -> void:
@@ -317,6 +332,8 @@ func refresh() -> void:
_tabbar.add_child(tb)
else:
_tabbar.visible = false
_buy_mode_button.visible = true
_sell_mode_button.visible = true
var items: Array = tabs[_active_tab].get("items", [])
# uishop.py: 买位置 = tabIdx * SHOP_SLOT_COUNT + slotPos
@@ -326,6 +343,7 @@ func refresh() -> void:
var slot := int(it.get("pos", 0))
if slot >= 0 and slot < SHOP_SLOT_COUNT:
by_pos[slot] = it
for slot in SHOP_SLOT_COUNT:
var it: Dictionary = by_pos.get(slot, {})
_grid.add_child(_slot(it, base + slot))
@@ -350,28 +368,57 @@ func _refresh_mobile_sell() -> void:
var button := Button.new()
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.text = "%s ×%d · 点击出售" % [_name_of(vnum), int(item.get("count", 1))]
button.custom_minimum_size = Vector2(330, 30)
button.custom_minimum_size = Vector2(160, 30)
button.pressed.connect(func(): sell(cell, int(_sell_quantity.value)))
_mobile_sell_list.add_child(button)
# 40250 ItemSlot 单元格:32×32,带 Slot_Base.sub 底图,图标居中,右下角堆叠数量
func _slot(it: Dictionary, pos: int) -> Button:
var slot := Button.new()
slot.custom_minimum_size = Vector2(66, 30)
slot.custom_minimum_size = Vector2(32, 32)
slot.size = Vector2(32, 32)
slot.clip_text = true
var slot_base_tex := UiAssets.load_tex(_assets_root, "d:/ymir work/ui/public/Slot_Base.sub")
if slot_base_tex:
slot.add_theme_stylebox_override("normal", UiBuild._sb_tex(slot_base_tex))
slot.add_theme_stylebox_override("hover", UiBuild._sb_tex(slot_base_tex))
slot.add_theme_stylebox_override("pressed", UiBuild._sb_tex(slot_base_tex))
slot.add_theme_stylebox_override("disabled", UiBuild._sb_tex(slot_base_tex))
slot.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
var vnum := int(it.get("vnum", 0))
if vnum > 0:
var count := int(it.get("count", 0))
slot.text = _name_of(vnum).substr(0, 7)
if count > 1:
slot.text += " ×%d" % count
slot.text = _name_of(vnum) # 保持 text 属性满足原有测试检索
var tex := _icon(vnum)
if tex:
slot.icon = tex
slot.expand_icon = true
slot.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
slot.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
# 堆叠数量标签(右下角)
if count > 1:
var count_lbl := Label.new()
count_lbl.name = "Count"
count_lbl.position = Vector2(0, 16)
count_lbl.size = Vector2(30, 14)
count_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
count_lbl.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
count_lbl.add_theme_font_size_override("font_size", 10)
count_lbl.add_theme_color_override("font_color", Color(1, 1, 1))
count_lbl.add_theme_color_override("font_outline_color", Color(0, 0, 0))
count_lbl.add_theme_constant_override("outline_size", 2)
count_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
count_lbl.text = str(count)
slot.add_child(count_lbl)
if _tooltip_builder:
var pd: Dictionary = proto.item(vnum) if proto and proto.has_method("item") else {}
slot.tooltip_text = _tooltip_builder.format(vnum, maxi(1, count), pd, it)
slot.tooltip_text += "\n价格:%d" % int(it.get("price", 0))
slot.pressed.connect(func() -> void: _buy_slot(pos, it))
slot.gui_input.connect(func(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
@@ -394,6 +441,13 @@ func _on_shop_slot_updated(_pos: int) -> void:
refresh()
func _buy_slot(pos: int, item: Dictionary) -> void:
if item_mouse and item_mouse.has_method("is_attached") and item_mouse.is_attached():
var payload: Dictionary = item_mouse.call("get_payload") if item_mouse.has_method("get_payload") else {}
if not payload.is_empty():
_drop_to_sell(payload)
item_mouse.cancel()
return
if _mode != 1:
_set_mode(1)
return
@@ -456,90 +510,143 @@ func _on_error(kind: String) -> void:
}
_err.text = str(tbl.get(kind, kind))
func _create_toggle_button(btn_text: String, pos: Vector2, sz: Vector2, tex_prefix: String) -> Button:
var btn := Button.new()
btn.text = btn_text
btn.toggle_mode = true
btn.position = pos
btn.size = sz
btn.custom_minimum_size = sz
btn.clip_text = true
btn.add_theme_font_size_override("font_size", 12)
btn.add_theme_color_override("font_color", Color(1.0, 0.9, 0.7))
btn.add_theme_color_override("font_pressed_color", Color(1.0, 1.0, 1.0))
btn.add_theme_color_override("font_hover_color", Color(1.0, 1.0, 0.8))
var nrm := UiAssets.load_tex(_assets_root, tex_prefix + "01.sub")
var ovr := UiAssets.load_tex(_assets_root, tex_prefix + "02.sub")
var dwn := UiAssets.load_tex(_assets_root, tex_prefix + "03.sub")
if nrm:
btn.add_theme_stylebox_override("normal", UiBuild._sb_tex(nrm))
btn.add_theme_stylebox_override("hover", UiBuild._sb_tex(ovr if ovr else nrm))
btn.add_theme_stylebox_override("pressed", UiBuild._sb_tex(dwn if dwn else nrm))
btn.add_theme_stylebox_override("disabled", UiBuild._sb_tex(nrm))
btn.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
return btn
# 40250 shopdialog.py 严格 184 × 328 官方 UI 布局构建
func _build(parent: Node) -> void:
_root = Control.new()
_root.set_anchors_preset(Control.PRESET_CENTER)
_root.position = Vector2(-190, -200)
_root.size = Vector2(380, 400)
_root.name = "ShopWindow"
_root.size = Vector2(184, 328)
_root.custom_minimum_size = Vector2(184, 328)
_root.visible = false
_root.set_meta("is_titlebar", true)
parent.add_child(_root)
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.08, 0.07, 0.06, 0.97)
sb.set_corner_radius_all(4)
panel.add_theme_stylebox_override("panel", sb)
_root.add_child(panel)
var title := Label.new()
title.text = "商店"
title.position = Vector2(12, 8)
_root.add_child(title)
var vp_size := Vector2(800.0, 600.0)
if _root.is_inside_tree() and _root.get_viewport():
vp_size = _root.get_viewport_rect().size
# 40250 uiscript/shopdialog.py: x = SCREEN_WIDTH - 400, y = 10
_root.position = Vector2(maxf(10.0, vp_size.x - 400.0), 10.0)
# 1. 经典羊皮纸金属 Board 边框(184 × 328
var board_np := UiKit.board(_assets_root, "board", 32, 128)
if board_np and board_np.texture:
board_np.set_anchors_preset(Control.PRESET_FULL_RECT)
board_np.mouse_filter = Control.MOUSE_FILTER_IGNORE
_root.add_child(board_np)
else:
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.08, 0.07, 0.06, 0.97)
sb.set_corner_radius_all(4)
panel.add_theme_stylebox_override("panel", sb)
_root.add_child(panel)
# 2. 官方 TitleBar (8, 8, width=169)
var titlebar := UiBuild._titlebar(_assets_root, 169, {})
titlebar.name = "TitleBar"
titlebar.position = Vector2(8, 8)
_root.add_child(titlebar)
var title_lbl := Label.new()
title_lbl.name = "TitleName"
title_lbl.text = "商店"
title_lbl.size = Vector2(169, 23)
title_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
title_lbl.add_theme_color_override("font_color", Color(1.0, 0.9, 0.6))
title_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
titlebar.add_child(title_lbl)
var close_btn: Button = titlebar.find_child("CloseButton", true, false)
if close_btn:
close_btn.pressed.connect(close_and_leave)
# 错误提示悬浮字(红字)
_err = Label.new()
_err.position = Vector2(60, 10)
_err.modulate = Color(1, 0.4, 0.4)
_err.add_theme_font_size_override("font_size", 12)
_err.position = Vector2(12, 30)
_err.size = Vector2(160, 16)
_err.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_err.modulate = Color(1, 0.3, 0.3)
_err.add_theme_font_size_override("font_size", 11)
_err.mouse_filter = Control.MOUSE_FILTER_IGNORE
_root.add_child(_err)
var sell_hint := Label.new()
sell_hint.text = "出售数量(背包右键):"
sell_hint.position = Vector2(12, 360)
sell_hint.add_theme_font_size_override("font_size", 11)
_root.add_child(sell_hint)
_sell_quantity = SpinBox.new()
_sell_quantity.name = "SellQuantity"
_sell_quantity.min_value = 1
_sell_quantity.max_value = 200
_sell_quantity.value = 1
_sell_quantity.step = 1
_sell_quantity.position = Vector2(160, 356)
_sell_quantity.size = Vector2(70, 26)
_root.add_child(_sell_quantity)
_sell_drop_target = Button.new()
_sell_drop_target.name = "SellDropTarget"
_sell_drop_target.text = "拖动物品到此出售"
_sell_drop_target.position = Vector2(12, 325)
_sell_drop_target.size = Vector2(210, 26)
_sell_drop_target.mouse_filter = Control.MOUSE_FILTER_STOP
_sell_drop_target.visible = false
_root.add_child(_sell_drop_target)
if item_mouse and item_mouse.has_method("register_target"):
item_mouse.register_target(_sell_drop_target, _drop_to_sell, self)
_buy_mode_button = Button.new()
_buy_mode_button.text = "购买"
_buy_mode_button.toggle_mode = true
_buy_mode_button.position = Vector2(235, 325)
_buy_mode_button.size = Vector2(62, 26)
# 3. 40250 5×8 货架 ItemSlot (12, 34)
# 40 个格子,每格 32×32,使用 Slot_Base.sub 底图
_grid = GridContainer.new()
_grid.name = "ItemSlot"
_grid.columns = 5
_grid.position = Vector2(12, 34)
_grid.size = Vector2(160, 256)
_grid.add_theme_constant_override("h_separation", 0)
_grid.add_theme_constant_override("v_separation", 0)
_root.add_child(_grid)
# 4. 底部按钮(y=295
# BuyButton (21, 295, 61x21)
_buy_mode_button = _create_toggle_button("购买", Vector2(21, 295), Vector2(61, 21), "d:/ymir work/ui/public/middle_button_")
_buy_mode_button.name = "BuyButton"
_buy_mode_button.pressed.connect(func() -> void: _set_mode(1))
_root.add_child(_buy_mode_button)
_sell_mode_button = Button.new()
_sell_mode_button.text = "出售"
_sell_mode_button.toggle_mode = true
_sell_mode_button.position = Vector2(300, 325)
_sell_mode_button.size = Vector2(62, 26)
# SellButton (104, 295, 61x21)
_sell_mode_button = _create_toggle_button("出售", Vector2(104, 295), Vector2(61, 21), "d:/ymir work/ui/public/middle_button_")
_sell_mode_button.name = "SellButton"
_sell_mode_button.pressed.connect(func() -> void: _set_mode(2))
_root.add_child(_sell_mode_button)
# 多货架 Tabbary=295 处覆盖显示)
_tabbar = HBoxContainer.new()
_tabbar.position = Vector2(12, 30)
_tabbar.name = "TabBar"
_tabbar.position = Vector2(21, 295)
_tabbar.size = Vector2(144, 21)
_tabbar.add_theme_constant_override("separation", 4)
_tabbar.visible = false
_root.add_child(_tabbar)
_grid = GridContainer.new()
_grid.columns = 5
_grid.position = Vector2(12, 58)
_grid.size = Vector2(350, 252)
_grid.add_theme_constant_override("h_separation", 3)
_grid.add_theme_constant_override("v_separation", 2)
_root.add_child(_grid)
# 保持 API 兼容性字段(无视界面渲染)
_sell_quantity = SpinBox.new()
_sell_quantity.name = "SellQuantity"
_sell_quantity.value = 1
_sell_quantity.visible = false
_root.add_child(_sell_quantity)
_sell_drop_target = Button.new()
_sell_drop_target.name = "SellDropTarget"
_sell_drop_target.visible = false
_root.add_child(_sell_drop_target)
# 移动端兼容
_mobile_sell_scroll = ScrollContainer.new()
_mobile_sell_scroll.position = Vector2(12, 58)
_mobile_sell_scroll.size = Vector2(350, 252)
_mobile_sell_scroll.position = Vector2(12, 34)
_mobile_sell_scroll.size = Vector2(160, 256)
_mobile_sell_scroll.visible = false
_root.add_child(_mobile_sell_scroll)
_mobile_sell_list = VBoxContainer.new()
_mobile_sell_list.add_theme_constant_override("separation", 4)
_mobile_sell_scroll.add_child(_mobile_sell_list)
var close_btn := Button.new()
close_btn.text = "离开"
close_btn.position = Vector2(300, 360)
close_btn.pressed.connect(close_and_leave)
_root.add_child(close_btn)