使用 JFXColorPicker 代替 ColorPicker (#4865)
This commit is contained in:
@@ -117,10 +117,6 @@ tasks.checkstyleMain {
|
|||||||
exclude("**/org/jackhuang/hmcl/ui/image/apng/**")
|
exclude("**/org/jackhuang/hmcl/ui/image/apng/**")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.compileJava {
|
|
||||||
options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED")
|
|
||||||
}
|
|
||||||
|
|
||||||
val addOpens = listOf(
|
val addOpens = listOf(
|
||||||
"java.base/java.lang",
|
"java.base/java.lang",
|
||||||
"java.base/java.lang.reflect",
|
"java.base/java.lang.reflect",
|
||||||
@@ -128,8 +124,11 @@ val addOpens = listOf(
|
|||||||
"javafx.base/com.sun.javafx.binding",
|
"javafx.base/com.sun.javafx.binding",
|
||||||
"javafx.base/com.sun.javafx.event",
|
"javafx.base/com.sun.javafx.event",
|
||||||
"javafx.base/com.sun.javafx.runtime",
|
"javafx.base/com.sun.javafx.runtime",
|
||||||
|
"javafx.base/javafx.beans.property",
|
||||||
"javafx.graphics/javafx.css",
|
"javafx.graphics/javafx.css",
|
||||||
|
"javafx.graphics/javafx.stage",
|
||||||
"javafx.graphics/com.sun.javafx.stage",
|
"javafx.graphics/com.sun.javafx.stage",
|
||||||
|
"javafx.graphics/com.sun.javafx.util",
|
||||||
"javafx.graphics/com.sun.prism",
|
"javafx.graphics/com.sun.prism",
|
||||||
"javafx.controls/com.sun.javafx.scene.control",
|
"javafx.controls/com.sun.javafx.scene.control",
|
||||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||||
@@ -137,6 +136,10 @@ val addOpens = listOf(
|
|||||||
"jdk.attach/sun.tools.attach",
|
"jdk.attach/sun.tools.attach",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tasks.compileJava {
|
||||||
|
options.compilerArgs.addAll(addOpens.map { "--add-exports=$it=ALL-UNNAMED" })
|
||||||
|
}
|
||||||
|
|
||||||
val hmclProperties = buildList {
|
val hmclProperties = buildList {
|
||||||
add("hmcl.version" to project.version.toString())
|
add("hmcl.version" to project.version.toString())
|
||||||
add("hmcl.add-opens" to addOpens.joinToString(" "))
|
add("hmcl.add-opens" to addOpens.joinToString(" "))
|
||||||
|
|||||||
60
HMCL/src/main/java/com/jfoenix/controls/JFXClippedPane.java
Normal file
60
HMCL/src/main/java/com/jfoenix/controls/JFXClippedPane.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.controls;
|
||||||
|
|
||||||
|
import com.jfoenix.utils.JFXNodeUtils;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JFXClippedPane is a StackPane that clips its content if exceeding the pane bounds.
|
||||||
|
*
|
||||||
|
* @author Shadi Shaheen
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2018-06-02
|
||||||
|
*/
|
||||||
|
public class JFXClippedPane extends StackPane {
|
||||||
|
|
||||||
|
private final Region clip = new Region();
|
||||||
|
|
||||||
|
public JFXClippedPane() {
|
||||||
|
super();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JFXClippedPane(Node... children) {
|
||||||
|
super(children);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setClip(clip);
|
||||||
|
clip.setBackground(new Background(new BackgroundFill(Color.BLACK, new CornerRadii(2), Insets.EMPTY)));
|
||||||
|
backgroundProperty().addListener(observable -> JFXNodeUtils.updateBackground(getBackground(), clip));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void layoutChildren() {
|
||||||
|
super.layoutChildren();
|
||||||
|
clip.resizeRelocate(0, 0, getWidth(), getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
145
HMCL/src/main/java/com/jfoenix/controls/JFXColorPicker.java
Normal file
145
HMCL/src/main/java/com/jfoenix/controls/JFXColorPicker.java
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.controls;
|
||||||
|
|
||||||
|
import com.jfoenix.skins.JFXColorPickerSkin;
|
||||||
|
import javafx.css.CssMetaData;
|
||||||
|
import javafx.css.SimpleStyleableBooleanProperty;
|
||||||
|
import javafx.css.Styleable;
|
||||||
|
import javafx.css.StyleableBooleanProperty;
|
||||||
|
import javafx.css.converter.BooleanConverter;
|
||||||
|
import javafx.scene.control.ColorPicker;
|
||||||
|
import javafx.scene.control.Skin;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JFXColorPicker is the metrial design implementation of color picker.
|
||||||
|
*
|
||||||
|
* @author Shadi Shaheen
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2016-03-09
|
||||||
|
*/
|
||||||
|
public class JFXColorPicker extends ColorPicker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public JFXColorPicker() {
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public JFXColorPicker(Color color) {
|
||||||
|
super(color);
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Skin<?> createDefaultSkin() {
|
||||||
|
return new JFXColorPickerSkin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
|
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the style class to 'jfx-color-picker'.
|
||||||
|
* <p>
|
||||||
|
* This is the selector class from which CSS can be used to style
|
||||||
|
* this control.
|
||||||
|
*/
|
||||||
|
private static final String DEFAULT_STYLE_CLASS = "jfx-color-picker";
|
||||||
|
|
||||||
|
private double[] preDefinedColors = null;
|
||||||
|
|
||||||
|
public double[] getPreDefinedColors() {
|
||||||
|
return preDefinedColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreDefinedColors(double[] preDefinedColors) {
|
||||||
|
this.preDefinedColors = preDefinedColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disable animation on button action
|
||||||
|
*/
|
||||||
|
private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION,
|
||||||
|
JFXColorPicker.this,
|
||||||
|
"disableAnimation",
|
||||||
|
false);
|
||||||
|
|
||||||
|
public final StyleableBooleanProperty disableAnimationProperty() {
|
||||||
|
return this.disableAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Boolean isDisableAnimation() {
|
||||||
|
return disableAnimation != null && this.disableAnimationProperty().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setDisableAnimation(final Boolean disabled) {
|
||||||
|
this.disableAnimationProperty().set(disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StyleableProperties {
|
||||||
|
|
||||||
|
private static final CssMetaData<JFXColorPicker, Boolean> DISABLE_ANIMATION =
|
||||||
|
new CssMetaData<JFXColorPicker, Boolean>("-jfx-disable-animation",
|
||||||
|
BooleanConverter.getInstance(), false) {
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(JFXColorPicker control) {
|
||||||
|
return control.disableAnimation == null || !control.disableAnimation.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableBooleanProperty getStyleableProperty(JFXColorPicker control) {
|
||||||
|
return control.disableAnimationProperty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||||
|
new ArrayList<>(ColorPicker.getClassCssMetaData());
|
||||||
|
Collections.addAll(styleables, DISABLE_ANIMATION);
|
||||||
|
CHILD_STYLEABLES = Collections.unmodifiableList(styleables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
|
||||||
|
return getClassCssMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||||
|
return StyleableProperties.CHILD_STYLEABLES;
|
||||||
|
}
|
||||||
|
}
|
||||||
800
HMCL/src/main/java/com/jfoenix/controls/JFXRippler.java
Normal file
800
HMCL/src/main/java/com/jfoenix/controls/JFXRippler.java
Normal file
@@ -0,0 +1,800 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.controls;
|
||||||
|
|
||||||
|
import com.jfoenix.converters.RipplerMaskTypeConverter;
|
||||||
|
import com.jfoenix.utils.JFXNodeUtils;
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.beans.DefaultProperty;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.css.*;
|
||||||
|
import javafx.css.converter.BooleanConverter;
|
||||||
|
import javafx.css.converter.PaintConverter;
|
||||||
|
import javafx.css.converter.SizeConverter;
|
||||||
|
import javafx.geometry.Bounds;
|
||||||
|
import javafx.scene.CacheHint;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.paint.Paint;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
import javafx.scene.shape.Shape;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JFXRippler is the material design implementation of a ripple effect.
|
||||||
|
* the ripple effect can be applied to any node in the scene. JFXRippler is
|
||||||
|
* a {@link StackPane} container that holds a specified node (control node) and a ripple generator.
|
||||||
|
* <p>
|
||||||
|
* UPDATE NOTES:
|
||||||
|
* - fireEventProgrammatically(Event) method has been removed as the ripple controller is
|
||||||
|
* the control itself, so you can trigger manual ripple by firing mouse event on the control
|
||||||
|
* instead of JFXRippler
|
||||||
|
*
|
||||||
|
* @author Shadi Shaheen
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2016-03-09
|
||||||
|
*/
|
||||||
|
@DefaultProperty(value = "control")
|
||||||
|
public class JFXRippler extends StackPane {
|
||||||
|
public enum RipplerPos {
|
||||||
|
FRONT, BACK
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RipplerMask {
|
||||||
|
CIRCLE, RECT, FIT
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RippleGenerator rippler;
|
||||||
|
protected Pane ripplerPane;
|
||||||
|
protected Node control;
|
||||||
|
|
||||||
|
protected static final double RIPPLE_MAX_RADIUS = 300;
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
private boolean forceOverlay = false;
|
||||||
|
private final Interpolator rippleInterpolator = Interpolator.SPLINE(0.0825,
|
||||||
|
0.3025,
|
||||||
|
0.0875,
|
||||||
|
0.9975); //0.1, 0.54, 0.28, 0.95);
|
||||||
|
|
||||||
|
/// creates empty rippler node
|
||||||
|
public JFXRippler() {
|
||||||
|
this(null, RipplerMask.RECT, RipplerPos.FRONT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates a rippler for the specified control
|
||||||
|
public JFXRippler(Node control) {
|
||||||
|
this(control, RipplerMask.RECT, RipplerPos.FRONT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates a rippler for the specified control
|
||||||
|
///
|
||||||
|
/// @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control)
|
||||||
|
public JFXRippler(Node control, RipplerPos pos) {
|
||||||
|
this(control, RipplerMask.RECT, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates a rippler for the specified control and apply the specified mask to it
|
||||||
|
///
|
||||||
|
/// @param mask can be either rectangle/cricle
|
||||||
|
public JFXRippler(Node control, RipplerMask mask) {
|
||||||
|
this(control, mask, RipplerPos.FRONT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates a rippler for the specified control, mask and position.
|
||||||
|
///
|
||||||
|
/// @param mask can be either rectangle/cricle
|
||||||
|
/// @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control)
|
||||||
|
public JFXRippler(Node control, RipplerMask mask, RipplerPos pos) {
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
setMaskType(mask);
|
||||||
|
setPosition(pos);
|
||||||
|
createRippleUI();
|
||||||
|
setControl(control);
|
||||||
|
|
||||||
|
// listen to control position changed
|
||||||
|
position.addListener(observable -> updateControlPosition());
|
||||||
|
|
||||||
|
setPickOnBounds(false);
|
||||||
|
setCache(true);
|
||||||
|
setCacheHint(CacheHint.SPEED);
|
||||||
|
setCacheShape(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void createRippleUI() {
|
||||||
|
// create rippler panels
|
||||||
|
rippler = new RippleGenerator();
|
||||||
|
ripplerPane = new StackPane();
|
||||||
|
ripplerPane.setMouseTransparent(true);
|
||||||
|
ripplerPane.getChildren().add(rippler);
|
||||||
|
getChildren().add(ripplerPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Setters / Getters *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
public void setControl(Node control) {
|
||||||
|
if (control != null) {
|
||||||
|
this.control = control;
|
||||||
|
// position control
|
||||||
|
positionControl(control);
|
||||||
|
// add control listeners to generate / release ripples
|
||||||
|
initControlListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override this method to create JFXRippler for a control outside the ripple
|
||||||
|
protected void positionControl(Node control) {
|
||||||
|
if (this.position.get() == RipplerPos.BACK) {
|
||||||
|
getChildren().add(control);
|
||||||
|
} else {
|
||||||
|
getChildren().add(0, control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateControlPosition() {
|
||||||
|
if (this.position.get() == RipplerPos.BACK) {
|
||||||
|
ripplerPane.toBack();
|
||||||
|
} else {
|
||||||
|
ripplerPane.toFront();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getControl() {
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enable) {
|
||||||
|
this.enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods that can be changed by extending the rippler class
|
||||||
|
|
||||||
|
/// generate the clipping mask
|
||||||
|
///
|
||||||
|
/// @return the mask node
|
||||||
|
protected Node getMask() {
|
||||||
|
double borderWidth = ripplerPane.getBorder() != null ? ripplerPane.getBorder().getInsets().getTop() : 0;
|
||||||
|
Bounds bounds = control.getBoundsInParent();
|
||||||
|
double width = control.getLayoutBounds().getWidth();
|
||||||
|
double height = control.getLayoutBounds().getHeight();
|
||||||
|
double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());
|
||||||
|
double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());
|
||||||
|
double diffMaxX = Math.abs(control.getBoundsInLocal().getMaxX() - control.getLayoutBounds().getMaxX());
|
||||||
|
double diffMaxY = Math.abs(control.getBoundsInLocal().getMaxY() - control.getLayoutBounds().getMaxY());
|
||||||
|
Node mask;
|
||||||
|
switch (getMaskType()) {
|
||||||
|
case RECT:
|
||||||
|
mask = new Rectangle(bounds.getMinX() + diffMinX - snappedLeftInset(),
|
||||||
|
bounds.getMinY() + diffMinY - snappedTopInset(),
|
||||||
|
width - 2 * borderWidth,
|
||||||
|
height - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane
|
||||||
|
break;
|
||||||
|
case CIRCLE:
|
||||||
|
double radius = Math.min((width / 2) - 2 * borderWidth, (height / 2) - 2 * borderWidth);
|
||||||
|
mask = new Circle((bounds.getMinX() + diffMinX + bounds.getMaxX() - diffMaxX) / 2 - snappedLeftInset(),
|
||||||
|
(bounds.getMinY() + diffMinY + bounds.getMaxY() - diffMaxY) / 2 - snappedTopInset(),
|
||||||
|
radius,
|
||||||
|
Color.BLUE);
|
||||||
|
break;
|
||||||
|
case FIT:
|
||||||
|
mask = new Region();
|
||||||
|
if (control instanceof Shape) {
|
||||||
|
((Region) mask).setShape((Shape) control);
|
||||||
|
} else if (control instanceof Region) {
|
||||||
|
((Region) mask).setShape(((Region) control).getShape());
|
||||||
|
JFXNodeUtils.updateBackground(((Region) control).getBackground(), (Region) mask);
|
||||||
|
}
|
||||||
|
mask.resize(width, height);
|
||||||
|
mask.relocate(bounds.getMinX() + diffMinX, bounds.getMinY() + diffMinY);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mask = new Rectangle(bounds.getMinX() + diffMinX - snappedLeftInset(),
|
||||||
|
bounds.getMinY() + diffMinY - snappedTopInset(),
|
||||||
|
width - 2 * borderWidth,
|
||||||
|
height - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compute the ripple radius
|
||||||
|
*
|
||||||
|
* @return the ripple radius size
|
||||||
|
*/
|
||||||
|
protected double computeRippleRadius() {
|
||||||
|
double width2 = control.getLayoutBounds().getWidth() * control.getLayoutBounds().getWidth();
|
||||||
|
double height2 = control.getLayoutBounds().getHeight() * control.getLayoutBounds().getHeight();
|
||||||
|
return Math.min(Math.sqrt(width2 + height2), RIPPLE_MAX_RADIUS) * 1.1 + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setOverLayBounds(Rectangle overlay) {
|
||||||
|
overlay.setWidth(control.getLayoutBounds().getWidth());
|
||||||
|
overlay.setHeight(control.getLayoutBounds().getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init mouse listeners on the control
|
||||||
|
*/
|
||||||
|
protected void initControlListeners() {
|
||||||
|
// if the control got resized the overlay rect must be rest
|
||||||
|
control.layoutBoundsProperty().addListener(observable -> resetRippler());
|
||||||
|
if (getChildren().contains(control)) {
|
||||||
|
control.boundsInParentProperty().addListener(observable -> resetRippler());
|
||||||
|
}
|
||||||
|
control.addEventHandler(MouseEvent.MOUSE_PRESSED,
|
||||||
|
(event) -> createRipple(event.getX(), event.getY()));
|
||||||
|
// create fade out transition for the ripple
|
||||||
|
control.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> releaseRipple());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates Ripple effect
|
||||||
|
*/
|
||||||
|
protected void createRipple(double x, double y) {
|
||||||
|
if (!isRipplerDisabled()) {
|
||||||
|
rippler.setGeneratorCenterX(x);
|
||||||
|
rippler.setGeneratorCenterY(y);
|
||||||
|
rippler.createRipple();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void releaseRipple() {
|
||||||
|
rippler.releaseRipple();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates Ripple effect in the center of the control
|
||||||
|
*
|
||||||
|
* @return a runnable to release the ripple when needed
|
||||||
|
*/
|
||||||
|
public Runnable createManualRipple() {
|
||||||
|
if (!isRipplerDisabled()) {
|
||||||
|
rippler.setGeneratorCenterX(control.getLayoutBounds().getWidth() / 2);
|
||||||
|
rippler.setGeneratorCenterY(control.getLayoutBounds().getHeight() / 2);
|
||||||
|
rippler.createRipple();
|
||||||
|
return () -> {
|
||||||
|
// create fade out transition for the ripple
|
||||||
|
releaseRipple();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return () -> {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// show/hide the ripple overlay
|
||||||
|
///
|
||||||
|
/// @param forceOverlay used to hold the overlay after ripple action
|
||||||
|
public void setOverlayVisible(boolean visible, boolean forceOverlay) {
|
||||||
|
this.forceOverlay = forceOverlay;
|
||||||
|
setOverlayVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// show/hide the ripple overlay
|
||||||
|
/// NOTE: setting overlay visibility to false will reset forceOverlay to false
|
||||||
|
public void setOverlayVisible(boolean visible) {
|
||||||
|
if (visible) {
|
||||||
|
showOverlay();
|
||||||
|
} else {
|
||||||
|
forceOverlay = false;
|
||||||
|
hideOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method will be set to private in future versions of JFoenix,
|
||||||
|
* user the method {@link #setOverlayVisible(boolean)}
|
||||||
|
*/
|
||||||
|
public void showOverlay() {
|
||||||
|
if (rippler.overlayRect != null) {
|
||||||
|
rippler.overlayRect.outAnimation.stop();
|
||||||
|
}
|
||||||
|
rippler.createOverlay();
|
||||||
|
rippler.overlayRect.inAnimation.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideOverlay() {
|
||||||
|
if (!forceOverlay) {
|
||||||
|
if (rippler.overlayRect != null) {
|
||||||
|
rippler.overlayRect.inAnimation.stop();
|
||||||
|
}
|
||||||
|
if (rippler.overlayRect != null) {
|
||||||
|
rippler.overlayRect.outAnimation.play();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println("Ripple Overlay is forced!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates ripples on the screen every 0.3 seconds or whenever
|
||||||
|
* the createRipple method is called. Ripples grow and fade out
|
||||||
|
* over 0.6 seconds
|
||||||
|
*/
|
||||||
|
protected final class RippleGenerator extends Group {
|
||||||
|
|
||||||
|
private double generatorCenterX = 0;
|
||||||
|
private double generatorCenterY = 0;
|
||||||
|
private OverLayRipple overlayRect;
|
||||||
|
private final AtomicBoolean generating = new AtomicBoolean(false);
|
||||||
|
private boolean cacheRipplerClip = false;
|
||||||
|
private boolean resetClip = false;
|
||||||
|
private final Queue<Ripple> ripplesQueue = new LinkedList<>();
|
||||||
|
|
||||||
|
RippleGenerator() {
|
||||||
|
// improve in performance, by preventing
|
||||||
|
// redrawing the parent when the ripple effect is triggered
|
||||||
|
this.setManaged(false);
|
||||||
|
this.setCache(true);
|
||||||
|
this.setCacheHint(CacheHint.SPEED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createRipple() {
|
||||||
|
if (enabled) {
|
||||||
|
if (!generating.getAndSet(true)) {
|
||||||
|
// create overlay once then change its color later
|
||||||
|
createOverlay();
|
||||||
|
if (this.getClip() == null || (getChildren().size() == 1 && !cacheRipplerClip) || resetClip) {
|
||||||
|
this.setClip(getMask());
|
||||||
|
}
|
||||||
|
this.resetClip = false;
|
||||||
|
|
||||||
|
// create the ripple effect
|
||||||
|
final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY);
|
||||||
|
getChildren().add(ripple);
|
||||||
|
ripplesQueue.add(ripple);
|
||||||
|
|
||||||
|
// animate the ripple
|
||||||
|
overlayRect.outAnimation.stop();
|
||||||
|
overlayRect.inAnimation.play();
|
||||||
|
ripple.inAnimation.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseRipple() {
|
||||||
|
Ripple ripple = ripplesQueue.poll();
|
||||||
|
if (ripple != null) {
|
||||||
|
ripple.inAnimation.stop();
|
||||||
|
ripple.outAnimation = new Timeline(
|
||||||
|
new KeyFrame(Duration.millis(Math.min(800, (0.9 * 500) / ripple.getScaleX()))
|
||||||
|
, ripple.outKeyValues));
|
||||||
|
ripple.outAnimation.setOnFinished((event) -> getChildren().remove(ripple));
|
||||||
|
ripple.outAnimation.play();
|
||||||
|
if (generating.getAndSet(false)) {
|
||||||
|
if (overlayRect != null) {
|
||||||
|
overlayRect.inAnimation.stop();
|
||||||
|
if (!forceOverlay) {
|
||||||
|
overlayRect.outAnimation.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cacheRippleClip(boolean cached) {
|
||||||
|
cacheRipplerClip = cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
void createOverlay() {
|
||||||
|
if (overlayRect == null) {
|
||||||
|
overlayRect = new OverLayRipple();
|
||||||
|
overlayRect.setClip(getMask());
|
||||||
|
getChildren().add(0, overlayRect);
|
||||||
|
overlayRect.fillProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
if (ripplerFill.get() instanceof Color) {
|
||||||
|
return new Color(((Color) ripplerFill.get()).getRed(),
|
||||||
|
((Color) ripplerFill.get()).getGreen(),
|
||||||
|
((Color) ripplerFill.get()).getBlue(),
|
||||||
|
0.2);
|
||||||
|
} else {
|
||||||
|
return Color.TRANSPARENT;
|
||||||
|
}
|
||||||
|
}, ripplerFill));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGeneratorCenterX(double generatorCenterX) {
|
||||||
|
this.generatorCenterX = generatorCenterX;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGeneratorCenterY(double generatorCenterY) {
|
||||||
|
this.generatorCenterY = generatorCenterY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class OverLayRipple extends Rectangle {
|
||||||
|
// Overlay ripple animations
|
||||||
|
Animation inAnimation = new Timeline(new KeyFrame(Duration.millis(300),
|
||||||
|
new KeyValue(opacityProperty(), 1, Interpolator.EASE_IN)));
|
||||||
|
|
||||||
|
Animation outAnimation = new Timeline(new KeyFrame(Duration.millis(300),
|
||||||
|
new KeyValue(opacityProperty(), 0, Interpolator.EASE_OUT)));
|
||||||
|
|
||||||
|
OverLayRipple() {
|
||||||
|
super();
|
||||||
|
setOverLayBounds(this);
|
||||||
|
this.getStyleClass().add("jfx-rippler-overlay");
|
||||||
|
// update initial position
|
||||||
|
if (JFXRippler.this.getChildrenUnmodifiable().contains(control)) {
|
||||||
|
double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());
|
||||||
|
double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());
|
||||||
|
Bounds bounds = control.getBoundsInParent();
|
||||||
|
this.setX(bounds.getMinX() + diffMinX - snappedLeftInset());
|
||||||
|
this.setY(bounds.getMinY() + diffMinY - snappedTopInset());
|
||||||
|
}
|
||||||
|
// set initial attributes
|
||||||
|
setOpacity(0);
|
||||||
|
setCache(true);
|
||||||
|
setCacheHint(CacheHint.SPEED);
|
||||||
|
setCacheShape(true);
|
||||||
|
setManaged(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Ripple extends Circle {
|
||||||
|
|
||||||
|
KeyValue[] outKeyValues;
|
||||||
|
Animation outAnimation = null;
|
||||||
|
Animation inAnimation = null;
|
||||||
|
|
||||||
|
private Ripple(double centerX, double centerY) {
|
||||||
|
super(centerX,
|
||||||
|
centerY,
|
||||||
|
ripplerRadius.get().doubleValue() == Region.USE_COMPUTED_SIZE ?
|
||||||
|
computeRippleRadius() : ripplerRadius.get().doubleValue(), null);
|
||||||
|
setCache(true);
|
||||||
|
setCacheHint(CacheHint.SPEED);
|
||||||
|
setCacheShape(true);
|
||||||
|
setManaged(false);
|
||||||
|
setSmooth(true);
|
||||||
|
|
||||||
|
KeyValue[] inKeyValues = new KeyValue[isRipplerRecenter() ? 4 : 2];
|
||||||
|
outKeyValues = new KeyValue[isRipplerRecenter() ? 5 : 3];
|
||||||
|
|
||||||
|
inKeyValues[0] = new KeyValue(scaleXProperty(), 0.9, rippleInterpolator);
|
||||||
|
inKeyValues[1] = new KeyValue(scaleYProperty(), 0.9, rippleInterpolator);
|
||||||
|
|
||||||
|
outKeyValues[0] = new KeyValue(this.scaleXProperty(), 1, rippleInterpolator);
|
||||||
|
outKeyValues[1] = new KeyValue(this.scaleYProperty(), 1, rippleInterpolator);
|
||||||
|
outKeyValues[2] = new KeyValue(this.opacityProperty(), 0, rippleInterpolator);
|
||||||
|
|
||||||
|
if (isRipplerRecenter()) {
|
||||||
|
double dx = (control.getLayoutBounds().getWidth() / 2 - centerX) / 1.55;
|
||||||
|
double dy = (control.getLayoutBounds().getHeight() / 2 - centerY) / 1.55;
|
||||||
|
inKeyValues[2] = outKeyValues[3] = new KeyValue(translateXProperty(),
|
||||||
|
Math.signum(dx) * Math.min(Math.abs(dx),
|
||||||
|
this.getRadius() / 2),
|
||||||
|
rippleInterpolator);
|
||||||
|
inKeyValues[3] = outKeyValues[4] = new KeyValue(translateYProperty(),
|
||||||
|
Math.signum(dy) * Math.min(Math.abs(dy),
|
||||||
|
this.getRadius() / 2),
|
||||||
|
rippleInterpolator);
|
||||||
|
}
|
||||||
|
inAnimation = new Timeline(new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(scaleXProperty(),
|
||||||
|
0,
|
||||||
|
rippleInterpolator),
|
||||||
|
new KeyValue(scaleYProperty(),
|
||||||
|
0,
|
||||||
|
rippleInterpolator),
|
||||||
|
new KeyValue(translateXProperty(),
|
||||||
|
0,
|
||||||
|
rippleInterpolator),
|
||||||
|
new KeyValue(translateYProperty(),
|
||||||
|
0,
|
||||||
|
rippleInterpolator),
|
||||||
|
new KeyValue(opacityProperty(),
|
||||||
|
1,
|
||||||
|
rippleInterpolator)
|
||||||
|
), new KeyFrame(Duration.millis(900), inKeyValues));
|
||||||
|
|
||||||
|
setScaleX(0);
|
||||||
|
setScaleY(0);
|
||||||
|
if (ripplerFill.get() instanceof Color) {
|
||||||
|
Color circleColor = new Color(((Color) ripplerFill.get()).getRed(),
|
||||||
|
((Color) ripplerFill.get()).getGreen(),
|
||||||
|
((Color) ripplerFill.get()).getBlue(),
|
||||||
|
0.3);
|
||||||
|
setStroke(circleColor);
|
||||||
|
setFill(circleColor);
|
||||||
|
} else {
|
||||||
|
setStroke(ripplerFill.get());
|
||||||
|
setFill(ripplerFill.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
getChildren().clear();
|
||||||
|
rippler.overlayRect = null;
|
||||||
|
generating.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetOverLay() {
|
||||||
|
if (rippler.overlayRect != null) {
|
||||||
|
rippler.overlayRect.inAnimation.stop();
|
||||||
|
final RippleGenerator.OverLayRipple oldOverlay = rippler.overlayRect;
|
||||||
|
rippler.overlayRect.outAnimation.setOnFinished((finish) -> rippler.getChildren().remove(oldOverlay));
|
||||||
|
rippler.overlayRect.outAnimation.play();
|
||||||
|
rippler.overlayRect = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetClip() {
|
||||||
|
this.rippler.resetClip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resetRippler() {
|
||||||
|
resetOverLay();
|
||||||
|
resetClip();
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Stylesheet Handling *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the style class to 'jfx-rippler'.
|
||||||
|
* <p>
|
||||||
|
* This is the selector class from which CSS can be used to style
|
||||||
|
* this control.
|
||||||
|
*/
|
||||||
|
private static final String DEFAULT_STYLE_CLASS = "jfx-rippler";
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
|
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the ripple recenter property, by default it's false.
|
||||||
|
* if true the ripple effect will show gravitational pull to the center of its control
|
||||||
|
*/
|
||||||
|
private final StyleableObjectProperty<Boolean> ripplerRecenter = new SimpleStyleableObjectProperty<>(
|
||||||
|
StyleableProperties.RIPPLER_RECENTER,
|
||||||
|
JFXRippler.this,
|
||||||
|
"ripplerRecenter",
|
||||||
|
false);
|
||||||
|
|
||||||
|
public Boolean isRipplerRecenter() {
|
||||||
|
return ripplerRecenter != null && ripplerRecenter.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleableObjectProperty<Boolean> ripplerRecenterProperty() {
|
||||||
|
return this.ripplerRecenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRipplerRecenter(Boolean radius) {
|
||||||
|
this.ripplerRecenter.set(radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the ripple radius size, by default it will be automatically computed.
|
||||||
|
*/
|
||||||
|
private final StyleableObjectProperty<Number> ripplerRadius = new SimpleStyleableObjectProperty<>(
|
||||||
|
StyleableProperties.RIPPLER_RADIUS,
|
||||||
|
JFXRippler.this,
|
||||||
|
"ripplerRadius",
|
||||||
|
Region.USE_COMPUTED_SIZE);
|
||||||
|
|
||||||
|
public Number getRipplerRadius() {
|
||||||
|
return ripplerRadius == null ? Region.USE_COMPUTED_SIZE : ripplerRadius.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleableObjectProperty<Number> ripplerRadiusProperty() {
|
||||||
|
return this.ripplerRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRipplerRadius(Number radius) {
|
||||||
|
this.ripplerRadius.set(radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the default color of the ripple effect
|
||||||
|
*/
|
||||||
|
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,
|
||||||
|
JFXRippler.this,
|
||||||
|
"ripplerFill",
|
||||||
|
Color.rgb(0,
|
||||||
|
200,
|
||||||
|
255));
|
||||||
|
|
||||||
|
public Paint getRipplerFill() {
|
||||||
|
return ripplerFill == null ? Color.rgb(0, 200, 255) : ripplerFill.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleableObjectProperty<Paint> ripplerFillProperty() {
|
||||||
|
return this.ripplerFill;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRipplerFill(Paint color) {
|
||||||
|
this.ripplerFill.set(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// mask property used for clipping the rippler.
|
||||||
|
/// can be either CIRCLE/RECT
|
||||||
|
private final StyleableObjectProperty<RipplerMask> maskType = new SimpleStyleableObjectProperty<>(
|
||||||
|
StyleableProperties.MASK_TYPE,
|
||||||
|
JFXRippler.this,
|
||||||
|
"maskType",
|
||||||
|
RipplerMask.RECT);
|
||||||
|
|
||||||
|
public RipplerMask getMaskType() {
|
||||||
|
return maskType == null ? RipplerMask.RECT : maskType.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleableObjectProperty<RipplerMask> maskTypeProperty() {
|
||||||
|
return this.maskType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaskType(RipplerMask type) {
|
||||||
|
this.maskType.set(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the ripple disable, by default it's false.
|
||||||
|
* if true the ripple effect will be hidden
|
||||||
|
*/
|
||||||
|
private final StyleableBooleanProperty ripplerDisabled = new SimpleStyleableBooleanProperty(
|
||||||
|
StyleableProperties.RIPPLER_DISABLED,
|
||||||
|
JFXRippler.this,
|
||||||
|
"ripplerDisabled",
|
||||||
|
false);
|
||||||
|
|
||||||
|
public Boolean isRipplerDisabled() {
|
||||||
|
return ripplerDisabled != null && ripplerDisabled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleableBooleanProperty ripplerDisabledProperty() {
|
||||||
|
return this.ripplerDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRipplerDisabled(Boolean disabled) {
|
||||||
|
this.ripplerDisabled.set(disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* indicates whether the ripple effect is infront of or behind the node
|
||||||
|
*/
|
||||||
|
protected ObjectProperty<RipplerPos> position = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
public void setPosition(RipplerPos pos) {
|
||||||
|
this.position.set(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RipplerPos getPosition() {
|
||||||
|
return position == null ? RipplerPos.FRONT : position.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<RipplerPos> positionProperty() {
|
||||||
|
return this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StyleableProperties {
|
||||||
|
private static final CssMetaData<JFXRippler, Boolean> RIPPLER_RECENTER =
|
||||||
|
new CssMetaData<>("-jfx-rippler-recenter",
|
||||||
|
BooleanConverter.getInstance(), false) {
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(JFXRippler control) {
|
||||||
|
return control.ripplerRecenter == null || !control.ripplerRecenter.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableProperty<Boolean> getStyleableProperty(JFXRippler control) {
|
||||||
|
return control.ripplerRecenterProperty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final CssMetaData<JFXRippler, Boolean> RIPPLER_DISABLED =
|
||||||
|
new CssMetaData<>("-jfx-rippler-disabled",
|
||||||
|
BooleanConverter.getInstance(), false) {
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(JFXRippler control) {
|
||||||
|
return control.ripplerDisabled == null || !control.ripplerDisabled.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableProperty<Boolean> getStyleableProperty(JFXRippler control) {
|
||||||
|
return control.ripplerDisabledProperty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final CssMetaData<JFXRippler, Paint> RIPPLER_FILL =
|
||||||
|
new CssMetaData<>("-jfx-rippler-fill",
|
||||||
|
PaintConverter.getInstance(), Color.rgb(0, 200, 255)) {
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(JFXRippler control) {
|
||||||
|
return control.ripplerFill == null || !control.ripplerFill.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableProperty<Paint> getStyleableProperty(JFXRippler control) {
|
||||||
|
return control.ripplerFillProperty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final CssMetaData<JFXRippler, Number> RIPPLER_RADIUS =
|
||||||
|
new CssMetaData<>("-jfx-rippler-radius",
|
||||||
|
SizeConverter.getInstance(), Region.USE_COMPUTED_SIZE) {
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(JFXRippler control) {
|
||||||
|
return control.ripplerRadius == null || !control.ripplerRadius.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableProperty<Number> getStyleableProperty(JFXRippler control) {
|
||||||
|
return control.ripplerRadiusProperty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final CssMetaData<JFXRippler, RipplerMask> MASK_TYPE =
|
||||||
|
new CssMetaData<>("-jfx-mask-type",
|
||||||
|
RipplerMaskTypeConverter.getInstance(), RipplerMask.RECT) {
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(JFXRippler control) {
|
||||||
|
return control.maskType == null || !control.maskType.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableProperty<RipplerMask> getStyleableProperty(JFXRippler control) {
|
||||||
|
return control.maskTypeProperty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||||
|
new ArrayList<>(StackPane.getClassCssMetaData());
|
||||||
|
Collections.addAll(styleables,
|
||||||
|
RIPPLER_RECENTER,
|
||||||
|
RIPPLER_RADIUS,
|
||||||
|
RIPPLER_FILL,
|
||||||
|
MASK_TYPE,
|
||||||
|
RIPPLER_DISABLED
|
||||||
|
);
|
||||||
|
STYLEABLES = Collections.unmodifiableList(styleables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||||
|
return getClassCssMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||||
|
return StyleableProperties.STYLEABLES;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.controls.behavior;
|
||||||
|
|
||||||
|
import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;
|
||||||
|
import javafx.scene.control.ComboBoxBase;
|
||||||
|
import javafx.scene.control.PopupControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shadi Shaheen
|
||||||
|
* @version 2.0
|
||||||
|
* @since 2017-10-05
|
||||||
|
*/
|
||||||
|
public class JFXGenericPickerBehavior<T> extends ComboBoxBaseBehavior<T> {
|
||||||
|
|
||||||
|
public JFXGenericPickerBehavior(ComboBoxBase<T> var1) {
|
||||||
|
super(var1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAutoHide(PopupControl var1) {
|
||||||
|
if (!var1.isShowing() && this.getNode().isShowing()) {
|
||||||
|
this.getNode().hide();
|
||||||
|
}
|
||||||
|
if (!this.getNode().isShowing()) {
|
||||||
|
super.onAutoHide(var1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
629
HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java
Normal file
629
HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java
Normal file
@@ -0,0 +1,629 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.skins;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXColorPicker;
|
||||||
|
import com.jfoenix.utils.JFXNodeUtils;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener.Change;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.Event;
|
||||||
|
import javafx.geometry.Bounds;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.NodeOrientation;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.PopupControl;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
import javafx.scene.shape.StrokeType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shadi Shaheen FUTURE WORK: this UI will get re-designed to match material design guidlines
|
||||||
|
*/
|
||||||
|
final class JFXColorPalette extends Region {
|
||||||
|
|
||||||
|
private static final int SQUARE_SIZE = 15;
|
||||||
|
|
||||||
|
// package protected for testing purposes
|
||||||
|
JFXColorGrid colorPickerGrid;
|
||||||
|
final JFXButton customColorLink = new JFXButton(i18n("color.custom"));
|
||||||
|
JFXCustomColorPickerDialog customColorDialog = null;
|
||||||
|
|
||||||
|
private final JFXColorPicker colorPicker;
|
||||||
|
private final GridPane customColorGrid = new GridPane();
|
||||||
|
private final Label customColorLabel = new Label(i18n("color.recent"));
|
||||||
|
|
||||||
|
private PopupControl popupControl;
|
||||||
|
private ColorSquare focusedSquare;
|
||||||
|
|
||||||
|
private Color mouseDragColor = null;
|
||||||
|
private boolean dragDetected = false;
|
||||||
|
|
||||||
|
private final ColorSquare hoverSquare = new ColorSquare();
|
||||||
|
|
||||||
|
public JFXColorPalette(final JFXColorPicker colorPicker) {
|
||||||
|
getStyleClass().add("color-palette-region");
|
||||||
|
this.colorPicker = colorPicker;
|
||||||
|
colorPickerGrid = new JFXColorGrid();
|
||||||
|
colorPickerGrid.getChildren().get(0).requestFocus();
|
||||||
|
customColorLabel.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
customColorLink.setPrefWidth(colorPickerGrid.prefWidth(-1));
|
||||||
|
customColorLink.setAlignment(Pos.CENTER);
|
||||||
|
customColorLink.setFocusTraversable(true);
|
||||||
|
customColorLink.setOnAction(ev -> {
|
||||||
|
if (customColorDialog == null) {
|
||||||
|
customColorDialog = new JFXCustomColorPickerDialog(popupControl);
|
||||||
|
customColorDialog.customColorProperty().addListener((ov, t1, t2) -> {
|
||||||
|
colorPicker.setValue(customColorDialog.customColorProperty().get());
|
||||||
|
});
|
||||||
|
customColorDialog.setOnSave(() -> {
|
||||||
|
Color customColor = customColorDialog.customColorProperty().get();
|
||||||
|
buildCustomColors();
|
||||||
|
colorPicker.getCustomColors().add(customColor);
|
||||||
|
updateSelection(customColor);
|
||||||
|
Event.fireEvent(colorPicker, new ActionEvent());
|
||||||
|
colorPicker.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
customColorDialog.setCurrentColor(colorPicker.valueProperty().get());
|
||||||
|
if (popupControl != null) {
|
||||||
|
popupControl.setAutoHide(false);
|
||||||
|
}
|
||||||
|
customColorDialog.show();
|
||||||
|
customColorDialog.setOnHidden(event -> {
|
||||||
|
if (popupControl != null) {
|
||||||
|
popupControl.setAutoHide(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
initNavigation();
|
||||||
|
customColorGrid.getStyleClass().add("color-picker-grid");
|
||||||
|
customColorGrid.setVisible(false);
|
||||||
|
|
||||||
|
buildCustomColors();
|
||||||
|
|
||||||
|
colorPicker.getCustomColors().addListener((Change<? extends Color> change) -> buildCustomColors());
|
||||||
|
VBox paletteBox = new VBox();
|
||||||
|
paletteBox.getStyleClass().add("color-palette");
|
||||||
|
paletteBox.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
|
paletteBox.setBorder(new Border(new BorderStroke(Color.valueOf("#9E9E9E"),
|
||||||
|
BorderStrokeStyle.SOLID,
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
BorderWidths.DEFAULT)));
|
||||||
|
paletteBox.getChildren().addAll(colorPickerGrid);
|
||||||
|
if (colorPicker.getPreDefinedColors() == null) {
|
||||||
|
paletteBox.getChildren().addAll(customColorLabel, customColorGrid, customColorLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverSquare.setMouseTransparent(true);
|
||||||
|
hoverSquare.getStyleClass().addAll("hover-square");
|
||||||
|
setFocusedSquare(null);
|
||||||
|
|
||||||
|
getChildren().addAll(paletteBox, hoverSquare);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFocusedSquare(ColorSquare square) {
|
||||||
|
hoverSquare.setVisible(square != null);
|
||||||
|
|
||||||
|
if (square == focusedSquare) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
focusedSquare = square;
|
||||||
|
|
||||||
|
hoverSquare.setVisible(focusedSquare != null);
|
||||||
|
if (focusedSquare == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!focusedSquare.isFocused()) {
|
||||||
|
focusedSquare.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverSquare.rectangle.setFill(focusedSquare.rectangle.getFill());
|
||||||
|
|
||||||
|
Bounds b = square.localToScene(square.getLayoutBounds());
|
||||||
|
|
||||||
|
double x = b.getMinX();
|
||||||
|
double y = b.getMinY();
|
||||||
|
|
||||||
|
double xAdjust;
|
||||||
|
double scaleAdjust = hoverSquare.getScaleX() == 1.0 ? 0 : hoverSquare.getWidth() / 4.0;
|
||||||
|
|
||||||
|
if (colorPicker.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
|
||||||
|
x = focusedSquare.getLayoutX();
|
||||||
|
xAdjust = -focusedSquare.getWidth() + scaleAdjust;
|
||||||
|
} else {
|
||||||
|
xAdjust = focusedSquare.getWidth() / 2.0 + scaleAdjust;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverSquare.setLayoutX(snapPositionX(x) - xAdjust);
|
||||||
|
hoverSquare.setLayoutY(snapPositionY(y) - focusedSquare.getHeight() / 2.0 + (hoverSquare.getScaleY() == 1.0 ? 0 : focusedSquare.getHeight() / 4.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildCustomColors() {
|
||||||
|
final ObservableList<Color> customColors = colorPicker.getCustomColors();
|
||||||
|
customColorGrid.getChildren().clear();
|
||||||
|
if (customColors.isEmpty()) {
|
||||||
|
customColorLabel.setVisible(false);
|
||||||
|
customColorLabel.setManaged(false);
|
||||||
|
customColorGrid.setVisible(false);
|
||||||
|
customColorGrid.setManaged(false);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
customColorLabel.setVisible(true);
|
||||||
|
customColorLabel.setManaged(true);
|
||||||
|
customColorGrid.setVisible(true);
|
||||||
|
customColorGrid.setManaged(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int customColumnIndex = 0;
|
||||||
|
int customRowIndex = 0;
|
||||||
|
int remainingSquares = customColors.size() % NUM_OF_COLUMNS;
|
||||||
|
int numEmpty = (remainingSquares == 0) ? 0 : NUM_OF_COLUMNS - remainingSquares;
|
||||||
|
|
||||||
|
for (int i = 0; i < customColors.size(); i++) {
|
||||||
|
Color c = customColors.get(i);
|
||||||
|
ColorSquare square = new ColorSquare(c, i, true);
|
||||||
|
customColorGrid.add(square, customColumnIndex, customRowIndex);
|
||||||
|
customColumnIndex++;
|
||||||
|
if (customColumnIndex == NUM_OF_COLUMNS) {
|
||||||
|
customColumnIndex = 0;
|
||||||
|
customRowIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < numEmpty; i++) {
|
||||||
|
ColorSquare emptySquare = new ColorSquare();
|
||||||
|
customColorGrid.add(emptySquare, customColumnIndex, customRowIndex);
|
||||||
|
customColumnIndex++;
|
||||||
|
}
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initNavigation() {
|
||||||
|
setOnKeyPressed(ke -> {
|
||||||
|
switch (ke.getCode()) {
|
||||||
|
case SPACE:
|
||||||
|
case ENTER:
|
||||||
|
// select the focused color
|
||||||
|
if (focusedSquare != null) {
|
||||||
|
focusedSquare.selectColor(ke);
|
||||||
|
}
|
||||||
|
ke.consume();
|
||||||
|
break;
|
||||||
|
default: // no-op
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPopupControl(PopupControl pc) {
|
||||||
|
this.popupControl = pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JFXColorGrid getColorGrid() {
|
||||||
|
return colorPickerGrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCustomColorDialogShowing() {
|
||||||
|
return customColorDialog != null && customColorDialog.isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ColorSquare extends StackPane {
|
||||||
|
Rectangle rectangle;
|
||||||
|
boolean isEmpty;
|
||||||
|
|
||||||
|
public ColorSquare() {
|
||||||
|
this(null, -1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorSquare(Color color, int index) {
|
||||||
|
this(color, index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorSquare(Color color, int index, boolean isCustom) {
|
||||||
|
// Add style class to handle selected color square
|
||||||
|
getStyleClass().add("color-square");
|
||||||
|
if (color != null) {
|
||||||
|
setFocusTraversable(true);
|
||||||
|
focusedProperty().addListener((s, ov, nv) -> setFocusedSquare(nv ? this : null));
|
||||||
|
addEventHandler(MouseEvent.MOUSE_ENTERED, event -> setFocusedSquare(ColorSquare.this));
|
||||||
|
addEventHandler(MouseEvent.MOUSE_EXITED, event -> setFocusedSquare(null));
|
||||||
|
addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
|
||||||
|
if (!dragDetected && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
|
||||||
|
if (!isEmpty) {
|
||||||
|
Color fill = (Color) rectangle.getFill();
|
||||||
|
colorPicker.setValue(fill);
|
||||||
|
colorPicker.fireEvent(new ActionEvent());
|
||||||
|
updateSelection(fill);
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
colorPicker.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rectangle = new Rectangle(SQUARE_SIZE, SQUARE_SIZE);
|
||||||
|
if (color == null) {
|
||||||
|
rectangle.setFill(Color.WHITE);
|
||||||
|
isEmpty = true;
|
||||||
|
} else {
|
||||||
|
rectangle.setFill(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
rectangle.setStrokeType(StrokeType.INSIDE);
|
||||||
|
|
||||||
|
String tooltipStr = JFXNodeUtils.colorToHex(color);
|
||||||
|
Tooltip.install(this, new Tooltip((tooltipStr == null) ? "" : tooltipStr));
|
||||||
|
|
||||||
|
rectangle.getStyleClass().add("color-rect");
|
||||||
|
getChildren().add(rectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectColor(KeyEvent event) {
|
||||||
|
if (rectangle.getFill() != null) {
|
||||||
|
if (rectangle.getFill() instanceof Color) {
|
||||||
|
colorPicker.setValue((Color) rectangle.getFill());
|
||||||
|
colorPicker.fireEvent(new ActionEvent());
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
colorPicker.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The skin can update selection if colorpicker value changes..
|
||||||
|
public void updateSelection(Color color) {
|
||||||
|
setFocusedSquare(null);
|
||||||
|
|
||||||
|
for (ColorSquare c : colorPickerGrid.getSquares()) {
|
||||||
|
if (c.rectangle.getFill().equals(color)) {
|
||||||
|
setFocusedSquare(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check custom colors
|
||||||
|
for (Node n : customColorGrid.getChildren()) {
|
||||||
|
ColorSquare c = (ColorSquare) n;
|
||||||
|
if (c.rectangle.getFill().equals(color)) {
|
||||||
|
setFocusedSquare(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JFXColorGrid extends GridPane {
|
||||||
|
|
||||||
|
private final List<ColorSquare> squares;
|
||||||
|
final int NUM_OF_COLORS;
|
||||||
|
final int NUM_OF_ROWS;
|
||||||
|
|
||||||
|
public JFXColorGrid() {
|
||||||
|
getStyleClass().add("color-picker-grid");
|
||||||
|
setId("ColorCustomizerColorGrid");
|
||||||
|
int columnIndex = 0;
|
||||||
|
int rowIndex = 0;
|
||||||
|
squares = FXCollections.observableArrayList();
|
||||||
|
double[] limitedColors = colorPicker.getPreDefinedColors();
|
||||||
|
limitedColors = limitedColors == null ? RAW_VALUES : limitedColors;
|
||||||
|
NUM_OF_COLORS = limitedColors.length / 3;
|
||||||
|
NUM_OF_ROWS = (int) Math.ceil((double) NUM_OF_COLORS / (double) NUM_OF_COLUMNS);
|
||||||
|
final int numColors = limitedColors.length / 3;
|
||||||
|
Color[] colors = new Color[numColors];
|
||||||
|
for (int i = 0; i < numColors; i++) {
|
||||||
|
colors[i] = new Color(limitedColors[i * 3] / 255,
|
||||||
|
limitedColors[(i * 3) + 1] / 255, limitedColors[(i * 3) + 2] / 255,
|
||||||
|
1.0);
|
||||||
|
ColorSquare cs = new ColorSquare(colors[i], i);
|
||||||
|
squares.add(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ColorSquare square : squares) {
|
||||||
|
add(square, columnIndex, rowIndex);
|
||||||
|
columnIndex++;
|
||||||
|
if (columnIndex == NUM_OF_COLUMNS) {
|
||||||
|
columnIndex = 0;
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnMouseDragged(t -> {
|
||||||
|
if (!dragDetected) {
|
||||||
|
dragDetected = true;
|
||||||
|
mouseDragColor = colorPicker.getValue();
|
||||||
|
}
|
||||||
|
int xIndex = clamp(0,
|
||||||
|
(int) t.getX() / (SQUARE_SIZE + 1), NUM_OF_COLUMNS - 1);
|
||||||
|
int yIndex = clamp(0,
|
||||||
|
(int) t.getY() / (SQUARE_SIZE + 1), NUM_OF_ROWS - 1);
|
||||||
|
int index = xIndex + yIndex * NUM_OF_COLUMNS;
|
||||||
|
colorPicker.setValue((Color) squares.get(index).rectangle.getFill());
|
||||||
|
updateSelection(colorPicker.getValue());
|
||||||
|
});
|
||||||
|
addEventHandler(MouseEvent.MOUSE_RELEASED, t -> {
|
||||||
|
if (colorPickerGrid.getBoundsInLocal().contains(t.getX(), t.getY())) {
|
||||||
|
updateSelection(colorPicker.getValue());
|
||||||
|
colorPicker.fireEvent(new ActionEvent());
|
||||||
|
colorPicker.hide();
|
||||||
|
} else {
|
||||||
|
// restore color as mouse release happened outside the grid.
|
||||||
|
if (mouseDragColor != null) {
|
||||||
|
colorPicker.setValue(mouseDragColor);
|
||||||
|
updateSelection(mouseDragColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dragDetected = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ColorSquare> getSquares() {
|
||||||
|
return squares;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computePrefWidth(double height) {
|
||||||
|
return (SQUARE_SIZE + 1) * NUM_OF_COLUMNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computePrefHeight(double width) {
|
||||||
|
return (SQUARE_SIZE + 1) * NUM_OF_ROWS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int NUM_OF_COLUMNS = 10;
|
||||||
|
private static final double[] RAW_VALUES = {
|
||||||
|
// WARNING: always make sure the number of colors is a divisable by NUM_OF_COLUMNS
|
||||||
|
250, 250, 250, // first row
|
||||||
|
245, 245, 245,
|
||||||
|
238, 238, 238,
|
||||||
|
224, 224, 224,
|
||||||
|
189, 189, 189,
|
||||||
|
158, 158, 158,
|
||||||
|
117, 117, 117,
|
||||||
|
97, 97, 97,
|
||||||
|
66, 66, 66,
|
||||||
|
33, 33, 33,
|
||||||
|
// second row
|
||||||
|
236, 239, 241,
|
||||||
|
207, 216, 220,
|
||||||
|
176, 190, 197,
|
||||||
|
144, 164, 174,
|
||||||
|
120, 144, 156,
|
||||||
|
96, 125, 139,
|
||||||
|
84, 110, 122,
|
||||||
|
69, 90, 100,
|
||||||
|
55, 71, 79,
|
||||||
|
38, 50, 56,
|
||||||
|
// third row
|
||||||
|
255, 235, 238,
|
||||||
|
255, 205, 210,
|
||||||
|
239, 154, 154,
|
||||||
|
229, 115, 115,
|
||||||
|
239, 83, 80,
|
||||||
|
244, 67, 54,
|
||||||
|
229, 57, 53,
|
||||||
|
211, 47, 47,
|
||||||
|
198, 40, 40,
|
||||||
|
183, 28, 28,
|
||||||
|
// forth row
|
||||||
|
252, 228, 236,
|
||||||
|
248, 187, 208,
|
||||||
|
244, 143, 177,
|
||||||
|
240, 98, 146,
|
||||||
|
236, 64, 122,
|
||||||
|
233, 30, 99,
|
||||||
|
216, 27, 96,
|
||||||
|
194, 24, 91,
|
||||||
|
173, 20, 87,
|
||||||
|
136, 14, 79,
|
||||||
|
// fifth row
|
||||||
|
243, 229, 245,
|
||||||
|
225, 190, 231,
|
||||||
|
206, 147, 216,
|
||||||
|
186, 104, 200,
|
||||||
|
171, 71, 188,
|
||||||
|
156, 39, 176,
|
||||||
|
142, 36, 170,
|
||||||
|
123, 31, 162,
|
||||||
|
106, 27, 154,
|
||||||
|
74, 20, 140,
|
||||||
|
// sixth row
|
||||||
|
237, 231, 246,
|
||||||
|
209, 196, 233,
|
||||||
|
179, 157, 219,
|
||||||
|
149, 117, 205,
|
||||||
|
126, 87, 194,
|
||||||
|
103, 58, 183,
|
||||||
|
94, 53, 177,
|
||||||
|
81, 45, 168,
|
||||||
|
69, 39, 160,
|
||||||
|
49, 27, 146,
|
||||||
|
// seventh row
|
||||||
|
232, 234, 246,
|
||||||
|
197, 202, 233,
|
||||||
|
159, 168, 218,
|
||||||
|
121, 134, 203,
|
||||||
|
92, 107, 192,
|
||||||
|
63, 81, 181,
|
||||||
|
57, 73, 171,
|
||||||
|
48, 63, 159,
|
||||||
|
40, 53, 147,
|
||||||
|
26, 35, 126,
|
||||||
|
// eigth row
|
||||||
|
227, 242, 253,
|
||||||
|
187, 222, 251,
|
||||||
|
144, 202, 249,
|
||||||
|
100, 181, 246,
|
||||||
|
66, 165, 245,
|
||||||
|
33, 150, 243,
|
||||||
|
30, 136, 229,
|
||||||
|
25, 118, 210,
|
||||||
|
21, 101, 192,
|
||||||
|
13, 71, 161,
|
||||||
|
// ninth row
|
||||||
|
225, 245, 254,
|
||||||
|
179, 229, 252,
|
||||||
|
129, 212, 250,
|
||||||
|
79, 195, 247,
|
||||||
|
41, 182, 246,
|
||||||
|
3, 169, 244,
|
||||||
|
3, 155, 229,
|
||||||
|
2, 136, 209,
|
||||||
|
2, 119, 189,
|
||||||
|
1, 87, 155,
|
||||||
|
// tenth row
|
||||||
|
224, 247, 250,
|
||||||
|
178, 235, 242,
|
||||||
|
128, 222, 234,
|
||||||
|
77, 208, 225,
|
||||||
|
38, 198, 218,
|
||||||
|
0, 188, 212,
|
||||||
|
0, 172, 193,
|
||||||
|
0, 151, 167,
|
||||||
|
0, 131, 143,
|
||||||
|
0, 96, 100,
|
||||||
|
// eleventh row
|
||||||
|
224, 242, 241,
|
||||||
|
178, 223, 219,
|
||||||
|
128, 203, 196,
|
||||||
|
77, 182, 172,
|
||||||
|
38, 166, 154,
|
||||||
|
0, 150, 136,
|
||||||
|
0, 137, 123,
|
||||||
|
0, 121, 107,
|
||||||
|
0, 105, 92,
|
||||||
|
0, 77, 64,
|
||||||
|
// twelfth row
|
||||||
|
232, 245, 233,
|
||||||
|
200, 230, 201,
|
||||||
|
165, 214, 167,
|
||||||
|
129, 199, 132,
|
||||||
|
102, 187, 106,
|
||||||
|
76, 175, 80,
|
||||||
|
67, 160, 71,
|
||||||
|
56, 142, 60,
|
||||||
|
46, 125, 50,
|
||||||
|
27, 94, 32,
|
||||||
|
|
||||||
|
// thirteenth row
|
||||||
|
241, 248, 233,
|
||||||
|
220, 237, 200,
|
||||||
|
197, 225, 165,
|
||||||
|
174, 213, 129,
|
||||||
|
156, 204, 101,
|
||||||
|
139, 195, 74,
|
||||||
|
124, 179, 66,
|
||||||
|
104, 159, 56,
|
||||||
|
85, 139, 47,
|
||||||
|
51, 105, 30,
|
||||||
|
// fourteenth row
|
||||||
|
249, 251, 231,
|
||||||
|
240, 244, 195,
|
||||||
|
230, 238, 156,
|
||||||
|
220, 231, 117,
|
||||||
|
212, 225, 87,
|
||||||
|
205, 220, 57,
|
||||||
|
192, 202, 51,
|
||||||
|
175, 180, 43,
|
||||||
|
158, 157, 36,
|
||||||
|
130, 119, 23,
|
||||||
|
|
||||||
|
// fifteenth row
|
||||||
|
255, 253, 231,
|
||||||
|
255, 249, 196,
|
||||||
|
255, 245, 157,
|
||||||
|
255, 241, 118,
|
||||||
|
255, 238, 88,
|
||||||
|
255, 235, 59,
|
||||||
|
253, 216, 53,
|
||||||
|
251, 192, 45,
|
||||||
|
249, 168, 37,
|
||||||
|
245, 127, 23,
|
||||||
|
|
||||||
|
// sixteenth row
|
||||||
|
255, 248, 225,
|
||||||
|
255, 236, 179,
|
||||||
|
255, 224, 130,
|
||||||
|
255, 213, 79,
|
||||||
|
255, 202, 40,
|
||||||
|
255, 193, 7,
|
||||||
|
255, 179, 0,
|
||||||
|
255, 160, 0,
|
||||||
|
255, 143, 0,
|
||||||
|
255, 111, 0,
|
||||||
|
|
||||||
|
// seventeenth row
|
||||||
|
255, 243, 224,
|
||||||
|
255, 224, 178,
|
||||||
|
255, 204, 128,
|
||||||
|
255, 183, 77,
|
||||||
|
255, 167, 38,
|
||||||
|
255, 152, 0,
|
||||||
|
251, 140, 0,
|
||||||
|
245, 124, 0,
|
||||||
|
239, 108, 0,
|
||||||
|
230, 81, 0,
|
||||||
|
|
||||||
|
// eighteenth row
|
||||||
|
251, 233, 231,
|
||||||
|
255, 204, 188,
|
||||||
|
255, 171, 145,
|
||||||
|
255, 138, 101,
|
||||||
|
255, 112, 67,
|
||||||
|
255, 87, 34,
|
||||||
|
244, 81, 30,
|
||||||
|
230, 74, 25,
|
||||||
|
216, 67, 21,
|
||||||
|
191, 54, 12,
|
||||||
|
|
||||||
|
// nineteenth row
|
||||||
|
239, 235, 233,
|
||||||
|
215, 204, 200,
|
||||||
|
188, 170, 164,
|
||||||
|
161, 136, 127,
|
||||||
|
141, 110, 99,
|
||||||
|
121, 85, 72,
|
||||||
|
109, 76, 65,
|
||||||
|
93, 64, 55,
|
||||||
|
78, 52, 46,
|
||||||
|
62, 39, 35,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static int clamp(int min, int value, int max) {
|
||||||
|
if (value < min) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
if (value > max) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
251
HMCL/src/main/java/com/jfoenix/skins/JFXColorPickerSkin.java
Normal file
251
HMCL/src/main/java/com/jfoenix/skins/JFXColorPickerSkin.java
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.skins;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXClippedPane;
|
||||||
|
import com.jfoenix.controls.JFXColorPicker;
|
||||||
|
import com.jfoenix.controls.JFXRippler;
|
||||||
|
import com.jfoenix.effects.JFXDepthManager;
|
||||||
|
import com.jfoenix.utils.JFXNodeUtils;
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.css.*;
|
||||||
|
import javafx.css.converter.BooleanConverter;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.ColorPicker;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.skin.ComboBoxPopupControl;
|
||||||
|
import javafx.scene.layout.Background;
|
||||||
|
import javafx.scene.layout.BackgroundFill;
|
||||||
|
import javafx.scene.layout.CornerRadii;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shadi Shaheen
|
||||||
|
*/
|
||||||
|
public final class JFXColorPickerSkin extends JFXGenericPickerSkin<Color> {
|
||||||
|
|
||||||
|
private final Label displayNode;
|
||||||
|
private final JFXClippedPane colorBox;
|
||||||
|
private JFXColorPalette popupContent;
|
||||||
|
StyleableBooleanProperty colorLabelVisible = new SimpleStyleableBooleanProperty(StyleableProperties.COLOR_LABEL_VISIBLE,
|
||||||
|
JFXColorPickerSkin.this,
|
||||||
|
"colorLabelVisible",
|
||||||
|
true);
|
||||||
|
|
||||||
|
public JFXColorPickerSkin(final ColorPicker colorPicker) {
|
||||||
|
super(colorPicker);
|
||||||
|
|
||||||
|
// create displayNode
|
||||||
|
displayNode = new Label("");
|
||||||
|
displayNode.getStyleClass().add("color-label");
|
||||||
|
displayNode.setMouseTransparent(true);
|
||||||
|
|
||||||
|
// label graphic
|
||||||
|
colorBox = new JFXClippedPane(displayNode);
|
||||||
|
colorBox.getStyleClass().add("color-box");
|
||||||
|
colorBox.setManaged(false);
|
||||||
|
initColor();
|
||||||
|
final JFXRippler rippler = new JFXRippler(colorBox, JFXRippler.RipplerMask.FIT);
|
||||||
|
rippler.ripplerFillProperty().bind(displayNode.textFillProperty());
|
||||||
|
getChildren().setAll(rippler);
|
||||||
|
JFXDepthManager.setDepth(getSkinnable(), 1);
|
||||||
|
getSkinnable().setPickOnBounds(false);
|
||||||
|
|
||||||
|
colorPicker.focusedProperty().addListener(observable -> {
|
||||||
|
if (colorPicker.isFocused()) {
|
||||||
|
if (!getSkinnable().isPressed()) {
|
||||||
|
rippler.setOverlayVisible(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rippler.setOverlayVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// add listeners
|
||||||
|
registerChangeListener(colorPicker.valueProperty(), obs -> updateColor());
|
||||||
|
|
||||||
|
colorLabelVisible.addListener(invalidate -> {
|
||||||
|
if (colorLabelVisible.get()) {
|
||||||
|
displayNode.setText(JFXNodeUtils.colorToHex(getSkinnable().getValue()));
|
||||||
|
} else {
|
||||||
|
displayNode.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||||
|
double width = 100;
|
||||||
|
String displayNodeText = displayNode.getText();
|
||||||
|
displayNode.setText("#DDDDDD");
|
||||||
|
width = Math.max(width, super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset));
|
||||||
|
displayNode.setText(displayNodeText);
|
||||||
|
return width + rightInset + leftInset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||||
|
if (colorBox == null) {
|
||||||
|
reflectUpdateDisplayArea();
|
||||||
|
}
|
||||||
|
return topInset + colorBox.prefHeight(width) + bottomInset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void layoutChildren(double x, double y, double w, double h) {
|
||||||
|
super.layoutChildren(x, y, w, h);
|
||||||
|
double hInsets = snappedLeftInset() + snappedRightInset();
|
||||||
|
double vInsets = snappedTopInset() + snappedBottomInset();
|
||||||
|
double width = w + hInsets;
|
||||||
|
double height = h + vInsets;
|
||||||
|
colorBox.resizeRelocate(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Node getPopupContent() {
|
||||||
|
if (popupContent == null) {
|
||||||
|
popupContent = new JFXColorPalette((JFXColorPicker) getSkinnable());
|
||||||
|
}
|
||||||
|
return popupContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
super.show();
|
||||||
|
final ColorPicker colorPicker = (ColorPicker) getSkinnable();
|
||||||
|
popupContent.updateSelection(colorPicker.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getDisplayNode() {
|
||||||
|
return displayNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateColor() {
|
||||||
|
final ColorPicker colorPicker = (ColorPicker) getSkinnable();
|
||||||
|
Color color = colorPicker.getValue();
|
||||||
|
Color circleColor = color == null ? Color.WHITE : color;
|
||||||
|
// update picker box color
|
||||||
|
if (((JFXColorPicker) getSkinnable()).isDisableAnimation()) {
|
||||||
|
JFXNodeUtils.updateBackground(colorBox.getBackground(), colorBox, circleColor);
|
||||||
|
} else {
|
||||||
|
Circle colorCircle = new Circle();
|
||||||
|
colorCircle.setFill(circleColor);
|
||||||
|
colorCircle.setManaged(false);
|
||||||
|
colorCircle.setLayoutX(colorBox.getWidth() / 4);
|
||||||
|
colorCircle.setLayoutY(colorBox.getHeight() / 2);
|
||||||
|
colorBox.getChildren().add(colorCircle);
|
||||||
|
Timeline animateColor = new Timeline(new KeyFrame(Duration.millis(240),
|
||||||
|
new KeyValue(colorCircle.radiusProperty(),
|
||||||
|
200,
|
||||||
|
Interpolator.EASE_BOTH)));
|
||||||
|
animateColor.setOnFinished((finish) -> {
|
||||||
|
JFXNodeUtils.updateBackground(colorBox.getBackground(), colorBox, colorCircle.getFill());
|
||||||
|
colorBox.getChildren().remove(colorCircle);
|
||||||
|
});
|
||||||
|
animateColor.play();
|
||||||
|
}
|
||||||
|
// update label color
|
||||||
|
displayNode.setTextFill(circleColor.grayscale().getRed() < 0.5 ? Color.valueOf(
|
||||||
|
"rgba(255, 255, 255, 0.87)") : Color.valueOf("rgba(0, 0, 0, 0.87)"));
|
||||||
|
if (colorLabelVisible.get()) {
|
||||||
|
displayNode.setText(JFXNodeUtils.colorToHex(circleColor));
|
||||||
|
} else {
|
||||||
|
displayNode.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initColor() {
|
||||||
|
final ColorPicker colorPicker = (ColorPicker) getSkinnable();
|
||||||
|
Color color = colorPicker.getValue();
|
||||||
|
Color circleColor = color == null ? Color.WHITE : color;
|
||||||
|
// update picker box color
|
||||||
|
colorBox.setBackground(new Background(new BackgroundFill(circleColor, new CornerRadii(3), Insets.EMPTY)));
|
||||||
|
// update label color
|
||||||
|
displayNode.setTextFill(circleColor.grayscale().getRed() < 0.5 ? Color.valueOf(
|
||||||
|
"rgba(255, 255, 255, 0.87)") : Color.valueOf("rgba(0, 0, 0, 0.87)"));
|
||||||
|
if (colorLabelVisible.get()) {
|
||||||
|
displayNode.setText(JFXNodeUtils.colorToHex(circleColor));
|
||||||
|
} else {
|
||||||
|
displayNode.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Stylesheet Handling *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
private static final class StyleableProperties {
|
||||||
|
private static final CssMetaData<ColorPicker, Boolean> COLOR_LABEL_VISIBLE =
|
||||||
|
new CssMetaData<ColorPicker, Boolean>("-fx-color-label-visible",
|
||||||
|
BooleanConverter.getInstance(), Boolean.TRUE) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(ColorPicker n) {
|
||||||
|
final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin();
|
||||||
|
return skin.colorLabelVisible == null || !skin.colorLabelVisible.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableProperty<Boolean> getStyleableProperty(ColorPicker n) {
|
||||||
|
final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin();
|
||||||
|
return skin.colorLabelVisible;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||||
|
new ArrayList<>(ComboBoxPopupControl.getClassCssMetaData());
|
||||||
|
styleables.add(COLOR_LABEL_VISIBLE);
|
||||||
|
STYLEABLES = Collections.unmodifiableList(styleables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||||
|
return StyleableProperties.STYLEABLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||||
|
return getClassCssMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TextField getEditor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected javafx.util.StringConverter<Color> getConverter() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
629
HMCL/src/main/java/com/jfoenix/skins/JFXColorPickerUI.java
Normal file
629
HMCL/src/main/java/com/jfoenix/skins/JFXColorPickerUI.java
Normal file
@@ -0,0 +1,629 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.skins;
|
||||||
|
|
||||||
|
import com.jfoenix.effects.JFXDepthManager;
|
||||||
|
import com.jfoenix.transitions.CachedTransition;
|
||||||
|
import javafx.animation.Animation.Status;
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.effect.ColorAdjust;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.image.PixelWriter;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import javafx.scene.shape.Path;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shadi Shaheen & Bassel El Mabsout this UI allows the user to pick a color using HSL color system
|
||||||
|
*/
|
||||||
|
final class JFXColorPickerUI extends Pane {
|
||||||
|
|
||||||
|
private CachedTransition selectorTransition;
|
||||||
|
private int pickerSize = 400;
|
||||||
|
// sl circle selector size
|
||||||
|
private final int selectorSize = 20;
|
||||||
|
private final double centerX;
|
||||||
|
private final double centerY;
|
||||||
|
private final double huesRadius;
|
||||||
|
private final double slRadius;
|
||||||
|
private double currentHue = 0;
|
||||||
|
|
||||||
|
private final ImageView huesCircleView;
|
||||||
|
private final ImageView slCircleView;
|
||||||
|
private final Pane colorSelector;
|
||||||
|
private final Pane selector;
|
||||||
|
private CurveTransition colorsTransition;
|
||||||
|
|
||||||
|
public JFXColorPickerUI(int pickerSize) {
|
||||||
|
JFXDepthManager.setDepth(this, 1);
|
||||||
|
|
||||||
|
this.pickerSize = pickerSize;
|
||||||
|
this.centerX = (double) pickerSize / 2;
|
||||||
|
this.centerY = (double) pickerSize / 2;
|
||||||
|
final double pickerRadius = (double) pickerSize / 2;
|
||||||
|
this.huesRadius = pickerRadius * 0.9;
|
||||||
|
final double huesSmallR = pickerRadius * 0.8;
|
||||||
|
final double huesLargeR = pickerRadius;
|
||||||
|
this.slRadius = pickerRadius * 0.7;
|
||||||
|
|
||||||
|
// Create Hues Circle
|
||||||
|
huesCircleView = new ImageView(getHuesCircle(pickerSize, pickerSize));
|
||||||
|
// clip to smooth the edges
|
||||||
|
Circle outterCircle = new Circle(centerX, centerY, huesLargeR - 2);
|
||||||
|
Circle innterCircle = new Circle(centerX, centerY, huesSmallR + 2);
|
||||||
|
huesCircleView.setClip(Path.subtract(outterCircle, innterCircle));
|
||||||
|
this.getChildren().add(huesCircleView);
|
||||||
|
|
||||||
|
// create Hues Circle Selector
|
||||||
|
Circle r1 = new Circle(pickerRadius - huesSmallR);
|
||||||
|
Circle r2 = new Circle(pickerRadius - huesRadius);
|
||||||
|
colorSelector = new Pane();
|
||||||
|
colorSelector.setStyle("-fx-border-color:#424242; -fx-border-width:1px; -fx-background-color:rgba(255, 255, 255, 0.87);");
|
||||||
|
colorSelector.setPrefSize(pickerRadius - huesSmallR, pickerRadius - huesSmallR);
|
||||||
|
colorSelector.setShape(Path.subtract(r1, r2));
|
||||||
|
colorSelector.setCache(true);
|
||||||
|
colorSelector.setMouseTransparent(true);
|
||||||
|
colorSelector.setPickOnBounds(false);
|
||||||
|
this.getChildren().add(colorSelector);
|
||||||
|
|
||||||
|
// add Hues Selection Listeners
|
||||||
|
huesCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {
|
||||||
|
if (colorsTransition != null) {
|
||||||
|
colorsTransition.stop();
|
||||||
|
}
|
||||||
|
double dx = event.getX() - centerX;
|
||||||
|
double dy = event.getY() - centerY;
|
||||||
|
double theta = Math.atan2(dy, dx);
|
||||||
|
double x = centerX + huesRadius * Math.cos(theta);
|
||||||
|
double y = centerY + huesRadius * Math.sin(theta);
|
||||||
|
colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));
|
||||||
|
colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);
|
||||||
|
colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);
|
||||||
|
});
|
||||||
|
huesCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
|
||||||
|
double dx = event.getX() - centerX;
|
||||||
|
double dy = event.getY() - centerY;
|
||||||
|
double theta = Math.atan2(dy, dx);
|
||||||
|
double x = centerX + huesRadius * Math.cos(theta);
|
||||||
|
double y = centerY + huesRadius * Math.sin(theta);
|
||||||
|
colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,
|
||||||
|
colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2),
|
||||||
|
new Point2D(x, y));
|
||||||
|
colorsTransition.play();
|
||||||
|
});
|
||||||
|
colorSelector.translateXProperty()
|
||||||
|
.addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (newVal.intValue() + colorSelector.getPrefWidth() / 2),
|
||||||
|
(int) (colorSelector.getTranslateY() + colorSelector
|
||||||
|
.getPrefHeight() / 2)));
|
||||||
|
colorSelector.translateYProperty()
|
||||||
|
.addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (colorSelector.getTranslateX() + colorSelector
|
||||||
|
.getPrefWidth() / 2), (int) (newVal.intValue() + colorSelector.getPrefHeight() / 2)));
|
||||||
|
|
||||||
|
|
||||||
|
// Create SL Circle
|
||||||
|
slCircleView = new ImageView(getSLCricle(pickerSize, pickerSize));
|
||||||
|
slCircleView.setClip(new Circle(centerX, centerY, slRadius - 2));
|
||||||
|
slCircleView.setPickOnBounds(false);
|
||||||
|
this.getChildren().add(slCircleView);
|
||||||
|
|
||||||
|
// create SL Circle Selector
|
||||||
|
selector = new Pane();
|
||||||
|
Circle c1 = new Circle(selectorSize / 2);
|
||||||
|
Circle c2 = new Circle((selectorSize / 2) * 0.5);
|
||||||
|
selector.setShape(Path.subtract(c1, c2));
|
||||||
|
selector.setStyle(
|
||||||
|
"-fx-border-color:#424242; -fx-border-width:1px;-fx-background-color:rgba(255, 255, 255, 0.87);");
|
||||||
|
selector.setPrefSize(selectorSize, selectorSize);
|
||||||
|
selector.setMinSize(selectorSize, selectorSize);
|
||||||
|
selector.setMaxSize(selectorSize, selectorSize);
|
||||||
|
selector.setCache(true);
|
||||||
|
selector.setMouseTransparent(true);
|
||||||
|
this.getChildren().add(selector);
|
||||||
|
|
||||||
|
|
||||||
|
// add SL selection Listeners
|
||||||
|
slCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {
|
||||||
|
if (selectorTransition != null) {
|
||||||
|
selectorTransition.stop();
|
||||||
|
}
|
||||||
|
if (Math.pow(event.getX() - centerX, 2) + Math.pow(event.getY() - centerY, 2) < Math.pow(slRadius - 2, 2)) {
|
||||||
|
selector.setTranslateX(event.getX() - selector.getPrefWidth() / 2);
|
||||||
|
selector.setTranslateY(event.getY() - selector.getPrefHeight() / 2);
|
||||||
|
} else {
|
||||||
|
double dx = event.getX() - centerX;
|
||||||
|
double dy = event.getY() - centerY;
|
||||||
|
double theta = Math.atan2(dy, dx);
|
||||||
|
double x = centerX + (slRadius - 2) * Math.cos(theta);
|
||||||
|
double y = centerY + (slRadius - 2) * Math.sin(theta);
|
||||||
|
selector.setTranslateX(x - selector.getPrefWidth() / 2);
|
||||||
|
selector.setTranslateY(y - selector.getPrefHeight() / 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
slCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
|
||||||
|
selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),
|
||||||
|
new KeyValue(selector.translateXProperty(),
|
||||||
|
event.getX() - selector.getPrefWidth() / 2,
|
||||||
|
Interpolator.EASE_BOTH),
|
||||||
|
new KeyValue(selector.translateYProperty(),
|
||||||
|
event.getY() - selector.getPrefHeight() / 2,
|
||||||
|
Interpolator.EASE_BOTH)))) {
|
||||||
|
{
|
||||||
|
setCycleDuration(Duration.millis(160));
|
||||||
|
setDelay(Duration.seconds(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
selectorTransition.play();
|
||||||
|
});
|
||||||
|
// add slCircleView listener
|
||||||
|
selector.translateXProperty()
|
||||||
|
.addListener((o, oldVal, newVal) -> setColorAtLocation(newVal.intValue() + selectorSize / 2,
|
||||||
|
(int) selector.getTranslateY() + selectorSize / 2));
|
||||||
|
selector.translateYProperty()
|
||||||
|
.addListener((o, oldVal, newVal) -> setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,
|
||||||
|
newVal.intValue() + selectorSize / 2));
|
||||||
|
|
||||||
|
|
||||||
|
// initial color selection
|
||||||
|
double dx = 20 - centerX;
|
||||||
|
double dy = 20 - centerY;
|
||||||
|
double theta = Math.atan2(dy, dx);
|
||||||
|
double x = centerX + huesRadius * Math.cos(theta);
|
||||||
|
double y = centerY + huesRadius * Math.sin(theta);
|
||||||
|
colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));
|
||||||
|
colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);
|
||||||
|
colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);
|
||||||
|
selector.setTranslateX(centerX - selector.getPrefWidth() / 2);
|
||||||
|
selector.setTranslateY(centerY - selector.getPrefHeight() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of Color Nodes that needs to be updated when picking a color
|
||||||
|
*/
|
||||||
|
private final ObservableList<Node> colorNodes = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
public void addColorSelectionNode(Node... nodes) {
|
||||||
|
colorNodes.addAll(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeColorSelectionNode(Node... nodes) {
|
||||||
|
colorNodes.removeAll(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHSLCircleColor(int x, int y) {
|
||||||
|
// transform color to HSL space
|
||||||
|
Color color = huesCircleView.getImage().getPixelReader().getColor(x, y);
|
||||||
|
double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue()));
|
||||||
|
double min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue()));
|
||||||
|
double hue = 0;
|
||||||
|
if (max != min) {
|
||||||
|
double d = max - min;
|
||||||
|
if (max == color.getRed()) {
|
||||||
|
hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);
|
||||||
|
} else if (max == color.getGreen()) {
|
||||||
|
hue = (color.getBlue() - color.getRed()) / d + 2;
|
||||||
|
} else if (max == color.getBlue()) {
|
||||||
|
hue = (color.getRed() - color.getGreen()) / d + 4;
|
||||||
|
}
|
||||||
|
hue /= 6;
|
||||||
|
}
|
||||||
|
currentHue = map(hue, 0, 1, 0, 255);
|
||||||
|
|
||||||
|
// refresh the HSL circle
|
||||||
|
refreshHSLCircle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshHSLCircle() {
|
||||||
|
ColorAdjust colorAdjust = new ColorAdjust();
|
||||||
|
colorAdjust.setHue(map(currentHue + (currentHue < 127.5 ? 1 : -1) * 127.5, 0, 255, -1, 1));
|
||||||
|
slCircleView.setEffect(colorAdjust);
|
||||||
|
setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,
|
||||||
|
(int) selector.getTranslateY() + selectorSize / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method is used to move selectors to a certain color
|
||||||
|
*/
|
||||||
|
private boolean allowColorChange = true;
|
||||||
|
private ParallelTransition pTrans;
|
||||||
|
|
||||||
|
public void moveToColor(Color color) {
|
||||||
|
allowColorChange = false;
|
||||||
|
double max = Math.max(color.getRed(),
|
||||||
|
Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(),
|
||||||
|
Math.min(color.getGreen(),
|
||||||
|
color.getBlue()));
|
||||||
|
double hue = 0;
|
||||||
|
double l = (max + min) / 2;
|
||||||
|
double s = 0;
|
||||||
|
if (max == min) {
|
||||||
|
hue = s = 0; // achromatic
|
||||||
|
} else {
|
||||||
|
double d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
if (max == color.getRed()) {
|
||||||
|
hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);
|
||||||
|
} else if (max == color.getGreen()) {
|
||||||
|
hue = (color.getBlue() - color.getRed()) / d + 2;
|
||||||
|
} else if (max == color.getBlue()) {
|
||||||
|
hue = (color.getRed() - color.getGreen()) / d + 4;
|
||||||
|
}
|
||||||
|
hue /= 6;
|
||||||
|
}
|
||||||
|
currentHue = map(hue, 0, 1, 0, 255);
|
||||||
|
|
||||||
|
// Animate Hue
|
||||||
|
double theta = map(currentHue, 0, 255, -Math.PI, Math.PI);
|
||||||
|
double x = centerX + huesRadius * Math.cos(theta);
|
||||||
|
double y = centerY + huesRadius * Math.sin(theta);
|
||||||
|
colorsTransition = new CurveTransition(
|
||||||
|
new Point2D(
|
||||||
|
colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,
|
||||||
|
colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2
|
||||||
|
),
|
||||||
|
new Point2D(x, y));
|
||||||
|
|
||||||
|
// Animate SL
|
||||||
|
s = map(s, 0, 1, 0, 255);
|
||||||
|
l = map(l, 0, 1, 0, 255);
|
||||||
|
Point2D point = getPointFromSL((int) s, (int) l, slRadius);
|
||||||
|
double pX = centerX - point.getX();
|
||||||
|
double pY = centerY - point.getY();
|
||||||
|
|
||||||
|
double endPointX;
|
||||||
|
double endPointY;
|
||||||
|
if (Math.pow(pX - centerX, 2) + Math.pow(pY - centerY, 2) < Math.pow(slRadius - 2, 2)) {
|
||||||
|
endPointX = pX - selector.getPrefWidth() / 2;
|
||||||
|
endPointY = pY - selector.getPrefHeight() / 2;
|
||||||
|
} else {
|
||||||
|
double dx = pX - centerX;
|
||||||
|
double dy = pY - centerY;
|
||||||
|
theta = Math.atan2(dy, dx);
|
||||||
|
x = centerX + (slRadius - 2) * Math.cos(theta);
|
||||||
|
y = centerY + (slRadius - 2) * Math.sin(theta);
|
||||||
|
endPointX = x - selector.getPrefWidth() / 2;
|
||||||
|
endPointY = y - selector.getPrefHeight() / 2;
|
||||||
|
}
|
||||||
|
selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),
|
||||||
|
new KeyValue(selector.translateXProperty(),
|
||||||
|
endPointX,
|
||||||
|
Interpolator.EASE_BOTH),
|
||||||
|
new KeyValue(selector.translateYProperty(),
|
||||||
|
endPointY,
|
||||||
|
Interpolator.EASE_BOTH)))) {
|
||||||
|
{
|
||||||
|
setCycleDuration(Duration.millis(160));
|
||||||
|
setDelay(Duration.seconds(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pTrans != null) {
|
||||||
|
pTrans.stop();
|
||||||
|
}
|
||||||
|
pTrans = new ParallelTransition(colorsTransition, selectorTransition);
|
||||||
|
pTrans.setOnFinished((finish) -> {
|
||||||
|
if (pTrans.getStatus() == Status.STOPPED) {
|
||||||
|
allowColorChange = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pTrans.play();
|
||||||
|
|
||||||
|
refreshHSLCircle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setColorAtLocation(int x, int y) {
|
||||||
|
if (allowColorChange) {
|
||||||
|
Color color = getColorAtLocation(x, y);
|
||||||
|
String colorString = "rgb(" + color.getRed() * 255 + "," + color.getGreen() * 255 + "," + color.getBlue() * 255 + ");";
|
||||||
|
for (Node node : colorNodes)
|
||||||
|
node.setStyle("-fx-background-color:" + colorString + "; -fx-fill:" + colorString + ";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getColorAtLocation(double x, double y) {
|
||||||
|
double dy = x - centerX;
|
||||||
|
double dx = y - centerY;
|
||||||
|
return getColor(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image getHuesCircle(int width, int height) {
|
||||||
|
WritableImage raster = new WritableImage(width, height);
|
||||||
|
PixelWriter pixelWriter = raster.getPixelWriter();
|
||||||
|
Point2D center = new Point2D((double) width / 2, (double) height / 2);
|
||||||
|
double rsmall = 0.8 * width / 2;
|
||||||
|
double rbig = (double) width / 2;
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
double dx = x - center.getX();
|
||||||
|
double dy = y - center.getY();
|
||||||
|
double distance = Math.sqrt((dx * dx) + (dy * dy));
|
||||||
|
double o = Math.atan2(dy, dx);
|
||||||
|
if (distance > rsmall && distance < rbig) {
|
||||||
|
double H = map(o, -Math.PI, Math.PI, 0, 255);
|
||||||
|
double S = 255;
|
||||||
|
double L = 152;
|
||||||
|
pixelWriter.setColor(x, y, HSL2RGB(H, S, L));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raster;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image getSLCricle(int width, int height) {
|
||||||
|
WritableImage raster = new WritableImage(width, height);
|
||||||
|
PixelWriter pixelWriter = raster.getPixelWriter();
|
||||||
|
Point2D center = new Point2D((double) width / 2, (double) height / 2);
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
double dy = x - center.getX();
|
||||||
|
double dx = y - center.getY();
|
||||||
|
pixelWriter.setColor(x, y, getColor(dx, dy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raster;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double clamp(double from, double small, double big) {
|
||||||
|
return Math.min(Math.max(from, small), big);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getColor(double dx, double dy) {
|
||||||
|
double distance = Math.sqrt((dx * dx) + (dy * dy));
|
||||||
|
double rverysmall = 0.65 * ((double) pickerSize / 2);
|
||||||
|
Color pixelColor = Color.BLUE;
|
||||||
|
|
||||||
|
if (distance <= rverysmall * 1.1) {
|
||||||
|
double angle = -Math.PI / 2.;
|
||||||
|
double angle1 = angle + 2 * Math.PI / 3.;
|
||||||
|
double angle2 = angle1 + 2 * Math.PI / 3.;
|
||||||
|
double x1 = rverysmall * Math.sin(angle1);
|
||||||
|
double y1 = rverysmall * Math.cos(angle1);
|
||||||
|
double x2 = rverysmall * Math.sin(angle2);
|
||||||
|
double y2 = rverysmall * Math.cos(angle2);
|
||||||
|
dx += 0.01;
|
||||||
|
double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(x2, y2), new Point2D(dx, dy));
|
||||||
|
double xArc = circle[0];
|
||||||
|
double yArc = 0;
|
||||||
|
double arcR = circle[2];
|
||||||
|
double Arco = Math.atan2(dx - xArc, dy - yArc);
|
||||||
|
double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);
|
||||||
|
double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);
|
||||||
|
|
||||||
|
double finalX = xArc > 0 ? xArc - arcR : xArc + arcR;
|
||||||
|
|
||||||
|
double saturation = map(finalX, -rverysmall, rverysmall, 255, 0);
|
||||||
|
|
||||||
|
double lightness = 255;
|
||||||
|
double diffAngle = Arco2 - Arco1;
|
||||||
|
double diffArco = Arco - Arco1;
|
||||||
|
if (dx < x1) {
|
||||||
|
diffAngle = diffAngle < 0 ? 2 * Math.PI + diffAngle : diffAngle;
|
||||||
|
diffAngle = Math.abs(2 * Math.PI - diffAngle);
|
||||||
|
diffArco = diffArco < 0 ? 2 * Math.PI + diffArco : diffArco;
|
||||||
|
diffArco = Math.abs(2 * Math.PI - diffArco);
|
||||||
|
}
|
||||||
|
lightness = map(diffArco, 0, diffAngle, 0, 255);
|
||||||
|
|
||||||
|
|
||||||
|
if (distance > rverysmall) {
|
||||||
|
saturation = 255 - saturation;
|
||||||
|
if (lightness < 0 && dy < 0) {
|
||||||
|
lightness = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lightness = clamp(lightness, 0, 255);
|
||||||
|
if ((saturation < 10 && dx < x1) || (saturation > 240 && dx > x1)) {
|
||||||
|
saturation = 255 - saturation;
|
||||||
|
}
|
||||||
|
saturation = clamp(saturation, 0, 255);
|
||||||
|
pixelColor = HSL2RGB(currentHue, saturation, lightness);
|
||||||
|
}
|
||||||
|
return pixelColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Hues Animation *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
private final class CurveTransition extends Transition {
|
||||||
|
Point2D from;
|
||||||
|
double fromTheta;
|
||||||
|
double toTheta;
|
||||||
|
|
||||||
|
public CurveTransition(Point2D from, Point2D to) {
|
||||||
|
this.from = from;
|
||||||
|
double fromDx = from.getX() - centerX;
|
||||||
|
double fromDy = from.getY() - centerY;
|
||||||
|
fromTheta = Math.atan2(fromDy, fromDx);
|
||||||
|
double toDx = to.getX() - centerX;
|
||||||
|
double toDy = to.getY() - centerY;
|
||||||
|
toTheta = Math.atan2(toDy, toDx);
|
||||||
|
setInterpolator(Interpolator.EASE_BOTH);
|
||||||
|
setDelay(Duration.millis(0));
|
||||||
|
setCycleDuration(Duration.millis(240));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void interpolate(double frac) {
|
||||||
|
double dif = Math.min(Math.abs(toTheta - fromTheta), 2 * Math.PI - Math.abs(toTheta - fromTheta));
|
||||||
|
if (dif == 2 * Math.PI - Math.abs(toTheta - fromTheta)) {
|
||||||
|
int dir = -1;
|
||||||
|
if (toTheta < fromTheta) {
|
||||||
|
dir = 1;
|
||||||
|
}
|
||||||
|
dif = dir * dif;
|
||||||
|
} else {
|
||||||
|
dif = toTheta - fromTheta;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point2D newP = rotate(from, new Point2D(centerX, centerY), frac * dif);
|
||||||
|
colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(newP.getY() - centerY, newP.getX() - centerX)));
|
||||||
|
colorSelector.setTranslateX(newP.getX() - colorSelector.getPrefWidth() / 2);
|
||||||
|
colorSelector.setTranslateY(newP.getY() - colorSelector.getPrefHeight() / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Util methods *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
private double map(double val, double min1, double max1, double min2, double max2) {
|
||||||
|
return min2 + (max2 - min2) * ((val - min1) / (max1 - min1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color HSL2RGB(double hue, double sat, double lum) {
|
||||||
|
hue = map(hue, 0, 255, 0, 359);
|
||||||
|
sat = map(sat, 0, 255, 0, 1);
|
||||||
|
lum = map(lum, 0, 255, 0, 1);
|
||||||
|
double v;
|
||||||
|
double red, green, blue;
|
||||||
|
double m;
|
||||||
|
double sv;
|
||||||
|
int sextant;
|
||||||
|
double fract, vsf, mid1, mid2;
|
||||||
|
|
||||||
|
red = lum; // default to gray
|
||||||
|
green = lum;
|
||||||
|
blue = lum;
|
||||||
|
v = (lum <= 0.5) ? (lum * (1.0 + sat)) : (lum + sat - lum * sat);
|
||||||
|
m = lum + lum - v;
|
||||||
|
sv = (v - m) / v;
|
||||||
|
hue /= 60.0; //get into range 0..6
|
||||||
|
sextant = (int) Math.floor(hue); // int32 rounds up or down.
|
||||||
|
fract = hue - sextant;
|
||||||
|
vsf = v * sv * fract;
|
||||||
|
mid1 = m + vsf;
|
||||||
|
mid2 = v - vsf;
|
||||||
|
|
||||||
|
if (v > 0) {
|
||||||
|
switch (sextant) {
|
||||||
|
case 0:
|
||||||
|
red = v;
|
||||||
|
green = mid1;
|
||||||
|
blue = m;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
red = mid2;
|
||||||
|
green = v;
|
||||||
|
blue = m;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
red = m;
|
||||||
|
green = v;
|
||||||
|
blue = mid1;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
red = m;
|
||||||
|
green = mid2;
|
||||||
|
blue = v;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
red = mid1;
|
||||||
|
green = m;
|
||||||
|
blue = v;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
red = v;
|
||||||
|
green = m;
|
||||||
|
blue = mid2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Color(red, green, blue, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double[] circleFrom3Points(Point2D a, Point2D b, Point2D c) {
|
||||||
|
double ax, ay, bx, by, cx, cy, x1, y11, dx1, dy1, x2, y2, dx2, dy2, ox, oy, dx, dy, radius; // Variables Used and to Declared
|
||||||
|
ax = a.getX();
|
||||||
|
ay = a.getY(); //first Point X and Y
|
||||||
|
bx = b.getX();
|
||||||
|
by = b.getY(); // Second Point X and Y
|
||||||
|
cx = c.getX();
|
||||||
|
cy = c.getY(); // Third Point X and Y
|
||||||
|
|
||||||
|
////****************Following are Basic Procedure**********************///
|
||||||
|
x1 = (bx + ax) / 2;
|
||||||
|
y11 = (by + ay) / 2;
|
||||||
|
dy1 = bx - ax;
|
||||||
|
dx1 = -(by - ay);
|
||||||
|
|
||||||
|
x2 = (cx + bx) / 2;
|
||||||
|
y2 = (cy + by) / 2;
|
||||||
|
dy2 = cx - bx;
|
||||||
|
dx2 = -(cy - by);
|
||||||
|
|
||||||
|
ox = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2) / (dx1 * dy2 - dy1 * dx2);
|
||||||
|
oy = (ox - x1) * dy1 / dx1 + y11;
|
||||||
|
|
||||||
|
dx = ox - ax;
|
||||||
|
dy = oy - ay;
|
||||||
|
radius = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
return new double[]{ox, oy, radius};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D getPointFromSL(int saturation, int lightness, double radius) {
|
||||||
|
double dy = map(saturation, 0, 255, -radius, radius);
|
||||||
|
double angle = 0.;
|
||||||
|
double angle1 = angle + 2 * Math.PI / 3.;
|
||||||
|
double angle2 = angle1 + 2 * Math.PI / 3.;
|
||||||
|
double x1 = radius * Math.sin(angle1);
|
||||||
|
double y1 = radius * Math.cos(angle1);
|
||||||
|
double x2 = radius * Math.sin(angle2);
|
||||||
|
double y2 = radius * Math.cos(angle2);
|
||||||
|
double dx = 0;
|
||||||
|
double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(dx, dy), new Point2D(x2, y2));
|
||||||
|
double xArc = circle[0];
|
||||||
|
double yArc = circle[1];
|
||||||
|
double arcR = circle[2];
|
||||||
|
double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);
|
||||||
|
double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);
|
||||||
|
double ArcoFinal = map(lightness, 0, 255, Arco2, Arco1);
|
||||||
|
double finalX = xArc + arcR * Math.sin(ArcoFinal);
|
||||||
|
double finalY = yArc + arcR * Math.cos(ArcoFinal);
|
||||||
|
if (dy < y1) {
|
||||||
|
ArcoFinal = map(lightness, 0, 255, Arco1, Arco2 + 2 * Math.PI);
|
||||||
|
finalX = -xArc - arcR * Math.sin(ArcoFinal);
|
||||||
|
finalY = yArc + arcR * Math.cos(ArcoFinal);
|
||||||
|
}
|
||||||
|
return new Point2D(finalX, finalY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D rotate(Point2D a, Point2D center, double angle) {
|
||||||
|
double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math
|
||||||
|
.sin(angle);
|
||||||
|
double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math
|
||||||
|
.cos(angle);
|
||||||
|
return new Point2D(resultX, resultY);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
520
HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPicker.java
Normal file
520
HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPicker.java
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.skins;
|
||||||
|
|
||||||
|
import com.jfoenix.effects.JFXDepthManager;
|
||||||
|
import com.jfoenix.transitions.CachedTransition;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.effect.DropShadow;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.*;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static javafx.animation.Interpolator.EASE_BOTH;
|
||||||
|
|
||||||
|
/// @author Shadi Shaheen
|
||||||
|
final class JFXCustomColorPicker extends Pane {
|
||||||
|
|
||||||
|
ObjectProperty<RecentColorPath> selectedPath = new SimpleObjectProperty<>();
|
||||||
|
private MoveTo startPoint;
|
||||||
|
private CubicCurveTo curve0To;
|
||||||
|
private CubicCurveTo outerCircleCurveTo;
|
||||||
|
private CubicCurveTo curve1To;
|
||||||
|
private CubicCurveTo innerCircleCurveTo;
|
||||||
|
private final ArrayList<CubicCurve> curves = new ArrayList<>();
|
||||||
|
|
||||||
|
private final double distance = 200;
|
||||||
|
private final double centerX = distance;
|
||||||
|
private final double centerY = distance;
|
||||||
|
private final double radius = 110;
|
||||||
|
|
||||||
|
private static final int shapesNumber = 13;
|
||||||
|
private final ArrayList<RecentColorPath> shapes = new ArrayList<>();
|
||||||
|
private CachedTransition showAnimation;
|
||||||
|
private final JFXColorPickerUI hslColorPicker;
|
||||||
|
|
||||||
|
public JFXCustomColorPicker() {
|
||||||
|
this.setPickOnBounds(false);
|
||||||
|
this.setMinSize(distance * 2, distance * 2);
|
||||||
|
|
||||||
|
final DoubleProperty rotationAngle = new SimpleDoubleProperty(2.1);
|
||||||
|
|
||||||
|
// draw recent colors shape using cubic curves
|
||||||
|
init(rotationAngle, centerX + 53, centerY + 162);
|
||||||
|
|
||||||
|
hslColorPicker = new JFXColorPickerUI((int) distance);
|
||||||
|
hslColorPicker.setLayoutX(centerX - distance / 2);
|
||||||
|
hslColorPicker.setLayoutY(centerY - distance / 2);
|
||||||
|
this.getChildren().add(hslColorPicker);
|
||||||
|
// add recent colors shapes
|
||||||
|
final int shapesStartIndex = this.getChildren().size();
|
||||||
|
final int shapesEndIndex = shapesStartIndex + shapesNumber;
|
||||||
|
for (int i = 0; i < shapesNumber; i++) {
|
||||||
|
final double angle = 2 * i * Math.PI / shapesNumber;
|
||||||
|
final RecentColorPath path = new RecentColorPath(startPoint,
|
||||||
|
curve0To,
|
||||||
|
outerCircleCurveTo,
|
||||||
|
curve1To,
|
||||||
|
innerCircleCurveTo);
|
||||||
|
shapes.add(path);
|
||||||
|
path.setPickOnBounds(false);
|
||||||
|
final Rotate rotate = new Rotate(Math.toDegrees(angle), centerX, centerY);
|
||||||
|
path.getTransforms().add(rotate);
|
||||||
|
this.getChildren().add(shapesStartIndex, path);
|
||||||
|
path.setFill(Color.valueOf(getDefaultColor(i)));
|
||||||
|
path.setFocusTraversable(true);
|
||||||
|
path.addEventHandler(MouseEvent.MOUSE_CLICKED, (event) -> {
|
||||||
|
path.requestFocus();
|
||||||
|
selectedPath.set(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// add selection listeners
|
||||||
|
selectedPath.addListener((o, oldVal, newVal) -> {
|
||||||
|
if (oldVal != null) {
|
||||||
|
hslColorPicker.removeColorSelectionNode(oldVal);
|
||||||
|
oldVal.playTransition(-1);
|
||||||
|
}
|
||||||
|
// re-arrange children
|
||||||
|
while (this.getChildren().indexOf(newVal) != shapesEndIndex - 1) {
|
||||||
|
final Node temp = this.getChildren().get(shapesEndIndex - 1);
|
||||||
|
this.getChildren().remove(shapesEndIndex - 1);
|
||||||
|
this.getChildren().add(shapesStartIndex, temp);
|
||||||
|
}
|
||||||
|
// update path fill according to the color picker
|
||||||
|
newVal.setStroke(Color.rgb(255, 255, 255, 0.87));
|
||||||
|
newVal.playTransition(1);
|
||||||
|
hslColorPicker.moveToColor((Color) newVal.getFill());
|
||||||
|
hslColorPicker.addColorSelectionNode(newVal);
|
||||||
|
});
|
||||||
|
// init selection
|
||||||
|
selectedPath.set((RecentColorPath) this.getChildren().get(shapesStartIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getShapesNumber() {
|
||||||
|
return shapesNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelectedIndex() {
|
||||||
|
if (selectedPath.get() != null) {
|
||||||
|
return shapes.indexOf(selectedPath.get());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(final Color color) {
|
||||||
|
shapes.get(getSelectedIndex()).setFill(color);
|
||||||
|
hslColorPicker.moveToColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor(final int index) {
|
||||||
|
if (index >= 0 && index < shapes.size()) {
|
||||||
|
return (Color) shapes.get(index).getFill();
|
||||||
|
} else {
|
||||||
|
return Color.WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void preAnimate() {
|
||||||
|
final CubicCurve firstCurve = curves.get(0);
|
||||||
|
final double x = firstCurve.getStartX();
|
||||||
|
final double y = firstCurve.getStartY();
|
||||||
|
firstCurve.setStartX(centerX);
|
||||||
|
firstCurve.setStartY(centerY);
|
||||||
|
|
||||||
|
final CubicCurve secondCurve = curves.get(1);
|
||||||
|
final double x1 = secondCurve.getStartX();
|
||||||
|
final double y1 = secondCurve.getStartY();
|
||||||
|
secondCurve.setStartX(centerX);
|
||||||
|
secondCurve.setStartY(centerY);
|
||||||
|
|
||||||
|
final double cx1 = firstCurve.getControlX1();
|
||||||
|
final double cy1 = firstCurve.getControlY1();
|
||||||
|
firstCurve.setControlX1(centerX + radius);
|
||||||
|
firstCurve.setControlY1(centerY + radius / 2);
|
||||||
|
|
||||||
|
final KeyFrame keyFrame = new KeyFrame(Duration.millis(1000),
|
||||||
|
new KeyValue(firstCurve.startXProperty(), x, EASE_BOTH),
|
||||||
|
new KeyValue(firstCurve.startYProperty(), y, EASE_BOTH),
|
||||||
|
new KeyValue(secondCurve.startXProperty(), x1, EASE_BOTH),
|
||||||
|
new KeyValue(secondCurve.startYProperty(), y1, EASE_BOTH),
|
||||||
|
new KeyValue(firstCurve.controlX1Property(), cx1, EASE_BOTH),
|
||||||
|
new KeyValue(firstCurve.controlY1Property(), cy1, EASE_BOTH)
|
||||||
|
);
|
||||||
|
final Timeline timeline = new Timeline(keyFrame);
|
||||||
|
showAnimation = new CachedTransition(this, timeline) {
|
||||||
|
{
|
||||||
|
setCycleDuration(Duration.millis(240));
|
||||||
|
setDelay(Duration.millis(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void animate() {
|
||||||
|
showAnimation.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {
|
||||||
|
|
||||||
|
final Circle innerCircle = new Circle(centerX, centerY, radius, Color.TRANSPARENT);
|
||||||
|
final Circle outerCircle = new Circle(centerX, centerY, radius * 2, Color.web("blue", 0.5));
|
||||||
|
|
||||||
|
// Create a composite shape of 4 cubic curves
|
||||||
|
// create 2 cubic curves of the shape
|
||||||
|
createQuadraticCurve(rotationAngle, initControlX1, initControlY1);
|
||||||
|
|
||||||
|
// inner circle curve
|
||||||
|
final CubicCurve innerCircleCurve = new CubicCurve();
|
||||||
|
innerCircleCurve.startXProperty().bind(curves.get(0).startXProperty());
|
||||||
|
innerCircleCurve.startYProperty().bind(curves.get(0).startYProperty());
|
||||||
|
innerCircleCurve.endXProperty().bind(curves.get(1).startXProperty());
|
||||||
|
innerCircleCurve.endYProperty().bind(curves.get(1).startYProperty());
|
||||||
|
curves.get(0).startXProperty().addListener((o, oldVal, newVal) -> {
|
||||||
|
final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),
|
||||||
|
curves.get(0).getStartY(),
|
||||||
|
innerCircle,
|
||||||
|
shapesNumber,
|
||||||
|
-1);
|
||||||
|
innerCircleCurve.setControlX1(controlPoint.getX());
|
||||||
|
innerCircleCurve.setControlY1(controlPoint.getY());
|
||||||
|
});
|
||||||
|
curves.get(0).startYProperty().addListener((o, oldVal, newVal) -> {
|
||||||
|
final Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),
|
||||||
|
newVal.doubleValue(),
|
||||||
|
innerCircle,
|
||||||
|
shapesNumber,
|
||||||
|
-1);
|
||||||
|
innerCircleCurve.setControlX1(controlPoint.getX());
|
||||||
|
innerCircleCurve.setControlY1(controlPoint.getY());
|
||||||
|
});
|
||||||
|
curves.get(1).startXProperty().addListener((o, oldVal, newVal) -> {
|
||||||
|
final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),
|
||||||
|
curves.get(1).getStartY(),
|
||||||
|
innerCircle,
|
||||||
|
shapesNumber,
|
||||||
|
1);
|
||||||
|
innerCircleCurve.setControlX2(controlPoint.getX());
|
||||||
|
innerCircleCurve.setControlY2(controlPoint.getY());
|
||||||
|
});
|
||||||
|
curves.get(1).startYProperty().addListener((o, oldVal, newVal) -> {
|
||||||
|
final Point2D controlPoint = makeControlPoint(curves.get(1).getStartX(),
|
||||||
|
newVal.doubleValue(),
|
||||||
|
innerCircle,
|
||||||
|
shapesNumber,
|
||||||
|
1);
|
||||||
|
innerCircleCurve.setControlX2(controlPoint.getX());
|
||||||
|
innerCircleCurve.setControlY2(controlPoint.getY());
|
||||||
|
});
|
||||||
|
Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),
|
||||||
|
curves.get(0).getStartY(),
|
||||||
|
innerCircle,
|
||||||
|
shapesNumber,
|
||||||
|
-1);
|
||||||
|
innerCircleCurve.setControlX1(controlPoint.getX());
|
||||||
|
innerCircleCurve.setControlY1(controlPoint.getY());
|
||||||
|
controlPoint = makeControlPoint(curves.get(1).getStartX(),
|
||||||
|
curves.get(1).getStartY(),
|
||||||
|
innerCircle,
|
||||||
|
shapesNumber,
|
||||||
|
1);
|
||||||
|
innerCircleCurve.setControlX2(controlPoint.getX());
|
||||||
|
innerCircleCurve.setControlY2(controlPoint.getY());
|
||||||
|
|
||||||
|
// outer circle curve
|
||||||
|
final CubicCurve outerCircleCurve = new CubicCurve();
|
||||||
|
outerCircleCurve.startXProperty().bind(curves.get(0).endXProperty());
|
||||||
|
outerCircleCurve.startYProperty().bind(curves.get(0).endYProperty());
|
||||||
|
outerCircleCurve.endXProperty().bind(curves.get(1).endXProperty());
|
||||||
|
outerCircleCurve.endYProperty().bind(curves.get(1).endYProperty());
|
||||||
|
controlPoint = makeControlPoint(curves.get(0).getEndX(),
|
||||||
|
curves.get(0).getEndY(),
|
||||||
|
outerCircle,
|
||||||
|
shapesNumber,
|
||||||
|
-1);
|
||||||
|
outerCircleCurve.setControlX1(controlPoint.getX());
|
||||||
|
outerCircleCurve.setControlY1(controlPoint.getY());
|
||||||
|
controlPoint = makeControlPoint(curves.get(1).getEndX(), curves.get(1).getEndY(), outerCircle, shapesNumber, 1);
|
||||||
|
outerCircleCurve.setControlX2(controlPoint.getX());
|
||||||
|
outerCircleCurve.setControlY2(controlPoint.getY());
|
||||||
|
|
||||||
|
startPoint = new MoveTo();
|
||||||
|
startPoint.xProperty().bind(curves.get(0).startXProperty());
|
||||||
|
startPoint.yProperty().bind(curves.get(0).startYProperty());
|
||||||
|
|
||||||
|
curve0To = new CubicCurveTo();
|
||||||
|
curve0To.controlX1Property().bind(curves.get(0).controlX1Property());
|
||||||
|
curve0To.controlY1Property().bind(curves.get(0).controlY1Property());
|
||||||
|
curve0To.controlX2Property().bind(curves.get(0).controlX2Property());
|
||||||
|
curve0To.controlY2Property().bind(curves.get(0).controlY2Property());
|
||||||
|
curve0To.xProperty().bind(curves.get(0).endXProperty());
|
||||||
|
curve0To.yProperty().bind(curves.get(0).endYProperty());
|
||||||
|
|
||||||
|
outerCircleCurveTo = new CubicCurveTo();
|
||||||
|
outerCircleCurveTo.controlX1Property().bind(outerCircleCurve.controlX1Property());
|
||||||
|
outerCircleCurveTo.controlY1Property().bind(outerCircleCurve.controlY1Property());
|
||||||
|
outerCircleCurveTo.controlX2Property().bind(outerCircleCurve.controlX2Property());
|
||||||
|
outerCircleCurveTo.controlY2Property().bind(outerCircleCurve.controlY2Property());
|
||||||
|
outerCircleCurveTo.xProperty().bind(outerCircleCurve.endXProperty());
|
||||||
|
outerCircleCurveTo.yProperty().bind(outerCircleCurve.endYProperty());
|
||||||
|
|
||||||
|
curve1To = new CubicCurveTo();
|
||||||
|
curve1To.controlX1Property().bind(curves.get(1).controlX2Property());
|
||||||
|
curve1To.controlY1Property().bind(curves.get(1).controlY2Property());
|
||||||
|
curve1To.controlX2Property().bind(curves.get(1).controlX1Property());
|
||||||
|
curve1To.controlY2Property().bind(curves.get(1).controlY1Property());
|
||||||
|
curve1To.xProperty().bind(curves.get(1).startXProperty());
|
||||||
|
curve1To.yProperty().bind(curves.get(1).startYProperty());
|
||||||
|
|
||||||
|
innerCircleCurveTo = new CubicCurveTo();
|
||||||
|
innerCircleCurveTo.controlX1Property().bind(innerCircleCurve.controlX2Property());
|
||||||
|
innerCircleCurveTo.controlY1Property().bind(innerCircleCurve.controlY2Property());
|
||||||
|
innerCircleCurveTo.controlX2Property().bind(innerCircleCurve.controlX1Property());
|
||||||
|
innerCircleCurveTo.controlY2Property().bind(innerCircleCurve.controlY1Property());
|
||||||
|
innerCircleCurveTo.xProperty().bind(innerCircleCurve.startXProperty());
|
||||||
|
innerCircleCurveTo.yProperty().bind(innerCircleCurve.startYProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createQuadraticCurve(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
|
||||||
|
double angle = 2 * i * Math.PI / shapesNumber;
|
||||||
|
double xOffset = radius * Math.cos(angle);
|
||||||
|
double yOffset = radius * Math.sin(angle);
|
||||||
|
final double startx = centerX + xOffset;
|
||||||
|
final double starty = centerY + yOffset;
|
||||||
|
|
||||||
|
final double diffStartCenterX = startx - centerX;
|
||||||
|
final double diffStartCenterY = starty - centerY;
|
||||||
|
final double sinRotAngle = Math.sin(rotationAngle.get());
|
||||||
|
final double cosRotAngle = Math.cos(rotationAngle.get());
|
||||||
|
final double startXR = cosRotAngle * diffStartCenterX - sinRotAngle * diffStartCenterY + centerX;
|
||||||
|
final double startYR = sinRotAngle * diffStartCenterX + cosRotAngle * diffStartCenterY + centerY;
|
||||||
|
|
||||||
|
angle = 2 * i * Math.PI / shapesNumber;
|
||||||
|
xOffset = distance * Math.cos(angle);
|
||||||
|
yOffset = distance * Math.sin(angle);
|
||||||
|
|
||||||
|
final double endx = centerX + xOffset;
|
||||||
|
final double endy = centerY + yOffset;
|
||||||
|
|
||||||
|
final CubicCurve curvedLine = new CubicCurve();
|
||||||
|
curvedLine.setStartX(startXR);
|
||||||
|
curvedLine.setStartY(startYR);
|
||||||
|
curvedLine.setControlX1(startXR);
|
||||||
|
curvedLine.setControlY1(startYR);
|
||||||
|
curvedLine.setControlX2(endx);
|
||||||
|
curvedLine.setControlY2(endy);
|
||||||
|
curvedLine.setEndX(endx);
|
||||||
|
curvedLine.setEndY(endy);
|
||||||
|
curvedLine.setStroke(Color.FORESTGREEN);
|
||||||
|
curvedLine.setStrokeWidth(1);
|
||||||
|
curvedLine.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||||
|
curvedLine.setFill(Color.TRANSPARENT);
|
||||||
|
curvedLine.setMouseTransparent(true);
|
||||||
|
rotationAngle.addListener((o, oldVal, newVal) -> {
|
||||||
|
final double newstartXR = ((cosRotAngle * diffStartCenterX) - (sinRotAngle * diffStartCenterY)) + centerX;
|
||||||
|
final double newstartYR = (sinRotAngle * diffStartCenterX) + (cosRotAngle * diffStartCenterY) + centerY;
|
||||||
|
curvedLine.setStartX(newstartXR);
|
||||||
|
curvedLine.setStartY(newstartYR);
|
||||||
|
});
|
||||||
|
|
||||||
|
curves.add(curvedLine);
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
curvedLine.setControlX1(initControlX1);
|
||||||
|
curvedLine.setControlY1(initControlY1);
|
||||||
|
} else {
|
||||||
|
final CubicCurve firstCurve = curves.get(0);
|
||||||
|
final double curveTheta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber;
|
||||||
|
|
||||||
|
curvedLine.controlX1Property().bind(Bindings.createDoubleBinding(() -> {
|
||||||
|
final double a = firstCurve.getControlX1() - centerX;
|
||||||
|
final double b = Math.sin(curveTheta) * (firstCurve.getControlY1() - centerY);
|
||||||
|
return ((Math.cos(curveTheta) * a) - b) + centerX;
|
||||||
|
}, firstCurve.controlX1Property(), firstCurve.controlY1Property()));
|
||||||
|
|
||||||
|
curvedLine.controlY1Property().bind(Bindings.createDoubleBinding(() -> {
|
||||||
|
final double a = Math.sin(curveTheta) * (firstCurve.getControlX1() - centerX);
|
||||||
|
final double b = Math.cos(curveTheta) * (firstCurve.getControlY1() - centerY);
|
||||||
|
return a + b + centerY;
|
||||||
|
}, firstCurve.controlX1Property(), firstCurve.controlY1Property()));
|
||||||
|
|
||||||
|
|
||||||
|
curvedLine.controlX2Property().bind(Bindings.createDoubleBinding(() -> {
|
||||||
|
final double a = firstCurve.getControlX2() - centerX;
|
||||||
|
final double b = firstCurve.getControlY2() - centerY;
|
||||||
|
return ((Math.cos(curveTheta) * a) - (Math.sin(curveTheta) * b)) + centerX;
|
||||||
|
}, firstCurve.controlX2Property(), firstCurve.controlY2Property()));
|
||||||
|
|
||||||
|
curvedLine.controlY2Property().bind(Bindings.createDoubleBinding(() -> {
|
||||||
|
final double a = Math.sin(curveTheta) * (firstCurve.getControlX2() - centerX);
|
||||||
|
final double b = Math.cos(curveTheta) * (firstCurve.getControlY2() - centerY);
|
||||||
|
return a + b + centerY;
|
||||||
|
}, firstCurve.controlX2Property(), firstCurve.controlY2Property()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDefaultColor(final int i) {
|
||||||
|
String color = "#FFFFFF";
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
color = "#8F3F7E";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
color = "#B5305F";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
color = "#CE584A";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
color = "#DB8D5C";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
color = "#DA854E";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
color = "#E9AB44";
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
color = "#FEE435";
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
color = "#99C286";
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
color = "#01A05E";
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
color = "#4A8895";
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
color = "#16669B";
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
color = "#2F65A5";
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
color = "#4E6A9C";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D rotate(final Point2D a, final Point2D center, final double angle) {
|
||||||
|
final double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math
|
||||||
|
.sin(angle);
|
||||||
|
final double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math
|
||||||
|
.cos(angle);
|
||||||
|
return new Point2D(resultX, resultY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D makeControlPoint(final double endX, final double endY, final Circle circle, final int numSegments, int direction) {
|
||||||
|
final double controlPointDistance = (4.0 / 3.0) * Math.tan(Math.PI / (2 * numSegments)) * circle.getRadius();
|
||||||
|
final Point2D center = new Point2D(circle.getCenterX(), circle.getCenterY());
|
||||||
|
final Point2D end = new Point2D(endX, endY);
|
||||||
|
Point2D perp = rotate(center, end, direction * Math.PI / 2.);
|
||||||
|
Point2D diff = perp.subtract(end);
|
||||||
|
diff = diff.normalize();
|
||||||
|
diff = scale(diff, controlPointDistance);
|
||||||
|
return end.add(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D scale(final Point2D a, final double scale) {
|
||||||
|
return new Point2D(a.getX() * scale, a.getY() * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RecentColorPath extends Path {
|
||||||
|
PathClickTransition transition;
|
||||||
|
|
||||||
|
RecentColorPath(final PathElement... elements) {
|
||||||
|
super(elements);
|
||||||
|
this.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||||
|
this.setStrokeWidth(0);
|
||||||
|
this.setStrokeType(StrokeType.CENTERED);
|
||||||
|
this.setCache(true);
|
||||||
|
JFXDepthManager.setDepth(this, 2);
|
||||||
|
this.transition = new PathClickTransition(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void playTransition(final double rate) {
|
||||||
|
transition.setRate(rate);
|
||||||
|
transition.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PathClickTransition extends CachedTransition {
|
||||||
|
PathClickTransition(final Path path) {
|
||||||
|
super(JFXCustomColorPicker.this, new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(2).radiusProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(2).spreadProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(2).offsetXProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(2).offsetYProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(path.strokeWidthProperty(), 0, EASE_BOTH)
|
||||||
|
),
|
||||||
|
new KeyFrame(Duration.millis(1000),
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(5).radiusProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(5).spreadProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(5).offsetXProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),
|
||||||
|
JFXDepthManager.getShadowAt(5).offsetYProperty().get(),
|
||||||
|
EASE_BOTH),
|
||||||
|
new KeyValue(path.strokeWidthProperty(), 2, EASE_BOTH)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// reduce the number to increase the shifting , increase number to reduce shifting
|
||||||
|
setCycleDuration(Duration.millis(120));
|
||||||
|
setDelay(Duration.seconds(0));
|
||||||
|
setAutoReverse(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.skins;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.*;
|
||||||
|
import com.jfoenix.svg.SVGGlyph;
|
||||||
|
import com.jfoenix.transitions.JFXFillTransition;
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.InvalidationListener;
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Rectangle2D;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.stage.*;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shadi Shaheen
|
||||||
|
*/
|
||||||
|
public class JFXCustomColorPickerDialog extends StackPane {
|
||||||
|
|
||||||
|
public static final String rgbFieldStyle = "-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;";
|
||||||
|
private final Stage dialog = new Stage();
|
||||||
|
// used for concurrency control and preventing FX-thread over use
|
||||||
|
private final AtomicInteger concurrencyController = new AtomicInteger(-1);
|
||||||
|
|
||||||
|
private final ObjectProperty<Color> currentColorProperty = new SimpleObjectProperty<>(Color.WHITE);
|
||||||
|
private final ObjectProperty<Color> customColorProperty = new SimpleObjectProperty<>(Color.TRANSPARENT);
|
||||||
|
private Runnable onSave;
|
||||||
|
|
||||||
|
private final Scene customScene;
|
||||||
|
private final JFXCustomColorPicker curvedColorPicker;
|
||||||
|
private ParallelTransition paraTransition;
|
||||||
|
private final JFXDecorator pickerDecorator;
|
||||||
|
private boolean systemChange = false;
|
||||||
|
private boolean userChange = false;
|
||||||
|
private boolean initOnce = true;
|
||||||
|
private final Runnable initRun;
|
||||||
|
|
||||||
|
public JFXCustomColorPickerDialog(Window owner) {
|
||||||
|
getStyleClass().add("custom-color-dialog");
|
||||||
|
if (owner != null) {
|
||||||
|
dialog.initOwner(owner);
|
||||||
|
}
|
||||||
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
dialog.initStyle(StageStyle.TRANSPARENT);
|
||||||
|
dialog.setResizable(false);
|
||||||
|
|
||||||
|
// create JFX Decorator
|
||||||
|
pickerDecorator = new JFXDecorator(dialog, this, false, false, false);
|
||||||
|
pickerDecorator.setOnCloseButtonAction(this::updateColor);
|
||||||
|
pickerDecorator.setPickOnBounds(false);
|
||||||
|
customScene = new Scene(pickerDecorator, Color.TRANSPARENT);
|
||||||
|
if (owner != null) {
|
||||||
|
final Scene ownerScene = owner.getScene();
|
||||||
|
if (ownerScene != null) {
|
||||||
|
if (ownerScene.getUserAgentStylesheet() != null) {
|
||||||
|
customScene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet());
|
||||||
|
}
|
||||||
|
customScene.getStylesheets().addAll(ownerScene.getStylesheets());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curvedColorPicker = new JFXCustomColorPicker();
|
||||||
|
|
||||||
|
StackPane pane = new StackPane(curvedColorPicker);
|
||||||
|
pane.setPadding(new Insets(18));
|
||||||
|
|
||||||
|
VBox container = new VBox();
|
||||||
|
container.getChildren().add(pane);
|
||||||
|
|
||||||
|
JFXTabPane tabs = new JFXTabPane();
|
||||||
|
|
||||||
|
JFXTextField rgbField = new JFXTextField();
|
||||||
|
JFXTextField hsbField = new JFXTextField();
|
||||||
|
JFXTextField hexField = new JFXTextField();
|
||||||
|
|
||||||
|
rgbField.setStyle(rgbFieldStyle);
|
||||||
|
rgbField.setPromptText("RGB Color");
|
||||||
|
rgbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
|
||||||
|
|
||||||
|
hsbField.setStyle(
|
||||||
|
"-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;");
|
||||||
|
hsbField.setPromptText("HSB Color");
|
||||||
|
hsbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
|
||||||
|
|
||||||
|
hexField.setStyle(
|
||||||
|
"-fx-background-color:TRANSPARENT;-fx-font-weight: BOLD;-fx-prompt-text-fill: #808080; -fx-alignment: top-left ; -fx-max-width: 300;");
|
||||||
|
hexField.setPromptText("#HEX Color");
|
||||||
|
hexField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
|
||||||
|
|
||||||
|
StackPane tabContent = new StackPane();
|
||||||
|
tabContent.getChildren().add(rgbField);
|
||||||
|
tabContent.setMinHeight(100);
|
||||||
|
|
||||||
|
Tab rgbTab = new Tab("RGB");
|
||||||
|
rgbTab.setContent(tabContent);
|
||||||
|
Tab hsbTab = new Tab("HSB");
|
||||||
|
hsbTab.setContent(hsbField);
|
||||||
|
Tab hexTab = new Tab("HEX");
|
||||||
|
hexTab.setContent(hexField);
|
||||||
|
|
||||||
|
tabs.getTabs().add(rgbTab);
|
||||||
|
tabs.getTabs().add(hsbTab);
|
||||||
|
tabs.getTabs().add(hexTab);
|
||||||
|
|
||||||
|
curvedColorPicker.selectedPath.addListener((o, oldVal, newVal) -> {
|
||||||
|
if (paraTransition != null) {
|
||||||
|
paraTransition.stop();
|
||||||
|
}
|
||||||
|
Region tabsHeader = (Region) tabs.lookup(".tab-header-background");
|
||||||
|
pane.backgroundProperty().unbind();
|
||||||
|
tabsHeader.backgroundProperty().unbind();
|
||||||
|
JFXFillTransition fillTransition = new JFXFillTransition(Duration.millis(240),
|
||||||
|
pane,
|
||||||
|
(Color) oldVal.getFill(),
|
||||||
|
(Color) newVal.getFill());
|
||||||
|
JFXFillTransition tabsFillTransition = new JFXFillTransition(Duration.millis(240),
|
||||||
|
tabsHeader,
|
||||||
|
(Color) oldVal.getFill(),
|
||||||
|
(Color) newVal.getFill());
|
||||||
|
paraTransition = new ParallelTransition(fillTransition, tabsFillTransition);
|
||||||
|
paraTransition.setOnFinished((finish) -> {
|
||||||
|
tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY));
|
||||||
|
}, newVal.fillProperty()));
|
||||||
|
pane.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY));
|
||||||
|
}, newVal.fillProperty()));
|
||||||
|
});
|
||||||
|
paraTransition.play();
|
||||||
|
});
|
||||||
|
|
||||||
|
initRun = () -> {
|
||||||
|
// change tabs labels font color according to the selected color
|
||||||
|
pane.backgroundProperty().addListener((o, oldVal, newVal) -> {
|
||||||
|
if (concurrencyController.getAndSet(1) == -1) {
|
||||||
|
Color fontColor = ((Color) newVal.getFills().get(0).getFill()).grayscale()
|
||||||
|
.getRed() > 0.5 ? Color.valueOf(
|
||||||
|
"rgba(40, 40, 40, 0.87)") : Color.valueOf("rgba(255, 255, 255, 0.87)");
|
||||||
|
for (Node tabNode : tabs.lookupAll(".tab")) {
|
||||||
|
for (Node node : tabNode.lookupAll(".tab-label")) {
|
||||||
|
((Label) node).setTextFill(fontColor);
|
||||||
|
}
|
||||||
|
for (Node node : tabNode.lookupAll(".jfx-rippler")) {
|
||||||
|
((JFXRippler) node).setRipplerFill(fontColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
((Pane) tabs.lookup(".tab-selected-line")).setBackground(new Background(new BackgroundFill(fontColor, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
|
pickerDecorator.lookupAll(".jfx-decorator-button").forEach(button -> {
|
||||||
|
((JFXButton) button).setRipplerFill(fontColor);
|
||||||
|
((SVGGlyph) ((JFXButton) button).getGraphic()).setFill(fontColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
Color newColor = (Color) newVal.getFills().get(0).getFill();
|
||||||
|
String hex = String.format("#%02X%02X%02X",
|
||||||
|
(int) (newColor.getRed() * 255),
|
||||||
|
(int) (newColor.getGreen() * 255),
|
||||||
|
(int) (newColor.getBlue() * 255));
|
||||||
|
String rgb = String.format("rgba(%d, %d, %d, 1)",
|
||||||
|
(int) (newColor.getRed() * 255),
|
||||||
|
(int) (newColor.getGreen() * 255),
|
||||||
|
(int) (newColor.getBlue() * 255));
|
||||||
|
String hsb = String.format("hsl(%d, %d%%, %d%%)",
|
||||||
|
(int) (newColor.getHue()),
|
||||||
|
(int) (newColor.getSaturation() * 100),
|
||||||
|
(int) (newColor.getBrightness() * 100));
|
||||||
|
|
||||||
|
if (!userChange) {
|
||||||
|
systemChange = true;
|
||||||
|
rgbField.setText(rgb);
|
||||||
|
hsbField.setText(hsb);
|
||||||
|
hexField.setText(hex);
|
||||||
|
systemChange = false;
|
||||||
|
}
|
||||||
|
concurrencyController.getAndSet(-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// initial selected colors
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
pane.setBackground(new Background(new BackgroundFill(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()),
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
Insets.EMPTY)));
|
||||||
|
((Region) tabs.lookup(".tab-header-background")).setBackground(new Background(new BackgroundFill(
|
||||||
|
curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()),
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
Insets.EMPTY)));
|
||||||
|
Region tabsHeader = (Region) tabs.lookup(".tab-header-background");
|
||||||
|
pane.backgroundProperty().unbind();
|
||||||
|
tabsHeader.backgroundProperty().unbind();
|
||||||
|
tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(),
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
Insets.EMPTY));
|
||||||
|
}, curvedColorPicker.selectedPath.get().fillProperty()));
|
||||||
|
pane.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(),
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
Insets.EMPTY));
|
||||||
|
}, curvedColorPicker.selectedPath.get().fillProperty()));
|
||||||
|
|
||||||
|
// bind text field line color
|
||||||
|
rgbField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return pane.getBackground().getFills().get(0).getFill();
|
||||||
|
}, pane.backgroundProperty()));
|
||||||
|
hsbField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return pane.getBackground().getFills().get(0).getFill();
|
||||||
|
}, pane.backgroundProperty()));
|
||||||
|
hexField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return pane.getBackground().getFills().get(0).getFill();
|
||||||
|
}, pane.backgroundProperty()));
|
||||||
|
|
||||||
|
|
||||||
|
((Pane) pickerDecorator.lookup(".jfx-decorator-buttons-container")).backgroundProperty()
|
||||||
|
.bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return new Background(new BackgroundFill(
|
||||||
|
pane.getBackground()
|
||||||
|
.getFills()
|
||||||
|
.get(0)
|
||||||
|
.getFill(),
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
Insets.EMPTY));
|
||||||
|
}, pane.backgroundProperty()));
|
||||||
|
|
||||||
|
((Pane) pickerDecorator.lookup(".jfx-decorator-content-container")).borderProperty()
|
||||||
|
.bind(Bindings.createObjectBinding(() -> {
|
||||||
|
return new Border(new BorderStroke(
|
||||||
|
pane.getBackground()
|
||||||
|
.getFills()
|
||||||
|
.get(0)
|
||||||
|
.getFill(),
|
||||||
|
BorderStrokeStyle.SOLID,
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
new BorderWidths(0,
|
||||||
|
4,
|
||||||
|
4,
|
||||||
|
4)));
|
||||||
|
}, pane.backgroundProperty()));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
container.getChildren().add(tabs);
|
||||||
|
|
||||||
|
this.getChildren().add(container);
|
||||||
|
this.setPadding(new Insets(0));
|
||||||
|
|
||||||
|
dialog.setScene(customScene);
|
||||||
|
final EventHandler<KeyEvent> keyEventListener = key -> {
|
||||||
|
switch (key.getCode()) {
|
||||||
|
case ESCAPE:
|
||||||
|
close();
|
||||||
|
break;
|
||||||
|
case ENTER:
|
||||||
|
updateColor();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dialog.addEventHandler(KeyEvent.ANY, keyEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateColor() {
|
||||||
|
close();
|
||||||
|
this.customColorProperty.set(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()));
|
||||||
|
this.onSave.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateColorFromUserInput(String colorWebString) {
|
||||||
|
if (!systemChange) {
|
||||||
|
userChange = true;
|
||||||
|
try {
|
||||||
|
curvedColorPicker.setColor(Color.valueOf(colorWebString));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
// if color is not valid then do nothing
|
||||||
|
}
|
||||||
|
userChange = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close() {
|
||||||
|
dialog.setScene(null);
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentColor(Color currentColor) {
|
||||||
|
this.currentColorProperty.set(currentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getCurrentColor() {
|
||||||
|
return currentColorProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<Color> customColorProperty() {
|
||||||
|
return customColorProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCustomColor(Color color) {
|
||||||
|
customColorProperty.set(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getCustomColor() {
|
||||||
|
return customColorProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runnable getOnSave() {
|
||||||
|
return onSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnSave(Runnable onSave) {
|
||||||
|
this.onSave = onSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnHidden(EventHandler<WindowEvent> onHidden) {
|
||||||
|
dialog.setOnHidden(onHidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
dialog.setOpacity(0);
|
||||||
|
// pickerDecorator.setOpacity(0);
|
||||||
|
if (dialog.getOwner() != null) {
|
||||||
|
dialog.widthProperty().addListener(positionAdjuster);
|
||||||
|
dialog.heightProperty().addListener(positionAdjuster);
|
||||||
|
positionAdjuster.invalidated(null);
|
||||||
|
}
|
||||||
|
if (dialog.getScene() == null) {
|
||||||
|
dialog.setScene(customScene);
|
||||||
|
}
|
||||||
|
curvedColorPicker.preAnimate();
|
||||||
|
dialog.show();
|
||||||
|
if (initOnce) {
|
||||||
|
initRun.run();
|
||||||
|
initOnce = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(120),
|
||||||
|
new KeyValue(dialog.opacityProperty(),
|
||||||
|
1,
|
||||||
|
Interpolator.EASE_BOTH)));
|
||||||
|
timeline.setOnFinished((finish) -> curvedColorPicker.animate());
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// add option to show color picker using JFX Dialog
|
||||||
|
private InvalidationListener positionAdjuster = new InvalidationListener() {
|
||||||
|
@Override
|
||||||
|
public void invalidated(Observable ignored) {
|
||||||
|
if (Double.isNaN(dialog.getWidth()) || Double.isNaN(dialog.getHeight())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dialog.widthProperty().removeListener(positionAdjuster);
|
||||||
|
dialog.heightProperty().removeListener(positionAdjuster);
|
||||||
|
fixPosition();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void fixPosition() {
|
||||||
|
Window w = dialog.getOwner();
|
||||||
|
Screen s = com.sun.javafx.util.Utils.getScreen(w);
|
||||||
|
Rectangle2D sb = s.getBounds();
|
||||||
|
double xR = w.getX() + w.getWidth();
|
||||||
|
double xL = w.getX() - dialog.getWidth();
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
if (sb.getMaxX() >= xR + dialog.getWidth()) {
|
||||||
|
x = xR;
|
||||||
|
} else if (sb.getMinX() <= xL) {
|
||||||
|
x = xL;
|
||||||
|
} else {
|
||||||
|
x = Math.max(sb.getMinX(), sb.getMaxX() - dialog.getWidth());
|
||||||
|
}
|
||||||
|
y = Math.max(sb.getMinY(), Math.min(sb.getMaxY() - dialog.getHeight(), w.getY()));
|
||||||
|
dialog.setX(x);
|
||||||
|
dialog.setY(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void layoutChildren() {
|
||||||
|
super.layoutChildren();
|
||||||
|
if (dialog.getMinWidth() > 0 && dialog.getMinHeight() > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double minWidth = Math.max(0, computeMinWidth(getHeight()) + (dialog.getWidth() - customScene.getWidth()));
|
||||||
|
double minHeight = Math.max(0, computeMinHeight(getWidth()) + (dialog.getHeight() - customScene.getHeight()));
|
||||||
|
dialog.setMinWidth(minWidth);
|
||||||
|
dialog.setMinHeight(minHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
242
HMCL/src/main/java/com/jfoenix/skins/JFXGenericPickerSkin.java
Normal file
242
HMCL/src/main/java/com/jfoenix/skins/JFXGenericPickerSkin.java
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.skins;
|
||||||
|
|
||||||
|
import com.jfoenix.adapters.ReflectionHelper;
|
||||||
|
import com.jfoenix.controls.behavior.JFXGenericPickerBehavior;
|
||||||
|
import com.sun.javafx.binding.ExpressionHelper;
|
||||||
|
import com.sun.javafx.event.EventHandlerManager;
|
||||||
|
import com.sun.javafx.stage.WindowEventDispatcher;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanPropertyBase;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.event.EventType;
|
||||||
|
import javafx.scene.control.ComboBoxBase;
|
||||||
|
import javafx.scene.control.PopupControl;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.skin.ComboBoxBaseSkin;
|
||||||
|
import javafx.scene.control.skin.ComboBoxPopupControl;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
|
public abstract class JFXGenericPickerSkin<T> extends ComboBoxPopupControl<T> {
|
||||||
|
|
||||||
|
private final EventHandler<MouseEvent> mouseEnteredEventHandler;
|
||||||
|
private final EventHandler<MouseEvent> mousePressedEventHandler;
|
||||||
|
private final EventHandler<MouseEvent> mouseReleasedEventHandler;
|
||||||
|
private final EventHandler<MouseEvent> mouseExitedEventHandler;
|
||||||
|
|
||||||
|
protected JFXGenericPickerBehavior<T> behavior;
|
||||||
|
|
||||||
|
// reference of the arrow button node in getChildren (not the actual field)
|
||||||
|
protected Pane arrowButton;
|
||||||
|
protected PopupControl popup;
|
||||||
|
|
||||||
|
public JFXGenericPickerSkin(ComboBoxBase<T> comboBoxBase) {
|
||||||
|
super(comboBoxBase);
|
||||||
|
behavior = new JFXGenericPickerBehavior<T>(comboBoxBase);
|
||||||
|
|
||||||
|
removeParentFakeFocusListener(comboBoxBase);
|
||||||
|
|
||||||
|
this.mouseEnteredEventHandler = event -> behavior.mouseEntered(event);
|
||||||
|
this.mousePressedEventHandler = event -> {
|
||||||
|
behavior.mousePressed(event);
|
||||||
|
event.consume();
|
||||||
|
};
|
||||||
|
this.mouseReleasedEventHandler = event -> {
|
||||||
|
behavior.mouseReleased(event);
|
||||||
|
event.consume();
|
||||||
|
};
|
||||||
|
this.mouseExitedEventHandler = event -> behavior.mouseExited(event);
|
||||||
|
|
||||||
|
arrowButton = (Pane) getChildren().get(0);
|
||||||
|
|
||||||
|
parentArrowEventHandlerTerminator.accept("mouseEnteredEventHandler", MouseEvent.MOUSE_ENTERED);
|
||||||
|
parentArrowEventHandlerTerminator.accept("mousePressedEventHandler", MouseEvent.MOUSE_PRESSED);
|
||||||
|
parentArrowEventHandlerTerminator.accept("mouseReleasedEventHandler", MouseEvent.MOUSE_RELEASED);
|
||||||
|
parentArrowEventHandlerTerminator.accept("mouseExitedEventHandler", MouseEvent.MOUSE_EXITED);
|
||||||
|
this.unregisterChangeListeners(comboBoxBase.editableProperty());
|
||||||
|
|
||||||
|
updateArrowButtonListeners();
|
||||||
|
registerChangeListener(comboBoxBase.editableProperty(), obs -> {
|
||||||
|
updateArrowButtonListeners();
|
||||||
|
reflectUpdateDisplayArea();
|
||||||
|
});
|
||||||
|
|
||||||
|
removeParentPopupHandlers();
|
||||||
|
|
||||||
|
popup = ReflectionHelper.getFieldContent(ComboBoxPopupControl.class, this, "popup");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
if (this.behavior != null) {
|
||||||
|
this.behavior.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Reflections internal API *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
private final BiConsumer<String, EventType<?>> parentArrowEventHandlerTerminator = (handlerName, eventType) -> {
|
||||||
|
try {
|
||||||
|
EventHandler handler = ReflectionHelper.getFieldContent(ComboBoxBaseSkin.class, this, handlerName);
|
||||||
|
arrowButton.removeEventHandler(eventType, handler);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final VarHandle READ_ONLY_BOOLEAN_PROPERTY_BASE_HELPER =
|
||||||
|
findVarHandle(ReadOnlyBooleanPropertyBase.class, "helper", ExpressionHelper.class);
|
||||||
|
|
||||||
|
/// @author Glavo
|
||||||
|
private static VarHandle findVarHandle(Class<?> targetClass, String fieldName, Class<?> type) {
|
||||||
|
try {
|
||||||
|
return MethodHandles.privateLookupIn(targetClass, MethodHandles.lookup()).findVarHandle(targetClass, fieldName, type);
|
||||||
|
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||||
|
LOG.warning("Failed to get var handle", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeParentFakeFocusListener(ComboBoxBase<T> comboBoxBase) {
|
||||||
|
// handle FakeFocusField cast exception
|
||||||
|
try {
|
||||||
|
final ReadOnlyBooleanProperty focusedProperty = comboBoxBase.focusedProperty();
|
||||||
|
//noinspection unchecked
|
||||||
|
ExpressionHelper<Boolean> value = (ExpressionHelper<Boolean>) READ_ONLY_BOOLEAN_PROPERTY_BASE_HELPER.get(focusedProperty);
|
||||||
|
ChangeListener<? super Boolean>[] changeListeners = ReflectionHelper.getFieldContent(value.getClass(), value, "changeListeners");
|
||||||
|
// remove parent focus listener to prevent editor class cast exception
|
||||||
|
for (int i = changeListeners.length - 1; i > 0; i--) {
|
||||||
|
if (changeListeners[i] != null && changeListeners[i].getClass().getName().contains("ComboBoxPopupControl")) {
|
||||||
|
focusedProperty.removeListener(changeListeners[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeParentPopupHandlers() {
|
||||||
|
try {
|
||||||
|
PopupControl popup = ReflectionHelper.invoke(ComboBoxPopupControl.class, this, "getPopup");
|
||||||
|
popup.setOnAutoHide(event -> behavior.onAutoHide(popup));
|
||||||
|
WindowEventDispatcher dispatcher = ReflectionHelper.invoke(Window.class, popup, "getInternalEventDispatcher");
|
||||||
|
Map compositeEventHandlersMap = ReflectionHelper.getFieldContent(EventHandlerManager.class, dispatcher.getEventHandlerManager(), "eventHandlerMap");
|
||||||
|
compositeEventHandlersMap.remove(MouseEvent.MOUSE_CLICKED);
|
||||||
|
// CompositeEventHandler compositeEventHandler = (CompositeEventHandler) compositeEventHandlersMap.get(MouseEvent.MOUSE_CLICKED);
|
||||||
|
// Object obj = fieldConsumer.apply(()->CompositeEventHandler.class.getDeclaredField("firstRecord"),compositeEventHandler);
|
||||||
|
// EventHandler handler = (EventHandler) fieldConsumer.apply(() -> obj.getClass().getDeclaredField("eventHandler"), obj);
|
||||||
|
// popup.removeEventHandler(MouseEvent.MOUSE_CLICKED, handler);
|
||||||
|
popup.addEventHandler(MouseEvent.MOUSE_CLICKED, click -> behavior.onAutoHide(popup));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateArrowButtonListeners() {
|
||||||
|
if (getSkinnable().isEditable()) {
|
||||||
|
arrowButton.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);
|
||||||
|
arrowButton.addEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);
|
||||||
|
arrowButton.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);
|
||||||
|
arrowButton.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);
|
||||||
|
} else {
|
||||||
|
arrowButton.removeEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);
|
||||||
|
arrowButton.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);
|
||||||
|
arrowButton.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);
|
||||||
|
arrowButton.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Reflections internal API for ComboBoxPopupControl *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
private final HashMap<String, Method> parentCachedMethods = new HashMap<>();
|
||||||
|
|
||||||
|
Function<String, Method> methodSupplier = name -> {
|
||||||
|
if (!parentCachedMethods.containsKey(name)) {
|
||||||
|
try {
|
||||||
|
Method method = ComboBoxPopupControl.class.getDeclaredMethod(name);
|
||||||
|
method.setAccessible(true);
|
||||||
|
parentCachedMethods.put(name, method);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parentCachedMethods.get(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
final Consumer<Method> methodInvoker = method -> {
|
||||||
|
try {
|
||||||
|
method.invoke(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Function<Method, Object> methodReturnInvoker = method -> {
|
||||||
|
try {
|
||||||
|
return method.invoke(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected void reflectUpdateDisplayArea() {
|
||||||
|
methodInvoker.accept(methodSupplier.apply("updateDisplayArea"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void reflectSetTextFromTextFieldIntoComboBoxValue() {
|
||||||
|
methodInvoker.accept(methodSupplier.apply("setTextFromTextFieldIntoComboBoxValue"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TextField reflectGetEditableInputNode() {
|
||||||
|
return (TextField) methodReturnInvoker.apply(methodSupplier.apply("getEditableInputNode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void reflectUpdateDisplayNode() {
|
||||||
|
methodInvoker.accept(methodSupplier.apply("updateDisplayNode"));
|
||||||
|
}
|
||||||
|
}
|
||||||
64
HMCL/src/main/java/com/jfoenix/utils/JFXNodeUtils.java
Normal file
64
HMCL/src/main/java/com/jfoenix/utils/JFXNodeUtils.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.utils;
|
||||||
|
|
||||||
|
import javafx.scene.layout.Background;
|
||||||
|
import javafx.scene.layout.BackgroundFill;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.paint.Paint;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/// @author Shadi Shaheen
|
||||||
|
/// @version 1.0
|
||||||
|
/// @since 2017-02-11
|
||||||
|
public final class JFXNodeUtils {
|
||||||
|
|
||||||
|
public static void updateBackground(Background newBackground, Region nodeToUpdate) {
|
||||||
|
updateBackground(newBackground, nodeToUpdate, Color.BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateBackground(Background newBackground, Region nodeToUpdate, Paint fill) {
|
||||||
|
if (newBackground != null && !newBackground.getFills().isEmpty()) {
|
||||||
|
final BackgroundFill[] fills = new BackgroundFill[newBackground.getFills().size()];
|
||||||
|
for (int i = 0; i < newBackground.getFills().size(); i++) {
|
||||||
|
BackgroundFill bf = newBackground.getFills().get(i);
|
||||||
|
fills[i] = new BackgroundFill(fill, bf.getRadii(), bf.getInsets());
|
||||||
|
}
|
||||||
|
nodeToUpdate.setBackground(new Background(fills));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String colorToHex(Color c) {
|
||||||
|
if (c != null) {
|
||||||
|
return String.format((Locale) null, "#%02X%02X%02X",
|
||||||
|
Math.round(c.getRed() * 255),
|
||||||
|
Math.round(c.getGreen() * 255),
|
||||||
|
Math.round(c.getBlue() * 255));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JFXNodeUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.construct;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXColorPicker;
|
||||||
import com.jfoenix.controls.JFXRadioButton;
|
import com.jfoenix.controls.JFXRadioButton;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
||||||
import com.jfoenix.validation.base.ValidatorBase;
|
import com.jfoenix.validation.base.ValidatorBase;
|
||||||
@@ -301,7 +302,7 @@ public final class MultiFileItem<T> extends VBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final class PaintOption<T> extends Option<T> {
|
public static final class PaintOption<T> extends Option<T> {
|
||||||
private final ColorPicker colorPicker = new ColorPicker();
|
private final ColorPicker colorPicker = new JFXColorPicker();
|
||||||
|
|
||||||
public PaintOption(String title, T data) {
|
public PaintOption(String title, T data) {
|
||||||
super(title, data);
|
super(title, data);
|
||||||
|
|||||||
@@ -17,10 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.main;
|
package org.jackhuang.hmcl.ui.main;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.*;
|
||||||
import com.jfoenix.controls.JFXComboBox;
|
|
||||||
import com.jfoenix.controls.JFXSlider;
|
|
||||||
import com.jfoenix.controls.JFXTextField;
|
|
||||||
import com.jfoenix.effects.JFXDepthManager;
|
import com.jfoenix.effects.JFXDepthManager;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
@@ -93,7 +90,7 @@ public class PersonalizationPage extends StackPane {
|
|||||||
themeColorPickerContainer.setMinHeight(30);
|
themeColorPickerContainer.setMinHeight(30);
|
||||||
themePane.setRight(themeColorPickerContainer);
|
themePane.setRight(themeColorPickerContainer);
|
||||||
|
|
||||||
ColorPicker picker = new ColorPicker(Color.web(Theme.getTheme().getColor()));
|
ColorPicker picker = new JFXColorPicker(Color.web(Theme.getTheme().getColor()));
|
||||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||||
picker.setOnAction(e ->
|
picker.setOnAction(e ->
|
||||||
config().setTheme(Theme.custom(Theme.getColorDisplayName(picker.getValue()))));
|
config().setTheme(Theme.custom(Theme.getColorDisplayName(picker.getValue()))));
|
||||||
|
|||||||
Reference in New Issue
Block a user