├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── workflows │ ├── build.yml │ └── project │ ├── build.xml │ └── src │ └── Main.hx ├── .gitignore ├── LICENSE.md ├── README.md ├── classpath.exclusions ├── extraParams.hxml ├── haxe └── ui │ └── backend │ ├── AppImpl.hx │ ├── AssetsImpl.hx │ ├── BackendImpl.hx │ ├── CallLaterImpl.hx │ ├── ComponentGraphicsImpl.hx │ ├── ComponentImpl.hx │ ├── ComponentSurface.hx │ ├── EventImpl.hx │ ├── FontData.hx │ ├── ImageData.hx │ ├── ImageDisplayImpl.hx │ ├── ImageSurface.hx │ ├── OpenFileDialogImpl.hx │ ├── PlatformImpl.hx │ ├── SaveFileDialogImpl.hx │ ├── ScreenImpl.hx │ ├── TextDisplayImpl.hx │ ├── TextInputImpl.hx │ ├── TimerImpl.hx │ ├── ToolkitOptions.hx │ ├── flixel │ ├── CursorHelper.hx │ ├── FlxHaxeUIAppState.hx │ ├── FlxStyleHelper.hx │ ├── InputManager.hx │ ├── KeyboardHelper.hx │ ├── MouseHelper.hx │ ├── OpenFLStyleHelper.hx │ ├── StateHelper.hx │ ├── UIFragment.hx │ ├── UIFragmentBase.hx │ ├── UIRTTITools.hx │ ├── UIRuntimeFragment.hx │ ├── UIRuntimeState.hx │ ├── UIRuntimeSubState.hx │ ├── UIState.hx │ ├── UIStateBase.hx │ ├── UISubState.hx │ ├── UISubStateBase.hx │ ├── _module │ │ └── styles │ │ │ └── default │ │ │ ├── cursors │ │ │ └── pointer.png │ │ │ └── main.css │ ├── components │ │ ├── SparrowPlayer.hx │ │ └── SpriteWrapper.hx │ ├── macros │ │ └── UIStateMacro.hx │ └── textinputs │ │ ├── FlxTextInput.hx │ │ └── OpenFLTextInput.hx │ └── module.xml ├── haxelib.json └── include.xml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ianharrigan] 2 | patreon: haxeui 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Media 24 | 25 | 26 | ## Test app / minimal test case 27 | 28 | 29 | 30 | ## Context 31 | 32 | 33 | 34 | ## Your Environment 35 | 36 | * Version used: 37 | * Environment name and version (e.g. Chrome 39, node.js 5.4): 38 | * Operating System and version (desktop or mobile): 39 | * Link to your project: 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, repository_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-13, windows-latest] 12 | haxe-version: [4.2.5, 4.3.0, 4.3.1] 13 | target: [html5, linux, windows, mac] 14 | exclude: 15 | - os: ubuntu-latest 16 | target: windows 17 | - os: ubuntu-latest 18 | target: mac 19 | - os: windows-latest 20 | target: linux 21 | - os: windows-latest 22 | target: mac 23 | - os: macos-13 24 | target: linux 25 | - os: macos-13 26 | target: windows 27 | 28 | 29 | steps: 30 | - uses: actions/checkout@v1 31 | - name: Setup Haxe (${{ matrix.target }}, haxe ${{ matrix.haxe-version }}, ${{ matrix.os }}) 32 | uses: krdlab/setup-haxe@v1 33 | with: 34 | haxe-version: ${{ matrix.haxe-version }} 35 | 36 | - name: Setup app (${{ matrix.target }}, haxe ${{ matrix.haxe-version }}, ${{ matrix.os }}) 37 | run: | 38 | git clone --branch master https://github.com/haxeui/haxeui-core.git --depth=1 39 | haxelib dev haxeui-core haxeui-core 40 | haxelib dev haxeui-flixel . 41 | haxelib install hxcpp --always --quiet 42 | haxelib install actuate --always --quiet 43 | haxelib install openfl --always --quiet 44 | haxelib install flixel --always --quiet 45 | echo "y" | haxelib run openfl setup 46 | 47 | - name: Build app (${{ matrix.target }}, haxe ${{ matrix.haxe-version }}, ${{ matrix.os }}) 48 | run: | 49 | cd .github/workflows/project 50 | haxelib run openfl build ${{ matrix.target }} 51 | -------------------------------------------------------------------------------- /.github/workflows/project/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/project/src/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | class Main { 4 | public static function main() { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | *_i.c 46 | *_p.c 47 | *.ilk 48 | *.meta 49 | *.obj 50 | *.pch 51 | *.pdb 52 | *.pgc 53 | *.pgd 54 | *.rsp 55 | *.sbr 56 | *.tlb 57 | *.tli 58 | *.tlh 59 | *.tmp 60 | *.vspscc 61 | .builds 62 | *.dotCover 63 | 64 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 65 | #packages/ 66 | 67 | # Visual C++ cache files 68 | ipch/ 69 | *.aps 70 | *.ncb 71 | *.opensdf 72 | *.sdf 73 | 74 | # Visual Studio profiler 75 | *.psess 76 | *.vsp 77 | 78 | # ReSharper is a .NET coding add-in 79 | _ReSharper* 80 | 81 | # Installshield output folder 82 | [Ee]xpress 83 | 84 | # DocProject is a documentation generator add-in 85 | DocProject/buildhelp/ 86 | DocProject/Help/*.HxT 87 | DocProject/Help/*.HxC 88 | DocProject/Help/*.hhc 89 | DocProject/Help/*.hhk 90 | DocProject/Help/*.hhp 91 | DocProject/Help/Html2 92 | DocProject/Help/html 93 | 94 | # Click-Once directory 95 | publish 96 | 97 | # Others 98 | [Bb]in 99 | [Oo]bj 100 | sql 101 | TestResults 102 | *.Cache 103 | ClientBin 104 | stylecop.* 105 | ~$* 106 | *.dbmdl 107 | Generated_Code #added for RIA/Silverlight projects 108 | 109 | # Backup & report files from converting an old project file to a newer 110 | # Visual Studio version. Backup files are not needed, because we have git ;-) 111 | _UpgradeReport_Files/ 112 | Backup*/ 113 | UpgradeLog*.XML 114 | 115 | 116 | 117 | ############ 118 | ## Windows 119 | ############ 120 | 121 | # Windows image file caches 122 | Thumbs.db 123 | 124 | # Folder config file 125 | Desktop.ini 126 | 127 | 128 | ############# 129 | ## Python 130 | ############# 131 | 132 | *.py[co] 133 | 134 | # Packages 135 | *.egg 136 | *.egg-info 137 | dist 138 | build 139 | eggs 140 | parts 141 | bin 142 | var 143 | sdist 144 | develop-eggs 145 | .installed.cfg 146 | 147 | # Installer logs 148 | pip-log.txt 149 | 150 | # Unit test / coverage reports 151 | .coverage 152 | .tox 153 | 154 | #Translations 155 | *.mo 156 | 157 | #Mr Developer 158 | .mr.developer.cfg 159 | 160 | # Mac crap 161 | .DS_Store 162 | 163 | 164 | *.backup 165 | dox.xml -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ian Harrigan 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build status](https://github.com/haxeui/haxeui-flixel/actions/workflows/build.yml/badge.svg) 2 | 3 | # haxeui-flixel 4 | `haxeui-flixel` is the `Flixel` backend for `HaxeUI`. 5 | 6 | 7 | ## Installation 8 | `haxeui-flixel` relies on `haxeui-core` as well as `Flixel`. To install: 9 | 10 | ``` 11 | haxelib install flixel 12 | haxelib install haxeui-core 13 | haxelib install haxeui-flixel 14 | ``` 15 | 16 | ## Usage 17 | 18 | After installing `Lime`, `OpenFL`, `Flixel`, `haxeui-core`, and `haxeui-flixel`, the latter three should be included in `project.xml`. In the future, including `haxeui-flixel` will also handle the dependencies automatically. 19 | 20 | ```xml 21 | 22 | 23 | 24 | ``` 25 | 26 | ### Toolkit initialization and usage 27 | Before you start using `HaxeUI` in your project, you must first initialize the `Toolkit`. 28 | 29 | ```haxe 30 | Toolkit.init(); 31 | ``` 32 | 33 | Once the toolkit is initialized, you can add components using the methods specified here. 34 | 35 | ```haxe 36 | var app = new HaxeUIApp(); 37 | app.ready( 38 | function() { 39 | var main = ComponentMacros.buildComponent("assets/xml/test.xml"); // whatever your XML layout path is 40 | app.addComponent(main); 41 | app.start(); 42 | } 43 | ); 44 | ``` 45 | 46 | Some examples are [here](https://github.com/haxeui/component-examples). 47 | 48 | ## Addtional resources 49 | * component-explorer - Browse HaxeUI components 50 | * playground - Write and test HaxeUI layouts in your browser 51 | * component-examples - Various componet examples 52 | * haxeui-api - The HaxeUI api docs. 53 | * haxeui-guides - Set of guides to working with HaxeUI and backends. 54 | -------------------------------------------------------------------------------- /classpath.exclusions: -------------------------------------------------------------------------------- 1 | ; exclude paths from classpath when searching for haxeui arifacts (module.xml, native.xml, etc) 2 | ; speeds up build 3 | \/flixel\/ 4 | \/lime\/.*$ 5 | \/openfl\/.*$ 6 | \/actuate\/.*$ -------------------------------------------------------------------------------- /extraParams.hxml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxeui/haxeui-flixel/100f2c96beab619cfe72c567a058c41c71e3e998/extraParams.hxml -------------------------------------------------------------------------------- /haxe/ui/backend/AppImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.FlxGame; 4 | import haxe.ui.backend.flixel.FlxHaxeUIAppState; 5 | import lime.graphics.Image; 6 | import openfl.Lib; 7 | 8 | class AppImpl extends AppBase { 9 | public function new() { 10 | } 11 | 12 | private override function build() { 13 | var targetFramerate = Toolkit.backendProperties.getPropInt("haxe.ui.flixel.fps", 60); 14 | 15 | #if (flixel < "5.0.0") 16 | Lib.current.stage.addChild(new FlxGame(0, 0, FlxHaxeUIAppState, 1, targetFramerate, targetFramerate, true)); 17 | #else 18 | Lib.current.stage.addChild(new FlxGame(0, 0, FlxHaxeUIAppState, targetFramerate, targetFramerate, true)); 19 | #end 20 | if (Toolkit.backendProperties.getPropBool("haxe.ui.flixel.fps.show")) { 21 | var x = Toolkit.backendProperties.getPropInt("haxe.ui.flixel.fps.left"); 22 | var y = Toolkit.backendProperties.getPropInt("haxe.ui.flixel.fps.top"); 23 | var c = Toolkit.backendProperties.getPropCol("haxe.ui.flixel.fps.color"); 24 | Lib.current.stage.addChild(new openfl.display.FPS(x, y, c)); 25 | } 26 | } 27 | 28 | private override function set_icon(value:String):String { 29 | if (_icon == value) { 30 | return value; 31 | } 32 | _icon = value; 33 | 34 | ToolkitAssets.instance.getImage(_icon, function(imageInfo) { 35 | if (imageInfo != null) { 36 | var iconImage = Image.fromBitmapData(imageInfo.data.parent.bitmap); 37 | Lib.current.stage.window.setIcon(iconImage); 38 | } 39 | }); 40 | 41 | return value; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /haxe/ui/backend/AssetsImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.graphics.FlxGraphic; 4 | import flixel.graphics.frames.FlxFrame; 5 | import flixel.graphics.frames.FlxImageFrame; 6 | import haxe.io.Bytes; 7 | import haxe.ui.assets.FontInfo; 8 | import haxe.ui.assets.ImageInfo; 9 | import haxe.ui.backend.ImageData; 10 | import openfl.Assets; 11 | import openfl.display.Bitmap; 12 | import openfl.display.Loader; 13 | import openfl.events.Event; 14 | import openfl.utils.AssetType; 15 | import openfl.utils.ByteArray; 16 | 17 | class AssetsImpl extends AssetsBase { 18 | private override function getImageInternal(resourceId:String, callback:ImageInfo->Void):Void { 19 | var graphic:FlxGraphic = null; 20 | var frame:FlxFrame = null; 21 | 22 | if (Assets.exists(resourceId)) { 23 | graphic = FlxGraphic.fromAssetKey(resourceId); 24 | frame = FlxImageFrame.fromGraphic(graphic).frame; 25 | } 26 | 27 | if (frame != null) { 28 | frame.parent.persist = true; 29 | frame.parent.destroyOnNoUse = false; 30 | callback({ 31 | data : frame, 32 | width : Std.int(frame.sourceSize.x), 33 | height : Std.int(frame.sourceSize.y) 34 | }); 35 | } else { 36 | callback(null); 37 | } 38 | } 39 | 40 | private override function getImageFromHaxeResource(resourceId:String, callback:String->ImageInfo->Void):Void { 41 | if (Resource.listNames().indexOf(resourceId) == -1) { 42 | callback(resourceId, null); 43 | } else { 44 | var bytes = Resource.getBytes(resourceId); 45 | imageFromBytes(bytes, callback.bind(resourceId)); 46 | } 47 | } 48 | 49 | public override function imageFromBytes(bytes:Bytes, callback:ImageInfo->Void):Void { 50 | var ba:ByteArray = ByteArray.fromBytes(bytes); 51 | var loader:Loader = new Loader(); 52 | loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e) { 53 | if (loader.content != null) { 54 | var frame = FlxImageFrame.fromImage(cast(loader.content, Bitmap).bitmapData).frame; 55 | frame.parent.persist = true; // these two booleans will screw up the UI unless changed from the default values 56 | frame.parent.destroyOnNoUse = false; 57 | callback({ 58 | data : frame, 59 | width : Std.int(frame.sourceSize.x), 60 | height : Std.int(frame.sourceSize.y) 61 | }); 62 | } else { 63 | callback(null); 64 | } 65 | }); 66 | loader.contentLoaderInfo.addEventListener("ioError", function(e) { 67 | trace(e); 68 | callback(null); 69 | }); 70 | 71 | loader.loadBytes(ba); 72 | } 73 | 74 | private override function getFontInternal(resourceId:String, callback:FontInfo->Void):Void { 75 | var fontName:String = null; 76 | if (isEmbeddedFont(resourceId) && Assets.exists(resourceId, AssetType.FONT)) { 77 | fontName = Assets.getFont(resourceId).fontName; 78 | } else { 79 | fontName = resourceId; 80 | } 81 | callback({ 82 | data : fontName 83 | }); 84 | } 85 | 86 | private override function getTextDelegate(resourceId:String):String { 87 | if (Assets.exists(resourceId)) { 88 | return Assets.getText(resourceId); 89 | } 90 | return null; 91 | } 92 | 93 | public override function imageInfoFromImageData(imageData:ImageData):ImageInfo { 94 | return { 95 | data: imageData, 96 | width: Std.int(imageData.frame.width), 97 | height: Std.int(imageData.frame.height) 98 | } 99 | } 100 | 101 | private static inline function isEmbeddedFont(fontName:String):Bool { 102 | return fontName != "_sans" && fontName != "_serif" && fontName != "_typewriter"; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /haxe/ui/backend/BackendImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | class BackendImpl { 4 | public static var id:String = "flixel"; 5 | } -------------------------------------------------------------------------------- /haxe/ui/backend/CallLaterImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.FlxG; 4 | 5 | // we'll flip between two list references between updates meaning there is breathing space 6 | // between calls rather that just filling a list and blocking 7 | class CallLaterImpl { 8 | 9 | private static var added:Bool = false; 10 | private static var current:ArrayVoid>; 11 | private static var list1:ArrayVoid> = []; 12 | private static var list2:ArrayVoid> = []; 13 | public function new(fn:Void->Void) { 14 | if (!added) { 15 | added = true; 16 | current = list1; 17 | FlxG.signals.preUpdate.add(onUpdate); 18 | } 19 | current.insert(0, fn); 20 | } 21 | 22 | private static function onUpdate() { 23 | var ref = current; 24 | if (current == list1) { 25 | current = list2; 26 | } else { 27 | current = list1; 28 | } 29 | while (ref.length > 0) { 30 | ref.pop()(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /haxe/ui/backend/ComponentGraphicsImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.FlxSprite; 4 | import haxe.io.Bytes; 5 | import haxe.ui.core.Component; 6 | import haxe.ui.loaders.image.ImageLoader; 7 | import haxe.ui.util.Color; 8 | import haxe.ui.util.Variant; 9 | import openfl.display.BitmapData; 10 | import openfl.display.GraphicsPath; 11 | import openfl.display.GraphicsPathCommand; 12 | import openfl.display.Sprite; 13 | import openfl.geom.Matrix; 14 | import openfl.geom.Rectangle; 15 | import openfl.utils.ByteArray; 16 | 17 | @:allow(haxe.ui.backend.ComponentGraphicsSprite) 18 | class ComponentGraphicsImpl extends ComponentGraphicsBase { 19 | private var _hasSize:Bool = false; 20 | private var bitmapData:BitmapData = null; 21 | private var sprite:ComponentGraphicsSprite; 22 | 23 | private var flashGfxSprite:Sprite = new Sprite(); 24 | 25 | private var _currentFillColor:Null = null; 26 | private var _currentFillAlpha:Null = null; 27 | private var _globalFillColor:Null = null; 28 | private var _globalFillAlpha:Null = null; 29 | 30 | private var _globalLineThickness:Null = null; 31 | private var _globalLineColor:Null = null; 32 | private var _globalLineAlpha:Null = null; 33 | 34 | private var currentPath:GraphicsPath; 35 | 36 | public function new(component:Component) { 37 | super(component); 38 | sprite = new ComponentGraphicsSprite(this); 39 | sprite.active = false; 40 | sprite.visible = false; 41 | _component.add(sprite); 42 | } 43 | 44 | public override function clear() { 45 | super.clear(); 46 | if (_hasSize == false) { 47 | return; 48 | } 49 | flashGfxSprite.graphics.clear(); 50 | 51 | sprite.pixels.fillRect(sprite.pixels.rect, 0x00000000); 52 | sprite._needsDraw = true; 53 | } 54 | 55 | public override function setPixel(x:Float, y:Float, color:Color) { 56 | super.setPixel(x, y, color); 57 | if (_hasSize == false) { 58 | return; 59 | } 60 | flashGfxSprite.graphics.beginFill(color); 61 | flashGfxSprite.graphics.drawRect(x, y, 1, 1); 62 | flashGfxSprite.graphics.endFill(); 63 | sprite._needsDraw = true; 64 | } 65 | 66 | public override function setPixels(pixels:Bytes) { 67 | super.setPixels(pixels); 68 | if (_hasSize == false) { 69 | return; 70 | } 71 | 72 | var w = Std.int(_component.width); 73 | var h = Std.int(_component.height); 74 | 75 | if (bitmapData != null && (bitmapData.width != w || bitmapData.height != h)) { 76 | bitmapData.dispose(); 77 | bitmapData = null; 78 | } 79 | 80 | if (bitmapData == null) { 81 | bitmapData = new BitmapData(w, h, true, 0x00000000); 82 | } 83 | 84 | // convert RGBA -> ARGB (well, actually BGRA for some reason) 85 | var bytesData = pixels.getData(); 86 | var length:Int = pixels.length; 87 | var newPixels = Bytes.alloc(length); 88 | var i:Int = 0; 89 | while (i < length) { 90 | var r = Bytes.fastGet(bytesData, i + 0); 91 | var g = Bytes.fastGet(bytesData, i + 1); 92 | var b = Bytes.fastGet(bytesData, i + 2); 93 | var a = Bytes.fastGet(bytesData, i + 3); 94 | newPixels.set(i + 0, b); 95 | newPixels.set(i + 1, g); 96 | newPixels.set(i + 2, r); 97 | newPixels.set(i + 3, a); 98 | i += 4; 99 | } 100 | var byteArray = ByteArray.fromBytes(newPixels); 101 | bitmapData.setPixels(new Rectangle(0, 0, bitmapData.width, bitmapData.height), byteArray); 102 | 103 | sprite.width = w; 104 | sprite.height = h; 105 | 106 | sprite.pixels = bitmapData; 107 | sprite.visible = (w > 0 && h > 0); 108 | } 109 | 110 | public override function moveTo(x:Float, y:Float) { 111 | super.moveTo(x, y); 112 | if (_hasSize == false) { 113 | return; 114 | } 115 | if (currentPath != null) { 116 | currentPath.moveTo(x, y); 117 | } else { 118 | flashGfxSprite.graphics.moveTo(x, y); 119 | sprite._needsDraw = true; 120 | } 121 | } 122 | 123 | public override function lineTo(x:Float, y:Float) { 124 | super.lineTo(x, y); 125 | if (_hasSize == false) { 126 | return; 127 | } 128 | if (currentPath != null) { 129 | currentPath.lineTo(x, y); 130 | } else { 131 | flashGfxSprite.graphics.lineTo(x, y); 132 | sprite._needsDraw = true; 133 | } 134 | } 135 | 136 | public override function strokeStyle(color:Null, thickness:Null = 1, alpha:Null = 1) { 137 | super.strokeStyle(color, thickness, alpha); 138 | if (_hasSize == false) { 139 | return; 140 | } 141 | if (currentPath == null) { 142 | _globalLineThickness = thickness; 143 | _globalLineColor = color; 144 | _globalLineAlpha = alpha; 145 | } 146 | 147 | flashGfxSprite.graphics.lineStyle(thickness, color, alpha); 148 | } 149 | 150 | public override function circle(x:Float, y:Float, radius:Float) { 151 | super.circle(x, y, radius); 152 | if (_hasSize == false) { 153 | return; 154 | } 155 | if (_currentFillColor != null) { 156 | flashGfxSprite.graphics.beginFill(_currentFillColor, _currentFillAlpha); 157 | } 158 | flashGfxSprite.graphics.drawCircle(x, y, radius); 159 | if (_currentFillColor != null) { 160 | flashGfxSprite.graphics.endFill(); 161 | } 162 | sprite._needsDraw = true; 163 | } 164 | 165 | public override function fillStyle(color:Null, alpha:Null = 1) { 166 | super.fillStyle(color, alpha); 167 | if (_hasSize == false) { 168 | return; 169 | } 170 | if (currentPath == null) { 171 | _globalFillColor = color; 172 | _globalFillAlpha = alpha; 173 | } 174 | _currentFillColor = color; 175 | _currentFillAlpha = alpha; 176 | } 177 | 178 | public override function curveTo(controlX:Float, controlY:Float, anchorX:Float, anchorY:Float) { 179 | super.curveTo(controlX, controlY, anchorX, anchorY); 180 | if (_hasSize == false) { 181 | return; 182 | } 183 | 184 | if (currentPath != null) { 185 | currentPath.curveTo(controlX, controlY, anchorX, anchorY); 186 | } else { 187 | flashGfxSprite.graphics.curveTo(controlX, controlY, anchorX, anchorY); 188 | sprite._needsDraw = true; 189 | } 190 | } 191 | 192 | public override function cubicCurveTo(controlX1:Float, controlY1:Float, controlX2:Float, controlY2:Float, anchorX:Float, anchorY:Float) { 193 | super.cubicCurveTo(controlX1, controlY1, controlX2, controlY2, anchorX, anchorY); 194 | if (_hasSize == false) { 195 | return; 196 | } 197 | if (currentPath != null) { 198 | currentPath.cubicCurveTo(controlX1, controlY1, controlX2, controlY2, anchorX, anchorY); 199 | } else { 200 | flashGfxSprite.graphics.cubicCurveTo(controlX1, controlY1, controlX2, controlY2, anchorX, anchorY); 201 | sprite._needsDraw = true; 202 | } 203 | } 204 | 205 | public override function rectangle(x:Float, y:Float, width:Float, height:Float) { 206 | super.rectangle(x, y, width, height); 207 | if (_hasSize == false) { 208 | return; 209 | } 210 | if (_currentFillColor != null) { 211 | flashGfxSprite.graphics.beginFill(_currentFillColor, _currentFillAlpha); 212 | } 213 | flashGfxSprite.graphics.drawRect(x, y, width, height); 214 | if (_currentFillColor != null) { 215 | flashGfxSprite.graphics.endFill(); 216 | } 217 | sprite._needsDraw = true; 218 | } 219 | 220 | public override function image(resource:Variant, x:Null = null, y:Null = null, width:Null = null, height:Null = null) { 221 | super.image(resource, x, y, width, height); 222 | if (_hasSize == false) { 223 | return; 224 | } 225 | ImageLoader.instance.load(resource, function(imageInfo) { 226 | if (imageInfo != null) { 227 | if (x == null) x = 0; 228 | if (y == null) y = 0; 229 | if (width == null) width = imageInfo.width; 230 | if (height == null) height = imageInfo.height; 231 | 232 | var mat:Matrix = new Matrix(); 233 | mat.scale(width / imageInfo.width, height / imageInfo.width); 234 | mat.translate(x, y); 235 | 236 | flashGfxSprite.graphics.beginBitmapFill(imageInfo.data.parent.bitmap, mat); 237 | flashGfxSprite.graphics.drawRect(x, y, width, height); 238 | flashGfxSprite.graphics.endFill(); 239 | sprite._needsDraw = true; 240 | } else { 241 | trace("could not load: " + resource); 242 | } 243 | }); 244 | } 245 | 246 | public override function beginPath() { 247 | super.beginPath(); 248 | if (_hasSize == false) { 249 | return; 250 | } 251 | currentPath = new GraphicsPath(); 252 | } 253 | 254 | public override function closePath() { 255 | super.closePath(); 256 | if (_hasSize == false) { 257 | return; 258 | } 259 | if (currentPath != null && currentPath.commands != null && currentPath.commands.length > 0) { 260 | if (_currentFillColor != null) { 261 | flashGfxSprite.graphics.beginFill(_currentFillColor, _currentFillAlpha); 262 | } 263 | if (currentPath.commands[0] != GraphicsPathCommand.MOVE_TO) { 264 | currentPath.commands.insertAt(0, GraphicsPathCommand.MOVE_TO); 265 | #if !flash 266 | @:privateAccess currentPath.data.insertAt(0, flashGfxSprite.graphics.__positionX); 267 | @:privateAccess currentPath.data.insertAt(0, flashGfxSprite.graphics.__positionY); 268 | #end 269 | } 270 | flashGfxSprite.graphics.drawPath(currentPath.commands, currentPath.data); 271 | if (_currentFillColor != null) { 272 | flashGfxSprite.graphics.endFill(); 273 | } 274 | sprite._needsDraw = true; 275 | } 276 | currentPath = null; 277 | _currentFillColor = _globalFillColor; 278 | _currentFillAlpha = _globalFillAlpha; 279 | 280 | // it seems openfl forgets about lineStyle after drawing a shape; 281 | flashGfxSprite.graphics.lineStyle(_globalLineThickness, _globalLineColor, _globalLineAlpha); 282 | } 283 | 284 | public override function resize(width:Null, height:Null) { 285 | if (width > 0 && height > 0) { 286 | if (_hasSize == false) { 287 | _hasSize = true; 288 | sprite.makeGraphic(Std.int(width), Std.int(height), 0x00000000, true); 289 | sprite.visible = true; 290 | replayDrawCommands(); 291 | } 292 | } 293 | } 294 | } 295 | 296 | @:allow(haxe.ui.backend.ComponentGraphicsImpl) 297 | class ComponentGraphicsSprite extends FlxSprite { 298 | private var componentGraphics:ComponentGraphicsImpl; 299 | 300 | private var _needsDraw:Bool = false; 301 | 302 | public function new(componentGraphics:ComponentGraphicsImpl) { 303 | super(); 304 | this.componentGraphics = componentGraphics; 305 | } 306 | 307 | public override function draw() { 308 | if (pixels != null && _needsDraw) { 309 | pixels.draw(componentGraphics.flashGfxSprite); 310 | _needsDraw = false; 311 | } 312 | super.draw(); 313 | } 314 | } -------------------------------------------------------------------------------- /haxe/ui/backend/ComponentImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.FlxG; 4 | import flixel.FlxSprite; 5 | import flixel.FlxState; 6 | import flixel.math.FlxRect; 7 | import flixel.text.FlxText.FlxTextBorderStyle; 8 | import haxe.ui.Toolkit; 9 | import haxe.ui.backend.TextInputImpl.TextInputEvent; 10 | import haxe.ui.backend.flixel.FlxStyleHelper; 11 | import haxe.ui.core.Component; 12 | import haxe.ui.core.ImageDisplay; 13 | import haxe.ui.core.Platform; 14 | import haxe.ui.core.Screen; 15 | import haxe.ui.core.TextDisplay; 16 | import haxe.ui.core.TextInput; 17 | import haxe.ui.events.KeyboardEvent; 18 | import haxe.ui.events.MouseEvent; 19 | import haxe.ui.events.UIEvent; 20 | import haxe.ui.filters.DropShadow; 21 | import haxe.ui.filters.Outline; 22 | import haxe.ui.geom.Rectangle; 23 | import haxe.ui.styles.Style; 24 | 25 | class ComponentImpl extends ComponentBase { 26 | private var _eventMap:MapVoid>; 27 | 28 | private var _surface:FlxSprite; 29 | 30 | private var asComponent:Component; 31 | 32 | public function new() { 33 | super(); 34 | _eventMap = new MapVoid>(); 35 | 36 | this.pixelPerfectRender = true; 37 | this.moves = false; 38 | superVisible(false); 39 | 40 | asComponent = cast(this, Component); 41 | 42 | if (Platform.instance.isMobile) { 43 | asComponent.addClass(":mobile"); 44 | } 45 | 46 | scrollFactor.set(0, 0); // ui doesn't scroll by default 47 | 48 | _surface = new FlxSprite(); 49 | _surface.pixelPerfectRender = true; 50 | _surface.active = false; 51 | _surface.visible = false; 52 | add(_surface); 53 | 54 | //recursiveReady(); 55 | } 56 | 57 | private function recursiveReady() { 58 | asComponent.ready(); 59 | for (child in asComponent.childComponents) { 60 | child.recursiveReady(); 61 | } 62 | } 63 | 64 | private var _state:FlxState; 65 | public var state(get, set):FlxState; 66 | private function get_state():FlxState { 67 | return findRootComponent()._state; 68 | } 69 | private function set_state(value:FlxState):FlxState { 70 | findRootComponent()._state = value; 71 | return value; 72 | } 73 | 74 | 75 | // lets cache certain items so we dont have to loop multiple times per frame 76 | private var _cachedScreenX:Null = null; 77 | private var _cachedScreenY:Null = null; 78 | private var _cachedClipComponent:Component = null; 79 | private var _cachedClipComponentNone:Null = null; 80 | private var _cachedRootComponent:Component = null; 81 | 82 | private function clearCaches() { 83 | _cachedScreenX = null; 84 | _cachedScreenY = null; 85 | _cachedClipComponent = null; 86 | _cachedClipComponentNone = null; 87 | _cachedRootComponent = null; 88 | } 89 | 90 | private function cacheScreenPos() { 91 | if (_cachedScreenX != null && _cachedScreenY != null) { 92 | return; 93 | } 94 | var screenBounds = asComponent.screenBounds; 95 | _cachedScreenX = screenBounds.left; 96 | _cachedScreenY = screenBounds.top; 97 | } 98 | 99 | private var cachedScreenX(get, null):Float; 100 | private function get_cachedScreenX():Float { 101 | cacheScreenPos(); 102 | return _cachedScreenX; 103 | } 104 | 105 | private var cachedScreenY(get, null):Float; 106 | private function get_cachedScreenY():Float { 107 | cacheScreenPos(); 108 | return _cachedScreenY; 109 | } 110 | 111 | private function findRootComponent():Component { 112 | if (_cachedRootComponent != null) { 113 | return _cachedRootComponent; 114 | } 115 | 116 | var c:Component = asComponent; 117 | while (c.parentComponent != null) { 118 | c = c.parentComponent; 119 | } 120 | 121 | _cachedRootComponent = c; 122 | 123 | return c; 124 | } 125 | 126 | private function isRootComponent():Bool { 127 | return (findRootComponent() == this); 128 | } 129 | 130 | private function findClipComponent():Component { 131 | if (_cachedClipComponent != null) { 132 | return _cachedClipComponent; 133 | } else if (_cachedClipComponentNone == true) { 134 | return null; 135 | } 136 | 137 | var c:Component = asComponent; 138 | var clip:Component = null; 139 | while (c != null) { 140 | if (c.componentClipRect != null) { 141 | clip = c; 142 | break; 143 | } 144 | c = c.parentComponent; 145 | } 146 | 147 | _cachedClipComponent = clip; 148 | if (clip == null) { 149 | _cachedClipComponentNone = true; 150 | } 151 | 152 | return clip; 153 | } 154 | 155 | @:access(haxe.ui.core.Component) 156 | private function inBounds(x:Float, y:Float):Bool { 157 | if (asComponent.hidden == true) { 158 | return false; 159 | } 160 | 161 | var b:Bool = false; 162 | var sx = cachedScreenX; 163 | var sy = cachedScreenY; 164 | var cx = asComponent.componentWidth * Toolkit.scaleX; 165 | var cy = asComponent.componentHeight * Toolkit.scaleY; 166 | 167 | if (x >= sx && y >= sy && x <= sx + cx && y < sy + cy) { 168 | b = true; 169 | } 170 | 171 | // let make sure its in the clip rect too 172 | if (b == true) { 173 | var clip:Component = findClipComponent(); 174 | if (clip != null) { 175 | b = false; 176 | var sx = (clip.cachedScreenX + (clip.componentClipRect.left * Toolkit.scaleX)); 177 | var sy = (clip.cachedScreenY + (clip.componentClipRect.top * Toolkit.scaleY)); 178 | var cx = clip.componentClipRect.width * Toolkit.scaleX; 179 | var cy = clip.componentClipRect.height * Toolkit.scaleY; 180 | if (x >= sx && y >= sy && x <= sx + cx && y < sy + cy) { 181 | b = true; 182 | } 183 | } 184 | } 185 | return b; 186 | } 187 | 188 | private override function handlePosition(left:Null, top:Null, style:Style) { 189 | if (left == null && top == null) { 190 | return; 191 | } 192 | 193 | if (parentComponent == null) { 194 | if (left != null) { 195 | //this.x = left; 196 | } 197 | if (top != null) { 198 | //this.y = top; 199 | } 200 | } 201 | } 202 | 203 | private override function handleSize(width:Null, height:Null, style:Style) { 204 | if (_surface == null) { 205 | return; 206 | } 207 | 208 | if (width == null || height == null) { 209 | return; 210 | } 211 | 212 | var w:Int = Std.int(width * Toolkit.scaleX); 213 | var h:Int = Std.int(height * Toolkit.scaleY); 214 | if (_surface.width != w || _surface.height != h) { 215 | if (w <= 0 || h <= 0) { 216 | _surface.makeGraphic(1, 1, 0x0, true); 217 | _surface.visible = false; 218 | } else { 219 | _surface.makeGraphic(w, h, 0x0, true); 220 | applyStyle(style); 221 | } 222 | } 223 | } 224 | 225 | //*********************************************************************************************************** 226 | // Display tree 227 | //*********************************************************************************************************** 228 | 229 | private override function handleDestroy() { 230 | destroyInternal(); 231 | } 232 | 233 | private override function handleSetComponentIndex(child:Component, index:Int) { 234 | handleAddComponentAt(child, index); 235 | } 236 | 237 | private override function handleAddComponent(child:Component):Component { 238 | handleAddComponentAt(child, childComponents.length - 1); 239 | return child; 240 | } 241 | 242 | private function superVisible(value:Bool) { 243 | _skipTransformChildren = true; 244 | super.set_visible(value); 245 | _skipTransformChildren = false; 246 | } 247 | 248 | private override function handleAddComponentAt(child:Component, index:Int):Component { 249 | // index is in terms of haxeui components, not flixel children 250 | var indexOffset = 0; 251 | while (indexOffset < members.length) { 252 | if (!(members[indexOffset] is Component)) { 253 | indexOffset++; 254 | } else{ 255 | break; 256 | } 257 | } 258 | 259 | insert(index + indexOffset, child); 260 | return child; 261 | } 262 | 263 | private var _unsolicitedMembers:Array<{sprite: FlxSprite, originalX:Float, originalY:Float}> = null; 264 | private override function preAdd(sprite:FlxSprite) { 265 | if (isUnsolicitedMember(sprite)) { 266 | if (_unsolicitedMembers == null) { 267 | _unsolicitedMembers = []; 268 | } 269 | if (findUnsolictedEntryFromSprite(sprite) == null) { 270 | var use = true; 271 | if (_textInput != null && _textInput.equals(sprite)) { 272 | use = false; 273 | } 274 | if (use) { 275 | _unsolicitedMembers.push({ 276 | sprite: sprite, 277 | originalX: sprite.x, 278 | originalY: sprite.y 279 | }); 280 | } 281 | } 282 | } 283 | super.preAdd(sprite); 284 | } 285 | 286 | public override function remove(sprite:FlxSprite, splice:Bool = false):FlxSprite { 287 | if (isUnsolicitedMember(sprite) && _unsolicitedMembers != null) { 288 | var um = findUnsolictedEntryFromSprite(sprite); 289 | _unsolicitedMembers.remove(um); 290 | } 291 | return super.remove(sprite, splice); 292 | } 293 | 294 | private function findUnsolictedEntryFromSprite(sprite:FlxSprite):{sprite: FlxSprite, originalX:Float, originalY:Float} { 295 | if (_unsolicitedMembers == null) { 296 | return null; 297 | } 298 | for (um in _unsolicitedMembers) { 299 | if (um.sprite == sprite) { 300 | return um; 301 | } 302 | } 303 | return null; 304 | } 305 | 306 | private var _destroy:Bool = false; 307 | private override function handleRemoveComponent(child:Component, dispose:Bool = true):Component { 308 | if (this.exists == false) { // lets make sure this component exists - it could have been destroyed through a variety of different ways already (like switching state for example, or simply manually destroying it) 309 | return child; 310 | } 311 | if (members.indexOf(child) > -1) { 312 | remove(child, true); 313 | if (dispose == true) { 314 | child._destroy = true; 315 | child.destroyInternal(); 316 | } 317 | } 318 | return child; 319 | } 320 | 321 | private override function handleRemoveComponentAt(index:Int, dispose:Bool = true):Component { 322 | return handleRemoveComponent(this.childComponents[index], dispose); 323 | } 324 | 325 | private override function handleClipRect(value:Rectangle):Void { 326 | if (value == null) { 327 | clipRect = null; 328 | } 329 | } 330 | 331 | private override function handleVisibility(show:Bool):Void { 332 | applyVisibility(show); 333 | } 334 | 335 | private function applyVisibility(show:Bool):Void { 336 | superVisible(show); 337 | 338 | if (hasTextDisplay()) { 339 | _textDisplay.tf.visible = show; 340 | } 341 | if (hasTextInput()) { 342 | _textInput.visible = show; 343 | } 344 | 345 | for (c in this.childComponents) { 346 | c.applyVisibility(show && !c.hidden); 347 | } 348 | } 349 | 350 | //*********************************************************************************************************** 351 | // Style 352 | //*********************************************************************************************************** 353 | private override function applyStyle(style:Style) { 354 | if (style.opacity != null) { 355 | applyAlpha(style.opacity); 356 | } else if (_surface.alpha != 1) { 357 | //applyAlpha(1); 358 | } 359 | 360 | /* 361 | if (style != null && style.cursor != null && _mouseOverFlag) { 362 | Screen.instance.setCursor(this.style.cursor, this.style.cursorOffsetX, this.style.cursorOffsetY); 363 | _cursorSet = true; 364 | } 365 | */ 366 | 367 | _surface.visible = FlxStyleHelper.applyStyle(_surface, style); 368 | applyFilters(style); 369 | } 370 | 371 | private function applyAlpha(value:Float) { 372 | _surface.alpha = value; 373 | if (hasTextDisplay()) { 374 | getTextDisplay().tf.alpha = value; 375 | } 376 | if (hasTextInput()) { 377 | getTextInput().alpha = value; 378 | } 379 | if (hasImageDisplay()) { 380 | getImageDisplay().alpha = value; 381 | } 382 | for (c in childComponents) { 383 | if (c.style != null && c.style.opacity != null) { 384 | c.applyAlpha(c.style.opacity * value); 385 | } else { 386 | c.applyAlpha(value); 387 | } 388 | } 389 | } 390 | 391 | public override function set_alpha(alpha:Float):Float { 392 | _surface.alpha = alpha; 393 | if (hasTextDisplay()) { 394 | getTextDisplay().tf.alpha = alpha; 395 | } 396 | if (hasTextInput()) { 397 | getTextInput().alpha = alpha; 398 | } 399 | if (hasImageDisplay()) { 400 | getImageDisplay().alpha = alpha; 401 | } 402 | return super.set_alpha(alpha); 403 | } 404 | 405 | private function applyFilters(style:Style) { 406 | if (style.filter != null && style.filter.length > 0) { 407 | for (f in style.filter) { 408 | if (_textDisplay != null && (f is Outline)) { 409 | var o = cast(f, Outline); 410 | var col = o.color; 411 | _textDisplay.tf.setBorderStyle(FlxTextBorderStyle.OUTLINE, 0xFF000000 | o.color, o.size); 412 | } else if (_textDisplay != null && (f is DropShadow)) { 413 | var o = cast(f, DropShadow); 414 | _textDisplay.tf.setBorderStyle(FlxTextBorderStyle.SHADOW, 0xFF000000 | o.color, o.distance); 415 | } 416 | } 417 | } 418 | } 419 | 420 | //*********************************************************************************************************** 421 | // Image 422 | //*********************************************************************************************************** 423 | public override function createImageDisplay():ImageDisplay { 424 | if (_imageDisplay == null) { 425 | super.createImageDisplay(); 426 | _imageDisplay.visible = false; 427 | add(_imageDisplay); 428 | Toolkit.callLater(function() { // lets show it a frame later so its had a chance to reposition 429 | if (_imageDisplay != null) { 430 | _imageDisplay.visible = true; 431 | } 432 | }); 433 | } 434 | 435 | return _imageDisplay; 436 | } 437 | 438 | public override function removeImageDisplay():Void { 439 | if (_imageDisplay != null) { 440 | remove(_imageDisplay, true); 441 | _imageDisplay.destroy(); 442 | _imageDisplay = null; 443 | } 444 | } 445 | 446 | //*********************************************************************************************************** 447 | // Events 448 | //*********************************************************************************************************** 449 | private override function mapEvent(type:String, listener:UIEvent->Void) { 450 | switch (type) { 451 | case MouseEvent.MOUSE_DOWN: 452 | if (_eventMap.exists(MouseEvent.MOUSE_DOWN) == false) { 453 | if (hasTextInput()) { 454 | _eventMap.set(MouseEvent.MOUSE_DOWN, listener); 455 | getTextInput().onMouseDown = __onTextInputMouseEvent; 456 | } 457 | } 458 | 459 | case MouseEvent.MOUSE_UP: 460 | if (_eventMap.exists(MouseEvent.MOUSE_UP) == false) { 461 | if (hasTextInput()) { 462 | _eventMap.set(MouseEvent.MOUSE_UP, listener); 463 | getTextInput().onMouseUp = __onTextInputMouseEvent; 464 | } 465 | } 466 | 467 | case MouseEvent.CLICK: 468 | if (_eventMap.exists(MouseEvent.CLICK) == false) { 469 | if (hasTextInput()) { 470 | _eventMap.set(MouseEvent.CLICK, listener); 471 | getTextInput().onClick = __onTextInputMouseEvent; 472 | } 473 | } 474 | 475 | case KeyboardEvent.KEY_DOWN: 476 | if (_eventMap.exists(KeyboardEvent.KEY_DOWN) == false) { 477 | if (hasTextInput()) { 478 | _eventMap.set(KeyboardEvent.KEY_DOWN, listener); 479 | getTextInput().onKeyDown = __onTextInputKeyboardEvent; 480 | } 481 | } 482 | 483 | case KeyboardEvent.KEY_UP: 484 | if (_eventMap.exists(KeyboardEvent.KEY_UP) == false) { 485 | if (hasTextInput()) { 486 | _eventMap.set(KeyboardEvent.KEY_UP, listener); 487 | getTextInput().onKeyUp = __onTextInputKeyboardEvent; 488 | } 489 | } 490 | 491 | case UIEvent.CHANGE: 492 | if (_eventMap.exists(UIEvent.CHANGE) == false) { 493 | if (hasTextInput() == true) { 494 | _eventMap.set(UIEvent.CHANGE, listener); 495 | getTextInput().onChange = __onTextInputChange; 496 | } 497 | } 498 | } 499 | } 500 | 501 | private override function unmapEvent(type:String, listener:UIEvent->Void) { 502 | switch (type) { 503 | case MouseEvent.MOUSE_DOWN: 504 | if (hasTextInput()) { 505 | _eventMap.remove(type); 506 | getTextInput().onMouseDown = null; 507 | } 508 | 509 | case MouseEvent.MOUSE_UP: 510 | if (hasTextInput()) { 511 | _eventMap.remove(type); 512 | getTextInput().onMouseUp = null; 513 | } 514 | 515 | case MouseEvent.CLICK: 516 | if (hasTextInput()) { 517 | _eventMap.remove(type); 518 | getTextInput().onClick = null; 519 | } 520 | 521 | case KeyboardEvent.KEY_DOWN: 522 | if (hasTextInput()) { 523 | _eventMap.remove(type); 524 | getTextInput().onKeyDown = null; 525 | } 526 | 527 | case KeyboardEvent.KEY_UP: 528 | if (hasTextInput()) { 529 | _eventMap.remove(type); 530 | getTextInput().onKeyUp = null; 531 | } 532 | 533 | case UIEvent.CHANGE: 534 | if (hasTextInput()) { 535 | _eventMap.remove(type); 536 | getTextInput().onChange = null; 537 | } 538 | } 539 | } 540 | 541 | private function __onTextInputChange(event:TextInputEvent) { 542 | var fn:UIEvent->Void = _eventMap.get(UIEvent.CHANGE); 543 | if (fn != null) { 544 | fn(new UIEvent(UIEvent.CHANGE)); 545 | } 546 | } 547 | 548 | private function __onTextInputMouseEvent(event:TextInputEvent) { 549 | var type = null; 550 | switch (event.type) { 551 | case openfl.events.MouseEvent.MOUSE_DOWN: 552 | type = MouseEvent.MOUSE_DOWN; 553 | case openfl.events.MouseEvent.MOUSE_UP: 554 | type = MouseEvent.MOUSE_UP; 555 | case openfl.events.MouseEvent.CLICK: 556 | type = MouseEvent.CLICK; 557 | } 558 | var fn:UIEvent->Void = _eventMap.get(type); 559 | if (fn != null) { 560 | var mouseEvent = new haxe.ui.events.MouseEvent(type); 561 | mouseEvent.screenX = event.stageX / Toolkit.scaleX; 562 | mouseEvent.screenY = event.stageY / Toolkit.scaleY; 563 | if (Platform.instance.isMobile) { 564 | mouseEvent.touchEvent = true; 565 | } 566 | fn(mouseEvent); 567 | } 568 | } 569 | 570 | /* 571 | private var _mouseOverFlag:Bool = false; 572 | private var _cursorSet:Bool = false; 573 | private function __onMouseOver(event:MouseEvent) { 574 | _mouseOverFlag = true; 575 | } 576 | 577 | private function __onMouseOut(event:MouseEvent) { 578 | _mouseOverFlag = false; 579 | } 580 | */ 581 | 582 | #if haxeui_dont_impose_base_class 583 | private function applyRootLayout(l:String) { 584 | } 585 | #end 586 | 587 | private function __onTextInputKeyboardEvent(event:openfl.events.KeyboardEvent) { 588 | var type = switch (event.type) { 589 | case openfl.events.KeyboardEvent.KEY_DOWN: 590 | KeyboardEvent.KEY_DOWN; 591 | case openfl.events.KeyboardEvent.KEY_UP: 592 | KeyboardEvent.KEY_UP; 593 | default: 594 | null; 595 | } 596 | 597 | var fn = _eventMap.get(type); 598 | if (fn == null) { 599 | return; 600 | } 601 | 602 | var keyboardEvent = new KeyboardEvent(type); 603 | keyboardEvent.keyCode = event.keyCode; 604 | keyboardEvent.altKey = event.altKey; 605 | keyboardEvent.ctrlKey = event.ctrlKey; 606 | keyboardEvent.shiftKey = event.shiftKey; 607 | fn(keyboardEvent); 608 | } 609 | 610 | //*********************************************************************************************************** 611 | // Text related 612 | //*********************************************************************************************************** 613 | public override function createTextDisplay(text:String = null):TextDisplay { 614 | if (_textDisplay == null) { 615 | super.createTextDisplay(text); 616 | _textDisplay.tf.visible = false; 617 | add(_textDisplay.tf); 618 | Toolkit.callLater(function() { // lets show it a frame later so its had a chance to reposition 619 | //_textDisplay.tf.visible = true; 620 | applyFilters(style); 621 | }); 622 | } 623 | 624 | return _textDisplay; 625 | } 626 | 627 | public override function createTextInput(text:String = null):TextInput { 628 | if (_textInput == null) { 629 | super.createTextInput(text); 630 | _textInput.attach(); 631 | _textInput.visible = false; 632 | _textInput.addToComponent(cast this); 633 | /* 634 | Toolkit.callLater(function() { // lets show it a frame later so its had a chance to reposition 635 | if (_textInput != null) { 636 | _textInput.tf.visible = true; 637 | } 638 | }); 639 | */ 640 | } 641 | 642 | return _textInput; 643 | } 644 | //*********************************************************************************************************** 645 | // Util 646 | //*********************************************************************************************************** 647 | private function repositionChildren() { 648 | var xpos = this.cachedScreenX; 649 | var ypos = this.cachedScreenY; 650 | if (_surface != null) { 651 | _surface.x = xpos; 652 | _surface.y = ypos; 653 | } 654 | 655 | if (_textDisplay != null) { 656 | var offsetX = 2 / Toolkit.scaleX; 657 | var offsetY = 2 / Toolkit.scaleY; 658 | _textDisplay.tf.x = xpos + _textDisplay.left - offsetX; 659 | _textDisplay.tf.y = ypos + _textDisplay.top - offsetY; 660 | } 661 | 662 | if (_textInput != null) { 663 | var offsetX = 2 / Toolkit.scaleX; 664 | var offsetY = 2 / Toolkit.scaleY; 665 | _textInput.x = (xpos + _textInput.left - offsetX); 666 | _textInput.y = (ypos + _textInput.top - offsetY); 667 | _textInput.scaleX = FlxG.scaleMode.scale.x; 668 | _textInput.scaleY = FlxG.scaleMode.scale.y; 669 | _textInput.update(); 670 | } 671 | 672 | if (_imageDisplay != null) { 673 | var offsetX = 0; 674 | var offsetY = 0; 675 | _imageDisplay.x = xpos + _imageDisplay.left - offsetX; 676 | _imageDisplay.y = ypos + _imageDisplay.top - offsetY; 677 | } 678 | 679 | if (_unsolicitedMembers != null) { 680 | for (m in _unsolicitedMembers) { 681 | m.sprite.x = m.originalX + this.cachedScreenX; 682 | m.sprite.y = m.originalY + this.cachedScreenY; 683 | } 684 | } 685 | } 686 | 687 | private function isUnsolicitedMember(m:FlxSprite) { 688 | if (m == _surface) { 689 | return false; 690 | } 691 | 692 | if (_textDisplay != null && m == _textDisplay.tf) { 693 | return false; 694 | } 695 | 696 | if (m == _imageDisplay) { 697 | return false; 698 | } 699 | 700 | return !(m is Component); 701 | } 702 | 703 | private function hasComponentOver(ref:Component, x:Float, y:Float):Bool { 704 | var array:Array = getComponentsAtPoint(x, y); 705 | if (array.length == 0) { 706 | return false; 707 | } 708 | 709 | return !hasChildRecursive(cast ref, cast array[array.length - 1]); 710 | } 711 | 712 | private function getComponentsAtPoint(x:Float, y:Float, reverse:Bool = false):Array { 713 | var array:Array = new Array(); 714 | for (r in Screen.instance.rootComponents) { 715 | findChildrenAtPoint(r, x, y, array); 716 | } 717 | 718 | if (reverse == true) { 719 | array.reverse(); 720 | } 721 | 722 | return array; 723 | } 724 | 725 | private function findChildrenAtPoint(child:Component, x:Float, y:Float, array:Array) { 726 | if (child.inBounds(x, y) == true) { 727 | array.push(child); 728 | } 729 | for (c in child.childComponents) { 730 | findChildrenAtPoint(c, x, y, array); 731 | } 732 | } 733 | 734 | public function hasChildRecursive(parent:Component, child:Component):Bool { 735 | if (parent == child) { 736 | return true; 737 | } 738 | var r = false; 739 | for (t in parent.childComponents) { 740 | if (t == child) { 741 | r = true; 742 | break; 743 | } 744 | 745 | r = hasChildRecursive(t, child); 746 | if (r == true) { 747 | break; 748 | } 749 | } 750 | 751 | return r; 752 | } 753 | 754 | //*********************************************************************************************************** 755 | // Flixel overrides 756 | //*********************************************************************************************************** 757 | 758 | private var _updates:Float = 0; 759 | public override function update(elapsed:Float) { 760 | if (_destroyed) { 761 | super.update(elapsed); 762 | return; 763 | } 764 | if (_destroy == true) { 765 | clearCaches(); 766 | destroyInternal(); 767 | super.update(elapsed); 768 | return; 769 | } 770 | 771 | clearCaches(); 772 | repositionChildren(); 773 | 774 | _updates++; 775 | if (_updates == 2) { 776 | if (asComponent.hidden == false) { 777 | applyVisibility(true); 778 | } else { 779 | applyVisibility(false); 780 | } 781 | } 782 | 783 | super.update(elapsed); 784 | 785 | // only the root component will start the applyClipRect() chain 786 | if (parentComponent == null) { 787 | // this needs to be called after super.update() so the children's positions 788 | // get updated before clipping them 789 | applyClipRect(); 790 | } 791 | } 792 | 793 | private function applyClipRect() { 794 | if (componentClipRect != null) { 795 | clipRect = getFlixelClipRect(clipRect); 796 | } 797 | 798 | for (c in childComponents) { 799 | c.applyClipRect(); 800 | } 801 | } 802 | 803 | private function getFlixelClipRect(?rect:FlxRect):FlxRect { 804 | var value = componentClipRect; 805 | if (value == null) { 806 | return null; 807 | } 808 | 809 | value.top = Std.int(value.top); 810 | value.left = Std.int(value.left); 811 | 812 | if (rect == null) { 813 | rect = FlxRect.get(); 814 | } 815 | rect.set((value.left * Toolkit.scaleX) + _surface.x - x, 816 | (value.top * Toolkit.scaleY) + _surface.y - y, 817 | (value.width * Toolkit.scaleX), (value.height * Toolkit.scaleY)); 818 | 819 | // find the nearest parent with a clip rect so we can intersect it with 820 | // the one from this component 821 | var p = parentComponent; 822 | while (p != null) { 823 | if (p.componentClipRect != null) { 824 | var pRect = p.getFlixelClipRect(); 825 | var oldRect = rect; 826 | rect = rect.intersection(pRect); 827 | pRect.put(); 828 | oldRect.put(); 829 | break; 830 | } 831 | p = p.parentComponent; 832 | } 833 | 834 | return rect; 835 | } 836 | 837 | // these functions (applyAddInternal / applyRemoveInternal) are called when a component is added / removed 838 | // from the screen - the main (only) reason this is important is because of textinputs: 839 | // textinputs are using openfl textfields, which means they are effectively floating over the top of the 840 | // application, this means that when things are removed from the screen (and not destroyed) it can leave them 841 | // behind 842 | private function applyAddInternal() { 843 | if (!TextInputImpl.USE_ON_ADDED) { 844 | return; 845 | } 846 | 847 | if (hasTextInput() && asComponent.hidden == false) { 848 | getTextInput().visible = true; 849 | } 850 | for (c in childComponents) { 851 | c.applyAddInternal(); 852 | } 853 | } 854 | 855 | private function applyRemoveInternal() { 856 | if (!TextInputImpl.USE_ON_REMOVED) { 857 | return; 858 | } 859 | 860 | if (hasTextInput()) { 861 | getTextInput().visible = false; 862 | } 863 | for (c in childComponents) { 864 | c.applyRemoveInternal(); 865 | } 866 | } 867 | 868 | private var _destroyed:Bool = false; 869 | private function destroyInternal() { 870 | if (_surface != null) { 871 | _surface.destroy(); 872 | _surface = null; 873 | } 874 | 875 | if (_textDisplay != null) { 876 | _textDisplay.tf.destroy(); 877 | _textDisplay = null; 878 | } 879 | 880 | if (_textInput != null) { 881 | _textInput.destroy(cast this); 882 | _textInput = null; 883 | } 884 | 885 | if (_imageDisplay != null) { 886 | _imageDisplay.destroy(); 887 | _imageDisplay = null; 888 | } 889 | 890 | if (_unsolicitedMembers != null) { 891 | _unsolicitedMembers = null; 892 | } 893 | 894 | _state = null; 895 | _destroy = false; 896 | _destroyed = true; 897 | super.destroy(); 898 | } 899 | 900 | public override function destroy():Void { 901 | if (parentComponent != null) { 902 | if (parentComponent.getComponentIndex(cast this) != -1) { 903 | parentComponent.removeComponent(cast this); 904 | return; 905 | } 906 | } else { 907 | Screen.instance.removeComponent(cast this); 908 | } 909 | 910 | _destroy = true; 911 | } 912 | 913 | private override function set_x(value:Float):Float { 914 | var r = super.set_x(value); 915 | if (this.parentComponent == null && _surface != null) { 916 | this.left = value; 917 | } 918 | return r; 919 | } 920 | 921 | private override function set_y(value:Float):Float { 922 | var r = super.set_y(value); 923 | if (this.parentComponent == null && _surface != null) { 924 | this.top = value; 925 | } 926 | return r; 927 | } 928 | } -------------------------------------------------------------------------------- /haxe/ui/backend/ComponentSurface.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | typedef ComponentSurface = flixel.group.FlxSpriteContainer; -------------------------------------------------------------------------------- /haxe/ui/backend/EventImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | class EventImpl extends EventBase { 4 | } -------------------------------------------------------------------------------- /haxe/ui/backend/FontData.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | typedef FontData = String; -------------------------------------------------------------------------------- /haxe/ui/backend/ImageData.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | //typedef ImageData = flixel.graphics.FlxGraphic; 4 | typedef ImageData = flixel.graphics.frames.FlxFrame; -------------------------------------------------------------------------------- /haxe/ui/backend/ImageDisplayImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.graphics.frames.FlxImageFrame; 4 | import flixel.math.FlxRect; 5 | import haxe.ui.Toolkit; 6 | 7 | class ImageDisplayImpl extends ImageBase { 8 | public function new() { 9 | super(); 10 | this.pixelPerfectRender = true; 11 | this.active = false; 12 | } 13 | 14 | private override function validateData():Void { 15 | if (_imageInfo != null) { 16 | frames = FlxImageFrame.fromFrame(_imageInfo.data); 17 | 18 | aspectRatio = _imageInfo.width / _imageInfo.height; 19 | 20 | origin.set(0, 0); 21 | } 22 | } 23 | 24 | private override function validateDisplay() { 25 | var scaleX:Float = _imageWidth / (_imageInfo.width / Toolkit.scaleX); 26 | var scaleY:Float = _imageHeight / (_imageInfo.height / Toolkit.scaleY); 27 | scale.set(scaleX, scaleY); 28 | 29 | width = Math.abs(scaleX) * frameWidth; 30 | height = Math.abs(scaleY) * frameHeight; 31 | } 32 | 33 | override function set_clipRect(rect:FlxRect):FlxRect { 34 | if (rect != null) { 35 | return super.set_clipRect(FlxRect.get(rect.x / scale.x, rect.y / scale.y, rect.width / scale.x, rect.height / scale.y)); 36 | } else { 37 | return super.set_clipRect(null); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /haxe/ui/backend/ImageSurface.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | typedef ImageSurface = flixel.FlxSprite; -------------------------------------------------------------------------------- /haxe/ui/backend/OpenFileDialogImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.containers.dialogs.Dialogs.SelectedFileInfo; 4 | 5 | using StringTools; 6 | #if !js 7 | import haxe.io.Bytes; 8 | import haxe.ui.containers.dialogs.Dialog.DialogButton; 9 | import openfl.events.Event; 10 | import openfl.net.FileFilter; 11 | import openfl.net.FileReference; 12 | import openfl.net.FileReferenceList; 13 | #end 14 | 15 | 16 | class OpenFileDialogImpl extends OpenFileDialogBase { 17 | #if js 18 | 19 | private var _fileSelector:haxe.ui.util.html5.FileSelector = new haxe.ui.util.html5.FileSelector(); 20 | 21 | public override function show() { 22 | var readMode = haxe.ui.util.html5.FileSelector.ReadMode.None; 23 | if (options.readContents == true) { 24 | if (options.readAsBinary == false) { 25 | readMode = haxe.ui.util.html5.FileSelector.ReadMode.Text; 26 | } else { 27 | readMode = haxe.ui.util.html5.FileSelector.ReadMode.Binary; 28 | } 29 | } 30 | _fileSelector.selectFile(onFileSelected, readMode, options.multiple, options.extensions); 31 | } 32 | 33 | private function onFileSelected(cancelled:Bool, files:Array) { 34 | if (cancelled == false) { 35 | dialogConfirmed(files); 36 | } else { 37 | dialogCancelled(); 38 | } 39 | } 40 | 41 | #else 42 | 43 | private var _fr:FileReferenceList = null; 44 | private var _refToInfo:Map; 45 | private var _infos:Array; 46 | 47 | public override function show() { 48 | _refToInfo = new Map(); 49 | _infos = []; 50 | _fr = new FileReferenceList(); 51 | _fr.addEventListener(Event.SELECT, onSelect, false, 0, true); 52 | _fr.addEventListener(Event.CANCEL, onCancel, false, 0, true); 53 | _fr.browse(buildFileFilters()); 54 | } 55 | 56 | private function buildFileFilters():Array { 57 | var f = null; 58 | 59 | if (options.extensions != null && options.extensions.length > 0) { 60 | f = []; 61 | for (e in options.extensions) { 62 | var ext = e.extension; 63 | ext = ext.trim(); 64 | if (ext.length == 0) { 65 | continue; 66 | } 67 | var parts = ext.split(","); 68 | var finalParts = []; 69 | for (p in parts) { 70 | p = p.trim(); 71 | if (p.length == 0) { 72 | continue; 73 | } 74 | finalParts.push("*." + p); 75 | } 76 | 77 | f.push(new FileFilter(e.label, finalParts.join(";"))); 78 | } 79 | } 80 | 81 | return f; 82 | } 83 | 84 | private function onSelect(e:Event) { 85 | var fileList:Array = _fr.fileList; 86 | destroyFileRef(); 87 | var infos:Array = []; 88 | for (fileRef in fileList) { 89 | 90 | var fullPath:String = null; 91 | #if sys 92 | fullPath = @:privateAccess fileRef.__path; 93 | #end 94 | 95 | var info:SelectedFileInfo = { 96 | isBinary: false, 97 | name: fileRef.name, 98 | fullPath: fullPath 99 | } 100 | if (options.readContents == true) { 101 | _refToInfo.set(fileRef, info); 102 | } 103 | infos.push(info); 104 | } 105 | 106 | if (options.readContents == false) { 107 | dialogConfirmed(infos); 108 | } else { 109 | for (fileRef in _refToInfo.keys()) { 110 | fileRef.addEventListener(Event.COMPLETE, onFileComplete, false, 0, true); 111 | fileRef.load(); 112 | } 113 | } 114 | 115 | } 116 | 117 | private function onFileComplete(e:Event) { 118 | var fileRef = cast(e.target, FileReference); 119 | fileRef.removeEventListener(Event.COMPLETE, onFileComplete); 120 | var info = _refToInfo.get(fileRef); 121 | if (options.readAsBinary == true) { 122 | info.isBinary = true; 123 | info.bytes = Bytes.ofData(fileRef.data); 124 | } else { 125 | info.isBinary = false; 126 | info.text = fileRef.data.toString(); 127 | } 128 | 129 | _infos.push(info); 130 | _refToInfo.remove(fileRef); 131 | if (isMapEmpty()) { 132 | var copy = _infos.copy(); 133 | _infos = null; 134 | _refToInfo = null; 135 | dialogConfirmed(copy); 136 | } 137 | } 138 | 139 | private function isMapEmpty() { 140 | if (_refToInfo == null) { 141 | return true; 142 | } 143 | 144 | var n = 0; 145 | for (_ in _refToInfo.keys()) { 146 | n++; 147 | } 148 | 149 | return (n == 0); 150 | } 151 | 152 | private function onCancel(e:Event) { 153 | destroyFileRef(); 154 | dialogCancelled(); 155 | } 156 | 157 | private function destroyFileRef() { 158 | if (_fr == null) { 159 | return; 160 | } 161 | 162 | _fr.removeEventListener(Event.SELECT, onSelect); 163 | _fr.removeEventListener(Event.CANCEL, onCancel); 164 | _fr = null; 165 | } 166 | 167 | #end 168 | } -------------------------------------------------------------------------------- /haxe/ui/backend/PlatformImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import openfl.system.Capabilities; 4 | 5 | class PlatformImpl extends PlatformBase { 6 | public override function getSystemLocale():String { 7 | return Capabilities.language; 8 | } 9 | } -------------------------------------------------------------------------------- /haxe/ui/backend/SaveFileDialogImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | #if !js 4 | import openfl.events.Event; 5 | import openfl.net.FileFilter; 6 | import openfl.net.FileReference; 7 | import openfl.utils.ByteArray; 8 | #end 9 | 10 | class SaveFileDialogImpl extends SaveFileDialogBase { 11 | #if js 12 | 13 | private var _fileSaver:haxe.ui.util.html5.FileSaver = new haxe.ui.util.html5.FileSaver(); 14 | 15 | public override function show() { 16 | if (fileInfo == null || (fileInfo.text == null && fileInfo.bytes == null)) { 17 | throw "Nothing to write"; 18 | } 19 | 20 | if (fileInfo.text != null) { 21 | _fileSaver.saveText(fileInfo.name, fileInfo.text, onSaveResult); 22 | } else if (fileInfo.bytes != null) { 23 | _fileSaver.saveBinary(fileInfo.name, fileInfo.bytes, onSaveResult); 24 | } 25 | } 26 | 27 | private function onSaveResult(r:Bool) { 28 | if (r == true) { 29 | dialogConfirmed(); 30 | } else { 31 | dialogCancelled(); 32 | } 33 | } 34 | 35 | #else 36 | 37 | private var _fr:FileReference = null; 38 | 39 | public override function show() { 40 | if (fileInfo == null || (fileInfo.text == null && fileInfo.bytes == null)) { 41 | throw "Nothing to write"; 42 | } 43 | 44 | var data:Dynamic = fileInfo.text; 45 | if (data == null) { 46 | data = ByteArray.fromBytes(fileInfo.bytes); 47 | } 48 | _fr = new FileReference(); 49 | _fr.addEventListener(Event.SELECT, onSelect, false, 0, true); 50 | _fr.addEventListener(Event.CANCEL, onCancel, false, 0, true); 51 | _fr.save(data, fileInfo.name); 52 | } 53 | 54 | private function onSelect(e:Event) { 55 | destroyFileRef(); 56 | dialogConfirmed(); 57 | } 58 | 59 | private function onCancel(e:Event) { 60 | destroyFileRef(); 61 | dialogCancelled(); 62 | } 63 | 64 | private function destroyFileRef() { 65 | if (_fr == null) { 66 | return; 67 | } 68 | 69 | _fr.removeEventListener(Event.SELECT, onSelect); 70 | _fr.removeEventListener(Event.CANCEL, onCancel); 71 | _fr = null; 72 | } 73 | 74 | #end 75 | } 76 | -------------------------------------------------------------------------------- /haxe/ui/backend/ScreenImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.FlxBasic; 4 | import flixel.FlxG; 5 | import flixel.FlxSprite; 6 | import flixel.group.FlxGroup; 7 | import flixel.group.FlxSpriteGroup; 8 | import haxe.ui.Toolkit; 9 | import haxe.ui.backend.flixel.CursorHelper; 10 | import haxe.ui.backend.flixel.KeyboardHelper; 11 | import haxe.ui.backend.flixel.MouseHelper; 12 | import haxe.ui.backend.flixel.StateHelper; 13 | import haxe.ui.core.Component; 14 | import haxe.ui.core.Screen; 15 | import haxe.ui.events.KeyboardEvent; 16 | import haxe.ui.events.MouseEvent; 17 | import haxe.ui.events.UIEvent; 18 | import haxe.ui.tooltips.ToolTipManager; 19 | import lime.system.System; 20 | import openfl.Lib; 21 | 22 | @:access(haxe.ui.backend.ComponentImpl) 23 | class ScreenImpl extends ScreenBase { 24 | private var _mapping:MapVoid>; 25 | 26 | public function new() { 27 | _mapping = new MapVoid>(); 28 | 29 | MouseHelper.init(); 30 | KeyboardHelper.init(); 31 | 32 | FlxG.signals.postGameStart.add(onPostGameStart); 33 | FlxG.signals.preStateSwitch.add(onPreStateSwitch); 34 | FlxG.signals.postStateSwitch.add(onPostStateSwitch); 35 | FlxG.signals.preStateCreate.add(onPreStateCreate); 36 | onPostStateSwitch(); 37 | 38 | addResizeHandler(); 39 | } 40 | 41 | private function onPreStateCreate(state:flixel.FlxState) { 42 | if (!state.memberAdded.has(onMemberAdded)) { 43 | state.memberAdded.add(onMemberAdded); 44 | } 45 | checkMembers(state); 46 | if (!state.memberRemoved.has(onMemberRemoved)) { 47 | state.memberRemoved.add(onMemberRemoved); 48 | } 49 | } 50 | 51 | private function onPostGameStart() { 52 | onPostStateSwitch(); 53 | } 54 | 55 | private function onPreStateSwitch() { 56 | if (FlxG.game == null) { 57 | return; 58 | } 59 | ToolTipManager.instance.reset(); 60 | if (rootComponents != null) { 61 | while (rootComponents.length > 0) { 62 | var root = rootComponents[rootComponents.length - 1]; 63 | removeComponent(root); 64 | } 65 | } 66 | rootComponents = []; 67 | } 68 | 69 | private function onPostStateSwitch() { 70 | if (FlxG.game == null) { 71 | return; 72 | } 73 | 74 | if (!FlxG.state.subStateOpened.has(onMemberAdded)) { 75 | FlxG.state.subStateOpened.add(onMemberAdded); 76 | } 77 | if (!FlxG.state.memberAdded.has(onMemberAdded)) { 78 | FlxG.state.memberAdded.add(onMemberAdded); 79 | } 80 | checkMembers(FlxG.state); 81 | 82 | if (!FlxG.state.memberRemoved.has(onMemberRemoved)) { 83 | FlxG.state.memberRemoved.add(onMemberRemoved); 84 | } 85 | if (!FlxG.state.subStateClosed.has(onMemberRemoved)) { 86 | FlxG.state.subStateClosed.add(onMemberRemoved); 87 | } 88 | } 89 | 90 | private function onMemberAdded(m:FlxBasic) { 91 | if ((m is Component)) { 92 | var c = cast(m, Component); 93 | if (rootComponents.indexOf(c) == -1) { 94 | addComponent(c); 95 | } 96 | } else if ((m is FlxTypedGroup)) { 97 | var group:FlxTypedGroup = cast m; 98 | checkMembers(group); 99 | } 100 | } 101 | 102 | private function onMemberRemoved(m:FlxBasic) { 103 | if ((m is Component) && rootComponents.indexOf(cast(m, Component)) != -1) { 104 | @:privateAccess var isDisposed = cast(m, Component)._isDisposed; 105 | removeComponent(cast m, isDisposed); 106 | } 107 | } 108 | 109 | private function checkMembers(state:FlxTypedGroup) { 110 | if (state == null || !state.exists) { 111 | return false; 112 | } 113 | 114 | var found = false; // we only want top level components 115 | for (m in state.members) { 116 | if ((m is Component)) { 117 | var c = cast(m, Component); 118 | if (rootComponents.indexOf(c) == -1) { 119 | addComponent(c); 120 | found = true; 121 | } 122 | } else if ((m is FlxTypedGroup)) { 123 | var group:FlxTypedGroup = cast m; 124 | group.memberAdded.addOnce(onMemberAdded); 125 | if (checkMembers(group) == true) { 126 | found = true; 127 | break; 128 | } 129 | } else if ((m is FlxTypedSpriteGroup)) { 130 | var spriteGroup:FlxTypedSpriteGroup = cast m; 131 | spriteGroup.group.memberAdded.addOnce(onMemberAdded); 132 | if (checkMembers(cast spriteGroup.group) == true) { 133 | found = true; 134 | break; 135 | } 136 | } 137 | } 138 | return found; 139 | } 140 | 141 | private override function get_width():Float { 142 | return FlxG.width / Toolkit.scaleX; 143 | } 144 | 145 | private override function get_height() { 146 | return FlxG.height / Toolkit.scaleY; 147 | } 148 | 149 | private override function get_actualWidth():Float { 150 | return FlxG.width; 151 | } 152 | 153 | private override function get_actualHeight():Float { 154 | return FlxG.height; 155 | } 156 | 157 | private override function get_dpi():Float { 158 | return System.getDisplay(0).dpi; 159 | } 160 | 161 | private override function get_title():String { 162 | return Lib.current.stage.window.title; 163 | } 164 | 165 | private override function set_title(s:String):String { 166 | Lib.current.stage.window.title = s; 167 | return s; 168 | } 169 | 170 | private var _cursor:String = null; 171 | public function setCursor(cursor:String, offsetX:Null = null, offsetY:Null = null) { 172 | #if (haxeui_flixel_no_custom_cursors || FLX_NO_MOUSE) 173 | return; 174 | #else 175 | 176 | if (!CursorHelper.useCustomCursors) { 177 | return; 178 | } 179 | 180 | if (_cursor == cursor) { 181 | return; 182 | } 183 | 184 | _cursor = cursor; 185 | if (CursorHelper.hasCursor(_cursor)) { 186 | var cursorInfo = CursorHelper.registeredCursors.get(_cursor); 187 | FlxG.mouse.load(CursorHelper.mouseLoadFunction(cursorInfo.graphic), cursorInfo.scale, cursorInfo.offsetX, cursorInfo.offsetY); 188 | } else if (openfl.Assets.exists(_cursor)) { 189 | FlxG.mouse.load(CursorHelper.mouseLoadFunction(_cursor), 1, offsetX, offsetY); 190 | } else { 191 | FlxG.mouse.load(null); 192 | } 193 | #end 194 | } 195 | 196 | public override function addComponent(component:Component):Component { 197 | if (rootComponents.contains(component) && StateHelper.currentState.members.contains(component)) { 198 | return component; 199 | } 200 | 201 | if (rootComponents.length > 0) { 202 | var cameras = StateHelper.findCameras(rootComponents[0]); 203 | if (cameras != null) { 204 | component.cameras = cameras; 205 | } 206 | } 207 | 208 | if (StateHelper.currentState.exists == true) { 209 | if (rootComponents.indexOf(component) == -1) { 210 | rootComponents.push(component); 211 | } 212 | StateHelper.currentState.add(component); 213 | component.state = StateHelper.currentState; 214 | onContainerResize(); 215 | component.recursiveReady(); 216 | component.syncComponentValidation(); 217 | component.applyAddInternal(); 218 | checkResetCursor(); 219 | } 220 | return component; 221 | } 222 | 223 | public override function removeComponent(component:Component, dispose:Bool = true, invalidate:Bool = true):Component { 224 | if (@:privateAccess !component._allowDispose) { 225 | dispose = false; 226 | } 227 | if (rootComponents.indexOf(component) == -1) { 228 | if (dispose) { 229 | component.disposeComponent(); 230 | } 231 | return component; 232 | } 233 | rootComponents.remove(component); 234 | if (rootComponents.indexOf(component) != -1) { 235 | throw "component wasnt actually removed from array, or there is a duplicate in the array"; 236 | } 237 | if (StateHelper.currentState.exists == true) { 238 | StateHelper.currentState.remove(component, true); 239 | } 240 | // Destroying a sprite makes it get removed from its container (in this case, the state) without 241 | // being spliced, causing issues later with `addComponent()` not actually adding components on top 242 | // of everything else, so this has to go after the component has been properly removed. 243 | if (dispose) { 244 | component.disposeComponent(); 245 | } else { 246 | component.applyRemoveInternal(); 247 | } 248 | checkResetCursor(); 249 | onContainerResize(); 250 | return component; 251 | } 252 | 253 | private var _resizeHandlerAdded:Bool = false; 254 | private function addResizeHandler() { 255 | if (_resizeHandlerAdded == true) { 256 | return; 257 | } 258 | _resizeHandlerAdded = true; 259 | FlxG.signals.gameResized.add(onGameResized); 260 | } 261 | 262 | private function onGameResized(width:Int, height:Int) { 263 | onContainerResize(); 264 | } 265 | 266 | private function onContainerResize() { 267 | for (c in rootComponents) { 268 | if (c.percentWidth > 0) { 269 | c.width = (this.width * c.percentWidth) / 100; 270 | } 271 | if (c.percentHeight > 0) { 272 | c.height = (this.height * c.percentHeight) / 100; 273 | } 274 | } 275 | } 276 | 277 | private override function handleSetComponentIndex(child:Component, index:Int) { 278 | var offset = 0; 279 | StateHelper.currentState.forEach((item) -> { 280 | offset++; 281 | }); 282 | /* see: https://github.com/haxeui/haxeui-flixel/issues/61 283 | for (i in 0...StateHelper.currentState.length) { 284 | if ((StateHelper.currentState.members[i] is Component)) { 285 | offset = i; 286 | break; 287 | } 288 | } 289 | */ 290 | 291 | StateHelper.currentState.remove(child, true); 292 | StateHelper.currentState.insert(index + offset, child); 293 | } 294 | 295 | private override function supportsEvent(type:String):Bool { 296 | if (type == MouseEvent.MOUSE_MOVE 297 | || type == MouseEvent.MOUSE_OVER 298 | || type == MouseEvent.MOUSE_OUT 299 | || type == MouseEvent.MOUSE_DOWN 300 | || type == MouseEvent.MOUSE_UP 301 | || type == MouseEvent.MOUSE_WHEEL 302 | || type == MouseEvent.CLICK 303 | || type == MouseEvent.DBL_CLICK 304 | || type == MouseEvent.RIGHT_CLICK 305 | || type == MouseEvent.RIGHT_MOUSE_DOWN 306 | || type == MouseEvent.RIGHT_MOUSE_UP 307 | || type == MouseEvent.MIDDLE_CLICK 308 | || type == MouseEvent.MIDDLE_MOUSE_DOWN 309 | || type == MouseEvent.MIDDLE_MOUSE_UP 310 | || type == UIEvent.RESIZE 311 | || type == KeyboardEvent.KEY_DOWN 312 | || type == KeyboardEvent.KEY_UP) { 313 | return true; 314 | } 315 | return false; 316 | } 317 | 318 | private override function mapEvent(type:String, listener:UIEvent->Void) { 319 | switch (type) { 320 | case MouseEvent.MOUSE_MOVE | MouseEvent.MOUSE_OVER | MouseEvent.MOUSE_OUT | MouseEvent.MOUSE_DOWN | MouseEvent.MOUSE_UP | MouseEvent.MOUSE_WHEEL | MouseEvent.CLICK | MouseEvent.DBL_CLICK | MouseEvent.RIGHT_CLICK | MouseEvent.RIGHT_MOUSE_DOWN | MouseEvent.RIGHT_MOUSE_UP | MouseEvent.MIDDLE_CLICK | MouseEvent.MIDDLE_MOUSE_DOWN | MouseEvent.MIDDLE_MOUSE_UP: 321 | if (_mapping.exists(type) == false) { 322 | _mapping.set(type, listener); 323 | MouseHelper.notify(type, __onMouseEvent, 10); 324 | } 325 | 326 | case KeyboardEvent.KEY_DOWN | KeyboardEvent.KEY_UP: 327 | if (_mapping.exists(type) == false) { 328 | _mapping.set(type, listener); 329 | KeyboardHelper.notify(type, __onKeyEvent, 10); 330 | } 331 | } 332 | } 333 | 334 | private function __onMouseEvent(event:MouseEvent) { 335 | var fn = _mapping.get(event.type); 336 | if (fn != null) { 337 | fn(event); 338 | } 339 | } 340 | 341 | private function __onKeyEvent(event:KeyboardEvent) { 342 | var fn = _mapping.get(event.type); 343 | if (fn != null) { 344 | fn(event); 345 | } 346 | } 347 | 348 | private function containsUnsolicitedMemberAt(x:Float, y:Float, state:FlxTypedGroup):Bool { 349 | if (state == null || !state.exists) { 350 | return false; 351 | } 352 | 353 | for (m in state.members) { 354 | if ((m is Component)) { 355 | var c:Component = cast m; 356 | if (c.hidden) { 357 | continue; 358 | } 359 | if (!c.hitTest(x, y, true)) { 360 | continue; 361 | } 362 | if (c._unsolicitedMembers != null && c._unsolicitedMembers.length > 0) { 363 | for (um in c._unsolicitedMembers) { 364 | var umx = um.sprite.x; 365 | var umy = um.sprite.y; 366 | var umw = um.sprite.width; 367 | var umh = um.sprite.height; 368 | if (x >= umx && y >= umy && x <= umx + umw && y <= umy + umh) { 369 | return true; 370 | } 371 | } 372 | } 373 | var spriteGroup:FlxTypedSpriteGroup = cast m; 374 | if (containsUnsolicitedMemberAt(x, y, cast spriteGroup.group) == true) { 375 | return true; 376 | } 377 | } else if ((m is FlxTypedSpriteGroup)) { 378 | var spriteGroup:FlxTypedSpriteGroup = cast m; 379 | if (!spriteGroup.visible) { 380 | continue; 381 | } 382 | if (containsUnsolicitedMemberAt(x, y, cast spriteGroup.group) == true) { 383 | return true; 384 | } 385 | } else if ((m is FlxSprite)) { 386 | var sprite:FlxSprite = cast m; 387 | var umx = sprite.x; 388 | var umy = sprite.y; 389 | var umw = sprite.width; 390 | var umh = sprite.height; 391 | if (x >= umx && y >= umy && x <= umx + umw && y <= umy + umh) { 392 | return true; 393 | } 394 | } 395 | } 396 | 397 | return false; 398 | } 399 | 400 | @:allow(haxe.ui.backend.flixel.MouseHelper) 401 | private function checkResetCursor(x:Null = null, y:Null = null) { 402 | if (x == null) { 403 | x = MouseHelper.currentWorldX; 404 | } 405 | if (y == null) { 406 | y = MouseHelper.currentWorldY; 407 | } 408 | var components = Screen.instance.findComponentsUnderPoint(x, y); 409 | components.reverse(); 410 | var desiredCursor = "default"; 411 | var desiredCursorOffsetX:Null = null; 412 | var desiredCursorOffsetY:Null = null; 413 | for (c in components) { 414 | if (c.style == null) { 415 | c.validateNow(); 416 | } 417 | if (c.style.cursor != null) { 418 | desiredCursor = c.style.cursor; 419 | desiredCursorOffsetX = c.style.cursorOffsetX; 420 | desiredCursorOffsetX = c.style.cursorOffsetY; 421 | break; 422 | } 423 | } 424 | 425 | setCursor(desiredCursor, desiredCursorOffsetX, desiredCursorOffsetY); 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /haxe/ui/backend/TextDisplayImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import flixel.text.FlxText; 4 | import haxe.ui.Toolkit; 5 | import haxe.ui.util.Color; 6 | 7 | class TextDisplayImpl extends TextBase { 8 | private static inline var PADDING_X:Int = 2; 9 | 10 | public var tf:FlxText; 11 | 12 | public function new() { 13 | super(); 14 | tf = new FlxText(); 15 | tf.pixelPerfectRender = true; 16 | tf.autoSize = true; 17 | tf.active = false; 18 | } 19 | 20 | private override function validateData() { 21 | if (_text != null) { 22 | if (_dataSource == null) { 23 | tf.text = normalizeText(_text); 24 | } 25 | } else if (_htmlText != null) { 26 | var rules = []; 27 | var outText = processTags(_htmlText, rules); 28 | if (rules.length > 0) { 29 | tf.applyMarkup(outText, rules); 30 | } else { 31 | tf.text = normalizeText(_htmlText); 32 | } 33 | } 34 | } 35 | 36 | private override function validateStyle():Bool { 37 | var measureTextRequired:Bool = false; 38 | if (_textStyle != null) { 39 | if (_textStyle.textAlign != null) { 40 | //tf.autoSize = false; 41 | tf.alignment = _textStyle.textAlign; 42 | measureTextRequired = true; 43 | } 44 | 45 | if (_textStyle.fontSize != null) { 46 | tf.size = Std.int(_textStyle.fontSize * Toolkit.scale); 47 | measureTextRequired = true; 48 | } 49 | 50 | if (_fontInfo != null) { 51 | tf.font = _fontInfo.data; 52 | measureTextRequired = true; 53 | } 54 | 55 | if (_textStyle.fontBold != null) { 56 | tf.bold = _textStyle.fontBold; 57 | measureTextRequired = true; 58 | } 59 | if (_textStyle.fontItalic != null) { 60 | tf.italic = _textStyle.fontItalic; 61 | measureTextRequired = true; 62 | } 63 | 64 | if (_textStyle.color != null) { 65 | tf.color = _textStyle.color; 66 | } 67 | 68 | if (tf.wordWrap != _displayData.wordWrap) { 69 | tf.wordWrap = _displayData.wordWrap; 70 | //tf.autoSize = !_displayData.wordWrap; 71 | measureTextRequired = true; 72 | } 73 | 74 | if (tf.textField.multiline != _displayData.multiline) { 75 | tf.textField.multiline = _displayData.multiline; 76 | //tf.autoSize = !_displayData.multiline; 77 | measureTextRequired = true; 78 | } 79 | } 80 | 81 | return measureTextRequired; 82 | } 83 | 84 | private override function validateDisplay() { 85 | if (tf.textField.width != _width) { 86 | var width = _width * Toolkit.scaleX; 87 | tf.textField.width = (width >= 1 ? width : 1); 88 | } 89 | if (tf.textField.height != _height) { 90 | var height = _height * Toolkit.scaleY; 91 | tf.textField.height = (height >= 1 ? height : 1); 92 | } 93 | } 94 | 95 | private override function measureText() { 96 | _textWidth = (Math.fround(tf.textField.textWidth) + (PADDING_X * Toolkit.scaleX)) / Toolkit.scaleX; 97 | _textHeight = Math.fround(tf.textField.textHeight) / Toolkit.scaleY; 98 | if (_textHeight == 0) { 99 | var tmpText:String = tf.text; 100 | tf.text = "|"; 101 | _textHeight = tf.textField.textHeight; 102 | tf.text = tmpText; 103 | } 104 | } 105 | 106 | private function normalizeText(text:String):String { 107 | text = StringTools.replace(text, "\\n", "\n"); 108 | return text; 109 | } 110 | 111 | private override function get_supportsHtml():Bool { 112 | return true; 113 | } 114 | 115 | private static function processTags(s:String, rules:Array) { 116 | var inTag:Bool = false; 117 | var endTag:Bool = false; 118 | var tagDetails = ""; 119 | var out = ""; 120 | 121 | var tagStack:Array = []; 122 | var colorMap:Map = new Map(); 123 | 124 | for (i in 0...s.length) { 125 | var c = s.charAt(i); 126 | switch (c) { 127 | case "<": 128 | var temp = s.substring(i + 1, i + 6); 129 | if (temp == "font " || temp == "/font") { // bit hacky! 130 | inTag = true; 131 | endTag = false; 132 | tagDetails = ""; 133 | } 134 | case "/": 135 | if (inTag == true) { 136 | endTag = true; 137 | } 138 | case ">": 139 | if (inTag == true) { 140 | if (endTag == false) { 141 | var n = tagDetails.indexOf("color="); 142 | if (n != -1) { 143 | var col = tagDetails.substring(n + "color=".length); 144 | col = StringTools.replace(col, "'", ""); 145 | col = StringTools.replace(col, "\"", ""); 146 | tagStack.push(col); 147 | out += "<" + col + ">"; 148 | colorMap.set("<" + col + ">", Color.fromString(col)); 149 | } else { 150 | tagStack.push(tagDetails); 151 | out += tagDetails; 152 | } 153 | 154 | } else { 155 | var startTagDetails = tagStack.pop(); 156 | out += "<" + startTagDetails + ">"; 157 | } 158 | inTag = false; 159 | } 160 | default: 161 | if (inTag == true) { 162 | tagDetails += c.toLowerCase(); 163 | } else { 164 | out += c; 165 | } 166 | } 167 | } 168 | 169 | for (k in colorMap.keys()) { 170 | rules.push(new FlxTextFormatMarkerPair(new FlxTextFormat(colorMap.get(k)), k)); 171 | } 172 | 173 | return out; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /haxe/ui/backend/TextInputImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | typedef TextInputEvent = {type:String, stageX:Float, stageY:Float}; 4 | 5 | #if (flixel >= "5.9.0") 6 | typedef TextInputImpl = haxe.ui.backend.flixel.textinputs.FlxTextInput; 7 | #else 8 | typedef TextInputImpl = haxe.ui.backend.flixel.textinputs.OpenFLTextInput; 9 | #end 10 | -------------------------------------------------------------------------------- /haxe/ui/backend/TimerImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import openfl.events.Event; 4 | import openfl.Lib; 5 | import haxe.Timer; 6 | 7 | class TimerImpl { 8 | static private var __timers:Array = []; 9 | 10 | static public function update(e:Event) { 11 | var currentTime:Float = Timer.stamp(); 12 | var count:Int = __timers.length; 13 | for (i in 0...count) { 14 | var timer:TimerImpl = __timers[i]; 15 | if (timer._start <= currentTime && !timer._stopped) { 16 | timer._start = currentTime + (timer._delay / 1000); 17 | timer._callback(); 18 | } 19 | } 20 | 21 | while (--count >= 0) { 22 | var timer:TimerImpl = __timers[count]; 23 | if (timer._stopped) { 24 | timer._callback = null; 25 | __timers.remove(timer); 26 | } 27 | } 28 | 29 | if (__timers.length == 0) { 30 | Lib.current.stage.removeEventListener(Event.ENTER_FRAME, update); 31 | } 32 | } 33 | 34 | private var _callback:Void->Void; 35 | private var _start:Float; 36 | private var _stopped:Bool; 37 | private var _delay:Int; 38 | 39 | public function new(delay:Int, callback:Void->Void) { 40 | this._callback = callback; 41 | _delay = delay; 42 | _start = Timer.stamp() + (delay / 1000); 43 | __timers.push(this); 44 | if (__timers.length == 1) { 45 | Lib.current.stage.addEventListener(Event.ENTER_FRAME, update); 46 | } 47 | } 48 | 49 | public function stop() { 50 | _stopped = true; 51 | } 52 | } -------------------------------------------------------------------------------- /haxe/ui/backend/ToolkitOptions.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | typedef ToolkitOptions = { 4 | } 5 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/CursorHelper.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import openfl.display.BitmapData; 4 | 5 | class CursorHelper { 6 | public static var useCustomCursors:Bool = true; 7 | public static var registeredCursors:Map = new Map(); 8 | 9 | public static var mouseLoadFunction(default, set):String->BitmapData = (bmd) -> openfl.Assets.getBitmapData(bmd); 10 | static function set_mouseLoadFunction(v:String->BitmapData):String->BitmapData { 11 | if (v == null) 12 | return mouseLoadFunction; 13 | 14 | return mouseLoadFunction = v; 15 | } 16 | 17 | public static function registerCursor(name:String, graphic:String, scale:Float = 1, offsetX:Int = 0, offsetY:Int = 0) { 18 | registeredCursors.set(name, { 19 | name: name, 20 | graphic: graphic, 21 | scale: scale, 22 | offsetX: offsetX, 23 | offsetY: offsetY, 24 | }); 25 | } 26 | 27 | public static function hasCursor(name:String):Bool { 28 | #if haxeui_flixel_no_custom_cursors 29 | return false; 30 | #end 31 | 32 | if (!useCustomCursors) { 33 | return false; 34 | } 35 | preregisterCursors(); 36 | return registeredCursors.exists(name); 37 | } 38 | 39 | private static var _cursorsPreregistered:Bool = false; 40 | private static function preregisterCursors() { 41 | if (_cursorsPreregistered) { 42 | return; 43 | } 44 | 45 | _cursorsPreregistered = true; 46 | 47 | if (!registeredCursors.exists("pointer")) { 48 | registerCursor("pointer", "haxeui-flixel/styles/default/cursors/pointer.png", 1, -10, -1); 49 | } 50 | } 51 | } 52 | 53 | private typedef CursorInfo = { 54 | var name:String; 55 | var graphic:String; 56 | var scale:Float; 57 | var offsetX:Int; 58 | var offsetY:Int; 59 | } 60 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/FlxHaxeUIAppState.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import flixel.FlxG; 4 | import flixel.FlxState; 5 | 6 | class FlxHaxeUIAppState extends FlxState { 7 | public override function create() { 8 | if (Toolkit.backendProperties.getProp("haxe.ui.flixel.scaleMode") == "stage") { 9 | FlxG.scaleMode = new flixel.system.scaleModes.StageSizeScaleMode(); 10 | } 11 | 12 | if (Toolkit.backendProperties.exists("haxe.ui.flixel.mouse.useSystemCursor")) { 13 | #if !FLX_NO_MOUSE 14 | FlxG.mouse.useSystemCursor = Toolkit.backendProperties.getPropBool("haxe.ui.flixel.mouse.useSystemCursor"); 15 | #end 16 | } 17 | 18 | super.create(); 19 | 20 | if (Toolkit.backendProperties.exists("haxe.ui.flixel.background.color")) { 21 | this.bgColor = Toolkit.backendProperties.getPropCol("haxe.ui.flixel.background.color") | 0xFF000000; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/FlxStyleHelper.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import flixel.FlxSprite; 4 | import flixel.graphics.frames.FlxFrame.FlxFrameAngle; 5 | import flixel.util.FlxColor; 6 | import flixel.util.FlxSpriteUtil; 7 | import haxe.ui.Toolkit; 8 | import haxe.ui.assets.ImageInfo; 9 | import haxe.ui.geom.Slice9; 10 | import haxe.ui.styles.Style; 11 | import haxe.ui.util.ColorUtil; 12 | import openfl.display.BitmapData; 13 | import openfl.geom.Matrix; 14 | import openfl.geom.Point; 15 | import openfl.geom.Rectangle; 16 | 17 | class FlxStyleHelper { 18 | public static function applyStyle(sprite:FlxSprite, style:Style):Bool { 19 | if (sprite == null || sprite.pixels == null) { 20 | return false; 21 | } 22 | 23 | var pixels:BitmapData = sprite.pixels; 24 | 25 | var left:Float = 0; 26 | var top:Float = 0; 27 | var width:Float = sprite.frameWidth; 28 | var height:Float = sprite.frameHeight; 29 | 30 | if (width <= 0 || height <= 0) { 31 | return false; 32 | } 33 | 34 | var rc:Rectangle = new Rectangle(top, left, width, height); 35 | pixels.fillRect(rc, 0x0); 36 | 37 | #if !no_openfl_drawing 38 | var useOpenFLDrawing = false; 39 | if (width > 1 && height > 1 && style.backgroundImage == null) { 40 | if ((style.borderRadius != null && (style.borderRadius > 0)) 41 | || (style.borderRadiusTopLeft != null && (style.borderRadiusTopLeft > 0)) 42 | || (style.borderRadiusTopRight != null && (style.borderRadiusTopRight > 0)) 43 | || (style.borderRadiusBottomLeft != null && (style.borderRadiusBottomLeft > 0)) 44 | || (style.borderRadiusBottomRight != null && (style.borderRadiusBottomRight > 0)) 45 | ) { 46 | useOpenFLDrawing = true; 47 | } 48 | } 49 | 50 | if (useOpenFLDrawing) { 51 | var g = FlxSpriteUtil.flashGfx; 52 | var painted = OpenFLStyleHelper.paintStyleSection(g, style, width, height, left, top); 53 | FlxSpriteUtil.updateSpriteGraphic(sprite); 54 | return painted; 55 | } 56 | #end 57 | 58 | var painted = false; 59 | 60 | if (style.borderLeftSize != null && style.borderLeftSize != 0 61 | && style.borderLeftSize == style.borderRightSize 62 | && style.borderLeftSize == style.borderBottomSize 63 | && style.borderLeftSize == style.borderTopSize 64 | 65 | && style.borderLeftColor != null 66 | && style.borderLeftColor == style.borderRightColor 67 | && style.borderLeftColor == style.borderBottomColor 68 | && style.borderLeftColor == style.borderTopColor) { 69 | var borderSize = style.borderLeftSize * Toolkit.scale; 70 | var opacity = style.borderOpacity == null ? 1 : style.borderOpacity; 71 | var color:FlxColor = Std.int(opacity * 0xFF) << 24 | style.borderLeftColor; 72 | 73 | pixels.fillRect(new Rectangle(rc.left, rc.top, rc.width, borderSize), color); // top 74 | pixels.fillRect(new Rectangle(rc.right - borderSize, rc.top + borderSize, borderSize, rc.height - (borderSize * 2)), color); // right 75 | pixels.fillRect(new Rectangle(rc.left, rc.height - borderSize, rc.width, borderSize), color); // bottom 76 | pixels.fillRect(new Rectangle(rc.left, rc.top + borderSize, borderSize, rc.height - (borderSize * 2)), color); // left 77 | rc.inflate(-borderSize, -borderSize); 78 | 79 | painted = true; 80 | } else { // compound border 81 | var org = rc.clone(); 82 | 83 | var borderTopSize:Float = 0; 84 | if (style.borderTopSize != null && style.borderTopSize > 0) { 85 | borderTopSize = style.borderTopSize * Toolkit.scale; 86 | } 87 | var borderLeftSize:Float = 0; 88 | if (style.borderLeftSize != null && style.borderLeftSize > 0) { 89 | borderLeftSize = style.borderLeftSize * Toolkit.scale; 90 | } 91 | var borderRightSize:Float = 0; 92 | if (style.borderRightSize != null && style.borderRightSize > 0) { 93 | borderRightSize = style.borderRightSize * Toolkit.scale; 94 | } 95 | 96 | if (style.borderTopSize != null && style.borderTopSize > 0) { 97 | var borderSize = style.borderTopSize * Toolkit.scale; 98 | var opacity = style.borderOpacity == null ? 1 : style.borderOpacity; 99 | var color:FlxColor = Std.int(opacity * 0xFF) << 24 | style.borderTopColor; 100 | pixels.fillRect(new Rectangle(rc.left + borderLeftSize, rc.top, org.width - (borderLeftSize + borderRightSize), borderSize), color); // top 101 | rc.top += borderSize; 102 | 103 | if (opacity > 0) { 104 | painted = true; 105 | } 106 | } 107 | 108 | if (style.borderBottomSize != null && style.borderBottomSize > 0) { 109 | var borderSize = style.borderBottomSize * Toolkit.scale; 110 | var opacity = style.borderOpacity == null ? 1 : style.borderOpacity; 111 | var color:FlxColor = Std.int(opacity * 0xFF) << 24 | style.borderBottomColor; 112 | pixels.fillRect(new Rectangle(rc.left, org.height - borderSize, rc.width, borderSize), color); // bottom 113 | rc.bottom -= borderSize; 114 | 115 | if (opacity > 0) { 116 | painted = true; 117 | } 118 | } 119 | 120 | if (style.borderLeftSize != null && style.borderLeftSize > 0) { 121 | var borderSize = style.borderLeftSize * Toolkit.scale; 122 | var opacity = style.borderOpacity == null ? 1 : style.borderOpacity; 123 | var color:FlxColor = Std.int(opacity * 0xFF) << 24 | style.borderLeftColor; 124 | pixels.fillRect(new Rectangle(rc.left, rc.top - borderTopSize, borderSize, org.height - rc.top + borderTopSize), color); // left 125 | rc.left += borderSize; 126 | 127 | if (opacity > 0) { 128 | painted = true; 129 | } 130 | } 131 | 132 | if (style.borderRightSize != null && style.borderRightSize > 0) { 133 | var borderSize = style.borderRightSize * Toolkit.scale; 134 | var opacity = style.borderOpacity == null ? 1 : style.borderOpacity; 135 | var color:FlxColor = Std.int(opacity * 0xFF) << 24 | style.borderRightColor; 136 | pixels.fillRect(new Rectangle(org.width - borderSize, rc.top - borderTopSize, borderSize, org.height + borderTopSize), color); // right 137 | rc.right -= borderSize; 138 | 139 | if (opacity > 0) { 140 | painted = true; 141 | } 142 | } 143 | } 144 | 145 | 146 | if (style.backgroundColor != null) { 147 | var opacity = style.backgroundOpacity == null ? 1 : style.backgroundOpacity; 148 | if (style.backgroundColorEnd != null && style.backgroundColor != style.backgroundColorEnd) { 149 | var gradientType:String = "vertical"; 150 | if (style.backgroundGradientStyle != null) { 151 | gradientType = style.backgroundGradientStyle; 152 | } 153 | 154 | var arr:Array = null; 155 | var n:Int = 0; 156 | var rcLine:Rectangle = new Rectangle(); 157 | if (gradientType == "vertical") { 158 | arr = ColorUtil.buildColorArray(style.backgroundColor, style.backgroundColorEnd, Std.int(rc.height)); 159 | for (c in arr) { 160 | rcLine.setTo(rc.left, rc.top + n, rc.width, 1); 161 | pixels.fillRect(rcLine, Std.int(opacity * 0xFF) << 24 | c); 162 | n++; 163 | } 164 | 165 | if (opacity > 0 && n > 0) { 166 | painted = true; 167 | } 168 | } else if (gradientType == "horizontal") { 169 | arr = ColorUtil.buildColorArray(style.backgroundColor, style.backgroundColorEnd, Std.int(rc.width)); 170 | for (c in arr) { 171 | rcLine.setTo(rc.left + n, rc.top, 1, rc.height); 172 | pixels.fillRect(rcLine, Std.int(opacity * 0xFF) << 24 | c); 173 | n++; 174 | } 175 | 176 | if (opacity > 0 && n > 0) { 177 | painted = true; 178 | } 179 | } 180 | } else { 181 | var color:FlxColor = Std.int(opacity * 0xFF) << 24 | style.backgroundColor; 182 | pixels.fillRect(rc, color); 183 | 184 | if (opacity > 0) { 185 | painted = true; 186 | } 187 | } 188 | } 189 | 190 | if (style.backgroundImage != null) { 191 | painted = true; 192 | Toolkit.assets.getImage(style.backgroundImage, function(info:ImageInfo) { 193 | if (info != null && info.data != null) { 194 | paintBackroundImage(sprite, info.data, style); 195 | } 196 | }); 197 | } 198 | 199 | return painted; 200 | } 201 | 202 | private static function paintBackroundImage(sprite:FlxSprite, data:ImageData, style:Style) { 203 | var bmd = data.parent.bitmap; 204 | var rect = data.frame.copyToFlash(); 205 | 206 | // if it's a spritesheet and the frame is rotated or flipped, paint the "original" sprite 207 | if (!bmd.rect.equals(rect) && (data.angle != FlxFrameAngle.ANGLE_0 || data.flipX || data.flipY)) { 208 | bmd = data.paintRotatedAndFlipped(); 209 | rect.setTo(0, 0, data.sourceSize.x, data.sourceSize.y); 210 | } 211 | 212 | if (style.backgroundImageClipTop != null && style.backgroundImageClipBottom != null && style.backgroundImageClipLeft != null && style.backgroundImageClipRight != null) { 213 | rect.x += style.backgroundImageClipLeft; 214 | rect.y += style.backgroundImageClipTop; 215 | rect.width = Math.min(rect.width, style.backgroundImageClipRight - style.backgroundImageClipLeft); 216 | rect.height = Math.min(rect.height, style.backgroundImageClipBottom - style.backgroundImageClipTop); 217 | } 218 | 219 | var slice:haxe.ui.geom.Rectangle = null; 220 | 221 | if (style.backgroundImageSliceTop != null && style.backgroundImageSliceBottom != null && style.backgroundImageSliceLeft != null && style.backgroundImageSliceRight != null) { 222 | slice = new haxe.ui.geom.Rectangle(style.backgroundImageSliceLeft, style.backgroundImageSliceTop, style.backgroundImageSliceRight - style.backgroundImageSliceLeft, style.backgroundImageSliceBottom - style.backgroundImageSliceTop); 223 | } 224 | 225 | if (slice == null) { 226 | 227 | var matrix:Matrix = null; 228 | 229 | if (style.backgroundImageRepeat == "stretch") { 230 | matrix = new Matrix(); 231 | matrix.translate( -rect.x, -rect.y); 232 | matrix.scale(sprite.frameWidth / rect.width, sprite.frameHeight / rect.height); 233 | } 234 | 235 | if (matrix == null) { 236 | 237 | var blitPt = new Point(); 238 | 239 | if (style.backgroundImageRepeat == null) { 240 | sprite.pixels.copyPixels(bmd, rect, blitPt); 241 | } 242 | 243 | else if (style.backgroundImageRepeat == "repeat") { 244 | 245 | var repX = Math.ceil(sprite.frameWidth / rect.width); 246 | var repY = Math.ceil(sprite.frameHeight / rect.height); 247 | 248 | for (i in 0...repX) { 249 | 250 | blitPt.x = i * rect.width; 251 | 252 | for (j in 0...repY) { 253 | 254 | blitPt.y = j * rect.height; 255 | 256 | sprite.pixels.copyPixels(bmd, rect, blitPt); 257 | } 258 | } 259 | } 260 | } 261 | 262 | else { 263 | sprite.pixels.draw(bmd, matrix, null, null, null); 264 | } 265 | 266 | sprite.dirty = true; 267 | } 268 | 269 | else { 270 | 271 | var rects = Slice9.buildRects(sprite.frameWidth, sprite.frameHeight, rect.width, rect.height, slice); 272 | 273 | var src = null; 274 | for (i in 0...rects.src.length) { 275 | 276 | src = rects.src[i]; 277 | 278 | src.left += rect.x; 279 | src.top += rect.y; 280 | } 281 | 282 | var srcOpenFL = new Rectangle(); 283 | var dstOpenFL = new Rectangle(); 284 | var dstPt = new Point(); 285 | var mat = new Matrix(); 286 | 287 | var pixels = sprite.pixels; 288 | 289 | // 9-slice credit @MSGhero and @IBwWG: https://gist.github.com/IBwWG/9ffe25a059983e7e4eeb7640d6645a37 290 | 291 | // copyPx() the corners 292 | pixels.copyPixels(bmd, setOpenFLRect(srcOpenFL, rects.src[0]), setOpenFLRect(dstOpenFL, rects.dst[0]).topLeft, null, null, true); // TL 293 | pixels.copyPixels(bmd, setOpenFLRect(srcOpenFL, rects.src[2]), setOpenFLRect(dstOpenFL, rects.dst[2]).topLeft, null, null, true); // TR 294 | pixels.copyPixels(bmd, setOpenFLRect(srcOpenFL, rects.src[6]), setOpenFLRect(dstOpenFL, rects.dst[6]).topLeft, null, null, true); // BL 295 | pixels.copyPixels(bmd, setOpenFLRect(srcOpenFL, rects.src[8]), setOpenFLRect(dstOpenFL, rects.dst[8]).topLeft, null, null, true); // BR 296 | 297 | // draw() the sides and center 298 | var tl = rects.src[0]; 299 | var center = rects.src[4]; 300 | var br = rects.src[8]; 301 | 302 | var scaleH = rects.dst[4].width / center.width; 303 | var scaleV = rects.dst[4].height / center.height; 304 | 305 | // is it better to draw() from bmd (scaling a large bmd) or from a temp copyPx slice (creates a new bmd each time)? 306 | 307 | setOpenFLRect(dstOpenFL, rects.dst[1]); 308 | mat.translate(tl.width * (1 / scaleH - 1) - rect.x, -rect.y); 309 | mat.scale(scaleH, 1); 310 | pixels.draw(bmd, mat, null, null, dstOpenFL); // T 311 | 312 | mat.identity(); 313 | 314 | setOpenFLRect(dstOpenFL, rects.dst[3]); 315 | mat.translate( -rect.x, tl.height * (1 / scaleV - 1) - rect.y); 316 | mat.scale(1, scaleV); 317 | pixels.draw(bmd, mat, null, null, dstOpenFL); // L 318 | 319 | mat.identity(); 320 | 321 | setOpenFLRect(dstOpenFL, rects.dst[4]); 322 | mat.translate(tl.width * (1 / scaleH - 1) - rect.x, tl.height * (1 / scaleV - 1) - rect.y); 323 | mat.scale(scaleH, scaleV); 324 | pixels.draw(bmd, mat, null, null, dstOpenFL); // C 325 | 326 | mat.identity(); 327 | 328 | setOpenFLRect(dstOpenFL, rects.dst[5]); 329 | mat.translate(sprite.frameWidth - rect.width - rect.x, tl.height * (1 / scaleV - 1) - rect.y); 330 | mat.scale(1, scaleV); 331 | pixels.draw(bmd, mat, null, null, dstOpenFL); // R 332 | 333 | mat.identity(); 334 | 335 | setOpenFLRect(dstOpenFL, rects.dst[7]); 336 | mat.translate(tl.width * (1 / scaleH - 1) - rect.x, sprite.frameHeight - rect.height - rect.y); 337 | mat.scale(scaleH, 1); 338 | pixels.draw(bmd, mat, null, null, dstOpenFL); // B 339 | } 340 | } 341 | 342 | static function setOpenFLRect(oflRect:flash.geom.Rectangle, uiRect:haxe.ui.geom.Rectangle):flash.geom.Rectangle { 343 | oflRect.setTo(uiRect.left, uiRect.top, uiRect.width, uiRect.height); 344 | return oflRect; 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/InputManager.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | import flixel.input.IFlxInputManager; 3 | 4 | class InputManager implements IFlxInputManager { 5 | public var onResetCb:Void->Void; 6 | 7 | public function new() { 8 | } 9 | 10 | public function reset():Void { 11 | if (onResetCb != null) { 12 | onResetCb(); 13 | } 14 | } 15 | private function update():Void { 16 | 17 | } 18 | private function onFocus():Void { 19 | 20 | } 21 | private function onFocusLost():Void { 22 | 23 | } 24 | public function destroy():Void { 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/KeyboardHelper.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import flixel.FlxG; 4 | import haxe.ui.core.Component; 5 | import haxe.ui.events.KeyboardEvent; 6 | import haxe.ui.focus.FocusManager; 7 | 8 | @:structInit 9 | class KeyboardCallback { 10 | public var fn:KeyboardEvent->Void; 11 | public var priority:Int; 12 | } 13 | 14 | class KeyboardHelper { 15 | private static var _initialized = false; 16 | private static var _callbacks:Map> = new Map>(); 17 | 18 | public static function init() { 19 | if (_initialized == true) { 20 | return; 21 | } 22 | 23 | _initialized = true; 24 | 25 | FlxG.stage.addEventListener(openfl.events.KeyboardEvent.KEY_DOWN, onKeyDown); 26 | FlxG.stage.addEventListener(openfl.events.KeyboardEvent.KEY_UP, onKeyUp); 27 | } 28 | 29 | public static function notify(event:String, callback:KeyboardEvent->Void, priority:Int = 5) { 30 | var list = _callbacks.get(event); 31 | if (list == null) { 32 | list = new Array(); 33 | _callbacks.set(event, list); 34 | } 35 | 36 | if (!hasCallback(list, callback)) { 37 | list.insert(0, { 38 | fn: callback, 39 | priority: priority 40 | }); 41 | 42 | list.sort(function(a, b) { 43 | return a.priority - b.priority; 44 | }); 45 | } 46 | } 47 | 48 | public static function remove(event:String, callback:KeyboardEvent->Void) { 49 | var list = _callbacks.get(event); 50 | if (list != null) { 51 | removeCallback(list, callback); 52 | if (list.length == 0) { 53 | _callbacks.remove(event); 54 | } 55 | } 56 | } 57 | 58 | private static function onKeyDown(e:openfl.events.KeyboardEvent) { 59 | dispatchEvent(KeyboardEvent.KEY_DOWN, e); 60 | } 61 | 62 | private static function onKeyUp(e:openfl.events.KeyboardEvent) { 63 | dispatchEvent(KeyboardEvent.KEY_UP, e); 64 | } 65 | 66 | private static function dispatchEvent(type:String, e:openfl.events.KeyboardEvent) { 67 | var event = new KeyboardEvent(type); 68 | event.keyCode = e.keyCode; 69 | event.altKey = e.altKey; 70 | event.ctrlKey = e.ctrlKey; 71 | event.shiftKey = e.shiftKey; 72 | 73 | var target = getTarget(); 74 | // recreate a bubbling effect, so components will pass events onto their parents 75 | // can't use the `bubble` property as it causes a crash when `target` isn't the expected result, for example, on ListView.onRendererClick 76 | while (target != null) { 77 | if (target.hasEvent(event.type)) { 78 | target.dispatch(event); 79 | if (event.canceled == true) { 80 | return; 81 | } 82 | } 83 | target = target.parentComponent; 84 | } 85 | 86 | var list = _callbacks.get(type); 87 | if (list == null || list.length == 0) { 88 | return; 89 | } 90 | 91 | list = list.copy(); 92 | 93 | for (l in list) { 94 | l.fn(event); 95 | if (event.canceled == true) { 96 | break; 97 | } 98 | } 99 | } 100 | 101 | private static function getTarget():Component { 102 | var target:Component = cast FocusManager.instance.focus; 103 | if (target != null && target.state == StateHelper.currentState) { 104 | return target; 105 | } 106 | return null; 107 | } 108 | 109 | private static function hasCallback(list:Array, fn:KeyboardEvent->Void):Bool { 110 | var has = false; 111 | for (item in list) { 112 | if (item.fn == fn) { 113 | has = true; 114 | break; 115 | } 116 | } 117 | return has; 118 | } 119 | 120 | private static function removeCallback(list:Array, fn:KeyboardEvent->Void) { 121 | var itemToRemove:KeyboardCallback = null; 122 | for (item in list) { 123 | if (item.fn == fn) { 124 | itemToRemove = item; 125 | break; 126 | } 127 | } 128 | if (itemToRemove != null) { 129 | list.remove(itemToRemove); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/MouseHelper.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import flixel.FlxG; 4 | import haxe.ui.core.Component; 5 | import haxe.ui.core.Platform; 6 | import haxe.ui.core.Screen; 7 | import haxe.ui.events.MouseEvent; 8 | 9 | @:structInit 10 | class MouseCallback { 11 | public var fn:MouseEvent->Void; 12 | public var priority:Int; 13 | } 14 | 15 | class MouseHelper { 16 | public static var currentMouseX:Float = 0; 17 | public static var currentMouseY:Float = 0; 18 | public static var currentWorldX:Float = 0; 19 | public static var currentWorldY:Float = 0; 20 | 21 | private static var _initialized = false; 22 | private static var _callbacks:Map> = new Map>(); 23 | 24 | private static var _mouseDownLeft:Dynamic; 25 | private static var _mouseDownMiddle:Dynamic; 26 | private static var _mouseDownRight:Dynamic; 27 | private static var _lastClickTarget:Dynamic; 28 | private static var _lastClickTime:Float; 29 | private static var _mouseOverTarget:Dynamic; 30 | 31 | public static function init() { 32 | if (_initialized == true) { 33 | return; 34 | } 35 | 36 | _initialized = true; 37 | 38 | FlxG.stage.addEventListener(openfl.events.MouseEvent.MOUSE_DOWN, onMouseDown); 39 | FlxG.stage.addEventListener(openfl.events.MouseEvent.RIGHT_MOUSE_DOWN, onMouseDown); 40 | FlxG.stage.addEventListener(openfl.events.MouseEvent.MIDDLE_MOUSE_DOWN, onMouseDown); 41 | 42 | FlxG.stage.addEventListener(openfl.events.MouseEvent.MOUSE_UP, onMouseUp); 43 | FlxG.stage.addEventListener(openfl.events.MouseEvent.RIGHT_MOUSE_UP, onMouseUp); 44 | FlxG.stage.addEventListener(openfl.events.MouseEvent.MIDDLE_MOUSE_UP, onMouseUp); 45 | 46 | FlxG.stage.addEventListener(openfl.events.MouseEvent.MOUSE_MOVE, onMouseMove); 47 | 48 | FlxG.stage.addEventListener(openfl.events.MouseEvent.MOUSE_WHEEL, onMouseWheel); 49 | 50 | FlxG.signals.preStateSwitch.add(onPreStateSwitched); 51 | } 52 | 53 | public static function notify(event:String, callback:MouseEvent->Void, priority:Int = 5) { 54 | var list = _callbacks.get(event); 55 | if (list == null) { 56 | list = new Array(); 57 | _callbacks.set(event, list); 58 | } 59 | 60 | if (!hasCallback(list, callback)) { 61 | list.insert(0, { 62 | fn: callback, 63 | priority: priority 64 | }); 65 | 66 | list.sort(function(a, b) { 67 | return a.priority - b.priority; 68 | }); 69 | } 70 | } 71 | 72 | public static function remove(event:String, callback:MouseEvent->Void) { 73 | var list = _callbacks.get(event); 74 | if (list != null) { 75 | removeCallback(list, callback); 76 | if (list.length == 0) { 77 | _callbacks.remove(event); 78 | } 79 | } 80 | } 81 | 82 | private static function onPreStateSwitched() { 83 | // simulate mouse events when states switch to mop up any visual styles 84 | onMouse(MouseEvent.MOUSE_UP, currentMouseX, currentMouseY); 85 | onMouse(MouseEvent.MOUSE_MOVE, currentMouseX, currentMouseY); 86 | } 87 | 88 | private static function onMouseDown(e:openfl.events.MouseEvent) { 89 | var type = switch (e.type) { 90 | case openfl.events.MouseEvent.MIDDLE_MOUSE_DOWN: MouseEvent.MIDDLE_MOUSE_DOWN; 91 | case openfl.events.MouseEvent.RIGHT_MOUSE_DOWN: MouseEvent.RIGHT_MOUSE_DOWN; 92 | default: MouseEvent.MOUSE_DOWN; 93 | } 94 | onMouse(type, e.stageX, e.stageY, e.buttonDown, e.ctrlKey, e.shiftKey); 95 | } 96 | 97 | private static function onMouseUp(e:openfl.events.MouseEvent) { 98 | var type = switch (e.type) { 99 | case openfl.events.MouseEvent.MIDDLE_MOUSE_UP: MouseEvent.MIDDLE_MOUSE_UP; 100 | case openfl.events.MouseEvent.RIGHT_MOUSE_UP: MouseEvent.RIGHT_MOUSE_UP; 101 | default: MouseEvent.MOUSE_UP; 102 | } 103 | onMouse(type, e.stageX, e.stageY, e.buttonDown, e.ctrlKey, e.shiftKey); 104 | } 105 | 106 | private static function onMouseMove(e:openfl.events.MouseEvent) { 107 | onMouse(MouseEvent.MOUSE_MOVE, e.stageX, e.stageY, e.buttonDown, e.ctrlKey, e.shiftKey); 108 | } 109 | 110 | private static function onMouseWheel(e:openfl.events.MouseEvent) { 111 | var event = createEvent(MouseEvent.MOUSE_WHEEL, e.buttonDown, e.ctrlKey, e.shiftKey); 112 | event.delta = Math.max(-1, Math.min(1, e.delta)); 113 | 114 | var target:Dynamic = getTarget(currentWorldX, currentWorldY); 115 | 116 | dispatchEvent(event, target); 117 | } 118 | 119 | private static function onMouse(type:String, x:Float, y:Float, buttonDown:Bool = false, ctrlKey:Bool = false, shiftKey:Bool = false) { 120 | if (currentMouseX != x) { 121 | currentMouseX = x; 122 | currentWorldX = ((currentMouseX - FlxG.scaleMode.offset.x) / (FlxG.scaleMode.scale.x * initialZoom())) / Toolkit.scaleX; 123 | } 124 | if (currentMouseY != y) { 125 | currentMouseY = y; 126 | currentWorldY = ((currentMouseY - FlxG.scaleMode.offset.y) / (FlxG.scaleMode.scale.y * initialZoom())) / Toolkit.scaleY; 127 | } 128 | 129 | var target:Dynamic = getTarget(currentWorldX, currentWorldY); 130 | 131 | if (target != _mouseOverTarget) { 132 | Screen.instance.checkResetCursor(); 133 | if (_mouseOverTarget != null) { 134 | dispatchEventType(MouseEvent.MOUSE_OUT, buttonDown, ctrlKey, shiftKey, _mouseOverTarget); 135 | } 136 | dispatchEventType(MouseEvent.MOUSE_OVER, buttonDown, ctrlKey, shiftKey, target); 137 | _mouseOverTarget = target; 138 | } 139 | 140 | var clickType:String = null; 141 | switch (type) { 142 | case MouseEvent.MOUSE_DOWN: 143 | _mouseDownLeft = target; 144 | 145 | case MouseEvent.MIDDLE_MOUSE_DOWN: 146 | _mouseDownMiddle = target; 147 | 148 | case MouseEvent.RIGHT_MOUSE_DOWN: 149 | _mouseDownRight = target; 150 | 151 | case MouseEvent.MOUSE_UP: 152 | if (_mouseDownLeft == target) { 153 | clickType = MouseEvent.CLICK; 154 | } 155 | _mouseDownLeft = null; 156 | 157 | case MouseEvent.MIDDLE_MOUSE_UP: 158 | if (_mouseDownMiddle == target) { 159 | clickType = MouseEvent.MIDDLE_CLICK; 160 | } 161 | _mouseDownMiddle = null; 162 | 163 | case MouseEvent.RIGHT_MOUSE_UP: 164 | if (_mouseDownRight == target) { 165 | clickType = MouseEvent.RIGHT_CLICK; 166 | } 167 | _mouseDownRight = null; 168 | } 169 | 170 | dispatchEventType(type, buttonDown, ctrlKey, shiftKey, target); 171 | 172 | if (clickType != null) { 173 | dispatchEventType(clickType, buttonDown, ctrlKey, shiftKey, target); 174 | 175 | if (type == MouseEvent.MOUSE_UP) { 176 | var currentTime = Timer.stamp(); 177 | if (currentTime - _lastClickTime < 0.5 && target == _lastClickTarget) { 178 | dispatchEventType(MouseEvent.DBL_CLICK, buttonDown, ctrlKey, shiftKey, target); 179 | _lastClickTime = 0; 180 | _lastClickTarget = null; 181 | } else { 182 | _lastClickTarget = target; 183 | _lastClickTime = currentTime; 184 | } 185 | } 186 | } 187 | } 188 | 189 | private static function dispatchEventType(type:String, buttonDown:Bool, ctrlKey:Bool, shiftKey:Bool, target:Dynamic) { 190 | var event = createEvent(type, buttonDown, ctrlKey, shiftKey); 191 | dispatchEvent(event, target); 192 | } 193 | 194 | private static function dispatchEvent(event:MouseEvent, target:Dynamic) { 195 | if ((target is Component)) { 196 | var c:Component = cast target; 197 | // recreate a bubbling effect, so components will pass events onto their parents 198 | // can't use the `bubble` property as it causes a crash when `target` isn't the expected result, for example, on ListView.onRendererClick 199 | while (c != null) { 200 | if (c.hasEvent(event.type)) { 201 | c.dispatch(event); 202 | if (event.canceled == true) { 203 | return; 204 | } 205 | } 206 | c = c.parentComponent; 207 | } 208 | } 209 | 210 | var list = _callbacks.get(event.type); 211 | if (list == null || list.length == 0) { 212 | return; 213 | } 214 | 215 | list = list.copy(); 216 | 217 | for (l in list) { 218 | l.fn(event); 219 | if (event.canceled == true) { 220 | break; 221 | } 222 | } 223 | } 224 | 225 | private static function createEvent(type:String, buttonDown:Bool, ctrlKey:Bool, shiftKey:Bool):MouseEvent { 226 | var event = new MouseEvent(type); 227 | event.screenX = currentWorldX; 228 | event.screenY = currentWorldY; 229 | event.buttonDown = buttonDown; 230 | event.touchEvent = Platform.instance.isMobile; 231 | event.ctrlKey = ctrlKey; 232 | event.shiftKey = shiftKey; 233 | return event; 234 | } 235 | 236 | private static function getTarget(x:Float, y:Float):Dynamic { 237 | var components = Screen.instance.findComponentsUnderPoint(x, y); 238 | components.reverse(); 239 | for (c in components) { 240 | if (c.state == StateHelper.currentState) { 241 | return c; 242 | } 243 | } 244 | return Screen.instance; 245 | } 246 | 247 | private static inline function initialZoom():Float { 248 | #if (flixel <= "4.11.0") // FlxG.initialZoom removed in later versions of flixel 249 | 250 | return FlxG.initialZoom; 251 | 252 | #else 253 | 254 | return 1; 255 | 256 | #end 257 | } 258 | 259 | private static function hasCallback(list:Array, fn:MouseEvent->Void):Bool { 260 | var has = false; 261 | for (item in list) { 262 | if (item.fn == fn) { 263 | has = true; 264 | break; 265 | } 266 | } 267 | return has; 268 | } 269 | 270 | private static function removeCallback(list:Array, fn:MouseEvent->Void) { 271 | var itemToRemove:MouseCallback = null; 272 | for (item in list) { 273 | if (item.fn == fn) { 274 | itemToRemove = item; 275 | break; 276 | } 277 | } 278 | if (itemToRemove != null) { 279 | list.remove(itemToRemove); 280 | } 281 | } 282 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/OpenFLStyleHelper.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import haxe.ui.styles.Style; 4 | import openfl.display.GradientType; 5 | import openfl.display.Graphics; 6 | import openfl.display.InterpolationMethod; 7 | import openfl.display.SpreadMethod; 8 | import openfl.geom.Matrix; 9 | import openfl.geom.Rectangle; 10 | 11 | class OpenFLStyleHelper { 12 | public function new() { 13 | } 14 | 15 | public static function paintStyleSection(graphics:Graphics, style:Style, width:Float, height:Float, left:Float = 0, top:Float = 0, clear:Bool = true):Bool { 16 | if (clear == true) { 17 | graphics.clear(); 18 | } 19 | 20 | if (width <= 0 || height <= 0) { 21 | return false; 22 | } 23 | 24 | /* 25 | left = Math.fround(left); 26 | top = Math.fround(top); 27 | width = Math.fround(width); 28 | height = Math.fround(height); 29 | */ 30 | 31 | left = Std.int(left); 32 | top = Std.int(top); 33 | width = Std.int(width); 34 | height = Std.int(height); 35 | 36 | var hasFullStyledBorder:Bool = false; 37 | var borderStyle = style.borderStyle; 38 | if (borderStyle == null) { 39 | borderStyle = "solid"; 40 | } 41 | 42 | var rc:Rectangle = new Rectangle(top, left, width, height); 43 | var borderRadius:Float = 0; 44 | if (style.borderRadius != null) { 45 | borderRadius = style.borderRadius * Toolkit.scale; 46 | } 47 | 48 | var painted = false; 49 | 50 | if (style.borderLeftSize != null && style.borderLeftSize != 0 51 | && style.borderLeftSize == style.borderRightSize 52 | && style.borderLeftSize == style.borderBottomSize 53 | && style.borderLeftSize == style.borderTopSize 54 | 55 | && style.borderLeftColor != null 56 | && style.borderLeftColor == style.borderRightColor 57 | && style.borderLeftColor == style.borderBottomColor 58 | && style.borderLeftColor == style.borderTopColor) { // full border 59 | 60 | graphics.lineStyle(style.borderLeftSize * Toolkit.scale, style.borderLeftColor); 61 | rc.left += (style.borderLeftSize * Toolkit.scale) / 2; 62 | rc.top += (style.borderLeftSize * Toolkit.scale) / 2; 63 | rc.bottom -= (style.borderLeftSize * Toolkit.scale) / 2; 64 | rc.right -= (style.borderLeftSize * Toolkit.scale) / 2; 65 | //rc.inflate( -(style.borderLeftSize / 2), -(style.borderLeftSize / 2)); 66 | 67 | painted = true; 68 | } else { // compound border 69 | if ((style.borderTopSize != null && style.borderTopSize > 0) 70 | || (style.borderBottomSize != null && style.borderBottomSize > 0) 71 | || (style.borderLeftSize != null && style.borderLeftSize > 0) 72 | || (style.borderRightSize != null && style.borderRightSize > 0)) { 73 | 74 | var org = rc.clone(); 75 | 76 | if (style.borderTopSize != null && style.borderTopSize > 0) { 77 | graphics.beginFill(style.borderTopColor); 78 | graphics.drawRect(0, 0, org.width, (style.borderTopSize * Toolkit.scale)); 79 | graphics.endFill(); 80 | 81 | rc.top += (style.borderTopSize * Toolkit.scale); 82 | } 83 | 84 | if (style.borderBottomSize != null && style.borderBottomSize > 0) { 85 | graphics.beginFill(style.borderBottomColor); 86 | graphics.drawRect(0, org.height - (style.borderBottomSize * Toolkit.scale), org.width, (style.borderBottomSize * Toolkit.scale)); 87 | graphics.endFill(); 88 | 89 | rc.bottom -= (style.borderBottomSize * Toolkit.scale); 90 | } 91 | 92 | if (style.borderLeftSize != null && style.borderLeftSize > 0) { 93 | graphics.beginFill(style.borderLeftColor); 94 | graphics.drawRect(0, rc.top, (style.borderLeftSize * Toolkit.scale), org.height - rc.top); 95 | graphics.endFill(); 96 | 97 | rc.left += (style.borderLeftSize * Toolkit.scale); 98 | } 99 | 100 | if (style.borderRightSize != null && style.borderRightSize > 0) { 101 | graphics.beginFill(style.borderRightColor); 102 | graphics.drawRect(org.width - (style.borderRightSize * Toolkit.scale), rc.top, (style.borderRightSize * Toolkit.scale), org.height - rc.top); 103 | graphics.endFill(); 104 | 105 | rc.right -= (style.borderRightSize * Toolkit.scale); 106 | } 107 | 108 | painted = true; 109 | } 110 | } 111 | 112 | var backgroundColor:Null = style.backgroundColor; 113 | var backgroundColorEnd:Null = style.backgroundColorEnd; 114 | var backgroundOpacity:Null = style.backgroundOpacity; 115 | #if html5 // TODO: fix for html5 not working with non-gradient fills 116 | if (backgroundColor != null && backgroundColorEnd == null) { 117 | backgroundColorEnd = backgroundColor; 118 | } 119 | #end 120 | 121 | if(backgroundOpacity == null) { 122 | backgroundOpacity = 1; 123 | } 124 | 125 | if (backgroundColor != null) { 126 | if (backgroundColorEnd != null) { 127 | var w:Int = Std.int(rc.width); 128 | var h:Int = Std.int(rc.height); 129 | var colors:Array = [backgroundColor, backgroundColorEnd]; 130 | var alphas:Array = [backgroundOpacity, backgroundOpacity]; 131 | var ratios:Array = [0, 255]; 132 | var matrix:Matrix = new Matrix(); 133 | 134 | var gradientType:String = "vertical"; 135 | if (style.backgroundGradientStyle != null) { 136 | gradientType = style.backgroundGradientStyle; 137 | } 138 | 139 | if (gradientType == "vertical") { 140 | matrix.createGradientBox(w - 2, h - 2, Math.PI / 2, 0, 0); 141 | } else if (gradientType == "horizontal") { 142 | matrix.createGradientBox(w - 2, h - 2, 0, 0, 0); 143 | } 144 | 145 | graphics.beginGradientFill(GradientType.LINEAR, 146 | colors, 147 | alphas, 148 | ratios, 149 | matrix, 150 | SpreadMethod.PAD, 151 | InterpolationMethod.LINEAR_RGB, 152 | 0); 153 | } else { 154 | graphics.beginFill(backgroundColor, backgroundOpacity); 155 | } 156 | 157 | if (backgroundOpacity > 0) { 158 | painted = true; 159 | } 160 | } 161 | 162 | if (borderRadius == 0) { 163 | if (style.borderRadiusTopLeft != null || style.borderRadiusTopRight != null || style.borderRadiusBottomLeft != null || style.borderRadiusBottomRight != null) { 164 | graphics.drawRoundRectComplex(rc.left, rc.top, rc.width, rc.height, style.borderRadiusTopLeft, style.borderRadiusTopRight, style.borderRadiusBottomLeft, style.borderRadiusBottomRight); 165 | } else if (hasFullStyledBorder) { 166 | graphics.drawRect(rc.left, rc.top, rc.width, rc.height); 167 | } else { 168 | graphics.drawRect(rc.left, rc.top, rc.width, rc.height); 169 | } 170 | } else { 171 | if (rc.width == rc.height && borderRadius >= rc.width / 2) { 172 | borderRadius = rc.width - 1; 173 | } 174 | graphics.drawRoundRect(rc.left, rc.top, rc.width, rc.height, borderRadius + 1, borderRadius + 1); 175 | } 176 | 177 | graphics.endFill(); 178 | 179 | return painted; 180 | } 181 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/StateHelper.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import flixel.FlxBasic; 4 | import flixel.FlxCamera; 5 | import flixel.FlxG; 6 | import flixel.FlxState; 7 | import flixel.group.FlxGroup; 8 | import flixel.group.FlxSpriteGroup; 9 | 10 | class StateHelper { 11 | public static var currentState(get, null):FlxState; 12 | private static function get_currentState():FlxState { 13 | var s:FlxState = FlxG.state; 14 | if (s != null && s.subState != null) { 15 | var r = s.subState; 16 | while (r != null) { 17 | if (r.subState == null) { 18 | break; 19 | } 20 | r = r.subState; 21 | } 22 | s = r; 23 | } 24 | return s; 25 | } 26 | 27 | public static function hasMember(member:FlxBasic, group:FlxTypedGroup = null):Bool { 28 | // TODO: we drop frames because of this, need to revise / thing of a better way (or simply remove) 29 | return true; 30 | 31 | if (group == null) { 32 | group = currentState; 33 | } 34 | 35 | if (group == null || !group.exists) { 36 | return false; 37 | } 38 | 39 | if (member == group) { 40 | return true; 41 | } 42 | 43 | for (m in group.members) { 44 | if (m == member) { 45 | return true; 46 | } 47 | 48 | if ((m is FlxTypedGroup)) { 49 | if (hasMember(member, cast m) == true) { 50 | return true; 51 | } 52 | } else if ((m is FlxSpriteGroup)) { 53 | if (groupHasMember(member, cast m) == true) { 54 | return true; 55 | } 56 | } 57 | } 58 | 59 | return false; 60 | } 61 | 62 | private static function groupHasMember(member:FlxBasic, group:FlxSpriteGroup) { 63 | if (group == null || !group.exists) { 64 | return false; 65 | } 66 | 67 | if (member == group) { 68 | return true; 69 | } 70 | 71 | for (m in group.members) { 72 | if (m == member) { 73 | return true; 74 | } 75 | 76 | if ((m is FlxTypedGroup)) { 77 | if (hasMember(member, cast m) == true) { 78 | return true; 79 | } 80 | } else if ((m is FlxSpriteGroup)) { 81 | if (groupHasMember(member, cast m) == true) { 82 | return true; 83 | } 84 | } 85 | } 86 | 87 | return false; 88 | } 89 | 90 | public static function findCameras(member:FlxBasic, group:FlxTypedGroup = null):Array { 91 | if (group == null) { 92 | group = currentState; 93 | } 94 | 95 | if (group == null || !group.exists) { 96 | return null; 97 | } 98 | 99 | for (m in group.members) { 100 | if (m != null && m.cameras != null && m.cameras.length > 0) { 101 | if (m == member || ((m is FlxTypedGroup) && hasMember(member, cast m))) { 102 | return m.cameras; 103 | } 104 | } 105 | } 106 | 107 | return null; 108 | } 109 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIFragment.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | class UIFragment extends UIFragmentBase { 4 | 5 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIFragmentBase.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | typedef UIFragmentBase = flixel.group.FlxSpriteContainer; -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIRTTITools.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import haxe.ui.RuntimeComponentBuilder; 4 | import haxe.ui.core.Component; 5 | import haxe.rtti.CType; 6 | import haxe.ui.core.ComponentClassMap; 7 | 8 | using StringTools; 9 | 10 | class UIRTTITools { 11 | public static function buildViaRTTI(rtti:Classdef):Component { 12 | var root:Component = null; 13 | var m = getMetaWithValueRTTI(rtti.meta, "build", "haxe.ui.RuntimeComponentBuilder.build"); 14 | if (m != null) { 15 | var assetId = m.params[0].replace("haxe.ui.RuntimeComponentBuilder.build(", "").replace(")", ""); 16 | assetId = assetId.replace("\"", ""); 17 | assetId = assetId.replace("'", ""); 18 | root = RuntimeComponentBuilder.fromAsset(assetId); 19 | if (root == null) { 20 | throw "could not loading runtime ui from asset (" + assetId + ")"; 21 | } 22 | } 23 | m = getMetaRTTI(rtti.meta, "xml"); 24 | if (m != null) { // comes back as an escaped CDATA section 25 | var xmlString = m.params[0].trim(); 26 | if (xmlString.startsWith("")) { 30 | xmlString = xmlString.substring(0, xmlString.length - "]]>".length); 31 | } 32 | if (xmlString.startsWith("\"")) { 33 | xmlString = xmlString.substring(1); 34 | } 35 | if (xmlString.endsWith("\"")) { 36 | xmlString = xmlString.substring(0, xmlString.length - 1); 37 | } 38 | xmlString = xmlString.replace("\\r", "\r"); 39 | xmlString = xmlString.replace("\\n", "\n"); 40 | xmlString = xmlString.replace("\\\"", "\""); 41 | xmlString = xmlString.trim(); 42 | try { 43 | root = RuntimeComponentBuilder.fromString(xmlString); 44 | } catch (e:Dynamic) { 45 | trace("ERROR", e); 46 | trace(haxe.CallStack.toString(haxe.CallStack.exceptionStack(true))); 47 | } 48 | } 49 | return root; 50 | } 51 | 52 | public static function linkViaRTTI(rtti:Classdef, target:Dynamic, root:Component, force:Bool = false) { 53 | if (root == null) { 54 | return; 55 | } 56 | for (f in rtti.fields) { 57 | switch (f.type) { 58 | case CClass(name, params): 59 | if (ComponentClassMap.instance.hasClassName(name)) { 60 | var candidate = root.findComponent(f.name); 61 | if (force) { 62 | Reflect.setField(target, f.name, null); 63 | } 64 | if (candidate != null && Reflect.field(target, f.name) == null) { 65 | var temp = Type.createEmptyInstance(Type.resolveClass(name)); 66 | if ((temp is IComponentDelegate)) { 67 | var componentDelegate:IComponentDelegate = Type.createEmptyInstance(Type.resolveClass(name)); 68 | componentDelegate.component = candidate; 69 | Reflect.setField(target, f.name, componentDelegate); 70 | } else { 71 | Reflect.setField(target, f.name, candidate); 72 | } 73 | } 74 | } 75 | case CFunction(args, ret): 76 | var m = getMetaRTTI(f.meta, "bind"); 77 | if (m != null) { 78 | if (m.params[0] == "this") { 79 | if ((target is IComponentDelegate)) { 80 | var componentDelegate:IComponentDelegate = cast target; 81 | bindEvent(rtti, componentDelegate.component, f.name, target, m.params[1], true); 82 | } else { 83 | bindEvent(rtti, root, f.name, target, m.params[1]); 84 | } 85 | } else { 86 | var candidate:Component = root.findComponent(m.params[0]); 87 | if (candidate != null) { 88 | bindEvent(rtti, candidate, f.name, target, m.params[1]); 89 | } else { 90 | // another perfectly valid contruct, albeit less common (though still useful), is the ability to use 91 | // @:bind to bind to variables on static fields, eg: 92 | // @:bind(MyClass.instance, SomeEvent.EventType) 93 | // this code facilitates that by attempting to resolve the item and binding the event to it 94 | var parts = m.params[0].split("."); 95 | var className = parts.shift(); 96 | var c = Type.resolveClass(className); 97 | 98 | if (c == null) { 99 | // this allows full qualified class names: 100 | // @:bind(some.pkg.MyClass.instance, SomeEvent.EventType) 101 | // by looping over each part looking for a valid class 102 | // its not fast (or pretty), but it will only happen once (per @:bind) 103 | var candidateClass = className; 104 | while (parts.length > 0) { 105 | var part = parts.shift(); 106 | candidateClass += "." + part; 107 | var temp = Type.resolveClass(candidateClass); 108 | if (temp != null) { 109 | c = temp; 110 | break; 111 | } 112 | } 113 | } 114 | 115 | if (c != null) { 116 | var ref:Dynamic = c; 117 | var found = (parts.length > 0); 118 | for (part in parts) { 119 | if (!Reflect.hasField(ref, part)) { 120 | found = false; 121 | break; 122 | } 123 | ref = Reflect.field(ref, part); 124 | } 125 | if (found) { 126 | if (ref != null) { 127 | if ((ref is UIRuntimeState)) { 128 | var state:UIRuntimeState = cast ref; 129 | bindEvent(rtti, state.root, f.name, target, m.params[1]); 130 | } else if ((ref is UIRuntimeSubState)) { 131 | var subState:UIRuntimeSubState = cast ref; 132 | bindEvent(rtti, subState.root, f.name, target, m.params[1]); 133 | } else if ((ref is IComponentDelegate)) { 134 | var componentDelegate:IComponentDelegate = cast ref; 135 | bindEvent(rtti, componentDelegate.component, f.name, target, m.params[1]); 136 | } else if ((ref is Component)) { 137 | var component:Component = cast ref; 138 | bindEvent(rtti, component, f.name, target, m.params[1]); 139 | } 140 | } else { 141 | throw "bind param resolved, but was null '" + m.params[0] + "'"; 142 | } 143 | } else { 144 | throw "could not resolve bind param '" + m.params[0] + "'"; 145 | } 146 | } else { 147 | throw "could not resolve class '" + className + "'"; 148 | } 149 | } 150 | } 151 | } 152 | case _: 153 | } 154 | } 155 | } 156 | 157 | private static function bindEvent(rtti:Classdef, candidate:Component, fieldName:String, target:Dynamic, eventClass:String, isComponentDelegate:Bool = false) { 158 | if (candidate == null) { 159 | return; 160 | } 161 | var parts = eventClass.split("."); 162 | var eventName = parts.pop(); 163 | eventClass = parts.join("."); 164 | var c = resolveEventClass(rtti, eventClass); 165 | if (c != null) { 166 | var eventString = Reflect.field(c, eventName); 167 | var fn = Reflect.field(target, fieldName); 168 | // this may be ill-concieved, but if we are talking about a component delegate (ie, a fragment) 169 | // it means we are going to attach a component to an "empty" class which means this code has 170 | // already run once, meaning there are two event listeners, this way we remove them first 171 | // in practice its probably _exactly_ what we want, but this could also clear up binding 172 | // two functions to the same event (which isnt common at all) 173 | if (isComponentDelegate) { 174 | candidate.unregisterEvents(eventString); 175 | } 176 | candidate.registerEvent(eventString, fn); 177 | } else { 178 | throw "could not resolve event class '" + eventClass + "' (you may need to use fully qualified class names)"; 179 | } 180 | } 181 | 182 | private static function resolveEventClass(rtti:Classdef, eventClass:String) { 183 | var candidateEvent = "haxe.ui.events." + eventClass; 184 | var event = Type.resolveClass(candidateEvent); 185 | if (event != null) { 186 | return event; 187 | } 188 | 189 | var event = Type.resolveClass(eventClass); 190 | if (event != null) { 191 | return event; 192 | } 193 | 194 | // this is pretty brute force method, were going to see if we can find any functions 195 | // with @:bind meta, these are presumably the event handlers, if we can find one 196 | // where the the last part of the arg type (which would be the event type) matches 197 | // the event we are looking for, we'll consider that match, and can use that as a 198 | // fully qualified event class 199 | for (f in rtti.fields) { 200 | switch (f.type) { 201 | case CFunction(args, ret): 202 | if (getMetaRTTI(f.meta, "bind") != null) { 203 | for (arg in args) { 204 | switch (arg.t) { 205 | case CClass(name, params): 206 | if (name.endsWith(eventClass)) { 207 | var event = Type.resolveClass(name); 208 | if (event != null) { 209 | return event; 210 | } 211 | } 212 | case _: 213 | } 214 | } 215 | } 216 | case _: 217 | } 218 | } 219 | 220 | return null; 221 | } 222 | 223 | private static function getMetaRTTI(metadata:MetaData, name:String):{name:String, params:Array} { 224 | for (m in metadata) { 225 | if (m.name == name || m.name == ":" + name) { 226 | return m; 227 | } 228 | } 229 | return null; 230 | } 231 | 232 | private static function getMetasRTTI(metadata:MetaData, name:String):Array<{name:String, params:Array}> { 233 | var metas = []; 234 | for (m in metadata) { 235 | if (m.name == name || m.name == ":" + name) { 236 | metas.push(m); 237 | } 238 | } 239 | return metas; 240 | } 241 | 242 | private static function getMetaWithValueRTTI(metadata:MetaData, name:String, value:String, paramIndex:Int = 0):{name:String, params:Array} { 243 | for (m in metadata) { 244 | if (m.name == name || m.name == ":" + name) { 245 | if (m.params[paramIndex].startsWith(value)) { 246 | return m; 247 | } 248 | } 249 | } 250 | return null; 251 | } 252 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIRuntimeFragment.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import haxe.ui.RuntimeComponentBuilder; 4 | import haxe.ui.core.Component; 5 | import haxe.ui.events.UIEvent; 6 | import haxe.ui.events.EventType; 7 | import haxe.ui.backend.flixel.UIRTTITools.*; 8 | 9 | using StringTools; 10 | 11 | @:rtti 12 | class UIRuntimeFragment extends UIFragmentBase implements IComponentDelegate { // uses rtti to "build" a class with a similar experience to using macros 13 | public var root:Component; 14 | 15 | public function new() { 16 | super(); 17 | 18 | scrollFactor.set(0, 0); // ui doesn't scroll by default 19 | 20 | var rtti = haxe.rtti.Rtti.getRtti(Type.getClass(this)); 21 | root = buildViaRTTI(rtti); 22 | linkViaRTTI(rtti, this, root); 23 | if (root != null) { 24 | root.registerEvent(UIEvent.READY, (_) -> { 25 | onReady(); 26 | }); 27 | add(root); 28 | } 29 | } 30 | 31 | private function onReady() { 32 | } 33 | 34 | public var component(get, set):Component; 35 | private function get_component():Component { 36 | return root; 37 | } 38 | private function set_component(value:Component):Component { 39 | root = value; 40 | var rtti = haxe.rtti.Rtti.getRtti(Type.getClass(this)); 41 | linkViaRTTI(rtti, this, root); 42 | return value; 43 | } 44 | 45 | ///////////////////////////////////////////////////////////////////////////////////////////////// 46 | // util functions 47 | ///////////////////////////////////////////////////////////////////////////////////////////////// 48 | public function addComponent(child:Component):Component { 49 | if (root == null) { 50 | throw "no root component"; 51 | } 52 | 53 | return root.addComponent(child); 54 | } 55 | 56 | public function removeComponent(child:Component):Component { 57 | if (root == null) { 58 | throw "no root component"; 59 | } 60 | 61 | return root.removeComponent(child); 62 | } 63 | 64 | public function findComponent(criteria:String = null, type:Class = null, recursive:Null = null, searchType:String = "id"):Null { 65 | if (root == null) { 66 | throw "no root component"; 67 | } 68 | 69 | return root.findComponent(criteria, type, recursive, searchType); 70 | } 71 | 72 | public function findComponents(styleName:String = null, type:Class = null, maxDepth:Int = 5):Array { 73 | if (root == null) { 74 | throw "no root component"; 75 | } 76 | 77 | return root.findComponents(styleName, type, maxDepth); 78 | } 79 | 80 | public function findAncestor(criteria:String = null, type:Class = null, searchType:String = "id"):Null { 81 | if (root == null) { 82 | throw "no root component"; 83 | } 84 | 85 | return root.findAncestor(criteria, type, searchType); 86 | } 87 | 88 | public function findComponentsUnderPoint(screenX:Float, screenY:Float, type:Class = null):Array { 89 | if (root == null) { 90 | throw "no root component"; 91 | } 92 | 93 | return root.findComponentsUnderPoint(screenX, screenY, type); 94 | } 95 | 96 | public function dispatch(event:T) { 97 | if (root == null) { 98 | throw "no root component"; 99 | } 100 | 101 | root.dispatch(event); 102 | } 103 | 104 | public function registerEvent(type:EventType, listener:T->Void, priority:Int = 0) { 105 | if (root == null) { 106 | throw "no root component"; 107 | } 108 | 109 | root.registerEvent(type, listener, priority); 110 | } 111 | 112 | public function show() { 113 | if (root == null) { 114 | throw "no root component"; 115 | } 116 | root.show(); 117 | } 118 | 119 | public function hide() { 120 | if (root == null) { 121 | throw "no root component"; 122 | } 123 | root.hide(); 124 | } 125 | 126 | public override function destroy() { 127 | super.destroy(); 128 | root = null; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIRuntimeState.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import haxe.ui.core.Component; 4 | import haxe.ui.core.Screen; 5 | import haxe.ui.events.UIEvent; 6 | import haxe.ui.events.EventType; 7 | import haxe.ui.backend.flixel.UIRTTITools.*; 8 | 9 | using StringTools; 10 | 11 | @:rtti 12 | class UIRuntimeState extends UIStateBase { // uses rtti to "build" a class with a similar experience to using macros 13 | public var root:Component; 14 | 15 | public function new() { 16 | super(); 17 | } 18 | 19 | public override function create() { 20 | var rtti = haxe.rtti.Rtti.getRtti(Type.getClass(this)); 21 | root = buildViaRTTI(rtti); 22 | linkViaRTTI(rtti, this, root); 23 | if (root != null) { 24 | root.registerEvent(UIEvent.READY, (_) -> { 25 | onReady(); 26 | }); 27 | Screen.instance.addComponent(root); 28 | } 29 | super.create(); 30 | } 31 | 32 | private function onReady() { 33 | } 34 | 35 | ///////////////////////////////////////////////////////////////////////////////////////////////// 36 | // util functions 37 | ///////////////////////////////////////////////////////////////////////////////////////////////// 38 | public function addComponent(child:Component):Component { 39 | if (root == null) { 40 | throw "no root component"; 41 | } 42 | 43 | return root.addComponent(child); 44 | } 45 | 46 | public function removeComponent(child:Component):Component { 47 | if (root == null) { 48 | throw "no root component"; 49 | } 50 | 51 | return root.removeComponent(child); 52 | } 53 | 54 | public function findComponent(criteria:String = null, type:Class = null, recursive:Null = null, searchType:String = "id"):Null { 55 | if (root == null) { 56 | throw "no root component"; 57 | } 58 | 59 | return root.findComponent(criteria, type, recursive, searchType); 60 | } 61 | 62 | public function findComponents(styleName:String = null, type:Class = null, maxDepth:Int = 5):Array { 63 | if (root == null) { 64 | throw "no root component"; 65 | } 66 | 67 | return root.findComponents(styleName, type, maxDepth); 68 | } 69 | 70 | public function findAncestor(criteria:String = null, type:Class = null, searchType:String = "id"):Null { 71 | if (root == null) { 72 | throw "no root component"; 73 | } 74 | 75 | return root.findAncestor(criteria, type, searchType); 76 | } 77 | 78 | public function findComponentsUnderPoint(screenX:Float, screenY:Float, type:Class = null):Array { 79 | if (root == null) { 80 | throw "no root component"; 81 | } 82 | 83 | return root.findComponentsUnderPoint(screenX, screenY, type); 84 | } 85 | 86 | public function dispatch(event:T) { 87 | if (root == null) { 88 | throw "no root component"; 89 | } 90 | 91 | root.dispatch(event); 92 | } 93 | 94 | public function registerEvent(type:EventType, listener:T->Void, priority:Int = 0) { 95 | if (root == null) { 96 | throw "no root component"; 97 | } 98 | 99 | root.registerEvent(type, listener, priority); 100 | } 101 | 102 | public function show() { 103 | if (root == null) { 104 | throw "no root component"; 105 | } 106 | root.show(); 107 | } 108 | 109 | public function hide() { 110 | if (root == null) { 111 | throw "no root component"; 112 | } 113 | root.hide(); 114 | } 115 | 116 | public override function destroy() { 117 | if (root != null) { 118 | Screen.instance.removeComponent(root); 119 | } 120 | super.destroy(); 121 | root = null; 122 | } 123 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIRuntimeSubState.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import haxe.ui.core.Component; 4 | import haxe.ui.core.Screen; 5 | import haxe.ui.events.UIEvent; 6 | import haxe.ui.events.EventType; 7 | import haxe.ui.backend.flixel.UIRTTITools.*; 8 | 9 | using StringTools; 10 | 11 | @:rtti 12 | class UIRuntimeSubState extends UISubStateBase { // uses rtti to "build" a class with a similar experience to using macros 13 | public var root:Component; 14 | 15 | public function new() { 16 | super(); 17 | } 18 | 19 | public override function create() { 20 | var rtti = haxe.rtti.Rtti.getRtti(Type.getClass(this)); 21 | root = buildViaRTTI(rtti); 22 | linkViaRTTI(rtti, this, root); 23 | if (root != null) { 24 | root.registerEvent(UIEvent.READY, (_) -> { 25 | onReady(); 26 | }); 27 | Screen.instance.addComponent(root); 28 | } 29 | super.create(); 30 | } 31 | 32 | private function onReady() { 33 | } 34 | 35 | ///////////////////////////////////////////////////////////////////////////////////////////////// 36 | // util functions 37 | ///////////////////////////////////////////////////////////////////////////////////////////////// 38 | public function addComponent(child:Component):Component { 39 | if (root == null) { 40 | throw "no root component"; 41 | } 42 | 43 | return root.addComponent(child); 44 | } 45 | 46 | public function removeComponent(child:Component):Component { 47 | if (root == null) { 48 | throw "no root component"; 49 | } 50 | 51 | return root.removeComponent(child); 52 | } 53 | 54 | public function findComponent(criteria:String = null, type:Class = null, recursive:Null = null, searchType:String = "id"):Null { 55 | if (root == null) { 56 | throw "no root component"; 57 | } 58 | 59 | return root.findComponent(criteria, type, recursive, searchType); 60 | } 61 | 62 | public function findComponents(styleName:String = null, type:Class = null, maxDepth:Int = 5):Array { 63 | if (root == null) { 64 | throw "no root component"; 65 | } 66 | 67 | return root.findComponents(styleName, type, maxDepth); 68 | } 69 | 70 | public function findAncestor(criteria:String = null, type:Class = null, searchType:String = "id"):Null { 71 | if (root == null) { 72 | throw "no root component"; 73 | } 74 | 75 | return root.findAncestor(criteria, type, searchType); 76 | } 77 | 78 | public function findComponentsUnderPoint(screenX:Float, screenY:Float, type:Class = null):Array { 79 | if (root == null) { 80 | throw "no root component"; 81 | } 82 | 83 | return root.findComponentsUnderPoint(screenX, screenY, type); 84 | } 85 | 86 | public function dispatch(event:T) { 87 | if (root == null) { 88 | throw "no root component"; 89 | } 90 | 91 | root.dispatch(event); 92 | } 93 | 94 | public function registerEvent(type:EventType, listener:T->Void, priority:Int = 0) { 95 | if (root == null) { 96 | throw "no root component"; 97 | } 98 | 99 | root.registerEvent(type, listener, priority); 100 | } 101 | 102 | public function show() { 103 | if (root == null) { 104 | throw "no root component"; 105 | } 106 | root.show(); 107 | } 108 | 109 | public function hide() { 110 | if (root == null) { 111 | throw "no root component"; 112 | } 113 | root.hide(); 114 | } 115 | 116 | public override function destroy() { 117 | if (root != null) { 118 | Screen.instance.removeComponent(root); 119 | } 120 | super.destroy(); 121 | root = null; 122 | } 123 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIState.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import haxe.ui.containers.Box; 4 | import haxe.ui.core.Component; 5 | import haxe.ui.events.UIEvent; 6 | import haxe.ui.layouts.LayoutFactory; 7 | 8 | @:autoBuild(haxe.ui.macros.Macros.buildBehaviours()) 9 | @:autoBuild(haxe.ui.macros.Macros.build()) 10 | @:autoBuild(haxe.ui.backend.flixel.macros.UIStateMacro.checkDefine()) 11 | class UIState extends UIStateBase { // must use -D haxeui_dont_impose_base_class 12 | public var bindingRoot:Bool = false; 13 | 14 | private var root:Box = new Box(); // root component is always a box for now, since there is no nice / easy way to get the root node info from the macro 15 | 16 | public function new() { 17 | super(); 18 | } 19 | 20 | private function applyRootLayout(l:String) { 21 | if (l == "vbox") { 22 | root.layout = LayoutFactory.createFromName("vertical"); 23 | } else if (l == "hbox") { 24 | root.layout = LayoutFactory.createFromName("horizontal"); 25 | } 26 | } 27 | 28 | public override function create() { 29 | super.create(); 30 | root.registerEvent(UIEvent.READY, (_) -> { 31 | onReady(); 32 | }); 33 | add(root); 34 | } 35 | 36 | private function onReady() { 37 | } 38 | 39 | public function validateNow() { 40 | root.validateNow(); 41 | } 42 | 43 | private function registerBehaviours() { 44 | } 45 | 46 | public function addComponent(child:Component):Component { 47 | return root.addComponent(child); 48 | } 49 | 50 | public var width(get, set):Float; 51 | private function get_width():Float { 52 | return root.width; 53 | } 54 | private function set_width(value:Float):Float { 55 | root.width = value; 56 | return value; 57 | } 58 | 59 | public var percentWidth(get, set):Float; 60 | private function get_percentWidth():Float { 61 | return root.percentWidth; 62 | } 63 | private function set_percentWidth(value:Float):Float { 64 | root.percentWidth = value; 65 | return value; 66 | } 67 | 68 | public var height(get, set):Float; 69 | private function get_height():Float { 70 | return root.height ; 71 | } 72 | private function set_height(value:Float):Float { 73 | root.height = value; 74 | return value; 75 | } 76 | 77 | public var percentHeight(get, set):Float; 78 | private function get_percentHeight():Float { 79 | return root.percentHeight ; 80 | } 81 | private function set_percentHeight(value:Float):Float { 82 | root.percentHeight = value; 83 | return value; 84 | } 85 | 86 | public var styleString(get, set):String; 87 | private function get_styleString():String { 88 | return root.styleString ; 89 | } 90 | private function set_styleString(value:String):String { 91 | root.styleString = value; 92 | return value; 93 | } 94 | 95 | public function show() { 96 | root.show(); 97 | } 98 | 99 | public function hide() { 100 | root.hide(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UIStateBase.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | typedef UIStateBase = flixel.FlxState; -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UISubState.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | import haxe.ui.layouts.LayoutFactory; 4 | import haxe.ui.containers.Box; 5 | import haxe.ui.core.Component; 6 | import haxe.ui.events.UIEvent; 7 | 8 | @:autoBuild(haxe.ui.macros.Macros.buildBehaviours()) 9 | @:autoBuild(haxe.ui.macros.Macros.build()) 10 | @:autoBuild(haxe.ui.backend.flixel.macros.UIStateMacro.checkDefine()) 11 | class UISubState extends UISubStateBase { // must use -D haxeui_dont_impose_base_class 12 | public var bindingRoot:Bool = false; 13 | 14 | private var root:Box = new Box(); // root component is always a box for now, since there is no nice / easy way to get the root node info from the macro 15 | 16 | public function new() { 17 | super(); 18 | } 19 | 20 | private function applyRootLayout(l:String) { 21 | if (l == "vbox") { 22 | root.layout = LayoutFactory.createFromName("vertical"); 23 | } else if (l == "hbox") { 24 | root.layout = LayoutFactory.createFromName("horizontal"); 25 | } 26 | } 27 | 28 | public override function create() { 29 | super.create(); 30 | root.registerEvent(UIEvent.READY, (_) -> { 31 | onReady(); 32 | }); 33 | add(root); 34 | } 35 | 36 | private function onReady() { 37 | } 38 | 39 | public function validateNow() { 40 | root.validateNow(); 41 | } 42 | 43 | private function registerBehaviours() { 44 | } 45 | 46 | public function addComponent(child:Component):Component { 47 | return root.addComponent(child); 48 | } 49 | 50 | public var width(get, set):Float; 51 | private function get_width():Float { 52 | return root.width; 53 | } 54 | private function set_width(value:Float):Float { 55 | root.width = value; 56 | return value; 57 | } 58 | 59 | public var percentWidth(get, set):Float; 60 | private function get_percentWidth():Float { 61 | return root.percentWidth; 62 | } 63 | private function set_percentWidth(value:Float):Float { 64 | root.percentWidth = value; 65 | return value; 66 | } 67 | 68 | public var height(get, set):Float; 69 | private function get_height():Float { 70 | return root.height ; 71 | } 72 | private function set_height(value:Float):Float { 73 | root.height = value; 74 | return value; 75 | } 76 | 77 | public var percentHeight(get, set):Float; 78 | private function get_percentHeight():Float { 79 | return root.percentHeight ; 80 | } 81 | private function set_percentHeight(value:Float):Float { 82 | root.percentHeight = value; 83 | return value; 84 | } 85 | 86 | public var styleString(get, set):String; 87 | private function get_styleString():String { 88 | return root.styleString ; 89 | } 90 | private function set_styleString(value:String):String { 91 | root.styleString = value; 92 | return value; 93 | } 94 | 95 | public function show() { 96 | root.show(); 97 | } 98 | 99 | public function hide() { 100 | root.hide(); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/UISubStateBase.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel; 2 | 3 | typedef UISubStateBase = flixel.FlxSubState; -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/_module/styles/default/cursors/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxeui/haxeui-flixel/100f2c96beab619cfe72c567a058c41c71e3e998/haxe/ui/backend/flixel/_module/styles/default/cursors/pointer.png -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/_module/styles/default/main.css: -------------------------------------------------------------------------------- 1 | .label { 2 | font-name: "flixel/fonts/nokiafc22.ttf"; 3 | font-size: 8px; 4 | } 5 | 6 | .textfield, .textarea { 7 | font-name: "flixel/fonts/nokiafc22.ttf"; 8 | font-size: 8px; 9 | } 10 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/components/SparrowPlayer.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel.components; 2 | 3 | import flixel.FlxSprite; 4 | import flixel.graphics.frames.FlxAtlasFrames; 5 | import flixel.graphics.frames.FlxFramesCollection; 6 | import haxe.ui.containers.Box; 7 | import haxe.ui.core.Component; 8 | import haxe.ui.core.IDataComponent; 9 | import haxe.ui.data.DataSource; 10 | import haxe.ui.events.AnimationEvent; 11 | import haxe.ui.geom.Size; 12 | import haxe.ui.layouts.DefaultLayout; 13 | import openfl.Assets; 14 | 15 | private typedef AnimationInfo = { 16 | var name:String; 17 | var prefix:String; 18 | var frameRate:Null; // default 30 19 | var looped:Null; // default true 20 | var flipX:Null; // default false 21 | var flipY:Null; // default false 22 | } 23 | 24 | /* 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | */ 34 | 35 | @:composite(Layout) 36 | class SparrowPlayer extends Box implements IDataComponent { 37 | private var sprite:FlxSprite; 38 | 39 | public function new() { 40 | super(); 41 | sprite = new FlxSprite(1, 1); 42 | add(sprite); 43 | } 44 | 45 | private var _xmlFile:String; 46 | public var xmlFile(get, set):String; 47 | private function get_xmlFile():String { 48 | return _xmlFile; 49 | } 50 | private function set_xmlFile(value:String):String { 51 | _xmlFile = value; 52 | loadAnimation(_xmlFile, _pngFile); 53 | return value; 54 | } 55 | 56 | private var _pngFile:String; 57 | public var pngFile(get, set):String; 58 | private function get_pngFile():String { 59 | return _pngFile; 60 | } 61 | private function set_pngFile(value:String):String { 62 | _pngFile = value; 63 | loadAnimation(_xmlFile, _pngFile); 64 | return value; 65 | } 66 | 67 | private var _dataSource:DataSource = null; 68 | private override function get_dataSource():DataSource { 69 | return _dataSource; 70 | } 71 | private override function set_dataSource(value:DataSource):DataSource { 72 | _dataSource = value; 73 | for (i in 0..._dataSource.size) { 74 | var item:Dynamic = _dataSource.get(i); 75 | if (item.frameRate == null) { 76 | item.frameRate = 30; 77 | } 78 | if (item.looped == null) { 79 | item.looped = true; 80 | } 81 | if (item.flipX == null) { 82 | item.flipX = false; 83 | } 84 | if (item.flipY == null) { 85 | item.flipY = false; 86 | } 87 | addAnimationByPrefix(item.name, item.prefix, Std.parseInt(item.frameRate), Std.string(item.looped) == "true", Std.string(item.flipX) == "true", Std.string(item.flipY) == "true"); 88 | } 89 | return value; 90 | } 91 | 92 | private var _cachedAnimationName:String = null; // component might not have an animation yet, so we'll cache it if thats the case 93 | private var _animationName:String = null; 94 | public var animationName(get, set):String; 95 | private function get_animationName() { 96 | return _animationName; 97 | } 98 | private function set_animationName(value:String):String { 99 | if (!_animationLoaded) { 100 | _cachedAnimationName = value; 101 | return value; 102 | } 103 | 104 | if (sprite.animation.getByName(value) != null) { 105 | _cachedAnimationName = null; 106 | _animationName = value; 107 | sprite.animation.play(_animationName); 108 | invalidateComponentLayout(); 109 | 110 | if (hasEvent(AnimationEvent.START)) { 111 | dispatch(new AnimationEvent(AnimationEvent.START)); 112 | } else { 113 | _redispatchStart = true; 114 | } 115 | } else { 116 | _cachedAnimationName = value; 117 | } 118 | return value; 119 | } 120 | 121 | private var _redispatchLoaded:Bool = false; // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... needs thinking about, is it smart to "collect and redispatch"? Not sure 122 | private var _redispatchStart:Bool = false; // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... needs thinking about, is it smart to "collect and redispatch"? Not sure 123 | public override function onReady() { 124 | super.onReady(); 125 | if (_cachedAnimationName != null) { 126 | animationName = _cachedAnimationName; 127 | } 128 | invalidateComponentLayout(); 129 | 130 | if (_redispatchLoaded) { 131 | _redispatchLoaded = false; 132 | dispatch(new AnimationEvent(AnimationEvent.LOADED)); 133 | } 134 | 135 | if (_redispatchStart) { 136 | _redispatchStart = false; 137 | dispatch(new AnimationEvent(AnimationEvent.START)); 138 | } 139 | } 140 | 141 | private var _cachedAnimationPrefixes:Array = []; // component might not have an animation yet, so we'll cache it if thats the case 142 | public function addAnimationByPrefix(name:String, prefix:String, frameRate:Int = 30, looped:Bool = true, flipX:Bool = false, flipY:Bool = false) { 143 | if (!_animationLoaded) { 144 | if (_cachedAnimationPrefixes == null) { 145 | _cachedAnimationPrefixes = []; 146 | } 147 | _cachedAnimationPrefixes.push({ 148 | name: name, 149 | prefix: prefix, 150 | frameRate: frameRate, 151 | looped: looped, 152 | flipX: flipX, 153 | flipY: flipY 154 | }); 155 | return; 156 | } 157 | 158 | sprite.animation.addByPrefix(name, prefix, frameRate, looped, flipX, flipY); 159 | if (_cachedAnimationName != null) { 160 | animationName = _cachedAnimationName; 161 | } 162 | } 163 | 164 | private var _animationLoaded:Bool = false; 165 | public function loadAnimation(xml:String, png:String) { 166 | if (xml == null || png == null) { 167 | return; 168 | } 169 | 170 | var frames:FlxFramesCollection = FlxAtlasFrames.fromSparrow(png, Assets.getText(xml)); 171 | sprite.frames = frames; 172 | #if (flixel >= "5.9.0") 173 | if (!sprite.animation.onFrameChange.has(onFrame)) { 174 | sprite.animation.onFrameChange.add(onFrame); 175 | } 176 | if (!sprite.animation.onFinish.has(onFinish)) { 177 | sprite.animation.onFinish.add(onFinish); 178 | } 179 | #else 180 | if (sprite.animation.callback == null) { 181 | sprite.animation.callback = onFrame; 182 | } 183 | if (sprite.animation.finishCallback == null) { 184 | sprite.animation.finishCallback = onFinish; 185 | } 186 | #end 187 | invalidateComponentLayout(); 188 | _animationLoaded = true; 189 | 190 | if (_cachedAnimationPrefixes != null) { 191 | while (_cachedAnimationPrefixes.length > 0) { 192 | var item = _cachedAnimationPrefixes.shift(); 193 | addAnimationByPrefix(item.name, item.prefix, item.frameRate, item.looped, item.flipX, item.flipY); 194 | } 195 | _cachedAnimationPrefixes = null; 196 | } 197 | 198 | if (_cachedAnimationName != null) { 199 | animationName = _cachedAnimationName; 200 | } 201 | 202 | if (hasEvent(AnimationEvent.LOADED)) { 203 | dispatch(new AnimationEvent(AnimationEvent.LOADED)); 204 | } else { 205 | _redispatchLoaded = true; 206 | } 207 | } 208 | 209 | public var currentFrameCount(get, null):Int; 210 | private function get_currentFrameCount() { 211 | if (sprite.animation == null || sprite.animation.curAnim == null) { 212 | return 0; 213 | } 214 | 215 | return sprite.animation.curAnim.numFrames; 216 | } 217 | 218 | public var currentFrameNumber(get, null):Int; 219 | private function get_currentFrameNumber() { 220 | if (sprite.animation == null || sprite.animation.curAnim == null) { 221 | return 0; 222 | } 223 | 224 | return sprite.animation.curAnim.curFrame; 225 | } 226 | 227 | public var frameNames(get, null):Array; 228 | private function get_frameNames():Array { 229 | if (sprite.animation == null) { 230 | return []; 231 | } 232 | 233 | return sprite.animation.getNameList(); 234 | } 235 | 236 | private function onFrame(name:String, frameNumber:Int, frameIndex:Int) { 237 | dispatch(new AnimationEvent(AnimationEvent.FRAME)); 238 | } 239 | 240 | private function onFinish(name:String) { 241 | dispatch(new AnimationEvent(AnimationEvent.END)); 242 | } 243 | 244 | /* 245 | // lets override a few flixel / haxeui-flixel specifics to get things nice and smooth 246 | public override function update(elapsed:Float) { 247 | super.update(elapsed); 248 | // these lines make the clipping _much_ better but im not sure if its smart 249 | var cc = findClipComponent(); 250 | if (cc != null) { 251 | var cr = cc.componentClipRect; 252 | var rc = FlxRect.get(screenLeft + cr.left, screenTop + cr.top, cr.width, cr.height); 253 | this.clipRect = rc; 254 | rc.put(); 255 | } 256 | } 257 | */ 258 | 259 | private override function repositionChildren() { 260 | super.repositionChildren(); 261 | sprite.x = this.cachedScreenX; 262 | sprite.y = this.cachedScreenY; 263 | } 264 | } 265 | 266 | @:access(haxe.ui.backend.flixel.components.SparrowPlayer) 267 | private class Layout extends DefaultLayout { 268 | public override function resizeChildren() { 269 | super.resizeChildren(); 270 | 271 | var player = cast(_component, SparrowPlayer); 272 | var sprite = player.sprite; 273 | if (sprite == null) { 274 | return super.resizeChildren(); 275 | } 276 | 277 | sprite.origin.set(0, 0); 278 | sprite.setGraphicSize(Std.int(innerWidth), Std.int(innerHeight)); 279 | } 280 | 281 | public override function calcAutoSize(exclusions:Array = null):Size { 282 | var player = cast(_component, SparrowPlayer); 283 | var sprite = player.sprite; 284 | if (sprite == null) { 285 | return super.calcAutoSize(exclusions); 286 | } 287 | var size = new Size(); 288 | size.width = sprite.frameWidth + paddingLeft + paddingRight; 289 | size.height = sprite.frameHeight + paddingTop + paddingBottom; 290 | return size; 291 | } 292 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/components/SpriteWrapper.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel.components; 2 | 3 | import flixel.FlxSprite; 4 | import flixel.math.FlxRect; 5 | import haxe.ui.containers.Box; 6 | import haxe.ui.core.Component; 7 | import haxe.ui.geom.Size; 8 | import haxe.ui.layouts.DefaultLayout; 9 | 10 | @:composite(Layout) 11 | class SpriteWrapper extends Box { 12 | public var spriteOffsetX:Float = 0; 13 | public var spriteOffsetY:Float = 0; 14 | 15 | private var _sprite:FlxSprite = null; 16 | public var sprite(get, set):FlxSprite; 17 | private function get_sprite():FlxSprite { 18 | return _sprite; 19 | } 20 | private function set_sprite(value:FlxSprite):FlxSprite { 21 | if (_sprite != null) { 22 | remove(_sprite); 23 | } 24 | _sprite = value; 25 | add(_sprite); 26 | invalidateComponentLayout(); 27 | return value; 28 | } 29 | 30 | private override function repositionChildren() { 31 | super.repositionChildren(); 32 | if (sprite != null) { 33 | sprite.x = spriteOffsetX + this.cachedScreenX; 34 | sprite.y = spriteOffsetY + this.cachedScreenY; 35 | } 36 | } 37 | } 38 | 39 | @:access(haxe.ui.backend.flixel.components.SpriteWrapper) 40 | private class Layout extends DefaultLayout { 41 | public override function resizeChildren() { 42 | super.resizeChildren(); 43 | 44 | var wrapper = cast(_component, SpriteWrapper); 45 | var sprite = wrapper.sprite; 46 | if (sprite == null) { 47 | return super.resizeChildren(); 48 | } 49 | 50 | sprite.origin.set(0, 0); 51 | sprite.setGraphicSize(Std.int(innerWidth), Std.int(innerHeight)); 52 | } 53 | 54 | public override function calcAutoSize(exclusions:Array = null):Size { 55 | var wrapper = cast(_component, SpriteWrapper); 56 | var sprite = wrapper.sprite; 57 | if (sprite == null) { 58 | return super.calcAutoSize(exclusions); 59 | } 60 | var size = new Size(); 61 | size.width = sprite.width + paddingLeft + paddingRight; 62 | size.height = sprite.height + paddingTop + paddingBottom; 63 | return size; 64 | } 65 | } -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/macros/UIStateMacro.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel.macros; 2 | 3 | #if macro 4 | import haxe.macro.Expr.Field; 5 | import haxe.macro.Context; 6 | 7 | /** 8 | * Macro which makes an error at compile time if the user attemps to use `UIState` or `UISubState` without having `haxeui_dont_impose_base_class` defined. 9 | */ 10 | class UIStateMacro { 11 | public static function checkDefine():Array { 12 | var localClass:String = Context.getLocalClass().get().name; 13 | 14 | if (!Context.defined("haxeui_dont_impose_base_class")) 15 | Context.error("You must define haxeui_dont_impose_base_class in order to use " + findUIClass() + " (for class " + localClass + ")", Context.currentPos()); 16 | 17 | return Context.getBuildFields(); 18 | } 19 | 20 | static function findUIClass():String { 21 | var cls = Context.getLocalClass().get(); 22 | 23 | while (!isUIState(cls.name)) { 24 | if (cls.superClass == null) 25 | break; 26 | 27 | cls = cls.superClass.t.get(); 28 | } 29 | 30 | if (!isUIState(cls.name)) { 31 | // shouldn't happen, but just in case 32 | return "UI states"; 33 | } 34 | 35 | return cls.name; 36 | } 37 | 38 | static function isUIState(name:String):Bool { 39 | return name == "UIState" || name == "UISubState"; 40 | } 41 | } 42 | #end 43 | -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/textinputs/FlxTextInput.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel.textinputs; 2 | 3 | import flixel.FlxSprite; 4 | import flixel.text.FlxInputText; 5 | import flixel.util.FlxColor; 6 | import haxe.ui.backend.TextInputImpl.TextInputEvent; 7 | import haxe.ui.core.Component; 8 | import openfl.events.Event; 9 | import openfl.events.KeyboardEvent; 10 | 11 | #if (flixel >= "5.9.0") 12 | class FlxTextInput extends TextBase { 13 | public static var USE_ON_ADDED:Bool = false; 14 | public static var USE_ON_REMOVED:Bool = false; 15 | 16 | private static inline var PADDING_X:Int = 4; 17 | private static inline var PADDING_Y:Int = 2; 18 | 19 | private var tf:FlxInputText; 20 | 21 | public function new() { 22 | super(); 23 | tf = new FlxInputText(0, 0, 0, null, 8, FlxColor.BLACK, FlxColor.TRANSPARENT); 24 | tf.onTextChange.add(onInternalChange); 25 | tf.onScrollChange.add(onScroll); 26 | tf.pixelPerfectRender = true; 27 | _inputData.vscrollPageStep = 1; 28 | _inputData.vscrollNativeWheel = true; 29 | } 30 | 31 | public override function focus() { 32 | tf.startFocus(); 33 | } 34 | 35 | public override function blur() { 36 | tf.endFocus(); 37 | } 38 | 39 | public function attach() { 40 | } 41 | 42 | public var visible(get, set):Bool; 43 | private function get_visible():Bool { 44 | return tf.visible; 45 | } 46 | private function set_visible(value:Bool):Bool { 47 | tf.active = tf.visible = value; // text input shouldn't be active if it's hidden 48 | return value; 49 | } 50 | 51 | public var x(get, set):Float; 52 | private function get_x():Float { 53 | return tf.x; 54 | } 55 | private function set_x(value:Float):Float { 56 | tf.x = value; 57 | return value; 58 | } 59 | 60 | public var y(get, set):Float; 61 | private function get_y():Float { 62 | return tf.y; 63 | } 64 | private function set_y(value:Float):Float { 65 | tf.y = value; 66 | return value; 67 | } 68 | 69 | public var scaleX(get, set):Float; 70 | private function get_scaleX():Float { 71 | return tf.scale.x; 72 | } 73 | private function set_scaleX(value:Float):Float { 74 | // do nothing 75 | return value; 76 | } 77 | 78 | public var scaleY(get, set):Float; 79 | private function get_scaleY():Float { 80 | return tf.scale.y; 81 | } 82 | private function set_scaleY(value:Float):Float { 83 | // do nothing 84 | return value; 85 | } 86 | 87 | public var alpha(get, set):Float; 88 | private function get_alpha():Float { 89 | return tf.alpha; 90 | } 91 | private function set_alpha(value:Float):Float { 92 | tf.alpha = value; 93 | return value; 94 | } 95 | 96 | private override function validateData() { 97 | if (_text != null) { 98 | if (_dataSource == null) { 99 | tf.text = normalizeText(_text); 100 | } 101 | } 102 | 103 | var hscrollValue = Std.int(_inputData.hscrollPos); 104 | if (tf.scrollH != hscrollValue) { 105 | tf.scrollH = hscrollValue; 106 | } 107 | 108 | var vscrollValue = Std.int(_inputData.vscrollPos) + 1; 109 | if (tf.scrollV != vscrollValue) { 110 | tf.scrollV = vscrollValue; 111 | } 112 | } 113 | 114 | private function normalizeText(text:String):String { 115 | text = StringTools.replace(text, "\\n", "\n"); 116 | return text; 117 | } 118 | 119 | private override function measureText() { 120 | //tf.width = _width * Toolkit.scaleX; 121 | _textHeight = tf.textField.textHeight; 122 | if (_textHeight == 0) { 123 | var tmpText:String = tf.text; 124 | tf.text = "|"; 125 | _textHeight = tf.textField.textHeight; 126 | tf.text = tmpText; 127 | } 128 | 129 | _textWidth = Math.round(_textWidth) / Toolkit.scaleX; 130 | _textHeight = Math.round(_textHeight) / Toolkit.scaleY; 131 | 132 | ////////////////////////////////////////////////////////////////////////////// 133 | 134 | _inputData.hscrollMax = tf.maxScrollH; 135 | // see below 136 | _inputData.hscrollPageSize = (_width * _inputData.hscrollMax) / _textWidth; 137 | 138 | _inputData.vscrollMax = tf.maxScrollV - 1; 139 | _inputData.vscrollPageSize = (_height * _inputData.vscrollMax) / _textHeight; 140 | } 141 | 142 | private override function validateStyle():Bool { 143 | var measureTextRequired:Bool = false; 144 | 145 | if (_textStyle != null) { 146 | var textAlign = (_textStyle.textAlign != null ? _textStyle.textAlign : "left"); 147 | if (tf.alignment != textAlign) { 148 | tf.alignment = textAlign; 149 | } 150 | 151 | var fontSizeValue = Std.int(_textStyle.fontSize); 152 | if (tf.size != fontSizeValue) { 153 | tf.size = Std.int(fontSizeValue * Toolkit.scale); 154 | 155 | measureTextRequired = true; 156 | } 157 | 158 | if (_fontInfo != null && tf.font != _fontInfo.data) { 159 | tf.font = _fontInfo.data; 160 | measureTextRequired = true; 161 | } 162 | 163 | if (tf.color != _textStyle.color) { 164 | tf.color = _textStyle.color; 165 | } 166 | 167 | var fontBold = (_textStyle.fontBold != null ? _textStyle.fontBold : false); 168 | if (tf.bold != fontBold) { 169 | tf.bold = fontBold; 170 | measureTextRequired = true; 171 | } 172 | 173 | var fontItalic = (_textStyle.fontItalic != null ? _textStyle.fontItalic : false); 174 | if (tf.italic != fontItalic) { 175 | tf.italic = fontItalic; 176 | measureTextRequired = true; 177 | } 178 | } 179 | 180 | if (tf.wordWrap != _displayData.wordWrap) { 181 | tf.wordWrap = _displayData.wordWrap; 182 | measureTextRequired = true; 183 | } 184 | 185 | if (tf.multiline != _displayData.multiline) { 186 | tf.multiline = _displayData.multiline; 187 | // `multiline` only decides whether the user can add new lines, 188 | // so measuring the text is not required. 189 | } 190 | 191 | if (tf.passwordMode != _inputData.password) { 192 | tf.passwordMode = _inputData.password; 193 | measureTextRequired = true; 194 | } 195 | 196 | tf.editable = !parentComponent.disabled; 197 | 198 | return measureTextRequired; 199 | } 200 | 201 | private override function validatePosition() { 202 | _left = Math.round(_left * Toolkit.scaleX); 203 | _top = Math.round(_top * Toolkit.scaleY); 204 | } 205 | 206 | private override function validateDisplay() { 207 | if (_width <= 0 || _height <= 0) { 208 | return; 209 | } 210 | 211 | if (tf.width != _width * Toolkit.scaleX) { 212 | tf.width = _width * Toolkit.scaleX; 213 | tf.fieldWidth = tf.width; 214 | } 215 | 216 | if (tf.height != (_height + PADDING_Y) * Toolkit.scaleY) { 217 | tf.height = (_height + PADDING_Y) * Toolkit.scaleY; 218 | tf.fieldHeight = tf.height; 219 | } 220 | } 221 | 222 | private var _onMouseDown:TextInputEvent->Void = null; 223 | public var onMouseDown(null, set):TextInputEvent->Void; 224 | private function set_onMouseDown(value:TextInputEvent->Void):TextInputEvent->Void { 225 | if (_onMouseDown != null) { 226 | //tf.removeEventListener(openfl.events.MouseEvent.MOUSE_DOWN, __onTextInputMouseDownEvent); 227 | } 228 | _onMouseDown = value; 229 | if (_onMouseDown != null) { 230 | //tf.addEventListener(openfl.events.MouseEvent.MOUSE_DOWN, __onTextInputMouseDownEvent); 231 | } 232 | return value; 233 | } 234 | 235 | /* 236 | private function __onTextInputMouseDownEvent(event:openfl.events.MouseEvent) { 237 | if (_onMouseDown != null) { 238 | _onMouseDown({ 239 | type: event.type, 240 | stageX: event.stageX, 241 | stageY: event.stageY 242 | }); 243 | } 244 | } 245 | */ 246 | 247 | private var _onMouseUp:TextInputEvent->Void = null; 248 | public var onMouseUp(null, set):TextInputEvent->Void; 249 | private function set_onMouseUp(value:TextInputEvent->Void):TextInputEvent->Void { 250 | if (_onMouseUp != null) { 251 | //tf.removeEventListener(openfl.events.MouseEvent.MOUSE_UP, __onTextInputMouseUpEvent); 252 | } 253 | _onMouseUp = value; 254 | if (_onMouseUp != null) { 255 | //tf.addEventListener(openfl.events.MouseEvent.MOUSE_UP, __onTextInputMouseUpEvent); 256 | } 257 | return value; 258 | } 259 | 260 | /* 261 | private function __onTextInputMouseUpEvent(event:openfl.events.MouseEvent) { 262 | if (_onMouseUp != null) { 263 | _onMouseUp({ 264 | type: event.type, 265 | stageX: event.stageX, 266 | stageY: event.stageY 267 | }); 268 | } 269 | } 270 | */ 271 | 272 | public function equals(sprite:FlxSprite):Bool { 273 | return sprite == tf; 274 | } 275 | 276 | private var _onClick:TextInputEvent->Void = null; 277 | public var onClick(null, set):TextInputEvent->Void; 278 | private function set_onClick(value:TextInputEvent->Void):TextInputEvent->Void { 279 | if (_onClick != null) { 280 | //tf.removeEventListener(openfl.events.MouseEvent.CLICK, __onTextInputClickEvent); 281 | } 282 | _onClick = value; 283 | if (_onClick != null) { 284 | //tf.addEventListener(openfl.events.MouseEvent.CLICK, __onTextInputClickEvent); 285 | } 286 | return value; 287 | } 288 | 289 | /* 290 | private function __onTextInputClickEvent(event:openfl.events.MouseEvent) { 291 | if (_onClick != null) { 292 | _onClick({ 293 | type: event.type, 294 | stageX: event.stageX, 295 | stageY: event.stageY 296 | }); 297 | } 298 | } 299 | */ 300 | 301 | private var _onChange:TextInputEvent->Void = null; 302 | public var onChange(null, set):TextInputEvent->Void; 303 | private function set_onChange(value:TextInputEvent->Void):TextInputEvent->Void { 304 | if (_onChange != null) { 305 | tf.onTextChange.remove(__onTextInputChangeEvent); 306 | } 307 | _onChange = value; 308 | if (_onChange != null) { 309 | tf.onTextChange.add(__onTextInputChangeEvent); 310 | } 311 | return value; 312 | } 313 | 314 | private function __onTextInputChangeEvent(text:String, action:FlxInputTextChange) { 315 | if (_onChange != null) { 316 | _onChange({ 317 | type: "change", 318 | stageX: 0, 319 | stageY: 0 320 | }); 321 | } 322 | } 323 | 324 | private var _onKeyDown:KeyboardEvent->Void = null; 325 | public var onKeyDown(null, set):KeyboardEvent->Void; 326 | private function set_onKeyDown(value:KeyboardEvent->Void):KeyboardEvent->Void { 327 | if (_onKeyDown != null) { 328 | //tf.textField.removeEventListener(KeyboardEvent.KEY_DOWN, __onTextInputKeyDown); 329 | } 330 | _onKeyDown = value; 331 | if (_onKeyDown != null) { 332 | //tf.textField.addEventListener(KeyboardEvent.KEY_DOWN, __onTextInputKeyDown); 333 | } 334 | return value; 335 | } 336 | 337 | /* 338 | private function __onTextInputKeyDown(e:KeyboardEvent) { 339 | if (_onKeyDown != null) 340 | _onKeyDown(e); 341 | } 342 | */ 343 | 344 | private var _onKeyUp:KeyboardEvent->Void = null; 345 | public var onKeyUp(null, set):KeyboardEvent->Void; 346 | private function set_onKeyUp(value:KeyboardEvent->Void):KeyboardEvent->Void { 347 | if (_onKeyUp != null) { 348 | //tf.textField.removeEventListener(KeyboardEvent.KEY_UP, __onTextInputKeyUp); 349 | } 350 | _onKeyUp = value; 351 | if (_onKeyUp != null) { 352 | //tf.textField.addEventListener(KeyboardEvent.KEY_UP, __onTextInputKeyUp); 353 | } 354 | return value; 355 | } 356 | 357 | /* 358 | private function __onTextInputKeyUp(e:KeyboardEvent) { 359 | if (_onKeyUp != null) 360 | _onKeyUp(e); 361 | } 362 | */ 363 | 364 | private function onInternalChange(text:String, action:FlxInputTextChange) { 365 | _text = tf.text; 366 | 367 | measureText(); 368 | 369 | if (_inputData.onChangedCallback != null) { 370 | _inputData.onChangedCallback(); 371 | } 372 | } 373 | 374 | private function onScroll(scrollH:Int, scrollV:Int) { 375 | _inputData.hscrollPos = tf.scrollH; 376 | _inputData.vscrollPos = tf.scrollV - 1; 377 | 378 | if (_inputData.onScrollCallback != null) { 379 | _inputData.onScrollCallback(); 380 | } 381 | } 382 | 383 | public function update() { 384 | } 385 | 386 | public function addToComponent(component:Component) { 387 | //StateHelper.currentState.add(tf); 388 | component.add(tf); 389 | } 390 | 391 | public function destroy(component:Component) { 392 | tf.visible = false; 393 | component.remove(tf, true); 394 | tf.destroy(); 395 | tf = null; 396 | } 397 | } 398 | #end -------------------------------------------------------------------------------- /haxe/ui/backend/flixel/textinputs/OpenFLTextInput.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend.flixel.textinputs; 2 | 3 | import flixel.FlxG; 4 | import flixel.FlxSprite; 5 | import haxe.ui.Toolkit; 6 | import haxe.ui.backend.TextInputImpl.TextInputEvent; 7 | import haxe.ui.core.Component; 8 | import haxe.ui.core.Screen; 9 | import haxe.ui.events.UIEvent; 10 | import haxe.ui.geom.Rectangle; 11 | import openfl.events.Event; 12 | import openfl.events.KeyboardEvent; 13 | import openfl.text.TextField; 14 | import openfl.text.TextFieldAutoSize; 15 | import openfl.text.TextFieldType; 16 | import openfl.text.TextFormat; 17 | 18 | class OpenFLTextInput extends TextBase { 19 | public static var USE_ON_ADDED:Bool = true; 20 | public static var USE_ON_REMOVED:Bool = true; 21 | 22 | private var PADDING_X:Int = 4; 23 | private var PADDING_Y:Int = 0; 24 | 25 | public var tf:TextField; 26 | 27 | public function new() { 28 | super(); 29 | tf = new TextField(); 30 | tf.type = TextFieldType.INPUT; 31 | tf.selectable = true; 32 | tf.mouseEnabled = true; 33 | tf.autoSize = TextFieldAutoSize.NONE; 34 | tf.multiline = true; 35 | tf.wordWrap = true; 36 | tf.tabEnabled = false; 37 | //tf.stage.focus = null; 38 | tf.addEventListener(Event.CHANGE, onInternalChange); 39 | _inputData.vscrollPageStep = 1; 40 | _inputData.vscrollNativeWheel = true; 41 | } 42 | 43 | public override function focus() { 44 | if (tf.stage != null) { 45 | //tf.stage.focus = tf; 46 | } 47 | } 48 | 49 | public override function blur() { 50 | if (tf.stage != null) { 51 | //tf.stage.focus = null; 52 | } 53 | } 54 | 55 | public function attach() { 56 | } 57 | 58 | public var visible(get, set):Bool; 59 | private function get_visible():Bool { 60 | return tf.visible; 61 | } 62 | private function set_visible(value:Bool):Bool { 63 | tf.visible = value; 64 | return value; 65 | } 66 | 67 | public var x(get, set):Float; 68 | private function get_x():Float { 69 | return tf.x; 70 | } 71 | private function set_x(value:Float):Float { 72 | tf.x = value * FlxG.scaleMode.scale.x; 73 | return value; 74 | } 75 | 76 | public var y(get, set):Float; 77 | private function get_y():Float { 78 | return tf.y; 79 | } 80 | private function set_y(value:Float):Float { 81 | tf.y = value * FlxG.scaleMode.scale.y; 82 | return value; 83 | } 84 | 85 | public var scaleX(get, set):Float; 86 | private function get_scaleX():Float { 87 | return tf.scaleX; 88 | } 89 | private function set_scaleX(value:Float):Float { 90 | tf.scaleX = value; 91 | return value; 92 | } 93 | 94 | public var scaleY(get, set):Float; 95 | private function get_scaleY():Float { 96 | return tf.scaleY; 97 | } 98 | private function set_scaleY(value:Float):Float { 99 | tf.scaleY = value; 100 | return value; 101 | } 102 | 103 | public var alpha(get, set):Float; 104 | private function get_alpha():Float { 105 | return tf.alpha; 106 | } 107 | private function set_alpha(value:Float):Float { 108 | tf.alpha = value; 109 | return value; 110 | } 111 | 112 | 113 | private var _onMouseDown:TextInputEvent->Void = null; 114 | public var onMouseDown(null, set):TextInputEvent->Void; 115 | private function set_onMouseDown(value:TextInputEvent->Void):TextInputEvent->Void { 116 | if (_onMouseDown != null) { 117 | tf.removeEventListener(openfl.events.MouseEvent.MOUSE_DOWN, __onTextInputMouseDownEvent); 118 | } 119 | _onMouseDown = value; 120 | if (_onMouseDown != null) { 121 | tf.addEventListener(openfl.events.MouseEvent.MOUSE_DOWN, __onTextInputMouseDownEvent); 122 | } 123 | return value; 124 | } 125 | 126 | private function __onTextInputMouseDownEvent(event:openfl.events.MouseEvent) { 127 | if (_onMouseDown != null) { 128 | _onMouseDown({ 129 | type: event.type, 130 | stageX: event.stageX, 131 | stageY: event.stageY 132 | }); 133 | } 134 | } 135 | 136 | private var _onMouseUp:TextInputEvent->Void = null; 137 | public var onMouseUp(null, set):TextInputEvent->Void; 138 | private function set_onMouseUp(value:TextInputEvent->Void):TextInputEvent->Void { 139 | if (_onMouseUp != null) { 140 | tf.removeEventListener(openfl.events.MouseEvent.MOUSE_UP, __onTextInputMouseUpEvent); 141 | } 142 | _onMouseUp = value; 143 | if (_onMouseUp != null) { 144 | tf.addEventListener(openfl.events.MouseEvent.MOUSE_UP, __onTextInputMouseUpEvent); 145 | } 146 | return value; 147 | } 148 | 149 | private function __onTextInputMouseUpEvent(event:openfl.events.MouseEvent) { 150 | if (_onMouseUp != null) { 151 | _onMouseUp({ 152 | type: event.type, 153 | stageX: event.stageX, 154 | stageY: event.stageY 155 | }); 156 | } 157 | } 158 | 159 | private var _onClick:TextInputEvent->Void = null; 160 | public var onClick(null, set):TextInputEvent->Void; 161 | private function set_onClick(value:TextInputEvent->Void):TextInputEvent->Void { 162 | if (_onClick != null) { 163 | tf.removeEventListener(openfl.events.MouseEvent.CLICK, __onTextInputClickEvent); 164 | } 165 | _onClick = value; 166 | if (_onClick != null) { 167 | tf.addEventListener(openfl.events.MouseEvent.CLICK, __onTextInputClickEvent); 168 | } 169 | return value; 170 | } 171 | 172 | private function __onTextInputClickEvent(event:openfl.events.MouseEvent) { 173 | if (_onClick != null) { 174 | _onClick({ 175 | type: event.type, 176 | stageX: event.stageX, 177 | stageY: event.stageY 178 | }); 179 | } 180 | } 181 | 182 | private var _onChange:TextInputEvent->Void = null; 183 | public var onChange(null, set):TextInputEvent->Void; 184 | private function set_onChange(value:TextInputEvent->Void):TextInputEvent->Void { 185 | if (_onChange != null) { 186 | tf.removeEventListener(Event.CHANGE, __onTextInputChangeEvent); 187 | } 188 | _onChange = value; 189 | if (_onChange != null) { 190 | tf.addEventListener(Event.CHANGE, __onTextInputChangeEvent); 191 | } 192 | return value; 193 | } 194 | 195 | private function __onTextInputChangeEvent(event:Event) { 196 | if (_onChange != null) { 197 | _onChange({ 198 | type: event.type, 199 | stageX: 0, 200 | stageY: 0 201 | }); 202 | } 203 | } 204 | 205 | private var _onKeyDown:KeyboardEvent->Void = null; 206 | public var onKeyDown(null, set):KeyboardEvent->Void; 207 | private function set_onKeyDown(value:KeyboardEvent->Void):KeyboardEvent->Void { 208 | if (_onKeyDown != null) { 209 | tf.removeEventListener(KeyboardEvent.KEY_DOWN, __onTextInputKeyDown); 210 | } 211 | _onKeyDown = value; 212 | if (_onKeyDown != null) { 213 | tf.addEventListener(KeyboardEvent.KEY_DOWN, __onTextInputKeyDown); 214 | } 215 | return value; 216 | } 217 | 218 | private function __onTextInputKeyDown(e:KeyboardEvent) { 219 | if (_onKeyDown != null) 220 | _onKeyDown(e); 221 | } 222 | 223 | private var _onKeyUp:KeyboardEvent->Void = null; 224 | public var onKeyUp(null, set):KeyboardEvent->Void; 225 | private function set_onKeyUp(value:KeyboardEvent->Void):KeyboardEvent->Void { 226 | if (_onKeyUp != null) { 227 | tf.removeEventListener(KeyboardEvent.KEY_UP, __onTextInputKeyUp); 228 | } 229 | _onKeyUp = value; 230 | if (_onKeyUp != null) { 231 | tf.addEventListener(KeyboardEvent.KEY_UP, __onTextInputKeyUp); 232 | } 233 | return value; 234 | } 235 | 236 | private function __onTextInputKeyUp(e:KeyboardEvent) { 237 | if (_onKeyUp != null) 238 | _onKeyUp(e); 239 | } 240 | 241 | public function addToComponent(component:Component) { 242 | FlxG.addChildBelowMouse(tf, 0xffffff); 243 | } 244 | 245 | public function equals(sprite:FlxSprite):Bool { 246 | return false; 247 | } 248 | 249 | private var _parentHidden:Bool = false; 250 | public function update() { 251 | var ref = parentComponent; 252 | // TODO: perf? 253 | while (ref != null) { 254 | if (ref.hidden) { 255 | tf.visible = false; 256 | _parentHidden = true; 257 | break; 258 | } 259 | ref = ref.parentComponent; 260 | } 261 | 262 | if (_parentHidden == true) { 263 | return; 264 | } 265 | 266 | tf.visible = true; 267 | 268 | var x1 = tf.x; 269 | var y1 = tf.y; 270 | var x2 = tf.x + tf.textWidth; 271 | var y2 = tf.y + tf.textHeight; 272 | 273 | var rc = new Rectangle(tf.x, tf.y, tf.textWidth, tf.textHeight); 274 | 275 | var components:Array = []; 276 | var overlaps:Bool = false; 277 | var after = false; 278 | for (r in Screen.instance.rootComponents) { 279 | if (parentComponent.rootComponent == r) { 280 | after = true; 281 | continue; 282 | } 283 | 284 | var rootRect = new Rectangle(r.screenLeft, r.screenTop, r.width, r.height); 285 | if (after == true && rootRect.intersects(rc)) { 286 | overlaps = true; 287 | break; 288 | } 289 | } 290 | 291 | /* 292 | if (overlaps == true && tf.visible == true) { 293 | tf.visible = false; 294 | } else if (overlaps == false && tf.visible == false) { 295 | tf.visible = true; 296 | } 297 | */ 298 | } 299 | 300 | public function destroy(component:Component) { 301 | _parentHidden = true; 302 | tf.visible = false; 303 | FlxG.removeChild(tf); 304 | tf = null; 305 | } 306 | 307 | private override function validateData() { 308 | if (_text != null) { 309 | if (_dataSource == null) { 310 | tf.text = normalizeText(_text); 311 | } 312 | } 313 | } 314 | 315 | private function normalizeText(text:String):String { 316 | text = StringTools.replace(text, "\\n", "\n"); 317 | return text; 318 | } 319 | 320 | private override function measureText() { 321 | tf.width = _width * Toolkit.scaleX; 322 | 323 | #if !flash 324 | _textWidth = tf.textWidth + PADDING_X; 325 | //_textWidth = textField.textWidth + PADDING_X; 326 | #else 327 | //_textWidth = textField.textWidth - 2; 328 | #end 329 | _textHeight = tf.textHeight; 330 | if (_textHeight == 0) { 331 | var tmpText:String = tf.text; 332 | tf.text = "|"; 333 | _textHeight = tf.textHeight; 334 | tf.text = tmpText; 335 | } 336 | #if !flash 337 | //_textHeight += PADDING_Y; 338 | #else 339 | //_textHeight -= 2; 340 | #end 341 | 342 | _textWidth = Math.round(_textWidth) / Toolkit.scaleX; 343 | _textHeight = Math.round(_textHeight) / Toolkit.scaleY; 344 | 345 | ////////////////////////////////////////////////////////////////////////////// 346 | 347 | _inputData.hscrollMax = tf.maxScrollH; 348 | // see below 349 | _inputData.hscrollPageSize = (_width * _inputData.hscrollMax) / _textWidth; 350 | 351 | _inputData.vscrollMax = tf.maxScrollV - 1; 352 | // cant have page size yet as there seems to be an openfl issue with bottomScrollV 353 | // https://github.com/openfl/openfl/issues/2220 354 | _inputData.vscrollPageSize = (_height * _inputData.vscrollMax) / _textHeight; 355 | } 356 | 357 | private override function validateStyle():Bool { 358 | var measureTextRequired:Bool = false; 359 | 360 | var format:TextFormat = tf.getTextFormat(); 361 | 362 | if (_textStyle != null) { 363 | if (format.align != _textStyle.textAlign) { 364 | format.align = _textStyle.textAlign; 365 | } 366 | 367 | var fontSizeValue = Std.int(_textStyle.fontSize); 368 | if (_textStyle.fontSize == null) { 369 | //fontSizeValue = 13; 370 | } 371 | if (format.size != fontSizeValue) { 372 | format.size = Std.int(fontSizeValue * Toolkit.scale); 373 | 374 | measureTextRequired = true; 375 | } 376 | 377 | if (_fontInfo != null && format.font != _fontInfo.data) { 378 | format.font = _fontInfo.data; 379 | measureTextRequired = true; 380 | } 381 | 382 | if (format.color != _textStyle.color) { 383 | format.color = _textStyle.color; 384 | } 385 | 386 | if (format.bold != _textStyle.fontBold) { 387 | //format.bold = _textStyle.fontBold; 388 | measureTextRequired = true; 389 | } 390 | 391 | if (format.italic != _textStyle.fontItalic) { 392 | //format.italic = _textStyle.fontItalic; 393 | measureTextRequired = true; 394 | } 395 | 396 | if (format.underline != _textStyle.fontUnderline) { 397 | //format.underline = _textStyle.fontUnderline; 398 | measureTextRequired = true; 399 | } 400 | } 401 | 402 | tf.defaultTextFormat = format; 403 | tf.setTextFormat(format); 404 | if (tf.wordWrap != _displayData.wordWrap) { 405 | tf.wordWrap = _displayData.wordWrap; 406 | measureTextRequired = true; 407 | } 408 | 409 | if (tf.multiline != _displayData.multiline) { 410 | tf.multiline = _displayData.multiline; 411 | measureTextRequired = true; 412 | } 413 | 414 | if (tf.displayAsPassword != _inputData.password) { 415 | tf.displayAsPassword = _inputData.password; 416 | } 417 | 418 | tf.type = (parentComponent.disabled ? DYNAMIC : INPUT); 419 | 420 | return measureTextRequired; 421 | } 422 | 423 | private function onInternalChange(e:Event) { 424 | _text = tf.text; 425 | 426 | measureText(); 427 | 428 | if (_inputData.onChangedCallback != null) { 429 | _inputData.onChangedCallback(); 430 | } 431 | } 432 | 433 | private function onScroll(e) { 434 | _inputData.hscrollPos = tf.scrollH; 435 | _inputData.vscrollPos = tf.scrollV - 1; 436 | 437 | if (_inputData.onScrollCallback != null) { 438 | _inputData.onScrollCallback(); 439 | } 440 | } 441 | 442 | private override function validatePosition() { 443 | _left = Math.round(_left * Toolkit.scaleX); 444 | _top = Math.round(_top * Toolkit.scaleY); 445 | } 446 | 447 | private override function validateDisplay() { 448 | if (tf.width != _width * Toolkit.scaleX) { 449 | tf.width = _width * Toolkit.scaleX; 450 | } 451 | 452 | if (tf.height != _height * Toolkit.scaleY) { 453 | #if flash 454 | tf.height = _height * Toolkit.scaleY; 455 | //textField.height = _height + 4; 456 | #else 457 | tf.height = _height * Toolkit.scaleY; 458 | #end 459 | } 460 | } 461 | } -------------------------------------------------------------------------------- /haxe/ui/backend/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |