From e6e025cc171f5e568a599e79bd12d86570b80329 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Wed, 16 Oct 2024 09:55:22 +0200 Subject: [PATCH 1/9] Improved tag functionality and performance. --- src/engines/L3/element.js | 39 +++++++++++++++++++++++------- src/lib/codegenerator/generator.js | 6 ++++- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/engines/L3/element.js b/src/engines/L3/element.js index f01133f0..5637ca43 100644 --- a/src/engines/L3/element.js +++ b/src/engines/L3/element.js @@ -22,6 +22,20 @@ import { Log } from '../../lib/log.js' import symbols from '../../lib/symbols.js' import Settings from '../../settings.js' +const layoutFn = function (config) { + let offset = 0 + const xy = config.direction === 'vertical' ? 'y' : 'x' + const wh = config.direction === 'vertical' ? 'height' : 'width' + + const children = this.children + const childrenLength = children.length + for (let i = 0; i < childrenLength; i++) { + const node = children[i] + node[xy] = offset + offset += node[wh] + (config.gap || 0) + } +} + const isTransition = (value) => { return value !== null && typeof value === 'object' && 'transition' in value === true } @@ -305,11 +319,13 @@ const Element = { }) } - // if (this.component !== undefined && '___layout' in this.component) { - // this.node.on('loaded', () => { - // this.component.___layout() - // }) - // } + if (props.__layout === true) { + this.triggerLayout = layoutFn.bind(this.node) + } + + if (this.config.parent.props && this.config.parent.props.__layout === true) { + this.config.parent.triggerLayout(this.config.parent.props) + } }, set(prop, value) { if (value === undefined) return @@ -338,10 +354,9 @@ const Element = { } } - // todo: review naming - // if (this.component && this.component.___layout) { - // this.component.___layout() - // } + if (this.config.parent.props && this.config.parent.props.__layout === true) { + this.config.parent.triggerLayout(this.config.parent.props) + } }, animate(prop, value, transition) { // check if a transition is already scheduled to run on the same prop @@ -393,6 +408,12 @@ const Element = { }) } + if (this.config.parent.props && this.config.parent.props.__layout === true) { + f.on('tick', (node, progress) => { + this.config.parent.triggerLayout(this.config.parent.props) + }) + } + f.once('stopped', () => { // remove the prop from scheduled transitions this.scheduledTransitions[prop] = undefined diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index cdb6ad8e..db0c9311 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -491,11 +491,15 @@ const generateCode = function (templateObject, parent = false, options = {}) { if ( childTemplateObject[Symbol.for('componentType')] === 'Element' || childTemplateObject[Symbol.for('componentType')] === 'Slot' || - childTemplateObject[Symbol.for('componentType')] === 'Text' + childTemplateObject[Symbol.for('componentType')] === 'Text' || + childTemplateObject[Symbol.for('componentType')] === 'Layout' ) { if (childTemplateObject[Symbol.for('componentType')] === 'Text') { childTemplateObject.__textnode = 'true' } + if (childTemplateObject[Symbol.for('componentType')] === 'Layout') { + childTemplateObject.__layout = 'true' + } generateElementCode.call(this, childTemplateObject, parent, options) } else { generateComponentCode.call(this, childTemplateObject, parent, options) From f3fb12773278b91f942695469d4fa240565a5dc5 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Wed, 16 Oct 2024 09:56:26 +0200 Subject: [PATCH 2/9] Removed Layout component (in favour of handling directly in generated code). --- src/components/Layout.js | 34 ---------------------------------- src/components/index.js | 2 -- 2 files changed, 36 deletions(-) delete mode 100644 src/components/Layout.js diff --git a/src/components/Layout.js b/src/components/Layout.js deleted file mode 100644 index 32da4ff7..00000000 --- a/src/components/Layout.js +++ /dev/null @@ -1,34 +0,0 @@ -import Component from '../component.js' - -export default () => { - return Component('Layout', { - template: ` - - `, - props: [ - 'direction', - { - key: 'gap', - default: 0, - }, - ], - hooks: { - ready() { - this.___layout() - }, - }, - methods: { - // todo: review naming - ___layout() { - let offset = 0 - - this.$select('slot').children.forEach((el) => { - el.set(this.direction === 'vertical' ? 'y' : 'x', offset) - // todo: grab width from interface, not directly from node - offset += - (el.node[this.direction === 'vertical' ? 'height' : 'width'] || 0) + (this.gap || 0) - }) - }, - }, - }) -} diff --git a/src/components/index.js b/src/components/index.js index 5c973c2f..3533f10b 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -19,12 +19,10 @@ import Circle from './Circle.js' import RouterView from './RouterView.js' import Sprite from './Sprite.js' import FPScounter from './FPScounter.js' -import Layout from './Layout.js' export default () => ({ Circle: Circle(), RouterView: RouterView(), Sprite: Sprite(), FPScounter: FPScounter(), - Layout: Layout(), }) From f2c1fc7f1e3706c1937cb0c227e98a63644e30b7 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 1 Nov 2024 11:59:05 +0100 Subject: [PATCH 3/9] Removed commented code from generator. Added support for resizing layout on loaded event. Added auto-resize of layout component. --- src/engines/L3/element.js | 11 ++++++++++- src/lib/codegenerator/generator.js | 3 --- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/engines/L3/element.js b/src/engines/L3/element.js index 5e6172ad..9f668122 100644 --- a/src/engines/L3/element.js +++ b/src/engines/L3/element.js @@ -26,14 +26,20 @@ const layoutFn = function (config) { let offset = 0 const xy = config.direction === 'vertical' ? 'y' : 'x' const wh = config.direction === 'vertical' ? 'height' : 'width' + const opositeWh = config.direction === 'vertical' ? 'width' : 'height' const children = this.children const childrenLength = children.length + let otherDimension = 0 for (let i = 0; i < childrenLength; i++) { const node = children[i] node[xy] = offset offset += node[wh] + (config.gap || 0) + otherDimension = Math.max(otherDimension, node[opositeWh]) } + // adjust the size of the layout container + this[wh] = offset - (config.gap || 0) + this[opositeWh] = otherDimension } const isTransition = (value) => { @@ -323,8 +329,11 @@ const Element = { this.triggerLayout = layoutFn.bind(this.node) } - if (this.config.parent.props && this.config.parent.props.__layout === true) { + if (this.config.parent.props !== undefined && this.config.parent.props.__layout === true) { this.config.parent.triggerLayout(this.config.parent.props) + this.node.on('loaded', () => { + this.config.parent.triggerLayout(this.config.parent.props) + }) } }, set(prop, value) { diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index 0d5ae02a..9a6864a0 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -414,9 +414,6 @@ const generateForLoopCode = function (templateObject, parent) { `) } }) - // if(elms[${forStartCounter}][0] && elms[${forStartCounter}][0].forComponent && elms[${forStartCounter}][0].forComponent.___layout) { - // elms[${forStartCounter}][0].forComponent.___layout() - // } ctx.renderCode.push(` } }`) From 410054302656d2dfc0e37131151f05aba14d2d35 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 1 Nov 2024 14:26:40 +0100 Subject: [PATCH 4/9] Added check for adding offset only for nodes that have width. --- src/engines/L3/element.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engines/L3/element.js b/src/engines/L3/element.js index 9f668122..aaac4ccc 100644 --- a/src/engines/L3/element.js +++ b/src/engines/L3/element.js @@ -34,7 +34,8 @@ const layoutFn = function (config) { for (let i = 0; i < childrenLength; i++) { const node = children[i] node[xy] = offset - offset += node[wh] + (config.gap || 0) + // todo: temporary text check, due to 1px width of empty text node + offset += node[wh] + ((node[wh] !== ('text' in node ? 1 : 0) && config.gap) || 0) otherDimension = Math.max(otherDimension, node[opositeWh]) } // adjust the size of the layout container From 5fafa62e4bf33532a0903a294f2105bb9cdc534e Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 1 Nov 2024 16:47:55 +0100 Subject: [PATCH 5/9] Added fix for vertical gap calculation of empty node. --- src/engines/L3/element.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/engines/L3/element.js b/src/engines/L3/element.js index aaac4ccc..5db4f022 100644 --- a/src/engines/L3/element.js +++ b/src/engines/L3/element.js @@ -35,7 +35,18 @@ const layoutFn = function (config) { const node = children[i] node[xy] = offset // todo: temporary text check, due to 1px width of empty text node - offset += node[wh] + ((node[wh] !== ('text' in node ? 1 : 0) && config.gap) || 0) + if (wh === 'width') { + offset += node.width + (node.width !== ('text' in node ? 1 : 0) ? config.gap : 0) + } else if (wh === 'height') { + offset += + 'text' in node + ? node.width > 1 + ? node.height + config.gap + : 0 + : node.height !== 0 + ? node.height + config.gap + : 0 + } otherDimension = Math.max(otherDimension, node[opositeWh]) } // adjust the size of the layout container From 8d32fde6d22469512a0be5a26b83394ada874cde Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 4 Nov 2024 10:11:43 +0100 Subject: [PATCH 6/9] Added documentation for Layout component. --- docs/built-in/layout.md | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/built-in/layout.md diff --git a/docs/built-in/layout.md b/docs/built-in/layout.md new file mode 100644 index 00000000..52a84f06 --- /dev/null +++ b/docs/built-in/layout.md @@ -0,0 +1,76 @@ +# Layout + +A big portion of building a pixel perfect App is all about correctly positioning Elements and Components on screen. In Lightning and Blits _absolute_ positions are used. This fits the whole concept of building a TV app where the viewport dimensions are known and fixed. Absolute positioning (versus spacing Elements relative to each other) is also more performant. + +Imagine a complex design with several nested layers. Relying solely on _relative_ positioning and elements automatically floating as space is available, may lead to heavy and recursive calculations. Then throw some dynamicly changing dimensions and animations in the mix as well, and your App may be starting to noticably drop frames due to expensive re-calculations. + +That being said, there are often cases where it becomes very cumbersome to calculate all the positions manually. Especially when dimensions are not known in advance and elements may load asynchronously. + +If you come from a CSS background, you may be tempted to say: "_this is why we have Flexbox!_". And while Flexbox is great and versatile in CSS and running on a powerful device, a full flexbox implementation does come at a cost and the risk of slowing down your App, when applied extensively or incorrectly. + +In order to address the core problem "_how do I easily position a bunch of Elements and Components, without writing a lot of code with `@loaded` event handlers_", Blits offers the built-in ``-component - a basic and performant solution that automatically puts your Elements in the right place. + +## How to use + +Since the Layout-component is a built-in component, there is no need to register it seperately. You can use the ``-tag anywhere in your templates, just as you would use `` and ``. + +The Layout component is a wrapper component, that encloses a number of children. All the direct children that are wrapped in a ``-tag are automatically positioned in order, one next to the other. + +Whenever the dimensions of a child change, the positioning of the next children is automatically recalculated and re-applied. + +```xml + + + + Hello world + + +``` + +### Horizontal and vertical layout + +By default the Layout component lays out it's contents _horizontally_. The Layout component accepts a `direction` attribute which allows you to control the direction. + +In order to align vertically use ``. And use `` to explicitly apply the default horizontal layout. + +### Spacing between children + +By default the Layout-component places each Element directly besides (or below) the previous one. By adding the `gap`-attribute you can control how much space will be added between each Element or Component. The `gap`-attribute accepts a number in pixels. + +### Dynamic dimensions + +For the Layout component to work properly, the direct children all need to have dimensions (i.e `w` and `h` attributes). The exception here being Text elements. + +Due to the asynchronous nature of rendering text, the final text dimensions are not known beforehand. The width and height of Text elements are automatically set when the text has been loaded. This fires a `@loaded` event, that you can tap into as described [here](../essentials/displaying_text#text-dimensions). + +The Layout component also uses this event, to execute its calculation and position the text and other elements around it properly. + +When the children of the Layout-component have _reactive_ dimensions (i.e. `), the Layout component ensures that all child elements are properly re-positioned whenever a dimension changes. + +### Nesting Layouts + +It's also possible to nest layouts. Simple place a new ``-tag, with it's own children in between the children of another Layout-component. The Layout-component itself will grow automatically with the dimensions of it's children. In other words, it's not required to specify a width (`w`) or height (`h`) on the ``-tag itself. + +And of course you can nest _vertical_ Layouts inside a _horizontal_ one - and vice versa. + +### Transitioning layouts + +The ``-component can also take into account when dimensions of children change with a transition applied to it. The component will recalculate the position of its children as the transition progresses, making sure that elements are always perfectly positioned relative to one another. + +## Performance + +The Layout-component is a very useful utility, that will make it a lot easier to put together complex layouts. It will reduce the code required, it means less hardcoding of values and removes the need to manually wire up `@loaded` events when working with dynamic / asynchronous elements. + +We've done our best to make the Component as performant as possible, by deliberately keeping it simple and straight forward. But it's important to understand that the Layout component does come at a cost. Depending on the use case these costs may be negligable, but there are cases where the Layout component might prove to be a bottle-neck. + +The performance costs may always be there to some degree. Whether it's the Layout component that does the complex calculations, or you do it in your custom code. So please feel encouraged to use the ``-tag! + +Some pointers to keep in mind, though: + +- Every change in the dimensions of a child, triggers the Layout component to recalculate and reposition. If your child elements change frequently, the Layout component may have a performance impact. + +- The Layout component support transitions, but beware that when transitioning child dimensions a recalculation happens every frame-tick. If you see frame-drops in your App, you may want to see if the ``-tag has an impact on this. + +- Be mindful to not apply uncessary deep nesting of Layout tags. Each change in a deep child, will trigger recalculations up the tree of Layout tags. + +- When using the ``-tag with a for-loop, it may be more performant to use the `$index` of the for-loop and manually position the elements yourself (i.e. ``). Especially if the dimensions are know before hand and possibly are the same for each child. From 763580f915f1615fe7e6e82e60bb069f1624cab4 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 4 Nov 2024 12:44:52 +0100 Subject: [PATCH 7/9] Fixed typos in layout documentation. --- docs/built-in/layout.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/built-in/layout.md b/docs/built-in/layout.md index 52a84f06..e3c7867d 100644 --- a/docs/built-in/layout.md +++ b/docs/built-in/layout.md @@ -2,7 +2,7 @@ A big portion of building a pixel perfect App is all about correctly positioning Elements and Components on screen. In Lightning and Blits _absolute_ positions are used. This fits the whole concept of building a TV app where the viewport dimensions are known and fixed. Absolute positioning (versus spacing Elements relative to each other) is also more performant. -Imagine a complex design with several nested layers. Relying solely on _relative_ positioning and elements automatically floating as space is available, may lead to heavy and recursive calculations. Then throw some dynamicly changing dimensions and animations in the mix as well, and your App may be starting to noticably drop frames due to expensive re-calculations. +Imagine a complex design with several nested layers. Relying solely on _relative_ positioning and elements automatically floating as space is available, may lead to heavy and recursive calculations. Then throw some dynamically changing dimensions and animations in the mix as well, and your App may be starting to noticeably drop frames due to expensive re-calculations. That being said, there are often cases where it becomes very cumbersome to calculate all the positions manually. Especially when dimensions are not known in advance and elements may load asynchronously. @@ -12,7 +12,7 @@ In order to address the core problem "_how do I easily position a bunch of Eleme ## How to use -Since the Layout-component is a built-in component, there is no need to register it seperately. You can use the ``-tag anywhere in your templates, just as you would use `` and ``. +Since the Layout-component is a built-in component, there is no need to register it separately. You can use the ``-tag anywhere in your templates, just as you would use `` and ``. The Layout component is a wrapper component, that encloses a number of children. All the direct children that are wrapped in a ``-tag are automatically positioned in order, one next to the other. @@ -49,7 +49,7 @@ When the children of the Layout-component have _reactive_ dimensions (i.e. ``-tag, with it's own children in between the children of another Layout-component. The Layout-component itself will grow automatically with the dimensions of it's children. In other words, it's not required to specify a width (`w`) or height (`h`) on the ``-tag itself. +It's also possible to nest layouts. Simply place a new ``-tag, with it's own children in between the children of another Layout-component. The Layout-component itself will grow automatically with the dimensions of it's children. In other words, it's not required to specify a width (`w`) or height (`h`) on the ``-tag itself. And of course you can nest _vertical_ Layouts inside a _horizontal_ one - and vice versa. @@ -61,7 +61,7 @@ The ``-component can also take into account when dimensions of children The Layout-component is a very useful utility, that will make it a lot easier to put together complex layouts. It will reduce the code required, it means less hardcoding of values and removes the need to manually wire up `@loaded` events when working with dynamic / asynchronous elements. -We've done our best to make the Component as performant as possible, by deliberately keeping it simple and straight forward. But it's important to understand that the Layout component does come at a cost. Depending on the use case these costs may be negligable, but there are cases where the Layout component might prove to be a bottle-neck. +We've done our best to make the Component as performant as possible, by deliberately keeping it simple and straight forward. But it's important to understand that the Layout component does come at a cost. Depending on the use case these costs may be negligible, but there are cases where the Layout component might prove to be a bottle-neck. The performance costs may always be there to some degree. Whether it's the Layout component that does the complex calculations, or you do it in your custom code. So please feel encouraged to use the ``-tag! @@ -71,6 +71,6 @@ Some pointers to keep in mind, though: - The Layout component support transitions, but beware that when transitioning child dimensions a recalculation happens every frame-tick. If you see frame-drops in your App, you may want to see if the ``-tag has an impact on this. -- Be mindful to not apply uncessary deep nesting of Layout tags. Each change in a deep child, will trigger recalculations up the tree of Layout tags. +- Be mindful to not apply unnecessary deep nesting of Layout tags. Each change in a deep child, will trigger recalculations up the tree of Layout tags. - When using the ``-tag with a for-loop, it may be more performant to use the `$index` of the for-loop and manually position the elements yourself (i.e. ``). Especially if the dimensions are know before hand and possibly are the same for each child. From c9ee623a7075dafb32b241904d56705bbc1a1d93 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 4 Nov 2024 12:49:54 +0100 Subject: [PATCH 8/9] Bumped version to 1.9.0 and updated changelog. --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0676142..a556cc7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.0 + +_4 nov 2024_ + +- Added Layout component + ## v1.8.3 _31 oct 2024_ diff --git a/package-lock.json b/package-lock.json index fdd3968a..4681276f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lightningjs/blits", - "version": "1.8.3", + "version": "1.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lightningjs/blits", - "version": "1.8.3", + "version": "1.9.0", "license": "Apache-2.0", "dependencies": { "@lightningjs/msdf-generator": "^1.1.0", diff --git a/package.json b/package.json index 7593d48b..92980f80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lightningjs/blits", - "version": "1.8.3", + "version": "1.9.0", "description": "Blits: The Lightning 3 App Development Framework", "bin": "bin/index.js", "exports": { From e54835b07dc32527bf89016aeabb7fadf2da1b99 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 4 Nov 2024 12:59:42 +0100 Subject: [PATCH 9/9] Added links to sidebar. --- docs/_sidebar.md | 1 + docs/sidebar.json | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 16720858..de863210 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -23,6 +23,7 @@ - Built-in components - [Directives](/built-in/directives.md) - [For loop](/built-in/for-loop.md) + - [Layout](/built-in/layout.md) - Plugins - [Text-to-Speech / Announcer](/plugins/text-to-speech-announcer.md) - [Language](/plugins/language.md) diff --git a/docs/sidebar.json b/docs/sidebar.json index 7a766b35..ddf3fc94 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -101,6 +101,10 @@ { "text": "For loop", "link": "/built-in/for-loop" + }, + { + "text": "Layout", + "link": "/built-in/layout.md" } ] },