Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
bad2640
Add a registry-driven reflection API to read and write PAGX node chan…
Hparty Jun 11, 2026
7679bfa
Add matrix keyframe animation with TRS interpolation and geometry siz…
Hparty Jun 11, 2026
40d0a79
Register runtime writers for all animatable render channels and add a…
Hparty Jun 11, 2026
b0c24dd
Split node channel reflection into animatable and layout-requiring qu…
Hparty Jun 12, 2026
4372aa3
Mark Text x and y channels as layout-affecting to match other shape p…
Hparty Jun 12, 2026
f82d828
Drop value-encoding note for Matrix channels that the reflection API …
Hparty Jun 12, 2026
e555f6c
Reset layer mask during in-place refresh so a removed mask no longer …
Hparty Jun 12, 2026
9b0808a
Add direct unordered_set include used by child-layer reconciliation.
Hparty Jun 12, 2026
d107612
Correct Path position channel flags to layout-only since no runtime w…
Hparty Jun 12, 2026
a94dbef
Detach composition child slot on removal so empty slot containers no …
Hparty Jun 12, 2026
3cd2103
Update applyLayout doc to reflect that notifyChange safely re-runs la…
Hparty Jun 12, 2026
e632a80
Drop transform from notifyChange render-only example since layer posi…
Hparty Jun 12, 2026
92fa545
Guard in-place content refresh against layer-type mismatch to avoid i…
Hparty Jun 12, 2026
96e6b4b
Unbind vector content-element nodes when removing a layer subtree to …
Hparty Jun 12, 2026
1e85c1c
Document that matrix interpolation loses rotation winding; use a scal…
Hparty Jun 12, 2026
31b3355
Extract shared matrix decompose/mix/recompose into MatrixDecompose.h …
Hparty Jun 12, 2026
ed3929f
Unbind a removed painter's color source and gradient stops unless ano…
Hparty Jun 12, 2026
988e207
Promote an empty layer to a VectorLayer when it gains contents during…
Hparty Jun 12, 2026
7a89437
Expose the source node id on PAGLayer so callers can identify the ori…
Hparty Jun 12, 2026
a1f7495
Re-sync runtimeLayer only for plain child layers so composition child…
Hparty Jun 12, 2026
dfe1280
Add a composition-child hit-test-after-notifyChange test covering the…
Hparty Jun 12, 2026
bb59eb0
Unbind an ImagePattern's image only when no surviving pattern still r…
Hparty Jun 12, 2026
a7d1467
Rebuild scene timelines when a timeline node changes so animation add…
Hparty Jun 12, 2026
4178748
Document the mark-the-reference-chain-dirty rule and unsupported cros…
Hparty Jun 12, 2026
11dfcd8
Sync external composition edits to embedding scenes and reject notify…
Hparty Jun 12, 2026
dc6fe49
Rebuild a scene's runtime tree when an embedded external document is …
Hparty Jun 12, 2026
17d15c3
Resolve top-level timeline binding lazily so a cached handle survives…
Hparty Jun 12, 2026
23d0adc
Add tests for display-option persistence and deep A-B-C reverse-regis…
Hparty Jun 12, 2026
953fd96
Re-seed layer transform baseline on in-place refresh and broaden the …
Hparty Jun 12, 2026
e9b1e6e
Add ResetNodeChannel to restore a node channel to its type default va…
Hparty Jun 12, 2026
c74c6cf
Add runtime add and remove composition tests covering the syncChildre…
Hparty Jun 12, 2026
b508a45
Apply PAGX review feedback: filter foreign nodes in notifyChange expo…
Hparty Jun 15, 2026
24e56fd
Fix lambda violation in LayerBuilder by adding reverse index and look…
Hparty Jun 16, 2026
1a92f36
Rename RuntimeBinding reverse-index methods to track/untrack + shared
Hparty Jun 16, 2026
a044747
Fix refreshLayerInPlace to unbind elements removed from Layer contents.
Hparty Jun 16, 2026
6f8ccf7
Fix old style/filter binding entries not cleaned up in refreshLayerIn…
Hparty Jun 16, 2026
d8705dd
Add tests for shared ColorSource/Image/Gradient unbind via notifyChan…
Hparty Jun 16, 2026
7924fa0
Fix duplicate reverse index entries and nested element unbind in cont…
Hparty Jun 16, 2026
d2c1484
Fix ImagePattern imageUsers repeat duplicate accumulation on refresh.
Hparty Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions include/pagx/PAGComposition.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class PAGTimeline;
class PAGScene;
class PAGXDocument;
class Composition;
class Node;
struct RuntimeBinding;

/**
Expand Down Expand Up @@ -100,6 +101,34 @@ class PAGComposition : public PAGLayer {
// Returns nullptr if no persistent node owns the layer (internal sub-layer).
std::shared_ptr<PAGLayer> findChildForLayer(const tgfx::Layer* hitLayer);

private:
// Refreshes this composition after edits: reconciles its child layer list and refreshes any dirty
// leaf layers in place, then recurses into child compositions. Called by PAGScene::onNodesChanged.
// visited carries the source compositions on the current ancestor path: when this method recurses
// into a child composition, the child's own source is inserted before recursing and erased on
// return, so any newly added layer that references an ancestor composition is detected at the top
// of MakeChild rather than one frame deeper.
void refreshNodes(const std::vector<Node*>& dirtyNodes,
std::unordered_set<const Composition*>& visited);

// Reconciles this composition's runtime children with the given source layer list: reuses
// children whose source layer still maps to a tgfx layer (handles stay valid), builds newly added
// layers into the binding, removes runtime children whose source layer is gone, and reorders the
// parent's tgfx children to match the document order. visited is the ancestor path of source
// compositions; the caller must include this composition's own source so MakeChild rejects a
// newly added layer that points back to this composition or any ancestor at the top of the call.
void syncChildren(const std::vector<Layer*>& sourceLayers,
std::unordered_set<const Composition*>& visited);

// Rebuilds this composition's timelines from the owner layer's animation drivers, discarding any
// existing ones first so removed drivers or animations simply stop driving. Used at build time and
// when a timeline node changes. The root composition has no owner layer and spawns no timelines.
void spawnTimelines(const std::shared_ptr<PAGScene>& scene);

// Resets the timelines of this composition and all descendant compositions. Called when an edit
// touches a timeline node, rebuilding the whole timeline tree rather than patching it in place.
void resetTimelines();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nit] refreshNodes / syncChildren / spawnTimelines / resetTimelines 应改为 private

这 4 个新增方法当前是 protected,但仓库中 pagx::PAGComposition 没有任何子类(同名 pag::PAGComposition 是不同命名空间的旧 SDK 类型,不相关)。注释也明确写了 Called by PAGScene::onNodesChanged 等——它们是 friend 内部协作的实现细节,不是给子类继承的扩展点。

protected 暴露了不必要的扩展面,提高了未来误用风险。建议改为 private,已有 friend class PAGScene 足以访问。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

认同,已改 private。

refreshNodes / syncChildren / spawnTimelines / resetTimelines 这 4 个都是 PAGScene/PAGComposition 内部协作的实现细节,没有"给子类继承的扩展点"语义;改成 private 后由现有的 friend class PAGScene 提供必要访问,扩展面收紧。

MakeChild / buildChildren / findChildForLayer 仍保留在 protected:它们是构造或 hit-test 路径上 PAGComposition 自身递归的方法,未来若有子类做 hit-test 增强还可能依赖;这次没动它们,避免动到不在 review 关注范围内的访问性。


// Document used to resolve channel target IDs for timelines spawned by this composition. For a
// sealed external composition this is the layer's externalDoc; otherwise the scene's document.
PAGXDocument* document = nullptr;
Expand Down
7 changes: 7 additions & 0 deletions include/pagx/PAGLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ class PAGLayer {
*/
std::string name() const;

/**
* Returns the unique identifier of the source node this layer was built from, matching the node's
* "id" attribute in the PAGX document. Returns an empty string if the source node has no id (for
* example, the root composition or a node created without one).
*/
std::string id() const;

/**
* Returns the matrix that maps this layer's local coordinate space to the surface coordinate
* space, reflecting the layer's current on-screen position (including any animation applied so
Expand Down
9 changes: 8 additions & 1 deletion include/pagx/PAGScene.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,16 @@ class PAGScene : public std::enable_shared_from_this<PAGScene> {
private:
PAGScene();

// Intended dispatch target for PAGXDocument::notifyChange; wiring is not yet implemented.
// Dispatch target for PAGXDocument::notifyChange. Refreshes the runtime tree in place for edits to
// this document's own nodes; rebuilds the whole tree when the edit comes from an embedded external
// document (its nodes are not owned by this scene's document).
void onNodesChanged(const std::vector<Node*>& dirtyNodes);

// Builds or rebuilds the runtime layer tree and binding from the document, detaching any previous
// tree first. Used at creation and when an embedded external document changes (an external
// composition is built into the tree once and cannot be patched in place).
void buildRuntimeTree();

RuntimeBinding* mutableBinding();

tgfx::DisplayList* getDisplayListForOptions() const;
Expand Down
19 changes: 9 additions & 10 deletions include/pagx/PAGTimeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,28 +136,27 @@ class PAGTimeline {
std::weak_ptr<PAGScene> owner);

// Resolves each animation object's target node against contextDoc once and caches the
// (node, channels) pairs, so apply() avoids a per-frame findNode() hash lookup for every
// object. Built lazily on the first apply(). The cache is valid only as long as the animation's
// objects and contextDoc's nodeMap are not mutated after the owning PAGScene is built.
// Cache invalidation will be wired through PAGXDocument::notifyChange → PAGScene::onNodesChanged
// which resets resolved to false on affected timelines whenever nodeMap entries change.
// (node, channels) pairs, so apply() avoids a per-frame findNode() hash lookup. Built lazily on
// the first apply(). Stale caches are replaced by rebuilding the PAGTimeline (driven by
// PAGXDocument::notifyChange when a timeline node is dirty); this cache is never reset in place.
void resolveTargets();

// Owning scene. animation / binding / contextDoc point into content this scene keeps alive, so
// advance() and apply() bail out once the scene is gone to avoid dereferencing freed memory.
std::weak_ptr<PAGScene> owner;
Animation* animation = nullptr;
// Runtime binding the channel writers should target. Top-level timelines use the owning
// PAGScene's binding; composition timelines use the binding built for that composition.
// Runtime binding the channel writers should target. A null binding marks a top-level timeline:
// the owning scene's current root binding is resolved lazily at apply time, so the timeline keeps
// working after the scene rebuilds its runtime tree (which frees and replaces that binding). A
// non-null binding is a fixed composition binding co-owned with that composition.
RuntimeBinding* binding = nullptr;
// Document used to resolve channel target IDs at apply time. Top-level timelines use the scene's
// primary document; timelines spawned by external composition layers use the layer's externalDoc
// so internal IDs of the external file stay self-contained.
PAGXDocument* contextDoc = nullptr;
// Cached target resolution: each entry pairs a resolved target node with the channels driving
// it. Populated by resolveTargets() on first apply(); resolved stays false until then. Once
// resolved is set it is never reset, so this cache assumes animation->objects and
// contextDoc->nodeMap stay stable after the owning PAGScene is built (see resolveTargets()).
// it. Populated by resolveTargets() on first apply(); never reset in place — stale caches are
// replaced by rebuilding the PAGTimeline (see resolveTargets()).
std::vector<std::pair<Node*, std::vector<Channel*>>> resolvedTargets = {};
bool resolved = false;
int64_t currentTimeUs = 0;
Expand Down
50 changes: 39 additions & 11 deletions include/pagx/PAGXDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,10 @@ class PAGXDocument : public Node {

/**
* Executes auto layout on the document, positioning layers according to their layout
* constraints. Must be called before rendering or font embedding. This method should only
* be called once per document — repeated calls may produce incorrect results because
* measurement data is cached and some layout operations permanently modify source geometry.
* constraints. Must be called before rendering or font embedding. Re-running layout on an
* already-laid-out document is supported (notifyChange relies on this to reflect edits): the
* reset branch discards the cached layout outputs first so nodes are re-measured from their
* current fields.
* @param fontConfig Optional font config for text measurement and rendering. When provided,
* updates the internal config before layout. Pass nullptr to use the
* previously set config (or no config).
Expand Down Expand Up @@ -175,14 +176,40 @@ class PAGXDocument : public Node {
void clearEmbed();

/**
* Performs internal bookkeeping for live PAGScene instances created from this document after the
* given nodes have been mutated. Currently this only prunes expired live-scene references; it
* does not yet rebuild or refresh any rendered content. Runtime rebuild dispatch to live scenes
* is not implemented yet.
* @param dirtyNodes the nodes whose fields were mutated. Pointers must reference nodes still
* owned by this document. Null entries are ignored. Passing an empty list is a no-op.
*/
void notifyChange(const std::vector<Node*>& dirtyNodes);
* Reflects post-build edits to the given nodes in every scene created from this document, while
* preserving existing layer handles wherever possible. Pass a container node to reflect changes
* to its child list; editing an animation, animation object, or channel applies the new timeline
* data to subsequent playback.
*
* When an edit changes a node's "@id" reference (e.g. AnimationObject.target, Fill.color), mark
* every node on the affected reference chain dirty, not just the mutated one — notifyChange only
* refreshes the nodes it is given.
*
* Editing an external composition: call notifyChange on the document that owns the edited nodes.
* Scenes that embed this document as an external composition are refreshed automatically. A node
* may only be notified through its owning document; foreign nodes (e.g. a node owned by a child
* externalDoc when notifyChange is called on the parent) are skipped, leaving the rest of the
* dirty list to refresh as usual. Use ownsNode() if the caller does not statically know which
* document owns a given node.
* @param dirtyNodes the nodes whose fields (or child lists) were mutated. Pointers must reference
* nodes still owned by this document. Null entries and foreign nodes are skipped. Passing an
* empty list (or one whose entries are all skipped) is a no-op.
* @param layoutChanged whether any mutated field affects layout (size, constraints, padding,
* fonts, text, geometry) or a child list changed. Pass true to re-run layout before refreshing;
* pass false for a cheaper render-only refresh, only safe for edits that do not affect layout
* (e.g. alpha, color). Callers that mutate via SetNodeChannel can derive the right value from
* RequiresLayout(NodeType, channel); structural add/remove must pass true.
*/
void notifyChange(const std::vector<Node*>& dirtyNodes, bool layoutChanged);

/**
* Returns true if the node belongs to this document. A node belongs to exactly one document;
* passing a node owned by a different document to notifyChange has no effect on this document's
* scenes. Use this to dispatch a multi-document edit to the right owners, or to validate a node's
* origin before notifying.
* @param node the node to check; null returns false.
*/
bool ownsNode(const Node* node) const;

NodeType nodeType() const override {
return NodeType::Document;
Expand Down Expand Up @@ -215,6 +242,7 @@ class PAGXDocument : public Node {
friend class PAGXExporter;
friend class TextLayoutContext;
friend class PAGScene;
friend class PAGComposition;
};

} // namespace pagx
119 changes: 119 additions & 0 deletions include/pagx/PAGXNodeChannel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/////////////////////////////////////////////////////////////////////////////////////////////////
//
// Tencent is pleased to support the open source community by making libpag available.
//
// Copyright (C) 2026 Tencent. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// unless required by applicable law or agreed to in writing, software distributed under the
// license is distributed on an "as is" basis, without warranties or conditions of any kind,
// either express or implied. see the license for the specific language governing permissions
// and limitations under the license.
//
/////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include <string>
#include <vector>
#include "pagx/nodes/Channel.h"
#include "pagx/nodes/Node.h"

namespace pagx {

/**
* Reflective, by-name read/write access to a PAGX node's scalar properties. Channels use the same
* attribute names as PAGX XML (e.g. "alpha", "position.x", "blendMode"). This API edits a
* PAGXDocument; refresh any associated scene afterwards via PAGXDocument::notifyChange (use
* RequiresLayout to decide the layoutChanged flag). Typical use: editor property panels and
* data-driven CLI edits, where the caller holds a string channel name rather than a compile-time
* field reference.
*
* Value encoding (KeyValue alternatives): scalars map directly (float/bool/int/string/Color);
* enums are passed as their string name (e.g. blendMode = "multiply"); Point/Size fields are
* addressed component-wise via suffixed channels ("position.x", "size.width"). Multi-component
* fields without a component channel are not exposed. The set of channels available for each node
* type can be enumerated at runtime via ListChannels(); ChannelExists() is the corresponding
* existence predicate, useful to distinguish a typo from a known-but-not-animatable channel
* (IsAnimatableChannel and RequiresLayout both return false for unknown channels).
*/

/**
* Reads the value of the given channel on the node into out.
* @param node the node to read from; must not be null.
* @param channel the channel name (see the encoding notes above).
* @param out receives the value on success; must not be null.
* @return true on success; false if node/out is null, the channel is unknown for the node type, or
* an optional field is unset.
*/
bool GetNodeChannel(const Node* node, const std::string& channel, KeyValue* out);

/**
* Writes value into the node field identified by channel. Edits are applied to the document;
* refresh any associated scene separately via PAGXDocument::notifyChange.
* @param node the node to write to; must not be null.
* @param channel the channel name (see the encoding notes above).
* @param value the value to write; its KeyValue alternative must match the field's type.
* @return true on success; false if node is null, the channel is unknown for the node type, the
* KeyValue type does not match the field, or an enum string is invalid.
*/
bool SetNodeChannel(Node* node, const std::string& channel, const KeyValue& value);

/**
* Resets the node field identified by channel to its default — the value a freshly created node of
* the same type carries. Use this to "clear" a previously edited channel: for optional fields
* (e.g. a TextModifier's strokeWidth) the default is the unset state, so resetting removes the
* value. Only the addressed component is reset for component-wise channels ("position.x" resets x
* but leaves y). Refresh any associated scene separately via PAGXDocument::notifyChange.
* @param node the node to reset; must not be null.
* @param channel the channel name (see the encoding notes above).
* @return true on success; false if node is null or the channel is unknown for the node type.
*/
bool ResetNodeChannel(Node* node, const std::string& channel);

/**
* Returns true if the given channel can be driven by an animation channel (i.e. an animation may
* target it during playback). Returns false for channels that only take effect through a
* layout/content rebuild and for unknown channels. Independent of RequiresLayout: a geometry
* channel such as a shape's "size.width" is both animatable during playback and layout-affecting
* when edited on the document.
* @param type the node type.
* @param channel the channel name.
*/
bool IsAnimatableChannel(NodeType type, const std::string& channel);

/**
* Returns true if editing the given channel on the document requires a layout pass before the
* change is visible in a scene. Callers that mutate a channel via SetNodeChannel should pass this
* as the layoutChanged flag to PAGXDocument::notifyChange. Returns false for channels that refresh
* without layout and for unknown channels.
* @param type the node type.
* @param channel the channel name.
*/
bool RequiresLayout(NodeType type, const std::string& channel);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 反射 API 缺少『列出/查询合法 channel』的 introspection 接口

IsAnimatableChannel / RequiresLayout 对未知 channel 一律返回 false(注释明确说 for unknown channels),调用方无法区分『通道存在但不可动画化』vs『通道根本不存在(typo)』。GetNodeChannel 失败也是返回 false 没有日志(仅 SetNodeChannel 在 .cpp:912 输出 LOGE)。

编辑器场景下 channel 名 typo(如 Alpha vs alpha)是常见错误,目前完全静默;同时调用方除了翻 PAGX schema 文档,没有运行时手段枚举一个 NodeType 的所有合法 channel。

建议新增至少一个 introspection API:

  • std::vector<std::string> ListChannels(NodeType type);
  • bool ChannelExists(NodeType type, const std::string& channel);

或把 IsAnimatableChannel 返回 enum(Unknown / Animatable / NotAnimatable)以区分三态。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

认同,已加 introspection 接口 + 补 LOGE。

新增的 public API

bool ChannelExists(NodeType type, const std::string& channel);
std::vector<std::string> ListChannels(NodeType type);

ChannelExists 是单点判断,让调用方区分"通道不存在(typo)" vs "通道存在但不可动画化/不需布局"——前面 IsAnimatableChannel / RequiresLayout 把这两种情况都收敛成 false,确实信息丢了。

ListChannels 是 editor 属性面板的批量场景,返回当前节点类型的所有通道名(顺序是表里声明序,不保证跨 release 稳定,注释里写了)。

没改 IsAnimatableChannel 的返回类型成三态枚举,避免破坏 ABI;编辑器要"三态"就 ChannelExists + IsAnimatableChannel 组合一下足够。

GetNodeChannel 失败补 LOGE

SetNodeChannel / ResetNodeChannel 对齐:未知通道 / 读失败都打日志,错误信息带 channel 名和 nodeType,便于编辑器排查。


/**
* Returns true if the given channel name is defined for the node type, regardless of its flags.
* Use this to distinguish a typo (channel does not exist) from a channel that exists but is not
* animatable / does not require layout — IsAnimatableChannel and RequiresLayout collapse both into
* false. Editor scenarios that pass user-provided channel names through Get/SetNodeChannel should
* validate with this first to surface typos as a distinct error.
* @param type the node type.
* @param channel the channel name.
*/
bool ChannelExists(NodeType type, const std::string& channel);

/**
* Returns the list of channel names defined for the given node type. The returned names match the
* strings accepted by Get/SetNodeChannel/ResetNodeChannel and the queries above. Order is stable
* within a release but is not guaranteed across releases. Returns an empty vector for node types
* that expose no reflectable scalar channels.
* @param type the node type.
*/
std::vector<std::string> ListChannels(NodeType type);

} // namespace pagx
4 changes: 3 additions & 1 deletion include/pagx/nodes/Channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <vector>
#include "pagx/nodes/Keyframe.h"
#include "pagx/nodes/Node.h"
#include "pagx/types/Matrix.h"

namespace pagx {

Expand All @@ -32,7 +33,7 @@ namespace pagx {
* ordered to match ChannelValueType, so the active alternative index equals the corresponding
* ChannelValueType value.
*/
using KeyValue = std::variant<float, bool, int, std::string, ImageRef, Color>;
using KeyValue = std::variant<float, bool, int, std::string, ImageRef, Color, Matrix>;

/**
* Discriminator for the value type carried by a Channel's keyframes. Aligned with the order of
Expand All @@ -45,6 +46,7 @@ enum class ChannelValueType : uint8_t {
String = 3,
ImageRef = 4,
Color = 5,
Matrix = 6,
};

/**
Expand Down
11 changes: 11 additions & 0 deletions include/pagx/nodes/LayoutNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ class LayoutNode {
/** Returns true if any constraint attribute is set. */
bool hasConstraints() const;

/**
* Clears the layout-computed outputs (preferred and resolved position/size) so the node is
* re-measured on the next layout pass. Authored inputs (width/height, constraints, percent sizes)
* are left unchanged.
*
* Called automatically by `PAGXDocument::applyLayout` before re-running layout on an
* already-laid-out document, so callers normally do not invoke this directly. Calling it without
* a subsequent layout pass leaves the node without resolved geometry.
*/
void resetLayout();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nit] resetLayout() 注释缺调用时机说明

注释说『Used when re-running layout on an already-laid-out document after edits』,但没说明:

  • PAGXDocument::applyLayout 在 reset 分支自动调用,外部用户不需直接调用
  • 还是用户必须在某些场景下主动调用?忘记调用的后果是什么?

PAGXDocument.h:147-149 已经描述了 applyLayout 的 reset 行为,但 resetLayout() 作为 public 方法的文档应该自洽。建议补充一行说明调用方与时机,例如:

Called automatically by PAGXDocument::applyLayout when re-running layout on an already laid-out document; external callers normally don't invoke this directly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

认同,已补。

resetLayout() 注释加了一段说明:

  • PAGXDocument::applyLayout 自动调用(notifyChangelayoutChanged=true 时会触发)
  • 外部用户通常不需要直接调用
  • 如果有代码路径绕开 applyLayout 自己跑布局,调用 resetLayout 后必须紧跟一次布局,否则节点会处在半初始化的瞬态


/**
* Returns the layout-resolved bounds of this node in its parent's coordinate space.
* Only valid after applyLayout() has been called. Before layout, returns an empty Rect.
Expand Down
14 changes: 14 additions & 0 deletions src/pagx/LayoutNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ bool LayoutNode::hasConstraints() const {
!std::isnan(percentHeight);
}

void LayoutNode::resetLayout() {
// Reset only the layout-computed outputs so a subsequent updateSize() re-measures and re-resolves
// from the current authored fields. The authored inputs (width/height, constraints, percent*) are
// left untouched.
preferredX = 0;
preferredY = 0;
preferredWidth = NAN;
preferredHeight = NAN;
layoutX = NAN;
layoutY = NAN;
layoutWidth = NAN;
layoutHeight = NAN;
}

Rect LayoutNode::layoutBounds() const {
float x = std::isnan(layoutX) ? preferredX : layoutX;
float y = std::isnan(layoutY) ? preferredY : layoutY;
Expand Down
4 changes: 4 additions & 0 deletions src/pagx/PAGLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ std::string PAGLayer::name() const {
return node != nullptr ? node->name : std::string();
}

std::string PAGLayer::id() const {
return node != nullptr ? node->id : std::string();
}

Matrix PAGLayer::getGlobalMatrix() const {
auto scene = rootScene.lock();
if (runtimeLayer == nullptr || scene == nullptr) {
Expand Down
Loading
Loading