docs: explain page window and scrolling example callers

This commit is contained in:
2026-11-12 19:46:10 +08:00
parent ce17cb6ed3
commit 99ce4e3fc6
8 changed files with 41 additions and 0 deletions
+2
View File
@@ -4,8 +4,10 @@
let title = ui.text("Hello Endor · 你好,Tessera", 2, "#242422")
// 同一文字控件包含中英文和重音字符,用于观察字体回退与基线。
let detail = ui.text("中文 English 123 — café", 1, "#606060")
// title/detail 是两个独立 TextBlock,字体系统在各自测量和绘制阶段使用一致的整形规则,颜色不参与字宽估算。
// 两组相同尺寸色块跟在窄/宽字符后,12px gap 保持一致,差异来自真实文字测量。
let row = ui.row(0, 12, ui.text("iiii"), ui.rect(24, 20, 4, "#DCE8FF"), ui.text("WWWW"), ui.rect(24, 20, 4, "#DCE8FF"))
// 根Column统一padding和间距,render后这些对象才有具体Mount与绘图命令。
let page = ui.page()
// 整个脚本顶层构造完成后请求展示,没有点击回调或定时刷新;宿主布局负责把 row 中的真实测量宽度累加到色块位置。
page.render(ui.column(24, 12, title, detail, row))
+7
View File
@@ -2,24 +2,30 @@
// 同一 paragraph 出现两次,文字内容共享,换行/偏移/范围在各 Mount 中独立。
// 通过具体出现位置的 Mount 操作偏移;ScrollBlock.axis 则是对象级策略,可影响其所有出现位置。
class Actions {
// 两个不同 ScrollBlock 对象引用同一 paragraph;对象级 axis 策略各自保存,Mount 偏移也各自保存。
vertical: ScrollBlock
horizontal: ScrollBlock
// 只对 vertical 第一个 Mount 累加纵偏移,运行时按当前范围夹取。
// down.onClick 调用零参方法;点击发生在挂载之后,本例 vertical 仅有一个有效 Mount,故选择 at(0)。
function down(self: Actions): Unit { self.vertical.mounts().at(0).scrollBy(0, 60) }
// 只对 horizontal 第一个 Mount 累加横偏移,禁止轴仍保持零。
// right.onClick 选择另一个滚动对象,60 是相对增量,不是强制把偏移设为 60。
function right(self: Actions): Unit { self.horizontal.mounts().at(0).scrollBy(60, 0) }
// 分别把两个出现位置移回原点,不修改共享段落文字。
function reset(self: Actions): Unit {
// reset.onClick 调用;这里用 scrollTo 指定绝对原点,保留容器尺寸和当前 axis 策略。
self.vertical.mounts().at(0).scrollTo(0, 0)
self.horizontal.mounts().at(0).scrollTo(0, 0)
}
// 把左对象策略改为双轴,下一次求解重新计算约束和范围。
// both.onClick 改左侧对象策略而不是某次出现的偏移,也不把右侧 horizontal 改成双轴。
function both(self: Actions): Unit { self.vertical.axis = "both" }
}
paragraph = TextBlock("这里展示的是同一个 TextBlock。左侧只允许纵向滚动,文字会按照这个 Mount 的实际视口宽度换行;右侧只允许横向滚动,文字保留横向展开。对象的文字和业务状态只有一份,每次展示的约束、几何和滚动位置分别保存。修改滚动轴也会重新测量受影响内容,并将已经不再允许的偏移夹回零。布局传播持续到受影响部分稳定,不会按固定跳数丢弃后续更新。这里展示的是同一个 TextBlock,两个视图可以拥有不同的换行结果。")
// 纵向区域padding后内容宽为244,长文字按此有限宽换行。
vertical = ScrollBlock(260, 140, paragraph)
vertical.padding = 8
// 先给左对象设置 vertical,右对象随后独立设 horizontal;同一 paragraph 不保存任何一个父容器的轴策略。
vertical.axis = "vertical"
// 横向区域沿滚动轴允许内容自然展开,所以同一文字可以比左侧更宽、更矮。
horizontal = ScrollBlock(260, 140, paragraph)
@@ -35,6 +41,7 @@ reset = Button("回到起点")
reset.onClick(actions.reset)
both = Button("左侧恢复双轴")
both.onClick(actions.both)
// 注册只保存函数,不提前查询 mounts;render 建立两个滚动区域的出现后,点击才会执行上述偏移操作。
page = Page()
page.title = "滚动轴"
page.render(Column(24, 16, TextBlock("同一个对象,不同的滚动轴", 2), Row(0, 24, Column(0, 8, TextBlock("vertical"), vertical), Column(0, 8, TextBlock("horizontal"), horizontal)), Row(0, 12, down, right, reset, both)))
+6
View File
@@ -4,13 +4,16 @@
class Painted: RectBlock {
// 使用当前 geometry 交给原生基类;外层滚动变换/clip 由展示流程统一组合。
function paint(self: Painted, mount: Mount, geometry: Bounds): DisplayList {
// source_paint 为当前出现调用此覆盖;只返回基类新命令,不把滚动偏移写回共享矩形坐标。
return RectBlock.paint(self,mount,geometry)
}
}
// 三个按钮复用同一个 Action,任意可见按钮点击都递增同一 count。
class Actions {
// 三个按钮连接同一 Actions,count 是共享业务状态;每个 ScrollBlock 出现的偏移不保存在这里。
count: Object
// 签名接受 Event,业务逻辑只需修改计数,不直接处理滚动位置。
// 原生指针点击先经过滚动裁剪命中,再由事件分发调用此方法;event 不承担滚动状态存储。
function click(self: Actions, event: Event): Unit { self.count.increment() }
}
count = ui.state(0)
@@ -19,11 +22,13 @@ first = Button("顶部按钮")
middle = Button("中间按钮")
last = Button("底部按钮")
firstConnection = first.on("click",actions.click)
// 三个连接都在创建树时注册一次,第二窗口复用对象不会额外注册或导致一次点击重复计数。
middleConnection = middle.on("click",actions.click)
lastConnection = last.on("click",actions.click)
// 高内容夹有三个按钮与矩形,使固定320x180 scroller产生可观察的横纵滚动范围。
body = Column(12,12,first,Painted(520,180,8,"#DCE8FF"),middle,RectBlock(520,180,8,"#E3EFE5"),last)
scroller = ScrollBlock(320,180,body)
// body 自然内容大于固定视口,宿主为各 Mount 计算范围;同一 scroller 不需要克隆 body 来独立滚动。
root = Column(24,16,TextBlock("同一 Block,两处 Mount 独立滚动"),ui.textOf(count,"value"),scroller)
page = Page()
page.title = "滚动示例"
@@ -34,5 +39,6 @@ other.setTitle("Scroll Secondary")
second = Page()
second.title = "独立滚动位置"
second.open(other)
// 先指定副窗口归属再 render,使这份 root 的新出现进入 other,而不是新建另一份业务根。
second.render(root)
ui.window().setTitle("Scroll Primary")
+6
View File
@@ -2,14 +2,17 @@
// 当前侧栏提供选择/关闭,文案中的拖放场景也可经版本化事件 API 测试;不要把它理解成已实现的侧栏拖动手势。
// 继承 Page 后观察 moveToWindow/moveTab 的真实方法调用,保留原生迁移/重排行为。
class TrackedPage: Page {
// 每个 TrackedPage 引用同一 calls 状态,统计的是这两个覆盖方法的调用,不是渲染或所有页面操作次数。
calls: Object
// 先递增 calls,再显式调用基类执行跨窗迁移;Page 身份保留,旧 Mount 上下文会替换。
function moveToWindow(self: TrackedPage, window: Window): Page {
// 外部代码/API 选择此源覆盖时进入;显式 Page.moveToWindow 绕过再次动态分派,避免递归调用自身。
self.calls.increment()
return Page.moveToWindow(self, window)
}
// position 是所属窗口内最终零基位置,重排不等于创建新的业务 Page。
function moveTab(self: TrackedPage, position: Number): Page {
// 显式调用原生基类并返回它的 Page 结果,计数副作用不会把位置参数改成相对偏移。
self.calls.increment()
return Page.moveTab(self, position)
}
@@ -19,11 +22,13 @@ class Actions {
list: Column
// 在索引 1 前插入新 TextBlock,返回的稳定槽可供 remove 等后续操作使用。
function insert(self: Actions, event: Event): Unit {
// 由 button 的 click 连接派发;索引 1 是本次调用时的位置,每次点击新建文字并插入到当前第二项之前。
self.list.insert(1, TextBlock("插入的新对象"))
}
}
calls = ui.state(0)
list = Column(0, 8, TextBlock("第一个子项"), TextBlock("最后一个子项"))
// list 的可变孩子槽由同一 Column 持有,所有共享页面观察插入后的同一序列,不为各页单独执行回调。
actions = Actions(list)
button = Button("在位置 1 插入 Block")
connection = button.on("click", actions.insert)
@@ -46,6 +51,7 @@ other.moveTo(1700,80)
target = TrackedPage(calls)
target.title = "目标窗口"
target.render(shared)
// 这里调用继承的 open,不直接调用上面的 moveToWindow 覆盖;不能把初始建页过程当作这两个方法的演示调用。
target.open(other)
// 恢复源窗口首个页面,最后排出 resize/title/move/focus,便于手动或 API 验证。
first.activate()
+4
View File
@@ -5,19 +5,23 @@
first = Page()
first.title = "共享计数"
count = ui.state(0)
// connection 保存输入订阅,textOf 另外建立值到文字的绑定;二者共用 count,但承担不同调用职责。
increment = Button("加一")
connection = increment.on("click", count.increment)
first.render(Column(24,16,TextBlock("切换页面后,计数对象继续存在"),ui.textOf(count,"value"),increment))
// 另一页演示 Page 生命周期与快捷键;界面提示是示例文案,实际导航位于当前宿主侧栏。
second = Page()
// 第二页有自己的内容根,没有引用 count;切页不等于把第一页面里的对象移进第二页面。
second.title = "另一个页面"
second.render(Column(24,16,TextBlock("点击上方 Tab 切换;× 关闭页面"),TextBlock("Ctrl+Tab 切换,Cmd/Ctrl+W 关闭;代码可调用 first.open() 重开")))
// 独立内容页面仍用同一个运行时的原生类型和资源注册表。
third = Page()
// 三个全局 Page 引用在 close 后仍可保留页面业务身份,重新 open 会建立新的有效展示上下文。
third.title = "窗口与布局"
third.render(Column(24,16,TextBlock("每个 Tab 都是同一个 VM 中的 Page 对象"),RectBlock(240,80,8,"#237A45")))
// 重新激活第一页面,原生侧栏和源查询可观察相同 active 状态。
first.activate()
// 当前活动页切回 first 后请求该窗口标题,命令由成功输入后的 GUI 处理,不在这里同步操作原生窗口。
ui.window().setTitle("Endor Pages")
+6
View File
@@ -2,13 +2,16 @@
// 实际可用视口再返回布局约束,原生窗口尺寸不是业务对象上凭空写入的一份几何副本。
// 保存目标 Window,两个按钮对同一资源请求不同尺寸。
class WindowActions {
// 保存一个 Window 资源引用;事件发生在哪个按钮并不重新选择目标窗口,两个方法始终使用这个字段。
window: Window
// 请求 900x600 逻辑尺寸,最终大小仍由窗口管理器决定。
function compact(self: WindowActions, event: Event): Unit {
// compactConnection 派发此绑定方法,只发 resize 请求,不给 band 或 Mount 直接写宽高。
self.window.resize(900, 600)
}
// 请求 1120x720;同次输入多次 resize 可由命令队列合并到最后值。
function wide(self: WindowActions, event: Event): Unit {
// wideConnection 调用此事件入口;实际窗口变化回报后,新父约束才进入下面 WindowBand.measure。
self.window.resize(1120, 720)
}
}
@@ -16,6 +19,7 @@ class WindowActions {
class WindowBand: RectBlock {
// 取当前出现位置 maxWidth 作为期望宽度,固定高 32;不读取其他窗口的尺寸。
function measure(self: WindowBand, mount: Mount, constraints: Constraints): MeasureResult {
// source_layout 在每个出现的测量阶段调用,maxWidth 是上层已扣除布局预算的约束,不是整窗原始宽度。
return MeasureResult(constraints.maxWidth, 32)
}
}
@@ -26,9 +30,11 @@ band = WindowBand(80, 32, 0, "#237A45")
compact = Button("紧凑窗口 900 × 600")
wide = Button("宽窗口 1120 × 720")
compactConnection = compact.on("click", actions.compact)
// 两个回调均接收 Event,保存连接不等于立即请求尺寸;初次尺寸由文件最后的 resize 设置。
wideConnection = wide.on("click", actions.wide)
page = Page()
page.render(Column(24, 16, TextBlock("代码与 GUI 操作同一个受管窗口对象"), band, compact, wide))
page.open(window)
// 明确归属后向同一 window 排队标题和尺寸命令;Page 的业务内容与原生窗口资源分别保留身份。
window.setTitle("Endor Window")
window.resize(1120, 720)
+4
View File
@@ -3,15 +3,18 @@
// 跨窗共享的是业务对象,不是某个窗口中的具体 Mount。
// 唯一计数来源;两个按钮直接使用同一零参数原生 increment 方法。
count = ui.state(0)
// 左右按钮是两个视觉对象,连接的却是绑定同一 count 的原生零参 increment 方法。
leftButton = Button("共享计数 +1")
leftConnection = leftButton.on("click",count.increment)
// 先在主窗口建立页面,render 默认选择该页并挂载它的内容。
left = Page()
left.title = "主窗口页面"
// 此次 textOf 建立左侧文字绑定,右页还会建立另一个;计数业务状态仍只有上面一份。
left.render(Column(24,16,TextBlock("两个窗口操作同一个计数对象"),ui.textOf(count,"value"),leftButton))
// Window() 分配同 VM 中的独立窗口资源,不创建第二个运行时。
other = Window()
// 新窗口资源不改变 rightButton 回调的业务接收者;从哪个窗口点击都只增加同一个 count.value。
rightButton = Button("另一个窗口 +1")
rightConnection = rightButton.on("click",count.increment)
right = Page()
@@ -19,6 +22,7 @@ right.title = "独立窗口页面"
right.render(Column(24,16,TextBlock("关闭窗口会解除 Page 归属,保留的对象仍可重开"),ui.textOf(count,"value"),rightButton))
// 把页面放到 other,上下文更换将重建出现位置,业务 count 仍不变。
right.open(other)
// open 把 right 归入副窗口;下面 ui.window() 仍用于源会话的主窗口,other 则显式选中副窗口资源。
// 窗口命令针对各自资源编号排队;成功输入后再交 GUI,另一窗口设置自己的尺寸。
ui.window().setTitle("Endor Main")
other.setTitle("Endor Shared")
+6
View File
@@ -4,6 +4,7 @@
// 把标签、大数值、说明和色条纵向组合,参数控制文字与强调颜色。
function metric(label: Text, value: Text, note: Text, color: Text): View {
// 由顶层 statistics 连续调用三次,每次返回新的容器和文字;value 是展示字符串,不在这里解析或累计任务数。
return ui.column(0, 6,
ui.text(label, 1, "#637082"),
ui.text(value, 2, "#243047"),
@@ -14,6 +15,7 @@ function metric(label: Text, value: Text, note: Text, color: Text): View {
// 生成项目标题和状态,再用两个相邻矩形表示已完成/剩余长度;filled/remaining 是逻辑像素。
function project(name: Text, state: Text, detail: Text, filled: Number, remaining: Number, color: Text): View {
// 顶层 projects 为三组演示数据调用,state 只是状态文案,不是 ui.state 托管状态对象。
// 项目名和状态横排,状态颜色与进度前景相同。
let caption = ui.row(0, 12,
ui.text(name, 1, "#243047"),
@@ -24,6 +26,7 @@ function project(name: Text, state: Text, detail: Text, filled: Number, remainin
ui.rect(filled, 6, 3, color),
ui.rect(remaining, 6, 3, "#E5EAF2")
)
// caption/progress 是本次函数调用的局部视觉对象,返回根通过子项关系保留它们,不需要放到全局逐个挂载。
return ui.column(0, 6,
caption,
ui.text(detail, 1, "#637082"),
@@ -33,7 +36,9 @@ function project(name: Text, state: Text, detail: Text, filled: Number, remainin
// 组合小圆点、时间戳、标题与说明;spacer 仅作视觉对齐,不代表真实事件时间线。
function activity(time: Text, title: Text, detail: Text, color: Text): View {
// 由 timeline 的三次普通调用构造记录,time 是固定字符串;没有时钟读取、排序或后续自动推进。
let dot = ui.column(0, 0, ui.spacer(7), ui.rect(6, 6, 3, color))
// stamp 把小圆点列与时间文字横排;上方 spacer 给圆点增加局部纵向留白,不是一个全局坐标。
let stamp = ui.row(0, 8, dot, ui.text(time, 1, "#8490A0"))
return ui.column(0, 4,
stamp,
@@ -72,6 +77,7 @@ let timeline = ui.column(0, 14,
)
let body = ui.row(0, 20, projects, timeline)
// body 只建立两个已构造子树的组合关系,页面尚未 render,不会在构造这一行单独发布一帧。
// 底部提示以细色块和文字组合,明确说明页面还未接业务交互。
let note = ui.row(0, 12,