├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── license.txt ├── settings.gradle └── src └── main ├── java ├── eu │ └── hansolo │ │ └── fx │ │ └── customcontrols │ │ ├── canvasbased │ │ ├── CanvasControl.java │ │ ├── DemoCanvasBased.java │ │ └── LauncherCanvasBased.java │ │ ├── combined │ │ ├── CombinedControl.java │ │ ├── DemoCombined.java │ │ └── LauncherCombined.java │ │ ├── controlskinbased │ │ ├── CustomControl.java │ │ ├── DemoControlSkinBased.java │ │ ├── LauncherControlSkinBased.java │ │ ├── LedSkin.java │ │ └── SwitchSkin.java │ │ ├── extended │ │ ├── DemoExtended.java │ │ ├── ExtendedControl.java │ │ └── LauncherExtended.java │ │ ├── regionbased │ │ ├── DemoRegionBased.java │ │ ├── LauncherRegionBased.java │ │ ├── RegionControl.java │ │ └── buttons.svg │ │ ├── restyled │ │ ├── DemoRestyled.java │ │ ├── LauncherRestyled.java │ │ └── Switch.java │ │ └── tools │ │ └── Helper.java └── module-info.java └── resources └── eu └── hansolo └── fx └── customcontrols ├── canvasbased ├── bubble.png ├── canvas-based.css ├── duke.png └── heart.png ├── combined └── combined.css ├── controlskinbased ├── custom-control.css ├── styles.css └── switch.css ├── extended └── extended.css ├── regionbased └── region-based.css └── restyled └── restyled.css /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.gradle 3 | .DS_Store 4 | /build 5 | /out 6 | /classes 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## JavaFX Custom Controls 2 | 3 | This project will show different ways on how to create custom controls in JavaFX. 4 | It will cover the following approaches: 5 | * Restyle existing controls 6 | * Combine existing controls 7 | * Extending existing controls 8 | * Create a Region based custom control 9 | * Create a Control + Skin based custom control 10 | * Create a Canvas based custom control 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'idea' 3 | id 'java-library' 4 | id 'application' 5 | id 'org.kordamp.gradle.java-project' 6 | id 'org.openjfx.javafxplugin' 7 | id 'biz.aQute.bnd.builder' 8 | } 9 | 10 | if (!project.hasProperty('bintrayUsername')) ext.bintrayUsername = '**undefined**' 11 | if (!project.hasProperty('bintrayApiKey')) ext.bintrayApiKey = '**undefined**' 12 | if (!project.hasProperty('sonatypeUsername')) ext.sonatypeUsername = '**undefined**' 13 | if (!project.hasProperty('sonatypePassword')) ext.sonatypePassword = '**undefined**' 14 | 15 | config { 16 | release = (rootProject.findProperty('release') ?: false).toBoolean() 17 | 18 | info { 19 | name = 'JavaFXCustomControls' 20 | description = 'JavaFXCustomControls is a project that shows different approaches to create custom controls in JavaFX' 21 | vendor = 'Hansolo' 22 | inceptionYear = '2020' 23 | tags = ['javafx', 'java'] 24 | 25 | links { 26 | website = 'https://github.com/HanSolo/JavaFXCustomControls/wiki' 27 | issueTracker = 'https://github.com/HanSolo/JavaFXCustomControls/issues' 28 | scm = 'https://github.com/HanSolo/JavaFXCustomControls.git' 29 | } 30 | 31 | people { 32 | person { 33 | id = 'HanSolo' 34 | name = 'Gerrit Grunwald' 35 | url = 'https://harmoniccode.blogspot.com/' 36 | roles = ['developer'] 37 | } 38 | } 39 | 40 | credentials { 41 | sonatype { 42 | username = project.sonatypeUsername 43 | password = project.sonatypePassword 44 | } 45 | } 46 | 47 | repositories { 48 | repository { 49 | name = 'localRelease' 50 | url = "${project.rootProject.buildDir}/repos/local/release" 51 | } 52 | repository { 53 | name = 'localSnapshot' 54 | url = "${project.rootProject.buildDir}/repos/local/snapshot" 55 | } 56 | } 57 | } 58 | 59 | licensing { 60 | licenses { 61 | license { 62 | id = 'Apache-2.0' 63 | } 64 | } 65 | } 66 | 67 | bintray { 68 | enabled = true 69 | userOrg = 'hansolo' 70 | repo = 'JavaFXCustomControls' 71 | name = rootProject.name 72 | publish = config.release 73 | credentials { 74 | username = project.bintrayUsername 75 | password = project.bintrayApiKey 76 | } 77 | } 78 | 79 | publishing { 80 | signing = false 81 | releasesRepository = 'localRelease' 82 | snapshotsRepository = 'localSnapshot' 83 | } 84 | 85 | docs { 86 | javadoc { 87 | autoLinks { 88 | enabled = false 89 | } 90 | } 91 | } 92 | } 93 | 94 | normalization { 95 | runtimeClasspath { 96 | ignore('/META-INF/MANIFEST.MF') 97 | } 98 | } 99 | 100 | repositories { 101 | jcenter() 102 | mavenCentral() 103 | } 104 | 105 | dependencies { 106 | } 107 | 108 | java { 109 | modularity.inferModulePath.set(true) 110 | } 111 | 112 | javafx { 113 | version = javafxVersion 114 | modules = [ 'javafx.base', 'javafx.graphics', 'javafx.controls' ] 115 | } 116 | 117 | jar { 118 | manifest { 119 | attributes( 120 | 'Bundle-Name': project.name, 121 | 'Bundle-License': 'https://www.apache.org/licenses/LICENSE-2.0;description=Apache License Version 2.0;link=https://spdx.org/licenses/Apache-2.0.html', 122 | 'Bundle-Description': config.info.description, 123 | 'Bundle-SymbolicName': 'eu.hansolo.fx.customcontrols', 124 | 'Export-Package': 'eu.hansolo.fx.customcontrols,eu.hansolo.fx.customcontrols.restyled,eu.hansolo.fx.customcontrols.combined,eu.hansolo.fx.customcontrols.tools' 125 | ) 126 | } 127 | } 128 | 129 | publishing { 130 | publications { 131 | main(MavenPublication) { 132 | pom.withXml { 133 | asNode().dependencies.'*'.findAll { 134 | it.groupId.text() == 'org.openjfx' 135 | }.each { 136 | it.remove(it.classifier) 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | // Fix problems with loading resources 144 | sourceSets { 145 | main { 146 | output.setResourcesDir(java.outputDir) 147 | } 148 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 by Gerrit Grunwald 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | sourceCompatibility = 15 18 | targetCompatibility = 15 19 | 20 | group = eu.hansolo 21 | version = 15.0 22 | javafxVersion = 15.0.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HanSolo/JavaFXCustomControls/eea8220d6d5bec477fb4176434562e9cc6a5545f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | jcenter() 4 | gradlePluginPortal() 5 | } 6 | plugins { 7 | id 'com.gradle.enterprise' version '3.5' 8 | id 'org.kordamp.gradle.java-project' version '0.42.1' 9 | id 'org.openjfx.javafxplugin' version '0.0.9' 10 | id 'biz.aQute.bnd.builder' version '5.2.0' 11 | } 12 | } 13 | 14 | rootProject.name = 'JavaFXCustomControls' 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/canvasbased/CanvasControl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.canvasbased; 18 | 19 | 20 | import eu.hansolo.fx.customcontrols.tools.Helper; 21 | import javafx.animation.AnimationTimer; 22 | import javafx.beans.DefaultProperty; 23 | import javafx.beans.property.BooleanProperty; 24 | import javafx.beans.property.BooleanPropertyBase; 25 | import javafx.beans.property.ObjectProperty; 26 | import javafx.beans.property.ObjectPropertyBase; 27 | import javafx.beans.property.StringProperty; 28 | import javafx.beans.property.StringPropertyBase; 29 | import javafx.collections.ObservableList; 30 | import javafx.event.ActionEvent; 31 | import javafx.geometry.VPos; 32 | import javafx.scene.Node; 33 | import javafx.scene.canvas.Canvas; 34 | import javafx.scene.canvas.GraphicsContext; 35 | import javafx.scene.effect.BlurType; 36 | import javafx.scene.effect.DropShadow; 37 | import javafx.scene.effect.InnerShadow; 38 | import javafx.scene.image.Image; 39 | import javafx.scene.input.MouseEvent; 40 | import javafx.scene.layout.Pane; 41 | import javafx.scene.layout.Region; 42 | import javafx.scene.paint.Color; 43 | import javafx.scene.paint.CycleMethod; 44 | import javafx.scene.paint.LinearGradient; 45 | import javafx.scene.paint.RadialGradient; 46 | import javafx.scene.paint.Stop; 47 | import javafx.scene.shape.Rectangle; 48 | import javafx.scene.text.Font; 49 | import javafx.scene.text.TextAlignment; 50 | 51 | import java.util.Random; 52 | import java.util.function.Consumer; 53 | 54 | 55 | /** 56 | * User: hansolo 57 | * Date: 01.02.21 58 | * Time: 13:38 59 | */ 60 | @DefaultProperty("children") 61 | public class CanvasControl extends Region { 62 | private static final double PREFERRED_WIDTH = 268; 63 | private static final double PREFERRED_HEIGHT = 85; 64 | private static final double MINIMUM_WIDTH = 20; 65 | private static final double MINIMUM_HEIGHT = 20; 66 | private static final double MAXIMUM_WIDTH = 1024; 67 | private static final double MAXIMUM_HEIGHT = 1024; 68 | private static final Color DEFAULT_BACKGROUND_COLOR = Color.web("#3a609be6"); 69 | private static final Color DEFAULT_HIGHLIGHT_COLOR = Color.web("#ffffff80"); 70 | private static final Color DEFAULT_FOREGROUND_COLOR = Color.WHITESMOKE; 71 | private static final LinearGradient TOP_HIGHLIGHT_GRADIENT = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, 72 | new Stop(0.0, Color.rgb(255, 255, 255, 0.5)), 73 | new Stop(1.0, Color.rgb(255, 255, 255, 0.1))); 74 | private static final int NO_OF_PARTICLES = 30; 75 | private static final long UPDATE_INTERVAL = 100_000l; 76 | private final Image particleImg; 77 | private final double imgOffsetX; 78 | private final double imgOffsetY; 79 | private String userAgentStyleSheet; 80 | private double aspectRatio; 81 | private boolean keepAspect; 82 | private double size; 83 | private double width; 84 | private double height; 85 | private Canvas canvas; 86 | private GraphicsContext ctx; 87 | private Rectangle clip; 88 | private Pane pane; 89 | private boolean hovered; 90 | private boolean pressed; 91 | private InnerShadow innerShadow; 92 | private DropShadow dropShadow; 93 | private StringProperty text; 94 | private ObjectProperty backgroundColor; 95 | private ObjectProperty foregroundColor; 96 | private BooleanProperty active; 97 | private ImageParticle[] particles; 98 | private long lastTimerCalled; 99 | private AnimationTimer timer; 100 | private Consumer actionConsumer; 101 | 102 | 103 | // ******************** Constructors ************************************** 104 | public CanvasControl() { 105 | this("", null); 106 | } 107 | public CanvasControl(final String text) { 108 | this(text, null); 109 | } 110 | public CanvasControl(final Image image) { 111 | this("", image); 112 | } 113 | public CanvasControl(final String text, final Image image) { 114 | if (null == image || image.getWidth() != image.getHeight()) { 115 | this.particleImg = new Image(CanvasControl.class.getResourceAsStream("bubble.png")); 116 | } else { 117 | this.particleImg = image; 118 | } 119 | this.imgOffsetX = particleImg.getWidth() * (-0.5); 120 | this.imgOffsetY = particleImg.getHeight() * (-0.5); 121 | this.aspectRatio = PREFERRED_HEIGHT / PREFERRED_WIDTH; 122 | this.keepAspect = true; 123 | this.hovered = false; 124 | this.pressed = false; 125 | this.text = new StringPropertyBase(text) { 126 | @Override protected void invalidated() { redraw(); } 127 | @Override public Object getBean() { return CanvasControl.this; } 128 | @Override public String getName() { return "text"; } 129 | }; 130 | this.innerShadow = new InnerShadow(BlurType.TWO_PASS_BOX, Color.rgb(0, 0, 0, 0.65), 20, 0.0, 0, 0); 131 | this.dropShadow = new DropShadow(BlurType.TWO_PASS_BOX, Color.rgb(0, 0, 0, 0.25), 5, 0.0, 0, 0); 132 | this.backgroundColor = new ObjectPropertyBase<>(DEFAULT_BACKGROUND_COLOR) { 133 | @Override protected void invalidated() { redraw(); } 134 | @Override public Object getBean() { return CanvasControl.this; } 135 | @Override public String getName() { return "backgroundColorTop"; } 136 | }; 137 | this.foregroundColor = new ObjectPropertyBase<>(DEFAULT_FOREGROUND_COLOR) { 138 | @Override protected void invalidated() { redraw(); } 139 | @Override public Object getBean() { return CanvasControl.this; } 140 | @Override public String getName() { return "foregroundColor"; } 141 | }; 142 | this.active = new BooleanPropertyBase(false) { 143 | @Override protected void invalidated() { 144 | if (get()) { 145 | timer.start(); 146 | } else { 147 | timer.stop(); 148 | } 149 | } 150 | @Override public Object getBean() { return CanvasControl.this;} 151 | @Override public String getName() { return "active"; } 152 | }; 153 | this.particles = new ImageParticle[NO_OF_PARTICLES]; 154 | this.lastTimerCalled = System.nanoTime(); 155 | this.timer = new AnimationTimer() { 156 | @Override public void handle(final long now) { 157 | if (now - lastTimerCalled > UPDATE_INTERVAL) { 158 | redraw(); 159 | lastTimerCalled = now; 160 | } 161 | } 162 | }; 163 | for (int i = 0; i < NO_OF_PARTICLES; i++) { 164 | particles[i] = new ImageParticle(PREFERRED_WIDTH, PREFERRED_HEIGHT, particleImg); 165 | } 166 | initGraphics(); 167 | registerListeners(); 168 | } 169 | 170 | 171 | // ******************** Initialization ************************************ 172 | private void initGraphics() { 173 | if (Double.compare(getPrefWidth(), 0.0) <= 0 || Double.compare(getPrefHeight(), 0.0) <= 0 || Double.compare(getWidth(), 0.0) <= 0 || 174 | Double.compare(getHeight(), 0.0) <= 0) { 175 | if (getPrefWidth() > 0 && getPrefHeight() > 0) { 176 | setPrefSize(getPrefWidth(), getPrefHeight()); 177 | } else { 178 | setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT); 179 | } 180 | } 181 | 182 | getStyleClass().add("canvas-control"); 183 | 184 | canvas = new Canvas(getPrefWidth(), getPrefHeight()); 185 | canvas.setPickOnBounds(true); 186 | 187 | ctx = canvas.getGraphicsContext2D(); 188 | ctx.setTextBaseline(VPos.CENTER); 189 | ctx.setTextAlign(TextAlignment.CENTER); 190 | 191 | clip = new Rectangle(); 192 | canvas.setClip(clip); 193 | 194 | pane = new Pane(canvas); 195 | 196 | getChildren().setAll(pane); 197 | } 198 | 199 | private void registerListeners() { 200 | widthProperty().addListener(o -> resize()); 201 | heightProperty().addListener(o -> resize()); 202 | canvas.addEventFilter(MouseEvent.MOUSE_ENTERED, e -> { 203 | hovered = true; 204 | setActive(true); 205 | redraw(); 206 | }); 207 | canvas.addEventFilter(MouseEvent.MOUSE_EXITED, e -> { 208 | hovered = false; 209 | redraw(); 210 | }); 211 | canvas.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> { 212 | pressed = true; 213 | redraw(); 214 | if (null == actionConsumer) { return; } 215 | actionConsumer.accept(new ActionEvent()); 216 | }); 217 | canvas.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> { 218 | pressed = false; 219 | redraw(); 220 | }); 221 | } 222 | 223 | 224 | // ******************** Methods ******************************************* 225 | @Override protected double computeMinWidth(final double height) { return MINIMUM_WIDTH; } 226 | @Override protected double computeMinHeight(final double width) { return MINIMUM_HEIGHT; } 227 | @Override protected double computePrefWidth(final double height) { return super.computePrefWidth(height); } 228 | @Override protected double computePrefHeight(final double width) { return super.computePrefHeight(width); } 229 | @Override protected double computeMaxWidth(final double height) { return MAXIMUM_WIDTH; } 230 | @Override protected double computeMaxHeight(final double width) { return MAXIMUM_HEIGHT; } 231 | 232 | @Override public ObservableList getChildren() { return super.getChildren(); } 233 | 234 | public String getText() { return text.get(); } 235 | public void setText(final String text) { this.text.set(text); } 236 | public StringProperty textProperty() { return text; } 237 | 238 | public Color getBackgroundColor() { return backgroundColor.get(); } 239 | public void setBackgroundColor(final Color color) { backgroundColor.set(color); } 240 | public ObjectProperty backgroundColorProperty() { return backgroundColor; } 241 | 242 | public Color getForegroundColor() { return foregroundColor.get(); } 243 | public void setForegroundColor(final Color color) { foregroundColor.set(color); } 244 | public ObjectProperty foregroundColorProperty() { return foregroundColor; } 245 | 246 | public boolean isActive() { return active.get(); } 247 | public void setActive(final boolean active) { this.active.set(active); } 248 | public BooleanProperty activeProperty() { return active; } 249 | 250 | public void setOnAction(final Consumer actionConsumer) { this.actionConsumer = actionConsumer; } 251 | 252 | 253 | // ******************** Layout ******************************************* 254 | @Override public void layoutChildren() { 255 | super.layoutChildren(); 256 | } 257 | 258 | @Override public String getUserAgentStylesheet() { 259 | if (null == userAgentStyleSheet) { userAgentStyleSheet = CanvasControl.class.getResource("canvas-based.css").toExternalForm(); } 260 | return userAgentStyleSheet; 261 | } 262 | 263 | private void resize() { 264 | width = getWidth() - getInsets().getLeft() - getInsets().getRight(); 265 | height = getHeight() - getInsets().getTop() - getInsets().getBottom(); 266 | size = width < height ? width : height; 267 | 268 | if (keepAspect) { 269 | if (aspectRatio * width > height) { 270 | width = 1 / (aspectRatio / height); 271 | } else if (1 / (aspectRatio / height) > width) { 272 | height = aspectRatio * width; 273 | } 274 | } 275 | 276 | if (width > 0 && height > 0) { 277 | pane.setMaxSize(width, height); 278 | pane.setPrefSize(width, height); 279 | pane.relocate((getWidth() - width) * 0.5, (getHeight() - height) * 0.5); 280 | 281 | clip.setX(1); 282 | clip.setY(1); 283 | clip.setWidth(width - 2); 284 | clip.setHeight(height - 2); 285 | clip.setArcWidth(height); 286 | clip.setArcHeight(height); 287 | 288 | canvas.setWidth(width); 289 | canvas.setHeight(height); 290 | 291 | innerShadow.setRadius(height * 0.25); 292 | dropShadow.setRadius(height * 0.01); 293 | dropShadow.setOffsetY(height * 0.025); 294 | 295 | for (ImageParticle bubble : particles) { bubble.adjustToSize(width, height); } 296 | 297 | redraw(); 298 | } 299 | } 300 | 301 | private void redraw() { 302 | double cornerRadius = height; 303 | Color backgroundColorTop = hovered ? getBackgroundColor().brighter() : getBackgroundColor(); 304 | Color backgroundColorBottom = hovered ? Color.hsb(backgroundColorTop.getHue(), backgroundColorTop.getSaturation(), Helper 305 | .clamp(0, 1, backgroundColorTop.getBrightness() * 1.5)).brighter() : Color.hsb(backgroundColorTop.getHue(), backgroundColorTop.getSaturation(), Helper 306 | .clamp(0, 1, backgroundColorTop.getBrightness() * 1.5)); 307 | 308 | ctx.clearRect(0, 0, width, height); 309 | 310 | // Background 311 | ctx.save(); // inner shadow 312 | ctx.setEffect(innerShadow); 313 | if (pressed) { 314 | ctx.setFill(new LinearGradient(0, 0, 0, 1.0, true, CycleMethod.NO_CYCLE, 315 | new Stop(0.0, Color.hsb(backgroundColorTop.getHue(), backgroundColorTop.getSaturation(), backgroundColorTop.getBrightness() * 0.7)), 316 | new Stop(1.0, Color.hsb(backgroundColorBottom.getHue(), backgroundColorBottom.getSaturation(), backgroundColorBottom.getBrightness() * 0.7)))); 317 | } else { 318 | ctx.setFill(new LinearGradient(0, 0, 0, 1.0, true, CycleMethod.NO_CYCLE, 319 | new Stop(0.0, backgroundColorTop), 320 | new Stop(1.0, backgroundColorBottom))); 321 | } 322 | ctx.fillRoundRect(1, 1, width - 2, height - 2, cornerRadius, cornerRadius); 323 | ctx.restore(); // shadow 324 | 325 | // Inner highlight 326 | ctx.setFill(new RadialGradient(0.0, 0.0, width * 0.5, height * 1.75, width * 0.5,false, CycleMethod.NO_CYCLE, 327 | new Stop(0.0, DEFAULT_HIGHLIGHT_COLOR), 328 | new Stop(1.0, Color.TRANSPARENT))); 329 | ctx.fillRoundRect((width - width * 0.85820896) * 0.5, height * 0.23529412, width * 0.85820896, height * 0.70588235, height * 0.70588235, height * 0.70588235); 330 | 331 | // Top highlight 332 | ctx.save(); // translate 333 | if (pressed) { ctx.translate(0, 1); } 334 | ctx.beginPath(); 335 | ctx.moveTo(width * 0.825886194029851, height * 0.0588235294117647); 336 | ctx.bezierCurveTo(width * 0.892958955223881, height * 0.0585176470588235, width * 0.92440671641791, height * 0.277141176470588, width * 0.887195895522388, height * 0.278105882352941); 337 | ctx.bezierCurveTo(width * 0.886925373134328, height * 0.278117647058824, width * 0.887389925373134, height * 0.276729411764706, width * 0.500067164179104, height * 0.282352941176471); 338 | ctx.bezierCurveTo(width * 0.500009328358209, height * 0.282352941176471, width * 0.113149253731343, height * 0.278117647058824, width * 0.112880597014925, height * 0.278105882352941); 339 | ctx.bezierCurveTo(width * 0.075669776119403, height * 0.277141176470588, width * 0.107117537313433, height * 0.0585176470588235, width * 0.174190298507463, height * 0.0588235294117647); 340 | ctx.lineTo(width * 0.825886194029851, height * 0.0588235294117647); 341 | ctx.closePath(); 342 | ctx.setFill(TOP_HIGHLIGHT_GRADIENT); 343 | ctx.fill(); 344 | 345 | // Text 346 | ctx.save(); // text dropshadow 347 | ctx.setEffect(dropShadow); 348 | ctx.setFill(getForegroundColor()); 349 | ctx.setFont(Font.font(height * 0.5)); 350 | ctx.fillText(getText(), width * 0.5, height * 0.5, width * 0.9); 351 | ctx.restore(); // text dropshadow 352 | ctx.restore(); // translate 353 | 354 | // Particles 355 | if (isActive()) { 356 | for (int i = 0; i < NO_OF_PARTICLES; i++) { 357 | ImageParticle particle = particles[i]; 358 | ctx.save(); // translate & scale 359 | ctx.translate(particle.x, particle.y); 360 | ctx.scale(particle.size, particle.size); 361 | ctx.translate(imgOffsetX, imgOffsetY); 362 | ctx.setGlobalAlpha(particle.opacity); 363 | ctx.drawImage(particle.image, 0, 0); 364 | ctx.restore(); // translate & scale 365 | 366 | particle.update(); 367 | particle.active = hovered; 368 | } 369 | } 370 | } 371 | 372 | 373 | // ******************** Inner Classes ************************************ 374 | class ImageParticle { 375 | private final Random rnd = new Random(); 376 | private final double velocityFactorX = 1.0; 377 | private final double velocityFactorY = 1.0; 378 | private final Image image; 379 | private double x; 380 | private double y; 381 | private double vx; 382 | private double vy; 383 | private double opacity; 384 | private double size; 385 | private double width; 386 | private double height; 387 | private boolean active; 388 | 389 | 390 | // ******************** Constructor *********************************** 391 | public ImageParticle(final double width, final double height, final Image image) { 392 | this.width = width; 393 | this.height = height; 394 | this.image = image; 395 | this.active = true; 396 | init(); 397 | } 398 | 399 | 400 | // ******************** Methods ************************************** 401 | public void init() { 402 | // Position 403 | x = rnd.nextDouble() * width; 404 | y = height + image.getHeight(); 405 | 406 | // Random Size 407 | size = (rnd.nextDouble() * 0.5) + 0.1; 408 | 409 | // Velocity 410 | vx = ((rnd.nextDouble() * 0.5) - 0.25) * velocityFactorX; 411 | vy = ((-(rnd.nextDouble() * 2) - 0.5) * size) * velocityFactorY; 412 | 413 | // Opacity 414 | opacity = (rnd.nextDouble() * 0.6) + 0.4; 415 | } 416 | 417 | public void adjustToSize(final double width, final double height) { 418 | this.width = width; 419 | this.height = height; 420 | x = rnd.nextDouble() * width; 421 | y = height + image.getHeight(); 422 | } 423 | 424 | public void update() { 425 | x += vx; 426 | y += vy; 427 | 428 | // Respawn particle if needed 429 | if(y < -image.getHeight()) { 430 | if (active) { respawn(); } 431 | } 432 | } 433 | 434 | public void respawn() { 435 | x = rnd.nextDouble() * width; 436 | y = height + image.getHeight(); 437 | opacity = (rnd.nextDouble() * 0.6) + 0.4; 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/canvasbased/DemoCanvasBased.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.canvasbased; 18 | 19 | import javafx.application.Application; 20 | import javafx.application.Platform; 21 | import javafx.geometry.Insets; 22 | import javafx.scene.image.Image; 23 | import javafx.scene.layout.Background; 24 | import javafx.scene.layout.BackgroundFill; 25 | import javafx.scene.layout.CornerRadii; 26 | import javafx.scene.layout.HBox; 27 | import javafx.scene.layout.Priority; 28 | import javafx.scene.paint.Color; 29 | import javafx.stage.Stage; 30 | import javafx.scene.layout.StackPane; 31 | import javafx.scene.Scene; 32 | 33 | 34 | /** 35 | * User: hansolo 36 | * Date: 01.02.21 37 | * Time: 13:38 38 | */ 39 | public class DemoCanvasBased extends Application { 40 | private Image dukeImg; 41 | private Image heartImg; 42 | private CanvasControl control1; 43 | private CanvasControl control2; 44 | private CanvasControl control3; 45 | 46 | @Override public void init() { 47 | dukeImg = new Image(DemoCanvasBased.class.getResourceAsStream("duke.png")); 48 | heartImg = new Image(DemoCanvasBased.class.getResourceAsStream("heart.png")); 49 | 50 | control1 = new CanvasControl("We"); 51 | control1.setPrefWidth(120); 52 | 53 | control2 = new CanvasControl("Love", heartImg); 54 | control2.setBackgroundColor(Color.RED.darker()); 55 | control2.setPrefWidth(120); 56 | 57 | control3 = new CanvasControl("Java", dukeImg); 58 | control3.setBackgroundColor(Color.web("#5382A1")); 59 | control3.setPrefWidth(120); 60 | 61 | registerListeners(); 62 | } 63 | 64 | private void registerListeners() { 65 | control1.setOnAction(e -> System.out.println("We button pressed")); 66 | control2.setOnAction(e -> System.out.println("Love button pressed")); 67 | control3.setOnAction(e -> System.out.println("Java button pressed")); 68 | } 69 | 70 | @Override public void start(Stage stage) { 71 | HBox.setHgrow(control1, Priority.ALWAYS); 72 | HBox.setHgrow(control2, Priority.ALWAYS); 73 | HBox.setHgrow(control3, Priority.ALWAYS); 74 | HBox pane = new HBox(20, control1, control2, control3); 75 | pane.setPadding(new Insets(20)); 76 | pane.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))); 77 | 78 | Scene scene = new Scene(pane); 79 | 80 | stage.setTitle("Canvas based Control"); 81 | stage.setScene(scene); 82 | stage.show(); 83 | } 84 | 85 | @Override public void stop() { 86 | Platform.exit(); 87 | System.exit(0); 88 | } 89 | 90 | public static void main(String[] args) { 91 | launch(args); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/canvasbased/LauncherCanvasBased.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.canvasbased; 18 | 19 | public class LauncherCanvasBased { 20 | public static void main(String[] args) { DemoCanvasBased.main(args); } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/combined/CombinedControl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.combined; 18 | 19 | import javafx.geometry.Pos; 20 | import javafx.scene.control.Button; 21 | import javafx.scene.control.TextField; 22 | import javafx.scene.control.TextFormatter; 23 | import javafx.scene.layout.HBox; 24 | 25 | import java.util.Locale; 26 | 27 | 28 | public class CombinedControl extends HBox { 29 | private TextField textField; 30 | private Button button; 31 | 32 | 33 | // ******************** Constructors ************************************** 34 | public CombinedControl() { 35 | getStylesheets().add(CombinedControl.class.getResource("combined.css").toExternalForm()); 36 | initGraphics(); 37 | registerListeners(); 38 | } 39 | 40 | 41 | // ******************** Initialization ************************************ 42 | private void initGraphics() { 43 | getStyleClass().add("combined-control"); 44 | 45 | textField = new TextField(); 46 | textField.setFocusTraversable(false); 47 | textField.setTextFormatter(new TextFormatter<>(change -> change.getText().matches("[0-9]*(\\.[0-9]*)?") ? change : null)); 48 | 49 | button = new Button("°C"); 50 | button.setFocusTraversable(false); 51 | 52 | setSpacing(0); 53 | setFocusTraversable(true); 54 | setFillHeight(false); 55 | setAlignment(Pos.CENTER); 56 | 57 | getChildren().addAll(textField, button); 58 | } 59 | 60 | private void registerListeners() { 61 | button.setOnMousePressed(e -> handleControlPropertyChanged("BUTTON_PRESSED")); 62 | } 63 | 64 | 65 | // ******************** Methods ******************************************* 66 | private void handleControlPropertyChanged(final String property) { 67 | if ("BUTTON_PRESSED".equals(property)) { 68 | String buttonText = button.getText(); 69 | String text = textField.getText(); 70 | if (text.matches("^[-+]?\\d+(\\.\\d+)?$")) { 71 | if ("°C".equals(buttonText)) { 72 | // Convert to Fahrenheit 73 | button.setText("°F"); 74 | textField.setText(toFahrenheit(textField.getText())); 75 | } else { 76 | // Convert to Celsius 77 | button.setText("°C"); 78 | textField.setText(toCelsius(textField.getText())); 79 | } 80 | } 81 | } 82 | } 83 | 84 | private String toFahrenheit(final String text) { 85 | try { 86 | double celsius = Double.parseDouble(text); 87 | return String.format(Locale.US, "%.2f", (celsius * 1.8 + 32)); 88 | } catch (NumberFormatException e) { 89 | return text; 90 | } 91 | } 92 | private String toCelsius(final String text) { 93 | try { 94 | double fahrenheit = Double.parseDouble(text); 95 | return String.format(Locale.US, "%.2f", ((fahrenheit - 32) / 1.8)); 96 | } catch (NumberFormatException e) { 97 | return text; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/combined/DemoCombined.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.combined; 18 | 19 | import javafx.application.Application; 20 | import javafx.application.Platform; 21 | import javafx.geometry.Insets; 22 | import javafx.scene.Scene; 23 | import javafx.scene.control.Button; 24 | import javafx.scene.layout.VBox; 25 | import javafx.stage.Stage; 26 | 27 | 28 | public class DemoCombined extends Application { 29 | private CombinedControl control; 30 | private Button button; 31 | 32 | 33 | @Override public void init() { 34 | control = new CombinedControl(); 35 | button = new Button("Focus"); 36 | } 37 | 38 | @Override public void start(final Stage stage) { 39 | VBox pane = new VBox(24, control, button); 40 | pane.setPadding(new Insets(20)); 41 | 42 | Scene scene = new Scene(pane); 43 | 44 | stage.setTitle("Combined Control"); 45 | stage.setScene(scene); 46 | stage.show(); 47 | 48 | button.requestFocus(); 49 | } 50 | 51 | @Override public void stop() { 52 | Platform.exit(); 53 | System.exit(0); 54 | } 55 | 56 | public static void main(String[] args) { 57 | launch(args); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/combined/LauncherCombined.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.combined; 18 | 19 | public class LauncherCombined { 20 | public static void main(String[] args) { DemoCombined.main(args); } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/controlskinbased/CustomControl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.controlskinbased; 18 | 19 | import javafx.beans.property.BooleanProperty; 20 | import javafx.beans.property.BooleanPropertyBase; 21 | import javafx.beans.property.ObjectProperty; 22 | import javafx.css.CssMetaData; 23 | import javafx.css.PseudoClass; 24 | import javafx.css.SimpleStyleableObjectProperty; 25 | import javafx.css.Styleable; 26 | import javafx.css.StyleableProperty; 27 | import javafx.css.StyleablePropertyFactory; 28 | import javafx.scene.control.Control; 29 | import javafx.scene.control.Skin; 30 | import javafx.scene.paint.Color; 31 | 32 | import java.util.List; 33 | 34 | 35 | public class CustomControl extends Control { 36 | public enum SkinType { LED, SWITCH } 37 | 38 | private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData()); 39 | 40 | // CSS pseudo classes 41 | private static final PseudoClass ON_PSEUDO_CLASS = PseudoClass.getPseudoClass("on"); 42 | private BooleanProperty state; 43 | 44 | // CSS Styleable property 45 | private static final CssMetaData COLOR = FACTORY.createColorCssMetaData("-color", s -> s.color, Color.RED, false); 46 | private final StyleableProperty color; 47 | 48 | private static String defaultUserAgentStyleSheet; 49 | private static String switchUserAgentStyleSheet; 50 | 51 | // Properties 52 | private SkinType skinType; 53 | 54 | 55 | // ******************** Constructors ************************************** 56 | public CustomControl() { 57 | this(SkinType.LED); 58 | } 59 | public CustomControl(final SkinType skinType) { 60 | getStyleClass().add("custom-control"); 61 | this.skinType = skinType; 62 | this.state = new BooleanPropertyBase(false) { 63 | @Override protected void invalidated() { pseudoClassStateChanged(ON_PSEUDO_CLASS, get()); } 64 | @Override public Object getBean() { return this; } 65 | @Override public String getName() { return "state"; } 66 | }; 67 | this.color = new SimpleStyleableObjectProperty<>(COLOR, this, "color"); 68 | } 69 | 70 | 71 | // ******************** Methods ******************************************* 72 | public boolean getState() { return state.get(); } 73 | public void setState(final boolean state) { this.state.set(state); } 74 | public BooleanProperty stateProperty() { return state; } 75 | 76 | 77 | // ******************** CSS Styleable Properties ************************** 78 | public Color getColor() { return color.getValue(); } 79 | public void setColor(final Color color) { this.color.setValue(color); } 80 | public ObjectProperty colorProperty() { return (ObjectProperty) color; } 81 | 82 | 83 | // ******************** Style related ************************************* 84 | @Override protected Skin createDefaultSkin() { 85 | switch(skinType) { 86 | case SWITCH: return new SwitchSkin(CustomControl.this); 87 | case LED : 88 | default : return new LedSkin(CustomControl.this); 89 | } 90 | } 91 | 92 | @Override public String getUserAgentStylesheet() { 93 | switch(skinType) { 94 | case SWITCH: 95 | if (null == switchUserAgentStyleSheet) { switchUserAgentStyleSheet = CustomControl.class.getResource("switch.css").toExternalForm(); } 96 | return switchUserAgentStyleSheet; 97 | case LED : 98 | default : 99 | if (null == defaultUserAgentStyleSheet) { defaultUserAgentStyleSheet = CustomControl.class.getResource("custom-control.css").toExternalForm(); } 100 | return defaultUserAgentStyleSheet; 101 | } 102 | } 103 | 104 | public static List> getClassCssMetaData() { return FACTORY.getCssMetaData(); } 105 | @Override public List> getControlCssMetaData() { return FACTORY.getCssMetaData(); } 106 | } -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/controlskinbased/DemoControlSkinBased.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.controlskinbased; 18 | 19 | import eu.hansolo.fx.customcontrols.controlskinbased.CustomControl.SkinType; 20 | import javafx.application.Application; 21 | import javafx.application.Platform; 22 | import javafx.geometry.Insets; 23 | import javafx.scene.Scene; 24 | import javafx.scene.layout.VBox; 25 | import javafx.scene.paint.Color; 26 | import javafx.stage.Stage; 27 | 28 | 29 | public class DemoControlSkinBased extends Application { 30 | private CustomControl control0; 31 | private CustomControl control1; 32 | 33 | 34 | @Override public void init() { 35 | control0 = new CustomControl(); 36 | control0.setState(true); 37 | control0.setPrefSize(100, 100); 38 | control0.setColor(Color.LIME); 39 | 40 | control1 = new CustomControl(SkinType.SWITCH); 41 | control1.setState(true); 42 | control1.setColor(Color.web("#4bd865")); 43 | control1.stateProperty().addListener((o, ov, nv) -> control0.setState(nv)); 44 | } 45 | 46 | @Override public void start(final Stage stage) { 47 | VBox pane = new VBox(20, control0, control1); 48 | pane.setPadding(new Insets(20)); 49 | 50 | Scene scene = new Scene(pane, 200, 200); 51 | scene.getStylesheets().add(DemoControlSkinBased.class.getResource("styles.css").toExternalForm()); 52 | 53 | stage.setTitle("Control-Skin based Control"); 54 | stage.setScene(scene); 55 | stage.show(); 56 | } 57 | 58 | @Override public void stop() { 59 | Platform.exit(); 60 | System.exit(0); 61 | } 62 | 63 | public static void main(String[] args) { 64 | launch(args); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/controlskinbased/LauncherControlSkinBased.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.controlskinbased; 18 | 19 | public class LauncherControlSkinBased { 20 | public static void main(String[] args) { DemoControlSkinBased.main(args); } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/controlskinbased/LedSkin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.controlskinbased; 18 | 19 | import javafx.beans.InvalidationListener; 20 | import javafx.scene.control.Skin; 21 | import javafx.scene.control.SkinBase; 22 | import javafx.scene.effect.BlurType; 23 | import javafx.scene.effect.DropShadow; 24 | import javafx.scene.effect.InnerShadow; 25 | import javafx.scene.layout.Region; 26 | import javafx.scene.paint.Color; 27 | 28 | 29 | public class LedSkin extends SkinBase implements Skin { 30 | private static final double PREFERRED_WIDTH = 16; 31 | private static final double PREFERRED_HEIGHT = 16; 32 | private static final double MINIMUM_WIDTH = 8; 33 | private static final double MINIMUM_HEIGHT = 8; 34 | private static final double MAXIMUM_WIDTH = 1024; 35 | private static final double MAXIMUM_HEIGHT = 1024; 36 | private double size; 37 | private Region frame; 38 | private Region main; 39 | private Region highlight; 40 | private InnerShadow innerShadow; 41 | private DropShadow glow; 42 | private CustomControl control; 43 | private InvalidationListener sizeListener; 44 | private InvalidationListener colorListener; 45 | private InvalidationListener stateListener; 46 | 47 | 48 | // ******************** Constructors ************************************** 49 | public LedSkin(final CustomControl control) { 50 | super(control); 51 | this.control = control; 52 | sizeListener = o -> handleControlPropertyChanged("RESIZE"); 53 | colorListener = o -> handleControlPropertyChanged("COLOR"); 54 | stateListener = o -> handleControlPropertyChanged("STATE"); 55 | initGraphics(); 56 | registerListeners(); 57 | } 58 | 59 | 60 | // ******************** Initialization ************************************ 61 | private void initGraphics() { 62 | if (Double.compare(control.getPrefWidth(), 0.0) <= 0 || Double.compare(control.getPrefHeight(), 0.0) <= 0 || 63 | Double.compare(control.getWidth(), 0.0) <= 0 || Double.compare(control.getHeight(), 0.0) <= 0) { 64 | if (control.getPrefWidth() > 0 && control.getPrefHeight() > 0) { 65 | control.setPrefSize(control.getPrefWidth(), control.getPrefHeight()); 66 | } else { 67 | control.setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT); 68 | } 69 | } 70 | 71 | frame = new Region(); 72 | frame.getStyleClass().setAll("frame"); 73 | 74 | main = new Region(); 75 | main.getStyleClass().setAll("main"); 76 | main.setStyle(String.join("", "-color: ", control.getColor().toString().replace("0x", "#"), ";")); 77 | 78 | innerShadow = new InnerShadow(BlurType.TWO_PASS_BOX, Color.rgb(0, 0, 0, 0.65), 8, 0, 0, 0); 79 | 80 | glow = new DropShadow(BlurType.TWO_PASS_BOX, control.getColor(), 20, 0, 0, 0); 81 | glow.setInput(innerShadow); 82 | 83 | highlight = new Region(); 84 | highlight.getStyleClass().setAll("highlight"); 85 | 86 | getChildren().addAll(frame, main, highlight); 87 | } 88 | 89 | private void registerListeners() { 90 | control.widthProperty().addListener(sizeListener); 91 | control.heightProperty().addListener(sizeListener); 92 | control.colorProperty().addListener(colorListener); 93 | control.stateProperty().addListener(stateListener); 94 | } 95 | 96 | 97 | // ******************** Methods ******************************************* 98 | @Override protected double computeMinWidth(final double height, final double top, final double right, final double bottom, final double left) { return MINIMUM_WIDTH; } 99 | @Override protected double computeMinHeight(final double width, final double top, final double right, final double bottom, final double left) { return MINIMUM_HEIGHT; } 100 | @Override protected double computePrefWidth(final double height, final double top, final double right, final double bottom, final double left) { return super.computePrefWidth(height, top, right, bottom, left); } 101 | @Override protected double computePrefHeight(final double width, final double top, final double right, final double bottom, final double left) { return super.computePrefHeight(width, top, right, bottom, left); } 102 | @Override protected double computeMaxWidth(final double width, final double top, final double right, final double bottom, final double left) { return MAXIMUM_WIDTH; } 103 | @Override protected double computeMaxHeight(final double width, final double top, final double right, final double bottom, final double left) { return MAXIMUM_HEIGHT; } 104 | 105 | protected void handleControlPropertyChanged(final String property) { 106 | if ("RESIZE".equals(property)) { 107 | resize(); 108 | } else if ("COLOR".equals(property)) { 109 | main.setStyle(String.join("", "-color: ", (control.getColor()).toString().replace("0x", "#"), ";")); 110 | resize(); 111 | } else if ("STATE".equals(property)) { 112 | main.setEffect(control.getState() ? glow : innerShadow); 113 | } 114 | } 115 | 116 | @Override public void dispose() { 117 | control.widthProperty().removeListener(sizeListener); 118 | control.heightProperty().removeListener(sizeListener); 119 | control.colorProperty().removeListener(colorListener); 120 | control.stateProperty().removeListener(stateListener); 121 | control = null; 122 | } 123 | 124 | 125 | // ******************** Layout ******************************************** 126 | @Override public void layoutChildren(final double x, final double y, final double width, final double height) { 127 | super.layoutChildren(x, y, width, height); 128 | } 129 | 130 | private void resize() { 131 | double width = control.getWidth() - control.getInsets().getLeft() - control.getInsets().getRight(); 132 | double height = control.getHeight() - control.getInsets().getTop() - control.getInsets().getBottom(); 133 | size = width < height ? width : height; 134 | 135 | if (size > 0) { 136 | innerShadow.setRadius(0.07 * size); 137 | glow.setRadius(0.36 * size); 138 | glow.setColor(control.getColor()); 139 | 140 | frame.setMaxSize(size, size); 141 | 142 | main.setMaxSize(0.72 * size, 0.72 * size); 143 | main.relocate(0.14 * size, 0.14 * size); 144 | main.setEffect(control.getState() ? glow : innerShadow); 145 | 146 | highlight.setMaxSize(0.58 * size, 0.58 * size); 147 | highlight.relocate(0.21 * size, 0.21 * size); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/controlskinbased/SwitchSkin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.controlskinbased; 18 | 19 | import javafx.animation.TranslateTransition; 20 | import javafx.beans.InvalidationListener; 21 | import javafx.event.EventHandler; 22 | import javafx.scene.control.Skin; 23 | import javafx.scene.control.SkinBase; 24 | import javafx.scene.input.MouseEvent; 25 | import javafx.scene.layout.Pane; 26 | import javafx.scene.layout.Region; 27 | import javafx.util.Duration; 28 | 29 | 30 | public class SwitchSkin extends SkinBase implements Skin { 31 | private static final double PREFERRED_WIDTH = 76; 32 | private static final double PREFERRED_HEIGHT = 46; 33 | private Region switchBackground; 34 | private Region thumb; 35 | private Pane pane; 36 | private TranslateTransition translate; 37 | private CustomControl control; 38 | private InvalidationListener colorListener; 39 | private InvalidationListener state; 40 | private EventHandler mouseEventHandler; 41 | 42 | 43 | // ******************** Constructors ************************************** 44 | public SwitchSkin(final CustomControl control) { 45 | super(control); 46 | this.control = control; 47 | colorListener = o -> handleControlPropertyChanged("COLOR"); 48 | state = o -> handleControlPropertyChanged("STATE"); 49 | mouseEventHandler = e -> this.control.setState(!this.control.getState()); 50 | initGraphics(); 51 | registerListeners(); 52 | } 53 | 54 | 55 | // ******************** Initialization ************************************ 56 | private void initGraphics() { 57 | switchBackground = new Region(); 58 | switchBackground.getStyleClass().add("switch-background"); 59 | switchBackground.setStyle(String.join("", "-color: ", control.getColor().toString().replace("0x", "#"), ";")); 60 | 61 | thumb = new Region(); 62 | thumb.getStyleClass().add("thumb"); 63 | thumb.setMouseTransparent(true); 64 | if (control.getState()) { thumb.setTranslateX(32); } 65 | 66 | translate = new TranslateTransition(Duration.millis(70), thumb); 67 | 68 | pane = new Pane(switchBackground, thumb); 69 | getChildren().add(pane); 70 | } 71 | 72 | private void registerListeners() { 73 | control.colorProperty().addListener(colorListener); 74 | control.stateProperty().addListener(state); 75 | switchBackground.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseEventHandler); 76 | } 77 | 78 | 79 | // ******************** Methods ******************************************* 80 | @Override public void layoutChildren(final double x, final double y, final double width, final double height) { 81 | super.layoutChildren(x, y, width, height); 82 | switchBackground.relocate((width - PREFERRED_WIDTH) * 0.5, (height - PREFERRED_HEIGHT) * 0.5); 83 | thumb.relocate((width - PREFERRED_WIDTH) * 0.5, (height - PREFERRED_HEIGHT) * 0.5); 84 | } 85 | 86 | protected void handleControlPropertyChanged(final String property) { 87 | if ("COLOR".equals(property)) { 88 | switchBackground.setStyle(String.join("", "-color: ", control.getColor().toString().replace("0x", "#"), ";")); 89 | } else if ("STATE".equals(property)) { 90 | if (control.getState()) { 91 | // move thumb to the right 92 | translate.setFromX(2); 93 | translate.setToX(32); 94 | } else { 95 | // move thumb to the left 96 | translate.setFromX(32); 97 | translate.setToX(2); 98 | } 99 | translate.play(); 100 | } 101 | } 102 | 103 | @Override public void dispose() { 104 | control.colorProperty().removeListener(colorListener); 105 | control.stateProperty().removeListener(state); 106 | switchBackground.removeEventHandler(MouseEvent.MOUSE_PRESSED, mouseEventHandler); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/extended/DemoExtended.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.extended; 18 | 19 | import javafx.application.Application; 20 | import javafx.application.Platform; 21 | import javafx.geometry.Insets; 22 | import javafx.scene.Scene; 23 | import javafx.scene.control.Button; 24 | import javafx.scene.layout.VBox; 25 | import javafx.stage.Stage; 26 | 27 | 28 | public class DemoExtended extends Application { 29 | private ExtendedControl control; 30 | private Button button; 31 | 32 | 33 | @Override public void init() { 34 | control = new ExtendedControl(); 35 | control.setPromptText("Name"); 36 | 37 | button = new Button("Focus"); 38 | } 39 | 40 | @Override public void start(final Stage stage) { 41 | VBox pane = new VBox(24, control, button); 42 | pane.setPadding(new Insets(20)); 43 | 44 | Scene scene = new Scene(pane); 45 | 46 | stage.setTitle("Extended Control"); 47 | stage.setScene(scene); 48 | stage.show(); 49 | 50 | button.requestFocus(); 51 | } 52 | 53 | @Override public void stop() { 54 | Platform.exit(); 55 | System.exit(0); 56 | } 57 | 58 | public static void main(String[] args) { 59 | launch(args); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/extended/ExtendedControl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.extended; 18 | 19 | import javafx.animation.KeyFrame; 20 | import javafx.animation.KeyValue; 21 | import javafx.animation.Timeline; 22 | import javafx.beans.property.DoubleProperty; 23 | import javafx.beans.property.ObjectProperty; 24 | import javafx.beans.property.SimpleDoubleProperty; 25 | import javafx.css.CssMetaData; 26 | import javafx.css.SimpleStyleableObjectProperty; 27 | import javafx.css.Styleable; 28 | import javafx.css.StyleableProperty; 29 | import javafx.css.StyleablePropertyFactory; 30 | import javafx.scene.control.TextField; 31 | import javafx.scene.layout.HBox; 32 | import javafx.scene.paint.Color; 33 | import javafx.scene.text.Font; 34 | import javafx.scene.text.Text; 35 | import javafx.util.Duration; 36 | 37 | import java.util.List; 38 | 39 | 40 | public class ExtendedControl extends TextField { 41 | private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(TextField.getClassCssMetaData()); 42 | private static final Color DEFAULT_MATERIAL_DESIGN_COLOR = Color.web("#009688"); 43 | private static final Color DEFAULT_PROMPT_TEXT_COLOR = Color.web("#757575"); 44 | private static final double STD_FONT_SIZE = 13; 45 | private static final double SMALL_FONT_SIZE = 10; 46 | private static final double TOP_OFFSET_Y = 4; 47 | private static final int ANIMATION_DURATION = 60; 48 | private static final CssMetaData MATERIAL_DESIGN_COLOR = FACTORY.createColorCssMetaData("-material-design-color", s -> s.materialDesignColor, DEFAULT_MATERIAL_DESIGN_COLOR, false); 49 | private static final CssMetaData PROMPT_TEXT_COLOR = FACTORY.createColorCssMetaData("-prompt-text-color", s -> s.promptTextColor, DEFAULT_PROMPT_TEXT_COLOR, false); 50 | private static String userAgentStyleSheet; 51 | private final StyleableProperty materialDesignColor; 52 | private final StyleableProperty promptTextColor; 53 | private Text promptText; 54 | private HBox promptTextBox; 55 | private DoubleProperty fontSize; 56 | private Timeline timeline; 57 | 58 | 59 | // ******************** Constructors ************************************** 60 | public ExtendedControl() { 61 | this(""); 62 | } 63 | public ExtendedControl(final String promptTextBox) { 64 | super(promptTextBox); 65 | 66 | materialDesignColor = new SimpleStyleableObjectProperty<>(MATERIAL_DESIGN_COLOR, this, "materialDesignColor"); 67 | promptTextColor = new SimpleStyleableObjectProperty<>(PROMPT_TEXT_COLOR, this, "promptTextColor"); 68 | 69 | fontSize = new SimpleDoubleProperty(ExtendedControl.this, "fontSize", getFont().getSize()); 70 | timeline = new Timeline(); 71 | 72 | initGraphics(); 73 | registerListeners(); 74 | } 75 | 76 | 77 | // ******************** Initialization ************************************ 78 | private void initGraphics() { 79 | getStyleClass().addAll("material-field"); 80 | 81 | final String fontFamily = getFont().getFamily(); 82 | final int length = getText().length(); 83 | 84 | promptText = new Text(getPromptText()); 85 | promptText.getStyleClass().add("prompt-text"); 86 | 87 | promptTextBox = new HBox(promptText); 88 | promptTextBox.getStyleClass().add("material-field"); 89 | 90 | if (!isEditable() || isDisabled() || length > 0) { 91 | promptText.setFont(Font.font(fontFamily, SMALL_FONT_SIZE)); 92 | promptTextBox.setTranslateY(-STD_FONT_SIZE - TOP_OFFSET_Y); 93 | } else { 94 | promptText.setFont(Font.font(fontFamily, STD_FONT_SIZE)); 95 | } 96 | 97 | getChildren().addAll(promptTextBox); 98 | } 99 | 100 | private void registerListeners() { 101 | textProperty().addListener(o -> handleTextAndFocus(isFocused())); 102 | promptTextProperty().addListener(o -> promptText.setText(getPromptText())); 103 | focusedProperty().addListener(o -> handleTextAndFocus(isFocused())); 104 | promptTextColorProperty().addListener(o -> promptText.setFill(getPromptTextColor())); 105 | fontSize.addListener(o -> promptText.setFont(Font.font(fontSize.get()))); 106 | timeline.setOnFinished(evt -> { 107 | final int length = null == getText() ? 0 : getText().length(); 108 | if (length > 0 && promptTextBox.getTranslateY() >= 0) { 109 | promptTextBox.setTranslateY(-STD_FONT_SIZE - TOP_OFFSET_Y); 110 | fontSize.set(SMALL_FONT_SIZE); 111 | } 112 | }); 113 | } 114 | 115 | 116 | // ******************** CSS Stylable Properties *************************** 117 | public Color getMaterialDesignColor() { return materialDesignColor.getValue(); } 118 | public void setMaterialDesignColor(final Color color) { materialDesignColor.setValue(color); } 119 | public ObjectProperty materialDesignColorProperty() { return (ObjectProperty) materialDesignColor; } 120 | 121 | public Color getPromptTextColor() { return promptTextColor.getValue(); } 122 | public void setPromptTextColor(final Color color) { promptTextColor.setValue(color); } 123 | public ObjectProperty promptTextColorProperty() { return (ObjectProperty) promptTextColor; } 124 | 125 | 126 | // ******************** Misc ********************************************** 127 | private void handleTextAndFocus(final boolean isFocused) { 128 | final int length = null == getText() ? 0 : getText().length(); 129 | 130 | KeyFrame kf0; 131 | KeyFrame kf1; 132 | 133 | KeyValue kvTextY0; 134 | KeyValue kvTextY1; 135 | KeyValue kvTextFontSize0; 136 | KeyValue kvTextFontSize1; 137 | KeyValue kvPromptTextFill0; 138 | KeyValue kvPromptTextFill1; 139 | 140 | if (isFocused | length > 0 || isDisabled() || !isEditable()) { 141 | if (Double.compare(promptTextBox.getTranslateY(), -STD_FONT_SIZE - TOP_OFFSET_Y) != 0) { 142 | kvTextY0 = new KeyValue(promptTextBox.translateYProperty(), 0); 143 | kvTextY1 = new KeyValue(promptTextBox.translateYProperty(), -STD_FONT_SIZE - TOP_OFFSET_Y); 144 | kvTextFontSize0 = new KeyValue(fontSize, STD_FONT_SIZE); 145 | kvTextFontSize1 = new KeyValue(fontSize, SMALL_FONT_SIZE); 146 | kvPromptTextFill0 = new KeyValue(promptTextColorProperty(), DEFAULT_PROMPT_TEXT_COLOR); 147 | kvPromptTextFill1 = new KeyValue(promptTextColorProperty(), isFocused ? getMaterialDesignColor() : DEFAULT_PROMPT_TEXT_COLOR); 148 | 149 | kf0 = new KeyFrame(Duration.ZERO, kvTextY0, kvTextFontSize0, kvPromptTextFill0); 150 | kf1 = new KeyFrame(Duration.millis(ANIMATION_DURATION), kvTextY1, kvTextFontSize1, kvPromptTextFill1); 151 | 152 | timeline.getKeyFrames().setAll(kf0, kf1); 153 | timeline.play(); 154 | } 155 | promptText.setFill(isFocused ? getMaterialDesignColor() : DEFAULT_PROMPT_TEXT_COLOR); 156 | } else { 157 | if (Double.compare(promptTextBox.getTranslateY(), 0) != 0) { 158 | kvTextY0 = new KeyValue(promptTextBox.translateYProperty(), promptTextBox.getTranslateY()); 159 | kvTextY1 = new KeyValue(promptTextBox.translateYProperty(), 0); 160 | kvTextFontSize0 = new KeyValue(fontSize, SMALL_FONT_SIZE); 161 | kvTextFontSize1 = new KeyValue(fontSize, STD_FONT_SIZE); 162 | kvPromptTextFill0 = new KeyValue(promptTextColorProperty(), getMaterialDesignColor()); 163 | kvPromptTextFill1 = new KeyValue(promptTextColorProperty(), DEFAULT_PROMPT_TEXT_COLOR); 164 | 165 | kf0 = new KeyFrame(Duration.ZERO, kvTextY0, kvTextFontSize0, kvPromptTextFill0); 166 | kf1 = new KeyFrame(Duration.millis(ANIMATION_DURATION), kvTextY1, kvTextFontSize1, kvPromptTextFill1); 167 | 168 | timeline.getKeyFrames().setAll(kf0, kf1); 169 | timeline.play(); 170 | } 171 | } 172 | } 173 | 174 | 175 | // ******************** Style related ************************************* 176 | @Override public String getUserAgentStylesheet() { 177 | if (null == userAgentStyleSheet) { userAgentStyleSheet = getClass().getResource("extended.css").toExternalForm(); } 178 | return userAgentStyleSheet; 179 | } 180 | 181 | public static List> getClassCssMetaData() { return FACTORY.getCssMetaData(); } 182 | @Override public List> getControlCssMetaData() { return FACTORY.getCssMetaData(); } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/extended/LauncherExtended.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.extended; 18 | 19 | public class LauncherExtended { 20 | public static void main(String[] args) { DemoExtended.main(args); } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/regionbased/DemoRegionBased.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.regionbased; 18 | 19 | import eu.hansolo.fx.customcontrols.regionbased.RegionControl.Type; 20 | import javafx.application.Application; 21 | import javafx.application.Platform; 22 | import javafx.geometry.Insets; 23 | import javafx.scene.input.MouseEvent; 24 | import javafx.scene.layout.HBox; 25 | import javafx.stage.Stage; 26 | import javafx.scene.layout.StackPane; 27 | import javafx.scene.Scene; 28 | 29 | import java.util.function.Consumer; 30 | 31 | 32 | /** 33 | * User: hansolo 34 | * Date: 01.02.21 35 | * Time: 08:18 36 | */ 37 | public class DemoRegionBased extends Application { 38 | private RegionControl redButton; 39 | private RegionControl yellowButton; 40 | private RegionControl greenButton; 41 | private HBox buttonBox; 42 | 43 | 44 | @Override public void init() { 45 | redButton = new RegionControl(Type.CLOSE); 46 | yellowButton = new RegionControl(Type.MINIMIZE); 47 | greenButton = new RegionControl(Type.ZOOM); 48 | buttonBox = new HBox(8, redButton, yellowButton, greenButton); 49 | 50 | registerListeners(); 51 | } 52 | 53 | private void registerListeners() { 54 | redButton.setOnMousePressed((Consumer) e -> System.out.println("Close pressed")); 55 | redButton.setOnMouseReleased((Consumer) e -> System.out.println("Close released")); 56 | 57 | yellowButton.setOnMousePressed((Consumer) e -> System.out.println("Minimized pressed")); 58 | yellowButton.setOnMouseReleased((Consumer) e -> System.out.println("Minimized released")); 59 | 60 | greenButton.setOnMousePressed((Consumer) e -> { 61 | System.out.println("Zoom pressed"); 62 | greenButton.setState(!greenButton.getState()); 63 | }); 64 | greenButton.setOnMouseReleased((Consumer) e -> System.out.println("Zoom released")); 65 | 66 | buttonBox.addEventFilter(MouseEvent.MOUSE_ENTERED, e -> { 67 | redButton.setHovered(true); 68 | yellowButton.setHovered(true); 69 | greenButton.setHovered(true); 70 | }); 71 | buttonBox.addEventFilter(MouseEvent.MOUSE_EXITED, e -> { 72 | redButton.setHovered(false); 73 | yellowButton.setHovered(false); 74 | greenButton.setHovered(false); 75 | }); 76 | } 77 | 78 | @Override public void start(final Stage stage) { 79 | StackPane pane = new StackPane(buttonBox); 80 | pane.setPadding(new Insets(8)); 81 | 82 | Scene scene = new Scene(pane); 83 | 84 | stage.setTitle("Region based Control"); 85 | stage.setScene(scene); 86 | stage.show(); 87 | } 88 | 89 | @Override public void stop() { 90 | Platform.exit(); 91 | System.exit(0); 92 | } 93 | 94 | public static void main(String[] args) { 95 | launch(args); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/regionbased/LauncherRegionBased.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.regionbased; 18 | 19 | public class LauncherRegionBased { 20 | public static void main(String[] args) { DemoRegionBased.main(args); } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/regionbased/RegionControl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.regionbased; 18 | 19 | import javafx.beans.DefaultProperty; 20 | import javafx.beans.property.BooleanProperty; 21 | import javafx.beans.property.BooleanPropertyBase; 22 | import javafx.beans.property.ObjectProperty; 23 | import javafx.beans.property.ObjectPropertyBase; 24 | import javafx.collections.ObservableList; 25 | import javafx.css.PseudoClass; 26 | import javafx.scene.Node; 27 | import javafx.scene.input.MouseEvent; 28 | import javafx.scene.layout.Region; 29 | import javafx.scene.shape.Circle; 30 | import javafx.scene.shape.StrokeType; 31 | 32 | import java.util.function.Consumer; 33 | 34 | 35 | /** 36 | * User: hansolo 37 | * Date: 01.02.21 38 | * Time: 08:17 39 | */ 40 | @DefaultProperty("children") 41 | public class RegionControl extends Region { 42 | public enum Type { CLOSE, MINIMIZE, ZOOM } 43 | 44 | private static final double PREFERRED_WIDTH = 12; 45 | private static final double PREFERRED_HEIGHT = 12; 46 | private static final double MINIMUM_WIDTH = 12; 47 | private static final double MINIMUM_HEIGHT = 12; 48 | private static final double MAXIMUM_WIDTH = 12; 49 | private static final double MAXIMUM_HEIGHT = 12; 50 | private static final PseudoClass CLOSE_PSEUDO_CLASS = PseudoClass.getPseudoClass("close"); 51 | private static final PseudoClass MINIMIZE_PSEUDO_CLASS = PseudoClass.getPseudoClass("minimize"); 52 | private static final PseudoClass ZOOM_PSEUDO_CLASS = PseudoClass.getPseudoClass("zoom"); 53 | private static final PseudoClass HOVERED_PSEUDO_CLASS = PseudoClass.getPseudoClass("hovered"); 54 | private static final PseudoClass PRESSED_PSEUDO_CLASS = PseudoClass.getPseudoClass("pressed"); 55 | private static final PseudoClass STATE_PSEUDO_CLASS = PseudoClass.getPseudoClass("state"); 56 | private BooleanProperty hovered; 57 | private BooleanProperty state; 58 | private static String userAgentStyleSheet; 59 | private ObjectProperty type; 60 | private double size; 61 | private double width; 62 | private double height; 63 | private Circle circle; 64 | private Region symbol; 65 | private Consumer mousePressedConsumer; 66 | private Consumer mouseReleasedConsumer; 67 | 68 | 69 | // ******************** Constructors ************************************** 70 | public RegionControl() { 71 | this(Type.CLOSE); 72 | } 73 | public RegionControl(final Type type) { 74 | this.type = new ObjectPropertyBase<>(type) { 75 | @Override protected void invalidated() { 76 | switch(get()) { 77 | case CLOSE -> { 78 | pseudoClassStateChanged(CLOSE_PSEUDO_CLASS, true); 79 | pseudoClassStateChanged(MINIMIZE_PSEUDO_CLASS, false); 80 | pseudoClassStateChanged(ZOOM_PSEUDO_CLASS, false); 81 | } 82 | case MINIMIZE -> { 83 | pseudoClassStateChanged(CLOSE_PSEUDO_CLASS, false); 84 | pseudoClassStateChanged(MINIMIZE_PSEUDO_CLASS, true); 85 | pseudoClassStateChanged(ZOOM_PSEUDO_CLASS, false); 86 | } 87 | case ZOOM -> { 88 | pseudoClassStateChanged(CLOSE_PSEUDO_CLASS, false); 89 | pseudoClassStateChanged(MINIMIZE_PSEUDO_CLASS, false); 90 | pseudoClassStateChanged(ZOOM_PSEUDO_CLASS, true); 91 | } 92 | } 93 | } 94 | @Override public Object getBean() { return RegionControl.this; } 95 | @Override public String getName() { return "type"; } 96 | }; 97 | this.hovered = new BooleanPropertyBase() { 98 | @Override protected void invalidated() { pseudoClassStateChanged(HOVERED_PSEUDO_CLASS, get()); } 99 | @Override public Object getBean() { return RegionControl.this; } 100 | @Override public String getName() { return "hovered"; } 101 | }; 102 | this.state = new BooleanPropertyBase(false) { 103 | @Override protected void invalidated() { pseudoClassStateChanged(STATE_PSEUDO_CLASS, get()); } 104 | @Override public Object getBean() { return RegionControl.this; } 105 | @Override public String getName() { return "state"; } 106 | }; 107 | 108 | pseudoClassStateChanged(CLOSE_PSEUDO_CLASS, Type.CLOSE == type); 109 | pseudoClassStateChanged(MINIMIZE_PSEUDO_CLASS, Type.MINIMIZE == type); 110 | pseudoClassStateChanged(ZOOM_PSEUDO_CLASS, Type.ZOOM == type); 111 | 112 | initGraphics(); 113 | registerListeners(); 114 | } 115 | 116 | 117 | // ******************** Initialization ************************************ 118 | private void initGraphics() { 119 | if (Double.compare(getPrefWidth(), 0.0) <= 0 || Double.compare(getPrefHeight(), 0.0) <= 0 || Double.compare(getWidth(), 0.0) <= 0 || 120 | Double.compare(getHeight(), 0.0) <= 0) { 121 | if (getPrefWidth() > 0 && getPrefHeight() > 0) { 122 | setPrefSize(getPrefWidth(), getPrefHeight()); 123 | } else { 124 | setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT); 125 | } 126 | } 127 | 128 | getStyleClass().add("region-based"); 129 | 130 | circle = new Circle(); 131 | circle.getStyleClass().add("circle"); 132 | circle.setStrokeType(StrokeType.INSIDE); 133 | 134 | symbol = new Region(); 135 | symbol.getStyleClass().add("symbol"); 136 | 137 | getChildren().setAll(circle, symbol); 138 | } 139 | 140 | private void registerListeners() { 141 | widthProperty().addListener(o -> resize()); 142 | heightProperty().addListener(o -> resize()); 143 | addEventFilter(MouseEvent.MOUSE_PRESSED, e -> { 144 | pseudoClassStateChanged(PRESSED_PSEUDO_CLASS, true); 145 | if (null == mousePressedConsumer) { return; } 146 | mousePressedConsumer.accept(e); 147 | }); 148 | addEventFilter(MouseEvent.MOUSE_RELEASED, e -> { 149 | pseudoClassStateChanged(PRESSED_PSEUDO_CLASS, false); 150 | if (null == mouseReleasedConsumer) { return; } 151 | mouseReleasedConsumer.accept(e); 152 | }); 153 | } 154 | 155 | 156 | // ******************** Methods ******************************************* 157 | @Override protected double computeMinWidth(final double height) { return MINIMUM_WIDTH; } 158 | @Override protected double computeMinHeight(final double width) { return MINIMUM_HEIGHT; } 159 | @Override protected double computePrefWidth(final double height) { return super.computePrefWidth(height); } 160 | @Override protected double computePrefHeight(final double width) { return super.computePrefHeight(width); } 161 | @Override protected double computeMaxWidth(final double height) { return MAXIMUM_WIDTH; } 162 | @Override protected double computeMaxHeight(final double width) { return MAXIMUM_HEIGHT; } 163 | 164 | @Override public ObservableList getChildren() { return super.getChildren(); } 165 | 166 | public Type getType() { return type.get(); } 167 | public void setType(final Type type) { this.type.set(type); } 168 | public ObjectProperty typeProperty() { return type; } 169 | 170 | public boolean isHovered() { return hovered.get(); } 171 | public void setHovered(final boolean hovered) { this.hovered.set(hovered); } 172 | public BooleanProperty hoveredProperty() { return hovered; } 173 | 174 | public boolean getState() { return state.get(); } 175 | public void setState(final boolean state) { this.state.set(state); } 176 | public BooleanProperty stateProperty() { return state; } 177 | 178 | public void setOnMousePressed(final Consumer mousePressedConsumer) { this.mousePressedConsumer = mousePressedConsumer; } 179 | public void setOnMouseReleased(final Consumer mouseReleasedConsumer) { this.mouseReleasedConsumer = mouseReleasedConsumer; } 180 | 181 | 182 | // ******************** Layout ******************************************** 183 | private void resize() { 184 | width = getWidth() - getInsets().getLeft() - getInsets().getRight(); 185 | height = getHeight() - getInsets().getTop() - getInsets().getBottom(); 186 | size = width < height ? width : height; 187 | 188 | 189 | if (width > 0 && height > 0) { 190 | setMaxSize(size, size); 191 | setPrefSize(size, size); 192 | 193 | double center = size * 0.5; 194 | circle.setRadius(center); 195 | circle.setCenterX(center); 196 | circle.setCenterY(center); 197 | 198 | symbol.setPrefSize(size, size); 199 | } 200 | } 201 | 202 | @Override public String getUserAgentStylesheet() { 203 | if (null == userAgentStyleSheet) { userAgentStyleSheet = RegionControl.class.getResource("region-based.css").toExternalForm(); } 204 | return userAgentStyleSheet; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/regionbased/buttons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/restyled/DemoRestyled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.restyled; 18 | 19 | import javafx.application.Application; 20 | import javafx.application.Platform; 21 | import javafx.geometry.Insets; 22 | import javafx.scene.Scene; 23 | import javafx.scene.control.CheckBox; 24 | import javafx.scene.layout.VBox; 25 | import javafx.stage.Stage; 26 | 27 | 28 | public class DemoRestyled extends Application { 29 | private CheckBox control0; 30 | private Switch control1; 31 | 32 | 33 | @Override public void init() { 34 | control0 = new CheckBox("Material Design"); 35 | control1 = new Switch("Material Design"); 36 | } 37 | 38 | @Override public void start(final Stage stage) { 39 | VBox pane = new VBox(24, control0, control1); 40 | pane.setPadding(new Insets(20)); 41 | 42 | Scene scene = new Scene(pane); 43 | scene.getStylesheets().add(DemoRestyled.class.getResource("restyled.css").toExternalForm()); 44 | 45 | stage.setTitle("Restyled Control"); 46 | stage.setScene(scene); 47 | stage.show(); 48 | } 49 | 50 | @Override public void stop() { 51 | Platform.exit(); 52 | System.exit(0); 53 | } 54 | 55 | public static void main(String[] args) { 56 | launch(args); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/restyled/LauncherRestyled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.restyled; 18 | 19 | public class LauncherRestyled { 20 | public static void main(String[] args) { DemoRestyled.main(args); } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/restyled/Switch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.restyled; 18 | 19 | import javafx.scene.control.CheckBox; 20 | 21 | 22 | public class Switch extends CheckBox { 23 | 24 | // ******************** Constructors ************************************** 25 | public Switch() { 26 | this(""); 27 | } 28 | public Switch(final String text) { 29 | super(text); 30 | getStyleClass().setAll("switch"); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/eu/hansolo/fx/customcontrols/tools/Helper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eu.hansolo.fx.customcontrols.tools; 18 | 19 | public class Helper { 20 | public static final double clamp(final double min, final double max, final double value) { 21 | if (value < min) { return min; } 22 | if (value > max) { return max; } 23 | return value; 24 | } 25 | 26 | public static final long clamp(final long min, final long max, final long value) { 27 | if (value < min) { return min; } 28 | if (value > max) { return max; } 29 | return value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module eu.hansolo.fx.customcontrols { 2 | // Java 3 | requires java.base; 4 | 5 | // Java-FX 6 | requires transitive javafx.base; 7 | requires transitive javafx.graphics; 8 | requires transitive javafx.controls; 9 | 10 | // Exports 11 | exports eu.hansolo.fx.customcontrols.restyled; 12 | exports eu.hansolo.fx.customcontrols.combined; 13 | exports eu.hansolo.fx.customcontrols.extended; 14 | exports eu.hansolo.fx.customcontrols.controlskinbased; 15 | exports eu.hansolo.fx.customcontrols.regionbased; 16 | exports eu.hansolo.fx.customcontrols.canvasbased; 17 | exports eu.hansolo.fx.customcontrols.tools; 18 | } -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/canvasbased/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HanSolo/JavaFXCustomControls/eea8220d6d5bec477fb4176434562e9cc6a5545f/src/main/resources/eu/hansolo/fx/customcontrols/canvasbased/bubble.png -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/canvasbased/canvas-based.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .canvas-control { 18 | 19 | } -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/canvasbased/duke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HanSolo/JavaFXCustomControls/eea8220d6d5bec477fb4176434562e9cc6a5545f/src/main/resources/eu/hansolo/fx/customcontrols/canvasbased/duke.png -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/canvasbased/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HanSolo/JavaFXCustomControls/eea8220d6d5bec477fb4176434562e9cc6a5545f/src/main/resources/eu/hansolo/fx/customcontrols/canvasbased/heart.png -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/combined/combined.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .combined-control { 18 | 19 | } 20 | .combined-control:focused { 21 | -fx-highlight-fill : -fx-accent; 22 | -fx-background-color : -fx-focus-color, -fx-control-inner-background, -fx-faint-focus-color; 23 | -fx-background-insets: -1.2, 1, -2.4; 24 | -fx-background-radius: 3, 2, 4, 0; 25 | -fx-border-color : -fx-faint-focus-color; 26 | -fx-border-insets : -1; 27 | } 28 | .combined-control:focused > .button { 29 | -fx-background-color : -fx-focus-color, -fx-outer-border, -fx-inner-border, -fx-body-color, -fx-faint-focus-color, -fx-body-color; 30 | -fx-background-insets: -0.2 -0.2 -0.2 1, 1 1 1 0, 1 1 1 1, 2, -1.4 -1.4 -1.4 1, 2.6; 31 | -fx-background-radius: 0 3 3 0, 0 2 2 0, 0 1 1 0, 0 4 4 0, 0 1 1 0; 32 | } 33 | 34 | .combined-control > .text-input, 35 | .combined-control > .text-input:focused { 36 | -fx-background-color : linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border), 37 | linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background); 38 | -fx-background-insets : 0, 1 0 1 1; 39 | -fx-background-radius : 3 0 0 3, 2 0 0 2; 40 | -fx-pref-width : 120px; 41 | } 42 | 43 | .combined-control > .button { 44 | -fx-background-radius: 0 3 3 0, 0 3 3 0, 0 2 2 0, 0 1 1 0; 45 | -fx-pref-width : 36px; 46 | -fx-min-width : 36px; 47 | } 48 | .combined-control > .button:focused { 49 | -fx-background-color : -fx-outer-border, -fx-inner-border, -fx-body-color, -fx-body-color; 50 | -fx-background-insets: 0, 1, 2, 2; 51 | } 52 | -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/controlskinbased/custom-control.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .custom-control { 18 | -color: red; 19 | } 20 | 21 | .custom-control .frame { 22 | -fx-background-color : linear-gradient(from 14% 14% to 84% 84%, 23 | rgba(20, 20, 20, 0.64706) 0%, 24 | rgba(20, 20, 20, 0.64706) 15%, 25 | rgba(41, 41, 41, 0.64706) 26%, 26 | rgba(200, 200, 200, 0.40631) 85%, 27 | rgba(200, 200, 200, 0.3451) 100%); 28 | -fx-background-radius: 1024px; 29 | } 30 | .custom-control .main { 31 | -fx-background-color : linear-gradient(from 15% 15% to 83% 83%, 32 | derive(-color, -80%) 0%, 33 | derive(-color, -87%) 49%, 34 | derive(-color, -80%) 100%); 35 | -fx-background-radius: 1024px; 36 | } 37 | .custom-control:on .main { 38 | -fx-background-color: linear-gradient(from 15% 15% to 83% 83%, 39 | derive(-color, -23%) 0%, 40 | derive(-color, -50%) 49%, 41 | -color 100%); 42 | } 43 | .custom-control .highlight { 44 | -fx-background-color : radial-gradient(center 15% 15%, radius 50%, white 0%, transparent 100%); 45 | -fx-background-radius: 1024; 46 | } -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/controlskinbased/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .custom-control { 18 | -color: magenta; 19 | } -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/controlskinbased/switch.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .custom-control { 18 | -color: #4bd865; 19 | } 20 | 21 | .custom-control .switch-background { 22 | -fx-pref-width : 76; 23 | -fx-pref-height : 46; 24 | -fx-min-width : 76; 25 | -fx-min-height : 46; 26 | -fx-max-width : 76; 27 | -fx-max-height : 46; 28 | -fx-background-radius: 1024; 29 | -fx-background-color : #a3a4a6; 30 | } 31 | .custom-control:on .switch-background { 32 | -fx-background-radius: 1024; 33 | -fx-background-color : -color; 34 | } 35 | .custom-control .thumb { 36 | -fx-translate-x : 2; 37 | -fx-translate-y : 2; 38 | -fx-pref-width : 42; 39 | -fx-pref-height : 42; 40 | -fx-min-width : 42; 41 | -fx-min-height : 42; 42 | -fx-max-width : 42; 43 | -fx-max-height : 42; 44 | -fx-background-radius: 1024; 45 | -fx-background-color : white; 46 | -fx-effect : dropshadow(two-pass-box, rgba(0, 0, 0, 0.3), 1, 0.0, 0, 1); 47 | } -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/extended/extended.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .material-field { 18 | -material-design-color : #3f51b5; 19 | -material-design-color-transparent: #3f51b51f; 20 | -prompt-text-color : #757575; 21 | } 22 | .material-field:readonly { 23 | -fx-prompt-text-fill: transparent; 24 | } 25 | .material-field:disabled { 26 | -fx-prompt-text-fill: transparent; 27 | } 28 | 29 | .text-input { 30 | -fx-font-family : "Arial"; 31 | -fx-font-size : 13px; 32 | -fx-text-fill : -fx-text-inner-color; 33 | -fx-highlight-fill : derive(-fx-control-inner-background,-20%); 34 | -fx-highlight-text-fill: -fx-text-inner-color; 35 | -fx-prompt-text-fill : transparent; 36 | -fx-background-color : transparent; 37 | -fx-background-insets : 0; 38 | -fx-background-radius : 0; 39 | -fx-border-color : transparent transparent #616161 transparent; 40 | -fx-border-width : 1; 41 | -fx-border-insets : 0 0 1 0; 42 | -fx-border-style : hidden hidden solid hidden; 43 | -fx-cursor : text; 44 | -fx-padding : 0.166667em 0em 0.333333em 0em; 45 | } 46 | .text-input:focused { 47 | -fx-highlight-fill : -material-design-color-transparent; 48 | -fx-highlight-text-fill: -material-design-color; 49 | -fx-text-fill : -fx-text-inner-color; 50 | -fx-background-color : transparent; 51 | -fx-border-color : transparent transparent -material-design-color transparent; 52 | -fx-border-width : 2; 53 | -fx-border-insets : 0 0 2 -1; 54 | -fx-prompt-text-fill : transparent; 55 | -fx-padding : 2 0 4 0; 56 | } 57 | .text-input:readonly { 58 | -fx-background-color: transparent; 59 | -fx-text-fill : derive(-fx-text-base-color, 35%); 60 | -fx-border-style : segments(2, 3) line-cap butt; 61 | -fx-border-color : transparent transparent #616161 transparent; 62 | } 63 | .text-input:focused:readonly { 64 | -fx-text-fill : derive(-fx-text-base-color, 35%); 65 | -fx-border-style: segments(2, 3) line-cap butt; 66 | -fx-border-color: transparent transparent -material-design-color transparent; 67 | } 68 | .text-input:disabled { 69 | -fx-opacity : 0.46; 70 | -fx-border-style: segments(2, 3) line-cap butt; 71 | -fx-border-color: transparent transparent black transparent; 72 | } 73 | -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/regionbased/region-based.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .region-based { 18 | -RED : #ff6058; 19 | -YELLOW : #ffbc35; 20 | -GREEN : #00c844; 21 | -GRAY : #535353; 22 | -DARK_GRAY: #343535; 23 | } 24 | 25 | .region-based .circle { 26 | -fx-stroke-width: 0.5px; 27 | } 28 | 29 | .region-based:close .circle, 30 | .region-based:close:hovered .circle { 31 | -fx-fill : -RED; 32 | -fx-stroke: derive(-RED, -10%); 33 | } 34 | .region-based:close:pressed .circle { 35 | -fx-fill : derive(-RED, -20%); 36 | -fx-stroke: derive(-RED, -30%); 37 | } 38 | 39 | .region-based:minimize .circle, 40 | .region-based:minimize:hovered .circle { 41 | -fx-fill : -YELLOW; 42 | -fx-stroke: derive(-YELLOW, -10%); 43 | } 44 | .region-based:minimize:pressed .circle { 45 | -fx-fill : derive(-YELLOW, -20%); 46 | -fx-stroke: derive(-YELLOW, -30%); 47 | } 48 | 49 | .region-based:zoom .circle, 50 | .region-based:zoom:hovered .circle { 51 | -fx-fill : -GREEN; 52 | -fx-stroke: derive(-GREEN, -10%); 53 | } 54 | .region-based:zoom:pressed .circle { 55 | -fx-fill : derive(-GREEN, -20%); 56 | -fx-stroke: derive(-GREEN, -30%); 57 | } 58 | 59 | .region-based:disabled:close .circle, 60 | .region-based:disabled:minimize .circle, 61 | .region-based:disabled:zoom .circle { 62 | -fx-fill : -GRAY; 63 | -fx-stroke: transparent; 64 | } 65 | 66 | .region-based:close .symbol, 67 | .region-based:minimize .symbol, 68 | .region-based:zoom .symbol { 69 | -fx-background-color: transparent; 70 | } 71 | 72 | .region-based:hovered:close .symbol { 73 | -fx-background-color: -DARK_GRAY; 74 | -fx-border-color : -DARK_GRAY; 75 | -fx-scale-shape : false; 76 | -fx-shape : "M6.001,5.429l2.554,-2.555l0.571,0.571l-2.555,2.554l2.55,2.55l-0.572,0.572l-2.55,-2.55l-2.554,2.555l-0.571,-0.571l2.555,-2.554l-2.55,-2.55l0.572,-0.572l2.55,2.55Z"; 77 | } 78 | .region-based:hovered:minimize .symbol { 79 | -fx-background-color: -DARK_GRAY; 80 | -fx-scale-shape : false; 81 | -fx-shape : "M2.0,5.5l8,0l0,1l-8,0l0,-1Z"; 82 | } 83 | .region-based:hovered:zoom .symbol { 84 | -fx-background-color: -DARK_GRAY; 85 | -fx-scale-shape : false; 86 | -fx-shape : "M2.696,2.582l4.545,0.656l-3.889,3.889l-0.656,-4.545ZM9.533,9.418l-0.656,-4.545l-3.889,3.889l4.545,0.656Z"; 87 | } 88 | .region-based:hovered:zoom:state .symbol { 89 | -fx-background-color: -DARK_GRAY; 90 | -fx-scale-shape : false; 91 | -fx-shape : "M6.225,6.111L10.77,6.767L6.881,10.656L6.225,6.111ZM6.004,5.889L5.348,1.344L1.459,5.233L6.004,5.889Z"; 92 | } -------------------------------------------------------------------------------- /src/main/resources/eu/hansolo/fx/customcontrols/restyled/restyled.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 by Gerrit Grunwald 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .check-box { 18 | -material-design-color : #3f51b5; 19 | -material-design-color-transparent-12: #3f51b51f; 20 | -material-design-color-transparent-24: #3f51b53e; 21 | -material-design-color-transparent-40: #3f51b566; 22 | 23 | -fx-font-family : "Arial"; /* Roboto Regular */ 24 | -fx-font-size : 13px; 25 | -fx-label-padding: 0em 0em 0em 1.1em; 26 | -fx-text-fill : -fx-text-background-color; 27 | } 28 | .check-box > .box { 29 | -fx-background-color : transparent; 30 | -fx-background-insets : 0; 31 | -fx-border-color : #0000008a; 32 | -fx-border-width : 2px; 33 | -fx-border-radius : 2px; 34 | -fx-padding : 0.083333em; /* 1px */ 35 | -fx-text-fill : -fx-text-base-color; 36 | -fx-alignment : CENTER; 37 | -fx-content-display : LEFT; 38 | } 39 | .check-box:hover > .box { 40 | -fx-background-color : #61616110, transparent; 41 | -fx-background-insets : -14, 0; 42 | -fx-background-radius : 1024; 43 | -fx-cursor : hand; 44 | } 45 | .check-box:focused > .box { 46 | -fx-background-color : #6161613e, transparent; 47 | -fx-background-insets : -14, 0; 48 | -fx-background-radius : 1024; 49 | } 50 | .check-box:pressed > .box { 51 | -fx-background-color : -material-design-color-transparent-12, transparent; 52 | -fx-background-insets : -14, 0; 53 | -fx-background-radius : 1024; 54 | } 55 | .check-box:selected > .box { 56 | -fx-background-color : -material-design-color; 57 | -fx-background-radius : 2px; 58 | -fx-background-insets : 0; 59 | -fx-border-color : transparent; 60 | } 61 | .check-box:selected:hover > .box { 62 | -fx-background-color : -material-design-color-transparent-12, -material-design-color; 63 | -fx-background-insets : -14, 0; 64 | -fx-background-radius : 1024, 2px; 65 | -fx-border-color : transparent; 66 | -fx-cursor : hand; 67 | } 68 | .check-box:selected:focused > .box { 69 | -fx-background-color : -material-design-color-transparent-24, -material-design-color; 70 | -fx-background-insets : -14, 0; 71 | -fx-background-radius : 1024, 2px; 72 | -fx-border-color : transparent; 73 | } 74 | .check-box:disabled { 75 | -fx-opacity: 0.46; 76 | } 77 | .check-box > .box > .mark { 78 | -fx-background-color: null; 79 | -fx-padding : 0.45em; 80 | -fx-scale-x : 1.1; 81 | -fx-scale-y : 0.8; 82 | -fx-shape : "M9.998,13.946L22.473,1.457L26.035,5.016L10.012,21.055L0.618,11.688L4.178,8.127L9.998,13.946Z"; 83 | } 84 | .check-box:indeterminate:hover > .box { 85 | cursor:hand; 86 | } 87 | .check-box:indeterminate > .box { 88 | -fx-background-color : -material-design-color-transparent-40; 89 | -fx-background-radius : 2px; 90 | -fx-background-insets : 0; 91 | -fx-border-color : transparent; 92 | } 93 | .check-box:indeterminate > .box > .mark { 94 | -fx-background-color: rgba(255, 255, 255, 0.87); 95 | -fx-shape : "M0,0H10V2H0Z"; 96 | /* 97 | -fx-scale-shape: false; 98 | -fx-padding : 0.666667em; 99 | */ 100 | } 101 | .check-box:selected > .box > .mark { 102 | -fx-background-color : rgba(255, 255, 255, 0.87); 103 | -fx-background-insets: 0; 104 | } 105 | 106 | .switch { 107 | -material-design-color : #3f51b5; 108 | -material-design-color-transparent-12: #3f51b51f; 109 | -material-design-color-transparent-24: #3f51b53e; 110 | -material-design-color-transparent-40: #3f51b566; 111 | 112 | -fx-font-family : "Arial"; 113 | -fx-font-size : 13.0px; 114 | -fx-label-padding: 0em 0em 0em 1.1em; 115 | -fx-text-fill : -fx-text-background-color; 116 | } 117 | .switch > *.box { 118 | -fx-background-color : #00000066; 119 | -fx-pref-height : 20; 120 | -fx-pref-width : 40; 121 | -fx-background-radius: 1024px; 122 | -fx-background-insets: 2.5; 123 | -fx-padding : 0; 124 | } 125 | .switch:selected > *.box { 126 | -fx-background-color: -material-design-color-transparent-40; 127 | } 128 | .switch:disabled > *.box { 129 | -fx-background-color: #0000001f; 130 | } 131 | .switch > *.box > *.mark { 132 | -fx-background-color : fafafa; 133 | -fx-padding : 0; 134 | -fx-background-insets: 0 10 0 10; 135 | -fx-background-radius: 1024px; 136 | -fx-translate-x : -8px; 137 | -fx-effect : dropshadow(gaussian, rgba(0, 0, 0, 0.3), 4.0, 0.5, 0.0, 1); 138 | } 139 | .switch:hover > *.box > *.mark { 140 | -fx-background-color : #61616110, white; 141 | -fx-background-insets: -14 -4 -14 -4, 0 10 0 10; 142 | -fx-background-radius: 1024px, 1024px; 143 | -fx-effect : dropshadow(gaussian, rgba(0, 0, 0, 0.3), 4.0, 0.2, 0.0, 1); 144 | } 145 | .switch:selected:hover > *.box > *.mark { 146 | -fx-background-color : -material-design-color-transparent-12, -material-design-color; 147 | -fx-background-insets: -14 -4 -14 -4, 0 10 0 10; 148 | -fx-background-radius: 1024px, 1024px; 149 | -fx-effect : dropshadow(gaussian, rgba(0, 0, 0, 0.3), 4.0, 0.2, 0.0, 1); 150 | } 151 | .switch:selected > *.box > *.mark { 152 | -fx-background-color : -material-design-color; 153 | -fx-background-insets: 0 10 0 10; 154 | -fx-background-radius: 1024px; 155 | -fx-translate-x : 8px; 156 | } 157 | .switch:focused > *.box > *.mark { 158 | -fx-background-color : #6161613e, white; 159 | -fx-background-insets: -14 -4 -14 -4, 0 10 0 10; 160 | -fx-background-radius: 1024px, 1024px; 161 | -fx-effect : dropshadow(gaussian, rgba(0, 0, 0, 0.3), 4.0, 0.2, 0.0, 1); 162 | } 163 | .switch:selected:focused > *.box > *.mark { 164 | -fx-background-color : -material-design-color-transparent-24, -material-design-color; 165 | -fx-background-insets: -14 -4 -14 -4, 0 10 0 10; 166 | -fx-background-radius: 1024px, 1024px; 167 | -fx-effect : dropshadow(gaussian, rgba(0, 0, 0, 0.3), 4.0, 0.2, 0.0, 1); 168 | } 169 | .switch:indeterminate > *.text { 170 | -fx-fill: -required-text-color; 171 | } 172 | .switch:indeterminate > *.box > *.mark { 173 | -fx-background-color : transparent; 174 | -fx-border-color : -material-design-color; 175 | -fx-border-radius : 1024px; 176 | -fx-border-insets : 0 10 0 10; 177 | -fx-border-width : 3; 178 | -fx-translate-x : 0; 179 | } 180 | .switch:disabled > *.box > *.mark { 181 | -fx-background-color: bdbdbd; 182 | } 183 | .switch:disabled { 184 | -fx-opacity: 1; 185 | } 186 | --------------------------------------------------------------------------------