├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── images │ └── screen.png └── workflows │ ├── build.yml │ └── project │ ├── build.xml │ └── src │ └── Main.hx ├── .gitignore ├── LICENSE.md ├── README.md ├── classpath.exclusions ├── haxe └── ui │ └── backend │ ├── AppImpl.hx │ ├── AssetsImpl.hx │ ├── BackendImpl.hx │ ├── CallLaterImpl.hx │ ├── ComponentGraphicsImpl.hx │ ├── ComponentImpl.hx │ ├── ComponentSurface.hx │ ├── EventImpl.hx │ ├── FocusManagerImpl.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 │ ├── module.xml │ └── openfl │ ├── BitmapCache.hx │ ├── EventMapper.hx │ ├── FilterConverter.hx │ ├── OpenFLStyleHelper.hx │ ├── _module │ └── styles │ │ ├── dark │ │ └── main.css │ │ ├── default │ │ └── main.css │ │ └── main.css │ ├── filters │ ├── BrightnessFilter.hx │ ├── ContrastFilter.hx │ ├── GrayscaleFilter.hx │ ├── HueRotateFilter.hx │ ├── InvertFilter.hx │ ├── SaturateFilter.hx │ └── TintFilter.hx │ └── util │ ├── FontDetect.hx │ └── GraphicsExt.hx ├── 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/images/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxeui/haxeui-openfl/7841e98e55fa118ba3a3d3644f4e0997e00147b3/.github/images/screen.png -------------------------------------------------------------------------------- /.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.0.5, 4.1.5, 4.2.2, 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-openfl . 41 | haxelib install hxcpp --always --quiet 42 | haxelib install actuate --always --quiet 43 | haxelib install openfl --always --quiet 44 | echo "y" | haxelib run openfl setup 45 | 46 | - name: Build app (${{ matrix.target }}, haxe ${{ matrix.haxe-version }}, ${{ matrix.os }}) 47 | run: | 48 | cd .github/workflows/project 49 | haxelib run openfl build ${{ matrix.target }} 50 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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-openfl/actions/workflows/build.yml/badge.svg) 2 | 3 | # haxeui-openfl 4 | `haxeui-openfl` is the `OpenFL` backend for HaxeUI. 5 | 6 |

7 | 8 |

9 | 10 | 11 | ## Installation 12 | * `haxeui-openfl` has a dependency to `haxeui-core`, and so that too must be installed. 13 | * `haxeui-openfl` also has a dependency to OpenFL, please refer to the installation instructions on their site. 14 | 15 | Eventually all these libs will become haxelibs, however, currently in their alpha form they do not even contain a `haxelib.json` file (for dependencies, etc) and therefore can only be used by downloading the source and using the `haxelib dev` command or by directly using the git versions using the `haxelib git` command (recommended). Eg: 16 | 17 | ``` 18 | haxelib git haxeui-core https://github.com/haxeui/haxeui-core 19 | haxelib dev haxeui-openfl path/to/expanded/source/archive 20 | ``` 21 | 22 | ## Usage 23 | The simplest method to create a new `OpenFL` application that is HaxeUI ready is to use one of the haxeui-templates. These templates will allow you to start a new project rapidly with HaxeUI support baked in. 24 | 25 | If however you already have an existing application, then incorporating HaxeUI into that application is straight forward: 26 | 27 | ### project/application.xml 28 | Assuming `haxeui-core` and `haxeui-openfl` have been installed, then adding HaxeUI to your existing application is as simple as adding these two lines to your `project.xml` or your `application.xml`: 29 | 30 | ```xml 31 | 32 | 33 | ``` 34 | 35 | _Note: Currently you must also include `haxeui-core` explicitly during the alpha, eventually `haxelib.json` files will exist to take care of this dependency automatically._ 36 | 37 | ### Toolkit initialisation and usage 38 | Initialising the toolkit requires you to add this single line somewhere _before_ you start to actually use HaxeUI in your application: 39 | 40 | ``` 41 | Toolkit.init(); 42 | ``` 43 | Once the toolkit is initialised you can add components using the methods specified here. 44 | 45 | ## OpenFL specifics 46 | 47 | As well as using the generic `Screen.instance.addComponent`, it is also possible to add components directly to any other `OpenFL` sprite (eg: `Lib.current.stage.addChild`) 48 | 49 | ## Addtional resources 50 | * component-explorer - Browse HaxeUI components 51 | * playground - Write and test HaxeUI layouts in your browser 52 | * component-examples - Various componet examples 53 | * haxeui-api - The HaxeUI api docs. 54 | * haxeui-guides - Set of guides to working with HaxeUI and backends. 55 | -------------------------------------------------------------------------------- /classpath.exclusions: -------------------------------------------------------------------------------- 1 | ; exclude paths from classpath when searching for haxeui arifacts (module.xml, native.xml, etc) 2 | ; speeds up build 3 | \/lime\/_internal\/.*$ 4 | \/lime\/src\/.*$ 5 | \/lime\/.*\/src\/.*$ 6 | \/lime\/app\/.*$ 7 | \/lime\/graphics\/.*$ 8 | \/lime\/math\/.*$ 9 | \/lime\/media\/.*$ 10 | \/lime\/net\/.*$ 11 | \/lime\/system\/.*$ 12 | \/lime\/text\/.*$ 13 | \/lime\/tools\/.*$ 14 | \/lime\/ui\/.*$ 15 | \/lime\/utils\/.*$ 16 | \/openfl\/src\/.*$ 17 | \/openfl\/.*\/src\/.*$ 18 | \/openfl\/desktop\/.*$ 19 | \/openfl\/display\/.*$ 20 | \/openfl\/display3D\/.*$ 21 | \/openfl\/errors\/.*$ 22 | \/openfl\/events\/.*$ 23 | \/openfl\/external\/.*$ 24 | \/openfl\/filters\/.*$ 25 | \/openfl\/geom\/.*$ 26 | \/openfl\/media\/.*$ 27 | \/openfl\/net\/.*$ 28 | \/openfl\/printing\/.*$ 29 | \/openfl\/profiler\/.*$ 30 | \/openfl\/sensors\/.*$ 31 | \/openfl\/system\/.*$ 32 | \/openfl\/text\/.*$ 33 | \/openfl\/ui\/.*$ 34 | \/openfl\/utils\/.*$ 35 | \/actuate\/.*$ -------------------------------------------------------------------------------- /haxe/ui/backend/AppImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.events.AppEvent; 4 | 5 | class AppImpl extends AppBase { 6 | public function new() { 7 | } 8 | 9 | private override function init(onReady:Void->Void, onEnd:Void->Void = null) { 10 | openfl.Lib.current.stage.application.onExit.add(function(_) { 11 | dispatch(new AppEvent(AppEvent.APP_EXITED)); 12 | }); 13 | onReady(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /haxe/ui/backend/AssetsImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.io.Bytes; 4 | import haxe.io.Path; 5 | import haxe.ui.assets.FontInfo; 6 | import haxe.ui.assets.ImageInfo; 7 | import openfl.Assets; 8 | import openfl.display.Bitmap; 9 | import openfl.display.BitmapData; 10 | import openfl.display.Loader; 11 | import openfl.events.Event; 12 | import openfl.utils.ByteArray; 13 | 14 | class AssetsImpl extends AssetsBase { 15 | private override function getTextDelegate(resourceId:String):String { 16 | if (Assets.exists(resourceId) == true) { 17 | return Assets.getText(resourceId); 18 | } else if (Resource.listNames().indexOf(resourceId) != -1) { 19 | return Resource.getString(resourceId); 20 | } 21 | return null; 22 | } 23 | 24 | private override function getImageInternal(resourceId:String, callback:ImageInfo->Void):Void { 25 | if (Assets.exists(resourceId) == true) { 26 | if(Path.extension(resourceId).toLowerCase() == "svg") { 27 | #if svg 28 | var content:String = Assets.getText(resourceId); 29 | var svg = new format.SVG(content); 30 | var imageInfo:ImageInfo = { 31 | svg: svg, 32 | width: Std.int(svg.data.width), 33 | height: Std.int(svg.data.height) 34 | }; 35 | callback(imageInfo); 36 | #else 37 | trace("WARNING: SVG not supported"); 38 | #end 39 | } else { 40 | Assets.loadBitmapData(resourceId).onComplete(function(bmpData:BitmapData) { 41 | var imageInfo:ImageInfo = { 42 | data: bmpData, 43 | width: bmpData.width, 44 | height: bmpData.height 45 | } 46 | callback(imageInfo); 47 | }); 48 | } 49 | } else { 50 | callback(null); 51 | } 52 | } 53 | 54 | private override function getImageFromHaxeResource(resourceId:String, callback:String->ImageInfo->Void) { 55 | var imageInfo:ImageInfo = null; 56 | if (Path.extension(resourceId).toLowerCase() == "svg") { 57 | #if svg 58 | var svgContent = Resource.getString(resourceId); 59 | var svg = new format.SVG(svgContent); 60 | imageInfo = { 61 | svg: svg, 62 | width: Std.int(svg.data.width), 63 | height: Std.int(svg.data.height) 64 | } 65 | #else 66 | trace("WARNING: SVG not supported"); 67 | #end 68 | 69 | callback(resourceId, imageInfo); 70 | return; 71 | } 72 | 73 | var bytes = Resource.getBytes(resourceId); 74 | imageFromBytes(bytes, function(imageInfo) { 75 | callback(resourceId, imageInfo); 76 | }); 77 | } 78 | 79 | public override function imageFromBytes(bytes:Bytes, callback:ImageInfo->Void):Void { 80 | var ba:ByteArray = ByteArray.fromBytes(bytes); 81 | var loader:Loader = new Loader(); 82 | var onCompleteEvent = null; 83 | onCompleteEvent = function(e) { 84 | if (loader.content != null) { 85 | var bmpData = cast(loader.content, Bitmap).bitmapData; 86 | var imageInfo:ImageInfo = { 87 | data: bmpData, 88 | width: bmpData.width, 89 | height: bmpData.height 90 | } 91 | 92 | callback(imageInfo); 93 | } 94 | loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onCompleteEvent); 95 | }; 96 | loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onCompleteEvent, false, 0, false); 97 | loader.contentLoaderInfo.addEventListener("ioError", function(e) { 98 | trace(e); 99 | callback(null); 100 | loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onCompleteEvent); 101 | }, false, 0, true); 102 | loader.loadBytes(ba); 103 | } 104 | 105 | private override function getFontInternal(resourceId:String, callback:FontInfo->Void):Void { 106 | var fontInfo = null; 107 | if (isEmbeddedFont(resourceId) == true) { 108 | if (Assets.exists(resourceId)) { 109 | fontInfo = { 110 | data: Assets.getFont(resourceId).fontName 111 | } 112 | } else if (Resource.listNames().indexOf(resourceId) != -1) { 113 | getFontFromHaxeResource(resourceId, function(r, info) { 114 | callback(info); 115 | }); 116 | } else { 117 | fontInfo = { 118 | data: resourceId 119 | } 120 | } 121 | } else { 122 | fontInfo = { 123 | data: resourceId 124 | } 125 | } 126 | callback(fontInfo); 127 | } 128 | 129 | private override function getFontFromHaxeResource(resourceId:String, callback:String->FontInfo->Void) { 130 | var bytes = Resource.getBytes(resourceId); 131 | if (bytes == null) { 132 | callback(resourceId, null); 133 | return; 134 | } 135 | 136 | #if (js && html5) 137 | 138 | loadWebFontFontResourceDynamically(resourceId, bytes, callback); 139 | 140 | #else 141 | 142 | var font = openfl.text.Font.fromBytes(bytes); 143 | openfl.text.Font.registerFont(font); 144 | var fontInfo = { 145 | data: font.fontName 146 | } 147 | callback(resourceId, fontInfo); 148 | 149 | #end 150 | } 151 | 152 | #if (js && html5) 153 | private function loadWebFontFontResourceDynamically(resourceId:String, bytes:Bytes, callback:String->FontInfo->Void) { 154 | var fontFamilyParts = resourceId.split("/"); 155 | var fontFamily = fontFamilyParts[fontFamilyParts.length - 1]; 156 | if (fontFamily.indexOf(".") != -1) { 157 | fontFamily = fontFamily.substr(0, fontFamily.indexOf(".")); 158 | } 159 | 160 | var fontFace = new js.html.FontFace(fontFamily, bytes.getData()); 161 | fontFace.load().then(function(loadedFace) { 162 | js.Browser.document.fonts.add(loadedFace); 163 | haxe.ui.backend.openfl.util.FontDetect.onFontLoaded(fontFamily, function(f) { 164 | var fontInfo = { 165 | data: fontFamily 166 | } 167 | callback(resourceId, fontInfo); 168 | }, function(f) { 169 | callback(resourceId, null); 170 | }); 171 | }).catchError(function(error) { 172 | #if debug 173 | trace("WARNING: problem loading font '" + resourceId + "' (" + error + ")"); 174 | #end 175 | // error occurred 176 | callback(resourceId, null); 177 | }); 178 | } 179 | #end 180 | 181 | 182 | public override function imageInfoFromImageData(imageData:ImageData):ImageInfo { 183 | return { 184 | data: imageData, 185 | width: imageData.width, 186 | height: imageData.height 187 | } 188 | } 189 | 190 | //*********************************************************************************************************** 191 | // Util functions 192 | //*********************************************************************************************************** 193 | 194 | private static inline function isEmbeddedFont(name:String) { 195 | return (name != "_sans" && name != "_serif" && name != "_typewriter"); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /haxe/ui/backend/BackendImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | class BackendImpl { 4 | public static var id:String = "openfl"; 5 | } 6 | -------------------------------------------------------------------------------- /haxe/ui/backend/CallLaterImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | class CallLaterImpl { 4 | public function new(fn:Void->Void) { 5 | haxe.ui.util.Timer.delay(fn, 0); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /haxe/ui/backend/ComponentGraphicsImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import openfl.display.GraphicsPathCommand; 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.Bitmap; 10 | import openfl.display.BitmapData; 11 | import openfl.display.GraphicsPath; 12 | import openfl.geom.Matrix; 13 | import openfl.geom.Rectangle; 14 | import openfl.utils.ByteArray; 15 | 16 | class ComponentGraphicsImpl extends ComponentGraphicsBase { 17 | private var _currentFillColor:Null = null; 18 | private var _currentFillAlpha:Null = null; 19 | private var _globalFillColor:Null = null; 20 | private var _globalFillAlpha:Null = null; 21 | private var _hasSize:Bool = false; 22 | 23 | private var _globalLineThickness :Null = null; 24 | private var _globalLineColor:Null = null; 25 | private var _globalLineAlpha:Null = null; 26 | 27 | private var currentPath:GraphicsPath; 28 | 29 | public function new(component:Component) { 30 | super(component); 31 | component.styleable = false; 32 | } 33 | 34 | public override function clear() { 35 | if (_hasSize == false) { 36 | return super.clear(); 37 | } 38 | _component.graphics.clear(); 39 | } 40 | 41 | public override function moveTo(x:Float, y:Float) { 42 | if (_hasSize == false) { 43 | return super.moveTo(x, y); 44 | } 45 | if (currentPath != null) { 46 | currentPath.moveTo(x,y); 47 | } else { 48 | _component.graphics.moveTo(x, y); 49 | } 50 | 51 | } 52 | 53 | public override function lineTo(x:Float, y:Float) { 54 | if (_hasSize == false) { 55 | return super.lineTo(x, y); 56 | } 57 | if (currentPath != null) { 58 | currentPath.lineTo(x,y); 59 | } else { 60 | _component.graphics.lineTo(x, y); 61 | } 62 | } 63 | 64 | public override function strokeStyle(color:Null, thickness:Null = 1, alpha:Null = 1) { 65 | if (_hasSize == false) { 66 | return super.strokeStyle(color, thickness, alpha); 67 | } 68 | if (currentPath == null) { 69 | _globalLineThickness = thickness; 70 | _globalLineColor = color; 71 | _globalLineAlpha = alpha; 72 | } 73 | 74 | _component.graphics.lineStyle(thickness, color, alpha); 75 | } 76 | 77 | public override function fillStyle(color:Null, alpha:Null = 1) { 78 | if (_hasSize == false) { 79 | return super.fillStyle(color, alpha); 80 | } 81 | if (currentPath == null) { 82 | _globalFillColor = color; 83 | _globalFillAlpha = alpha; 84 | } 85 | _currentFillColor = color; 86 | _currentFillAlpha = alpha; 87 | } 88 | 89 | public override function circle(x:Float, y:Float, radius:Float) { 90 | if (_hasSize == false) { 91 | return super.circle(x, y, radius); 92 | } 93 | if (_currentFillColor != null) { 94 | _component.graphics.beginFill(_currentFillColor, _currentFillAlpha); 95 | } 96 | _component.graphics.drawCircle(x, y, radius); 97 | if (_currentFillColor != null) { 98 | _component.graphics.endFill(); 99 | } 100 | } 101 | 102 | public override function curveTo(controlX:Float, controlY:Float, anchorX:Float, anchorY:Float) { 103 | if (_hasSize == false) { 104 | return super.curveTo(controlX, controlY, anchorX, anchorY); 105 | } 106 | 107 | if (currentPath != null) { 108 | currentPath.curveTo(controlX, controlY, anchorX, anchorY); 109 | } else { 110 | _component.graphics.curveTo(controlX, controlY, anchorX, anchorY); 111 | } 112 | } 113 | 114 | public override function cubicCurveTo(controlX1:Float, controlY1:Float, controlX2:Float, controlY2:Float, anchorX:Float, anchorY:Float) { 115 | if (_hasSize == false) { 116 | return super.cubicCurveTo(controlX1, controlY1, controlX2, controlY2, anchorX, anchorY); 117 | } 118 | if (currentPath != null) { 119 | currentPath.cubicCurveTo(controlX1, controlY1, controlX2, controlY2, anchorX, anchorY); 120 | } else { 121 | _component.graphics.cubicCurveTo(controlX1, controlY1, controlX2, controlY2, anchorX, anchorY); 122 | } 123 | 124 | } 125 | 126 | public override function beginPath() { 127 | if (_hasSize == false) { 128 | return super.beginPath(); 129 | } 130 | currentPath = new GraphicsPath(); 131 | } 132 | 133 | public override function closePath() { 134 | if (_hasSize == false) { 135 | return super.closePath(); 136 | } 137 | if (currentPath!=null && currentPath.commands != null && currentPath.commands.length>0 ) { 138 | if (_currentFillColor != null) { 139 | _component.graphics.beginFill(_currentFillColor, _currentFillAlpha); 140 | } 141 | if (currentPath.commands[0] != GraphicsPathCommand.MOVE_TO) { 142 | currentPath.commands.insertAt(0, GraphicsPathCommand.MOVE_TO); 143 | @:privateAccess currentPath.data.insertAt(0, _component.graphics.__positionX); 144 | @:privateAccess currentPath.data.insertAt(0, _component.graphics.__positionY); 145 | } 146 | _component.graphics.drawPath(currentPath.commands, currentPath.data); 147 | if (_currentFillColor != null) { 148 | _component.graphics.endFill(); 149 | } 150 | } 151 | currentPath = null; 152 | _currentFillColor = _globalFillColor; 153 | _currentFillAlpha = _globalFillAlpha; 154 | 155 | // it seems openfl forgets about lineStyle after drawing a shape; 156 | _component.graphics.lineStyle(_globalLineThickness , _globalLineColor, _globalLineAlpha); 157 | } 158 | 159 | public override function rectangle(x:Float, y:Float, width:Float, height:Float) { 160 | if (_hasSize == false) { 161 | return super.rectangle(x, y, width, height); 162 | } 163 | if (_currentFillColor != null) { 164 | _component.graphics.beginFill(_currentFillColor, _currentFillAlpha); 165 | } 166 | _component.graphics.drawRect(x, y, width, height); 167 | if (_currentFillColor != null) { 168 | _component.graphics.endFill(); 169 | } 170 | } 171 | 172 | public override function image(resource:Variant, x:Null = null, y:Null = null, width:Null = null, height:Null = null) { 173 | if (_hasSize == false) { 174 | return super.image(resource, x, y, width, height); 175 | } 176 | ImageLoader.instance.load(resource, function(imageInfo) { 177 | if (imageInfo != null) { 178 | if (x == null) x = 0; 179 | if (y == null) y = 0; 180 | if (width == null) width = imageInfo.width; 181 | if (height == null) height = imageInfo.height; 182 | 183 | var mat:Matrix = new Matrix(); 184 | mat.scale(width / imageInfo.width, height / imageInfo.width); 185 | mat.translate(x, y); 186 | 187 | _component.graphics.beginBitmapFill(imageInfo.data, mat); 188 | _component.graphics.drawRect(x, y, width, height); 189 | _component.graphics.endFill(); 190 | } else { 191 | trace("could not load: " + resource); 192 | } 193 | }); 194 | } 195 | 196 | public override function setPixel(x:Float, y:Float, color:Color) { 197 | if (_hasSize == false) { 198 | return super.setPixel(x, y, color); 199 | } 200 | _component.graphics.beginFill(color); 201 | _component.graphics.drawRect(x, y, 1, 1); 202 | _component.graphics.endFill(); 203 | } 204 | 205 | private var _bitmap:Bitmap = null; 206 | private var _bitmapData:BitmapData = null; 207 | public override function setPixels(pixels:Bytes) { 208 | if (_hasSize == false) { 209 | return super.setPixels(pixels); 210 | } 211 | 212 | if (_bitmap == null) { 213 | _bitmapData = new BitmapData(Std.int(_component.width), Std.int(_component.height), true, 0x00000000); 214 | _bitmap = new Bitmap(_bitmapData); 215 | _component.addChild(_bitmap); 216 | } 217 | 218 | // convert RGBA -> ARGB (well, actually BGRA for some reason) 219 | var bytesData = pixels.getData(); 220 | var length:Int = pixels.length; 221 | var newPixels = Bytes.alloc(length); 222 | var i:Int = 0; 223 | while (i < length) { 224 | var r = Bytes.fastGet(bytesData, i + 0); 225 | var g = Bytes.fastGet(bytesData, i + 1); 226 | var b = Bytes.fastGet(bytesData, i + 2); 227 | var a = Bytes.fastGet(bytesData, i + 3); 228 | newPixels.set(i + 0, b); 229 | newPixels.set(i + 1, g); 230 | newPixels.set(i + 2, r); 231 | newPixels.set(i + 3, a); 232 | i += 4; 233 | } 234 | 235 | var byteArray = ByteArray.fromBytes(newPixels); 236 | _bitmapData.setPixels(new Rectangle(0, 0, _bitmapData.width, _bitmapData.height), byteArray); 237 | } 238 | 239 | public override function resize(width:Null, height:Null) { 240 | if (width > 0 && height > 0) { 241 | if (_hasSize == false) { 242 | _hasSize = true; 243 | replayDrawCommands(); 244 | } 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /haxe/ui/backend/ComponentImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.backend.openfl.EventMapper; 4 | import haxe.ui.backend.openfl.FilterConverter; 5 | import haxe.ui.backend.openfl.OpenFLStyleHelper; 6 | import haxe.ui.core.Component; 7 | import haxe.ui.core.ImageDisplay; 8 | import haxe.ui.core.Screen; 9 | import haxe.ui.core.TextDisplay; 10 | import haxe.ui.core.TextInput; 11 | import haxe.ui.events.KeyboardEvent; 12 | import haxe.ui.events.MouseEvent; 13 | import haxe.ui.events.UIEvent; 14 | import haxe.ui.focus.FocusManager; 15 | import haxe.ui.geom.Point; 16 | import haxe.ui.geom.Rectangle; 17 | import haxe.ui.styles.Style; 18 | import openfl.display.DisplayObjectContainer; 19 | import openfl.display.Sprite; 20 | import openfl.events.Event; 21 | 22 | class ComponentImpl extends ComponentBase { 23 | private var _eventMap:MapVoid>; 24 | 25 | public function new() { 26 | super(); 27 | tabChildren = false; 28 | doubleClickEnabled = true; 29 | #if flash 30 | focusRect = false; 31 | #end 32 | _eventMap = new MapVoid>(); 33 | 34 | #if mobile 35 | cast(this, Component).addClass(":mobile"); 36 | #end 37 | 38 | addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true); 39 | addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true); 40 | } 41 | 42 | @:access(haxe.ui.backend.ScreenImpl) 43 | private function onAddedToStage(event:Event) { 44 | removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); 45 | var component:Component = cast(this, Component); 46 | if (component.parentComponent == null && Screen.instance.rootComponents.indexOf(component) == -1) { 47 | this.scaleX = Toolkit.scaleX; 48 | this.scaleY = Toolkit.scaleY; 49 | Screen.instance.rootComponents.push(component); 50 | FocusManager.instance.pushView(component); 51 | Screen.instance.onContainerResize(null); 52 | } 53 | recursiveReady(); 54 | } 55 | 56 | @:access(haxe.ui.backend.ScreenImpl) 57 | private function onRemovedFromStage(event:Event) { 58 | removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); 59 | var component:Component = cast(this, Component); 60 | if (component.parentComponent == null && Screen.instance.rootComponents.indexOf(component) != -1) { 61 | Screen.instance.rootComponents.remove(component); 62 | FocusManager.instance.removeView(component); 63 | } 64 | } 65 | 66 | private function recursiveReady() { 67 | var component:Component = cast(this, Component); 68 | var isReady = component.isReady; 69 | component.ready(); 70 | if (isReady == false) { 71 | component.syncComponentValidation(); 72 | } 73 | for (child in component.childComponents) { 74 | child.recursiveReady(); 75 | } 76 | } 77 | 78 | private override function handlePosition(left:Null, top:Null, style:Style):Void { 79 | if (left != null) { 80 | this.x = Math.fround(left); 81 | } 82 | if (top != null) { 83 | this.y = Math.fround(top); 84 | } 85 | } 86 | 87 | public var styleable:Bool = true; 88 | private override function handleSize(width:Null, height:Null, style:Style) { 89 | if (width == null || height == null || width <= 0 || height <= 0) { 90 | return; 91 | } 92 | 93 | if (styleable == true) { 94 | OpenFLStyleHelper.paintStyleSection(graphics, style, Math.fround(width), Math.fround(height)); 95 | } 96 | } 97 | 98 | private override function handleClipRect(value:Rectangle):Void { 99 | if (value == null) { 100 | this.scrollRect = null; 101 | } else { 102 | this.scrollRect = new openfl.geom.Rectangle(Math.fround(value.left), 103 | Math.fround(value.top), 104 | Math.fround(value.width), 105 | Math.fround(value.height)); 106 | } 107 | } 108 | 109 | //*********************************************************************************************************** 110 | // Text related 111 | //*********************************************************************************************************** 112 | public override function createTextDisplay(text:String = null):TextDisplay { 113 | if (_textDisplay == null) { 114 | super.createTextDisplay(text); 115 | addChild(_textDisplay.textField); 116 | } 117 | 118 | return _textDisplay; 119 | } 120 | 121 | public override function createTextInput(text:String = null):TextInput { 122 | if (_textInput == null) { 123 | super.createTextInput(text); 124 | addChild(_textInput.textField); 125 | } 126 | return _textInput; 127 | } 128 | 129 | //*********************************************************************************************************** 130 | // Image related 131 | //*********************************************************************************************************** 132 | public override function createImageDisplay():ImageDisplay { 133 | if (_imageDisplay == null) { 134 | super.createImageDisplay(); 135 | addChild(_imageDisplay.sprite); 136 | } 137 | return _imageDisplay; 138 | } 139 | 140 | public override function removeImageDisplay():Void { 141 | if (_imageDisplay != null) { 142 | if (contains(_imageDisplay.sprite) == true) { 143 | removeChild(_imageDisplay.sprite); 144 | } 145 | _imageDisplay.dispose(); 146 | _imageDisplay = null; 147 | } 148 | } 149 | 150 | //*********************************************************************************************************** 151 | // Display tree 152 | //*********************************************************************************************************** 153 | private override function handleAddComponent(child:Component):Component { 154 | addChild(child); 155 | return child; 156 | } 157 | 158 | private override function handleAddComponentAt(child:Component, index:Int):Component { 159 | addChildAt(child, index); 160 | return child; 161 | } 162 | 163 | private override function handleRemoveComponent(child:Component, dispose:Bool = true):Component { 164 | if (contains(child)) { 165 | removeChild(child); 166 | } 167 | return child; 168 | } 169 | 170 | private override function handleRemoveComponentAt(index:Int, dispose:Bool = true):Component { 171 | removeChildAt(index); 172 | return null; 173 | } 174 | 175 | private override function handleSetComponentIndex(child:Component, index:Int) { 176 | setChildIndex(child, index); 177 | } 178 | 179 | private override function applyStyle(style:Style) { 180 | var useHandCursor = false; 181 | if (style.cursor != null && style.cursor == "pointer") { 182 | useHandCursor = true; 183 | } 184 | applyUseHandCursor(useHandCursor); 185 | 186 | if (_imageDisplay != null) { 187 | _imageDisplay.applyStyle(style); 188 | } 189 | 190 | if (style.filter != null && style.filter.length > 0) { 191 | var array = []; 192 | for (fa in style.filter) { 193 | var f = FilterConverter.convertFilter(fa); 194 | if (f != null) { 195 | array.push(f); 196 | } 197 | } 198 | this.filters = array; 199 | } else { 200 | this.filters = null; 201 | } 202 | 203 | if (style.hidden != null) { 204 | this.visible = !style.hidden; 205 | } 206 | 207 | if (style.opacity != null) { 208 | this.alpha = style.opacity; 209 | } 210 | } 211 | 212 | private function applyUseHandCursor(use:Bool) { 213 | this.buttonMode = use; 214 | this.useHandCursor = use; 215 | if (hasImageDisplay()) { 216 | getImageDisplay().sprite.buttonMode = use; 217 | getImageDisplay().sprite.useHandCursor = use; 218 | } 219 | for (n in 0...this.numChildren) { 220 | var c = this.getChildAt(n); 221 | if ((c is Sprite)) { 222 | var s = cast(c, Sprite); 223 | s.buttonMode = use; 224 | s.useHandCursor = use; 225 | } 226 | } 227 | } 228 | 229 | #if flash override #else override #end 230 | private function set_visible(value:Bool): #if flash Bool #else Bool #end { 231 | #if flash 232 | super.visible = value; 233 | #else 234 | var v = super.set_visible(value); 235 | #end 236 | cast(this, Component).hidden = !value; 237 | #if !flash return v; #else return value; #end 238 | } 239 | 240 | private override function handleVisibility(show:Bool):Void { 241 | if (show != super.visible) { 242 | super.visible = show; 243 | } 244 | } 245 | 246 | private var _componentOffset:Point = new Point(0, 0); 247 | private override function getComponentOffset():Point { 248 | var p:DisplayObjectContainer = this; 249 | var s:DisplayObjectContainer = null; 250 | while (p != null) { 251 | if ((p is ComponentImpl) == false) { 252 | s = p; 253 | break; 254 | } 255 | p = p.parent; 256 | } 257 | if (s == null) { 258 | _componentOffset.x = 0; 259 | _componentOffset.y = 0; 260 | } else { 261 | _componentOffset.x = s.x; 262 | _componentOffset.y = s.y; 263 | } 264 | return _componentOffset; 265 | } 266 | 267 | private override function handleFrameworkProperty(id:String, value:Any) { 268 | switch (id) { 269 | case "allowMouseInteraction": 270 | if (value == true) { 271 | this.mouseEnabled = true; 272 | if (this.hasImageDisplay()) { 273 | this.getImageDisplay().sprite.mouseEnabled = true; 274 | } 275 | } else { 276 | this.mouseEnabled = false; 277 | if (this.hasImageDisplay()) { 278 | this.getImageDisplay().sprite.mouseEnabled = false; 279 | } 280 | } 281 | } 282 | } 283 | 284 | //*********************************************************************************************************** 285 | // Events 286 | //*********************************************************************************************************** 287 | private override function mapEvent(type:String, listener:UIEvent->Void) { 288 | switch (type) { 289 | case MouseEvent.MOUSE_MOVE | MouseEvent.MOUSE_OVER | MouseEvent.MOUSE_OUT 290 | | MouseEvent.MOUSE_DOWN | MouseEvent.MOUSE_UP | MouseEvent.MOUSE_WHEEL 291 | | MouseEvent.CLICK | MouseEvent.DBL_CLICK | MouseEvent.RIGHT_CLICK 292 | | MouseEvent.RIGHT_MOUSE_DOWN | MouseEvent.RIGHT_MOUSE_UP: 293 | if (_eventMap.exists(type) == false) { 294 | _eventMap.set(type, listener); 295 | addEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onMouseEvent, false, 0, true); 296 | } 297 | 298 | case KeyboardEvent.KEY_DOWN | KeyboardEvent.KEY_UP: 299 | if (_eventMap.exists(type) == false) { 300 | _eventMap.set(type, listener); 301 | addEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onKeyboardEvent, false, 0, true); 302 | } 303 | } 304 | } 305 | 306 | private override function unmapEvent(type:String, listener:UIEvent->Void) { 307 | switch (type) { 308 | case MouseEvent.MOUSE_MOVE | MouseEvent.MOUSE_OVER | MouseEvent.MOUSE_OUT 309 | | MouseEvent.MOUSE_DOWN | MouseEvent.MOUSE_UP | MouseEvent.MOUSE_WHEEL 310 | | MouseEvent.CLICK | MouseEvent.DBL_CLICK | MouseEvent.RIGHT_CLICK 311 | | MouseEvent.RIGHT_MOUSE_DOWN | MouseEvent.RIGHT_MOUSE_UP: 312 | _eventMap.remove(type); 313 | removeEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onMouseEvent); 314 | 315 | case KeyboardEvent.KEY_DOWN | KeyboardEvent.KEY_UP: 316 | _eventMap.remove(type); 317 | removeEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onKeyboardEvent); 318 | 319 | case UIEvent.CHANGE: 320 | _eventMap.remove(type); 321 | if (hasTextInput() == true) { 322 | getTextInput().textField.removeEventListener(Event.CHANGE, __onTextInputChange); 323 | } 324 | } 325 | } 326 | 327 | //*********************************************************************************************************** 328 | // Event handlers 329 | //*********************************************************************************************************** 330 | private function __onMouseEvent(event:openfl.events.MouseEvent) { 331 | var type:String = EventMapper.OPENFL_TO_HAXEUI.get(event.type); 332 | if (type != null) { 333 | var fn = _eventMap.get(type); 334 | if (fn != null) { 335 | var mouseEvent = new MouseEvent(type); 336 | mouseEvent._originalEvent = event; 337 | mouseEvent.screenX = event.stageX / Toolkit.scaleX; 338 | mouseEvent.screenY = event.stageY / Toolkit.scaleY; 339 | mouseEvent.buttonDown = event.buttonDown; 340 | mouseEvent.delta = event.delta; 341 | mouseEvent.ctrlKey = event.ctrlKey; 342 | mouseEvent.shiftKey = event.shiftKey; 343 | fn(mouseEvent); 344 | } 345 | } 346 | } 347 | 348 | private function __onKeyboardEvent(event:openfl.events.KeyboardEvent) { 349 | var type:String = EventMapper.OPENFL_TO_HAXEUI.get(event.type); 350 | if (type != null) { 351 | var fn = _eventMap.get(type); 352 | if (fn != null) { 353 | var keyboardEvent = new KeyboardEvent(type); 354 | keyboardEvent._originalEvent = event; 355 | keyboardEvent.keyCode = event.keyCode; 356 | keyboardEvent.altKey = event.altKey; 357 | keyboardEvent.ctrlKey = event.ctrlKey; 358 | keyboardEvent.shiftKey = event.shiftKey; 359 | fn(keyboardEvent); 360 | } 361 | } 362 | } 363 | 364 | private function __onTextInputChange(event:Event) { 365 | var fn:UIEvent->Void = _eventMap.get(UIEvent.CHANGE); 366 | if (fn != null) { 367 | fn(new UIEvent(UIEvent.CHANGE)); 368 | } 369 | } 370 | } -------------------------------------------------------------------------------- /haxe/ui/backend/ComponentSurface.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | typedef ComponentSurface = openfl.display.Sprite; -------------------------------------------------------------------------------- /haxe/ui/backend/EventImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.events.UIEvent; 4 | import openfl.events.Event; 5 | 6 | @:allow(haxe.ui.backend.ScreenImpl) 7 | @:allow(haxe.ui.backend.ComponentImpl) 8 | class EventImpl extends EventBase { 9 | private var _originalEvent:Event; 10 | 11 | public override function cancel() { 12 | if (_originalEvent != null) { 13 | _originalEvent.preventDefault(); 14 | _originalEvent.stopImmediatePropagation(); 15 | _originalEvent.stopPropagation(); 16 | } 17 | } 18 | 19 | private override function postClone(event:UIEvent) { 20 | event._originalEvent = this._originalEvent; 21 | } 22 | } -------------------------------------------------------------------------------- /haxe/ui/backend/FocusManagerImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.core.Component; 4 | import openfl.Lib; 5 | 6 | class FocusManagerImpl extends FocusManagerBase { 7 | private override function applyFocus(c:Component) { 8 | super.applyFocus(c); 9 | if (c != null && c.hasTextInput()) { 10 | Lib.current.stage.focus = c.getTextInput().textField; 11 | } else { 12 | Lib.current.stage.focus = c; 13 | } 14 | } 15 | 16 | private override function unapplyFocus(c:Component) { 17 | super.unapplyFocus(c); 18 | Lib.current.stage.focus = null; 19 | } 20 | } -------------------------------------------------------------------------------- /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 = openfl.display.BitmapData; 4 | -------------------------------------------------------------------------------- /haxe/ui/backend/ImageDisplayImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.geom.Rectangle; 4 | import haxe.ui.styles.Style; 5 | import openfl.display.Bitmap; 6 | import openfl.display.BitmapData; 7 | import openfl.display.Sprite; 8 | 9 | class ImageDisplayImpl extends ImageBase { 10 | public var sprite:Sprite; 11 | 12 | private var _bmp:Bitmap; 13 | 14 | public function new() { 15 | super(); 16 | sprite = new Sprite(); 17 | sprite.mouseEnabled = true; 18 | } 19 | 20 | public override function dispose():Void { 21 | if (_bmp != null) { 22 | //_bmp.bitmapData.dispose(); 23 | sprite.removeChild(_bmp); 24 | _bmp = null; 25 | } 26 | super.dispose(); 27 | } 28 | 29 | private inline function containsBitmapDataInfo():Bool { 30 | return _imageInfo != null && _imageInfo.data != null && (_imageInfo.data is BitmapData); 31 | } 32 | 33 | #if svg 34 | 35 | private inline function containsSVGInfo():Bool { 36 | return _imageInfo != null && _imageInfo.svg != null && (_imageInfo.svg is format.SVG); 37 | } 38 | 39 | private function renderSVG():Void { 40 | sprite.graphics.clear(); 41 | if(_imageInfo != null && _imageWidth > 0 && _imageHeight > 0) { 42 | var svg:format.SVG = cast _imageInfo.svg; 43 | svg.render(sprite.graphics, 0, 0, Std.int(_imageWidth), Std.int(_imageHeight)); 44 | } 45 | } 46 | 47 | #end 48 | 49 | //*********************************************************************************************************** 50 | // Validation functions 51 | //*********************************************************************************************************** 52 | 53 | private override function validateData() { 54 | if (_imageInfo != null) { 55 | if(containsBitmapDataInfo()) { 56 | if (_bmp == null) { 57 | _bmp = new Bitmap(_imageInfo.data); 58 | sprite.addChild(_bmp); 59 | } else { 60 | _bmp.bitmapData = _imageInfo.data; 61 | } 62 | _imageWidth = _bmp.width; 63 | _imageHeight = _bmp.height; 64 | _bmp.smoothing = true; 65 | } 66 | #if svg 67 | else if(containsSVGInfo()) { 68 | var svg:format.SVG = cast _imageInfo.svg; 69 | _imageWidth = svg.data.width; 70 | _imageHeight = svg.data.height; 71 | renderSVG(); 72 | } 73 | #end 74 | } else { 75 | if (_bmp != null && sprite.contains(_bmp) == true) { 76 | sprite.removeChild(_bmp); 77 | //_bmp.bitmapData.dispose(); 78 | _bmp = null; 79 | } else { 80 | sprite.graphics.clear(); 81 | } 82 | 83 | _imageWidth = 0; 84 | _imageHeight = 0; 85 | } 86 | } 87 | 88 | private override function validatePosition() { 89 | if (sprite.x != _left) { 90 | sprite.x = _left; 91 | } 92 | 93 | if (sprite.y != _top) { 94 | sprite.y = _top; 95 | } 96 | } 97 | 98 | private override function validateDisplay() { 99 | if(containsBitmapDataInfo()) { 100 | var scaleX:Float = _imageWidth / _bmp.bitmapData.width; 101 | if (_bmp.scaleX != scaleX) { 102 | _bmp.scaleX = scaleX; 103 | } 104 | 105 | var scaleY:Float = _imageHeight / _bmp.bitmapData.height; 106 | if (_bmp.scaleY != scaleY) { 107 | _bmp.scaleY = scaleY; 108 | } 109 | } 110 | #if svg 111 | else if(containsSVGInfo()) { 112 | renderSVG(); 113 | } 114 | #end 115 | 116 | if(_imageClipRect == null) { 117 | sprite.scrollRect = null; 118 | } else { 119 | sprite.scrollRect = new openfl.geom.Rectangle(-_left, -_top, Math.fround(_imageClipRect.width), Math.fround(_imageClipRect.height)); 120 | sprite.x = _imageClipRect.left; 121 | sprite.y = _imageClipRect.top; 122 | } 123 | } 124 | 125 | public function applyStyle(style:Style) { 126 | if (style != null && _bmp != null) { 127 | if (style.imageRendering == "pixelated") { 128 | _bmp.smoothing = false; 129 | } else { 130 | _bmp.smoothing = true; 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /haxe/ui/backend/ImageSurface.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | class ImageSurface { 4 | public function new() { 5 | } 6 | } -------------------------------------------------------------------------------- /haxe/ui/backend/OpenFileDialogImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | 4 | #if !js 5 | import haxe.io.Bytes; 6 | import haxe.ui.containers.dialogs.Dialog.DialogButton; 7 | import haxe.ui.containers.dialogs.Dialogs.FileDialogExtensionInfo; 8 | import haxe.ui.containers.dialogs.Dialogs.SelectedFileInfo; 9 | import openfl.events.Event; 10 | import openfl.net.FileFilter; 11 | import openfl.net.FileReference; 12 | import openfl.net.FileReferenceList; 13 | 14 | using StringTools; 15 | #end 16 | 17 | 18 | 19 | @:access(openfl.net.FileReference) 20 | class OpenFileDialogImpl extends OpenFileDialogBase { 21 | #if js 22 | 23 | private var _fileSelector:haxe.ui.util.html5.FileSelector = new haxe.ui.util.html5.FileSelector(); 24 | 25 | public override function show() { 26 | var readMode = haxe.ui.util.html5.FileSelector.ReadMode.None; 27 | if (options.readContents == true) { 28 | if (options.readAsBinary == false) { 29 | readMode = haxe.ui.util.html5.FileSelector.ReadMode.Text; 30 | } else { 31 | readMode = haxe.ui.util.html5.FileSelector.ReadMode.Binary; 32 | } 33 | } 34 | _fileSelector.selectFile(onFileSelected, readMode, options.multiple, options.extensions); 35 | } 36 | 37 | private function onFileSelected(cancelled:Bool, files:Array) { 38 | if (cancelled == false) { 39 | dialogConfirmed(files); 40 | } else { 41 | dialogCancelled(); 42 | } 43 | } 44 | 45 | #else 46 | 47 | private var _fr:FileReferenceList = null; 48 | private var _refToInfo:Map; 49 | private var _infos:Array; 50 | 51 | public override function show() { 52 | var extensions = null; 53 | if (options != null) { 54 | extensions = options.extensions; 55 | } 56 | 57 | _refToInfo = new Map(); 58 | _infos = []; 59 | _fr = new FileReferenceList(); 60 | _fr.addEventListener(Event.SELECT, onSelect, false, 0, true); 61 | _fr.addEventListener(Event.CANCEL, onCancel, false, 0, true); 62 | _fr.browse(buildFileFilters(extensions)); 63 | } 64 | 65 | private function buildFileFilters(extensions:Array):Array { 66 | if (extensions == null) { 67 | return null; 68 | } 69 | 70 | var fileFilters:Array = []; 71 | for (extension in extensions) { 72 | var extensionList = extension.extension.split(","); 73 | var fileFilterExtensions = ""; 74 | for (extensionItem in extensionList) { 75 | extensionItem = extensionItem.trim(); 76 | if (extensionItem.length == 0) { 77 | continue; 78 | } 79 | 80 | fileFilterExtensions += "*." + extensionItem + ";"; 81 | } 82 | if (fileFilterExtensions.length != 0) { 83 | fileFilters.push(new FileFilter(extension.label, fileFilterExtensions)); 84 | } 85 | 86 | } 87 | return fileFilters; 88 | } 89 | 90 | private function onSelect(e:Event) { 91 | var fileList:Array = _fr.fileList; 92 | destroyFileRef(); 93 | var infos:Array = []; 94 | for (fileRef in fileList) { 95 | var info:SelectedFileInfo = { 96 | isBinary: false, 97 | name: fileRef.name, 98 | fullPath : fileRef.__path 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 | } 10 | -------------------------------------------------------------------------------- /haxe/ui/backend/SaveFileDialogImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | #if !js 4 | import haxe.ui.containers.dialogs.Dialogs.FileDialogExtensionInfo; 5 | import haxe.ui.containers.dialogs.Dialogs.SelectedFileInfo; 6 | import openfl.events.Event; 7 | import openfl.filesystem.File; 8 | import openfl.net.FileFilter; 9 | import openfl.net.FileReference; 10 | import openfl.utils.ByteArray; 11 | 12 | using StringTools; 13 | #end 14 | 15 | class SaveFileDialogImpl extends SaveFileDialogBase { 16 | #if js 17 | 18 | private var _fileSaver:haxe.ui.util.html5.FileSaver = new haxe.ui.util.html5.FileSaver(); 19 | 20 | public override function show() { 21 | if (fileInfo == null || (fileInfo.text == null && fileInfo.bytes == null)) { 22 | throw "Nothing to write"; 23 | } 24 | 25 | if (fileInfo.text != null) { 26 | _fileSaver.saveText(fileInfo.name, fileInfo.text, onSaveResult); 27 | } else if (fileInfo.bytes != null) { 28 | _fileSaver.saveBinary(fileInfo.name, fileInfo.bytes, onSaveResult); 29 | } 30 | } 31 | 32 | private function onSaveResult(r:Bool) { 33 | if (r == true) { 34 | dialogConfirmed(); 35 | } else { 36 | dialogCancelled(); 37 | } 38 | } 39 | 40 | #else 41 | 42 | private var _file:File; 43 | 44 | public override function show() { 45 | var extensions = null; 46 | if (options != null) { 47 | extensions = options.extensions; 48 | } 49 | var data:Dynamic = null; 50 | var defaultFilename = null; 51 | if (fileInfo != null) { 52 | defaultFilename = fileInfo.name; 53 | if (fileInfo.text != null) { 54 | data = fileInfo.text; 55 | } else if (fileInfo.bytes != null) { 56 | data = ByteArray.fromBytes(fileInfo.bytes); 57 | } 58 | } 59 | 60 | _file = new File(); 61 | _file.addEventListener(Event.SELECT, onSelect, false, 0, true); 62 | _file.addEventListener(Event.CANCEL, onCancel, false, 0, true); 63 | if (data != null) { 64 | _file.save(data, defaultFilename); 65 | } else { 66 | _file.browseForSave(options.title); 67 | } 68 | } 69 | 70 | private function buildFileFilters(extensions:Array):Array { 71 | if (extensions == null) { 72 | return null; 73 | } 74 | 75 | var fileFilters:Array = []; 76 | for (extension in extensions) { 77 | var extensionList = extension.extension.split(","); 78 | var fileFilterExtensions = ""; 79 | for (extensionItem in extensionList) { 80 | extensionItem = extensionItem.trim(); 81 | if (extensionItem.length == 0) { 82 | continue; 83 | } 84 | 85 | fileFilterExtensions += "*." + extensionItem + ";"; 86 | } 87 | if (fileFilterExtensions.length != 0) { 88 | fileFilters.push(new FileFilter(extension.label, fileFilterExtensions)); 89 | } 90 | 91 | } 92 | return fileFilters; 93 | } 94 | 95 | private function onSelect(e:Event) { 96 | var selectedFileInfo:SelectedFileInfo = { 97 | name: _file.name, 98 | fullPath: _file.nativePath 99 | } 100 | destroyFileRef(); 101 | dialogConfirmed(selectedFileInfo); 102 | } 103 | 104 | private function onCancel(e:Event) { 105 | destroyFileRef(); 106 | dialogCancelled(); 107 | } 108 | 109 | private function destroyFileRef() { 110 | if (_file == null) { 111 | return; 112 | } 113 | 114 | _file.removeEventListener(Event.SELECT, onSelect); 115 | _file.removeEventListener(Event.CANCEL, onCancel); 116 | _file = null; 117 | } 118 | 119 | #end 120 | } 121 | -------------------------------------------------------------------------------- /haxe/ui/backend/ScreenImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.Toolkit; 4 | import haxe.ui.backend.openfl.EventMapper; 5 | import haxe.ui.core.Component; 6 | import haxe.ui.events.KeyboardEvent; 7 | import haxe.ui.events.MouseEvent; 8 | import haxe.ui.events.UIEvent; 9 | import lime.system.System; 10 | import openfl.Lib; 11 | import openfl.display.DisplayObjectContainer; 12 | import openfl.display.StageAlign; 13 | import openfl.display.StageQuality; 14 | import openfl.display.StageScaleMode; 15 | 16 | class ScreenImpl extends ScreenBase { 17 | private var _mapping:MapVoid>; 18 | 19 | public function new() { 20 | _mapping = new MapVoid>(); 21 | } 22 | 23 | private override function get_width():Float { 24 | if (container == Lib.current.stage) { 25 | return Lib.current.stage.stageWidth / Toolkit.scaleX; 26 | } 27 | return container.width / Toolkit.scaleX; 28 | } 29 | 30 | private override function get_height():Float { 31 | if (container == Lib.current.stage) { 32 | return Lib.current.stage.stageHeight / Toolkit.scaleY; 33 | } 34 | return container.height / Toolkit.scaleY; 35 | } 36 | 37 | private override function get_dpi():Float { 38 | return System.getDisplay(0).dpi; 39 | } 40 | 41 | private override function set_title(s:String):String { 42 | #if (flash || android || ios ) 43 | trace("WARNING: this platform doesnt support dynamic titles"); 44 | #end 45 | Lib.current.stage.window.title = s; 46 | return s; 47 | } 48 | private override function get_title():String { 49 | #if (flash || android || ios ) 50 | trace("WARNING: this platform doesnt support dynamic titles"); 51 | #end 52 | return Lib.current.stage.window.title; 53 | } 54 | 55 | public override function addComponent(component:Component):Component { 56 | component.scaleX = Toolkit.scaleX; 57 | component.scaleY = Toolkit.scaleY; 58 | if (rootComponents.indexOf(component) == -1) { 59 | rootComponents.push(component); 60 | container.addChild(component); 61 | onContainerResize(null); 62 | } 63 | return component; 64 | } 65 | 66 | public override function removeComponent(component:Component, dispose:Bool = true, invalidate:Bool = true):Component { 67 | rootComponents.remove(component); 68 | container.removeChild(component); 69 | return component; 70 | } 71 | 72 | private override function handleSetComponentIndex(child:Component, index:Int) { 73 | rootComponents.remove(child); 74 | rootComponents.insert(index, child); 75 | 76 | var offset = 0; 77 | for (i in 0...container.numChildren) { 78 | var c = container.getChildAt(i); 79 | if ((c is Component)) { 80 | offset = i; 81 | break; 82 | } 83 | } 84 | 85 | container.setChildIndex(child, index + offset); 86 | } 87 | 88 | private function onContainerResize(event:openfl.events.Event) { 89 | resizeRootComponents(); 90 | 91 | var fn = _mapping.get(UIEvent.RESIZE); 92 | if (fn != null) { 93 | var uiEvent = new UIEvent(UIEvent.RESIZE); 94 | fn(uiEvent); 95 | } 96 | } 97 | 98 | private var _containerReady:Bool = false; 99 | private var container(get, null):DisplayObjectContainer; 100 | private function get_container():DisplayObjectContainer { 101 | var c = null; 102 | if (options == null || options.container == null) { 103 | c = Lib.current.stage; 104 | } else { 105 | c = options.container; 106 | } 107 | 108 | if (_containerReady == false) { 109 | c.stage.quality = StageQuality.BEST; 110 | c.scaleMode = StageScaleMode.NO_SCALE; 111 | c.align = StageAlign.TOP_LEFT; 112 | c.addEventListener(openfl.events.Event.RESIZE, onContainerResize, false, 0, true); 113 | _containerReady = true; 114 | } 115 | 116 | return c; 117 | } 118 | 119 | //*********************************************************************************************************** 120 | // Events 121 | //*********************************************************************************************************** 122 | private override function supportsEvent(type:String):Bool { 123 | if (type == UIEvent.RESIZE) { 124 | return true; 125 | } 126 | return EventMapper.HAXEUI_TO_OPENFL.get(type) != null; 127 | } 128 | 129 | private override function mapEvent(type:String, listener:UIEvent->Void) { 130 | switch (type) { 131 | case MouseEvent.MOUSE_MOVE | MouseEvent.MOUSE_OVER | MouseEvent.MOUSE_OUT 132 | | MouseEvent.MOUSE_DOWN | MouseEvent.MOUSE_UP | MouseEvent.CLICK | MouseEvent.DBL_CLICK 133 | | MouseEvent.RIGHT_MOUSE_DOWN | MouseEvent.RIGHT_MOUSE_UP | MouseEvent.RIGHT_CLICK: 134 | if (_mapping.exists(type) == false) { 135 | _mapping.set(type, listener); 136 | Lib.current.stage.addEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onMouseEvent, false, 0, true); 137 | } 138 | 139 | case KeyboardEvent.KEY_DOWN | KeyboardEvent.KEY_UP: 140 | if (_mapping.exists(type) == false) { 141 | _mapping.set(type, listener); 142 | Lib.current.stage.addEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onKeyEvent, false, 0, true); 143 | } 144 | 145 | case UIEvent.RESIZE: 146 | if (_mapping.exists(type) == false) { 147 | _mapping.set(type, listener); 148 | } 149 | } 150 | } 151 | 152 | private override function unmapEvent(type:String, listener:UIEvent->Void) { 153 | switch (type) { 154 | case MouseEvent.MOUSE_MOVE | MouseEvent.MOUSE_OVER | MouseEvent.MOUSE_OUT 155 | | MouseEvent.MOUSE_DOWN | MouseEvent.MOUSE_UP | MouseEvent.CLICK | MouseEvent.DBL_CLICK 156 | | MouseEvent.RIGHT_MOUSE_DOWN | MouseEvent.RIGHT_MOUSE_UP | MouseEvent.RIGHT_CLICK: 157 | _mapping.remove(type); 158 | Lib.current.stage.removeEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onMouseEvent); 159 | 160 | case KeyboardEvent.KEY_DOWN | KeyboardEvent.KEY_UP: 161 | _mapping.remove(type); 162 | Lib.current.stage.removeEventListener(EventMapper.HAXEUI_TO_OPENFL.get(type), __onKeyEvent); 163 | 164 | case UIEvent.RESIZE: 165 | _mapping.remove(type); 166 | } 167 | } 168 | 169 | private function __onMouseEvent(event:openfl.events.MouseEvent) { 170 | var type:String = EventMapper.OPENFL_TO_HAXEUI.get(event.type); 171 | if (type != null) { 172 | var fn = _mapping.get(type); 173 | if (fn != null) { 174 | var mouseEvent = new MouseEvent(type); 175 | mouseEvent._originalEvent = event; 176 | mouseEvent.screenX = event.stageX / Toolkit.scaleX; 177 | mouseEvent.screenY = event.stageY / Toolkit.scaleY; 178 | mouseEvent.buttonDown = event.buttonDown; 179 | mouseEvent.ctrlKey = event.ctrlKey; 180 | mouseEvent.shiftKey = event.shiftKey; 181 | fn(mouseEvent); 182 | } 183 | } 184 | } 185 | 186 | private function __onKeyEvent(event:openfl.events.KeyboardEvent) { 187 | var type:String = EventMapper.OPENFL_TO_HAXEUI.get(event.type); 188 | if (type != null) { 189 | var fn = _mapping.get(type); 190 | if (fn != null) { 191 | var keyboardEvent = new KeyboardEvent(type); 192 | keyboardEvent._originalEvent = event; 193 | keyboardEvent.keyCode = event.keyCode; 194 | keyboardEvent.ctrlKey = event.ctrlKey; 195 | keyboardEvent.shiftKey = event.shiftKey; 196 | fn(keyboardEvent); 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /haxe/ui/backend/TextDisplayImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import openfl.text.TextField; 4 | import openfl.text.TextFieldAutoSize; 5 | import openfl.text.TextFieldType; 6 | import openfl.text.TextFormat; 7 | 8 | #if cache_text_metrics 9 | typedef TextMetricsCache = { 10 | var text:String; 11 | var width:Float; 12 | var textWidth:Float; 13 | var textHeight:Float; 14 | } 15 | #end 16 | 17 | class TextDisplayImpl extends TextBase { 18 | private var PADDING_X:Int = 4; 19 | private var PADDING_Y:Int = 0; 20 | 21 | public var textField:TextField; 22 | 23 | private var _resetHtmlText:Bool = true; 24 | 25 | public function new() { 26 | super(); 27 | 28 | textField = createTextField(); 29 | 30 | _text = ""; 31 | } 32 | 33 | private function createTextField() { 34 | var tf:TextField = new TextField(); 35 | tf.type = TextFieldType.DYNAMIC; 36 | tf.selectable = false; 37 | tf.mouseEnabled = false; 38 | tf.autoSize = TextFieldAutoSize.LEFT; 39 | 40 | #if flash 41 | var format:TextFormat = tf.getTextFormat(); 42 | format.font = "_sans"; 43 | format.size = 13; 44 | tf.defaultTextFormat = format; 45 | #end 46 | 47 | #if cache_text_metrics 48 | var format:TextFormat = tf.getTextFormat(); 49 | format.font = "_sans"; 50 | format.size = 13; 51 | tf.defaultTextFormat = format; 52 | tf.wordWrap = true; 53 | tf.multiline = true; 54 | #end 55 | 56 | return tf; 57 | } 58 | 59 | //*********************************************************************************************************** 60 | // Validation functions 61 | //*********************************************************************************************************** 62 | 63 | private override function validateData() { 64 | if (_text != null) { 65 | if (_dataSource == null) { 66 | textField.text = normalizeText(_text); 67 | } 68 | } else if (_htmlText != null) { 69 | textField.htmlText = normalizeText(_htmlText); 70 | } 71 | } 72 | 73 | private override function validateStyle():Bool { 74 | var measureTextRequired:Bool = false; 75 | 76 | var format:TextFormat = textField.getTextFormat(); 77 | 78 | if (_textStyle != null) { 79 | if (format.align != _textStyle.textAlign) { 80 | format.align = _textStyle.textAlign; 81 | } 82 | 83 | var fontSizeValue = Std.int(_textStyle.fontSize); 84 | if (_textStyle.fontSize == null) { 85 | fontSizeValue = 13; 86 | } 87 | if (format.size != fontSizeValue) { 88 | format.size = fontSizeValue; 89 | measureTextRequired = true; 90 | } 91 | 92 | if (_fontInfo != null && format.font != _fontInfo.data) { 93 | format.font = _fontInfo.data; 94 | measureTextRequired = true; 95 | } 96 | 97 | if (format.color != _textStyle.color) { 98 | format.color = _textStyle.color; 99 | } 100 | 101 | if (_textStyle.fontBold != null && format.bold != _textStyle.fontBold) { 102 | format.bold = _textStyle.fontBold; 103 | measureTextRequired = true; 104 | } 105 | 106 | if (_textStyle.fontItalic != null && format.italic != _textStyle.fontItalic) { 107 | format.italic = _textStyle.fontItalic; 108 | measureTextRequired = true; 109 | } 110 | 111 | if (_textStyle.fontUnderline != null && format.underline != _textStyle.fontUnderline) { 112 | format.underline = _textStyle.fontUnderline; 113 | measureTextRequired = true; 114 | } 115 | } 116 | 117 | textField.defaultTextFormat = format; 118 | #if flash 119 | textField.setTextFormat(format); 120 | #end 121 | if (textField.wordWrap != _displayData.wordWrap) { 122 | textField.wordWrap = _displayData.wordWrap; 123 | measureTextRequired = true; 124 | } 125 | if (_resetHtmlText == true && _htmlText != null) { 126 | textField.htmlText = normalizeText(_htmlText); 127 | } 128 | 129 | if (textField.multiline != _displayData.multiline) { 130 | textField.multiline = _displayData.multiline; 131 | measureTextRequired = true; 132 | } 133 | 134 | #if cache_text_metrics 135 | if (measureTextRequired) { 136 | _cachedMetrics = null; 137 | } 138 | #end 139 | 140 | return measureTextRequired; 141 | } 142 | 143 | private override function validatePosition() { 144 | _left = Math.round(_left); 145 | _top = Math.round(_top); 146 | 147 | #if html5 148 | textField.x = _left; 149 | textField.y = _top - 2; 150 | #elseif flash 151 | textField.x = _left - 2; 152 | textField.y = _top - 2; 153 | #else 154 | textField.x = _left - 1; 155 | textField.y = _top - 2; 156 | #end 157 | } 158 | 159 | private override function validateDisplay() { 160 | if (textField.width != _width) { 161 | textField.width = _width; 162 | } 163 | 164 | if (textField.height != _height) { 165 | #if flash 166 | textField.height = _height; 167 | //textField.height = _height + 4; 168 | #else 169 | textField.height = _height + 1; 170 | #end 171 | } 172 | } 173 | 174 | #if cache_text_metrics 175 | private var _cachedMetrics:TextMetricsCache = null; 176 | #end 177 | private override function measureText() { 178 | #if cache_text_metrics 179 | if (_cachedMetrics != null) { 180 | if (_cachedMetrics.width == _width && _cachedMetrics.text == _text) { 181 | _textWidth = _cachedMetrics.textWidth; 182 | _textHeight = _cachedMetrics.textHeight; 183 | return; 184 | } 185 | } 186 | #end 187 | 188 | if (_width > 0) { 189 | textField.width = _width; 190 | } 191 | 192 | #if !flash 193 | _textWidth = textField.textWidth + PADDING_X; 194 | //_textWidth = textField.textWidth + PADDING_X; 195 | #else 196 | _textWidth = textField.textWidth - 2; 197 | #end 198 | 199 | _textHeight = textField.textHeight; 200 | if (_textHeight == 0) { 201 | var tmpText:String = textField.text; 202 | textField.text = "|"; 203 | _textHeight = textField.textHeight; 204 | textField.text = tmpText; 205 | } 206 | 207 | #if !flash 208 | //_textHeight += PADDING_Y; 209 | #else 210 | //_textHeight += 2; 211 | #end 212 | 213 | _textWidth = Math.round(_textWidth); 214 | if (_textWidth % 2 != 0) { 215 | _textWidth += 1; 216 | } 217 | _textHeight = Math.round(_textHeight); 218 | if (_textHeight % 2 == 0) { 219 | _textHeight += 1; 220 | } 221 | 222 | #if cache_text_metrics 223 | _cachedMetrics = { 224 | text: _text, 225 | width: _width, 226 | textWidth: _textWidth, 227 | textHeight: _textHeight 228 | } 229 | #end 230 | } 231 | 232 | private override function get_supportsHtml():Bool { 233 | return true; 234 | } 235 | 236 | private function normalizeText(text:String):String { 237 | text = StringTools.replace(text, "\\n", "\n"); 238 | text = StringTools.replace(text, "
", "\n"); 239 | return text; 240 | } 241 | 242 | private var _tempField:TextField = null; 243 | public override function measureTextWidth():Float { 244 | if (_tempField == null) { 245 | _tempField = new TextField(); 246 | _tempField.type = TextFieldType.DYNAMIC; 247 | _tempField.selectable = false; 248 | _tempField.mouseEnabled = false; 249 | _tempField.autoSize = TextFieldAutoSize.LEFT; 250 | } 251 | 252 | _tempField.defaultTextFormat = textField.defaultTextFormat; 253 | _tempField.text = textField.text; 254 | return _tempField.textWidth + PADDING_X; 255 | } 256 | } -------------------------------------------------------------------------------- /haxe/ui/backend/TextInputImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.ui.data.DataSource; 4 | import haxe.ui.validation.InvalidationFlags; 5 | import openfl.events.Event; 6 | import openfl.text.TextField; 7 | import openfl.text.TextFieldAutoSize; 8 | import openfl.text.TextFieldType; 9 | import openfl.text.TextFormat; 10 | 11 | class TextInputImpl extends TextDisplayImpl { 12 | public function new() { 13 | super(); 14 | 15 | _resetHtmlText = false; 16 | 17 | textField.addEventListener(Event.CHANGE, onChange, false, 0, true); 18 | textField.addEventListener(Event.SCROLL, onScroll, false, 0, true); 19 | _inputData.vscrollPageStep = 1; 20 | _inputData.vscrollNativeWheel = true; 21 | } 22 | 23 | private override function createTextField() { 24 | var tf:TextField = new TextField(); 25 | tf.type = TextFieldType.INPUT; 26 | tf.selectable = true; 27 | tf.mouseEnabled = true; 28 | tf.autoSize = TextFieldAutoSize.NONE; 29 | tf.multiline = true; 30 | tf.wordWrap = true; 31 | 32 | #if flash 33 | var format:TextFormat = tf.getTextFormat(); 34 | format.font = "_sans"; 35 | format.size = 13; 36 | tf.defaultTextFormat = format; 37 | #end 38 | 39 | return tf; 40 | } 41 | 42 | public override function focus() { 43 | if (textField.stage != null) { 44 | textField.stage.focus = textField; 45 | } 46 | } 47 | 48 | public override function blur() { 49 | if (textField.stage != null) { 50 | textField.stage.focus = null; 51 | } 52 | } 53 | 54 | public override function dispose() { 55 | if (textField != null) { 56 | if (parentComponent != null) { 57 | parentComponent.removeChild(textField); 58 | } 59 | textField.removeEventListener(Event.CHANGE, onChange); 60 | textField.removeEventListener(Event.SCROLL, onScroll); 61 | textField = null; 62 | } 63 | super.dispose(); 64 | } 65 | 66 | private override function set_dataSource(value:DataSource):DataSource { 67 | if (_dataSource != null) { 68 | _dataSource.onAdd = null; 69 | _dataSource.onChange = null; 70 | } 71 | _dataSource = value; 72 | if (_dataSource != null) { 73 | _dataSource.onAdd = onDataSourceAdd; 74 | _dataSource.onClear = onDataSourceClear; 75 | } 76 | return value; 77 | } 78 | 79 | private function onDataSourceAdd(s:String) { 80 | textField.appendText(s); 81 | parentComponent.text = textField.text; 82 | measureText(); 83 | parentComponent.syncComponentValidation(); 84 | } 85 | 86 | private function onDataSourceClear() { 87 | textField.text = ""; 88 | parentComponent.text = ""; 89 | measureText(); 90 | parentComponent.syncComponentValidation(); 91 | } 92 | 93 | private override function get_caretIndex():Int { 94 | return textField.caretIndex; 95 | } 96 | private override function set_caretIndex(value:Int):Int { 97 | textField.setSelection(value, value); 98 | return value; 99 | } 100 | 101 | //*********************************************************************************************************** 102 | // Validation functions 103 | //*********************************************************************************************************** 104 | private override function validateData() { 105 | textField.removeEventListener(Event.SCROLL, onScroll); 106 | 107 | super.validateData(); 108 | 109 | var changed = false; 110 | var hscrollValue:Int = Math.round(_inputData.hscrollPos); 111 | if (textField.scrollH != hscrollValue) { 112 | textField.scrollH = hscrollValue; 113 | changed = true; 114 | } 115 | 116 | var vscrollValue:Int = Math.round(_inputData.vscrollPos); 117 | if (textField.scrollV != vscrollValue) { 118 | textField.scrollV = vscrollValue; 119 | changed = true; 120 | } 121 | 122 | textField.addEventListener(Event.SCROLL, onScroll, false, 0, true); 123 | 124 | if (changed == true) { 125 | onScroll(null); 126 | } 127 | } 128 | 129 | private override function validateStyle():Bool { 130 | var measureTextRequired:Bool = super.validateStyle(); 131 | 132 | if (textField.displayAsPassword != _inputData.password) { 133 | textField.displayAsPassword = _inputData.password; 134 | } 135 | 136 | if (parentComponent.disabled) { 137 | textField.selectable = false; 138 | } else { 139 | textField.selectable = true; 140 | } 141 | 142 | return measureTextRequired; 143 | } 144 | 145 | private override function validatePosition() { 146 | _left = Math.round(_left); 147 | _top = Math.round(_top); 148 | 149 | #if html5 150 | textField.x = _left - 3; 151 | textField.y = _top - 2; 152 | #elseif flash 153 | textField.x = _left; 154 | textField.y = _top; 155 | #else 156 | textField.x = _left - 3; 157 | textField.y = _top - 3; 158 | #end 159 | } 160 | 161 | private override function measureText() { 162 | #if openfl_textfield_workarounds // not required for alot of apps, or later versions of openfl 163 | if (_width <= 0) { 164 | return; 165 | } 166 | #end 167 | super.measureText(); 168 | 169 | #if flash 170 | _textHeight += 2; 171 | #end 172 | 173 | #if openfl_textfield_workarounds // not required for alot of apps, or later versions of openfl 174 | if (StringTools.endsWith(_text, "\n")) { 175 | _textHeight += textField.getLineMetrics(textField.numLines - 2).height; 176 | } 177 | #end 178 | 179 | _inputData.hscrollMax = textField.maxScrollH; 180 | // see below 181 | _inputData.hscrollPageSize = (_width * _inputData.hscrollMax) / _textWidth; 182 | 183 | var msv = textField.maxScrollV; 184 | #if openfl_textfield_workarounds // not required for alot of apps, or later versions of openfl 185 | if (msv > 1) { 186 | msv--; 187 | } 188 | #end 189 | _inputData.vscrollMax = msv; 190 | // cant have page size yet as there seems to be an openfl issue with bottomScrollV 191 | // https://github.com/openfl/openfl/issues/2220 192 | _inputData.vscrollPageSize = (_height * _inputData.vscrollMax) / _textHeight; 193 | } 194 | 195 | private function onChange(e) { 196 | _text = textField.text; 197 | _htmlText = textField.htmlText; 198 | 199 | measureText(); 200 | 201 | if (_inputData.onChangedCallback != null) { 202 | _inputData.onChangedCallback(); 203 | } 204 | } 205 | 206 | private function onScroll(e) { 207 | if (_inputData.vscrollPos - textField.scrollV > 2) { // weird openfl bug - randomly throws out scroll event and scrollV = 1 208 | return; 209 | } 210 | _inputData.hscrollPos = textField.scrollH; 211 | _inputData.vscrollPos = textField.scrollV; 212 | 213 | if (_inputData.onScrollCallback != null) { 214 | _inputData.onScrollCallback(); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /haxe/ui/backend/TimerImpl.hx: -------------------------------------------------------------------------------- 1 | package haxe.ui.backend; 2 | 3 | import haxe.Timer; 4 | import openfl.Lib; 5 | import openfl.events.Event; 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 | import openfl.display.Stage; 4 | 5 | typedef ToolkitOptions = { 6 | ?container:Stage 7 | } 8 | -------------------------------------------------------------------------------- /haxe/ui/backend/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |