2.0.3 • Published 12 days ago

@md5crypt/layout-pixi v2.0.3

Weekly downloads
-
License
MIT
Repository
github
Last release
12 days ago

PIXI layout engine

This package builds on top of @md5crypt/layout to provide a layout engine for PIXI.js. Reading that package's docs is recommended before starting doing anything with this one.

Layout elements

The following layout elements are available:

typeclassdescription
containerContainerElementcontainer for other elements
graphicGraphicElementallows drawing shapes with PIXI.Graphics
spriteSpriteElementfor displaying textures
sprite-slicedSlicedSpriteElementa 9-slice mesh element
sprite-tilingTiledSpriteElementallows drawing repeated backgrounds
textTextElementrenders text using PIXI.Text
-RootElementmeant to be used as the layout root element

BaseElement

abstract class BaseElement extends LayoutElement

Configuration reference

filedtypedefaultdescription
maskbooleanfalsewhen set to true a PIXI mask is used to clip the element's content to it's size.
sortedbooleanfalseenables z-index sorting of children for this container. Note that z-index sorting works only for direct children
zIndexnumber0z-index value of the element, only meangfull if parent has z-index sorting enabled
alphanumber1opacity of the element
rotationnumber0rotation around the center of the element, value in degrees
flippedfalse "vertical" "horizontal"falseshould the element be mirrored vertically / horizontally
interactivebooleanfalseenables interaction events on the underling PIXI object
noPropagationbooleanfalsesets interactiveChildren to false on the underling PIXI object disabling interaction event propagation
anchornumber [number, number]0sets the anchor point (0.5 being the center) used for element positioning. If a single number is provided it is used for both axis. This not the same as PIXI's anchor property its used only for positioning, the pivot point used for rotation and scaling is set always to the element's center.

Properties reference

filedtypedescription
handlePIXI.DisplayObject(readonly) a reference to the underlying PIXI object that this element is using.
sortedbooleansee sorted in configuration
zIndexnumbersee zIndex in configuration
alphanumbersee alpha in configuration
rotationnumbersee rotation in configuration
flippedfalse "vertical" "horizontal"see flipped in configuration
interactivebooleansee interactive in configuration
noPropagationbooleansee noPropagation in configuration
maskbooleansee mask in configuration
anchor[number, number]see anchor in configuration
scalenumber(readonly) the scale of the current element
globalScalenumber(readonly) the global scale of the current element (relative to the layout's root element)
globalBoundingBox{top: number, left: number, width: number, height: number}(readonly) the global position of the object (relative to the layout's root element)

Function reference

namesignaturedescription
setAnchor(x: number, y?: number) => voidan alternative way to set the anchor property
on(event: string, callback: Function) => voidpassthrough to the underlying PIXI object's on function.

ContainerElement (container)

class ContainerElement extends BaseElement

Configuration reference

filedtypedefaultdescription
scaleboolean1The element's scale. The scale does not change the element's dimensions, it's applied after the layout has been computed.

Properties reference

filedtypedescription
handlePIXI.Container(readonly) reference to the underlying PIXI object.
scalebooleansee scale in configuration

GraphicElement (graphic)

class GraphicElement extends BaseElement

Configuration reference

filedtypedefaultdescription
onDraw(self: GraphicElement) => voidundefinedA callback called every time the objects needs to be redrawn. Clear is called automatically before calling onDraw.

Properties reference

filedtypedescription
handlePIXI.GraphicElement(readonly) reference to the underlying PIXI object.
onDraw(self: GraphicElement) => voidsee onDraw in configuration

SpriteElement (sprite)

class SpriteElement extends BaseElement

Configuration reference

filedtypedefaultdescription
imagestring PIXI.Texture nullnullTexture the element should use, see the LayoutFactory documentation below to see how string value are resolved. When null Texture.WHITE is used.
scalingScalingType"none"controls how the texture is scaled to fit the layout element. See ScalingType description in the section below.
verticalAlign"top" "middle" "bottom""top"controls how the texture should be positioned inside the container.
horizontalAlign"left" "center" "right""left"controls how the texture should be positioned inside the container.
tintnumber0xFFFFFFtint applied to the texture

Properties reference

filedtypedescription
handlePIXI.Sprite(readonly) reference to the underlying PIXI object.
imagestring PIXI.Texture nullsee image in configuration
scalingScalingTypesee scaling in configuration
verticalAlign"top" "middle" "bottom"see verticalAlign in configuration
horizontalAlign"left" "center" "right"see horizontalAlign in configuration
tintnumbersee tint in configuration

Texture scaling modes

modedescription
nonetexture is positioned inside the element based on verticalAlign and horizontalAlign and no additional transformations are applied
clippedsame as none but the texture is cropped to the element size after positioning. The same can be achieved using mask = true but the scaling mode is more efficient as it does not interrupt batch rendering with masks.
stretchstretch the texture to fit the element, verticalAlign and horizontalAlign are ignored
containscale the image maintaining aspect ratio in such a way that the entire image is visible. verticalAlign and horizontalAlign are used to position the texture.
coverscale and crop the image maintaining the aspect ratio in such a way that the entire element is filled. verticalAlign and horizontalAlign are used to position the texture before cropping.

SlicedSpriteElement (sprite-sliced)

class SlicedSpriteElement extends BaseElement

Configuration reference

filedtypedefaultdescription
imagestring PIXI.Texture nullnullTexture the element should use, see the LayoutFactory documentation below to see how string value are resolved. When null Texture.WHITE is used.
tintnumber0xFFFFFFtint applied to the texture
slicesPositioningBox0the slicing regions, uses PositioningBox from @md5crypt/layout.

Properties reference

filedtypedescription
handlePIXI.NineSlicePlane(readonly) reference to the underlying PIXI object.
imagestring PIXI.Texture nullsee image in configuration
tintnumbersee tint in configuration

Function reference

namesignaturedescription
setSlices(slices: PositioningBox) => voidallows updating the slicing regions

TiledSpriteElement (sprite-tiled)

class TiledSpriteElement extends BaseElement

Configuration reference

filedtypedefaultdescription
imagestring PIXI.Texture nullnullTexture the element should use, see the LayoutFactory documentation below to see how string value are resolved. When null Texture.WHITE is used.
tintnumber0xFFFFFFtint applied to the texture

Properties reference

filedtypedescription
handlePIXI.TilingSprite(readonly) reference to the underlying PIXI object.
imagestring PIXI.Texture nullsee image in configuration
tintnumbersee tint in configuration

TextElement (text)

class TextElement extends BaseElement

Configuration reference

filedtypedefaultdescription
textstring""Text to render.
fitbooleantrueShould the text be shrank to fit the element. See text fitting section below for more details.
verticalAlign"top" "bottom" "middle""top"controls how the text is positioned inside the element. For horizontal control set text align in text style.
stylePIXI.ITextStyle{}The text style to use for the text.
resolutionnumber1the resolution the text should be rendered at. The actual text resolution is computed based on the element's global scale, this value multiplies that value allowing for oversampling.
roundPixelsbooleanfalsesets roundPixels in the underlying PIXI object. In theory this can improve text readability.

Properties reference

filedtypedescription
handlePIXI.Text(readonly) reference to the underlying PIXI object.
textstringsee text in configuration
fitbooleansee fit in configuration
verticalAlign"top" "bottom" "middle"see verticalAlign in configuration
resolutionnumbersee resolution in configuration
roundPixelsbooleansee roundPixels in configuration

Function reference

namesignaturedescription
setStyle(style: Partial<ITextStyle>) => voidsets the text style.
updateStyle(style: Partial<ITextStyle>) => voidupdates the current style by merging it with the provided object.
setTextsetText(text: string, style?: Partial<ITextStyle>) => voidA convenience function that allows setting the text and style at the same time.

Text fitting

If text fitting is enabled the text font size will be automatically decreased to fit the element size. Text fitting will never increase the configured font size it will only decrease it.

Text fitting operates in two modes:

  • If word wrap is disabled, a single iteration is made to compute the target font size, as no re-flow is needed.
  • If word wrap is enabled, a binary search is executed to find the target font size. The search is capped at 8 iterations.

RootElement (root)

class RootElement extends BaseElement

The RootElement class is meant to be used as the layout root. It can not be crated via the factory and is meant to be crated by its constructor.

RootElement has a pre-configured layout config of volatile = true, a hardcoded name @root and type equal to root.

Note that RootElement does not have to be used as the root element. For example a ContainerElement will do just as well.

Properties reference

filedtypedescription
handlePIXI.Container(readonly) reference to the underlying PIXI object.
scalebooleansets the base scale for the entire layout

Function reference

namesignaturedescription
constructor(factory: LayoutFactory, config?: BaseConfig) => RootElementthe intended way of creating a RootElement instance

Layout factory

The layout factory shipped with this package extends the LayoutFactory from @md5crypt/layout with a few additional features.

Default values

Default config / layout values can be set for created elements. The defaults can be set for all elements or for specific element types individually. This can be done by calling setDefaults on a factory instance.

Texture resolver

Layout elements using textures allow them to be reference via string names. These string names will be resolved using the factories resolveAsset function, which by default will fail with an exception.

To use this feature a onResolveAsset callback must be set that accepts string names and returns texture instances.

Properties reference

filedtypedescription
onResolveAsset(key: string) => Texturethe texture resolver callback used by resolveAsset

Function reference

namesignaturedescription
setDefaults(defaults: ElementDefaults): voidsets default values for all created elements.
setDefaults(type: string, defaults: ElementDefaults): voidsets default values for created elements of a given type.

Using the module

There are two ways to use this library. The simplest one is to just import layoutFactory from @md5crypt/layout-pixi. This will create a default LayoutFactory instance with all the element types registered.

For some applications this can be unwanted as it will bloat the output code with all the element implementations and their underlying PIXI objects.

To avoid this, the individual elements can be imported one by one and registered to a custom factory instance. For example, if all we need is the container and sprite elements, we can do the following:

import ContainerElement from "@md5crypt/layout-pixi/ContainerElement"
import SpriteElement from "@md5crypt/layout-pixi/SpriteElement"
import LayoutFactory from "@md5crypt/layout-pixi/LayoutFactory"

const layoutFactory = new LayoutFactory()

ContainerElement.register(layoutFactory)
SpriteElement.register(layoutFactory)

Using JSX for writing layouts

JSX bindings are provided as an alternative way for writing the layout configurations.

To use the provided JSX bindings where intended to be used with typescript (tsconfig.json should have jsx set to react).

To use the bindings simply cerate a .tsx file and add the following import:

import React from "@md5crypt/layout-pixi/JSXSupport"

JSX.Element is assignable to LayoutElementJSON and can be passed directly to the layout factory.

One gotcha to watch out for is a top level React.Fragment which will result with a element of type jsx-fragment that the factory will refuse to create. To solve this React.toArray can be used. For more details about this function see the reference section below.

Basic syntax

The tag names are mapped to element types, so <container/> will be compiled into {type: "container"}.

All keys from layout and config objects are merged into a single property namespace, together with name and metadata.

So <sprite name="hello" width={100} image="foo" /> will be compiled into

{
    "type": "sprite",
    "name": "hello",
    "layout": {
        "width": 100
    },
    "config": {
        "image": "foo"
    }
}

This (obviously) means that config and layout keys can not overlap.

Components

Stateless function components are supported, see example below:

const Foo = (props: {name: string, children?: React.ReactNode}) => (
    <container name={props.name}>
        {props.children}
    </container>
)

<Foo name="bar">
    <sprite image="foo-bar" />
</Foo>

This will be compiled to:

{
    "type": "container",
    "name": "bar",
    "children": [
        {
            "type": "sprite",
            "config": {
                "image": "foo-bar"
            }
        }
    ]
}

Slots

As a bonus basic slot support was added, see example below:

const Foo = (props: {children?: React.ReactNode}, slots: React.Slots<"bar">) => (
    <container name={props.name}>
        {slots.bar}
        <container>
            {props.children}
        </container>
    </container>
)

<Foo>
    <React.Slot name="bar">
        <sprite image="rab-oof" />
    </React.Slot>
    <sprite image="foo-bar" />
</Foo>

This will be compiled to:

{
    "type": "container",
    "children": [
        {
            "type": "container",
            "children": [
                {
                    "type": "sprite",
                    "config": {
                        "image": "rab-oof"
                    }
                }
            ]
        },
        {
            "type": "sprite",
            "config": {
                "image": "foo-bar"
            }
        }
    ]
}

Function reference

namesignaturedescription
React.Fragment(props: {children?: ReactNode}) => JSX.ElementThe build-in Fragment component
React.Slot(props: {name: string, children?: ReactNode}) => JSX.ElementThe build-in Slot Component
React.isFragment(element: JSX.Element) => booleanreturns true if the passed element is a top-level fragment element
React.toArray(element: JSX.Element) => JSX.Element[]for a top-level fragment will return its children. For other elements will return the same element by wrapped in an array. This function is needed to unpack a top-level React.Fragment as layout factory will refuse to render it.
React.createElement(type, props, ...children) => JSX.Elementthe internal function JSX gets compiled into

Type reference

namedescription
React.ReactNodeType to use for JSX children
React.Slots<T>Type to use for the slot parameter, T should be a union of literal strings that will be used as keys.
React.ComponentProps<T>Gets the type of properties of the given component. Works for intrinsic elements (like "sprite") as well as for user defined function components.

Implementing new elements

New element types can be easily added outside the module's code. Let's use the following example implementation of an simple AnimatedSpriteElement to explain the process.

import {
    // our new element will extend BaseElement so it needs to be imported
    BaseElement,

    // the new element's config will extend BaseElement config
    BaseConfig,

    // this is the type of BaseElement's constructor parameter
    BaseConstructorProperties
} from "@md5crypt/layout-pixi/BaseElement"

// we need that type for the register function
import type LayoutFactory from "@md5crypt/layout-pixi/LayoutFactory"

// PIXI stuff that will be needed
import { Texture } from "@pixi/core"
import { AnimatedSprite } from "@pixi/sprite-animated"

// Here we define the element's config properties that will be available in LayoutElementJSON
export interface AnimatedSpriteElementConfig extends BaseConfig {
    images?: (Texture | string)[]
    playing?: boolean
}

export class AnimatedSpriteElement extends BaseElement {
    // we must override handle type to match the PIXI object that this element will use
    declare public handle: AnimatedSprite

    // the static register function that is used to add the element to the layout factory
    public static register(layoutFactory: LayoutFactory) {
        layoutFactory.register("sprite-animated", (factory, name, config) => new this({
            factory,
            name,
            config,
            type: "sprite-animated",
            // the PIXI object instance this element will be using
            handle: new AnimatedSprite([])
        }))
    }

    constructor(props: BaseConstructorProperties<AnimatedSpriteElementConfig>) {
        super(props)
    
        // BaseElement expects all PIXI object's to anchored at the center
        this.handle.anchor.set(0.5, 0.5)
        const config = props.config

        // apply config (if provided)
        if (config) {
            if (config.images) {
                this.images = config.images
            }
            if (config.playing) {
                this.handle.play()
            }
        }
    }

    public set images(value: (Texture | null | string)[]) {
        // here we resolve the string names to Textures using factory.resolveAsset
        this.handle.textures = value.map(x => this.factory.resolveAsset(x))

        // changing the texture can change the element dimensions so we must notify
        // the underlying LayoutElement that its layout must be recalculated
        this.setDirty()
    }

    public get playing() {
        return this.handle.playing
    }

    public set playing(value: boolean) {
        if (value) {
            this.handle.play()
        } else {
            this.handle.stop()
        }
    }

    // this is called every time the layout has changed
    protected onUpdate() {        
        super.onUpdate()

        // we must set the PIXI object's position
        // in most cases the computedLeft / computedTop helper properties from BaseElement can be used
        this.handle.position.set(this.computedLeft, this.computedTop)

        // no fancy scaling options, just stretch the sprite to size
        this.handle.width = this.innerWidth
        this.handle.height = this.innerHeight
    }

    // we must override contentHeight and contentWidth to let the layout know what size
    // is this element's content (in this case the texture dimensions)

    public get contentHeight() {
        return (this.handle.textures[0] as Texture).height
    }

    public get contentWidth() {
        return (this.handle.textures[0] as Texture).width
    }
}

export default AnimatedSpriteElement

// we must inject the new element to the type system by adding
// it to a special interface declared inside the layout-pixi package
declare module "@md5crypt/layout-pixi/ElementTypes" {
    export interface ElementTypes {
        "sprite-animated": {
            config: AnimatedSpriteElementConfig,
            element: AnimatedSpriteElement
        }
    }
}

Remember that the created AnimatedSpriteElement class must be registered in a LayoutFactory instance using AnimatedSpriteElement.register.

If the element type was correctly injected into ElementTypes LayoutElementJSON and JSX elements should automatically recognize the new element type.

2.0.3

12 days ago

2.0.2

29 days ago

2.0.1

2 months ago

2.0.0

3 months ago

1.4.9

4 months ago

1.4.6

6 months ago

1.4.5

8 months ago

1.4.4

9 months ago

1.4.3

10 months ago

1.4.2

10 months ago

1.4.1

10 months ago

1.4.8

6 months ago

1.4.7

6 months ago

1.4.0

11 months ago

1.3.7

1 year ago

1.3.6

1 year ago

1.3.8

1 year ago

1.3.5

1 year ago

1.3.4

2 years ago

1.3.3

2 years ago

1.3.2

2 years ago

1.3.1

2 years ago

1.2.0

2 years ago

1.3.0

2 years ago

1.1.11

2 years ago

1.1.10

2 years ago

1.1.9

2 years ago

1.1.8

2 years ago

1.1.7

2 years ago

1.1.6

2 years ago

1.1.5

2 years ago

1.1.4

3 years ago

1.1.3

3 years ago

1.1.2

3 years ago

1.1.1

3 years ago

1.1.0

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago