Compare commits

...

No commits in common. "gh-pages" and "master" have entirely different histories.

1845 changed files with 54020 additions and 14632 deletions

2
.gitattributes vendored Executable file

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

17
.github/actions/prepare-java/action.yml vendored Normal file

@ -0,0 +1,17 @@
name: "Prepare Java"
description: "Install Java and cache Maven dependencies"
runs:
using: "composite"
steps:
- name: Build | Setup OpenJDK
uses: actions/setup-java@v1
with:
java-version: 17
- name: Build | Cache Maven dependencies
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2

17
.github/workflows/documentation.yml vendored Normal file

@ -0,0 +1,17 @@
name: docs
on:
push:
paths:
- docs/**
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.x
- run: |
pip install mkdocs-material==8.5.11 mkdocs-redirects
cd docs
mkdocs gh-deploy --force

83
.github/workflows/tagged-release.yml vendored Normal file

@ -0,0 +1,83 @@
name: Tagged Release
on:
push:
tags:
- 'v*'
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest ]
runs-on: ${{ matrix.os }}
continue-on-error: true
steps:
- name: Prepare | Checkout
uses: actions/checkout@v2
- name: Prepare | Java
uses: ./.github/actions/prepare-java
- name: Extract release notes
id: extract-release-notes
uses: ffurrer2/extract-release-notes@v1
with:
release_notes_file: RELEASE_NOTES.md
- name: Build | Package styles
run: mvn clean install -B --file styles/pom.xml
- name: Build | Package other modules (Unix)
# some tests won't start without X11
run: xvfb-run mvn clean install -B -pl '!styles' --file pom.xml
if: matrix.os == 'ubuntu-latest'
- name: Build | Package other modules (Windows)
run: mvn clean install -B -pl '!styles' --file pom.xml
if: matrix.os == 'windows-latest'
- name: Build | List artifacts (Unix)
shell: sh
run: ls -l ./sampler/target/release
if: matrix.os == 'ubuntu-latest'
- name: Build | List artifacts (Windows)
shell: pwsh
run: ls sampler\target\release
if: matrix.os == 'windows-latest'
- name: Build | Upload binaries
uses: actions/upload-artifact@v2
with:
name: binaries
path: ./sampler/target/release/*
retention-days: 1
- name: Build | Upload resources
uses: actions/upload-artifact@v2
with:
name: resources
path: |
./styles/**/*-themes.zip
RELEASE_NOTES.md
retention-days: 1
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Release | Download files
uses: actions/download-artifact@v2
- name: Release | List content
run: ls -R
- name: Release | Publish to Github
uses: softprops/action-gh-release@v1
with:
files: |
binaries/*
resources/**/*-themes.zip
body_path: resources/RELEASE_NOTES.md

382
.gitignore vendored Executable file

@ -0,0 +1,382 @@
# Conveyor
output
signed.conveyor.conf
self-signed.conveyor.conf
ms-store-creds.conf
# Created by https://www.toptal.com/developers/gitignore/api/maven,linux,eclipse,windows,intellij+all,grunt,node
# Edit at https://www.toptal.com/developers/gitignore?templates=maven,linux,eclipse,windows,intellij+all,grunt,node
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### Eclipse Patch ###
# Spring Boot Tooling
.sts4-cache/
### grunt ###
# Grunt usually compiles files inside this directory
dist/
# Grunt usually preprocesses files such as coffeescript, compass... inside the .tmp directory
.tmp/
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
# Eclipse m2e generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/maven,linux,eclipse,windows,intellij+all,grunt,node
/node
/node_modules
_site/
.sass-cache/
.jekyll-cache/
.jekyll-metadata
# ignore folders generated by Bundler
/docs/.bundle/
/docs/vendor/

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file

@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 369 KiB

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

438
404.html

@ -1,438 +0,0 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="/assets/atlantafx.png">
<meta name="generator" content="mkdocs-1.4.3, mkdocs-material-8.5.10">
<title>AtlantaFX</title>
<link rel="stylesheet" href="/assets/stylesheets/main.975780f9.min.css">
<link rel="stylesheet" href="/assets/stylesheets/palette.2505c338.min.css">
<meta name="theme-color" content="#2094f3">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("/",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="blue" data-md-color-accent="">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="/." title="AtlantaFX" class="md-header__button md-logo" aria-label="AtlantaFX" data-md-component="logo">
<img src="/assets/atlantafx.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
AtlantaFX
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/mkpaz/atlantafx" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="/." title="AtlantaFX" class="md-nav__button md-logo" aria-label="AtlantaFX" data-md-component="logo">
<img src="/assets/atlantafx.png" alt="logo">
</a>
AtlantaFX
</label>
<div class="md-nav__source">
<a href="https://github.com/mkpaz/atlantafx" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="/." class="md-nav__link">
Overview
</a>
</li>
<li class="md-nav__item">
<a href="/getting-started/" class="md-nav__link">
Getting Started
</a>
</li>
<li class="md-nav__item">
<a href="/build/" class="md-nav__link">
Build
</a>
</li>
<li class="md-nav__item">
<a href="/theming/" class="md-nav__link">
Theming
</a>
</li>
<li class="md-nav__item">
<a href="/fxml/" class="md-nav__link">
FXML
</a>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" data-md-toggle="__nav_6" type="checkbox" id="__nav_6" checked>
<label class="md-nav__link" for="__nav_6">
Reference
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" aria-label="Reference" data-md-level="1">
<label class="md-nav__title" for="__nav_6">
<span class="md-nav__icon md-icon"></span>
Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="/apidocs/" class="md-nav__link">
API
</a>
</li>
<li class="md-nav__item">
<a href="/reference/global-colors/" class="md-nav__link">
Global Colors
</a>
</li>
<li class="md-nav__item">
<a href="/reference/typography/" class="md-nav__link">
Typography
</a>
</li>
<li class="md-nav__item">
<a href="/reference/controls/" class="md-nav__link">
Controls
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1>404 - Not found</h1>
</article>
</div>
</div>
<a href="#" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12Z"/></svg>
Back to top
</a>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "/", "features": ["navigation.sections", "navigation.expand", "navigation.path", "navigation.top"], "search": "/assets/javascripts/workers/search.16e2a7d4.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version.title": "Select version"}}</script>
<script src="/assets/javascripts/bundle.5a2dcb6a.min.js"></script>
</body>
</html>

135
CHANGELOG.md Normal file

@ -0,0 +1,135 @@
# Changelog
## [Unreleased]
## [2.0.1] - 2023-06-18
### Fixed
- (Base) Incorrect Notification text width.
- (CSS) Increased overlay contrast of ModalPane.
- (Sampler) SceneBuilder installer classpath #42 (thanks to **ennerf**).
- (Sampler) Music player can't load demo files from jrt.
- (Sampler) Contrast checker bg color.
## [2.0.0] - 2023-06-02
### Breaking changes
- The `InlineDatePicker` control was renamed to `Calendar`.
### Added
- (Base) 🚀 [BBCode](https://ru.wikipedia.org/wiki/BBCode) markup support.
- (Base) 🚀 `DeckPane` layout with support for swipe and slide transitions.
- (Base) 🚀 `MaskTextField` (and `MaskTextFormatter`) control to support masked text input.
- (Base) 🚀 `Message` control for displaying banners or alerts.
- (Base) 🚀 `ModalPane` and `ModalBox` controls to display modal dialogs on the top of the current scene.
- (Base) 🚀 `Notification` control for displaying notifications.
- (Base) 🚀 The `Card` and `Tile` controls, which are both versatile containers that can be used in various contexts.
- (Base) All themes are now additionally available in the BSS format.
- (Base) Animations library.
- (Base) `InputGroup` layout to simplify creating, well, input groups.
- (Base) `PasswordTextField` control to simplify `PasswordTextFormatter` usage.
- (Base) `ToggleGroup` support for the `ToggleSwitch`
- (Base) `ToggleSwitch` property to control the label position (left or right).
- (Base) New utility methods in `Styles` class.
- (CSS) 🚀 MacOS-like Cupertino theme in light and dark variants.
- (CSS) 🚀 [Dracula](https://ui.draculatheme.com/) theme.
- (CSS) Classic `TabPane` style. There are three styles supported: default, floating and classic.
- (CSS) Regular outlined buttons. There was only colored option before.
- (CSS) `.no-header` tweak support for the `TableView` and `TreeTableView`.
- (CSS) `.edge-to-edge` tweak support for the `TextInput` and `Calendar`.
- (CSS) Intent pseudo-classes (`success`, `danger`) support for the `ToggleSwitch`.
- (CSS) An utility CSS classes for setting background colors.
- (CSS) Distinctive background color for the readonly text input state.
- (CSS) Breadcrumbs support for the `Toolbar`.
- (CSS) `Button` shadow effect support (`-color-button-shadow`). Only for themes compiled with the `button.$use-shadow` flag enabled.
- (Sampler) 🚀 The Sampler app is completely rewritten to give it a more modern look and feel.
- (Sampler) 🚀 SceneBuilder integration. AtlantaFX themes can be installed (or updated, or uninstalled) directly from the Sampler app.
### Improved
- (Build) JavaFX version bump to 20 (March 2023).
- (Base) A proper [Javadoc](https://mkpaz.github.io/atlantafx/apidocs/atlantafx.base/module-summary.html) for all controls.
- (Base) All controls are now more FXML-friendly.
- (CSS) Looked-up color variables for `Separator` and the selected `TabPane` tab..
- (CSS) Border radius and shadow effect to popup menu for `ComboBox` and all `ComboBox`-based controls.
- (CSS) `TextFieldTableCell` is highlighted when in the editable state thanks to the new `:focus-within` state support.
- (CSS) Icon buttons are now use `-fx-content-display: graphic-only` as the default.
- (CSS) Better `TreeView` alt icon. It's chevron character instead of `+/-`.
- (CSS) Better toolbar buttons styling.
- (CSS) Baseline-left is the default alignment for virtualized controls, because center-left sometimes lags on scrolling in large tables.
### Fixed
- (Base) Incorrect `Slider` progress track length calculation.
- (Base) NPE when the Popover owner is not added to the scene.
- (CSS) `Popover` arrow background color.
- (CSS) `ListView` with `.bordered` class displays borders on empty cells.
- (CSS) Baseline-left is now the default alignment for virtualized controls. This change was made because center-left alignment can lead to scrolling lags in large tables.
- (CSS) Tooltip inherits font properties from parent node.
- (CSS) Double-opacity in disabled `ChoiceBox`.
## [1.2.0] - 2023-02-11
This is a bugfix/maintenance release that also contains a few style improvements.
### Added
- (Build) ErrorProne plugin.
- (Build) Checkstyle plugin.
- (Build) SceneBuilder theme pack generation (#28) (thanks to **ennerf**).
- (CSS) Pseudo-classes to set the `Label` color.
- (CSS) Intent classes to set `FontIcon` color.
### Improved
- (Build) JavaFX version bump to 19 (September 2022).
- (CSS) Inner border radius to input controls (#24) (thanks to **mimoguz**).
- (CSS) Hover effect for `CheckBox` and `RadioButton`.
- (CSS) Hover effect for `TabPane` close button.
- (CSS) Increased `Menu`/`Menubar` paddings.
### Fixed
- (Base) Remove `ToggleSwitch` left padding when text is empty.
- (Base) `PasswordTextFormatter` garbled input.
- (CSS) `Tooltip` text not showing for circular buttons.
- (CSS) Prevent context menu from inheriting text input font properties.
- (CSS) Invalid text inputs borders color (#21).
- (CSS) Invalid `DatePicker` cell size.
## [1.1.0] - 2022-10-10
### Added
- (Sampler) 🚀 External themes support. Sampler can now be used to develop custom themes.
- (Sampler) 🚀 Brand new improved user interface.
- (Sampler) Widget page that aims to provide examples of some well-known components.
- (Base) `RingProgressIndicator` control. Like `ProgressIndicator`, but fully customizable and uses arc instead of fill to indicate the progress value.
- (Base) `ProgressSliderSkin` skin. A slider with color track.
- (Base) `Breadcrumbs` API to provide more control customization:
- Anything that extends `ButtonBase` can be used as `Breadcrumbs` item.
- Divider is now customizable via corresponding factory.
- (Base) `PasswordTextFormatter` utility. An alternative to the `PasswordField`, the formatter that masks or unmasks `TextField` content based on boolean property.
- (Base) Properties for setting the top and bottom node for `DatePicker`. E.g. those can be a clock widget or event list.
- (CSS) Size style support for the `TextField`, `Button`, `Slider`.
- (CSS) Rounded style support for the`TextField`, `Button`.
- (CSS) Dense style support for the `TabPane`, `TitledPane`, `Accordion`.
- (CSS) `.alt-icon` tweak support for the `TreeView`, `TitledPane`, `Accordion`.
- (CSS) Input group support for the `Label`. `Label` graphic property can be used to add arbitrary node to the input group.
- (CSS) Utility classes for muted and subtle text style.
- (CSS) Utility classes for box elevation effect: `.elevated-[1-4]`.
- (CSS) New global looked-up color variable `-color-shadow-default` for creating shadow effects.
-
### Improved
- (CSS) 🚀 Nord light and dark themes rewamp with better color contrast and improved design.
- (CSS) Refactoring and improved control design for the `Button`, `DatePicker`, `Slider`.
- (CSS) Looked-up color variables support for the `Hyperlink`, `TextField`, `TextArea`, `ProgressBar`.
- (CSS) Shadow effect for popup controls.
## [1.0.0] - 2022-09-06
Initial release.

21
LICENSE Executable file

@ -0,0 +1,21 @@
MIT License
Copyright (c) [2022] [mkpaz]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

123
README.md Normal file

@ -0,0 +1,123 @@
<h3 align="center">
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/sampler/icons/icon-rounded-64.png" alt="Logo"/><br/>
AtlantaFX
</h3>
<p align="center">
<a href="https://github.com/mkpaz/atlantafx/stargazers"><img src="https://img.shields.io/github/license/mkpaz/atlantafx?style=for-the-badge" alt="License"></a>
<a href="https://github.com/mkpaz/atlantafx/releases"><img src="https://img.shields.io/github/v/release/mkpaz/atlantafx?5&style=for-the-badge" alt="Latest Version"></a>
<a href="https://github.com/mkpaz/atlantafx/issues"><img src="https://img.shields.io/github/issues/mkpaz/atlantafx?style=for-the-badge" alt="Open Issues"></a>
<a href="https://github.com/mkpaz/atlantafx/contributors"><img src="https://img.shields.io/github/contributors/mkpaz/atlantafx?5&style=for-the-badge" alt="Contributors"></a>
</p>
<p align="center">
Modern JavaFX CSS theme collection with additional controls.
</p>
<p align="center"><b>
See the <a href="https://mkpaz.github.io/atlantafx/">docs</a> for more info.
</b></p>
<p align="center">
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/blueprints_primer-light.png" alt="blueprints"/><br/>
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/overview_primer-dark.png" alt="overview"/><br/>
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/toolbar_dracula.png" alt="page"/><br/>
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/notifications_cupertino-dark.png" alt="page"/><br/>
</p>
* Flat interface inspired by the variety of Web component frameworks.
* CSS first! It works with existing JavaFX controls.
* Two themes in both light and dark variants.
* Simple and intuitive color system based on the [GitHub Primer guidelines](https://primer.style/design/foundations/color).
* Fully customizable. Easily change global accent (brand) color or individual control via looked-up color variables.
* Written in modular [SASS](https://sass-lang.com/). No more digging in 3,500 lines of CSS code.
* [Custom themes support](https://github.com/mkpaz/atlantafx-sample-theme). Compile your own theme from existing SASS sources.
* Additional controls that essential for modern GUI development.
* Beautiful demo app:
* Preview all supported themes.
* Test every feature of each existing control and check source code directly in the app to learn how to implement it.
* Check color palette and modify theme color contrast.
* Hot reload. Play with control styles without restarting the whole app.
* Showcases to demonstrate real-world project usage.
## Try it out
Grab a **[self-updating download of the Sampler app](https://downloads.hydraulic.dev/atlantafx/sampler/download.html)** for Windows, macOS and Linux, packaged with [Conveyor](https://www.hydraulic.software).
Or download the latest build on the [releases page](https://github.com/mkpaz/atlantafx/releases).
## Getting started
**Requirements:** JavaFX 17+ (because of `data-url` support).
Maven:
```xml
<dependency>
<groupId>io.github.mkpaz</groupId>
<artifactId>atlantafx-base</artifactId>
<version>2.0.1</version>
</dependency>
```
Gradle:
```groovy
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.mkpaz:atlantafx-base:2.0.1'
}
```
Set a theme:
```java
public class Launcher extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
// find more themes in 'atlantafx.base.theme' package
Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());
Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet());
// the rest of the code ...
}
}
```
### Local Installation
If you don't want to use additional dependencies, you can download compiled CSS themes from the [GitHub Releases](https://github.com/mkpaz/atlantafx/releases). Unpack `AtlantaFX-*-themes.zip` and place it to your project's classpath.
Set CSS theme:
```java
Application.setUserAgentStylesheet(/* path to the CSS file */);
```
Or use Java property:
```text
-Djavafx.userAgentStylesheetUrl=[URL]
```
## Contributing
Contributions are always welcome! Contributing can mean many things such as participating in discussion or proposing changes. Feel free to open an issue if you've found a bug or want to raise a question, or discuss a possible feature.
Please, note that AtlantaFX is primarily CSS theme library. Controls and skins support will probably grow over time, but creating yet another control's library is not a main goal.
Here are some areas, where you can help the project:
1. Fixing or reporting bugs. Please, check [OpenJFX bug tracker](https://bugs.openjdk.org/browse/JDK-8294722?jql=project%20%3D%20JDK%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20javafx%20%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC) first if the bug you're experiencing isn't related to CSS or custom AtlantaFX control.
2. Adding or improving control samples, which helps people to learn more about existing controls and we can also test how controls look and work with different themes.
3. Adding or improving widget samples, which provides basic examples of how to implement some conventional UI components.
4. Adding or improving app showcases, which demonstrates how AtlantaFX looks in real-world that helps to find more areas for improvement.
5. Improving docs, because good docs is the face of the project.
6. Advertising the project.

@ -1,554 +0,0 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="../../../assets/atlantafx.png">
<meta name="generator" content="mkdocs-1.4.3, mkdocs-material-8.5.10">
<title>Jquery - AtlantaFX</title>
<link rel="stylesheet" href="../../../assets/stylesheets/main.975780f9.min.css">
<link rel="stylesheet" href="../../../assets/stylesheets/palette.2505c338.min.css">
<meta name="theme-color" content="#2094f3">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="blue" data-md-color-accent="">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#jquery-v360" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../../.." title="AtlantaFX" class="md-header__button md-logo" aria-label="AtlantaFX" data-md-component="logo">
<img src="../../../assets/atlantafx.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
AtlantaFX
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Jquery
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/mkpaz/atlantafx" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../.." title="AtlantaFX" class="md-nav__button md-logo" aria-label="AtlantaFX" data-md-component="logo">
<img src="../../../assets/atlantafx.png" alt="logo">
</a>
AtlantaFX
</label>
<div class="md-nav__source">
<a href="https://github.com/mkpaz/atlantafx" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../.." class="md-nav__link">
Overview
</a>
</li>
<li class="md-nav__item">
<a href="../../../getting-started/" class="md-nav__link">
Getting Started
</a>
</li>
<li class="md-nav__item">
<a href="../../../build/" class="md-nav__link">
Build
</a>
</li>
<li class="md-nav__item">
<a href="../../../theming/" class="md-nav__link">
Theming
</a>
</li>
<li class="md-nav__item">
<a href="../../../fxml/" class="md-nav__link">
FXML
</a>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" data-md-toggle="__nav_6" type="checkbox" id="__nav_6" checked>
<label class="md-nav__link" for="__nav_6">
Reference
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" aria-label="Reference" data-md-level="1">
<label class="md-nav__title" for="__nav_6">
<span class="md-nav__icon md-icon"></span>
Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../" class="md-nav__link">
API
</a>
</li>
<li class="md-nav__item">
<a href="../../../reference/global-colors/" class="md-nav__link">
Global Colors
</a>
</li>
<li class="md-nav__item">
<a href="../../../reference/typography/" class="md-nav__link">
Typography
</a>
</li>
<li class="md-nav__item">
<a href="../../../reference/controls/" class="md-nav__link">
Controls
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#jquery-v360" class="md-nav__link">
jQuery v3.6.0
</a>
<nav class="md-nav" aria-label="jQuery v3.6.0">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#jquery-license" class="md-nav__link">
jQuery License
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<a href="https://github.com/mkpaz/atlantafx/edit/master/docs/apidocs/legal/jquery.md" title="Edit this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25Z"/></svg>
</a>
<h1>Jquery</h1>
<h2 id="jquery-v360">jQuery v3.6.0</h2>
<h3 id="jquery-license">jQuery License</h3>
<div class="highlight"><pre><span></span><code>jQuery v 3.6.0
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
&quot;Software&quot;), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************
The jQuery JavaScript Library v3.6.0 also includes Sizzle.js
Sizzle.js includes the following license:
Copyright JS Foundation and other contributors, https://js.foundation/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/sizzle
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
&quot;Software&quot;), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.
*********************
</code></pre></div>
</article>
</div>
</div>
<a href="#" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12Z"/></svg>
Back to top
</a>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "../../..", "features": ["navigation.sections", "navigation.expand", "navigation.path", "navigation.top"], "search": "../../../assets/javascripts/workers/search.16e2a7d4.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version.title": "Select version"}}</script>
<script src="../../../assets/javascripts/bundle.5a2dcb6a.min.js"></script>
</body>
</html>

@ -1,531 +0,0 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="../../../assets/atlantafx.png">
<meta name="generator" content="mkdocs-1.4.3, mkdocs-material-8.5.10">
<title>jqueryUI - AtlantaFX</title>
<link rel="stylesheet" href="../../../assets/stylesheets/main.975780f9.min.css">
<link rel="stylesheet" href="../../../assets/stylesheets/palette.2505c338.min.css">
<meta name="theme-color" content="#2094f3">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="blue" data-md-color-accent="">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#jquery-ui-v1121" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../../.." title="AtlantaFX" class="md-header__button md-logo" aria-label="AtlantaFX" data-md-component="logo">
<img src="../../../assets/atlantafx.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
AtlantaFX
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
jqueryUI
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/mkpaz/atlantafx" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../.." title="AtlantaFX" class="md-nav__button md-logo" aria-label="AtlantaFX" data-md-component="logo">
<img src="../../../assets/atlantafx.png" alt="logo">
</a>
AtlantaFX
</label>
<div class="md-nav__source">
<a href="https://github.com/mkpaz/atlantafx" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../.." class="md-nav__link">
Overview
</a>
</li>
<li class="md-nav__item">
<a href="../../../getting-started/" class="md-nav__link">
Getting Started
</a>
</li>
<li class="md-nav__item">
<a href="../../../build/" class="md-nav__link">
Build
</a>
</li>
<li class="md-nav__item">
<a href="../../../theming/" class="md-nav__link">
Theming
</a>
</li>
<li class="md-nav__item">
<a href="../../../fxml/" class="md-nav__link">
FXML
</a>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" data-md-toggle="__nav_6" type="checkbox" id="__nav_6" checked>
<label class="md-nav__link" for="__nav_6">
Reference
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" aria-label="Reference" data-md-level="1">
<label class="md-nav__title" for="__nav_6">
<span class="md-nav__icon md-icon"></span>
Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../" class="md-nav__link">
API
</a>
</li>
<li class="md-nav__item">
<a href="../../../reference/global-colors/" class="md-nav__link">
Global Colors
</a>
</li>
<li class="md-nav__item">
<a href="../../../reference/typography/" class="md-nav__link">
Typography
</a>
</li>
<li class="md-nav__item">
<a href="../../../reference/controls/" class="md-nav__link">
Controls
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#jquery-ui-v1121" class="md-nav__link">
jQuery UI v1.12.1
</a>
<nav class="md-nav" aria-label="jQuery UI v1.12.1">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#jquery-ui-license" class="md-nav__link">
jQuery UI License
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<a href="https://github.com/mkpaz/atlantafx/edit/master/docs/apidocs/legal/jqueryUI.md" title="Edit this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25Z"/></svg>
</a>
<h1>jqueryUI</h1>
<h2 id="jquery-ui-v1121">jQuery UI v1.12.1</h2>
<h3 id="jquery-ui-license">jQuery UI License</h3>
<div class="highlight"><pre><span></span><code>Copyright jQuery Foundation and other contributors, https://jquery.org/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/jquery-ui
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
&quot;Software&quot;), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code contained within the demos directory.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.
</code></pre></div>
</article>
</div>
</div>
<a href="#" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12Z"/></svg>
Back to top
</a>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "../../..", "features": ["navigation.sections", "navigation.expand", "navigation.path", "navigation.top"], "search": "../../../assets/javascripts/workers/search.16e2a7d4.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version.title": "Select version"}}</script>
<script src="../../../assets/javascripts/bundle.5a2dcb6a.min.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,18 +0,0 @@
/*!
* Lunr languages, `Danish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){var e,r=f.cursor+3;if(d=f.limit,0<=r&&r<=f.limit){for(a=r;;){if(e=f.cursor,f.in_grouping(w,97,248)){f.cursor=e;break}if(f.cursor=e,e>=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d<a&&(d=a)}}function n(){var e,r;if(f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Z--0-9-",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n<p.length;n++)r?a.push(new e.Token(p[n],{position:[f,p[n].length],index:a.length})):a.push(p[n]),f+=p[n].length;l=c+1}return a},e.ja.stemmer=function(){return function(e){return e}}(),e.Pipeline.registerFunction(e.ja.stemmer,"stemmer-ja"),e.ja.wordCharacters="一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Z--0-9-",e.ja.trimmer=e.trimmerSupport.generateTrimmer(e.ja.wordCharacters),e.Pipeline.registerFunction(e.ja.trimmer,"trimmer-ja"),e.ja.stopWordFilter=e.generateStopWordFilter("これ それ あれ この その あの ここ そこ あそこ こちら どこ だれ なに なん 何 私 貴方 貴方方 我々 私達 あの人 あのかた 彼女 彼 です あります おります います は が の に を で え から まで より も どの と し それで しかし".split(" ")),e.Pipeline.registerFunction(e.ja.stopWordFilter,"stopWordFilter-ja"),e.jp=e.ja,e.Pipeline.registerFunction(e.jp.stemmer,"stemmer-jp"),e.Pipeline.registerFunction(e.jp.trimmer,"trimmer-jp"),e.Pipeline.registerFunction(e.jp.stopWordFilter,"stopWordFilter-jp")}});

@ -1 +0,0 @@
module.exports=require("./lunr.ja");

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),i=t.join("-"),r="",n=[],s=[],p=0;p<t.length;++p)"en"==t[p]?(r+="\\w",n.unshift(e.stopWordFilter),n.push(e.stemmer),s.push(e.stemmer)):(r+=e[t[p]].wordCharacters,e[t[p]].stopWordFilter&&n.unshift(e[t[p]].stopWordFilter),e[t[p]].stemmer&&(n.push(e[t[p]].stemmer),s.push(e[t[p]].stemmer)));var o=e.trimmerSupport.generateTrimmer(r);return e.Pipeline.registerFunction(o,"lunr-multi-trimmer-"+i),n.unshift(o),function(){this.pipeline.reset(),this.pipeline.add.apply(this.pipeline,n),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add.apply(this.searchPipeline,s))}}}});

File diff suppressed because one or more lines are too long

@ -1,18 +0,0 @@
/*!
* Lunr languages, `Norwegian` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a<s&&(a=s)}}function i(){var e,r,n;if(w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
!function(r,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(r.lunr)}(this,function(){return function(r){r.stemmerSupport={Among:function(r,t,i,s){if(this.toCharArray=function(r){for(var t=r.length,i=new Array(t),s=0;s<t;s++)i[s]=r.charCodeAt(s);return i},!r&&""!=r||!t&&0!=t||!i)throw"Bad Among initialisation: s:"+r+", substring_i: "+t+", result: "+i;this.s_size=r.length,this.s=this.toCharArray(r),this.substring_i=t,this.result=i,this.method=s},SnowballProgram:function(){var r;return{bra:0,ket:0,limit:0,cursor:0,limit_backward:0,setCurrent:function(t){r=t,this.cursor=0,this.limit=t.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},getCurrent:function(){var t=r;return r=null,t},in_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e>s||e<i)return this.cursor++,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e<i)return this.cursor--,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor+s)!=i.charCodeAt(s))return!1;return this.cursor+=t,!0},eq_s_b:function(t,i){if(this.cursor-this.limit_backward<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor-t+s)!=i.charCodeAt(s))return!1;return this.cursor-=t,!0},find_among:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=l;m<_.s_size;m++){if(n+l==u){f=-1;break}if(f=r.charCodeAt(n+l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=_.s_size-1-l;m>=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}});

@ -1,18 +0,0 @@
/*!
* Lunr languages, `Swedish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o<a&&(o=a)}}function t(){var e,r=w.limit_backward;if(w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}});

@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Z--0-9-",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}});

@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}});

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}});

@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 以 于 上 他 而 后 之 来 及 了 因 下 可 到 由 这 与 也 此 但 并 个 其 已 无 小 我 们 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 从 到 得 打 凡 儿 尔 该 各 给 跟 和 何 还 即 几 既 看 据 距 靠 啦 了 另 么 每 们 嘛 拿 哪 那 您 凭 且 却 让 仍 啥 如 若 使 谁 虽 随 同 所 她 哇 嗡 往 哪 些 向 沿 哟 用 于 咱 则 怎 曾 至 致 着 诸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}});

@ -1,206 +0,0 @@
/**
* export the module via AMD, CommonJS or as a browser global
* Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
*/
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory)
} else if (typeof exports === 'object') {
/**
* Node. Does not work with strict CommonJS, but
* only CommonJS-like environments that support module.exports,
* like Node.
*/
module.exports = factory()
} else {
// Browser globals (root is window)
factory()(root.lunr);
}
}(this, function () {
/**
* Just return a value to define the module export.
* This example returns an object, but the module
* can return a function as the exported value.
*/
return function(lunr) {
// TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript
// (c) 2008 Taku Kudo <taku@chasen.org>
// TinySegmenter is freely distributable under the terms of a new BSD licence.
// For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt
function TinySegmenter() {
var patterns = {
"[一二三四五六七八九十百千万億兆]":"M",
"[一-龠々〆ヵヶ]":"H",
"[ぁ-ん]":"I",
"[ァ-ヴーア-ン゙ー]":"K",
"[a-zA-Z--]":"A",
"[0-9-]":"N"
}
this.chartype_ = [];
for (var i in patterns) {
var regexp = new RegExp(i);
this.chartype_.push([regexp, patterns[i]]);
}
this.BIAS__ = -332
this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378};
this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920};
this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266};
this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352};
this.BP2__ = {"BO":60,"OO":-1762};
this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965};
this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146};
this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699};
this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973};
this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682};
this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"":-669};
this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990};
this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832};
this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649};
this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393};
this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841};
this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68};
this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591};
this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685};
this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156};
this.TW1__ = {"につい":-4681,"東京都":2026};
this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216};
this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287};
this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865};
this.UC1__ = {"A":484,"K":93,"M":645,"O":-505};
this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646};
this.UC3__ = {"A":-1370,"I":2311};
this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646};
this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831};
this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387};
this.UP1__ = {"O":-214};
this.UP2__ = {"B":69,"O":935};
this.UP3__ = {"B":189};
this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422};
this.UQ2__ = {"BH":216,"BI":113,"OK":1759};
this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212};
this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135};
this.UW2__ = {",":-829,"、":-829,"":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568};
this.UW3__ = {",":4889,"1":-800,"":-1723,"、":4889,"々":-2311,"":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278};
this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637};
this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"":-514,"":-32768,"「":363,"イ":241,"ル":451,"ン":-343};
this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"":-270,"":306,"ル":-673,"ン":-496};
return this;
}
TinySegmenter.prototype.ctype_ = function(str) {
for (var i in this.chartype_) {
if (str.match(this.chartype_[i][0])) {
return this.chartype_[i][1];
}
}
return "O";
}
TinySegmenter.prototype.ts_ = function(v) {
if (v) { return v; }
return 0;
}
TinySegmenter.prototype.segment = function(input) {
if (input == null || input == undefined || input == "") {
return [];
}
var result = [];
var seg = ["B3","B2","B1"];
var ctype = ["O","O","O"];
var o = input.split("");
for (i = 0; i < o.length; ++i) {
seg.push(o[i]);
ctype.push(this.ctype_(o[i]))
}
seg.push("E1");
seg.push("E2");
seg.push("E3");
ctype.push("O");
ctype.push("O");
ctype.push("O");
var word = seg[3];
var p1 = "U";
var p2 = "U";
var p3 = "U";
for (var i = 4; i < seg.length - 3; ++i) {
var score = this.BIAS__;
var w1 = seg[i-3];
var w2 = seg[i-2];
var w3 = seg[i-1];
var w4 = seg[i];
var w5 = seg[i+1];
var w6 = seg[i+2];
var c1 = ctype[i-3];
var c2 = ctype[i-2];
var c3 = ctype[i-1];
var c4 = ctype[i];
var c5 = ctype[i+1];
var c6 = ctype[i+2];
score += this.ts_(this.UP1__[p1]);
score += this.ts_(this.UP2__[p2]);
score += this.ts_(this.UP3__[p3]);
score += this.ts_(this.BP1__[p1 + p2]);
score += this.ts_(this.BP2__[p2 + p3]);
score += this.ts_(this.UW1__[w1]);
score += this.ts_(this.UW2__[w2]);
score += this.ts_(this.UW3__[w3]);
score += this.ts_(this.UW4__[w4]);
score += this.ts_(this.UW5__[w5]);
score += this.ts_(this.UW6__[w6]);
score += this.ts_(this.BW1__[w2 + w3]);
score += this.ts_(this.BW2__[w3 + w4]);
score += this.ts_(this.BW3__[w4 + w5]);
score += this.ts_(this.TW1__[w1 + w2 + w3]);
score += this.ts_(this.TW2__[w2 + w3 + w4]);
score += this.ts_(this.TW3__[w3 + w4 + w5]);
score += this.ts_(this.TW4__[w4 + w5 + w6]);
score += this.ts_(this.UC1__[c1]);
score += this.ts_(this.UC2__[c2]);
score += this.ts_(this.UC3__[c3]);
score += this.ts_(this.UC4__[c4]);
score += this.ts_(this.UC5__[c5]);
score += this.ts_(this.UC6__[c6]);
score += this.ts_(this.BC1__[c2 + c3]);
score += this.ts_(this.BC2__[c3 + c4]);
score += this.ts_(this.BC3__[c4 + c5]);
score += this.ts_(this.TC1__[c1 + c2 + c3]);
score += this.ts_(this.TC2__[c2 + c3 + c4]);
score += this.ts_(this.TC3__[c3 + c4 + c5]);
score += this.ts_(this.TC4__[c4 + c5 + c6]);
// score += this.ts_(this.TC5__[c4 + c5 + c6]);
score += this.ts_(this.UQ1__[p1 + c1]);
score += this.ts_(this.UQ2__[p2 + c2]);
score += this.ts_(this.UQ3__[p3 + c3]);
score += this.ts_(this.BQ1__[p2 + c2 + c3]);
score += this.ts_(this.BQ2__[p2 + c3 + c4]);
score += this.ts_(this.BQ3__[p3 + c2 + c3]);
score += this.ts_(this.BQ4__[p3 + c3 + c4]);
score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]);
score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]);
score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]);
score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]);
var p = "O";
if (score > 0) {
result.push(word);
word = "";
p = "B";
}
p1 = p2;
p2 = p3;
p3 = p;
word += seg[i];
}
result.push(word);
return result;
}
lunr.TinySegmenter = TinySegmenter;
};
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
{"version":3,"sources":["src/assets/stylesheets/palette/_scheme.scss","../../../src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"AA2BA,cAGE,6BAKE,YAAA,CAGA,mDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CACA,mDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CAGA,gDAAA,CACA,gDAAA,CAGA,4BAAA,CACA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CAGA,+CAAA,CAGA,iCAAA,CAGA,uDAAA,CACA,6DAAA,CACA,2DAAA,CAGA,yDAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,qDAAA,CACA,wDAAA,CAGA,0DAAA,CAKA,8DAAA,CAKA,0DCxDF,CD6DE,kHAEE,YC3DJ,CD+DE,gHAEE,eC7DJ,CDoFE,yDACE,4BClFJ,CDiFE,2DACE,4BC/EJ,CD8EE,gEACE,4BC5EJ,CD2EE,2DACE,4BCzEJ,CDwEE,yDACE,4BCtEJ,CDqEE,0DACE,4BCnEJ,CDkEE,gEACE,4BChEJ,CD+DE,0DACE,4BC7DJ,CD4DE,2OACE,4BCjDJ,CDwDA,+FAGE,iCCtDF,CACF,CCjDE,2BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD6CN,CCvDE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDoDN,CC9DE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD2DN,CCrEE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDkEN,CC5EE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDyEN,CCnFE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDgFN,CC1FE,kCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDuFN,CCjGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD8FN,CCxGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDqGN,CC/GE,6BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD4GN,CCtHE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDmHN,CC7HE,4BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD6HN,CCpIE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDoIN,CC3IE,6BACE,yBAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD2IN,CClJE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDkJN,CCzJE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDsJN,CE3JE,4BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwJN,CEnKE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgKN,CE3KE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwKN,CEnLE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgLN,CE3LE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwLN,CEnME,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgMN,CE3ME,mCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwMN,CEnNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgNN,CE3NE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwNN,CEnOE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgON,CE3OE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwON,CEnPE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmPN,CE3PE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2PN,CEnQE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmQN,CE3QE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2QN,CEnRE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgRN,CE3RE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwRN,CEnSE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BF4RN,CE5SE,kCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BFqSN,CEtRE,sEACE,4BFyRJ,CE1RE,+DACE,4BF6RJ,CE9RE,iEACE,4BFiSJ,CElSE,gEACE,4BFqSJ,CEtSE,iEACE,4BFySJ,CEhSA,8BACE,0BAAA,CACA,sCAAA,CACA,qCAAA,CACA,+BAAA,CACA,sCAAA,CAGA,4BFiSF,CE9RE,yCACE,+BFgSJ,CE7RI,kDAEE,0CAAA,CACA,sCAAA,CAFA,UFiSN,CG7MI,mCD1EA,+CACE,0BF0RJ,CEvRI,qDACE,0BFyRN,CEpRE,iEACE,eFsRJ,CACF,CGxNI,sCDvDA,uCACE,oCFkRJ,CACF,CEzQA,8BACE,0BAAA,CACA,sCAAA,CACA,gCAAA,CACA,0BAAA,CACA,sCAAA,CAGA,4BF0QF,CEvQE,yCACE,+BFyQJ,CEtQI,kDAEE,0CAAA,CACA,sCAAA,CAFA,UF0QN,CEnQE,yCACE,qBFqQJ,CG9NI,wCDhCA,8CACE,0BFiQJ,CACF,CGtPI,mCDJA,+CACE,0BF6PJ,CE1PI,qDACE,0BF4PN,CACF,CG3OI,wCDTA,iFACE,qBFuPJ,CACF,CGnQI,sCDmBA,uCACE,qBFmPJ,CACF","file":"palette.css"}

10
base/.mvn/jvm.config Normal file

@ -0,0 +1,10 @@
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED

85
base/pom.xml Executable file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.mkpaz</groupId>
<artifactId>atlantafx-parent</artifactId>
<version>2.0.1</version>
</parent>
<artifactId>atlantafx-base</artifactId>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<!-- copy compiled CSS to the classpath -->
<resource>
<directory>../styles/dist</directory>
<targetPath>atlantafx/base/theme</targetPath>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<!-- check code style before compilation -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- compile themes to BSS -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>compile-to-bss</id>
<phase>compile</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>atlantafx.base.theme.ThemeCompiler</mainClass>
<arguments>
<argument>${project.build.directory}/classes/atlantafx/base/theme</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,68 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package atlantafx.base.controls;
import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
/**
* Encapsulates behavior interaction logic for a skin. The main functionality
* in BehaviorBase revolves around infrastructure for resolving events into
* function calls. A BehaviorBase implementation will usually contain logic for
* handling key events based on the host platform, as well as view-specific
* functions for handling mouse and key and other input events.
*
* <p>Although BehaviorBase is typically used as a base class, it is not abstract and
* several skins instantiate an instance of BehaviorBase directly.
*
* <p>Note: This is an excerpt of the private Behavior API from the JavaFX codebase.
* It serves as a compatibility layer for implementing certain controls, although it
* can also be useful for new controls.
*/
public class BehaviorBase<C extends Control, S extends SkinBase<C>> {
private C control;
private S skin;
/**
* Constructor for all BehaviorBase instances.
*
* @param control The control for which this Skin should attach to.
* @param skin The skin used by the control.
*/
protected BehaviorBase(C control, S skin) {
this.control = control;
this.skin = skin;
}
/**
* Gets the control associated with this behavior.
*
* @return The control for this behavior.
*/
public C getControl() {
return control;
}
/**
* Gets the skin associated with this behavior.
*
* @return The control for this behavior.
*/
public S getSkin() {
return skin;
}
/**
* Called from {@link SkinBase#dispose()} to clean up the behavior state.
*/
public void dispose() {
this.control = null;
this.skin = null;
}
}

@ -0,0 +1,86 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package atlantafx.base.controls;
import java.util.function.Consumer;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
/**
* Base implementation class for defining the visual representation of user
* interface controls that need to handle platform events and therefore can take
* advantage of using the Behavior API.
*
* <p>Note: This is an excerpt of the private Behavior API from the JavaFX codebase.
* It serves as a compatibility layer for implementing certain controls, although it
* can also be useful for new controls.
*/
public abstract class BehaviorSkinBase<C extends Control, B extends BehaviorBase<C, ?>> extends SkinBase<C> {
protected B behavior;
/**
* Constructor for all BehaviorSkinBase instances.
*
* @param control The control for which this Skin should attach to.
*/
protected BehaviorSkinBase(C control) {
super(control);
behavior = createDefaultBehavior();
}
/**
* An abstract method for creating the behavior instance to be used by this skin.
*/
public abstract B createDefaultBehavior();
/**
* Gets the control associated with this skin.
*
* @return The control for this Skin.
*/
public C getControl() {
return getSkinnable();
}
/**
* Gets the behavior associated with this skin.
*
* @return The behavior for this skin.
*/
public B getBehavior() {
return behavior;
}
/**
* Unbinds all properties and removes any listeners before disposing the skin.
* There's no need to remove listeners, which has been registered using
* {@link SkinBase#registerChangeListener(ObservableValue, Consumer)} method,
* because it will be done automatically from dispose method.
*/
protected void unregisterListeners() {
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
unregisterListeners();
// unregister weak listeners and remove reference to the control
super.dispose();
// cleanup the behavior
if (behavior != null) {
behavior.dispose();
behavior = null;
}
}
}

@ -0,0 +1,363 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2014, 2020, ControlsFX
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package atlantafx.base.controls;
import java.util.UUID;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.Control;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.Region;
import javafx.util.Callback;
import org.jetbrains.annotations.Nullable;
/**
* A bread crumb bar. This control is useful to visualize and navigate
* a hierarchical path structure, such as file systems.
*
* <pre>{@code
* String[] list = {"Root", "Folder", "file.txt"};
* BreadCrumbItem<String> selectedCrumb = Breadcrumbs.buildTreeModel(list);
* Breadcrumbs<String> breadcrumbs = new Breadcrumbs<>(selectedCrumb);
* }</pre>
*
* <p>A breadcrumbs consist of two types of elements: a button (default is
* {@code Hyperlink}) and a divider (default is for {@code Label}). You can
* customize both by providing the corresponding factory.
*/
@SuppressWarnings("unused")
public class Breadcrumbs<T> extends Control {
protected static final String DEFAULT_STYLE_CLASS = "breadcrumbs";
protected final Callback<BreadCrumbItem<T>, ButtonBase> defaultCrumbNodeFactory =
item -> new Hyperlink(item.getStringValue());
protected final Callback<BreadCrumbItem<T>, ? extends Node> defaultDividerFactory =
item -> item != null && !item.isLast() ? new Label("/") : null;
/**
* Creates an empty bread crumb bar.
*/
public Breadcrumbs() {
this(null);
}
/**
* Creates a bread crumb bar with the given BreadCrumbItem as the
* currently {@link #selectedCrumbProperty()}.
*
* @param selectedCrumb The currently selected crumb.
*/
public Breadcrumbs(@Nullable BreadCrumbItem<T> selectedCrumb) {
getStyleClass().add(DEFAULT_STYLE_CLASS);
// breadcrumbs should be the size of its content
setPrefWidth(Region.USE_COMPUTED_SIZE);
setMaxWidth(Region.USE_PREF_SIZE);
setPrefHeight(Region.USE_COMPUTED_SIZE);
setMaxHeight(Region.USE_PREF_SIZE);
setSelectedCrumb(selectedCrumb);
setCrumbFactory(defaultCrumbNodeFactory);
setDividerFactory(defaultDividerFactory);
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new BreadcrumbsSkin<>(this);
}
/**
* Constructs a tree model from the flat list which then can be set
* as the {@code selectedCrumb} node to be shown.
*
* @param crumbs The flat list of values used to build the tree model
*/
@SafeVarargs
public static <T> BreadCrumbItem<T> buildTreeModel(T... crumbs) {
BreadCrumbItem<T> subRoot = null;
for (T crumb : crumbs) {
BreadCrumbItem<T> currentNode = new BreadCrumbItem<>(crumb);
if (subRoot != null) {
subRoot.getChildren().add(currentNode);
}
subRoot = currentNode;
}
return subRoot;
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the bottom-most path node (the node on the most-right side in
* terms of the bread crumb bar). The full path is then being constructed
* using getParent() of the tree-items.
*
* <p>Consider the following hierarchy:
* [Root] &gt; [Folder] &gt; [SubFolder] &gt; [file.txt]
*
* <p>To show the above bread crumb bar, you have to set the [file.txt]
* tree-node as selected crumb.
*/
public final ObjectProperty<BreadCrumbItem<T>> selectedCrumbProperty() {
return selectedCrumb;
}
protected final ObjectProperty<BreadCrumbItem<T>> selectedCrumb =
new SimpleObjectProperty<>(this, "selectedCrumb");
public final BreadCrumbItem<T> getSelectedCrumb() {
return selectedCrumb.get();
}
public final void setSelectedCrumb(BreadCrumbItem<T> selectedCrumb) {
this.selectedCrumb.set(selectedCrumb);
}
/**
* Enables or disables auto navigation (default is enabled).
* If auto navigation is enabled, it will automatically navigate to the
* crumb which was clicked by the user, meaning it will set the
* {@link #selectedCrumbProperty()} upon click.
*/
public final BooleanProperty autoNavigationEnabledProperty() {
return autoNavigation;
}
protected final BooleanProperty autoNavigation =
new SimpleBooleanProperty(this, "autoNavigationEnabled", true);
public final boolean isAutoNavigationEnabled() {
return autoNavigation.get();
}
public final void setAutoNavigationEnabled(boolean enabled) {
autoNavigation.set(enabled);
}
/**
* The crumb factory is used to create custom bread crumb instances.
* A null value is not allowed and will result in a fallback to the default factory.
* <ul>
* <li><code>BreadCrumbItem&lt;T&gt;</code> specifies the tree item for creating bread crumb.</li>
* <li><code>ButtonBase</code> stands for resulting bread crumb node.</li>
* </ul>
*
* <p>Use {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to
* create bread crumb depending on item position.
*/
public final ObjectProperty<Callback<BreadCrumbItem<T>, ButtonBase>> crumbFactoryProperty() {
return crumbFactory;
}
protected final ObjectProperty<Callback<BreadCrumbItem<T>, ButtonBase>> crumbFactory =
new SimpleObjectProperty<>(this, "crumbFactory");
public final void setCrumbFactory(Callback<BreadCrumbItem<T>, ButtonBase> value) {
if (value == null) {
value = defaultCrumbNodeFactory;
}
crumbFactoryProperty().set(value);
}
public final Callback<BreadCrumbItem<T>, ButtonBase> getCrumbFactory() {
return crumbFactory.get();
}
/**
* The divider factory is used to create custom instances of dividers.
* A null value is not allowed and will result in a fallback to the default factory.
*
* <ul>
* <li><code>BreadCrumbItem&lt;T&gt;</code> specifies the preceding tree item.
* It can be null, which allows for inserting a divider before the first bread crumb,
* such as when creating a Unix path.</li>
* <li><code>? extends Node</code> stands for resulting divider node. It can also be null,
* indicating that there will be no divider inserted after the specified bread crumb.</li>
* </ul>
*
* <p>Use {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to
* create bread crumb depending on item position.
*/
public final ObjectProperty<Callback<BreadCrumbItem<T>, ? extends Node>> dividerFactoryProperty() {
return dividerFactory;
}
protected final ObjectProperty<Callback<BreadCrumbItem<T>, ? extends Node>> dividerFactory =
new SimpleObjectProperty<>(this, "dividerFactory");
public final void setDividerFactory(Callback<BreadCrumbItem<T>, ? extends Node> value) {
if (value == null) {
value = defaultDividerFactory;
}
dividerFactoryProperty().set(value);
}
public final Callback<BreadCrumbItem<T>, ? extends Node> getDividerFactory() {
return dividerFactory.get();
}
/**
* Represents the EventHandler that is called when a user selects a bread crumb.
*/
public final ObjectProperty<EventHandler<BreadCrumbActionEvent<T>>> onCrumbActionProperty() {
return onCrumbAction;
}
protected final ObjectProperty<EventHandler<BreadCrumbActionEvent<T>>> onCrumbAction = new ObjectPropertyBase<>() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void invalidated() {
setEventHandler(BreadCrumbActionEvent.CRUMB_ACTION, (EventHandler<BreadCrumbActionEvent>) (Object) get());
}
@Override
public Object getBean() {
return Breadcrumbs.this;
}
@Override
public String getName() {
return "onCrumbAction";
}
};
public final void setOnCrumbAction(EventHandler<BreadCrumbActionEvent<T>> value) {
onCrumbActionProperty().set(value);
}
public final EventHandler<BreadCrumbActionEvent<T>> getOnCrumbAction() {
return onCrumbActionProperty().get();
}
///////////////////////////////////////////////////////////////////////////
/**
* {@code BreadCrumbItem} extends {@link TreeItem}, providing support
* for navigating hierarchical structures.
*
* @param <T> The type of the value property within BreadCrumbItem.
*/
public static class BreadCrumbItem<T> extends TreeItem<T> {
protected boolean first;
protected boolean last;
/**
* Creates a BreadCrumbItem with the value property set to the provided object.
*
* @param value The object to be stored as the value of this BreadCrumbItem.
*/
public BreadCrumbItem(@Nullable T value) {
super(value);
}
/**
* Use this method to determine if this item is at the beginning,
* so you can create bread crumbs accordingly.
*/
public boolean isFirst() {
return first;
}
/**
* Use this method to determine if this item is at the end,
* so you can create breadcrumbs accordingly.
*/
public boolean isLast() {
return last;
}
///////////////////////////////////////////////////
// package private //
///////////////////////////////////////////////////
protected void setFirst(boolean first) {
this.first = first;
}
protected void setLast(boolean last) {
this.last = last;
}
protected String getStringValue() {
return getValue() != null ? getValue().toString() : "";
}
}
/**
* An {@code Event} which is fired when a bread crumb was activated.
*/
public static class BreadCrumbActionEvent<T> extends Event {
/**
* Represents the event type that should be listened to by people who are
* interested in knowing when the selected crumb has changed. This is accomplished
* through listening to the {@link Breadcrumbs#selectedCrumbProperty() selected crumb
* property}.
*/
public static final EventType<BreadCrumbActionEvent<?>> CRUMB_ACTION
= new EventType<>("CRUMB_ACTION" + UUID.randomUUID());
private final BreadCrumbItem<T> selectedCrumb;
/**
* Creates a new event that can subsequently be fired.
*
* @param selectedCrumb The currently selected crumb.
*/
public BreadCrumbActionEvent(BreadCrumbItem<T> selectedCrumb) {
super(CRUMB_ACTION);
this.selectedCrumb = selectedCrumb;
}
public BreadCrumbItem<T> getSelectedCrumb() {
return selectedCrumb;
}
}
}

@ -0,0 +1,213 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2014, 2021, ControlsFX
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package atlantafx.base.controls;
import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.css.PseudoClass;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.SkinBase;
import javafx.scene.control.TreeItem.TreeModificationEvent;
/**
* The default skin for the {@link Breadcrumbs} control.
*/
public class BreadcrumbsSkin<T> extends SkinBase<Breadcrumbs<T>> {
protected static final PseudoClass FIRST = PseudoClass.getPseudoClass("first");
protected static final PseudoClass LAST = PseudoClass.getPseudoClass("last");
protected final EventHandler<TreeModificationEvent<Object>> treeChildrenModifiedHandler =
e -> updateBreadCrumbs();
public BreadcrumbsSkin(final Breadcrumbs<T> control) {
super(control);
control.selectedCrumbProperty().addListener(
(obs, old, val) -> updateSelectedPath(old, val)
);
updateSelectedPath(getSkinnable().selectedCrumbProperty().get(), null);
}
@Override
protected void layoutChildren(double x, double y, double width, double height) {
double controlHeight = getSkinnable().getHeight();
double nodeX = x, nodeY;
for (int i = 0; i < getChildren().size(); i++) {
Node node = getChildren().get(i);
double nodeWidth = snapSizeX(node.prefWidth(height));
double nodeHeight = snapSizeY(node.prefHeight(-1));
// center node within the breadcrumbs
nodeY = nodeHeight < controlHeight ? (controlHeight - nodeHeight) / 2 : y;
node.resizeRelocate(nodeX, nodeY, nodeWidth, nodeHeight);
nodeX += nodeWidth;
}
}
@Override
protected double computeMinWidth(double height, double topInset, double rightInset,
double bottomInset, double leftInset) {
double width = 0;
for (Node node : getChildren()) {
if (!node.isManaged()) {
continue;
}
width += snapSizeX(node.prefWidth(height));
}
return width + rightInset + leftInset;
}
protected void updateSelectedPath(BreadCrumbItem<T> old, BreadCrumbItem<T> val) {
if (old != null) {
old.removeEventHandler(BreadCrumbItem.childrenModificationEvent(), treeChildrenModifiedHandler);
}
if (val != null) {
val.addEventHandler(BreadCrumbItem.childrenModificationEvent(), treeChildrenModifiedHandler);
}
updateBreadCrumbs();
}
protected void updateBreadCrumbs() {
getChildren().clear();
BreadCrumbItem<T> selectedTreeItem = getSkinnable().getSelectedCrumb();
Node divider;
if (selectedTreeItem != null) {
// optionally insert divider before the first node
divider = createDivider(null);
if (divider != null) {
divider.pseudoClassStateChanged(FIRST, true);
divider.pseudoClassStateChanged(LAST, false);
getChildren().add(divider);
}
List<BreadCrumbItem<T>> crumbs = constructFlatPath(selectedTreeItem);
for (BreadCrumbItem<T> treeItem : crumbs) {
ButtonBase crumb = createCrumb(treeItem);
crumb.pseudoClassStateChanged(FIRST, treeItem.isFirst());
crumb.pseudoClassStateChanged(LAST, treeItem.isLast());
getChildren().add(crumb);
// for the sake of flexibility, it's user responsibility to decide
// whether insert divider after the last node or not
divider = createDivider(treeItem);
if (divider != null) {
if (treeItem.isLast()) {
divider.pseudoClassStateChanged(FIRST, false);
divider.pseudoClassStateChanged(LAST, true);
}
getChildren().add(divider);
}
}
}
}
/**
* Construct a flat list for the crumbs.
*
* @param bottomMost The crumb node at the end of the path
*/
protected List<BreadCrumbItem<T>> constructFlatPath(BreadCrumbItem<T> bottomMost) {
List<BreadCrumbItem<T>> path = new ArrayList<>();
BreadCrumbItem<T> current = bottomMost;
do {
path.add(current);
current.setFirst(false);
current.setLast(false);
current = (BreadCrumbItem<T>) current.getParent();
} while (current != null);
Collections.reverse(path);
// if the path consists of a single item it considered as first, but not last
if (path.size() > 0) {
path.get(0).setFirst(true);
}
if (path.size() > 1) {
path.get(path.size() - 1).setLast(true);
}
return path;
}
protected ButtonBase createCrumb(BreadCrumbItem<T> treeItem) {
ButtonBase crumb = getSkinnable().getCrumbFactory().call(treeItem);
crumb.setMnemonicParsing(false);
if (!crumb.getStyleClass().contains("crumb")) {
crumb.getStyleClass().add("crumb");
}
// listen to the action event of each bread crumb
crumb.setOnAction(e -> onBreadCrumbAction(treeItem));
return crumb;
}
protected Node createDivider(BreadCrumbItem<T> treeItem) {
Node divider = getSkinnable().getDividerFactory().call(treeItem);
if (divider == null) {
return null;
}
if (!divider.getStyleClass().contains("divider")) {
divider.getStyleClass().add("divider");
}
return divider;
}
/**
* Occurs when a bread crumb gets the action event.
*
* @param crumbModel The crumb which received the action event
*/
protected void onBreadCrumbAction(BreadCrumbItem<T> crumbModel) {
final Breadcrumbs<T> breadCrumbBar = getSkinnable();
// fire the composite event in the breadCrumbBar
Event.fireEvent(breadCrumbBar, new Breadcrumbs.BreadCrumbActionEvent<>(crumbModel));
// navigate to the clicked crumb
if (breadCrumbBar.isAutoNavigationEnabled()) {
breadCrumbBar.setSelectedCrumb(crumbModel);
}
}
}

@ -0,0 +1,355 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package atlantafx.base.controls;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.WritableValue;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableProperty;
import javafx.css.converter.BooleanConverter;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.Control;
import javafx.scene.control.DateCell;
import javafx.scene.control.Skin;
import javafx.util.Callback;
import org.jetbrains.annotations.Nullable;
/**
* The Calendar control allows the user to select a date. The calendar is based on either
* the standard ISO-8601 chronology or any of the other chronology classes defined in the
* <code>java.time.chrono</code> package.
*
* <ul>
* <li>The {@link #valueProperty() value} property represents the currently selected
* {@link LocalDate}. The default value is null.</li>
* <li>The {@link #chronologyProperty() chronology} property specifies a calendar system to be used
* for parsing, displaying, and choosing dates.</li>
* <li>The {@link #valueProperty() value} property is always defined in the ISO calendar system,
* however, so applications based on a different chronology may use the conversion methods
* provided in the {@link java.time.chrono.Chronology} API to get or set the corresponding
* {@link java.time.chrono.ChronoLocalDate} value.</li>
* </ul>
*/
public class Calendar extends Control {
protected LocalDate lastValidDate = null;
protected Chronology lastValidChronology = IsoChronology.INSTANCE;
/**
* Creates a default Calendar instance with a <code>null</code>
* date value set.
*/
public Calendar() {
this(null);
valueProperty().addListener(obs -> {
LocalDate date = getValue();
Chronology chrono = getChronology();
if (isValidDate(chrono, date)) {
lastValidDate = date;
} else {
System.err.println("[ERROR] Restoring value to " + (lastValidDate == null ? "null" : lastValidDate));
setValue(lastValidDate);
}
});
chronologyProperty().addListener(observable -> {
LocalDate date = getValue();
Chronology chrono = getChronology();
if (isValidDate(chrono, date)) {
lastValidChronology = chrono;
} else {
System.err.println("[ERROR] Restoring value to " + lastValidChronology);
setChronology(lastValidChronology);
}
});
}
/**
* Creates a Calendar instance and sets the {@link #valueProperty() value}
* to the specified date.
*
* @param localDate The date to be set as the currently selected date in the Calendar.
*/
public Calendar(@Nullable LocalDate localDate) {
setValue(localDate);
getStyleClass().add(DEFAULT_STYLE_CLASS);
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new CalendarSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the currently selected {@link LocalDate}. The default value is null.
*/
public ObjectProperty<LocalDate> valueProperty() {
return value;
}
private final ObjectProperty<LocalDate> value = new SimpleObjectProperty<>(this, "value");
public final LocalDate getValue() {
return valueProperty().get();
}
public final void setValue(LocalDate value) {
valueProperty().set(value);
}
/**
* A custom cell factory can be provided to customize individual day cells
* Refer to {@link DateCell} and {@link Cell} for more information on cell factories.
*/
private ObjectProperty<Callback<Calendar, DateCell>> dayCellFactory;
public final void setDayCellFactory(Callback<Calendar, DateCell> value) {
dayCellFactoryProperty().set(value);
}
public final Callback<Calendar, DateCell> getDayCellFactory() {
return (dayCellFactory != null) ? dayCellFactory.get() : null;
}
public final ObjectProperty<Callback<Calendar, DateCell>> dayCellFactoryProperty() {
if (dayCellFactory == null) {
dayCellFactory = new SimpleObjectProperty<>(this, "dayCellFactory");
}
return dayCellFactory;
}
/**
* The calendar system used for parsing, displaying, and choosing dates in the
* Calendar control.
*
* <p>The default is usually {@link IsoChronology} unless provided explicitly
* in the {@link Locale} by use of a Locale calendar extension.
*
* <p>Setting the value to <code>null</code> will restore the default chronology.
*
* @return a property representing the Chronology being used
*/
public ObjectProperty<Chronology> chronologyProperty() {
return chronology;
}
private final ObjectProperty<Chronology> chronology
= new SimpleObjectProperty<>(this, "chronology", null);
@SuppressWarnings("CatchAndPrintStackTrace")
public final Chronology getChronology() {
Chronology chrono = chronology.get();
if (chrono == null) {
try {
chrono = Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT));
} catch (Exception e) {
e.printStackTrace();
}
if (chrono == null) {
chrono = IsoChronology.INSTANCE;
}
}
return chrono;
}
public final void setChronology(Chronology value) {
chronology.setValue(value);
}
/**
* Whether the Calendar should display a column showing week numbers.
*
* <p>The default value is specified in a resource bundle, and depends on the country of the
* current locale.
*
* @return "true" if popup should display a column showing week numbers
*/
public final BooleanProperty showWeekNumbersProperty() {
if (showWeekNumbers == null) {
showWeekNumbers = new StyleableBooleanProperty(false) {
@Override
public CssMetaData<Calendar, Boolean> getCssMetaData() {
return StyleableProperties.SHOW_WEEK_NUMBERS;
}
@Override
public Object getBean() {
return Calendar.this;
}
@Override
public String getName() {
return "showWeekNumbers";
}
};
}
return showWeekNumbers;
}
private BooleanProperty showWeekNumbers;
public final void setShowWeekNumbers(boolean value) {
showWeekNumbersProperty().setValue(value);
}
public final boolean isShowWeekNumbers() {
return showWeekNumbersProperty().getValue();
}
/**
* Represents the custom node to be placed at the top of the Calendar above the month-year area.
*/
public ObjectProperty<Node> topNodeProperty() {
return topNode;
}
private final ObjectProperty<Node> topNode
= new SimpleObjectProperty<>(this, "topNode", null);
public final void setTopNode(Node value) {
topNode.setValue(value);
}
public final Node getTopNode() {
return topNode.getValue();
}
/**
* Represents the custom node to be placed at the bottom of the Calendar below the day-cell grid.
*/
public ObjectProperty<Node> bottomNodeProperty() {
return bottomNode;
}
private final ObjectProperty<Node> bottomNode
= new SimpleObjectProperty<>(this, "bottomNode", null);
public final void setBottomNode(Node value) {
bottomNode.setValue(value);
}
public final Node getBottomNode() {
return bottomNode.getValue();
}
///////////////////////////////////////////////////////////////////////////
// Stylesheet Handling //
///////////////////////////////////////////////////////////////////////////
private static final String DEFAULT_STYLE_CLASS = "calendar";
private static class StyleableProperties {
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
private static final CssMetaData<Calendar, Boolean> SHOW_WEEK_NUMBERS =
new CssMetaData<>("-fx-show-week-numbers", BooleanConverter.getInstance(), false) {
@Override
public boolean isSettable(Calendar n) {
return n.showWeekNumbers == null || !n.showWeekNumbers.isBound();
}
@Override
@SuppressWarnings("RedundantCast")
public StyleableProperty<Boolean> getStyleableProperty(Calendar n) {
return (StyleableProperty<Boolean>) (WritableValue<Boolean>) n.showWeekNumbersProperty();
}
};
static {
final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());
Collections.addAll(styleables, SHOW_WEEK_NUMBERS);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
/**
* Returns the CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses.
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
/**
* {@inheritDoc}
*/
@Override
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
@SuppressWarnings("CatchAndPrintStackTrace")
static boolean isValidDate(Chronology chrono, LocalDate date, int offset, ChronoUnit unit) {
if (date != null) {
try {
return isValidDate(chrono, date.plus(offset, unit));
} catch (DateTimeException e) {
e.printStackTrace();
}
}
return false;
}
@SuppressWarnings("ReturnValueIgnored")
static boolean isValidDate(Chronology chrono, LocalDate date) {
try {
if (date != null) {
chrono.date(date);
}
return true;
} catch (DateTimeException e) {
e.printStackTrace();
return false;
}
}
}

@ -0,0 +1,93 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import static atlantafx.base.util.PlatformUtils.isMac;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.time.temporal.ChronoUnit.YEARS;
import static javafx.scene.input.KeyCode.ESCAPE;
import java.time.LocalDate;
import java.time.ZoneId;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
/**
* The default behavior for the {@link Calendar} control.
*/
public class CalendarBehavior extends BehaviorBase<Calendar, CalendarSkin> {
public CalendarBehavior(Calendar control, CalendarSkin skin) {
super(control, skin);
}
@SuppressWarnings("MissingCasesInEnumSwitch")
public void onKeyPressed(KeyEvent e) {
getSkin().rememberFocusedDayCell();
if (e.getEventType() == KeyEvent.KEY_PRESSED) {
switch (e.getCode()) {
case HOME -> {
getSkin().goToDate(LocalDate.now(ZoneId.systemDefault()), true);
e.consume();
}
case PAGE_UP -> {
if ((isMac() && e.isMetaDown()) || (!isMac() && e.isControlDown())) {
if (getSkin().canGoYearForward()) {
getSkin().forward(1, YEARS, true);
}
} else {
if (getSkin().canGoMonthForward()) {
getSkin().forward(1, MONTHS, true);
}
}
e.consume();
}
case PAGE_DOWN -> {
if ((isMac() && e.isMetaDown()) || (!isMac() && e.isControlDown())) {
if (getSkin().canGoYearBack()) {
getSkin().forward(-1, YEARS, true);
}
} else {
if (getSkin().canGoMonthBack()) {
getSkin().forward(-1, MONTHS, true);
}
}
e.consume();
}
}
getSkin().rememberFocusedDayCell();
}
// prevents any other key events but ESC from reaching the control owner
if (e.getCode() != ESCAPE) {
e.consume();
}
}
public void moveForward(MouseEvent e) {
if ((isMac() && e.isMetaDown()) || (!isMac() && e.isControlDown())) {
if (getSkin().canGoYearForward()) {
getSkin().forward(1, YEARS, true);
}
} else {
if (getSkin().canGoMonthForward()) {
getSkin().forward(1, MONTHS, true);
}
}
e.consume();
}
public void moveBackward(MouseEvent e) {
if ((isMac() && e.isMetaDown()) || (!isMac() && e.isControlDown())) {
if (getSkin().canGoYearBack()) {
getSkin().forward(-1, YEARS, true);
}
} else {
if (getSkin().canGoMonthBack()) {
getSkin().forward(-1, MONTHS, true);
}
}
e.consume();
}
}

@ -0,0 +1,638 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package atlantafx.base.controls;
import static atlantafx.base.controls.Calendar.isValidDate;
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.time.temporal.ChronoUnit.WEEKS;
import static java.time.temporal.ChronoUnit.YEARS;
import static javafx.scene.layout.Region.USE_PREF_SIZE;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Chronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DecimalStyle;
import java.time.temporal.ChronoUnit;
import java.time.temporal.ValueRange;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.DateCell;
import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
/**
* The default skin for the {@link Calendar} control.
*/
public class CalendarSkin extends BehaviorSkinBase<Calendar, CalendarBehavior> {
// formatters
final DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("y");
final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM");
final DateTimeFormatter weekNumberFormatter = DateTimeFormatter.ofPattern("w");
final DateTimeFormatter dayCellFormatter = DateTimeFormatter.ofPattern("d");
final DateTimeFormatter monthFormatterSO = DateTimeFormatter.ofPattern("LLLL"); // standalone month name
final DateTimeFormatter weekDayNameFormatter = DateTimeFormatter.ofPattern("ccc"); // standalone day name
// UI
protected final VBox rootPane = new VBox();
protected CalendarGrid calendarGrid;
protected Button forwardButton;
protected Button backButton;
protected Label monthLabel;
protected Label yearLabel;
// model
protected final List<DateCell> dayNameCells = new ArrayList<>();
protected final List<DateCell> weekNumberCells = new ArrayList<>();
protected final List<DateCell> dayCells = new ArrayList<>();
protected LocalDate[] dayCellDates;
protected DateCell lastFocusedDayCell = null;
protected final int daysPerWeek = getDaysPerWeek();
private final ObjectProperty<YearMonth> displayedYearMonth
= new SimpleObjectProperty<>(this, "displayedYearMonth");
public ObjectProperty<YearMonth> displayedYearMonthProperty() {
return displayedYearMonth;
}
private final ObjectBinding<LocalDate> firstDayOfMonth =
Bindings.createObjectBinding(() -> displayedYearMonth.get().atDay(1), displayedYearMonth);
public LocalDate getFirstDayOfMonth() {
return firstDayOfMonth.get();
}
public CalendarSkin(Calendar control) {
super(control);
createUI();
registerChangeListener(control.valueProperty(), e -> {
LocalDate date = control.getValue();
displayedYearMonthProperty().set(
date != null ? YearMonth.from(date) : YearMonth.now(ZoneId.systemDefault())
);
updateValues();
control.fireEvent(new ActionEvent());
});
registerChangeListener(control.showWeekNumbersProperty(), e -> {
updateGrid();
updateWeekNumberCells();
});
registerChangeListener(control.topNodeProperty(), e -> {
Node node = control.getTopNode();
if (node == null) {
rootPane.getChildren().removeIf(c -> c.getStyleClass().contains("top-node"));
} else {
if (!node.getStyleClass().contains("top-node")) {
node.getStyleClass().add("top-node");
}
rootPane.getChildren().add(0, node);
}
});
registerChangeListener(control.bottomNodeProperty(), e -> {
Node node = control.getBottomNode();
if (node == null) {
rootPane.getChildren().removeIf(c -> c.getStyleClass().contains("bottom-node"));
} else {
if (!node.getStyleClass().contains("bottom-node")) {
node.getStyleClass().add("bottom-node");
}
rootPane.getChildren().add(node);
}
});
}
@Override
public CalendarBehavior createDefaultBehavior() {
return new CalendarBehavior(getControl(), this);
}
public Locale getLocale() {
return Locale.getDefault(Locale.Category.FORMAT);
}
public Scene getScene() {
return getControl().getScene();
}
/**
* The primary chronology for display.
*/
public Chronology getPrimaryChronology() {
return getControl().getChronology();
}
public int getMonthsPerYear() {
ValueRange range = getPrimaryChronology().range(MONTH_OF_YEAR);
return (int) (range.getMaximum() - range.getMinimum() + 1);
}
public int getDaysPerWeek() {
ValueRange range = getPrimaryChronology().range(DAY_OF_WEEK);
return (int) (range.getMaximum() - range.getMinimum() + 1);
}
///////////////////////////////////////////////////////////////////////////
// UI //
///////////////////////////////////////////////////////////////////////////
protected void createUI() {
if (getControl().getTopNode() != null) {
getControl().getTopNode().getStyleClass().add("top-node");
rootPane.getChildren().add(getControl().getTopNode());
}
// YearMonth //
LocalDate value = getControl().getValue();
displayedYearMonth.set(
value != null ? YearMonth.from(value) : YearMonth.now(ZoneId.systemDefault())
);
displayedYearMonth.addListener((observable, oldValue, newValue) -> updateValues());
rootPane.getChildren().add(createMonthYearPane());
// Calendar //
calendarGrid = new CalendarGrid();
calendarGrid.getStyleClass().add("calendar-grid");
calendarGrid.setFocusTraversable(true);
calendarGrid.setVgap(-1);
calendarGrid.setHgap(-1);
// get the weekday labels starting with the weekday that is the first-day-of-the-week
// according to the locale in the displayed LocalDate
for (int i = 0; i < daysPerWeek; i++) {
DateCell cell = new DateCell();
cell.getStyleClass().add("day-name-cell");
dayNameCells.add(cell);
}
// week number column
for (int i = 0; i < 6; i++) {
DateCell cell = new DateCell();
cell.getStyleClass().add("week-number-cell");
weekNumberCells.add(cell);
}
createDayCells();
updateGrid();
// preserve default class name for compatibility reasons
rootPane.getStyleClass().addAll("date-picker-popup", "calendar");
rootPane.getChildren().add(calendarGrid);
if (getControl().getBottomNode() != null) {
getControl().getBottomNode().getStyleClass().add("bottom-node");
rootPane.getChildren().add(getControl().getBottomNode());
}
getChildren().add(rootPane);
getControl().setOnKeyPressed(e -> behavior.onKeyPressed(e));
refresh();
}
protected HBox createMonthYearPane() {
HBox monthYearPane = new HBox();
monthYearPane.getStyleClass().add("month-year-pane");
backButton = new Button();
backButton.getStyleClass().addAll("back-button");
backButton.setOnMouseClicked(behavior::moveBackward);
StackPane leftArrow = new StackPane();
leftArrow.getStyleClass().add("left-arrow");
leftArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
backButton.setGraphic(leftArrow);
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
monthLabel = new Label();
monthLabel.getStyleClass().add("month-label");
yearLabel = new Label();
yearLabel.getStyleClass().add("year-label");
forwardButton = new Button();
forwardButton.getStyleClass().addAll("forward-button");
forwardButton.setOnMouseClicked(behavior::moveForward);
StackPane rightArrow = new StackPane();
rightArrow.getStyleClass().add("right-arrow");
rightArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
forwardButton.setGraphic(rightArrow);
monthYearPane.getChildren().addAll(monthLabel, yearLabel, spacer, backButton, forwardButton);
return monthYearPane;
}
protected class CalendarGrid extends GridPane {
@Override
protected double computePrefWidth(double height) {
final double width = super.computePrefWidth(height);
// Make sure width snaps to pixel when divided by number of columns.
// GridPane doesn't do this with percentage width constraints.
// See GridPane.adjustColumnWidths().
final int nCols = daysPerWeek + (getControl().isShowWeekNumbers() ? 1 : 0);
final double snapHGap = snapSpaceX(getHgap());
final double hGaps = snapHGap * (nCols - 1);
final double left = snapSpaceX(getInsets().getLeft());
final double right = snapSpaceX(getInsets().getRight());
final double contentWidth = width - left - right - hGaps;
return (snapSizeX(contentWidth / nCols) * nCols) + left + right + hGaps;
}
@Override
protected void layoutChildren() {
// prevent AssertionError in GridPane
if (getWidth() > 0 && getHeight() > 0) {
super.layoutChildren();
}
}
}
///////////////////////////////////////////////////////////////////////////
// API //
///////////////////////////////////////////////////////////////////////////
public void refresh() {
updateDayNameCells();
updateValues();
}
public void updateValues() {
// preserve this order
updateWeekNumberCells();
updateDayCells();
updateMonthYearPane();
}
public void updateGrid() {
calendarGrid.getColumnConstraints().clear();
calendarGrid.getChildren().clear();
final int nCols = daysPerWeek + (getControl().isShowWeekNumbers() ? 1 : 0);
// column constraints
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(100); // treated as weight
for (int i = 0; i < nCols; i++) {
calendarGrid.getColumnConstraints().add(columnConstraints);
}
// day names row
for (int i = 0; i < daysPerWeek; i++) {
calendarGrid.add(dayNameCells.get(i), i + nCols - daysPerWeek, 1);
}
// week number column
if (getControl().isShowWeekNumbers()) {
for (int i = 0; i < 6; i++) {
calendarGrid.add(weekNumberCells.get(i), 0, i + 2);
}
}
// setup 6 rows of daysPerWeek, which is the maximum number of cells
// required in the worst case layout
for (int row = 0; row < 6; row++) {
for (int col = 0; col < daysPerWeek; col++) {
calendarGrid.add(dayCells.get(row * daysPerWeek + col), col + nCols - daysPerWeek, row + 2);
}
}
}
public void updateDayNameCells() {
// first day of week, 1 = monday, 7 = sunday
final int firstDayOfWeek = WeekFields.of(getLocale()).getFirstDayOfWeek().getValue();
// july 13th 2009 is a Monday, so a firstDayOfWeek = 1 must come out of the 13th
final LocalDate date = LocalDate.of(2009, 7, 12 + firstDayOfWeek);
for (int i = 0; i < daysPerWeek; i++) {
String name = weekDayNameFormatter.withLocale(getLocale()).format(date.plus(i, DAYS));
dayNameCells.get(i).setText(capitalize(name));
}
}
public void updateWeekNumberCells() {
if (getControl().isShowWeekNumbers()) {
final Locale locale = getLocale();
final int maxWeeksPerMonth = 6;
final LocalDate firstOfMonth = displayedYearMonth.get().atDay(1);
for (int i = 0; i < maxWeeksPerMonth; i++) {
LocalDate date = firstOfMonth.plus(i, WEEKS);
// use a formatter to ensure correct localization
// such as when Thai numerals are required.
String cellText = weekNumberFormatter
.withLocale(locale)
.withDecimalStyle(DecimalStyle.of(locale))
.format(date);
weekNumberCells.get(i).setText(cellText);
}
}
}
public void updateDayCells() {
final Locale locale = getLocale();
final Chronology chrono = getPrimaryChronology();
final YearMonth curMonth = displayedYearMonth.get();
final int firstOfMonthIdx = determineFirstOfMonthDayOfWeek();
YearMonth prevMonth = null;
YearMonth nextMonth = null;
int daysInCurMonth = -1;
int daysInPrevMonth = -1;
for (int i = 0; i < 6 * daysPerWeek; i++) {
final DateCell dayCell = dayCells.get(i);
dayCell.getStyleClass().setAll("cell", "date-cell", "day-cell");
dayCell.setDisable(false);
dayCell.setStyle(null);
dayCell.setGraphic(null);
dayCell.setTooltip(null);
try {
daysInCurMonth = daysInCurMonth == -1 ? curMonth.lengthOfMonth() : daysInCurMonth;
YearMonth month = curMonth;
int day = i - firstOfMonthIdx + 1;
if (i < firstOfMonthIdx) {
if (prevMonth == null) {
prevMonth = curMonth.minusMonths(1);
daysInPrevMonth = prevMonth.lengthOfMonth();
}
month = prevMonth;
day = i + daysInPrevMonth - firstOfMonthIdx + 1;
dayCell.getStyleClass().add("previous-month");
} else if (i >= firstOfMonthIdx + daysInCurMonth) {
if (nextMonth == null) {
nextMonth = curMonth.plusMonths(1);
}
month = nextMonth;
day = i - daysInCurMonth - firstOfMonthIdx + 1;
dayCell.getStyleClass().add("next-month");
}
LocalDate date = month.atDay(day);
dayCellDates[i] = date;
ChronoLocalDate cDate = chrono.date(date);
dayCell.setDisable(false);
if (isToday(date)) {
dayCell.getStyleClass().add("today");
}
if (date.equals(getControl().getValue())) {
dayCell.getStyleClass().add("selected");
}
String cellText = dayCellFormatter.withLocale(locale)
.withChronology(chrono)
.withDecimalStyle(DecimalStyle.of(locale))
.format(cDate);
dayCell.setText(cellText);
dayCell.updateItem(date, false);
} catch (DateTimeException ex) {
// date is out of range
dayCell.setText(" ");
dayCell.setDisable(true);
}
}
}
// determine on which day of week idx the first of the months is
private int determineFirstOfMonthDayOfWeek() {
// determine with which cell to start
int firstDayOfWeek = WeekFields.of(getLocale()).getFirstDayOfWeek().getValue();
int firstOfMonthIdx = displayedYearMonth.get().atDay(1).getDayOfWeek().getValue() - firstDayOfWeek;
return firstOfMonthIdx < 0 ? firstOfMonthIdx + daysPerWeek : firstOfMonthIdx;
}
public void updateMonthYearPane() {
YearMonth yearMonth = displayedYearMonth.get();
monthLabel.setText(formatMonth(yearMonth));
yearLabel.setText(formatYear(yearMonth));
backButton.setDisable(!canGoMonthBack());
forwardButton.setDisable(!canGoMonthForward());
}
protected String formatMonth(YearMonth yearMonth) {
Chronology chrono = getPrimaryChronology();
try {
ChronoLocalDate chronoDate = chrono.date(yearMonth.atDay(1));
String str = monthFormatterSO.withLocale(getLocale())
.withChronology(chrono)
.format(chronoDate);
if (Character.isDigit(str.charAt(0))) {
// fallback: if standalone format returned a number, use standard format instead
str = monthFormatter.withLocale(getLocale())
.withChronology(chrono)
.format(chronoDate);
}
return capitalize(str);
} catch (DateTimeException ex) {
// date is out of range
return "";
}
}
protected String formatYear(YearMonth yearMonth) {
Chronology chrono = getPrimaryChronology();
try {
ChronoLocalDate chronoDate = chrono.date(yearMonth.atDay(1));
return yearFormatter.withLocale(getLocale())
.withChronology(chrono)
.withDecimalStyle(DecimalStyle.of(getLocale()))
.format(chronoDate);
} catch (DateTimeException ex) {
// date is out of range
return "";
}
}
public void forward(int offset, ChronoUnit unit, boolean focusDayCell) {
YearMonth yearMonth = displayedYearMonth.get();
DateCell dateCell = lastFocusedDayCell;
if (dateCell == null || !getDayCellDate(dateCell).getMonth().equals(yearMonth.getMonth())) {
dateCell = findDayCellForDate(yearMonth.atDay(1));
}
goToDayCell(dateCell, offset, unit, focusDayCell);
}
public void goToDayCell(DateCell dateCell, int offset, ChronoUnit unit, boolean focusDayCell) {
goToDate(getDayCellDate(dateCell).plus(offset, unit), focusDayCell);
}
public void goToDate(LocalDate date, boolean focusDayCell) {
if (isValidDate(getPrimaryChronology(), date)) {
displayedYearMonth.set(YearMonth.from(date));
if (focusDayCell) {
findDayCellForDate(date).requestFocus();
}
}
}
private DateCell findDayCellForDate(LocalDate date) {
for (int i = 0; i < dayCellDates.length; i++) {
if (date.equals(dayCellDates[i])) {
return dayCells.get(i);
}
}
return dayCells.get(dayCells.size() / 2 + 1);
}
public void selectDayCell(DateCell dateCell) {
getControl().setValue(getDayCellDate(dateCell));
}
private LocalDate getDayCellDate(DateCell dateCell) {
return dayCellDates[dayCells.indexOf(dateCell)];
}
protected void createDayCells() {
EventHandler<MouseEvent> dayCellActionHandler = e -> {
if (e.getButton() != MouseButton.PRIMARY) {
return;
}
DateCell dayCell = (DateCell) e.getSource();
selectDayCell(dayCell);
lastFocusedDayCell = dayCell;
};
for (int row = 0; row < 6; row++) {
for (int col = 0; col < daysPerWeek; col++) {
DateCell dayCell = createDayCell();
dayCell.addEventHandler(MouseEvent.MOUSE_CLICKED, dayCellActionHandler);
dayCells.add(dayCell);
}
}
dayCellDates = new LocalDate[6 * daysPerWeek];
}
protected DateCell createDayCell() {
Callback<Calendar, DateCell> factory = getControl().getDayCellFactory();
return Objects.requireNonNullElseGet(
factory != null ? factory.call(getControl()) : null,
DateCell::new
);
}
public void rememberFocusedDayCell() {
Node node = getControl().getScene().getFocusOwner();
if (node instanceof DateCell dc) {
lastFocusedDayCell = dc;
}
}
public boolean canGoMonthBack() {
return isValidDate(getPrimaryChronology(), getFirstDayOfMonth(), -1, DAYS);
}
public boolean canGoMonthForward() {
return isValidDate(getPrimaryChronology(), getFirstDayOfMonth(), +1, MONTHS);
}
public boolean canGoYearBack() {
return isValidDate(getPrimaryChronology(), getFirstDayOfMonth(), -1, YEARS);
}
public boolean canGoYearForward() {
return isValidDate(getPrimaryChronology(), getFirstDayOfMonth(), +1, YEARS);
}
public void clearFocus() {
LocalDate focusDate = Objects.requireNonNullElseGet(getControl().getValue(), LocalDate::now);
if (YearMonth.from(focusDate).equals(displayedYearMonth.get())) {
goToDate(focusDate, true); // focus date
} else {
backButton.requestFocus(); // should not happen
}
}
private static String capitalize(String word) {
if (word.length() > 0) {
int firstChar = word.codePointAt(0);
if (!Character.isTitleCase(firstChar)) {
word = new String(new int[] {Character.toTitleCase(firstChar)}, 0, 1)
+ word.substring(Character.offsetByCodePoints(word, 0, 1));
}
}
return word;
}
private static boolean isToday(LocalDate date) {
return date != null && date.equals(today());
}
private static LocalDate today() {
return LocalDate.now(ZoneId.systemDefault());
}
}

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.StringProperty;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import org.jetbrains.annotations.Nullable;
/**
* A MenuItem that is intended to contain a caption for a group of menu items
* that share a common purpose.
*/
public class CaptionMenuItem extends CustomMenuItem {
protected final Label title = new Label();
/**
* Creates an empty menu item.
*/
public CaptionMenuItem() {
this(null);
}
/**
* Creates a CaptionMenuItem with the specified text as the title.
*/
public CaptionMenuItem(@Nullable @NamedArg("text") String text) {
super();
setTitle(text);
setContent(title);
setHideOnClick(false);
getStyleClass().addAll("caption-menu-item");
}
/**
* Contains the title of the menu item.
*/
public StringProperty titleProperty() {
return title.textProperty();
}
public String getTitle() {
return title.getText();
}
public void setTitle(String text) {
title.setText(text);
}
}

@ -0,0 +1,107 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
/**
* A versatile container that can be used in various contexts, such as headings,
* text, dialogs and more. It includes a header to provide a brief overview
* or context of the information. The sub-header and body sections provide
* more detailed content, while the footer may include additional actions or
* information.
*/
public class Card extends Control {
/**
* Creates an empty Card.
*/
public Card() {
super();
getStyleClass().add("card");
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new CardSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the cards header node.
*/
public ObjectProperty<Node> headerProperty() {
return header;
}
private final ObjectProperty<Node> header = new SimpleObjectProperty<>(this, "header");
public Node getHeader() {
return header.get();
}
public void setHeader(Node header) {
this.header.set(header);
}
/**
* Represents the cards sub-header node.
*/
public final ObjectProperty<Node> subHeaderProperty() {
return subHeader;
}
private final ObjectProperty<Node> subHeader = new SimpleObjectProperty<>(this, "subHeader");
public Node getSubHeader() {
return subHeader.get();
}
public void setSubHeader(Node subHeader) {
this.subHeader.set(subHeader);
}
/**
* Represents the cards body node.
*/
public ObjectProperty<Node> bodyProperty() {
return body;
}
private final ObjectProperty<Node> body = new SimpleObjectProperty<>(this, "body");
public Node getBody() {
return body.get();
}
public void setBody(Node body) {
this.body.set(body);
}
/**
* Represents the cards footer node.
*/
public ObjectProperty<Node> footerProperty() {
return footer;
}
private final ObjectProperty<Node> footer = new SimpleObjectProperty<>(this, "footer");
public Node getFooter() {
return footer.get();
}
public void setFooter(Node footer) {
this.footer.set(footer);
}
}

@ -0,0 +1,101 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.value.ChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
/**
* The default skin for the {@link Card} control.
*/
public class CardSkin implements Skin<Card> {
protected static final PseudoClass HAS_HEADER = PseudoClass.getPseudoClass("has-header");
protected static final PseudoClass HAS_SUBHEADER = PseudoClass.getPseudoClass("has-subheader");
protected static final PseudoClass HAS_BODY = PseudoClass.getPseudoClass("has-body");
protected static final PseudoClass HAS_FOOTER = PseudoClass.getPseudoClass("has-footer");
protected static final PseudoClass HAS_IMAGE = PseudoClass.getPseudoClass("has-image");
protected final Card control;
protected final VBox root = new VBox();
protected final StackPane headerSlot;
protected final ChangeListener<Node> headerSlotListener;
protected final StackPane subHeaderSlot;
protected final ChangeListener<Node> subHeaderSlotListener;
protected final StackPane bodySlot;
protected final ChangeListener<Node> bodySlotListener;
protected final StackPane footerSlot;
protected final ChangeListener<Node> footerSlotListener;
protected CardSkin(Card control) {
this.control = control;
headerSlot = new StackPane();
headerSlot.getStyleClass().add("header");
headerSlotListener = new SlotListener(
headerSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_HEADER, active)
);
control.headerProperty().addListener(headerSlotListener);
headerSlotListener.changed(control.headerProperty(), null, control.getHeader());
subHeaderSlot = new StackPane();
subHeaderSlot.getStyleClass().add("sub-header");
subHeaderSlotListener = new SlotListener(
subHeaderSlot,
(n, active) -> {
getSkinnable().pseudoClassStateChanged(HAS_SUBHEADER, active);
getSkinnable().pseudoClassStateChanged(HAS_IMAGE, n instanceof ImageView);
}
);
control.subHeaderProperty().addListener(subHeaderSlotListener);
subHeaderSlotListener.changed(control.subHeaderProperty(), null, control.getSubHeader());
bodySlot = new StackPane();
bodySlot.getStyleClass().add("body");
VBox.setVgrow(bodySlot, Priority.ALWAYS);
bodySlotListener = new SlotListener(
bodySlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_BODY, active)
);
control.bodyProperty().addListener(bodySlotListener);
bodySlotListener.changed(control.bodyProperty(), null, control.getBody());
footerSlot = new StackPane();
footerSlot.getStyleClass().add("footer");
footerSlotListener = new SlotListener(
footerSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_FOOTER, active)
);
control.footerProperty().addListener(footerSlotListener);
footerSlotListener.changed(control.footerProperty(), null, control.getFooter());
root.getStyleClass().add("container");
root.getChildren().setAll(headerSlot, subHeaderSlot, bodySlot, footerSlot);
}
@Override
public Card getSkinnable() {
return control;
}
@Override
public Node getNode() {
return root;
}
@Override
public void dispose() {
control.headerProperty().removeListener(headerSlotListener);
control.subHeaderProperty().removeListener(subHeaderSlotListener);
control.bodyProperty().removeListener(bodySlotListener);
control.footerProperty().removeListener(footerSlotListener);
}
}

@ -0,0 +1,129 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2015, ControlsFX
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package atlantafx.base.controls;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
/**
* A base class for people wanting to customize a {@link TextField}
* to contain nodes inside the text field itself, without being on top
* of the users typed-in text.
*/
public class CustomTextField extends TextField {
/**
* Creates an empty CustomTextField.
*/
public CustomTextField() {
getStyleClass().add("custom-text-field");
}
/**
* Creates a CustomTextField with initial text content.
*
* @param text A string for text content.
*/
public CustomTextField(String text) {
this();
setText(text);
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new CustomTextFieldSkin(this) {
@Override
public ObjectProperty<Node> leftProperty() {
return CustomTextField.this.leftProperty();
}
@Override
public ObjectProperty<Node> rightProperty() {
return CustomTextField.this.rightProperty();
}
};
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the {@link Node} that is placed on the left of the text field.
*/
public final ObjectProperty<Node> leftProperty() {
return left;
}
private final ObjectProperty<Node> left = new SimpleObjectProperty<>(this, "left");
/**
* Returns the {@link Node} that is placed on the left of the text field.
*/
public final Node getLeft() {
return left.get();
}
/**
* Sets the {@link Node} that is placed on the left of the text field.
*/
public final void setLeft(Node value) {
left.set(value);
}
/**
* Represents the {@link Node} that is placed on the right of the text field.
*/
public final ObjectProperty<Node> rightProperty() {
return right;
}
private final ObjectProperty<Node> right = new SimpleObjectProperty<>(this, "right");
/**
* Returns the {@link Node} that is placed on the right of the text field.
*/
public final Node getRight() {
return right.get();
}
/**
* Sets the {@link Node} that is placed on the right of the text field.
*/
public final void setRight(Node value) {
right.set(value);
}
}

@ -0,0 +1,177 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2019 ControlsFX
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package atlantafx.base.controls;
import javafx.beans.property.ObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.TextField;
import javafx.scene.control.skin.TextFieldSkin;
import javafx.scene.layout.StackPane;
import javafx.scene.text.HitInfo;
/**
* The default skin for the {@link CustomTextField} control.
*/
public abstract class CustomTextFieldSkin extends TextFieldSkin {
private static final PseudoClass HAS_NO_SIDE_NODE = PseudoClass.getPseudoClass("no-side-nodes");
private static final PseudoClass HAS_LEFT_NODE = PseudoClass.getPseudoClass("left-node-visible");
private static final PseudoClass HAS_RIGHT_NODE = PseudoClass.getPseudoClass("right-node-visible");
private StackPane leftPane;
private StackPane rightPane;
private final TextField control;
public CustomTextFieldSkin(final TextField control) {
super(control);
this.control = control;
updateChildren();
registerChangeListener(leftProperty(), e -> updateChildren());
registerChangeListener(rightProperty(), e -> updateChildren());
}
public abstract ObjectProperty<Node> leftProperty();
public abstract ObjectProperty<Node> rightProperty();
private void updateChildren() {
Node newLeft = leftProperty().get();
// remove leftPane in any case
getChildren().remove(leftPane);
Node left;
if (newLeft != null) {
leftPane = new StackPane(newLeft);
leftPane.setManaged(false);
leftPane.setAlignment(Pos.CENTER_LEFT);
leftPane.getStyleClass().add("left-pane");
getChildren().add(leftPane);
left = newLeft;
} else {
leftPane = null;
left = null;
}
Node newRight = rightProperty().get();
// remove rightPane in any case
getChildren().remove(rightPane);
Node right;
if (newRight != null) {
rightPane = new StackPane(newRight);
rightPane.setManaged(false);
rightPane.setAlignment(Pos.CENTER_RIGHT);
rightPane.getStyleClass().add("right-pane");
getChildren().add(rightPane);
right = newRight;
} else {
rightPane = null;
right = null;
}
control.pseudoClassStateChanged(HAS_LEFT_NODE, left != null);
control.pseudoClassStateChanged(HAS_RIGHT_NODE, right != null);
control.pseudoClassStateChanged(HAS_NO_SIDE_NODE, left == null && right == null);
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
final double fullHeight = h + snappedTopInset() + snappedBottomInset();
final double leftWidth = leftPane == null ? 0.0 : snapSizeX(leftPane.prefWidth(fullHeight));
final double rightWidth = rightPane == null ? 0.0 : snapSizeX(rightPane.prefWidth(fullHeight));
final double textFieldStartX = snapPositionX(x) + snapSizeX(leftWidth);
final double textFieldWidth = w - snapSizeX(leftWidth) - snapSizeX(rightWidth);
super.layoutChildren(textFieldStartX, 0, textFieldWidth, fullHeight);
if (leftPane != null) {
final double leftStartX = 0;
leftPane.resizeRelocate(leftStartX, 0, leftWidth, fullHeight);
}
if (rightPane != null) {
final double rightStartX = w - rightWidth + snappedLeftInset();
rightPane.resizeRelocate(rightStartX, 0, rightWidth, fullHeight);
}
}
@Override
public HitInfo getIndex(double x, double y) {
// This resolves an issue when we have a left Node and the click point is badly returned
// because we weren't considering the shift induced by the leftPane.
final double leftWidth = leftPane == null ? 0.0 : snapSizeX(leftPane.prefWidth(getSkinnable().getHeight()));
return super.getIndex(x - leftWidth, y);
}
@Override
protected double computePrefWidth(double h, double topInset, double rightInset, double bottomInset,
double leftInset) {
final double pw = super.computePrefWidth(h, topInset, rightInset, bottomInset, leftInset);
final double leftWidth = leftPane == null ? 0.0 : snapSizeX(leftPane.prefWidth(h));
final double rightWidth = rightPane == null ? 0.0 : snapSizeX(rightPane.prefWidth(h));
return pw + leftWidth + rightWidth;
}
@Override
protected double computePrefHeight(double w, double topInset, double rightInset, double bottomInset,
double leftInset) {
final double ph = super.computePrefHeight(w, topInset, rightInset, bottomInset, leftInset);
final double leftHeight = leftPane == null ? 0.0 : snapSizeX(leftPane.prefHeight(-1));
final double rightHeight = rightPane == null ? 0.0 : snapSizeX(rightPane.prefHeight(-1));
return Math.max(ph, Math.max(leftHeight, rightHeight));
}
@Override
protected double computeMinWidth(double h, double topInset, double rightInset, double bottomInset,
double leftInset) {
final double mw = super.computeMinWidth(h, topInset, rightInset, bottomInset, leftInset);
final double leftWidth = leftPane == null ? 0.0 : snapSizeX(leftPane.minWidth(h));
final double rightWidth = rightPane == null ? 0.0 : snapSizeX(rightPane.minWidth(h));
return mw + leftWidth + rightWidth;
}
@Override
protected double computeMinHeight(double w, double topInset, double rightInset, double bottomInset,
double leftInset) {
final double mh = super.computeMinHeight(w, topInset, rightInset, bottomInset, leftInset);
final double leftHeight = leftPane == null ? 0.0 : snapSizeX(leftPane.minHeight(-1));
final double rightHeight = rightPane == null ? 0.0 : snapSizeX(rightPane.minHeight(-1));
return Math.max(mh, Math.max(leftHeight, rightHeight));
}
}

@ -0,0 +1,146 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package atlantafx.base.controls;
import atlantafx.base.util.MaskChar;
import atlantafx.base.util.MaskTextFormatter;
import atlantafx.base.util.SimpleMaskChar;
import java.util.List;
import java.util.Objects;
import javafx.beans.NamedArg;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.Nullable;
/**
* A convenience wrapper for instantiating a {@link CustomTextField} with a
* {@code MaskTextFormatter}. For additional info refer to the
* {@link MaskTextFormatter} documentation.
*/
public class MaskTextField extends CustomTextField {
/**
* The whole dancing around the editable mask property is solely due to SceneBuilder
* not works without no-arg constructor. It requires to make formatter value mutable
* as well, which is not really tested and never intended to be supported. Also, since
* the formatter property is not bound to the text field formatter property, setting the
* latter manually can lead to memory leak.
*/
protected final StringProperty mask = new SimpleStringProperty(this, "mask");
protected final ReadOnlyObjectWrapper<MaskTextFormatter> formatter =
new ReadOnlyObjectWrapper<>(this, "formatter");
/**
* Creates an empty MaskTextField.
*/
public MaskTextField() {
super("");
init();
}
/**
* Creates an empty MaskTextField with the specified input mask.
*
* <p>The input mask is specified as a string that must follow the
* rules described in the {@link MaskTextFormatter} documentation.
*
* @param mask The input mask.
*/
public MaskTextField(@NamedArg("mask") String mask) {
this("", mask);
}
/**
* Creates a MaskTextField with initial text content and the specified input mask.
*
* <p>The input mask is specified as a string that must follow the
* rules described in the {@link MaskTextFormatter} documentation.
*
* @param text A string for text content.
* @param mask An input mask.
*/
private MaskTextField(@NamedArg("text") String text,
@NamedArg("mask") String mask) {
super(Objects.requireNonNullElse(text, ""));
formatter.set(MaskTextFormatter.create(this, mask));
setMask(mask); // set mask only after creating a formatter, for validation
init();
}
/**
* Creates a MaskTextField with initial text content and the specified input mask.
*
* <p>The input mask is specified as a list of {@code MaskChar}. You can use
* the {@link SimpleMaskChar} as the default implementation.
*
* @param text A string for text content.
* @param mask An input mask.
*/
public MaskTextField(String text, List<MaskChar> mask) {
super(Objects.requireNonNullElse(text, ""));
formatter.set(MaskTextFormatter.create(this, mask));
setMask(null);
init();
}
protected void init() {
mask.addListener((obs, old, val) -> {
// this will replace the current text value with placeholder mask,
// so, neither text no prompt won't be shown in the SceneBuilder
formatter.set(val != null ? MaskTextFormatter.create(this, val) : null);
});
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the input mask.
*
* <p>Note that the MaskTextField allows for specifying the input mask as either a string
* or a list of {@code MaskChar}. These formats cannot be converted to one another. Therefore,
* if the input mask was specified as a list of {@code MaskChar}, this property will return
* null value.
*/
public StringProperty maskProperty() {
return mask;
}
public @Nullable String getMask() {
return mask.get();
}
public void setMask(@Nullable String mask) {
this.mask.set(mask);
}
}

@ -0,0 +1,103 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import org.jetbrains.annotations.Nullable;
/**
* A control for displaying banners or alerts that is specifically
* designed to grab the users attention. It is based on the {@link Tile}
* layout and shares its structure.
*/
public class Message extends TileBase {
/**
* Creates an empty Message.
*/
public Message() {
this(null, null, null);
}
/**
* Creates a new Message with an initial title and description.
*
* @param title A string for the title.
* @param description A string for the description.
*/
public Message(@Nullable @NamedArg("title") String title,
@Nullable @NamedArg("description") String description) {
this(title, description, null);
}
/**
* Creates a new Message with an initial title, description and graphic.
*
* @param title A string for the title.
* @param description A string for the description.
* @param graphic A graphic or icon.
*/
public Message(@Nullable String title,
@Nullable String description,
@Nullable Node graphic) {
super(title, description, graphic);
getStyleClass().add("message");
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new MessageSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the messages action handler. Setting an action handler makes the
* message interactive (or clickable). When a user clicks on the interactive
* message, the specified action handler will be called.
*/
public ObjectProperty<Runnable> actionHandlerProperty() {
return actionHandler;
}
private final ObjectProperty<Runnable> actionHandler = new SimpleObjectProperty<>(this, "actionHandler");
public Runnable getActionHandler() {
return actionHandler.get();
}
public void setActionHandler(Runnable actionHandler) {
this.actionHandler.set(actionHandler);
}
/**
* Represents the user-specified close handler, which is intended to be used to close
* or dismiss the message. When a user clicks on the message's close button, the specified
* close handler will be called.
*/
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");
public EventHandler<? super Event> getOnClose() {
return onClose.get();
}
public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose);
}
}

@ -0,0 +1,81 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import atlantafx.base.theme.Styles;
import javafx.css.PseudoClass;
import javafx.event.Event;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.layout.StackPane;
/**
* The default skin for the {@link Message} control.
*/
public class MessageSkin extends TileSkinBase<Message> {
protected static final PseudoClass CLOSEABLE = PseudoClass.getPseudoClass("closeable");
protected final StackPane closeButton = new StackPane();
protected final StackPane closeButtonIcon = new StackPane();
public MessageSkin(Message control) {
super(control);
// ACTION
pseudoClassStateChanged(Styles.STATE_INTERACTIVE, control.getActionHandler() != null);
registerChangeListener(
control.actionHandlerProperty(),
o -> pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
);
container.setOnMouseClicked(e -> {
if (getSkinnable().getActionHandler() != null) {
getSkinnable().getActionHandler().run();
}
});
// CLOSE BUTTON
closeButton.getStyleClass().add("close-button");
closeButton.getChildren().setAll(closeButtonIcon);
closeButton.setOnMouseClicked(e -> handleClose());
closeButton.setVisible(control.getOnClose() != null);
closeButton.setManaged(control.getOnClose() != null);
closeButtonIcon.getStyleClass().add("icon");
getChildren().add(closeButton);
pseudoClassStateChanged(CLOSEABLE, control.getOnClose() != null);
registerChangeListener(control.onCloseProperty(), o -> {
closeButton.setVisible(getSkinnable().getOnClose() != null);
closeButton.setManaged(getSkinnable().getOnClose() != null);
pseudoClassStateChanged(CLOSEABLE, getSkinnable().onCloseProperty() != null);
});
}
protected void handleClose() {
if (getSkinnable().getOnClose() != null) {
getSkinnable().getOnClose().handle(new Event(Event.ANY));
}
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
if (closeButton.isManaged()) {
var lb = closeButton.getLayoutBounds();
layoutInArea(closeButton, w - lb.getWidth() - 5, 5, lb.getWidth(), lb.getHeight(), -1, HPos.RIGHT,
VPos.TOP);
}
layoutInArea(container, x, y, w, h, -1, HPos.CENTER, VPos.CENTER);
}
@Override
public void dispose() {
unregisterChangeListeners(getSkinnable().actionHandlerProperty());
unregisterChangeListeners(getSkinnable().onCloseProperty());
super.dispose();
}
}

@ -0,0 +1,302 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import atlantafx.base.util.Animations;
import java.util.Objects;
import java.util.function.Function;
import javafx.animation.Animation;
import javafx.beans.NamedArg;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jetbrains.annotations.Nullable;
/**
* A container for displaying application dialogs ot top of the current scene
* without opening a modal {@link Stage}. It's a translucent (glass) pane
* that can hold arbitrary content as well as animate its appearance.
*
* <p>When {@link #displayProperty()} value is changed the modal pane modifies own
* {@link #viewOrderProperty()} value accordingly, thus moving itself on top of the
* parent container or vise versa. You can change the target view order value via the
* constructor param. This also means that one <b>must not</b> change the modal pane
* {@link #viewOrderProperty()} property manually.
*
* <p>Example:
*
* <pre>{@code
* ModalPane modalPane = new ModalPane();
*
* Label content = new Label("Content");
* content.setSize(450, 450);
*
* Button openBtn = new Button("Open");
* openBtn.setOnAction(evt -> modalPane.show(content));
* }</pre>
*/
public class ModalPane extends Control {
/**
* The default value that is set to the modal pane
* when it must be on top of other nodes.
*/
public static final int Z_FRONT = -10;
/**
* The default value that is set to the modal pane
* when it must be below of other nodes.
*/
public static final int Z_BACK = 10;
/**
* The default animation duration that is applied to the modal content
* when it enters the user view.
*/
public static final Duration DEFAULT_DURATION_IN = Duration.millis(200);
/**
* The default animation duration that is applied to the modal content
* when it leaves the user view.
*/
public static final Duration DEFAULT_DURATION_OUT = Duration.millis(100);
private final int topViewOrder;
/**
* Creates a new modal pane with the default {@code topViewOrder}
* property value.
*/
public ModalPane() {
this(Z_FRONT);
}
/**
* Creates a new modal pane with the specified {@code topViewOrder} property.
*
* @param topViewOrder The {@link #viewOrderProperty()} value to be set in order
* to display the ModalPane on top of the parent container.
*/
public ModalPane(@NamedArg("topViewOrder") int topViewOrder) {
super();
this.topViewOrder = topViewOrder;
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new ModalPaneSkin(this);
}
/**
* Returns the value of {@link #viewOrderProperty()} to be set in order to display
* the ModalPane on top of its parent container. This is a constructor parameter
* that cannot be changed after the ModalPane has been instantiated.
*/
public int getTopViewOrder() {
return topViewOrder;
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Specifies the content node to display inside the modal pane.
*/
public ObjectProperty<Node> contentProperty() {
return content;
}
protected final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content", null);
public Node getContent() {
return content.get();
}
public void setContent(Node node) {
this.content.set(node);
}
/**
* Indicates whether the modal pane is set to be on top or not.
* When changed, the {@link #viewOrderProperty()} value will be modified accordingly.
* See the {@link #getTopViewOrder()}.
*/
public BooleanProperty displayProperty() {
return display;
}
protected final BooleanProperty display = new SimpleBooleanProperty(this, "display", false);
public boolean isDisplay() {
return display.get();
}
public void setDisplay(boolean display) {
this.display.set(display);
}
/**
* Specifies the alignment of the content node.
*/
public ObjectProperty<Pos> alignmentProperty() {
return alignment;
}
protected final ObjectProperty<Pos> alignment = new SimpleObjectProperty<>(this, "alignment", Pos.CENTER);
public Pos getAlignment() {
return alignment.get();
}
public void setAlignment(Pos alignment) {
this.alignment.set(alignment);
}
/**
* The factory that provides a transition to be played on content appearance,
* i.e. when {@link #displayProperty()} is set to 'true'.
*/
public ObjectProperty<Function<Node, Animation>> inTransitionFactoryProperty() {
return inTransitionFactory;
}
protected final ObjectProperty<Function<Node, Animation>> inTransitionFactory = new SimpleObjectProperty<>(
this, "inTransitionFactory", node -> Animations.zoomIn(node, DEFAULT_DURATION_IN)
);
public Function<Node, Animation> getInTransitionFactory() {
return inTransitionFactory.get();
}
public void setInTransitionFactory(Function<Node, Animation> inTransitionFactory) {
this.inTransitionFactory.set(inTransitionFactory);
}
/**
* The factory that provides a transition to be played on content disappearance,
* i.e. when {@link #displayProperty()} is set to 'false'.
*/
public ObjectProperty<Function<Node, Animation>> outTransitionFactoryProperty() {
return outTransitionFactory;
}
protected final ObjectProperty<Function<Node, Animation>> outTransitionFactory = new SimpleObjectProperty<>(
this, "outTransitionFactory", node -> Animations.zoomOut(node, DEFAULT_DURATION_OUT)
);
public Function<Node, Animation> getOutTransitionFactory() {
return outTransitionFactory.get();
}
public void setOutTransitionFactory(Function<Node, Animation> outTransitionFactory) {
this.outTransitionFactory.set(outTransitionFactory);
}
/**
* Specifies whether the content should be treated as persistent or not.
*
* <p>By default, the modal pane exits when the ESC button is pressed or when
* the mouse is clicked outside the content area. This property prevents
* this behavior and plays a bouncing animation instead.
*/
public BooleanProperty persistentProperty() {
return persistent;
}
protected final BooleanProperty persistent = new SimpleBooleanProperty(this, "persistent", false);
public boolean getPersistent() {
return persistent.get();
}
public void setPersistent(boolean persistent) {
this.persistent.set(persistent);
}
///////////////////////////////////////////////////////////////////////////
// Public API //
///////////////////////////////////////////////////////////////////////////
/**
* A convenience method for setting the content of the modal pane content
* and triggering its display state at the same time.
*/
public void show(Node node) {
// calling show method with no content specified doesn't make any sense
Objects.requireNonNull(content, "Content cannot be null.");
setContent(node);
setDisplay(true);
}
/**
* A convenience method for clearing the content of the modal pane content
* and triggering its display state at the same time.
*/
public void hide(boolean clear) {
setDisplay(false);
if (clear) {
setContent(null);
}
}
/**
* See {@link #hide(boolean)}.
*/
public void hide() {
hide(false);
}
/**
* See {@link #usePredefinedTransitionFactories(Side, Duration, Duration)}.
*/
public void usePredefinedTransitionFactories(@Nullable Side side) {
usePredefinedTransitionFactories(side, DEFAULT_DURATION_IN, DEFAULT_DURATION_OUT);
}
/**
* Sets the predefined factory for both {@link #inTransitionFactoryProperty()} and
* {@link #outTransitionFactoryProperty()} based on content position.
*/
public void usePredefinedTransitionFactories(@Nullable Side side,
@Nullable Duration inDuration,
@Nullable Duration outDuration) {
Duration durIn = Objects.requireNonNullElse(inDuration, DEFAULT_DURATION_IN);
Duration durOut = Objects.requireNonNullElse(outDuration, DEFAULT_DURATION_OUT);
if (side == null) {
setInTransitionFactory(node -> Animations.zoomIn(node, durIn));
setOutTransitionFactory(node -> Animations.fadeOut(node, durOut));
} else {
switch (side) {
case TOP -> {
setInTransitionFactory(node -> Animations.slideInDown(node, durIn));
setOutTransitionFactory(node -> Animations.slideOutUp(node, durOut));
}
case RIGHT -> {
setInTransitionFactory(node -> Animations.slideInRight(node, durIn));
setOutTransitionFactory(node -> Animations.slideOutRight(node, durOut));
}
case BOTTOM -> {
setInTransitionFactory(node -> Animations.slideInUp(node, durIn));
setOutTransitionFactory(node -> Animations.slideOutDown(node, durOut));
}
case LEFT -> {
setInTransitionFactory(node -> Animations.slideInLeft(node, durIn));
setOutTransitionFactory(node -> Animations.slideOutLeft(node, durOut));
}
}
}
}
}

@ -0,0 +1,272 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import atlantafx.base.util.Animations;
import java.util.List;
import javafx.animation.Animation;
import javafx.animation.Timeline;
import javafx.beans.value.ChangeListener;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SkinBase;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import org.jetbrains.annotations.Nullable;
/**
* The default skin for the {@link ModalPane} control.
*/
public class ModalPaneSkin extends SkinBase<ModalPane> {
protected ModalPane control;
protected final StackPane root;
protected final ScrollPane scrollPane;
protected final StackPane contentWrapper;
protected final EventHandler<KeyEvent> keyHandler = createKeyHandler();
protected final EventHandler<MouseEvent> mouseHandler = createMouseHandler();
protected final ChangeListener<Animation.Status> animationInListener = createAnimationInListener();
protected final ChangeListener<Animation.Status> animationOutListener = createAnimationOutListener();
protected @Nullable List<ScrollBar> scrollbars;
protected @Nullable Animation inTransition;
protected @Nullable Animation outTransition;
protected ModalPaneSkin(ModalPane control) {
super(control);
root = new StackPane();
contentWrapper = new StackPane();
contentWrapper.getStyleClass().add("scrollable-content");
contentWrapper.setAlignment(Pos.CENTER);
scrollPane = new ScrollPane();
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
scrollPane.setFitToHeight(true);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
scrollPane.setFitToWidth(true);
scrollPane.setMaxHeight(20_000); // scroll pane won't work without height specified
scrollPane.setContent(contentWrapper);
getChildren().add(scrollPane);
control.getStyleClass().add("modal-pane");
doHide();
registerListeners();
}
protected void registerListeners() {
registerChangeListener(getSkinnable().contentProperty(), obs -> {
@Nullable Node content = getSkinnable().getContent();
if (content != null) {
contentWrapper.getChildren().setAll(content);
} else {
contentWrapper.getChildren().clear();
}
// JavaFX defers initial layout until node is first _shown_ on the scene,
// which means that animations that use node bounds won't work.
// So, we have to call it manually to init boundsInParent beforehand.
contentWrapper.layout();
});
registerChangeListener(getSkinnable().displayProperty(), obs -> {
boolean display = getSkinnable().isDisplay();
if (display) {
show();
} else {
hide();
}
});
registerChangeListener(getSkinnable().inTransitionFactoryProperty(), obs -> {
// invalidate cached value
if (inTransition != null) {
inTransition.statusProperty().removeListener(animationInListener);
}
inTransition = null;
});
registerChangeListener(getSkinnable().outTransitionFactoryProperty(), obs -> {
// invalidate cached value
if (outTransition != null) {
outTransition.statusProperty().removeListener(animationOutListener);
}
outTransition = null;
});
contentWrapper.paddingProperty().bind(getSkinnable().paddingProperty());
contentWrapper.alignmentProperty().bind(getSkinnable().alignmentProperty());
// Hide overlay by pressing ESC.
// It only works when modal pane or one of its children has focus.
scrollPane.addEventHandler(KeyEvent.KEY_PRESSED, keyHandler);
// Hide overlay by clicking outside the content area. Don't use MOUSE_CLICKED,
// because it's the same as MOUSE_RELEASED event, thus it doesn't prevent case
// when user pressed mouse button inside the content and released outside of it.
scrollPane.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);
}
@Override
public void dispose() {
super.dispose();
unregisterChangeListeners(getSkinnable().contentProperty());
unregisterChangeListeners(getSkinnable().displayProperty());
unregisterChangeListeners(getSkinnable().inTransitionFactoryProperty());
unregisterChangeListeners(getSkinnable().outTransitionFactoryProperty());
contentWrapper.paddingProperty().unbind();
contentWrapper.alignmentProperty().unbind();
scrollPane.removeEventFilter(KeyEvent.KEY_PRESSED, keyHandler);
scrollPane.removeEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);
}
@SuppressWarnings("ShortCircuitBoolean")
protected boolean isClickInArea(MouseEvent e, Node area) {
return (e.getX() >= area.getLayoutX() & e.getX() <= area.getLayoutX() + area.getLayoutBounds().getWidth())
&& (e.getY() >= area.getLayoutY() & e.getY() <= area.getLayoutY() + area.getLayoutBounds().getHeight());
}
protected EventHandler<KeyEvent> createKeyHandler() {
return event -> {
if (event.getCode() == KeyCode.ESCAPE) {
if (getSkinnable().getPersistent()) {
createCloseBlockedAnimation().playFromStart();
} else {
hideAndConsume(event);
}
}
};
}
protected EventHandler<MouseEvent> createMouseHandler() {
return event -> {
@Nullable Node content = getSkinnable().getContent();
if (event.getButton() != MouseButton.PRIMARY) {
return;
}
if (content == null) {
hideAndConsume(event);
return;
}
if (isClickInArea(event, content)) {
return;
}
if (scrollbars == null || scrollbars.isEmpty()) {
scrollbars = scrollPane.lookupAll(".scroll-bar").stream()
.filter(node -> node instanceof ScrollBar)
.map(node -> (ScrollBar) node)
.toList();
}
var scrollBarClick = scrollbars.stream().anyMatch(scrollBar -> isClickInArea(event, scrollBar));
if (!scrollBarClick) {
if (getSkinnable().getPersistent()) {
createCloseBlockedAnimation().playFromStart();
} else {
hideAndConsume(event);
}
}
};
}
protected ChangeListener<Animation.Status> createAnimationInListener() {
return (obs, old, val) -> {
if (val == Animation.Status.RUNNING) {
doShow();
}
};
}
protected ChangeListener<Animation.Status> createAnimationOutListener() {
return (obs, old, val) -> {
if (val == Animation.Status.STOPPED) {
doHide();
}
};
}
protected Timeline createCloseBlockedAnimation() {
return Animations.zoomOut(getSkinnable().getContent(), Duration.millis(100), 0.98);
}
protected void show() {
if (getSkinnable().getViewOrder() <= getSkinnable().getTopViewOrder()) {
return;
}
@Nullable Node content = getSkinnable().getContent();
if (content == null) {
doShow();
return;
}
if (inTransition == null && getSkinnable().getInTransitionFactory() != null) {
inTransition = getSkinnable().getInTransitionFactory().apply(content);
inTransition.statusProperty().addListener(animationInListener);
}
if (inTransition != null) {
inTransition.playFromStart();
} else {
doShow();
}
}
protected void hide() {
if (getSkinnable().getViewOrder() >= ModalPane.Z_BACK) {
return;
}
@Nullable Node content = getSkinnable().getContent();
if (content == null) {
doHide();
return;
}
if (outTransition == null && getSkinnable().getOutTransitionFactory() != null) {
outTransition = getSkinnable().getOutTransitionFactory().apply(content);
outTransition.statusProperty().addListener(animationOutListener);
}
if (outTransition != null) {
outTransition.playFromStart();
} else {
doHide();
}
}
protected void hideAndConsume(Event e) {
hide();
e.consume();
}
protected void doShow() {
getSkinnable().setDisplay(true);
getSkinnable().setOpacity(1);
getSkinnable().setViewOrder(getSkinnable().getTopViewOrder());
}
protected void doHide() {
getSkinnable().setOpacity(0);
getSkinnable().setViewOrder(ModalPane.Z_BACK);
getSkinnable().setDisplay(false);
}
}

@ -0,0 +1,186 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.Control;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Skin;
import javafx.scene.layout.Region;
import org.jetbrains.annotations.Nullable;
/**
* A control that is intended for displaying notifications to users as pop-ups.
* It is customizable with different colors and icons, can contain a graphic or image,
* along with the message and additional actions for users to take.
*/
public class Notification extends Control {
/**
* Creates an empty Notification.
*/
public Notification() {
this(null, null);
}
/**
* Creates a Notification with initial message text.
*
* @param message A string for the notification message.
*/
public Notification(@Nullable @NamedArg("message") String message) {
this(message, null);
}
/**
* Creates a Notification with initial message text and graphic.
*
* @param message A string for the notification message.
* @param graphic A graphic or icon.
*/
public Notification(@Nullable @NamedArg("message") String message,
@Nullable @NamedArg("graphic") Node graphic) {
super();
setMessage(message);
setGraphic(graphic);
// set reasonable default width
setPrefWidth(400);
setMaxWidth(Region.USE_PREF_SIZE);
getStyleClass().add("notification");
}
@Override
protected Skin<?> createDefaultSkin() {
return new NotificationSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents an optional graphical component that can be displayed alongside
* the notification message.
*/
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
public Node getGraphic() {
return graphic.get();
}
public void setGraphic(Node graphic) {
this.graphic.set(graphic);
}
/**
* Stores a short text message that will be displayed to users when the
* notification appears. This property doesn't support the formatted text.
*/
public StringProperty messageProperty() {
return message;
}
private final StringProperty message = new SimpleStringProperty(this, "message");
public String getMessage() {
return message.get();
}
public void setMessage(String message) {
this.message.set(message);
}
/**
* Specifies the primary actions associated with this notification.
*
* <p>This property is used to store one or more action buttons that will
* be displayed at the bottom of the notification when it appears. These
* buttons will be placed inside the {@link ButtonBar} and use the alignment
* that is described in the ButtonBar documentation.
*/
public ReadOnlyObjectProperty<ObservableList<Button>> primaryActionsProperty() {
return primaryActions.getReadOnlyProperty();
}
private final ReadOnlyObjectWrapper<ObservableList<Button>> primaryActions =
new ReadOnlyObjectWrapper<>(this, "primaryActions", FXCollections.observableArrayList());
public ObservableList<Button> getPrimaryActions() {
return primaryActions.get();
}
public void setPrimaryActions(ObservableList<Button> buttons) {
this.primaryActions.set(buttons);
}
public void setPrimaryActions(Button... buttons) {
getPrimaryActions().setAll(buttons);
}
/**
* Specifies the secondary actions associated with this notification.
*
* <p>This property is used to store one or more menu items that will be displayed
* as a dropdown menu at the top corner of the notification when it appears.
*
* <p>The dropdown menu button will not appear if the list is empty.
*/
public ReadOnlyObjectProperty<ObservableList<MenuItem>> secondaryActionsProperty() {
return secondaryActions.getReadOnlyProperty();
}
private final ReadOnlyObjectWrapper<ObservableList<MenuItem>> secondaryActions =
new ReadOnlyObjectWrapper<>(this, "secondaryActions", FXCollections.observableArrayList());
public ObservableList<MenuItem> getSecondaryActions() {
return secondaryActions.get();
}
public void setSecondaryActions(ObservableList<MenuItem> items) {
this.secondaryActions.set(items);
}
public void setSecondaryActions(MenuItem... items) {
getSecondaryActions().setAll(items);
}
/**
* Specifies the close handler used to dismiss this notification.
*
* <p>The close button will not appear if the handler is not set for it.
*/
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");
public EventHandler<? super Event> getOnClose() {
return onClose.get();
}
public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose);
}
}

@ -0,0 +1,179 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
/**
* The default skin for the {@link Notification} control.
*/
public class NotificationSkin extends SkinBase<Notification> {
protected final VBox container = new VBox();
protected final HBox header = new HBox();
protected final StackPane graphicSlot = new StackPane();
protected final ChangeListener<Node> graphicSlotListener = new SlotListener(graphicSlot);
protected final TextFlow messageText = new TextFlow();
protected final StackPane closeButton = new StackPane();
protected final StackPane closeButtonIcon = new StackPane();
protected final StackPane menuButton = new StackPane();
protected final StackPane menuButtonIcon = new StackPane();
protected final ContextMenu actionsMenu = new ContextMenu();
protected final HBox actionsBox = new HBox();
protected final ButtonBar buttonBar = new ButtonBar();
protected NotificationSkin(Notification control) {
super(control);
// == GRAPHIC ==
graphicSlot.getStyleClass().add("graphic");
control.graphicProperty().addListener(graphicSlotListener);
graphicSlotListener.changed(control.graphicProperty(), null, control.getGraphic());
// == MESSAGE ==
messageText.getStyleClass().add("message");
HBox.setHgrow(messageText, Priority.ALWAYS);
setMessageText();
registerChangeListener(control.messageProperty(), o -> setMessageText());
// text wrapping won't work without this
messageText.setMaxWidth(Double.MAX_VALUE);
messageText.setMinHeight(Region.USE_PREF_SIZE);
// == TOP BUTTONS ==
menuButton.getStyleClass().add("secondary-menu-button");
menuButton.getChildren().setAll(menuButtonIcon);
menuButton.setOnMouseClicked(e -> actionsMenu.show(
menuButton,
menuButton.localToScreen(menuButton.getLayoutBounds()).getMinX(),
menuButton.localToScreen(menuButton.getLayoutBounds()).getMaxY()
));
menuButton.setVisible(!getSkinnable().getSecondaryActions().isEmpty());
menuButton.setManaged(!getSkinnable().getSecondaryActions().isEmpty());
menuButtonIcon.getStyleClass().add("icon");
Bindings.bindContent(actionsMenu.getItems(), getSkinnable().getSecondaryActions());
registerListChangeListener(actionsMenu.getItems(), o -> {
menuButton.setVisible(!getSkinnable().getSecondaryActions().isEmpty());
menuButton.setManaged(!getSkinnable().getSecondaryActions().isEmpty());
});
closeButton.getStyleClass().add("close-button");
closeButton.getChildren().setAll(closeButtonIcon);
closeButton.setOnMouseClicked(e -> handleClose());
closeButton.setVisible(control.getOnClose() != null);
closeButton.setManaged(control.getOnClose() != null);
closeButtonIcon.getStyleClass().add("icon");
registerChangeListener(control.onCloseProperty(), o -> {
closeButton.setVisible(getSkinnable().getOnClose() != null);
closeButton.setManaged(getSkinnable().getOnClose() != null);
});
actionsBox.getStyleClass().add("actions");
actionsBox.getChildren().setAll(menuButton, closeButton);
actionsBox.setFillHeight(false);
HBox.setMargin(actionsBox, new Insets(-8, -8, 0, 0));
// == HEADER ==
// use pref size for slots, or they will be resized
// to the bare minimum due to Priority.ALWAYS
graphicSlot.setMinWidth(Region.USE_PREF_SIZE);
actionsBox.setMinWidth(Region.USE_PREF_SIZE);
// do not resize children or container won't restore
// to its original size after expanding
header.setFillHeight(false);
header.getStyleClass().add("header");
header.getChildren().setAll(graphicSlot, messageText, actionsBox);
header.setAlignment(Pos.TOP_LEFT);
// == BUTTON BAR ==
buttonBar.getStyleClass().add("button-bar");
buttonBar.setVisible(!getSkinnable().getPrimaryActions().isEmpty());
buttonBar.setManaged(!getSkinnable().getPrimaryActions().isEmpty());
Bindings.bindContent(buttonBar.getButtons(), getSkinnable().getPrimaryActions());
registerListChangeListener(buttonBar.getButtons(), o -> {
buttonBar.setVisible(!getSkinnable().getPrimaryActions().isEmpty());
buttonBar.setManaged(!getSkinnable().getPrimaryActions().isEmpty());
});
// == CONTAINER ==
container.getChildren().setAll(header, buttonBar);
container.getStyleClass().add("container");
getChildren().setAll(container);
}
protected void setMessageText() {
if (!messageText.getChildren().isEmpty()) {
messageText.getChildren().clear();
}
if (getSkinnable().getMessage() != null && !getSkinnable().getMessage().isBlank()) {
messageText.getChildren().setAll(new Text(getSkinnable().getMessage()));
}
}
protected void handleClose() {
if (getSkinnable().getOnClose() != null) {
getSkinnable().getOnClose().handle(new Event(Event.ANY));
}
}
protected double calcHeight() {
var messageHeight = messageText.getBoundsInLocal().getHeight();
return Math.max(Math.max(graphicSlot.getHeight(), actionsBox.getHeight()), messageHeight)
+ (buttonBar.isManaged() ? buttonBar.getHeight() + container.getSpacing() : 0)
+ header.getPadding().getTop()
+ header.getPadding().getBottom()
+ container.getPadding().getTop()
+ container.getPadding().getBottom();
}
@Override
protected double computeMinHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
return calcHeight();
}
@Override
public void dispose() {
Bindings.unbindContent(actionsMenu.getItems(), getSkinnable().getSecondaryActions());
unregisterListChangeListeners(actionsMenu.getItems());
Bindings.unbindContent(buttonBar.getButtons(), getSkinnable().getPrimaryActions());
unregisterListChangeListeners(buttonBar.getButtons());
getSkinnable().graphicProperty().removeListener(graphicSlotListener);
unregisterChangeListeners(getSkinnable().messageProperty());
unregisterChangeListeners(getSkinnable().onCloseProperty());
super.dispose();
}
}

@ -0,0 +1,112 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package atlantafx.base.controls;
import atlantafx.base.util.PasswordTextFormatter;
import javafx.beans.NamedArg;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
/**
* A convenience wrapper for instantiating a {@link CustomTextField}
* with a {@code PasswordTextFormatter}. For additional info refer to the
* {@link PasswordTextFormatter} documentation.
*/
public class PasswordTextField extends CustomTextField {
protected final ReadOnlyObjectWrapper<PasswordTextFormatter> formatter
= new ReadOnlyObjectWrapper<>(this, "formatter");
/**
* Creates an empty PasswordTextField.
*/
public PasswordTextField() {
this("", PasswordTextFormatter.BULLET);
}
/**
* Creates a PasswordTextField with initial text content.
*
* @param text A string for text content.
*/
public PasswordTextField(@NamedArg("text") String text) {
this(text, PasswordTextFormatter.BULLET);
}
/**
* Creates a PasswordTextField with initial text content and bullet character.
*
* @param text A string for text content.
* @param bullet A bullet character to mask the password string.
*/
protected PasswordTextField(@NamedArg("text") String text,
@NamedArg("bullet") char bullet) {
super(text);
formatter.set(PasswordTextFormatter.create(this, bullet));
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* See {@link PasswordTextFormatter#passwordProperty()}.
*/
public ReadOnlyStringProperty passwordProperty() {
return formatter.get().passwordProperty();
}
/**
* See {@link PasswordTextFormatter#getPassword()}.
*/
public String getPassword() {
return formatter.get().getPassword();
}
/**
* See {@link PasswordTextFormatter#revealPasswordProperty()}.
*/
public BooleanProperty revealPasswordProperty() {
return formatter.get().revealPasswordProperty();
}
/**
* See {@link PasswordTextFormatter#getRevealPassword()}.
*/
public boolean getRevealPassword() {
return formatter.get().getRevealPassword();
}
/**
* See {@link PasswordTextFormatter#setRevealPassword(boolean)}.
*/
public void setRevealPassword(boolean reveal) {
formatter.get().setRevealPassword(reveal);
}
}

@ -0,0 +1,925 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2022 ControlsFX
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package atlantafx.base.controls;
import static javafx.scene.input.MouseEvent.MOUSE_CLICKED;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.WeakChangeListener;
import javafx.event.EventHandler;
import javafx.event.WeakEventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.PopupControl;
import javafx.scene.control.Skin;
import javafx.scene.layout.StackPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
/**
* A control that is intended to provide detailed information about
* an owning node in a popup window. The popup window has a lightweight
* appearance (no default window decorations) and an arrow pointing at the owner.
* Due to the nature of popup windows the Popover will move around with the parent
* window when the user drags it.
*
* <p>The Popover can be detached from the owning node by dragging it away from the
* owner. It stops displaying an arrow and starts displaying a title and a close
* icon.
*
* <p>Example
*
* <pre>{@code
* var textFlow = new TextFlow(new Text("Some content"));
* textFlow.setPrefWidth(300);
*
* var popover = new Popover(textFlow);
* popover.setTitle("Title");
*
* var ownerLink = new Hyperlink("Show popover");
* ownerLink.setOnAction(e -> popover.show(ownerLink));
* }</pre>
*/
public class Popover extends PopupControl {
private static final String DEFAULT_STYLE_CLASS = "popover";
private static final Duration DEFAULT_FADE_DURATION = Duration.seconds(.2);
private final StackPane root = new StackPane();
private double targetX;
private double targetY;
/**
* Creates a popover with a label as the content node.
*/
public Popover() {
super();
getStyleClass().add(DEFAULT_STYLE_CLASS);
setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
setOnHiding(evt -> setDetached(false));
// create some initial content
Label label = new Label("No Content");
label.setPrefSize(200, 200);
label.setPadding(new Insets(4));
setContentNode(label);
InvalidationListener repositionListener = observable -> {
if (isShowing() && !isDetached()) {
show(getOwnerNode(), targetX, targetY);
adjustWindowLocation();
}
};
arrowSize.addListener(repositionListener);
cornerRadius.addListener(repositionListener);
arrowLocation.addListener(repositionListener);
arrowIndent.addListener(repositionListener);
headerAlwaysVisible.addListener(repositionListener);
// a detached popover should of course not automatically hide itself
detached.addListener(it -> setAutoHide(!isDetached()));
setAutoHide(true);
}
/**
* Creates a popover with the given node as the content node.
*
* @param content The content shown by the popover.
*/
public Popover(Node content) {
this();
setContentNode(content);
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new PopoverSkin(this);
}
/**
* The root pane stores the content node of the popover. It is accessible
* via this method in order to support proper styling.
*
* <p>Example:
*
* <pre>{@code
* Popover popOver = new Popover();
* popOver.getRoot().getStylesheets().add(...);
* }</pre>
*
* @return the root pane
*/
public final StackPane getRoot() {
return root;
}
///////////////////////////////////////////////////////////////////////////
// Listeners //
///////////////////////////////////////////////////////////////////////////
private final InvalidationListener hideListener = observable -> {
if (!isDetached()) {
hide(Duration.ZERO);
}
};
private final WeakInvalidationListener weakHideListener = new WeakInvalidationListener(hideListener);
private final ChangeListener<Number> xListener = (value, oldX, newX) -> {
if (!isDetached()) {
setAnchorX(getAnchorX() + (newX.doubleValue() - oldX.doubleValue()));
}
};
private final WeakChangeListener<Number> weakXListener = new WeakChangeListener<>(xListener);
private final ChangeListener<Number> yListener = (value, oldY, newY) -> {
if (!isDetached()) {
setAnchorY(getAnchorY() + (newY.doubleValue() - oldY.doubleValue()));
}
};
private final WeakChangeListener<Number> weakYListener = new WeakChangeListener<>(yListener);
private Window ownerWindow;
private final EventHandler<WindowEvent> closePopoverOnOwnerWindowCloseLambda
= event -> ownerWindowHiding();
private final WeakEventHandler<WindowEvent> closePopoverOnOwnerWindowClose =
new WeakEventHandler<>(closePopoverOnOwnerWindowCloseLambda);
///////////////////////////////////////////////////////////////////////////
// API //
///////////////////////////////////////////////////////////////////////////
/**
* Shows the popover in a position relative to the edges of the given owner
* node. The position is dependent on the arrow location. If the arrow is
* pointing to the right then the popover will be placed to the left of the
* given owner. If the arrow points up then the popover will be placed
* below the given owner node. The arrow will slightly overlap with the
* owner node.
*
* @param owner The owner of the popover.
*/
public final void show(Node owner) {
show(owner, 4);
}
/**
* Shows the popover in a position relative to the edges of the given owner
* node. The position is dependent on the arrow location. If the arrow is
* pointing to the right then the popover will be placed to the left of the
* given owner. If the arrow points up then the popover will be placed
* below the given owner node.
*
* @param owner The owner of the popover.
* @param offset If negative specifies the distance to the owner node or when
* positive specifies the number of pixels that the arrow will
* overlap with the owner node (positive values are recommended).
*/
public final void show(Node owner, double offset) {
Objects.requireNonNull(owner, "Owner node cannot be null!");
Bounds bounds = owner.localToScreen(owner.getBoundsInLocal());
if (bounds == null) {
throw new IllegalStateException(
"The owner node is not added to the scene. It cannot be used as a popover anchor."
);
}
switch (getArrowLocation()) {
case BOTTOM_CENTER, BOTTOM_LEFT, BOTTOM_RIGHT -> show(
owner, bounds.getMinX() + bounds.getWidth() / 2, bounds.getMinY() + offset
);
case LEFT_BOTTOM, LEFT_CENTER, LEFT_TOP -> show(
owner, bounds.getMaxX() - offset, bounds.getMinY() + bounds.getHeight() / 2
);
case RIGHT_BOTTOM, RIGHT_CENTER, RIGHT_TOP -> show(
owner, bounds.getMinX() + offset, bounds.getMinY() + bounds.getHeight() / 2
);
case TOP_CENTER, TOP_LEFT, TOP_RIGHT -> show(
owner, bounds.getMinX() + bounds.getWidth() / 2, bounds.getMinY() + bounds.getHeight() - offset
);
}
}
/**
* {@inheritDoc}
*/
@Override
public final void show(Window owner) {
super.show(owner);
ownerWindow = owner;
if (isAnimated()) {
showFadeInAnimation(getFadeInDuration());
}
ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING, closePopoverOnOwnerWindowClose);
}
/**
* {@inheritDoc}
*/
@Override
public final void show(Window ownerWindow, double anchorX, double anchorY) {
super.show(ownerWindow, anchorX, anchorY);
this.ownerWindow = ownerWindow;
if (isAnimated()) {
showFadeInAnimation(getFadeInDuration());
}
ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING, closePopoverOnOwnerWindowClose);
}
/**
* Makes the popover visible at the give location and associates it with
* the given owner node. The x and y coordinate will be the target location
* of the arrow of the popover and not the location of the window.
*
* @param owner The owning node.
* @param x The x coordinate for the popover arrow tip.
* @param y The y coordinate for the popover arrow tip.
*/
@Override
public final void show(Node owner, double x, double y) {
show(owner, x, y, getFadeInDuration());
}
/**
* Makes the popover visible at the give location and associates it with
* the given owner node. The x and y coordinate will be the target location
* of the arrow of the popover and not the location of the window.
*
* @param owner The owning node.
* @param x The x coordinate for the popover arrow tip.
* @param y The y coordinate for the popover arrow tip.
* @param fadeInDuration The time it takes for the popover to be fully visible.
* This duration takes precedence over the fade-in property without setting.
*/
public final void show(Node owner, double x, double y, Duration fadeInDuration) {
// Calling show() a second time without first closing the popover
// causes it to be placed at the wrong location.
if (ownerWindow != null && isShowing()) {
super.hide();
}
targetX = x;
targetY = y;
if (owner == null) {
throw new NullPointerException("Owner Node cannot be null!");
}
// this is all needed because children windows do not get their x and y
// coordinate updated when the owning window gets moved by the user
if (ownerWindow != null) {
ownerWindow.xProperty().removeListener(weakXListener);
ownerWindow.yProperty().removeListener(weakYListener);
ownerWindow.widthProperty().removeListener(weakHideListener);
ownerWindow.heightProperty().removeListener(weakHideListener);
}
ownerWindow = owner.getScene().getWindow();
ownerWindow.xProperty().addListener(weakXListener);
ownerWindow.yProperty().addListener(weakYListener);
ownerWindow.widthProperty().addListener(weakHideListener);
ownerWindow.heightProperty().addListener(weakHideListener);
setOnShown(evt -> {
// the user clicked somewhere into the transparent background,
// if this is the case then hide the window (when attached)
getScene().addEventHandler(MOUSE_CLICKED, mouseEvent -> {
if (mouseEvent.getTarget().equals(getScene().getRoot()) && !isDetached()) {
hide();
}
});
// move the window so that the arrow will end up pointing at the target coordinates
adjustWindowLocation();
// Popover flickering fix:
// The reason of flickering is that for calculating popup bounds show() method have to
// be called PRIOR TO adjusting window position. So, in a very short period we see the
// window in its initial position. Ideally, we have to call adjustWindowLocation() right
// after window is added to the scene, but before it's rendered, which is not possible
// due to JavaFX async nature. The only way seems to start popover as invisible (not opaque)
// and then restore its visibility after a fixed delay to hide window repositioning.
// Still it's not a 100% guarantee,but better than nothing.
int delay =
Math.min((int) Objects.requireNonNullElse(fadeInDuration, DEFAULT_FADE_DURATION).toMillis() / 2, 250);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> getSkin().getNode().setVisible(true));
}
}, delay);
});
super.show(owner, x, y);
if (isAnimated()) {
showFadeInAnimation(Objects.requireNonNullElse(fadeInDuration, DEFAULT_FADE_DURATION));
}
ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING, closePopoverOnOwnerWindowClose);
}
private void showFadeInAnimation(Duration fadeInDuration) {
// fade in
Node skinNode = getSkin().getNode();
skinNode.setOpacity(0);
FadeTransition fadeIn = new FadeTransition(fadeInDuration, skinNode);
fadeIn.setFromValue(0);
fadeIn.setToValue(1);
fadeIn.play();
}
private void ownerWindowHiding() {
hide(Duration.ZERO);
if (ownerWindow != null) {
// remove EventFilter to prevent memory leak
ownerWindow.removeEventFilter(WindowEvent.WINDOW_HIDING, closePopoverOnOwnerWindowClose);
}
}
/**
* Hides the popover by quickly changing its opacity to 0.
*
* @see #hide(Duration)
*/
@Override
public final void hide() {
hide(getFadeOutDuration());
}
/**
* Hides the popover by quickly changing its opacity to 0.
*
* @param fadeOutDuration The duration of the fade transition that is being used to
* change the opacity of the popover.
*/
public final void hide(Duration fadeOutDuration) {
if (fadeOutDuration == null) {
fadeOutDuration = DEFAULT_FADE_DURATION;
}
if (isShowing()) {
if (isAnimated()) {
// fade out
Node skinNode = getSkin().getNode();
FadeTransition fadeOut = new FadeTransition(fadeOutDuration, skinNode);
fadeOut.setFromValue(skinNode.getOpacity());
fadeOut.setToValue(0);
fadeOut.setOnFinished(evt -> super.hide());
fadeOut.play();
} else {
super.hide();
}
getSkin().getNode().setVisible(false);
}
}
private void adjustWindowLocation() {
Bounds bounds = getSkin().getNode().getBoundsInParent();
switch (getArrowLocation()) {
case TOP_CENTER, TOP_LEFT, TOP_RIGHT -> {
setAnchorX(getAnchorX() + bounds.getMinX() - computeXOffset());
setAnchorY(getAnchorY() + bounds.getMinY() + getArrowSize());
}
case LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM -> {
setAnchorX(getAnchorX() + bounds.getMinX() + getArrowSize());
setAnchorY(getAnchorY() + bounds.getMinY() - computeYOffset());
}
case BOTTOM_CENTER, BOTTOM_LEFT, BOTTOM_RIGHT -> {
setAnchorX(getAnchorX() + bounds.getMinX() - computeXOffset());
setAnchorY(getAnchorY() - bounds.getMinY() - bounds.getMaxY() - 1);
}
case RIGHT_TOP, RIGHT_BOTTOM, RIGHT_CENTER -> {
setAnchorX(getAnchorX() - bounds.getMinX() - bounds.getMaxX() - 1);
setAnchorY(getAnchorY() + bounds.getMinY() - computeYOffset());
}
}
}
private double computeXOffset() {
return switch (getArrowLocation()) {
case TOP_LEFT, BOTTOM_LEFT -> (
getCornerRadius() + getArrowIndent() + getArrowSize()
);
case TOP_CENTER, BOTTOM_CENTER -> (
getContentNode().prefWidth(-1) / 2
);
case TOP_RIGHT, BOTTOM_RIGHT -> (
getContentNode().prefWidth(-1) - getArrowIndent() - getCornerRadius() - getArrowSize()
);
default -> 0;
};
}
private double computeYOffset() {
double prefContentHeight = getContentNode().prefHeight(-1);
return switch (getArrowLocation()) {
case LEFT_TOP, RIGHT_TOP -> getCornerRadius() + getArrowIndent() + getArrowSize();
case LEFT_CENTER, RIGHT_CENTER -> Math.max(
prefContentHeight, 2 * (getCornerRadius() + getArrowIndent() + getArrowSize())
) / 2;
case LEFT_BOTTOM, RIGHT_BOTTOM -> Math.max(
prefContentHeight - getCornerRadius() - getArrowIndent() - getArrowSize(),
getCornerRadius() + getArrowIndent() + getArrowSize()
);
default -> 0;
};
}
/**
* Detaches the popover from the owning node. The popover will no longer
* display an arrow pointing at the owner node.
*/
public final void detach() {
if (isDetachable()) {
setDetached(true);
}
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Specifies the content shown by the popover.
*/
public final ObjectProperty<Node> contentNodeProperty() {
return contentNode;
}
private final ObjectProperty<Node> contentNode = new SimpleObjectProperty<>(this, "contentNode") {
@Override
public void setValue(Node node) {
if (node == null) {
throw new NullPointerException("Node cannot be null!");
}
this.set(node);
}
};
/**
* Returns the value of the content property.
*
* @return the content node.
* @see #contentNodeProperty()
*/
public final Node getContentNode() {
return contentNodeProperty().get();
}
/**
* Sets the value of the content property.
*
* @param content The new content node value.
* @see #contentNodeProperty()
*/
public final void setContentNode(Node content) {
contentNodeProperty().set(content);
}
/**
* Determines whether the {@link Popover} header should remain visible or not,
* even while attached.
*/
public final BooleanProperty headerAlwaysVisibleProperty() {
return headerAlwaysVisible;
}
private final BooleanProperty headerAlwaysVisible = new SimpleBooleanProperty(this, "headerAlwaysVisible");
/**
* Sets the value of the headerAlwaysVisible property.
*
* @param visible If "true", then the header is visible even while attached.
* @see #headerAlwaysVisibleProperty()
*/
public final void setHeaderAlwaysVisible(boolean visible) {
headerAlwaysVisible.setValue(visible);
}
/**
* Returns the value of the detachable property.
*
* @return "true" if the header is visible even while attached
* @see #headerAlwaysVisibleProperty()
*/
public final boolean isHeaderAlwaysVisible() {
return headerAlwaysVisible.getValue();
}
/**
* Determines whether the header's close button should be available or not.
*/
public final BooleanProperty closeButtonEnabledProperty() {
return closeButtonEnabled;
}
private final BooleanProperty closeButtonEnabled = new SimpleBooleanProperty(this, "closeButtonEnabled", true);
/**
* Sets the value of the closeButtonEnabled property.
*
* @param enabled If "false", the popover will not be closeable by the header's close button.
* @see #closeButtonEnabledProperty()
*/
public final void setCloseButtonEnabled(boolean enabled) {
closeButtonEnabled.setValue(enabled);
}
/**
* Returns the value of the closeButtonEnabled property.
*
* @return "true" if the header's close button is enabled
* @see #closeButtonEnabledProperty()
*/
public final boolean isCloseButtonEnabled() {
return closeButtonEnabled.getValue();
}
/**
* Determines if the popover is detachable at all.
*/
public final BooleanProperty detachableProperty() {
return detachable;
}
private final BooleanProperty detachable = new SimpleBooleanProperty(this, "detachable", true);
/**
* Sets the value of the detachable property.
*
* @param detachable If "true" then the user can detach / tear off the popover.
* @see #detachableProperty()
*/
public final void setDetachable(boolean detachable) {
detachableProperty().set(detachable);
}
/**
* Returns the value of the detachable property.
*
* @return "true" if the user is allowed to detach / tear off the popover
* @see #detachableProperty()
*/
public final boolean isDetachable() {
return detachableProperty().get();
}
/**
* Determines whether the popover is detached from the owning node or not.
* A detached popover no longer shows an arrow pointing at the owner and
* features its own title bar.
*/
public final BooleanProperty detachedProperty() {
return detached;
}
private final BooleanProperty detached = new SimpleBooleanProperty(this, "detached", false);
/**
* Sets the value of the detached property.
*
* @param detached If "true" the popover will change its appearance to "detached" mode.
* @see #detachedProperty()
*/
public final void setDetached(boolean detached) {
detachedProperty().set(detached);
}
/**
* Returns the value of the detached property.
*
* @return "true" if the popover is currently detached
* @see #detachedProperty()
*/
public final boolean isDetached() {
return detachedProperty().get();
}
/**
* Controls the size of the arrow.
* Default value is "12".
*/
public final DoubleProperty arrowSizeProperty() {
return arrowSize;
}
private final DoubleProperty arrowSize = new SimpleDoubleProperty(this, "arrowSize", 12);
/**
* Returns the value of the arrow size property.
*
* @return the arrow size property value
* @see #arrowSizeProperty()
*/
public final double getArrowSize() {
return arrowSizeProperty().get();
}
/**
* Sets the value of the arrow size property.
*
* @param size The new value of the arrow size property.
* @see #arrowSizeProperty()
*/
public final void setArrowSize(double size) {
arrowSizeProperty().set(size);
}
/**
* Controls the distance between the arrow and the corners of the popover.
* Default value is "12".
*/
public final DoubleProperty arrowIndentProperty() {
return arrowIndent;
}
private final DoubleProperty arrowIndent = new SimpleDoubleProperty(this, "arrowIndent", 12);
/**
* Returns the value of the arrow indent property.
*
* @return the arrow indent value
* @see #arrowIndentProperty()
*/
public final double getArrowIndent() {
return arrowIndentProperty().get();
}
/**
* Sets the value of the arrow indent property.
*
* @param size The arrow indent value.
* @see #arrowIndentProperty()
*/
public final void setArrowIndent(double size) {
arrowIndentProperty().set(size);
}
/**
* Returns the corner radius property for the popover.
* Default value is "6".
*/
public final DoubleProperty cornerRadiusProperty() {
return cornerRadius;
}
private final DoubleProperty cornerRadius = new SimpleDoubleProperty(this, "cornerRadius", 6);
/**
* Returns the value of the corner radius property.
*
* @return the corner radius
* @see #cornerRadiusProperty()
*/
public final double getCornerRadius() {
return cornerRadiusProperty().get();
}
/**
* Sets the value of the corner radius property.
*
* @param radius The corner radius.
* @see #cornerRadiusProperty()
*/
public final void setCornerRadius(double radius) {
cornerRadiusProperty().set(radius);
}
/**
* Stores the title to display in the Popover's header.
*/
public final StringProperty titleProperty() {
return title;
}
private final StringProperty title = new SimpleStringProperty(this, "title", "Info");
/**
* Returns the value of the title property.
*
* @return the detached title
* @see #titleProperty()
*/
public final String getTitle() {
return titleProperty().get();
}
/**
* Sets the value of the title property.
*
* @param title The title to use when detached.
* @see #titleProperty()
*/
public final void setTitle(String title) {
if (title == null) {
throw new NullPointerException("Title cannot be null!");
}
titleProperty().set(title);
}
/**
* Stores the preferred arrow location. This might not be the actual
* location of the arrow if auto fix is enabled.
*
* @see #setAutoFix(boolean)
*/
public final ObjectProperty<ArrowLocation> arrowLocationProperty() {
return arrowLocation;
}
private final ObjectProperty<ArrowLocation> arrowLocation =
new SimpleObjectProperty<>(this, "arrowLocation", ArrowLocation.LEFT_TOP);
/**
* Sets the value of the arrow location property.
*
* @param location The requested location.
* @see #arrowLocationProperty()
*/
public final void setArrowLocation(ArrowLocation location) {
arrowLocationProperty().set(location);
}
/**
* Returns the value of the arrow location property.
*
* @return the preferred arrow location
* @see #arrowLocationProperty()
*/
public final ArrowLocation getArrowLocation() {
return arrowLocationProperty().get();
}
/**
* All possible arrow locations.
*/
public enum ArrowLocation {
LEFT_TOP,
LEFT_CENTER,
LEFT_BOTTOM,
RIGHT_TOP,
RIGHT_CENTER,
RIGHT_BOTTOM,
TOP_LEFT,
TOP_CENTER,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM_CENTER,
BOTTOM_RIGHT
}
/**
* Stores the fade-in duration. This should be set before calling <code>Popover.show(..)</code>.
*/
public final ObjectProperty<Duration> fadeInDurationProperty() {
return fadeInDuration;
}
private final ObjectProperty<Duration> fadeInDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
/**
* Returns the value of the fade-in duration property.
*
* @return the fade-in duration
* @see #fadeInDurationProperty()
*/
public final Duration getFadeInDuration() {
return fadeInDurationProperty().get();
}
/**
* Sets the value of the fade-in duration property. This should be set before calling
* Popover.show(..).
*
* @param duration The requested fade-in duration.
* @see #fadeInDurationProperty()
*/
public final void setFadeInDuration(Duration duration) {
fadeInDurationProperty().setValue(duration);
}
/**
* Stores the fade-out duration.
*/
public final ObjectProperty<Duration> fadeOutDurationProperty() {
return fadeOutDuration;
}
private final ObjectProperty<Duration> fadeOutDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
/**
* Returns the value of the fade-out duration property.
*
* @return the fade-out duration
* @see #fadeOutDurationProperty()
*/
public final Duration getFadeOutDuration() {
return fadeOutDurationProperty().get();
}
/**
* Sets the value of the fade-out duration property.
*
* @param duration The requested fade-out duration.
* @see #fadeOutDurationProperty()
*/
public final void setFadeOutDuration(Duration duration) {
fadeOutDurationProperty().setValue(duration);
}
/**
* Stores the "animated" flag. If true then the Popover will be shown / hidden with a short
* fade in / out animation.
*/
public final BooleanProperty animatedProperty() {
return animated;
}
private final SimpleBooleanProperty animated = new SimpleBooleanProperty(true);
/**
* Returns the value of the "animated" property.
*
* @return "true" if the Popover will be shown and hidden with a short fade animation
* @see #animatedProperty()
*/
public final boolean isAnimated() {
return animatedProperty().get();
}
/**
* Sets the value of the "animated" property.
*
* @param animated If "true" the Popover will be shown and hidden with a short fade animation.
* @see #animatedProperty()
*/
public final void setAnimated(boolean animated) {
animatedProperty().set(animated);
}
}

@ -0,0 +1,643 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013 - 2015, ControlsFX
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package atlantafx.base.controls;
import static atlantafx.base.controls.Popover.ArrowLocation;
import static java.lang.Double.MAX_VALUE;
import static javafx.geometry.Pos.TOP_RIGHT;
import static javafx.scene.control.ContentDisplay.GRAPHIC_ONLY;
import static javafx.scene.paint.Color.YELLOW;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.HLineTo;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.VLineTo;
import javafx.stage.Window;
/**
* The default skin for the {@link Popover} control.
*/
public class PopoverSkin implements Skin<Popover> {
private static final String DETACHED_STYLE_CLASS = "detached";
private double xOffset;
private double yOffset;
private boolean tornOff;
private final Path path;
private final Path clip;
private final BorderPane content;
private final StackPane titlePane;
private final StackPane stackPane;
private Point2D dragStartLocation;
private final Popover popover;
@SuppressWarnings("MissingCasesInEnumSwitch")
public PopoverSkin(final Popover popover) {
this.popover = popover;
stackPane = popover.getRoot();
stackPane.setPickOnBounds(false);
Bindings.bindContent(stackPane.getStyleClass(), popover.getStyleClass());
// the min width and height equal (2 * corner radius + 2 * arrow indent + 2 * arrow size)
stackPane.minWidthProperty().bind(
Bindings.add(Bindings.multiply(2, popover.arrowSizeProperty()),
Bindings.add(
Bindings.multiply(2, popover.cornerRadiusProperty()),
Bindings.multiply(2, popover.arrowIndentProperty())
)
)
);
stackPane.minHeightProperty().bind(stackPane.minWidthProperty());
Label title = new Label();
title.textProperty().bind(popover.titleProperty());
title.setMaxSize(MAX_VALUE, MAX_VALUE);
title.setAlignment(Pos.CENTER);
title.getStyleClass().add("text");
Label closeIcon = new Label();
closeIcon.setGraphic(createCloseIcon());
closeIcon.setMaxSize(MAX_VALUE, MAX_VALUE);
closeIcon.setContentDisplay(GRAPHIC_ONLY);
closeIcon.visibleProperty().bind(
popover.closeButtonEnabledProperty().and(
popover.detachedProperty().or(popover.headerAlwaysVisibleProperty())));
closeIcon.getStyleClass().add("icon");
closeIcon.setAlignment(TOP_RIGHT);
closeIcon.getGraphic().setOnMouseClicked(evt -> popover.hide());
titlePane = new StackPane();
titlePane.getChildren().add(title);
titlePane.getChildren().add(closeIcon);
titlePane.getStyleClass().add("title");
content = new BorderPane();
content.setCenter(popover.getContentNode());
content.getStyleClass().add("content");
if (popover.isDetached() || popover.isHeaderAlwaysVisible()) {
content.setTop(titlePane);
}
if (popover.isDetached()) {
popover.getStyleClass().add(DETACHED_STYLE_CLASS);
content.getStyleClass().add(DETACHED_STYLE_CLASS);
}
popover.headerAlwaysVisibleProperty().addListener((o, oV, isVisible) -> {
if (isVisible) {
content.setTop(titlePane);
} else if (!popover.isDetached()) {
content.setTop(null);
}
});
InvalidationListener updatePathListener = observable -> updatePath();
getPopupWindow().xProperty().addListener(updatePathListener);
getPopupWindow().yProperty().addListener(updatePathListener);
popover.arrowLocationProperty().addListener(updatePathListener);
popover.contentNodeProperty().addListener((obs, oldContent, newContent) -> content.setCenter(newContent));
popover.detachedProperty().addListener((value, oldDetached, newDetached) -> {
if (newDetached) {
popover.getStyleClass().add(DETACHED_STYLE_CLASS);
content.getStyleClass().add(DETACHED_STYLE_CLASS);
content.setTop(titlePane);
switch (getSkinnable().getArrowLocation()) {
case LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM -> popover.setAnchorX(
popover.getAnchorX() + popover.getArrowSize()
);
case TOP_LEFT, TOP_CENTER, TOP_RIGHT -> popover.setAnchorY(
popover.getAnchorY() + popover.getArrowSize()
);
}
} else {
popover.getStyleClass().remove(DETACHED_STYLE_CLASS);
content.getStyleClass().remove(DETACHED_STYLE_CLASS);
if (!popover.isHeaderAlwaysVisible()) {
content.setTop(null);
}
}
popover.sizeToScene();
updatePath();
});
path = new Path();
path.getStyleClass().add("border");
path.setManaged(false);
clip = new Path();
// the clip is a path and the path has to be filled with a color,
// otherwise clipping will not work.
clip.setFill(YELLOW);
createPathElements();
updatePath();
final EventHandler<MouseEvent> mousePressedHandler = evt -> {
if (popover.isDetachable() || popover.isDetached()) {
tornOff = false;
xOffset = evt.getScreenX();
yOffset = evt.getScreenY();
dragStartLocation = new Point2D(xOffset, yOffset);
}
};
final EventHandler<MouseEvent> mouseReleasedHandler = evt -> {
if (tornOff && !getSkinnable().isDetached()) {
tornOff = false;
getSkinnable().detach();
}
};
final EventHandler<MouseEvent> mouseDragHandler = evt -> {
if (popover.isDetachable() || popover.isDetached()) {
double deltaX = evt.getScreenX() - xOffset;
double deltaY = evt.getScreenY() - yOffset;
Window window = getSkinnable().getScene().getWindow();
window.setX(window.getX() + deltaX);
window.setY(window.getY() + deltaY);
xOffset = evt.getScreenX();
yOffset = evt.getScreenY();
if (dragStartLocation.distance(xOffset, yOffset) > 20) {
tornOff = true;
updatePath();
} else if (tornOff) {
tornOff = false;
updatePath();
}
}
};
stackPane.setOnMousePressed(mousePressedHandler);
stackPane.setOnMouseDragged(mouseDragHandler);
stackPane.setOnMouseReleased(mouseReleasedHandler);
stackPane.setVisible(false);
stackPane.getChildren().add(path);
stackPane.getChildren().add(content);
content.setClip(clip);
}
@Override
public Node getNode() {
return stackPane;
}
@Override
public Popover getSkinnable() {
return popover;
}
@Override
public void dispose() {
}
private Node createCloseIcon() {
Group group = new Group();
group.getStyleClass().add("graphics");
Circle circle = new Circle();
circle.getStyleClass().add("circle");
circle.setRadius(12);
circle.setCenterX(12);
circle.setCenterY(12);
group.getChildren().add(circle);
Line line1 = new Line();
line1.getStyleClass().add("line");
line1.setStartX(8);
line1.setStartY(8);
line1.setEndX(16);
line1.setEndY(16);
group.getChildren().add(line1);
Line line2 = new Line();
line2.getStyleClass().add("line");
line2.setStartX(16);
line2.setStartY(8);
line2.setEndX(8);
line2.setEndY(16);
group.getChildren().add(line2);
return group;
}
private MoveTo moveTo;
private QuadCurveTo topCurveTo, rightCurveTo, bottomCurveTo, leftCurveTo;
private HLineTo lineBTop, lineETop, lineHTop, lineKTop;
private LineTo lineCTop, lineDTop, lineFTop, lineGTop, lineITop, lineJTop;
private VLineTo lineBRight, lineERight, lineHRight, lineKRight;
private LineTo lineCRight, lineDRight, lineFRight, lineGRight, lineIRight,
lineJRight;
private HLineTo lineBBottom, lineEBottom, lineHBottom, lineKBottom;
private LineTo lineCBottom, lineDBottom, lineFBottom, lineGBottom,
lineIBottom, lineJBottom;
private VLineTo lineBLeft, lineELeft, lineHLeft, lineKLeft;
private LineTo lineCLeft, lineDLeft, lineFLeft, lineGLeft, lineILeft,
lineJLeft;
private void createPathElements() {
final DoubleProperty centerYProperty = new SimpleDoubleProperty();
final DoubleProperty centerXProperty = new SimpleDoubleProperty();
final DoubleProperty leftEdgeProperty = new SimpleDoubleProperty();
final DoubleProperty leftEdgePlusRadiusProperty = new SimpleDoubleProperty();
final DoubleProperty topEdgeProperty = new SimpleDoubleProperty();
final DoubleProperty topEdgePlusRadiusProperty = new SimpleDoubleProperty();
final DoubleProperty rightEdgeProperty = new SimpleDoubleProperty();
final DoubleProperty rightEdgeMinusRadiusProperty = new SimpleDoubleProperty();
final DoubleProperty bottomEdgeProperty = new SimpleDoubleProperty();
final DoubleProperty bottomEdgeMinusRadiusProperty = new SimpleDoubleProperty();
final DoubleProperty cornerProperty = getSkinnable().cornerRadiusProperty();
final DoubleProperty arrowSizeProperty = getSkinnable().arrowSizeProperty();
final DoubleProperty arrowIndentProperty = getSkinnable().arrowIndentProperty();
centerYProperty.bind(Bindings.divide(stackPane.heightProperty(), 2));
centerXProperty.bind(Bindings.divide(stackPane.widthProperty(), 2));
leftEdgePlusRadiusProperty.bind(Bindings.add(leftEdgeProperty,
getSkinnable().cornerRadiusProperty()));
topEdgePlusRadiusProperty.bind(Bindings.add(topEdgeProperty,
getSkinnable().cornerRadiusProperty()));
rightEdgeProperty.bind(stackPane.widthProperty());
rightEdgeMinusRadiusProperty.bind(Bindings.subtract(rightEdgeProperty,
getSkinnable().cornerRadiusProperty()));
bottomEdgeProperty.bind(stackPane.heightProperty());
bottomEdgeMinusRadiusProperty.bind(Bindings.subtract(
bottomEdgeProperty, getSkinnable().cornerRadiusProperty()));
// == INIT ==
moveTo = new MoveTo();
moveTo.xProperty().bind(leftEdgePlusRadiusProperty);
moveTo.yProperty().bind(topEdgeProperty);
// == TOP EDGE ==
lineBTop = new HLineTo();
lineBTop.xProperty().bind(Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty));
lineCTop = new LineTo();
lineCTop.xProperty().bind(Bindings.add(lineBTop.xProperty(), arrowSizeProperty));
lineCTop.yProperty().bind(Bindings.subtract(topEdgeProperty, arrowSizeProperty));
lineDTop = new LineTo();
lineDTop.xProperty().bind(Bindings.add(lineCTop.xProperty(), arrowSizeProperty));
lineDTop.yProperty().bind(topEdgeProperty);
lineETop = new HLineTo();
lineETop.xProperty().bind(Bindings.subtract(centerXProperty, arrowSizeProperty));
lineFTop = new LineTo();
lineFTop.xProperty().bind(centerXProperty);
lineFTop.yProperty().bind(Bindings.subtract(topEdgeProperty, arrowSizeProperty));
lineGTop = new LineTo();
lineGTop.xProperty().bind(Bindings.add(centerXProperty, arrowSizeProperty));
lineGTop.yProperty().bind(topEdgeProperty);
lineHTop = new HLineTo();
lineHTop.xProperty().bind(Bindings.subtract(
Bindings.subtract(rightEdgeMinusRadiusProperty, arrowIndentProperty),
Bindings.multiply(arrowSizeProperty, 2)
));
lineITop = new LineTo();
lineITop.xProperty().bind(Bindings.subtract(
Bindings.subtract(rightEdgeMinusRadiusProperty, arrowIndentProperty),
arrowSizeProperty
));
lineITop.yProperty().bind(Bindings.subtract(topEdgeProperty, arrowSizeProperty));
lineJTop = new LineTo();
lineJTop.xProperty().bind(Bindings.subtract(
rightEdgeMinusRadiusProperty, arrowIndentProperty
));
lineJTop.yProperty().bind(topEdgeProperty);
lineKTop = new HLineTo();
lineKTop.xProperty().bind(rightEdgeMinusRadiusProperty);
// == RIGHT EDGE ==
rightCurveTo = new QuadCurveTo();
rightCurveTo.xProperty().bind(rightEdgeProperty);
rightCurveTo.yProperty().bind(Bindings.add(topEdgeProperty, cornerProperty));
rightCurveTo.controlXProperty().bind(rightEdgeProperty);
rightCurveTo.controlYProperty().bind(topEdgeProperty);
lineBRight = new VLineTo();
lineBRight.yProperty().bind(Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty));
lineCRight = new LineTo();
lineCRight.xProperty().bind(Bindings.add(rightEdgeProperty, arrowSizeProperty));
lineCRight.yProperty().bind(Bindings.add(lineBRight.yProperty(), arrowSizeProperty));
lineDRight = new LineTo();
lineDRight.xProperty().bind(rightEdgeProperty);
lineDRight.yProperty().bind(Bindings.add(lineCRight.yProperty(), arrowSizeProperty));
lineERight = new VLineTo();
lineERight.yProperty().bind(Bindings.subtract(centerYProperty, arrowSizeProperty));
lineFRight = new LineTo();
lineFRight.xProperty().bind(Bindings.add(rightEdgeProperty, arrowSizeProperty));
lineFRight.yProperty().bind(centerYProperty);
lineGRight = new LineTo();
lineGRight.xProperty().bind(rightEdgeProperty);
lineGRight.yProperty().bind(Bindings.add(centerYProperty, arrowSizeProperty));
lineHRight = new VLineTo();
lineHRight.yProperty().bind(Bindings.subtract(
Bindings.subtract(bottomEdgeMinusRadiusProperty, arrowIndentProperty),
Bindings.multiply(arrowSizeProperty, 2)
));
lineIRight = new LineTo();
lineIRight.xProperty().bind(Bindings.add(rightEdgeProperty, arrowSizeProperty));
lineIRight.yProperty().bind(Bindings.subtract(
Bindings.subtract(bottomEdgeMinusRadiusProperty, arrowIndentProperty),
arrowSizeProperty
));
lineJRight = new LineTo();
lineJRight.xProperty().bind(rightEdgeProperty);
lineJRight.yProperty().bind(Bindings.subtract(
bottomEdgeMinusRadiusProperty,
arrowIndentProperty
));
lineKRight = new VLineTo();
lineKRight.yProperty().bind(bottomEdgeMinusRadiusProperty);
// == BOTTOM EDGE ==
bottomCurveTo = new QuadCurveTo();
bottomCurveTo.xProperty().bind(rightEdgeMinusRadiusProperty);
bottomCurveTo.yProperty().bind(bottomEdgeProperty);
bottomCurveTo.controlXProperty().bind(rightEdgeProperty);
bottomCurveTo.controlYProperty().bind(bottomEdgeProperty);
lineBBottom = new HLineTo();
lineBBottom.xProperty().bind(Bindings.subtract(rightEdgeMinusRadiusProperty, arrowIndentProperty));
lineCBottom = new LineTo();
lineCBottom.xProperty().bind(Bindings.subtract(lineBBottom.xProperty(), arrowSizeProperty));
lineCBottom.yProperty().bind(Bindings.add(bottomEdgeProperty, arrowSizeProperty));
lineDBottom = new LineTo();
lineDBottom.xProperty().bind(Bindings.subtract(lineCBottom.xProperty(), arrowSizeProperty));
lineDBottom.yProperty().bind(bottomEdgeProperty);
lineEBottom = new HLineTo();
lineEBottom.xProperty().bind(Bindings.add(centerXProperty, arrowSizeProperty));
lineFBottom = new LineTo();
lineFBottom.xProperty().bind(centerXProperty);
lineFBottom.yProperty().bind(Bindings.add(bottomEdgeProperty, arrowSizeProperty));
lineGBottom = new LineTo();
lineGBottom.xProperty().bind(Bindings.subtract(centerXProperty, arrowSizeProperty));
lineGBottom.yProperty().bind(bottomEdgeProperty);
lineHBottom = new HLineTo();
lineHBottom.xProperty().bind(Bindings.add(
Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty),
Bindings.multiply(arrowSizeProperty, 2)
));
lineIBottom = new LineTo();
lineIBottom.xProperty().bind(Bindings.add(
Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty),
arrowSizeProperty
));
lineIBottom.yProperty().bind(Bindings.add(bottomEdgeProperty, arrowSizeProperty));
lineJBottom = new LineTo();
lineJBottom.xProperty().bind(Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty));
lineJBottom.yProperty().bind(bottomEdgeProperty);
lineKBottom = new HLineTo();
lineKBottom.xProperty().bind(leftEdgePlusRadiusProperty);
// == LEFT EDGE ==
leftCurveTo = new QuadCurveTo();
leftCurveTo.xProperty().bind(leftEdgeProperty);
leftCurveTo.yProperty().bind(Bindings.subtract(bottomEdgeProperty, cornerProperty));
leftCurveTo.controlXProperty().bind(leftEdgeProperty);
leftCurveTo.controlYProperty().bind(bottomEdgeProperty);
lineBLeft = new VLineTo();
lineBLeft.yProperty().bind(Bindings.subtract(bottomEdgeMinusRadiusProperty, arrowIndentProperty));
lineCLeft = new LineTo();
lineCLeft.xProperty().bind(Bindings.subtract(leftEdgeProperty, arrowSizeProperty));
lineCLeft.yProperty().bind(Bindings.subtract(lineBLeft.yProperty(), arrowSizeProperty));
lineDLeft = new LineTo();
lineDLeft.xProperty().bind(leftEdgeProperty);
lineDLeft.yProperty().bind(Bindings.subtract(lineCLeft.yProperty(), arrowSizeProperty));
lineELeft = new VLineTo();
lineELeft.yProperty().bind(Bindings.add(centerYProperty, arrowSizeProperty));
lineFLeft = new LineTo();
lineFLeft.xProperty().bind(Bindings.subtract(leftEdgeProperty, arrowSizeProperty));
lineFLeft.yProperty().bind(centerYProperty);
lineGLeft = new LineTo();
lineGLeft.xProperty().bind(leftEdgeProperty);
lineGLeft.yProperty().bind(Bindings.subtract(centerYProperty, arrowSizeProperty));
lineHLeft = new VLineTo();
lineHLeft.yProperty().bind(Bindings.add(
Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty),
Bindings.multiply(arrowSizeProperty, 2)
));
lineILeft = new LineTo();
lineILeft.xProperty().bind(Bindings.subtract(leftEdgeProperty, arrowSizeProperty));
lineILeft.yProperty().bind(Bindings.add(
Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty),
arrowSizeProperty
));
lineJLeft = new LineTo();
lineJLeft.xProperty().bind(leftEdgeProperty);
lineJLeft.yProperty().bind(Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty));
lineKLeft = new VLineTo();
lineKLeft.yProperty().bind(topEdgePlusRadiusProperty);
topCurveTo = new QuadCurveTo();
topCurveTo.xProperty().bind(leftEdgePlusRadiusProperty);
topCurveTo.yProperty().bind(topEdgeProperty);
topCurveTo.controlXProperty().bind(leftEdgeProperty);
topCurveTo.controlYProperty().bind(topEdgeProperty);
}
private Window getPopupWindow() {
return getSkinnable().getScene().getWindow();
}
private boolean showArrow(ArrowLocation location) {
ArrowLocation arrowLocation = getSkinnable().getArrowLocation();
return location.equals(arrowLocation) && !getSkinnable().isDetached() && !tornOff;
}
private void updatePath() {
List<PathElement> elements = new ArrayList<>();
elements.add(moveTo);
if (showArrow(ArrowLocation.TOP_LEFT)) {
elements.add(lineBTop);
elements.add(lineCTop);
elements.add(lineDTop);
}
if (showArrow(ArrowLocation.TOP_CENTER)) {
elements.add(lineETop);
elements.add(lineFTop);
elements.add(lineGTop);
}
if (showArrow(ArrowLocation.TOP_RIGHT)) {
elements.add(lineHTop);
elements.add(lineITop);
elements.add(lineJTop);
}
elements.add(lineKTop);
elements.add(rightCurveTo);
if (showArrow(ArrowLocation.RIGHT_TOP)) {
elements.add(lineBRight);
elements.add(lineCRight);
elements.add(lineDRight);
}
if (showArrow(ArrowLocation.RIGHT_CENTER)) {
elements.add(lineERight);
elements.add(lineFRight);
elements.add(lineGRight);
}
if (showArrow(ArrowLocation.RIGHT_BOTTOM)) {
elements.add(lineHRight);
elements.add(lineIRight);
elements.add(lineJRight);
}
elements.add(lineKRight);
elements.add(bottomCurveTo);
if (showArrow(ArrowLocation.BOTTOM_RIGHT)) {
elements.add(lineBBottom);
elements.add(lineCBottom);
elements.add(lineDBottom);
}
if (showArrow(ArrowLocation.BOTTOM_CENTER)) {
elements.add(lineEBottom);
elements.add(lineFBottom);
elements.add(lineGBottom);
}
if (showArrow(ArrowLocation.BOTTOM_LEFT)) {
elements.add(lineHBottom);
elements.add(lineIBottom);
elements.add(lineJBottom);
}
elements.add(lineKBottom);
elements.add(leftCurveTo);
if (showArrow(ArrowLocation.LEFT_BOTTOM)) {
elements.add(lineBLeft);
elements.add(lineCLeft);
elements.add(lineDLeft);
}
if (showArrow(ArrowLocation.LEFT_CENTER)) {
elements.add(lineELeft);
elements.add(lineFLeft);
elements.add(lineGLeft);
}
if (showArrow(ArrowLocation.LEFT_TOP)) {
elements.add(lineHLeft);
elements.add(lineILeft);
elements.add(lineJLeft);
}
elements.add(lineKLeft);
elements.add(topCurveTo);
path.getElements().setAll(elements);
clip.getElements().setAll(elements);
}
}

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.geometry.Orientation;
import javafx.scene.control.Slider;
import javafx.scene.control.skin.SliderSkin;
import javafx.scene.layout.StackPane;
/**
* A {@link Slider} skin that supports progress color indication.
*/
public class ProgressSliderSkin extends SliderSkin {
protected final StackPane thumb;
protected final StackPane track;
protected final StackPane progressTrack;
public ProgressSliderSkin(Slider slider) {
super(slider);
track = (StackPane) getSkinnable().lookup(".track");
thumb = (StackPane) getSkinnable().lookup(".thumb");
progressTrack = new StackPane();
progressTrack.getStyleClass().add("progress");
progressTrack.setMouseTransparent(true);
getSkinnable().getStyleClass().add("progress-slider");
getChildren().add(getChildren().indexOf(thumb), progressTrack);
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
double progressX = track.getLayoutX();
double progressY;
double progressWidth;
double progressHeight;
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
progressY = track.getLayoutY();
progressWidth = thumb.getLayoutX() - track.getLayoutX()
+ thumb.getLayoutBounds().getCenterX() - snappedLeftInset();
progressHeight = track.getHeight();
} else {
progressY = thumb.getLayoutY() + thumb.getLayoutBounds().getCenterY();
progressWidth = track.getWidth();
progressHeight = track.getLayoutBounds().getMaxY() + track.getLayoutY()
- thumb.getLayoutY() - thumb.getLayoutBounds().getCenterY() - snappedBottomInset();
}
progressTrack.resizeRelocate(progressX, progressY, progressWidth, progressHeight);
}
}

@ -0,0 +1,113 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.Skin;
import javafx.util.StringConverter;
/**
* A ProgressIndicator that displays progress value as a ring that gradually
* empties out as a task is completed.
*/
public class RingProgressIndicator extends ProgressIndicator {
/**
* Creates a new indeterminate ProgressIndicator.
*/
public RingProgressIndicator() {
super();
}
/**
* Creates a new ProgressIndicator with the given progress value.
*
* @param progress The progress, represented as a value between 0 and 1.
*/
public RingProgressIndicator(@NamedArg("progress") double progress) {
this(progress, false);
}
/**
* Creates a new ProgressIndicator with the given progress value and type.
*
* @param progress The progress, represented as a value between 0 and 1.
* @param reverse A flag to indicate whether the indicator is reversed or not.
*/
public RingProgressIndicator(@NamedArg("progress") double progress,
@NamedArg("reverse") boolean reverse) {
super(progress);
this.reverse.set(reverse);
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new RingProgressIndicatorSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the node to be displayed within the progress indicator. If null,
* it will fall back to the Label with an integer progress value from 1 to 100.
*/
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
protected final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic", null);
public Node getGraphic() {
return graphicProperty().get();
}
public void setGraphic(Node graphic) {
graphicProperty().set(graphic);
}
/**
* Represents an optional converter to transform the progress value to a string.
* It is only used if a custom graphic node is not set.
*
* @see #graphicProperty()
*/
public ObjectProperty<StringConverter<Double>> stringConverterProperty() {
return stringConverter;
}
protected final ObjectProperty<StringConverter<Double>> stringConverter =
new SimpleObjectProperty<>(this, "converter", null);
public StringConverter<Double> getStringConverter() {
return stringConverterProperty().get();
}
public void setStringConverter(StringConverter<Double> stringConverter) {
this.stringConverterProperty().set(stringConverter);
}
/**
* Reverses the progress indicator scale. For the indeterminate variant,
* this means it will be rotated counterclockwise.
*/
public ReadOnlyBooleanProperty reverseProperty() {
return reverse.getReadOnlyProperty();
}
protected final ReadOnlyBooleanWrapper reverse = new ReadOnlyBooleanWrapper(this, "reverse", false);
public boolean isReverse() {
return reverse.get();
}
}

@ -0,0 +1,266 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.beans.property.DoubleProperty;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableProperty;
import javafx.css.converter.SizeConverter;
import javafx.scene.CacheHint;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
/**
* The default skin for the {@link RingProgressIndicator} control.
*/
public class RingProgressIndicatorSkin extends SkinBase<RingProgressIndicator> {
protected static final double DEFAULT_ANIMATION_TIME = 3;
protected final StackPane container = new StackPane();
protected final Circle trackCircle = new Circle();
protected final Arc progressArc = new Arc();
protected final Label progressLabel = new Label();
protected final RotateTransition transition = new RotateTransition(
Duration.seconds(DEFAULT_ANIMATION_TIME), progressArc
);
public RingProgressIndicatorSkin(RingProgressIndicator indicator) {
super(indicator);
trackCircle.getStyleClass().add("track");
trackCircle.setManaged(false);
trackCircle.setFill(Color.TRANSPARENT);
progressArc.getStyleClass().add("ring");
progressArc.setManaged(false);
progressArc.setStartAngle(90);
progressArc.setLength(calcProgressArcLength());
progressArc.setCache(true);
progressArc.setCacheHint(CacheHint.ROTATE);
progressArc.setFill(Color.TRANSPARENT);
transition.setAutoReverse(false);
transition.setByAngle(-getMaxAngle());
transition.setCycleCount(Animation.INDEFINITE);
transition.setDelay(Duration.ZERO);
transition.setInterpolator(Interpolator.LINEAR);
progressLabel.getStyleClass().add("progress");
container.getStyleClass().addAll("container");
container.setMaxHeight(Region.USE_PREF_SIZE);
container.setMaxWidth(Region.USE_PREF_SIZE);
container.getChildren().addAll(trackCircle, progressArc);
container.getChildren().add(indicator.getGraphic() != null ? indicator.getGraphic() : progressLabel);
indicator.getStyleClass().add("ring-progress-indicator");
indicator.setMaxHeight(Region.USE_PREF_SIZE);
indicator.setMaxWidth(Region.USE_PREF_SIZE);
getChildren().add(container);
// == INIT LISTENERS ==
updateProgressLabel();
toggleIndeterminate();
registerChangeListener(indicator.progressProperty(), e -> {
updateProgressLabel();
progressArc.setLength(calcProgressArcLength());
});
registerChangeListener(indicator.indeterminateProperty(), e -> toggleIndeterminate());
registerChangeListener(indicator.visibleProperty(), e -> {
if (indicator.isVisible() && indicator.isIndeterminate()) {
transition.play();
} else {
transition.pause();
}
});
registerChangeListener(indeterminateAnimationTimeProperty(), e -> {
transition.setDuration(Duration.seconds(getIndeterminateAnimationTime()));
if (indicator.isIndeterminate()) {
transition.playFromStart();
}
});
registerChangeListener(indicator.graphicProperty(), e -> {
if (indicator.getGraphic() != null) {
container.getChildren().remove(progressLabel);
container.getChildren().add(indicator.getGraphic());
} else {
if (container.getChildren().size() > 1) {
container.getChildren().remove(1);
container.getChildren().add(progressLabel);
updateProgressLabel();
}
}
});
}
private int getMaxAngle() {
return getSkinnable().isReverse() ? 360 : -360;
}
private double calcProgressArcLength() {
var progress = getSkinnable().getProgress();
return getSkinnable().isReverse() ? (1 - progress) * getMaxAngle() : progress * getMaxAngle();
}
protected void updateProgressLabel() {
var progress = getSkinnable().getProgress();
if (getSkinnable().getStringConverter() != null) {
progressLabel.setText(getSkinnable().getStringConverter().toString(progress));
return;
}
if (progress >= 0) {
progressLabel.setText((int) Math.ceil(progress * 100) + "%");
}
}
protected void toggleIndeterminate() {
var indeterminate = getSkinnable().isIndeterminate();
progressLabel.setManaged(!indeterminate);
progressLabel.setVisible(!indeterminate);
if (indeterminate) {
if (getSkinnable().isVisible()) {
transition.play();
}
} else {
progressArc.setRotate(0);
transition.stop();
}
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
var size = Math.max(w, h);
var radius = (size / 2) - (progressArc.getStrokeWidth() / 2);
trackCircle.setCenterX(size / 2);
trackCircle.setCenterY(size / 2);
trackCircle.setRadius(radius);
progressArc.setCenterX(size / 2);
progressArc.setCenterY(size / 2);
progressArc.setRadiusX(radius);
progressArc.setRadiusY(radius);
container.resizeRelocate(x, y, size, size);
}
// Control height is always equal to its width.
@Override
protected double computeMinHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
return super.computeMinWidth(0, topInset, rightInset, bottomInset, leftInset);
}
@Override
protected double computePrefHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
return super.computePrefWidth(0, topInset, rightInset, bottomInset, leftInset);
}
@Override
protected double computeMaxHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
return super.computeMaxWidth(0, topInset, rightInset, bottomInset, leftInset);
}
@Override
public void dispose() {
transition.stop();
}
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return getClassCssMetaData();
}
///////////////////////////////////////////////////////////////////////////
// Styleable Properties //
///////////////////////////////////////////////////////////////////////////
protected DoubleProperty indeterminateAnimationTime = null;
private DoubleProperty indeterminateAnimationTimeProperty() {
if (indeterminateAnimationTime == null) {
indeterminateAnimationTime = new StyleableDoubleProperty(DEFAULT_ANIMATION_TIME) {
@Override
public Object getBean() {
return RingProgressIndicatorSkin.this;
}
@Override
public String getName() {
return "indeterminateAnimationTime";
}
@Override
public CssMetaData<RingProgressIndicator, Number> getCssMetaData() {
return StyleableProperties.INDETERMINATE_ANIMATION_TIME;
}
};
}
return indeterminateAnimationTime;
}
public double getIndeterminateAnimationTime() {
return indeterminateAnimationTime == null ? DEFAULT_ANIMATION_TIME : indeterminateAnimationTime.get();
}
private static class StyleableProperties {
private static final CssMetaData<RingProgressIndicator, Number> INDETERMINATE_ANIMATION_TIME =
new CssMetaData<>("-fx-indeterminate-animation-time", SizeConverter.getInstance(), DEFAULT_ANIMATION_TIME) {
@Override
public boolean isSettable(RingProgressIndicator n) {
return n.getSkin() instanceof RingProgressIndicatorSkin s
&& (s.indeterminateAnimationTime == null || !s.indeterminateAnimationTime.isBound());
}
@Override
@SuppressWarnings("unchecked")
public StyleableProperty<Number> getStyleableProperty(RingProgressIndicator n) {
final RingProgressIndicatorSkin skin = (RingProgressIndicatorSkin) n.getSkin();
return (StyleableProperty<Number>) skin.indeterminateAnimationTimeProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(
SkinBase.getClassCssMetaData()
);
styleables.add(INDETERMINATE_ANIMATION_TIME);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return RingProgressIndicatorSkin.StyleableProperties.STYLEABLES;
}
}

@ -0,0 +1,75 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import java.util.Objects;
import java.util.function.BiConsumer;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import org.jetbrains.annotations.Nullable;
/**
* An internal convenience class for implementing slot-based approach.
*
* <p>It is intended to be used for controls that allow custom user nodes
* to be placed inside their skins. his class automatically adds or removes
* an updated <code>ObservableValue<? extends Node></code> value to/from the
* given container and also maintains the <code>:filled</code> pseudo-class
* state to indicate whether the corresponding slot is empty or not.
*/
final class SlotListener implements ChangeListener<Node> {
private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled");
private final Pane slot;
private final @Nullable BiConsumer<Node, Boolean> onContentUpdate;
/**
* Creates a new listener and binds it to the specified container.
*
* @param slot The container for user-specified node.
*/
public SlotListener(Pane slot) {
this(slot, null);
}
/**
* Creates a new listener and binds it to the specified container.
* Also, it registers the custom callback handler that will be notified
* upon the container content changed.
*
* @param slot The container for user-specified node.
* @param onContentUpdate The callback handler to be notified upon
* the container content changing.
*/
public SlotListener(Node slot, @Nullable BiConsumer<Node, Boolean> onContentUpdate) {
Objects.requireNonNull(slot, "Slot cannot be null.");
this.onContentUpdate = onContentUpdate;
if (slot instanceof Pane pane) {
this.slot = pane;
} else {
throw new IllegalArgumentException("Invalid slot type. Pane is required.");
}
}
@Override
public void changed(ObservableValue<? extends Node> obs, Node old, Node val) {
if (val != null) {
slot.getChildren().setAll(val);
} else {
slot.getChildren().clear();
}
slot.setVisible(val != null);
slot.setManaged(val != null);
slot.pseudoClassStateChanged(FILLED, val != null);
if (onContentUpdate != null) {
onContentUpdate.accept(val, val != null);
}
}
}

@ -0,0 +1,83 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.geometry.Orientation;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
/**
* A spacing component used to distribute remaining width between
* a parent's child components.
*
* <p>When placing a single Spacer before or after the child components,
* the components will be pushed to the right and left of its container
* for horizontally oriented Spacer, or to the top and bottom for vertically
* oriented Spacer.
*
* <p>You can also specify the `Spacer` size. In this case, it will not be
* extended and will work like a gap with the given size between sibling components.
*
* <p>Note that this control is not intended to be used in FXML unless SceneBuilder
* supports constructor arguments, because none of the properties mentioned above are
* observable.
*/
public class Spacer extends Region {
/**
* Creates a new horizontally oriented Spacer that expands
* to fill remaining space.
*/
public Spacer() {
this(Orientation.HORIZONTAL);
}
/**
* Creates a new Spacer with the given orientation that expands
* to fill remaining space.
*
* @param orientation The orientation of the spacer.
*/
public Spacer(Orientation orientation) {
super();
switch (orientation) {
case HORIZONTAL -> HBox.setHgrow(this, Priority.ALWAYS);
case VERTICAL -> VBox.setVgrow(this, Priority.ALWAYS);
}
}
/**
* Creates a new Spacer with the fixed size.
*
* @param size The size of the spacer.
*/
public Spacer(double size) {
this(size, Orientation.HORIZONTAL);
}
/**
* Creates a new Spacer with the fixed size and orientation.
*
* @param size The size of the spacer.
* @param orientation The orientation of the spacer.
*/
public Spacer(double size, Orientation orientation) {
super();
switch (orientation) {
case HORIZONTAL -> {
setMinWidth(size);
setPrefWidth(size);
setMaxWidth(size);
}
case VERTICAL -> {
setMinHeight(size);
setPrefHeight(size);
setMaxHeight(size);
}
}
}
}

@ -0,0 +1,101 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import org.jetbrains.annotations.Nullable;
/**
* A versatile container that can used in various contexts such as dialog
* headers, list items, and cards. It can contain a graphic, a title, description,
* and optional actions.
*/
public class Tile extends TileBase {
/**
* Creates a new empty Tile.
*/
public Tile() {
this(null, null, null);
}
/**
* Creates a new Tile with an initial title and description.
*
* @param title A string for the title.
* @param description A string for the description.
*/
public Tile(@Nullable @NamedArg("title") String title,
@Nullable @NamedArg("description") String description) {
this(title, description, null);
}
/**
* Creates a new Tile with an initial title, description and graphic.
*
* @param title A string for the title.
* @param description A string for the description.
* @param graphic A graphic or icon.
*/
public Tile(@Nullable String title,
@Nullable String description,
@Nullable Node graphic) {
super(title, description, graphic);
getStyleClass().add("tile");
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new TileSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the node to be placed in the tiles action slot. It is commonly
* used to place action controls that are associated with the tile.
*/
public ObjectProperty<Node> actionProperty() {
return action;
}
private final ObjectProperty<Node> action = new SimpleObjectProperty<>(this, "action");
public Node getAction() {
return action.get();
}
public void setAction(Node action) {
this.action.set(action);
}
/**
* Represents the tiles action handler.
*
* <p>Setting an action handler makes the tile interactive (or clickable).
* When a user clicks on the interactive tile, the specified action handler will be called.
*/
public ObjectProperty<Runnable> actionHandlerProperty() {
return actionHandler;
}
private final ObjectProperty<Runnable> actionHandler
= new SimpleObjectProperty<>(this, "actionHandler");
public Runnable getActionHandler() {
return actionHandler.get();
}
public void setActionHandler(Runnable actionHandler) {
this.actionHandler.set(actionHandler);
}
}

@ -0,0 +1,98 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import atlantafx.base.util.BBCodeParser;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Node;
import javafx.scene.control.Control;
import org.jetbrains.annotations.Nullable;
/**
* A common class for implementing tile-based controls, specifically the
* {@link Message} and the {@link Tile}.
*/
public abstract class TileBase extends Control {
public TileBase() {
this(null, null, null);
}
public TileBase(@Nullable String title,
@Nullable String description) {
this(title, description, null);
}
public TileBase(@Nullable String title,
@Nullable String description,
@Nullable Node graphic) {
super();
setTitle(title);
setDescription(description);
setGraphic(graphic);
getStyleClass().add("tile-base");
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the tiles graphic node. It is commonly used to add images or icons
* that are associated with the tile.
*/
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
public Node getGraphic() {
return graphic.get();
}
public void setGraphic(Node graphic) {
this.graphic.set(graphic);
}
/**
* Represents the tiles title (or header).
*/
public StringProperty titleProperty() {
return title;
}
private final StringProperty title = new SimpleStringProperty(this, "title");
public String getTitle() {
return title.get();
}
public void setTitle(String title) {
this.title.set(title);
}
/**
* Represents the tiles description (or optional text).
*
* <p>This property supports BBCode formatted text. Refer to the {@link BBCodeParser}
* for more information.
*/
public StringProperty descriptionProperty() {
return description;
}
private final StringProperty description = new SimpleStringProperty(this, "description");
public String getDescription() {
return description.get();
}
public void setDescription(String description) {
this.description.set(description);
}
}

@ -0,0 +1,38 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import atlantafx.base.theme.Styles;
/**
* The default skin for the {@link Tile} control.
*/
public class TileSkin extends TileSkinBase<Tile> {
public TileSkin(Tile control) {
super(control);
control.actionProperty().addListener(actionSlotListener);
actionSlotListener.changed(control.actionProperty(), null, control.getAction());
pseudoClassStateChanged(Styles.STATE_INTERACTIVE, control.getActionHandler() != null);
registerChangeListener(
control.actionHandlerProperty(),
o -> pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
);
container.setOnMouseClicked(e -> {
if (getSkinnable().getActionHandler() != null) {
getSkinnable().getActionHandler().run();
}
});
}
@Override
public void dispose() {
getSkinnable().actionProperty().removeListener(actionSlotListener);
unregisterChangeListeners(getSkinnable().actionHandlerProperty());
super.dispose();
}
}

@ -0,0 +1,148 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import atlantafx.base.util.BBCodeParser;
import javafx.beans.value.ChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextFlow;
/**
* A common skin for implementing tile-based controls, specifically the
* {@link MessageSkin} and the {@link TileSkin}.
*/
public abstract class TileSkinBase<T extends TileBase> extends SkinBase<T> {
protected static final PseudoClass HAS_GRAPHIC = PseudoClass.getPseudoClass("has-graphic");
protected static final PseudoClass HAS_TITLE = PseudoClass.getPseudoClass("has-title");
protected static final PseudoClass HAS_DESCRIPTION = PseudoClass.getPseudoClass("has-description");
protected static final PseudoClass HAS_ACTION = PseudoClass.getPseudoClass("has-action");
protected final HBox container = new HBox();
protected final StackPane graphicSlot;
protected final ChangeListener<Node> graphicSlotListener;
protected final VBox headerBox;
protected final Label titleLbl;
protected final TextFlow descriptionText;
protected final StackPane actionSlot;
protected final ChangeListener<Node> actionSlotListener;
public TileSkinBase(T control) {
super(control);
graphicSlot = new StackPane();
graphicSlot.getStyleClass().add("graphic");
graphicSlotListener = new SlotListener(
graphicSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_GRAPHIC, active)
);
control.graphicProperty().addListener(graphicSlotListener);
graphicSlotListener.changed(control.graphicProperty(), null, control.getGraphic());
titleLbl = new Label(control.getTitle());
titleLbl.getStyleClass().add("title");
titleLbl.setVisible(control.getTitle() != null);
titleLbl.setManaged(control.getTitle() != null);
descriptionText = new TextFlow();
descriptionText.getStyleClass().add("description");
descriptionText.setVisible(control.getDescription() != null);
descriptionText.setManaged(control.getDescription() != null);
setDescriptionText();
headerBox = new VBox(titleLbl, descriptionText);
headerBox.setFillWidth(true);
headerBox.getStyleClass().add("header");
HBox.setHgrow(headerBox, Priority.ALWAYS);
headerBox.setMinHeight(Region.USE_COMPUTED_SIZE);
headerBox.setPrefHeight(Region.USE_COMPUTED_SIZE);
headerBox.setMaxHeight(Region.USE_COMPUTED_SIZE);
control.pseudoClassStateChanged(HAS_TITLE, control.getTitle() != null);
registerChangeListener(control.titleProperty(), o -> {
var value = getSkinnable().getDescription();
titleLbl.setText(value);
titleLbl.setVisible(value != null);
titleLbl.setManaged(value != null);
getSkinnable().pseudoClassStateChanged(HAS_TITLE, value != null);
});
control.pseudoClassStateChanged(HAS_DESCRIPTION, control.getDescription() != null);
registerChangeListener(control.descriptionProperty(), o -> {
var value = getSkinnable().getDescription();
setDescriptionText();
descriptionText.setVisible(value != null);
descriptionText.setManaged(value != null);
getSkinnable().pseudoClassStateChanged(HAS_DESCRIPTION, value != null);
});
actionSlot = new StackPane();
actionSlot.getStyleClass().add("action");
actionSlotListener = new SlotListener(
actionSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_ACTION, active)
);
// use pref size for slots, or they will be resized
// to the bare minimum due to Priority.ALWAYS
graphicSlot.setMinWidth(Region.USE_PREF_SIZE);
actionSlot.setMinWidth(Region.USE_PREF_SIZE);
// text wrapping inside VBox won't work without this
descriptionText.setMaxWidth(Region.USE_PREF_SIZE);
descriptionText.setMinHeight(Region.USE_PREF_SIZE);
// do not resize children or container won't restore
// to its original size after expanding
container.setFillHeight(false);
container.getChildren().setAll(graphicSlot, headerBox, actionSlot);
container.getStyleClass().add("container");
getChildren().setAll(container);
}
protected void setDescriptionText() {
if (!descriptionText.getChildren().isEmpty()) {
descriptionText.getChildren().clear();
}
if (getSkinnable().getDescription() != null && !getSkinnable().getDescription().isBlank()) {
BBCodeParser.createLayout(getSkinnable().getDescription(), descriptionText);
}
}
protected double calcHeight() {
var headerHeight = headerBox.getSpacing()
+ headerBox.getInsets().getTop()
+ headerBox.getInsets().getBottom()
+ titleLbl.getBoundsInLocal().getHeight()
+ (descriptionText.isManaged() ? descriptionText.getBoundsInLocal().getHeight() : 0);
return Math.max(Math.max(graphicSlot.getHeight(), actionSlot.getHeight()), headerHeight)
+ container.getPadding().getTop()
+ container.getPadding().getBottom();
}
@Override
protected double computeMinHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
// change the control height when label changed its height due to text wrapping,
// no other way to do that, all JavaFX containers completely ignore _the actual_
// height of its children
return calcHeight();
}
@Override
public void dispose() {
unregisterChangeListeners(getSkinnable().titleProperty());
unregisterChangeListeners(getSkinnable().descriptionProperty());
getSkinnable().graphicProperty().removeListener(graphicSlotListener);
super.dispose();
}
}

@ -0,0 +1,314 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2015, ControlsFX
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package atlantafx.base.controls;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.value.WritableValue;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.css.converter.EnumConverter;
import javafx.event.ActionEvent;
import javafx.geometry.HorizontalDirection;
import javafx.scene.AccessibleAttribute;
import javafx.scene.control.Labeled;
import javafx.scene.control.Skin;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
/**
* A control that provides users with the ability to choose between two distinct values.
* It is functionally similar, though aesthetically different, from the RadioButton
* and Checkbox.
*/
public class ToggleSwitch extends Labeled implements Toggle {
protected static final String DEFAULT_STYLE_CLASS = "toggle-switch";
protected static final PseudoClass PSEUDO_CLASS_SELECTED = PseudoClass.getPseudoClass("selected");
protected static final PseudoClass PSEUDO_CLASS_RIGHT = PseudoClass.getPseudoClass("right");
/**
* Creates a toggle switch with empty string for its label.
*/
public ToggleSwitch() {
initialize();
}
/**
* Creates a toggle switch with the specified label.
*
* @param text The label string of the control.
*/
public ToggleSwitch(String text) {
super(text);
initialize();
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new ToggleSwitchSkin(this);
}
private void initialize() {
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Returns whether this Toggle Switch is selected.
*/
@Override
public final BooleanProperty selectedProperty() {
if (selected == null) {
selected = new BooleanPropertyBase() {
@Override
protected void invalidated() {
final boolean selected = get();
final ToggleGroup tg = getToggleGroup();
pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, selected);
notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTED);
if (tg != null) {
if (selected) {
tg.selectToggle(ToggleSwitch.this);
} else if (tg.getSelectedToggle() == ToggleSwitch.this) {
// This code was copied from ToggleButton, and originally
// it should use the following method, which is like almost
// everything in JavaFX is private. Probably it fixes some
// internal toggle group state.
// tg.clearSelectedToggle();
// This is kind of an equivalent code even though
// "!tg.getSelectedToggle().isSelected()"
// looks like absurd and should always return false.
if (!tg.getSelectedToggle().isSelected()) {
for (Toggle toggle : tg.getToggles()) {
if (toggle.isSelected()) {
return;
}
}
}
tg.selectToggle(null);
}
}
}
@Override
public Object getBean() {
return ToggleSwitch.this;
}
@Override
public String getName() {
return "selected";
}
};
}
return selected;
}
private BooleanProperty selected;
@Override
public final void setSelected(boolean value) {
selectedProperty().set(value);
}
@Override
public final boolean isSelected() {
return selected != null && selected.get();
}
/**
* The {@link ToggleGroup} to which this ToggleSwitch belongs. A toggle can only
* be in one group at any one time. If the group is changed, then the toggle is
* removed from the old group prior to being added to the new group.
*/
@Override
public final ObjectProperty<ToggleGroup> toggleGroupProperty() {
if (toggleGroup == null) {
toggleGroup = new ObjectPropertyBase<>() {
private ToggleGroup old;
@Override
protected void invalidated() {
final ToggleGroup tg = get();
if (tg != null && !tg.getToggles().contains(ToggleSwitch.this)) {
if (old != null) {
old.getToggles().remove(ToggleSwitch.this);
}
tg.getToggles().add(ToggleSwitch.this);
} else if (tg == null) {
old.getToggles().remove(ToggleSwitch.this);
}
old = tg;
}
@Override
public Object getBean() {
return ToggleSwitch.this;
}
@Override
public String getName() {
return "toggleGroup";
}
};
}
return toggleGroup;
}
private ObjectProperty<ToggleGroup> toggleGroup;
@Override
public final void setToggleGroup(ToggleGroup value) {
toggleGroupProperty().set(value);
}
@Override
public final ToggleGroup getToggleGroup() {
return toggleGroup == null ? null : toggleGroup.get();
}
/**
* Specifies the side where {@link #textProperty()} value should be placed.
* Default is {@link HorizontalDirection#LEFT}.
*/
public final ObjectProperty<HorizontalDirection> labelPositionProperty() {
if (labelPosition == null) {
labelPosition = new StyleableObjectProperty<>(HorizontalDirection.LEFT) {
@Override
public Object getBean() {
return ToggleSwitch.this;
}
@Override
public String getName() {
return "labelPosition";
}
@Override
protected void invalidated() {
final HorizontalDirection v = get();
pseudoClassStateChanged(ToggleSwitch.PSEUDO_CLASS_RIGHT, v == HorizontalDirection.RIGHT);
}
@Override
public CssMetaData<ToggleSwitch, HorizontalDirection> getCssMetaData() {
return StyleableProperties.LABEL_POSITION;
}
};
}
return labelPosition;
}
private ObjectProperty<HorizontalDirection> labelPosition;
public final void setLabelPosition(HorizontalDirection pos) {
labelPositionProperty().setValue(pos);
}
public final HorizontalDirection getLabelPosition() {
return labelPosition == null ? HorizontalDirection.LEFT : labelPosition.getValue();
}
///////////////////////////////////////////////////////////////////////////
// Methods //
///////////////////////////////////////////////////////////////////////////
/**
* Toggles the state of the switch, cycling through the selected and unselected states.
*/
public void fire() {
if (!isDisabled()) {
setSelected(!isSelected());
fireEvent(new ActionEvent());
}
}
///////////////////////////////////////////////////////////////////////////
// Styleable Properties //
///////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return StyleableProperties.STYLEABLES;
}
private static class StyleableProperties {
private static final CssMetaData<ToggleSwitch, HorizontalDirection> LABEL_POSITION = new CssMetaData<>(
"-fx-label-position", new EnumConverter<>(HorizontalDirection.class), HorizontalDirection.LEFT
) {
@Override
public boolean isSettable(ToggleSwitch c) {
return c.labelPositionProperty() == null || !c.labelPositionProperty().isBound();
}
@Override
public StyleableProperty<HorizontalDirection> getStyleableProperty(ToggleSwitch c) {
var val = (WritableValue<HorizontalDirection>) c.labelPositionProperty();
return (StyleableProperty<HorizontalDirection>) val;
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Labeled.getClassCssMetaData());
styleables.add(LABEL_POSITION);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
}

Some files were not shown because too many files have changed in this diff Show More