├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── component-inspector-core ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── tangorabox │ └── componentinspector │ └── core │ ├── AbstractComponentInspector.java │ ├── CSSStyleClass.java │ ├── ComponentDetails.java │ └── ObjectMetadataExtractor.java ├── component-inspector-fx ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── tangorabox │ │ └── componentinspector │ │ └── fx │ │ ├── FXComponentInspector.java │ │ └── FXComponentInspectorHandler.java │ └── resources │ ├── component-inspector.css │ └── highlight.css ├── component-inspector-swing ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── tangorabox │ │ └── componentinspector │ │ └── swing │ │ ├── SwingComponentInspector.java │ │ ├── SwingComponentInspectorHandler.java │ │ └── styling │ │ ├── DropShadowPanel.java │ │ └── SwingCSSStyleDecorator.java │ └── resources │ └── component-inspector.css ├── doc └── images │ ├── FXInspector.png │ ├── JavaFXWithSwingNode.png │ ├── SwingInspector.png │ ├── SwingWithJavaFXPanel.png │ ├── css-class.png │ ├── demo.gif │ └── field-name.png ├── hybrid-example ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── tangorabox │ │ └── componentinspector │ │ └── hybrid │ │ ├── javafxwithswing │ │ ├── JavaFXExampleWithSwingLauncher.java │ │ ├── JavaFXExampleWithSwingNodeApp.java │ │ └── JavaFXWindowWithSwingPanel.java │ │ └── swingwithjavafx │ │ ├── SwingExampleWithJavaFXLauncher.java │ │ └── SwingWindowWithJavaFXPanel.java │ └── resources │ └── JavaFxWindow.fxml ├── javafx-example ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── tangorabox │ │ └── componentinspector │ │ └── fx │ │ ├── JavaFXExampleApp.java │ │ ├── JavaFXExampleLauncher.java │ │ └── JavaFXWindowPanel.java │ └── resources │ └── JavaFxWindow.fxml ├── pom.xml └── swing-example ├── pom.xml └── src └── main └── java └── com └── tangorabox └── componentinspector └── swing ├── SwingExampleLauncher.java └── SwingWindowPanel.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 11 | .mvn/wrapper/maven-wrapper.jar 12 | /.idea/ 13 | *.iml 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | languague: java 2 | addons: 3 | sonarcloud: 4 | organization: "tangorabox" 5 | 6 | script: 7 | # the following command line builds the project and then execute the SonarCloud analysis 8 | - mvn clean install sonar:sonar -Dsonar.login=${SONAR_TOKEN} 9 | 10 | 11 | cache: 12 | directories: 13 | - '$HOME/.m2/repository' 14 | - '$HOME/.sonar/cache' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Desktop (JavaFX and Swing) Component Inspector 2 | 3 | > A Tool for help you to inspect the location and some properties (see features section) of the component under mouse, in a window hierarchy 4 | 5 | [![License: LGPL v3](https://img.shields.io/badge/License-LGPLv3-blue.svg)](https://opensource.org/licenses/LGPL-3.0) 6 | [![Build Status](https://travis-ci.com/TangoraBox/ComponentInspector.svg?branch=master)](https://travis-ci.com/TangoraBox/ComponentInspector) 7 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=com.tangorabox%3Acomponent-inspector&metric=alert_status)](https://sonarcloud.io/dashboard?id=com.tangorabox%3Acomponent-inspector) 8 | 9 | 10 | ## Features 11 | 12 | - CSS class name in javafx node components [![css-class](doc/images/css-class.png)]() 13 | - Field name of component declaration in parent (when possible) [![css-class](doc/images/field-name.png)]() 14 | - The inspected component is highlighted _(since v1.1.0)_ 15 | --- 16 | 17 | ## Usage 18 | 19 | > The inspector window is only shown if you hold down the CONTROL key when you move the mouse 20 | 21 | --- 22 | 23 | ## Example Demo 24 | 25 | ![Example Demo](doc/images/demo.gif) 26 | 27 | ## ScreenShots 28 | 29 | ***JavaFX Component Inspector*** 30 | 31 | [![FXInspector](doc/images/FXInspector.png)]() 32 | 33 | ***Swing Component Inspector*** 34 | 35 | [![FXInspector](doc/images/SwingInspector.png)]() 36 | 37 | ***Swing inside JavaFX*** 38 | 39 | [![FXInspector](doc/images/JavaFXWithSwingNode.png)]() 40 | 41 | ***JavaFX inside Swing*** 42 | 43 | [![FXInspector](doc/images/SwingWithJavaFXPanel.png)]() 44 | 45 | --- 46 | 47 | ## How to use 48 | 49 | Simple, one line of code to handle all: 50 | 51 | ***Java FX Example*** 52 | 53 | Add this line in the `public void start(Stage primaryStage)` of your main JavaFX class that extends `Application`: 54 | 55 | ```java 56 | FXComponentInspectorHandler.handleAll(); 57 | ``` 58 | 59 | ***Swing Example*** 60 | 61 | Add this line in the `public static void main(String[] args)` of your application launch class: 62 | 63 | ```java 64 | SwingComponentInspectorHandler.handleAll(); 65 | ``` 66 | 67 | --- 68 | 69 | ## Library import with Maven 70 | 71 | The artifacts have been published to maven central: 72 | 73 | 74 | ### FXComponentInspector 75 | 76 | ***Java 11+*** 77 | 78 | ```xml 79 | 80 | com.tangorabox 81 | component-inspector-fx 82 | 1.1.0 83 | 84 | ``` 85 | 86 | ***Java 8*** 87 | 88 | ```xml 89 | 90 | com.tangorabox 91 | component-inspector-fx 92 | 1.1.0-java8 93 | 94 | ``` 95 | 96 | --- 97 | 98 | ### SwingComponentInspector 99 | 100 | ***Java 11+*** 101 | 102 | ```xml 103 | 104 | com.tangorabox 105 | component-inspector-swing 106 | 1.1.0 107 | 108 | ``` 109 | 110 | ***Java 8*** 111 | 112 | ```xml 113 | 114 | com.tangorabox 115 | component-inspector-swing 116 | 1.1.0-java8 117 | 118 | ``` 119 | 120 | --- 121 | 122 | 123 | ## Contributing 124 | 125 | > If you want to contribute to upgrade this project with new features or fixing bugs, you're welcome, please make a pull request. 126 | 127 | --- 128 | 129 | ## Team 130 | 131 | 132 | | **GaRzY** | 133 | | :---: 134 | | [![GaRzY](https://avatars0.githubusercontent.com/u/10849239?s=200)](https://github.com/garzy) 135 | | `github.com/garzy` | 136 | 137 | 138 | --- 139 | 140 | ## Support 141 | 142 | Reach out to me at one of the following places! 143 | 144 | - Mail to [info@tangorabox.com](mailto:info@tangorabox.com) 145 | 146 | --- 147 | 148 | 149 | ## License 150 | 151 | [![License: LGPL v3](https://img.shields.io/badge/License-LGPLv3-blue.svg)](https://opensource.org/licenses/LGPL-3.0) 152 | -------------------------------------------------------------------------------- /component-inspector-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.tangorabox 6 | component-inspector 7 | 1.1.1-SNAPSHOT 8 | 9 | 10 | component-inspector-core 11 | component-inspector-core 12 | 1.1.1-SNAPSHOT 13 | 14 | -------------------------------------------------------------------------------- /component-inspector-core/src/main/java/com/tangorabox/componentinspector/core/AbstractComponentInspector.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.core; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.stream.Collectors; 9 | 10 | public abstract class AbstractComponentInspector { 11 | 12 | protected static final int HORIZONTAL_SPACING = 25; 13 | protected static final int VERTICAL_SPACING = 25; 14 | 15 | 16 | public List inspect(T component) { 17 | return inspectDetails(component).stream().map(this::createComponentDetailsPanel).collect(Collectors.toList()); 18 | } 19 | 20 | private List> inspectDetails(T component) { 21 | List> hierarchy = buildHierarchyRecursive(component); 22 | buildCascade(hierarchy); 23 | return hierarchy; 24 | } 25 | 26 | private List> buildHierarchyRecursive(T component) { 27 | if (component == null) { 28 | return Collections.emptyList(); 29 | } 30 | List> hierarchy = new ArrayList<>(); 31 | hierarchy.add(createComponentDetails(component)); 32 | hierarchy.addAll(buildHierarchyRecursive(getParent(component))); 33 | return hierarchy; 34 | } 35 | 36 | private ComponentDetails createComponentDetails(T component) { 37 | ComponentDetails details = new ComponentDetails<>(); 38 | details.setFieldNameComponent(createFieldNameComponent(component).orElse(null)); 39 | details.setClassComponent(createClassComponent(component)); 40 | details.setStylesComponent(createStylesComponent(component)); 41 | return details; 42 | } 43 | 44 | protected abstract T getParent(T component); 45 | 46 | protected abstract Optional createFieldNameComponent(T component); 47 | 48 | protected abstract T createClassComponent(T component); 49 | 50 | protected abstract T createStylesComponent(T component); 51 | 52 | protected abstract T createComponentDetailsPanel(ComponentDetails details); 53 | 54 | protected abstract void buildCascade(List> hierarchy); 55 | } 56 | -------------------------------------------------------------------------------- /component-inspector-core/src/main/java/com/tangorabox/componentinspector/core/CSSStyleClass.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.core; 2 | 3 | public enum CSSStyleClass { 4 | 5 | COMPONENT_DETAILS("component-details"), 6 | FIELD_COMPONENT("field-component"), 7 | CLASS_COMPONENT("class-component"), 8 | STYLES_COMPONENT("styles-component"); 9 | 10 | private final String cssClassName; 11 | 12 | CSSStyleClass(String cssClassName) { 13 | this.cssClassName = cssClassName; 14 | } 15 | 16 | public String getCssClassName() { 17 | return cssClassName; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /component-inspector-core/src/main/java/com/tangorabox/componentinspector/core/ComponentDetails.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.core; 2 | 3 | import java.util.Optional; 4 | 5 | public class ComponentDetails { 6 | 7 | private T fieldNameComponent = null; 8 | private T classComponent; 9 | private T stylesComponent; 10 | 11 | private int locationX; 12 | private int locationY; 13 | 14 | 15 | public void setLocation(int x, int y) { 16 | this.locationX = x; 17 | this.locationY = y; 18 | } 19 | 20 | public int getLocationX() { 21 | return locationX; 22 | } 23 | 24 | public int getLocationY() { 25 | return locationY; 26 | } 27 | 28 | public void setFieldNameComponent(T fieldNameComponent) { 29 | this.fieldNameComponent = fieldNameComponent; 30 | } 31 | 32 | public Optional getFieldNameComponent() { 33 | return Optional.ofNullable(fieldNameComponent); 34 | } 35 | 36 | public void setClassComponent(T classComponent) { 37 | this.classComponent = classComponent; 38 | } 39 | 40 | public T getClassComponent() { 41 | return classComponent; 42 | } 43 | 44 | public T getStylesComponent() { 45 | return stylesComponent; 46 | } 47 | 48 | public void setStylesComponent(T stylesComponent) { 49 | this.stylesComponent = stylesComponent; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /component-inspector-core/src/main/java/com/tangorabox/componentinspector/core/ObjectMetadataExtractor.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.core; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.Arrays; 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | 8 | 9 | public class ObjectMetadataExtractor { 10 | 11 | private final T component; 12 | private final AbstractComponentInspector inspector; 13 | 14 | public ObjectMetadataExtractor(T component, AbstractComponentInspector inspector) { 15 | this.component = component; 16 | this.inspector = inspector; 17 | } 18 | 19 | public Optional getDeclaredFieldNameInParent() { 20 | return Optional.ofNullable(findFieldNameInHierarchy(inspector.getParent(component))); 21 | } 22 | 23 | private String findFieldNameInHierarchy(T parentOfComponent) { 24 | if (parentOfComponent == null) { 25 | return null; 26 | } 27 | return Arrays.stream(parentOfComponent.getClass().getDeclaredFields()) 28 | .filter(field -> field.getType().isAssignableFrom(component.getClass())) 29 | .map(parentField -> getFieldNameInParent(component, parentOfComponent, parentField)) 30 | .filter(Objects::nonNull) 31 | .findFirst() 32 | .orElseGet(() -> findFieldNameInHierarchy(inspector.getParent(parentOfComponent))); 33 | } 34 | 35 | 36 | private String getFieldNameInParent(Object component, Object parent, Field field) { 37 | try { 38 | // Allow access to private fields 39 | field.setAccessible(true); 40 | Object fieldObj = field.get(parent); 41 | if (fieldObj == component) { 42 | return field.getName(); 43 | } 44 | } catch (Exception ex) { 45 | // sssssshhhh! 46 | } 47 | return null; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /component-inspector-fx/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.tangorabox 6 | component-inspector 7 | 1.1.1-SNAPSHOT 8 | 9 | 10 | component-inspector-fx 11 | component-inspector-fx 12 | 1.1.1-SNAPSHOT 13 | 14 | 15 | 16 | com.tangorabox 17 | component-inspector-core 18 | 1.1.1-SNAPSHOT 19 | 20 | 21 | org.openjfx 22 | javafx-graphics 23 | 11.0.2 24 | 25 | 26 | org.openjfx 27 | javafx-controls 28 | 11.0.1 29 | compile 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /component-inspector-fx/src/main/java/com/tangorabox/componentinspector/fx/FXComponentInspector.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.fx; 2 | 3 | import com.tangorabox.componentinspector.core.AbstractComponentInspector; 4 | import com.tangorabox.componentinspector.core.CSSStyleClass; 5 | import com.tangorabox.componentinspector.core.ComponentDetails; 6 | import com.tangorabox.componentinspector.core.ObjectMetadataExtractor; 7 | import javafx.scene.Node; 8 | import javafx.scene.control.Label; 9 | import javafx.scene.layout.HBox; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | class FXComponentInspector extends AbstractComponentInspector { 15 | 16 | @Override 17 | protected Node getParent(Node component) { 18 | if (component == null) { 19 | return null; 20 | } 21 | return component.getParent(); 22 | } 23 | 24 | @Override 25 | protected Optional createFieldNameComponent(Node component) { 26 | return new ObjectMetadataExtractor<>(component, this).getDeclaredFieldNameInParent() 27 | .map(fieldName -> { 28 | Label result = new Label(fieldName); 29 | result.getStyleClass().add(CSSStyleClass.FIELD_COMPONENT.getCssClassName()); 30 | return result; 31 | }); 32 | } 33 | 34 | @Override 35 | protected Node createClassComponent(Node component) { 36 | Label result = new Label(component.getClass().getName()); 37 | result.getStyleClass().add(CSSStyleClass.CLASS_COMPONENT.getCssClassName()); 38 | return result; 39 | } 40 | 41 | @Override 42 | protected Node createStylesComponent(Node component) { 43 | String labelText = String.join(", ",component.getStyleClass()); 44 | if (labelText.isBlank()) { 45 | labelText = "(empty)"; 46 | } 47 | Label result = new Label(labelText); 48 | result.getStyleClass().add(CSSStyleClass.STYLES_COMPONENT.getCssClassName()); 49 | return result; 50 | } 51 | 52 | @Override 53 | protected Node createComponentDetailsPanel(ComponentDetails details) { 54 | HBox result = new HBox(details.getClassComponent(), details.getStylesComponent()); 55 | result.getStyleClass().add(CSSStyleClass.COMPONENT_DETAILS.getCssClassName()); 56 | result.relocate(details.getLocationX(), details.getLocationY()); 57 | details.getFieldNameComponent().ifPresent(fieldNameComponent -> result.getChildren().add(fieldNameComponent)); 58 | return result; 59 | } 60 | 61 | @Override 62 | protected void buildCascade(List> hierarchy) { 63 | for (int componentLevel = 0, componentIndex = hierarchy.size() - 1; componentIndex >= 0; componentIndex--, componentLevel++) { 64 | ComponentDetails componentChild = hierarchy.get(componentIndex); 65 | componentChild.setLocation(componentLevel * HORIZONTAL_SPACING, componentLevel * (VERTICAL_SPACING + 5)); 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /component-inspector-fx/src/main/java/com/tangorabox/componentinspector/fx/FXComponentInspectorHandler.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.fx; 2 | 3 | import javafx.geometry.Bounds; 4 | import javafx.scene.Node; 5 | import javafx.scene.Scene; 6 | import javafx.scene.input.MouseEvent; 7 | import javafx.scene.layout.Pane; 8 | import javafx.stage.Popup; 9 | import javafx.stage.Window; 10 | 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.Set; 15 | 16 | public class FXComponentInspectorHandler { 17 | 18 | private static final int OFFSET = 25; 19 | private final Popup popup = new Popup(); 20 | private final Popup highlightPopup = new Popup(); 21 | private final Pane pane = new Pane(); 22 | private final Pane highlightPane = new Pane(); 23 | private final FXComponentInspector componentInspector = new FXComponentInspector(); 24 | private final Set handledWindows = new HashSet<>(); 25 | private Scene currentScene = null; 26 | 27 | private static FXComponentInspectorHandler instance = null; 28 | 29 | public static void handleAll() { 30 | getInstance().inspectAllWindows(); 31 | } 32 | 33 | public static void handle(Node node) { 34 | getInstance().inspectNode(node); 35 | } 36 | 37 | private static FXComponentInspectorHandler getInstance() { 38 | if (instance == null) { 39 | instance = new FXComponentInspectorHandler(); 40 | } 41 | return instance; 42 | } 43 | 44 | private FXComponentInspectorHandler() { 45 | popup.getContent().add(pane); 46 | pane.getStyleClass().add("content-pane"); 47 | pane.getStylesheets().add(Objects.requireNonNull(this.getClass().getResource("/component-inspector.css")).toExternalForm()); 48 | 49 | highlightPopup.getContent().add(highlightPane); 50 | highlightPane.getStyleClass().add("highlight-pane"); 51 | highlightPane.getStylesheets().add(Objects.requireNonNull(this.getClass().getResource("/highlight.css")).toExternalForm()); 52 | 53 | } 54 | 55 | 56 | private void inspectAllWindows() { 57 | Window.getWindows().stream().filter(window -> window != popup && window != highlightPopup && !handledWindows.contains(window)).forEach(window -> { 58 | handledWindows.add(window); 59 | window.addEventFilter(MouseEvent.MOUSE_MOVED, event -> handle(event, window.getScene())); 60 | }); 61 | } 62 | 63 | private void inspectNode(Node node) { 64 | node.addEventFilter(MouseEvent.MOUSE_MOVED, event -> handle(event, null)); 65 | } 66 | 67 | 68 | private void handle(MouseEvent event, Scene scene) { 69 | if (!event.isControlDown()) { 70 | popup.hide(); 71 | highlightPopup.hide(); 72 | return; 73 | } 74 | 75 | if (!(event.getTarget() instanceof Node)) { 76 | return; 77 | } 78 | 79 | Node componentUnderMouse = (Node) event.getTarget(); 80 | if (scene == null) { 81 | scene = componentUnderMouse.getScene(); 82 | } 83 | 84 | List hierarchyNodes = componentInspector.inspect(componentUnderMouse); 85 | 86 | pane.getChildren().setAll(hierarchyNodes); 87 | popup.sizeToScene(); 88 | if (currentScene != scene) { 89 | popup.hide(); 90 | highlightPopup.hide(); 91 | currentScene = scene; 92 | } 93 | highlightComponent(componentUnderMouse, scene); 94 | popup.show(scene.getRoot(), event.getScreenX() - pane.getWidth(), event.getScreenY() - pane.getHeight() - OFFSET); 95 | inspectAllWindows(); 96 | } 97 | 98 | private void highlightComponent(Node component, Scene scene) { 99 | final Bounds bounds = component.localToScreen(component.getBoundsInLocal()); 100 | highlightPane.setPrefSize(bounds.getWidth(), bounds.getHeight()); 101 | highlightPopup.sizeToScene(); 102 | highlightPopup.show(scene.getRoot(), bounds.getMinX(), bounds.getMinY()); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /component-inspector-fx/src/main/resources/component-inspector.css: -------------------------------------------------------------------------------- 1 | .content-pane { 2 | -fx-padding: 10; 3 | } 4 | 5 | .content-pane > * { 6 | -fx-font-size: 10; 7 | -fx-focus-traversable: false; 8 | } 9 | 10 | .component-details { 11 | -fx-spacing: 5; 12 | -fx-padding: 5; 13 | -fx-background-color: white; 14 | -fx-border-color: black; 15 | -fx-border-radius: 8; 16 | -fx-background-radius: 8; 17 | -fx-effect:dropshadow(gaussian, black, 10, 0.5, 0, 0); 18 | } 19 | 20 | 21 | .class-component { 22 | -fx-text-fill: black; 23 | -fx-font-style: italic; 24 | -fx-font-weight: bold; 25 | } 26 | 27 | .field-component, .styles-component { 28 | -fx-opacity: 0.75; 29 | -fx-background-color: lightgray; 30 | -fx-text-fill: black; 31 | -fx-font-weight: bold; 32 | -fx-border-color: black; 33 | -fx-border-width: 2; 34 | -fx-border-radius: 4; 35 | -fx-background-radius: 8; 36 | -fx-padding: 0 10 0 10; 37 | -fx-alignment: center-right; 38 | } 39 | 40 | .styles-component { 41 | -fx-background-color: lightskyblue; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /component-inspector-fx/src/main/resources/highlight.css: -------------------------------------------------------------------------------- 1 | .highlight-pane { 2 | -fx-background-color: transparent; 3 | -fx-border-color: blue; 4 | -fx-border-style: dotted; 5 | } -------------------------------------------------------------------------------- /component-inspector-swing/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.tangorabox 6 | component-inspector 7 | 1.1.1-SNAPSHOT 8 | 9 | 10 | component-inspector-swing 11 | component-inspector-swing 12 | 1.1.1-SNAPSHOT 13 | 14 | 15 | 16 | com.tangorabox 17 | component-inspector-core 18 | 1.1.1-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /component-inspector-swing/src/main/java/com/tangorabox/componentinspector/swing/SwingComponentInspector.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.swing; 2 | 3 | import com.tangorabox.componentinspector.core.AbstractComponentInspector; 4 | import com.tangorabox.componentinspector.core.CSSStyleClass; 5 | import com.tangorabox.componentinspector.core.ComponentDetails; 6 | import com.tangorabox.componentinspector.core.ObjectMetadataExtractor; 7 | import com.tangorabox.componentinspector.swing.styling.SwingCSSStyleDecorator; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | class SwingComponentInspector extends AbstractComponentInspector { 15 | 16 | private final SwingCSSStyleDecorator cssStyleDecorator = new SwingCSSStyleDecorator(); 17 | 18 | @Override 19 | protected Component getParent(Component component) { 20 | if (component == null) { 21 | return null; 22 | } 23 | return component.getParent(); 24 | } 25 | 26 | @Override 27 | protected Optional createFieldNameComponent(Component component) { 28 | return new ObjectMetadataExtractor<>(component,this) 29 | .getDeclaredFieldNameInParent() 30 | .map(fieldName -> cssStyleDecorator.decorate(new JLabel(fieldName), CSSStyleClass.FIELD_COMPONENT)); 31 | } 32 | 33 | @Override 34 | protected Component createClassComponent(Component component) { 35 | JLabel result = new JLabel(component.getClass().getName()); 36 | return cssStyleDecorator.decorate(result, CSSStyleClass.CLASS_COMPONENT); 37 | } 38 | 39 | @Override 40 | protected Component createStylesComponent(Component component) { 41 | return null; 42 | } 43 | 44 | @Override 45 | protected Component createComponentDetailsPanel(ComponentDetails details) { 46 | JPanel jPanel = new JPanel(new FlowLayout()); 47 | jPanel.add(details.getClassComponent()); 48 | details.getFieldNameComponent().ifPresent(jPanel::add); 49 | Component result = cssStyleDecorator.decorate(jPanel, CSSStyleClass.COMPONENT_DETAILS); 50 | result.setBounds(details.getLocationX(), details.getLocationY(), 51 | result.getPreferredSize().width, result.getPreferredSize().height); 52 | return result; 53 | } 54 | 55 | @Override 56 | protected void buildCascade(List> hierarchy) { 57 | for (int componentLevel = 0, componentIndex = hierarchy.size() - 1; componentIndex >= 0; componentIndex--, componentLevel++) { 58 | ComponentDetails componentChild = hierarchy.get(componentIndex); 59 | componentChild.setLocation(componentIndex * HORIZONTAL_SPACING, componentLevel * VERTICAL_SPACING); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /component-inspector-swing/src/main/java/com/tangorabox/componentinspector/swing/SwingComponentInspectorHandler.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.swing; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.awt.event.MouseEvent; 6 | import java.awt.event.MouseMotionAdapter; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public class SwingComponentInspectorHandler { 12 | 13 | private static final int OFFSET = 25; 14 | private final JDialog popup = new JDialog(); 15 | private final JDialog highlightPopup = new JDialog(); 16 | private final JPanel pane = new JPanel(null); 17 | private final SwingComponentInspector componentInspector = new SwingComponentInspector(); 18 | 19 | private static SwingComponentInspectorHandler instance = null; 20 | 21 | public static void handleAll() { 22 | getInstance().inspectAllWindows(); 23 | } 24 | 25 | public static void handle(Component component) { 26 | getInstance().inspectComponent(component); 27 | } 28 | 29 | 30 | private static SwingComponentInspectorHandler getInstance() { 31 | if (instance == null) { 32 | instance = new SwingComponentInspectorHandler(); 33 | } 34 | return instance; 35 | } 36 | 37 | 38 | private SwingComponentInspectorHandler() { 39 | initPopup(popup); 40 | initPopup(highlightPopup); 41 | highlightPopup.getRootPane().setBorder(BorderFactory.createDashedBorder(Color.BLUE)); 42 | popup.getContentPane().add(pane, BorderLayout.CENTER); 43 | pane.setOpaque(false); 44 | } 45 | 46 | private void initPopup(JDialog popup) { 47 | popup.setAlwaysOnTop(true); 48 | popup.setUndecorated(true); 49 | popup.setBackground(new Color(0, 0, 0, 0)); 50 | popup.setFocusableWindowState(false); 51 | } 52 | 53 | 54 | private void inspectAllWindows() { 55 | long eventMask = AWTEvent.MOUSE_MOTION_EVENT_MASK; 56 | Toolkit.getDefaultToolkit().addAWTEventListener(event -> handleMouseEvent((MouseEvent) event), eventMask); 57 | } 58 | 59 | private void inspectComponent(Component component) { 60 | component.addMouseMotionListener(new MouseMotionAdapter() { 61 | @Override 62 | public void mouseMoved(MouseEvent e) { 63 | handleMouseEvent(e); 64 | } 65 | }); 66 | } 67 | 68 | private void handleMouseEvent(MouseEvent mouseEvent) { 69 | 70 | if (!mouseEvent.isControlDown()) { 71 | popup.setVisible(false); 72 | highlightPopup.setVisible(false); 73 | return; 74 | } 75 | 76 | final Component componentUnderMouse = findComponentUnderMouse(mouseEvent); 77 | List hierarchyNodes = componentInspector.inspect(componentUnderMouse); 78 | if (hierarchyNodes.isEmpty()) { 79 | popup.setVisible(false); 80 | highlightPopup.setVisible(false); 81 | } else { 82 | highlightComponent(componentUnderMouse); 83 | showHierarchyPopup(hierarchyNodes); 84 | } 85 | } 86 | 87 | 88 | private void highlightComponent(Component componentUnderMouse) { 89 | final Rectangle bounds = componentUnderMouse.getBounds(); 90 | highlightPopup.getContentPane().setPreferredSize(new Dimension(bounds.width, bounds.height)); 91 | highlightPopup.setSize(bounds.width, bounds.height); 92 | highlightPopup.setVisible(true); 93 | highlightPopup.setLocation(componentUnderMouse.getLocationOnScreen()); 94 | } 95 | 96 | private Component findComponentUnderMouse(MouseEvent mouseEvent) { 97 | final Point location = MouseInfo.getPointerInfo().getLocation(); 98 | final Component topLevelAncestor = findWindowUnderMouse().orElse(mouseEvent.getComponent()); 99 | SwingUtilities.convertPointFromScreen(location, topLevelAncestor); 100 | return SwingUtilities.getDeepestComponentAt(topLevelAncestor, location.x, location.y); 101 | } 102 | 103 | private static Optional findWindowUnderMouse() { 104 | for (Window window : Window.getWindows()) { 105 | if (window.getMousePosition(true) != null) { 106 | return Optional.of(window); 107 | } 108 | } 109 | return Optional.empty(); 110 | } 111 | 112 | private void showHierarchyPopup(List hierarchyNodes) { 113 | Dimension panelSize = calculatePanelSize(hierarchyNodes); 114 | pane.invalidate(); 115 | pane.removeAll(); 116 | Collections.reverse(hierarchyNodes); 117 | hierarchyNodes.forEach(pane::add); 118 | pane.revalidate(); 119 | pane.repaint(); 120 | popup.setSize(panelSize.width, panelSize.height); 121 | popup.setVisible(true); 122 | Point absoluteLocation = MouseInfo.getPointerInfo().getLocation(); 123 | popup.setLocation(absoluteLocation.x, absoluteLocation.y - pane.getHeight() - OFFSET); 124 | } 125 | 126 | private Dimension calculatePanelSize(List hierarchyNodes) { 127 | final int maxWidth = hierarchyNodes.stream() 128 | .mapToInt(hierarchyNode -> hierarchyNode.getX() + hierarchyNode.getPreferredSize().width) 129 | .max().orElse(0); 130 | 131 | final int maxHeight = hierarchyNodes.stream().findFirst() 132 | .map(firstComponent -> firstComponent.getPreferredSize().height + firstComponent.getY()) 133 | .orElse(0); 134 | 135 | return new Dimension(maxWidth, maxHeight); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /component-inspector-swing/src/main/java/com/tangorabox/componentinspector/swing/styling/DropShadowPanel.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.swing.styling; 2 | 3 | import javax.swing.*; 4 | import javax.swing.border.Border; 5 | import java.awt.*; 6 | 7 | class DropShadowPanel extends JPanel { 8 | 9 | private final int pixels; 10 | 11 | public DropShadowPanel(int pix) { 12 | this.pixels = pix; 13 | Border border = BorderFactory.createEmptyBorder(pixels, pixels, pixels, pixels); 14 | this.setBorder(BorderFactory.createCompoundBorder(this.getBorder(), border)); 15 | this.setLayout(new BorderLayout()); 16 | } 17 | 18 | @Override 19 | protected void paintComponent(Graphics g) { 20 | int shade = 0; 21 | int topOpacity = 255; 22 | for (int i = 0; i < pixels; i++) { 23 | g.setColor(new Color(shade, shade, shade, ((topOpacity / pixels) * i))); 24 | g.drawRect(i, i, this.getWidth() - ((i * 2) + 1), this.getHeight() - ((i * 2) + 1)); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /component-inspector-swing/src/main/java/com/tangorabox/componentinspector/swing/styling/SwingCSSStyleDecorator.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.swing.styling; 2 | 3 | import com.tangorabox.componentinspector.core.CSSStyleClass; 4 | 5 | import javax.swing.*; 6 | import javax.swing.border.Border; 7 | import java.awt.*; 8 | 9 | public class SwingCSSStyleDecorator { 10 | 11 | private static final float FONT_SIZE = 10f; 12 | private static final int PADDING = 1; 13 | 14 | public Component decorate(JComponent component, CSSStyleClass styleClass) { 15 | if (styleClass == null) { 16 | return component; 17 | } 18 | Font font = component.getFont().deriveFont(FONT_SIZE); 19 | component.setFont(font); 20 | 21 | switch (styleClass) { 22 | case COMPONENT_DETAILS: return decorateComponentDetails(component); 23 | case CLASS_COMPONENT: return decorateClassComponent(component); 24 | case FIELD_COMPONENT: return decorateFieldComponent(component); 25 | default: return component; 26 | } 27 | } 28 | 29 | 30 | private Component decorateComponentDetails(JComponent component) { 31 | Border paddingBorder = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING); 32 | component.setBorder(paddingBorder); 33 | DropShadowPanel dropShadowPanel = new DropShadowPanel(5); 34 | 35 | dropShadowPanel.add(component, BorderLayout.CENTER); 36 | return dropShadowPanel; 37 | } 38 | 39 | private Component decorateClassComponent(Component component) { 40 | Font font = component.getFont().deriveFont(Font.BOLD + Font.ITALIC); 41 | component.setFont(font); 42 | return component; 43 | } 44 | 45 | private Component decorateFieldComponent(JComponent component) { 46 | component.setOpaque(true); 47 | component.setBackground(Color.LIGHT_GRAY); 48 | Font font = component.getFont().deriveFont(Font.BOLD); 49 | component.setFont(font); 50 | 51 | Border lineBorder = BorderFactory.createLineBorder(Color.BLACK, 1, true); 52 | Border paddingBorder = BorderFactory.createEmptyBorder(0, 10, 0, 10); 53 | 54 | component.setBorder(BorderFactory.createCompoundBorder(lineBorder, paddingBorder)); 55 | return component; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /component-inspector-swing/src/main/resources/component-inspector.css: -------------------------------------------------------------------------------- 1 | .content-pane > * { 2 | -fx-font-size: 10; 3 | } 4 | 5 | .component-details { 6 | -fx-spacing: 5; 7 | -fx-padding: 5; 8 | -fx-background-color: white; 9 | -fx-border-color: black; 10 | -fx-border-radius: 8; 11 | -fx-background-radius: 8; 12 | -fx-effect:dropshadow(gaussian, black, 10, 0.5, 0, 0); 13 | } 14 | 15 | 16 | .class-component { 17 | -fx-text-fill: black; 18 | -fx-font-style: italic; 19 | -fx-font-weight: bold; 20 | } 21 | 22 | .field-component, .styles-component { 23 | -fx-opacity: 0.75; 24 | -fx-background-color: lightgray; 25 | -fx-text-fill: black; 26 | -fx-font-weight: bold; 27 | -fx-border-color: black; 28 | -fx-border-width: 2; 29 | -fx-border-radius: 4; 30 | -fx-background-radius: 8; 31 | -fx-padding: 0 10 0 10; 32 | -fx-alignment: center-right; 33 | } 34 | 35 | .styles-component { 36 | -fx-background-color: lightskyblue; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /doc/images/FXInspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoraBox/ComponentInspector/9d2707132473eab25a148dc04a7435e399746137/doc/images/FXInspector.png -------------------------------------------------------------------------------- /doc/images/JavaFXWithSwingNode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoraBox/ComponentInspector/9d2707132473eab25a148dc04a7435e399746137/doc/images/JavaFXWithSwingNode.png -------------------------------------------------------------------------------- /doc/images/SwingInspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoraBox/ComponentInspector/9d2707132473eab25a148dc04a7435e399746137/doc/images/SwingInspector.png -------------------------------------------------------------------------------- /doc/images/SwingWithJavaFXPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoraBox/ComponentInspector/9d2707132473eab25a148dc04a7435e399746137/doc/images/SwingWithJavaFXPanel.png -------------------------------------------------------------------------------- /doc/images/css-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoraBox/ComponentInspector/9d2707132473eab25a148dc04a7435e399746137/doc/images/css-class.png -------------------------------------------------------------------------------- /doc/images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoraBox/ComponentInspector/9d2707132473eab25a148dc04a7435e399746137/doc/images/demo.gif -------------------------------------------------------------------------------- /doc/images/field-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangoraBox/ComponentInspector/9d2707132473eab25a148dc04a7435e399746137/doc/images/field-name.png -------------------------------------------------------------------------------- /hybrid-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.tangorabox 6 | component-inspector 7 | 1.1.1-SNAPSHOT 8 | 9 | 10 | hybrid-example 11 | hybrid-example 12 | 1.1.1-SNAPSHOT 13 | 14 | 15 | 16 | com.tangorabox 17 | component-inspector-fx 18 | 1.1.1-SNAPSHOT 19 | 20 | 21 | com.tangorabox 22 | component-inspector-swing 23 | 1.1.1-SNAPSHOT 24 | 25 | 26 | com.tangorabox 27 | javafx-example 28 | 1.1.1-SNAPSHOT 29 | 30 | 31 | com.tangorabox 32 | swing-example 33 | 1.1.1-SNAPSHOT 34 | 35 | 36 | org.openjfx 37 | javafx-graphics 38 | 11.0.2 39 | 40 | 41 | org.openjfx 42 | javafx-fxml 43 | 11.0.2 44 | 45 | 46 | org.openjfx 47 | javafx-swing 48 | 11.0.2 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /hybrid-example/src/main/java/com/tangorabox/componentinspector/hybrid/javafxwithswing/JavaFXExampleWithSwingLauncher.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.hybrid.javafxwithswing; 2 | 3 | import javafx.application.Application; 4 | 5 | public class JavaFXExampleWithSwingLauncher { 6 | 7 | public static void main(String[] args) { 8 | Application.launch(JavaFXExampleWithSwingNodeApp.class, args); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /hybrid-example/src/main/java/com/tangorabox/componentinspector/hybrid/javafxwithswing/JavaFXExampleWithSwingNodeApp.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.hybrid.javafxwithswing; 2 | 3 | import com.tangorabox.componentinspector.fx.FXComponentInspectorHandler; 4 | import javafx.application.Application; 5 | import javafx.scene.Scene; 6 | import javafx.stage.Stage; 7 | 8 | public class JavaFXExampleWithSwingNodeApp extends Application { 9 | @Override 10 | public void start(Stage primaryStage) { 11 | JavaFXWindowWithSwingPanel windowPanel = new JavaFXWindowWithSwingPanel(); 12 | primaryStage.setScene(new Scene(windowPanel)); 13 | primaryStage.setWidth(800); 14 | primaryStage.setHeight(600); 15 | primaryStage.show(); 16 | FXComponentInspectorHandler.handleAll(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hybrid-example/src/main/java/com/tangorabox/componentinspector/hybrid/javafxwithswing/JavaFXWindowWithSwingPanel.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.hybrid.javafxwithswing; 2 | 3 | import com.tangorabox.componentinspector.swing.SwingComponentInspectorHandler; 4 | import com.tangorabox.componentinspector.swing.SwingWindowPanel; 5 | import javafx.embed.swing.SwingNode; 6 | import javafx.fxml.FXML; 7 | import javafx.fxml.FXMLLoader; 8 | import javafx.scene.control.ComboBox; 9 | import javafx.scene.control.TitledPane; 10 | import javafx.scene.layout.BorderPane; 11 | import javafx.scene.layout.HBox; 12 | import javafx.scene.layout.VBox; 13 | 14 | import javax.swing.*; 15 | import java.io.IOException; 16 | import java.io.UncheckedIOException; 17 | 18 | public class JavaFXWindowWithSwingPanel extends BorderPane { 19 | 20 | @FXML 21 | private SwingNode swingNode; 22 | 23 | @FXML 24 | private HBox panelUp; 25 | 26 | @FXML 27 | private ComboBox comboBox; 28 | 29 | @FXML 30 | private HBox panelDown; 31 | 32 | @FXML 33 | private TitledPane titledPane; 34 | 35 | @FXML 36 | private VBox panelLeft; 37 | 38 | @FXML 39 | private VBox panelRight; 40 | 41 | public JavaFXWindowWithSwingPanel() { 42 | FXMLLoader loader = new FXMLLoader(getClass().getResource("/JavaFxWindow.fxml")); 43 | loader.setRoot(this); 44 | loader.setController(this); 45 | try { 46 | loader.load(); 47 | comboBox.getItems().setAll("One", "Two", "Tree"); 48 | comboBox.getSelectionModel().selectFirst(); 49 | createSwingContent(); 50 | } catch (IOException ex) { 51 | throw new UncheckedIOException(ex); 52 | } 53 | } 54 | 55 | private void createSwingContent() { 56 | SwingUtilities.invokeLater(() -> { 57 | SwingWindowPanel swingWindowPanel = new SwingWindowPanel(); 58 | swingNode.setContent(swingWindowPanel); 59 | SwingComponentInspectorHandler.handle(swingWindowPanel); 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /hybrid-example/src/main/java/com/tangorabox/componentinspector/hybrid/swingwithjavafx/SwingExampleWithJavaFXLauncher.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.hybrid.swingwithjavafx; 2 | 3 | import com.tangorabox.componentinspector.swing.SwingComponentInspectorHandler; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.awt.event.WindowAdapter; 8 | import java.awt.event.WindowEvent; 9 | 10 | public class SwingExampleWithJavaFXLauncher { 11 | 12 | public static void main(String[] args) { 13 | SwingUtilities.invokeLater(() -> { 14 | JFrame frame = new JFrame(); 15 | frame.getContentPane().add(new SwingWindowWithJavaFXPanel(), BorderLayout.CENTER); 16 | frame.setSize(800, 600); 17 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 18 | frame.setVisible(true); 19 | frame.addWindowListener(new WindowAdapter() { 20 | @Override 21 | public void windowClosing(WindowEvent e) { 22 | System.exit(0); 23 | } 24 | }); 25 | SwingComponentInspectorHandler.handleAll(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hybrid-example/src/main/java/com/tangorabox/componentinspector/hybrid/swingwithjavafx/SwingWindowWithJavaFXPanel.java: -------------------------------------------------------------------------------- 1 | package com.tangorabox.componentinspector.hybrid.swingwithjavafx; 2 | 3 | import com.tangorabox.componentinspector.fx.FXComponentInspectorHandler; 4 | import com.tangorabox.componentinspector.fx.JavaFXWindowPanel; 5 | import javafx.application.Platform; 6 | import javafx.embed.swing.JFXPanel; 7 | import javafx.scene.Scene; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | 12 | public class SwingWindowWithJavaFXPanel extends JPanel { 13 | 14 | private final JFXPanel javaFXPanel = new JFXPanel(); 15 | 16 | private final Component panelUp = createUpPanel(); 17 | private final Component panelDown = createPanelDown(); 18 | private final Component panelLeft = createPanelLeft(); 19 | private final Component panelRight = createPanelRight(); 20 | private final Component panelCenter = createPanelCenter(); 21 | 22 | 23 | public SwingWindowWithJavaFXPanel() { 24 | setLayout(new BorderLayout()); 25 | add(panelUp, BorderLayout.NORTH); 26 | add(panelDown, BorderLayout.SOUTH); 27 | add(panelLeft, BorderLayout.WEST); 28 | add(panelRight, BorderLayout.EAST); 29 | add(panelCenter, BorderLayout.CENTER); 30 | createJavaFXContent(); 31 | } 32 | 33 | private void createJavaFXContent() { 34 | Platform.runLater(() -> { 35 | // This method is invoked on the JavaFX thread 36 | JavaFXWindowPanel javaFXWindowPanel = new JavaFXWindowPanel(); 37 | Scene scene = new Scene(javaFXWindowPanel); 38 | javaFXPanel.setScene(scene); 39 | FXComponentInspectorHandler.handle(javaFXWindowPanel); 40 | }); 41 | } 42 | 43 | private Component createUpPanel() { 44 | return new JComboBox<>(new String[]{"One", "Two", "Tree"}); 45 | } 46 | 47 | private Component createPanelDown() { 48 | JPanel result = new JPanel(new FlowLayout(FlowLayout.CENTER)); 49 | result.setBorder(BorderFactory.createTitledBorder("South Panel")); 50 | int buttonCount = 4; 51 | for (int i = 1; i <= buttonCount; i++) { 52 | JButton button = new JButton("Button" + i); 53 | result.add(button); 54 | } 55 | return result; 56 | } 57 | 58 | private Component createPanelLeft() { 59 | JPanel result = new JPanel(new BorderLayout()); 60 | result.add(new JButton("Left Panel"), BorderLayout.CENTER); 61 | return result; 62 | } 63 | 64 | private Component createPanelRight() { 65 | JPanel result = new JPanel(new BorderLayout()); 66 | result.add(new JButton("Right Panel"), BorderLayout.CENTER); 67 | return result; 68 | } 69 | 70 | private Component createPanelCenter() { 71 | JPanel result = new JPanel(new BorderLayout()); 72 | result.setBorder(BorderFactory.createTitledBorder("ParentPanel")); 73 | 74 | int panelCount = 4; 75 | 76 | JPanel targetPanel = result; 77 | 78 | for (int i = 1; i <= panelCount; i++) { 79 | JPanel child = new JPanel(new BorderLayout()); 80 | child.setBorder(BorderFactory.createTitledBorder("ChildPanel" + i)); 81 | targetPanel.add(child); 82 | targetPanel = child; 83 | } 84 | 85 | JPanel helloWorldPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); 86 | helloWorldPanel.add(javaFXPanel); 87 | targetPanel.add(helloWorldPanel, BorderLayout.CENTER); 88 | 89 | return result; 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /hybrid-example/src/main/resources/JavaFxWindow.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 |