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.