Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
66e80b8
Decouple text shaping from Text node via TextGlyphParams to enable ru…
Hparty Jul 15, 2026
c630f82
Add TextHolder to reshape ViewModel-driven text at draw time without …
Hparty Jul 15, 2026
2236e36
Thread TextBox holder context through convert methods explicitly inst…
Hparty Jul 15, 2026
6230877
Split LayerBuilderContext into static base and runtime subclass via v…
Hparty Jul 15, 2026
f8bc5cc
Hold TextBox convert context as runtime subclass state to keep shared…
Hparty Jul 15, 2026
c5413eb
Merge TextGlyphParams into TextLayoutParams.h to keep text layout par…
Hparty Jul 15, 2026
eeebd05
Adapt flushTextHolders to the PAGXDocument fontConfig accessor after …
Hparty Jul 15, 2026
cefacdc
Access PAGXDocument font config through friend member instead of the …
Hparty Jul 15, 2026
f5c91ca
Add tests for font size reshape, position preservation, and TextBox c…
Hparty Jul 15, 2026
93f4a16
Reshape text in place via tgfx setTextBlob so the render tree updates…
Hparty Jul 16, 2026
e22e551
Update reshape tests for in-place blob swap and add ViewModel/Animati…
Hparty Jul 16, 2026
0f3c674
Mark Text shaping channels as animatable now that they carry runtime …
Hparty Jul 16, 2026
d5d6cd8
Cover Text shaping channels in AnimatableChannelsHaveWriters now that…
Hparty Jul 16, 2026
e0c9c2a
Extract ApplyPaddingToRuns helper shared by TextBox layout and TextHo…
Hparty Jul 16, 2026
e519a14
Skip creating a TextHolder for a TextBox with no Text children to avo…
Hparty Jul 16, 2026
d69aeef
Clean up TextHolder entries when removing binding nodes to prevent UAF.
Hparty Jul 16, 2026
70fae65
Move removeNode definition inside pagx namespace to fix compilation.
Hparty Jul 16, 2026
12f6bbd
Report pending text reshapes in scene content-changed check and skip …
Hparty Jul 16, 2026
3a606d6
update baseline
Hparty Jul 20, 2026
db9b218
Drop unused PAGAnimation include from PAGXView and widen reshape base…
Hparty Jul 20, 2026
b69522e
update baseline
Hparty Jul 20, 2026
ee4dab1
Extend pagx schema and verify to cover ViewModel, DataBind, Animation…
Hparty Jul 20, 2026
32ef502
Skip text-holder tree walk when no holder exists and drop const_cast …
Hparty Jul 21, 2026
ce227d0
Replace per-frame text-holder tree walk with a flat list cached on PA…
Hparty Jul 21, 2026
f4a3f99
Update hasContentChanged doc to cover pending TextHolder reshapes.
Hparty Jul 21, 2026
e803d51
Fix stale closures comment in LayerBuilder.h textHolders.
Hparty Jul 21, 2026
3a39f33
Erase all matching entries in TextHolder removeNode.
Hparty Jul 21, 2026
940fabe
Add Animations DataBind and viewModel to CompositionType schema.
Hparty Jul 21, 2026
49b005e
Drop stale StateMachineInput.defaultImage comment in CollectRefsFromS…
Hparty Jul 21, 2026
c9cff9e
Collect comp-level ViewModel properties dataBinds and animations refs.
Hparty Jul 21, 2026
a0faa0d
Drive size property in reshape demo test to cover fontSize DataBind.
Hparty Jul 21, 2026
28196d0
Clear stale TextHolder entries for surviving text elements before reb…
Hparty Jul 21, 2026
78a5ba8
Keep text_vertical baseline at main version since reshape does not af…
Hparty Jul 21, 2026
b8ea514
update baseline
Hparty Jul 22, 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
9 changes: 8 additions & 1 deletion include/pagx/PAGComposition.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Composition;
class DataBindRuntime;
class DataContext;
class Node;
class TextHolder;
struct RuntimeBinding;

/**
Expand Down Expand Up @@ -123,7 +124,7 @@ class PAGComposition : public PAGLayer {
// compositions themselves. Used by the composition-tree walks (view-model build, data-bind
// update, view-model advance) so that compositions nested under plain container layers are not
// skipped.
static void CollectChildCompositions(PAGLayer* layer, std::vector<PAGComposition*>& outChildren);
static void CollectChildCompositions(const PAGLayer* layer, std::vector<PAGComposition*>& outChildren);

// 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.
Expand Down Expand Up @@ -185,6 +186,12 @@ class PAGComposition : public PAGLayer {

void updateDataBinds(float mix = 1.0f);

// Appends every TextHolder registered in this composition's binding and its descendant
// compositions to `out`. Used by PAGScene to maintain a flat list of all holders across the
// runtime tree so per-frame flush / dirty checks can iterate it directly instead of walking the
// composition tree every frame.
void collectTextHolders(std::vector<std::shared_ptr<TextHolder>>& out) const;

friend class PAGScene;
};

Expand Down
17 changes: 14 additions & 3 deletions include/pagx/PAGScene.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class PAGViewModel;
class PAGXDocument;
class SuppressDelegation;
class PAGViewModelValue;
class TextHolder;
class ViewModel;
class Image;
struct Matrix;
Expand Down Expand Up @@ -156,9 +157,9 @@ class PAGScene : public std::enable_shared_from_this<PAGScene> {
/**
* Returns true if the scene's content has changed since the last render, i.e. a redraw would
* produce different pixels. Hosts driving their own render loop can consult this to skip
* redundant full renders on idle frames. Reflects only the runtime layer tree state captured
* by the last renderTo()/Record(); pending ViewModel data binds flushed at the next Record()
* are not accounted for here.
* redundant full renders on idle frames. Returns true when either the underlying display list
* has changed or a pending TextHolder reshape (driven by a ViewModel data bind or animation
* text-shaping channel) has not yet been flushed — flush runs inside Record().
*/
bool hasContentChanged() const;

Expand Down Expand Up @@ -215,6 +216,10 @@ class PAGScene : public std::enable_shared_from_this<PAGScene> {
ViewModel* schema, const std::shared_ptr<PAGScene>& scene,
std::unordered_set<const ViewModel*>& visited);
void flushDataBinds();
void flushTextHolders();
// Rebuilds the flat `textHolders` list from the current runtime tree. Called at the end of
// buildRuntimeTree and after refreshNodes so the list stays in sync with the binding state.
void collectTextHolders();
void clearAllViewModelsDirty();
static void ClearCompositionTreeDirty(PAGComposition* comp);

Expand Down Expand Up @@ -251,6 +256,12 @@ class PAGScene : public std::enable_shared_from_this<PAGScene> {
bool suppressNotify = false;
std::vector<PAGViewModelValue*> pendingNotifications = {};

// Flat list of every TextHolder registered across the runtime composition tree. Rebuilt at the
// end of buildRuntimeTree and after refreshNodes (which can replace a binding's holders via
// RefreshLayerInPlace). flushTextHolders and hasContentChanged iterate this list directly so the
// common case (no text reshape in the document) avoids walking the composition tree every frame.
std::vector<std::shared_ptr<TextHolder>> textHolders = {};

// Maps tgfx layers in the runtime tree to their PAGLayer nodes for hit-test resolution.
std::unordered_map<const tgfx::Layer*, PAGLayer*> layerRegistry = {};

Expand Down
36 changes: 36 additions & 0 deletions resources/text/text_reshape_demo.pagx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<pagx width="400" height="200" viewModel="@MainVM">
<Resources>
<ViewModel id="MainVM">
<Property id="title" name="title" type="String" default="VM Default"/>
<Property id="size" name="size" type="Number" default="40"/>
</ViewModel>
</Resources>

<Layer id="animLayer" name="AnimText" width="400" height="100">
<Text id="animText" text="Hello" fontFamily="NotoSansSC" fontSize="40" position="20,70"/>
<Fill color="#000000"/>
<Timelines>
<Animation ref="@textAnim"/>
</Timelines>
</Layer>

<Layer id="vmLayer" name="VMText" width="400" height="100" y="100">
<Text id="vmText" text="VM Default" fontFamily="NotoSansSC" fontSize="40" position="20,170"/>
<Fill color="#0000FF"/>
</Layer>

<DataBind source="$vm.title" target="@vmText" channel="text"/>
<DataBind source="$vm.size" target="@vmText" channel="fontSize"/>

<Animations>
<Animation id="textAnim" duration="120" frameRate="60" loop="once">
<Object target="animText">
<Channel name="text" type="string">
<Key time="0" value="Hello" interpolation="hold"/>
<Key time="60" value="Animated" interpolation="hold"/>
</Channel>
</Object>
</Animation>
</Animations>
</pagx>
Loading
Loading