diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81c9d4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin/ +obj/ +demo/bin/ +demo/obj/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a032390 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "as3mxml.sdk.framework": "c:\\AIR\\AIR_SDK_50_2", + "as3mxml.sources.organizeImports.insertNewLineBetweenTopLevelPackages": false, + "as3mxml.codeGeneration.getterSetter.forcePublicFunctions": true, + "files.trimTrailingWhitespace": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..8ab230f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "actionscript", + "debug": false + }, + { + "type": "actionscript", + "debug": true + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1d2bddf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Starling Inspector: Changelog + +## v0.1 (2023-10-28) + +- Initial version of the library +- Added Display List Inspector +- Added inspector entries for primitive types & few Starling related types (color, texture, blend mode, etc.) \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9d7f5c5 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,12 @@ +Simplified BSD License +====================== + +Copyright (c) 2023 Aurélien Da Campo (Adolio). All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..25f9e08 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Starling Inspector + +The Starling Inspector is an AS3 library which will enable display list inspection of your [Starling Framework](https://github.com/Gamua/Starling-Framework) project. This project is relying on the Starling version of [Feathers UI](https://github.com/feathersui/feathersui-starling). + +👉 Beware: the project is currently in its early stage, you might encounter issues while using it. + +![](demo/media/images/Starling-Inspector-Demo.png) + +## ⭐ Key features + +- Inspect your scene without using a debugger +- Tweak your scene objects live +- Customize your inspector if needed + +## ▶️ Try it! + +Go to the [demo](./demo/) folder, configure the project & run it or if you get the latest release, the demo binary should be available in the archive. + +## ⌨️ How to use? + +### 🎛️ Display List Inspector + +In order to start exploring a `Display Object Container`, check the following snippet: + +```actionscript +private var _scene:Sprite; // Your scene root object +private var _inspectorLayer:Sprite; + +public function setupInspector() +{ + // Setup the inspector layer + _inspectorLayer = new Sprite(); + addChild(_inspectorLayer); + + // Setup the inspector configuration + InspectorConfiguration.ROOT_LAYER = _inspectorLayer; + + // Create the Display List Inspector Panel + _displayListInspectorPanel = new DisplayListInspectorPanel(_scene, true); + _displayListInspectorPanel.height = Starling.current.nativeStage.stageHeight; + _inspectorLayer.addChild(_displayListInspectorPanel); +} +``` +Feel free to checkout the demo for more usage examples. + +### 🖌️ Customize inspector styles + +The style of the inspector is based on your current `Feathers UI` theme (see [Feathers themes](https://feathersui.com/learn/as3-starling/themes)). +For further customization, check the following class: `InspectorConfiguration`. + +```actionscript +// Change the style used for panel title +InspectorConfiguration.STYLE_NAME_LABEL_PANEL_TITLE = "custom-inspector-panel-title-style"; + +// Change the padding between components +InspectorConfiguration.COMPONENTS_PADDING = 12; +``` + +## 📦 How to install? + +- Use the `.swc` file provided in each release. + +or + +- Checkout this repository & add `src` folder in your `classpath` or copy paste the content of the `src` folder in your source folder. + +Don't forget to include dependencies (see below). + +## 🖇 Dependencies + +- [Feathers UI (Starling)](https://github.com/feathersui/feathersui-starling) 4.x +- [Adobe AIR SDK](https://airsdk.harman.com) +- AS3 Signal (see `as3-signal.swc` in library folder) \ No newline at end of file diff --git a/asconfig.json b/asconfig.json new file mode 100644 index 0000000..88c2330 --- /dev/null +++ b/asconfig.json @@ -0,0 +1,17 @@ +{ + "type": "lib", + "config": "air", + "compilerOptions": { + "source-path": [ + "src" + ], + "swf-version": 50, + "external-library-path": [ + "libs" + ], + "include-sources": [ + "src" + ], + "output": "bin/starling-inspector.swc" + } +} \ No newline at end of file diff --git a/demo/.vscode/launch.json b/demo/.vscode/launch.json new file mode 100644 index 0000000..5c44939 --- /dev/null +++ b/demo/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "swf", + "request": "launch", + "name": "AIR desktop: Build release & launch", + "profile": "extendedDesktop", + "preLaunchTask": "ActionScript: compile release - asconfig.json" + }, + { + "type": "swf", + "request": "launch", + "name": "AIR desktop: Build debug & launch", + "profile": "extendedDesktop", + "preLaunchTask": "ActionScript: compile debug - asconfig.json" + } + ] +} \ No newline at end of file diff --git a/demo/.vscode/settings.json b/demo/.vscode/settings.json new file mode 100644 index 0000000..a032390 --- /dev/null +++ b/demo/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "as3mxml.sdk.framework": "c:\\AIR\\AIR_SDK_50_2", + "as3mxml.sources.organizeImports.insertNewLineBetweenTopLevelPackages": false, + "as3mxml.codeGeneration.getterSetter.forcePublicFunctions": true, + "files.trimTrailingWhitespace": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + } +} \ No newline at end of file diff --git a/demo/.vscode/tasks.json b/demo/.vscode/tasks.json new file mode 100644 index 0000000..8ab230f --- /dev/null +++ b/demo/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "actionscript", + "debug": false + }, + { + "type": "actionscript", + "debug": true + } + ] +} \ No newline at end of file diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..3d7615c --- /dev/null +++ b/demo/README.md @@ -0,0 +1,18 @@ +# Starling Inspector - Demo + +by Aurélien Da Campo ([Adolio](https://twitter.com/AurelienDaCampo)) + +## 📍 Introduction + +The demo shows the main features provided by the *Starling Inspector* extension. + +![](media/images/Starling-Inspector-Demo.png) + +## 🎶 Resources origin + +### Images +- [Starling Framework Logo](https://en.wikipedia.org/wiki/Starling_Framework), Original drawing from Chris Georgenes - Own work (CC BY 4.0) + +## 🔨 How to build? + +Install [Visual Studio Code](https://code.visualstudio.com/) and [ActionScript & MXML](https://as3mxml.com/#install-extension) and then follow the build procedure provided by the *ActionScript & MXML* extension. \ No newline at end of file diff --git a/demo/application.xml b/demo/application.xml new file mode 100644 index 0000000..c61cc72 --- /dev/null +++ b/demo/application.xml @@ -0,0 +1,21 @@ + + + Starling-Inspector-Demo + 0.1 + 0.1 + Starling-Inspector-Demo + Starling-Inspector-Demo + + Starling-Inspector-Demo + Starling-Inspector-Demo.swf + standard + false + true + true + false + false + direct + false + + desktop extendedDesktop + \ No newline at end of file diff --git a/demo/asconfig.json b/demo/asconfig.json new file mode 100644 index 0000000..a9bfb33 --- /dev/null +++ b/demo/asconfig.json @@ -0,0 +1,45 @@ +{ + "config": "air", + "compilerOptions": { + "output": "bin/Starling-Inspector-Demo.swf", + "library-path": [ + "libs/starling.swc", + "libs/feathers.swc", + "libs/AeonDesktopTheme.swc", + "libs/MinimalDesktopTheme.swc", + "libs/MetalWorksDesktopTheme.swc", + "../libs/as3-signals.swc" + ], + "source-path": [ + "src", + "../src/", + "media" + ], + "default-size": { + "width": 1280, + "height": 720 + } + }, + "application": "application.xml", + "files": [ + "src/ch/adolio/Main.as" + ], + "airOptions": { + "windows": { + "target": "native", + "output": "bin/Starling-Inspector-Demo.exe", + "signingOptions": { + "storetype": "pkcs12", + "keystore": "cert/starling-inspector-demo.p12" + } + }, + "air": + { + "output": "bin/Starling-Inspector.air", + "signingOptions": { + "storetype": "pkcs12", + "keystore": "cert/starling-inspector-demo.p12" + } + } + } +} \ No newline at end of file diff --git a/demo/cert/starling-inspector-demo.p12 b/demo/cert/starling-inspector-demo.p12 new file mode 100644 index 0000000..a76b0e2 Binary files /dev/null and b/demo/cert/starling-inspector-demo.p12 differ diff --git a/demo/libs/AeonDesktopTheme.swc b/demo/libs/AeonDesktopTheme.swc new file mode 100644 index 0000000..fc95244 Binary files /dev/null and b/demo/libs/AeonDesktopTheme.swc differ diff --git a/demo/libs/MetalWorksDesktopTheme.swc b/demo/libs/MetalWorksDesktopTheme.swc new file mode 100644 index 0000000..d91c0af Binary files /dev/null and b/demo/libs/MetalWorksDesktopTheme.swc differ diff --git a/demo/libs/MinimalDesktopTheme.swc b/demo/libs/MinimalDesktopTheme.swc new file mode 100644 index 0000000..345f17c Binary files /dev/null and b/demo/libs/MinimalDesktopTheme.swc differ diff --git a/demo/libs/feathers.swc b/demo/libs/feathers.swc new file mode 100644 index 0000000..35b55f5 Binary files /dev/null and b/demo/libs/feathers.swc differ diff --git a/demo/libs/starling.swc b/demo/libs/starling.swc new file mode 100644 index 0000000..515216e Binary files /dev/null and b/demo/libs/starling.swc differ diff --git a/demo/media/images/Starling-Inspector-Demo.png b/demo/media/images/Starling-Inspector-Demo.png new file mode 100644 index 0000000..3f689fd Binary files /dev/null and b/demo/media/images/Starling-Inspector-Demo.png differ diff --git a/demo/media/textures/starling-flying.png b/demo/media/textures/starling-flying.png new file mode 100644 index 0000000..74725c4 Binary files /dev/null and b/demo/media/textures/starling-flying.png differ diff --git a/demo/src/ch/adolio/DisplayListInspectorTest.as b/demo/src/ch/adolio/DisplayListInspectorTest.as new file mode 100644 index 0000000..a3e26de --- /dev/null +++ b/demo/src/ch/adolio/DisplayListInspectorTest.as @@ -0,0 +1,125 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.panel.DisplayListInspectorPanel; + import ch.adolio.display.ui.inspector.panel.ObjectInspectorPanel; + import ch.adolio.utils.InspectionUtils; + import starling.core.Starling; + import starling.display.Image; + import starling.display.Quad; + import starling.display.Sprite; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + import starling.text.TextField; + import starling.textures.Texture; + + public class DisplayListInspectorTest extends Sprite + { + private var _displayListInspectorPanel:DisplayListInspectorPanel; + private var _scene:Sprite; + private var _inspectorLayer:Sprite; + private var _bird:Image; + + [Embed(source="../../../media/textures/starling-flying.png")] private static const AlbedoTexture:Class; + + public function DisplayListInspectorTest() + { + setupScene(); + setupInspection(); + + // inspect the bird at startup + _displayListInspectorPanel.selectObject(_bird); + ObjectInspectorPanel.instance.object = _bird; // this line is needed only because the display list inspector events listener are not yet registered + + // register to events + Starling.current.stage.addEventListener(TouchEvent.TOUCH, onTouch); + } + + private function setupScene():void + { + // create scene root object + _scene = new Sprite(); + addChild(_scene); + + // create a red quad + var redQuad:Quad = new Quad(100, 100, 0xff0000); + redQuad.name = "A red quad"; + redQuad.x = 400; + redQuad.y = 100; + _scene.addChild(redQuad); + + // create a blue quad + var blueQuad:Quad = new Quad(80, 80, 0x0000ff); + blueQuad.name = "A blue quad"; + blueQuad.x = 450; + blueQuad.y = 180; + _scene.addChild(blueQuad); + + // create a label + var label:TextField = new TextField(200, 50); + label.text = "Hello World!"; + label.x = 500; + label.y = 50; + _scene.addChild(label); + + // create a bird + var starlingTexture:Texture = Texture.fromEmbeddedAsset(AlbedoTexture, false, false, 1, "bgra", true); + _bird = new Image(starlingTexture); + _bird.name = "A lovely bird"; + _bird.x = 500; + _bird.y = 200; + _scene.addChild(_bird); + } + + private function setupInspection():void + { + // create inspection layer + _inspectorLayer = new Sprite(); + addChild(_inspectorLayer); + + // setup inspector + InspectorConfiguration.ROOT_LAYER = _inspectorLayer; + InspectorConfiguration.COLOR_PANEL_HEADER_BACKGROUND_COLOR = 0xdddddd; + + // create display list inspector + _displayListInspectorPanel = new DisplayListInspectorPanel(_scene, true); + _displayListInspectorPanel.ignoreList.push(_displayListInspectorPanel, ObjectInspectorPanel.instance); + _displayListInspectorPanel.height = Starling.current.nativeStage.stageHeight; + _inspectorLayer.addChild(_displayListInspectorPanel); + + // setup the object inspector + ObjectInspectorPanel.instance.height = Starling.current.nativeStage.stageHeight; + ObjectInspectorPanel.instance.x = Starling.current.nativeStage.stageWidth - ObjectInspectorPanel.instance.width; + } + + private function onTouch(event:TouchEvent):void + { + var touch:Touch = event.touches[0]; + if (!touch) + return; + + if (touch.phase != TouchPhase.BEGAN) + return; + + // ignore inspectors layer and already selected object + if (!InspectionUtils.isChildOf(_inspectorLayer, touch.target) && ObjectInspectorPanel.instance.object != touch.target) + { + if (_displayListInspectorPanel.parent) + _displayListInspectorPanel.selectObject(touch.target); + else + ObjectInspectorPanel.instance.object = touch.target; + } + } + } +} \ No newline at end of file diff --git a/demo/src/ch/adolio/Main.as b/demo/src/ch/adolio/Main.as new file mode 100644 index 0000000..b37da2a --- /dev/null +++ b/demo/src/ch/adolio/Main.as @@ -0,0 +1,28 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio +{ + import flash.display.Sprite; + import starling.core.Starling; + + public class Main extends Sprite + { + public function Main() + { + // Setup targetted frame rate + stage.frameRate = 60; + + // Setup Starling + var starling:Starling = new Starling(StarlingMain, stage); + starling.start(); + } + } +} \ No newline at end of file diff --git a/demo/src/ch/adolio/StarlingMain.as b/demo/src/ch/adolio/StarlingMain.as new file mode 100644 index 0000000..aa2d4df --- /dev/null +++ b/demo/src/ch/adolio/StarlingMain.as @@ -0,0 +1,29 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio +{ + import starling.display.Sprite; + import feathers.themes.MinimalDesktopTheme; + + public class StarlingMain extends Sprite + { + public function StarlingMain() + { + // Theme selection + //new AeonDesktopTheme(); + //new MetalWorksDesktopTheme(); + new MinimalDesktopTheme(); + + // Add sound test scene + addChild(new DisplayListInspectorTest()); + } + } +} \ No newline at end of file diff --git a/libs/as3-signals.swc b/libs/as3-signals.swc new file mode 100644 index 0000000..77d0636 Binary files /dev/null and b/libs/as3-signals.swc differ diff --git a/libs/feathers.swc b/libs/feathers.swc new file mode 100644 index 0000000..35b55f5 Binary files /dev/null and b/libs/feathers.swc differ diff --git a/libs/starling.swc b/libs/starling.swc new file mode 100644 index 0000000..515216e Binary files /dev/null and b/libs/starling.swc differ diff --git a/src/ch/adolio/display/shape/BorderedRectangle.as b/src/ch/adolio/display/shape/BorderedRectangle.as new file mode 100644 index 0000000..7e62526 --- /dev/null +++ b/src/ch/adolio/display/shape/BorderedRectangle.as @@ -0,0 +1,150 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.shape +{ + import starling.display.Quad; + import starling.display.Sprite; + + /** + * A bordered rectangle + */ + public class BorderedRectangle extends Sprite + { + // internal + private var _width:Number; + private var _height:Number; + + // quads + private var _bodyQuad:Quad; + private var _borderTopQuad:Quad; + private var _borderBottomQuad:Quad; + private var _borderLeftQuad:Quad; + private var _borderRightQuad:Quad; + + // style + private var _bodyColor:uint; + private var _bodyAlpha:Number = 1.0; + private var _borderSize:Number; + private var _borderAlpha:Number = 1.0; + private var _borderColor:uint; + + public function BorderedRectangle(width:Number, height:Number, bodyColor:uint = 0x000000, borderSize:Number = 1.0, borderColor:uint = 0x000000) + { + // setup core variables + _width = width; + _height = height; + _bodyColor = bodyColor; + _borderSize = borderSize; + _borderColor = borderColor; + + // quads creation + _bodyQuad = new Quad(1, 1, 0x0); + addChild(_bodyQuad); + + _borderTopQuad = new Quad(1, 1, 0x0); + addChild(_borderTopQuad); + + _borderBottomQuad = new Quad(1, 1, 0x0); + addChild(_borderBottomQuad); + + _borderLeftQuad = new Quad(1, 1, 0x0); + addChild(_borderLeftQuad); + + _borderRightQuad = new Quad(1, 1, 0x0); + addChild(_borderRightQuad); + + // initial update + update(); + } + + override public function get width():Number + { + return _width; + } + + override public function set width(value:Number):void + { + _width = value; + + update(); + } + + override public function get height():Number + { + return _height; + } + + override public function set height(value:Number):void + { + _height = value; + + update(); + } + + public function get bodyAlpha():Number + { + return _bodyAlpha; + } + + public function set bodyAlpha(value:Number):void + { + _bodyAlpha = value; + + update(); + } + + public function get borderAlpha():Number + { + return _borderAlpha; + } + + public function set borderAlpha(value:Number):void + { + _borderAlpha = value; + + update(); + } + + private function update():void + { + _bodyQuad.width = _width - _borderSize * 2.0; + _bodyQuad.height = _height - _borderSize * 2.0; + _bodyQuad.x = _borderSize; + _bodyQuad.y = _borderSize; + _bodyQuad.color = _bodyColor; + _bodyQuad.alpha = _bodyAlpha; + + _borderTopQuad.width = _width; + _borderTopQuad.height = _borderSize; + _borderTopQuad.color = _borderColor; + _borderTopQuad.alpha = _borderAlpha; + + _borderBottomQuad.width = _width; + _borderBottomQuad.height = _borderSize; + _borderBottomQuad.y = _height - _borderSize; + _borderBottomQuad.color = _borderColor; + _borderBottomQuad.alpha = _borderAlpha; + + _borderLeftQuad.width = _borderSize; + _borderLeftQuad.height = _height - _borderSize * 2.0; + _borderLeftQuad.y = _borderSize; + _borderLeftQuad.color = _borderColor; + _borderLeftQuad.alpha = _borderAlpha; + + _borderRightQuad.width = _borderSize; + _borderRightQuad.height = _height - _borderSize * 2.0; + _borderRightQuad.x = _width - _borderSize; + _borderRightQuad.y = _borderSize; + _borderRightQuad.color = _borderColor; + _borderRightQuad.alpha = _borderAlpha; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/IInspectable.as b/src/ch/adolio/display/ui/inspector/IInspectable.as new file mode 100644 index 0000000..77bd3fd --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/IInspectable.as @@ -0,0 +1,19 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector +{ + import ch.adolio.display.ui.inspector.panel.InspectorPanel; + + public interface IInspectable + { + function getInspector():InspectorPanel; + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/InspectorConfiguration.as b/src/ch/adolio/display/ui/inspector/InspectorConfiguration.as new file mode 100644 index 0000000..b620b41 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/InspectorConfiguration.as @@ -0,0 +1,71 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector +{ + import starling.display.DisplayObjectContainer; + + public class InspectorConfiguration + { + // version of the library + public static const VERSION:String = "0.1"; + + // root layer + public static var ROOT_LAYER:DisplayObjectContainer; + + // style names + public static var STYLE_NAME_LABEL_PANEL_TITLE:String = ""; + public static var STYLE_NAME_LABEL_SEPARATOR_TITLE:String = ""; + public static var STYLE_NAME_LABEL_ENTRY_TITLE:String = ""; + public static var STYLE_NAME_LABEL_ENTRY_VALUE:String = ""; + public static var STYLE_NAME_TEXT_INPUT:String = ""; + public static var STYLE_NAME_TEXT_AREA:String = ""; + public static var STYLE_NAME_SLIDER:String = ""; + public static var STYLE_NAME_BUTTON:String = ""; + public static var STYLE_NAME_PICKER_LIST:String = ""; + public static var STYLE_NAME_CHECK:String = ""; + public static var STYLE_NAME_TOGGLE_SWITCH:String = ""; + public static var STYLE_NAME_TAB_TOGGLE_BUTTON:String = ""; + public static var STYLE_NAME_PANEL_BACK_BUTTON:String = ""; + public static var STYLE_NAME_PANEL_CLOSE_BUTTON:String = ""; + + // colors + public static var COLOR_PANEL_HEADER_BACKGROUND_COLOR:uint = 0x000000; + public static var COLOR_PANEL_HEADER_BACKGROUND_ALPHA:Number = 0.8; + public static var COLOR_PANEL_FOOTER_BACKGROUND_COLOR:uint = 0x555555; + public static var COLOR_PANEL_FOOTER_BACKGROUND_ALPHA:Number = 0.8; + public static var COLOR_PANEL_BODY_BACKGROUND_COLOR:uint = 0xffffff; + public static var COLOR_PANEL_BODY_BACKGROUND_ALPHA:Number = 0.8; + public static var COLOR_BACKGROUND_HIGHLIGHT:uint = 0xBAD1DF; + + // inspection overlay + public static var INSPECTED_OBJECT_BOUNDS_COLOR:uint = 0x0000ff; + public static var INSPECTED_OBJECT_BOUNDS_BORDER_SIZE:Number = 0.6; + + // inspection panel + public static var PANEL_HEADER_MIN_HEIGHT:Number = 20; // pixels + public static var PANEL_FOOTER_HEIGHT:Number = 20; // pixels + public static var PANEL_FOOTER_SIZE_GRABBER_COLOR:uint = 0x666666; + public static var PANEL_DEFAULT_WIDTH:Number = 300; // pixels + public static var PANEL_DEFAULT_HEIGHT:Number = 300; // pixels + + // inspection entries + public static var ENTRY_TITLE_WIDTH_RATIO:Number = 0.35; // width ratio + public static var ENTRY_TITLE_MAX_WIDTH:Number = 200; // pixels + public static var ENTRY_PREFERRED_HEIGHT:Number = 18; // pixels + public static var COMPONENTS_PADDING:Number = 8; // pixels + + // assets + public static var TEXTURE_IMPORT_SCALE_MIN:Number = 1; + public static var TEXTURE_IMPORT_SCALE_MAX:Number = 4; + public static var TEXTURE_IMPORT_SCALE_STEP:Number = 1; + public static var TEXTURE_IMPORT_SCALE_DEFAULT:Number = 1; + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/ActionInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/ActionInspectorEntry.as new file mode 100644 index 0000000..ad06640 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/ActionInspectorEntry.as @@ -0,0 +1,75 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Button; + import starling.events.Event; + + /** Simple button entry. */ + public class ActionInspectorEntry extends InspectorEntry + { + private var _button:Button; + private var _triggerFunc:Function; + + public function ActionInspectorEntry(actionLabel:String, triggerFunc:Function) + { + _triggerFunc = triggerFunc; + + // button + _button = new Button(); + _button.styleName = InspectorConfiguration.STYLE_NAME_BUTTON; + _button.label = actionLabel; + _button.height = _preferredHeight; + addChild(_button); + _button.validate(); + + // setup height from button height + _preferredHeight = _button.height; + } + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + _button.addEventListener(Event.TRIGGERED, onButtonTriggered); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _button.removeEventListener(Event.TRIGGERED, onButtonTriggered); + } + + private function onButtonTriggered(e:Event):void + { + _triggerFunc(); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _button.x = _paddingLeft; + _button.width = getWidthWithoutPaddings(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/BlendModeInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/BlendModeInspectorEntry.as new file mode 100644 index 0000000..4c51a74 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/BlendModeInspectorEntry.as @@ -0,0 +1,46 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import starling.display.BlendMode; + + public class BlendModeInspectorEntry extends PickerListInspectorEntry + { + private static const BLEND_MODE_ITEMS:Array = [ + { label:"Add", value:BlendMode.ADD }, + { label:"Auto", value:BlendMode.AUTO }, + { label:"Below", value:BlendMode.BELOW }, + { label:"Erase", value:BlendMode.ERASE }, + { label:"Mask", value:BlendMode.MASK }, + { label:"Multiply", value:BlendMode.MULTIPLY }, + { label:"None", value:BlendMode.NONE }, + { label:"Normal", value:BlendMode.NORMAL }, + { label:"Screen", value:BlendMode.SCREEN } + ]; + + public function BlendModeInspectorEntry(title:String, getterFunc:Function, setterFunc:Function = null) + { + super(title, BLEND_MODE_ITEMS, + function():String + { + return getterFunc(); + }, + function(object:Object):void + { + if (setterFunc) + { + setterFunc(object.value); + } + } + ); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/CheckInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/CheckInspectorEntry.as new file mode 100644 index 0000000..7d21cf9 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/CheckInspectorEntry.as @@ -0,0 +1,102 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Check; + import feathers.controls.Label; + import starling.events.Event; + + public class CheckInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _check:Check; + private var _getterFunc:Function; + private var _setteFunc:Function; + + private var _disableCallback:Boolean = false; + + public function CheckInspectorEntry(title:String, getterFunc:Function, setterFunc:Function = null) + { + _getterFunc = getterFunc; + _setteFunc = setterFunc; + + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + _check = new Check(); + _check.styleName = InspectorConfiguration.STYLE_NAME_CHECK; + _check.isEnabled = setterFunc != null; + _check.isSelected = _getterFunc(); + _check.height = _preferredHeight; + _check.validate(); + addChild(_check); + + width = _preferredWidth; + } + + public function setIsSelected(value:Number, disableCallback:Boolean = true):void + { + _disableCallback = disableCallback; + _check.isSelected = value; + _disableCallback = false; + } + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + _check.addEventListener(Event.CHANGE, onCheckValueChanged); + + refresh(); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _check.removeEventListener(Event.CHANGE, onCheckValueChanged); + } + + private function onCheckValueChanged(e:Event):void + { + if (_setteFunc && !_disableCallback) + _setteFunc(_check.isSelected); + } + + override public function refresh():void + { + setIsSelected(_getterFunc(), true); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.x = _paddingLeft; + _label.width = getLabelWidth(); + _check.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/ColorInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/ColorInspectorEntry.as new file mode 100644 index 0000000..1bca1a7 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/ColorInspectorEntry.as @@ -0,0 +1,185 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.panel.ColorSelectorPanel; + import feathers.controls.Label; + import feathers.controls.TextInput; + import starling.display.Quad; + import starling.display.Sprite; + import starling.events.Event; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + + public class ColorInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _previewColor:Quad; + private var _colorTextInput:TextInput; + + private var _getterFunc:Function; + private var _setterFunc:Function; + + private var _disableCallback:Boolean = false; + + private static var _colorPickerPanel:ColorSelectorPanel; + private var _previewContainer:Sprite; + + public function ColorInspectorEntry(title:String, getterFunc:Function, setterFunc:Function = null) + { + _getterFunc = getterFunc; + _setterFunc = setterFunc; + + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + var color:uint = 0; + color = _getterFunc(); + + _previewContainer = new Sprite(); + var bgMargin:Number = 2; + var previewBackground:Quad = new Quad(_preferredHeight, _preferredHeight, 0x0); + _previewContainer.addChild(previewBackground); + _previewColor = new Quad(_preferredHeight - 2*bgMargin, _preferredHeight - 2*bgMargin, color); + _previewColor.x = bgMargin; + _previewColor.y = bgMargin; + _previewColor.useHandCursor = true; + _previewContainer.addChild(_previewColor); + addChild(_previewContainer); + + _colorTextInput = new TextInput(); + _colorTextInput.styleName = InspectorConfiguration.STYLE_NAME_TEXT_INPUT; + _colorTextInput.height = _preferredHeight; + _colorTextInput.text = ColorSelectorPanel.colorToHexString(color); + addChild(_colorTextInput); + + width = _preferredWidth; + + _colorTextInput.addEventListener(Event.CHANGE, onColorTextInputChanged); + } + + private function onColorTextInputChanged(e:Event):void + { + var color:uint = uint(_colorTextInput.text); + _previewColor.color = color; + + if (_setterFunc && !_disableCallback) + _setterFunc(color); + } + + public function getColor():uint + { + return _previewColor.color; + } + + // Expect Argb color + public function setColor(color:uint, disableCallback:Boolean = true):void + { + _disableCallback = disableCallback; + _previewColor.color = color; + _colorTextInput.text = ColorSelectorPanel.colorToHexString(color); + _disableCallback = false; + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + addEventListener(TouchEvent.TOUCH, onTouched); + + refresh(); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + removeEventListener(TouchEvent.TOUCH, onTouched); + + if (_colorPickerPanel) + _colorPickerPanel.colorChanged.remove(onPickerColorChanged); + } + + private function onTouched(e:TouchEvent):void + { + var touch:Touch = e.getTouch(_previewContainer); + if (touch && touch.phase == TouchPhase.ENDED) + { + if (!_colorPickerPanel) + _colorPickerPanel = new ColorSelectorPanel(); + + // if possible, place the color selector aside the current inspector + if (_inspector) + { + _colorPickerPanel.x = _inspector.x + _inspector.width + InspectorConfiguration.COMPONENTS_PADDING; + _colorPickerPanel.y = touch.globalY; + } + + // force stage re-addition to perfom checks + if (_colorPickerPanel.parent != null) + _colorPickerPanel.removeFromParent(); + + // setup & add to stage + InspectorConfiguration.ROOT_LAYER.addChild(_colorPickerPanel); + _colorPickerPanel.title = _label.text; + _colorPickerPanel.entry = this; + _colorPickerPanel.colorChanged.removeAll(); + _colorPickerPanel.color = _previewColor.color; + _colorPickerPanel.colorChanged.add(onPickerColorChanged); + } + } + + private function onPickerColorChanged(color:uint):void + { + _previewColor.color = color; + _colorTextInput.text = ColorSelectorPanel.colorToHexString(color); + + if (_setterFunc && !_disableCallback) + _setterFunc(color); + } + + override public function refresh():void + { + setColor(_getterFunc(), true); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.width = getLabelWidth(); + _colorTextInput.width = getAvailableWidthForInputComponents() - (_previewContainer.width + InspectorConfiguration.COMPONENTS_PADDING); + + _label.x = _paddingLeft; + _previewContainer.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + _colorTextInput.x = _previewContainer.x + _previewContainer.width + InspectorConfiguration.COMPONENTS_PADDING; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/InspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/InspectorEntry.as new file mode 100644 index 0000000..0aa4901 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/InspectorEntry.as @@ -0,0 +1,101 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.panel.InspectorPanel; + import starling.display.Sprite; + import starling.events.Event; + + public class InspectorEntry extends Sprite + { + protected var _inspector:InspectorPanel; + + protected var _preferredWidth:Number = 300; + protected var _preferredHeight:Number = InspectorConfiguration.ENTRY_PREFERRED_HEIGHT; + + protected var _labelWidthRatio:Number = 0.35; + protected var _paddingLeft:Number = 8; + protected var _paddingRight:Number = 8; + + public function InspectorEntry() + { + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + public function get inspector():InspectorPanel + { + return _inspector; + } + + public function set inspector(value:InspectorPanel):void + { + _inspector = value; + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + protected function onAddedToStage(e:Event):void + { + removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + } + + protected function onRemovedFromStage(e:Event):void + { + removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + //--------------------------------------------------------------------- + //-- Refreshment + //--------------------------------------------------------------------- + + public function refresh():void + { + /* to override */ + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + /** Return total available width without the side padding. */ + public function getWidthWithoutPaddings():Number + { + return (_preferredWidth - (_paddingLeft + _paddingRight)); + } + + /** Return the label width based on the label ratio. */ + public function getLabelWidth():Number + { + return Math.min(getWidthWithoutPaddings() * InspectorConfiguration.ENTRY_TITLE_WIDTH_RATIO, InspectorConfiguration.ENTRY_TITLE_MAX_WIDTH); + } + + /** Return the actual available with for input components (left side). */ + public function getAvailableWidthForInputComponents():Number + { + return getWidthWithoutPaddings() - getLabelWidth() - InspectorConfiguration.COMPONENTS_PADDING; + } + + override public function get height():Number + { + return _preferredHeight; + } + + override public function set height(value:Number):void + { + _preferredHeight = value; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/LabelInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/LabelInspectorEntry.as new file mode 100644 index 0000000..8ef799a --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/LabelInspectorEntry.as @@ -0,0 +1,80 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Label; + import starling.events.Event; + + /** Simple read-only label entry. */ + public class LabelInspectorEntry extends InspectorEntry + { + private var _titleLabel:Label; + private var _valueLabel:Label; + private var _getterFunc:Function; + + public function LabelInspectorEntry(title:String, getterFunc:Function) + { + _getterFunc = getterFunc; + + _titleLabel = new Label(); + _titleLabel.touchable = false; + _titleLabel.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _titleLabel.text = title; + _titleLabel.height = _preferredHeight; + addChild(_titleLabel); + + _valueLabel = new Label(); + _valueLabel.touchable = false; + _valueLabel.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_VALUE; + _valueLabel.text = _getterFunc(); + _valueLabel.height = _preferredHeight; + addChild(_valueLabel); + _valueLabel.validate(); + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + refresh(); + } + + override public function refresh():void + { + _valueLabel.text = _getterFunc(); + _valueLabel.validate(); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _titleLabel.x = _paddingLeft; + _titleLabel.width = getLabelWidth(); + _valueLabel.x = _titleLabel.x + _titleLabel.width + InspectorConfiguration.COMPONENTS_PADDING; + _valueLabel.width = getAvailableWidthForInputComponents(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/ObjectReferenceInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/ObjectReferenceInspectorEntry.as new file mode 100644 index 0000000..f208786 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/ObjectReferenceInspectorEntry.as @@ -0,0 +1,94 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.utils.InspectionUtils; + import feathers.controls.Button; + import feathers.controls.Label; + import starling.events.Event; + + /** Object reference entry. */ + public class ObjectReferenceInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _inspectButton:Button; + private var _getterFunc:Function; + private var _inspectRequestFunc:Function; + + public function ObjectReferenceInspectorEntry(title:String, getterFunc:Function, inspectRequestFunc:Function = null) + { + _getterFunc = getterFunc; + _inspectRequestFunc = inspectRequestFunc; + + // label + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + // button + _inspectButton = new Button(); + _inspectButton.styleName = InspectorConfiguration.STYLE_NAME_BUTTON; + _inspectButton.height = _preferredHeight; + addChild(_inspectButton); + + // setup value + var value:Object = getterFunc(); + _inspectButton.isEnabled = value != null && _inspectRequestFunc; + _inspectButton.label = "Inspect " + InspectionUtils.findObjectName(value); + + // setup height from button height + _preferredHeight = _inspectButton.height; + } + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + _inspectButton.addEventListener(Event.TRIGGERED, onButtonTriggered); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _inspectButton.removeEventListener(Event.TRIGGERED, onButtonTriggered); + } + + private function onButtonTriggered(e:Event):void + { + if (_inspectRequestFunc) + _inspectRequestFunc(); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.x = _paddingLeft; + _label.width = getLabelWidth(); + _inspectButton.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + _inspectButton.width = getAvailableWidthForInputComponents(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/PickerListInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/PickerListInspectorEntry.as new file mode 100644 index 0000000..9354430 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/PickerListInspectorEntry.as @@ -0,0 +1,158 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Label; + import feathers.controls.PickerList; + import feathers.data.ListCollection; + import starling.events.Event; + + public class PickerListInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _pickerList:PickerList; + private var _getterFunc:Function; + private var _setterFunc:Function; + + private var _disableCallback:Boolean = false; + private var _items:Array; + private var _itemsList:ListCollection; + + /** + * Constructor. + * + *

An item must be an `Object` and must look like: `{ label:"label", value:myObject }`.

+ *

Getter function must return a value.

+ *

Setter function must receive the item of type `Object`.

+ */ + public function PickerListInspectorEntry(title:String, items:Array, getterFunc:Function, setterFunc:Function = null) + { + _items = items; + _getterFunc = getterFunc; + _setterFunc = setterFunc; + + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + var selectedIndex:int = -1; + var value:String = getterFunc(); + _itemsList = new ListCollection(); + for (var i:uint = 0; i < items.length; ++i) + { + _itemsList.addItem( items[i] ); + + if (items[i].value == value) + selectedIndex = i; + } + + _pickerList = new PickerList(); + _pickerList.styleName = InspectorConfiguration.STYLE_NAME_PICKER_LIST; + _pickerList.height = _preferredHeight; + _pickerList.x = _label.x + _label.width + 5; + _pickerList.dataProvider = _itemsList; + _pickerList.selectedIndex = selectedIndex; + addChild(_pickerList); + _pickerList.validate(); + + width = _preferredWidth; + } + + public function setSelectedItem(value:*, disableCallback:Boolean = true):void + { + _disableCallback = disableCallback; + for (var i:uint = 0; i < _items.length; ++i) { + if (_items[i].value == value) { + _pickerList.selectedIndex = i; + break; + } + } + _disableCallback = false; + } + + public function updateItems(items:Array):void + { + _disableCallback = true; + + _itemsList.removeAll(); + + var selectedIndex:int = -1; + var value:String = _getterFunc(); + for (var i:uint = 0; i < items.length; ++i) + { + _itemsList.addItem( items[i]); + if (items[i].value == value) + selectedIndex = i; + } + _pickerList.selectedIndex = selectedIndex; + + _disableCallback = false; + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + _pickerList.addEventListener(Event.CHANGE, onPickerListValueChanged); + + refresh(); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _pickerList.removeEventListener(Event.CHANGE, onPickerListValueChanged); + } + + private function onPickerListValueChanged(e:Event):void + { + if (_setterFunc && !_disableCallback) + _setterFunc(_pickerList.selectedItem); + } + + //--------------------------------------------------------------------- + //-- Refreshment + //--------------------------------------------------------------------- + + override public function refresh():void + { + setSelectedItem(_getterFunc(), true); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.x = _paddingLeft; + _label.width = getLabelWidth(); + _pickerList.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + _pickerList.width = getAvailableWidthForInputComponents(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/SeparatorInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/SeparatorInspectorEntry.as new file mode 100644 index 0000000..d57ffdd --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/SeparatorInspectorEntry.as @@ -0,0 +1,63 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Label; + import starling.events.Event; + + public class SeparatorInspectorEntry extends InspectorEntry + { + private var _label:Label; + + public function SeparatorInspectorEntry(title:String) + { + // title + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_SEPARATOR_TITLE; + _label.text = title; + _label.height = _preferredHeight; + _label.validate(); + addChild(_label); + + width = _preferredWidth; + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + // invalidate components + _label.invalidate(); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.width = _preferredWidth; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/SliderInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/SliderInspectorEntry.as new file mode 100644 index 0000000..5439552 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/SliderInspectorEntry.as @@ -0,0 +1,369 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.panel.SliderConfigPanel; + import ch.adolio.utils.InspectionUtils; + import feathers.controls.Button; + import feathers.controls.Label; + import feathers.controls.Slider; + import feathers.controls.TextInput; + import feathers.events.FeathersEventType; + import starling.events.Event; + + public class SliderInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _slider:Slider; + private var _valueTextInput:TextInput; + private var _getterFunc:Function; + private var _setteFunc:Function; + private var _isValueClampingEnabled:Boolean = true; + + private var _disableSliderChangeEventReaction:Boolean; + private var _disableTextInputChangeEventReaction:Boolean; + + private var _sliderExtraRightPadding:Number = 5; + + // options + private var _numberPrecision:uint; + + // config + private static var _configPanel:SliderConfigPanel; + private var _configButton:Button; + + public function SliderInspectorEntry(title:String, + getterFunc:Function, + setterFunc:Function = null, + min:Number = 0, + max:Number = 1.0, + step:Number = 0.1, + showConfigButton:Boolean = false, + numberPrecision:int = -1) + { + _getterFunc = getterFunc; + _setteFunc = setterFunc; + + // setup precision + if (numberPrecision < 0) + setupPrecisionFromRange(max - min); + else + _numberPrecision = numberPrecision; + + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + _slider = new Slider(); + _slider.styleName = InspectorConfiguration.STYLE_NAME_SLIDER; + _slider.minimum = min; + _slider.maximum = max; + _slider.step = step; + _slider.height = _preferredHeight; + _slider.minWidth = 0; + _slider.value = _getterFunc(); + addChild(_slider); + _slider.validate(); + + _valueTextInput = new TextInput(); + _valueTextInput.styleName = InspectorConfiguration.STYLE_NAME_TEXT_INPUT; + _valueTextInput.text = formatNumber(_slider.value, _numberPrecision); + _valueTextInput.height = _preferredHeight; + addChild(_valueTextInput); + + // setup for read-only + if (!_setteFunc) + { + _slider.isEnabled = false; + _valueTextInput.isEditable = false; + } + // setup configuration + else if (showConfigButton) + { + _configButton = new Button(); + _configButton.styleName = InspectorConfiguration.STYLE_NAME_BUTTON; + _configButton.label = "c"; + _configButton.width = _preferredHeight; + _configButton.height = _preferredHeight; + addChild(_configButton); + } + + width = _preferredWidth; + } + + public function setValue(value:Number, disableCallback:Boolean = true):void + { + _disableSliderChangeEventReaction = disableCallback; + _slider.value = value; + _disableSliderChangeEventReaction = false; + + _disableTextInputChangeEventReaction = disableCallback; + _valueTextInput.text = formatNumber(value, _numberPrecision); + _disableTextInputChangeEventReaction = false; + } + + public function getValue():Number + { + return _slider.value; + } + + public function get isValueClampingEnabled():Boolean + { + return _isValueClampingEnabled; + } + + public function set isValueClampingEnabled(value:Boolean):void + { + _isValueClampingEnabled = value; + } + + /** + * Setup appropriate number precision from range. + * + *

< 10 -> 3 (.xxx)

+ *

10..100 -> 2 (.xx)

+ *

100..1000 -> 1 (.x)

+ *

> 1000 -> 0 (no floating part)

+ */ + public function setupPrecisionFromRange(range:Number):void + { + if (range < 10) + _numberPrecision = 3; + else if (range < 100) + _numberPrecision = 2; + else if (range < 1000) + _numberPrecision = 1; + else + _numberPrecision = 0; + } + + public function get numberPrecision():uint + { + return _numberPrecision; + } + + public function set numberPrecision(value:uint):void + { + _numberPrecision = value; + } + + private static function formatNumber(value:Number, precision:int):String + { + var pow:Number = Math.pow(10, precision); + return (Math.round(value * pow) / pow).toString(); + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + // invalidate components + _label.invalidate(); + _slider.invalidate(); + _valueTextInput.invalidate(); + + // register to events + _slider.addEventListener(Event.CHANGE, onSliderValueChanged); + _valueTextInput.addEventListener(Event.CHANGE, onTextInputValueChanged); + _valueTextInput.addEventListener(FeathersEventType.FOCUS_IN, onValueTextInputFocusedIn); + _valueTextInput.addEventListener(FeathersEventType.FOCUS_OUT, onValueTextInputFocusedOut); + + if (_configButton) + _configButton.addEventListener(Event.TRIGGERED, onConfigButtonTriggered); + + // refresh + refresh(); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _slider.removeEventListener(Event.CHANGE, onSliderValueChanged); + _valueTextInput.removeEventListener(Event.CHANGE, onTextInputValueChanged); + _valueTextInput.removeEventListener(FeathersEventType.FOCUS_IN, onValueTextInputFocusedIn); + _valueTextInput.removeEventListener(FeathersEventType.FOCUS_OUT, onValueTextInputFocusedOut); + + if (_configButton) + _configButton.removeEventListener(Event.TRIGGERED, onConfigButtonTriggered); + + if (_configPanel) + { + _configPanel.minChanged.remove(onMinValueChanged); + _configPanel.maxChanged.remove(onMaxValueChanged); + _configPanel.stepChanged.remove(onStepValueChanged); + } + } + + private function onConfigButtonTriggered(e:Event):void + { + // create global panel + if (!_configPanel) + _configPanel = new SliderConfigPanel(_slider.minimum, _slider.maximum, _slider.step); + + // update config panel title + _configPanel.title = "Slider Config: " + _label.text; + + // clear listeners + _configPanel.minChanged.removeAll(); + _configPanel.maxChanged.removeAll(); + _configPanel.stepChanged.removeAll(); + + // update default values + _configPanel.min = _slider.minimum; + _configPanel.max = _slider.maximum; + _configPanel.step = _slider.step; + + // add current entry as listener + _configPanel.minChanged.add(onMinValueChanged); + _configPanel.maxChanged.add(onMaxValueChanged); + _configPanel.stepChanged.add(onStepValueChanged); + + // show panel + InspectorConfiguration.ROOT_LAYER.addChild(_configPanel); + + // if possible, place the new panel aside the current inspector + if (_inspector) + { + _configPanel.x = _inspector.x + _inspector.width + InspectorConfiguration.COMPONENTS_PADDING; + _configPanel.y = _inspector.y; + } + } + + private function onMinValueChanged(value:Number):void + { + if (value >= _slider.maximum) + return; + + _slider.minimum = value; + setValue(_slider.value); + } + + private function onMaxValueChanged(value:Number):void + { + if (value <= _slider.minimum) + return; + + _slider.maximum = value; + setValue(_slider.value); + } + + private function onStepValueChanged(value:Number):void + { + if (value <= 0) + return; + + _slider.step = value; + setValue(_slider.value); + } + + private function onSliderValueChanged(e:Event):void + { + if (_disableSliderChangeEventReaction) + return; + + _disableTextInputChangeEventReaction = true; + _valueTextInput.text = formatNumber(_slider.value, _numberPrecision); + _disableTextInputChangeEventReaction = false; + + if (_setteFunc) + _setteFunc(_slider.value); + } + + private function onTextInputValueChanged(e:Event):void + { + if (_disableTextInputChangeEventReaction) + return; + + // get value from text input + var value:Number = Number(_valueTextInput.text); + + // clamp value (if enabled) + if (_isValueClampingEnabled) + value = InspectionUtils.clamp(value, _slider.minimum, _slider.maximum); + + // update slider component silently + _disableSliderChangeEventReaction = true; + _slider.value = value; + _disableSliderChangeEventReaction = false; + + // call callback + if (_setteFunc) + _setteFunc(value); + } + + private function onValueTextInputFocusedIn(e:Event):void + { + // select the whole text + _valueTextInput.selectRange(0, _valueTextInput.text.length); + } + + private function onValueTextInputFocusedOut(e:Event):void + { + // synchronize text with slider value + if (_isValueClampingEnabled) + { + _disableTextInputChangeEventReaction = true; + _valueTextInput.text = formatNumber(_slider.value, _numberPrecision); + _disableTextInputChangeEventReaction = false; + } + } + + public function get slider():Slider + { + return _slider; + } + + override public function refresh():void + { + setValue(_getterFunc(), true); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + // compute components width + _label.width = getLabelWidth(); + var availableWidth:Number = getAvailableWidthForInputComponents(); + + if (_configButton) + availableWidth -= _configButton.width + InspectorConfiguration.COMPONENTS_PADDING; + + _slider.width = availableWidth * 0.75; + _valueTextInput.width = availableWidth - (_slider.width + _sliderExtraRightPadding + InspectorConfiguration.COMPONENTS_PADDING); + + // place components + _label.x = _paddingLeft; + _slider.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + _valueTextInput.x = _slider.x + _slider.width + _sliderExtraRightPadding + InspectorConfiguration.COMPONENTS_PADDING; + if (_configButton) + _configButton.x = _valueTextInput.x + _valueTextInput.width + InspectorConfiguration.COMPONENTS_PADDING; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/TextAreaInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/TextAreaInspectorEntry.as new file mode 100644 index 0000000..c8d1a23 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/TextAreaInspectorEntry.as @@ -0,0 +1,107 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Label; + import feathers.controls.TextArea; + import starling.events.Event; + + public class TextAreaInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _input:TextArea; + private var _getterFunc:Function; + private var _setterFunc:Function; + + private var _disableCallback:Boolean = false; + + public function TextAreaInspectorEntry(title:String, textAreaHeight:Number, getterFunc:Function, setterFunc:Function = null) + { + _getterFunc = getterFunc; + _setterFunc = setterFunc; + + _preferredHeight = textAreaHeight; + + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + _input = new TextArea(); + _input.styleName = InspectorConfiguration.STYLE_NAME_TEXT_AREA; + _input.height = _preferredHeight; + _input.text = getterFunc(); + _input.isEnabled = _setterFunc; + addChild(_input); + _input.validate(); + + width = _preferredWidth; + } + + public function setText(text:String):void + { + _disableCallback = true; + _input.text = text; + _disableCallback = false; + } + + override public function refresh():void + { + setText(_getterFunc()); + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + _input.addEventListener(Event.CHANGE, onTextValueChanged); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _input.removeEventListener(Event.CHANGE, onTextValueChanged); + } + + private function onTextValueChanged(e:Event):void + { + if (_setterFunc && !_disableCallback) + _setterFunc(_input.text); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.x = _paddingLeft; + _label.width = getLabelWidth(); + _input.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + _input.width = getAvailableWidthForInputComponents(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/TextInputInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/TextInputInspectorEntry.as new file mode 100644 index 0000000..2d40cbf --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/TextInputInspectorEntry.as @@ -0,0 +1,105 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Label; + import feathers.controls.TextInput; + import starling.events.Event; + + public class TextInputInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _input:TextInput; + private var _getterFunc:Function; + private var _setterFunc:Function; + + private var _disableCallback:Boolean = false; + + public function TextInputInspectorEntry(title:String, getterFunc:Function, setterFunc:Function = null) + { + _getterFunc = getterFunc; + _setterFunc = setterFunc; + + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + _input = new TextInput(); + _input.styleName = InspectorConfiguration.STYLE_NAME_TEXT_INPUT; + _input.height = _preferredHeight; + _input.text = getterFunc(); + _input.isEnabled = _setterFunc; + addChild(_input); + _input.validate(); + + width = _preferredWidth; + } + + public function setText(text:String):void + { + _disableCallback = true; + _input.text = text; + _disableCallback = false; + } + + override public function refresh():void + { + setText(_getterFunc()); + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + _input.addEventListener(Event.CHANGE, onTextValueChanged); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _input.removeEventListener(Event.CHANGE, onTextValueChanged); + } + + private function onTextValueChanged(e:Event):void + { + if (_setterFunc && !_disableCallback) + _setterFunc(_input.text); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.x = _paddingLeft; + _label.width = getLabelWidth(); + _input.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + _input.width = getAvailableWidthForInputComponents(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/TextureInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/TextureInspectorEntry.as new file mode 100644 index 0000000..71e323f --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/TextureInspectorEntry.as @@ -0,0 +1,196 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.panel.asset.AssetManagerTexturesPickerPanel; + import feathers.controls.Button; + import feathers.controls.Label; + import flash.display.BitmapData; + import starling.assets.AssetManager; + import starling.display.Image; + import starling.events.Event; + import starling.textures.Texture; + + /** Texture entry. */ + public class TextureInspectorEntry extends InspectorEntry + { + private var _assetManager:AssetManager; + private var _titleLabel:Label; + private var _loadFromAssetManagerButton:Button; + private var _getterFunc:Function; // return starling.textures.Texture + private var _setterFunc:Function; // set starling.textures.Texture + + private var _texturePreview:Image; + private var _texture:Texture; + private var _textureAlias:String; + public var forcePotTexture:Boolean = false; + + public function TextureInspectorEntry(title:String, assetManager:AssetManager, getterFunc:Function, setterFunc:Function, forcePotTexture:Boolean = false) + { + _assetManager = assetManager; + _getterFunc = getterFunc; + _setterFunc = setterFunc; + this.forcePotTexture = forcePotTexture; + + _titleLabel = new Label(); + _titleLabel.touchable = false; + _titleLabel.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _titleLabel.text = title; + _titleLabel.height = _preferredHeight; + _titleLabel.validate(); + addChild(_titleLabel); + + _loadFromAssetManagerButton = new Button(); + _loadFromAssetManagerButton.styleName = InspectorConfiguration.STYLE_NAME_BUTTON; + _loadFromAssetManagerButton.label = "Select"; + _loadFromAssetManagerButton.validate(); + _loadFromAssetManagerButton.isEnabled = setterFunc != null && _assetManager != null; + addChild(_loadFromAssetManagerButton); + + _texture = _getterFunc(); + createImage(); + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:starling.events.Event):void + { + super.onAddedToStage(e); + + _loadFromAssetManagerButton.addEventListener(starling.events.Event.TRIGGERED, onLoadFromAssetManagerButtonTriggered); + + refresh(); + } + + override protected function onRemovedFromStage(e:starling.events.Event):void + { + super.onRemovedFromStage(e); + + _loadFromAssetManagerButton.removeEventListener(starling.events.Event.TRIGGERED, onLoadFromAssetManagerButtonTriggered); + } + + override public function refresh():void + { + _texture = _getterFunc(); + _textureAlias = getTextureAlias(_texture); + + refreshPreview(); + } + + public function refreshPreview():void + { + _texturePreview.texture = _texture; + } + + private function onLoadFromAssetManagerButtonTriggered(e:starling.events.Event):void + { + if (_assetManager == null) + return; + + var assetManagerTexturePicker:AssetManagerTexturesPickerPanel = new AssetManagerTexturesPickerPanel(_assetManager, forcePotTexture); + assetManagerTexturePicker.textureSelected.add(onTextureSelected); + + // if possible, place the new panel aside the current inspector + if (_inspector) + { + assetManagerTexturePicker.x = _inspector.x + _inspector.width + InspectorConfiguration.COMPONENTS_PADDING; + assetManagerTexturePicker.y = _inspector.y; + } + + InspectorConfiguration.ROOT_LAYER.addChild(assetManagerTexturePicker); + } + + protected function onTextureSelected(texture:Texture, textureAlias:String):void + { + // update texture + _texture = texture; + _textureAlias = textureAlias; + + // refresh entry + refreshPreview(); + + // update + _setterFunc(_texture); + } + + public function get textureAlias():String + { + return _textureAlias; + } + + public function getTextureAlias(texture:Texture):String + { + if (texture == null) + return null; + + if (_assetManager == null) + return null; + + var textures:Vector. = _assetManager.getTextures(); + var texturesNames:Vector. = _assetManager.getTextureNames(); + + var len:int = textures.length; + for (var i:int = 0; i < len; i++) + { + var tex:Texture = textures[i]; + if (texture == tex) + return texturesNames[i]; + } + + return null; + } + + private function createImage():void + { + if (_texture) + _texturePreview = new Image(_texture); + else + _texturePreview = new Image(Texture.fromBitmapData(new BitmapData(1, 1))); + + _texturePreview.width = 24; + _texturePreview.height = 24; + addChild(_texturePreview); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _titleLabel.x = _paddingLeft; + _titleLabel.width = getLabelWidth(); + + _texturePreview.x = _titleLabel.x + _titleLabel.width + InspectorConfiguration.COMPONENTS_PADDING; + _loadFromAssetManagerButton.x = _texturePreview.x + _texturePreview.width + InspectorConfiguration.COMPONENTS_PADDING; + } + + override public function get height():Number + { + return 24; + } + + override public function set height(value:Number):void + { + // nop + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/entry/ToggleSwitchInspectorEntry.as b/src/ch/adolio/display/ui/inspector/entry/ToggleSwitchInspectorEntry.as new file mode 100644 index 0000000..4d9ed4f --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/entry/ToggleSwitchInspectorEntry.as @@ -0,0 +1,105 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.entry +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Label; + import feathers.controls.ToggleSwitch; + import starling.events.Event; + + public class ToggleSwitchInspectorEntry extends InspectorEntry + { + private var _label:Label; + private var _toggleSwitch:ToggleSwitch; + private var _getterFunc:Function; + private var _setteFunc:Function; + + private var _disableCallback:Boolean = false; + + public function ToggleSwitchInspectorEntry(title:String, onText:String, offText:String, getterFunc:Function, setterFunc:Function = null) + { + _getterFunc = getterFunc; + _setteFunc = setterFunc; + + _label = new Label(); + _label.touchable = false; + _label.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_TITLE; + _label.text = title; + _label.height = _preferredHeight; + addChild(_label); + + _toggleSwitch = new ToggleSwitch(); + _toggleSwitch.styleName = InspectorConfiguration.STYLE_NAME_TOGGLE_SWITCH; + _toggleSwitch.isEnabled = setterFunc != null; + _toggleSwitch.isSelected = _getterFunc(); + _toggleSwitch.height = _preferredHeight; + _toggleSwitch.onText = onText; + _toggleSwitch.offText = offText; + _toggleSwitch.validate(); + addChild(_toggleSwitch); + + width = _preferredWidth; + } + + public function setIsSelected(value:Number, disableCallback:Boolean = true):void + { + _disableCallback = disableCallback; + _toggleSwitch.isSelected = value; + _disableCallback = false; + } + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + _toggleSwitch.addEventListener(Event.CHANGE, onCheckValueChanged); + + refresh(); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + _toggleSwitch.removeEventListener(Event.CHANGE, onCheckValueChanged); + } + + private function onCheckValueChanged(e:Event):void + { + if (_setteFunc && !_disableCallback) + _setteFunc(_toggleSwitch.isSelected); + } + + override public function refresh():void + { + setIsSelected(_getterFunc(), true); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + + _label.x = _paddingLeft; + _label.width = getLabelWidth(); + _toggleSwitch.x = _label.x + _label.width + InspectorConfiguration.COMPONENTS_PADDING; + _toggleSwitch.width = getAvailableWidthForInputComponents(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/ColorSelectorPanel.as b/src/ch/adolio/display/ui/inspector/panel/ColorSelectorPanel.as new file mode 100644 index 0000000..533d9fc --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/ColorSelectorPanel.as @@ -0,0 +1,240 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.display.ui.inspector.entry.ActionInspectorEntry; + import ch.adolio.display.ui.inspector.entry.SliderInspectorEntry; + import org.osflash.signals.Signal; + import starling.display.DisplayObject; + import starling.display.Quad; + import starling.display.Sprite; + import starling.events.Event; + import starling.utils.Color; + + public class ColorSelectorPanel extends InspectorPanel + { + // color + private var _color:uint; + + // parent + private var _entry:DisplayObject; + + // UI elements + private var _previewColor:Quad; + private var _redSlider:SliderInspectorEntry; + private var _greenSlider:SliderInspectorEntry; + private var _blueSlider:SliderInspectorEntry; + private var _hueSlider:SliderInspectorEntry; + private var _saturationSlider:SliderInspectorEntry; + private var _lightnessSlider:SliderInspectorEntry; + + // events + public var colorChanged:Signal = new Signal(uint); // color:uint + + public function ColorSelectorPanel() + { + super(true, true); + + // setup title + title = "Color Selector"; + + createColorPreview(); + + addSparatorEntry("RGB"); + + _redSlider = new SliderInspectorEntry("Red", + function():Number { return 0; }, + function(value:Number):void { updateColorFromRGB(); } + , 0, 1.0, 0.01, false); + addEntry(_redSlider); + + _greenSlider = new SliderInspectorEntry("Green", + function():Number { return 0; }, + function(value:Number):void { updateColorFromRGB(); } + , 0, 1.0, 0.01, false); + addEntry(_greenSlider); + + _blueSlider = new SliderInspectorEntry("Blue", + function():Number { return 0; }, + function(value:Number):void { updateColorFromRGB(); } + , 0, 1.0, 0.01, false); + addEntry(_blueSlider); + + addSparatorEntry("HSL"); + + _hueSlider = new SliderInspectorEntry("Hue", + function():Number { return 0; }, + function(value:Number):void { updateColorFromHSL(); } + , 0, 1.0, 0.01, false); + addEntry(_hueSlider); + + _saturationSlider = new SliderInspectorEntry("Saturation", + function():Number { return 0; }, + function(value:Number):void { updateColorFromHSL(); } + , 0, 1.0, 0.01, false); + addEntry(_saturationSlider); + + _lightnessSlider = new SliderInspectorEntry("Lightness", + function():Number { return 0; }, + function(value:Number):void { updateColorFromHSL(); } + , 0, 1.0, 0.01, false); + addEntry(_lightnessSlider); + + addEntry(new ActionInspectorEntry("OK", function():void { close(); })); + + // setup size + setupHeightFromContent(); + } + + private function createColorPreview():void + { + var previewContainer:Sprite = new Sprite(); + + var size:Number = 25; + var bgMargin:Number = 5; + var previewBackground:Quad = new Quad(size + bgMargin * 2, size + bgMargin * 2, 0x0); + previewContainer.addChild(previewBackground); + + _previewColor = new Quad(size, size, 0x000000); + _previewColor.x = bgMargin; + _previewColor.y = bgMargin; + previewContainer.addChild(_previewColor); + + addEntry(previewContainer); + } + + private function updateColorFromHSL():void + { + // compute color from HSL + var h:Number = _hueSlider.getValue(); + var s:Number = _saturationSlider.getValue(); + var l:Number = _lightnessSlider.getValue(); + _color = Color.hsl(h, s, l); + updatePreview(); + + // update RGB + _redSlider.setValue(extractRed(_color) / 255.0); + _greenSlider.setValue(extractGreen(_color) / 255.0); + _blueSlider.setValue(extractBlue(_color) / 255.0); + + // notify color change + colorChanged.dispatch(_color); + } + + private function updateColorFromRGB():void + { + // compute color from RGB + var r:Number = _redSlider.getValue() * 255; + var g:Number = _greenSlider.getValue() * 255; + var b:Number = _blueSlider.getValue() * 255; + _color = combineRgb(r, g, b); + updatePreview(); + + // update HSL sliders + var hsl:Vector. = Color.rgbToHsl(_color); + _hueSlider.setValue(hsl[0]); + _saturationSlider.setValue(hsl[1]); + _lightnessSlider.setValue(hsl[2]); + + // notify color change + colorChanged.dispatch(_color); + } + + private function updatePreview():void + { + _previewColor.color = _color; + } + + public function set color(color:uint):void + { + _color = color; + updatePreview(); + + // update RGB sliders + _redSlider.setValue(extractRed(_color) / 255.0); + _greenSlider.setValue(extractGreen(_color) / 255.0); + _blueSlider.setValue(extractBlue(_color) / 255.0); + + // update HSL sliders + var hsl:Vector. = Color.rgbToHsl(_color); + _hueSlider.setValue(hsl[0]); + _saturationSlider.setValue(hsl[1]); + _lightnessSlider.setValue(hsl[2]); + } + + public function get color():uint + { + return _color; + } + + public function get entry():DisplayObject + { + return _entry; + } + + public function set entry(value:DisplayObject):void + { + // remove previous entry event listeners + if (_entry) + _entry.removeEventListener(Event.REMOVED_FROM_STAGE, onEntryRemovedFromStage); + + // update value + _entry = value; + + // add new entry event listeners + if (_entry) + _entry.addEventListener(Event.REMOVED_FROM_STAGE, onEntryRemovedFromStage); + } + + private function onEntryRemovedFromStage(event:Event):void + { + close(); + } + + override public function close():void + { + // remove entry event listeners + if (_entry) + _entry.removeEventListener(Event.REMOVED_FROM_STAGE, onEntryRemovedFromStage); + + super.close(); + } + + //--------------------------------------------------------------------- + //-- Color tools + //--------------------------------------------------------------------- + + public static function combineRgb(r:uint, g:uint, b:uint):uint + { + return ( ( r << 16 ) | ( g << 8 ) | b ); + } + + public static function extractRed(c:uint):uint + { + return (( c >> 16 ) & 0xFF); + } + + public static function extractGreen(c:uint):uint + { + return ( (c >> 8) & 0xFF ); + } + + public static function extractBlue(c:uint):uint + { + return ( c & 0xFF ); + } + + public static function colorToHexString(color:uint):String + { + return "0x" + color.toString(16).toUpperCase(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/DefaultInspectorBody.as b/src/ch/adolio/display/ui/inspector/panel/DefaultInspectorBody.as new file mode 100644 index 0000000..1d255dd --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/DefaultInspectorBody.as @@ -0,0 +1,84 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.ScrollBarDisplayMode; + import feathers.controls.ScrollContainer; + import feathers.controls.ScrollInteractionMode; + import feathers.controls.ScrollPolicy; + import feathers.layout.HorizontalAlign; + import feathers.layout.VerticalAlign; + import feathers.layout.VerticalLayout; + + /** Inspector body with vertical layout container for entries. */ + public class DefaultInspectorBody extends InspectorBody + { + protected var _scrollContainer:ScrollContainer; + protected var _vLayout:VerticalLayout; + + public function DefaultInspectorBody(inspector:InspectorPanel) + { + super(inspector); + + // setup vertical scroll container + _scrollContainer = new ScrollContainer(); + _vLayout = new VerticalLayout(); + _vLayout.horizontalAlign = HorizontalAlign.LEFT; + _vLayout.verticalAlign = VerticalAlign.TOP; + _vLayout.gap = InspectorConfiguration.COMPONENTS_PADDING; + _scrollContainer.layout = _vLayout; + _scrollContainer.interactionMode = ScrollInteractionMode.TOUCH_AND_SCROLL_BARS; + _scrollContainer.horizontalScrollPolicy = ScrollPolicy.OFF; + _scrollContainer.verticalScrollPolicy = ScrollPolicy.AUTO; + _scrollContainer.scrollBarDisplayMode = ScrollBarDisplayMode.NONE; + _scrollContainer.padding = InspectorConfiguration.COMPONENTS_PADDING; + addChild(_scrollContainer); + + // setup the entries container + _entriesContainer = _scrollContainer; + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + public function refreshLayout():void + { + _scrollContainer.readjustLayout(); + _scrollContainer.validate(); + } + + override public function setupHeightFromContent():void + { + // readjust container to content + _scrollContainer.height = NaN; + _scrollContainer.readjustLayout(); + _scrollContainer.validate(); + + // adjust from entries container + super.setupHeightFromContent(); + } + + override protected function computeEntryWidth():Number + { + return _preferredWidth - (_scrollContainer.paddingLeft + _scrollContainer.paddingRight); + } + + override public function set height(value:Number):void + { + super.height = value; + + // force invalidation of the scroll container to make sure that the panel is refreshed properly + _scrollContainer.invalidate(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/DisplayListInspectorPanel.as b/src/ch/adolio/display/ui/inspector/panel/DisplayListInspectorPanel.as new file mode 100644 index 0000000..d4382c9 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/DisplayListInspectorPanel.as @@ -0,0 +1,237 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.utils.InspectionUtils; + import feathers.controls.Button; + import feathers.controls.Tree; + import feathers.controls.renderers.DefaultTreeItemRenderer; + import feathers.controls.renderers.ITreeItemRenderer; + import feathers.data.ArrayHierarchicalCollection; + import starling.display.DisplayObject; + import starling.display.DisplayObjectContainer; + import starling.events.Event; + + /** + * A display list inspector panel + */ + public class DisplayListInspectorPanel extends InspectorPanel + { + // core + protected var _root:DisplayObjectContainer; + protected var _includeRoot:Boolean; + protected var _tree:Tree; + protected var _refreshButton:Button; + + // state + protected var _isInitialized:Boolean; + + /** Used to ignore certain objects from the tree. */ + public var ignoreList:Vector. = new Vector.(); + + public function DisplayListInspectorPanel(root:DisplayObjectContainer, includeRoot:Boolean) + { + super(); + + _root = root; + _includeRoot = includeRoot; + + title = "Display List Explorer"; + + // refresh button + _refreshButton = new Button(); + _refreshButton.label = "Refresh"; + addEntry(_refreshButton); + + // display list tree + _tree = new Tree(); + addEntry(_tree); + + _tree.itemRendererFactory = function():ITreeItemRenderer + { + var itemRenderer:DefaultTreeItemRenderer = new DefaultTreeItemRenderer(); + itemRenderer.labelField = "text"; + return itemRenderer; + }; + + initialize(); + } + + private function initialize():void + { + refreshTree(); + _isInitialized = true; + } + + private function refreshTree():void + { + // keep track of selected object + var selectedDisplayObject:DisplayObject = _tree.selectedItem != null ? _tree.selectedItem.displayObject : null; + + // setup data provider + var dataProvider:ArrayHierarchicalCollection = new ArrayHierarchicalCollection(); + var rootItem:Object; + if (_includeRoot) + { + // build root item + rootItem = new Object(); + rootItem.parent = null; + buildEntriesRecursively(_root, rootItem); + dataProvider.addItemAt(rootItem, 0); + } + else + { + // build children of root item + for (var i:int = 0; i < _root.numChildren; i++) + { + var rootObject:DisplayObject = _root.getChildAt(i); + if (ignoreList.indexOf(rootObject) != -1) + continue; + + rootItem = new Object(); + rootItem.parent = null; + buildEntriesRecursively(rootObject, rootItem); + dataProvider.addItemAt(rootItem, i); + } + } + + _tree.dataProvider = dataProvider; + + // re-select previously selected object + if (selectedDisplayObject) + selectObject(selectedDisplayObject); + } + + private function buildEntriesRecursively(displayObject:DisplayObject, item:Object):void + { + item.text = InspectionUtils.findObjectName(displayObject); + item.displayObject = displayObject; + + if (displayObject is DisplayObjectContainer) + { + var container:DisplayObjectContainer = displayObject as DisplayObjectContainer; + if (container.numChildren > 0) + { + item.children = new Array(); + for (var i:int = 0; i < container.numChildren; i++) + { + var childDisplayObject:DisplayObject = container.getChildAt(i); + if (ignoreList.indexOf(childDisplayObject) != -1) + continue; + + var childItem:Object = new Object(); + childItem.parent = item; + item.children.push(childItem); + + buildEntriesRecursively(childDisplayObject, childItem); + } + } + } + } + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + // register to events + _refreshButton.addEventListener(Event.TRIGGERED, onRefreshButtonTriggered); + _tree.addEventListener(Event.CHANGE, onSelectedItemChanged); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + // unregister from events + _refreshButton.removeEventListener(Event.TRIGGERED, onRefreshButtonTriggered); + _tree.removeEventListener(Event.CHANGE, onSelectedItemChanged); + } + + public function selectObject(object:DisplayObject):void + { + var item:Object = findItemOfObject(object); + if (item) + { + _tree.selectedItem = item; + + // make sure the parent branch is open + if (item.parent != null && !_tree.isBranchOpen(item.parent)) + { + _tree.toggleBranch(item.parent, true); + + // enforce refreshing the size of the tree + _tree.invalidate(); + } + } + } + + public function findItemOfObject(object:DisplayObject):Object + { + // data provider not ready yet + if (_tree.dataProvider == null) + return null; + + var len:int = _tree.dataProvider.getLength(); + for (var i:int = 0; i < len; i++) + { + var item:Object = _tree.dataProvider.getItemAt(i); + var foundItem:Object = findChildItemOfObject(item, object); + if (foundItem != null) + return foundItem; + } + + return null; + } + + public function findChildItemOfObject(item:Object, needle:DisplayObject):Object + { + // found! + if (item.displayObject == needle) + return item; + + // no children + if (!item.hasOwnProperty("children")) + return null; + + // browse children + var children:Array = item.children; + for (var i:int = 0; i < children.length; i++) + { + var childItem:Object = children[i]; + var foundItem:Object = findChildItemOfObject(childItem, needle); + if (foundItem != null) + return foundItem; + } + + return null; + } + + private function onRefreshButtonTriggered(event:Event):void + { + refreshTree(); + } + + private function onSelectedItemChanged(event:Event):void + { + // nothing selected + if (_tree.selectedItem == null) + { + ObjectInspectorPanel.instance.object = null; + return; + } + + if (ObjectInspectorPanel.instance.object == _tree.selectedItem.displayObject) + return; + + ObjectInspectorPanel.instance.object = _tree.selectedItem.displayObject; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/InspectorBody.as b/src/ch/adolio/display/ui/inspector/panel/InspectorBody.as new file mode 100644 index 0000000..4b493cb --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/InspectorBody.as @@ -0,0 +1,154 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.entry.InspectorEntry; + import ch.adolio.display.ui.inspector.entry.SeparatorInspectorEntry; + import starling.display.DisplayObject; + import starling.display.Quad; + import starling.display.Sprite; + import starling.events.Event; + + public class InspectorBody extends Sprite + { + protected var _inspector:InspectorPanel; + + protected var _preferredWidth:Number = 100; + protected var _preferredHeight:Number = 100; + + private var _bg:Quad; + protected var _entriesContainer:Sprite; + protected var _entries:Vector. = new Vector.(); + + public function InspectorBody(inspector:InspectorPanel) + { + _inspector = inspector; + + // setup background + _bg = new Quad(1, 1, InspectorConfiguration.COLOR_PANEL_BODY_BACKGROUND_COLOR); + _bg.alpha = InspectorConfiguration.COLOR_PANEL_BODY_BACKGROUND_ALPHA; + addChild(_bg); + + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + public function get inspector():InspectorPanel + { + return _inspector; + } + + //--------------------------------------------------------------------- + //-- Entries management + //--------------------------------------------------------------------- + + public function addSparatorEntry(title:String):SeparatorInspectorEntry + { + var inspectorSeparatorEntry:SeparatorInspectorEntry = new SeparatorInspectorEntry(title); + addEntry(inspectorSeparatorEntry); + return inspectorSeparatorEntry; + } + + public function addEntry(entry:DisplayObject):void + { + // setup width + entry.width = computeEntryWidth(); + + // setup inspector if possible + if (_inspector && entry is InspectorEntry) + (entry as InspectorEntry).inspector = _inspector; + + // add to entries list + _entries.push(entry); + + // add to container + if (_entriesContainer) + _entriesContainer.addChild(entry); + } + + public function removeEntries(dispose:Boolean = false):void + { + _entriesContainer.removeChildren(0, -1, dispose); + _entries.length = 0; + } + + public function get entries():Vector. + { + return _entries; + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + protected function onAddedToStage(e:Event):void + { + removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + } + + protected function onRemovedFromStage(e:Event):void + { + removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + public function setupHeightFromContent():void + { + // update height + if (_entriesContainer) + height = _entriesContainer.height; + } + + protected function computeEntryWidth():Number + { + return _preferredWidth; + } + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + _preferredWidth = value; + super.width = _preferredWidth; + + if (_entriesContainer) + _entriesContainer.width = value; + _bg.width = value; + + var entryWidth:Number = computeEntryWidth(); + for each (var entry:DisplayObject in _entries) + entry.width = entryWidth; + } + + override public function get height():Number + { + return _preferredHeight; + } + + override public function set height(value:Number):void + { + _preferredHeight = value; + super.height = _preferredHeight; + + if (_entriesContainer) + _entriesContainer.height = value; + _bg.height = value; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/InspectorPanel.as b/src/ch/adolio/display/ui/inspector/panel/InspectorPanel.as new file mode 100644 index 0000000..0d08d0c --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/InspectorPanel.as @@ -0,0 +1,423 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.entry.InspectorEntry; + import ch.adolio.display.ui.inspector.entry.SeparatorInspectorEntry; + import feathers.controls.Button; + import feathers.controls.Label; + import feathers.layout.VerticalLayout; + import flash.geom.Point; + import flash.geom.Rectangle; + import starling.core.Starling; + import starling.display.DisplayObject; + import starling.display.Quad; + import starling.display.Sprite; + import starling.events.Event; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + + public class InspectorPanel extends Sprite + { + // panel + protected var _preferredWidth:Number = InspectorConfiguration.PANEL_DEFAULT_WIDTH; + protected var _preferredHeight:Number = InspectorConfiguration.PANEL_DEFAULT_HEIGHT; + + // dragging + protected var _isDraggable:Boolean = true; + protected var _dragOffset:Point = new Point(); + protected var _positionAtGrab:Point = new Point(); + protected var _staysInScreenBounds:Boolean = true; + + // header + protected var _header:Sprite; + protected var _headerBackground:Quad; + protected var _titleLabel:Label; + protected var _closeButton:Button; + + // body + protected var _bodyContainer:Sprite; + protected var _vLayout:VerticalLayout; + protected var _body:InspectorBody; + + // separators + private static var numSeparators:int = 0; + + // footer + protected var _footer:Sprite; + protected var _footerBackground:Quad; + + // size grabber + private var _isResizeable:Boolean; + private var _sideGrabOffset:Point = new Point(); + private var _widthAtGrab:Number; + private var _heightAtGrab:Number; + private var _sizeGrabber:Quad; + + // screen + private static var screenBounds:Rectangle = new Rectangle(); + + public function InspectorPanel(isClosable:Boolean = true, isResizeable:Boolean = true) + { + _isResizeable = isResizeable; + + // header + _header = new Sprite(); + addChild(_header); + + _titleLabel = new Label(); + _titleLabel.styleName = InspectorConfiguration.STYLE_NAME_LABEL_PANEL_TITLE; + _titleLabel.text = "Title"; + _titleLabel.minHeight = InspectorConfiguration.PANEL_HEADER_MIN_HEIGHT; + _titleLabel.touchable = false; + _titleLabel.validate(); + + _headerBackground = new Quad(_preferredWidth, _titleLabel.height, InspectorConfiguration.COLOR_PANEL_HEADER_BACKGROUND_COLOR); + _headerBackground.alpha = InspectorConfiguration.COLOR_PANEL_HEADER_BACKGROUND_ALPHA; + _header.addChild(_headerBackground); + + _header.addChild(_titleLabel); + _closeButton = new Button(); + _closeButton.styleName = InspectorConfiguration.STYLE_NAME_PANEL_CLOSE_BUTTON; + _closeButton.height = _headerBackground.height; + _closeButton.label = "X"; + _closeButton.maxWidth = 24; + _closeButton.visible = isClosable; + _closeButton.validate(); + _header.addChild(_closeButton); + + // body + _bodyContainer = new Sprite(); + _bodyContainer.y = _header.y + _header.height; + addChild(_bodyContainer); + + // default body + _body = new DefaultInspectorBody(this); + _bodyContainer.addChild(_body); + + // footer + _footer = new Sprite(); + addChild(_footer); + _footerBackground = new Quad(_preferredWidth, InspectorConfiguration.PANEL_FOOTER_HEIGHT, InspectorConfiguration.COLOR_PANEL_FOOTER_BACKGROUND_COLOR); + _footerBackground.alpha = InspectorConfiguration.COLOR_PANEL_FOOTER_BACKGROUND_ALPHA; + _footer.addChild(_footerBackground); + + // side grabbers + if (_isResizeable) + setupSizeGrabber(); + + // setup size + width = _preferredWidth; + height = _preferredHeight; + + // register to stage addition + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + private function setupSizeGrabber():void + { + _sizeGrabber = new Quad(_footerBackground.height, _footerBackground.height, InspectorConfiguration.PANEL_FOOTER_SIZE_GRABBER_COLOR); + _sizeGrabber.x = _footerBackground.width - _sizeGrabber.width; + _footer.addChild(_sizeGrabber); + } + + public function get title():String + { + return _titleLabel.text; + } + + public function set title(value:String):void + { + _titleLabel.text = value; + } + + public function close():void + { + removeFromParent(); + } + + public function checkScreenBounds():void + { + // compute screen bounds + Starling.current.stage.getScreenBounds(Starling.current.stage, screenBounds); + var panelBounds:Rectangle = bounds; + + // check top + if (panelBounds.top < screenBounds.top) + y = screenBounds.top; + + // check bottom + if (panelBounds.bottom > screenBounds.bottom) + { + // resize if larger than the screen + if (panelBounds.height > screenBounds.height) + { + height = screenBounds.height; + panelBounds = bounds; // update bounds + } + + // move up + y = screenBounds.bottom - panelBounds.height; + } + + // check left + if (panelBounds.left < screenBounds.left) + x = screenBounds.left; + + // check right + if (panelBounds.right > screenBounds.right) + x = screenBounds.right - width; + } + + public function bringInFront():void + { + // check that panel is in front + if (parent.getChildIndex(this) != parent.numChildren-1) + parent.addChild(this); + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + protected function onAddedToStage(e:Event):void + { + removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + + // check screen bounds + if (_staysInScreenBounds) + checkScreenBounds(); + + // invalidate components + _titleLabel.invalidate(); + _closeButton.invalidate(); + + // register to events + _headerBackground.addEventListener(TouchEvent.TOUCH, onHeaderTouched); + _footerBackground.addEventListener(TouchEvent.TOUCH, onHeaderTouched); + _closeButton.addEventListener(Event.TRIGGERED, onCloseButtonTriggered); + + if (_sizeGrabber) + _sizeGrabber.addEventListener(TouchEvent.TOUCH, onBottomSideTouched); + } + + protected function onRemovedFromStage(e:Event):void + { + removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + + // unregister from events + _headerBackground.removeEventListener(TouchEvent.TOUCH, onHeaderTouched); + _footerBackground.removeEventListener(TouchEvent.TOUCH, onHeaderTouched); + _closeButton.removeEventListener(Event.TRIGGERED, onCloseButtonTriggered); + + if (_sizeGrabber) + _sizeGrabber.removeEventListener(TouchEvent.TOUCH, onBottomSideTouched); + } + + private function onHeaderTouched(e:TouchEvent):void + { + if (!_isDraggable) + return; + + var touch:Touch = e.touches[0]; + + switch (touch.phase) + { + case TouchPhase.BEGAN: + + // setup drag + _dragOffset.x = touch.globalX; + _dragOffset.y = touch.globalY; + _positionAtGrab.x = x; + _positionAtGrab.y = y; + + // bring panel in front + bringInFront(); + + break; + case TouchPhase.MOVED: + + // update position + x = _positionAtGrab.x + Math.round(touch.globalX - _dragOffset.x); + y = _positionAtGrab.y + Math.round(touch.globalY - _dragOffset.y); + + // check bounds + if (_staysInScreenBounds) + checkScreenBounds(); + + break; + } + } + + private function onBottomSideTouched(e:TouchEvent):void + { + var touch:Touch = e.touches[0]; + + switch (touch.phase) + { + case TouchPhase.BEGAN: + + // setup size grab + _sideGrabOffset.x = touch.globalX; + _sideGrabOffset.y = touch.globalY; + _widthAtGrab = width; + _heightAtGrab = height; + + // bring panel in front + bringInFront(); + + break; + case TouchPhase.MOVED: + + // update dimensions + Starling.current.stage.getScreenBounds(Starling.current.stage, screenBounds); + width = Math.min(_widthAtGrab + Math.round(touch.globalX - _sideGrabOffset.x), screenBounds.right - x); + height = Math.min(_heightAtGrab + Math.round(touch.globalY - _sideGrabOffset.y), screenBounds.bottom - y); + break; + } + } + + private function onCloseButtonTriggered(event:Event):void + { + close(); + } + + //--------------------------------------------------------------------- + //-- Entries management + //--------------------------------------------------------------------- + + protected function addSparatorEntry(title:String):SeparatorInspectorEntry + { + return _body.addSparatorEntry(title); + } + + public function addEntry(entry:DisplayObject):void + { + _body.addEntry(entry); + } + + public function removeEntries(dispose:Boolean = false):void + { + _body.removeEntries(dispose); + } + + public function updateEntries():void + { + for each (var entry:DisplayObject in _body.entries) + { + if (entry is InspectorEntry) + (entry as InspectorEntry).refresh(); + } + } + + public function get body():InspectorBody + { + return _body; + } + + public function set body(body:InspectorBody):void + { + _body = body; + + _bodyContainer.removeChildren(); + + if (_body) + { + _bodyContainer.addChild(_body); + _body.width = width; + _body.height = height - (_header.height + _footer.height); + } + } + + //--------------------------------------------------------------------- + //-- Drag management + //--------------------------------------------------------------------- + + public function get isDraggable():Boolean + { + return _isDraggable; + } + + public function set isDraggable(value:Boolean):void + { + _isDraggable = value; + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + public function setupHeightFromContent():void + { + if (_body) + { + _body.setupHeightFromContent(); + height = _header.height + _body.height + _footer.height; + } + else + { + height = _header.height + _footer.height; + } + + checkScreenBounds(); + } + + override public function get width():Number + { + return _preferredWidth; + } + + override public function set width(value:Number):void + { + // check minimal allowed width + if (value < 64) + value = 64; + + // update components + _preferredWidth = value; + _closeButton.x = value - _closeButton.width; + _titleLabel.width = value - _closeButton.width; + _headerBackground.width = value; + _footerBackground.width = value; + + if (_sizeGrabber) + _sizeGrabber.x = value - _sizeGrabber.width; + + // update body + if (_body) + _body.width = value; + } + + override public function get height():Number + { + return _preferredHeight; + } + + override public function set height(value:Number):void + { + // check minimal allowed height + if (value < 64) + value = 64; + + // update components + _preferredHeight = value; + _footer.y = _preferredHeight - _footer.height; + + // update body + if (_body) + _body.height = value - (_header.height + _footer.height); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/InspectorTabPanel.as b/src/ch/adolio/display/ui/inspector/panel/InspectorTabPanel.as new file mode 100644 index 0000000..ecfd512 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/InspectorTabPanel.as @@ -0,0 +1,160 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.TabBar; + import feathers.data.ListCollection; + import starling.display.Quad; + import starling.events.Event; + + public class InspectorTabPanel extends InspectorPanel + { + protected var _tabBarBackground:Quad; + protected var _tabBar:TabBar; + + public function InspectorTabPanel() + { + // create tab bar & tab bar background for first size call + _tabBar = new TabBar(); + _tabBar.customTabStyleName = InspectorConfiguration.STYLE_NAME_TAB_TOGGLE_BUTTON; + _tabBarBackground = new Quad(1, 1, 0xaaaaaa); + + super(); + + // remove default container + body = null; + + // setup tab bar background + _tabBarBackground.y = _header.height; + addChild(_tabBarBackground); + + // setup tab bar + _tabBar.dataProvider = new ListCollection(); + _tabBar.width = width; + _tabBar.y = _header.height; + addChild(_tabBar); + _tabBar.validate(); + + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + protected function setupTabContentFromTabBar():void + { + // setup body for currently selected tab index + for (var i:uint; i < _tabBar.dataProvider.length; ++i) + { + if (_tabBar.selectedIndex == i) + { + body = _tabBar.dataProvider.getItemAt(i).body; + return; + } + } + + // no valid body found + body = null; + } + + public function addTab(id:String, body:InspectorBody):InspectorBody + { + // create new tab + _tabBar.dataProvider.addItem({ label: id, body: body }); + + // setup tab content + body.width = width; + body.height = _preferredHeight - _tabBar.height - _header.height; + + // adjust body container + _tabBar.validate(); + _bodyContainer.y = _tabBar.y + _tabBar.height; + _tabBarBackground.height = _tabBar.height; + + return body; + } + + public function replaceTabBody(previousBody:InspectorBody, newBody:InspectorBody):void + { + var selectedIndex:int = _tabBar.selectedIndex; + + var len:int = _tabBar.dataProvider.length; + for (var i:int = 0; i < len; i++) + { + var item:Object = _tabBar.dataProvider.getItemAt(i); + if (item.body == previousBody) + { + // update environment item + _tabBar.dataProvider.setItemAt({ label:item.label, body:newBody }, i); + + // update active body if selected index was the replaced body + if (selectedIndex == i) + body = newBody; + + return; + } + } + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + // register to events + _tabBar.addEventListener(Event.CHANGE, onTabSelectionChanged); + + // refresh selected index + setupTabContentFromTabBar(); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + // unregister from events + _tabBar.removeEventListener(Event.CHANGE, onTabSelectionChanged); + } + + private function onTabSelectionChanged(e:Event):void + { + setupTabContentFromTabBar(); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function set width(value:Number):void + { + super.width = value; + + // resize tabs bar + _tabBar.width = width; + _tabBarBackground.width = width; + _tabBar.validate(); + + // resize body + if (_body) + _body.width = width; + } + + override public function set height(value:Number):void + { + super.height = value; + + // resize body + if (_body) + _body.height = height - _tabBar.height - _header.height - _footer.height; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/ObjectInspectorPanel.as b/src/ch/adolio/display/ui/inspector/panel/ObjectInspectorPanel.as new file mode 100644 index 0000000..03edef2 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/ObjectInspectorPanel.as @@ -0,0 +1,620 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.display.shape.BorderedRectangle; + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.entry.BlendModeInspectorEntry; + import ch.adolio.display.ui.inspector.entry.CheckInspectorEntry; + import ch.adolio.display.ui.inspector.entry.ColorInspectorEntry; + import ch.adolio.display.ui.inspector.entry.ObjectReferenceInspectorEntry; + import ch.adolio.display.ui.inspector.entry.SliderInspectorEntry; + import ch.adolio.display.ui.inspector.entry.TextInputInspectorEntry; + import ch.adolio.display.ui.inspector.entry.TextureInspectorEntry; + import ch.adolio.utils.InspectionUtils; + import feathers.controls.Button; + import flash.geom.Rectangle; + import flash.utils.describeType; + import starling.animation.IAnimatable; + import starling.core.Starling; + import starling.display.DisplayObject; + import starling.events.Event; + import starling.textures.Texture; + + /** + * Inspector panel for any Object. + * + *

The `Inspectable` metadata can be used for `Number` field type. + * Usage: `[Inspectable(min=0, max=5.0, step=0.5)]`

+ */ + public class ObjectInspectorPanel extends InspectorPanel implements IAnimatable + { + // object + private var _object:Object; + private var _hasSizeBeenSetup:Boolean = false; + + // stack + private var _objectStack:Vector. = new Vector.(); + private var _backButton:Button; + + // inspection quad + private var _boundsIndicator:BorderedRectangle; + private var _objectBounds:Rectangle = new Rectangle(); + + // native types + private const TYPE_BOOLEAN:String = "Boolean"; + private const TYPE_NUMBER:String = "Number"; + private const TYPE_INT:String = "int"; + private const TYPE_UINT:String = "uint"; + private const TYPE_STRING:String = "String"; + + // special types + private const TYPE_TEXTURE:String = "starling.textures::Texture"; + + // access + private const ACCESS_READ_ONLY:String = "readonly"; + private const ACCESS_READ_WRITE:String = "readwrite"; + + // metadata + private const INSPECTABLE_METADATA_NAME:String = "Inspectable"; + private const INSPECTABLE_METADATA_KEY_MIN:String = "min"; + private const INSPECTABLE_METADATA_KEY_MAX:String = "max"; + private const INSPECTABLE_METADATA_KEY_STEP:String = "step"; + + // singleton + private static var _instance:ObjectInspectorPanel; + static public function get instance():ObjectInspectorPanel + { + if (_instance == null) + _instance = new ObjectInspectorPanel(); + + return _instance; + } + + public function ObjectInspectorPanel() + { + super(); + + _backButton = new Button(); + _backButton.styleName = InspectorConfiguration.STYLE_NAME_PANEL_BACK_BUTTON; + _backButton.height = _headerBackground.height; + _backButton.label = "Back"; + _backButton.visible = false; + _footer.addChild(_backButton); + } + + public function get object():Object + { + return _object; + } + + public function set object(value:Object):void + { + inspect(value, true, false); + } + + /** + * Inspects a given object. + * + * @param value The object to inspect + * @param resetStack Reset the stack (breadcrumb navigation) + * @param stackPreviousObject Stack the object to enable the back button and return to the previous object + */ + public function inspect(value:Object, resetStack:Boolean, stackPreviousObject:Boolean):void + { + // reset the breadcrumb + if (resetStack) + _objectStack.length = 0; + + // stack current object + if (object != null && stackPreviousObject) + _objectStack.push(object); + + // update the entity + _object = value; + + // remove the inspection rectangle, it will be re-added just after if available + if (_boundsIndicator) + _boundsIndicator.removeFromParent(); + + // setup panel + if (_object) + { + // setup title + title = InspectionUtils.findObjectName(_object); + + // setup entries + setupEntries(); + + // update overlay + updateInspectionOverlay(); + } + else + { + // reset title + title = ""; + + // dispoes all entries + _body.removeEntries(true); + } + + // force stage re-addition to perfom checks + if (parent != null) + removeFromParent(); + + // setup & add to stage + InspectorConfiguration.ROOT_LAYER.addChild(ObjectInspectorPanel.instance); + + // reset stack + refreshBackButton(); + } + + override public function close():void + { + object = null; + + super.close(); + } + + private function refreshBackButton():void + { + _backButton.visible = _objectStack.length > 0; + } + + //---------------------------------------------------------------------- + //-- Entries management + //---------------------------------------------------------------------- + + private function setupEntries():void + { + // dispoes all entries + _body.removeEntries(true); + + // variable (for static objects) + var description:XML = describeType(_object); + //Log.debug("Description: " + description + ""); + + var type:String; + var name:String; + var access:String; + + // variables + for each (var variable:XML in description.variable) + { + name = variable.@name; + type = variable.@type; + + if (type == TYPE_BOOLEAN) + addBooleanEntry(name, ACCESS_READ_WRITE); + else if (type == TYPE_NUMBER) + addNumberEntry(name, ACCESS_READ_WRITE, variable.metadata); + else if (type == TYPE_INT) + addIntEntry(name, ACCESS_READ_WRITE); + else if (type == TYPE_UINT) + addUintEntry(name, ACCESS_READ_WRITE); + else if (type == TYPE_STRING) + addStringEntry(name, ACCESS_READ_WRITE); + else if (type == TYPE_TEXTURE) + addTextureEntry(name, ACCESS_READ_WRITE); + else + addObjectReferenceEntry(name, ACCESS_READ_WRITE); + } + + // accessors + for each (var accessor:XML in description.accessor) + { + name = accessor.@name; + type = accessor.@type; + access = accessor.@access; + + if (type == TYPE_BOOLEAN) + addBooleanEntry(name, access); + else if (type == TYPE_NUMBER) + addNumberEntry(name, access, accessor.metadata); + else if (type == TYPE_INT) + addIntEntry(name, access); + else if (type == TYPE_UINT) + addUintEntry(name, access); + else if (type == TYPE_STRING) + addStringEntry(name, access); + else if (type == TYPE_TEXTURE) + addTextureEntry(name, access); + else + addObjectReferenceEntry(name, access); + } + + // setup size at the first inspection + if (_hasSizeBeenSetup) + { + setupHeightFromContent(); + _hasSizeBeenSetup = true; + } + } + + private function addBooleanEntry(fieldName:String, access:String):void + { + if (access == ACCESS_READ_ONLY) + { + addEntry(new CheckInspectorEntry(fieldName, + function():Boolean { return _object[fieldName]; }, + null) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new CheckInspectorEntry(fieldName, + function():Boolean { return _object[fieldName]; }, + function(value:Boolean):void { _object[fieldName] = value; }) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + private function addNumberEntry(fieldName:String, access:String, metadataList:XMLList):void + { + var value:Number = _object[fieldName]; + + // guess best min, max + var min:Number = -100; + var max:Number = 100; + + // check for "alpha" keyword in fieldName + if (fieldName.toLowerCase().indexOf("alpha") != -1) + { + min = 0; + max = 1.0; + step = 0.05; + } + else + { + if (value != 0) + { + min = value / 10.0; + max = value * 10.0; + } + + if (min > max) + { + var temp:Number = max; + max = min; + min = max; + } + + // guess best step from min/max + var step:Number = (max - min) / 100.0; + } + + // override guessed values with metadata + if (metadataList) + { + for each (var metadata:XML in metadataList) + { + if (metadata.@name == INSPECTABLE_METADATA_NAME) + { + for each (var metadataArg:XML in metadata.arg) + { + switch (metadataArg.@key.toString()) + { + case INSPECTABLE_METADATA_KEY_MIN: + min = Number(metadataArg.@value); + break; + case INSPECTABLE_METADATA_KEY_MAX: + max = Number(metadataArg.@value); + break; + case INSPECTABLE_METADATA_KEY_STEP: + step = Math.abs(Number(metadataArg.@value)); + break; + default: + trace("Unsupported argument '" + metadataArg.@key + "' in '" + INSPECTABLE_METADATA_NAME + "' metadata for field '" + fieldName + "'."); + break; + } + } + } + } + + // invert min / max if needed + if (min > max) + { + temp = max; + max = min; + min = max; + } + } + + if (access == ACCESS_READ_ONLY) + { + addEntry(new SliderInspectorEntry(fieldName, + function():Number { return _object[fieldName]; }, + null, min, max, step, false) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new SliderInspectorEntry(fieldName, + function():Number { return _object[fieldName]; }, + function(value:Number):void { _object[fieldName] = value; }, + min, max, step, false) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + private function addIntEntry(fieldName:String, access:String):void + { + // guest min, max & step values + var value:int = _object[fieldName]; + var min:int = -Math.abs(value) * 5.0; + var max:int = Math.abs(value) * 5.0; + + if (value == 0) + { + min = -100; + max = 100; + } + + var step:Number = Math.round((max - min) / 100.0); + + if (access == ACCESS_READ_ONLY) + { + addEntry(new SliderInspectorEntry(fieldName, + function():int { return _object[fieldName]; }, + null, min, max, step, false) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new SliderInspectorEntry(fieldName, + function():int { return _object[fieldName]; }, + function(value:int):void { _object[fieldName] = value; }, + min, max, step, false) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + private function addUintEntry(fieldName:String, access:String):void + { + // check for "color" keyword in fieldName + if (fieldName.toLowerCase().indexOf("color") != -1) + { + addColorEntry(fieldName, access); + return; + } + + // guest min, max & step values + var value:uint = _object[fieldName]; + var min:uint = Math.round(value / 10.0); + var max:uint = value * 10.0; + + if (value == 0) + { + min = 0; + max = 100; + } + + var step:Number = Math.round((max - min) / 100.0); + + if (access == ACCESS_READ_ONLY) + { + addEntry(new SliderInspectorEntry(fieldName, + function():uint { return _object[fieldName]; }, + null, min, max, step, false) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new SliderInspectorEntry(fieldName, + function():uint { return _object[fieldName]; }, + function(value:uint):void { _object[fieldName] = value; }, + min, max, step, false) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + private function addStringEntry(fieldName:String, access:String):void + { + // check for "blendMode" keyword in fieldName + if (fieldName.toLowerCase().indexOf("blendmode") != -1) + { + addBlendModeEntry(fieldName, access); + return; + } + + if (access == ACCESS_READ_ONLY) + { + addEntry(new TextInputInspectorEntry(fieldName, + function():String { return _object[fieldName]; }) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new TextInputInspectorEntry(fieldName, + function():String { return _object[fieldName]; }, + function(value:String):void { _object[fieldName] = value; }) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + private function addBlendModeEntry(fieldName:String, access:String):void + { + if (access == ACCESS_READ_ONLY) + { + addEntry(new BlendModeInspectorEntry(fieldName, + function():String { return _object[fieldName]; }) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new BlendModeInspectorEntry(fieldName, + function():String { return _object[fieldName]; }, + function(value:String):void { _object[fieldName] = value; }) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + private function addTextureEntry(fieldName:String, access:String):void + { + if (access == ACCESS_READ_ONLY) + { + addEntry(new TextureInspectorEntry(fieldName, null, + function():Texture { return _object[fieldName]; }, + null, + false) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new TextureInspectorEntry(fieldName, null, + function():Texture { return _object[fieldName]; }, + function(value:Texture):void { _object[fieldName] = value; }, + false) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + private function addObjectReferenceEntry(fieldName:String, access:String):void + { + addEntry(new ObjectReferenceInspectorEntry(fieldName, + function():Object { return _object[fieldName]; }, + function():void + { + if (_object[fieldName] != null) + ObjectInspectorPanel.instance.inspect(_object[fieldName], false, true); + }) + ); + } + + private function addColorEntry(fieldName:String, access:String):void + { + if (access == ACCESS_READ_ONLY) + { + addEntry(new ColorInspectorEntry(fieldName, + function():uint { return _object[fieldName]; }, + null) + ); + } + else if (access == ACCESS_READ_WRITE) + { + addEntry(new ColorInspectorEntry(fieldName, + function():uint { return _object[fieldName]; }, + function(value:uint):void { _object[fieldName] = value; }) + ); + } + else + { + trace("Unsupported access for field '"+ fieldName +"': " + access); + } + } + + //---------------------------------------------------------------------- + //-- Events handlers + //---------------------------------------------------------------------- + + override protected function onAddedToStage(e:Event):void + { + super.onAddedToStage(e); + + Starling.juggler.add(this); + + _backButton.addEventListener(Event.TRIGGERED, onBackButtonTriggered); + } + + override protected function onRemovedFromStage(e:Event):void + { + super.onRemovedFromStage(e); + + Starling.juggler.remove(this); + + _backButton.removeEventListener(Event.TRIGGERED, onBackButtonTriggered); + } + + private function onBackButtonTriggered(event:Event):void + { + if (_objectStack.length > 0) + { + inspect(_objectStack.pop(), false, false); + refreshBackButton(); + } + } + + //---------------------------------------------------------------------- + //-- Inspection overlay for Display Object + //---------------------------------------------------------------------- + + private function updateInspectionOverlay():void + { + if (!_object) + return; + + if (!(_object is DisplayObject)) + return; + + // instantiate bounds indicator + if (!_boundsIndicator) + { + _boundsIndicator = new BorderedRectangle(1, 1, + InspectorConfiguration.INSPECTED_OBJECT_BOUNDS_COLOR, + InspectorConfiguration.INSPECTED_OBJECT_BOUNDS_BORDER_SIZE, + InspectorConfiguration.INSPECTED_OBJECT_BOUNDS_COLOR); + _boundsIndicator.touchable = false; + _boundsIndicator.bodyAlpha = 0.1; + _boundsIndicator.borderAlpha = 0.5; + } + + // find the bounds in the root layer space + var displayObject:DisplayObject = _object as DisplayObject; + displayObject.getBounds(InspectorConfiguration.ROOT_LAYER, _objectBounds); + _boundsIndicator.x = _objectBounds.x; + _boundsIndicator.y = _objectBounds.y; + _boundsIndicator.width = _objectBounds.width; + _boundsIndicator.height = _objectBounds.height; + + // add the bound indicator in the root layer + InspectorConfiguration.ROOT_LAYER.addChild(_boundsIndicator); + } + + public function advanceTime(time:Number):void + { + if (!_object) + return; + + if (!(_object is DisplayObject)) + return; + + updateInspectionOverlay(); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/SliderConfigPanel.as b/src/ch/adolio/display/ui/inspector/panel/SliderConfigPanel.as new file mode 100644 index 0000000..f6f62a5 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/SliderConfigPanel.as @@ -0,0 +1,83 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel +{ + import ch.adolio.display.ui.inspector.entry.TextInputInspectorEntry; + import feathers.layout.VerticalLayout; + import org.osflash.signals.Signal; + + public class SliderConfigPanel extends InspectorPanel + { + private var _verticalLayout:VerticalLayout; + + // UI elements + private var _minTextInput:TextInputInspectorEntry; + private var _maxTextInput:TextInputInspectorEntry; + private var _stepTextInput:TextInputInspectorEntry; + + // events + public var minChanged:Signal = new Signal(Number); + public var maxChanged:Signal = new Signal(Number); + public var stepChanged:Signal = new Signal(Number); + + // values + private var _min:Number = 0; + private var _max:Number = 0; + private var _step:Number = 0; + + public function SliderConfigPanel(min:Number, max:Number, step:Number) + { + _min = min; + _max = max; + _step = step; + + // setup title + title = "Slider Config"; + + // create sliders + _minTextInput = new TextInputInspectorEntry("Min", + function():String { return _min.toString(); }, + function(value:String):void { _min = parseFloat(value); minChanged.dispatch(_min); }); + addEntry(_minTextInput); + + _maxTextInput = new TextInputInspectorEntry("Max", + function():String { return _max.toString(); }, + function(value:String):void { _max = parseFloat(value); maxChanged.dispatch(_max); }); + addEntry(_maxTextInput); + + _stepTextInput = new TextInputInspectorEntry("Step", + function():String { return _step.toString(); }, + function(value:String):void { _step = parseFloat(value); stepChanged.dispatch(_step); }); + addEntry(_stepTextInput); + + // setup default size + setupHeightFromContent(); + } + + public function set min(value:Number):void + { + _min = value; + _minTextInput.setText(value.toString()); + } + + public function set max(value:Number):void + { + _max = value; + _maxTextInput.setText(value.toString()); + } + + public function set step(value:Number):void + { + _step = value; + _stepTextInput.setText(value.toString()); + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTextureListItem.as b/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTextureListItem.as new file mode 100644 index 0000000..27acdb4 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTextureListItem.as @@ -0,0 +1,253 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel.asset +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import feathers.controls.Label; + import org.osflash.signals.Signal; + import starling.display.Image; + import starling.display.Quad; + import starling.display.Sprite; + import starling.events.Event; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + import starling.textures.SubTexture; + import starling.textures.Texture; + import starling.utils.Align; + + /** A visual item in the list of an Asset Manager's textures. */ + public class AssetManagerTextureListItem extends Sprite + { + // style + private static const ITEM_BACKGROUND_SIZE:Number = 144; + private static const IMAGE_SIZE_LIMIT:Number = 96; + private const BACKGROUND_COLOR_SELECTED:uint = InspectorConfiguration.COLOR_BACKGROUND_HIGHLIGHT; + private static const BACKGROUND_COLOR_DEFAULT:uint = 0xdddddd; + private const ITEM_PADDING:Number = InspectorConfiguration.COMPONENTS_PADDING; + + // core elements + private var _isSelected:Boolean; + private var _texture:Texture; + private var _textureAlias:String; + + // ui components + private var _background:Quad; + private var _aliasLabel:Label; + private var _texturePropertiesLabel:Label; + private var _previewImage:Image; + private var _previewImageBackground:Quad; + + // signals + public var selected:Signal = new Signal(AssetManagerTextureListItem, Boolean); + + /** + * Constructor. + * + *

Note that the item can be recycled by calling the `reset` method.

+ */ + public function AssetManagerTextureListItem(texture:Texture, textureAlias:String) + { + // setup core variables + _texture = texture; + _textureAlias = textureAlias; + + // background + _background = new Quad(ITEM_BACKGROUND_SIZE, ITEM_BACKGROUND_SIZE, BACKGROUND_COLOR_DEFAULT); + addChild(_background); + + // properties label + _texturePropertiesLabel = new Label(); + _texturePropertiesLabel.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_VALUE; + _texturePropertiesLabel.touchable = false; + _texturePropertiesLabel.x = ITEM_PADDING; + _texturePropertiesLabel.y = ITEM_PADDING; + _texturePropertiesLabel.width = _background.width - ITEM_PADDING * 2; + _texturePropertiesLabel.validate(); + _texturePropertiesLabel.fontStyles.horizontalAlign = Align.RIGHT; + _texturePropertiesLabel.fontStyles.size *= 0.8; + setupTexturePropertiesLabel(); + _texturePropertiesLabel.validate(); + addChild(_texturePropertiesLabel); + + // texture preview + _previewImage = new Image(_texture); + _previewImage.touchable = false; + setupPreviewImageSizeAndPosition(); + + // texture preview background + _previewImageBackground = new Quad(1, 1, 0xffffff); + _previewImageBackground.touchable = false; + _previewImageBackground.width = _previewImage.width; + _previewImageBackground.height = _previewImage.height; + _previewImageBackground.x = _previewImage.x; + _previewImageBackground.y = _previewImage.y; + addChild(_previewImageBackground); + addChild(_previewImage); + + // label + _aliasLabel = new Label(); + _aliasLabel.styleName = InspectorConfiguration.STYLE_NAME_LABEL_ENTRY_VALUE; + _aliasLabel.touchable = false; + _aliasLabel.text = _textureAlias; + _aliasLabel.width = _background.width - ITEM_PADDING * 2; + _aliasLabel.validate(); + _aliasLabel.x = ITEM_PADDING; + _aliasLabel.y = _background.height - _aliasLabel.height - ITEM_PADDING; + addChild(_aliasLabel); + + // register to stage addition + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + /** Reset to recycle with another texture. */ + public function reset(texture:Texture, textureAlias:String):void + { + // remove all listeners + selected.removeAll(); + + // setup core variables + _texture = texture; + _textureAlias = textureAlias; + + // setup alias label + _aliasLabel.text = _textureAlias; + _aliasLabel.validate(); + + // setup details + setupTexturePropertiesLabel(); + + // update preview texture + _previewImage.texture = _texture; + _previewImage.readjustSize(); + + // update size & position + _previewImage.width = _texture.frameWidth; + _previewImage.height = _texture.frameHeight; + setupPreviewImageSizeAndPosition(); + + // update preview image background + _previewImageBackground.width = _previewImage.width; + _previewImageBackground.height = _previewImage.height; + _previewImageBackground.x = _previewImage.x; + _previewImageBackground.y = _previewImage.y; + + // reset selection + isSelected = false; + } + + private static function formatNumber(value:Number, precision:int):String + { + var pow:Number = Math.pow(10, precision); + return (Math.round(value * pow) / pow).toString(); + } + + private function setupTexturePropertiesLabel():void + { + var properties:String = ""; + + // add texture size + properties += formatNumber(_texture.frameWidth, 1) + "x" + formatNumber(_texture.frameHeight, 1); + + // add source size + if (_texture.scale != 1.0) + properties += " (" + formatNumber(_texture.frameWidth * _texture.scale, 1) + "x" + formatNumber(_texture.frameHeight * _texture.scale, 1) + ")"; + + // add scale + properties += " - " + _texture.scale+"x"; + + // add pot flag + if (_texture.root.isPotTexture && !(_texture is SubTexture)) + properties += " - POT"; + + // update properties label + _texturePropertiesLabel.text = properties; + } + + private function setupPreviewImageSizeAndPosition():void + { + // setup size + if (_previewImage.width > IMAGE_SIZE_LIMIT || _previewImage.height > IMAGE_SIZE_LIMIT) + _previewImage.scale = _previewImage.width > _previewImage.height ? IMAGE_SIZE_LIMIT / _previewImage.width : IMAGE_SIZE_LIMIT / _previewImage.height; + + // setup position + _previewImage.x = (_background.width - _previewImage.width) * 0.5; + _previewImage.y = _texturePropertiesLabel.y + _texturePropertiesLabel.height + ITEM_PADDING; + } + + //--------------------------------------------------------------------- + //-- Accessors + //--------------------------------------------------------------------- + + public function get texture():Texture + { + return _texture; + } + + public function get textureAlias():String + { + return _textureAlias; + } + + public function get isSelected():Boolean + { + return _isSelected; + } + + public function set isSelected(value:Boolean):void + { + _isSelected = value; + + _background.color = value ? BACKGROUND_COLOR_SELECTED : BACKGROUND_COLOR_DEFAULT; + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + protected function onAddedToStage(e:Event):void + { + removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + + addEventListener(TouchEvent.TOUCH, onTouched); + } + + protected function onRemovedFromStage(e:Event):void + { + removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + + removeEventListener(TouchEvent.TOUCH, onTouched); + } + + protected function onTouched(e:TouchEvent):void + { + var touch:Touch = e.touches[0]; + + // double tap detection + if (touch.tapCount == 2) + { + isSelected = true; + selected.dispatch(this, true); + return; + } + + switch (touch.phase) + { + case TouchPhase.ENDED: + isSelected = true; + selected.dispatch(this, false); + break; + } + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTexturesPickerBody.as b/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTexturesPickerBody.as new file mode 100644 index 0000000..8b288f3 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTexturesPickerBody.as @@ -0,0 +1,270 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel.asset +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.panel.InspectorBody; + import ch.adolio.display.ui.inspector.panel.InspectorPanel; + import feathers.controls.Button; + import feathers.controls.ScrollBarDisplayMode; + import feathers.controls.ScrollContainer; + import feathers.controls.ScrollInteractionMode; + import feathers.controls.ScrollPolicy; + import feathers.controls.TextInput; + import feathers.layout.FlowLayout; + import feathers.layout.HorizontalAlign; + import feathers.layout.VerticalAlign; + import org.osflash.signals.Signal; + import starling.assets.AssetManager; + import starling.events.Event; + import starling.textures.SubTexture; + import starling.textures.Texture; + + public class AssetManagerTexturesPickerBody extends InspectorBody + { + // UI elements + private var _assetsManager:AssetManager; + private var _scrollContainer:ScrollContainer; + + // last inspector properties + private static var _lastFilter:String; + + // filter + private var MIN_FILTER_CHARACTERS:uint = 3; // prevent showing too much textures + private var _searchInput:TextInput; + private var _powerOfTwoTexturesOnly:Boolean = true; + + // selection + private var _selectButton:Button; + + // importation + private var _importButton:Button; + private var _importPanel:AssetTextureImportPanel; + + // items + private var _selectedItem:AssetManagerTextureListItem; + private var _itemsInUse:Vector. = new Vector.(); + private static var _itemsPool:Vector. = new Vector.(); + private static var _texturesTmp:Vector. = new Vector.(); + private static var _texturesNamesTmp:Vector. = new Vector.(); + + // events + public var textureSelected:Signal = new Signal(Texture, String); // texture, alias + + public function AssetManagerTexturesPickerBody(panel:InspectorPanel, assetsManager:AssetManager, powerOfTwoTexturesOnly:Boolean) + { + super(panel); + + _assetsManager = assetsManager; + _powerOfTwoTexturesOnly = powerOfTwoTexturesOnly; + + // search input + _searchInput = new TextInput(); + _searchInput.styleName = InspectorConfiguration.STYLE_NAME_TEXT_INPUT; + _searchInput.prompt = "Filter"; + _searchInput.text = _lastFilter; + _searchInput.validate(); + addChild(_searchInput); + + // setup vertical scroll container + _scrollContainer = new ScrollContainer(); + var layout:FlowLayout = new FlowLayout(); + layout.horizontalAlign = HorizontalAlign.LEFT; + layout.verticalAlign = VerticalAlign.TOP; + layout.gap = InspectorConfiguration.COMPONENTS_PADDING; + _scrollContainer.layout = layout; + _scrollContainer.interactionMode = ScrollInteractionMode.TOUCH_AND_SCROLL_BARS; + _scrollContainer.horizontalScrollPolicy = ScrollPolicy.OFF; + _scrollContainer.scrollBarDisplayMode = ScrollBarDisplayMode.FIXED_FLOAT; + _scrollContainer.interactionMode = ScrollInteractionMode.MOUSE; + _scrollContainer.padding = 5; + _scrollContainer.y = _searchInput.y + _searchInput.height + InspectorConfiguration.COMPONENTS_PADDING; + addChild(_scrollContainer); + + // select button + _selectButton = new Button(); + _selectButton.label = "Select"; + _selectButton.validate(); + _selectButton.height *= 2; // double size for better visibility + addChild(_selectButton); + + // import button + _importButton = new Button(); + _importButton.styleName = InspectorConfiguration.STYLE_NAME_BUTTON; + _importButton.label = "Import from disk"; + _importButton.validate(); + _importButton.height *= 2; // double size for better visibility + addChild(_importButton); + + // update + updateListOfTextures(); + } + + private function updateListOfTextures():void + { + // remove all items + _scrollContainer.removeChildren(); + + // move all used items in the cache + while (_itemsInUse.length > 0) + _itemsPool.push(_itemsInUse.pop()); + + // check minimum character in filter input + if (_searchInput.text.length < MIN_FILTER_CHARACTERS) + return; + + // get textures & textures names + _texturesTmp.length = 0; + _assetsManager.getTextures("", _texturesTmp); + _texturesNamesTmp.length = 0; + _assetsManager.getTextureNames("", _texturesNamesTmp); + + // update texture entries + var len:int = _texturesTmp.length; + for (var i:int = 0; i < len; i++) + { + // discard atlases + if (_assetsManager.getTextureAtlas(_texturesNamesTmp[i]) != null) + continue; + + // filter power of two textures + if (_powerOfTwoTexturesOnly && (!_texturesTmp[i].root.isPotTexture || _texturesTmp[i] is SubTexture)) + continue; + + // check filter + var alias:String = _texturesNamesTmp[i]; + if (alias.toLowerCase().indexOf(_searchInput.text.toLowerCase()) == -1) + continue; + + // create texture item + var textureItem:AssetManagerTextureListItem = getTextureItem(_texturesTmp[i], alias); + textureItem.selected.add(onTextureItemSelected); + _scrollContainer.addChild(textureItem); + } + } + + private function getTextureItem(texture:Texture, textureAlias:String):AssetManagerTextureListItem + { + var textureItem:AssetManagerTextureListItem; + if (_itemsPool.length > 0) + { + textureItem = _itemsPool.pop(); + textureItem.reset(texture, textureAlias); + } + else + { + textureItem = new AssetManagerTextureListItem(texture, textureAlias); + } + + _itemsInUse.push(textureItem); + return textureItem; + } + + //--------------------------------------------------------------------- + //-- Event handlers + //--------------------------------------------------------------------- + + override protected function onAddedToStage(e:starling.events.Event):void + { + super.onAddedToStage(e); + + _searchInput.addEventListener(starling.events.Event.CHANGE, onSearchTextChanged); + _selectButton.addEventListener(starling.events.Event.TRIGGERED, onSelectButtonTriggered); + _importButton.addEventListener(starling.events.Event.TRIGGERED, onImportButtonTriggered); + + // focus filter input + _searchInput.setFocus(); + } + + override protected function onRemovedFromStage(e:starling.events.Event):void + { + super.onRemovedFromStage(e); + + _searchInput.removeEventListener(starling.events.Event.CHANGE, onSearchTextChanged); + _selectButton.removeEventListener(starling.events.Event.TRIGGERED, onSelectButtonTriggered); + _importButton.removeEventListener(starling.events.Event.TRIGGERED, onImportButtonTriggered); + } + + protected function onSearchTextChanged(e:starling.events.Event):void + { + // update last filter for new inspector + _lastFilter = _searchInput.text; + + updateListOfTextures(); + } + + protected function onSelectButtonTriggered(e:starling.events.Event):void + { + if (_selectedItem) + textureSelected.dispatch(_selectedItem.texture, _selectedItem.textureAlias); + } + + protected function onTextureItemSelected(item:AssetManagerTextureListItem, withDirectValidation:Boolean):void + { + if (_selectedItem && _selectedItem != item) + _selectedItem.isSelected = false; + + _selectedItem = item; + + if (_selectedItem && withDirectValidation) + textureSelected.dispatch(_selectedItem.texture, _selectedItem.textureAlias); + } + + private function onImportButtonTriggered(e:starling.events.Event):void + { + // create import panel + if (!_importPanel) + { + _importPanel = new AssetTextureImportPanel(_assetsManager, _powerOfTwoTexturesOnly); + _importPanel.x = _inspector.x + _inspector.width + InspectorConfiguration.COMPONENTS_PADDING; + _importPanel.y = _inspector.y; + + // listen to texture importation event + _importPanel.textureImported.add( + function(texture:Texture, alias:String):void + { + _searchInput.text = alias; + } + ); + } + + // add import panel to stage + InspectorConfiguration.ROOT_LAYER.addChild(_importPanel); + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function set width(value:Number):void + { + _preferredWidth = value; + super.width = _preferredWidth; + + _searchInput.width = value; + _scrollContainer.width = value; + _selectButton.width = (value - InspectorConfiguration.COMPONENTS_PADDING) * 0.75; + _importButton.x = _selectButton.x + _selectButton.width + InspectorConfiguration.COMPONENTS_PADDING; + _importButton.width = (value - InspectorConfiguration.COMPONENTS_PADDING) * 0.25; + } + + override public function set height(value:Number):void + { + _preferredHeight = value; + super.height = _preferredHeight; + + _scrollContainer.y = _searchInput.y + _searchInput.height + InspectorConfiguration.COMPONENTS_PADDING; + _scrollContainer.height = value - (_searchInput.height + _selectButton.height + InspectorConfiguration.COMPONENTS_PADDING * 2); + _selectButton.y = _scrollContainer.y + _scrollContainer.height + InspectorConfiguration.COMPONENTS_PADDING; + _importButton.y = _scrollContainer.y + _scrollContainer.height + InspectorConfiguration.COMPONENTS_PADDING; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTexturesPickerPanel.as b/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTexturesPickerPanel.as new file mode 100644 index 0000000..693800a --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/asset/AssetManagerTexturesPickerPanel.as @@ -0,0 +1,115 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel.asset +{ + import ch.adolio.display.ui.inspector.panel.InspectorPanel; + import flash.geom.Point; + import org.osflash.signals.Signal; + import starling.assets.AssetManager; + import starling.textures.Texture; + + public class AssetManagerTexturesPickerPanel extends InspectorPanel + { + private var _assetsManager:AssetManager; + private var _assetManagerTexturesPickerBody:AssetManagerTexturesPickerBody; + + public var textureSelected:Signal = new Signal(Texture, String); // texture, alias + + private var _isTrackingSizeUpdate:Boolean = false; + private static var _lastPosition:Point = new Point(NaN, NaN); + private static var _lastSize:Point = new Point(NaN, NaN); + + public function AssetManagerTexturesPickerPanel(assetsManager:AssetManager, powerOfTwoTexturesOnly:Boolean) + { + super(true, true); + + _assetsManager = assetsManager; + + // setup title + title = "Textures from Asset Manager"; + + // add POT flag + if (powerOfTwoTexturesOnly) + title += " (POT only)"; + + // replace default body + _assetManagerTexturesPickerBody = new AssetManagerTexturesPickerBody(this, assetsManager, powerOfTwoTexturesOnly); + _assetManagerTexturesPickerBody.textureSelected.add(onTextureSelected); + body = _assetManagerTexturesPickerBody; + + // setup position from last panel + if (!isNaN(_lastPosition.x)) + x = _lastPosition.x; + + if (!isNaN(_lastPosition.y)) + y = _lastPosition.y; + + // setup size from last panel + if (!isNaN(_lastSize.x)) + width = _lastSize.x; + + if (!isNaN(_lastSize.y)) + height = _lastSize.y; + + // start tracking size update after initialization + _isTrackingSizeUpdate = true; + } + + public function get assetsManager():AssetManager + { + return _assetsManager; + } + + protected function onTextureSelected(texture:Texture, textureAlias:String):void + { + textureSelected.dispatch(texture, textureAlias); + close(); + } + + //--------------------------------------------------------------------- + //-- Position management + //--------------------------------------------------------------------- + + override public function set x(value:Number):void + { + super.x = value; + + _lastPosition.x = x; + } + + override public function set y(value:Number):void + { + super.y = value; + + _lastPosition.y = y; + } + + //--------------------------------------------------------------------- + //-- Size management + //--------------------------------------------------------------------- + + override public function set width(value:Number):void + { + super.width = value; + + if (_isTrackingSizeUpdate) + _lastSize.x = width; + } + + override public function set height(value:Number):void + { + super.height = value; + + if (_isTrackingSizeUpdate) + _lastSize.y = height; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/display/ui/inspector/panel/asset/AssetTextureImportPanel.as b/src/ch/adolio/display/ui/inspector/panel/asset/AssetTextureImportPanel.as new file mode 100644 index 0000000..b84fba5 --- /dev/null +++ b/src/ch/adolio/display/ui/inspector/panel/asset/AssetTextureImportPanel.as @@ -0,0 +1,170 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.display.ui.inspector.panel.asset +{ + import ch.adolio.display.ui.inspector.InspectorConfiguration; + import ch.adolio.display.ui.inspector.entry.ActionInspectorEntry; + import ch.adolio.display.ui.inspector.entry.CheckInspectorEntry; + import ch.adolio.display.ui.inspector.entry.LabelInspectorEntry; + import ch.adolio.display.ui.inspector.entry.SliderInspectorEntry; + import ch.adolio.display.ui.inspector.panel.InspectorPanel; + import flash.display.Bitmap; + import flash.display.Loader; + import flash.display3D.Context3DTextureFormat; + import flash.events.Event; + import flash.filesystem.File; + import flash.net.FileFilter; + import flash.net.URLRequest; + import org.osflash.signals.Signal; + import starling.assets.AssetManager; + import starling.events.Event; + import starling.textures.Texture; + + public class AssetTextureImportPanel extends InspectorPanel + { + private var _assetsManager:AssetManager; + + private var _texturePot:Boolean; + private var _textureScale:Number = InspectorConfiguration.TEXTURE_IMPORT_SCALE_DEFAULT; + private var _textureMipmaps:Boolean; + private var _textureFormat:String = Context3DTextureFormat.BGRA; + + private var _currentFileRef:File; + private var _loader:Loader; + private var _loadingTexturePath:String; + private var _importErrorMessage:String; + + private var _importErrorEntry:LabelInspectorEntry; + + public var textureImported:Signal = new Signal(Texture, String); // texture, alias + + public function AssetTextureImportPanel(assetsManager:AssetManager, powerOfTwoOnly:Boolean = false) + { + super(true, true); + + _assetsManager = assetsManager; + _texturePot = powerOfTwoOnly; + + // setup title + title = "Texture importation"; + + addSparatorEntry("Options"); + + addEntry(new CheckInspectorEntry("Generate mipmaps?", + function():Boolean { return _textureMipmaps; }, + function(value:Boolean):void { _textureMipmaps = value; } + )); + + addEntry(new CheckInspectorEntry("Is power of two?", + function():Boolean { return _texturePot; }, + powerOfTwoOnly ? null : function(value:Boolean):void { _texturePot = value; } + )); + + addSparatorEntry("Source file"); + + addEntry(new SliderInspectorEntry("Scale", + function():Number { return _textureScale; }, + function(value:Number):void { _textureScale = value; }, + InspectorConfiguration.TEXTURE_IMPORT_SCALE_MIN, InspectorConfiguration.TEXTURE_IMPORT_SCALE_MAX, InspectorConfiguration.TEXTURE_IMPORT_SCALE_STEP + )); + + addEntry(new ActionInspectorEntry("Select a file", + function():void { selectFile(); } + )); + + _importErrorEntry = new LabelInspectorEntry("", function():String { return _importErrorMessage; }); + addEntry(_importErrorEntry); + } + + //--------------------------------------------------------------------- + //-- Texture importation management + //--------------------------------------------------------------------- + + private function selectFile():void + { + _currentFileRef = new File(); + _currentFileRef.browse([new FileFilter("Image", "*.png;*.jpg;*.gif")]); + _currentFileRef.addEventListener(flash.events.Event.SELECT, onFileSelected); + _currentFileRef.addEventListener(flash.events.Event.CANCEL, onFileSelectionCanceled); + } + + private function onFileSelectionCanceled(e:flash.events.Event):void + { + _currentFileRef = null; + } + + private function onFileSelected(e:flash.events.Event):void + { + _currentFileRef.removeEventListener(starling.events.Event.SELECT, onFileSelected); + + loadTextureFile(_currentFileRef.nativePath); + } + + private function loadTextureFile(texturePath:String):void + { + trace("[Asset Manager Texture Picker Body] Loading texture..."); + + // create the loader + _loader = new Loader(); + + // load the texture + _loadingTexturePath = texturePath; + _loader.load(new URLRequest(texturePath)); + + // when texture is loaded + _loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, onComplete); + } + + private function onComplete(e:flash.events.Event):void + { + // grab the loaded bitmap + var loadedBitmap:Bitmap = _loader.content as Bitmap; + + // reset error + _importErrorMessage = ""; + _importErrorEntry.refresh(); + + // check POT + if (_texturePot && (!isPowerOfTwo(loadedBitmap.width) || !isPowerOfTwo(loadedBitmap.height))) + { + _importErrorMessage = "ERROR: Texture size is not POT!"; + _importErrorEntry.refresh(); + return; + } + + // check size + if (loadedBitmap.width > Texture.maxSize || loadedBitmap.height > Texture.maxSize) + { + _importErrorMessage = "ERROR: Texture size is too big! Max size: " + Texture.maxSize; + _importErrorEntry.refresh(); + return; + } + + // create a texture from the loaded bitmap + var texture:Texture = Texture.fromBitmap(loadedBitmap, _textureMipmaps, false, _textureScale, _textureFormat, _texturePot); + + // register to resources controller + var textureFile:File = new File(_loadingTexturePath); + var filename:String = textureFile.name; + var filenameWithoutExtension:String = filename.substr(0, filename.lastIndexOf('.')); + _assetsManager.addAsset(filenameWithoutExtension, texture); + + // done + close(); + textureImported.dispatch(texture, filenameWithoutExtension); + } + + public static function isPowerOfTwo(x:uint):Boolean + { + return (x & (x - 1)) == 0; + } + } +} \ No newline at end of file diff --git a/src/ch/adolio/utils/InspectionUtils.as b/src/ch/adolio/utils/InspectionUtils.as new file mode 100644 index 0000000..47a0440 --- /dev/null +++ b/src/ch/adolio/utils/InspectionUtils.as @@ -0,0 +1,79 @@ +// ================================================================================================= +// +// Starling Inspector +// Copyright (c) 2023 Aurelien Da Campo (Adolio), All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package ch.adolio.utils +{ + import avmplus.getQualifiedClassName; + import starling.display.DisplayObject; + import starling.display.DisplayObjectContainer; + + /** + * Inspection utility class + */ + public class InspectionUtils + { + /** Returns a name for a given object. */ + public static function findObjectName(object:Object):String + { + var className:String = getQualifiedClassName(object); + + var classSplitIndex:int = className.indexOf("::"); + if (classSplitIndex != -1) + className = className.substr(classSplitIndex+2, className.length - classSplitIndex+2); + + if (object is DisplayObject) + { + var displayObject:DisplayObject = object as DisplayObject; + if (!isNullOrEmpty(displayObject.name)) + return displayObject.name + " [" + className + "]"; + } + + return "[" + className + "]"; + } + + /** Checks if a string is `null` or empty. */ + [Inline] + public static function isNullOrEmpty(str:String):Boolean + { + return str == null || str.length == 0; + } + + /** Clamp value between min (inclusive) & max (inclusive). */ + [Inline] + public static function clamp(value:Number, min:Number, max:Number):Number + { + return value > max ? max : value < min ? min : value; + } + + /** Checks if a given display object is a child object of another. */ + public static function isChildOf(parent:DisplayObject, child:DisplayObject):Boolean + { + // is the parent a container? + if (parent is DisplayObjectContainer) + { + var container:DisplayObjectContainer = parent as DisplayObjectContainer; + for (var i:int = 0; i < container.numChildren; ++i) + { + var containerChild:DisplayObject = container.getChildAt(i); + + // check if the current child is found + if (containerChild == child) + return true; + + // recusively look for the child, if found stop the search + if (isChildOf(containerChild, child)) + return true; + } + } + + return false; + } + } +} \ No newline at end of file