diff --git a/.eslintrc b/.eslintrc index 09c2e42..f872f23 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,6 @@ { - "extends": "@antfu" + "extends": "@antfu", + "rules": { + "vue/no-deprecated-html-element-is": "off" + } } diff --git a/README.md b/README.md index 31c3aae..00cb588 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,11 @@ pixi version

- ###### Features - 💚 Lightweight and flexible [Vue 3](https://vuejs.org/) library for creating [PixiJS](https://pixijs.com/) applications. - ✏️ Provides a [Custom Vue Renderer](https://vuejs.org/api/custom-renderer.html#custom-renderer-api) that creates PixiJS objects instead of HTML elements. -- 📦 Supports all PixiJS objects, such as `Container`, `Sprite`, `Graphics`, `Text`, etc +- 📦 Supports all PixiJS objects, such as `Filter`, `Container`, `Sprite`, `Graphics`, `Text`, etc - 🧑‍💻 Support specifying `texture` paths in templates to load texture objects - ✨ All [events](https://pixijs.download/release/docs/PIXI.Sprite.html#onclick) emitted by PixiJS objects are supported - 🗃️ Offers a [Assets](#assets) component for bundling assets. @@ -154,6 +153,66 @@ function draw(g: Graphics) { ``` +## Filter + +To use `filter`, you need to place the Filter tag under the `` that sets the filter. Currently, the following filters are supported by default: + +- [BlurFilter](https://pixijs.download/release/docs/PIXI.BlurFilter.html) +- [AlphaFilter](https://pixijs.download/release/docs/PIXI.AlphaFilter.html) +- [DisplacementFilter](https://pixijs.download/release/docs/PIXI.DisplacementFilter.html) +- [ColorMatrixFilter](https://pixijs.download/release/docs/PIXI.ColorMatrixFilter.html) +- [NoiseFilter](https://pixijs.download/release/docs/PIXI.NoiseFilter.html) +- [FXAAFilter](https://pixijs.download/release/docs/PIXI.FXAAFilter.html) + +Example of using `BlurFilter` with a Container: + +```html + + + + + +``` + +### Custom Filter + +To use other filters, you can use the is attribute of ``. For example, to use the `ShockwaveFilter` in [pixi-filters](https://github.com/pixijs/filters): + +```html + + + + + +``` + +## Namespaces + +To avoid conflicts with other tags, such as ``, you can use the `pixi-` prefix or capitalize the first letter with ``. + +```html + +``` + +> Other components also support the `pixi-` prefix, so you can choose according to your personal preference. + ## Assets `vue3-pixi-renderer` provides a special component for bundling resources and obtaining resources from plugins. @@ -236,7 +295,6 @@ onMounted(() => { ``` - ## Creating an pixi application manually Using the custom renderer inside `vue3-pixi-renderer` diff --git a/playground/src/components/CursorSprite.vue b/playground/src/components/CursorSprite.vue index 549c3b7..52c9ad8 100644 --- a/playground/src/components/CursorSprite.vue +++ b/playground/src/components/CursorSprite.vue @@ -1,15 +1,8 @@ - diff --git a/src/elements/FXAAFilter.ts b/src/elements/FXAAFilter.ts new file mode 100644 index 0000000..e5d05d6 --- /dev/null +++ b/src/elements/FXAAFilter.ts @@ -0,0 +1,31 @@ +import type { + ComponentCustomProps, + ComponentOptionsMixin, + DefineComponent, + VNodeProps, +} from 'vue-demi' +import type { FXAAFilter } from 'pixi.js' +import type { AllowedFilterProps, ExtractFilterProps } from './props' + +interface Props extends ExtractFilterProps {} + +interface Events {} + +export type PixiFXAAFilterComponent = DefineComponent< + Props, + {}, + unknown, + {}, + {}, + ComponentOptionsMixin, + ComponentOptionsMixin, + (keyof Events)[], + keyof Events, + VNodeProps & AllowedFilterProps & ComponentCustomProps, + Readonly & { + [key in keyof Events as `on${Capitalize}`]?: + | ((...args: Events[key]) => any) + | undefined; + }, + {} +> diff --git a/src/elements/alphaFilter.ts b/src/elements/alphaFilter.ts new file mode 100644 index 0000000..0493fd4 --- /dev/null +++ b/src/elements/alphaFilter.ts @@ -0,0 +1,33 @@ +import type { + ComponentCustomProps, + ComponentOptionsMixin, + DefineComponent, + VNodeProps, +} from 'vue-demi' +import type { AlphaFilter } from 'pixi.js' +import type { AllowedFilterProps, ExtractFilterProps } from './props' + +interface Props extends ExtractFilterProps { + +} + +interface Events {} + +export type PixiAlphaFilterComponent = DefineComponent< + Props, + {}, + unknown, + {}, + {}, + ComponentOptionsMixin, + ComponentOptionsMixin, + (keyof Events)[], + keyof Events, + VNodeProps & AllowedFilterProps & ComponentCustomProps, + Readonly & { + [key in keyof Events as `on${Capitalize}`]?: + | ((...args: Events[key]) => any) + | undefined; + }, + {} +> diff --git a/src/elements/blurFilter.ts b/src/elements/blurFilter.ts new file mode 100644 index 0000000..2956d1f --- /dev/null +++ b/src/elements/blurFilter.ts @@ -0,0 +1,35 @@ +import type { + ComponentCustomProps, + ComponentOptionsMixin, + DefineComponent, + VNodeProps, +} from 'vue-demi' +import type { BlurFilter } from 'pixi.js' +import type { AllowedFilterProps, ExtractFilterProps } from './props' + +interface Props extends ExtractFilterProps { + strength?: number + resolution?: number + kernelSize?: number +} + +interface Events {} + +export type PixiBlurFilterComponent = DefineComponent< + Props, + {}, + unknown, + {}, + {}, + ComponentOptionsMixin, + ComponentOptionsMixin, + (keyof Events)[], + keyof Events, + VNodeProps & AllowedFilterProps & ComponentCustomProps, + Readonly & { + [key in keyof Events as `on${Capitalize}`]?: + | ((...args: Events[key]) => any) + | undefined; + }, + {} +> diff --git a/src/elements/colorMatrixFilter.ts b/src/elements/colorMatrixFilter.ts new file mode 100644 index 0000000..e44b7a3 --- /dev/null +++ b/src/elements/colorMatrixFilter.ts @@ -0,0 +1,31 @@ +import type { + ComponentCustomProps, + ComponentOptionsMixin, + DefineComponent, + VNodeProps, +} from 'vue-demi' +import type { ColorMatrixFilter } from 'pixi.js' +import type { AllowedFilterProps, ExtractFilterProps } from './props' + +interface Props extends ExtractFilterProps {} + +interface Events {} + +export type PixiColorMatrixFilterComponent = DefineComponent< + Props, + {}, + unknown, + {}, + {}, + ComponentOptionsMixin, + ComponentOptionsMixin, + (keyof Events)[], + keyof Events, + VNodeProps & AllowedFilterProps & ComponentCustomProps, + Readonly & { + [key in keyof Events as `on${Capitalize}`]?: + | ((...args: Events[key]) => any) + | undefined; + }, + {} +> diff --git a/src/elements/displacementFilter.ts b/src/elements/displacementFilter.ts new file mode 100644 index 0000000..35bae4d --- /dev/null +++ b/src/elements/displacementFilter.ts @@ -0,0 +1,33 @@ +import type { + ComponentCustomProps, + ComponentOptionsMixin, + DefineComponent, + VNodeProps, +} from 'vue-demi' +import type { DisplacementFilter, ISpriteMaskTarget } from 'pixi.js' +import type { AllowedFilterProps, ExtractFilterProps } from './props' + +interface Props extends ExtractFilterProps { + sprite: ISpriteMaskTarget +} + +interface Events {} + +export type PixiDisplacementFilterComponent = DefineComponent< + Props, + {}, + unknown, + {}, + {}, + ComponentOptionsMixin, + ComponentOptionsMixin, + (keyof Events)[], + keyof Events, + VNodeProps & AllowedFilterProps & ComponentCustomProps, + Readonly & { + [key in keyof Events as `on${Capitalize}`]?: + | ((...args: Events[key]) => any) + | undefined; + }, + {} +> diff --git a/src/elements/filter.ts b/src/elements/filter.ts new file mode 100644 index 0000000..8e2f236 --- /dev/null +++ b/src/elements/filter.ts @@ -0,0 +1,34 @@ +import type { + ComponentCustomProps, + ComponentOptionsMixin, + DefineComponent, + VNodeProps, +} from 'vue-demi' +import type { AllowedFilterProps } from './props' + +interface Props { + +} + +interface Events { + +} + +export type PixiFilterComponent = DefineComponent< + Props, + {}, + unknown, + {}, + {}, + ComponentOptionsMixin, + ComponentOptionsMixin, + (keyof Events)[], + keyof Events, + VNodeProps & AllowedFilterProps & ComponentCustomProps, + Readonly & { + [key in keyof Events as `on${Capitalize}`]?: + | ((...args: Events[key]) => any) + | undefined; + }, + {} +> diff --git a/src/elements/props/index.ts b/src/elements/props/index.ts index 80513a5..4dceb5c 100644 --- a/src/elements/props/index.ts +++ b/src/elements/props/index.ts @@ -63,3 +63,9 @@ export interface AllowedPixiProps { zIndex?: number } + +export interface AllowedFilterProps extends Partial> { + is?: (props: any) => Filter +} + +export type ExtractFilterProps = Partial> diff --git a/src/global.ts b/src/global.ts index 24e1e9c..bd03dab 100644 --- a/src/global.ts +++ b/src/global.ts @@ -1,3 +1,4 @@ +import '@vue/runtime-core' import type { PixiSimpleRopeComponent } from './elements/simpleRope' import type { PixiSimplePlaneComponent } from './elements/simplePlane' import type { PixiNineSlicePlaneComponent } from './elements/nineSlicePlane' @@ -9,11 +10,33 @@ import type { PixiGraphicsComponent } from './elements/graphics' import type { PixiContainerComponent } from './elements/container' import type { PixiSpriteComponent } from './elements/sprite' import type { PixiBitmapTextComponent } from './elements/bitmapText' - -import '@vue/runtime-core' +import type { PixiFilterComponent } from './elements/filter' +import type { PixiBlurFilterComponent } from './elements/blurFilter' +import type { PixiAlphaFilterComponent } from './elements/alphaFilter' +import type { PixiDisplacementFilterComponent } from './elements/displacementFilter' +import type { PixiColorMatrixFilterComponent } from './elements/colorMatrixFilter' +import type { PixiFXAAFilterComponent } from './elements/FXAAFilter' declare module '@vue/runtime-core' { export interface GlobalComponents { + Filter: PixiFilterComponent + PixiFilter: PixiFilterComponent + + BlurFilter: PixiBlurFilterComponent + PixiBlurFilter: PixiBlurFilterComponent + + AlphaFilter: PixiAlphaFilterComponent + PixiAlphaFilter: PixiAlphaFilterComponent + + DisplacementFilter: PixiDisplacementFilterComponent + PixiDisplacementFilter: PixiDisplacementFilterComponent + + ColorMatrixFilter: PixiColorMatrixFilterComponent + PixiColorMatrixFilter: PixiColorMatrixFilterComponent + + FXAAFilter: PixiFXAAFilterComponent + PixiFXAAFilter: PixiFXAAFilterComponent + Container: PixiContainerComponent PixiContainer: PixiContainerComponent diff --git a/src/renderer/elements.ts b/src/renderer/elements.ts new file mode 100644 index 0000000..f3e6efa --- /dev/null +++ b/src/renderer/elements.ts @@ -0,0 +1,86 @@ +import type { VNodeProps } from 'vue-demi' +import { + AlphaFilter, + AnimatedSprite, + BitmapText, + BlurFilter, + ColorMatrixFilter, + Container, + DisplacementFilter, + FXAAFilter, + Graphics, + Mesh, + NineSlicePlane, + NoiseFilter, + SimpleMesh, + SimplePlane, + SimpleRope, + Sprite, + Text, + TilingSprite, +} from 'pixi.js' +import { normalizeTexture } from './utils' + +export type PixiCustomElement = (props: (VNodeProps & { [key: string]: any })) => any + +export const elements: Record = { + Container: () => new Container(), + Sprite: () => new Sprite(), + SimpleMesh: () => new SimpleMesh(), + Graphics: props => new Graphics(props?.geometry), + Text: props => new Text( + props.text, + props.style, + props.canvas, + ), + BitmapText: props => new BitmapText( + props.text, + props.style, + ), + TilingSprite: props => new TilingSprite( + normalizeTexture(props!.texture), + props.width, + props.height, + ), + AnimatedSprite: props => new AnimatedSprite( + props.textures, + props.autoUpdate, + ), + Mesh: props => new Mesh( + props.geometry, + props.shader, + props.state, + props.drawMode, + ), + NineSlicePlane: props => new NineSlicePlane( + normalizeTexture(props.texture), + ), + SimplePlane: props => new SimplePlane( + normalizeTexture(props.texture), + props.verticesX, + props.verticesY, + ), + SimpleRope: props => new SimpleRope( + normalizeTexture(props.texture), + props.points, + props.textureScale, + ), + BlurFilter: props => new BlurFilter( + props.strength, + props.quality, + props.resolution, + props.kernelSize, + ), + AlphaFilter: props => new AlphaFilter(props.alpha), + DisplacementFilter: props => new DisplacementFilter( + props.sprite, + props.scale, + ), + ColorMatrixFilter: () => new ColorMatrixFilter(), + NoiseFilter: props => new NoiseFilter( + props.noise, + props.seed, + ), + FXAAFilter: () => new FXAAFilter(), +} + diff --git a/src/renderer/index.ts b/src/renderer/index.ts index e05075a..4ae994f 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -1,61 +1,30 @@ import { camelize, createRenderer, warn } from 'vue-demi' import { - AnimatedSprite, - BitmapText, Container, - Graphics, - Mesh, - NineSlicePlane, - SimpleMesh, - SimplePlane, - SimpleRope, - Sprite, + Filter, Text, - TilingSprite, } from 'pixi.js' -import { patchProp } from './props' +import { patchProp } from './patch' +import { elements } from './elements' +import { isCustomFilter } from './utils' interface CreatePixiRendererOptions { prefix?: string } -const elements = { - Container, - Sprite, - Graphics, - Text, - BitmapText, - TilingSprite, - AnimatedSprite, - Mesh, - NineSlicePlane, - SimpleMesh, - SimplePlane, - SimpleRope, -} as Record Container> - export function createPixiRenderer(options: CreatePixiRendererOptions = {}) { const { prefix = 'pixi' } = options return createRenderer({ createElement: (name, _, __, props) => { - const Constructor = findConstructor(prefix, name) + const element = isCustomFilter(prefix, name) + ? props?.is?.(props) + : createPixiElement(prefix, name, props) - if (!Constructor) { - warn(`Unknown element ${name}`) - return new Container() - } + if (element instanceof Container) + element.filters = [] - switch (Constructor) { - case Graphics: - return new Constructor(props?.geometry) - case Text: - return new Constructor(props?.text, props?.style, props?.canvas) - case BitmapText: - return new Constructor(props?.text, props?.style) - default: - return new Constructor() - } + return element }, patchProp, @@ -65,16 +34,16 @@ export function createPixiRenderer(options: CreatePixiRendererOptions = {}) { createComment: () => new Container(), remove: child => child.destroy(), insert: (child, parent, anchor) => { - if (anchor) - parent.addChildAt(child, parent.getChildIndex(anchor)) + if (child instanceof Filter) + insertFilter(child, parent, anchor) else - parent.addChild(child) + insertContainer(child, parent, anchor) }, nextSibling: (node) => { - const index = node.parent.getChildIndex(node) - if (node.parent.children.length <= index + 1) - return null - return node.parent.getChildAt(index + 1) as Container ?? null + if (node instanceof Filter) + return nextSiblingFilter(node) + else + return nextSiblingContainer(node) }, setElementText: (node, text) => { node instanceof Text @@ -87,18 +56,54 @@ export function createPixiRenderer(options: CreatePixiRendererOptions = {}) { }) } -function findConstructor(prefix: string, name: string) { - let c +function createPixiElement(prefix: string, name: string, props: any = {}) { + let is if (name.startsWith(prefix)) { name = camelize(name) - c = elements[name.slice(prefix.length)] + is = elements[name.slice(prefix.length)] } else { name = camelize(name) name = name.charAt(0).toUpperCase() + name.slice(1) - c = elements[name] + is = elements[name] + } + if (!is) { + warn(`Unknown element ${name}`) + return new Container() + } + return is(props) +} + +function insertContainer(child: Container, parent: Container, anchor?: Container | null) { + if (anchor) + parent.addChildAt(child, parent.getChildIndex(anchor)) + else + parent.addChild(child) +} + +function insertFilter(child: any, parent: Container, _anchor: any) { + function remove() { + const index = parent.filters!.indexOf(child) + if (index !== -1) + parent.filters?.splice(index, 1) } - return c as undefined | (new (...args: any) => Container) + child.parent = parent + child.destroy = remove + parent.filters!.push(child) +} + +function nextSiblingFilter(node: any) { + const index = node.parent.filters!.indexOf(node) + if (node.parent.filters!.length <= index + 1) + return null + return node +} + +function nextSiblingContainer(node: Container) { + const index = node.parent.getChildIndex(node) + if (node.parent.children.length <= index + 1) + return null + return node.parent.getChildAt(index + 1) as Container ?? null } export const { createApp, render } = createPixiRenderer() diff --git a/src/renderer/props.ts b/src/renderer/patch.ts similarity index 97% rename from src/renderer/props.ts rename to src/renderer/patch.ts index 6a87c56..6d68cdf 100644 --- a/src/renderer/props.ts +++ b/src/renderer/patch.ts @@ -8,10 +8,10 @@ import { Mesh, SimplePlane, Sprite, - Texture, TilingSprite, } from 'pixi.js' import { setPointNumber, setPointObject, setValueProp } from './setter' +import { normalizeTexture } from './utils' const defaultBooleanProps = ['accessible', 'cullable', 'renderable', 'visible'] as const const bitmapBooleanProps = ['dirty', 'roundPixels'] as const @@ -194,9 +194,3 @@ export function patchBooleanProps( } return false } - -export function normalizeTexture(value: Texture | string): Texture { - if (typeof value === 'string') - return Texture.from(value) - return value -} diff --git a/src/renderer/utils.ts b/src/renderer/utils.ts new file mode 100644 index 0000000..954dfec --- /dev/null +++ b/src/renderer/utils.ts @@ -0,0 +1,17 @@ +import { Texture } from 'pixi.js' +import { camelize } from 'vue-demi' + +export function normalizeTexture(value: Texture | string): Texture { + if (typeof value === 'string') + return Texture.from(value) + return value +} + +export function isCustomFilter(prefix: string, name: string) { + const isPrefix = name.startsWith(prefix) + name = camelize(name) + name = name.charAt(0).toUpperCase() + name.slice(1) + return ( + isPrefix && name.slice(prefix.length) === 'Filter' + ) || name === 'Filter' +}