{"id":13812,"date":"2026-05-09T07:37:53","date_gmt":"2026-05-09T05:37:53","guid":{"rendered":"https:\/\/store.algosyntax.com\/?post_type=documentation&#038;p=13812"},"modified":"2026-05-12T17:56:29","modified_gmt":"2026-05-12T15:56:29","slug":"linegridcanvaspanel-2-grids-timelines-piano-rolls-and-native-grid-modules","status":"publish","type":"documentation","link":"https:\/\/store.algosyntax.com\/documentation\/linegridcanvaspanel-2-grids-timelines-piano-rolls-and-native-grid-modules\/","title":{"rendered":"LineGridCanvasPanel 2: Grids, Timelines, Piano Rolls, And Native Grid Modules"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"13812\" class=\"elementor elementor-13812\" data-elementor-post-type=\"documentation\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ea3933f e-flex e-con-boxed e-con e-parent\" data-id=\"ea3933f\" 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-bf6841e elementor-widget elementor-widget-heading\" data-id=\"bf6841e\" 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\">LineGridCanvasPanel 2: Creating Grids, Timelines, Piano Rolls, And Native Grid Modules<\/h1>\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-10ed145 e-flex e-con-boxed e-con e-parent\" data-id=\"10ed145\" 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-e8278fa elementor-widget elementor-widget-text-editor\" data-id=\"e8278fa\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1sipi8n\" data-start=\"92\" data-end=\"111\">What this covers<\/h2><p>1. What changed from `LineGridCanvasPanel` 1.0 to 2.0<br \/>2. What `LineGridCanvasPanel` is for<br \/>3. The new version 2.0 architecture<br \/>4. Logical units, scrollable geometry, and viewport-space<br \/>5. The core building blocks<br \/>6. How line grids are rendered<br \/>7. How child widgets are positioned on the grid<br \/>8. How default child movement and snapping work<br \/>9. How scrolling and zooming work in version 2.0<br \/>10. How flipped logical units affect scrolling and resizing<br \/>11. How the native module system works<br \/>12. How to extend movement, snapping, rendering, timelines, piano rolls, and custom labels<br \/>13. How to synchronize child movement with backend data<br \/>14. What to adjust first<\/p><p>\u00a0<\/p><p>This article is for <code data-start=\"686\" data-end=\"707\">LineGridCanvasPanel<\/code> version 2.0. The older version 1.0 article explained the original panel model, visible-only rendering, child widget placement, flipped grids, and wrapper-based scrolling expectations. Version 2.0 keeps the same core purpose, but changes the internal architecture, naming model, navigation model, and C++ extension model.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3edf708 elementor-widget elementor-widget-button\" data-id=\"3edf708\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"https:\/\/store.algosyntax.com\/marketplace\/unreal-engine\/enhanced-ui\/\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">Download Enhanced UI Plugins<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\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-9d9b6fc e-flex e-con-boxed e-con e-parent\" data-id=\"9d9b6fc\" 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-ed2edbe elementor-widget elementor-widget-heading\" data-id=\"ed2edbe\" 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 LineGridCanvasPanel Solves<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-335bf3c elementor-widget elementor-widget-text-editor\" data-id=\"335bf3c\" 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 data-start=\"1108\" data-end=\"1199\"><code data-start=\"1108\" data-end=\"1132\">UAxLineGridCanvasPanel<\/code> is used when a UI needs to display widgets against a logical grid.<\/p><p data-start=\"1201\" data-end=\"1218\">Examples include:<\/p><ul data-start=\"1220\" data-end=\"1425\"><li data-section-id=\"1h6av8g\" data-start=\"1220\" data-end=\"1231\">Timelines<\/li><li data-section-id=\"2hix8f\" data-start=\"1232\" data-end=\"1245\">Piano rolls<\/li><li data-section-id=\"pwqhli\" data-start=\"1246\" data-end=\"1258\">Sequencers<\/li><li data-section-id=\"rn7q8l\" data-start=\"1259\" data-end=\"1274\">Track editors<\/li><li data-section-id=\"18xg9bf\" data-start=\"1275\" data-end=\"1294\">Bar \/ beat rulers<\/li><li data-section-id=\"cd2xcj\" data-start=\"1295\" data-end=\"1315\">Time-based editors<\/li><li data-section-id=\"l2vg99\" data-start=\"1316\" data-end=\"1340\">Row-and-column editors<\/li><li data-section-id=\"v5a1gs\" data-start=\"1341\" data-end=\"1360\">Animation editors<\/li><li data-section-id=\"v5a1gs\" data-start=\"1341\" data-end=\"1360\">Any editor where raw pixels are not the real meaning of the UI<\/li><\/ul><p data-start=\"1427\" data-end=\"1462\">The important problem it solves is:<\/p><blockquote data-start=\"1464\" data-end=\"1593\"><p data-start=\"1466\" data-end=\"1593\">How do we draw a large logical grid, only render the visible part, and place widgets using logical units instead of raw pixels?<\/p><\/blockquote><p data-start=\"1595\" data-end=\"1656\">A normal canvas places widgets using pixel position and size.<\/p><p data-start=\"1658\" data-end=\"1714\">A line grid canvas places widgets using logical meaning.<\/p><p data-start=\"1716\" data-end=\"1728\">For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-896bb72 elementor-widget elementor-widget-code-highlight\" data-id=\"896bb72\" 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>For a piano roll:\r\n\r\nX0 = Beat 4\r\nX1 = Beat 8\r\n\r\nY0 = MIDI Note 60\r\nY1 = MIDI Note 61<\/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-595aa7e elementor-widget elementor-widget-code-highlight\" data-id=\"595aa7e\" 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>For a timeline:\r\n\r\nX0 = 12.5 seconds\r\nX1 = 18.0 seconds\r\n\r\nY0 = Track 2\r\nY1 = Track 3<\/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-b165d9a elementor-widget elementor-widget-text-editor\" data-id=\"b165d9a\" 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 data-start=\"2092\" data-end=\"2104\">\u00a0The panel converts those logical values into the physical position and size needed by UMG.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-73fc80b elementor-widget elementor-widget-text-editor\" data-id=\"73fc80b\" 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 data-start=\"2092\" data-end=\"2104\">This article gets a bit technical, Its recommended you can jump to the<\/p><h2 data-section-id=\"17z3i6r\" data-start=\"17910\" data-end=\"17933\">How To Use Section<\/h2><p data-start=\"17935\" data-end=\"18003\">If you&#8217;re trying to recap or just get started quickly. Below we&#8217;ll cover the technicalities\/low level functionalities of the system in case you need to debug or extend it.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4f5a573 elementor-widget elementor-widget-button\" data-id=\"4f5a573\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"button.default\">\n\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-button elementor-button-link elementor-size-sm\" href=\"#HowToUseTheSystem\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\">How To Use Section<\/span>\n\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\n\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-0d759f3 e-flex e-con-boxed e-con e-parent\" data-id=\"0d759f3\" 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-b8835b8 elementor-widget elementor-widget-heading\" data-id=\"b8835b8\" 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 Changed In Version 2.0<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0db03f3 elementor-widget elementor-widget-text-editor\" data-id=\"0db03f3\" 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 data-start=\"2087\" data-end=\"2198\">Version 2.0 keeps the same core behavior, but the implementation is now more maintainable and easier to extend.<\/p><p data-start=\"2200\" data-end=\"2254\">The original panel handled almost everything directly:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3f1636a elementor-widget elementor-widget-code-highlight\" data-id=\"3f1636a\" 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>Grid creation\r\nGrid lookup\r\nGrid synchronization\r\nRendering\r\nScrolling\r\nZooming\r\nChild layout\r\nMouse coordinate conversion\r\nEditor validation<\/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-8191d33 elementor-widget elementor-widget-text-editor\" data-id=\"8191d33\" 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 data-start=\"2402\" data-end=\"2468\">Version 2.0 splits those responsibilities into native C++ modules:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8abaff7 elementor-widget elementor-widget-code-highlight\" data-id=\"8abaff7\" 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>FAxLineGridCanvasGridRegistry\nFAxLineGridCanvasNavigation\nFAxLineGridCanvasRenderer\nFAxLineGridCanvasChildLayout\nFAxLineGridCanvasInput\nFAxLineGridCanvasMovement\nFAxLineGridCanvasEditorValidator<\/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-7087c2b elementor-widget elementor-widget-text-editor\" data-id=\"7087c2b\" 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 data-start=\"2651\" data-end=\"2697\">The panel is now mostly the public API fa\u00e7ade.<\/p><p data-start=\"2699\" data-end=\"2746\">The modules do the focused implementation work.<\/p><p data-start=\"2748\" data-end=\"2759\">This means:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-68cfddb elementor-widget elementor-widget-code-highlight\" data-id=\"68cfddb\" 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>Navigation bug        -> check FAxLineGridCanvasNavigation\n    Rendering bug         -> check FAxLineGridCanvasRenderer\n    Child layout bug      -> check FAxLineGridCanvasChildLayout\n    Mouse conversion bug  -> check FAxLineGridCanvasInput\n    Child movement bug    -> check FAxLineGridCanvasMovement\n    Grid setup bug        -> check FAxLineGridCanvasGridRegistry\n    Editor validation     -> check FAxLineGridCanvasEditorValidator<\/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\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6ee6a95 e-flex e-con-boxed e-con e-parent\" data-id=\"6ee6a95\" 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-7a2a720 elementor-widget elementor-widget-heading\" data-id=\"7a2a720\" 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\">The Version 2.0 Mental Model<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0ef5f7f elementor-widget elementor-widget-text-editor\" data-id=\"0ef5f7f\" 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 data-start=\"3173\" data-end=\"3226\">Version 2.0 uses three important coordinate concepts:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ca1057d elementor-widget elementor-widget-code-highlight\" data-id=\"ca1057d\" 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>Logical units\r\nScrollable geometry\r\nViewport-space<\/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-79d3138 elementor-widget elementor-widget-text-editor\" data-id=\"79d3138\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"yfdngz\" data-start=\"3289\" data-end=\"3305\">Logical Units<\/h2><p data-start=\"3307\" data-end=\"3349\">Logical units are the meaning of the grid.<\/p><p data-start=\"3351\" data-end=\"3360\">Examples:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-eea7778 elementor-widget elementor-widget-code-highlight\" data-id=\"eea7778\" 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>50 bars\r\n128 pitches\r\n20 seconds\r\n12 tracks\r\n240 frames<\/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-19865ab elementor-widget elementor-widget-text-editor\" data-id=\"19865ab\" 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 data-start=\"3426\" data-end=\"3455\">This is why version 2.0 uses:<\/p><div class=\"relative w-full mt-4 mb-1\"><div class=\"\"><div class=\"relative\"><div class=\"h-full min-h-0 min-w-0\"><div class=\"h-full min-h-0 min-w-0\"><div class=\"border border-token-border-light border-radius-3xl corner-superellipse\/1.1 rounded-3xl\"><div class=\"h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse\/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback\"><div class=\"relative\"><div class=\"\"><div class=\"relative z-0 flex max-w-full\"><div id=\"code-block-viewer\" class=\"q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch \u037cs \u037c16\" dir=\"ltr\"><div class=\"cm-scroller\"><pre class=\"cm-content q9tKkq_readonly m-0\"><code><span class=\"\u037c11\">LogicalUnitsToRender<\/span><\/code><\/pre><p data-start=\"3490\" data-end=\"3515\">This value is not pixels.<\/p><p data-start=\"3517\" data-end=\"3528\">If you set:<\/p><div class=\"relative w-full mt-4 mb-1\"><div class=\"\"><div class=\"relative\"><div class=\"h-full min-h-0 min-w-0\"><div class=\"h-full min-h-0 min-w-0\"><div class=\"border border-token-border-light border-radius-3xl corner-superellipse\/1.1 rounded-3xl\"><div class=\"h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse\/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback\"><div class=\"pointer-events-none absolute inset-x-4 top-12 bottom-4\"><div class=\"pointer-events-none sticky z-40 shrink-0 z-1!\"><div class=\"sticky bg-token-border-light\">\u00a0<\/div><\/div><\/div><div class=\"relative\"><div class=\"\"><div class=\"relative z-0 flex max-w-full\"><div id=\"code-block-viewer\" class=\"q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch \u037cs \u037c16\" dir=\"ltr\"><div class=\"cm-scroller\"><pre class=\"cm-content q9tKkq_readonly m-0\"><code><span class=\"\u037c11\">LogicalUnitsToRender<\/span> = <span class=\"\u037cy\">50.0f<\/span>;<\/code><\/pre><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><div class=\"\"><div class=\"\">\u00a0<\/div><\/div><\/div><\/div><\/div><p data-start=\"3572\" data-end=\"3620\">that means the axis represents 50 logical units.<\/p><p data-start=\"3622\" data-end=\"3726\">Those units could be bars, seconds, pitches, tracks, or anything else represented by the axis line grid.<\/p><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div>\t\t\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1ce1088 e-con-full e-flex e-con e-child\" data-id=\"1ce1088\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ecf700a elementor-widget elementor-widget-text-editor\" data-id=\"ecf700a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1n4md14\" data-start=\"3733\" data-end=\"3755\">Scrollable Geometry<\/h2><p data-start=\"3757\" data-end=\"3857\">Scrollable geometry is the larger physical pixel area generated from logical units and line spacing.<\/p><p data-start=\"3859\" data-end=\"3890\">The simplified relationship is:<br \/><strong>ScrollableGeometrySize = LogicalUnitsToRender * LineSpacing<br \/><br \/><\/strong>Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-af77a40 elementor-widget elementor-widget-code-highlight\" data-id=\"af77a40\" 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>LogicalUnitsToRender = 50\r\nLineSpacing = 100 px\r\n\r\nScrollableGeometrySize = 5000 px<\/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-5b78f97 elementor-widget elementor-widget-code-highlight\" data-id=\"5b78f97\" 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>50 bars       = logical size\r\n5000 pixels   = scrollable geometry size<\/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-dceca34 elementor-widget elementor-widget-text-editor\" data-id=\"dceca34\" 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 data-start=\"4154\" data-end=\"4179\">This distinction matters.<\/p><p data-start=\"4181\" data-end=\"4222\">Logical size describes the editor domain.<\/p><p data-start=\"4224\" data-end=\"4291\">Scrollable geometry describes the physical space needed to draw it.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-9cfbcdf e-con-full e-flex e-con e-child\" data-id=\"9cfbcdf\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-17e32a0 elementor-widget elementor-widget-text-editor\" data-id=\"17e32a0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"vwctzs\" data-start=\"4298\" data-end=\"4315\">Viewport-Space<\/h2><p data-start=\"4620\" data-end=\"4676\">Viewport-space is the visible local space of the widget.<\/p><p data-start=\"4678\" data-end=\"4754\">If the widget is 1200 pixels wide and 600 pixels tall, then the viewport is: <span class=\"\u037cy\">1200<\/span> x <span class=\"\u037cy\">600<\/span><br \/><br \/><\/p><p data-start=\"4779\" data-end=\"4832\">The scrollable geometry may be much larger than this.<\/p><p data-start=\"4834\" data-end=\"4846\">For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-18f9b54 elementor-widget elementor-widget-code-highlight\" data-id=\"18f9b54\" 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>Viewport size:            1200 x 600\r\nScrollable geometry size: 5000 x 2000<\/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-09499a3 elementor-widget elementor-widget-text-editor\" data-id=\"09499a3\" 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 panel renders only the part of the scrollable geometry currently visible through the viewport.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-e640a29 e-con-full e-flex e-con e-child\" data-id=\"e640a29\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-beee6ff elementor-widget elementor-widget-text-editor\" data-id=\"beee6ff\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1bmgv1h\" data-start=\"5035\" data-end=\"5051\">Scroll Offset<\/h2><p data-start=\"5053\" data-end=\"5126\"><code data-start=\"5053\" data-end=\"5067\">ScrollOffset<\/code> is the positive pixel offset into the scrollable geometry.<\/p><p data-start=\"5128\" data-end=\"5203\">The panel uses it to convert scrollable geometry-space into viewport-space.<\/p><p data-start=\"5205\" data-end=\"5234\">The simplified conversion is:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-07338c9 elementor-widget elementor-widget-code-highlight\" data-id=\"07338c9\" 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>ViewportPosition = ScrollableGeometryPosition - ScrollOffset<\/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-b14aaf5 elementor-widget elementor-widget-text-editor\" data-id=\"b14aaf5\" 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>So if a note is physically located at X = 2000 inside the scrollable geometry, and the current scroll offset is X = 1500, that note appears at X = 500 in the viewport.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\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-6783fb1 e-flex e-con-boxed e-con e-parent\" data-id=\"6783fb1\" 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-3ee3ffc elementor-widget elementor-widget-heading\" data-id=\"3ee3ffc\" 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\">Core Building Blocks<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9298718 elementor-widget elementor-widget-text-editor\" data-id=\"9298718\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1dbeqtd\" data-start=\"4845\" data-end=\"4870\">UAxLineGridCanvasPanel<\/h2><p data-start=\"4872\" data-end=\"4920\"><code data-start=\"4872\" data-end=\"4896\">UAxLineGridCanvasPanel<\/code> is the main UMG widget.<\/p><p data-start=\"4922\" data-end=\"4976\">It owns the public API and designer-facing properties.<\/p><p data-start=\"4978\" data-end=\"5007\">Important properties include:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9b32a59 elementor-widget elementor-widget-code-highlight\" data-id=\"9b32a59\" 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>LineGrids\r\nLineGridObjects\r\nChildWidgetsLayoutProperties\r\nHorizontalNavigationProperties\r\nVerticalNavigationProperties\r\nBorderLines<\/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-556d035 elementor-widget elementor-widget-text-editor\" data-id=\"556d035\" 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 data-start=\"5839\" data-end=\"5880\">It also owns the internal native modules.<\/p><p data-start=\"5882\" data-end=\"5931\">The panel still handles the high-level lifecycle:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b57a4df elementor-widget elementor-widget-code-highlight\" data-id=\"b57a4df\" 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>RebuildWidget()\r\nSynchronizeProperties()\r\nRenderLineGrids()\r\nHandlePreRenderLineGrids()\r\nHandlePostRenderLineGrids()<\/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-63e9b60 elementor-widget elementor-widget-text-editor\" data-id=\"63e9b60\" 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 data-start=\"6058\" data-end=\"6114\">But the detailed implementation is delegated to modules.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-e0d0531 e-con-full e-flex e-con e-child\" data-id=\"e0d0531\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0f1e70d elementor-widget elementor-widget-text-editor\" data-id=\"0f1e70d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"168jj9a\" data-start=\"6116\" data-end=\"6140\">Where to look in code<\/h2><p data-start=\"6397\" data-end=\"6419\">Module initialization:<\/p><div class=\"relative w-full mt-4 mb-1\"><div class=\"\"><div class=\"relative\"><div class=\"h-full min-h-0 min-w-0\"><div class=\"h-full min-h-0 min-w-0\"><div class=\"border border-token-border-light border-radius-3xl corner-superellipse\/1.1 rounded-3xl\"><div class=\"h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse\/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback\"><div class=\"relative\"><div class=\"\"><div class=\"relative z-0 flex max-w-full\"><div id=\"code-block-viewer\" class=\"q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch \u037cs \u037c16\" dir=\"ltr\"><div class=\"cm-scroller\"><pre class=\"cm-content q9tKkq_readonly m-0\"><code><span class=\"\u037c11\">EnsureModulesInitialized<\/span>()<\/code><\/pre><p data-start=\"6460\" data-end=\"6484\">Native module factories:<\/p><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b94e24a elementor-widget elementor-widget-code-highlight\" data-id=\"b94e24a\" 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>CreateGridRegistryModule()\r\nCreateNavigationModule()\r\nCreateRendererModule()\r\nCreateChildLayoutModule()\r\nCreateInputModule()\r\nCreateEditorValidatorModule()<\/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>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5400c69 e-flex e-con-boxed e-con e-parent\" data-id=\"5400c69\" 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-b9de7df elementor-widget elementor-widget-heading\" data-id=\"b9de7df\" 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\">UAxLineGrid<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ba23ba6 elementor-widget elementor-widget-text-editor\" data-id=\"ba23ba6\" 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 data-start=\"6693\" data-end=\"6751\">A <code data-start=\"6695\" data-end=\"6708\">UAxLineGrid<\/code> represents one repeated logical unit grid.<\/p><p data-start=\"6753\" data-end=\"6779\">A line grid can represent:<\/p><ul data-start=\"6781\" data-end=\"6892\"><li data-section-id=\"1j423d6\" data-start=\"6781\" data-end=\"6787\">Bars<\/li><li data-section-id=\"16y6ll5\" data-start=\"6788\" data-end=\"6795\">Beats<\/li><li data-section-id=\"1vehsa3\" data-start=\"6796\" data-end=\"6805\">Seconds<\/li><li data-section-id=\"1yh1fd2\" data-start=\"6806\" data-end=\"6814\">Frames<\/li><li data-section-id=\"5g2iz8\" data-start=\"6815\" data-end=\"6823\">Tracks<\/li><li data-section-id=\"11s07sn\" data-start=\"6824\" data-end=\"6836\">Pitch rows<\/li><li data-section-id=\"16v8tuz\" data-start=\"6837\" data-end=\"6844\">Notes<\/li><li data-section-id=\"8itz2b\" data-start=\"6845\" data-end=\"6855\">Measures<\/li><li data-section-id=\"1aotc7i\" data-start=\"6856\" data-end=\"6892\">Any custom logical unit you define<\/li><\/ul><p data-start=\"6894\" data-end=\"6916\">A line grid has an ID.<\/p><p data-start=\"6918\" data-end=\"7016\">That ID matters because child widgets use it to say what logical unit their X and Y values are in.<br \/>Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c188857 elementor-widget elementor-widget-code-highlight\" data-id=\"c188857\" 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>LogicalXUnitsID = \"Beats\"\r\nLogicalYUnitsID = \"Pitch\"<\/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-aa7cd9b elementor-widget elementor-widget-text-editor\" data-id=\"aa7cd9b\" 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 data-start=\"7096\" data-end=\"7117\">A line grid controls:<\/p><ul data-start=\"7119\" data-end=\"7299\"><li data-section-id=\"1torla6\" data-start=\"7119\" data-end=\"7132\">Orientation<\/li><li data-section-id=\"1xf0xrb\" data-start=\"7133\" data-end=\"7147\">Line spacing<\/li><li data-section-id=\"z9ohfq\" data-start=\"7148\" data-end=\"7164\">Logical offset<\/li><li data-section-id=\"6julwb\" data-start=\"7165\" data-end=\"7200\">Whether logical units are flipped<\/li><li data-section-id=\"1y7njs6\" data-start=\"7201\" data-end=\"7215\">Line drawing<\/li><li data-section-id=\"1957vz4\" data-start=\"7216\" data-end=\"7228\">Tick lines<\/li><li data-section-id=\"1u4z4u8\" data-start=\"7229\" data-end=\"7242\">Text labels<\/li><li data-section-id=\"1j32hom\" data-start=\"7243\" data-end=\"7263\">Background drawing<\/li><li data-section-id=\"9rurpa\" data-start=\"7264\" data-end=\"7299\">Parent \/ child grid relationships<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3dc05a1 e-con-full e-flex e-con e-child\" data-id=\"3dc05a1\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-82c0efc elementor-widget elementor-widget-text-editor\" data-id=\"82c0efc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"10lmku5\" data-start=\"7301\" data-end=\"7331\">Parent and child line grids<\/h2><p data-start=\"7333\" data-end=\"7381\">A line grid can be a child of another line grid.<\/p><p data-start=\"7383\" data-end=\"7395\">For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fff73c7 elementor-widget elementor-widget-code-highlight\" data-id=\"fff73c7\" 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>Bars  -> parent grid\r\nBeats -> child grid<\/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-8cf261b elementor-widget elementor-widget-text-editor\" data-id=\"8cf261b\" 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 data-start=\"7450\" data-end=\"7480\">This is useful during zooming.<\/p><p data-start=\"7482\" data-end=\"7516\">Usually, you zoom the parent grid.<\/p><p data-start=\"7518\" data-end=\"7572\">Then child grids update their spacing from the parent.<\/p><p data-start=\"7574\" data-end=\"7582\">Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ae6602b elementor-widget elementor-widget-code-highlight\" data-id=\"ae6602b\" 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>Bar spacing = 400 px\r\nBeats per bar(ratio) = 4\r\n\r\nBeat spacing = 100 px<\/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-b5b656f elementor-widget elementor-widget-text-editor\" data-id=\"b5b656f\" 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 data-start=\"7658\" data-end=\"7711\">You do not need to manually zoom both bars and beats.<\/p><p data-start=\"7713\" data-end=\"7733\">You zoom the parent.<\/p><p data-start=\"7735\" data-end=\"7753\">The child and grandchildren follow.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\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-4be0ae5 e-flex e-con-boxed e-con e-parent\" data-id=\"4be0ae5\" 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-f1fbd7c elementor-widget elementor-widget-heading\" data-id=\"f1fbd7c\" 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\">UAxLineGridChildWidget<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4af2c2c elementor-widget elementor-widget-text-editor\" data-id=\"4af2c2c\" 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 data-start=\"5868\" data-end=\"5943\">A <code data-start=\"5870\" data-end=\"5894\">UAxLineGridChildWidget<\/code> represents a widget positioned by logical units.<\/p><p data-start=\"5945\" data-end=\"5957\">For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-caed7f0 elementor-widget elementor-widget-code-highlight\" data-id=\"caed7f0\" 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>X0 = Beat 4\r\nX1 = Beat 8\r\n\r\nY0 = Track 2\r\nY1 = Track 3<\/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-6a24e05 elementor-widget elementor-widget-text-editor\" data-id=\"6a24e05\" 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 data-start=\"8012\" data-end=\"8064\">The child does not calculate its own pixel position.<\/p><p data-start=\"8066\" data-end=\"8179\">Instead, the panel reads the child\u2019s logical values and converts them into a <code data-start=\"8143\" data-end=\"8160\">CanvasPanelSlot<\/code> position and size.<\/p><p data-start=\"8181\" data-end=\"8200\">The child provides:<\/p><p><strong><span class=\"\u037c11\">LogicalXUnitsID<\/span><\/strong><br \/><strong><span class=\"\u037c11\">LogicalYUnitsID<\/span><\/strong><br \/><strong><span class=\"\u037c11\">LogicalPositionRect<\/span><\/strong><\/p><p>\u00a0<\/p><p data-start=\"8266\" data-end=\"8343\">The panel resolves those unit IDs against the matching <code data-start=\"8321\" data-end=\"8334\">UAxLineGrid<\/code> objects.<\/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-b202c0f e-flex e-con-boxed e-con e-parent\" data-id=\"b202c0f\" 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-857d7aa elementor-widget elementor-widget-heading\" data-id=\"857d7aa\" 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\">Default Child Movement And Snapping<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d55f4bf elementor-widget elementor-widget-text-editor\" data-id=\"d55f4bf\" 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><strong>UAxLineGridCanvasPanel<\/strong> now has a default child movement and snapping system.<\/p><p>This is handled by:<\/p><p><strong>FAxLineGridCanvasMovement<\/strong><\/p><p>The movement module is responsible for moving and resizing <strong>UAxLineGridChildWidget<\/strong> instances in logical grid space.<\/p><p>This means child movement is not treated as raw pixel movement.<\/p><p>Instead, the panel follows this simplified flow:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7e6cd87 elementor-widget elementor-widget-code-highlight\" data-id=\"7e6cd87\" 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>Mouse movement\n        \u2193\n    Convert mouse position into scrollable geometry-space\n        \u2193\n    Convert scrollable geometry position into logical grid units\n        \u2193\n    Apply movement or resizing\n        \u2193\n    Optionally snap the result to a configured grid\n        \u2193\n    Apply the new logical rect to the child\n        \u2193\n    Update the child widget geometry\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\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-13b2e6b e-flex e-con-boxed e-con e-parent\" data-id=\"13b2e6b\" 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-4ec05e2 elementor-widget elementor-widget-heading\" data-id=\"4ec05e2\" 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\">Native Module Architecture<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4338621 elementor-widget elementor-widget-text-editor\" data-id=\"4338621\" 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 data-start=\"8378\" data-end=\"8424\">Version 2.0 uses a fa\u00e7ade plus native modules.<\/p><p data-start=\"8426\" data-end=\"8460\">The panel remains the API surface.<\/p><p data-start=\"8462\" data-end=\"8500\">The modules handle the implementation.<\/p><p data-start=\"8502\" data-end=\"8519\">The structure is:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7e4ebdf elementor-widget elementor-widget-code-highlight\" data-id=\"7e4ebdf\" 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>UAxLineGridCanvasPanel\r\n    owns FAxLineGridCanvasGridRegistry\r\n    owns FAxLineGridCanvasNavigation\r\n    owns FAxLineGridCanvasRenderer\r\n    owns FAxLineGridCanvasChildLayout\r\n    owns FAxLineGridCanvasInput\r\n    owns FAxLineGridCanvasEditorValidator\r\n    ...etc<\/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-67b727b elementor-widget elementor-widget-text-editor\" data-id=\"67b727b\" 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 data-start=\"13186\" data-end=\"13217\">The modules are not <code data-start=\"13206\" data-end=\"13215\">USTRUCT<\/code>s.<\/p><p data-start=\"13219\" data-end=\"13243\">They are not <code data-start=\"13232\" data-end=\"13241\">UObject<\/code>s.<\/p><p data-start=\"13245\" data-end=\"13306\">They are plain C++ classes so they can be inherited normally.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-eab11a5 elementor-widget elementor-widget-text-editor\" data-id=\"eab11a5\" 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 data-start=\"13745\" data-end=\"13818\">This lets you customize one subsystem without rewriting the entire panel.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-c18c360 e-con-full e-flex e-con e-child\" data-id=\"c18c360\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8aef74d elementor-widget elementor-widget-text-editor\" data-id=\"8aef74d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h3 data-section-id=\"1mrmw4t\" data-start=\"11165\" data-end=\"11203\">Example: Replacing Only The Renderer<\/h3><p data-start=\"11205\" data-end=\"11291\">For a piano roll, you may want logical pitch values to display as musical pitch names.<\/p><p data-start=\"11293\" data-end=\"11314\">Instead of rendering:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ac4eab6 elementor-widget elementor-widget-code-highlight\" data-id=\"ac4eab6\" 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>60\r\n61\r\n62<\/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-f6da786 elementor-widget elementor-widget-text-editor\" data-id=\"f6da786\" 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 data-start=\"11337\" data-end=\"11350\">You may want:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4f9e333 elementor-widget elementor-widget-code-highlight\" data-id=\"4f9e333\" 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>C4\r\nC#4\r\nD4<\/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-4245b9b elementor-widget elementor-widget-text-editor\" data-id=\"4245b9b\" 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 data-start=\"11374\" data-end=\"11439\">In version 1.0, you could override <code data-start=\"11409\" data-end=\"11425\">GetLineLabel()<\/code> on the panel.<\/p><p data-start=\"11441\" data-end=\"11473\">That still works in version 2.0.<\/p><p data-start=\"11475\" data-end=\"11539\">But version 2.0 also lets you override only the renderer module.<\/p><p data-start=\"11541\" data-end=\"11549\">Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-899510c elementor-widget elementor-widget-code-highlight\" data-id=\"899510c\" 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>class FAxPianoRollKeysRenderer : public FAxLineGridCanvasRenderer\r\n{\r\npublic:\r\n\r\n    virtual FString GetLineLabel(\r\n        int32 LogicalIndex,\r\n        FAxIntersectingRectRenderInfo IntersectingRectRenderInfo) override\r\n    {\r\n        static const TCHAR* PitchNames[] =\r\n        {\r\n            TEXT(\"C\"), TEXT(\"C#\"), TEXT(\"D\"), TEXT(\"D#\"),\r\n            TEXT(\"E\"), TEXT(\"F\"), TEXT(\"F#\"), TEXT(\"G\"),\r\n            TEXT(\"G#\"), TEXT(\"A\"), TEXT(\"A#\"), TEXT(\"B\")\r\n        };\r\n\r\n        if (LogicalIndex < 0 || LogicalIndex > 127)\r\n        {\r\n            return FString::FromInt(LogicalIndex);\r\n        }\r\n\r\n        int32 PitchClass = LogicalIndex % 12;\r\n        int32 Octave = (LogicalIndex \/ 12) - 1;\r\n\r\n        return FString::Printf(TEXT(\"%s%d\"), PitchNames[PitchClass], Octave);\r\n    }\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-e574f97 elementor-widget elementor-widget-text-editor\" data-id=\"e574f97\" 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 data-start=\"12322\" data-end=\"12360\">Then your panel returns that renderer:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b1f1f3e elementor-widget elementor-widget-code-highlight\" data-id=\"b1f1f3e\" 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>TUniquePtr<FAxLineGridCanvasRenderer> UAxPianoRollCanvasPanel::CreateRendererModule() const\r\n{\r\n    return MakeUnique<FAxPianoRollRenderer>();\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-f69bc78 elementor-widget elementor-widget-text-editor\" data-id=\"f69bc78\" 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 data-start=\"12517\" data-end=\"12559\">Now only the label behavior is customized.<\/p><p data-start=\"12561\" data-end=\"12617\">The rest of the grid rendering system remains unchanged.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\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-f69b1f3 e-flex e-con-boxed e-con e-parent\" data-id=\"f69b1f3\" 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-1bd974e elementor-widget elementor-widget-heading\" data-id=\"1bd974e\" 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 Each Module Does<\/h2>\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-2f76a86 e-con-full e-flex e-con e-child\" data-id=\"2f76a86\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a9e6c39 elementor-widget elementor-widget-text-editor\" data-id=\"a9e6c39\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1a68meh\" data-start=\"12649\" data-end=\"12681\">FAxLineGridCanvasGridRegistry<\/h2><p data-start=\"12683\" data-end=\"12731\">This module owns runtime line grid object setup.<\/p><p data-start=\"12733\" data-end=\"12755\">It is responsible for:<\/p><ul data-start=\"12757\" data-end=\"13027\"><li data-section-id=\"tzstu1\" data-start=\"12757\" data-end=\"12797\">Creating runtime <code data-start=\"12776\" data-end=\"12789\">UAxLineGrid<\/code> objects<\/li><li data-section-id=\"7d1i0p\" data-start=\"12798\" data-end=\"12827\">Looking up line grids by ID<\/li><li data-section-id=\"1iylde3\" data-start=\"12828\" data-end=\"12875\">Finding the main line grid for an orientation<\/li><li data-section-id=\"nlzjwp\" data-start=\"12876\" data-end=\"12920\">Setting line spacing on runtime line grids<\/li><li data-section-id=\"vuevhd\" data-start=\"12921\" data-end=\"12988\">Synchronizing runtime line grids from designer-facing <code data-start=\"12977\" data-end=\"12988\">LineGrids<\/code><\/li><li data-section-id=\"bb428r\" data-start=\"12989\" data-end=\"13027\">Rebuilding parent \/ child grid links<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-0c2114d e-con-full e-flex e-con e-child\" data-id=\"0c2114d\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a3c7de6 elementor-widget elementor-widget-text-editor\" data-id=\"a3c7de6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"lj8u6m\" data-start=\"13198\" data-end=\"13228\">FAxLineGridCanvasNavigation<\/h2><p data-start=\"13230\" data-end=\"13269\">This module owns scroll and zoom logic.<\/p><p data-start=\"13271\" data-end=\"13293\">It is responsible for:<\/p><ul data-start=\"13295\" data-end=\"13689\"><li data-section-id=\"1ply7n6\" data-start=\"13295\" data-end=\"13348\">Reading horizontal and vertical navigation settings<\/li><li data-section-id=\"3i7ty9\" data-start=\"13349\" data-end=\"13379\">Resolving the zoom line grid<\/li><li data-section-id=\"c3bhla\" data-start=\"13380\" data-end=\"13417\">Calculating logical units to render<\/li><li data-section-id=\"frivld\" data-start=\"13418\" data-end=\"13456\">Calculating scrollable geometry size<\/li><li data-section-id=\"1alzind\" data-start=\"13457\" data-end=\"13484\">Calculating scroll offset<\/li><li data-section-id=\"ifawiu\" data-start=\"13485\" data-end=\"13538\">Mapping normalized scroll to physical scroll offset<\/li><li data-section-id=\"14kb751\" data-start=\"13539\" data-end=\"13583\">Mapping logical units to normalized scroll<\/li><li data-section-id=\"wulmp4\" data-start=\"13584\" data-end=\"13621\">Applying zoom to the zoom line grid<\/li><li data-section-id=\"1bxv47s\" data-start=\"13622\" data-end=\"13689\">Matching scroll direction to flipped logical units when requested<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-adbb557 e-con-full e-flex e-con e-child\" data-id=\"adbb557\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-446e5f2 elementor-widget elementor-widget-text-editor\" data-id=\"446e5f2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"46ulsp\" data-start=\"13975\" data-end=\"14003\">FAxLineGridCanvasRenderer<\/h2><p data-start=\"14005\" data-end=\"14035\">This module owns grid drawing.<\/p><p data-start=\"14037\" data-end=\"14059\">It is responsible for:<\/p><ul data-start=\"14061\" data-end=\"14357\"><li data-section-id=\"wmh3jy\" data-start=\"14061\" data-end=\"14098\">Sorting line grids by draw priority<\/li><li data-section-id=\"1lurckj\" data-start=\"14099\" data-end=\"14127\">Drawing visible line grids<\/li><li data-section-id=\"zoz759\" data-start=\"14128\" data-end=\"14148\">Drawing grid lines<\/li><li data-section-id=\"ljvatc\" data-start=\"14149\" data-end=\"14169\">Drawing tick lines<\/li><li data-section-id=\"1ev2t40\" data-start=\"14170\" data-end=\"14191\">Drawing text labels<\/li><li data-section-id=\"1quyirk\" data-start=\"14192\" data-end=\"14225\">Drawing alternating backgrounds<\/li><li data-section-id=\"41op97\" data-start=\"14226\" data-end=\"14260\">Drawing last-section backgrounds<\/li><li data-section-id=\"18b3bo9\" data-start=\"14261\" data-end=\"14283\">Drawing border lines<\/li><li data-section-id=\"1f93q2o\" data-start=\"14284\" data-end=\"14357\">Converting scrollable geometry-space draw positions into viewport-space<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-66c8d0e e-con-full e-flex e-con e-child\" data-id=\"66c8d0e\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d915871 elementor-widget elementor-widget-text-editor\" data-id=\"d915871\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"e7rdqs\" data-start=\"14571\" data-end=\"14602\">FAxLineGridCanvasChildLayout<\/h2><p data-start=\"14604\" data-end=\"14641\">This module owns child widget layout.<\/p><p data-start=\"14643\" data-end=\"14665\">It is responsible for:<\/p><ul data-start=\"14667\" data-end=\"14989\"><li data-section-id=\"1lkfqut\" data-start=\"14667\" data-end=\"14708\">Resolving a child\u2019s logical X line grid<\/li><li data-section-id=\"h14qc\" data-start=\"14709\" data-end=\"14750\">Resolving a child\u2019s logical Y line grid<\/li><li data-section-id=\"1gqwe6g\" data-start=\"14751\" data-end=\"14787\">Calculating visible logical ranges<\/li><li data-section-id=\"tj9tg8\" data-start=\"14788\" data-end=\"14824\">Calling <code data-start=\"14798\" data-end=\"14824\">RemoveOutOfViewWidgets()<\/code><\/li><li data-section-id=\"iiv5fp\" data-start=\"14825\" data-end=\"14857\">Calling <code data-start=\"14835\" data-end=\"14857\">SpawnInViewWidgets()<\/code><\/li><li data-section-id=\"1v0h2bk\" data-start=\"14858\" data-end=\"14890\">Updating child widget geometry<\/li><li data-section-id=\"21sxjw\" data-start=\"14891\" data-end=\"14937\">Validating and auto-correcting child anchors<\/li><li data-section-id=\"19732xw\" data-start=\"14938\" data-end=\"14989\">Aligning smart handles with the primary line grid<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-ad87357 e-con-full e-flex e-con e-child\" data-id=\"ad87357\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-fe9c102 elementor-widget elementor-widget-text-editor\" data-id=\"fe9c102\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"13dqyky\" data-start=\"15248\" data-end=\"15273\">FAxLineGridCanvasInput<\/h2><p data-start=\"15275\" data-end=\"15320\">This module owns input coordinate conversion.<\/p><p data-start=\"15322\" data-end=\"15360\">Mouse input arrives in viewport-space.<\/p><p data-start=\"15362\" data-end=\"15440\">The grid system usually needs the mouse position in scrollable geometry-space.<\/p><p data-start=\"15442\" data-end=\"15478\">This module handles that conversion.<\/p><p data-start=\"15480\" data-end=\"15502\">It is responsible for:<\/p><ul data-start=\"15504\" data-end=\"15697\"><li data-section-id=\"1uuy88b\" data-start=\"15504\" data-end=\"15574\">Converting mouse viewport position into scrollable geometry position<\/li><li data-section-id=\"13a0iw0\" data-start=\"15575\" data-end=\"15635\">Clamping positions to the visible scrollable geometry rect<\/li><li data-section-id=\"11rjm1k\" data-start=\"15636\" data-end=\"15697\">Clamping rectangles to the visible scrollable geometry rect<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-c571dfc e-con-full e-flex e-con e-child\" data-id=\"c571dfc\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ce167d6 elementor-widget elementor-widget-text-editor\" data-id=\"ce167d6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"t1udnf\" data-start=\"15898\" data-end=\"15933\">FAxLineGridCanvasEditorValidator<\/h2><p data-start=\"15935\" data-end=\"15975\">This module owns editor-time validation.<\/p><p data-start=\"15977\" data-end=\"16060\">It is responsible for checking that important LineGrid IDs reference valid entries.<\/p><p data-start=\"16062\" data-end=\"16075\">It validates:<\/p><ul data-start=\"16077\" data-end=\"16264\"><li data-section-id=\"1nv68n8\" data-start=\"16077\" data-end=\"16100\">Default child X units<\/li><li data-section-id=\"8wer0l\" data-start=\"16101\" data-end=\"16124\">Default child Y units<\/li><li data-section-id=\"wxco0y\" data-start=\"16125\" data-end=\"16142\">In-view X units<\/li><li data-section-id=\"156dycj\" data-start=\"16143\" data-end=\"16160\">In-view Y units<\/li><li data-section-id=\"qrgvav\" data-start=\"16161\" data-end=\"16188\">Horizontal zoom line grid<\/li><li data-section-id=\"164qwpn\" data-start=\"16189\" data-end=\"16214\">Vertical zoom line grid<\/li><li data-section-id=\"15v8h6o\" data-start=\"16215\" data-end=\"16264\">Orientation matches for enabled navigation axes<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-11693f5 e-con-full e-flex e-con e-child\" data-id=\"11693f5\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-be39988 elementor-widget elementor-widget-text-editor\" data-id=\"be39988\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"t1udnf\" data-start=\"15898\" data-end=\"15933\">FAxLineGridCanvasMovement<\/h2><p>This module owns default child movement, resizing, snapping, and logical-range clamping.<\/p><p>It is responsible for:<\/p><ul><li>Detecting whether the captured smart child handle should move or resize the child<\/li><li>\u00a0Reading the child widget\u2019s starting logical rect<\/li><li>\u00a0Converting mouse position into logical X and Y values<\/li><li>\u00a0Applying movement or resizing in logical space<\/li><li>\u00a0Snapping movement per axis<\/li><li>\u00a0Snapping resizing per axis<\/li><li>\u00a0Clamping moved children to logical ranges<\/li><li>\u00a0Enforcing minimum logical resize sizes<\/li><li>\u00a0Applying the resulting logical rect through the panel backend hooks<\/li><\/ul><p>This module exists so basic grid interaction works out of the box.<\/p><p>Specialized widgets can still replace it by overriding:<\/p><p>CreateMovementModule()<\/p>\t\t\t\t\t\t\t\t<\/div>\n\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-ba5cc44 e-flex e-con-boxed e-con e-parent\" data-id=\"ba5cc44\" 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-ec97f14 elementor-widget elementor-widget-heading\" data-id=\"ec97f14\" 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\">Child Widget Movement Backend Synchronization<\/h2>\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-923b84b e-con-full e-flex e-con e-child\" data-id=\"923b84b\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-85468c5 elementor-widget elementor-widget-text-editor\" data-id=\"85468c5\" 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 movement module updates the child widget\u2019s logical rect by default.<\/p><p>\u00a0<\/p><p>However, many grid-based editors are data-backed.<\/p><p>For example:<\/p><p>A piano roll note widget represents a MidiNote.<br \/>A timeline clip widget represents a clip object.<br \/>A sequencer item widget represents a section or key range.<\/p><p>In those cases, the widget should usually be treated as a view over backend data.<\/p><p>The panel provides movement hooks for that.<\/p><p>Use:<\/p><p><strong>ApplyLineGridChildLogicalRect(&#8230;)<br \/><br \/><\/strong>when the logical rect changes during movement or resizing.<\/p><p>Use:<\/p><p><strong>FinishLineGridChildMovement(&#8230;)<\/strong><\/p><p>when the mouse interaction ends.<\/p><p>The default implementation can update the child widget directly, but data-backed panels can override these functions to update their own backend objects instead.<\/p><p>Simplified example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f104418 elementor-widget elementor-widget-code-highlight\" data-id=\"f104418\" 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 UMyTimelinePanel::ApplyLineGridChildLogicalRect_Implementation(\n        UAxLineGridChildWidget* InLineGridChild,\n        FAx2DRect InPreviousLogicalRect,\n        FAx2DRect InNewLogicalRect,\n        EAxSmartWidgetHandleType InHandleType)\n    {\n        UMyClipWidget* ClipWidget = Cast<UMyClipWidget>(InLineGridChild);\n\n        if (!ClipWidget)\n        {\n            return;\n        }\n\n        UMyClipData* ClipData = ClipWidget->GetClipData();\n\n        if (!ClipData)\n        {\n            return;\n        }\n\n        ClipData->StartTime = InNewLogicalRect.X0;\n        ClipData->EndTime = InNewLogicalRect.X1;\n        ClipData->TrackIndex = InNewLogicalRect.Y0;\n\n        Super::ApplyLineGridChildLogicalRect_Implementation(\n            InLineGridChild,\n            InPreviousLogicalRect,\n            InNewLogicalRect,\n            InHandleType);\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-6b48bd1 elementor-widget elementor-widget-text-editor\" data-id=\"6b48bd1\" 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 data-start=\"11538\" data-end=\"11573\">If the backend is authoritative, the child widget can also override its logical getters and return values directly from the backend object.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\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-399459f e-flex e-con-boxed e-con e-parent\" data-id=\"399459f\" 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-e9aa074 elementor-widget elementor-widget-heading\" data-id=\"e9aa074\" 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\">Spawning And Removing Widgets<\/h2>\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-a33b89b e-con-full e-flex e-con e-child\" data-id=\"a33b89b\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-262db6d elementor-widget elementor-widget-text-editor\" data-id=\"262db6d\" 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 data-start=\"11380\" data-end=\"11462\">Before rendering the grid, the panel calculates the current visible logical range.<\/p><p data-start=\"11464\" data-end=\"11478\">Then it calls:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-98cab87 elementor-widget elementor-widget-code-highlight\" data-id=\"98cab87\" 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>RemoveOutOfViewWidgets()\r\nSpawnInViewWidgets()<\/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-4b6755c elementor-widget elementor-widget-text-editor\" data-id=\"4b6755c\" 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 data-start=\"11538\" data-end=\"11573\">The base implementations are empty.<\/p><p data-start=\"11575\" data-end=\"11608\">Derived panels decide what to do.<\/p><p data-start=\"11610\" data-end=\"11619\">Examples:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5526e64 elementor-widget elementor-widget-code-highlight\" data-id=\"5526e64\" 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>Piano roll:\r\n    Spawn only notes that should be visible at that time.\r\n\r\nTimeline:\r\n    Spawn visible clips.\r\n\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>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-18458c2 e-flex e-con-boxed e-con e-parent\" data-id=\"18458c2\" 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-d16afaa elementor-widget elementor-widget-heading\" data-id=\"d16afaa\" 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 Panel<\/h2>\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-f7715c8 e-con-full e-flex e-con e-child\" data-id=\"f7715c8\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-900d1cd elementor-widget elementor-widget-text-editor\" data-id=\"900d1cd\" 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 data-start=\"31122\" data-end=\"31203\">You usually extend <code data-start=\"31141\" data-end=\"31165\">UAxLineGridCanvasPanel<\/code> when you need custom behavior around:<\/p><ul data-start=\"31205\" data-end=\"31439\"><li data-section-id=\"18558yx\" data-start=\"31205\" data-end=\"31240\">What appears in the visible range<\/li><li data-section-id=\"m0tczv\" data-start=\"31241\" data-end=\"31266\">How widgets are spawned<\/li><li data-section-id=\"165jmb6\" data-start=\"31267\" data-end=\"31293\">How widgets are recycled<\/li><li data-section-id=\"1pyyj6e\" data-start=\"31294\" data-end=\"31316\">How labels are shown<\/li><li data-section-id=\"1igxnae\" data-start=\"31317\" data-end=\"31338\">How ticks are drawn<\/li><li data-section-id=\"czxw4d\" data-start=\"31339\" data-end=\"31366\">How backgrounds are drawn<\/li><li data-section-id=\"qgh45v\" data-start=\"31367\" data-end=\"31404\">How mouse positions are interpreted<\/li><li data-section-id=\"7emcep\" data-start=\"31405\" data-end=\"31439\">How scrolling or zooming behaves<\/li><\/ul><p data-start=\"31441\" data-end=\"31484\">Version 2.0 gives you two extension styles.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-2a3858e e-con-full e-flex e-con e-child\" data-id=\"2a3858e\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-52ca29a elementor-widget elementor-widget-text-editor\" data-id=\"52ca29a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"5azhwp\" data-start=\"31486\" data-end=\"31516\">1. Override Panel Functions<\/h2><p data-start=\"31518\" data-end=\"31542\">This is still supported.<\/p><p data-start=\"31544\" data-end=\"31553\">Examples:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b0e344e elementor-widget elementor-widget-code-highlight\" data-id=\"b0e344e\" 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>RemoveOutOfViewWidgets()\r\nSpawnInViewWidgets()\r\nGetLineLabel()\r\nRenderLineLabel()\r\nRenderLineTick()\r\nUpdateChildWidgetGeometry()<\/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-9b08c64 elementor-widget elementor-widget-text-editor\" data-id=\"9b08c64\" 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 data-start=\"31691\" data-end=\"31764\">Use this when the custom behavior is small or directly tied to the panel.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-b163bde e-con-full e-flex e-con e-child\" data-id=\"b163bde\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-754a097 elementor-widget elementor-widget-text-editor\" data-id=\"754a097\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"xr6esv\" data-start=\"31766\" data-end=\"31795\">2. Replace A Native Module<\/h2><p data-start=\"31797\" data-end=\"31856\">Use this when the custom behavior belongs to one subsystem.<\/p><p data-start=\"31858\" data-end=\"31867\">Examples:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-287a8e6 elementor-widget elementor-widget-code-highlight\" data-id=\"287a8e6\" 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>Custom renderer      -> FAxLineGridCanvasRenderer\r\nCustom navigation    -> FAxLineGridCanvasNavigation\r\nCustom child layout  -> FAxLineGridCanvasChildLayout\r\nCustom input mapping -> FAxLineGridCanvasInput<\/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-fc94265 elementor-widget elementor-widget-text-editor\" data-id=\"fc94265\" 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 data-start=\"32083\" data-end=\"32157\">This is the cleaner option when you are building specialized and complex widgets where there is a lot to do like:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-44f11a2 elementor-widget elementor-widget-code-highlight\" data-id=\"44f11a2\" 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>UAxPianoRollCanvasPanel\r\nUAxTimelineCanvasPanel\r\nUAxSequencerCanvasPanel\r\nUAxTrackEditorCanvasPanel<\/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>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-79efe1d e-flex e-con-boxed e-con e-parent\" data-id=\"79efe1d\" 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-67fcabb elementor-widget elementor-widget-heading\" data-id=\"67fcabb\" 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\">Where Scrolling And Zooming Fit<\/h2>\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-cb90022 e-con-full e-flex e-con e-child\" data-id=\"cb90022\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a3727b8 elementor-widget elementor-widget-text-editor\" data-id=\"a3727b8\" 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 data-start=\"32308\" data-end=\"32366\">Version 2.0 has built-in per-axis scroll and zoom support.<\/p><p data-start=\"32368\" data-end=\"32470\">The panel no longer needs to be physically moved or resized by a wrapper to represent a larger canvas.<\/p><p data-start=\"32472\" data-end=\"32506\">Instead, navigation works through:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-889c837 elementor-widget elementor-widget-code-highlight\" data-id=\"889c837\" 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>LogicalUnitsToRender\r\nLineSpacing\r\nScrollableGeometrySize\r\nCurrentScrollValue\r\nCurrentZoomValue\r\nScrollOffset<\/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-2a9d58f elementor-widget elementor-widget-text-editor\" data-id=\"2a9d58f\" 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 data-start=\"32625\" data-end=\"32643\">The usual flow is:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f1937fb elementor-widget elementor-widget-code-highlight\" data-id=\"f1937fb\" 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>Set normalized zoom\r\n        \u2193\r\nZoom module updates LineSpacing on the zoom LineGrid\r\n        \u2193\r\nScrollableGeometrySize changes\r\n        \u2193\r\nSet normalized scroll\r\n        \u2193\r\nNavigation module calculates ScrollOffset\r\n        \u2193\r\nRenderer draws visible scrollable geometry rect\r\n        \u2193\r\nDraw calls subtract ScrollOffset<\/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-eb1325d elementor-widget elementor-widget-text-editor\" data-id=\"eb1325d\" 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 data-start=\"32966\" data-end=\"33040\">A higher-level scroll\/zoom widget may still control the normalized values by simply calling zoom and scroll functions.<\/p><p data-start=\"33042\" data-end=\"33107\">But the line grid canvas itself owns the internal viewport model.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\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-226719f e-flex e-con-boxed e-con e-parent\" data-id=\"226719f\" 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-b7f6ab5 elementor-widget elementor-widget-heading\" data-id=\"b7f6ab5\" 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 To Use The System<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-be54bc9 elementor-widget elementor-widget-menu-anchor\" data-id=\"be54bc9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"menu-anchor.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-menu-anchor\" id=\"HowToUseTheSystem\"><\/div>\n\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6e3987a e-con-full e-flex e-con e-child\" data-id=\"6e3987a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a4ce261 elementor-widget elementor-widget-text-editor\" data-id=\"a4ce261\" 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 data-start=\"33138\" data-end=\"33195\">If you are setting up a new version 2.0 grid, start here.<\/p><h2 data-section-id=\"m9jfqb\" data-start=\"33197\" data-end=\"33224\">1. Define the line grids<\/h2><p data-start=\"33226\" data-end=\"33282\">Create line grid entries for the logical units you need.<\/p><p data-start=\"33284\" data-end=\"33293\">Examples:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-41eee9f elementor-widget elementor-widget-code-highlight\" data-id=\"41eee9f\" 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>Bars\r\nBeats\r\nTracks\r\nPitch\r\nSeconds\r\nFrames<\/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>\n\t\t<div class=\"elementor-element elementor-element-c0ff21d e-con-full e-flex e-con e-child\" data-id=\"c0ff21d\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7fe5724 elementor-widget elementor-widget-text-editor\" data-id=\"7fe5724\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1o2iqfu\" data-start=\"33346\" data-end=\"33377\">2. Set each grid orientation<\/h2><p data-start=\"33379\" data-end=\"33435\">Use horizontal orientation for units that grow across X.<\/p><p data-start=\"33437\" data-end=\"33491\">Use vertical orientation for units that grow across Y.<\/p><p data-start=\"33493\" data-end=\"33512\">Example piano roll:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0e582f5 elementor-widget elementor-widget-code-highlight\" data-id=\"0e582f5\" 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>Beats -> Horizontal\r\nPitch -> Vertical<\/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-f93caed elementor-widget elementor-widget-text-editor\" data-id=\"f93caed\" 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 data-start=\"33564\" data-end=\"33581\">Example timeline:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-53d804d elementor-widget elementor-widget-code-highlight\" data-id=\"53d804d\" 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>Seconds -> Horizontal\r\nTracks  -> Vertical<\/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>\n\t\t<div class=\"elementor-element elementor-element-1b010fe e-con-full e-flex e-con e-child\" data-id=\"1b010fe\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-229c5ab elementor-widget elementor-widget-text-editor\" data-id=\"229c5ab\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1t1m9qt\" data-start=\"33637\" data-end=\"33659\">3. Set line spacing<\/h2><p data-start=\"33661\" data-end=\"33702\">This controls how dense the grid appears. This is just for the starting or initial size during design. At runtime, this value is controlled by zooming unless the grid is not meant to zoom at all, then it remains static.<\/p><p data-start=\"33752\" data-end=\"33760\">Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2e965c3 elementor-widget elementor-widget-code-highlight\" data-id=\"2e965c3\" 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>LineSpacing = 100<\/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>\n\t\t<div class=\"elementor-element elementor-element-feaa4df e-con-full e-flex e-con e-child\" data-id=\"feaa4df\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e05b53c elementor-widget elementor-widget-text-editor\" data-id=\"e05b53c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1e48f7n\" data-start=\"33792\" data-end=\"33827\">4. Configure default child units<\/h2><p data-start=\"33829\" data-end=\"33833\">Set:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3415d35 elementor-widget elementor-widget-code-highlight\" data-id=\"3415d35\" 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>DefaultLogicalXUnitsID\r\nDefaultLogicalYUnitsID<\/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-126ef80 elementor-widget elementor-widget-text-editor\" data-id=\"126ef80\" 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 data-start=\"33893\" data-end=\"33912\">Example piano roll:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1a1af16 elementor-widget elementor-widget-code-highlight\" data-id=\"1a1af16\" 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>DefaultLogicalXUnitsID = \"Beats\"\r\nDefaultLogicalYUnitsID = \"Pitch\"<\/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>\n\t\t<div class=\"elementor-element elementor-element-0c5e150 e-con-full e-flex e-con e-child\" data-id=\"0c5e150\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1bd8277 elementor-widget elementor-widget-text-editor\" data-id=\"1bd8277\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"3yywhw\" data-start=\"33992\" data-end=\"34021\">5. Configure in-view units<\/h2><p data-start=\"34023\" data-end=\"34027\">Set:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2dbab23 elementor-widget elementor-widget-code-highlight\" data-id=\"2dbab23\" 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>LogicalInViewXUnitsID\r\nLogicalInViewYUnitsID<\/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-f3a777c elementor-widget elementor-widget-text-editor\" data-id=\"f3a777c\" 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 data-start=\"34085\" data-end=\"34162\">These control the logical range used for spawning and removing child widgets. These are passed in during the Spawn() and Remove() functions.<\/p><p data-start=\"34164\" data-end=\"34172\">Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-494f090 elementor-widget elementor-widget-code-highlight\" data-id=\"494f090\" 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>LogicalInViewXUnitsID = \"Beats\"\r\nLogicalInViewYUnitsID = \"Pitch\"<\/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>\n\t\t<div class=\"elementor-element elementor-element-fb802de e-con-full e-flex e-con e-child\" data-id=\"fb802de\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a42d3d1 elementor-widget elementor-widget-text-editor\" data-id=\"a42d3d1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"10gaicf\" data-start=\"34249\" data-end=\"34275\">6. Configure navigation<\/h2><p data-start=\"34277\" data-end=\"34304\">For each enabled axis, set:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-300d24e elementor-widget elementor-widget-code-highlight\" data-id=\"300d24e\" 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>bEnableNavigation\r\nZoomLineGridID\r\nLogicalUnitsToRender\r\nMinLineSpacingForZoom\r\nMaxLineSpacingForZoom<\/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-92ccaff elementor-widget elementor-widget-text-editor\" data-id=\"92ccaff\" 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 data-start=\"34416\" data-end=\"34424\">Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1ddbe48 elementor-widget elementor-widget-code-highlight\" data-id=\"1ddbe48\" 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>HorizontalNavigationProperties.bEnableNavigation = true;\r\nHorizontalNavigationProperties.ZoomLineGridID = \"Bars\";\r\nHorizontalNavigationProperties.LogicalUnitsToRender = 64.0f;<\/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>\n\t\t<div class=\"elementor-element elementor-element-52e42a9 e-con-full e-flex e-con e-child\" data-id=\"52e42a9\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9c40572 elementor-widget elementor-widget-text-editor\" data-id=\"9c40572\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"qwd68n\" data-start=\"34612\" data-end=\"34636\">7. Check flipped axes<\/h2><p data-start=\"8888\" data-end=\"8966\">Some grids need logical values to increase in the opposite physical direction.<\/p><p data-start=\"8968\" data-end=\"8996\">The common example is pitch.<\/p><p data-start=\"8998\" data-end=\"9029\">UMG Y values increase downward.<\/p><p data-start=\"9031\" data-end=\"9062\">Pitch usually increases upward.<\/p><p data-start=\"9064\" data-end=\"9091\">So a pitch grid often uses:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-283ba6c elementor-widget elementor-widget-code-highlight\" data-id=\"283ba6c\" 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>bFlipLogicalUnits =true;<\/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-65e3d8a elementor-widget elementor-widget-text-editor\" data-id=\"65e3d8a\" 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 data-start=\"34752\" data-end=\"34770\">on that line grid.<\/p><p data-start=\"34806\" data-end=\"34819\">Then keep:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f70d1f7 elementor-widget elementor-widget-code-highlight\" data-id=\"f70d1f7\" 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>bMatchScrollDirectionToLogicalUnits = true<\/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>\n\t\t<div class=\"elementor-element elementor-element-88b0451 e-con-full e-flex e-con e-child\" data-id=\"88b0451\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-013465e elementor-widget elementor-widget-text-editor\" data-id=\"013465e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1rdlj0a\" data-start=\"34891\" data-end=\"34924\">8. Set child anchors correctly<\/h2><p data-start=\"34926\" data-end=\"34956\">Line grid children should use:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-70bace1 elementor-widget elementor-widget-code-highlight\" data-id=\"70bace1\" 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>Anchors.Minimum = (0, 0)\r\nAnchors.Maximum = (0, 0)\r\nAlignment       = (0, 0)<\/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-dd9fbb6 elementor-widget elementor-widget-text-editor\" data-id=\"dd9fbb6\" 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 data-start=\"35045\" data-end=\"35105\">Do this in the widget blueprint or in runtime spawning code.<\/p><p data-start=\"35107\" data-end=\"35184\">The panel can auto-correct, but the child should still be authored correctly.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6a47508 e-con-full e-flex e-con e-child\" data-id=\"6a47508\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-db8d00e elementor-widget elementor-widget-text-editor\" data-id=\"db8d00e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1rdlj0a\" data-start=\"34891\" data-end=\"34924\">9. Configure child movement and snapping<\/h2><p>Decide whether child widgets should move, resize, snap, and clamp.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-86feb9e elementor-widget elementor-widget-code-highlight\" data-id=\"86feb9e\" 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>For general movement:\n\nChildMovementProperties.bEnableChildMovement = true;\nChildMovementProperties.bEnableChildResizing = true;\n\nFor horizontal movement:\n\nChildMovementProperties.HorizontalMovementSettings.bEnableMovement = true;\nChildMovementProperties.HorizontalMovementSettings.bSnapMovementToGrid = true;\nChildMovementProperties.HorizontalMovementSettings.SnapLineGridID = \"Beats\";\nChildMovementProperties.HorizontalMovementSettings.SnapStep =1.0;<\/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>\n\t\t<div class=\"elementor-element elementor-element-109ff74 e-con-full e-flex e-con e-child\" data-id=\"109ff74\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a948336 elementor-widget elementor-widget-text-editor\" data-id=\"a948336\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<h2 data-section-id=\"1ma9cmf\" data-start=\"35186\" data-end=\"35219\">10. Decide your extension style<\/h2><p data-start=\"35221\" data-end=\"35267\">For simple behavior, override panel functions.<\/p><p data-start=\"35269\" data-end=\"35326\">For complex subsystem-specific behavior, replace a native module.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4d4a4c6 elementor-widget elementor-widget-code-highlight\" data-id=\"4d4a4c6\" 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>Custom labels only?\r\n    Replace renderer module.\r\n\r\nCustom scroll rules?\r\n    Replace navigation module.\r\n\r\nCustom child placement?\r\n    Replace child layout module.<\/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-9c28c1d elementor-widget elementor-widget-text-editor\" data-id=\"9c28c1d\" 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 data-start=\"35045\" data-end=\"35105\">Do this in the widget blueprint or in runtime spawning code.<\/p><p data-start=\"35107\" data-end=\"35184\">The panel can auto-correct, but the child should still be authored correctly.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\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-af77a85 e-flex e-con-boxed e-con e-parent\" data-id=\"af77a85\" 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-58c3e57 elementor-widget elementor-widget-heading\" data-id=\"58c3e57\" 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\">Debugging Checklist<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a516b3f elementor-widget elementor-widget-menu-anchor\" data-id=\"a516b3f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"menu-anchor.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-menu-anchor\" id=\"DebuggingChecklist\"><\/div>\n\t\t\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-9e13ddd e-con-full e-flex e-con e-child\" data-id=\"9e13ddd\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6f693b0 elementor-widget elementor-widget-text-editor\" data-id=\"6f693b0\" 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 data-start=\"33138\" data-end=\"33195\">If the grid does not render correctly, check:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-49aa2f4 elementor-widget elementor-widget-code-highlight\" data-id=\"49aa2f4\" 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>LineGrids has valid entries.\r\nLineGrid IDs are unique.\r\nZoomLineGridID references a valid LineGrid.\r\nZoomLineGridID orientation matches the navigation axis.\r\nLineSpacing is greater than 0.\r\nLogicalUnitsToRender is greater than 0.\r\nbEnableNavigation is set correctly for the axis.<\/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>\n\t\t<div class=\"elementor-element elementor-element-e99d299 e-con-full e-flex e-con e-child\" data-id=\"e99d299\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-13630df elementor-widget elementor-widget-text-editor\" data-id=\"13630df\" 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 data-start=\"37461\" data-end=\"37509\">If child widgets do not appear correctly, check:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-360d1be elementor-widget elementor-widget-code-highlight\" data-id=\"360d1be\" 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>Child is a UAxLineGridChildWidget.\r\nLogicalXUnitsID resolves to a valid line grid.\r\nLogicalYUnitsID resolves to a valid line grid.\r\nDefaultLogicalXUnitsID is valid.\r\nDefaultLogicalYUnitsID is valid.\r\nChild anchors are top-left.\r\nChild alignment is top-left.<\/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>\n\t\t<div class=\"elementor-element elementor-element-5f4abcf e-con-full e-flex e-con e-child\" data-id=\"5f4abcf\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-cde7669 elementor-widget elementor-widget-text-editor\" data-id=\"cde7669\" 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 data-start=\"37775\" data-end=\"37811\">If scrolling feels backwards, check:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-51eaa53 elementor-widget elementor-widget-code-highlight\" data-id=\"51eaa53\" 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>bFlipLogicalUnits on the zoom LineGrid.\r\nbMatchScrollDirectionToLogicalUnits on the navigation axis.\r\nbFlipScrolling only if manual override is needed.<\/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>\n\t\t<div class=\"elementor-element elementor-element-cdee9e7 e-con-full e-flex e-con e-child\" data-id=\"cdee9e7\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-47a32ea elementor-widget elementor-widget-text-editor\" data-id=\"47a32ea\" 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 data-start=\"37975\" data-end=\"38002\">If labels are wrong, check:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2498089 elementor-widget elementor-widget-code-highlight\" data-id=\"2498089\" 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>GetLineLabel()\r\nFAxLineGridCanvasRenderer::GetLineLabel()\r\nCreateRendererModule()<\/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>\n\t\t<div class=\"elementor-element elementor-element-0961755 e-con-full e-flex e-con e-child\" data-id=\"0961755\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-82b85e3 elementor-widget elementor-widget-text-editor\" data-id=\"82b85e3\" 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 data-start=\"38096\" data-end=\"38131\">If rendering order is wrong, check:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5190ba7 elementor-widget elementor-widget-code-highlight\" data-id=\"5190ba7\" 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>DrawPriority\r\nRenderLineGrids()\r\nRenderCurrentLine()\r\nRenderLineLabel()\r\nRenderLineTick()\r\nRenderLineBackground()\r\n\r\nEspecially the increment or decrement of rendering layer. <\/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>\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":[129,132],"tags":[136],"class_list":["post-13812","documentation","type-documentation","status-publish","hentry","category-unreal-engine","category-enhanced-ui","tag-userinterface","entry"],"acf":[],"_links":{"self":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation\/13812","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\/13812\/revisions"}],"predecessor-version":[{"id":13931,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation\/13812\/revisions\/13931"}],"wp:attachment":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/media?parent=13812"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/categories?post=13812"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/tags?post=13812"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}