├── test ├── .npmignore ├── UIProject │ ├── .npmignore │ ├── assets │ │ └── Package1 │ │ │ ├── logo128.png │ │ │ ├── package.xml │ │ │ └── Main.xml │ ├── FairyGUI-threejs-test.fairy │ └── settings │ │ └── Publish.json ├── assets │ └── UI │ │ ├── Package1.fui │ │ └── Package1_atlas0.png ├── tsconfig.json ├── template.html ├── src │ └── index.ts └── webpack.config.js ├── .vscode ├── settings.json └── tasks.json ├── .gitignore ├── images └── 20200610-084916.png ├── src ├── ui │ ├── GLoader3D.ts │ ├── Margin.ts │ ├── GRichTextField.ts │ ├── GObjectPool.ts │ ├── PackageItem.ts │ ├── DragDropManager.ts │ ├── GTextInput.ts │ ├── UIConfig.ts │ ├── UIContentScaler.ts │ ├── FieldTypes.ts │ ├── GGraph.ts │ ├── GImage.ts │ ├── GMovieClip.ts │ ├── UIObjectFactory.ts │ ├── Relations.ts │ └── GScrollBar.ts ├── core │ ├── mesh │ │ ├── MeshFactory.ts │ │ ├── CompositeMesh.ts │ │ ├── RectMesh.ts │ │ ├── RegularPolygonMesh.ts │ │ └── RoundedRectMesh.ts │ ├── hittest │ │ ├── IHitTest.ts │ │ ├── ShapeHitTest.ts │ │ └── PixelHitTest.ts │ ├── text │ │ ├── BaseFont.ts │ │ ├── FontManager.ts │ │ ├── TextFormat.ts │ │ ├── SelectionShape.ts │ │ ├── RichTextField.ts │ │ └── BitmapFont.ts │ ├── NTexture.ts │ ├── Shape.ts │ └── NMaterial.ts ├── utils │ ├── Color.ts │ ├── html │ │ ├── IHtmlPageContext.ts │ │ ├── HtmlParseOptions.ts │ │ ├── IHtmlObject.ts │ │ ├── HtmlPageContext.ts │ │ ├── HtmlLink.ts │ │ ├── HtmlImage.ts │ │ └── HtmlElement.ts │ ├── Pool.ts │ ├── ToolSet.ts │ ├── Rect.ts │ ├── Timers.ts │ ├── xml │ │ ├── XML.ts │ │ └── XMLUtils.ts │ ├── ColorMatrix.ts │ ├── ByteBuffer.ts │ └── UBBParser.ts ├── tween │ ├── EaseType.ts │ ├── TweenValue.ts │ ├── GTween.ts │ ├── GPathPoint.ts │ └── TweenManager.ts ├── gears │ ├── GearDisplay2.ts │ ├── GearIcon.ts │ ├── GearText.ts │ ├── GearDisplay.ts │ ├── GearFontSize.ts │ ├── GearAnimation.ts │ ├── GearColor.ts │ ├── GearBase.ts │ ├── GearLook.ts │ ├── GearXY.ts │ └── GearSize.ts ├── action │ ├── ControllerAction.ts │ ├── PlayTransitionAction.ts │ └── ChangePageAction.ts ├── FairyGUI.ts └── event │ ├── Event.ts │ └── EventDispatcher.ts ├── tsconfig.json ├── LICENSE ├── package.json ├── gulpfile.js └── README.md /test/.npmignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /test/UIProject/.npmignore: -------------------------------------------------------------------------------- 1 | .objs -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .objs 4 | build 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /images/20200610-084916.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairygui/FairyGUI-threejs/HEAD/images/20200610-084916.png -------------------------------------------------------------------------------- /src/ui/GLoader3D.ts: -------------------------------------------------------------------------------- 1 | import { GObject } from "./GObject"; 2 | 3 | export class GLoader3D extends GObject 4 | { 5 | } -------------------------------------------------------------------------------- /test/assets/UI/Package1.fui: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairygui/FairyGUI-threejs/HEAD/test/assets/UI/Package1.fui -------------------------------------------------------------------------------- /test/assets/UI/Package1_atlas0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairygui/FairyGUI-threejs/HEAD/test/assets/UI/Package1_atlas0.png -------------------------------------------------------------------------------- /test/UIProject/assets/Package1/logo128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairygui/FairyGUI-threejs/HEAD/test/UIProject/assets/Package1/logo128.png -------------------------------------------------------------------------------- /src/core/mesh/MeshFactory.ts: -------------------------------------------------------------------------------- 1 | import { VertexBuffer } from "./VertexBuffer"; 2 | 3 | export interface IMeshFactory { 4 | onPopulateMesh(vb: VertexBuffer): void; 5 | } -------------------------------------------------------------------------------- /test/UIProject/FairyGUI-threejs-test.fairy: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/core/hittest/IHitTest.ts: -------------------------------------------------------------------------------- 1 | import { Rect } from "../../utils/Rect"; 2 | 3 | export interface IHitTest { 4 | hitTest(contentRect: Rect, x: number, y: number): boolean; 5 | } -------------------------------------------------------------------------------- /src/utils/Color.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "three"; 2 | 3 | export class Color4 extends Color { 4 | public a: number; 5 | 6 | public constructor(rgb?: number, a?: number) { 7 | super(rgb || 0); 8 | this.a = a != null ? a : 1; 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/html/IHtmlPageContext.ts: -------------------------------------------------------------------------------- 1 | import { RichTextField } from "../../core/text/RichTextField"; 2 | import { HtmlElement } from "./HtmlElement"; 3 | import { IHtmlObject } from "./IHtmlObject"; 4 | 5 | export interface IHtmlPageContext { 6 | createObject(owner: RichTextField, element: HtmlElement): IHtmlObject; 7 | freeObject(obj: IHtmlObject): void; 8 | } -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "target": "es6", 5 | "module": "es6", 6 | "moduleResolution":"Node", 7 | "strict": true, 8 | "strictNullChecks": false 9 | }, 10 | 11 | "include": [ 12 | "./src/**/*" 13 | ], 14 | "exclude": [ 15 | "node_modules" 16 | ] 17 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "target": "es6", 5 | "module": "es6", 6 | "moduleResolution":"Node", 7 | "strict": true, 8 | "strictNullChecks": false, 9 | "sourceMap":true 10 | }, 11 | 12 | "include": [ 13 | "./src/**/*" 14 | ], 15 | "exclude": [ 16 | "node_modules" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/UIProject/assets/Package1/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/ui/Margin.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Margin { 3 | public left: number = 0; 4 | public right: number = 0; 5 | public top: number = 0; 6 | public bottom: number = 0; 7 | 8 | public copy(source: Margin): void { 9 | this.top = source.top; 10 | this.bottom = source.bottom; 11 | this.left = source.left; 12 | this.right = source.right; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build", 7 | "path": "/", 8 | "group": "build", 9 | "problemMatcher": [], 10 | "label": "build source" 11 | }, 12 | 13 | { 14 | "type": "npm", 15 | "script": "test", 16 | "path": "/", 17 | "group": "build", 18 | "problemMatcher": [], 19 | "label": "build test" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/utils/html/HtmlParseOptions.ts: -------------------------------------------------------------------------------- 1 | export class HtmlParseOptions { 2 | public linkUnderline: boolean; 3 | public linkColor: number; 4 | public ignoreWhiteSpace: boolean; 5 | 6 | public static defaultLinkUnderline: boolean = true; 7 | public static defaultLinkColor: number = 0x3A67CC; 8 | 9 | public constructor() { 10 | this.linkUnderline = HtmlParseOptions.defaultLinkUnderline; 11 | this.linkColor = HtmlParseOptions.defaultLinkColor; 12 | } 13 | } -------------------------------------------------------------------------------- /src/utils/html/IHtmlObject.ts: -------------------------------------------------------------------------------- 1 | import { HtmlElement } from "./HtmlElement"; 2 | import { RichTextField } from "../../core/text/RichTextField"; 3 | import { DisplayObject } from "../../core/DisplayObject"; 4 | 5 | export interface IHtmlObject { 6 | width: number; 7 | height: number; 8 | displayObject: DisplayObject; 9 | element: HtmlElement; 10 | 11 | create(owner: RichTextField, element: HtmlElement):void; 12 | setPosition(x: number, y: number):void; 13 | add():void; 14 | remove():void; 15 | release():void; 16 | dispose():void; 17 | } -------------------------------------------------------------------------------- /test/UIProject/assets/Package1/Main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 | <% for (var css in htmlWebpackPlugin.files.css) { %> 9 | 10 | <% } %> 11 | 12 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ui/GRichTextField.ts: -------------------------------------------------------------------------------- 1 | import { RichTextField } from "../core/text/RichTextField"; 2 | import { defaultParser } from "../utils/UBBParser"; 3 | import { GTextField } from "./GTextField"; 4 | 5 | export class GRichTextField extends GTextField { 6 | constructor() { 7 | super(); 8 | } 9 | 10 | protected createDisplayObject(): void { 11 | this._displayObject = this._textField = new RichTextField(); 12 | } 13 | 14 | protected setText() { 15 | let str = this._text; 16 | if (this._template) 17 | str = this.parseTemplate(str); 18 | 19 | this._textField.maxWidth = this.maxWidth; 20 | if (this._ubbEnabled) 21 | this._textField.htmlText = defaultParser.parse(str); 22 | else 23 | this._textField.htmlText = str; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/tween/EaseType.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum EaseType { 3 | Linear = 0, 4 | SineIn = 1, 5 | SineOut = 2, 6 | SineInOut = 3, 7 | QuadIn = 4, 8 | QuadOut = 5, 9 | QuadInOut = 6, 10 | CubicIn = 7, 11 | CubicOut = 8, 12 | CubicInOut = 9, 13 | QuartIn = 10, 14 | QuartOut = 11, 15 | QuartInOut = 12, 16 | QuintIn = 13, 17 | QuintOut = 14, 18 | QuintInOut = 15, 19 | ExpoIn = 16, 20 | ExpoOut = 17, 21 | ExpoInOut = 18, 22 | CircIn = 19, 23 | CircOut = 20, 24 | CircInOut = 21, 25 | ElasticIn = 22, 26 | ElasticOut = 23, 27 | ElasticInOut = 24, 28 | BackIn = 25, 29 | BackOut = 26, 30 | BackInOut = 27, 31 | BounceIn = 28, 32 | BounceOut = 29, 33 | BounceInOut = 30, 34 | Custom = 31, 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/core/text/BaseFont.ts: -------------------------------------------------------------------------------- 1 | import { NTexture } from "../NTexture"; 2 | import { TextFormat } from "./TextFormat"; 3 | import { VertexBuffer } from "../mesh/VertexBuffer"; 4 | 5 | export type GlyphInfo = { 6 | width: number; 7 | height: number; 8 | baseline: number; 9 | } 10 | 11 | export interface BaseFont { 12 | name: string; 13 | version: number; 14 | mainTexture: NTexture; 15 | isDynamic?: boolean; 16 | keepCrisp?: boolean; 17 | setFormat(format: TextFormat, fontSizeScale: number): void; 18 | prepareCharacters(text: string): void; 19 | getGlyph(ch: string, ret: GlyphInfo): boolean; 20 | drawGlyph(x: number, y: number, vb: VertexBuffer): number; 21 | drawLine(x: number, y: number, width: number, fontSize: number, type: number, vb: VertexBuffer): number; 22 | getLineHeight(size: number): number; 23 | } -------------------------------------------------------------------------------- /src/gears/GearDisplay2.ts: -------------------------------------------------------------------------------- 1 | import { GearBase } from "./GearBase"; 2 | 3 | export class GearDisplay2 extends GearBase { 4 | public pages: string[] = null; 5 | public condition: number = 0; 6 | 7 | private _visible: number = 0; 8 | 9 | protected init(): void { 10 | this.pages = null; 11 | } 12 | 13 | public apply(): void { 14 | if (this.pages == null || this.pages.length == 0 15 | || this.pages.indexOf(this._controller.selectedPageId) != -1) 16 | this._visible = 1; 17 | else 18 | this._visible = 0; 19 | } 20 | 21 | public evaluate(connected: boolean): boolean { 22 | var v: boolean = this._controller == null || this._visible > 0; 23 | if (this.condition == 0) 24 | v = v && connected; 25 | else 26 | v = v || connected; 27 | return v; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/UIProject/settings/Publish.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../assets/UI", 3 | "branchPath": "", 4 | "fileExtension": "fui", 5 | "packageCount": 2, 6 | "compressDesc": false, 7 | "binaryFormat": true, 8 | "jpegQuality": 80, 9 | "compressPNG": false, 10 | "codeGeneration": { 11 | "allowGenCode": true, 12 | "codePath": "", 13 | "classNamePrefix": "UI_", 14 | "memberNamePrefix": "m_", 15 | "packageName": null, 16 | "ignoreNoname": true, 17 | "getMemberByName": false, 18 | "codeType": "" 19 | }, 20 | "includeHighResolution": 0, 21 | "branchProcessing": 0, 22 | "seperatedAtlasForBranch": false, 23 | "atlasSetting": { 24 | "maxSize": 2048, 25 | "paging": true, 26 | "sizeOption": "pot", 27 | "forceSquare": false, 28 | "allowRotation": false, 29 | "trimImage": true 30 | }, 31 | "include2x": false, 32 | "include3x": false, 33 | "include4x": false, 34 | "fileName": "Publish" 35 | } -------------------------------------------------------------------------------- /src/core/text/FontManager.ts: -------------------------------------------------------------------------------- 1 | import { BaseFont } from "./BaseFont"; 2 | import { DynamicFont } from "./DynamicFont"; 3 | 4 | export class FontManager { 5 | public static fonts: Record = {}; 6 | public static packageFontGetter: (name: string) => BaseFont; 7 | 8 | public static registerFont(font: BaseFont) { 9 | this.fonts[font.name] = font; 10 | } 11 | 12 | public static unregisterFont(font: BaseFont) { 13 | this.fonts[font.name] = undefined; 14 | } 15 | 16 | public static getFont(name: string): BaseFont { 17 | if (this.packageFontGetter && name.startsWith("ui://")) { 18 | let font = this.packageFontGetter(name); 19 | if (font) 20 | return font; 21 | } 22 | 23 | let font = this.fonts[name]; 24 | if (!font) { 25 | font = new DynamicFont(); 26 | font.name = name; 27 | this.fonts[name] = font; 28 | } 29 | 30 | return font; 31 | } 32 | } -------------------------------------------------------------------------------- /src/gears/GearIcon.ts: -------------------------------------------------------------------------------- 1 | import { ByteBuffer } from "../utils/ByteBuffer"; 2 | import { GearBase } from "./GearBase"; 3 | 4 | export class GearIcon extends GearBase { 5 | private _storage: Record; 6 | private _default: string; 7 | 8 | protected init(): void { 9 | this._default = this._owner.icon; 10 | this._storage = {}; 11 | } 12 | 13 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 14 | if (!pageId) 15 | this._default = buffer.readS(); 16 | else 17 | this._storage[pageId] = buffer.readS(); 18 | } 19 | 20 | public apply(): void { 21 | this._owner._gearLocked = true; 22 | 23 | var data: any = this._storage[this._controller.selectedPageId]; 24 | if (data !== undefined) 25 | this._owner.icon = data; 26 | else 27 | this._owner.icon = this._default; 28 | 29 | this._owner._gearLocked = false; 30 | } 31 | 32 | public updateState(): void { 33 | this._storage[this._controller.selectedPageId] = this._owner.icon; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/core/hittest/ShapeHitTest.ts: -------------------------------------------------------------------------------- 1 | import { IHitTest } from "./IHitTest"; 2 | import { DisplayObject } from "../DisplayObject"; 3 | import { Rect } from "../../utils/Rect"; 4 | import { Vector2 } from "three"; 5 | 6 | var s_vec2: Vector2 = new Vector2(); 7 | 8 | export class ShapeHitTest implements IHitTest { 9 | public shape: DisplayObject; 10 | 11 | public constructor(obj: DisplayObject) { 12 | this.shape = obj; 13 | } 14 | 15 | public hitTest(contentRect: Rect, x: number, y: number): boolean { 16 | if (!this.shape.graphics) 17 | return false; 18 | 19 | if (this.shape.parent) { 20 | let p: DisplayObject = (this.shape.parent)["$owner"]; 21 | if (p) { 22 | p.transformPoint(x, y, this.shape.obj3D, s_vec2); 23 | x = s_vec2.x; 24 | y = s_vec2.y; 25 | } 26 | } 27 | 28 | let ht: any = this.shape.graphics.meshFactory; 29 | if (!('hitTest' in ht)) 30 | return false; 31 | 32 | return (ht).hitTest(this.shape.contentRect, x, y); 33 | } 34 | } -------------------------------------------------------------------------------- /src/gears/GearText.ts: -------------------------------------------------------------------------------- 1 | import { ByteBuffer } from "../utils/ByteBuffer"; 2 | import { GearBase } from "./GearBase"; 3 | 4 | export class GearText extends GearBase { 5 | private _storage: Record; 6 | private _default: string; 7 | 8 | protected init(): void { 9 | this._default = this._owner.text; 10 | this._storage = {}; 11 | } 12 | 13 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 14 | if (pageId == null) 15 | this._default = buffer.readS(); 16 | else 17 | this._storage[pageId] = buffer.readS(); 18 | } 19 | 20 | public apply(): void { 21 | this._owner._gearLocked = true; 22 | 23 | var data: any = this._storage[this._controller.selectedPageId]; 24 | if (data !== undefined) 25 | this._owner.text = data; 26 | else 27 | this._owner.text = this._default; 28 | 29 | this._owner._gearLocked = false; 30 | } 31 | 32 | public updateState(): void { 33 | this._storage[this._controller.selectedPageId] = this._owner.text; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/gears/GearDisplay.ts: -------------------------------------------------------------------------------- 1 | import { GearBase } from "./GearBase"; 2 | 3 | export class GearDisplay extends GearBase { 4 | public pages: string[] = null; 5 | 6 | private _visible: number = 0; 7 | private _displayLockToken: number = 1; 8 | 9 | protected init(): void { 10 | this.pages = null; 11 | } 12 | 13 | public addLock(): number { 14 | this._visible++; 15 | return this._displayLockToken; 16 | } 17 | 18 | public releaseLock(token: number): void { 19 | if (token == this._displayLockToken) 20 | this._visible--; 21 | } 22 | 23 | public get connected(): boolean { 24 | return this._controller == null || this._visible > 0; 25 | } 26 | 27 | public apply(): void { 28 | this._displayLockToken++; 29 | if (this._displayLockToken <= 0) 30 | this._displayLockToken = 1; 31 | 32 | if (this.pages == null || this.pages.length == 0 33 | || this.pages.indexOf(this._controller.selectedPageId) != -1) 34 | this._visible = 1; 35 | else 36 | this._visible = 0; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 FairyGUI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/gears/GearFontSize.ts: -------------------------------------------------------------------------------- 1 | import { ObjectPropID } from "../ui/FieldTypes"; 2 | import { ByteBuffer } from "../utils/ByteBuffer"; 3 | import { GearBase } from "./GearBase"; 4 | 5 | export class GearFontSize extends GearBase { 6 | private _storage: Record; 7 | private _default: number = 0; 8 | 9 | protected init(): void { 10 | this._default = this._owner.getProp(ObjectPropID.FontSize); 11 | this._storage = {}; 12 | } 13 | 14 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 15 | if (!pageId) 16 | this._default = buffer.readInt(); 17 | else 18 | this._storage[pageId] = buffer.readInt(); 19 | } 20 | 21 | public apply(): void { 22 | this._owner._gearLocked = true; 23 | 24 | var data: any = this._storage[this._controller.selectedPageId]; 25 | if (data !== undefined) 26 | this._owner.setProp(ObjectPropID.FontSize, data); 27 | else 28 | this._owner.setProp(ObjectPropID.FontSize, this._default); 29 | 30 | this._owner._gearLocked = false; 31 | } 32 | 33 | public updateState(): void { 34 | this._storage[this._controller.selectedPageId] = this._owner.getProp(ObjectPropID.FontSize); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/Pool.ts: -------------------------------------------------------------------------------- 1 | export class Pool 2 | { 3 | pool: Array = []; 4 | _init: (arg0: T, ...argArray: any[]) => void; 5 | _reset: (arg0: T) => void; 6 | _ct: new () => T; 7 | 8 | public constructor(type: new () => T, init?: (arg0: T) => void, reset?: (arg0: T) => void) { 9 | this._init = init; 10 | this._reset = reset; 11 | this._ct = type; 12 | } 13 | 14 | public borrow(...argArray: any[]): T { 15 | let ret: T; 16 | if (this.pool.length > 0) 17 | ret = this.pool.pop(); 18 | else 19 | ret = new this._ct(); 20 | 21 | if (this._init) 22 | this._init(ret, ...argArray); 23 | 24 | return ret; 25 | } 26 | 27 | public returns(element: T | Array) { 28 | if (Array.isArray(element)) { 29 | let count = element.length; 30 | for (let i = 0; i < count; i++) { 31 | let element2 = element[i]; 32 | if (this._reset) 33 | this._reset(element2); 34 | this.pool.push(element2); 35 | } 36 | element.length = 0; 37 | } 38 | else { 39 | if (this._reset) 40 | this._reset(element); 41 | this.pool.push(element); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/action/ControllerAction.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Controller } from "../ui/Controller"; 3 | import { ByteBuffer } from "../utils/ByteBuffer"; 4 | 5 | export class ControllerAction { 6 | public fromPage: Array; 7 | public toPage: Array; 8 | 9 | constructor() { 10 | } 11 | 12 | public run(controller: Controller, prevPage: string, curPage: string): void { 13 | if ((!this.fromPage || this.fromPage.length == 0 || this.fromPage.indexOf(prevPage) != -1) 14 | && (!this.toPage || this.toPage.length == 0 || this.toPage.indexOf(curPage) != -1)) 15 | this.enter(controller); 16 | else 17 | this.leave(controller); 18 | } 19 | 20 | protected enter(controller: Controller): void { 21 | 22 | } 23 | 24 | protected leave(controller: Controller): void { 25 | 26 | } 27 | 28 | public setup(buffer: ByteBuffer): void { 29 | var cnt: number; 30 | var i: number; 31 | 32 | cnt = buffer.readShort(); 33 | this.fromPage = []; 34 | for (i = 0; i < cnt; i++) 35 | this.fromPage[i] = buffer.readS(); 36 | 37 | cnt = buffer.readShort(); 38 | this.toPage = []; 39 | for (i = 0; i < cnt; i++) 40 | this.toPage[i] = buffer.readS(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ui/GObjectPool.ts: -------------------------------------------------------------------------------- 1 | import { GObject } from "./GObject"; 2 | import { UIPackage } from "./UIPackage"; 3 | 4 | export class GObjectPool { 5 | private _pool: Record>; 6 | private _count: number = 0; 7 | 8 | constructor() { 9 | this._pool = {}; 10 | } 11 | 12 | public clear(): void { 13 | for (var i1 in this._pool) { 14 | var arr: Array = this._pool[i1]; 15 | arr.forEach(obj => obj.dispose()); 16 | } 17 | this._pool = {}; 18 | this._count = 0; 19 | } 20 | 21 | public get count(): number { 22 | return this._count; 23 | } 24 | 25 | public getObject(url: string): GObject { 26 | url = UIPackage.normalizeURL(url); 27 | if (url == null) 28 | return null; 29 | 30 | var arr: Array = this._pool[url]; 31 | if (arr && arr.length > 0) { 32 | this._count--; 33 | return arr.shift(); 34 | } 35 | 36 | return UIPackage.createObjectFromURL(url); 37 | } 38 | 39 | public returnObject(obj: GObject): void { 40 | var url: string = obj.resourceURL; 41 | if (!url) 42 | return; 43 | 44 | var arr: Array = this._pool[url]; 45 | if (!arr) { 46 | arr = []; 47 | this._pool[url] = arr; 48 | } 49 | 50 | this._count++; 51 | arr.push(obj); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/html/HtmlPageContext.ts: -------------------------------------------------------------------------------- 1 | import { RichTextField } from "../../core/text/RichTextField"; 2 | import { Pool } from "../Pool"; 3 | import { HtmlElement, HtmlElementType } from "./HtmlElement"; 4 | import { HtmlImage } from "./HtmlImage"; 5 | import { HtmlLink } from "./HtmlLink"; 6 | import { IHtmlObject } from "./IHtmlObject"; 7 | import { IHtmlPageContext } from "./IHtmlPageContext"; 8 | 9 | export class HtmlPageContext implements IHtmlPageContext { 10 | private _imagePool: Pool; 11 | private _linkPool: Pool; 12 | 13 | public constructor() { 14 | this._imagePool = new Pool(HtmlImage); 15 | this._linkPool = new Pool(HtmlLink); 16 | } 17 | 18 | public createObject(owner: RichTextField, element: HtmlElement): IHtmlObject { 19 | let ret: IHtmlObject = null; 20 | if (element.type == HtmlElementType.Image) 21 | ret = this._imagePool.borrow(); 22 | else if (element.type == HtmlElementType.Link) 23 | ret = this._linkPool.borrow(); 24 | 25 | if (ret) 26 | ret.create(owner, element); 27 | 28 | return ret; 29 | } 30 | 31 | public freeObject(obj: IHtmlObject): void { 32 | obj.release(); 33 | if (obj instanceof HtmlImage) 34 | this._imagePool.returns(obj); 35 | else if (obj instanceof HtmlLink) 36 | this._linkPool.returns(obj); 37 | } 38 | } 39 | 40 | export var defaultContext: IHtmlPageContext = new HtmlPageContext(); 41 | -------------------------------------------------------------------------------- /src/action/PlayTransitionAction.ts: -------------------------------------------------------------------------------- 1 | import { ControllerAction } from "./ControllerAction"; 2 | import { Controller } from "../ui/Controller"; 3 | import { Transition } from "../ui/Transition"; 4 | import { ByteBuffer } from "../utils/ByteBuffer"; 5 | 6 | export class PlayTransitionAction extends ControllerAction { 7 | public transitionName: string; 8 | public playTimes: number = 1; 9 | public delay: number = 0; 10 | public stopOnExit: boolean; 11 | 12 | private _currentTransition: Transition; 13 | 14 | constructor() { 15 | super(); 16 | } 17 | 18 | protected enter(controller: Controller): void { 19 | var trans: Transition = controller.parent.getTransition(this.transitionName); 20 | if (trans) { 21 | if (this._currentTransition && this._currentTransition.playing) 22 | trans.changePlayTimes(this.playTimes); 23 | else 24 | trans.play(null, this.playTimes, this.delay); 25 | this._currentTransition = trans; 26 | } 27 | } 28 | 29 | protected leave(controller: Controller): void { 30 | if (this.stopOnExit && this._currentTransition) { 31 | this._currentTransition.stop(); 32 | this._currentTransition = null; 33 | } 34 | } 35 | 36 | public setup(buffer: ByteBuffer): void { 37 | super.setup(buffer); 38 | 39 | this.transitionName = buffer.readS(); 40 | this.playTimes = buffer.readInt(); 41 | this.delay = buffer.readFloat(); 42 | this.stopOnExit = buffer.readBool(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/action/ChangePageAction.ts: -------------------------------------------------------------------------------- 1 | import { GComponent } from "../ui/GComponent"; 2 | import { Controller } from "../ui/Controller"; 3 | import { ByteBuffer } from "../utils/ByteBuffer"; 4 | import { ControllerAction } from "./ControllerAction"; 5 | 6 | export class ChangePageAction extends ControllerAction { 7 | public objectId: string; 8 | public controllerName: string; 9 | public targetPage: string; 10 | 11 | constructor() { 12 | super(); 13 | } 14 | 15 | protected enter(controller: Controller): void { 16 | if (!this.controllerName) 17 | return; 18 | 19 | var gcom: GComponent; 20 | if (this.objectId) 21 | gcom = controller.parent.getChildById(this.objectId); 22 | else 23 | gcom = controller.parent; 24 | if (gcom) { 25 | var cc: Controller = gcom.getController(this.controllerName); 26 | if (cc && cc != controller && !cc.changing) { 27 | if (this.targetPage == "~1") { 28 | if (controller.selectedIndex < cc.pageCount) 29 | cc.selectedIndex = controller.selectedIndex; 30 | } 31 | else if (this.targetPage == "~2") 32 | cc.selectedPage = controller.selectedPage; 33 | else 34 | cc.selectedPageId = this.targetPage; 35 | } 36 | } 37 | } 38 | 39 | public setup(buffer: ByteBuffer): void { 40 | super.setup(buffer); 41 | 42 | this.objectId = buffer.readS(); 43 | this.controllerName = buffer.readS(); 44 | this.targetPage = buffer.readS(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/tween/TweenValue.ts: -------------------------------------------------------------------------------- 1 | 2 | export class TweenValue { 3 | public x: number; 4 | public y: number; 5 | public z: number; 6 | public w: number; 7 | 8 | public constructor() { 9 | this.x = this.y = this.z = this.w = 0; 10 | } 11 | 12 | public get color(): number { 13 | return (this.w << 24) + (this.x << 16) + (this.y << 8) + this.z; 14 | } 15 | 16 | public set color(value: number) { 17 | this.x = (value & 0xFF0000) >> 16; 18 | this.y = (value & 0x00FF00) >> 8; 19 | this.z = (value & 0x0000FF); 20 | this.w = (value & 0xFF000000) >> 24; 21 | } 22 | 23 | public getField(index: number): number { 24 | switch (index) { 25 | case 0: 26 | return this.x; 27 | case 1: 28 | return this.y; 29 | case 2: 30 | return this.z; 31 | case 3: 32 | return this.w; 33 | default: 34 | throw new Error("Index out of bounds: " + index); 35 | } 36 | } 37 | 38 | public setField(index: number, value: number): void { 39 | switch (index) { 40 | case 0: 41 | this.x = value; 42 | break; 43 | case 1: 44 | this.y = value; 45 | break; 46 | case 2: 47 | this.z = value; 48 | break; 49 | case 3: 50 | this.w = value; 51 | break; 52 | default: 53 | throw new Error("Index out of bounds: " + index); 54 | } 55 | } 56 | 57 | public setZero(): void { 58 | this.x = this.y = this.z = this.w = 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fairygui-three", 3 | "version": "1.0.0", 4 | "description": "A GUI Editor & framework for three.js", 5 | "main": "dist/fairygui.js", 6 | "module": "dist/fairygui.module.js", 7 | "types": "dist/fairygui.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/fairygui/FairyGUI-threejs" 11 | }, 12 | "files": [ 13 | "dist", 14 | "test" 15 | ], 16 | "scripts": { 17 | "build": "gulp build", 18 | "test": "webpack --config test/webpack.config.js" 19 | }, 20 | "keywords": [ 21 | "three", 22 | "three.js", 23 | "fairygui", 24 | "fgui", 25 | "gui" 26 | ], 27 | "author": "guzhu", 28 | "license": "MIT", 29 | "homepage": "https://fairygui.com/", 30 | "dependencies": { 31 | "@types/node": "^14.0.1" 32 | }, 33 | "devDependencies": { 34 | "three": "^0.154.0", 35 | "@types/three": "^0.154.0", 36 | "@types/node": "^14.0.1", 37 | "clean-webpack-plugin": "^3.0.0", 38 | "copy-webpack-plugin": "^5.1.1", 39 | "css-loader": "^3.5.3", 40 | "csv-loader": "^3.0.3", 41 | "expose-loader": "^0.7.5", 42 | "file-loader": "^6.0.0", 43 | "gulp": "^4.0.2", 44 | "gulp-clean": "^0.4.0", 45 | "gulp-concat": "^2.6.1", 46 | "gulp-minify": "^3.1.0", 47 | "gulp-rename": "^2.0.0", 48 | "gulp-replace": "^1.1.3", 49 | "gulp-typescript": "^6.0.0-alpha.1", 50 | "gulp-uglify": "^3.0.2", 51 | "gulp-uglify-es": "^2.0.0", 52 | "html-webpack-plugin": "^4.3.0", 53 | "rollup": "^3.29.4", 54 | "style-loader": "^1.2.1", 55 | "terser-webpack-plugin": "^3.0.3", 56 | "ts-loader": "^8.3.0", 57 | "webpack": "^4.43.0", 58 | "webpack-cli": "^3.3.11", 59 | "webpack-dev-server": "^3.11.0", 60 | "xml-loader": "^1.2.1", 61 | "dts-bundle": "^0.7.3" 62 | } 63 | } -------------------------------------------------------------------------------- /src/core/text/TextFormat.ts: -------------------------------------------------------------------------------- 1 | import { AlignType } from "../../ui/FieldTypes"; 2 | import { Vector2 } from "three"; 3 | 4 | export class TextFormat { 5 | public size: number = 0; 6 | public font: string; 7 | public color: number = 0; 8 | public lineSpacing: number = 0; 9 | public letterSpacing: number = 0; 10 | public bold: boolean; 11 | public underline: boolean; 12 | public italic: boolean; 13 | public strikethrough: boolean; 14 | 15 | public align: AlignType; 16 | public outline: number = 0; 17 | public outlineColor: number = 0; 18 | public shadowOffset: Vector2 = new Vector2(); 19 | public shadowColor: number = 0; 20 | 21 | /**@internal */ 22 | _colorChanged: boolean; 23 | 24 | public copy(source: TextFormat): void { 25 | this.size = source.size; 26 | this.font = source.font; 27 | this.color = source.color; 28 | this.lineSpacing = source.lineSpacing; 29 | this.letterSpacing = source.letterSpacing; 30 | this.bold = source.bold; 31 | this.underline = source.underline; 32 | this.italic = source.italic; 33 | this.strikethrough = source.strikethrough; 34 | this.align = source.align; 35 | this.outline = source.outline; 36 | this.outlineColor = source.outlineColor; 37 | this.shadowOffset.copy(source.shadowOffset); 38 | this.shadowColor = source.shadowColor; 39 | } 40 | 41 | public equalStyle(aFormat: TextFormat): boolean { 42 | return this.size == aFormat.size && this.color == aFormat.color 43 | && this.bold == aFormat.bold && this.underline == aFormat.underline 44 | && this.italic == aFormat.italic 45 | && this.strikethrough == aFormat.strikethrough 46 | && this.align == aFormat.align; 47 | } 48 | } -------------------------------------------------------------------------------- /src/core/mesh/CompositeMesh.ts: -------------------------------------------------------------------------------- 1 | import { IMeshFactory } from "./MeshFactory"; 2 | import { IHitTest } from "../hittest/IHitTest"; 3 | import { VertexBuffer } from "./VertexBuffer"; 4 | import { Rect } from "../../utils/Rect"; 5 | 6 | export class CompositeMesh implements IMeshFactory, IHitTest { 7 | public readonly elements: Array; 8 | 9 | public activeIndex: number; 10 | 11 | public constructor() { 12 | this.elements = []; 13 | this.activeIndex = -1; 14 | } 15 | 16 | public onPopulateMesh(vb: VertexBuffer) { 17 | let cnt = this.elements.length; 18 | if (cnt == 1) 19 | this.elements[0].onPopulateMesh(vb); 20 | else { 21 | let vb2: VertexBuffer = VertexBuffer.begin(vb); 22 | 23 | for (let i = 0; i < cnt; i++) { 24 | if (this.activeIndex == -1 || i == this.activeIndex) { 25 | vb2.clear(); 26 | this.elements[i].onPopulateMesh(vb2); 27 | vb.append(vb2); 28 | } 29 | } 30 | 31 | vb2.end(); 32 | } 33 | } 34 | 35 | public hitTest(contentRect: Rect, x: number, y: number): boolean { 36 | if (!contentRect.contains(x, y)) 37 | return false; 38 | 39 | let flag: boolean = false; 40 | let cnt = this.elements.length; 41 | for (let i = 0; i < cnt; i++) { 42 | if (this.activeIndex == -1 || i == this.activeIndex) { 43 | let ht = this.elements[i]; 44 | if ('hitTest' in ht) { 45 | if ((ht).hitTest(contentRect, x, y)) 46 | return true; 47 | } 48 | else 49 | flag = true; 50 | } 51 | } 52 | 53 | return flag; 54 | } 55 | } -------------------------------------------------------------------------------- /src/gears/GearAnimation.ts: -------------------------------------------------------------------------------- 1 | import { GearBase } from "./GearBase"; 2 | import { ByteBuffer } from "../utils/ByteBuffer"; 3 | import { ObjectPropID } from "../ui/FieldTypes"; 4 | 5 | export class GearAnimation extends GearBase { 6 | private _storage: Record; 7 | private _default: GearAnimationValue; 8 | 9 | protected init(): void { 10 | this._default = { 11 | playing: this._owner.getProp(ObjectPropID.Playing), 12 | frame: this._owner.getProp(ObjectPropID.Frame) 13 | }; 14 | this._storage = {}; 15 | } 16 | 17 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 18 | var gv: GearAnimationValue; 19 | if (!pageId) 20 | gv = this._default; 21 | else { 22 | gv = {}; 23 | this._storage[pageId] = gv; 24 | } 25 | gv.playing = buffer.readBool(); 26 | gv.frame = buffer.readInt(); 27 | } 28 | 29 | public apply(): void { 30 | this._owner._gearLocked = true; 31 | 32 | var gv: GearAnimationValue = this._storage[this._controller.selectedPageId] || this._default; 33 | this._owner.setProp(ObjectPropID.Playing, gv.playing); 34 | this._owner.setProp(ObjectPropID.Frame, gv.frame); 35 | 36 | this._owner._gearLocked = false; 37 | } 38 | 39 | public updateState(): void { 40 | var gv: GearAnimationValue = this._storage[this._controller.selectedPageId]; 41 | if (!gv) { 42 | gv = {}; 43 | this._storage[this._controller.selectedPageId] = gv; 44 | } 45 | 46 | gv.playing = this._owner.getProp(ObjectPropID.Playing); 47 | gv.frame = this._owner.getProp(ObjectPropID.Frame); 48 | } 49 | } 50 | 51 | interface GearAnimationValue { 52 | playing?: boolean; 53 | frame?: number; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/gears/GearColor.ts: -------------------------------------------------------------------------------- 1 | import { ObjectPropID } from "../ui/FieldTypes"; 2 | import { ByteBuffer } from "../utils/ByteBuffer"; 3 | import { GearBase } from "./GearBase"; 4 | 5 | export class GearColor extends GearBase { 6 | private _storage: Record; 7 | private _default: GearColorValue; 8 | 9 | protected init(): void { 10 | this._default = { 11 | color: this._owner.getProp(ObjectPropID.Color), 12 | strokeColor: this._owner.getProp(ObjectPropID.OutlineColor) 13 | }; 14 | this._storage = {}; 15 | } 16 | 17 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 18 | var gv: GearColorValue; 19 | if (!pageId) 20 | gv = this._default; 21 | else { 22 | gv = {}; 23 | this._storage[pageId] = gv; 24 | } 25 | 26 | gv.color = buffer.readColor(); 27 | gv.strokeColor = buffer.readColor(); 28 | } 29 | 30 | public apply(): void { 31 | this._owner._gearLocked = true; 32 | 33 | var gv: GearColorValue = this._storage[this._controller.selectedPageId] || this._default; 34 | this._owner.setProp(ObjectPropID.Color, gv.color); 35 | this._owner.setProp(ObjectPropID.OutlineColor, gv.strokeColor); 36 | 37 | this._owner._gearLocked = false; 38 | } 39 | 40 | public updateState(): void { 41 | var gv: GearColorValue = this._storage[this._controller.selectedPageId]; 42 | if (!gv) { 43 | gv = {}; 44 | this._storage[this._controller.selectedPageId] = gv; 45 | } 46 | 47 | gv.color = this._owner.getProp(ObjectPropID.Color); 48 | gv.strokeColor = this._owner.getProp(ObjectPropID.OutlineColor); 49 | } 50 | } 51 | 52 | interface GearColorValue { 53 | color?: number; 54 | strokeColor?: number; 55 | } 56 | -------------------------------------------------------------------------------- /src/core/hittest/PixelHitTest.ts: -------------------------------------------------------------------------------- 1 | import { ByteBuffer } from "../../utils/ByteBuffer"; 2 | import { Rect } from "../../utils/Rect"; 3 | import { IHitTest } from "./IHitTest"; 4 | 5 | export class PixelHitTestData { 6 | public pixelWidth: number; 7 | public scale: number; 8 | public pixels: Uint8Array; 9 | 10 | public load(ba: ByteBuffer) { 11 | ba.readInt(); 12 | this.pixelWidth = ba.readInt(); 13 | this.scale = 1.0 / ba.readByte(); 14 | let len = ba.readInt(); 15 | this.pixels = new Uint8Array(ba.data, ba.pos, len); 16 | ba.skip(len); 17 | } 18 | } 19 | 20 | export class PixelHitTest implements IHitTest { 21 | public offsetX: number; 22 | public offsetY: number; 23 | public sourceWidth: number; 24 | public sourceHeight: number; 25 | 26 | private _data: PixelHitTestData; 27 | 28 | public constructor(data: PixelHitTestData, offsetX: number, offsetY: number, sourceWidth: number, sourceHeight: number) { 29 | this._data = data; 30 | this.offsetX = offsetX; 31 | this.offsetY = offsetY; 32 | this.sourceWidth = sourceWidth; 33 | this.sourceHeight = sourceHeight; 34 | } 35 | 36 | public hitTest(contentRect: Rect, x: number, y: number): boolean { 37 | if (!contentRect.contains(x, y)) 38 | return false; 39 | 40 | let data = this._data; 41 | 42 | x = Math.floor((x * this.sourceWidth / contentRect.width - this.offsetX) * data.scale); 43 | y = Math.floor((y * this.sourceHeight / contentRect.height - this.offsetY) * data.scale); 44 | if (x < 0 || y < 0 || x >= data.pixelWidth) 45 | return false; 46 | 47 | let pos: number = y * data.pixelWidth + x; 48 | let pos2: number = Math.floor(pos / 8); 49 | let pos3: number = pos % 8; 50 | 51 | if (pos2 >= 0 && pos2 < data.pixels.length) 52 | return ((data.pixels[pos2] >> pos3) & 0x1) > 0; 53 | else 54 | return false; 55 | } 56 | } -------------------------------------------------------------------------------- /test/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import * as fgui from "../../build/FairyGUI"; 3 | 4 | export class Threescene { 5 | private scene: THREE.Scene 6 | private renderer: THREE.WebGLRenderer; 7 | 8 | private _mainMenu: fgui.GComponent; 9 | 10 | constructor() { 11 | this.init(); 12 | } 13 | 14 | private init() { 15 | this.renderer = new THREE.WebGLRenderer();//{ antialias: true } 16 | this.renderer.setClearColor(0X222222); 17 | this.renderer.sortObjects = false; 18 | this.renderer.setPixelRatio(window.devicePixelRatio); 19 | this.renderer.setSize(window.innerWidth, window.innerHeight); 20 | this.renderer.localClippingEnabled = true; 21 | document.body.appendChild(this.renderer.domElement); 22 | window.addEventListener('resize', () => { 23 | this.renderer.setSize(window.innerWidth, window.innerHeight) 24 | }, false); 25 | 26 | this.scene = new THREE.Scene(); 27 | 28 | fgui.Stage.init(this.renderer); 29 | fgui.Stage.scene = this.scene; 30 | 31 | fgui.UIContentScaler.scaleWithScreenSize(1136, 640, fgui.ScreenMatchMode.MatchWidthOrHeight); 32 | 33 | // let listener: THREE.AudioListener = new THREE.AudioListener(); 34 | // fgui.Stage.camera.add(listener); 35 | // fgui.Stage.audioListener = listener; 36 | 37 | fgui.UIPackage.loadPackage("assets/UI/Package1").then(this.start.bind(this)); 38 | 39 | this.animate(); 40 | } 41 | 42 | private start() { 43 | this._mainMenu = fgui.UIPackage.createObject("Package1", "Main").asCom; 44 | this._mainMenu.makeFullScreen(); 45 | fgui.GRoot.inst.addChild(this._mainMenu); 46 | } 47 | 48 | private render() { 49 | fgui.Stage.update(); 50 | this.renderer.render(this.scene, fgui.Stage.camera); 51 | } 52 | 53 | private animate = () => { 54 | requestAnimationFrame(this.animate) 55 | this.render() 56 | } 57 | } 58 | 59 | new Threescene(); -------------------------------------------------------------------------------- /src/core/text/SelectionShape.ts: -------------------------------------------------------------------------------- 1 | import { Vector2 } from "three"; 2 | import { Rect } from "../../utils/Rect"; 3 | import { DisplayObject } from "../DisplayObject"; 4 | import { IMeshFactory } from "../mesh/MeshFactory"; 5 | import { VertexBuffer } from "../mesh/VertexBuffer"; 6 | import { NGraphics } from "../NGraphics"; 7 | import { EmptyTexture } from "../NTexture"; 8 | import { HitTestContext } from "../Stage"; 9 | 10 | var s_rect: Rect = new Rect(); 11 | 12 | export class SelectionShape extends DisplayObject implements IMeshFactory { 13 | public readonly rects: Array; 14 | 15 | public constructor() { 16 | super(); 17 | 18 | this.rects = new Array(); 19 | 20 | this._graphics = new NGraphics(this._obj3D); 21 | this._graphics.texture = EmptyTexture; 22 | } 23 | 24 | public refresh() { 25 | let count = this.rects.length; 26 | if (count > 0) { 27 | s_rect.copy(this.rects[0]); 28 | for (let i = 1; i < count; i++) 29 | s_rect.union(this.rects[i]); 30 | this.setSize(s_rect.xMax, s_rect.yMax); 31 | } 32 | else 33 | this.setSize(0, 0); 34 | this.graphics.setMeshDirty(); 35 | } 36 | 37 | public clear() { 38 | this.rects.length = 0; 39 | this.graphics.setMeshDirty(); 40 | } 41 | 42 | public onPopulateMesh(vb: VertexBuffer) { 43 | let count = this.rects.length; 44 | if (count == 0) 45 | return; 46 | 47 | for (let i = 0; i < count; i++) 48 | vb.addQuad(this.rects[i]); 49 | vb.addTriangles(); 50 | } 51 | 52 | protected hitTest(context: HitTestContext): DisplayObject { 53 | let pt: Vector2 = context.getLocal(this); 54 | 55 | if (this._contentRect.contains(pt)) { 56 | let count = this.rects.length; 57 | for (let i = 0; i < count; i++) { 58 | if (this.rects[i].contains(pt)) 59 | return this; 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | } -------------------------------------------------------------------------------- /src/utils/ToolSet.ts: -------------------------------------------------------------------------------- 1 | 2 | export function convertToHtmlColor(argb: number, hasAlpha?: boolean): string { 3 | var alpha: string; 4 | if (hasAlpha) 5 | alpha = (argb >> 24 & 0xFF).toString(16); 6 | else 7 | alpha = ""; 8 | var red: string = (argb >> 16 & 0xFF).toString(16); 9 | var green: string = (argb >> 8 & 0xFF).toString(16); 10 | var blue: string = (argb & 0xFF).toString(16); 11 | if (alpha.length == 1) 12 | alpha = "0" + alpha; 13 | if (red.length == 1) 14 | red = "0" + red; 15 | if (green.length == 1) 16 | green = "0" + green; 17 | if (blue.length == 1) 18 | blue = "0" + blue; 19 | return "#" + alpha + red + green + blue; 20 | } 21 | 22 | export function convertFromHtmlColor(str: string, hasAlpha?: boolean): number { 23 | if (str.length < 1) 24 | return 0; 25 | 26 | if (str.charAt(0) == "#") 27 | str = str.substring(1); 28 | 29 | if (str.length == 8) 30 | return (parseInt(str.substring(0, 2), 16) << 24) + parseInt(str.substring(2), 16); 31 | else if (hasAlpha) 32 | return 0xFF000000 + parseInt(str, 16); 33 | else 34 | return parseInt(str, 16); 35 | } 36 | 37 | export function clamp(value: number, min: number, max: number): number { 38 | if (value < min) 39 | value = min; 40 | else if (value > max) 41 | value = max; 42 | return value; 43 | } 44 | 45 | export function clamp01(value: number): number { 46 | if (isNaN(value)) 47 | value = 0; 48 | else if (value > 1) 49 | value = 1; 50 | else if (value < 0) 51 | value = 0; 52 | return value; 53 | } 54 | 55 | export function lerp(start: number, end: number, percent: number): number { 56 | return (start + percent * (end - start)); 57 | } 58 | 59 | export function repeat(t: number, length: number): number { 60 | return t - Math.floor(t / length) * length; 61 | } 62 | 63 | export function distance(x1: number, y1: number, x2: number, y2: number): number { 64 | return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 65 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const rollup = require('rollup') 3 | const ts = require('gulp-typescript'); 4 | const rename = require("gulp-rename"); 5 | const uglify = require('gulp-uglify-es').default; 6 | const tsProject = ts.createProject('tsconfig.json', { declaration: true }); 7 | const dts = require('dts-bundle') 8 | 9 | const onwarn = warning => { 10 | // Silence circular dependency warning for moment package 11 | if (warning.code === 'CIRCULAR_DEPENDENCY') 12 | return 13 | 14 | console.warn(`(!) ${warning.message}`) 15 | } 16 | 17 | gulp.task('buildJs', () => { 18 | return tsProject.src().pipe(tsProject()).pipe(gulp.dest('./build')); 19 | }) 20 | 21 | gulp.task("rollup", async function () { 22 | const subTask = await rollup.rollup({ 23 | input: "build/FairyGUI.js", 24 | external: ['three'], 25 | onwarn: onwarn, 26 | }); 27 | await subTask.write({ 28 | file: 'dist/fairygui.js', 29 | format: 'umd', 30 | extend: true, 31 | name: 'fgui', 32 | sourcemap: true, 33 | globals: { three: 'three' } 34 | }); 35 | 36 | const subTask2 = await rollup.rollup({ 37 | input: "build/FairyGUI.js", 38 | external: ['three'], 39 | }); 40 | await subTask2.write({ 41 | file: 'dist/fairygui.module.js', 42 | format: 'esm', 43 | extend: true, 44 | name: 'fgui', 45 | sourcemap: true, 46 | globals: { three: 'three' } 47 | }); 48 | }); 49 | 50 | gulp.task("uglify", function () { 51 | return gulp.src("dist/fairygui.js") 52 | .pipe(rename({ suffix: '.min' })) 53 | .pipe(uglify(/* options */)) 54 | .pipe(gulp.dest("dist/")); 55 | }); 56 | 57 | gulp.task('buildDts', function () { 58 | return new Promise(function (resolve, reject) { 59 | dts.bundle({ name: "fairygui-three", main: "./build/FairyGUI.d.ts", out: "../dist/fairygui.d.ts" }); 60 | resolve(); 61 | }); 62 | }); 63 | 64 | gulp.task('build', gulp.series( 65 | 'buildJs', 66 | 'rollup', 67 | 'uglify', 68 | 'buildDts' 69 | )); -------------------------------------------------------------------------------- /src/core/mesh/RectMesh.ts: -------------------------------------------------------------------------------- 1 | import { Color4 } from "../../utils/Color"; 2 | import { Rect } from "../../utils/Rect"; 3 | import { IMeshFactory } from "./MeshFactory"; 4 | import { VertexBuffer } from "./VertexBuffer"; 5 | 6 | var s_rect: Rect = new Rect(); 7 | 8 | export class RectMesh implements IMeshFactory { 9 | public drawRect: Rect; 10 | public lineWidth: number; 11 | public lineColor: Color4; 12 | public fillColor: Color4; 13 | 14 | public constructor() { 15 | this.lineWidth = 1; 16 | } 17 | 18 | public onPopulateMesh(vb: VertexBuffer) { 19 | let rect: Rect = this.drawRect ? this.drawRect : vb.contentRect; 20 | let color: Color4 = this.fillColor ? this.fillColor : vb.vertexColor; 21 | let lineColor: Color4 = this.lineColor ? this.lineColor : vb.vertexColor; 22 | 23 | if (this.lineWidth == 0) { 24 | if (color.a != 0)//optimized 25 | vb.addQuad(rect, null, color); 26 | } 27 | else { 28 | let part: Rect = s_rect; 29 | 30 | //left,right 31 | part.set(rect.x, rect.y, this.lineWidth, rect.height); 32 | vb.addQuad(part, null, lineColor); 33 | part.set(rect.xMax - this.lineWidth, rect.y, this.lineWidth, rect.height); 34 | vb.addQuad(part, null, lineColor); 35 | 36 | //top, bottom 37 | part.set(rect.x + this.lineWidth, rect.y, rect.width - this.lineWidth * 2, this.lineWidth); 38 | vb.addQuad(part, null, lineColor); 39 | part.set(rect.x + this.lineWidth, rect.yMax - this.lineWidth, rect.width - this.lineWidth * 2, this.lineWidth); 40 | vb.addQuad(part, null, lineColor); 41 | 42 | //middle 43 | if (color.a != 0)//optimized 44 | { 45 | part.setMinMax(rect.x + this.lineWidth, rect.y + this.lineWidth, rect.xMax - this.lineWidth, rect.yMax - this.lineWidth); 46 | if (part.width > 0 && part.height > 0) 47 | vb.addQuad(part, null, color); 48 | } 49 | } 50 | 51 | vb.addTriangles(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/tween/GTween.ts: -------------------------------------------------------------------------------- 1 | import { GTweener } from "./GTweener"; 2 | import { TweenManager } from "./TweenManager"; 3 | 4 | export class GTween { 5 | public static catchCallbackExceptions: boolean = true; 6 | 7 | public static to(start: number, end: number, duration: number): GTweener { 8 | return TweenManager.createTween()._to(start, end, duration); 9 | } 10 | 11 | public static to2(start: number, start2: number, end: number, end2: number, duration: number): GTweener { 12 | return TweenManager.createTween()._to2(start, start2, end, end2, duration); 13 | } 14 | 15 | public static to3(start: number, start2: number, start3: number, 16 | end: number, end2: number, end3: number, duration: number): GTweener { 17 | return TweenManager.createTween()._to3(start, start2, start3, end, end2, end3, duration); 18 | } 19 | 20 | public static to4(start: number, start2: number, start3: number, start4: number, 21 | end: number, end2: number, end3: number, end4: number, duration: number): GTweener { 22 | return TweenManager.createTween()._to4(start, start2, start3, start4, end, end2, end3, end4, duration); 23 | } 24 | 25 | public static toColor(start: number, end: number, duration: number): GTweener { 26 | return TweenManager.createTween()._toColor(start, end, duration); 27 | } 28 | 29 | public static delayedCall(delay: number): GTweener { 30 | return TweenManager.createTween().setDelay(delay); 31 | } 32 | 33 | public static shake(startX: number, startY: number, amplitude: number, duration: number): GTweener { 34 | return TweenManager.createTween()._shake(startX, startY, amplitude, duration); 35 | } 36 | 37 | public static isTweening(target: any, propType?: any): Boolean { 38 | return TweenManager.isTweening(target, propType); 39 | } 40 | 41 | public static kill(target: any, complete?: boolean, propType?: any): void { 42 | TweenManager.killTweens(target, complete, propType); 43 | } 44 | 45 | public static getTween(target: any, propType?: any): GTweener { 46 | return TweenManager.getTween(target, propType); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/utils/html/HtmlLink.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "../../core/DisplayObject"; 2 | import { RichTextField } from "../../core/text/RichTextField"; 3 | import { SelectionShape } from "../../core/text/SelectionShape"; 4 | import { HtmlElement } from "./HtmlElement"; 5 | import { IHtmlObject } from "./IHtmlObject"; 6 | import { bubbleEvent } from "../../core/Stage"; 7 | 8 | export class HtmlLink implements IHtmlObject { 9 | private _owner: RichTextField; 10 | private _element: HtmlElement; 11 | private _shape: SelectionShape; 12 | 13 | public constructor() { 14 | this._shape = new SelectionShape(); 15 | this._shape.on("click", () => { 16 | bubbleEvent(this._owner.obj3D, "click_link", this._element.getAttrString("href")); 17 | }); 18 | } 19 | 20 | public get displayObject(): DisplayObject { 21 | return this._shape; 22 | } 23 | 24 | public get element(): HtmlElement { 25 | return this._element; 26 | } 27 | 28 | public get width(): number { 29 | return 0; 30 | } 31 | 32 | public get height(): number { 33 | return 0; 34 | } 35 | 36 | public create(owner: RichTextField, element: HtmlElement): void { 37 | this._owner = owner; 38 | this._element = element; 39 | } 40 | 41 | public setArea(startLine: number, startCharX: number, endLine: number, endCharX: number): void { 42 | if (startLine == endLine && startCharX > endCharX) { 43 | let tmp = startCharX; 44 | startCharX = endCharX; 45 | endCharX = tmp; 46 | } 47 | this._shape.rects.length = 0; 48 | this._owner.getLinesShape(startLine, startCharX, endLine, endCharX, true, this._shape.rects); 49 | this._shape.refresh(); 50 | } 51 | 52 | public setPosition(x: number, y: number): void { 53 | this._shape.setPosition(x, y); 54 | } 55 | 56 | public add(): void { 57 | this._owner.addChild(this._shape); 58 | } 59 | 60 | public remove(): void { 61 | if (this._shape.parent) 62 | this._owner.removeChild(this._shape); 63 | } 64 | 65 | public release(): void { 66 | this._shape.offAll(); 67 | this._owner = null; 68 | this._element = null; 69 | } 70 | 71 | public dispose(): void { 72 | this._shape.dispose(); 73 | } 74 | } -------------------------------------------------------------------------------- /src/ui/PackageItem.ts: -------------------------------------------------------------------------------- 1 | import { Audio } from 'three'; 2 | import { PixelHitTestData } from "../core/hittest/PixelHitTest"; 3 | import { Frame } from "../core/MovieClip"; 4 | import { NTexture } from "../core/NTexture"; 5 | import { BitmapFont } from "../core/text/BitmapFont"; 6 | import { ByteBuffer } from "../utils/ByteBuffer"; 7 | import { Rect } from "../utils/Rect"; 8 | import { UIContentScaler } from "./UIContentScaler"; 9 | import { UIPackage } from "./UIPackage"; 10 | 11 | export class PackageItem { 12 | public owner: UIPackage; 13 | 14 | public type: number; 15 | public objectType: number; 16 | 17 | public id: string; 18 | public name: string; 19 | public width: number = 0; 20 | public height: number = 0; 21 | public file: string; 22 | public decoded?: boolean; 23 | public rawData?: ByteBuffer; 24 | 25 | public highResolution?: Array; 26 | public branches?: Array; 27 | 28 | //image 29 | public scale9Grid?: Rect; 30 | public scaleByTile?: boolean; 31 | public tileGridIndice?: number; 32 | public smoothing?: boolean; 33 | public texture?: NTexture; 34 | public pixelHitTestData?: PixelHitTestData; 35 | 36 | //movieclip 37 | public interval?: number; 38 | public repeatDelay?: number; 39 | public swing?: boolean; 40 | public frames?: Frame[]; 41 | 42 | //componenet 43 | public extensionType?: any; 44 | 45 | //font 46 | public bitmapFont?: BitmapFont; 47 | 48 | //sound 49 | public audioBuffer?: AudioBuffer; 50 | public sound?: Audio; 51 | 52 | constructor() { 53 | } 54 | 55 | public load(): Object { 56 | return this.owner.getItemAsset(this); 57 | } 58 | 59 | public getBranch(): PackageItem { 60 | if (this.branches && this.owner._branchIndex != -1) { 61 | var itemId: string = this.branches[this.owner._branchIndex]; 62 | if (itemId) 63 | return this.owner.getItemById(itemId); 64 | } 65 | 66 | return this; 67 | } 68 | 69 | public getHighResolution(): PackageItem { 70 | if (this.highResolution && UIContentScaler.scaleLevel > 0) { 71 | var itemId: string = this.highResolution[UIContentScaler.scaleLevel - 1]; 72 | if (itemId) 73 | return this.owner.getItemById(itemId); 74 | } 75 | 76 | return this; 77 | } 78 | 79 | public toString(): string { 80 | return this.name; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/tween/GPathPoint.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum CurveType { 3 | CRSpline, 4 | Bezier, 5 | CubicBezier, 6 | Straight 7 | } 8 | 9 | export class GPathPoint { 10 | public x: number; 11 | public y: number; 12 | 13 | public control1_x: number; 14 | public control1_y: number; 15 | 16 | public control2_x: number; 17 | public control2_y: number; 18 | 19 | public curveType: number; 20 | 21 | constructor() { 22 | this.x = 0; 23 | this.y = 0; 24 | this.control1_x = 0; 25 | this.control1_y = 0; 26 | this.control2_x = 0; 27 | this.control2_y = 0; 28 | this.curveType = 0; 29 | } 30 | 31 | public static newPoint(x: number, y: number, curveType: number): GPathPoint { 32 | var pt: GPathPoint = new GPathPoint(); 33 | pt.x = x || 0; 34 | pt.y = y || 0; 35 | pt.control1_x = 0; 36 | pt.control1_y = 0; 37 | pt.control2_x = 0; 38 | pt.control2_y = 0; 39 | pt.curveType = curveType || CurveType.CRSpline; 40 | 41 | return pt; 42 | } 43 | 44 | public static newBezierPoint(x: number, y: number, control1_x: number, control1_y: number): GPathPoint { 45 | var pt: GPathPoint = new GPathPoint(); 46 | pt.x = x || 0; 47 | pt.y = y || 0; 48 | pt.control1_x = control1_x || 0; 49 | pt.control1_y = control1_y || 0; 50 | pt.control2_x = 0; 51 | pt.control2_y = 0; 52 | pt.curveType = CurveType.Bezier; 53 | 54 | return pt; 55 | } 56 | 57 | public static newCubicBezierPoint(x: number, y: number, control1_x: number, control1_y: number, 58 | control2_x: number, control2_y: number): GPathPoint { 59 | var pt: GPathPoint = new GPathPoint(); 60 | pt.x = x || 0; 61 | pt.y = y || 0; 62 | pt.control1_x = control1_x || 0; 63 | pt.control1_y = control1_y || 0; 64 | pt.control2_x = control2_x || 0; 65 | pt.control2_y = control2_y || 0; 66 | pt.curveType = CurveType.CubicBezier; 67 | 68 | return pt; 69 | } 70 | 71 | public clone(): GPathPoint { 72 | var ret: GPathPoint = new GPathPoint(); 73 | ret.x = this.x; 74 | ret.y = this.y; 75 | ret.control1_x = this.control1_x; 76 | ret.control1_y = this.control1_y; 77 | ret.control2_x = this.control2_x; 78 | ret.control2_y = this.control2_y; 79 | ret.curveType = this.curveType; 80 | 81 | return ret; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ui/DragDropManager.ts: -------------------------------------------------------------------------------- 1 | import { Vector2 } from "three"; 2 | import { Stage } from "../core/Stage"; 3 | import { GLoader } from "./GLoader"; 4 | import { GObject } from "./GObject"; 5 | import { GRoot } from "./GRoot"; 6 | import { Event } from "../event/Event"; 7 | 8 | var _inst: DragDropManager; 9 | 10 | export class DragDropManager { 11 | 12 | private _agent: GLoader; 13 | private _sourceData: any; 14 | 15 | public static get inst(): DragDropManager { 16 | if (!_inst) 17 | _inst = new DragDropManager(); 18 | return _inst; 19 | } 20 | 21 | constructor() { 22 | let a = this._agent = new GLoader(); 23 | a.draggable = true; 24 | a.touchable = false;////important 25 | a.setSize(100, 100); 26 | a.setPivot(0.5, 0.5, true); 27 | a.align = "center"; 28 | a.verticalAlign = "middle"; 29 | a.sortingOrder = 1000000; 30 | a.on("drag_end", this.__dragEnd, this); 31 | } 32 | 33 | public get dragAgent(): GObject { 34 | return this._agent; 35 | } 36 | 37 | public get dragging(): boolean { 38 | return this._agent.parent != null; 39 | } 40 | 41 | public startDrag(icon: string, sourceData?: any, touchPointID?: number): void { 42 | if (this._agent.parent) 43 | return; 44 | 45 | this._sourceData = sourceData; 46 | this._agent.url = icon; 47 | GRoot.inst.addChild(this._agent); 48 | var pt: Vector2 = GRoot.inst.globalToLocal(Stage.touchPos.x, Stage.touchPos.y); 49 | this._agent.setPosition(pt.x, pt.y); 50 | this._agent.startDrag(touchPointID != null ? touchPointID : -1); 51 | } 52 | 53 | public cancel(): void { 54 | if (this._agent.parent) { 55 | this._agent.stopDrag(); 56 | GRoot.inst.removeChild(this._agent); 57 | this._sourceData = null; 58 | } 59 | } 60 | 61 | private __dragEnd(evt: Event): void { 62 | if (this._agent.parent == null) //cancelled 63 | return; 64 | 65 | GRoot.inst.removeChild(this._agent); 66 | 67 | var sourceData: any = this._sourceData; 68 | this._sourceData = null; 69 | 70 | var obj: GObject = GObject.cast(Stage.touchTarget); 71 | while (obj) { 72 | if (obj.hasListener("drop")) { 73 | obj.dispatchEvent("drop", sourceData); 74 | return; 75 | } 76 | 77 | obj = obj.parent; 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /test/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const TerserPlugin = require('terser-webpack-plugin'); 6 | 7 | module.exports = { 8 | mode: 'development', 9 | entry: path.resolve(__dirname, './src/index.ts'), 10 | devtool: 'source-map', 11 | plugins: [ 12 | new CleanWebpackPlugin(), 13 | new HtmlWebpackPlugin({ 14 | title: 'FairyGUI-threejs test', 15 | template: path.resolve(__dirname, 'template.html') 16 | }), 17 | new CopyWebpackPlugin([ 18 | { 19 | from:path.resolve(__dirname, 'assets'), 20 | to:path.resolve(__dirname, 'build/assets') 21 | } 22 | ]) 23 | ], 24 | resolve: { 25 | extensions: ['.tsx', '.ts', '.js'] 26 | }, 27 | output: { 28 | filename: 'bundle.js', 29 | path: path.resolve(__dirname, 'build') 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.tsx?$/, 35 | use: 'ts-loader', 36 | exclude: /node_modules/ 37 | }, 38 | { 39 | test: /\.css$/, 40 | use: [ 41 | 'style-loader', 42 | 'css-loader' 43 | ] 44 | }, 45 | { 46 | test: /\.(png|svg|jpg|gif)$/, 47 | use: [ 48 | 'file-loader' 49 | ] 50 | }, 51 | { 52 | test: /\.(woff|woff2|eot|ttf|otf)$/, 53 | use: [ 54 | 'file-loader' 55 | ] 56 | }, 57 | { 58 | test: /\.(csv|tsv)$/, 59 | use: [ 60 | 'csv-loader' 61 | ] 62 | }, 63 | { 64 | test: /\.xml$/, 65 | use: [ 66 | 'xml-loader' 67 | ] 68 | } 69 | ] 70 | }, 71 | 72 | optimization: { 73 | minimize: true, 74 | minimizer: [new TerserPlugin({ sourceMap: true })], 75 | splitChunks: { 76 | chunks: 'all', 77 | automaticNameDelimiter: '_', 78 | name: true, 79 | cacheGroups: { 80 | three: { 81 | name: 'three', 82 | chunks: 'all', 83 | test: /[\\/]node_modules[\\/](three)[\\/]/, 84 | } 85 | }, 86 | } 87 | } 88 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FairyGUI-three 2 | 3 | #### A GUI Editor&framework for Three.js #### 4 | 5 | Official website: [www.fairygui.com](https://www.fairygui.com) 6 | 7 | ### Usage ### 8 | 9 | Step 1, we use the editor to create the UI. 10 | 11 | ![](images/20200610-084916.png) 12 | 13 | Step 2, we only need a little code to display it. 14 | 15 | ```javascript 16 | import * as fgui from "fairygui-three"; 17 | 18 | var renderer; 19 | var scene; 20 | var view; 21 | 22 | init(); 23 | animate(); 24 | 25 | function init() { 26 | //THREE initialization code here 27 | 28 | fgui.Stage.init(renderer, { screenMode:'horizontal' }); //screenMode is optional if you dont want to rotate the screen 29 | fgui.Stage.scene = scene; 30 | 31 | fgui.UIPackage.loadPackage('path/to/UI').then(()=> { 32 | view = fgui.UIPackage.CreateObject('Basics', 'Main'); 33 | view.makeFullScreen(); 34 | fgui.GRoot.inst.addChild(view); 35 | }); 36 | } 37 | 38 | function animate() { 39 | 40 | requestAnimationFrame( animate ); 41 | 42 | fgui.Stage.update(); 43 | renderer.render(scene, fgui.Stage.camera); 44 | } 45 | ``` 46 | 47 | You should see [this](https://fairygui.com/threejs-demo/main/) 48 | 49 | In the example above, an UI is created and displayed by an orthographic camera (fgui.Stage.camera) . It's easy to display UI by an specific perspective camera. 50 | 51 | ```javascript 52 | import * as fgui from "fairygui-three"; 53 | 54 | var renderer; 55 | var scene; 56 | var camera; 57 | var view; 58 | 59 | init(); 60 | animate(); 61 | 62 | function init() { 63 | //THREE initialization code here 64 | 65 | camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 ); 66 | camera.position.z = 1; 67 | 68 | fgui.Stage.init(renderer); 69 | fgui.Stage.scene = scene; 70 | 71 | fgui.UIPackage.loadPackage('path/to/UI').then(()=> { 72 | view = fgui.UIPackage.CreateObject('3DInventory', 'Main'); 73 | view.displayObject.camera = camera; 74 | view.displayObject.setLayer(0); 75 | 76 | let container = new Group(); 77 | container.scale.set(0.5, 0.5, 0.5); 78 | container.add(view.obj3D); 79 | scene.add(container); 80 | }); 81 | } 82 | 83 | function animate() { 84 | 85 | requestAnimationFrame( animate ); 86 | 87 | fgui.Stage.update(); 88 | renderer.render(scene, camera); 89 | } 90 | ``` 91 | 92 | You should see [this](https://fairygui.com/threejs-demo/3d/) 93 | 94 | If a perspective camera is using for all UI, 95 | 96 | ```javascript 97 | Stage.init(renderer, { defaultLayer:0 }); 98 | Stage.camera = yourCamera; 99 | ``` 100 | 101 | # License 102 | MIT 103 | -------------------------------------------------------------------------------- /src/ui/GTextInput.ts: -------------------------------------------------------------------------------- 1 | import { GTextField } from "./GTextField"; 2 | import { ByteBuffer } from "../utils/ByteBuffer"; 3 | import { InputTextField } from "../core/text/InputTextField"; 4 | 5 | export class GTextInput extends GTextField { 6 | constructor() { 7 | super(); 8 | } 9 | 10 | protected createDisplayObject(): void { 11 | this._displayObject = this._textField = new InputTextField(); 12 | } 13 | 14 | public get password(): boolean { 15 | return (this._textField).password; 16 | } 17 | 18 | public set password(value: boolean) { 19 | (this._textField).password = value; 20 | } 21 | 22 | public get keyboardType(): string { 23 | return (this._textField).keyboardType; 24 | } 25 | 26 | public set keyboardType(value: string) { 27 | (this._textField).keyboardType = value; 28 | } 29 | 30 | public set editable(value: boolean) { 31 | (this._textField).editable = value; 32 | } 33 | 34 | public get editable(): boolean { 35 | return (this._textField).editable; 36 | } 37 | 38 | public set maxLength(value: number) { 39 | (this._textField).maxLength = value; 40 | } 41 | 42 | public get maxLength(): number { 43 | return (this._textField).maxLength; 44 | } 45 | 46 | public set promptText(value: string) { 47 | (this._textField).promptText = value; 48 | } 49 | 50 | public get promptText(): string { 51 | return (this._textField).promptText; 52 | } 53 | 54 | public set restrict(value: string) { 55 | (this._textField).restrict = value; 56 | } 57 | 58 | public get restrict(): string { 59 | return (this._textField).restrict; 60 | } 61 | 62 | public requestFocus(): void { 63 | } 64 | 65 | public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void { 66 | super.setup_beforeAdd(buffer, beginPos); 67 | 68 | buffer.seek(beginPos, 4); 69 | 70 | var str: string = buffer.readS(); 71 | if (str != null) 72 | this.promptText = str; 73 | 74 | str = buffer.readS(); 75 | if (str != null) 76 | this.restrict = str; 77 | 78 | var iv: number = buffer.readInt(); 79 | if (iv != 0) 80 | this.maxLength = iv; 81 | iv = buffer.readInt(); 82 | if (iv != 0) { 83 | if (iv == 4) 84 | this.keyboardType = "number"; 85 | else if (iv == 3) 86 | this.keyboardType = "url"; 87 | } 88 | if (buffer.readBool()) 89 | this.password = true; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/utils/html/HtmlImage.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "../../core/DisplayObject"; 2 | import { RichTextField } from "../../core/text/RichTextField"; 3 | import { LoaderFillType, ObjectType } from "../../ui/FieldTypes"; 4 | import { GLoader } from "../../ui/GLoader"; 5 | import { PackageItem } from "../../ui/PackageItem"; 6 | import { UIPackage, Decls } from "../../ui/UIPackage"; 7 | import { HtmlElement } from "./HtmlElement"; 8 | import { IHtmlObject } from "./IHtmlObject"; 9 | 10 | export class HtmlImage implements IHtmlObject { 11 | public readonly loader: GLoader; 12 | 13 | private _owner: RichTextField; 14 | private _element: HtmlElement; 15 | 16 | public constructor() { 17 | this.loader = Decls.UIObjectFactory.newObject(ObjectType.Loader); 18 | this.loader.fill = LoaderFillType.ScaleFree; 19 | this.loader.touchable = false; 20 | } 21 | 22 | public get displayObject(): DisplayObject { 23 | return this.loader.displayObject; 24 | } 25 | 26 | public get element(): HtmlElement { 27 | return this._element; 28 | } 29 | 30 | public get width(): number { 31 | return this.loader.width; 32 | } 33 | 34 | public get height(): number { 35 | return this.loader.height; 36 | } 37 | 38 | public create(owner: RichTextField, element: HtmlElement): void { 39 | this._owner = owner; 40 | this._element = element; 41 | 42 | let sourceWidth = 0; 43 | let sourceHeight = 0; 44 | let src: string = element.getAttrString("src"); 45 | if (src != null) { 46 | let pi: PackageItem = UIPackage.getItemByURL(src); 47 | if (pi) { 48 | sourceWidth = pi.width; 49 | sourceHeight = pi.height; 50 | } 51 | } 52 | 53 | this.loader.url = src; 54 | 55 | let width = element.getAttrInt("width", sourceWidth); 56 | let height = element.getAttrInt("height", sourceHeight); 57 | 58 | if (width == 0) 59 | width = 5; 60 | if (height == 0) 61 | height = 10; 62 | this.loader.setSize(width, height); 63 | } 64 | 65 | public setPosition(x: number, y: number): void { 66 | this.loader.setPosition(x, y); 67 | } 68 | 69 | public add(): void { 70 | this._owner.addChild(this.loader.displayObject); 71 | } 72 | 73 | public remove(): void { 74 | if (this.loader.displayObject.parent) 75 | this._owner.removeChild(this.loader.displayObject); 76 | } 77 | 78 | public release(): void { 79 | this.loader.offAll(); 80 | this.loader.url = null; 81 | this._owner = null; 82 | this._element = null; 83 | } 84 | 85 | public dispose(): void { 86 | this.loader.dispose(); 87 | } 88 | } -------------------------------------------------------------------------------- /src/FairyGUI.ts: -------------------------------------------------------------------------------- 1 | export { GGroup } from "./ui/GGroup"; 2 | export { GObject } from "./ui/GObject"; 3 | export { GGraph } from "./ui/GGraph"; 4 | export { GImage } from "./ui/GImage"; 5 | export { GMovieClip } from "./ui/GMovieClip"; 6 | export { GRoot } from "./ui/GRoot"; 7 | export { GTextField } from "./ui/GTextField"; 8 | export { GRichTextField } from "./ui/GRichTextField"; 9 | export { GTextInput } from "./ui/GTextInput"; 10 | export { GLoader } from "./ui/GLoader"; 11 | export { GLoader3D } from "./ui/GLoader3D"; 12 | export { GComponent } from "./ui/GComponent"; 13 | export { GLabel } from "./ui/GLabel"; 14 | export { GButton } from "./ui/GButton"; 15 | export { GComboBox } from "./ui/GComboBox"; 16 | export { GSlider } from "./ui/GSlider"; 17 | export { GProgressBar } from "./ui/GProgressBar"; 18 | export { GScrollBar } from "./ui/GScrollBar"; 19 | export { GList } from "./ui/GList"; 20 | export { GTree } from "./ui/GTree"; 21 | export { GTreeNode } from "./ui/GTreeNode"; 22 | export { Window } from "./ui/Window"; 23 | export { PopupMenu } from "./ui/PopupMenu"; 24 | export { Controller } from "./ui/Controller"; 25 | export { Transition } from "./ui/Transition"; 26 | export { ScrollPane } from "./ui/ScrollPane"; 27 | export { RelationType } from "./ui/FieldTypes"; 28 | export { UIPackage } from "./ui/UIPackage"; 29 | export { PackageItem } from "./ui/PackageItem"; 30 | export { GObjectPool } from "./ui/GObjectPool"; 31 | export { UIObjectFactory } from "./ui/UIObjectFactory"; 32 | export { UIConfig } from "./ui/UIConfig"; 33 | export { DragDropManager } from "./ui/DragDropManager"; 34 | export { AsyncOperation } from "./ui/AsyncOperation"; 35 | export { TranslationHelper } from "./ui/TranslationHelper"; 36 | export * from "./ui/UIContentScaler"; 37 | export * from "./ui/FieldTypes" 38 | 39 | export { DisplayObject } from "./core/DisplayObject"; 40 | export { Image } from "./core/Image"; 41 | export { MovieClip, Frame } from "./core/MovieClip"; 42 | export { Shape } from "./core/Shape"; 43 | export { Stage } from "./core/Stage"; 44 | export { NTexture } from "./core/NTexture"; 45 | export { NGraphics } from "./core/NGraphics"; 46 | export { NMaterial } from "./core/NMaterial"; 47 | export { TextField } from "./core/text/TextField" 48 | export { RichTextField } from "./core/text/RichTextField" 49 | export { InputTextField } from "./core/text/InputTextField" 50 | export { TextFormat } from "./core/text/TextFormat" 51 | export { DynamicFont } from "./core/text/DynamicFont" 52 | export { FontManager } from "./core/text/FontManager" 53 | 54 | export { Event } from "./event/Event"; 55 | export { EventDispatcher } from "./event/EventDispatcher"; 56 | 57 | export { GTween } from "./tween/GTween"; 58 | export { GTweener } from "./tween/GTweener"; 59 | export { EaseType } from "./tween/EaseType"; 60 | 61 | export { UBBParser } from "./utils/UBBParser"; 62 | export { ByteBuffer } from "./utils/ByteBuffer"; 63 | export { Rect } from "./utils/Rect"; 64 | export { Color4 } from "./utils/Color"; 65 | export { Timers } from "./utils/Timers"; 66 | export * from "./utils/ToolSet" -------------------------------------------------------------------------------- /src/event/Event.ts: -------------------------------------------------------------------------------- 1 | import { Pool } from "../utils/Pool"; 2 | import { DisplayObject } from "../core/DisplayObject"; 3 | import { EventDispatcher } from "./EventDispatcher"; 4 | 5 | export type EventType = "touch_begin" | "touch_end" | "touch_move" | "click" | "right_click" | "roll_over" | "roll_out" | "mouse_wheel" 6 | | "content_scale_factor_changed" 7 | | "added_to_stage" | "removed_from_stage" 8 | | "pos_changed" | "size_changed" 9 | | "status_changed" 10 | | "focus_in" | "focus_out" 11 | | "drag_start" | "drag_move" | "drag_end" | "drop" 12 | | "scroll" | "scroll_end" | "pull_down_release" | "pull_up_release" 13 | | "click_item" | "click_link" 14 | | "play_end" | "gear_stop"; 15 | 16 | export interface InputInfo { 17 | x: number; 18 | y: number; 19 | mouseWheelDelta: number; 20 | touchId: number; 21 | button: number; 22 | clickCount: number; 23 | holdTime: number; 24 | shiftKey?: boolean; 25 | ctrlKey?: boolean; 26 | commandKey?: boolean; 27 | 28 | isDblClick: boolean; 29 | isRightButton: boolean; 30 | } 31 | 32 | export var lastInput: InputInfo = { 33 | x: 0, 34 | y: 0, 35 | mouseWheelDelta: 0, 36 | touchId: 0, 37 | button: 0, 38 | clickCount: 0, 39 | holdTime: 0, 40 | 41 | get isDblClick() { 42 | return this.clickCount == 2; 43 | }, 44 | 45 | get isRightButton() { 46 | return this.button == 2; 47 | } 48 | } 49 | 50 | export class Event { 51 | public data: any = null; 52 | 53 | public _defaultPrevented: boolean; 54 | public _stopsPropagation: boolean; 55 | public _touchCapture: boolean; 56 | public _callChain: Array = []; 57 | 58 | public _type: string; 59 | public _sender: EventDispatcher; 60 | public _initiator: DisplayObject; 61 | 62 | public constructor() { 63 | } 64 | 65 | public get type(): string { 66 | return this._type; 67 | } 68 | 69 | public get sender(): EventDispatcher { 70 | return this._sender; 71 | } 72 | 73 | public get initiator(): DisplayObject { 74 | return this._initiator; 75 | } 76 | 77 | public get input(): InputInfo { 78 | return lastInput; 79 | } 80 | 81 | public stopPropagation() { 82 | this._stopsPropagation = true; 83 | } 84 | 85 | public preventDefault() { 86 | this._defaultPrevented = true; 87 | } 88 | 89 | public captureTouch() { 90 | this._touchCapture = true; 91 | } 92 | 93 | public get isDefaultPrevented(): boolean { 94 | return this._defaultPrevented; 95 | } 96 | } 97 | 98 | export var EventPool: Pool = new Pool(Event, 99 | obj => { 100 | obj._stopsPropagation = false; 101 | obj._defaultPrevented = false; 102 | obj._touchCapture = false; 103 | }, 104 | obj => { 105 | obj.data = null; 106 | obj._initiator = null; 107 | obj._sender = null; 108 | }); -------------------------------------------------------------------------------- /src/ui/UIConfig.ts: -------------------------------------------------------------------------------- 1 | import { Color4 } from "../utils/Color"; 2 | import { ScrollBarDisplayType } from "./FieldTypes"; 3 | 4 | export class UIConfig { 5 | //Default font name 6 | public static defaultFont: string = "Arial"; 7 | 8 | //Resource using in Window.ShowModalWait for locking the window. 9 | public static windowModalWaiting: string; 10 | //Resource using in GRoot.ShowModalWait for locking the screen. 11 | public static globalModalWaiting: string; 12 | 13 | //When a modal window is in front, the background becomes dark. 14 | public static modalLayerColor: Color4 = new Color4(0x333333, 0.2); 15 | 16 | //Default button click sound 17 | public static buttonSound: string = null; 18 | public static buttonSoundVolumeScale: number = 1; 19 | 20 | //Default button click sound 21 | public static horizontalScrollBar: string = null; 22 | 23 | public static verticalScrollBar: string = null; 24 | 25 | //Scrolling step in pixels 26 | public static defaultScrollStep: number = 25; 27 | //Deceleration ratio of scrollpane when its in touch dragging. 28 | public static defaultScrollDecelerationRate: number = 0.967; 29 | 30 | //Default scrollbar display mode. Recommened visible for Desktop and Auto for mobile. 31 | public static defaultScrollBarDisplay: number = ScrollBarDisplayType.Visible; 32 | //Allow dragging the content to scroll. Recommeded true for mobile. 33 | public static defaultScrollTouchEffect: boolean = true; 34 | //The "rebound" effect in the scolling container. Recommeded true for mobile. 35 | public static defaultScrollBounceEffect: boolean = true; 36 | 37 | /** 38 | * 当滚动容器设置为“贴近ITEM”时,判定贴近到哪一个ITEM的滚动距离阀值。 39 | */ 40 | public static defaultScrollSnappingThreshold: number = 0.1; 41 | 42 | /** 43 | * 当滚动容器设置为“页面模式”时,判定翻到哪一页的滚动距离阀值。 44 | */ 45 | public static defaultScrollPagingThreshold: number = 0.3; 46 | 47 | //Resources for PopupMenu. 48 | public static popupMenu: string = null; 49 | //Resources for seperator of PopupMenu. 50 | public static popupMenu_seperator: string = null; 51 | //In case of failure of loading content for GLoader, use this sign to indicate an error. 52 | public static loaderErrorSign: string = null; 53 | //Resources for tooltips. 54 | public static tooltipsWin: string = null; 55 | 56 | //Max items displayed in combobox without scrolling. 57 | public static defaultComboBoxVisibleItemCount: number = 10; 58 | 59 | // Pixel offsets of finger to trigger scrolling. 60 | public static touchScrollSensitivity: number = 20; 61 | 62 | // Pixel offsets of finger to trigger dragging. 63 | public static touchDragSensitivity: number = 10; 64 | 65 | // Pixel offsets of mouse pointer to trigger dragging. 66 | public static clickDragSensitivity: number = 2; 67 | 68 | // When click the window, brings to front automatically. 69 | public static bringWindowToFrontOnClick: boolean = true; 70 | 71 | public static frameTimeForAsyncUIConstruction: number = 2; 72 | 73 | public static packageFileExtension: string = "fui"; 74 | } 75 | -------------------------------------------------------------------------------- /src/utils/html/HtmlElement.ts: -------------------------------------------------------------------------------- 1 | import { Vector2 } from "three"; 2 | import { TextFormat } from "../../core/text/TextFormat"; 3 | import { Pool } from "../Pool"; 4 | import { XMLIterator } from "../xml/XMLIterator"; 5 | import { XMLUtils } from "../xml/XMLUtils"; 6 | import { IHtmlObject } from "./IHtmlObject"; 7 | 8 | export enum HtmlElementType { 9 | Text, 10 | Link, 11 | Image, 12 | Input, 13 | Select, 14 | Object, 15 | 16 | //internal 17 | LinkEnd, 18 | } 19 | 20 | export class HtmlElement { 21 | public type: HtmlElementType; 22 | public name: string; 23 | public text: string; 24 | public format: TextFormat; 25 | public charIndex: number; 26 | 27 | public htmlObject: IHtmlObject; 28 | public status: number; //1 hidden 2 clipped 4 added 29 | public space: number; 30 | public position: Vector2; 31 | 32 | public _attributes: Record; 33 | 34 | public constructor() { 35 | this.format = new TextFormat(); 36 | this.position = new Vector2(); 37 | } 38 | 39 | public getAttr(attrName: string): any { 40 | if (this._attributes == null) 41 | return null; 42 | 43 | return this._attributes[attrName]; 44 | } 45 | 46 | public setAttr(attrName: string, attrValue: any) { 47 | if (this._attributes == null) 48 | this._attributes = {}; 49 | 50 | this._attributes[attrName] = attrValue; 51 | } 52 | 53 | public getAttrString(attrName: string, defValue?: string) { 54 | return XMLUtils.getString(this._attributes, attrName, defValue); 55 | } 56 | 57 | public getAttrInt(attrName: string, defValue?: number): number { 58 | return XMLUtils.getInt(this._attributes, attrName, defValue); 59 | } 60 | 61 | public getAttrFloat(attrName: string, defValue?: number): number { 62 | return XMLUtils.getFloat(this._attributes, attrName, defValue); 63 | } 64 | 65 | public getAttrBool(attrName: string, defValue?: boolean): boolean { 66 | return XMLUtils.getBool(this._attributes, attrName, defValue); 67 | } 68 | 69 | public getAttrColor(attrName: string, defValue?: number): number { 70 | return XMLUtils.getColor(this._attributes, attrName, defValue); 71 | } 72 | 73 | public fetchAttributes() { 74 | this._attributes = XMLIterator.getAttributes(this._attributes); 75 | } 76 | 77 | public get isEntity(): boolean { 78 | return this.type == HtmlElementType.Image || this.type == HtmlElementType.Select 79 | || this.type == HtmlElementType.Input || this.type == HtmlElementType.Object; 80 | } 81 | 82 | public reset() { 83 | this.name = null; 84 | this.text = null; 85 | this.htmlObject = null; 86 | this.status = 0; 87 | this._attributes = null; 88 | } 89 | } 90 | 91 | export var elementPool = new Pool(HtmlElement, 92 | (element, ...argArray: any[]) => { 93 | element.type = argArray[0]; 94 | if (element.type != HtmlElementType.Text && element._attributes == null) 95 | element._attributes = {}; 96 | }, 97 | element => element.reset()); -------------------------------------------------------------------------------- /src/core/mesh/RegularPolygonMesh.ts: -------------------------------------------------------------------------------- 1 | import { IMeshFactory } from "./MeshFactory"; 2 | import { IHitTest } from "../hittest/IHitTest"; 3 | import { Rect } from "../../utils/Rect"; 4 | import { VertexBuffer } from "./VertexBuffer"; 5 | import { Color4 } from "../../utils/Color"; 6 | 7 | export class RegularPolygonMesh implements IMeshFactory, IHitTest { 8 | public drawRect: Rect; 9 | public sides: number; 10 | public lineWidth: number; 11 | public lineColor: Color4; 12 | public centerColor: Color4; 13 | public fillColor: Color4; 14 | public distances: Array; 15 | public rotation: number; 16 | 17 | public constructor() { 18 | this.sides = 3; 19 | this.lineWidth = 1; 20 | this.lineColor = new Color4(); 21 | } 22 | 23 | public onPopulateMesh(vb: VertexBuffer) { 24 | if (this.distances != null && this.distances.length < this.sides) { 25 | console.error("distances.Length 0) { 47 | vb.addVert(xv + centerX, yv + centerY, 0, this.lineColor); 48 | 49 | xv = Math.cos(angle) * r + centerX; 50 | yv = Math.sin(angle) * r + centerY; 51 | vb.addVert(xv, yv, 0, this.lineColor); 52 | } 53 | angle += angleDelta; 54 | } 55 | 56 | if (this.lineWidth > 0) { 57 | let tmp: number = this.sides * 3; 58 | for (let i: number = 0; i < tmp; i += 3) { 59 | if (i != tmp - 3) { 60 | vb.addTriangle(0, i + 4, i + 1); 61 | vb.addTriangle(i + 5, i + 3, i + 2); 62 | vb.addTriangle(i + 3, i + 5, i + 6); 63 | } 64 | else { 65 | vb.addTriangle(0, 1, i + 1); 66 | vb.addTriangle(2, i + 3, i + 2); 67 | vb.addTriangle(i + 3, 2, 3); 68 | } 69 | } 70 | } 71 | else { 72 | for (let i: number = 0; i < this.sides; i++) 73 | vb.addTriangle(0, (i == this.sides - 1) ? 1 : i + 2, i + 1); 74 | } 75 | } 76 | 77 | public hitTest(contentRect: Rect, x: number, y: number): boolean { 78 | if (this.drawRect) 79 | return this.drawRect.contains(x, y); 80 | else 81 | return contentRect.contains(x, y); 82 | } 83 | } -------------------------------------------------------------------------------- /src/ui/UIContentScaler.ts: -------------------------------------------------------------------------------- 1 | import { Stage, broadcastEvent } from "../core/Stage"; 2 | 3 | export enum ScaleMode { 4 | ConstantPixelSize, 5 | ScaleWithScreenSize, 6 | ConstantPhysicalSize 7 | } 8 | 9 | export enum ScreenMatchMode { 10 | MatchWidthOrHeight, 11 | MatchWidth, 12 | MatchHeight 13 | } 14 | 15 | export class UIContentScaler { 16 | public static get scaleFactor() { return _scaleFactor; } 17 | public static get scaleLevel() { return _scaleLevel; } 18 | 19 | public static scaleWithScreenSize(designResolutionX: number, designResolutionY: number, screenMatchMode?: ScreenMatchMode) { 20 | _designResolutionX = designResolutionX; 21 | _designResolutionY = designResolutionY; 22 | _scaleMode = ScaleMode.ScaleWithScreenSize; 23 | _screenMatchMode = screenMatchMode || ScreenMatchMode.MatchWidthOrHeight; 24 | refresh(); 25 | } 26 | 27 | public static setConstant(constantScaleFactor?: number) { 28 | _scaleMode = ScaleMode.ConstantPixelSize; 29 | _constantScaleFactor = constantScaleFactor || 1; 30 | refresh(); 31 | } 32 | } 33 | 34 | var _scaleMode: ScaleMode = ScaleMode.ConstantPixelSize; 35 | var _screenMatchMode: ScreenMatchMode; 36 | var _designResolutionX: number = 1136; 37 | var _designResolutionY: number = 640; 38 | // var _fallbackScreenDPI: number; 39 | // var _defaultSpriteDPI: number; 40 | var _constantScaleFactor: number = 1; 41 | var _ignoreOrientation: boolean; 42 | 43 | var _scaleFactor: number = 1; 44 | var _scaleLevel: number = 0; 45 | 46 | Stage.eventDispatcher.on("size_changed", refresh); 47 | 48 | function refresh() { 49 | let screenWidth: number = Stage.width; 50 | let screenHeight: number = Stage.height; 51 | 52 | if (_scaleMode == ScaleMode.ScaleWithScreenSize) { 53 | if (_designResolutionX == 0 || _designResolutionY == 0) 54 | return; 55 | 56 | let dx = _designResolutionX; 57 | let dy = _designResolutionY; 58 | if (!_ignoreOrientation && (screenWidth > screenHeight && dx < dy || screenWidth < screenHeight && dx > dy)) { 59 | //scale should not change when orientation change 60 | let tmp = dx; 61 | dx = dy; 62 | dy = tmp; 63 | } 64 | 65 | if (_screenMatchMode == ScreenMatchMode.MatchWidthOrHeight) { 66 | let s1 = screenWidth / dx; 67 | let s2 = screenHeight / dy; 68 | _scaleFactor = Math.min(s1, s2); 69 | } 70 | else if (_screenMatchMode == ScreenMatchMode.MatchWidth) 71 | _scaleFactor = screenWidth / dx; 72 | else 73 | _scaleFactor = screenHeight / dy; 74 | } 75 | else if (_scaleMode == ScaleMode.ConstantPhysicalSize) { 76 | // let dpi = Screen.dpi; 77 | // if (dpi == 0) 78 | // dpi = _fallbackScreenDPI; 79 | // if (dpi == 0) 80 | // dpi = 96; 81 | // _scaleFactor = dpi / (defaultSpriteDPI == 0 ? 96 : defaultSpriteDPI); 82 | } 83 | else 84 | _scaleFactor = _constantScaleFactor; 85 | 86 | if (_scaleFactor > 10) 87 | _scaleFactor = 10; 88 | 89 | if (_scaleFactor > 3) 90 | _scaleLevel = 3; //x4 91 | else if (_scaleFactor > 2) 92 | _scaleLevel = 2; //x3 93 | else if (_scaleFactor > 1) 94 | _scaleLevel = 1; //x2 95 | else 96 | _scaleLevel = 0; 97 | 98 | broadcastEvent(Stage.scene, "content_scale_factor_changed"); 99 | } -------------------------------------------------------------------------------- /src/ui/FieldTypes.ts: -------------------------------------------------------------------------------- 1 | 2 | export type AlignType = "left" | "center" | "right"; 3 | export type VertAlignType = "top" | "middle" | "bottom"; 4 | 5 | export enum ButtonMode { 6 | Common, 7 | Check, 8 | Radio 9 | } 10 | export enum AutoSizeType { 11 | None, 12 | Both, 13 | Height, 14 | Shrink 15 | } 16 | 17 | export enum LoaderFillType { 18 | None, 19 | Scale, 20 | ScaleMatchHeight, 21 | ScaleMatchWidth, 22 | ScaleFree, 23 | ScaleNoBorder 24 | } 25 | export enum ListLayoutType { 26 | SingleColumn, 27 | SingleRow, 28 | FlowHorizontal, 29 | FlowVertical, 30 | Pagination 31 | } 32 | export enum ListSelectionMode { 33 | Single, 34 | Multiple, 35 | Multiple_SingleClick, 36 | None 37 | } 38 | export enum OverflowType { 39 | Visible, 40 | Hidden, 41 | Scroll 42 | } 43 | export enum PackageItemType { 44 | Image, 45 | MovieClip, 46 | Sound, 47 | Component, 48 | Atlas, 49 | Font, 50 | Swf, 51 | Misc, 52 | Unknown 53 | } 54 | export enum ObjectType { 55 | Image, 56 | MovieClip, 57 | Swf, 58 | Graph, 59 | Loader, 60 | Group, 61 | Text, 62 | RichText, 63 | InputText, 64 | Component, 65 | List, 66 | Label, 67 | Button, 68 | ComboBox, 69 | ProgressBar, 70 | Slider, 71 | ScrollBar, 72 | Tree, 73 | Loader3D 74 | } 75 | export enum ProgressTitleType { 76 | Percent, 77 | ValueAndMax, 78 | Value, 79 | Max 80 | } 81 | export enum ScrollBarDisplayType { 82 | Default, 83 | Visible, 84 | Auto, 85 | Hidden 86 | } 87 | export enum ScrollType { 88 | Horizontal, 89 | Vertical, 90 | Both 91 | } 92 | export enum FlipType { 93 | None, 94 | Horizontal, 95 | Vertical, 96 | Both 97 | } 98 | export enum ChildrenRenderOrder { 99 | Ascent, 100 | Descent, 101 | Arch 102 | } 103 | export enum GroupLayoutType { 104 | None, 105 | Horizontal, 106 | Vertical 107 | } 108 | export enum PopupDirection { 109 | Auto, 110 | Up, 111 | Down 112 | } 113 | export enum RelationType { 114 | Left_Left = 0, 115 | Left_Center = 1, 116 | Left_Right = 2, 117 | Center_Center = 3, 118 | Right_Left = 4, 119 | Right_Center = 5, 120 | Right_Right = 6, 121 | 122 | Top_Top = 7, 123 | Top_Middle = 8, 124 | Top_Bottom = 9, 125 | Middle_Middle = 10, 126 | Bottom_Top = 11, 127 | Bottom_Middle = 12, 128 | Bottom_Bottom = 13, 129 | 130 | Width = 14, 131 | Height = 15, 132 | 133 | LeftExt_Left = 16, 134 | LeftExt_Right = 17, 135 | RightExt_Left = 18, 136 | RightExt_Right = 19, 137 | TopExt_Top = 20, 138 | TopExt_Bottom = 21, 139 | BottomExt_Top = 22, 140 | BottomExt_Bottom = 23, 141 | 142 | Size = 24 143 | } 144 | 145 | export enum FillMethod { 146 | None, 147 | Horizontal, 148 | Vertical, 149 | Radial90, 150 | Radial180, 151 | Radial360, 152 | } 153 | 154 | export enum FillOrigin { 155 | Top, 156 | Bottom, 157 | Left, 158 | Right, 159 | 160 | TopLeft = 0, 161 | TopRight = 1, 162 | BottomLeft = 2, 163 | BottomRight = 3 164 | } 165 | 166 | export enum FillOrigin90 { 167 | TopLeft, 168 | TopRight, 169 | BottomLeft, 170 | BottomRight 171 | } 172 | 173 | export enum ObjectPropID { 174 | Text, 175 | Icon, 176 | Color, 177 | OutlineColor, 178 | Playing, 179 | Frame, 180 | DeltaTime, 181 | TimeScale, 182 | FontSize, 183 | Selected 184 | } 185 | -------------------------------------------------------------------------------- /src/ui/GGraph.ts: -------------------------------------------------------------------------------- 1 | import { GObject } from "./GObject"; 2 | import { ObjectPropID } from "./FieldTypes"; 3 | import { ByteBuffer } from "../utils/ByteBuffer"; 4 | import { Shape } from "../core/Shape"; 5 | 6 | export class GGraph extends GObject { 7 | private _shape: Shape; 8 | 9 | constructor() { 10 | super(); 11 | } 12 | 13 | public get shape(): Shape { 14 | return this._shape; 15 | } 16 | 17 | public get color(): number { 18 | return this._shape.graphics.color; 19 | } 20 | 21 | public set color(value: number) { 22 | if (this._shape.graphics.color != value) { 23 | this._shape.graphics.color = value; 24 | this.updateGear(4); 25 | } 26 | } 27 | 28 | protected createDisplayObject(): void { 29 | this._displayObject = this._shape = new Shape(); 30 | } 31 | 32 | public getProp(index: number): any { 33 | if (index == ObjectPropID.Color) 34 | return this.color; 35 | else 36 | return super.getProp(index); 37 | } 38 | 39 | public setProp(index: number, value: any): void { 40 | if (index == ObjectPropID.Color) 41 | this.color = value; 42 | else 43 | super.setProp(index, value); 44 | } 45 | 46 | public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void { 47 | super.setup_beforeAdd(buffer, beginPos); 48 | 49 | buffer.seek(beginPos, 5); 50 | 51 | let type = buffer.readByte(); 52 | if (type != 0) { 53 | let i: number; 54 | let cnt: number; 55 | 56 | let lineSize = buffer.readInt(); 57 | let lineColor = buffer.readFullColor(); 58 | let fillColor = buffer.readFullColor(); 59 | let roundedRect = buffer.readBool(); 60 | let cornerRadius: Array; 61 | if (roundedRect) { 62 | cornerRadius = []; 63 | for (i = 0; i < 4; i++) 64 | cornerRadius[i] = buffer.readFloat(); 65 | } 66 | 67 | if (type == 1) { 68 | if (roundedRect) 69 | this._shape.drawRoundRect(lineSize, lineColor, fillColor, cornerRadius[0], cornerRadius[1], cornerRadius[2], cornerRadius[3]); 70 | else 71 | this._shape.drawRect(lineSize, lineColor, fillColor); 72 | } 73 | else if (type == 2) { 74 | this._shape.drawEllipse(lineSize, fillColor, lineColor, fillColor, 0, 360); 75 | } 76 | else if (type == 3) { 77 | cnt = buffer.readShort(); 78 | let points: Array = []; 79 | points.length = cnt; 80 | for (i = 0; i < cnt; i++) 81 | points[i] = buffer.readFloat(); 82 | 83 | this._shape.drawPolygon(points, fillColor, lineSize, lineColor); 84 | } 85 | else if (type == 4) { 86 | let sides = buffer.readShort(); 87 | let startAngle = buffer.readFloat(); 88 | cnt = buffer.readShort(); 89 | let distances: Array; 90 | if (cnt > 0) { 91 | distances = []; 92 | for (i = 0; i < cnt; i++) 93 | distances[i] = buffer.readFloat(); 94 | } 95 | 96 | this._shape.drawRegularPolygon(sides, lineSize, fillColor, lineColor, fillColor, startAngle, distances); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/core/text/RichTextField.ts: -------------------------------------------------------------------------------- 1 | import { HtmlElement, HtmlElementType } from "../../utils/html/HtmlElement"; 2 | import { defaultContext } from "../../utils/html/HtmlPageContext"; 3 | import { HtmlParseOptions } from "../../utils/html/HtmlParseOptions"; 4 | import { IHtmlPageContext } from "../../utils/html/IHtmlPageContext"; 5 | import { TextField } from "./TextField"; 6 | 7 | export class RichTextField extends TextField { 8 | public htmlPageContext: IHtmlPageContext; 9 | public htmlParseOptions: HtmlParseOptions; 10 | 11 | public constructor() { 12 | super(); 13 | 14 | this._touchDisabled = false; 15 | this.opaque = true; 16 | this.isRich = true; 17 | 18 | this.htmlPageContext = defaultContext; 19 | this.htmlParseOptions = new HtmlParseOptions(); 20 | } 21 | 22 | public getHtmlElement(name: string): HtmlElement { 23 | let elements = this.htmlElements; 24 | let count = elements.length; 25 | for (let i = 0; i < count; i++) { 26 | let element = elements[i]; 27 | if (name == element.name) 28 | return element; 29 | } 30 | 31 | return null; 32 | } 33 | 34 | public showHtmlObject(index: number, show: boolean): void { 35 | let element = this.htmlElements[index]; 36 | if (element.htmlObject && element.type != HtmlElementType.Link) { 37 | //set hidden flag 38 | if (show) 39 | element.status &= 253; //~(1<<1) 40 | else 41 | element.status |= 2; 42 | 43 | if ((element.status & 3) == 0) //not (hidden and clipped) 44 | { 45 | if ((element.status & 4) == 0) //not added 46 | { 47 | element.status |= 4; 48 | element.htmlObject.add(); 49 | } 50 | } 51 | else { 52 | if ((element.status & 4) != 0) //added 53 | { 54 | element.status &= 251; 55 | element.htmlObject.remove(); 56 | } 57 | } 58 | } 59 | } 60 | 61 | public dispose() { 62 | this.cleanupObjects(); 63 | 64 | super.dispose(); 65 | } 66 | 67 | protected cleanupObjects() { 68 | let elements = this.htmlElements; 69 | let count = elements.length; 70 | for (let i = 0; i < count; i++) { 71 | let element = elements[i]; 72 | if (element.htmlObject) { 73 | element.htmlObject.remove(); 74 | this.htmlPageContext.freeObject(element.htmlObject); 75 | } 76 | } 77 | } 78 | 79 | protected refreshObjects() { 80 | let elements = this.htmlElements; 81 | let count = elements.length; 82 | for (let i = 0; i < count; i++) { 83 | let element = elements[i]; 84 | if (element.htmlObject) { 85 | if ((element.status & 3) == 0) //not (hidden and clipped) 86 | { 87 | if ((element.status & 4) == 0) //not added 88 | { 89 | element.status |= 4; 90 | element.htmlObject.add(); 91 | } 92 | } 93 | else { 94 | if ((element.status & 4) != 0) //added 95 | { 96 | element.status &= 251; 97 | element.htmlObject.remove(); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/core/text/BitmapFont.ts: -------------------------------------------------------------------------------- 1 | import { BaseFont, GlyphInfo } from "./BaseFont"; 2 | import { TextFormat } from "./TextFormat"; 3 | import { VertexBuffer } from "../mesh/VertexBuffer"; 4 | import { Color4 } from "../../utils/Color"; 5 | import { Rect } from "../../utils/Rect"; 6 | import { NTexture } from "../NTexture"; 7 | import { Vector2 } from "three"; 8 | 9 | var s_rect = new Rect(); 10 | var c_white = new Color4(0xFFFFFF, 1); 11 | 12 | export class BitmapFont implements BaseFont { 13 | public name: string; 14 | public version: number = 0; 15 | public mainTexture: NTexture; 16 | public size: number = 0; 17 | public glyphs: Record; 18 | public resizable: boolean; 19 | public hasChannel: boolean; 20 | public tint: boolean; 21 | 22 | private _color: Color4; 23 | private _scale: number; 24 | private _glyph: BMGlyph; 25 | 26 | constructor() { 27 | this.glyphs = {}; 28 | this._color = new Color4(); 29 | } 30 | 31 | public setFormat(format: TextFormat, fontSizeScale: number) { 32 | if (this.resizable) 33 | this._scale = format.size / this.size * fontSizeScale; 34 | else 35 | this._scale = fontSizeScale; 36 | this._color.setHex(format.color); 37 | } 38 | 39 | public prepareCharacters(text: string): void { 40 | } 41 | 42 | public getGlyph(ch: string, ret: GlyphInfo): boolean { 43 | if (ch == ' ') { 44 | ret.width = Math.round(this.size * this._scale / 2); 45 | ret.height = Math.round(this.size * this._scale); 46 | ret.baseline = ret.height; 47 | this._glyph = null; 48 | return true; 49 | } 50 | else if (this._glyph = this.glyphs[ch]) { 51 | ret.width = Math.round(this._glyph.advance * this._scale); 52 | ret.height = Math.round(this._glyph.lineHeight * this._scale); 53 | ret.baseline = ret.height; 54 | return true; 55 | } 56 | else { 57 | ret.width = 0; 58 | ret.height = 0; 59 | ret.baseline = 0; 60 | return false; 61 | } 62 | } 63 | 64 | public drawGlyph(x: number, y: number, vb: VertexBuffer): number { 65 | if (!this._glyph) 66 | return 0; 67 | 68 | let tx = x + this._glyph.x * this._scale; 69 | let ty = -y - this._glyph.y * this._scale; 70 | let bx = x + (this._glyph.x + this._glyph.width) * this._scale; 71 | let by = -y - (this._glyph.y + this._glyph.height) * this._scale; 72 | s_rect.setMinMax(tx, by, bx, ty); 73 | vb.addQuad(s_rect, this._glyph.uv, this.tint ? this._color : c_white); 74 | vb.addTriangles(-4); 75 | return 4; 76 | } 77 | 78 | public drawLine(x: number, y: number, width: number, fontSize: number, type: number, vb: VertexBuffer): number { 79 | return 0; 80 | } 81 | 82 | public getLineHeight(size: number): number { 83 | for (var key in this.glyphs) { 84 | let glyph = this.glyphs[key]; 85 | if (this.resizable) 86 | return Math.round(glyph.lineHeight * size / this.size); 87 | else 88 | return glyph.lineHeight; 89 | } 90 | 91 | return 0; 92 | } 93 | } 94 | 95 | export class BMGlyph { 96 | public x: number = 0; 97 | public y: number = 0; 98 | public width: number = 0; 99 | public height: number = 0; 100 | public advance: number = 0; 101 | public lineHeight: number = 0; 102 | public channel: number = 0; 103 | public uv: Array = [new Vector2(), new Vector2(), new Vector2(), new Vector2()]; 104 | } 105 | -------------------------------------------------------------------------------- /src/utils/Rect.ts: -------------------------------------------------------------------------------- 1 | import { Vector2 } from "three"; 2 | 3 | export class Rect { 4 | public x: number; 5 | public y: number; 6 | public width: number; 7 | public height: number; 8 | 9 | public constructor(x?: number, y?: number, width?: number, height?: number) { 10 | this.x = x || 0; 11 | this.y = y || 0; 12 | this.width = width || 0; 13 | this.height = height || 0; 14 | } 15 | 16 | public set(x: number, y: number, width: number, height: number) { 17 | this.x = x; 18 | this.y = y; 19 | this.width = width; 20 | this.height = height; 21 | } 22 | 23 | public setMinMax(xMin: number, yMin: number, xMax: number, yMax: number) { 24 | this.x = xMin; 25 | this.y = yMin; 26 | this.width = xMax - xMin; 27 | this.height = yMax - yMin; 28 | } 29 | 30 | public get position(): Vector2 { 31 | return new Vector2(this.x, this.y); 32 | } 33 | 34 | public get size(): Vector2 { 35 | return new Vector2(this.width, this.height); 36 | } 37 | 38 | public get xMin(): number { 39 | return this.x; 40 | } 41 | 42 | public set xMin(value: number) { 43 | let d = value - this.x; 44 | this.x = value; 45 | this.width -= d; 46 | } 47 | 48 | public get xMax(): number { 49 | return this.x + this.width; 50 | } 51 | 52 | public set xMax(value: number) { 53 | this.width = value - this.x; 54 | } 55 | 56 | public get yMin(): number { 57 | return this.y; 58 | } 59 | 60 | public set yMin(value: number) { 61 | let d = value - this.y; 62 | this.y = value; 63 | this.height -= d; 64 | } 65 | 66 | public get yMax(): number { 67 | return this.y + this.height; 68 | } 69 | 70 | public set yMax(value: number) { 71 | this.height = value - this.y; 72 | } 73 | 74 | public intersection(another: Rect): Rect { 75 | if (this.width == 0 || this.height == 0 || another.width == 0 || another.height == 0) 76 | return new Rect(0, 0, 0, 0); 77 | 78 | let left = this.x > another.x ? this.x : another.x; 79 | let right = this.xMax < another.xMax ? this.xMax : another.xMax; 80 | let top = this.y > another.y ? this.y : another.y; 81 | let bottom = this.yMax < another.yMax ? this.yMax : another.yMax; 82 | 83 | if (left > right || top > bottom) 84 | this.set(0, 0, 0, 0); 85 | else 86 | this.setMinMax(left, top, right, bottom); 87 | 88 | return this; 89 | } 90 | 91 | public union(another: Rect): Rect { 92 | if (another.width == 0 || another.height == 0) 93 | return this; 94 | 95 | if (this.width == 0 || this.height == 0) { 96 | this.copy(another); 97 | return this; 98 | } 99 | 100 | let x = Math.min(this.x, another.x); 101 | let y = Math.min(this.y, another.y); 102 | this.setMinMax(x, y, Math.max(this.xMax, another.xMax), Math.max(this.yMax, another.yMax)); 103 | 104 | return this; 105 | } 106 | 107 | public extend(x: number, y: number): void { 108 | this.x -= x; 109 | this.y -= y; 110 | this.width += x * 2; 111 | this.height += y * 2; 112 | } 113 | 114 | public contains(x: number | Vector2, y?: number): boolean { 115 | if (x instanceof Vector2) { 116 | y = x.y; 117 | x = x.x; 118 | } 119 | return x >= this.x && x < this.x + this.width && y >= this.y && y < this.y + this.height; 120 | } 121 | 122 | public copy(source: Rect): void { 123 | this.set(source.x, source.y, source.width, source.height); 124 | } 125 | 126 | public clone(): Rect { 127 | return new Rect(this.x, this.y, this.width, this.height); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/ui/GImage.ts: -------------------------------------------------------------------------------- 1 | import { Image } from "../core/Image"; 2 | import { ObjectPropID } from "./FieldTypes"; 3 | import { GObject } from "./GObject"; 4 | import { PackageItem } from "./PackageItem"; 5 | import { ByteBuffer } from "../utils/ByteBuffer"; 6 | 7 | export class GImage extends GObject { 8 | private _image: Image; 9 | private _contentItem: PackageItem; 10 | 11 | constructor() { 12 | super(); 13 | } 14 | 15 | public get color(): number { 16 | return this._image.graphics.color; 17 | } 18 | 19 | public set color(value: number) { 20 | if (this._image.graphics.color != value) { 21 | this._image.graphics.color = value; 22 | this.updateGear(4); 23 | } 24 | } 25 | 26 | public get flip(): number { 27 | return this._image.graphics.flip; 28 | } 29 | 30 | public set flip(value: number) { 31 | this._image.graphics.flip = value; 32 | } 33 | 34 | public get fillMethod(): number { 35 | return this._image.fillMethod; 36 | } 37 | 38 | public set fillMethod(value: number) { 39 | this._image.fillMethod = value; 40 | } 41 | 42 | public get fillOrigin(): number { 43 | return this._image.fillOrigin; 44 | } 45 | 46 | public set fillOrigin(value: number) { 47 | this._image.fillOrigin = value; 48 | } 49 | 50 | public get fillClockwise(): boolean { 51 | return this._image.fillClockwise; 52 | } 53 | 54 | public set fillClockwise(value: boolean) { 55 | this._image.fillClockwise = value; 56 | } 57 | 58 | public get fillAmount(): number { 59 | return this._image.fillAmount; 60 | } 61 | 62 | public set fillAmount(value: number) { 63 | this._image.fillAmount = value; 64 | } 65 | 66 | protected createDisplayObject(): void { 67 | this._displayObject = this._image = new Image(); 68 | } 69 | 70 | protected handleSizeChanged(): void { 71 | this._image.width = this._width; 72 | this._image.height = this._height; 73 | } 74 | 75 | public constructFromResource(): void { 76 | this._contentItem = this.packageItem.getBranch(); 77 | 78 | this.sourceWidth = this._contentItem.width; 79 | this.sourceHeight = this._contentItem.height; 80 | this.initWidth = this.sourceWidth; 81 | this.initHeight = this.sourceHeight; 82 | 83 | this._contentItem = this._contentItem.getHighResolution(); 84 | this._contentItem.load(); 85 | 86 | this._image.scale9Grid = this._contentItem.scale9Grid; 87 | this._image.scaleByTile = this._contentItem.scaleByTile; 88 | this._image.tileGridIndice = this._contentItem.tileGridIndice; 89 | this._image.texture = this._contentItem.texture; 90 | 91 | this.setSize(this.sourceWidth, this.sourceHeight); 92 | } 93 | 94 | public getProp(index: number): any { 95 | if (index == ObjectPropID.Color) 96 | return this.color; 97 | else 98 | return super.getProp(index); 99 | } 100 | 101 | public setProp(index: number, value: any): void { 102 | if (index == ObjectPropID.Color) 103 | this.color = value; 104 | else 105 | super.setProp(index, value); 106 | } 107 | 108 | public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void { 109 | super.setup_beforeAdd(buffer, beginPos); 110 | 111 | buffer.seek(beginPos, 5); 112 | 113 | if (buffer.readBool()) 114 | this.color = buffer.readColor(); 115 | this.flip = buffer.readByte(); 116 | this._image.fillMethod = buffer.readByte(); 117 | if (this._image.fillMethod != 0) { 118 | this._image.fillOrigin = buffer.readByte(); 119 | this._image.fillClockwise = buffer.readBool(); 120 | this._image.fillAmount = buffer.readFloat(); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/gears/GearBase.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "../ui/Controller"; 2 | import { GObject, constructingDepth } from "../ui/GObject"; 3 | import { EaseType } from "../tween/EaseType"; 4 | import { GTweener } from "../tween/GTweener"; 5 | import { ByteBuffer } from "../utils/ByteBuffer"; 6 | 7 | export class GearBase { 8 | public static disableAllTweenEffect?: boolean; 9 | 10 | public _owner: GObject; 11 | protected _controller: Controller; 12 | protected _tweenConfig: GearTweenConfig; 13 | 14 | public dispose(): void { 15 | if (this._tweenConfig && this._tweenConfig._tweener) { 16 | this._tweenConfig._tweener.kill(); 17 | this._tweenConfig._tweener = null; 18 | } 19 | } 20 | 21 | public get controller(): Controller { 22 | return this._controller; 23 | } 24 | 25 | public set controller(val: Controller) { 26 | if (val != this._controller) { 27 | this._controller = val; 28 | if (this._controller) 29 | this.init(); 30 | } 31 | } 32 | 33 | public get tweenConfig(): GearTweenConfig { 34 | if (!this._tweenConfig) 35 | this._tweenConfig = new GearTweenConfig(); 36 | return this._tweenConfig; 37 | } 38 | 39 | protected get allowTween(): boolean { 40 | return this._tweenConfig && this._tweenConfig.tween && constructingDepth.n == 0 && !GearBase.disableAllTweenEffect; 41 | } 42 | 43 | public setup(buffer: ByteBuffer): void { 44 | this._controller = this._owner.parent.getControllerAt(buffer.readShort()); 45 | this.init(); 46 | 47 | var i: number; 48 | var page: string; 49 | var cnt: number = buffer.readShort(); 50 | 51 | if ("pages" in this) { 52 | (this).pages = buffer.readSArray(cnt); 53 | } 54 | else { 55 | for (i = 0; i < cnt; i++) { 56 | page = buffer.readS(); 57 | if (page == null) 58 | continue; 59 | 60 | this.addStatus(page, buffer); 61 | } 62 | 63 | if (buffer.readBool()) 64 | this.addStatus(null, buffer); 65 | } 66 | 67 | if (buffer.readBool()) { 68 | this._tweenConfig = new GearTweenConfig(); 69 | this._tweenConfig.easeType = buffer.readByte(); 70 | this._tweenConfig.duration = buffer.readFloat(); 71 | this._tweenConfig.delay = buffer.readFloat(); 72 | } 73 | 74 | if (buffer.version >= 2) { 75 | if ("positionsInPercent" in this) { 76 | if (buffer.readBool()) { 77 | (this).positionsInPercent = true; 78 | for (i = 0; i < cnt; i++) { 79 | page = buffer.readS(); 80 | if (page == null) 81 | continue; 82 | 83 | (this).addExtStatus(page, buffer); 84 | } 85 | 86 | if (buffer.readBool()) 87 | (this).addExtStatus(null, buffer); 88 | } 89 | } 90 | else if ("condition" in this) 91 | (this).condition = buffer.readByte(); 92 | } 93 | } 94 | 95 | public updateFromRelations(dx: number, dy: number): void { 96 | 97 | } 98 | 99 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 100 | 101 | } 102 | 103 | protected init(): void { 104 | 105 | } 106 | 107 | public apply(): void { 108 | } 109 | 110 | public updateState(): void { 111 | } 112 | } 113 | 114 | export class GearTweenConfig { 115 | public tween: boolean; 116 | public easeType: number; 117 | public duration: number; 118 | public delay: number; 119 | 120 | public _displayLockToken: number; 121 | public _tweener: GTweener; 122 | 123 | constructor() { 124 | this.tween = true; 125 | this.easeType = EaseType.QuadOut; 126 | this.duration = 0.3; 127 | this.delay = 0; 128 | } 129 | } 130 | 131 | export interface IGearXY { 132 | 133 | } -------------------------------------------------------------------------------- /src/utils/Timers.ts: -------------------------------------------------------------------------------- 1 | import { Pool } from "./Pool"; 2 | 3 | export class Timers { 4 | public static deltaTime: number = 0; 5 | public static time: number = 0; 6 | public static frameCount: number = 0; 7 | 8 | public static add(delayInMiniseconds: number, repeat: number, callback: Function, target?: any, callbackParam?: any): void { 9 | let item: TimerItem; 10 | let index = _items.findIndex(e => e.target == target && e.callback == callback); 11 | if (index != -1) 12 | item = _items[index]; 13 | else { 14 | item = _pool.borrow(); 15 | item.callback = callback; 16 | item.target = target; 17 | _items.push(item); 18 | } 19 | item.delay = delayInMiniseconds; 20 | item.counter = 0; 21 | item.repeat = repeat; 22 | item.param = callbackParam; 23 | item.end = false; 24 | } 25 | 26 | public static callLater(callback: Function, target?: any, callbackParam?: any): void { 27 | this.add(0, 1, callback, target, callbackParam); 28 | } 29 | 30 | public static callDelay(delay: number, callback: Function, target?: any, callbackParam?: any): void { 31 | this.add(delay, 1, callback, target, callbackParam); 32 | } 33 | 34 | public static addUpdate(callback: Function, target?: any, callbackParam?: any): void { 35 | this.add(0, 0, callback, target, callbackParam); 36 | } 37 | 38 | public static exists(callback: Function, target?: any): boolean { 39 | return _items.findIndex(e => e.target == target && e.callback == callback) != -1; 40 | } 41 | 42 | public static remove(callback: Function, target?: any): void { 43 | let index = _items.findIndex(e => e.target == target && e.callback == callback); 44 | if (index != -1) { 45 | let item = _items[index]; 46 | _items.splice(index, 1); 47 | if (index < _enumI) 48 | _enumI--; 49 | _enumCount--; 50 | 51 | _pool.returns(item); 52 | } 53 | } 54 | } 55 | 56 | 57 | class TimerItem { 58 | public delay: number = 0; 59 | public counter: number = 0; 60 | public repeat: number = 0; 61 | public callback: Function; 62 | public target: any; 63 | public param: any; 64 | public end: boolean; 65 | 66 | public constructor() { 67 | } 68 | 69 | public advance(elapsed: number): boolean { 70 | this.counter += elapsed; 71 | if (this.counter >= this.delay) { 72 | this.counter -= this.delay; 73 | if (this.counter > this.delay) 74 | this.counter = this.delay; 75 | 76 | if (this.repeat > 0) { 77 | this.repeat--; 78 | if (this.repeat == 0) 79 | this.end = true; 80 | } 81 | 82 | return true; 83 | } 84 | else 85 | return false; 86 | } 87 | 88 | public reset(): void { 89 | this.callback = null; 90 | this.target = null; 91 | this.param = null; 92 | } 93 | } 94 | 95 | var _items: Array = new Array(); 96 | var _pool: Pool = new Pool(TimerItem, e => e.reset()); 97 | 98 | var _enumI: number = 0; 99 | var _enumCount: number = 0; 100 | var _lastTime: number = 0; 101 | 102 | requestAnimationFrame(__timer); 103 | 104 | function __timer(timeStamp: number): boolean { 105 | requestAnimationFrame(__timer); 106 | 107 | Timers.frameCount++; 108 | Timers.time = timeStamp; 109 | let deltaTime = timeStamp - _lastTime; 110 | Timers.deltaTime = deltaTime; 111 | _lastTime = timeStamp; 112 | 113 | _enumI = 0; 114 | _enumCount = _items.length; 115 | 116 | while (_enumI < _enumCount) { 117 | var item: TimerItem = _items[_enumI]; 118 | _enumI++; 119 | 120 | if (item.advance(deltaTime)) { 121 | if (item.end) { 122 | _enumI--; 123 | _enumCount--; 124 | _items.splice(_enumI, 1); 125 | } 126 | 127 | item.callback.call(item.target, item.param); 128 | 129 | if (item.end) 130 | _pool.returns(item); 131 | } 132 | } 133 | 134 | return false; 135 | } -------------------------------------------------------------------------------- /src/tween/TweenManager.ts: -------------------------------------------------------------------------------- 1 | import { Timers } from "../utils/Timers"; 2 | import { GTweener } from "./GTweener"; 3 | import { Pool } from "../utils/Pool"; 4 | 5 | export class TweenManager { 6 | 7 | public static createTween(): GTweener { 8 | if (!_inited) { 9 | Timers.addUpdate(TweenManager.update); 10 | _inited = true; 11 | } 12 | 13 | var tweener: GTweener = _tweenerPool.borrow(); 14 | _activeTweens[_totalActiveTweens++] = tweener; 15 | 16 | return tweener; 17 | } 18 | 19 | public static isTweening(target: any, propType?: any): boolean { 20 | if (target == null) 21 | return false; 22 | 23 | var anyType: boolean = !propType; 24 | for (var i: number = 0; i < _totalActiveTweens; i++) { 25 | var tweener: GTweener = _activeTweens[i]; 26 | if (tweener && tweener.target == target && !tweener._killed 27 | && (anyType || tweener._propType == propType)) 28 | return true; 29 | } 30 | 31 | return false; 32 | } 33 | 34 | public static killTweens(target: any, completed?: boolean, propType?: any): boolean { 35 | if (target == null) 36 | return false; 37 | 38 | var flag: boolean = false; 39 | var cnt: number = _totalActiveTweens; 40 | var anyType: boolean = !propType; 41 | for (var i: number = 0; i < cnt; i++) { 42 | var tweener: GTweener = _activeTweens[i]; 43 | if (tweener && tweener.target == target && !tweener._killed 44 | && (anyType || tweener._propType == propType)) { 45 | tweener.kill(completed); 46 | flag = true; 47 | } 48 | } 49 | 50 | return flag; 51 | } 52 | 53 | public static getTween(target: any, propType?: any): GTweener { 54 | if (target == null) 55 | return null; 56 | 57 | var cnt: number = _totalActiveTweens; 58 | var anyType: boolean = !propType; 59 | for (var i: number = 0; i < cnt; i++) { 60 | var tweener: GTweener = _activeTweens[i]; 61 | if (tweener && tweener.target == target && !tweener._killed 62 | && (anyType || tweener._propType == propType)) { 63 | return tweener; 64 | } 65 | } 66 | 67 | return null; 68 | } 69 | 70 | public static update(): void { 71 | var dt: number = Timers.deltaTime / 1000; 72 | 73 | var cnt: number = _totalActiveTweens; 74 | var freePosStart: number = -1; 75 | for (var i: number = 0; i < cnt; i++) { 76 | var tweener: GTweener = _activeTweens[i]; 77 | if (tweener == null) { 78 | if (freePosStart == -1) 79 | freePosStart = i; 80 | } 81 | else if (tweener._killed) { 82 | _tweenerPool.returns(tweener); 83 | _activeTweens[i] = null; 84 | 85 | if (freePosStart == -1) 86 | freePosStart = i; 87 | } 88 | else { 89 | if (tweener._target && ('isDisposed' in tweener._target) && tweener._target.isDisposed) 90 | tweener._killed = true; 91 | else if (!tweener._paused) 92 | tweener._update(dt); 93 | 94 | if (freePosStart != -1) { 95 | _activeTweens[freePosStart] = tweener; 96 | _activeTweens[i] = null; 97 | freePosStart++; 98 | } 99 | } 100 | } 101 | 102 | if (freePosStart >= 0) { 103 | if (_totalActiveTweens != cnt) //new tweens added 104 | { 105 | var j: number = cnt; 106 | cnt = _totalActiveTweens - cnt; 107 | for (i = 0; i < cnt; i++) 108 | _activeTweens[freePosStart++] = _activeTweens[j++]; 109 | } 110 | _totalActiveTweens = freePosStart; 111 | } 112 | } 113 | } 114 | 115 | var _activeTweens: GTweener[] = new Array(); 116 | var _tweenerPool: Pool = new Pool(GTweener, e => e._init(), e => e._reset()); 117 | var _totalActiveTweens: number = 0; 118 | var _inited: boolean = false; -------------------------------------------------------------------------------- /src/gears/GearLook.ts: -------------------------------------------------------------------------------- 1 | import { GTween } from "../tween/GTween"; 2 | import { GTweener } from "../tween/GTweener"; 3 | import { ByteBuffer } from "../utils/ByteBuffer"; 4 | import { GearBase } from "./GearBase"; 5 | 6 | export class GearLook extends GearBase { 7 | private _storage: Record; 8 | private _default: GearLookValue; 9 | 10 | protected init(): void { 11 | this._default = { 12 | alpha: this._owner.alpha, 13 | rotation: this._owner.rotation, 14 | grayed: this._owner.grayed, 15 | touchable: this._owner.touchable 16 | }; 17 | this._storage = {}; 18 | } 19 | 20 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 21 | var gv: GearLookValue; 22 | if (!pageId) 23 | gv = this._default; 24 | else { 25 | gv = {}; 26 | this._storage[pageId] = gv; 27 | } 28 | 29 | gv.alpha = buffer.readFloat(); 30 | gv.rotation = buffer.readFloat(); 31 | gv.grayed = buffer.readBool(); 32 | gv.touchable = buffer.readBool(); 33 | } 34 | 35 | public apply(): void { 36 | var gv: GearLookValue = this._storage[this._controller.selectedPageId] || this._default; 37 | 38 | if (this.allowTween) { 39 | this._owner._gearLocked = true; 40 | this._owner.grayed = gv.grayed; 41 | this._owner.touchable = gv.touchable; 42 | this._owner._gearLocked = false; 43 | 44 | if (this._tweenConfig._tweener) { 45 | if (this._tweenConfig._tweener.endValue.x != gv.alpha || this._tweenConfig._tweener.endValue.y != gv.rotation) { 46 | this._tweenConfig._tweener.kill(true); 47 | this._tweenConfig._tweener = null; 48 | } 49 | else 50 | return; 51 | } 52 | 53 | var a: boolean = gv.alpha != this._owner.alpha; 54 | var b: boolean = gv.rotation != this._owner.rotation; 55 | if (a || b) { 56 | if (this._owner.checkGearController(0, this._controller)) 57 | this._tweenConfig._displayLockToken = this._owner.addDisplayLock(); 58 | 59 | this._tweenConfig._tweener = GTween.to2(this._owner.alpha, this._owner.rotation, gv.alpha, gv.rotation, this._tweenConfig.duration) 60 | .setDelay(this._tweenConfig.delay) 61 | .setEase(this._tweenConfig.easeType) 62 | .setUserData((a ? 1 : 0) + (b ? 2 : 0)) 63 | .setTarget(this) 64 | .onUpdate(this.__tweenUpdate, this) 65 | .onComplete(this.__tweenComplete, this); 66 | } 67 | } 68 | else { 69 | this._owner._gearLocked = true; 70 | this._owner.grayed = gv.grayed; 71 | this._owner.alpha = gv.alpha; 72 | this._owner.rotation = gv.rotation; 73 | this._owner.touchable = gv.touchable; 74 | this._owner._gearLocked = false; 75 | } 76 | } 77 | 78 | private __tweenUpdate(tweener: GTweener): void { 79 | var flag: number = tweener.userData; 80 | this._owner._gearLocked = true; 81 | if ((flag & 1) != 0) 82 | this._owner.alpha = tweener.value.x; 83 | if ((flag & 2) != 0) 84 | this._owner.rotation = tweener.value.y; 85 | this._owner._gearLocked = false; 86 | } 87 | 88 | private __tweenComplete(): void { 89 | if (this._tweenConfig._displayLockToken != 0) { 90 | this._owner.releaseDisplayLock(this._tweenConfig._displayLockToken); 91 | this._tweenConfig._displayLockToken = 0; 92 | } 93 | this._tweenConfig._tweener = null; 94 | } 95 | 96 | public updateState(): void { 97 | var gv: GearLookValue = this._storage[this._controller.selectedPageId]; 98 | if (!gv) { 99 | gv = {}; 100 | this._storage[this._controller.selectedPageId] = gv; 101 | } 102 | 103 | gv.alpha = this._owner.alpha; 104 | gv.rotation = this._owner.rotation; 105 | gv.grayed = this._owner.grayed; 106 | gv.touchable = this._owner.touchable; 107 | } 108 | } 109 | 110 | interface GearLookValue { 111 | alpha?: number; 112 | rotation?: number; 113 | grayed?: boolean; 114 | touchable?: boolean; 115 | } 116 | -------------------------------------------------------------------------------- /src/utils/xml/XML.ts: -------------------------------------------------------------------------------- 1 | import { XMLIterator, XMLTagType } from "./XMLIterator"; 2 | import { XMLUtils } from "./XMLUtils"; 3 | 4 | export class XML { 5 | public name: string; 6 | public text: string; 7 | 8 | private _attributes: Record; 9 | private _children: Array; 10 | 11 | public constructor(XmlString?: string) { 12 | if (XmlString) 13 | this.parse(XmlString); 14 | } 15 | 16 | public get attributes(): Record { 17 | if (!this._attributes) 18 | this._attributes = {}; 19 | return this._attributes; 20 | } 21 | 22 | public getAttrString(attrName: string, defValue?: string) { 23 | return XMLUtils.getString(this._attributes, attrName, defValue); 24 | } 25 | 26 | public getAttrInt(attrName: string, defValue?: number): number { 27 | return XMLUtils.getInt(this._attributes, attrName, defValue); 28 | } 29 | 30 | public getAttrFloat(attrName: string, defValue?: number): number { 31 | return XMLUtils.getFloat(this._attributes, attrName, defValue); 32 | } 33 | 34 | public getAttrBool(attrName: string, defValue?: boolean): boolean { 35 | return XMLUtils.getBool(this._attributes, attrName, defValue); 36 | } 37 | 38 | public getAttrColor(attrName: string, defValue?: number): number { 39 | return XMLUtils.getColor(this._attributes, attrName, defValue); 40 | } 41 | 42 | public setAttribute(attrName: string, attrValue: string) { 43 | if (!this._attributes) 44 | this._attributes = {}; 45 | 46 | this._attributes[attrName] = attrValue; 47 | } 48 | 49 | public getNode(selector: string): XML { 50 | if (!this._children) 51 | return null; 52 | else 53 | this._children.find(value => { 54 | return value.name == selector; 55 | }); 56 | } 57 | 58 | public elements(selector?: string): Array { 59 | if (!this._children) 60 | this._children = new Array(); 61 | if (selector) 62 | return this._children.filter(value => { 63 | return value.name == selector; 64 | }); 65 | else 66 | return this._children; 67 | } 68 | 69 | public parse(aSource: string) { 70 | this.reset(); 71 | 72 | let lastOpenNode: XML; 73 | let nodeStack: Array = new Array(); 74 | 75 | XMLIterator.begin(aSource); 76 | while (XMLIterator.nextTag()) { 77 | if (XMLIterator.tagType == XMLTagType.Start || XMLIterator.tagType == XMLTagType.Void) { 78 | let childNode: XML; 79 | if (lastOpenNode) 80 | childNode = new XML(); 81 | else { 82 | if (this.name != null) { 83 | this.reset(); 84 | throw new Error("Invalid xml format - no root node."); 85 | } 86 | childNode = this; 87 | } 88 | 89 | childNode.name = XMLIterator.tagName; 90 | childNode._attributes = XMLIterator.getAttributes(childNode._attributes); 91 | 92 | if (lastOpenNode) { 93 | if (XMLIterator.tagType != XMLTagType.Void) 94 | nodeStack.push(lastOpenNode); 95 | if (lastOpenNode._children == null) 96 | lastOpenNode._children = new Array(); 97 | lastOpenNode._children.push(childNode); 98 | } 99 | if (XMLIterator.tagType != XMLTagType.Void) 100 | lastOpenNode = childNode; 101 | } 102 | else if (XMLIterator.tagType == XMLTagType.End) { 103 | if (lastOpenNode == null || lastOpenNode.name != XMLIterator.tagName) { 104 | this.reset(); 105 | throw new Error("Invalid xml format - <" + XMLIterator.tagName + "> dismatched."); 106 | } 107 | 108 | if (lastOpenNode._children == null || lastOpenNode._children.length == 0) { 109 | lastOpenNode.text = XMLIterator.getText(); 110 | } 111 | 112 | if (nodeStack.length > 0) 113 | lastOpenNode = nodeStack.pop(); 114 | else 115 | lastOpenNode = null; 116 | } 117 | } 118 | } 119 | 120 | public reset() { 121 | this._attributes = null; 122 | if (this._children != null) 123 | this._children.length == 0; 124 | this.text = null; 125 | } 126 | } -------------------------------------------------------------------------------- /src/ui/GMovieClip.ts: -------------------------------------------------------------------------------- 1 | import { MovieClip } from "../core/MovieClip"; 2 | import { GObject } from "./GObject"; 3 | import { ObjectPropID } from "./FieldTypes"; 4 | import { PackageItem } from "./PackageItem"; 5 | import { ByteBuffer } from "../utils/ByteBuffer"; 6 | 7 | export class GMovieClip extends GObject { 8 | private _movieClip: MovieClip; 9 | 10 | constructor() { 11 | super(); 12 | } 13 | 14 | public get color(): number { 15 | return this._movieClip.graphics.color; 16 | } 17 | 18 | public set color(value: number) { 19 | this._movieClip.graphics.color = value; 20 | } 21 | 22 | protected createDisplayObject(): void { 23 | this._displayObject = this._movieClip = new MovieClip(); 24 | } 25 | 26 | public get playing(): boolean { 27 | return this._movieClip.playing; 28 | } 29 | 30 | public set playing(value: boolean) { 31 | if (this._movieClip.playing != value) { 32 | this._movieClip.playing = value; 33 | this.updateGear(5); 34 | } 35 | } 36 | 37 | public get frame(): number { 38 | return this._movieClip.frame; 39 | } 40 | 41 | public set frame(value: number) { 42 | if (this._movieClip.frame != value) { 43 | this._movieClip.frame = value; 44 | this.updateGear(5); 45 | } 46 | } 47 | 48 | public get timeScale(): number { 49 | return this._movieClip.timeScale; 50 | } 51 | 52 | public set timeScale(value: number) { 53 | this._movieClip.timeScale = value; 54 | } 55 | 56 | public rewind(): void { 57 | this._movieClip.rewind(); 58 | } 59 | 60 | public syncStatus(anotherMc: GMovieClip): void { 61 | this._movieClip.syncStatus(anotherMc._movieClip); 62 | } 63 | 64 | public advance(timeInMiniseconds: number): void { 65 | this._movieClip.advance(timeInMiniseconds); 66 | } 67 | 68 | //从start帧开始,播放到end帧(-1表示结尾),重复times次(0表示无限循环),循环结束后,停止在endAt帧(-1表示参数end) 69 | public setPlaySettings(start?: number, end?: number, times?: number, endAt?: number): void { 70 | this._movieClip.setPlaySettings(start, end, times, endAt); 71 | } 72 | 73 | public getProp(index: number): any { 74 | switch (index) { 75 | case ObjectPropID.Color: 76 | return this.color; 77 | case ObjectPropID.Playing: 78 | return this.playing; 79 | case ObjectPropID.Frame: 80 | return this.frame; 81 | case ObjectPropID.TimeScale: 82 | return this.timeScale; 83 | default: 84 | return super.getProp(index); 85 | } 86 | } 87 | 88 | public setProp(index: number, value: any): void { 89 | switch (index) { 90 | case ObjectPropID.Color: 91 | this.color = value; 92 | break; 93 | case ObjectPropID.Playing: 94 | this.playing = value; 95 | break; 96 | case ObjectPropID.Frame: 97 | this.frame = value; 98 | break; 99 | case ObjectPropID.TimeScale: 100 | this.timeScale = value; 101 | break; 102 | case ObjectPropID.DeltaTime: 103 | this.advance(value); 104 | break; 105 | default: 106 | super.setProp(index, value); 107 | break; 108 | } 109 | } 110 | 111 | public constructFromResource(): void { 112 | var displayItem: PackageItem = this.packageItem.getBranch(); 113 | 114 | this.sourceWidth = displayItem.width; 115 | this.sourceHeight = displayItem.height; 116 | this.initWidth = this.sourceWidth; 117 | this.initHeight = this.sourceHeight; 118 | 119 | this.setSize(this.sourceWidth, this.sourceHeight); 120 | 121 | displayItem = displayItem.getHighResolution(); 122 | displayItem.load(); 123 | 124 | this._movieClip.interval = displayItem.interval; 125 | this._movieClip.swing = displayItem.swing; 126 | this._movieClip.repeatDelay = displayItem.repeatDelay; 127 | this._movieClip.frames = displayItem.frames; 128 | } 129 | 130 | public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void { 131 | super.setup_beforeAdd(buffer, beginPos); 132 | 133 | buffer.seek(beginPos, 5); 134 | 135 | if (buffer.readBool()) 136 | this.color = buffer.readColor(); 137 | this._movieClip.graphics.flip = buffer.readByte(); //flip 138 | this._movieClip.frame = buffer.readInt(); 139 | this._movieClip.playing = buffer.readBool(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/gears/GearXY.ts: -------------------------------------------------------------------------------- 1 | import { GTween } from "../tween/GTween"; 2 | import { GTweener } from "../tween/GTweener"; 3 | import { ByteBuffer } from "../utils/ByteBuffer"; 4 | import { GearBase } from "./GearBase"; 5 | 6 | export class GearXY extends GearBase { 7 | public positionsInPercent: boolean; 8 | 9 | private _storage: Record; 10 | private _default: GearXYValue; 11 | 12 | protected init(): void { 13 | this._default = { 14 | x: this._owner.x, 15 | y: this._owner.y, 16 | px: this._owner.x / this._owner.parent.width, 17 | py: this._owner.y / this._owner.parent.height 18 | }; 19 | this._storage = {}; 20 | } 21 | 22 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 23 | var gv: GearXYValue; 24 | if (!pageId) 25 | gv = this._default; 26 | else { 27 | gv = {}; 28 | this._storage[pageId] = gv; 29 | } 30 | gv.x = buffer.readInt(); 31 | gv.y = buffer.readInt(); 32 | } 33 | 34 | public addExtStatus(pageId: string, buffer: ByteBuffer): void { 35 | var gv: GearXYValue; 36 | if (!pageId) 37 | gv = this._default; 38 | else 39 | gv = this._storage[pageId]; 40 | gv.px = buffer.readFloat(); 41 | gv.py = buffer.readFloat(); 42 | } 43 | 44 | public apply(): void { 45 | var pt: GearXYValue = this._storage[this._controller.selectedPageId] || this._default; 46 | var ex: number; 47 | var ey: number; 48 | 49 | if (this.positionsInPercent && this._owner.parent) { 50 | ex = pt.px * this._owner.parent.width; 51 | ey = pt.py * this._owner.parent.height; 52 | } 53 | else { 54 | ex = pt.x; 55 | ey = pt.y; 56 | } 57 | 58 | if (this.allowTween) { 59 | if (this._tweenConfig._tweener) { 60 | if (this._tweenConfig._tweener.endValue.x != ex || this._tweenConfig._tweener.endValue.y != ey) { 61 | this._tweenConfig._tweener.kill(true); 62 | this._tweenConfig._tweener = null; 63 | } 64 | else 65 | return; 66 | } 67 | 68 | var ox: number = this._owner.x; 69 | var oy: number = this._owner.y; 70 | 71 | if (ox != ex || oy != ey) { 72 | if (this._owner.checkGearController(0, this._controller)) 73 | this._tweenConfig._displayLockToken = this._owner.addDisplayLock(); 74 | 75 | this._tweenConfig._tweener = GTween.to2(ox, oy, ex, ey, this._tweenConfig.duration) 76 | .setDelay(this._tweenConfig.delay) 77 | .setEase(this._tweenConfig.easeType) 78 | .setTarget(this) 79 | .onUpdate(this.__tweenUpdate, this) 80 | .onComplete(this.__tweenComplete, this); 81 | } 82 | } 83 | else { 84 | this._owner._gearLocked = true; 85 | this._owner.setPosition(ex, ey); 86 | this._owner._gearLocked = false; 87 | } 88 | } 89 | 90 | private __tweenUpdate(tweener: GTweener): void { 91 | this._owner._gearLocked = true; 92 | this._owner.setPosition(tweener.value.x, tweener.value.y); 93 | this._owner._gearLocked = false; 94 | } 95 | 96 | private __tweenComplete(): void { 97 | if (this._tweenConfig._displayLockToken != 0) { 98 | this._owner.releaseDisplayLock(this._tweenConfig._displayLockToken); 99 | this._tweenConfig._displayLockToken = 0; 100 | } 101 | this._tweenConfig._tweener = null; 102 | } 103 | 104 | public updateState(): void { 105 | var pt: GearXYValue = this._storage[this._controller.selectedPageId]; 106 | if (!pt) { 107 | pt = {}; 108 | this._storage[this._controller.selectedPageId] = pt; 109 | } 110 | 111 | pt.x = this._owner.x; 112 | pt.y = this._owner.y; 113 | pt.px = this._owner.x / this._owner.parent.width; 114 | pt.py = this._owner.y / this._owner.parent.height; 115 | } 116 | 117 | public updateFromRelations(dx: number, dy: number): void { 118 | if (this._controller == null || this._storage == null || this.positionsInPercent) 119 | return; 120 | 121 | for (var key in this._storage) { 122 | var pt: GearXYValue = this._storage[key]; 123 | pt.x += dx; 124 | pt.y += dy; 125 | } 126 | this._default.x += dx; 127 | this._default.y += dy; 128 | 129 | this.updateState(); 130 | } 131 | } 132 | 133 | interface GearXYValue { 134 | x?: number, 135 | y?: number, 136 | px?: number; 137 | py?: number 138 | } -------------------------------------------------------------------------------- /src/core/NTexture.ts: -------------------------------------------------------------------------------- 1 | import { Rect } from "../utils/Rect" 2 | import { Texture, Vector2 } from "three"; 3 | 4 | export class NTexture { 5 | public uvRect: Rect; 6 | public rotated: boolean; 7 | 8 | public region: Rect; 9 | public offset: Vector2; 10 | public originalSize: Vector2; 11 | 12 | private _root: NTexture; 13 | private _nativeTexture: Texture; 14 | 15 | public constructor(texture?: Texture, xScale?: number, yScale?: number) { 16 | xScale = xScale || 1; 17 | yScale = yScale || 1; 18 | this._nativeTexture = texture; 19 | this._root = this; 20 | this.uvRect = new Rect(0, 0, xScale, yScale); 21 | if (yScale < 0) { 22 | this.uvRect.y = -yScale; 23 | this.uvRect.height = yScale; 24 | } 25 | if (xScale < 0) { 26 | this.uvRect.x = -xScale; 27 | this.uvRect.width = xScale; 28 | } 29 | this.originalSize = texture ? new Vector2(texture.image.width, texture.image.height) : new Vector2(2, 2); 30 | this.region = new Rect(0, 0, this.originalSize.x, this.originalSize.y); 31 | this.offset = new Vector2(0, 0); 32 | } 33 | 34 | public createSubTexture(region: Rect, rotated?: boolean, offset?: Vector2, originalSize?: Vector2): NTexture { 35 | let nt = new NTexture(); 36 | nt._root = this; 37 | nt.rotated = rotated || false; 38 | nt.region.copy(region); 39 | nt.region.x += this.region.x; 40 | nt.region.y += this.region.y; 41 | nt.uvRect.set(nt.region.x * this.uvRect.width / this.width, 1 - nt.region.yMax * this.uvRect.height / this.height, 42 | nt.region.width * this.uvRect.width / this.width, nt.region.height * this.uvRect.height / this.height); 43 | if (rotated) { 44 | let tmp: number = nt.region.width; 45 | nt.region.width = nt.region.height; 46 | nt.region.height = tmp; 47 | 48 | tmp = nt.uvRect.width; 49 | nt.uvRect.width = nt.uvRect.height; 50 | nt.uvRect.height = tmp; 51 | } 52 | if (originalSize) 53 | nt.originalSize.copy(originalSize); 54 | else 55 | nt.originalSize.set(nt.region.width, nt.region.height); 56 | if (offset) 57 | nt.offset.copy(offset); 58 | return nt; 59 | } 60 | 61 | public get width(): number { 62 | return this.region.width; 63 | } 64 | 65 | public get height(): number { 66 | return this.region.height; 67 | } 68 | 69 | public get nativeTexture(): Texture { 70 | return this._root == this ? this._nativeTexture : this._root.nativeTexture; 71 | } 72 | 73 | public getDrawRect(drawRect: Rect): Rect { 74 | if (this.originalSize.x == this.region.width && this.originalSize.y == this.region.height) 75 | return drawRect; 76 | 77 | let sx = drawRect.width / this.originalSize.x; 78 | let sy = drawRect.height / this.originalSize.y; 79 | drawRect.x = this.offset.x * sx; 80 | drawRect.y = this.offset.y * sy; 81 | drawRect.width = this.region.width * sx; 82 | drawRect.height = this.region.height * sy; 83 | 84 | return drawRect; 85 | } 86 | 87 | public getUV(uv: Array) { 88 | uv[0] = this.uvRect.position; 89 | uv[1] = new Vector2(this.uvRect.x, this.uvRect.yMax); 90 | uv[2] = new Vector2(this.uvRect.xMax, this.uvRect.yMax); 91 | uv[3] = new Vector2(this.uvRect.xMax, this.uvRect.y); 92 | if (this.rotated) { 93 | let xMin = this.uvRect.xMin; 94 | let yMin = this.uvRect.yMin; 95 | let yMax = this.uvRect.yMax; 96 | 97 | for (let i = 0; i < 4; i++) { 98 | let m = uv[i]; 99 | let tmp = m.y; 100 | m.y = yMin + m.x - xMin; 101 | m.x = xMin + yMax - tmp; 102 | } 103 | } 104 | } 105 | 106 | public get root(): NTexture { 107 | return this._root; 108 | } 109 | 110 | public reload(nativeTexture: Texture) { 111 | if (this._root != this) 112 | throw new Error("Reload is not allow to call on none root NTexture."); 113 | 114 | if (this._nativeTexture && this._nativeTexture != nativeTexture) 115 | this._nativeTexture.dispose(); 116 | 117 | this._nativeTexture = nativeTexture; 118 | 119 | if (this._nativeTexture) 120 | this.originalSize.set(nativeTexture.image.width, nativeTexture.image.height); 121 | else 122 | this.originalSize.set(0, 0); 123 | this.region.set(0, 0, this.originalSize.x, this.originalSize.y); 124 | } 125 | 126 | public dispose(): void { 127 | if (this._root == this) 128 | this._nativeTexture.dispose(); 129 | } 130 | } 131 | 132 | export const EmptyTexture: NTexture = new NTexture(); -------------------------------------------------------------------------------- /src/ui/UIObjectFactory.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, PackageItemType } from "./FieldTypes"; 2 | import { GButton } from "./GButton"; 3 | import { GComboBox } from "./GComboBox"; 4 | import { GComponent } from "./GComponent"; 5 | import { GGraph } from "./GGraph"; 6 | import { GGroup } from "./GGroup"; 7 | import { GImage } from "./GImage"; 8 | import { GLabel } from "./GLabel"; 9 | import { GList } from "./GList"; 10 | import { GLoader } from "./GLoader"; 11 | import { GMovieClip } from "./GMovieClip"; 12 | import { GObject } from "./GObject"; 13 | import { GProgressBar } from "./GProgressBar"; 14 | import { GRichTextField } from "./GRichTextField"; 15 | import { GScrollBar } from "./GScrollBar"; 16 | import { GSlider } from "./GSlider"; 17 | import { GTextField } from "./GTextField"; 18 | import { GTextInput } from "./GTextInput"; 19 | import { GTree } from "./GTree"; 20 | import { PackageItem } from "./PackageItem"; 21 | import { UIPackage, Decls } from "./UIPackage"; 22 | import { GLoader3D } from "./GLoader3D"; 23 | 24 | export class UIObjectFactory { 25 | public static extensions: Record GComponent> = {}; 26 | public static loaderType: new () => GLoader; 27 | 28 | public static setExtension(url: string, type: new () => GComponent): void { 29 | if (url == null) 30 | throw new Error("Invaild url: " + url); 31 | 32 | var pi: PackageItem = UIPackage.getItemByURL(url); 33 | if (pi) 34 | pi.extensionType = type; 35 | 36 | UIObjectFactory.extensions[url] = type; 37 | } 38 | 39 | public static setLoaderExtension(type: new () => GLoader): void { 40 | UIObjectFactory.loaderType = type; 41 | } 42 | 43 | public static resolveExtension(pi: PackageItem): void { 44 | var extensionType = UIObjectFactory.extensions["ui://" + pi.owner.id + pi.id]; 45 | if (!extensionType) 46 | extensionType = UIObjectFactory.extensions["ui://" + pi.owner.name + "/" + pi.name]; 47 | if (extensionType) 48 | pi.extensionType = extensionType; 49 | } 50 | 51 | public static newObject(type: number | PackageItem, userClass?: new () => GObject): GObject { 52 | var obj: GObject; 53 | 54 | if (typeof type === 'number') { 55 | switch (type) { 56 | case ObjectType.Image: 57 | return new GImage(); 58 | 59 | case ObjectType.MovieClip: 60 | return new GMovieClip(); 61 | 62 | case ObjectType.Component: 63 | return new GComponent(); 64 | 65 | case ObjectType.Text: 66 | return new GTextField(); 67 | 68 | case ObjectType.RichText: 69 | return new GRichTextField(); 70 | 71 | case ObjectType.InputText: 72 | return new GTextInput(); 73 | 74 | case ObjectType.Group: 75 | return new GGroup(); 76 | 77 | case ObjectType.List: 78 | return new GList(); 79 | 80 | case ObjectType.Graph: 81 | return new GGraph(); 82 | 83 | case ObjectType.Loader: 84 | if (UIObjectFactory.loaderType) 85 | return new UIObjectFactory.loaderType(); 86 | else 87 | return new GLoader(); 88 | 89 | case ObjectType.Button: 90 | return new GButton(); 91 | 92 | case ObjectType.Label: 93 | return new GLabel(); 94 | 95 | case ObjectType.ProgressBar: 96 | return new GProgressBar(); 97 | 98 | case ObjectType.Slider: 99 | return new GSlider(); 100 | 101 | case ObjectType.ScrollBar: 102 | return new GScrollBar(); 103 | 104 | case ObjectType.ComboBox: 105 | return new GComboBox(); 106 | 107 | case ObjectType.Tree: 108 | return new GTree(); 109 | 110 | case ObjectType.Loader3D: 111 | return new GLoader3D(); 112 | 113 | default: 114 | return null; 115 | } 116 | } 117 | else { 118 | if (type.type == PackageItemType.Component) { 119 | if (userClass) 120 | obj = new userClass(); 121 | else if (type.extensionType) 122 | obj = new type.extensionType(); 123 | else 124 | obj = UIObjectFactory.newObject(type.objectType); 125 | } 126 | else 127 | obj = UIObjectFactory.newObject(type.objectType); 128 | 129 | if (obj) 130 | obj.packageItem = type; 131 | } 132 | 133 | return obj; 134 | } 135 | } 136 | 137 | Decls.UIObjectFactory = UIObjectFactory; -------------------------------------------------------------------------------- /src/gears/GearSize.ts: -------------------------------------------------------------------------------- 1 | import { GTween } from "../tween/GTween"; 2 | import { GTweener } from "../tween/GTweener"; 3 | import { ByteBuffer } from "../utils/ByteBuffer"; 4 | import { GearBase } from "./GearBase"; 5 | 6 | export class GearSize extends GearBase { 7 | private _storage: Record; 8 | private _default: GearSizeValue; 9 | 10 | protected init(): void { 11 | this._default = { 12 | width: this._owner.width, 13 | height: this._owner.height, 14 | scaleX: this._owner.scaleX, 15 | scaleY: this._owner.scaleY 16 | }; 17 | this._storage = {}; 18 | } 19 | 20 | protected addStatus(pageId: string, buffer: ByteBuffer): void { 21 | var gv: GearSizeValue; 22 | if (!pageId) 23 | gv = this._default; 24 | else { 25 | gv = {}; 26 | this._storage[pageId] = gv; 27 | } 28 | 29 | gv.width = buffer.readInt(); 30 | gv.height = buffer.readInt(); 31 | gv.scaleX = buffer.readFloat(); 32 | gv.scaleY = buffer.readFloat(); 33 | } 34 | 35 | public apply(): void { 36 | var gv: GearSizeValue = this._storage[this._controller.selectedPageId] || this._default; 37 | 38 | if (this.allowTween) { 39 | if (this._tweenConfig._tweener) { 40 | if (this._tweenConfig._tweener.endValue.x != gv.width || this._tweenConfig._tweener.endValue.y != gv.height 41 | || this._tweenConfig._tweener.endValue.z != gv.scaleX || this._tweenConfig._tweener.endValue.w != gv.scaleY) { 42 | this._tweenConfig._tweener.kill(true); 43 | this._tweenConfig._tweener = null; 44 | } 45 | else 46 | return; 47 | } 48 | 49 | var a: boolean = gv.width != this._owner.width || gv.height != this._owner.height; 50 | var b: boolean = gv.scaleX != this._owner.scaleX || gv.scaleY != this._owner.scaleY; 51 | if (a || b) { 52 | if (this._owner.checkGearController(0, this._controller)) 53 | this._tweenConfig._displayLockToken = this._owner.addDisplayLock(); 54 | 55 | this._tweenConfig._tweener = GTween.to4(this._owner.width, this._owner.height, this._owner.scaleX, this._owner.scaleY, gv.width, gv.height, gv.scaleX, gv.scaleY, this._tweenConfig.duration) 56 | .setDelay(this._tweenConfig.delay) 57 | .setEase(this._tweenConfig.easeType) 58 | .setUserData((a ? 1 : 0) + (b ? 2 : 0)) 59 | .setTarget(this) 60 | .onUpdate(this.__tweenUpdate, this) 61 | .onComplete(this.__tweenComplete, this); 62 | } 63 | } 64 | else { 65 | this._owner._gearLocked = true; 66 | this._owner.setSize(gv.width, gv.height, this._owner.checkGearController(1, this._controller)); 67 | this._owner.setScale(gv.scaleX, gv.scaleY); 68 | this._owner._gearLocked = false; 69 | } 70 | } 71 | 72 | private __tweenUpdate(tweener: GTweener): void { 73 | var flag: number = tweener.userData; 74 | this._owner._gearLocked = true; 75 | if ((flag & 1) != 0) 76 | this._owner.setSize(tweener.value.x, tweener.value.y, this._owner.checkGearController(1, this._controller)); 77 | if ((flag & 2) != 0) 78 | this._owner.setScale(tweener.value.z, tweener.value.w); 79 | this._owner._gearLocked = false; 80 | } 81 | 82 | private __tweenComplete(): void { 83 | if (this._tweenConfig._displayLockToken != 0) { 84 | this._owner.releaseDisplayLock(this._tweenConfig._displayLockToken); 85 | this._tweenConfig._displayLockToken = 0; 86 | } 87 | this._tweenConfig._tweener = null; 88 | } 89 | 90 | public updateState(): void { 91 | var gv: GearSizeValue = this._storage[this._controller.selectedPageId]; 92 | if (!gv) { 93 | gv = {}; 94 | this._storage[this._controller.selectedPageId] = gv; 95 | } 96 | 97 | gv.width = this._owner.width; 98 | gv.height = this._owner.height; 99 | gv.scaleX = this._owner.scaleX; 100 | gv.scaleY = this._owner.scaleY; 101 | } 102 | 103 | public updateFromRelations(dx: number, dy: number): void { 104 | if (this._controller == null || this._storage == null) 105 | return; 106 | 107 | for (var key in this._storage) { 108 | var gv: GearSizeValue = this._storage[key]; 109 | gv.width += dx; 110 | gv.height += dy; 111 | } 112 | this._default.width += dx; 113 | this._default.height += dy; 114 | 115 | this.updateState(); 116 | } 117 | } 118 | 119 | interface GearSizeValue { 120 | width?: number; 121 | height?: number; 122 | scaleX?: number; 123 | scaleY?: number; 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/ui/Relations.ts: -------------------------------------------------------------------------------- 1 | import { GComponent } from "./GComponent"; 2 | import { GObject } from "./GObject"; 3 | import { RelationItem } from "./RelationItem"; 4 | import { ByteBuffer } from "../utils/ByteBuffer"; 5 | 6 | export class Relations { 7 | private _owner: GObject; 8 | private _items: RelationItem[]; 9 | 10 | public handling: GObject; 11 | 12 | constructor(owner: GObject) { 13 | this._owner = owner; 14 | this._items = []; 15 | } 16 | 17 | public add(target: GObject, relationType: number, usePercent?: boolean): void { 18 | var length: number = this._items.length; 19 | for (var i: number = 0; i < length; i++) { 20 | var item: RelationItem = this._items[i]; 21 | if (item.target == target) { 22 | item.add(relationType, usePercent); 23 | return; 24 | } 25 | } 26 | var newItem: RelationItem = new RelationItem(this._owner); 27 | newItem.target = target; 28 | newItem.add(relationType, usePercent); 29 | this._items.push(newItem); 30 | } 31 | 32 | public remove(target: GObject, relationType?: number): void { 33 | relationType = relationType || 0; 34 | var cnt: number = this._items.length; 35 | var i: number = 0; 36 | while (i < cnt) { 37 | var item: RelationItem = this._items[i]; 38 | if (item.target == target) { 39 | item.remove(relationType); 40 | if (item.isEmpty) { 41 | item.dispose(); 42 | this._items.splice(i, 1); 43 | cnt--; 44 | } 45 | else 46 | i++; 47 | } 48 | else 49 | i++; 50 | } 51 | } 52 | 53 | public contains(target: GObject): boolean { 54 | var length: number = this._items.length; 55 | for (var i: number = 0; i < length; i++) { 56 | var item: RelationItem = this._items[i]; 57 | if (item.target == target) 58 | return true; 59 | } 60 | return false; 61 | } 62 | 63 | public clearFor(target: GObject): void { 64 | var cnt: number = this._items.length; 65 | var i: number = 0; 66 | while (i < cnt) { 67 | var item: RelationItem = this._items[i]; 68 | if (item.target == target) { 69 | item.dispose(); 70 | this._items.splice(i, 1); 71 | cnt--; 72 | } 73 | else 74 | i++; 75 | } 76 | } 77 | 78 | public clearAll(): void { 79 | var length: number = this._items.length; 80 | for (var i: number = 0; i < length; i++) { 81 | var item: RelationItem = this._items[i]; 82 | item.dispose(); 83 | } 84 | this._items.length = 0; 85 | } 86 | 87 | public copyFrom(source: Relations): void { 88 | this.clearAll(); 89 | 90 | var arr: RelationItem[] = source._items; 91 | var length: number = arr.length; 92 | for (var i: number = 0; i < length; i++) { 93 | var ri: RelationItem = arr[i]; 94 | var item: RelationItem = new RelationItem(this._owner); 95 | item.copy(ri); 96 | this._items.push(item); 97 | } 98 | } 99 | 100 | public dispose(): void { 101 | this.clearAll(); 102 | } 103 | 104 | public onOwnerSizeChanged(dWidth: number, dHeight: number, applyPivot: boolean): void { 105 | if (this._items.length == 0) 106 | return; 107 | 108 | var length: number = this._items.length; 109 | for (var i: number = 0; i < length; i++) { 110 | var item: RelationItem = this._items[i]; 111 | item.applyOnSelfResized(dWidth, dHeight, applyPivot); 112 | } 113 | } 114 | 115 | public get empty(): boolean { 116 | return this._items.length == 0; 117 | } 118 | 119 | public setup(buffer: ByteBuffer, parentToChild: boolean): void { 120 | var cnt: number = buffer.readByte(); 121 | var target: GObject; 122 | for (var i: number = 0; i < cnt; i++) { 123 | var targetIndex: number = buffer.readShort(); 124 | if (targetIndex == -1) 125 | target = this._owner.parent; 126 | else if (parentToChild) 127 | target = ((this._owner)).getChildAt(targetIndex); 128 | else 129 | target = this._owner.parent.getChildAt(targetIndex); 130 | 131 | var newItem: RelationItem = new RelationItem(this._owner); 132 | newItem.target = target; 133 | this._items.push(newItem); 134 | 135 | var cnt2: number = buffer.readByte(); 136 | for (var j: number = 0; j < cnt2; j++) { 137 | var rt: number = buffer.readByte(); 138 | var usePercent: boolean = buffer.readBool(); 139 | newItem.internalAdd(rt, usePercent); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/event/EventDispatcher.ts: -------------------------------------------------------------------------------- 1 | import { Event, EventPool, EventType } from "./Event"; 2 | 3 | type Listeners = { dispatching?: number, callbacks: Array, captures: Array }; 4 | 5 | export class EventDispatcher { 6 | public _listeners: Record; 7 | 8 | constructor() { 9 | this._listeners = {}; 10 | } 11 | 12 | public on(type: EventType, callback: Function, target?: any, capture?: boolean): void; 13 | public on(type: string, callback: Function, target?: any, capture?: boolean): void; 14 | public on(type: EventType | string, callback: Function, target?: any, capture?: boolean): void { 15 | let col = this._listeners[type]; 16 | if (!col) { 17 | col = { dispatching: 0, callbacks: [], captures: [] }; 18 | this._listeners[type] = col; 19 | } 20 | let arr = capture ? col.captures : col.callbacks; 21 | let index = arr.findIndex((value, index, arr) => value == callback && arr[index + 1] == target); 22 | if (index != -1) 23 | arr[index + 2] = false; 24 | else 25 | arr.push(callback, target, false); 26 | } 27 | 28 | public off(type: EventType, callback: Function, target?: any, capture?: boolean): void; 29 | public off(type: string, callback: Function, target?: any, capture?: boolean): void; 30 | public off(type: EventType | string, callback: Function, target?: any, capture?: boolean): void { 31 | let col = this._listeners[type]; 32 | if (!col) 33 | return; 34 | 35 | let arr = capture ? col.captures : col.callbacks; 36 | let index = arr.findIndex((value, index, arr) => value == callback && arr[index + 1] == target); 37 | if (index != -1) { 38 | if (col.dispatching != 0) { 39 | arr[index + 2] = true; 40 | col.dispatching = 2; 41 | } 42 | else 43 | arr.splice(index, 3); 44 | } 45 | } 46 | 47 | public offAll(type?: EventType): void; 48 | public offAll(type?: string): void; 49 | public offAll(type?: EventType | string): void { 50 | if (type) { 51 | let col = this._listeners[type]; 52 | if (col) { 53 | if (col.dispatching != 0) { 54 | col.callbacks.forEach((value, index, arr) => { if (index % 3 == 2) arr[index] = true; }); 55 | col.captures.forEach((value, index, arr) => { if (index % 3 == 2) arr[index] = true; }); 56 | col.dispatching = 2; 57 | } 58 | else { 59 | col.callbacks.length = 0; 60 | col.captures.length = 0; 61 | } 62 | } 63 | } 64 | else { 65 | for (var key in this._listeners) { 66 | delete this._listeners[key]; 67 | } 68 | } 69 | } 70 | 71 | public hasListener(type: EventType, callback?: Function, target?: any, capture?: boolean): boolean; 72 | public hasListener(type: string, callback?: Function, target?: any, capture?: boolean): boolean; 73 | public hasListener(type: EventType | string, callback?: Function, target?: any, capture?: boolean): boolean { 74 | let col = this._listeners[type]; 75 | if (!col) 76 | return false; 77 | 78 | let arr = capture ? col.captures : col.callbacks; 79 | if (!callback) 80 | return arr.length > 0; 81 | else 82 | arr.findIndex((value, index, arr) => value == callback && arr[index + 1] == target) != -1; 83 | } 84 | 85 | public dispatchEvent(type: EventType, data?: any): boolean; 86 | public dispatchEvent(type: string, data?: any): boolean; 87 | public dispatchEvent(type: EventType | string, data?: any): boolean { 88 | let col = this._listeners[type]; 89 | if (!col || col.callbacks.length == 0 && col.captures.length == 0) 90 | return; 91 | 92 | let ev = EventPool.borrow(type); 93 | ev._type = type; 94 | ev.data = data; 95 | 96 | this._dispatch(col, ev, true); 97 | this._dispatch(col, ev, false); 98 | 99 | EventPool.returns(ev); 100 | 101 | return ev._defaultPrevented; 102 | } 103 | 104 | public _dispatch(col: Listeners, ev: Event, capture?: boolean): void { 105 | if (col.dispatching != 0) 106 | return; 107 | 108 | col.dispatching = 1; 109 | ev._sender = this; 110 | let arr = capture ? col.captures : col.callbacks; 111 | 112 | let cnt = arr.length; 113 | for (let i = 0; i < cnt; i += 3) { 114 | (arr[i]).call(arr[i + 1], ev); 115 | } 116 | 117 | if (col.dispatching == 2) { 118 | let cnt = arr.length; 119 | let i = 0; 120 | while (i < cnt) { 121 | if (arr[i + 2]) { 122 | arr.splice(i, 3); 123 | cnt -= 3; 124 | continue; 125 | } 126 | else 127 | i += 3; 128 | } 129 | } 130 | col.dispatching = 0; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/core/Shape.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "./DisplayObject"; 2 | import { NGraphics } from "./NGraphics"; 3 | import { RectMesh } from "./mesh/RectMesh"; 4 | import { Color4 } from "../utils/Color"; 5 | import { EmptyTexture } from "./NTexture"; 6 | import { RoundedRectMesh } from "./mesh/RoundedRectMesh"; 7 | import { EllipseMesh } from "./mesh/EllipseMesh"; 8 | import { PolygonMesh } from "./mesh/PolygonMesh"; 9 | import { RegularPolygonMesh } from "./mesh/RegularPolygonMesh"; 10 | import { IHitTest } from "./hittest/IHitTest"; 11 | import { Vector2 } from "three"; 12 | import { HitTestContext } from "./Stage"; 13 | 14 | export class Shape extends DisplayObject { 15 | public constructor() { 16 | super(); 17 | 18 | this._graphics = new NGraphics(this._obj3D); 19 | this._graphics.texture = EmptyTexture; 20 | } 21 | 22 | public drawRect(lineWidth: number, lineColor: Color4, fillColor: Color4) { 23 | let mesh: RectMesh = this._graphics.getMeshFactory(RectMesh); 24 | mesh.lineWidth = lineWidth; 25 | mesh.lineColor = lineColor; 26 | mesh.fillColor = fillColor; 27 | 28 | this._graphics.setMeshDirty(); 29 | if (fillColor.a == 1) { 30 | mesh.fillColor = null; 31 | this._graphics.color = fillColor.getHex(); 32 | } 33 | else 34 | this._graphics.color = 0xFFFFFF; 35 | } 36 | 37 | public drawRoundRect(lineWidth: number, lineColor: Color4, fillColor: Color4, 38 | topLeftRadius: number, topRightRadius: number, bottomLeftRadius: number, bottomRightRadius: number) { 39 | let mesh = this._graphics.getMeshFactory(RoundedRectMesh); 40 | mesh.lineWidth = lineWidth; 41 | mesh.lineColor = lineColor; 42 | mesh.fillColor = fillColor; 43 | mesh.topLeftRadius = topLeftRadius; 44 | mesh.topRightRadius = topRightRadius; 45 | mesh.bottomLeftRadius = bottomLeftRadius; 46 | mesh.bottomRightRadius = bottomRightRadius; 47 | 48 | this._graphics.setMeshDirty(); 49 | if (fillColor.a == 1) { 50 | mesh.fillColor = null; 51 | this._graphics.color = fillColor.getHex(); 52 | } 53 | else 54 | this._graphics.color = 0xFFFFFF; 55 | } 56 | 57 | public drawEllipse(lineWidth: number, centerColor: Color4, lineColor: Color4, fillColor: Color4, startDegree?: number, endDegree?: number) { 58 | let mesh = this._graphics.getMeshFactory(EllipseMesh); 59 | mesh.lineWidth = lineWidth; 60 | mesh.lineColor = lineColor; 61 | mesh.fillColor = fillColor; 62 | if (centerColor.equals(fillColor)) 63 | mesh.centerColor = null; 64 | else 65 | mesh.centerColor = centerColor; 66 | mesh.startDegree = startDegree; 67 | mesh.endDegreee = endDegree; 68 | 69 | this._graphics.setMeshDirty(); 70 | if (fillColor.a == 1) { 71 | mesh.fillColor = null; 72 | this._graphics.color = fillColor.getHex(); 73 | } 74 | else 75 | this._graphics.color = 0xFFFFFF; 76 | } 77 | 78 | public drawPolygon(points: Array, fillColor: Color4, lineWidth?: number, lineColor?: Color4) { 79 | let mesh = this._graphics.getMeshFactory(PolygonMesh); 80 | mesh.points.length = 0; 81 | mesh.points.push.apply(mesh.points, points); 82 | mesh.fillColor = fillColor; 83 | mesh.lineWidth = lineWidth; 84 | mesh.lineColor = lineColor; 85 | 86 | this._graphics.setMeshDirty(); 87 | if (fillColor.a == 1) { 88 | mesh.fillColor = null; 89 | this._graphics.color = fillColor.getHex(); 90 | } 91 | else 92 | this._graphics.color = 0xFFFFFF; 93 | } 94 | 95 | public drawRegularPolygon(sides: number, lineWidth: number, centerColor: Color4, lineColor: Color4, 96 | fillColor: Color4, rotation: number, distances: Array) { 97 | let mesh = this._graphics.getMeshFactory(RegularPolygonMesh); 98 | mesh.sides = sides; 99 | mesh.lineWidth = lineWidth; 100 | mesh.centerColor = centerColor; 101 | mesh.lineColor = lineColor; 102 | mesh.fillColor = fillColor; 103 | mesh.rotation = rotation; 104 | mesh.distances = distances; 105 | 106 | this._graphics.setMeshDirty(); 107 | if (fillColor.a == 1) { 108 | mesh.fillColor = null; 109 | this._graphics.color = fillColor.getHex(); 110 | } 111 | else 112 | this._graphics.color = 0xFFFFFF; 113 | } 114 | 115 | public clear() { 116 | this._graphics.meshFactory = null; 117 | this._graphics.setMeshDirty(); 118 | } 119 | 120 | protected hitTest(context: HitTestContext): DisplayObject { 121 | if (!this._graphics.meshFactory) 122 | return null; 123 | 124 | let pt: Vector2 = context.getLocal(this); 125 | 126 | let ht: any = this._graphics.meshFactory; 127 | if ('hitTest' in ht) 128 | return (ht).hitTest(this._contentRect, pt.x, pt.y) ? this : null; 129 | else if (this._contentRect.contains(pt)) 130 | return this; 131 | else 132 | return null; 133 | } 134 | } -------------------------------------------------------------------------------- /src/utils/xml/XMLUtils.ts: -------------------------------------------------------------------------------- 1 | import { convertFromHtmlColor } from "../ToolSet"; 2 | 3 | export class XMLUtils { 4 | public static decodeString(aSource: string): string { 5 | let len = aSource.length; 6 | let sb: string = ""; 7 | let pos1 = 0, pos2 = 0; 8 | 9 | while (true) { 10 | pos2 = aSource.indexOf('&', pos1); 11 | if (pos2 == -1) { 12 | sb += aSource.substring(pos1); 13 | break; 14 | } 15 | sb += aSource.substring(pos1, pos2); 16 | 17 | pos1 = pos2 + 1; 18 | pos2 = pos1; 19 | let end = Math.min(len, pos2 + 10); 20 | for (; pos2 < end; pos2++) { 21 | if (aSource[pos2] == ';') 22 | break; 23 | } 24 | if (pos2 < end && pos2 > pos1) { 25 | let entity: string = aSource.substring(pos1, pos2); 26 | let u = 0; 27 | if (entity[0] == '#') { 28 | if (entity.length > 1) { 29 | if (entity[1] == 'x') 30 | u = parseInt(entity.substring(2), 16); 31 | else 32 | u = parseInt(entity.substring(1)); 33 | sb += String.fromCharCode(u); 34 | pos1 = pos2 + 1; 35 | } 36 | else 37 | sb += '&'; 38 | } 39 | else { 40 | switch (entity) { 41 | case "amp": 42 | u = 38; 43 | break; 44 | 45 | case "apos": 46 | u = 39; 47 | break; 48 | 49 | case "gt": 50 | u = 62; 51 | break; 52 | 53 | case "lt": 54 | u = 60; 55 | break; 56 | 57 | case "nbsp": 58 | u = 32; 59 | break; 60 | 61 | case "quot": 62 | u = 34; 63 | break; 64 | } 65 | if (u > 0) { 66 | sb += String.fromCharCode(u); 67 | pos1 = pos2 + 1; 68 | } 69 | else 70 | sb += '&'; 71 | } 72 | } 73 | else { 74 | sb += '&'; 75 | } 76 | } 77 | 78 | return sb; 79 | } 80 | 81 | public static encodeString(str: string): string { 82 | return str.replace(/&/g, "&").replace(//g, ">").replace(/'/g, "'").replace(/"/g, """); 84 | } 85 | 86 | public static getString(attrs: any, attrName: string, defValue?: string): string { 87 | if (attrs == null) 88 | return defValue == null ? null : defValue; 89 | 90 | let ret = attrs[attrName]; 91 | if (ret != null) 92 | return "" + ret; 93 | else 94 | return defValue == null ? null : defValue; 95 | } 96 | 97 | public static getInt(attrs: any, attrName: string, defValue?: number): number { 98 | let value: string = this.getString(attrs, attrName); 99 | if (value == null || value.length == 0) 100 | return defValue == null ? 0 : defValue; 101 | 102 | if (value[value.length - 1] == '%') { 103 | let ret = parseInt(value.substring(0, value.length - 1)); 104 | return Math.ceil(ret / 100.0 * defValue); 105 | } 106 | else 107 | return parseInt(value); 108 | } 109 | 110 | public static getFloat(attrs: any, attrName: string, defValue?: number): number { 111 | let value: string = this.getString(attrs, attrName); 112 | if (value == null || value.length == 0) 113 | return defValue == null ? 0 : defValue; 114 | 115 | let ret = parseFloat(value); 116 | if (isNaN(ret)) 117 | return defValue == null ? 0 : defValue; 118 | else 119 | return ret; 120 | } 121 | 122 | public static getBool(attrs: any, attrName: string, defValue?: boolean): boolean { 123 | let value: string = this.getString(attrs, attrName); 124 | if (value == null || value.length == 0) 125 | return defValue == null ? false : defValue; 126 | 127 | if (value == "true" || value == "1") 128 | return true; 129 | else if (value == "false" || value == "0") 130 | return false; 131 | else 132 | return defValue == null ? false : defValue; 133 | } 134 | 135 | public static getColor(attrs: any, attrName: string, defValue?: number): number { 136 | let value: string = this.getString(attrs, attrName); 137 | if (value == null || value.length == 0) 138 | return defValue == null ? 0 : defValue; 139 | 140 | return convertFromHtmlColor(value); 141 | } 142 | } 143 | 144 | const ESCAPES: Array = [ 145 | "&", "&", 146 | "<", "<", 147 | ">", ">", 148 | "'", "'", 149 | "\"", """ 150 | ]; -------------------------------------------------------------------------------- /src/core/mesh/RoundedRectMesh.ts: -------------------------------------------------------------------------------- 1 | import { IMeshFactory } from "./MeshFactory"; 2 | import { IHitTest } from "../hittest/IHitTest"; 3 | import { Rect } from "../../utils/Rect"; 4 | import { VertexBuffer } from "./VertexBuffer"; 5 | import { Color4 } from "../../utils/Color"; 6 | 7 | export class RoundedRectMesh implements IMeshFactory, IHitTest { 8 | public drawRect: Rect; 9 | public lineWidth: number; 10 | public lineColor: Color4; 11 | public fillColor: Color4; 12 | public topLeftRadius: number = 0; 13 | public topRightRadius: number = 0; 14 | public bottomLeftRadius: number = 0; 15 | public bottomRightRadius: number = 0; 16 | 17 | public constructor() { 18 | this.lineWidth = 1; 19 | this.lineColor = new Color4(); 20 | } 21 | 22 | public onPopulateMesh(vb: VertexBuffer) { 23 | let rect: Rect = this.drawRect ? this.drawRect : vb.contentRect; 24 | let color: Color4 = this.fillColor ? this.fillColor : vb.vertexColor; 25 | let lineColor = this.lineColor; 26 | 27 | let radiusX: number = rect.width / 2; 28 | let radiusY: number = rect.height / 2; 29 | let cornerMaxRadius: number = Math.min(radiusX, radiusY); 30 | let centerX: number = radiusX + rect.x; 31 | let centerY: number = radiusY + rect.y; 32 | 33 | vb.addVert(centerX, centerY, 0, color); 34 | 35 | let cnt: number = vb.currentVertCount; 36 | for (let i: number = 0; i < 4; i++) { 37 | let radius: number = 0; 38 | switch (i) { 39 | case 0: 40 | radius = this.bottomRightRadius; 41 | break; 42 | 43 | case 1: 44 | radius = this.bottomLeftRadius; 45 | break; 46 | 47 | case 2: 48 | radius = this.topLeftRadius; 49 | break; 50 | 51 | case 3: 52 | radius = this.topRightRadius; 53 | break; 54 | } 55 | radius = Math.min(cornerMaxRadius, radius); 56 | 57 | let offsetX: number = rect.x; 58 | let offsetY: number = rect.y; 59 | 60 | if (i == 0 || i == 3) 61 | offsetX = rect.xMax - radius * 2; 62 | if (i == 0 || i == 1) 63 | offsetY = rect.yMax - radius * 2; 64 | 65 | if (radius != 0) { 66 | let partNumSides: number = Math.max(1, Math.ceil(Math.PI * radius / 8)) + 1; 67 | let angleDelta: number = Math.PI / 2 / partNumSides; 68 | let angle: number = Math.PI / 2 * i; 69 | let startAngle: number = angle; 70 | 71 | for (let j: number = 1; j <= partNumSides; j++) { 72 | if (j == partNumSides) //消除精度误差带来的不对齐 73 | angle = startAngle + Math.PI / 2; 74 | let vx = offsetX + Math.cos(angle) * (radius - this.lineWidth) + radius; 75 | let vy = offsetY + Math.sin(angle) * (radius - this.lineWidth) + radius; 76 | vb.addVert(vx, vy, 0, color); 77 | if (this.lineWidth != 0) { 78 | vb.addVert(vx, vy, 0, lineColor); 79 | vb.addVert(offsetX + Math.cos(angle) * radius + radius, offsetY + Math.sin(angle) * radius + radius, 0, lineColor); 80 | } 81 | angle += angleDelta; 82 | } 83 | } 84 | else { 85 | let vx = offsetX; 86 | let vy = offsetY; 87 | if (this.lineWidth != 0) { 88 | if (i == 0 || i == 3) 89 | offsetX -= this.lineWidth; 90 | else 91 | offsetX += this.lineWidth; 92 | if (i == 0 || i == 1) 93 | offsetY -= this.lineWidth; 94 | else 95 | offsetY += this.lineWidth; 96 | vb.addVert(offsetX, offsetY, 0, color); 97 | vb.addVert(offsetX, offsetY, 0, lineColor); 98 | vb.addVert(vx, vy, 0, lineColor); 99 | } 100 | else 101 | vb.addVert(vx, vy, 0, color); 102 | } 103 | } 104 | cnt = vb.currentVertCount - cnt; 105 | 106 | if (this.lineWidth > 0) { 107 | for (let i: number = 0; i < cnt; i += 3) { 108 | if (i != cnt - 3) { 109 | vb.addTriangle(0, i + 4, i + 1); 110 | vb.addTriangle(i + 5, i + 3, i + 2); 111 | vb.addTriangle(i + 3, i + 5, i + 6); 112 | } 113 | else { 114 | vb.addTriangle(0, 1, i + 1); 115 | vb.addTriangle(2, i + 3, i + 2); 116 | vb.addTriangle(i + 3, 2, 3); 117 | } 118 | } 119 | } 120 | else { 121 | for (let i: number = 0; i < cnt; i++) 122 | vb.addTriangle(0, (i == cnt - 1) ? 1 : i + 2, i + 1); 123 | } 124 | } 125 | 126 | public hitTest(contentRect: Rect, x: number, y: number): boolean { 127 | if (this.drawRect) 128 | return this.drawRect.contains(x, y); 129 | else 130 | return contentRect.contains(x, y); 131 | } 132 | } -------------------------------------------------------------------------------- /src/utils/ColorMatrix.ts: -------------------------------------------------------------------------------- 1 | const LUMA_R: number = 0.299; 2 | const LUMA_G: number = 0.587; 3 | const LUMA_B: number = 0.114; 4 | const IDENTITY_MATRIX: Array = [ 5 | 1, 0, 0, 0, 0, 6 | 0, 1, 0, 0, 0, 7 | 0, 0, 1, 0, 0, 8 | 0, 0, 0, 1, 0 9 | ]; 10 | const LENGTH: number = IDENTITY_MATRIX.length; 11 | 12 | export class ColorMatrix { 13 | public matrix: Array; 14 | 15 | public static create(p_brightness: number, p_contrast: number, p_saturation: number, p_hue: number): ColorMatrix { 16 | var ret: ColorMatrix = new ColorMatrix(); 17 | ret.adjustColor(p_brightness, p_contrast, p_saturation, p_hue); 18 | return ret; 19 | } 20 | 21 | public static getMatrix(p_brightness: number, p_contrast: number, p_saturation: number, p_hue: number, result?: number[]): number[] { 22 | if (!result) 23 | result = new Array(ColorMatrix.length); 24 | let mat = helper; 25 | mat.reset(); 26 | mat.adjustColor(p_brightness, p_contrast, p_saturation, p_hue); 27 | var l: number = LENGTH; 28 | for (var i: number = 0; i < l; i++) { 29 | result[i] = mat.matrix[i]; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | public constructor() { 36 | this.matrix = new Array(LENGTH); 37 | this.reset(); 38 | } 39 | 40 | public reset(): void { 41 | for (var i: number = 0; i < LENGTH; i++) { 42 | this.matrix[i] = IDENTITY_MATRIX[i]; 43 | } 44 | } 45 | 46 | public invert(): void { 47 | this.multiplyMatrix([-1, 0, 0, 0, 255, 48 | 0, -1, 0, 0, 255, 49 | 0, 0, -1, 0, 255, 50 | 0, 0, 0, 1, 0]); 51 | } 52 | 53 | public adjustColor(p_brightness: number, p_contrast: number, p_saturation: number, p_hue: number): void { 54 | this.adjustBrightness(p_brightness); 55 | this.adjustContrast(p_contrast); 56 | this.adjustSaturation(p_saturation); 57 | this.adjustHue(p_hue); 58 | } 59 | 60 | public adjustBrightness(p_val: number): void { 61 | p_val = this.cleanValue(p_val, 1) * 255; 62 | this.multiplyMatrix([ 63 | 1, 0, 0, 0, p_val, 64 | 0, 1, 0, 0, p_val, 65 | 0, 0, 1, 0, p_val, 66 | 0, 0, 0, 1, 0 67 | ]); 68 | } 69 | 70 | public adjustContrast(p_val: number): void { 71 | p_val = this.cleanValue(p_val, 1); 72 | var s: number = p_val + 1; 73 | var o: number = 128 * (1 - s); 74 | this.multiplyMatrix([ 75 | s, 0, 0, 0, o, 76 | 0, s, 0, 0, o, 77 | 0, 0, s, 0, o, 78 | 0, 0, 0, 1, 0 79 | ]); 80 | } 81 | 82 | public adjustSaturation(p_val: number): void { 83 | p_val = this.cleanValue(p_val, 1); 84 | p_val += 1; 85 | 86 | var invSat: number = 1 - p_val; 87 | var invLumR: number = invSat * LUMA_R; 88 | var invLumG: number = invSat * LUMA_G; 89 | var invLumB: number = invSat * LUMA_B; 90 | 91 | this.multiplyMatrix([ 92 | (invLumR + p_val), invLumG, invLumB, 0, 0, 93 | invLumR, (invLumG + p_val), invLumB, 0, 0, 94 | invLumR, invLumG, (invLumB + p_val), 0, 0, 95 | 0, 0, 0, 1, 0 96 | ]); 97 | } 98 | 99 | public adjustHue(p_val: number): void { 100 | p_val = this.cleanValue(p_val, 1); 101 | p_val *= Math.PI; 102 | 103 | var cos: number = Math.cos(p_val); 104 | var sin: number = Math.sin(p_val); 105 | 106 | this.multiplyMatrix([ 107 | ((LUMA_R + (cos * (1 - LUMA_R))) + (sin * -(LUMA_R))), ((LUMA_G + (cos * -(LUMA_G))) + (sin * -(LUMA_G))), ((LUMA_B + (cos * -(LUMA_B))) + (sin * (1 - LUMA_B))), 0, 0, 108 | ((LUMA_R + (cos * -(LUMA_R))) + (sin * 0.143)), ((LUMA_G + (cos * (1 - LUMA_G))) + (sin * 0.14)), ((LUMA_B + (cos * -(LUMA_B))) + (sin * -0.283)), 0, 0, 109 | ((LUMA_R + (cos * -(LUMA_R))) + (sin * -((1 - LUMA_R)))), ((LUMA_G + (cos * -(LUMA_G))) + (sin * LUMA_G)), ((LUMA_B + (cos * (1 - LUMA_B))) + (sin * LUMA_B)), 0, 0, 110 | 0, 0, 0, 1, 0 111 | ]); 112 | } 113 | 114 | public concat(p_matrix: Array): void { 115 | if (p_matrix.length != LENGTH) { return; } 116 | this.multiplyMatrix(p_matrix); 117 | } 118 | 119 | public clone(): ColorMatrix { 120 | var result: ColorMatrix = new ColorMatrix(); 121 | result.copyMatrix(this.matrix); 122 | return result; 123 | } 124 | 125 | protected copyMatrix(p_matrix: Array): void { 126 | var l: number = LENGTH; 127 | for (var i: number = 0; i < l; i++) { 128 | this.matrix[i] = p_matrix[i]; 129 | } 130 | } 131 | 132 | protected multiplyMatrix(p_matrix: Array): void { 133 | var col: Array = []; 134 | 135 | var i: number = 0; 136 | 137 | for (var y: number = 0; y < 4; ++y) { 138 | for (var x: number = 0; x < 5; ++x) { 139 | col[i + x] = p_matrix[i] * this.matrix[x] + 140 | p_matrix[i + 1] * this.matrix[x + 5] + 141 | p_matrix[i + 2] * this.matrix[x + 10] + 142 | p_matrix[i + 3] * this.matrix[x + 15] + 143 | (x == 4 ? p_matrix[i + 4] : 0); 144 | } 145 | 146 | i += 5; 147 | } 148 | 149 | this.copyMatrix(col); 150 | } 151 | 152 | protected cleanValue(p_val: number, p_limit: number): number { 153 | return Math.min(p_limit, Math.max(-p_limit, p_val)); 154 | } 155 | } 156 | 157 | let helper: ColorMatrix = new ColorMatrix(); -------------------------------------------------------------------------------- /src/utils/ByteBuffer.ts: -------------------------------------------------------------------------------- 1 | import { Color4 } from "./Color"; 2 | 3 | export class ByteBuffer { 4 | public stringTable: Array; 5 | public version: number = 0; 6 | public littleEndian?: boolean; 7 | 8 | protected _buffer: ArrayBuffer; 9 | protected _view: DataView; 10 | protected _pos: number; 11 | protected _length: number; 12 | 13 | public constructor(buffer: ArrayBuffer, offset?: number, length?: number) { 14 | offset = offset || 0; 15 | if (length == null || length == -1) 16 | length = buffer.byteLength - offset; 17 | 18 | this._buffer = buffer; 19 | this._view = new DataView(this._buffer, offset, length); 20 | this._pos = 0; 21 | this._length = length; 22 | } 23 | 24 | public get data(): ArrayBuffer { 25 | return this._buffer; 26 | } 27 | 28 | public get pos(): number { 29 | return this._pos; 30 | } 31 | 32 | public set pos(value: number) { 33 | if (value > this._length) throw "Out of bounds"; 34 | this._pos = value; 35 | } 36 | 37 | public get length(): number { 38 | return this._length; 39 | } 40 | 41 | public skip(count: number): void { 42 | this._pos += count; 43 | } 44 | 45 | private validate(forward: number): void { 46 | if (this._pos + forward > this._length) throw "Out of bounds"; 47 | } 48 | 49 | public readByte(): number { 50 | this.validate(1); 51 | let ret: number = this._view.getUint8(this._pos); 52 | this._pos++; 53 | return ret; 54 | } 55 | 56 | public readBool(): boolean { 57 | return this.readByte() == 1; 58 | } 59 | 60 | public readShort(): number { 61 | this.validate(2); 62 | let ret: number = this._view.getInt16(this._pos, this.littleEndian); 63 | this._pos += 2; 64 | return ret; 65 | } 66 | 67 | public readUshort(): number { 68 | this.validate(2); 69 | let ret: number = this._view.getUint16(this._pos, this.littleEndian); 70 | this._pos += 2; 71 | return ret; 72 | } 73 | 74 | public readInt(): number { 75 | this.validate(4); 76 | let ret: number = this._view.getInt32(this._pos, this.littleEndian); 77 | this._pos += 4; 78 | return ret; 79 | } 80 | 81 | public readUint(): number { 82 | this.validate(4); 83 | let ret: number = this._view.getUint32(this._pos, this.littleEndian); 84 | this._pos += 4; 85 | return ret; 86 | } 87 | 88 | public readFloat(): number { 89 | this.validate(4); 90 | let ret: number = this._view.getFloat32(this._pos, this.littleEndian); 91 | this._pos += 4; 92 | return ret; 93 | } 94 | 95 | public readString(len?: number): string { 96 | if (len == undefined) len = this.readUshort(); 97 | this.validate(len); 98 | 99 | let decoder = new TextDecoder(); 100 | let ret: string = decoder.decode(new DataView(this._buffer, this._view.byteOffset + this._pos, len)); 101 | this._pos += len; 102 | 103 | return ret; 104 | } 105 | 106 | public readS(): string { 107 | var index: number = this.readUshort(); 108 | if (index == 65534) //null 109 | return null; 110 | else if (index == 65533) 111 | return "" 112 | else 113 | return this.stringTable[index]; 114 | } 115 | 116 | public readSArray(cnt: number): Array { 117 | var ret: Array = new Array(cnt); 118 | for (var i: number = 0; i < cnt; i++) 119 | ret[i] = this.readS(); 120 | 121 | return ret; 122 | } 123 | 124 | public writeS(value: string): void { 125 | var index: number = this.readUshort(); 126 | if (index != 65534 && index != 65533) 127 | this.stringTable[index] = value; 128 | } 129 | 130 | public readColor(): number { 131 | var r: number = this.readByte(); 132 | var g: number = this.readByte(); 133 | var b: number = this.readByte(); 134 | this.readByte(); //a 135 | 136 | return (r << 16) + (g << 8) + b; 137 | } 138 | 139 | public readFullColor(): Color4 { 140 | var r: number = this.readByte(); 141 | var g: number = this.readByte(); 142 | var b: number = this.readByte(); 143 | var a: number = this.readByte(); 144 | return new Color4((r << 16) + (g << 8) + b, a / 255); 145 | } 146 | 147 | public readChar(): string { 148 | var i: number = this.readUshort(); 149 | return String.fromCharCode(i); 150 | } 151 | 152 | public readBuffer(): ByteBuffer { 153 | var count: number = this.readUint(); 154 | this.validate(count); 155 | var ba: ByteBuffer = new ByteBuffer(this._buffer, this._view.byteOffset + this._pos, count); 156 | ba.stringTable = this.stringTable; 157 | ba.version = this.version; 158 | this._pos += count; 159 | return ba; 160 | } 161 | 162 | public seek(indexTablePos: number, blockIndex: number): boolean { 163 | var tmp: number = this._pos; 164 | this._pos = indexTablePos; 165 | var segCount: number = this.readByte(); 166 | if (blockIndex < segCount) { 167 | var useShort: boolean = this.readByte() == 1; 168 | var newPos: number; 169 | if (useShort) { 170 | this._pos += 2 * blockIndex; 171 | newPos = this.readUshort(); 172 | } 173 | else { 174 | this._pos += 4 * blockIndex; 175 | newPos = this.readUint(); 176 | } 177 | 178 | if (newPos > 0) { 179 | this._pos = indexTablePos + newPos; 180 | return true; 181 | } 182 | else { 183 | this._pos = tmp; 184 | return false; 185 | } 186 | } 187 | else { 188 | this._pos = tmp; 189 | return false; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/ui/GScrollBar.ts: -------------------------------------------------------------------------------- 1 | import { GComponent } from "./GComponent"; 2 | import { GObject } from "./GObject"; 3 | import { ScrollPane } from "./ScrollPane"; 4 | import { ByteBuffer } from "../utils/ByteBuffer"; 5 | import { Vector2 } from "three"; 6 | import { Event } from "../event/Event"; 7 | 8 | var s_vec2: Vector2 = new Vector2(); 9 | 10 | export class GScrollBar extends GComponent { 11 | private _grip: GObject; 12 | private _arrowButton1: GObject; 13 | private _arrowButton2: GObject; 14 | private _bar: GObject; 15 | private _target: ScrollPane; 16 | 17 | private _vertical: boolean; 18 | private _scrollPerc: number; 19 | private _fixedGripSize: boolean; 20 | 21 | private _dragOffset: Vector2; 22 | private _gripDragging: boolean; 23 | 24 | constructor() { 25 | super(); 26 | this._dragOffset = new Vector2(); 27 | this._scrollPerc = 0; 28 | } 29 | 30 | public setScrollPane(target: ScrollPane, vertical: boolean): void { 31 | this._target = target; 32 | this._vertical = vertical; 33 | } 34 | 35 | public setDisplayPerc(value: number) { 36 | if (this._vertical) { 37 | if (!this._fixedGripSize) 38 | this._grip.height = Math.floor(value * this._bar.height); 39 | this._grip.y = this._bar.y + (this._bar.height - this._grip.height) * this._scrollPerc; 40 | 41 | } 42 | else { 43 | if (!this._fixedGripSize) 44 | this._grip.width = Math.floor(value * this._bar.width); 45 | this._grip.x = this._bar.x + (this._bar.width - this._grip.width) * this._scrollPerc; 46 | } 47 | this._grip.visible = value != 0 && value != 1; 48 | } 49 | 50 | public setScrollPerc(val: number) { 51 | this._scrollPerc = val; 52 | if (this._vertical) 53 | this._grip.y = this._bar.y + (this._bar.height - this._grip.height) * this._scrollPerc; 54 | else 55 | this._grip.x = this._bar.x + (this._bar.width - this._grip.width) * this._scrollPerc; 56 | } 57 | 58 | public get minSize(): number { 59 | if (this._vertical) 60 | return (this._arrowButton1 ? this._arrowButton1.height : 0) + (this._arrowButton2 ? this._arrowButton2.height : 0); 61 | else 62 | return (this._arrowButton1 ? this._arrowButton1.width : 0) + (this._arrowButton2 ? this._arrowButton2.width : 0); 63 | } 64 | 65 | public get gripDragging(): boolean { 66 | return this._gripDragging; 67 | } 68 | 69 | protected constructExtension(buffer: ByteBuffer): void { 70 | buffer.seek(0, 6); 71 | 72 | this._fixedGripSize = buffer.readBool(); 73 | 74 | this._grip = this.getChild("grip"); 75 | if (!this._grip) { 76 | console.warn("需要定义grip"); 77 | return; 78 | } 79 | 80 | this._bar = this.getChild("bar"); 81 | if (!this._bar) { 82 | console.warn("需要定义bar"); 83 | return; 84 | } 85 | 86 | this._arrowButton1 = this.getChild("arrow1"); 87 | this._arrowButton2 = this.getChild("arrow2"); 88 | 89 | this._grip.on("touch_begin", this.__gripTouchBegin, this); 90 | this._grip.on("touch_move", this.__gripTouchMove, this); 91 | this._grip.on("touch_end", this.__gripTouchEnd, this); 92 | 93 | this.on("touch_begin", this.__barTouchBegin, this); 94 | 95 | if (this._arrowButton1) 96 | this._arrowButton1.on("touch_begin", this.__arrowButton1Click, this); 97 | if (this._arrowButton2) 98 | this._arrowButton2.on("touch_begin", this.__arrowButton2Click, this); 99 | } 100 | 101 | private __gripTouchBegin(evt: Event): void { 102 | if (this._bar == null) 103 | return; 104 | 105 | evt.stopPropagation(); 106 | 107 | this._gripDragging = true; 108 | this._target.updateScrollBarVisible(); 109 | 110 | this.globalToLocal(evt.input.x, evt.input.y, this._dragOffset); 111 | this._dragOffset.x -= this._grip.x; 112 | this._dragOffset.y -= this._grip.y; 113 | } 114 | 115 | private __gripTouchMove(evt: Event): void { 116 | if (!this.onStage) 117 | return; 118 | 119 | var pt: Vector2 = this.globalToLocal(evt.input.x, evt.input.y, s_vec2); 120 | if (this._vertical) { 121 | let curY: number = pt.y - this._dragOffset.y; 122 | let diff = this._bar.height - this._grip.height; 123 | if (diff == 0) 124 | this._target.percY = 0; 125 | else 126 | this._target.percY = (curY - this._bar.y) / diff; 127 | } 128 | else { 129 | let curX: number = pt.x - this._dragOffset.x; 130 | let diff = this._bar.width - this._grip.width; 131 | if (diff == 0) 132 | this._target.percX = 0; 133 | else 134 | this._target.percX = (curX - this._bar.x) / (this._bar.width - this._grip.width); 135 | } 136 | } 137 | 138 | private __gripTouchEnd(evt: Event): void { 139 | this._gripDragging = false; 140 | this._target.updateScrollBarVisible(); 141 | } 142 | 143 | private __arrowButton1Click(evt: Event): void { 144 | evt.stopPropagation(); 145 | 146 | if (this._vertical) 147 | this._target.scrollUp(); 148 | else 149 | this._target.scrollLeft(); 150 | } 151 | 152 | private __arrowButton2Click(evt: Event): void { 153 | evt.stopPropagation(); 154 | 155 | if (this._vertical) 156 | this._target.scrollDown(); 157 | else 158 | this._target.scrollRight(); 159 | } 160 | 161 | private __barTouchBegin(evt: Event): void { 162 | evt.stopPropagation(); 163 | 164 | var pt: Vector2 = this._grip.globalToLocal(evt.input.x, evt.input.y, s_vec2); 165 | if (this._vertical) { 166 | if (pt.y < 0) 167 | this._target.scrollUp(4); 168 | else 169 | this._target.scrollDown(4); 170 | } 171 | else { 172 | if (pt.x < 0) 173 | this._target.scrollLeft(4); 174 | else 175 | this._target.scrollRight(4); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/core/NMaterial.ts: -------------------------------------------------------------------------------- 1 | import { ShaderMaterial, ShaderLib, UniformsUtils, Uniform, Matrix4, Vector4, Texture, DoubleSide, DepthModes, Color } from "three"; 2 | 3 | export class NMaterial extends ShaderMaterial { 4 | public map: Texture; 5 | 6 | public constructor() { 7 | super(); 8 | 9 | let customUniforms = UniformsUtils.merge([ 10 | ShaderLib.basic.uniforms, 11 | { _ColorMatrix: new Uniform(new Matrix4()) }, 12 | { _ColorOffset: new Uniform(new Vector4()) }, 13 | { 14 | diffuse: { 15 | value: new Color(0xffffff) 16 | } 17 | } 18 | ]); 19 | 20 | this.uniforms = customUniforms; 21 | this.vertexShader = ` 22 | #ifdef TEXT 23 | #define USE_UV 24 | #endif 25 | #include 26 | #include 27 | #include 28 | varying vec4 vColor; 29 | attribute vec4 color; 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | void main() { 37 | 38 | #include 39 | 40 | vColor = color; 41 | 42 | #include 43 | 44 | #ifdef USE_ENVMAP 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #endif 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #include 60 | #include 61 | #include 62 | #include 63 | 64 | } 65 | `; 66 | 67 | this.fragmentShader = ` 68 | #ifdef TEXT 69 | #define USE_UV 70 | #endif 71 | uniform bool grayed; 72 | uniform bool colorFilter; 73 | uniform mat4 colorMatrix; 74 | uniform vec4 colorOffset; 75 | 76 | uniform vec3 diffuse; 77 | uniform float opacity; 78 | #ifndef FLAT_SHADED 79 | varying vec3 vNormal; 80 | #endif 81 | #include 82 | #include 83 | 84 | varying vec4 vColor; 85 | 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | void main() { 99 | #include 100 | vec4 diffuseColor = vec4( diffuse, opacity ); 101 | #include 102 | #ifdef USE_MAP 103 | #ifdef TEXT 104 | vec4 sampleColor = texture2D( map, vUv ); 105 | if(vColor.a<0.1) 106 | diffuseColor.a *= sampleColor.r; 107 | else if(vColor.a<0.4) 108 | diffuseColor.a *= sampleColor.g; 109 | else 110 | diffuseColor.a *= sampleColor.b; 111 | #else 112 | #include 113 | #endif 114 | #endif 115 | 116 | #ifdef TEXT 117 | diffuseColor.rgb *= vColor.rgb; 118 | #else 119 | diffuseColor *= vColor; 120 | #endif 121 | 122 | #include 123 | #include 124 | #include 125 | ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); 126 | // accumulation (baked indirect lighting only) 127 | #ifdef USE_LIGHTMAP 128 | vec4 lightMapTexel= texture2D( lightMap, vUv2 ); 129 | reflectedLight.indirectDiffuse += lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity; 130 | #else 131 | reflectedLight.indirectDiffuse += vec3( 1.0 ); 132 | #endif 133 | // modulation 134 | #include 135 | reflectedLight.indirectDiffuse *= diffuseColor.rgb; 136 | vec3 outgoingLight = reflectedLight.indirectDiffuse; 137 | #include 138 | gl_FragColor = vec4( outgoingLight, diffuseColor.a ); 139 | #include 140 | #include 141 | #include 142 | #include 143 | #include 144 | 145 | #ifdef GRAYED 146 | float grey = dot(gl_FragColor.rgb, vec3(0.299, 0.587, 0.114)); 147 | gl_FragColor.rgb = vec3(grey, grey, grey); 148 | #endif 149 | 150 | #ifdef COLOR_FILTER 151 | vec4 col = gl_FragColor; 152 | gl_FragColor.r = dot(col, _ColorMatrix[0]) + _ColorOffset.x; 153 | gl_FragColor.g = dot(col, _ColorMatrix[1]) + _ColorOffset.y; 154 | gl_FragColor.b = dot(col, _ColorMatrix[2]) + _ColorOffset.z; 155 | gl_FragColor.a = dot(col, _ColorMatrix[3]) + _ColorOffset.w; 156 | #endif 157 | } 158 | `; 159 | 160 | this.name = "ui-material"; 161 | this.lights = false; 162 | this.transparent = true; 163 | this.depthTest = false; 164 | this.side = DoubleSide; 165 | //this.wireframe = true; 166 | (this)["isMeshBasicMaterial"] = true; 167 | } 168 | } -------------------------------------------------------------------------------- /src/utils/UBBParser.ts: -------------------------------------------------------------------------------- 1 | 2 | export class UBBParser { 3 | private _text: string; 4 | private _readPos: number = 0; 5 | 6 | protected _handlers: Record string>; 7 | 8 | public defaultImgWidth: number = 0; 9 | public defaultImgHeight: number = 0; 10 | public lastColor: string; 11 | public lastSize: string; 12 | 13 | constructor() { 14 | this._handlers = {}; 15 | this._handlers["url"] = this.onTag_URL; 16 | this._handlers["img"] = this.onTag_IMG; 17 | this._handlers["b"] = this.onTag_B; 18 | this._handlers["i"] = this.onTag_I; 19 | this._handlers["u"] = this.onTag_U; 20 | this._handlers["sup"] = this.onTag_Simple; 21 | this._handlers["sub"] = this.onTag_Simple; 22 | this._handlers["color"] = this.onTag_COLOR; 23 | this._handlers["font"] = this.onTag_FONT; 24 | this._handlers["size"] = this.onTag_SIZE; 25 | } 26 | 27 | protected onTag_URL(tagName: string, end: boolean, attr: string): string { 28 | if (!end) { 29 | if (attr != null) 30 | return ""; 31 | else { 32 | var href: string = this.getTagText(); 33 | return ""; 34 | } 35 | } 36 | else 37 | return ""; 38 | } 39 | 40 | protected onTag_IMG(tagName: string, end: boolean, attr: string): string { 41 | if (!end) { 42 | var src: string = this.getTagText(true); 43 | if (!src) 44 | return null; 45 | 46 | if (this.defaultImgWidth) 47 | return ""; 48 | else 49 | return ""; 50 | } 51 | else 52 | return null; 53 | } 54 | 55 | protected onTag_B(tagName: string, end: boolean, attr: string): string { 56 | return end ? ("") : (""); 57 | } 58 | 59 | protected onTag_I(tagName: string, end: boolean, attr: string): string { 60 | return end ? ("") : (""); 61 | } 62 | 63 | protected onTag_U(tagName: string, end: boolean, attr: string): string { 64 | return end ? ("") : (""); 65 | } 66 | 67 | protected onTag_Simple(tagName: string, end: boolean, attr: string): string { 68 | return end ? ("") : ("<" + tagName + ">"); 69 | } 70 | 71 | protected onTag_COLOR(tagName: string, end: boolean, attr: string): string { 72 | if (!end) { 73 | this.lastColor = attr; 74 | return ""; 75 | } 76 | else 77 | return ""; 78 | } 79 | 80 | protected onTag_FONT(tagName: string, end: boolean, attr: string): string { 81 | if (!end) 82 | return ""; 83 | else 84 | return ""; 85 | } 86 | 87 | protected onTag_SIZE(tagName: string, end: boolean, attr: string): string { 88 | if (!end) { 89 | this.lastSize = attr; 90 | return ""; 91 | } 92 | else 93 | return ""; 94 | } 95 | 96 | protected getTagText(remove?: boolean): string { 97 | var pos1: number = this._readPos; 98 | var pos2: number; 99 | var result: string = ""; 100 | while ((pos2 = this._text.indexOf("[", pos1)) != -1) { 101 | if (this._text.charCodeAt(pos2 - 1) == 92)//\ 102 | { 103 | result += this._text.substring(pos1, pos2 - 1); 104 | result += "["; 105 | pos1 = pos2 + 1; 106 | } 107 | else { 108 | result += this._text.substring(pos1, pos2); 109 | break; 110 | } 111 | } 112 | if (pos2 == -1) 113 | return null; 114 | 115 | if (remove) 116 | this._readPos = pos2; 117 | 118 | return result; 119 | } 120 | 121 | public parse(text: string, remove?: boolean): string { 122 | this._text = text; 123 | this.lastColor = null; 124 | this.lastSize = null; 125 | 126 | var pos1: number = 0, pos2: number, pos3: number; 127 | var end: boolean; 128 | var tag: string, attr: string; 129 | var repl: string; 130 | var func: Function; 131 | var result: string = ""; 132 | while ((pos2 = this._text.indexOf("[", pos1)) != -1) { 133 | if (pos2 > 0 && this._text.charCodeAt(pos2 - 1) == 92)//\ 134 | { 135 | result += this._text.substring(pos1, pos2 - 1); 136 | result += "["; 137 | pos1 = pos2 + 1; 138 | continue; 139 | } 140 | 141 | result += this._text.substring(pos1, pos2); 142 | pos1 = pos2; 143 | pos2 = this._text.indexOf("]", pos1); 144 | if (pos2 == -1) 145 | break; 146 | 147 | end = this._text.charAt(pos1 + 1) == '/'; 148 | tag = this._text.substring(end ? pos1 + 2 : pos1 + 1, pos2); 149 | this._readPos = pos2 + 1; 150 | attr = null; 151 | repl = null; 152 | pos3 = tag.indexOf("="); 153 | if (pos3 != -1) { 154 | attr = tag.substring(pos3 + 1); 155 | tag = tag.substring(0, pos3); 156 | } 157 | tag = tag.toLowerCase(); 158 | func = this._handlers[tag]; 159 | if (func != null) { 160 | if (!remove) { 161 | repl = func.call(this, tag, end, attr); 162 | if (repl != null) 163 | result += repl; 164 | } 165 | } 166 | else 167 | result += this._text.substring(pos1, this._readPos); 168 | pos1 = this._readPos; 169 | } 170 | 171 | if (pos1 < this._text.length) 172 | result += this._text.substring(pos1); 173 | 174 | this._text = null; 175 | 176 | return result; 177 | } 178 | } 179 | 180 | export var defaultParser: UBBParser = new UBBParser(); --------------------------------------------------------------------------------