{"id":14452,"date":"2026-06-22T06:17:08","date_gmt":"2026-06-22T04:17:08","guid":{"rendered":"https:\/\/store.algosyntax.com\/?post_type=documentation&#038;p=14452"},"modified":"2026-06-22T06:35:05","modified_gmt":"2026-06-22T04:35:05","slug":"linegridcanvaspanel-2-4-master-follower-grid-synchronization","status":"publish","type":"documentation","link":"https:\/\/store.algosyntax.com\/documentation\/linegridcanvaspanel-2-4-master-follower-grid-synchronization\/","title":{"rendered":"LineGridCanvasPanel 2.4 : Master \/ Follower Grid Synchronization"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"14452\" class=\"elementor elementor-14452\" data-elementor-post-type=\"documentation\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8098c38 e-flex e-con-boxed e-con e-parent\" data-id=\"8098c38\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9d861db elementor-widget elementor-widget-heading\" data-id=\"9d861db\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Line Grid Canvas Panel 2.4 : Master Follower Grid Synchronization<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-378acf9 elementor-widget elementor-widget-text-editor\" data-id=\"378acf9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>The master\/follower system lets multiple canvases represent one shared navigable domain without turning them into one giant widget.<br \/>A Notes grid can be the master. A ruler grid and a velocity grid can follow it. When the master changes its supported navigation state, the same public function is called on every compatible follower.<br \/>This keeps the grids visually aligned while allowing each canvas to retain its own rendering, children, and domain-specific behavior.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-7d08b0c e-flex e-con-boxed e-con e-parent\" data-id=\"7d08b0c\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8ed1851 elementor-widget elementor-widget-heading\" data-id=\"8ed1851\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">How it works<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-10c9625 elementor-widget elementor-widget-text-editor\" data-id=\"10c9625\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Each canvas can be in one of two roles:<\/p><ul><li>A master owns a runtime list of follower grids.<\/li><li>A follower has one runtime master grid.<\/li><li>A master can have many followers.<\/li><li>A follower cannot follow more than one master at a time.<\/li><li>Cycles are rejected, so a follower can never eventually point back to itself.<\/li><\/ul><p>The links use weak references. They do not keep widgets alive, are not editor-authored relationships, and are cleared during load or duplication. This is intentionally a runtime setup system.<\/p><p>Use these base functions:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-081e410 elementor-widget elementor-widget-code-highlight\" data-id=\"081e410\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-cpp line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-cpp\">\n\t\t\t\t\t<xmp>NotesGrid->AddFollowerGrid(RulerGrid);\r\nNotesGrid->AddFollowerGrid(VelocityGrid);\r\n\r\n\/\/ Later, if required:\r\nNotesGrid->RemoveFollowerGrid(VelocityGrid);\r\nRulerGrid->DetachFromMasterGrid();<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-019d889 elementor-widget elementor-widget-text-editor\" data-id=\"019d889\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p><code>AddFollowerGrid(...)<\/code> immediately applies the master\u2019s current supported state to the new follower. It is the right place to set up the relationship from a UMG Construct\/OnInitialized path, after the participating widgets exist.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-4e01e5f e-flex e-con-boxed e-con e-parent\" data-id=\"4e01e5f\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1867e68 elementor-widget elementor-widget-heading\" data-id=\"1867e68\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">What every LineGridCanvas follower receives<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-539a190 elementor-widget elementor-widget-text-editor\" data-id=\"539a190\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>The base canvas synchronizes navigation behavior that has the same meaning across ordinary line-grid canvases.<\/p><p>When the master calls one of these functions, each follower receives the same call:<\/p><ul><li><code>SetScrollingEnabled<\/code><\/li><li><code>SetZoomingEnabled<\/code><\/li><li><code>SetLogicalSpan<\/code><\/li><li><code>ZoomNormalized<\/code><\/li><li><code>ScrollNormalized<\/code><\/li><li><code>ScrollToLogicalUnit<\/code><\/li><li><code>ScrollToGeometryPosition<\/code><\/li><\/ul><p>So, for example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-df006b3 elementor-widget elementor-widget-code-highlight\" data-id=\"df006b3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-cpp line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-cpp\">\n\t\t\t\t\t<xmp>NotesGrid->SetLogicalSpan(FFloatInterval(-25.0f, 50.0f), Orient_Horizontal);<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c79aca7 elementor-widget elementor-widget-text-editor\" data-id=\"c79aca7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>updates the master and then calls the same span function on its followers.<\/p><p>When a follower is first attached, the master also applies its current horizontal and vertical navigation snapshot:<\/p><ul><li>scrolling enabled state;<\/li><li>zooming enabled state;<\/li><li>logical span;<\/li><li>normalized zoom;<\/li><li>normalized scroll position.<\/li><\/ul><p>This means a ruler added after the Notes grid has already been zoomed and scrolled starts aligned rather than waiting for the next user interaction.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-a001b8e e-flex e-con-boxed e-con e-parent\" data-id=\"a001b8e\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1498779 elementor-widget elementor-widget-heading\" data-id=\"1498779\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Extending the system<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d73830c elementor-widget elementor-widget-text-editor\" data-id=\"d73830c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Further specialized canvases can extend follower synchronization without changing the base relationship.<br \/>The pattern is:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-32e22ae elementor-widget elementor-widget-code-highlight\" data-id=\"32e22ae\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-cpp line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-cpp\">\n\t\t\t\t\t<xmp>void UMyGridCanvas::SynchronizeFollowerGrid(UAxLineGridCanvasPanel* InFollowerGrid)\r\n{\r\n    UMyGridCanvas* MatchingFollower = Cast<UMyGridCanvas>(InFollowerGrid);\r\n\r\n    if (MatchingFollower)\r\n    {\r\n        MatchingFollower->SetMySharedDomainSetting(GetMySharedDomainSetting());\r\n    }\r\n\r\n    Super::SynchronizeFollowerGrid(InFollowerGrid);\r\n}<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3c22664 elementor-widget elementor-widget-text-editor\" data-id=\"3c22664\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>For a new shared mutation, add a public setter on the specialized canvas, update its own state, then cast compatible followers and call that same setter on them.<br \/>That keeps propagation explicit and domain-safe: shared timeline state moves together, while each panel remains free to own what makes it distinct.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-b252860 e-flex e-con-boxed e-con e-parent\" data-id=\"b252860\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1136e48 elementor-widget elementor-widget-heading\" data-id=\"1136e48\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Example: MediaGrid followers<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f5e8fd1 elementor-widget elementor-widget-text-editor\" data-id=\"f5e8fd1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p><code>UAxMediaGridCanvas<\/code> extends the base relationship for followers that are also media grids.<\/p><p>A compatible media follower receives:<\/p><ul><li><code>SetPlaybackPosition<\/code><\/li><li><code>SetPlayheadPositionMode<\/code><\/li><li><code>SetPlayheadViewportAnchor<\/code><\/li><\/ul><p>This is useful when several timeline views should track the same playhead and fixed-viewport playback behavior.<\/p><p>The following remain local:<\/p><ul><li>playback LineGrid ID;<\/li><li>whether the cursor is drawn;<\/li><li>cursor brush, width, and draw layer.<\/li><\/ul><p>That distinction matters: two panels may follow the same playback position but intentionally draw very different playheads\u2014or none at all.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-c12fbd0 e-flex e-con-boxed e-con e-parent\" data-id=\"c12fbd0\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e1fc88a elementor-widget elementor-widget-heading\" data-id=\"e1fc88a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Example 2: MusicalGrid followers<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a93fdcf elementor-widget elementor-widget-text-editor\" data-id=\"a93fdcf\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p><code>UAxMusicalGridCanvas<\/code> extends the relationship again for followers that are also musical grids.<\/p><p>A compatible musical follower receives:<\/p><ul><li><code>SetTimeSignature<\/code><\/li><li><code>SetTPQ<\/code><\/li><li><code>SetTimeGridOrientation<\/code><\/li><li><code>SetDefaultBarLineSpacing<\/code><\/li><li><code>SetBeatsLogicalSpan<\/code><\/li><li><code>SetGridQuantization<\/code><\/li><\/ul><p>This is the useful shared time-domain layer for a piano roll, bar ruler, and velocity lane.<\/p><p>For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-408b06f elementor-widget elementor-widget-code-highlight\" data-id=\"408b06f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-cpp line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-cpp\">\n\t\t\t\t\t<xmp>NotesGrid->SetBeatsLogicalSpan(FFloatInterval(-25.0f, 50.0f));\r\nNotesGrid->SetGridQuantization(EAxMusicalTimeDivision::OneQuarterBeat);<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fdb4912 elementor-widget elementor-widget-text-editor\" data-id=\"fdb4912\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>updates compatible musical followers through their public musical APIs. The follower therefore rebuilds its Bars\/Beats relationship correctly rather than having another canvas write into its internal variables.<\/p><p><code>SetDefaultBarLineSpacing<\/code> and <code>GetDefaultBarLineSpacing<\/code> were also added so bar spacing can be changed through the same function-based path.<\/p><p>These remain local to each musical canvas:<\/p><ul><li>pitch enablement and pitch span;<\/li><li>pitch LineGrid ID and pitch spacing;<\/li><li>grid density;<\/li><li>visual styling;<\/li><li>movement-snap policy.<\/li><\/ul><p>A velocity lane may share musical time with a piano roll while having entirely different vertical-axis meaning. The system deliberately does not force those settings to match.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"featured_media":0,"template":"elementor_header_footer","categories":[132,129],"tags":[136],"class_list":["post-14452","documentation","type-documentation","status-publish","hentry","category-enhanced-ui","category-unreal-engine","tag-userinterface","entry"],"acf":[],"_links":{"self":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation\/14452","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation"}],"about":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/types\/documentation"}],"version-history":[{"count":2,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation\/14452\/revisions"}],"predecessor-version":[{"id":14463,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation\/14452\/revisions\/14463"}],"wp:attachment":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/media?parent=14452"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/categories?post=14452"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/tags?post=14452"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}