diff --git a/examples/events.endor b/examples/events.endor index 28562e6..87022d3 100644 --- a/examples/events.endor +++ b/examples/events.endor @@ -2,6 +2,7 @@ // EventLog 用一个递增序列记录实际次序;改变监听器/传播标记可对比跳过与停止的语义。 // 保存各阶段序号与最近 phase;count 是共享状态,其他字段直接写在本模型中。 class EventLog { + // count 保存跨阶段递增的总序列;四个 Step 保存各阶段最近一次序号,不是各自独立递增的计数器。 count: Object captureStep: Number methodStep: Number @@ -10,18 +11,21 @@ class EventLog { phase: String // 祖先捕获阶段先递增序列,再保存 captureStep 和 Event.phase。 function capture(self: EventLog, event: Event): Unit { + // 由 root 的 capture 连接调用,当前 event.phase 来自本次派发路径,不由业务模型自行推断。 self.count.increment() self.captureStep = self.count.value self.phase = event.phase } // 目标监听器的执行点;它排在目标类型 onEvent 方法之后。 function target(self: EventLog, event: Event): Unit { + // button 的普通连接在目标 onEvent 后调用,读取已被前面阶段推进的同一个 count。 self.count.increment() self.targetStep = self.count.value self.phase = event.phase } // 祖先冒泡阶段记录最后序号,未被 stopPropagation 时才到达。 function bubble(self: EventLog, event: Event): Unit { + // 由 root 的非捕获连接调用;本例不设置停止标记,因此点击目标后可继续冒泡到这里。 self.count.increment() self.bubbleStep = self.count.value self.phase = event.phase @@ -29,21 +33,25 @@ class EventLog { } // 覆盖目标方法,比额外 on(click,...) 监听器更早处理目标阶段事件。 class EventButton: Button { + // 原生 Button 参数之外追加模型引用,onEvent 和三个监听器共同写入该模型。 model: EventLog // 把模型序列递增后写 methodStep,供断言与 capture/target/bubble 比较。 function onEvent(self: EventButton, event: Event): Unit { + // 分发器查找对象方法后进入这里,无需再给此方法单独注册 on 连接;方法返回后继续目标监听器。 self.model.count.increment() self.model.methodStep = self.model.count.value self.model.phase = event.phase } } model = EventLog(ui.state(0), 0, 0, 0, 0, "等待点击") +// 只在顶层创建一次日志,重复点击累计序列,四个 Step 会被各自最近一次阶段调用覆盖。 button = EventButton("捕获 → 对象方法 → 监听器 → 冒泡", model) root = Column(24, 16, TextBlock("同一对象系统中的事件分派", 2), button, ui.textOf(model.count, "value"), ui.textOf(model, "phase")) // capture 选项显式选择捕获;不带选项的祖先监听器默认在冒泡阶段执行。 // 返回的令牌可 disconnect,同次派发中断开尚未执行的监听器也应生效。 capture = root.on("click", model.capture, "capture") target = button.on("click", model.target) +// root 是目标的祖先;若把监听器放在另一无关容器,它不会仅因共享 model 而加入这条传播路径。 bubble = root.on("click", model.bubble) page = Page() page.render(root) diff --git a/examples/gc-lifecycle.endor b/examples/gc-lifecycle.endor index b4c9e1e..1479cc9 100644 --- a/examples/gc-lifecycle.endor +++ b/examples/gc-lifecycle.endor @@ -2,47 +2,57 @@ // 关闭临时页仍可因业务引用保留等待任务;releaseCycle 清掉外部引用后,缓存不应让整个引用环常驻。 // 比较操作前后对象数时,关闭页面只解除展示根;holder/pending 外部引用也释放后才应回到基线。 holder = ui.state(0) +// pending.value 后续保存 Task;它和 holder.value 保存的 Page 是两条需要显式清除的外部可达路径。 pending = ui.state(0) phase = ui.state(0) // 通过 peer 指回包含自己的 Page,配合等待协程形成包含视觉缓存读依赖的引用环。 class CycleRect: RectBlock { + // 指回 transient 页面;页面又通过 render 的根保留 rect,形成真实托管对象环。 peer: Object // sleep 归属该 rect,await 保留协程帧;先写 phase=1,只有实际恢复后才写 2 并改 Page 标题。 function wait(self: CycleRect): Unit { + // ui.spawn 调用绑定 wait,sleep 的 owner 仍是同一 rect,键 sleep 与外层 workflow 任务键不同。 gate = ui.sleep(self,"sleep",300000) phase.set(1) + // 在 gate 完成前挂起此调用,下面两条写入尚未执行;done 只是保存恢复结果,本例不读取它。 done = await gate phase.set(2) self.peer.title = "等待已结束" } // 故意读取 peer.title,让布局依赖包含循环中的 Page;缓存应借用观察,不增加长期 GC 根。 function measure(self: CycleRect, mount: Mount, constraints: Constraints): MeasureResult { + // source_layout 的只读测量调用;observed 虽不参与数值计算,成员访问仍是需要跟踪的实际读取。 observed = self.peer.title return MeasureResult(180,40) } // 绘制也读取 peer.title,检查绘制缓存与等待帧不会共同意外保活循环。 function paint(self: CycleRect, mount: Mount, geometry: Bounds): DisplayList { + // source_paint 的只读计算也读取环中页面;委托基类生成绘制数据,不把 Page 放进输出命令。 observed = self.peer.title return RectBlock.paint(self,mount,geometry) } } // 创建临时 Page 和 CycleRect,相互引用后挂载,再把 Page/Task 分别放入两个外部 holder。 function mountCycle(): Unit { + // 供 Console 手工调用的零参入口,加载此文件只定义函数,不立即创建临时环或开始五分钟等待。 transient = Page() transient.title = "临时引用环" rect = CycleRect(180,40,8,"#3868D9",transient) transient.render(rect) holder.set(transient) phase.set(0) + // spawn 返回工作流 Task 立即存入 pending,等待期间可经 pending.value.status() 观察,不能在此同步等完成。 pending.set(ui.spawn(rect,"workflow",rect.wait)) } // 先关闭临时页,随后去掉两个外部引用;等待依赖无需等满五分钟,失去可达性后应整体回收。 function releaseCycle(): Unit { + // 应在 mountCycle 建立 holder 后由 Console 调用;close 先解除展示关系,再清除外部 Page 与 Task 引用。 holder.value.close() holder.set(0) pending.set(0) } // 基线页面始终存在,用它比较操作前后的托管对象数,不把临时页面当作基线。 base = Page() +// base/holder/pending/phase 这些顶层对象仍保留,比较 GC 数量时不能要求整个会话回到零对象。 base.title = "GC 基线" base.render(TextBlock("关闭临时 Page 后应回收引用环",2)) diff --git a/examples/global-dependencies.endor b/examples/global-dependencies.endor index 4e4091d..78a0cc7 100644 --- a/examples/global-dependencies.endor +++ b/examples/global-dependencies.endor @@ -5,13 +5,17 @@ color = "#ED6A5A" class GlobalPaint: RectBlock { // 调用原生基类产出新命令,读取当前 color 并写到新命令;不在只读阶段修改全局。 function paint(self: GlobalPaint, mount: Mount, geometry: Bounds): DisplayList { + // source_paint 在当前出现绘制时调用;基类用本轮 geometry 构造新的 DisplayList 和矩形命令。 drawing = RectBlock.paint(self,mount,geometry) + // 右侧按全局名称读取 color,左侧只修改本次新结果;不是写 self.color,也不修改已有业务对象。 drawing.commands.at(0).color = color + // 宿主解码校验返回命令后缓存绘制结果;缓存失效观察上面真实发生的全局 color 读取。 return drawing } } // 显示固定大小矩形和操作提示,后续通过 Console 改同一个会话的 color。 rect = GlobalPaint(480,120,12,"#DDEEFF") +// 构造颜色是原生载荷,但每轮源 paint 会用全局 color 覆盖返回命令,所以初次可见颜色来自 #ED6A5A。 page = Page() page.title = "全局名字依赖" page.render(Column(24,16,TextBlock("在 REPL 求值 42 不重绘;修改 color 会更新矩形颜色",2),rect)) diff --git a/examples/managed-types.endor b/examples/managed-types.endor index 9e2670b..95b4250 100644 --- a/examples/managed-types.endor +++ b/examples/managed-types.endor @@ -2,13 +2,16 @@ // 两个文字绑定读同一 counter;原生点击和 Console 都能操作该对象并传播显示变化。 // state 提供可写 value 和 increment 方法,textOf 建立显式字段到文本的依赖。 counter = ui.state(9) +// 两次 textOf 分别创建文字展示对象,但读取同一 counter.value;一次 increment 会更新两处绑定。 first = ui.textOf(counter, "value", 2) second = ui.textOf(counter, "value", 2) title = TextBlock("Endor:同一份对象,代码与 GUI 共同操作", 2) button = Button("增加计数") // 零参数源回调委托原生 state.increment;事件分发器根据签名调用,不强塞 Event 参数。 +// 由 Button.onClick 在点击时调用,函数从当前会话全局取得 counter,不维护另一份局部数值。 function increment(): Unit { counter.increment() } // 把实际函数作为回调而非函数名文本;render 之前只创建业务对象,render 后才有出现位置。 button.onClick(increment) +// Page 和 Column 将已有对象组成展示树;此时不调用 increment,首次绑定读到的值仍为 9。 page = Page() page.render(Column(24, 16, title, Row(0, 16, first, second, button))) diff --git a/examples/mount-lifecycle.endor b/examples/mount-lifecycle.endor index 40f19a7..60bb8d1 100644 --- a/examples/mount-lifecycle.endor +++ b/examples/mount-lifecycle.endor @@ -2,6 +2,7 @@ // 普通数据变化不会重新挂载;页面关闭时子项先卸载,父项随后卸载,旧 Mount 不因重开而复活。 // order 是统一时序,mounted/unmounted 是子项次数;latest 保存最近通知的实际 Mount。 class LifecycleLog { + // order/mounted/unmounted/latest 为可写 state,四个顺序字段为 Number;构造参数按这里的字段次序传入。 order: Object mounted: Object unmounted: Object @@ -16,14 +17,17 @@ class WatchedText: TextBlock { log: LifecycleLog // 记录顺序和次数,保存 Mount 并修改原文字;运行时在回调后重新收敛才发布。 function onMount(self: WatchedText, mount: Mount): Unit { + // 宿主为 child 的每个新出现分别调用;同一 self 会收到两次通知,但传入的是两个不同 Mount。 self.log.order.increment() self.log.mounted.increment() self.log.childMounted = self.log.order.value + // 保存最近一次参数供 Console 查询,后一次通知会覆盖前一次;mounted 累计值则保留两次调用。 self.log.latest.set(mount) self.setText("同一个对象,两个独立挂载位置") } // 记录已经脱离的出现位置;保留 Mount 可查询 mounted,但不能再当有效 bounds 使用。 function onUnmount(self: WatchedText, mount: Mount): Unit { + // 展示树移除此出现时由宿主调用,不能通过再次 setText 或保存 mount 让已经卸载的出现重新生效。 self.log.order.increment() self.log.unmounted.increment() self.log.childUnmounted = self.log.order.value @@ -35,11 +39,13 @@ class WatchedColumn: Column { log: LifecycleLog // 记录父挂载顺序,不修改容器结构;子项随后各自执行通知。 function onMount(self: WatchedColumn, mount: Mount): Unit { + // 父 Mount 建立后的业务通知,只改日志;初始父通知先于下方两个 child 出现的通知。 self.log.order.increment() self.log.parentMounted = self.log.order.value } // 父卸载时子项卸载已完成,序列号应位于所有子项通知之后。 function onUnmount(self: WatchedColumn, mount: Mount): Unit { + // 对整页的卸载,两个子出现先各计一次,父回调最后把当前总序号写入 parentUnmounted。 self.log.order.increment() self.log.parentUnmounted = self.log.order.value } @@ -47,6 +53,7 @@ class WatchedColumn: Column { // 多个 state 容器分别保存计数/最后 Mount,root 最后参数是源 log 字段,前面才是可变视觉子项。 log = LifecycleLog(ui.state(0), ui.state(0), ui.state(0), 0, 0, 0, 0, ui.state(0)) child = WatchedText("等待挂载", 2, log) +// 两个 child 参数是同一对象引用;新增 log 字段位于所有原生视觉子项之后,不会被安排成额外孩子。 root = WatchedColumn(24, 16, child, child, TextBlock("挂载次数"), ui.textOf(log.mounted, "value"), TextBlock("卸载次数"), ui.textOf(log.unmounted, "value"), log) page = Page() page.render(root) diff --git a/examples/presentable.endor b/examples/presentable.endor index ef25a07..0619534 100644 --- a/examples/presentable.endor +++ b/examples/presentable.endor @@ -10,18 +10,22 @@ class Product { function shade(self: Product): String { return self.color } // 点击更新原业务对象,现有绑定会同步文字和矩形,不重新调用整个 present 构造树。 function update(self: Product, event: Event): Unit { + // 由 present 中 button.on 注册的绑定方法调用,self 仍是顶层 product,不是 result 容器或 title。 self.name = "原业务对象已更新" self.color = "#237A45" } // 在指定 Window 上下文创建文字、色块和按钮,连接原 self 的方法后返回组合根。 function present(self: Product, window: Window): Block { + // ui.present 验证显式能力后调用本方法,window 是本次展示上下文;每次调用都会新建下面这些视觉对象。 title = TextBlock(self.name) swatch = RectBlock(180,48,8,self.color) // 绑定令牌本身不承载另一份业务状态,绑定计算仍调用同一 Product。 titleBinding = title.bind("text",self.label) colorBinding = swatch.bind("color",self.shade) + // title/swatch 的初值只是构造输入,后续 getter 绑定读取原 Product,使事件写入能继续传播。 button = Button("调用业务对象方法") connection = button.on("click",self.update) + // result 保留三个视觉孩子,返回该根给调用方;这里尚未通过 Page.render 建立出现树。 result = Column(24,16,title,swatch,button) // 保留原始业务身份,便于代码从展示根回到来源;contextWindow 仅记录创建时的窗口编号。 result.source = self @@ -31,8 +35,10 @@ class Product { } // 显式声明能力并验证方法契约;只有同名 present 不会自动成为 Presentable。 ui.implement(Product,Presentable) +// 能力注册针对 Product 类型,实际业务字段在这个唯一实例上初始化;label/shade 将读取此实例。 product = Product("普通对象的显式展示能力", "#526AC7") // 先获得视觉根,再由 ui.open 把它挂载到页面;最后设置 Page 标题。 view = ui.present(product) +// ui.open 接收上面已经生成的 Block,不会再次把 product 交给 present 重新生成另一棵视觉树。 page = ui.open(view) page.title = "Presentable" diff --git a/examples/property-bindings.endor b/examples/property-bindings.endor index 384c093..e7ee271 100644 --- a/examples/property-bindings.endor +++ b/examples/property-bindings.endor @@ -2,6 +2,7 @@ // 绑定在目标隐藏时也会更新;两个页面共享同一组 Block,不需要两份模型。 // width/color/label 是唯一业务来源,绑定计算只读,expand 是显式事件写入入口。 class Model { + // 三个业务值由下面三个 getter 分别读取;绑定的目标属性保存在视觉对象上,业务修改入口在 expand。 width: Number color: String label: String @@ -13,21 +14,25 @@ class Model { function getLabel(self: Model): String { return self.label } // 一个事件同时改三个属性,随后绑定传播,布局与绘制在完整刷新后发布。 function expand(self: Model, event: Event): Unit { + // 由 clickConnection 派发调用,可写业务状态;三个 getter 则由绑定求值调用,不能反向修改模型。 self.width = 260 self.color = "#237A45" self.label = "绑定已更新" } } model = Model(120, "#526AC7", "点击更新共享对象") +// 原生构造给初始载荷,后面的 bind 再把这些属性接到模型;label 的初始化字符串也会被绑定结果替换。 rect = RectBlock(120, 40, 8, "#526AC7") label = TextBlock("初始化") // 保存三个可断开的绑定令牌;令牌不强持有隐藏目标,目标仍需由业务对象图保留。 widthBinding = rect.bind("width", model.getWidth) +// 每次传入的是已绑定 model 的零参函数,注册不会调用 expand,也不把方法名当作待执行源码字符串。 colorBinding = rect.bind("color", model.getColor) labelBinding = label.bind("text", model.getLabel) button = Button("更新宽度、颜色和文字") clickConnection = button.on("click", model.expand) root = Column(24, 16, label, rect, button) +// 两个页面共享 root 及其子对象,因此只有一组属性绑定,不需要为每个 Mount 再注册一组。 page = Page() page.title = "通用属性绑定" page.render(root) @@ -35,4 +40,5 @@ page.render(root) second = Page() second.title = "同一组 Block" second.render(root) +// 切回第一页不销毁第二页或重建模型;隐藏页仍引用同一组更新后的业务/视觉对象。 page.activate() diff --git a/examples/visual-protocols.endor b/examples/visual-protocols.endor index afe3dd3..0a5de36 100644 --- a/examples/visual-protocols.endor +++ b/examples/visual-protocols.endor @@ -2,6 +2,7 @@ // onMount 作为业务阶段收集结果;实际布局/绘制计算仍受只读规则约束。 // 把协议探测结果保存到普通业务字段,textOf 将其中一部分显示出来。 class VisualResults { + // width/height 来自测量返回值,childX/childY 来自安排返回值,color 来自新绘制命令;都不是最终 Mount 的几何快照。 width: Number height: Number childX: Number @@ -13,10 +14,13 @@ class ProbeRect: RectBlock { results: VisualResults // 有限最大宽 120 约束自然宽 200;另用自定义 Bounds 查询绘制颜色,把返回结果写进共享 results。 function onMount(self: ProbeRect, mount: Mount): Unit { + // 宿主挂载通知进入此业务回调,允许写 results;这里主动调用继承的原生 measure 来查询指定约束下的结果。 measured = self.measure(mount, Constraints(0, 0, 120, 80)) self.results.width = measured.width self.results.height = measured.height + // 自定义 Bounds 仅作为这次 paint 的输入,不会把当前页面里 rect 的实际布局强制设为该位置和大小。 drawing = self.paint(mount, Bounds(12, 18, 200, 60)) + // 第一个命令来自 RectBlock 的原生矩形绘制,本例读取颜色后保存到业务模型供下方 textOf 展示。 self.results.color = drawing.commands.at(0).color } } @@ -25,6 +29,7 @@ class ProbeColumn: Column { results: VisualResults // 给定绝对起点(20,30)及容器范围,原生 padding=24 使首子项位于(44,54)。 function onMount(self: ProbeColumn, mount: Mount): Unit { + // 当前父出现的子槽已存在,at(0) 对应下面 root 的标题 TextBlock;查询结果不替换宿主随后发布的最终安排。 positions = self.arrange(mount, Bounds(20, 30, 500, 300)) self.results.childX = positions.children.at(0).x self.results.childY = positions.children.at(0).y @@ -32,6 +37,7 @@ class ProbeColumn: Column { } // 先创建结果容器,再将它作为源子类字段共享;root 的最后一个参数是该字段,不是视觉子项。 results = VisualResults(0, 0, 0, 0, "等待挂载") +// 父 ProbeColumn 与子 ProbeRect 共享同一结果容器,分别填写自己的探测字段,避免把结果误写到原生宽高载荷。 rect = ProbeRect(200, 60, 12, "#DDEEFF", results) root = ProbeColumn(24, 16, TextBlock("对象的原生视觉协议", 2), rect, TextBlock("约束测量宽度"), ui.textOf(results, "width"), TextBlock("绘制颜色"), ui.textOf(results, "color"), results) page = Page()