{"id":13679,"date":"2026-05-06T16:42:55","date_gmt":"2026-05-06T14:42:55","guid":{"rendered":"https:\/\/store.algosyntax.com\/?post_type=documentation&#038;p=13679"},"modified":"2026-05-08T07:59:14","modified_gmt":"2026-05-08T05:59:14","slug":"linegridcanvaspanel-creating-grids-timelines-and-piano-rolls","status":"publish","type":"documentation","link":"https:\/\/store.algosyntax.com\/documentation\/linegridcanvaspanel-creating-grids-timelines-and-piano-rolls\/","title":{"rendered":"LineGridCanvasPanel: Creating Grids, Timelines And Piano Rolls"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"13679\" class=\"elementor elementor-13679\" data-elementor-post-type=\"documentation\">\n\t\t\t\t<div class=\"elementor-element elementor-element-90eee4c e-flex e-con-boxed e-con e-parent\" data-id=\"90eee4c\" 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-b47b0df elementor-widget elementor-widget-heading\" data-id=\"b47b0df\" 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: Creating Grids, Timelines And Piano Rolls<\/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-9d8987c e-flex e-con-boxed e-con e-parent\" data-id=\"9d8987c\" 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-3f6398d elementor-widget elementor-widget-text-editor\" data-id=\"3f6398d\" 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=\"1122\" data-end=\"1141\">What this covers<\/h2><ol data-start=\"1143\" data-end=\"1485\"><li data-section-id=\"1st8krd\" data-start=\"1143\" data-end=\"1181\">What <code data-start=\"1151\" data-end=\"1172\">LineGridCanvasPanel<\/code> is for<\/li><li data-section-id=\"yj6i8n\" data-start=\"1182\" data-end=\"1211\">The core building blocks<\/li><li data-section-id=\"1wsq07k\" data-start=\"1212\" data-end=\"1244\">How line grids are rendered<\/li><li data-section-id=\"aq68jk\" data-start=\"1245\" data-end=\"1282\">How visible-only rendering works<\/li><li data-section-id=\"5fd2et\" data-start=\"1283\" data-end=\"1332\">How child widgets are positioned on the grid<\/li><li data-section-id=\"akf9nv\" data-start=\"1333\" data-end=\"1387\">What <code data-start=\"1341\" data-end=\"1362\">LineGridChildWidget<\/code> is expected to provide<\/li><li data-section-id=\"1ert98z\" data-start=\"1388\" data-end=\"1432\">What to consider when a grid is flipped<\/li><li data-section-id=\"1od06pg\" data-start=\"1433\" data-end=\"1485\">Where scrolling and zooming fit into the system<\/li><\/ol>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-299dc39 elementor-widget elementor-widget-button\" data-id=\"299dc39\" 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-2f748ca e-flex e-con-boxed e-con e-parent\" data-id=\"2f748ca\" 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-951eebc elementor-widget elementor-widget-heading\" data-id=\"951eebc\" 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-02bc1ac elementor-widget elementor-widget-text-editor\" data-id=\"02bc1ac\" 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=\"1523\" data-end=\"1614\"><code data-start=\"1523\" data-end=\"1547\">UAxLineGridCanvasPanel<\/code> is used when a UI needs to display content against a logical grid.<\/p><p data-start=\"1616\" data-end=\"1633\">Examples include:<\/p><ul data-start=\"1635\" data-end=\"1755\"><li data-section-id=\"1lg8rzb\" data-start=\"1635\" data-end=\"1646\">Timelines<\/li><li data-section-id=\"10r8jeg\" data-start=\"1647\" data-end=\"1660\">Piano rolls<\/li><li data-section-id=\"1o0paup\" data-start=\"1661\" data-end=\"1673\">Sequencers<\/li><li data-section-id=\"y3nu4y\" data-start=\"1674\" data-end=\"1689\">Track editors<\/li><li data-section-id=\"1xunhrg\" data-start=\"1690\" data-end=\"1709\">Bar \/ beat rulers<\/li><li data-section-id=\"mios1g\" data-start=\"1710\" data-end=\"1730\">Time-based editors<\/li><li data-section-id=\"rj60fe\" data-start=\"1731\" data-end=\"1755\">Row-and-column editors<\/li><\/ul><p data-start=\"1757\" data-end=\"1792\">The important problem it solves is:<\/p><blockquote data-start=\"1794\" data-end=\"1966\"><p data-start=\"1796\" data-end=\"1966\">How to draw a large logical grid while only rendering the part that is currently visible, and how to place widgets on that grid using logical units instead of raw pixels.<\/p><\/blockquote><p data-start=\"1968\" data-end=\"2032\">A normal canvas places widgets using physical position and size.<\/p><p data-start=\"2034\" data-end=\"2090\">A line grid canvas places widgets using logical meaning.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5e9bea9 elementor-widget elementor-widget-text-editor\" data-id=\"5e9bea9\" 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\">For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c7674d2 elementor-widget elementor-widget-code-highlight\" data-id=\"c7674d2\" 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:\nX0 = Beat 4, X1 = Beat 8\ny0 = Midi Note 60 , y1 = Midi Note 61\n\nFor Timeline\/Sequencer:\nX0 = 12.5 seconds, X1= 18.0 seconds\nY0 = Track2 , Y1 = Track3<\/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-db69a93 elementor-widget elementor-widget-text-editor\" data-id=\"db69a93\" 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-7cd8ec4 elementor-widget elementor-widget-text-editor\" data-id=\"7cd8ec4\" 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\">What To Adjust First 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-d9ddf5e elementor-widget elementor-widget-button\" data-id=\"d9ddf5e\" 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=\"#WhatToAdjustFirst\">\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\">What to Adjust First 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-435c488 e-flex e-con-boxed e-con e-parent\" data-id=\"435c488\" 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-0b6b55b elementor-widget elementor-widget-heading\" data-id=\"0b6b55b\" 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-f908a56 elementor-widget elementor-widget-text-editor\" data-id=\"f908a56\" 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=\"2340\" data-end=\"2368\">There are three main pieces:<\/p><h3 data-section-id=\"apr3v8\" data-start=\"2370\" data-end=\"2423\"><span role=\"text\">1. LineGridCanvasPanel (<code data-start=\"2398\" data-end=\"2422\">UAxLineGridCanvasPanel<\/code>)<\/span><\/h3><p data-start=\"2425\" data-end=\"2447\">This is the UMG panel.<\/p><p data-start=\"2449\" data-end=\"2471\">It is responsible for:<\/p><ul data-start=\"2473\" data-end=\"2825\"><li data-section-id=\"ji60b4\" data-start=\"2473\" data-end=\"2507\">Owning the line grid definitions<\/li><li data-section-id=\"12y9q3y\" data-start=\"2508\" data-end=\"2548\">Creating runtime <code data-start=\"2527\" data-end=\"2540\">UAxLineGrid<\/code> objects<\/li><li data-section-id=\"hn6gt3\" data-start=\"2549\" data-end=\"2604\">Updating those runtime objects when properties change<\/li><li data-section-id=\"1ljweez\" data-start=\"2605\" data-end=\"2646\">Rendering the visible part of each grid<\/li><li data-section-id=\"16wtmd\" data-start=\"2647\" data-end=\"2700\">Resolving which logical units are currently in view<\/li><li data-section-id=\"147408m\" data-start=\"2701\" data-end=\"2739\">Positioning grid-aware child widgets<\/li><li data-section-id=\"1v2euw\" data-start=\"2740\" data-end=\"2825\">Giving derived classes hooks to spawn and remove widgets based on the visible range<br \/><br \/><\/li><\/ul><p>This class extends <code data-start=\"2846\" data-end=\"2867\">UAxSmartCanvasPanel<\/code>, so it still participates in the Smart Canvas model, but it adds line-grid rendering and logical child positioning.<br \/><br \/><\/p><h3 data-section-id=\"15z6dh9\" data-start=\"2985\" data-end=\"3010\">Where to look in code<\/h3><ul data-start=\"3012\" data-end=\"3231\"><li data-section-id=\"xvgl4y\" data-start=\"3012\" data-end=\"3060\">Main panel class:<ul data-start=\"3034\" data-end=\"3060\"><li data-section-id=\"1ipgs2z\" data-start=\"3034\" data-end=\"3060\"><code data-start=\"3036\" data-end=\"3060\">UAxLineGridCanvasPanel<\/code><\/li><\/ul><\/li><li data-section-id=\"pwhyxp\" data-start=\"3061\" data-end=\"3122\">Runtime grid storage:<ul data-start=\"3087\" data-end=\"3122\"><li data-section-id=\"6fpfai\" data-start=\"3087\" data-end=\"3100\"><code data-start=\"3089\" data-end=\"3100\">LineGrids<\/code><\/li><li data-section-id=\"1rrmm73\" data-start=\"3103\" data-end=\"3122\"><code data-start=\"3105\" data-end=\"3122\">LineGridObjects<\/code><\/li><\/ul><\/li><li data-section-id=\"hfjn3g\" data-start=\"3123\" data-end=\"3231\">Runtime setup:<ul data-start=\"3142\" data-end=\"3231\"><li data-section-id=\"hitl7p\" data-start=\"3142\" data-end=\"3169\"><code data-start=\"3144\" data-end=\"3169\">SynchronizeProperties()<\/code><\/li><li data-section-id=\"1a98h77\" data-start=\"3172\" data-end=\"3205\"><code data-start=\"3174\" data-end=\"3205\">MakeAndInsertLineGridObject()<\/code><\/li><li data-section-id=\"9nmykb\" data-start=\"3208\" data-end=\"3231\"><code data-start=\"3210\" data-end=\"3231\">GetLineGridObject()<\/code><\/li><\/ul><\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2057fda elementor-widget elementor-widget-text-editor\" data-id=\"2057fda\" 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=\"1i113ro\" data-start=\"3233\" data-end=\"3263\"><span role=\"text\">2. LineGrid (<code data-start=\"3249\" data-end=\"3262\">UAxLineGrid<\/code>)<\/span><\/h2><p data-start=\"3265\" data-end=\"3320\">A <code data-start=\"3267\" data-end=\"3280\">UAxLineGrid<\/code> represents one group of repeated lines.<\/p><p data-start=\"3322\" data-end=\"3348\">A line grid can represent:<\/p><ul data-start=\"3350\" data-end=\"3436\"><li data-section-id=\"8xc8su\" data-start=\"3350\" data-end=\"3357\">Beats<\/li><li data-section-id=\"1mdx8vh\" data-start=\"3358\" data-end=\"3364\">Bars<\/li><li data-section-id=\"1b6ndgs\" data-start=\"3365\" data-end=\"3374\">Seconds<\/li><li data-section-id=\"8c1h9d\" data-start=\"3375\" data-end=\"3383\">Frames<\/li><li data-section-id=\"grqlv7\" data-start=\"3384\" data-end=\"3392\">Tracks<\/li><li data-section-id=\"8pcd9o\" data-start=\"3393\" data-end=\"3400\">Notes<\/li><li data-section-id=\"1hpcxoe\" data-start=\"3401\" data-end=\"3436\">Any other logical unit you define<\/li><\/ul><p data-start=\"3438\" data-end=\"3552\">A line grid has an ID. That ID matters because child widgets use it to say what units their X and Y values are in.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a8bf6a0 elementor-widget elementor-widget-text-editor\" data-id=\"a8bf6a0\" 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=\"3621\" data-end=\"3647\">A line grid also controls:<\/p><ul data-start=\"3649\" data-end=\"3829\"><li data-section-id=\"1dr301l\" data-start=\"3649\" data-end=\"3662\">Orientation<\/li><li data-section-id=\"1aizp6o\" data-start=\"3663\" data-end=\"3677\">Line spacing<\/li><li data-section-id=\"1kyrk0s\" data-start=\"3678\" data-end=\"3713\">Whether logical units are flipped<\/li><li data-section-id=\"a1txb5\" data-start=\"3714\" data-end=\"3730\">Logical offset<\/li><li data-section-id=\"1bt67gh\" data-start=\"3731\" data-end=\"3745\">Line drawing<\/li><li data-section-id=\"63t3lz\" data-start=\"3746\" data-end=\"3758\">Tick lines<\/li><li data-section-id=\"1u44op3\" data-start=\"3759\" data-end=\"3772\">Text labels<\/li><li data-section-id=\"1geilcx\" data-start=\"3773\" data-end=\"3793\">Background drawing<\/li><li data-section-id=\"1hy4rax\" data-start=\"3794\" data-end=\"3829\">Parent \/ child grid relationships<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7099d79 elementor-widget elementor-widget-text-editor\" data-id=\"7099d79\" 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=\"1e01hxa\" data-start=\"3831\" data-end=\"3862\">Parent and child line grids<\/h3><p data-start=\"3864\" data-end=\"3912\">A line grid can be a child of another line grid.<\/p><p data-start=\"3914\" data-end=\"3926\">For example:<\/p><ul data-start=\"3928\" data-end=\"4035\"><li data-section-id=\"16kr6hn\" data-start=\"3928\" data-end=\"3957\">Bars can be the parent grid<\/li><li data-section-id=\"1p1c8ae\" data-start=\"3958\" data-end=\"3985\">Beats can be a child grid<\/li><li data-section-id=\"ry62tt\" data-start=\"3986\" data-end=\"4035\">Beat spacing can be calculated from bar spacing<\/li><\/ul><p data-start=\"4037\" data-end=\"4183\">This is useful during zooming because you usually only want to change the parent grid\u2019s line spacing. Child grids can then update from the parent.<br \/><br \/><\/p><h3 data-section-id=\"15z6dh9\" data-start=\"4185\" data-end=\"4210\">Where to look in code<\/h3><ul data-start=\"4212\" data-end=\"4557\"><li data-section-id=\"bsqdfm\" data-start=\"4212\" data-end=\"4244\">Grid object:<ul data-start=\"4229\" data-end=\"4244\"><li data-section-id=\"1pd8g8l\" data-start=\"4229\" data-end=\"4244\"><code data-start=\"4231\" data-end=\"4244\">UAxLineGrid<\/code><\/li><\/ul><\/li><li data-section-id=\"kz41r8\" data-start=\"4245\" data-end=\"4291\">Grid properties:<ul data-start=\"4266\" data-end=\"4291\"><li data-section-id=\"13fbhd3\" data-start=\"4266\" data-end=\"4291\"><code data-start=\"4268\" data-end=\"4291\">FAxLineGridProperties<\/code><\/li><\/ul><\/li><li data-section-id=\"1idx0oz\" data-start=\"4292\" data-end=\"4415\">Parent \/ child grid functions:<ul data-start=\"4327\" data-end=\"4415\"><li data-section-id=\"7x9nsj\" data-start=\"4327\" data-end=\"4349\"><code data-start=\"4329\" data-end=\"4349\">AddChildLineGrid()<\/code><\/li><li data-section-id=\"h3wimw\" data-start=\"4352\" data-end=\"4377\"><code data-start=\"4354\" data-end=\"4377\">ClearChildLineGrids()<\/code><\/li><li data-section-id=\"zdscf6\" data-start=\"4380\" data-end=\"4415\"><code data-start=\"4382\" data-end=\"4415\">UpdateChildLineGridProperties()<\/code><\/li><\/ul><\/li><li data-section-id=\"1ikyowp\" data-start=\"4416\" data-end=\"4557\">Position conversion:<ul data-start=\"4441\" data-end=\"4557\"><li data-section-id=\"2715ig\" data-start=\"4441\" data-end=\"4483\"><code data-start=\"4443\" data-end=\"4483\">GetGeometryPositionOfLogicalPosition()<\/code><\/li><li data-section-id=\"s53zyr\" data-start=\"4486\" data-end=\"4525\"><code data-start=\"4488\" data-end=\"4525\">GetLogicalIndexAtGeometryPosition()<\/code><\/li><li data-section-id=\"12uateh\" data-start=\"4528\" data-end=\"4557\"><code data-start=\"4530\" data-end=\"4557\">GetLogicalIndexesInView()<\/code><\/li><\/ul><\/li><\/ul>\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-f9672d7 e-flex e-con-boxed e-con e-parent\" data-id=\"f9672d7\" 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-eb0d1f2 elementor-widget elementor-widget-heading\" data-id=\"eb0d1f2\" 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\">Rendering Model<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-11856a3 elementor-widget elementor-widget-text-editor\" data-id=\"11856a3\" 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=\"5228\" data-end=\"5288\">The line grid is not rendered as one giant prebuilt texture.<\/p><p data-start=\"5290\" data-end=\"5333\">Instead, it is drawn during the paint pass.<\/p><p data-start=\"5335\" data-end=\"5358\">The simplified flow is:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1e0055d elementor-widget elementor-widget-code-highlight\" data-id=\"1e0055d\" 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>Slate paint starts\r\n        \u2193\r\nVisible local rect is calculated\r\n        \u2193\r\nPanel resolves logical units in view\r\n        \u2193\r\nDerived classes may remove out-of-view widgets\r\n        \u2193\r\nDerived classes may spawn in-view widgets\r\n        \u2193\r\nChild widget geometry is updated\r\n        \u2193\r\nVisible grid lines, backgrounds, labels, and ticks are drawn<\/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-b4901d4 elementor-widget elementor-widget-text-editor\" data-id=\"b4901d4\" 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=\"5705\" data-end=\"5799\">This is very important for performance because the panel can be larger than the area currently visible to the user.<\/p><p data-start=\"5801\" data-end=\"5899\">For example, a timeline might be 80,000 pixels wide, but the user may only see a 1,200 pixel area.<\/p><p data-start=\"5901\" data-end=\"6055\">The line grid canvas does not need to draw all 80,000 pixels worth of grid lines. It only needs to draw the part that intersects the visible display area.<\/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-4d8484a e-flex e-con-boxed e-con e-parent\" data-id=\"4d8484a\" 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-812cc4b elementor-widget elementor-widget-heading\" data-id=\"812cc4b\" 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\">Visible-Only Rendering<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a203351 elementor-widget elementor-widget-text-editor\" data-id=\"a203351\" 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=\"6084\" data-end=\"6136\"><code data-start=\"6084\" data-end=\"6103\">SAxLineGridCanvas<\/code> calculates an intersecting rect.<\/p><p data-start=\"6138\" data-end=\"6217\">That rect represents the part of the line grid canvas that is actually visible.<\/p><p data-start=\"6219\" data-end=\"6300\">If the line grid canvas has no parent, the full local rect is considered visible.<\/p><p data-start=\"6302\" data-end=\"6348\">If it has a parent, the Slate widget compares:<\/p><ul data-start=\"6350\" data-end=\"6395\"><li data-section-id=\"igtyx4\" data-start=\"6350\" data-end=\"6377\">The line grid canvas rect<\/li><li data-section-id=\"1hd1pu2\" data-start=\"6378\" data-end=\"6395\">The parent rect<\/li><\/ul><p data-start=\"6397\" data-end=\"6464\">The result is converted back into the line grid canvas local space.<\/p><p data-start=\"6466\" data-end=\"6569\">This is why moving or resizing the line grid canvas does not automatically make drawing more expensive.<\/p><p data-start=\"6571\" data-end=\"6679\">The cost is based mainly on the visible rect and the line spacing, not the total logical size of the canvas.<br \/><br \/>For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0b64562 elementor-widget elementor-widget-code-highlight\" data-id=\"0b64562\" 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>Canvas size: 30,000 px wide\r\nVisible display area: 1,000 px wide\r\nLine spacing: 100 px\r\n\r\nApproximate rendered vertical lines:\r\n1,000 \/ 100 = 10 visible lines<\/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-f7e7afb elementor-widget elementor-widget-text-editor\" data-id=\"f7e7afb\" 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=\"6862\" data-end=\"6916\">The panel only needs to render the visible line range.<\/p><h3 data-section-id=\"15z6dh9\" data-start=\"6918\" data-end=\"6943\">Where to look in code<\/h3><ul data-start=\"6945\" data-end=\"7228\"><li data-section-id=\"jijqe5\" data-start=\"6945\" data-end=\"7007\">Visible rect:<ul data-start=\"6963\" data-end=\"7007\"><li data-section-id=\"kvn0dl\" data-start=\"6963\" data-end=\"7007\"><code data-start=\"6965\" data-end=\"7007\">SAxLineGridCanvas::GetIntersectingRect()<\/code><\/li><\/ul><\/li><li data-section-id=\"stq25q\" data-start=\"7008\" data-end=\"7082\">Rendering visible line grids:<ul data-start=\"7042\" data-end=\"7082\"><li data-section-id=\"foqx5h\" data-start=\"7042\" data-end=\"7082\"><code data-start=\"7044\" data-end=\"7082\">SAxLineGridCanvas::RenderLineGrids()<\/code><\/li><\/ul><\/li><li data-section-id=\"ydi55d\" data-start=\"7083\" data-end=\"7158\">Pre-render hook:<ul data-start=\"7104\" data-end=\"7158\"><li data-section-id=\"1vovmqw\" data-start=\"7104\" data-end=\"7158\"><code data-start=\"7106\" data-end=\"7158\">UAxLineGridCanvasPanel::HandlePreRenderLineGrids()<\/code><\/li><\/ul><\/li><li data-section-id=\"93woy3\" data-start=\"7159\" data-end=\"7228\">Visible logical range:<ul data-start=\"7186\" data-end=\"7228\"><li data-section-id=\"152dhcz\" data-start=\"7186\" data-end=\"7228\"><code data-start=\"7188\" data-end=\"7228\">UAxLineGrid::GetLogicalIndexesInView()<\/code><\/li><\/ul><\/li><\/ul>\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-316633e e-flex e-con-boxed e-con e-parent\" data-id=\"316633e\" 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-ac45550 elementor-widget elementor-widget-heading\" data-id=\"ac45550\" 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\">Line Grid Properties<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-993481b elementor-widget elementor-widget-text-editor\" data-id=\"993481b\" 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=\"7255\" data-end=\"7310\">Each entry in <code data-start=\"7269\" data-end=\"7280\">LineGrids<\/code> describes one grid to render.<\/p><p data-start=\"7312\" data-end=\"7351\">The common properties to configure are:<\/p><h3 data-section-id=\"yo5ll7\" data-start=\"7353\" data-end=\"7361\"><span role=\"text\"><code data-start=\"7357\" data-end=\"7361\">ID<\/code><\/span><\/h3><p data-start=\"7363\" data-end=\"7392\">The unique logical unit name.<\/p><p data-start=\"7394\" data-end=\"7403\">Examples:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fdc2f43 elementor-widget elementor-widget-code-highlight\" data-id=\"fdc2f43\" 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 class=\"elementor-element elementor-element-c006236 elementor-widget elementor-widget-text-editor\" data-id=\"c006236\" 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=\"7457\" data-end=\"7550\">This ID is also what child widgets use when they say which units their X and Y values are in.<\/p><h3 data-section-id=\"8ifrps\" data-start=\"7552\" data-end=\"7569\"><span role=\"text\"><code data-start=\"7556\" data-end=\"7569\">Orientation<\/code><\/span><\/h3><p data-start=\"7571\" data-end=\"7616\">Controls the direction of the repeated lines.<\/p><p data-start=\"7618\" data-end=\"7719\">A horizontal orientation means the grid grows across the X axis and draws vertical-looking divisions.<\/p><p data-start=\"7721\" data-end=\"7822\">A vertical orientation means the grid grows across the Y axis and draws horizontal-looking divisions.<\/p><p data-start=\"7824\" data-end=\"7854\">For a piano roll, you may use:<\/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=\"pe-11 pt-3\"><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>Horizontal grid: Beats or Bars<br \/>Vertical grid: Pitch rows<\/code><\/pre><p>\u00a0<\/p><p data-start=\"7926\" data-end=\"7954\">For a timeline, you may use:<\/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 end-1.5 top-1 z-2 md:end-2 md:top-1\">\u00a0<\/div><div class=\"relative\"><div class=\"pe-11 pt-3\"><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>Horizontal grid: Seconds, Frames, Beats, or Bars<br \/>Vertical grid: Tracks<\/code><\/pre><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/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-29acd4c elementor-widget elementor-widget-text-editor\" data-id=\"29acd4c\" 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=\"1a8iax\" data-start=\"8040\" data-end=\"8057\"><span role=\"text\"><code data-start=\"8044\" data-end=\"8057\">LineSpacing<\/code><\/span><\/h3><p data-start=\"8059\" data-end=\"8105\">The physical pixel spacing between grid lines.<\/p><p data-start=\"8107\" data-end=\"8162\">This is the value that changes when the grid is zoomed.<\/p><p data-start=\"8164\" data-end=\"8214\">A larger line spacing means the user is zoomed in.<\/p><p data-start=\"8216\" data-end=\"8268\">A smaller line spacing means the user is zoomed out.<\/p><h3 data-section-id=\"19r0uwx\" data-start=\"8270\" data-end=\"8293\"><span role=\"text\"><code data-start=\"8274\" data-end=\"8293\">bFlipLogicalUnits<\/code><\/span><\/h3><p data-start=\"8295\" data-end=\"8395\">Controls whether logical values increase in the normal physical direction or the opposite direction.<\/p><p data-start=\"8397\" data-end=\"8523\">This matters for cases like piano rolls where pitch may need to increase upward while Slate\/UMG Y positions increase downward.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ca10cce elementor-widget elementor-widget-text-editor\" data-id=\"ca10cce\" 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=\"jr6lsm\" data-start=\"8694\" data-end=\"8713\">Drawing options<\/h3><p data-start=\"8715\" data-end=\"8738\">The grid can also draw:<\/p><ul data-start=\"8740\" data-end=\"8815\"><li data-section-id=\"8nskxe\" data-start=\"8740\" data-end=\"8747\">Lines<\/li><li data-section-id=\"63t3lz\" data-start=\"8748\" data-end=\"8760\">Tick lines<\/li><li data-section-id=\"1u44op3\" data-start=\"8761\" data-end=\"8774\">Text labels<\/li><li data-section-id=\"13dz6g7\" data-start=\"8775\" data-end=\"8800\">Alternating backgrounds<\/li><li data-section-id=\"14308ym\" data-start=\"8801\" data-end=\"8815\">Border lines<\/li><\/ul><p data-start=\"8817\" data-end=\"8890\">These are visual features layered on top of the same logical grid system.<\/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-9a00e0f e-flex e-con-boxed e-con e-parent\" data-id=\"9a00e0f\" 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-4e50772 elementor-widget elementor-widget-heading\" data-id=\"4e50772\" 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 Widgets On The Grid<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-97c97bc elementor-widget elementor-widget-text-editor\" data-id=\"97c97bc\" 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=\"8922\" data-end=\"9002\">The line grid canvas expects positioned children to be <code data-start=\"8977\" data-end=\"9001\">UAxLineGridChildWidget<\/code>.<\/p><p data-start=\"9004\" data-end=\"9131\">A <code data-start=\"9006\" data-end=\"9030\">UAxLineGridChildWidget<\/code> is a SmartWidget specialization that represents a rectangle in logical grid units instead of pixels.<\/p><p data-start=\"9133\" data-end=\"9187\">For example, a note widget in a piano roll might mean:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-168268a elementor-widget elementor-widget-code-highlight\" data-id=\"168268a\" 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 5\r\nY0 = Pitch 60\r\nY1 = Pitch 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-20acab5 elementor-widget elementor-widget-text-editor\" data-id=\"20acab5\" 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=\"9254\" data-end=\"9314\">The child does not need to calculate its own pixel position.<\/p><p data-start=\"9316\" data-end=\"9341\">Instead, the panel reads:<\/p><ul data-start=\"9343\" data-end=\"9406\"><li data-section-id=\"jqn53g\" data-start=\"9343\" data-end=\"9362\"><code data-start=\"9345\" data-end=\"9362\">LogicalXUnitsID<\/code><\/li><li data-section-id=\"w891hp\" data-start=\"9363\" data-end=\"9382\"><code data-start=\"9365\" data-end=\"9382\">LogicalYUnitsID<\/code><\/li><li data-section-id=\"9h03lh\" data-start=\"9383\" data-end=\"9406\"><code data-start=\"9385\" data-end=\"9406\">LogicalPositionRect<\/code><\/li><\/ul><p data-start=\"9408\" data-end=\"9470\">Then it resolves those values against the matching line grids.<\/p><p data-start=\"9472\" data-end=\"9548\">The final physical <code data-start=\"9491\" data-end=\"9508\">CanvasPanelSlot<\/code> position and size are set by the panel.<\/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-01bf1f6 e-flex e-con-boxed e-con e-parent\" data-id=\"01bf1f6\" 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-6934abb elementor-widget elementor-widget-heading\" data-id=\"6934abb\" 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 Unit Resolution<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b1327d8 elementor-widget elementor-widget-text-editor\" data-id=\"b1327d8\" 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=\"9576\" data-end=\"9624\">Each child can provide its own logical unit IDs.<\/p><p data-start=\"9626\" data-end=\"9638\">For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-22af4dc elementor-widget elementor-widget-code-highlight\" data-id=\"22af4dc\" 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-0d56114 elementor-widget elementor-widget-text-editor\" data-id=\"0d56114\" 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=\"9705\" data-end=\"9768\">If a child does not provide a unit ID, the panel falls back to:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ff571a3 elementor-widget elementor-widget-code-highlight\" data-id=\"ff571a3\" 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>ChildWidgetsLayoutProperties.DefaultLogicalXUnitsID\r\nChildWidgetsLayoutProperties.DefaultLogicalYUnitsID<\/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-0062fd1 elementor-widget elementor-widget-text-editor\" data-id=\"0062fd1\" 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=\"9887\" data-end=\"9946\">This is useful when most children use the same unit system.<\/p><p data-start=\"9948\" data-end=\"9997\">For example, in a piano roll, most notes may use: Beats and Pitch exclusively<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1aaa344 elementor-widget elementor-widget-text-editor\" data-id=\"1aaa344\" 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=\"10078\" data-end=\"10186\">Then individual note widgets do not need to set their own unit IDs unless they need to override the default.<\/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-efc261c e-flex e-con-boxed e-con e-parent\" data-id=\"efc261c\" 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-cf03f81 elementor-widget elementor-widget-heading\" data-id=\"cf03f81\" 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\">Important rule<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b3e90b9 elementor-widget elementor-widget-text-editor\" data-id=\"b3e90b9\" 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=\"10208\" data-end=\"10262\">The resolved unit IDs must match valid <code data-start=\"10247\" data-end=\"10257\">LineGrid<\/code> IDs.<\/p><p data-start=\"10264\" data-end=\"10362\">If the panel cannot resolve the child\u2019s X or Y unit grid, it cannot position that child correctly.<\/p><h2 data-section-id=\"1w28zyy\" data-start=\"10364\" data-end=\"10394\">Expected Child Widget Setup<\/h2><p data-start=\"10396\" data-end=\"10444\">A line grid child widget is expected to provide:<\/p><ol data-start=\"10446\" data-end=\"10632\"><li data-section-id=\"vufsgv\" data-start=\"10446\" data-end=\"10468\">A logical X range<\/li><li data-section-id=\"1f6sce5\" data-start=\"10469\" data-end=\"10491\">A logical Y range<\/li><li data-section-id=\"qlz77s\" data-start=\"10492\" data-end=\"10560\">X and Y unit IDs, either directly or through the panel defaults<\/li><\/ol><p data-start=\"10634\" data-end=\"10666\">The anchoring rule is important.<\/p><p data-start=\"10668\" data-end=\"10707\">Positioned line grid children must use (Top Left):<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-96e5315 elementor-widget elementor-widget-code-highlight\" data-id=\"96e5315\" 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-93f9636 elementor-widget elementor-widget-text-editor\" data-id=\"93f9636\" 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=\"10797\" data-end=\"10912\">The reason is that the panel writes explicit top-left position and size values into the child\u2019s <code data-start=\"10893\" data-end=\"10911\">UCanvasPanelSlot<\/code>.<\/p><p data-start=\"10914\" data-end=\"11008\">If the child uses different anchors or alignment, UMG will interpret the position differently.<\/p><p data-start=\"11010\" data-end=\"11139\">The panel validates this and auto-corrects the slot at runtime, but it is still best to set it correctly in the widget blueprint.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b91f3e3 elementor-widget elementor-widget-text-editor\" data-id=\"b91f3e3\" 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=\"15z6dh9\" data-start=\"11141\" data-end=\"11166\">Where to look in code<\/h3><ul data-start=\"11168\" data-end=\"11584\"><li data-section-id=\"rsks7\" data-start=\"11168\" data-end=\"11217\">Child widget type:<ul data-start=\"11191\" data-end=\"11217\"><li data-section-id=\"1g8lng3\" data-start=\"11191\" data-end=\"11217\"><code data-start=\"11193\" data-end=\"11217\">UAxLineGridChildWidget<\/code><\/li><\/ul><\/li><li data-section-id=\"1aw74tz\" data-start=\"11218\" data-end=\"11295\">Logical unit getters:<ul data-start=\"11244\" data-end=\"11295\"><li data-section-id=\"cg2qd7\" data-start=\"11244\" data-end=\"11268\"><code data-start=\"11246\" data-end=\"11268\">GetLogicalXUnitsID()<\/code><\/li><li data-section-id=\"1acfdkq\" data-start=\"11271\" data-end=\"11295\"><code data-start=\"11273\" data-end=\"11295\">GetLogicalYUnitsID()<\/code><\/li><\/ul><\/li><li data-section-id=\"1dqzim\" data-start=\"11296\" data-end=\"11349\">Logical rect getter:<ul data-start=\"11321\" data-end=\"11349\"><li data-section-id=\"o3curm\" data-start=\"11321\" data-end=\"11349\"><code data-start=\"11323\" data-end=\"11349\">GetLogicalPositionRect()<\/code><\/li><\/ul><\/li><li data-section-id=\"csx5kr\" data-start=\"11350\" data-end=\"11430\">Logical value getters:<ul data-start=\"11377\" data-end=\"11430\"><li data-section-id=\"9ep91s\" data-start=\"11377\" data-end=\"11388\"><code data-start=\"11379\" data-end=\"11388\">GetX0()<\/code><\/li><li data-section-id=\"9emzqp\" data-start=\"11391\" data-end=\"11402\"><code data-start=\"11393\" data-end=\"11402\">GetX1()<\/code><\/li><li data-section-id=\"9cmhu9\" data-start=\"11405\" data-end=\"11416\"><code data-start=\"11407\" data-end=\"11416\">GetY0()<\/code><\/li><li data-section-id=\"9clrsg\" data-start=\"11419\" data-end=\"11430\"><code data-start=\"11421\" data-end=\"11430\">GetY1()<\/code><\/li><\/ul><\/li><li data-section-id=\"1xp4yc5\" data-start=\"11431\" data-end=\"11520\">Child positioning:<ul data-start=\"11454\" data-end=\"11520\"><li data-section-id=\"1am6v1s\" data-start=\"11454\" data-end=\"11486\"><code data-start=\"11456\" data-end=\"11486\">UpdateChildWidgetsGeometry()<\/code><\/li><li data-section-id=\"vtvc4j\" data-start=\"11489\" data-end=\"11520\"><code data-start=\"11491\" data-end=\"11520\">UpdateChildWidgetGeometry()<\/code><\/li><\/ul><\/li><li data-section-id=\"6ydfmc\" data-start=\"11521\" data-end=\"11584\">Slot validation:<ul data-start=\"11542\" data-end=\"11584\"><li data-section-id=\"ba1pk1\" data-start=\"11542\" data-end=\"11584\"><code data-start=\"11544\" data-end=\"11584\">ValidateAndAutoCorrectChildAnchoring()<\/code><\/li><\/ul><\/li><\/ul>\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-ae4e4a9 e-flex e-con-boxed e-con e-parent\" data-id=\"ae4e4a9\" 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-f1d2778 elementor-widget elementor-widget-heading\" data-id=\"f1d2778\" 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 Logical Rect To Physical Rect<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fd95c18 elementor-widget elementor-widget-text-editor\" data-id=\"fd95c18\" 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=\"11620\" data-end=\"11673\">During painting, the panel updates the child widgets.<\/p><p data-start=\"11675\" data-end=\"11704\">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-6e55f5e elementor-widget elementor-widget-code-highlight\" data-id=\"6e55f5e\" 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 logical X0\/X1\r\n        \u2193\r\nResolve X LineGrid\r\n        \u2193\r\nConvert logical X values to physical X positions\r\n\r\nChild logical Y0\/Y1\r\n        \u2193\r\nResolve Y LineGrid\r\n        \u2193\r\nConvert logical Y values to physical Y positions\r\n\r\nFinal physical rect\r\n        \u2193\r\nSet CanvasPanelSlot position and 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-855644f elementor-widget elementor-widget-text-editor\" data-id=\"855644f\" 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=\"12005\" data-end=\"12028\">For a non-flipped grid:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8d986b6 elementor-widget elementor-widget-code-highlight\" data-id=\"8d986b6\" 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>Left  = PositionOf(X0)\nRight = PositionOf(X1)\nTop   = PositionOf(Y0)\nBottom = PositionOf(Y1)<\/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-2b5d0c8 elementor-widget elementor-widget-text-editor\" data-id=\"2b5d0c8\" 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=\"12136\" data-end=\"12221\">For a flipped grid, the start and end values are intentionally swapped for that axis:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-692102e elementor-widget elementor-widget-code-highlight\" data-id=\"692102e\" 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>Left  = PositionOf(X1)\r\nRight = PositionOf(X0)\r\n\r\nor\r\n\r\nTop    = PositionOf(Y1)\r\nBottom = PositionOf(Y0)<\/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-4f71150 elementor-widget elementor-widget-text-editor\" data-id=\"4f71150\" 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=\"12348\" data-end=\"12497\">After that, the panel normalizes the physical rect so the left side is smaller than the right side, and the top side is smaller than the bottom side.<\/p><p data-start=\"12499\" data-end=\"12577\">This keeps the final canvas slot valid even when logical direction is flipped.<\/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-b31cd25 e-flex e-con-boxed e-con e-parent\" data-id=\"b31cd25\" 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-d5ba673 elementor-widget elementor-widget-heading\" data-id=\"d5ba673\" 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\">Size Clamping And Breakpoints<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7d566fb elementor-widget elementor-widget-text-editor\" data-id=\"7d566fb\" 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=\"12613\" data-end=\"12719\">After the logical rect is converted into physical space, the resulting child widget can become very small.<\/p><p data-start=\"12721\" data-end=\"12764\">This often happens when the user zooms out.<\/p><p data-start=\"12766\" data-end=\"12778\">For example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-efbcdb7 elementor-widget elementor-widget-code-highlight\" data-id=\"efbcdb7\" 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>A note that is 1 beat long may become only 4 pixels wide.<\/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-cb6948c elementor-widget elementor-widget-text-editor\" data-id=\"cb6948c\" 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=\"12851\" data-end=\"12889\">That may be too small for interaction.<\/p><p data-start=\"12891\" data-end=\"13022\"><code data-start=\"12891\" data-end=\"12935\">ChildWidgetsLayoutProperties.ClampSizeAxis<\/code> controls which axis may be expanded to preserve the child widget\u2019s minimum input size.<\/p><p data-start=\"13024\" data-end=\"13076\">This does not change the logical value of the child.<\/p><p data-start=\"13078\" data-end=\"13150\">It only changes the rendered physical size so the widget remains usable.<\/p><p data-start=\"13152\" data-end=\"13193\"><code data-start=\"13152\" data-end=\"13168\">BreakpointSize<\/code> is a separate threshold.<\/p><p data-start=\"13195\" data-end=\"13409\">The base panel does not automatically hide text or simplify visuals when the breakpoint is reached. Instead, child widgets or derived panels can use this threshold to decide when to render a simpler representation.<br \/><br \/><\/p><p data-start=\"13411\" data-end=\"13423\">For example:<\/p><ul data-start=\"13425\" data-end=\"13554\"><li data-section-id=\"r5ctbq\" data-start=\"13425\" data-end=\"13464\">Hide text when the note is too narrow<\/li><li data-section-id=\"dtalhw\" data-start=\"13465\" data-end=\"13492\">Draw only a compact block<\/li><li data-section-id=\"j8owqq\" data-start=\"13493\" data-end=\"13525\">Disable dense internal visuals<\/li><li data-section-id=\"17if1pi\" data-start=\"13526\" data-end=\"13554\">Use a simpler hover target<\/li><\/ul>\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-b6d7b4a e-flex e-con-boxed e-con e-parent\" data-id=\"b6d7b4a\" 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-4b427d2 elementor-widget elementor-widget-heading\" data-id=\"4b427d2\" 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\">In-View Logical Range<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-63e3695 elementor-widget elementor-widget-text-editor\" data-id=\"63e3695\" 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=\"13582\" data-end=\"13692\">Before child widget geometry is updated, the panel resolves which logical X and Y units are currently visible.<\/p><p data-start=\"13694\" data-end=\"13718\">These are controlled by:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c5d6b13 elementor-widget elementor-widget-code-highlight\" data-id=\"c5d6b13\" 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>ChildWidgetsLayoutProperties.LogicalInViewXUnitsID\r\nChildWidgetsLayoutProperties.LogicalInViewYUnitsID<\/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-637c360 elementor-widget elementor-widget-text-editor\" data-id=\"637c360\" 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=\"13835\" data-end=\"13908\">These IDs are not required to match every child widget\u2019s placement units.<\/p><p data-start=\"13910\" data-end=\"13982\">Their job is to define the logical window used for visibility decisions.<\/p><p data-start=\"13984\" data-end=\"14089\">For example, a child might be positioned in beats, but the panel may decide the visible range using bars.<\/p><p data-start=\"14091\" data-end=\"14112\">The panel then calls:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0ed7681 elementor-widget elementor-widget-code-highlight\" data-id=\"0ed7681\" 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-79efde0 elementor-widget elementor-widget-text-editor\" data-id=\"79efde0\" 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=\"14173\" data-end=\"14222\">The base implementations are intentionally empty.<\/p><p data-start=\"14224\" data-end=\"14286\">Derived classes decide what widget ownership policy they need.<\/p><p data-start=\"14288\" data-end=\"14300\">For example:<\/p><ul data-start=\"14302\" data-end=\"14538\"><li data-section-id=\"1dkbbu1\" data-start=\"14302\" data-end=\"14362\">A piano roll may spawn note widgets for visible notes only<\/li><li data-section-id=\"1v4ebda\" data-start=\"14363\" data-end=\"14400\">A timeline may recycle clip widgets<\/li><li data-section-id=\"rvdl22\" data-start=\"14401\" data-end=\"14467\">A sequencer may keep all widgets alive but hide out-of-view ones<\/li><li data-section-id=\"baje54\" data-start=\"14468\" data-end=\"14538\">A track editor may spawn row headers separately from content widgets<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7640e6d elementor-widget elementor-widget-text-editor\" data-id=\"7640e6d\" 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=\"15z6dh9\" data-start=\"14540\" data-end=\"14565\">Where to look in code<\/h3><ul data-start=\"14567\" data-end=\"14698\"><li data-section-id=\"8aa8by\" data-start=\"14567\" data-end=\"14616\">In-view range:<ul data-start=\"14586\" data-end=\"14616\"><li data-section-id=\"1dm6hsc\" data-start=\"14586\" data-end=\"14616\"><code data-start=\"14588\" data-end=\"14616\">HandlePreRenderLineGrids()<\/code><\/li><\/ul><\/li><li data-section-id=\"1m2kslz\" data-start=\"14617\" data-end=\"14698\">Virtualization hooks:<ul data-start=\"14643\" data-end=\"14698\"><li data-section-id=\"1i1aykd\" data-start=\"14643\" data-end=\"14671\"><code data-start=\"14645\" data-end=\"14671\">RemoveOutOfViewWidgets()<\/code><\/li><li data-section-id=\"heoyo\" data-start=\"14674\" data-end=\"14698\"><code data-start=\"14676\" data-end=\"14698\">SpawnInViewWidgets()<\/code><\/li><\/ul><\/li><\/ul>\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-37785a5 e-flex e-con-boxed e-con e-parent\" data-id=\"37785a5\" 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-6607170 elementor-widget elementor-widget-heading\" data-id=\"6607170\" 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\">Working With Flipped Grids<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4642e3e elementor-widget elementor-widget-text-editor\" data-id=\"4642e3e\" 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=\"14731\" data-end=\"14830\">Flipped grids require extra care because logical direction and physical direction are not the same.<\/p><p data-start=\"14832\" data-end=\"14911\">In a normal horizontal grid, larger logical X values usually move to the right.<\/p><p data-start=\"14913\" data-end=\"14988\">In a flipped horizontal grid, larger logical X values may move to the left.<\/p><p data-start=\"14990\" data-end=\"15063\">In a normal vertical grid, larger logical Y values usually move downward.<\/p><p data-start=\"15065\" data-end=\"15133\">In a flipped vertical grid, larger logical Y values may move upward.<br \/><br \/><\/p><p data-start=\"15135\" data-end=\"15164\">The <strong>UAxScrollableZoomableGridWidget and The LineGridCanvasPanel<br \/><br \/><\/strong>Already handles most of this logic for you , but this information is for debugging incase it does not work as expectected.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-06a4d3c elementor-widget elementor-widget-text-editor\" data-id=\"06a4d3c\" 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=\"1l1h75f\" data-start=\"16664\" data-end=\"16686\">Example: Piano Roll<\/h2><p data-start=\"16688\" data-end=\"16733\">A piano roll usually has two important grids:<\/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 end-1.5 top-1 z-2 md:end-2 md:top-1\">\u00a0<\/div><div class=\"relative\"><div class=\"pe-11 pt-3\"><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>Horizontal grid: Beats<br \/>Vertical grid: Pitch<\/code><\/pre><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><div class=\"\"><div class=\"\">\u00a0<\/div><\/div><\/div><\/div><\/div><p data-start=\"16792\" data-end=\"16816\">A note widget might use:<\/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 end-1.5 top-1 z-2 md:end-2 md:top-1\">\u00a0<\/div><div class=\"relative\"><div class=\"pe-11 pt-3\"><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>LogicalXUnitsID = \"Beats\"<br \/>LogicalYUnitsID = \"Pitch\"<br \/><br \/>X0 = 4.0<br \/>X1 = 5.0<br \/>Y0 = 60<br \/>Y1 = 61<\/code><\/pre><\/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-d716cb8 elementor-widget elementor-widget-text-editor\" data-id=\"d716cb8\" 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=\"16918\" data-end=\"17006\">If pitch should increase upward, the pitch grid will usually need flipped logical units.<\/p><p data-start=\"17008\" data-end=\"17124\">That lets higher pitch values appear higher on the screen, even though UMG Y coordinates normally increase downward.<\/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-30c3f5d e-flex e-con-boxed e-con e-parent\" data-id=\"30c3f5d\" 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-803b880 elementor-widget elementor-widget-heading\" data-id=\"803b880\" 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\t\t<div class=\"elementor-element elementor-element-35005e3 elementor-widget elementor-widget-text-editor\" data-id=\"35005e3\" 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=\"17150\" data-end=\"17290\">You usually extend <code data-start=\"17169\" data-end=\"17193\">UAxLineGridCanvasPanel<\/code> when you need custom behavior around what appears in the visible range or how the grid is drawn.<\/p><p data-start=\"17292\" data-end=\"17324\">Common extension points include:<\/p><h3 data-section-id=\"qehvw4\" data-start=\"17326\" data-end=\"17356\"><span role=\"text\"><code data-start=\"17330\" data-end=\"17356\">RemoveOutOfViewWidgets()<\/code><\/span><\/h3><p data-start=\"17358\" data-end=\"17462\">Use this to remove, hide, or recycle widgets that no longer belong to the current visible logical range.<\/p><h3 data-section-id=\"xde95\" data-start=\"17464\" data-end=\"17490\"><span role=\"text\"><code data-start=\"17468\" data-end=\"17490\">SpawnInViewWidgets()<\/code><\/span><\/h3><p data-start=\"17492\" data-end=\"17555\">Use this to create widgets for data that should now be visible.<\/p><h3 data-section-id=\"1u19i6i\" data-start=\"17557\" data-end=\"17582\"><span role=\"text\"><code data-start=\"17561\" data-end=\"17582\">RenderCurrentLine()<\/code><\/span><\/h3><p data-start=\"17584\" data-end=\"17638\">Use this if the line drawing behavior needs to change.<br \/><br \/><\/p><h3 data-section-id=\"4hyshx\" data-start=\"17640\" data-end=\"17663\"><span role=\"text\"><code data-start=\"17644\" data-end=\"17663\">RenderLineLabel()<\/code><\/span><\/h3><p data-start=\"17665\" data-end=\"17703\">Use this to customize label rendering.<\/p><h3 data-section-id=\"cgoss6\" data-start=\"17705\" data-end=\"17727\"><span role=\"text\"><code data-start=\"17709\" data-end=\"17727\">RenderLineTick()<\/code><\/span><\/h3><p data-start=\"17729\" data-end=\"17769\">Use this to customize tick line drawing.<\/p><h3 data-section-id=\"1ijovi1\" data-start=\"17771\" data-end=\"17791\"><span role=\"text\"><code data-start=\"17775\" data-end=\"17791\">GetLineLabel()<\/code><\/span><\/h3><p data-start=\"17793\" data-end=\"17842\">Use this when logical indexes need custom labels.<br \/><br \/><\/p><p data-start=\"17844\" data-end=\"17856\">For example:<\/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 end-1.5 top-1 z-2 md:end-2 md:top-1\">\u00a0<\/div><div class=\"relative\"><div class=\"pe-11 pt-3\"><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>0  \u2192 Bar 1<br \/>1  \u2192 Bar 2<br \/>60 \u2192 C4<br \/>61 \u2192 C#4<\/code><\/pre><\/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\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-0efc025 e-flex e-con-boxed e-con e-parent\" data-id=\"0efc025\" 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-0e5eb85 elementor-widget elementor-widget-heading\" data-id=\"0e5eb85\" 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 To Adjust First<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e67e03f elementor-widget elementor-widget-menu-anchor\" data-id=\"e67e03f\" 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=\"WhatToAdjustFirst\"><\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-37674c4 elementor-widget elementor-widget-text-editor\" data-id=\"37674c4\" 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=\"17935\" data-end=\"18003\">Create a UUserWidget Blueprint And Add A <code data-start=\"17954\" data-end=\"17975\">LineGridCanvasPanel<\/code>, then start with these settings:<\/p><h3 data-section-id=\"tk0g74\" data-start=\"18005\" data-end=\"18033\">1. Define the line grids<\/h3><p data-start=\"18035\" data-end=\"18091\">Create line grid entries for the logical units you need.<\/p><p data-start=\"18093\" data-end=\"18101\">Example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-55a32ec elementor-widget elementor-widget-code-highlight\" data-id=\"55a32ec\" 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 class=\"elementor-element elementor-element-64bc9aa elementor-widget elementor-widget-text-editor\" data-id=\"64bc9aa\" 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=\"1qdjvll\" data-start=\"18155\" data-end=\"18187\">2. Set each grid orientation<\/h3><p data-start=\"18189\" data-end=\"18254\">Use horizontal orientation for units that grow across the X axis.<\/p><p data-start=\"18256\" data-end=\"18319\">Use vertical orientation for units that grow across the Y axis.<\/p><h3 data-section-id=\"nj42c6\" data-start=\"18321\" data-end=\"18344\">3. Set line spacing<\/h3><p data-start=\"18346\" data-end=\"18387\">This controls how dense the grid appears.<\/p><p data-start=\"18389\" data-end=\"18435\">It is also the main value affected by zooming.<br \/><br \/><\/p><h3 data-section-id=\"1f1slls\" data-start=\"18437\" data-end=\"18473\">4. Configure default child units<\/h3><p data-start=\"18475\" data-end=\"18479\">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 end-1.5 top-1 z-2 md:end-2 md:top-1\">\u00a0<\/div><div class=\"relative\"><div class=\"pe-11 pt-3\"><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>DefaultLogicalXUnitsID<br \/>DefaultLogicalYUnitsID<\/code><\/pre><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><div class=\"\"><div class=\"\">\u00a0<\/div><\/div><\/div><\/div><\/div><p data-start=\"18540\" data-end=\"18582\">This reduces setup work for child widgets.<br \/><br \/><\/p><h3 data-section-id=\"5e8c87\" data-start=\"18584\" data-end=\"18614\">5. Configure in-view units<\/h3><p data-start=\"18616\" data-end=\"18620\">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 end-1.5 top-1 z-2 md:end-2 md:top-1\">\u00a0<\/div><div class=\"relative\"><div class=\"pe-11 pt-3\"><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>LogicalInViewXUnitsID<br \/>LogicalInViewYUnitsID<\/code><\/pre><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><div class=\"\"><div class=\"\">\u00a0<\/div><\/div><\/div><\/div><\/div><p data-start=\"18679\" data-end=\"18756\">These control the logical range used for spawning and removing child widgets.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8fa12c5 elementor-widget elementor-widget-text-editor\" data-id=\"8fa12c5\" 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=\"4p5lhx\" data-start=\"18758\" data-end=\"18783\">6. Check flipped axes<\/h3><p data-start=\"18785\" data-end=\"18900\">If your logical direction should run opposite to UMG\u2019s physical direction, enable <code data-start=\"18867\" data-end=\"18886\">bFlipLogicalUnits<\/code> on that grid.<\/p><p data-start=\"18902\" data-end=\"18946\">This is most common on vertical pitch grids.<\/p><h3 data-section-id=\"1shblx2\" data-start=\"18948\" data-end=\"18982\">7. Set child anchors correctly<\/h3><p data-start=\"18984\" data-end=\"19060\">Line grid children should use top-left point anchors and top-left alignment.<br \/>At runtime as you spawn widgets , use this anchor. If testing while in the designer, use this anchor.<\/p><p>The expectation is you&#8217;ll be spawning children dynamically at runtime during SpawnInViewWidgets().<\/p><p data-start=\"19062\" data-end=\"19155\">The panel can auto-correct this, but the widget blueprint\/ dynamic code\u00a0 should still be authored correctly.<\/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-0b42d41 e-flex e-con-boxed e-con e-parent\" data-id=\"0b42d41\" 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-ecbcb68 elementor-widget elementor-widget-heading\" data-id=\"ecbcb68\" 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\t\t<div class=\"elementor-element elementor-element-188e565 elementor-widget elementor-widget-text-editor\" data-id=\"188e565\" 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=\"19193\" data-end=\"19273\"><code data-start=\"19193\" data-end=\"19214\">LineGridCanvasPanel<\/code> knows how to draw grids and place children on those grids.<\/p><p data-start=\"19275\" data-end=\"19358\">It does not, by itself, define the complete user-facing scroll and zoom experience.<\/p><p data-start=\"19360\" data-end=\"19435\">Scrolling and zooming are usually handled by a wrapper widget that changes:<\/p><ul data-start=\"19437\" data-end=\"19585\"><li data-section-id=\"hdvh0x\" data-start=\"19437\" data-end=\"19468\">The line grid canvas position<\/li><li data-section-id=\"igstsd\" data-start=\"19469\" data-end=\"19496\">The line grid canvas size<\/li><li data-section-id=\"z4fny5\" data-start=\"19497\" data-end=\"19527\">The parent line grid spacing<\/li><li data-section-id=\"ba34r5\" data-start=\"19528\" data-end=\"19557\">The normalized scroll value<\/li><li data-section-id=\"7k0vpn\" data-start=\"19558\" data-end=\"19585\">The normalized zoom value<br \/><br \/><\/li><\/ul><p>At a higher level, you don&#8217;t need to worry about that, as you&#8217;re expected to use or inherit from<\/p><pre>UAxScrollableZoomableGridWidget<\/pre><p data-start=\"19587\" data-end=\"19681\">and Add your custom LineGridCanvas to it.\u00a0<br \/>This wrapper widget is pre setup to zoom and scroll whatever linegrid widget you hand to it.<br \/><br \/>Read the next article to understand how to scroll and zoom the <code data-start=\"19650\" data-end=\"19671\">LineGridCanvasPanel<\/code>\u00a0 and how the UAxScrollableZoomableGridWidget works .<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3a66984 elementor-widget elementor-widget-button\" data-id=\"3a66984\" 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\/documentation\/enhanced-ui-documentation\/\">\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\">Enhanced UI Documentation<\/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\t\t<\/div>\n\t\t","protected":false},"featured_media":0,"template":"elementor_header_footer","categories":[132,129],"tags":[136],"class_list":["post-13679","documentation","type-documentation","status-publish","hentry","category-enhanced-ui","category-unreal-engine","tag-userinterface","entry"],"acf":[],"_links":{"self":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation\/13679","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\/13679\/revisions"}],"predecessor-version":[{"id":13805,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/documentation\/13679\/revisions\/13805"}],"wp:attachment":[{"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/media?parent=13679"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/categories?post=13679"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/store.algosyntax.com\/asx-rest\/wp\/v2\/tags?post=13679"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}