From 99ce4e3fc62c5d501092e7d9010f2bbaaef06d34 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 19:46:10 +0800 Subject: [PATCH] docs: explain page window and scrolling example callers --- examples/fonts.endor | 2 ++ examples/scroll-axis.endor | 7 +++++++ examples/scroll.endor | 6 ++++++ examples/tab-placement.endor | 6 ++++++ examples/tabs.endor | 4 ++++ examples/window.endor | 6 ++++++ examples/windows.endor | 4 ++++ examples/workspace.endor | 6 ++++++ 8 files changed, 41 insertions(+) diff --git a/examples/fonts.endor b/examples/fonts.endor index 808e6fb..18a9ecc 100644 --- a/examples/fonts.endor +++ b/examples/fonts.endor @@ -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)) diff --git a/examples/scroll-axis.endor b/examples/scroll-axis.endor index 355c238..ac6f2c9 100644 --- a/examples/scroll-axis.endor +++ b/examples/scroll-axis.endor @@ -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))) diff --git a/examples/scroll.endor b/examples/scroll.endor index f6aad2c..3516bfa 100644 --- a/examples/scroll.endor +++ b/examples/scroll.endor @@ -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") diff --git a/examples/tab-placement.endor b/examples/tab-placement.endor index c3a42dc..88c9db1 100644 --- a/examples/tab-placement.endor +++ b/examples/tab-placement.endor @@ -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() diff --git a/examples/tabs.endor b/examples/tabs.endor index afe823a..83dcd82 100644 --- a/examples/tabs.endor +++ b/examples/tabs.endor @@ -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") diff --git a/examples/window.endor b/examples/window.endor index f78dc63..61adcf1 100644 --- a/examples/window.endor +++ b/examples/window.endor @@ -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) diff --git a/examples/windows.endor b/examples/windows.endor index b608051..7abf4d3 100644 --- a/examples/windows.endor +++ b/examples/windows.endor @@ -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") diff --git a/examples/workspace.endor b/examples/workspace.endor index 78314ec..72a240a 100644 --- a/examples/workspace.endor +++ b/examples/workspace.endor @@ -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,