在 GUI 上打开子页面
子页面适合制作确认框、详情卡片和临时设置面板。它不会替换当前 GUI,而是显示在已有页面上方。父页面和子页面都能点击:鼠标先从上到下检查子页面,子页面空白处没有命中目标时,点击会继续到父页面。
子页面本身必须是 display.mode=screen。根页面可以是普通 screen 页面,也可以是正在替换原版容器的 container 页面;HUD 和世界页面不能作为子页面宿主。页面链最多包含 8 层子页面,同一个页面 ID 不能在当前链中重复,因此页面自循环和 A → B → A 都会被拒绝。
第一步:创建父页面
保存为 subpage_parent.yml。点击按钮后,脚本会调用 openSub("subpage_child"):
id: subpage_parent
version: 1
title: 子页面父页
size:
width: 260
height: 150
coordinateMode: absolute
display:
mode: screen
screen:
dimBackground: false
vars:
parentClicks: 0
events:
mouse.left: |-
vars.parentClicks = vars.parentClicks + 1
methods: {}
elements:
- id: parent_panel
type: rect
layout:
x: 20
y: 20
width: 220
height: 110
rect:
color: "#26303A"
- id: parent_tip
type: text
layout:
x: 38
y: 38
width: 184
height: 20
text:
value: "父页面点击次数:{vars.parentClicks}"
textSize: 1.0
color: "#FFFFFF"
- id: open_child
type: button
layout:
x: 80
y: 82
width: 100
height: 24
button:
label: 打开子页面
textSize: 1.0
events:
leftClick: |-
openSub("subpage_child")页面根级鼠标事件只在指针路由实际到达这一层时触发。点击子页面自身按钮时,父页的 mouse.left 不会同时执行;点击子页面未拦截的空白处时,路由会继续到父页。
第二步:创建可穿透空白处的子页面
保存为 subpage_child.yml。这个页面只在中央绘制一块小面板;全页辅助矩形使用 pointerEvents: pass,因此它完全退出命中,面板外仍能操作父页面。
id: subpage_child
version: 1
title: 非模态子页面
size:
width: 260
height: 150
coordinateMode: absolute
display:
mode: screen
screen:
dimBackground: false
vars:
childClicks: 0
events: {}
methods: {}
elements:
- id: click_surface
type: rect
layout:
x: 0
y: 0
width: window.width
height: window.height
pointerEvents: pass
rect:
color: "#000000"
- id: child_panel
type: rect
layout:
x: 70
y: 42
width: 120
height: 68
pointerEvents: block
rect:
color: "#3B4652"
- id: child_text
type: text
layout:
x: 84
y: 56
width: 92
height: 16
text:
value: "子页:{vars.childClicks}"
textSize: 1.0
color: "#FFFFFF"
- id: child_button
type: button
layout:
x: 92
y: 78
width: 76
height: 20
button:
label: 点击子页
textSize: 1.0
events:
leftClick: |-
vars.childClicks = vars.childClicks + 1每层页面都有独立的 sessionId、变量、输入值、动画、计时器和素材资源。父页和子页即使使用相同变量名,也不会互相覆盖。
改成模态子页面
需要阻止父页面点击时,不要依赖隐式“模态”开关。创建一个铺满页面的 rect,并设置 pointerEvents: block。保存下面的完整示例为 subpage_modal.yml:
id: subpage_modal
version: 1
title: 模态子页面
size:
width: 260
height: 150
coordinateMode: absolute
display:
mode: screen
screen:
dimBackground: false
vars: {}
events: {}
methods: {}
elements:
- id: modal_mask
type: rect
layout:
x: 0
y: 0
width: window.width
height: window.height
pointerEvents: block
opacity: 0.6
rect:
color: "#000000"
- id: modal_panel
type: rect
layout:
x: 65
y: 40
width: 130
height: 72
pointerEvents: block
rect:
color: "#303A45"
- id: modal_title
type: text
layout:
x: 82
y: 56
width: 96
height: 18
text:
value: 确认操作?
textSize: 1.0
align: center
color: "#FFFFFF"
- id: modal_close
type: button
layout:
x: 92
y: 82
width: 76
height: 20
button:
label: 返回
textSize: 1.0
events:
leftClick: close()遮罩只阻断指针路由,不会暂停父页面的计时器、变量刷新或生命周期。需要暂停业务逻辑时,请用页面变量和脚本明确控制。
焦点、Esc 和关闭范围
页面栈只有一个键盘焦点归属。点击输入框所在的页面后,键盘输入会转移到那一层;没有焦点时默认由栈顶页面接收。Esc 的处理顺序固定为:先收起当前展开的下拉选择框,再关闭栈顶子页面,最后才按根 GUI 原有行为关闭页面。
close()/关闭():关闭触发脚本的当前页面。子页面调用时只关闭自己及其上方页面。/chaui closesub或ChaUIAPI.closeSubPage(...):只关闭当前栈顶子页面。closeAll()/全部关闭():从栈顶一路关闭到根页面,不影响 HUD 和世界页面。若根页面是容器替换页面,真实原版容器也会正常关闭,并继续触发 Bukkit 和第三方插件的库存关闭流程。
按下鼠标后,目标页面和组件会被捕获到本次点击结束;释放不会因为页面间临时重叠而误发给另一层。
指令与服务端 API
/chaui opensub <玩家> <页面ID>
/chaui closesub <玩家>ChaUIAPI.openSubPage(player, "subpage_child");
ChaUIAPI.closeSubPage(player);用子页面制作同级 Tab
顶部 Tab、分类页和分页式功能区通常不是“继续叠一层”,而是把根页面上方的当前内容页替换成另一个内容页。这类场景应使用 replaceSubPage:
ChaUIAPI.replaceSubPage(player, "barn_shop").thenAccept(sessionId -> {
ChaUIAPI.setVariables(player, "barn_shop", Map.of(
"selected", "cow",
"quantity", 1
));
});这个方法会先让旧子页面正常关闭,再把新页面直接挂到根页面。返回结果只会在新页面已经打开后完成,因此适合在回调中发送该页变量、物品展示和其他初始状态。
快速连续点击多个 Tab 时只保留最后一次目标,不会把每个内容页依次压入更深的页面栈。当前子页面已经是目标页面时会直接复用当前会话。
不要手动拼接关闭与打开
不要为了切换同级内容而先调用 closeSubPage,紧接着调用 openSubPage。关闭页面需要完成生命周期和临时槽物品收尾,立即打开可能把新页面挂到旧页面之上。请直接使用 replaceSubPage。
根页面适合只保留标题、状态、Tab 和公共按钮;每个 Tab 的卡片、物品展示、临时槽和操作按钮放在各自子页面中。这样页面更容易维护,打开时也只需要处理当前可见功能。
reload 时会发生什么
执行 /chaui reload 时,旧链从栈顶到根页面依次执行 close 并释放资源;新配置加载后,先恢复根页面,再从第一层到栈顶逐层执行 open 和组件 create。某一层页面缺失、无效或不再是 screen 时,会从这一层停止恢复,已恢复的父级继续保留。容器替换根页面不会因为 reload 关闭真实容器。
常见错误
子页面打不开
确认子页面是 display.mode=screen,根页面是当前活动的 screen 或 container GUI,并且当前链未达到 8 层。HUD、世界页面、重复页面 ID 和循环链都会被拒绝。
父页面完全点不到
检查子页面是否放置了 pointerEvents: block 的全屏组件。希望空白处穿透时,请缩小拦截组件,或把只作展示的全屏元素改为 pointerEvents: pass。
父页面错误地收到鼠标事件
页面根级鼠标事件会在路由到达该层时执行。如果子页面需要吃掉某个区域的点击,请确保该区域存在 auto 可交互组件或 pointerEvents: block 组件。
猹件开发组