Files
endorphin-tessera-gui/examples/world-ui.endor

77 lines
4.3 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 世界空间 UI:普通 Endor 树先在独立 UI 分辨率中布局/绘制,再作为 Panel3D 平面的纹理参与三维深度。
// 面板内还有 CanvasViewport,输入需经外层射线、面板 UV 和内层二维相机逐层转换。
// 面板内容仍是完整 Endor UI,可共享对象、绑定、源视觉协议与嵌套视口。
// 分别持有外层三维视口、面板变换和面板内二维画布,避免一次动作同时驱动多层相机。
class WorldActions {
// 三个引用分别指向观察相机所在视口、三维面板实体和内层二维视口,不把它们的坐标系混用。
view: Viewport3D
panel: Panel3D
canvas: CanvasViewport
// 重置三维相机位置与观察目标,不改变面板或内层画布业务状态。
// 不重建 Camera,也不复位其 fov/near/far/up 或 controller;reset 仅写下面显式列出的六个属性。
function reset(self: WorldActions): Unit {
// reset.onClick 派发零参绑定方法,按绝对值恢复外层相机位置/target;不会调用 canvas.fitView。
self.view.camera.x = 0
self.view.camera.y = 0.5
self.view.camera.z = 7
self.view.camera.targetX = 0
self.view.camera.targetY = 0
self.view.camera.targetZ = 0
}
// 只旋转 Panel3D 平面,面板内 UI 树仍用自己的逻辑宽高。
// turn.onClick 调用,每次给面板世界变换增加 15 度 Y 旋转,不修改 UI 分辨率或子树字段。
function turn(self: WorldActions): Unit { self.panel.rotate(0, 15, 0) }
// 找到画布当前 Mount,按该出现位置的有限尺寸 fitView,而非用外层三维视口大小。
// fit.onClick 在挂载后调用;本例 canvas 只在一个面板出现,at(0) 选择其有效 Mount,再按该出现计算相机适配。
function fit(self: WorldActions): Unit { self.canvas.mounts().at(0).fitView() }
}
count = ui.state(0)
increment = Button("点击:世界里的真实按钮")
increment.onClick(count.increment)
// 这个按钮在 Panel3D 内,后面的 reset/turn/fit 在外层页面;它们共享 VM,但输入需要不同坐标转换路径。
// 二维场景卡片使用带符号世界坐标;canvas 再提供有限可见区域和独立 camera。
card = Column(10, 6, TextBlock("CanvasViewport", 1), RectBlock(180, 26, 6, "#9CB8A8"))
card.boxWidth = 240
card.x = 10
card.y = 4
canvas = CanvasViewport(280, 80, Scene2D(card))
// 普通 Column、文字、按钮和二维画布组成 UI 树,按钮仍修改同一个 count。
ui_root = Column(8, 8, TextBlock("世界空间 UI", 2), ui.textOf(count, "value", 2), increment, canvas)
ui_root.crossAlign = "stretch"
// 4x3 是三维世界尺寸,320x240 是 UI 布局/纹理分辨率;两者独立控制显示比例与清晰度。
panel = Panel3D(4, 3, ui_root)
panel.uiWidth = 320
panel.uiHeight = 240
// panel 的 x/rotationY 改三维平面变换;UI 仍在 320×240 的逻辑区域里布局,不把世界单位当作字形像素。
panel.x = -1.2
panel.rotationY = 15
cube = Box3D(1.4, 1.4, 1.4, "#7B9CB7")
cube.x = 2
cube.rotationY = 25
floor = Box3D(10, 0.08, 5, "#D8DEE6")
floor.y = -1.8
// 面板与普通网格/地面共处一个深度场景,实体可遮挡面板,也阻挡被遮住的 UI 命中。
scene = Scene3D(panel, cube, floor, DirectionalLight(-1, -2, -3, "#FFFFFF", 0.9))
scene.ambient = 0.35
// 外层 Viewport3D 作为二维布局里的 flex 子项;相机控制外部观察,而非 UI 内部滚动。
view = Viewport3D(960, 520, scene)
view.flex = 1
view.camera.y = 0.5
view.camera.z = 7
// 控制器保存已创建的三层对象,注册回调不会提前执行重置、转动或 Mount 查询。
actions = WorldActions(view, panel, canvas)
reset = Button("重置摄像机")
reset.onClick(actions.reset)
turn = Button("转动 UI 面板")
turn.onClick(actions.turn)
fit = Button("适配面板内的画布")
fit.onClick(actions.fit)
// 控制按钮保持在二维页面空间,外层三维内容作为一个区域随布局伸展。
body = Column(24, 16, TextBlock("UI → 3D 世界 → UI → Canvas", 2), view, Row(0, 12, reset, turn, fit), TextBlock("空白处左键拖动旋转 3D · 中键/空格平移 · 滚轮缩放\n面板里的按钮、文本、布局和画布都由同一份 Endor 对象驱动。"))
body.flex = 1
body.crossAlign = "stretch"
page = Page()
page.title = "世界空间 UI"
page.crossAlign = "stretch"
page.render(body)