diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index acc107f824..aa395cf188 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,11 +14,12 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 8 + # TODO: Java 8 + - name: Set up JDK 11 uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 8 + java-version: 11 java-package: 'jdk+fx' - name: Build with Gradle run: ./gradlew build --no-daemon diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index a53d87750f..f2fb68a8f7 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -37,8 +37,8 @@ version = "$versionRoot.$buildNumber" dependencies { implementation(project(":HMCLCore")) - implementation("libs:JFoenix") implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0") + // implementation("com.jfoenix:jfoenix:9.0.10") } fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes) @@ -82,21 +82,25 @@ fun attachSignature(jar: File) { } } -val java11 = sourceSets.create("java11") { +tasks.checkstyleMain { + exclude("**/com/jfoenix/**") +} + +val java9 = sourceSets.create("java9") { java { - srcDir("src/main/java11") + srcDir("src/main/java9") } } -tasks.getByName(java11.compileJavaTaskName) { +tasks.getByName(java9.compileJavaTaskName) { if (JavaVersion.current() < JavaVersion.VERSION_11) { javaCompiler.set(javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(11)) }) } options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED") - sourceCompatibility = "11" - targetCompatibility = "11" + sourceCompatibility = "9" + targetCompatibility = "9" } tasks.jar { @@ -117,7 +121,7 @@ tasks.getByName("sha minimize { exclude(dependency("com.google.code.gson:.*:.*")) - exclude(dependency("libs:JFoenix:.*")) + // exclude(dependency("com.jfoenix:.*:.*")) } manifest { @@ -139,6 +143,7 @@ tasks.getByName("sha "javafx.base/com.sun.javafx.event", "javafx.base/com.sun.javafx.runtime", "javafx.graphics/javafx.css", + "javafx.graphics/com.sun.javafx.scene", "javafx.graphics/com.sun.javafx.stage", "javafx.graphics/com.sun.prism", "javafx.controls/com.sun.javafx.scene.control", @@ -172,10 +177,10 @@ fun createExecutable(suffix: String, header: String) { } tasks.processResources { - into("META-INF/versions/11") { - from(sourceSets["java11"].output) + into("META-INF/versions/9") { + from(java9.output) } - dependsOn(tasks["java11Classes"]) + dependsOn(tasks[java9.classesTaskName]) } val makeExecutables = tasks.create("makeExecutables") { diff --git a/HMCL/src/main/java/com/jfoenix/adapters/ReflectionHelper.java b/HMCL/src/main/java/com/jfoenix/adapters/ReflectionHelper.java new file mode 100644 index 0000000000..1844d49735 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/ReflectionHelper.java @@ -0,0 +1,35 @@ +/* + * 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.adapters; + +import java.lang.reflect.Field; + +public final class ReflectionHelper { + + public static Field getDeclaredField(Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + throw new InternalError(e); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/VirtualFlowAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/VirtualFlowAdapter.java new file mode 100644 index 0000000000..cff4d67d9a --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/VirtualFlowAdapter.java @@ -0,0 +1,50 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters; + +import javafx.scene.Node; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.skin.VirtualFlow; + +import java.util.Objects; + +public final class VirtualFlowAdapter> { + @SuppressWarnings("unchecked") + public static > VirtualFlowAdapter wrap(Node node) { + Objects.requireNonNull(node); + return new VirtualFlowAdapter<>((VirtualFlow) node); + } + + private final VirtualFlow flow; + + VirtualFlowAdapter(VirtualFlow flow) { + this.flow = flow; + } + + public Node getFlow() { + return flow; + } + + public int getCellCount() { + return flow.getCellCount(); + } + + public T getCell(int index) { + return flow.getCell(index); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/package-info.java b/HMCL/src/main/java/com/jfoenix/adapters/package-info.java new file mode 100644 index 0000000000..3f6366df70 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/package-info.java @@ -0,0 +1,2 @@ +// TODO: Multi-Release +package com.jfoenix.adapters; \ No newline at end of file diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/ButtonSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/ButtonSkinAdapter.java new file mode 100644 index 0000000000..4539fada92 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/ButtonSkinAdapter.java @@ -0,0 +1,27 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.scene.control.Button; +import javafx.scene.control.skin.ButtonSkin; + +public abstract class ButtonSkinAdapter extends ButtonSkin { + public ButtonSkinAdapter(Button control) { + super(control); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/CheckBoxSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/CheckBoxSkinAdapter.java new file mode 100644 index 0000000000..b483759f6e --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/CheckBoxSkinAdapter.java @@ -0,0 +1,34 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.beans.value.ObservableValue; +import javafx.scene.control.CheckBox; +import javafx.scene.control.skin.CheckBoxSkin; + +public abstract class CheckBoxSkinAdapter extends CheckBoxSkin { + public CheckBoxSkinAdapter(CheckBox control) { + super(control); + } + + protected final void __registerChangeListener(ObservableValue property, String key) { + this.registerChangeListener(property, ignored -> __handleControlPropertyChanged(key)); + } + + protected abstract void __handleControlPropertyChanged(String key); +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/ComboBoxListViewSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/ComboBoxListViewSkinAdapter.java new file mode 100644 index 0000000000..77ac42cc50 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/ComboBoxListViewSkinAdapter.java @@ -0,0 +1,43 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.beans.value.ObservableValue; +import javafx.scene.control.ComboBox; +import javafx.scene.control.skin.ComboBoxListViewSkin; + +public abstract class ComboBoxListViewSkinAdapter extends ComboBoxListViewSkin { + + public ComboBoxListViewSkinAdapter(ComboBox control) { + super(control); + } + + protected final void __registerChangeListener(ObservableValue property, String key) { + this.registerChangeListener(property, ignored -> __handleControlPropertyChanged(key)); + } + + protected abstract void __handleControlPropertyChanged(String key); + + protected final double __snapPositionX(double value) { + return super.snapPositionX(value); + } + + protected final double __snapPositionY(double value) { + return super.snapPositionY(value); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/ListViewSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/ListViewSkinAdapter.java new file mode 100644 index 0000000000..7ef147c10f --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/ListViewSkinAdapter.java @@ -0,0 +1,43 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import com.jfoenix.adapters.VirtualFlowAdapter; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.skin.ListViewSkin; +import javafx.scene.control.skin.VirtualFlow; + +public class ListViewSkinAdapter extends ListViewSkin { + public ListViewSkinAdapter(ListView control) { + super(control); + } + + @SuppressWarnings("unchecked") + protected final VirtualFlowAdapter> __getFlow() { + VirtualFlow> flow; + try { + // Since JavaFX 10 + flow = getVirtualFlow(); + } catch (NoSuchMethodError e) { + flow = (VirtualFlow>) getChildren().get(0); + } + + return VirtualFlowAdapter.wrap(flow); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/ProgressBarSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/ProgressBarSkinAdapter.java new file mode 100644 index 0000000000..1c3bf33017 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/ProgressBarSkinAdapter.java @@ -0,0 +1,58 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.animation.Animation; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.skin.ProgressBarSkin; +import javafx.scene.control.skin.ProgressIndicatorSkin; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + +public abstract class ProgressBarSkinAdapter extends ProgressBarSkin { + private static final VarHandle indeterminateTransitionFieldHandle; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ProgressIndicatorSkin.class, MethodHandles.lookup()); + indeterminateTransitionFieldHandle = lookup.findVarHandle(ProgressIndicatorSkin.class, "indeterminateTransition", Animation.class); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + } + + public ProgressBarSkinAdapter(ProgressBar control) { + super(control); + } + + protected final void __registerChangeListener(ObservableValue property, String key) { + this.registerChangeListener(property, ignored -> __handleControlPropertyChanged(key)); + } + + protected abstract void __handleControlPropertyChanged(String p); + + protected Animation __getIndeterminateTransition() { + return (Animation) indeterminateTransitionFieldHandle.get(this); + } + + protected void __setIndeterminateTransition(Animation animation) { + indeterminateTransitionFieldHandle.set(this, animation); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/ProgressIndicatorSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/ProgressIndicatorSkinAdapter.java new file mode 100644 index 0000000000..a59d4a74c0 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/ProgressIndicatorSkinAdapter.java @@ -0,0 +1,34 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.beans.value.ObservableValue; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.skin.ProgressIndicatorSkin; + +public abstract class ProgressIndicatorSkinAdapter extends ProgressIndicatorSkin { + public ProgressIndicatorSkinAdapter(ProgressIndicator control) { + super(control); + } + + protected final void __registerChangeListener(ObservableValue property, String key) { + this.registerChangeListener(property, ignored -> __handleControlPropertyChanged(key)); + } + + protected abstract void __handleControlPropertyChanged(String key); +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/RadioButtonSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/RadioButtonSkinAdapter.java new file mode 100644 index 0000000000..a5503f7953 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/RadioButtonSkinAdapter.java @@ -0,0 +1,42 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.beans.value.ObservableValue; +import javafx.scene.control.RadioButton; +import javafx.scene.control.skin.RadioButtonSkin; + +public abstract class RadioButtonSkinAdapter extends RadioButtonSkin { + public RadioButtonSkinAdapter(RadioButton control) { + super(control); + } + + protected final void __registerChangeListener(ObservableValue property, String key) { + this.registerChangeListener(property, ignored -> __handleControlPropertyChanged(key)); + } + + protected abstract void __handleControlPropertyChanged(String key); + + protected final double __snapSizeX(double value) { + return super.snapSizeX(value); + } + + protected final double __snapSizeY(double value) { + return super.snapSizeY(value); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/SliderSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/SliderSkinAdapter.java new file mode 100644 index 0000000000..a4473d255b --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/SliderSkinAdapter.java @@ -0,0 +1,27 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.scene.control.Slider; +import javafx.scene.control.skin.SliderSkin; + +public class SliderSkinAdapter extends SliderSkin { + public SliderSkinAdapter(Slider control) { + super(control); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/TextAreaSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/TextAreaSkinAdapter.java new file mode 100644 index 0000000000..bb2e9a1919 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/TextAreaSkinAdapter.java @@ -0,0 +1,63 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TextArea; +import javafx.scene.control.skin.TextAreaSkin; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + +public abstract class TextAreaSkinAdapter extends TextAreaSkin { + private static final VarHandle promptNodeHandle; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(TextAreaSkin.class, MethodHandles.lookup()); + promptNodeHandle = lookup.findVarHandle(TextAreaSkin.class, "promptNode", Text.class); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + } + + public TextAreaSkinAdapter(TextArea control) { + super(control); + } + + protected final void __registerChangeListener(ObservableValue property, String key) { + this.registerChangeListener(property, ignored -> __handleControlPropertyChanged(key)); + } + + protected abstract void __handleControlPropertyChanged(String key); + + protected final Text __getPromptNode() { + return (Text) promptNodeHandle.get(this); + } + + protected final void __setPromptNode(Text promptNode) { + promptNodeHandle.set(this, promptNode); + } + + protected final ObjectProperty __promptTextFillProperty() { + return super.promptTextFillProperty(); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/TextFieldSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/TextFieldSkinAdapter.java new file mode 100644 index 0000000000..302734c0b1 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/TextFieldSkinAdapter.java @@ -0,0 +1,95 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.value.ObservableBooleanValue; +import javafx.beans.value.ObservableDoubleValue; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TextField; +import javafx.scene.control.skin.TextFieldSkin; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + +public abstract class TextFieldSkinAdapter extends TextFieldSkin { + + private static final VarHandle textNodeHandle; + private static final VarHandle textTranslateXHandle; + private static final VarHandle textRightHandle; + private static final VarHandle usePromptTextHandle; + private static final VarHandle promptNodeHandle; + + static { + try { + Class clazz = TextFieldSkin.class; + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); + + textNodeHandle = lookup.findVarHandle(clazz, "textNode", Text.class); + textTranslateXHandle = lookup.findVarHandle(clazz, "textTranslateX", DoubleProperty.class); + textRightHandle = lookup.findVarHandle(clazz, "textRight", ObservableDoubleValue.class); + usePromptTextHandle = lookup.findVarHandle(clazz, "usePromptText", ObservableBooleanValue.class); + promptNodeHandle = lookup.findVarHandle(clazz, "promptNode", Text.class); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + } + + public TextFieldSkinAdapter(TextField textField) { + super(textField); + } + + protected final ObjectProperty __promptTextFillProperty() { + return super.promptTextFillProperty(); + } + + protected final void __registerChangeListener(ObservableValue property, String key) { + this.registerChangeListener(property, ignored -> __handleControlPropertyChanged(key)); + } + + protected abstract void __handleControlPropertyChanged(String key); + + protected final Text __getTextNode() { + return (Text) textNodeHandle.get(this); + } + + protected final DoubleProperty __getTextTranslateX() { + return (DoubleProperty) textTranslateXHandle.get(this); + } + + protected final ObservableDoubleValue __getTextRightHandle() { + return (ObservableDoubleValue) textRightHandle.get(this); + } + + protected final void __setUsePromptText(ObservableBooleanValue value) { + usePromptTextHandle.set(this, value); + } + + protected final Text __getPromptNode() { + return (Text) promptNodeHandle.get(this); + } + + protected final void __setPromptNode(Text promptNode) { + promptNodeHandle.set(this, promptNode); + } + +} + diff --git a/HMCL/src/main/java/com/jfoenix/adapters/skins/ToggleButtonSkinAdapter.java b/HMCL/src/main/java/com/jfoenix/adapters/skins/ToggleButtonSkinAdapter.java new file mode 100644 index 0000000000..63a94e0241 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/adapters/skins/ToggleButtonSkinAdapter.java @@ -0,0 +1,27 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jfoenix.adapters.skins; + +import javafx.scene.control.ToggleButton; +import javafx.scene.control.skin.ToggleButtonSkin; + +public abstract class ToggleButtonSkinAdapter extends ToggleButtonSkin { + public ToggleButtonSkinAdapter(ToggleButton control) { + super(control); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/assets/JFoenixResources.java b/HMCL/src/main/java/com/jfoenix/assets/JFoenixResources.java new file mode 100644 index 0000000000..4c19953cc2 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/assets/JFoenixResources.java @@ -0,0 +1,43 @@ +/* + * 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.assets; + +import java.net.URL; + +/** + * A helper class for external modules to load JFoenix resources. + *

+ * Example: + *

+ * JFoenixResources.load("css/jfoenix-design.css"); // no leading slash
+ * 
+ * + * @author yushijinhun + * @since 2018-06-06 + */ +public final class JFoenixResources { + + public static URL load(String path) { + return JFoenixResources.class.getResource(path); + } + + private JFoenixResources() {} + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXButton.java b/HMCL/src/main/java/com/jfoenix/controls/JFXButton.java new file mode 100644 index 0000000000..f751be17ac --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXButton.java @@ -0,0 +1,227 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.converters.ButtonTypeConverter; +import com.jfoenix.skins.JFXButtonSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Skin; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXButton is the material design implementation of a button. + * it contains ripple effect , the effect color is set according to text fill of the button 1st + * or the text fill of graphic node (if it was set to Label) 2nd. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXButton extends Button { + + public JFXButton() { + initialize(); + } + + public JFXButton(String text) { + super(text); + initialize(); + } + + public JFXButton(String text, Node graphic) { + super(text, graphic); + initialize(); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + @Override + protected Skin createDefaultSkin() { + return new JFXButtonSkin(this); + } + + @Override + public String getUserAgentStylesheet() { + return USER_AGENT_STYLESHEET; + } + + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + /** + * the ripple color property of JFXButton. + */ + private final ObjectProperty ripplerFill = new SimpleObjectProperty<>(null); + + public final ObjectProperty ripplerFillProperty() { + return this.ripplerFill; + } + + /** + * @return the ripple color + */ + public final Paint getRipplerFill() { + return this.ripplerFillProperty().get(); + } + + /** + * set the ripple color + * + * @param ripplerFill the color of the ripple effect + */ + public final void setRipplerFill(final Paint ripplerFill) { + this.ripplerFillProperty().set(ripplerFill); + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-button'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-button"; + private static final String USER_AGENT_STYLESHEET = JFoenixResources.load("css/controls/jfx-button.css").toExternalForm(); + + public enum ButtonType {FLAT, RAISED} + + /** + * according to material design the button has two types: + * - flat : only shows the ripple effect upon clicking the button + * - raised : shows the ripple effect and change in depth upon clicking the button + */ + private final StyleableObjectProperty buttonType = new SimpleStyleableObjectProperty<>( + StyleableProperties.BUTTON_TYPE, + JFXButton.this, + "buttonType", + ButtonType.FLAT); + + public ButtonType getButtonType() { + return buttonType == null ? ButtonType.FLAT : buttonType.get(); + } + + public StyleableObjectProperty buttonTypeProperty() { + return this.buttonType; + } + + public void setButtonType(ButtonType type) { + this.buttonType.set(type); + } + + /** + * Disable the visual indicator for focus + */ + private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS, + JFXButton.this, + "disableVisualFocus", + false); + + /** + * Setting this property disables this {@link JFXButton} from showing keyboard focus. + * @return A property that will disable visual focus if true and enable it if false. + */ + public final StyleableBooleanProperty disableVisualFocusProperty() { + return this.disableVisualFocus; + } + + /** + * Indicates whether or not this {@link JFXButton} will show focus when it receives keyboard focus. + * @return False if this {@link JFXButton} will show visual focus and true if it will not. + */ + public final Boolean isDisableVisualFocus() { + return disableVisualFocus != null && this.disableVisualFocusProperty().get(); + } + + /** + * Setting this to true will disable this {@link JFXButton} from showing focus when it receives keyboard focus. + * @param disabled True to disable visual focus and false to enable it. + */ + public final void setDisableVisualFocus(final Boolean disabled) { + this.disableVisualFocusProperty().set(disabled); + } + + private static class StyleableProperties { + private static final CssMetaData BUTTON_TYPE = + new CssMetaData("-jfx-button-type", ButtonTypeConverter.getInstance(), ButtonType.FLAT) { + @Override + public boolean isSettable(JFXButton control) { + return !control.buttonType.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXButton control) { + return control.buttonTypeProperty(); + } + }; + + private static final CssMetaData DISABLE_VISUAL_FOCUS = + new CssMetaData("-jfx-disable-visual-focus", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXButton control) { + return !control.disableVisualFocus.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXButton control) { + return control.disableVisualFocusProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(Button.getClassCssMetaData()); + Collections.addAll(styleables, BUTTON_TYPE,DISABLE_VISUAL_FOCUS); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXCheckBox.java b/HMCL/src/main/java/com/jfoenix/controls/JFXCheckBox.java new file mode 100644 index 0000000000..71a5f7d630 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXCheckBox.java @@ -0,0 +1,219 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.skins.JFXCheckBoxSkin; +import javafx.css.*; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXCheckBox is the material design implementation of a checkbox. + * it shows ripple effect and a custom selection animation. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXCheckBox extends CheckBox { + + public JFXCheckBox(String text) { + super(text); + initialize(); + } + + public JFXCheckBox() { + initialize(); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + @Override + protected Skin createDefaultSkin() { + return new JFXCheckBoxSkin(this); + } + + @Override + public String getUserAgentStylesheet() { + return USER_AGENT_STYLESHEET; + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-check-box'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-check-box"; + private static final String USER_AGENT_STYLESHEET = JFoenixResources.load("css/controls/jfx-check-box.css").toExternalForm(); + /** + * checkbox color property when selected + */ + private final StyleableObjectProperty checkedColor = new SimpleStyleableObjectProperty<>(StyleableProperties.CHECKED_COLOR, + JFXCheckBox.this, + "checkedColor", + Color.valueOf( + "#0F9D58")); + + public Paint getCheckedColor() { + return checkedColor == null ? Color.valueOf("#0F9D58") : checkedColor.get(); + } + + public StyleableObjectProperty checkedColorProperty() { + return this.checkedColor; + } + + public void setCheckedColor(Paint color) { + this.checkedColor.set(color); + } + + /** + * checkbox color property when not selected + */ + private final StyleableObjectProperty unCheckedColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNCHECKED_COLOR, + JFXCheckBox.this, + "unCheckedColor", + Color.valueOf( + "#5A5A5A")); + + public Paint getUnCheckedColor() { + return unCheckedColor == null ? Color.valueOf("#5A5A5A") : unCheckedColor.get(); + } + + public StyleableObjectProperty unCheckedColorProperty() { + return this.unCheckedColor; + } + + public void setUnCheckedColor(Paint color) { + this.unCheckedColor.set(color); + } + + /** + * Disable the visual indicator for focus. + */ + private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS, + JFXCheckBox.this, + "disableVisualFocus", + false); + + /** + * Setting this property disables this {@link JFXCheckBox} from showing keyboard focus. + * @return A property that will disable visual focus if true and enable it if false. + */ + public final StyleableBooleanProperty disableVisualFocusProperty() { + return this.disableVisualFocus; + } + + /** + * Indicates whether or not this {@link JFXCheckBox} will show focus when it receives keyboard focus. + * @return False if this {@link JFXCheckBox} will show visual focus and true if it will not. + */ + public final Boolean isDisableVisualFocus() { + return disableVisualFocus != null && this.disableVisualFocusProperty().get(); + } + + /** + * Setting this to true will disable this {@link JFXCheckBox} from showing focus when it receives keyboard focus. + * @param disabled True to disable visual focus and false to enable it. + */ + public final void setDisableVisualFocus(final Boolean disabled) { + this.disableVisualFocusProperty().set(disabled); + } + + private static class StyleableProperties { + private static final CssMetaData CHECKED_COLOR = + new CssMetaData("-jfx-checked-color", + StyleConverter.getPaintConverter(), Color.valueOf("#0F9D58")) { + @Override + public boolean isSettable(JFXCheckBox control) { + return !control.checkedColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXCheckBox control) { + return control.checkedColorProperty(); + } + }; + private static final CssMetaData UNCHECKED_COLOR = + new CssMetaData("-jfx-unchecked-color", + StyleConverter.getPaintConverter(), Color.valueOf("#5A5A5A")) { + @Override + public boolean isSettable(JFXCheckBox control) { + return !control.unCheckedColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXCheckBox control) { + return control.unCheckedColorProperty(); + } + }; + private static final CssMetaData DISABLE_VISUAL_FOCUS = + new CssMetaData("-jfx-disable-visual-focus", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXCheckBox control) { + return !control.disableVisualFocus.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXCheckBox control) { + return control.disableVisualFocusProperty(); + } + }; + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(CheckBox.getClassCssMetaData()); + Collections.addAll(styleables, + CHECKED_COLOR, + UNCHECKED_COLOR, + DISABLE_VISUAL_FOCUS + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXClippedPane.java b/HMCL/src/main/java/com/jfoenix/controls/JFXClippedPane.java new file mode 100644 index 0000000000..2cb0e7c98c --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXClippedPane.java @@ -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()); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXComboBox.java b/HMCL/src/main/java/com/jfoenix/controls/JFXComboBox.java new file mode 100644 index 0000000000..8076c039d4 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXComboBox.java @@ -0,0 +1,431 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.controls.base.IFXLabelFloatControl; +import com.jfoenix.converters.base.NodeConverter; +import com.jfoenix.skins.JFXComboBoxListViewSkin; +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.Skin; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.util.StringConverter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXComboBox is the material design implementation of a combobox. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXComboBox extends ComboBox implements IFXLabelFloatControl { + + /** + * {@inheritDoc} + */ + public JFXComboBox() { + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXComboBox(ObservableList items) { + super(items); + initialize(); + } + + private void initialize() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + this.setCellFactory(listView -> new JFXListCell() { + @Override + public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + updateDisplayText(this, item, empty); + } + }); + + // had to refactor the code out of the skin class to allow + // customization of the button cell + this.setButtonCell(new ListCell() { + { + // fixed clearing the combo box value is causing + // java prompt text to be shown because the button cell is not updated + JFXComboBox.this.valueProperty().addListener(observable -> { + if (JFXComboBox.this.getValue() == null) { + updateItem(null, true); + } + }); + } + + @Override + protected void updateItem(T item, boolean empty) { + updateDisplayText(this, item, empty); + this.setVisible(item != null || !empty); + } + + }); + } + + /** + * {@inheritDoc} + */ + @Override + public String getUserAgentStylesheet() { + return JFoenixResources.load("css/controls/jfx-combo-box.css").toExternalForm(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXComboBoxListViewSkin<>(this); + } + + /** + * Initialize the style class to 'jfx-combo-box'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-combo-box"; + + /*************************************************************************** + * * + * Node Converter Property * + * * + **************************************************************************/ + /** + * Converts the user-typed input (when the ComboBox is + * {@link #editableProperty() editable}) to an object of type T, such that + * the input may be retrieved via the {@link #valueProperty() value} property. + */ + public ObjectProperty> nodeConverterProperty() { + return nodeConverter; + } + + private final ObjectProperty> nodeConverter = new SimpleObjectProperty<>(this, "nodeConverter", + JFXComboBox.defaultNodeConverter()); + + public final void setNodeConverter(NodeConverter value) { + nodeConverterProperty().set(value); + } + + public final NodeConverter getNodeConverter() { + return nodeConverterProperty().get(); + } + + private static NodeConverter defaultNodeConverter() { + return new NodeConverter() { + @Override + public Node toNode(T object) { + if (object == null) { + return null; + } + StackPane selectedValueContainer = new StackPane(); + selectedValueContainer.getStyleClass().add("combo-box-selected-value-container"); + selectedValueContainer.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); + Label selectedValueLabel = object instanceof Label ? new Label(((Label) object).getText()) : new Label( + object.toString()); + selectedValueLabel.setTextFill(Color.BLACK); + selectedValueContainer.getChildren().add(selectedValueLabel); + StackPane.setAlignment(selectedValueLabel, Pos.CENTER_LEFT); + StackPane.setMargin(selectedValueLabel, new Insets(0, 0, 0, 5)); + return selectedValueContainer; + } + + @SuppressWarnings("unchecked") + @Override + public T fromNode(Node node) { + return (T) node; + } + + @Override + public String toString(T object) { + if (object == null) { + return null; + } + if (object instanceof Label) { + return ((Label) object).getText(); + } + return object.toString(); + } + }; + } + + private boolean updateDisplayText(ListCell cell, T item, boolean empty) { + if (empty) { + // create empty cell + if (cell == null) { + return true; + } + cell.setGraphic(null); + cell.setText(null); + return true; + } else if (item instanceof Node) { + Node currentNode = cell.getGraphic(); + Node newNode = (Node) item; + // create a node from the selected node of the listview + // using JFXComboBox {@link #nodeConverterProperty() NodeConverter}) + NodeConverter nc = this.getNodeConverter(); + Node node = nc == null ? null : nc.toNode(item); + if (currentNode == null || !currentNode.equals(newNode)) { + cell.setText(null); + cell.setGraphic(node == null ? newNode : node); + } + return node == null; + } else { + // run item through StringConverter if it isn't null + StringConverter c = this.getConverter(); + String s = item == null ? this.getPromptText() : (c == null ? item.toString() : c.toString(item)); + cell.setText(s); + cell.setGraphic(null); + return s == null || s.isEmpty(); + } + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * wrapper for validation properties / methods + */ + private final ValidationControl validationControl = new ValidationControl(this); + + @Override + public ValidatorBase getActiveValidator() { + return validationControl.getActiveValidator(); + } + + @Override + public ReadOnlyObjectProperty activeValidatorProperty() { + return validationControl.activeValidatorProperty(); + } + + @Override + public ObservableList getValidators() { + return validationControl.getValidators(); + } + + @Override + public void setValidators(ValidatorBase... validators) { + validationControl.setValidators(validators); + } + + @Override + public boolean validate() { + return validationControl.validate(); + } + + @Override + public void resetValidation() { + validationControl.resetValidation(); + } + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * set true to show a float the prompt text when focusing the field + */ + private final StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, + JFXComboBox.this, + "lableFloat", + false); + + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the field is unfocused + */ + private final StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR, + JFXComboBox.this, + "unFocusColor", + Color.rgb(77, + 77, + 77)); + + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the field is focused + */ + private final StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR, + JFXComboBox.this, + "focusColor", + Color.valueOf("#4059A9")); + + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + + /** + * disable animation on validation + */ + private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, + JFXComboBox.this, + "disableAnimation", + false); + + @Override + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + + @Override + public final Boolean isDisableAnimation() { + return disableAnimation != null && this.disableAnimationProperty().get(); + } + + @Override + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + + private static class StyleableProperties { + private static final CssMetaData, Paint> UNFOCUS_COLOR = new CssMetaData, Paint>( + "-jfx-unfocus-color", + StyleConverter.getPaintConverter(), + Color.valueOf("#A6A6A6")) { + @Override + public boolean isSettable(JFXComboBox control) { + return !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXComboBox control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData, Paint> FOCUS_COLOR = new CssMetaData, Paint>( + "-jfx-focus-color", + StyleConverter.getPaintConverter(), + Color.valueOf("#3f51b5")) { + @Override + public boolean isSettable(JFXComboBox control) { + return !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXComboBox control) { + return control.focusColorProperty(); + } + }; + private static final CssMetaData, Boolean> LABEL_FLOAT = new CssMetaData, Boolean>( + "-jfx-label-float", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXComboBox control) { + return !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXComboBox control) { + return control.labelFloatProperty(); + } + }; + + private static final CssMetaData, Boolean> DISABLE_ANIMATION = + new CssMetaData, Boolean>("-jfx-disable-animation", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXComboBox control) { + return !control.disableAnimation.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXComboBox control) { + return control.disableAnimationProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = new ArrayList<>(ComboBox.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXDialog.java b/HMCL/src/main/java/com/jfoenix/controls/JFXDialog.java new file mode 100644 index 0000000000..08294ea68a --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXDialog.java @@ -0,0 +1,646 @@ +/* + * 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.controls.events.JFXDialogEvent; +import com.jfoenix.converters.DialogTransitionConverter; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.CachedTransition; +import javafx.animation.*; +import javafx.beans.DefaultProperty; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.css.*; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.scene.CacheHint; +import javafx.scene.Node; +import javafx.scene.SnapshotParameters; +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Note: for JFXDialog to work properly, the root node MUST + * be of type {@link StackPane} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value = "content") +public class JFXDialog extends StackPane { + + // public static enum JFXDialogLayout{PLAIN, HEADING, ACTIONS, BACKDROP}; + public enum DialogTransition { + CENTER, TOP, RIGHT, BOTTOM, LEFT, NONE + } + + private StackPane contentHolder; + + private double offsetX = 0; + private double offsetY = 0; + + private StackPane dialogContainer; + private Region content; + private Transition animation; + + EventHandler closeHandler = e -> close(); + + /** + * creates empty JFXDialog control with CENTER animation type + */ + public JFXDialog() { + this(null, null, DialogTransition.CENTER); + } + + /** + * creates JFXDialog control with a specified animation type, the animation type + * can be one of the following: + *

    + *
  • CENTER
  • + *
  • TOP
  • + *
  • RIGHT
  • + *
  • BOTTOM
  • + *
  • LEFT
  • + *
+ * + * @param dialogContainer is the parent of the dialog, it + * @param content the content of dialog + * @param transitionType the animation type + */ + + public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType) { + initialize(); + setContent(content); + setDialogContainer(dialogContainer); + this.transitionType.set(transitionType); + // init change listeners + initChangeListeners(); + } + + /** + * creates JFXDialog control with a specified animation type that + * is closed when clicking on the overlay, the animation type + * can be one of the following: + *
    + *
  • CENTER
  • + *
  • TOP
  • + *
  • RIGHT
  • + *
  • BOTTOM
  • + *
  • LEFT
  • + *
+ */ + public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType, boolean overlayClose) { + setOverlayClose(overlayClose); + initialize(); + setContent(content); + setDialogContainer(dialogContainer); + this.transitionType.set(transitionType); + // init change listeners + initChangeListeners(); + } + + private void initChangeListeners() { + overlayCloseProperty().addListener((o, oldVal, newVal) -> { + if (newVal) { + this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler); + } else { + this.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler); + } + }); + } + + private void initialize() { + this.setVisible(false); + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + this.transitionType.addListener((o, oldVal, newVal) -> animation = getShowAnimation(transitionType.get())); + + contentHolder = new StackPane(); + contentHolder.setBackground(new Background(new BackgroundFill(Color.WHITE, new CornerRadii(2), null))); + JFXDepthManager.setDepth(contentHolder, 4); + contentHolder.setPickOnBounds(false); + // ensure stackpane is never resized beyond it's preferred size + contentHolder.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + this.getChildren().add(contentHolder); + this.getStyleClass().add("jfx-dialog-overlay-pane"); + StackPane.setAlignment(contentHolder, Pos.CENTER); + this.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null))); + // close the dialog if clicked on the overlay pane + if (overlayClose.get()) { + this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler); + } + // prevent propagating the events to overlay pane + contentHolder.addEventHandler(MouseEvent.ANY, Event::consume); + } + + /*************************************************************************** + * * + * Setters / Getters * + * * + **************************************************************************/ + + /** + * @return the dialog container + */ + public StackPane getDialogContainer() { + return dialogContainer; + } + + /** + * set the dialog container + * Note: the dialog container must be StackPane, its the container for the dialog to be shown in. + */ + public void setDialogContainer(StackPane dialogContainer) { + if (dialogContainer != null) { + this.dialogContainer = dialogContainer; + // FIXME: need to be improved to consider only the parent boundary + offsetX = dialogContainer.getBoundsInLocal().getWidth(); + offsetY = dialogContainer.getBoundsInLocal().getHeight(); + animation = getShowAnimation(transitionType.get()); + } + } + + /** + * @return dialog content node + */ + public Region getContent() { + return content; + } + + /** + * set the content of the dialog + */ + public void setContent(Region content) { + if (content != null) { + this.content = content; + this.content.setPickOnBounds(false); + contentHolder.getChildren().setAll(content); + } + } + + /** + * indicates whether the dialog will close when clicking on the overlay or not + */ + private final BooleanProperty overlayClose = new SimpleBooleanProperty(true); + + public final BooleanProperty overlayCloseProperty() { + return this.overlayClose; + } + + public final boolean isOverlayClose() { + return this.overlayCloseProperty().get(); + } + + public final void setOverlayClose(final boolean overlayClose) { + this.overlayCloseProperty().set(overlayClose); + } + + /** + * if sets to true, the content of dialog container will be cached and replaced with an image + * when displaying the dialog (better performance). + * this is recommended if the content behind the dialog will not change during the showing + * period + */ + private final BooleanProperty cacheContainer = new SimpleBooleanProperty(false); + + public boolean isCacheContainer() { + return cacheContainer.get(); + } + + public BooleanProperty cacheContainerProperty() { + return cacheContainer; + } + + public void setCacheContainer(boolean cacheContainer) { + this.cacheContainer.set(cacheContainer); + } + + /** + * it will show the dialog in the specified container + */ + public void show(StackPane dialogContainer) { + this.setDialogContainer(dialogContainer); + showDialog(); + } + + private ArrayList tempContent; + + /** + * show the dialog inside its parent container + */ + public void show() { + this.setDialogContainer(dialogContainer); + showDialog(); + } + + private void showDialog() { + if (dialogContainer == null) { + throw new RuntimeException("ERROR: JFXDialog container is not set!"); + } + if (isCacheContainer()) { + tempContent = new ArrayList<>(dialogContainer.getChildren()); + + SnapshotParameters snapShotparams = new SnapshotParameters(); + snapShotparams.setFill(Color.TRANSPARENT); + WritableImage temp = dialogContainer.snapshot(snapShotparams, + new WritableImage((int) dialogContainer.getWidth(), + (int) dialogContainer.getHeight())); + ImageView tempImage = new ImageView(temp); + tempImage.setCache(true); + tempImage.setCacheHint(CacheHint.SPEED); + dialogContainer.getChildren().setAll(tempImage, this); + } else { + //prevent error if opening an already opened dialog + dialogContainer.getChildren().remove(this); + tempContent = null; + dialogContainer.getChildren().add(this); + } + + if (animation != null) { + animation.play(); + } else { + setVisible(true); + setOpacity(1); + Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.OPENED)); + } + } + + /** + * close the dialog + */ + public void close() { + if (animation != null) { + animation.setRate(-1); + animation.play(); + animation.setOnFinished(e -> closeDialog()); + } else { + setOpacity(0); + setVisible(false); + closeDialog(); + } + } + + private void closeDialog() { + resetProperties(); + Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.CLOSED)); + if (tempContent == null) { + dialogContainer.getChildren().remove(this); + } else { + dialogContainer.getChildren().setAll(tempContent); + } + } + + /*************************************************************************** + * * + * Transitions * + * * + **************************************************************************/ + + private Transition getShowAnimation(DialogTransition transitionType) { + Transition animation = null; + if (contentHolder != null) { + switch (transitionType) { + case LEFT: + contentHolder.setScaleX(1); + contentHolder.setScaleY(1); + contentHolder.setTranslateX(-offsetX); + animation = new LeftTransition(); + break; + case RIGHT: + contentHolder.setScaleX(1); + contentHolder.setScaleY(1); + contentHolder.setTranslateX(offsetX); + animation = new RightTransition(); + break; + case TOP: + contentHolder.setScaleX(1); + contentHolder.setScaleY(1); + contentHolder.setTranslateY(-offsetY); + animation = new TopTransition(); + break; + case BOTTOM: + contentHolder.setScaleX(1); + contentHolder.setScaleY(1); + contentHolder.setTranslateY(offsetY); + animation = new BottomTransition(); + break; + case CENTER: + contentHolder.setScaleX(0); + contentHolder.setScaleY(0); + animation = new CenterTransition(); + break; + default: + animation = null; + contentHolder.setScaleX(1); + contentHolder.setScaleY(1); + contentHolder.setTranslateX(0); + contentHolder.setTranslateY(0); + break; + } + } + if (animation != null) { + animation.setOnFinished(finish -> + Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.OPENED))); + } + return animation; + } + + private void resetProperties() { + this.setVisible(false); + contentHolder.setTranslateX(0); + contentHolder.setTranslateY(0); + contentHolder.setScaleX(1); + contentHolder.setScaleY(1); + } + + private class LeftTransition extends CachedTransition { + LeftTransition() { + super(contentHolder, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(contentHolder.translateXProperty(), -offsetX, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.visibleProperty(), false, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(10), + new KeyValue(JFXDialog.this.visibleProperty(), true, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(contentHolder.translateXProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH) + )) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.4)); + setDelay(Duration.seconds(0)); + } + } + + private class RightTransition extends CachedTransition { + RightTransition() { + super(contentHolder, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(contentHolder.translateXProperty(), offsetX, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.visibleProperty(), false, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(10), + new KeyValue(JFXDialog.this.visibleProperty(), true, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(contentHolder.translateXProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH))) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.4)); + setDelay(Duration.seconds(0)); + } + } + + private class TopTransition extends CachedTransition { + TopTransition() { + super(contentHolder, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(contentHolder.translateYProperty(), -offsetY, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.visibleProperty(), false, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(10), + new KeyValue(JFXDialog.this.visibleProperty(), true, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(contentHolder.translateYProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH))) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.4)); + setDelay(Duration.seconds(0)); + } + } + + private class BottomTransition extends CachedTransition { + BottomTransition() { + super(contentHolder, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(contentHolder.translateYProperty(), offsetY, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.visibleProperty(), false, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(10), + new KeyValue(JFXDialog.this.visibleProperty(), true, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(contentHolder.translateYProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH))) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.4)); + setDelay(Duration.seconds(0)); + } + } + + private class CenterTransition extends CachedTransition { + CenterTransition() { + super(contentHolder, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(contentHolder.scaleXProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(contentHolder.scaleYProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.visibleProperty(), false, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(10), + new KeyValue(JFXDialog.this.visibleProperty(), true, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(contentHolder.scaleXProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(contentHolder.scaleYProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(JFXDialog.this.opacityProperty(), 1, Interpolator.EASE_BOTH) + )) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.4)); + setDelay(Duration.seconds(0)); + } + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + /** + * Initialize the style class to 'jfx-dialog'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-dialog"; + + /** + * dialog transition type property, it can be one of the following: + *

    + *
  • CENTER
  • + *
  • TOP
  • + *
  • RIGHT
  • + *
  • BOTTOM
  • + *
  • LEFT
  • + *
  • NONE
  • + *
+ */ + private final StyleableObjectProperty transitionType = new SimpleStyleableObjectProperty<>( + StyleableProperties.DIALOG_TRANSITION, + JFXDialog.this, + "dialogTransition", + DialogTransition.CENTER); + + public DialogTransition getTransitionType() { + return transitionType == null ? DialogTransition.CENTER : transitionType.get(); + } + + public StyleableObjectProperty transitionTypeProperty() { + return this.transitionType; + } + + public void setTransitionType(DialogTransition transition) { + this.transitionType.set(transition); + } + + private static class StyleableProperties { + private static final CssMetaData DIALOG_TRANSITION = + new CssMetaData("-jfx-dialog-transition", + DialogTransitionConverter.getInstance(), + DialogTransition.CENTER) { + @Override + public boolean isSettable(JFXDialog control) { + return !control.transitionType.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXDialog control) { + return control.transitionTypeProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(StackPane.getClassCssMetaData()); + Collections.addAll(styleables, + DIALOG_TRANSITION + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + + /*************************************************************************** + * * + * Custom Events * + * * + **************************************************************************/ + + private final ObjectProperty> onDialogClosedProperty = new ObjectPropertyBase>() { + @Override + protected void invalidated() { + setEventHandler(JFXDialogEvent.CLOSED, get()); + } + + @Override + public Object getBean() { + return JFXDialog.this; + } + + @Override + public String getName() { + return "onClosed"; + } + }; + + /** + * Defines a function to be called when the dialog is closed. + * Note: it will be triggered after the close animation is finished. + */ + public ObjectProperty> onDialogClosedProperty() { + return onDialogClosedProperty; + } + + public void setOnDialogClosed(EventHandler handler) { + onDialogClosedProperty().set(handler); + } + + public EventHandler getOnDialogClosed() { + return onDialogClosedProperty().get(); + } + + + private final ObjectProperty> onDialogOpenedProperty = new ObjectPropertyBase>() { + @Override + protected void invalidated() { + setEventHandler(JFXDialogEvent.OPENED, get()); + } + + @Override + public Object getBean() { + return JFXDialog.this; + } + + @Override + public String getName() { + return "onOpened"; + } + }; + + /** + * Defines a function to be called when the dialog is opened. + * Note: it will be triggered after the show animation is finished. + */ + public ObjectProperty> onDialogOpenedProperty() { + return onDialogOpenedProperty; + } + + public void setOnDialogOpened(EventHandler handler) { + onDialogOpenedProperty().set(handler); + } + + public EventHandler getOnDialogOpened() { + return onDialogOpenedProperty().get(); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXDialogLayout.java b/HMCL/src/main/java/com/jfoenix/controls/JFXDialogLayout.java new file mode 100644 index 0000000000..5c561af460 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXDialogLayout.java @@ -0,0 +1,127 @@ +/* + * 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 javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +import java.util.List; + +// Old +public class JFXDialogLayout extends VBox { + private final StackPane heading = new StackPane(); + private final StackPane body = new StackPane(); + private final FlowPane actions = new FlowPane() { + protected double computeMinWidth(double height) { + if (this.getContentBias() == Orientation.HORIZONTAL) { + double maxPref = 0.0; + List children = this.getChildren(); + int i = 0; + + for (int size = children.size(); i < size; ++i) { + Node child = children.get(i); + if (child.isManaged()) { + maxPref = Math.max(maxPref, child.minWidth(-1.0)); + } + } + + Insets insets = this.getInsets(); + return insets.getLeft() + this.snapSize(maxPref) + insets.getRight(); + } else { + return this.computePrefWidth(height); + } + } + + protected double computeMinHeight(double width) { + if (this.getContentBias() == Orientation.VERTICAL) { + double maxPref = 0.0; + List children = this.getChildren(); + int i = 0; + + for (int size = children.size(); i < size; ++i) { + Node child = children.get(i); + if (child.isManaged()) { + maxPref = Math.max(maxPref, child.minHeight(-1.0)); + } + } + + Insets insets = this.getInsets(); + return insets.getTop() + this.snapSize(maxPref) + insets.getBottom(); + } else { + return this.computePrefHeight(width); + } + } + }; + private static final String DEFAULT_STYLE_CLASS = "jfx-dialog-layout"; + + public JFXDialogLayout() { + this.initialize(); + this.heading.getStyleClass().add("jfx-layout-heading"); + this.heading.getStyleClass().add("title"); + this.body.getStyleClass().add("jfx-layout-body"); + this.body.prefHeightProperty().bind(this.prefHeightProperty()); + this.body.prefWidthProperty().bind(this.prefWidthProperty()); + this.actions.getStyleClass().add("jfx-layout-actions"); + this.getChildren().setAll(this.heading, this.body, this.actions); + } + + public ObservableList getHeading() { + return this.heading.getChildren(); + } + + public void setHeading(Node... titleContent) { + this.heading.getChildren().setAll(titleContent); + } + + public ObservableList getBody() { + return this.body.getChildren(); + } + + public void setBody(Node... body) { + this.body.getChildren().setAll(body); + } + + public ObservableList getActions() { + return this.actions.getChildren(); + } + + public void setActions(Node... actions) { + this.actions.getChildren().setAll(actions); + } + + public void setActions(List actions) { + this.actions.getChildren().setAll(actions); + } + + private void initialize() { + this.getStyleClass().add("jfx-dialog-layout"); + this.setPadding(new Insets(24.0, 24.0, 16.0, 24.0)); + this.heading.setStyle("-fx-font-weight: BOLD;-fx-alignment: center-left;"); + this.heading.setPadding(new Insets(5.0, 0.0, 5.0, 0.0)); + this.body.setStyle("-fx-pref-width: 400px;-fx-wrap-text: true;"); + this.actions.setStyle("-fx-alignment: center-right ;"); + this.actions.setPadding(new Insets(10.0, 0.0, 0.0, 0.0)); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXListCell.java b/HMCL/src/main/java/com/jfoenix/controls/JFXListCell.java new file mode 100644 index 0000000000..f222f12652 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXListCell.java @@ -0,0 +1,453 @@ +/* + * 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.svg.SVGGlyph; +import com.jfoenix.utils.JFXNodeUtils; +import javafx.animation.Animation.Status; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.event.Event; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; +import javafx.util.Duration; + +import java.util.Set; + +/** + * material design implementation of ListCell + *

+ * By default JFXListCell will try to create a graphic node for the cell, + * to override it you need to set graphic to null in {@link #updateItem(Object, boolean)} method. + *

+ * NOTE: passive nodes (Labels and Shapes) will be set to mouse transparent in order to + * show the ripple effect upon clicking , to change this behavior you can override the + * method {{@link #makeChildrenTransparent()} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXListCell extends ListCell { + + protected JFXRippler cellRippler = new JFXRippler(this) { + @Override + protected Node getMask() { + Region clip = new Region(); + JFXNodeUtils.updateBackground(JFXListCell.this.getBackground(), clip); + double width = control.getLayoutBounds().getWidth(); + double height = control.getLayoutBounds().getHeight(); + clip.resize(width, height); + return clip; + } + + @Override + protected void positionControl(Node control) { + // do nothing + } + }; + + protected Node cellContent; + private Rectangle clip; + + // private Timeline animateGap; + private Timeline expandAnimation; + private Timeline gapAnimation; + private double animatedHeight = 0; + private boolean playExpandAnimation = false; + private boolean selectionChanged = false; + + /** + * {@inheritDoc} + */ + public JFXListCell() { + initialize(); + initListeners(); + } + + /** + * init listeners to update the vertical gap / selection animation + */ + private void initListeners() { + listViewProperty().addListener((listObj, oldList, newList) -> { + if (newList != null) { + if (getListView() instanceof JFXListView) { + ((JFXListView) newList).currentVerticalGapProperty().addListener((o, oldVal, newVal) -> { + cellRippler.rippler.setClip(null); + if (newVal.doubleValue() != 0) { + playExpandAnimation = true; + getListView().requestLayout(); + } else { + // fake expand state + double gap = clip.getY() * 2; + gapAnimation = new Timeline( + new KeyFrame(Duration.millis(240), + new KeyValue(this.translateYProperty(), + -gap / 2 - (gap * (getIndex())), + Interpolator.EASE_BOTH) + )); + gapAnimation.play(); + gapAnimation.setOnFinished((finish) -> { + requestLayout(); + Platform.runLater(() -> getListView().requestLayout()); + }); + } + }); + + selectedProperty().addListener((o, oldVal, newVal) -> { + if (newVal) { + selectionChanged = true; + } + }); + } + } + }); + } + + @Override + protected void layoutChildren() { + super.layoutChildren(); + cellRippler.resizeRelocate(0, 0, getWidth(), getHeight()); + double gap = getGap(); + + if (clip == null) { + clip = new Rectangle(0, gap / 2, getWidth(), getHeight() - gap); + setClip(clip); + } else { + if (gap != 0) { + if (playExpandAnimation || selectionChanged) { + // fake list collapse state + if (playExpandAnimation) { + this.setTranslateY(-gap / 2 + (-gap * (getIndex()))); + clip.setY(gap / 2); + clip.setHeight(getHeight() - gap); + gapAnimation = new Timeline(new KeyFrame(Duration.millis(240), + new KeyValue(this.translateYProperty(), + 0, + Interpolator.EASE_BOTH))); + playExpandAnimation = false; + } else if (selectionChanged) { + clip.setY(0); + clip.setHeight(getHeight()); + gapAnimation = new Timeline( + new KeyFrame(Duration.millis(240), + new KeyValue(clip.yProperty(), gap / 2, Interpolator.EASE_BOTH), + new KeyValue(clip.heightProperty(), getHeight() - gap, Interpolator.EASE_BOTH) + )); + } + playExpandAnimation = false; + selectionChanged = false; + gapAnimation.play(); + } else { + if (gapAnimation != null) { + gapAnimation.stop(); + } + this.setTranslateY(0); + clip.setY(gap / 2); + clip.setHeight(getHeight() - gap); + } + } else { + this.setTranslateY(0); + clip.setY(0); + clip.setHeight(getHeight()); + } + clip.setX(0); + clip.setWidth(getWidth()); + } + if (!getChildren().contains(cellRippler)) { + makeChildrenTransparent(); + getChildren().add(0, cellRippler); + cellRippler.rippler.clear(); + } + + // refresh sublist style class + if (this.getGraphic() != null && this.getGraphic().getStyleClass().contains("sublist-container")) { + this.getStyleClass().add("sublist-item"); + } else { + this.getStyleClass().remove("sublist-item"); + } + } + + /** + * this method is used to set some nodes in cell content as mouse transparent nodes + * so clicking on them will trigger the ripple effect. + */ + protected void makeChildrenTransparent() { + for (Node child : getChildren()) { + if (child instanceof Label) { + Set texts = child.lookupAll("Text"); + for (Node text : texts) { + text.setMouseTransparent(true); + } + } else if (child instanceof Shape) { + child.setMouseTransparent(true); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setText(null); + setGraphic(null); + // remove empty (Trailing cells) + setMouseTransparent(true); + setStyle("-fx-background-color:TRANSPARENT;"); + } else { + setMouseTransparent(false); + setStyle(null); + if (item instanceof Node) { + setText(null); + Node currentNode = getGraphic(); + Node newNode = (Node) item; + if (currentNode == null || !currentNode.equals(newNode)) { + cellContent = newNode; + cellRippler.rippler.cacheRippleClip(false); + // build the Cell node + // RIPPLER ITEM : in case if the list item has its own rippler bind the list rippler and item rippler properties + if (newNode instanceof JFXRippler) { + // build cell container from exisiting rippler + cellRippler.ripplerFillProperty().bind(((JFXRippler) newNode).ripplerFillProperty()); + cellRippler.maskTypeProperty().bind(((JFXRippler) newNode).maskTypeProperty()); + cellRippler.positionProperty().bind(((JFXRippler) newNode).positionProperty()); + cellContent = ((JFXRippler) newNode).getControl(); + } + + // SUBLIST ITEM : build the Cell node as sublist the sublist + else if (newNode instanceof JFXListView) { + // add the sublist to the parent and style the cell as sublist item + ((JFXListView) getListView()).addSublist((JFXListView) newNode, this.getIndex()); + this.getStyleClass().add("sublist-item"); + + if (this.getPadding() != null) { + this.setPadding(new Insets(this.getPadding().getTop(), + 0, + this.getPadding().getBottom(), + 0)); + } + + // First build the group item used to expand / hide the sublist + StackPane groupNode = new StackPane(); + groupNode.getStyleClass().add("sublist-header"); + SVGGlyph dropIcon = new SVGGlyph(0, + "ANGLE_RIGHT", + "M340 548.571q0 7.429-5.714 13.143l-266.286 266.286q-5.714 5.714-13.143 5.714t-13.143-5.714l-28.571-28.571q-5.714-5.714-5.714-13.143t5.714-13.143l224.571-224.571-224.571-224.571q-5.714-5.714-5.714-13.143t5.714-13.143l28.571-28.571q5.714-5.714 13.143-5.714t13.143 5.714l266.286 266.286q5.714 5.714 5.714 13.143z", + Color.BLACK); + dropIcon.setStyle( + "-fx-min-width:0.4em;-fx-max-width:0.4em;-fx-min-height:0.6em;-fx-max-height:0.6em;"); + dropIcon.getStyleClass().add("drop-icon"); + // alignment of the group node can be changed using the following css selector + // .jfx-list-view .sublist-header{ } + groupNode.getChildren().setAll(((JFXListView) newNode).getGroupnode(), dropIcon); + // the margin is needed when rotating the angle + StackPane.setMargin(dropIcon, new Insets(0, 19, 0, 0)); + StackPane.setAlignment(dropIcon, Pos.CENTER_RIGHT); + + // Second build the sublist container + StackPane sublistContainer = new StackPane(); + sublistContainer.setMinHeight(0); + sublistContainer.setMaxHeight(0); + sublistContainer.getChildren().setAll(newNode); + sublistContainer.setTranslateY(this.snappedBottomInset()); + sublistContainer.setOpacity(0); + StackPane.setMargin(newNode, new Insets(-1, -1, 0, -1)); + + // Third, create container of group title and the sublist + VBox contentHolder = new VBox(); + contentHolder.getChildren().setAll(groupNode, sublistContainer); + contentHolder.getStyleClass().add("sublist-container"); + VBox.setVgrow(groupNode, Priority.ALWAYS); + cellContent = contentHolder; + cellRippler.ripplerPane.addEventHandler(MouseEvent.ANY, Event::consume); + contentHolder.addEventHandler(MouseEvent.ANY, e -> { + if (!e.isConsumed()) { + cellRippler.ripplerPane.fireEvent(e); + e.consume(); + } + }); + cellRippler.ripplerPane.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { + if (!e.isConsumed()) { + e.consume(); + contentHolder.fireEvent(e); + } + }); + // cache rippler clip in subnodes + cellRippler.rippler.cacheRippleClip(true); + + this.setOnMouseClicked(Event::consume); + // Finally, add sublist animation + contentHolder.setOnMouseClicked((click) -> { + click.consume(); + // stop the animation or change the list height + if (expandAnimation != null && expandAnimation.getStatus() == Status.RUNNING) { + expandAnimation.stop(); + } + + // invert the expand property + expandedProperty.set(!expandedProperty.get()); + + double newAnimatedHeight = newNode.prefHeight(-1) * (expandedProperty.get() ? 1 : -1); + double newHeight = expandedProperty.get() ? this.getHeight() + newAnimatedHeight : this.prefHeight( + -1); + // animate showing/hiding the sublist + double contentHeight = expandedProperty.get() ? newAnimatedHeight : 0; + + if (expandedProperty.get()) { + updateClipHeight(newHeight); + getListView().setPrefHeight(getListView().getHeight() + newAnimatedHeight + animatedHeight); + } + // update the animated height + animatedHeight = newAnimatedHeight; + + int opacity = expandedProperty.get() ? 1 : 0; + expandAnimation = new Timeline(new KeyFrame(Duration.millis(320), + new KeyValue(sublistContainer.minHeightProperty(), + contentHeight, + Interpolator.EASE_BOTH), + new KeyValue(sublistContainer.maxHeightProperty(), + contentHeight, + Interpolator.EASE_BOTH), + new KeyValue(sublistContainer.opacityProperty(), + opacity, + Interpolator.EASE_BOTH))); + + if (!expandedProperty.get()) { + expandAnimation.setOnFinished((finish) -> { + updateClipHeight(newHeight); + getListView().setPrefHeight(getListView().getHeight() + newAnimatedHeight); + animatedHeight = 0; + }); + } + expandAnimation.play(); + }); + + // animate arrow + expandedProperty.addListener((o, oldVal, newVal) -> { + if (newVal) { + new Timeline(new KeyFrame(Duration.millis(160), + new KeyValue(dropIcon.rotateProperty(), + 90, + Interpolator.EASE_BOTH))).play(); + } else { + new Timeline(new KeyFrame(Duration.millis(160), + new KeyValue(dropIcon.rotateProperty(), + 0, + Interpolator.EASE_BOTH))).play(); + } + }); + } + ((Region) cellContent).setMaxHeight(cellContent.prefHeight(-1)); + setGraphic(cellContent); + } + } else { + setText(item == null ? "null" : item.toString()); + setGraphic(null); + } + boolean isJFXListView = getListView() instanceof JFXListView; + // show cell tooltip if its toggled in JFXListView + if (isJFXListView && ((JFXListView) getListView()).isShowTooltip()) { + if (item instanceof Label) { + setTooltip(new Tooltip(((Label) item).getText())); + } else if (getText() != null) { + setTooltip(new Tooltip(getText())); + } + } + } + } + + + private void updateClipHeight(double newHeight) { + clip.setHeight(newHeight - getGap()); + } + + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + // indicate whether the sub list is expanded or not + @Deprecated + private final BooleanProperty expandedProperty = new SimpleBooleanProperty(false); + + @Deprecated + public BooleanProperty expandedProperty() { + return expandedProperty; + } + + @Deprecated + public void setExpanded(boolean expand) { + expandedProperty.set(expand); + } + + @Deprecated + public boolean isExpanded() { + return expandedProperty.get(); + } + + // Stylesheet Handling * + + /** + * Initialize the style class to 'jfx-list-cell'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-list-cell"; + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + this.setPadding(new Insets(8, 12, 8, 12)); + } + + @Override + protected double computePrefHeight(double width) { + double gap = getGap(); + return super.computePrefHeight(width) + gap; + } + + private double getGap() { + return (getListView() instanceof JFXListView) ? (((JFXListView) getListView()).isExpanded() ? ((JFXListView) getListView()) + .currentVerticalGapProperty() + .get() : 0) : 0; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXListView.java b/HMCL/src/main/java/com/jfoenix/controls/JFXListView.java new file mode 100644 index 0000000000..c01d01f82f --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXListView.java @@ -0,0 +1,367 @@ +/* + * 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.JFXListViewSkin; +import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.event.Event; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.Skin; +import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.MouseEvent; + +import java.util.*; + +/** + * Material design implementation of List View + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXListView extends ListView { + + /** + * {@inheritDoc} + */ + public JFXListView() { + this.setCellFactory(listView -> new JFXListCell<>()); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXListViewSkin<>(this); + } + + private final ObjectProperty depthProperty = new SimpleObjectProperty<>(0); + + public ObjectProperty depthProperty() { + return depthProperty; + } + + public int getDepth() { + return depthProperty.get(); + } + + public void setDepth(int depth) { + depthProperty.set(depth); + } + + private final ReadOnlyDoubleWrapper currentVerticalGapProperty = new ReadOnlyDoubleWrapper(); + + ReadOnlyDoubleProperty currentVerticalGapProperty() { + return currentVerticalGapProperty.getReadOnlyProperty(); + } + + private void expand() { + currentVerticalGapProperty.set(verticalGap.get()); + } + + private void collapse() { + currentVerticalGapProperty.set(0); + } + + /* + * this only works if the items were labels / strings + */ + private final BooleanProperty showTooltip = new SimpleBooleanProperty(false); + + public final BooleanProperty showTooltipProperty() { + return this.showTooltip; + } + + public final boolean isShowTooltip() { + return this.showTooltipProperty().get(); + } + + public final void setShowTooltip(final boolean showTooltip) { + this.showTooltipProperty().set(showTooltip); + } + + /*************************************************************************** + * * + * SubList Properties * + * * + **************************************************************************/ + + // @Deprecated + private final ObjectProperty groupnode = new SimpleObjectProperty<>(new Label("GROUP")); + + // @Deprecated + public Node getGroupnode() { + return groupnode.get(); + } + + // @Deprecated + public void setGroupnode(Node node) { + this.groupnode.set(node); + } + + /* + * selected index property that includes the sublists + */ + // @Deprecated + private final ReadOnlyObjectWrapper overAllIndexProperty = new ReadOnlyObjectWrapper<>(-1); + + // @Deprecated + public ReadOnlyObjectProperty overAllIndexProperty() { + return overAllIndexProperty.getReadOnlyProperty(); + } + + // private sublists property + // @Deprecated + private final ObjectProperty>> sublistsProperty = new SimpleObjectProperty<>( + FXCollections.observableArrayList()); + // @Deprecated + private final LinkedHashMap> sublistsIndices = new LinkedHashMap<>(); + + // this method shouldn't be called from user + // @Deprecated + void addSublist(JFXListView subList, int index) { + if (!sublistsProperty.get().contains(subList)) { + sublistsProperty.get().add(subList); + sublistsIndices.put(index, subList); + subList.getSelectionModel().selectedIndexProperty().addListener((o, oldVal, newVal) -> { + if (newVal.intValue() != -1) { + updateOverAllSelectedIndex(); + } + }); + } + } + + private void updateOverAllSelectedIndex() { + // if item from the list is selected + if (this.getSelectionModel().getSelectedIndex() != -1) { + int selectedIndex = this.getSelectionModel().getSelectedIndex(); + Iterator>> itr = sublistsIndices.entrySet().iterator(); + int preItemsSize = 0; + while (itr.hasNext()) { + Map.Entry> entry = itr.next(); + if (entry.getKey() < selectedIndex) { + preItemsSize += entry.getValue().getItems().size() - 1; + } + } + overAllIndexProperty.set(selectedIndex + preItemsSize); + } else { + Iterator>> itr = sublistsIndices.entrySet().iterator(); + ArrayList selectedList = new ArrayList<>(); + while (itr.hasNext()) { + Map.Entry> entry = itr.next(); + if (entry.getValue().getSelectionModel().getSelectedIndex() != -1) { + selectedList.add(entry.getKey()); + } + } + if (!selectedList.isEmpty()) { + itr = sublistsIndices.entrySet().iterator(); + int preItemsSize = 0; + while (itr.hasNext()) { + Map.Entry> entry = itr.next(); + if (entry.getKey() < ((Integer) selectedList.get(0))) { + preItemsSize += entry.getValue().getItems().size() - 1; + } + } + overAllIndexProperty.set(preItemsSize + (Integer) selectedList.get(0) + sublistsIndices.get((Integer) selectedList.get(0)) + .getSelectionModel() + .getSelectedIndex()); + } else { + overAllIndexProperty.set(-1); + } + } + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-list-view'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-list-view"; + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + expanded.addListener((o, oldVal, newVal) -> { + if (newVal) { + expand(); + } else { + collapse(); + } + }); + + verticalGap.addListener((o, oldVal, newVal) -> { + if (isExpanded()) { + expand(); + } else { + collapse(); + } + }); + + // handle selection model on the list ( FOR NOW : we only support single selection on the list if it contains sublists) + sublistsProperty.get().addListener((ListChangeListener.Change> c) -> { + while (c.next()) { + if (c.wasAdded() || c.wasUpdated() || c.wasReplaced()) { + if (sublistsProperty.get().size() == 1) { + this.getSelectionModel() + .selectedItemProperty() + .addListener((o, oldVal, newVal) -> clearSelection(this)); + // prevent selecting the sublist item by clicking the right mouse button + this.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, Event::consume); + } + c.getAddedSubList() + .forEach(item -> item.getSelectionModel() + .selectedItemProperty() + .addListener((o, oldVal, newVal) -> clearSelection(item))); + } + } + }); + + // listen to index changes + this.getSelectionModel().selectedIndexProperty().addListener((o, oldVal, newVal) -> { + if (newVal.intValue() != -1) { + updateOverAllSelectedIndex(); + } + }); + } + + + // allow single selection across the list and all sublits + private boolean allowClear = true; + + private void clearSelection(JFXListView selectedList) { + if (allowClear) { + allowClear = false; + if (this != selectedList) { + this.getSelectionModel().clearSelection(); + } + for (int i = 0; i < sublistsProperty.get().size(); i++) { + if (sublistsProperty.get().get(i) != selectedList) { + sublistsProperty.get().get(i).getSelectionModel().clearSelection(); + } + } + allowClear = true; + } + } + + /** + * propagate mouse events to the parent node ( e.g. to allow dragging while clicking on the list) + */ + public void propagateMouseEventsToParent() { + this.addEventHandler(MouseEvent.ANY, e -> { + e.consume(); + this.getParent().fireEvent(e); + }); + } + + private final StyleableDoubleProperty verticalGap = new SimpleStyleableDoubleProperty(StyleableProperties.VERTICAL_GAP, + JFXListView.this, + "verticalGap", + 0.0); + + public Double getVerticalGap() { + return verticalGap == null ? 0 : verticalGap.get(); + } + + public StyleableDoubleProperty verticalGapProperty() { + return this.verticalGap; + } + + public void setVerticalGap(Double gap) { + this.verticalGap.set(gap); + } + + private final StyleableBooleanProperty expanded = new SimpleStyleableBooleanProperty(StyleableProperties.EXPANDED, + JFXListView.this, + "expanded", + false); + + public Boolean isExpanded() { + return expanded != null && expanded.get(); + } + + public StyleableBooleanProperty expandedProperty() { + return this.expanded; + } + + public void setExpanded(Boolean expanded) { + this.expanded.set(expanded); + } + + private static class StyleableProperties { + private static final CssMetaData, Number> VERTICAL_GAP = + new CssMetaData, Number>("-jfx-vertical-gap", StyleConverter.getSizeConverter(), 0) { + @Override + public boolean isSettable(JFXListView control) { + return !control.verticalGap.isBound(); + } + + @Override + public StyleableDoubleProperty getStyleableProperty(JFXListView control) { + return control.verticalGapProperty(); + } + }; + private static final CssMetaData, Boolean> EXPANDED = + new CssMetaData, Boolean>("-jfx-expanded", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXListView control) { + // it's only settable if the List is not shown yet + return control.getHeight() == 0 && !control.expanded.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXListView control) { + return control.expandedProperty(); + } + }; + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(ListView.getClassCssMetaData()); + Collections.addAll(styleables, VERTICAL_GAP, EXPANDED); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXPasswordField.java b/HMCL/src/main/java/com/jfoenix/controls/JFXPasswordField.java new file mode 100644 index 0000000000..4736f3d24c --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXPasswordField.java @@ -0,0 +1,301 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.controls.base.IFXLabelFloatControl; +import com.jfoenix.skins.JFXTextFieldSkin; +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.scene.control.PasswordField; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXPasswordField is the material design implementation of a password Field. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXPasswordField extends PasswordField implements IFXLabelFloatControl { + + /** + * {@inheritDoc} + */ + public JFXPasswordField() { + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXTextFieldSkin<>(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * {@inheritDoc} + */ + @Override + public String getUserAgentStylesheet() { + return USER_AGENT_STYLESHEET; + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * wrapper for validation properties / methods + */ + private final ValidationControl validationControl = new ValidationControl(this); + + @Override + public ValidatorBase getActiveValidator() { + return validationControl.getActiveValidator(); + } + + @Override + public ReadOnlyObjectProperty activeValidatorProperty() { + return validationControl.activeValidatorProperty(); + } + + @Override + public ObservableList getValidators() { + return validationControl.getValidators(); + } + + @Override + public void setValidators(ValidatorBase... validators) { + validationControl.setValidators(validators); + } + + @Override + public boolean validate() { + return validationControl.validate(); + } + + @Override + public void resetValidation() { + validationControl.resetValidation(); + } + + /*************************************************************************** + * * + * Styleable Properties * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-password-field'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-password-field"; + private static final String USER_AGENT_STYLESHEET = JFoenixResources.load("css/controls/jfx-password-field.css").toExternalForm(); + + /** + * set true to show a float the prompt text when focusing the field + */ + private final StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, + JFXPasswordField.this, + "lableFloat", + false); + + @Override + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + @Override + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + @Override + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the field is unfocused + */ + private final StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR, + JFXPasswordField.this, + "unFocusColor", + Color.rgb(77, + 77, + 77)); + + @Override + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + @Override + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + @Override + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the field is focused + */ + private final StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR, + JFXPasswordField.this, + "focusColor", + Color.valueOf("#4059A9")); + + @Override + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + @Override + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + @Override + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + /** + * disable animation on validation + */ + private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, + JFXPasswordField.this, + "disableAnimation", + false); + + @Override + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + + @Override + public final Boolean isDisableAnimation() { + return disableAnimation != null && this.disableAnimationProperty().get(); + } + + @Override + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + + private static class StyleableProperties { + private static final CssMetaData UNFOCUS_COLOR = new CssMetaData( + "-jfx-unfocus-color", + StyleConverter.getPaintConverter(), + Color.valueOf("#A6A6A6")) { + @Override + public boolean isSettable(JFXPasswordField control) { + return control.unFocusColor == null || !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXPasswordField control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData FOCUS_COLOR = new CssMetaData( + "-jfx-focus-color", + StyleConverter.getPaintConverter(), + Color.valueOf("#3f51b5")) { + @Override + public boolean isSettable(JFXPasswordField control) { + return !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXPasswordField control) { + return control.focusColorProperty(); + } + }; + + private static final CssMetaData LABEL_FLOAT = new CssMetaData( + "-jfx-label-float", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXPasswordField control) { + return !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) { + return control.labelFloatProperty(); + } + }; + + private static final CssMetaData DISABLE_ANIMATION = + new CssMetaData("-fx-disable-animation", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXPasswordField control) { + return !control.disableAnimation.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) { + return control.disableAnimationProperty(); + } + }; + + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = new ArrayList<>( + PasswordField.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java b/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java new file mode 100644 index 0000000000..2fa49488bc --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java @@ -0,0 +1,185 @@ +/* + * 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.JFXPopupSkin; +import javafx.application.Platform; +import javafx.beans.DefaultProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.control.PopupControl; +import javafx.scene.control.Skin; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Region; +import javafx.stage.Window; + +/** + * JFXPopup is the material design implementation of a popup. + * + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-03-01 + */ +@DefaultProperty(value = "popupContent") +public class JFXPopup extends PopupControl { + + public enum PopupHPosition { + RIGHT, LEFT + } + + public enum PopupVPosition { + TOP, BOTTOM + } + + /** + * Creates empty popup. + */ + public JFXPopup() { + this(null); + } + + /** + * creates popup with a specified container and content + * + * @param content the node that will be shown in the popup + */ + public JFXPopup(Region content) { + setPopupContent(content); + initialize(); + } + + private void initialize() { + this.setAutoFix(false); + this.setAutoHide(true); + this.setHideOnEscape(true); + this.setConsumeAutoHidingEvents(false); + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + getScene().getRoot().setStyle("-fx-background-color: TRANSPARENT"); + } + + @Override + protected Skin createDefaultSkin() { + return new JFXPopupSkin(this); + } + + /*************************************************************************** + * * + * Setters / Getters * + * * + **************************************************************************/ + + private final ObjectProperty popupContent = new SimpleObjectProperty<>(new Pane()); + + public final ObjectProperty popupContentProperty() { + return this.popupContent; + } + + public final Region getPopupContent() { + return this.popupContentProperty().get(); + } + + public final void setPopupContent(final Region popupContent) { + this.popupContentProperty().set(popupContent); + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** + * show the popup using the default position + */ + public void show(Node node) { + this.show(node, PopupVPosition.TOP, PopupHPosition.LEFT, 0, 0); + } + + /** + * show the popup according to the specified position + * + * @param vAlign can be TOP/BOTTOM + * @param hAlign can be LEFT/RIGHT + */ + public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign) { + this.show(node, vAlign, hAlign, 0, 0); + } + + /** + * show the popup according to the specified position with a certain offset + * + * @param vAlign can be TOP/BOTTOM + * @param hAlign can be LEFT/RIGHT + * @param initOffsetX on the x axis + * @param initOffsetY on the y axis + */ + public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY) { + if (!isShowing()) { + if (node.getScene() == null || node.getScene().getWindow() == null) { + throw new IllegalStateException("Can not show popup. The node must be attached to a scene/window."); + } + Window parent = node.getScene().getWindow(); + final Point2D origin = node.localToScene(0, 0); + final double anchorX = parent.getX() + origin.getX() + + node.getScene().getX() + (hAlign == PopupHPosition.RIGHT ? ((Region) node).getWidth() : 0); + final double anchorY = parent.getY() + origin.getY() + + node.getScene() + .getY() + (vAlign == PopupVPosition.BOTTOM ? ((Region) node).getHeight() : 0); + this.show(parent, anchorX, anchorY); + ((JFXPopupSkin) getSkin()).reset(vAlign, hAlign, initOffsetX, initOffsetY); + Platform.runLater(() -> ((JFXPopupSkin) getSkin()).animate()); + } + } + + public void show(Window window, double x, double y, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY) { + if (!isShowing()) { + if (window == null) { + throw new IllegalStateException("Can not show popup. The node must be attached to a scene/window."); + } + final double anchorX = window.getX() + x + initOffsetX; + final double anchorY = window.getY() + y + initOffsetY; + this.show(window, anchorX, anchorY); + ((JFXPopupSkin) getSkin()).reset(vAlign, hAlign, initOffsetX, initOffsetY); + Platform.runLater(() -> ((JFXPopupSkin) getSkin()).animate()); + } + } + + @Override + public void hide() { + super.hide(); + ((JFXPopupSkin) getSkin()).init(); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-popup'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-popup"; +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java b/HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java new file mode 100644 index 0000000000..f3fd877f80 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java @@ -0,0 +1,61 @@ +/* + * 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.JFXProgressBarSkin; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.Skin; + +// Old +public class JFXProgressBar extends ProgressBar { + private static final String DEFAULT_STYLE_CLASS = "jfx-progress-bar"; + public boolean forbidsRequestingLayout = false; + + public JFXProgressBar() { + this.initialize(); + } + + public JFXProgressBar(double progress) { + super(progress); + this.initialize(); + } + + @Override + protected Skin createDefaultSkin() { + return new JFXProgressBarSkin(this); + } + + private void initialize() { + this.setPrefWidth(200.0); + this.getStyleClass().add("jfx-progress-bar"); + } + + public JFXProgressBar forbidsRequestingLayout() { + this.forbidsRequestingLayout = true; + return this; + } + + @Override + public void requestLayout() { + if (!this.forbidsRequestingLayout) { + super.requestLayout(); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXRadioButton.java b/HMCL/src/main/java/com/jfoenix/controls/JFXRadioButton.java new file mode 100644 index 0000000000..add719935f --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXRadioButton.java @@ -0,0 +1,236 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.skins.JFXRadioButtonSkin; +import javafx.css.*; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXRadioButton is the material design implementation of a radio button. + * + * @author Bashir Elias & Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXRadioButton extends RadioButton { + + public JFXRadioButton(String text) { + super(text); + initialize(); + } + + public JFXRadioButton() { + initialize(); + } + + @Override + protected Skin createDefaultSkin() { + return new JFXRadioButtonSkin(this); + } + + @Override + public String getUserAgentStylesheet() { + return USER_AGENT_STYLESHEET; + } + + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * Initialize the style class to 'jfx-radio-button'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-radio-button"; + private static final String USER_AGENT_STYLESHEET = JFoenixResources.load("css/controls/jfx-radio-button.css").toExternalForm(); + + /** + * default color used when the radio button is selected + */ + private final StyleableObjectProperty selectedColor = new SimpleStyleableObjectProperty<>(StyleableProperties.SELECTED_COLOR, + JFXRadioButton.this, + "selectedColor", + Color.valueOf( + "#0F9D58")); + + public final StyleableObjectProperty selectedColorProperty() { + return this.selectedColor; + } + + public final Color getSelectedColor() { + return selectedColor == null ? Color.rgb(0, 0, 0, 0.2) : this.selectedColorProperty().get(); + } + + public final void setSelectedColor(final Color selectedColor) { + this.selectedColorProperty().set(selectedColor); + } + + /** + * default color used when the radio button is not selected + */ + private final StyleableObjectProperty unSelectedColor = new SimpleStyleableObjectProperty<>( + StyleableProperties.UNSELECTED_COLOR, + JFXRadioButton.this, + "unSelectedColor", + Color.valueOf("#5A5A5A")); + + public final StyleableObjectProperty unSelectedColorProperty() { + return this.unSelectedColor; + } + + public final Color getUnSelectedColor() { + return unSelectedColor == null ? Color.TRANSPARENT : this.unSelectedColorProperty().get(); + } + + public final void setUnSelectedColor(final Color unSelectedColor) { + this.unSelectedColorProperty().set(unSelectedColor); + } + + /** + * Disable the visual indicator for focus + */ + private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS, + JFXRadioButton.this, + "disableVisualFocus", + false); + + public final StyleableBooleanProperty disableVisualFocusProperty() { + return this.disableVisualFocus; + } + + public final Boolean isDisableVisualFocus() { + return disableVisualFocus != null && this.disableVisualFocusProperty().get(); + } + + public final void setDisableVisualFocus(final Boolean disabled) { + this.disableVisualFocusProperty().set(disabled); + } + + /** + * disable animation on button action + */ + private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, + JFXRadioButton.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 class StyleableProperties { + private static final CssMetaData SELECTED_COLOR = + new CssMetaData("-jfx-selected-color", StyleConverter.getColorConverter(), Color.valueOf("#0F9D58")) { + @Override + public boolean isSettable(JFXRadioButton control) { + return !control.selectedColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXRadioButton control) { + return control.selectedColorProperty(); + } + }; + private static final CssMetaData UNSELECTED_COLOR = + new CssMetaData("-jfx-unselected-color", StyleConverter.getColorConverter(), Color.valueOf("#5A5A5A")) { + @Override + public boolean isSettable(JFXRadioButton control) { + return !control.unSelectedColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXRadioButton control) { + return control.unSelectedColorProperty(); + } + }; + private static final CssMetaData DISABLE_VISUAL_FOCUS = + new CssMetaData("-jfx-disable-visual-focus", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXRadioButton control) { + return !control.disableVisualFocus.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXRadioButton control) { + return control.disableVisualFocusProperty(); + } + }; + + private static final CssMetaData DISABLE_ANIMATION = + new CssMetaData("-jfx-disable-animation", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXRadioButton control) { + return !control.disableAnimation.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXRadioButton control) { + return control.disableAnimationProperty(); + } + }; + + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(RadioButton.getClassCssMetaData()); + Collections.addAll(styleables, + SELECTED_COLOR, + UNSELECTED_COLOR, + DISABLE_VISUAL_FOCUS, + DISABLE_ANIMATION + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXRippler.java b/HMCL/src/main/java/com/jfoenix/controls/JFXRippler.java new file mode 100644 index 0000000000..e54b709068 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXRippler.java @@ -0,0 +1,817 @@ +/* + * 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.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. + *

+ * 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 + } + + 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(); + // create fade out transition for the ripple + return this::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)} + */ + // @Deprecated + public void showOverlay() { + if (rippler.overlayRect != null) { + rippler.overlayRect.outAnimation.stop(); + } + rippler.createOverlay(); + rippler.overlayRect.inAnimation.play(); + } + + // @Deprecated + 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 + */ + 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 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; + + 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'. + *

+ * 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 ripplerRecenter = new SimpleStyleableObjectProperty<>( + StyleableProperties.RIPPLER_RECENTER, + JFXRippler.this, + "ripplerRecenter", + false); + + public Boolean isRipplerRecenter() { + return ripplerRecenter != null && ripplerRecenter.get(); + } + + public StyleableObjectProperty 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 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 ripplerRadiusProperty() { + return this.ripplerRadius; + } + + public void setRipplerRadius(Number radius) { + this.ripplerRadius.set(radius); + } + + /** + * the default color of the ripple effect + */ + private final StyleableObjectProperty 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 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 maskType = new SimpleStyleableObjectProperty<>( + StyleableProperties.MASK_TYPE, + JFXRippler.this, + "maskType", + RipplerMask.RECT); + + public RipplerMask getMaskType() { + return maskType == null ? RipplerMask.RECT : maskType.get(); + } + + public StyleableObjectProperty 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 position = new SimpleObjectProperty<>(); + + public void setPosition(RipplerPos pos) { + this.position.set(pos); + } + + public RipplerPos getPosition() { + return position == null ? RipplerPos.FRONT : position.get(); + } + + public ObjectProperty positionProperty() { + return this.position; + } + + + private static final class StyleableProperties { + private static final CssMetaData RIPPLER_RECENTER = + new CssMetaData("-jfx-rippler-recenter", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXRippler control) { + return !control.ripplerRecenter.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.ripplerRecenterProperty(); + } + }; + private static final CssMetaData RIPPLER_DISABLED = + new CssMetaData("-jfx-rippler-disabled", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXRippler control) { + return !control.ripplerDisabled.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.ripplerDisabledProperty(); + } + }; + private static final CssMetaData RIPPLER_FILL = + new CssMetaData("-jfx-rippler-fill", + StyleConverter.getPaintConverter(), Color.rgb(0, 200, 255)) { + @Override + public boolean isSettable(JFXRippler control) { + return !control.ripplerFill.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.ripplerFillProperty(); + } + }; + private static final CssMetaData RIPPLER_RADIUS = + new CssMetaData("-jfx-rippler-radius", StyleConverter.getSizeConverter(), Region.USE_COMPUTED_SIZE) { + @Override + public boolean isSettable(JFXRippler control) { + return !control.ripplerRadius.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.ripplerRadiusProperty(); + } + }; + private static final CssMetaData MASK_TYPE = + new CssMetaData("-jfx-mask-type", + RipplerMaskTypeConverter.getInstance(), RipplerMask.RECT) { + @Override + public boolean isSettable(JFXRippler control) { + return !control.maskType.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXRippler control) { + return control.maskTypeProperty(); + } + }; + + private static final List> STYLEABLES; + + static { + final List> 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> getCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXSlider.java b/HMCL/src/main/java/com/jfoenix/controls/JFXSlider.java new file mode 100644 index 0000000000..251a305641 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXSlider.java @@ -0,0 +1,192 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.converters.IndicatorPositionConverter; +import com.jfoenix.skins.JFXSliderSkin; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.scene.control.Skin; +import javafx.scene.control.Slider; +import javafx.util.Callback; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXSlider is the material design implementation of a slider. + * + * @author Bashir Elias & Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXSlider extends Slider { + + public JFXSlider() { + super(0, 100, 50); + initialize(); + } + + public JFXSlider(double min, double max, double value) { + super(min, max, value); + initialize(); + } + + @Override + protected Skin createDefaultSkin() { + return new JFXSliderSkin(this); + } + + @Override + public String getUserAgentStylesheet() { + return JFoenixResources.load("css/controls/jfx-slider.css").toExternalForm(); + } + + private void initialize() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + public enum IndicatorPosition { + LEFT, RIGHT + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * String binding factory for the slider value. + * Sets a custom string for the value text (by default, it shows the value rounded to the nearest whole number). + *

+ *

For example, to have the value displayed as a percentage (assuming the slider has a range of (0, 100)): + *


+     * JFXSlider mySlider = ...
+     * mySlider.setValueFactory(slider ->
+     * 		Bindings.createStringBinding(
+     * 			() -> ((int) slider.getValue()) + "%",
+     * 			slider.valueProperty()
+     * 		)
+     * );
+     * 
+ *

+ * NOTE: might be replaced later with a call back to create the animated thumb node + */ + private ObjectProperty> valueFactory; + + public final ObjectProperty> valueFactoryProperty() { + if (valueFactory == null) { + valueFactory = new SimpleObjectProperty<>(this, "valueFactory"); + } + return valueFactory; + } + + /** + * @return the current slider value factory + */ + public final Callback getValueFactory() { + return valueFactory == null ? null : valueFactory.get(); + } + + /** + * sets custom string binding for the slider text value + * + * @param valueFactory a callback to create the string value binding + */ + public final void setValueFactory(final Callback valueFactory) { + this.valueFactoryProperty().set(valueFactory); + } + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-slider'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-slider"; + + /** + * indicates the position of the slider indicator, can be + * either LEFT or RIGHT + */ + private final StyleableObjectProperty indicatorPosition = new SimpleStyleableObjectProperty<>( + StyleableProperties.INDICATOR_POSITION, + JFXSlider.this, + "indicatorPosition", + IndicatorPosition.LEFT); + + public IndicatorPosition getIndicatorPosition() { + return indicatorPosition == null ? IndicatorPosition.LEFT : indicatorPosition.get(); + } + + public StyleableObjectProperty indicatorPositionProperty() { + return this.indicatorPosition; + } + + public void setIndicatorPosition(IndicatorPosition pos) { + this.indicatorPosition.set(pos); + } + + private static class StyleableProperties { + private static final CssMetaData INDICATOR_POSITION = new CssMetaData( + "-jfx-indicator-position", + IndicatorPositionConverter.getInstance(), + IndicatorPosition.LEFT) { + @Override + public boolean isSettable(JFXSlider control) { + return !control.indicatorPosition.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXSlider control) { + return control.indicatorPositionProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = new ArrayList<>( + Slider.getClassCssMetaData()); + Collections.addAll(styleables, INDICATOR_POSITION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXSnackbar.java b/HMCL/src/main/java/com/jfoenix/controls/JFXSnackbar.java new file mode 100644 index 0000000000..d6b4021c31 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXSnackbar.java @@ -0,0 +1,445 @@ +/* + * 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 javafx.animation.*; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.WeakChangeListener; +import javafx.css.PseudoClass; +import javafx.event.Event; +import javafx.event.EventType; +import javafx.geometry.Bounds; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * "Snackbars provide brief messages about app processes at the bottom of the screen" + * (Material Design Guidelines). + *

+ * To show a snackbar you need to + *

    + *
  1. Have a {@link Pane} (snackbarContainer) to show the snackbar on top of. Register it in {@link + * #JFXSnackbar(Pane) the JFXSnackbar constructor} or using the {@link #registerSnackbarContainer(Pane)} method.
  2. + *
  3. Have or create a {@link JFXSnackbar}.
    • Having one snackbar where you pass all your {@link + * SnackbarEvent SnackbarEvents} will ensure that the {@link JFXSnackbar#enqueue(SnackbarEvent) enqueue + * method} works as intended.
    + *
  4. + *
  5. Have something to show in the snackbar. A {@link JFXSnackbarLayout} is nice and pretty, + * but any arbitrary {@link Node} will do.
  6. + *
  7. Create a {@link SnackbarEvent SnackbarEvent} specifying the contents and the + * duration.
  8. + *
+ *

+ * Finally, with all those things prepared, show your snackbar using + * {@link JFXSnackbar#enqueue(SnackbarEvent) snackbar.enqueue(snackbarEvent);}. + *

+ * It's most convenient to create functions to do most of this (creating the layout and event) with the default + * settings; that way all you need to do to show a snackbar is specify the message or just the message and the duration. + * + * @see The Material Design Snackbar + */ +public class JFXSnackbar extends Group { + + private static final String DEFAULT_STYLE_CLASS = "jfx-snackbar"; + + private Pane snackbarContainer; + private final ChangeListener sizeListener = (o, oldVal, newVal) -> refreshPopup(); + private final WeakChangeListener weakSizeListener = new WeakChangeListener<>(sizeListener); + + private final AtomicBoolean processingQueue = new AtomicBoolean(false); + private final ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue<>(); + private final ConcurrentHashMap.KeySetView eventsSet = ConcurrentHashMap.newKeySet(); + + private final Interpolator easeInterpolator = Interpolator.SPLINE(0.250, 0.100, 0.250, 1.000); + + private final Pane content; + private PseudoClass activePseudoClass = null; + private PauseTransition pauseTransition; + + /** + * This constructor assumes that you will eventually call the {@link #registerSnackbarContainer(Pane)} method before + * calling the {@link #enqueue(SnackbarEvent)} method. Otherwise, how will the snackbar know where to show itself? + *

+ * "Snackbars provide brief messages about app processes at the bottom of the screen" + * (Material Design Guidelines). + *

+ * To show a snackbar you need to + *

    + *
  1. Have a {@link Pane} (snackbarContainer) to show the snackbar on top of. Register it in {@link + * #JFXSnackbar(Pane) the JFXSnackbar constructor} or using the {@link #registerSnackbarContainer(Pane)} method.
  2. + *
  3. Have or create a {@link JFXSnackbar}.
    • Having one snackbar where you pass all your {@link + * SnackbarEvent SnackbarEvents} will ensure that the {@link JFXSnackbar#enqueue(SnackbarEvent) enqueue + * method} works as intended.
    + *
  4. + *
  5. Have something to show in the snackbar. A {@link JFXSnackbarLayout} is nice and pretty, + * but any arbitrary {@link Node} will do.
  6. + *
  7. Create a {@link SnackbarEvent SnackbarEvent} specifying the contents and the + * duration.
  8. + *
+ *

+ * Finally, with all those things prepared, show your snackbar using + * {@link JFXSnackbar#enqueue(SnackbarEvent) snackbar.enqueue(snackbarEvent);}. + *

+ */ + public JFXSnackbar() { + this(null); + } + + /** + * "Snackbars provide brief messages about app processes at the bottom of the screen" + * (Material Design Guidelines). + *

+ * To show a snackbar you need to + *

    + *
  1. Have a {@link Pane} (snackbarContainer) to show the snackbar on top of. Register it in {@link + * #JFXSnackbar(Pane) the JFXSnackbar constructor} or using the {@link #registerSnackbarContainer(Pane)} method.
  2. + *
  3. Have or create a {@link JFXSnackbar}.
    • Having one snackbar where you pass all your {@link + * SnackbarEvent SnackbarEvents} will ensure that the {@link JFXSnackbar#enqueue(SnackbarEvent) enqueue + * method} works as intended.
    + *
  4. + *
  5. Have something to show in the snackbar. A {@link JFXSnackbarLayout} is nice and pretty, + * but any arbitrary {@link Node} will do.
  6. + *
  7. Create a {@link SnackbarEvent SnackbarEvent} specifying the contents and the + * duration.
  8. + *
+ *

+ * Finally, with all those things prepared, show your snackbar using + * {@link JFXSnackbar#enqueue(SnackbarEvent) snackbar.enqueue(snackbarEvent);}. + *

+ * + * @param snackbarContainer where the snackbar will appear. Using a single snackbar instead of many, will ensure that + * the {@link #enqueue(SnackbarEvent)} method works correctly. + */ + public JFXSnackbar(Pane snackbarContainer) { + initialize(); + content = new StackPane(); + content.getStyleClass().add("jfx-snackbar-content"); + //wrap the content in a group so that the content is managed inside its own container + //but the group is not managed in the snackbarContainer so it does not affect any layout calculations + getChildren().add(content); + setManaged(false); + setVisible(false); + + // register the container before resizing it + registerSnackbarContainer(snackbarContainer); + + // resize the popup if its layout has been changed + layoutBoundsProperty().addListener((o, oldVal, newVal) -> refreshPopup()); + + addEventHandler(SnackbarEvent.SNACKBAR, this::enqueue); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + + /////////////////////////////////////////////////////////////////////////// + // Setters / Getters + /////////////////////////////////////////////////////////////////////////// + + public Pane getPopupContainer() { + return snackbarContainer; + } + + public void setPrefWidth(double width) { + content.setPrefWidth(width); + } + + public double getPrefWidth() { + return content.getPrefWidth(); + } + + /////////////////////////////////////////////////////////////////////////// + // Public API + /////////////////////////////////////////////////////////////////////////// + + public void registerSnackbarContainer(Pane snackbarContainer) { + if (snackbarContainer != null) { + if (this.snackbarContainer != null) { + //since listeners are added the container should be properly registered/unregistered + throw new IllegalArgumentException("Snackbar Container already set"); + } + this.snackbarContainer = snackbarContainer; + this.snackbarContainer.getChildren().add(this); + this.snackbarContainer.heightProperty().addListener(weakSizeListener); + this.snackbarContainer.widthProperty().addListener(weakSizeListener); + } + } + + public void unregisterSnackbarContainer(Pane snackbarContainer) { + if (snackbarContainer != null) { + if (this.snackbarContainer == null) { + throw new IllegalArgumentException("Snackbar Container not set"); + } + this.snackbarContainer.getChildren().remove(this); + this.snackbarContainer.heightProperty().removeListener(weakSizeListener); + this.snackbarContainer.widthProperty().removeListener(weakSizeListener); + this.snackbarContainer = null; + } + } + + private void show(SnackbarEvent event) { + content.getChildren().setAll(event.getContent()); + openAnimation = getTimeline(event.getTimeout()); + if (event.getPseudoClass() != null) { + activePseudoClass = event.getPseudoClass(); + content.pseudoClassStateChanged(activePseudoClass, true); + } + openAnimation.play(); + } + + private Timeline openAnimation = null; + + private Timeline getTimeline(Duration timeout) { + Timeline animation; + animation = new Timeline( + new KeyFrame( + Duration.ZERO, + e -> this.toBack(), + new KeyValue(this.visibleProperty(), false, Interpolator.EASE_BOTH), + new KeyValue(this.translateYProperty(), this.getLayoutBounds().getHeight(), easeInterpolator), + new KeyValue(this.opacityProperty(), 0, easeInterpolator) + ), + new KeyFrame( + Duration.millis(10), + e -> this.toFront(), + new KeyValue(this.visibleProperty(), true, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(300), + new KeyValue(this.opacityProperty(), 1, easeInterpolator), + new KeyValue(this.translateYProperty(), 0, easeInterpolator) + ) + ); + animation.setCycleCount(1); + pauseTransition = Duration.INDEFINITE.equals(timeout) ? null : new PauseTransition(timeout); + if (pauseTransition != null) { + animation.setOnFinished(finish -> { + pauseTransition.setOnFinished(done -> { + pauseTransition = null; + eventsSet.remove(currentEvent); + currentEvent = eventQueue.peek(); + close(); + }); + pauseTransition.play(); + }); + } + return animation; + } + + public void close() { + if (openAnimation != null) { + openAnimation.stop(); + } + if (this.isVisible()) { + Timeline closeAnimation = new Timeline( + new KeyFrame( + Duration.ZERO, + e -> this.toFront(), + new KeyValue(this.opacityProperty(), 1, easeInterpolator), + new KeyValue(this.translateYProperty(), 0, easeInterpolator) + ), + new KeyFrame( + Duration.millis(290), + new KeyValue(this.visibleProperty(), true, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(300), + e -> this.toBack(), + new KeyValue(this.visibleProperty(), false, Interpolator.EASE_BOTH), + new KeyValue(this.translateYProperty(), + this.getLayoutBounds().getHeight(), + easeInterpolator), + new KeyValue(this.opacityProperty(), 0, easeInterpolator) + ) + ); + closeAnimation.setCycleCount(1); + closeAnimation.setOnFinished(e -> { + resetPseudoClass(); + processSnackbar(); + }); + closeAnimation.play(); + } + } + + private SnackbarEvent currentEvent = null; + + public SnackbarEvent getCurrentEvent() { + return currentEvent; + } + + /** + * Shows {@link SnackbarEvent SnackbarEvents} one by one. The next event will be shown after the current event's duration. + * + * @param event the {@link SnackbarEvent event} to put in the queue. + */ + public void enqueue(SnackbarEvent event) { + synchronized (this) { + if (!eventsSet.contains(event)) { + eventsSet.add(event); + eventQueue.offer(event); + } else if (currentEvent == event && pauseTransition != null) { + pauseTransition.playFromStart(); + } + } + if (processingQueue.compareAndSet(false, true)) { + Platform.runLater(() -> { + currentEvent = eventQueue.poll(); + if (currentEvent != null) { + show(currentEvent); + } + }); + } + } + + private void resetPseudoClass() { + if (activePseudoClass != null) { + content.pseudoClassStateChanged(activePseudoClass, false); + activePseudoClass = null; + } + } + + private void processSnackbar() { + currentEvent = eventQueue.poll(); + if (currentEvent != null) { + eventsSet.remove(currentEvent); + show(currentEvent); + } else { + //The enqueue method and this listener should be executed sequentially on the FX Thread so there + //should not be a race condition + processingQueue.getAndSet(false); + } + } + + private void refreshPopup() { + if (snackbarContainer == null) { + return; + } + Bounds contentBound = this.getLayoutBounds(); + double offsetX = Math.ceil(snackbarContainer.getWidth() / 2) - Math.ceil(contentBound.getWidth() / 2); + double offsetY = snackbarContainer.getHeight() - contentBound.getHeight(); + this.setLayoutX(offsetX); + this.setLayoutY(offsetY); + + } + + /////////////////////////////////////////////////////////////////////////// + // Event API + /////////////////////////////////////////////////////////////////////////// + + /** + * Specifies what and how long to show a {@link JFXSnackbar}. + *

+ * The what can be any arbitrary {@link Node}; the {@link JFXSnackbarLayout} is a great choice. + *

+ * The how long is specified in the form of a {@link Duration javafx.util.Duration}, not to be + * confused with the {@link java.time.Duration}. + */ + public static class SnackbarEvent extends Event { + + public static final EventType SNACKBAR = new EventType<>(Event.ANY, "SNACKBAR"); + + /** + * The amount of time the snackbar will show for, if not otherwise specified. + *

+ * It's 1.5 seconds. + */ + public static Duration DEFAULT_DURATION = Duration.seconds(1.5); + + private final Node content; + private final PseudoClass pseudoClass; + private final Duration timeout; + + /** + * Creates a {@link SnackbarEvent} with the {@link #DEFAULT_DURATION default duration} and no pseudoClass. + * + * @param content what you want shown in the snackbar; a {@link JFXSnackbarLayout} is a great choice. + */ + public SnackbarEvent(Node content) { + this(content, DEFAULT_DURATION, null); + } + + /** + * Creates a {@link SnackbarEvent} with the {@link #DEFAULT_DURATION default duration}; you specify the contents and + * pseudoClass. + * + * @param content what you want shown in the snackbar; a {@link JFXSnackbarLayout} is a great choice. + */ + public SnackbarEvent(Node content, PseudoClass pseudoClass) { + this(content, DEFAULT_DURATION, pseudoClass); + } + + /** + * Creates a SnackbarEvent with no pseudoClass; you specify the contents and duration. + * pseudoClass. + * + * @param content what you want shown in the snackbar; a {@link JFXSnackbarLayout} is a great choice. + * @param timeout the amount of time you want the snackbar to show for. + */ + public SnackbarEvent(Node content, Duration timeout) { + this(content, timeout, null); + } + + /** + * Creates a SnackbarEvent; you specify the contents, duration and pseudoClass. + *

+ * If you don't need so much customization, try one of the other constructors. + * + * @param content what you want shown in the snackbar; a {@link JFXSnackbarLayout} is a great choice. + * @param timeout the amount of time you want the snackbar to show for. + */ + public SnackbarEvent(Node content, Duration timeout, PseudoClass pseudoClass) { + super(SNACKBAR); + this.content = content; + this.pseudoClass = pseudoClass; + this.timeout = timeout; + } + + public Node getContent() { + return content; + } + + public PseudoClass getPseudoClass() { + return pseudoClass; + } + + public Duration getTimeout() { + return timeout; + } + + @SuppressWarnings("unchecked") + @Override + public EventType getEventType() { + return (EventType) super.getEventType(); + } + + public boolean isPersistent() { + return Duration.INDEFINITE.equals(getTimeout()); + } + } +} + diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXSnackbarLayout.java b/HMCL/src/main/java/com/jfoenix/controls/JFXSnackbarLayout.java new file mode 100644 index 0000000000..9e945f5089 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXSnackbarLayout.java @@ -0,0 +1,113 @@ +/* + * 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 javafx.beans.binding.Bindings; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.scene.control.Control; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; + +/** + * JFXSnackbarLayout default layout for snackbar content + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2018-11-16 + */ +public class JFXSnackbarLayout extends BorderPane { + + private final Label toast; + private JFXButton action; + private final StackPane actionContainer; + + public JFXSnackbarLayout(String message) { + this(message, null, null); + } + + public JFXSnackbarLayout(String message, String actionText, EventHandler actionHandler) { + initialize(); + + toast = new Label(); + toast.setMinWidth(Control.USE_PREF_SIZE); + toast.getStyleClass().add("jfx-snackbar-toast"); + toast.setWrapText(true); + toast.setText(message); + StackPane toastContainer = new StackPane(toast); + toastContainer.setPadding(new Insets(20)); + actionContainer = new StackPane(); + actionContainer.setPadding(new Insets(0, 10, 0, 0)); + + toast.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> { + if (getPrefWidth() == -1) { + return getPrefWidth(); + } + double actionWidth = actionContainer.isVisible() ? actionContainer.getWidth() : 0.0; + return prefWidthProperty().get() - actionWidth; + }, prefWidthProperty(), actionContainer.widthProperty(), actionContainer.visibleProperty())); + + setLeft(toastContainer); + setRight(actionContainer); + + if (actionText != null) { + action = new JFXButton(); + action.setText(actionText); + action.setOnAction(actionHandler); + action.setMinWidth(Control.USE_PREF_SIZE); + action.setButtonType(JFXButton.ButtonType.FLAT); + action.getStyleClass().add("jfx-snackbar-action"); + // actions will be added upon showing the snackbar if needed + actionContainer.getChildren().add(action); + + if (!actionText.isEmpty()) { + action.setVisible(true); + actionContainer.setVisible(true); + actionContainer.setManaged(true); + // to force updating the layout bounds + action.setText(""); + action.setText(actionText); + action.setOnAction(actionHandler); + } else { + actionContainer.setVisible(false); + actionContainer.setManaged(false); + action.setVisible(false); + } + } + } + + private static final String DEFAULT_STYLE_CLASS = "jfx-snackbar-layout"; + + public String getToast() { + return toast.getText(); + } + + public void setToast(String toast) { + this.toast.setText(toast); + } + + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } +} + diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXSpinner.java b/HMCL/src/main/java/com/jfoenix/controls/JFXSpinner.java new file mode 100644 index 0000000000..2b6abf692a --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXSpinner.java @@ -0,0 +1,182 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package com.jfoenix.controls; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.css.CssMetaData; +import javafx.css.SimpleStyleableDoubleProperty; +import javafx.css.StyleConverter; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.scene.Parent; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Arc; +import javafx.util.Duration; + +// Old +public class JFXSpinner extends StackPane { + private static final Color GREEN_COLOR = Color.valueOf("#0F9D58"); + private static final Color RED_COLOR = Color.valueOf("#db4437"); + private static final Color YELLOW_COLOR = Color.valueOf("#f4b400"); + private static final Color BLUE_COLOR = Color.valueOf("#4285f4"); + + private static final String DEFAULT_STYLE_CLASS = "jfx-spinner"; + + private Timeline timeline; + private Arc arc; + private boolean initialized; + + public JFXSpinner() { + this.getStyleClass().add("jfx-spinner"); + this.initialize(); + } + + private void initialize() { + this.arc = new Arc(0.0, 0.0, 12.0, 12.0, 0.0, 360.0); + this.arc.setFill(Color.TRANSPARENT); + this.arc.setStrokeWidth(3.0); + this.arc.getStyleClass().add("arc"); + this.arc.radiusXProperty().bindBidirectional(this.radius); + this.arc.radiusYProperty().bindBidirectional(this.radius); + this.getChildren().add(this.arc); + this.minWidthProperty().bind(Bindings.createDoubleBinding(() -> this.getRadius() * 2.0 + this.arc.getStrokeWidth() + 5.0, this.radius, this.arc.strokeWidthProperty())); + this.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> this.getRadius() * 2.0 + this.arc.getStrokeWidth() + 5.0, this.radius, this.arc.strokeWidthProperty())); + this.minHeightProperty().bind(Bindings.createDoubleBinding(() -> this.getRadius() * 2.0 + this.arc.getStrokeWidth() + 5.0, this.radius, this.arc.strokeWidthProperty())); + this.maxHeightProperty().bind(Bindings.createDoubleBinding(() -> this.getRadius() * 2.0 + this.arc.getStrokeWidth() + 5.0, this.radius, this.arc.strokeWidthProperty())); + } + + private KeyFrame[] getKeyFrames(double angle, double duration, Color color) { + return new KeyFrame[]{ + new KeyFrame(Duration.seconds(duration), + new KeyValue(this.arc.lengthProperty(), 5, Interpolator.LINEAR), + new KeyValue(this.arc.startAngleProperty(), angle + 45.0 + this.getStartingAngle(), Interpolator.LINEAR)), + new KeyFrame(Duration.seconds(duration + 0.4), + new KeyValue(this.arc.lengthProperty(), 250, Interpolator.LINEAR), + new KeyValue(this.arc.startAngleProperty(), angle + 90.0 + this.getStartingAngle(), Interpolator.LINEAR)), + new KeyFrame(Duration.seconds(duration + 0.7), + new KeyValue(this.arc.lengthProperty(), 250, Interpolator.LINEAR), + new KeyValue(this.arc.startAngleProperty(), angle + 135.0 + this.getStartingAngle(), Interpolator.LINEAR)), + new KeyFrame(Duration.seconds(duration + 1.1), + new KeyValue(this.arc.lengthProperty(), 5, Interpolator.LINEAR), + new KeyValue(this.arc.startAngleProperty(), angle + 435.0 + this.getStartingAngle(), Interpolator.LINEAR), + new KeyValue(this.arc.strokeProperty(), color, Interpolator.EASE_BOTH)) + }; + } + + protected void layoutChildren() { + if (!this.initialized) { + super.layoutChildren(); + Color initialColor = (Color) this.arc.getStroke(); + if (initialColor == null) { + this.arc.setStroke(BLUE_COLOR); + } + + KeyFrame[] blueFrame = this.getKeyFrames(0.0, 0.0, initialColor == null ? BLUE_COLOR : initialColor); + KeyFrame[] redFrame = this.getKeyFrames(450.0, 1.4, initialColor == null ? RED_COLOR : initialColor); + KeyFrame[] yellowFrame = this.getKeyFrames(900.0, 2.8, initialColor == null ? YELLOW_COLOR : initialColor); + KeyFrame[] greenFrame = this.getKeyFrames(1350.0, 4.2, initialColor == null ? GREEN_COLOR : initialColor); + KeyFrame endingFrame = new KeyFrame(Duration.seconds(5.6), + new KeyValue(this.arc.lengthProperty(), 5, Interpolator.LINEAR), + new KeyValue(this.arc.startAngleProperty(), 1845.0 + this.getStartingAngle(), Interpolator.LINEAR) + ); + if (this.timeline != null) { + this.timeline.stop(); + } + + this.timeline = new Timeline( + blueFrame[0], blueFrame[1], blueFrame[2], blueFrame[3], + redFrame[0], redFrame[1], redFrame[2], redFrame[3], + yellowFrame[0], yellowFrame[1], yellowFrame[2], yellowFrame[3], + greenFrame[0], greenFrame[1], greenFrame[2], greenFrame[3], + endingFrame + ); + this.timeline.setCycleCount(-1); + this.timeline.setRate(1.0); + this.timeline.play(); + this.initialized = true; + } + + } + + private final StyleableDoubleProperty radius = new SimpleStyleableDoubleProperty(StyleableProperties.RADIUS, this, "radius", 12.0); + public final StyleableDoubleProperty radiusProperty() { + return this.radius; + } + + public final double getRadius() { + return this.radiusProperty().get(); + } + + public final void setRadius(double radius) { + this.radiusProperty().set(radius); + } + + private final StyleableDoubleProperty startingAngle = new SimpleStyleableDoubleProperty(StyleableProperties.STARTING_ANGLE, this, "starting_angle", 360.0 - Math.random() * 720.0); + + public final StyleableDoubleProperty startingAngleProperty() { + return this.startingAngle; + } + + public final double getStartingAngle() { + return this.startingAngleProperty().get(); + } + + public final void setStartingAngle(double startingAngle) { + this.startingAngleProperty().set(startingAngle); + } + + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + private static class StyleableProperties { + private static final CssMetaData RADIUS = + new CssMetaData("-jfx-radius", StyleConverter.getSizeConverter(), 12) { + public boolean isSettable(JFXSpinner control) { + return !control.radius.isBound(); + } + + public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { + return control.radius; + } + }; + private static final CssMetaData STARTING_ANGLE = + new CssMetaData("-jfx-starting-angle", StyleConverter.getSizeConverter(), 360.0 - Math.random() * 720.0) { + public boolean isSettable(JFXSpinner control) { + return !control.startingAngle.isBound(); + } + + public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) { + return control.startingAngle; + } + }; + private static final List> CHILD_STYLEABLES; + + private StyleableProperties() { + } + + static { + List> styleables = new ArrayList<>(ProgressIndicator.getClassCssMetaData()); + Collections.addAll(styleables, RADIUS, STARTING_ANGLE); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXTextArea.java b/HMCL/src/main/java/com/jfoenix/controls/JFXTextArea.java new file mode 100644 index 0000000000..fce821f21a --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXTextArea.java @@ -0,0 +1,289 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.controls.base.IFXLabelFloatControl; +import com.jfoenix.skins.JFXTextAreaSkin; +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.scene.control.Skin; +import javafx.scene.control.TextArea; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXTextArea is the material design implementation of a text area. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTextArea extends TextArea implements IFXLabelFloatControl { + + /** + * {@inheritDoc} + */ + public JFXTextArea() { + initialize(); + } + + /** + * {@inheritDoc} + */ + public JFXTextArea(String text) { + super(text); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXTextAreaSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + /** + * {@inheritDoc} + */ + @Override + public String getUserAgentStylesheet() { + return USER_AGENT_STYLESHEET; + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * wrapper for validation properties / methods + */ + ValidationControl validationControl = new ValidationControl(this); + + @Override + public ValidatorBase getActiveValidator() { + return validationControl.getActiveValidator(); + } + + @Override + public ReadOnlyObjectProperty activeValidatorProperty() { + return validationControl.activeValidatorProperty(); + } + + @Override + public ObservableList getValidators() { + return validationControl.getValidators(); + } + + @Override + public void setValidators(ValidatorBase... validators) { + validationControl.setValidators(validators); + } + + @Override + public boolean validate() { + return validationControl.validate(); + } + + @Override + public void resetValidation() { + validationControl.resetValidation(); + } + + /*************************************************************************** + * * + * Styleable Properties * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-text-field'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-text-area"; + private static final String USER_AGENT_STYLESHEET = JFoenixResources.load("css/controls/jfx-text-area.css").toExternalForm(); + /** + * set true to show a float the prompt text when focusing the field + */ + private final StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, + JFXTextArea.this, "lableFloat", false); + + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the text area is unfocused + */ + private final StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR, + JFXTextArea.this, "unFocusColor", Color.rgb(77, 77, 77)); + + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the text area is focused + */ + private final StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR, + JFXTextArea.this, + "focusColor", + Color.valueOf("#4059A9")); + + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + /** + * disable animation on validation + */ + private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, + JFXTextArea.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 class StyleableProperties { + private static final CssMetaData UNFOCUS_COLOR = new CssMetaData( + "-jfx-unfocus-color", + StyleConverter.getPaintConverter(), + Color.rgb(77, 77, 77)) { + @Override + public boolean isSettable(JFXTextArea control) { + return !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextArea control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData FOCUS_COLOR = new CssMetaData( + "-jfx-focus-color", + StyleConverter.getPaintConverter(), + Color.valueOf("#4059A9")) { + @Override + public boolean isSettable(JFXTextArea control) { + return !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextArea control) { + return control.focusColorProperty(); + } + }; + private static final CssMetaData LABEL_FLOAT = new CssMetaData( + "-jfx-label-float", + StyleConverter.getBooleanConverter(), + false) { + @Override + public boolean isSettable(JFXTextArea control) { + return !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) { + return control.labelFloatProperty(); + } + }; + + private static final CssMetaData DISABLE_ANIMATION = + new CssMetaData("-jfx-disable-animation", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXTextArea control) { + return !control.disableAnimation.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) { + return control.disableAnimationProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = new ArrayList<>( + TextArea.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXTextField.java b/HMCL/src/main/java/com/jfoenix/controls/JFXTextField.java new file mode 100644 index 0000000000..524ad6d4a9 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXTextField.java @@ -0,0 +1,295 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.controls.base.IFXLabelFloatControl; +import com.jfoenix.skins.JFXTextFieldSkin; +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.collections.ObservableList; +import javafx.css.*; +import javafx.scene.control.Skin; +import javafx.scene.control.TextField; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXTextField is the material design implementation of a text Field. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXTextField extends TextField implements IFXLabelFloatControl { + + public JFXTextField() { + initialize(); + } + + public JFXTextField(String text) { + super(text); + initialize(); + } + + @Override + protected Skin createDefaultSkin() { + return new JFXTextFieldSkin<>(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + @Override + public String getUserAgentStylesheet() { + return USER_AGENT_STYLESHEET; + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + /** + * wrapper for validation properties / methods + */ + ValidationControl validationControl = new ValidationControl(this); + + @Override + public ValidatorBase getActiveValidator() { + return validationControl.getActiveValidator(); + } + + @Override + public ReadOnlyObjectProperty activeValidatorProperty() { + return validationControl.activeValidatorProperty(); + } + + @Override + public ObservableList getValidators() { + return validationControl.getValidators(); + } + + @Override + public void setValidators(ValidatorBase... validators) { + validationControl.setValidators(validators); + } + + @Override + public boolean validate() { + return validationControl.validate(); + } + + @Override + public void resetValidation() { + validationControl.resetValidation(); + } + + /*************************************************************************** + * * + * Styleable Properties * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-text-field'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-text-field"; + private static final String USER_AGENT_STYLESHEET = JFoenixResources.load("css/controls/jfx-text-field.css").toExternalForm(); + + + /** + * set true to show a float the prompt text when focusing the field + */ + private final StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, + JFXTextField.this, + "lableFloat", + false); + + @Override + public final StyleableBooleanProperty labelFloatProperty() { + return this.labelFloat; + } + + @Override + public final boolean isLabelFloat() { + return this.labelFloatProperty().get(); + } + + @Override + public final void setLabelFloat(final boolean labelFloat) { + this.labelFloatProperty().set(labelFloat); + } + + /** + * default color used when the field is unfocused + */ + private final StyleableObjectProperty unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR, + JFXTextField.this, + "unFocusColor", + Color.rgb(77, + 77, + 77)); + + @Override + public Paint getUnFocusColor() { + return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get(); + } + + @Override + public StyleableObjectProperty unFocusColorProperty() { + return this.unFocusColor; + } + + @Override + public void setUnFocusColor(Paint color) { + this.unFocusColor.set(color); + } + + /** + * default color used when the field is focused + */ + private final StyleableObjectProperty focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR, + JFXTextField.this, + "focusColor", + Color.valueOf("#4059A9")); + + @Override + public Paint getFocusColor() { + return focusColor == null ? Color.valueOf("#4059A9") : focusColor.get(); + } + + @Override + public StyleableObjectProperty focusColorProperty() { + return this.focusColor; + } + + @Override + public void setFocusColor(Paint color) { + this.focusColor.set(color); + } + + /** + * disable animation on validation + */ + private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, + JFXTextField.this, + "disableAnimation", + false); + + @Override + public final StyleableBooleanProperty disableAnimationProperty() { + return this.disableAnimation; + } + + @Override + public final Boolean isDisableAnimation() { + return disableAnimation != null && this.disableAnimationProperty().get(); + } + + @Override + public final void setDisableAnimation(final Boolean disabled) { + this.disableAnimationProperty().set(disabled); + } + + + private static class StyleableProperties { + private static final CssMetaData UNFOCUS_COLOR = new CssMetaData( + "-jfx-unfocus-color", + StyleConverter.getPaintConverter(), + Color.valueOf("#A6A6A6")) { + @Override + public boolean isSettable(JFXTextField control) { + return control.unFocusColor == null || !control.unFocusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextField control) { + return control.unFocusColorProperty(); + } + }; + private static final CssMetaData FOCUS_COLOR = new CssMetaData( + "-jfx-focus-color", + StyleConverter.getPaintConverter(), + Color.valueOf("#3f51b5")) { + @Override + public boolean isSettable(JFXTextField control) { + return !control.focusColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXTextField control) { + return control.focusColorProperty(); + } + }; + private static final CssMetaData LABEL_FLOAT = new CssMetaData( + "-jfx-label-float", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXTextField control) { + return !control.labelFloat.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextField control) { + return control.labelFloatProperty(); + } + }; + + private static final CssMetaData DISABLE_ANIMATION = + new CssMetaData("-jfx-disable-animation", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXTextField control) { + return !control.disableAnimation.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXTextField control) { + return control.disableAnimationProperty(); + } + }; + + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = new ArrayList<>( + TextField.getClassCssMetaData()); + Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java new file mode 100644 index 0000000000..4a483a5df1 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java @@ -0,0 +1,382 @@ +/* + * 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.assets.JFoenixResources; +import com.jfoenix.skins.JFXToggleButtonSkin; +import javafx.css.*; +import javafx.scene.control.Skin; +import javafx.scene.control.ToggleButton; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * JFXToggleButton is the material design implementation of a toggle button. + * important CSS Selectors: + *

+ * .jfx-toggle-button{ + * -fx-toggle-color: color-value; + * -fx-untoggle-color: color-value; + * -fx-toggle-line-color: color-value; + * -fx-untoggle-line-color: color-value; + * } + *

+ * To change the rippler color when toggled: + *

+ * .jfx-toggle-button .jfx-rippler{ + * -fx-rippler-fill: color-value; + * } + *

+ * .jfx-toggle-button:selected .jfx-rippler{ + * -fx-rippler-fill: color-value; + * } + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXToggleButton extends ToggleButton { + + /** + * {@inheritDoc} + */ + public JFXToggleButton() { + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Skin createDefaultSkin() { + return new JFXToggleButtonSkin(this); + } + + private void initialize() { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + // it's up for the user to add this behavior +// toggleColor.addListener((o, oldVal, newVal) -> { +// // update line color in case not set by the user +// if(newVal instanceof Color) +// toggleLineColor.set(((Color)newVal).desaturate().desaturate().brighter()); +// }); + } + + /** + * {@inheritDoc} + */ + @Override + public String getUserAgentStylesheet() { + return JFoenixResources.load("css/controls/jfx-toggle-button.css").toExternalForm(); + } + + + /*************************************************************************** + * * + * styleable Properties * + * * + **************************************************************************/ + + /** + * Initialize the style class to 'jfx-toggle-button'. + *

+ * This is the selector class from which CSS can be used to style + * this control. + */ + private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button"; + + /** + * default color used when the button is toggled + */ + private final StyleableObjectProperty toggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.TOGGLE_COLOR, + JFXToggleButton.this, + "toggleColor", + Color.valueOf( + "#009688")); + + public Paint getToggleColor() { + return toggleColor == null ? Color.valueOf("#009688") : toggleColor.get(); + } + + public StyleableObjectProperty toggleColorProperty() { + return this.toggleColor; + } + + public void setToggleColor(Paint color) { + this.toggleColor.set(color); + } + + /** + * default color used when the button is not toggled + */ + private final StyleableObjectProperty untoggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNTOGGLE_COLOR, + JFXToggleButton.this, + "unToggleColor", + Color.valueOf( + "#FAFAFA")); + + public Paint getUnToggleColor() { + return untoggleColor == null ? Color.valueOf("#FAFAFA") : untoggleColor.get(); + } + + public StyleableObjectProperty unToggleColorProperty() { + return this.untoggleColor; + } + + public void setUnToggleColor(Paint color) { + this.untoggleColor.set(color); + } + + /** + * default line color used when the button is toggled + */ + private final StyleableObjectProperty toggleLineColor = new SimpleStyleableObjectProperty<>( + StyleableProperties.TOGGLE_LINE_COLOR, + JFXToggleButton.this, + "toggleLineColor", + Color.valueOf("#77C2BB")); + + public Paint getToggleLineColor() { + return toggleLineColor == null ? Color.valueOf("#77C2BB") : toggleLineColor.get(); + } + + public StyleableObjectProperty toggleLineColorProperty() { + return this.toggleLineColor; + } + + public void setToggleLineColor(Paint color) { + this.toggleLineColor.set(color); + } + + /** + * default line color used when the button is not toggled + */ + private final StyleableObjectProperty untoggleLineColor = new SimpleStyleableObjectProperty<>( + StyleableProperties.UNTOGGLE_LINE_COLOR, + JFXToggleButton.this, + "unToggleLineColor", + Color.valueOf("#999999")); + + public Paint getUnToggleLineColor() { + return untoggleLineColor == null ? Color.valueOf("#999999") : untoggleLineColor.get(); + } + + public StyleableObjectProperty unToggleLineColorProperty() { + return this.untoggleLineColor; + } + + public void setUnToggleLineColor(Paint color) { + this.untoggleLineColor.set(color); + } + + /** + * Default size of the toggle button. + */ + private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty( + StyleableProperties.SIZE, + JFXToggleButton.this, + "size", + 10.0); + + public double getSize() { + return size.get(); + } + + public StyleableDoubleProperty sizeProperty() { + return this.size; + } + + public void setSize(double size) { + this.size.set(size); + } + + /** + * Disable the visual indicator for focus + */ + private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS, + JFXToggleButton.this, + "disableVisualFocus", + false); + + public final StyleableBooleanProperty disableVisualFocusProperty() { + return this.disableVisualFocus; + } + + public final Boolean isDisableVisualFocus() { + return disableVisualFocus != null && this.disableVisualFocusProperty().get(); + } + + public final void setDisableVisualFocus(final Boolean disabled) { + this.disableVisualFocusProperty().set(disabled); + } + + + /** + * disable animation on button action + */ + private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, + JFXToggleButton.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 class StyleableProperties { + private static final CssMetaData TOGGLE_COLOR = + new CssMetaData("-jfx-toggle-color", + StyleConverter.getPaintConverter(), Color.valueOf("#009688")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.toggleColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.toggleColorProperty(); + } + }; + + private static final CssMetaData UNTOGGLE_COLOR = + new CssMetaData("-jfx-untoggle-color", + StyleConverter.getPaintConverter(), Color.valueOf("#FAFAFA")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.untoggleColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.unToggleColorProperty(); + } + }; + + private static final CssMetaData TOGGLE_LINE_COLOR = + new CssMetaData("-jfx-toggle-line-color", + StyleConverter.getPaintConverter(), Color.valueOf("#77C2BB")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.toggleLineColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.toggleLineColorProperty(); + } + }; + + private static final CssMetaData UNTOGGLE_LINE_COLOR = + new CssMetaData("-jfx-untoggle-line-color", + StyleConverter.getPaintConverter(), Color.valueOf("#999999")) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.untoggleLineColor.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.unToggleLineColorProperty(); + } + }; + + private static final CssMetaData SIZE = + new CssMetaData("-jfx-size", + StyleConverter.getSizeConverter(), 10.0) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.size.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.sizeProperty(); + } + }; + private static final CssMetaData DISABLE_VISUAL_FOCUS = + new CssMetaData("-jfx-disable-visual-focus", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.disableVisualFocus.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) { + return control.disableVisualFocusProperty(); + } + }; + + private static final CssMetaData DISABLE_ANIMATION = + new CssMetaData("-jfx-disable-animation", + StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(JFXToggleButton control) { + return !control.disableAnimation.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) { + return control.disableAnimationProperty(); + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(ToggleButton.getClassCssMetaData()); + Collections.addAll(styleables, + SIZE, + TOGGLE_COLOR, + UNTOGGLE_COLOR, + TOGGLE_LINE_COLOR, + UNTOGGLE_LINE_COLOR, + DISABLE_VISUAL_FOCUS, + DISABLE_ANIMATION + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } + + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXTreeCell.java b/HMCL/src/main/java/com/jfoenix/controls/JFXTreeCell.java new file mode 100644 index 0000000000..13f1ba1748 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXTreeCell.java @@ -0,0 +1,149 @@ +/* + * 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.beans.InvalidationListener; +import javafx.beans.WeakInvalidationListener; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.TreeCell; +import javafx.scene.control.TreeItem; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; + +import java.lang.ref.WeakReference; + +/** + * JFXTreeCell is simple material design implementation of a tree cell. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-02-15 + */ +public class JFXTreeCell extends TreeCell { + + protected JFXRippler cellRippler = new JFXRippler(this){ + @Override + protected Node getMask() { + Region clip = new Region(); + JFXNodeUtils.updateBackground(JFXTreeCell.this.getBackground(), clip); + double width = control.getLayoutBounds().getWidth(); + double height = control.getLayoutBounds().getHeight(); + clip.resize(width, height); + return clip; + } + + @Override + protected void positionControl(Node control) { + // do nothing + } + }; + private HBox hbox; + private final StackPane selectedPane = new StackPane(); + + private final InvalidationListener treeItemGraphicInvalidationListener = observable -> updateDisplay(getItem(), + isEmpty()); + private final WeakInvalidationListener weakTreeItemGraphicListener = new WeakInvalidationListener( + treeItemGraphicInvalidationListener); + + private WeakReference> treeItemRef; + + public JFXTreeCell() { + + selectedPane.getStyleClass().add("selection-bar"); + selectedPane.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY))); + selectedPane.setPrefWidth(3); + selectedPane.setMouseTransparent(true); + selectedProperty().addListener((o, oldVal, newVal) -> selectedPane.setVisible(newVal)); + + final InvalidationListener treeItemInvalidationListener = observable -> { + TreeItem oldTreeItem = treeItemRef == null ? null : treeItemRef.get(); + if (oldTreeItem != null) { + oldTreeItem.graphicProperty().removeListener(weakTreeItemGraphicListener); + } + + TreeItem newTreeItem = getTreeItem(); + if (newTreeItem != null) { + newTreeItem.graphicProperty().addListener(weakTreeItemGraphicListener); + treeItemRef = new WeakReference<>(newTreeItem); + } + }; + final WeakInvalidationListener weakTreeItemListener = new WeakInvalidationListener(treeItemInvalidationListener); + treeItemProperty().addListener(weakTreeItemListener); + if (getTreeItem() != null) { + getTreeItem().graphicProperty().addListener(weakTreeItemGraphicListener); + } + } + + @Override + protected void layoutChildren() { + super.layoutChildren(); + if (!getChildren().contains(selectedPane)) { + getChildren().add(0, cellRippler); + cellRippler.rippler.clear(); + getChildren().add(0, selectedPane); + } + cellRippler.resizeRelocate(0, 0, getWidth(), getHeight()); + cellRippler.releaseRipple(); + selectedPane.resizeRelocate(0, 0, selectedPane.prefWidth(-1), getHeight()); + selectedPane.setVisible(isSelected()); + } + + private void updateDisplay(T item, boolean empty) { + if (item == null || empty) { + hbox = null; + setText(null); + setGraphic(null); + } else { + TreeItem treeItem = getTreeItem(); + if (treeItem != null && treeItem.getGraphic() != null) { + if (item instanceof Node) { + setText(null); + if (hbox == null) { + hbox = new HBox(3); + } + hbox.getChildren().setAll(treeItem.getGraphic(), (Node) item); + setGraphic(hbox); + } else { + hbox = null; + setText(item.toString()); + setGraphic(treeItem.getGraphic()); + } + } else { + hbox = null; + if (item instanceof Node) { + setText(null); + setGraphic((Node) item); + } else { + setText(item.toString()); + setGraphic(null); + } + } + } + } + + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + updateDisplay(item, empty); + setMouseTransparent(item == null || empty); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXTreeView.java b/HMCL/src/main/java/com/jfoenix/controls/JFXTreeView.java new file mode 100644 index 0000000000..11ca9834cb --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXTreeView.java @@ -0,0 +1,50 @@ +/* + * 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 javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; + +/** + * JFXTreeView is the material design implementation of a TreeView + * with expand/collapse animation and selection indicator. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-02-15 + */ +public class JFXTreeView extends TreeView { + + private static final String DEFAULT_STYLE_CLASS = "jfx-tree-view"; + + public JFXTreeView() { + init(); + } + + public JFXTreeView(TreeItem root) { + super(root); + init(); + } + + private void init() { + this.setCellFactory((view) -> new JFXTreeCell<>()); + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/ValidationControl.java b/HMCL/src/main/java/com/jfoenix/controls/ValidationControl.java new file mode 100644 index 0000000000..64ebb9890c --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/ValidationControl.java @@ -0,0 +1,102 @@ +/* + * 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.controls.base.IFXValidatableControl; +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Control; + +/** + * this class used as validation model wrapper for all {@link IFXValidatableControl} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2018-07-19 + */ +class ValidationControl implements IFXValidatableControl { + + private final ReadOnlyObjectWrapper activeValidator = new ReadOnlyObjectWrapper<>(); + + private final Control control; + + public ValidationControl(Control control) { + this.control = control; + } + + @Override + public ValidatorBase getActiveValidator() { + return activeValidator.get(); + } + + @Override + public ReadOnlyObjectProperty activeValidatorProperty() { + return this.activeValidator.getReadOnlyProperty(); + } + + ReadOnlyObjectWrapper activeValidatorWritableProperty() { + return this.activeValidator; + } + + /** + * list of validators that will validate the text value upon calling + * {{@link #validate()} + */ + private final ObservableList validators = FXCollections.observableArrayList(); + + @Override + public ObservableList getValidators() { + return validators; + } + + @Override + public void setValidators(ValidatorBase... validators) { + this.validators.addAll(validators); + } + + /** + * validates the text value using the list of validators provided by the user + * {{@link #setValidators(ValidatorBase...)} + * + * @return true if the value is valid else false + */ + @Override + public boolean validate() { + for (ValidatorBase validator : validators) { + // source control must be set to allow validators re-usability + validator.setSrcControl(control); + validator.validate(); + if (validator.getHasErrors()) { + activeValidator.set(validator); + return false; + } + } + activeValidator.set(null); + return true; + } + + public void resetValidation() { + control.pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false); + activeValidator.set(null); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/base/IFXLabelFloatControl.java b/HMCL/src/main/java/com/jfoenix/controls/base/IFXLabelFloatControl.java new file mode 100644 index 0000000000..94e92b0f0d --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/base/IFXLabelFloatControl.java @@ -0,0 +1,51 @@ +/* + * 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.base; + +import javafx.css.StyleableBooleanProperty; +import javafx.css.StyleableObjectProperty; +import javafx.scene.paint.Paint; + +/** + * this class is created for internal use only, to remove duplication between text input controls + * skins + * + * Created by sshahine on 7/14/2017. + */ +public interface IFXLabelFloatControl extends IFXValidatableControl, IFXStaticControl { + + StyleableBooleanProperty labelFloatProperty(); + + boolean isLabelFloat(); + + void setLabelFloat(boolean labelFloat); + + Paint getUnFocusColor(); + + StyleableObjectProperty unFocusColorProperty(); + + void setUnFocusColor(Paint color); + + Paint getFocusColor(); + + StyleableObjectProperty focusColorProperty(); + + void setFocusColor(Paint color); +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/base/IFXStaticControl.java b/HMCL/src/main/java/com/jfoenix/controls/base/IFXStaticControl.java new file mode 100644 index 0000000000..1ea098807b --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/base/IFXStaticControl.java @@ -0,0 +1,31 @@ +/* + * 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.base; + +import javafx.css.StyleableBooleanProperty; + +public interface IFXStaticControl { + + StyleableBooleanProperty disableAnimationProperty(); + + Boolean isDisableAnimation(); + + void setDisableAnimation(Boolean disabled); +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/base/IFXValidatableControl.java b/HMCL/src/main/java/com/jfoenix/controls/base/IFXValidatableControl.java new file mode 100644 index 0000000000..8f310bbeb5 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/base/IFXValidatableControl.java @@ -0,0 +1,39 @@ +/* + * 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.base; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.collections.ObservableList; + +public interface IFXValidatableControl { + + ValidatorBase getActiveValidator(); + + ReadOnlyObjectProperty activeValidatorProperty(); + + ObservableList getValidators(); + + void setValidators(ValidatorBase... validators); + + boolean validate(); + + void resetValidation(); +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java b/HMCL/src/main/java/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java new file mode 100644 index 0000000000..692be1dcbe --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java @@ -0,0 +1,88 @@ +/* + * 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.datamodels.treetable; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.TreeTableColumn; + +/** + * data model that is used in JFXTreeTableView, it's used to implement + * the grouping feature. + *

+ * Note: the data object used in JFXTreeTableView must extends this class + * + * @param is the concrete object of the Tree table + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class RecursiveTreeObject { + + /** + * grouped children objects + */ + ObservableList children = FXCollections.observableArrayList(); + + public ObservableList getChildren() { + return children; + } + + public void setChildren(ObservableList children) { + this.children = children; + } + + /** + * Whether or not the object is grouped by a specified tree table column + */ + ObjectProperty> groupedColumn = new SimpleObjectProperty<>(); + + public final ObjectProperty> groupedColumnProperty() { + return this.groupedColumn; + } + + public final TreeTableColumn getGroupedColumn() { + return this.groupedColumnProperty().get(); + } + + public final void setGroupedColumn(final TreeTableColumn groupedColumn) { + this.groupedColumnProperty().set(groupedColumn); + } + + /** + * the value that must be shown when grouped + */ + ObjectProperty groupedValue = new SimpleObjectProperty<>(); + + public final ObjectProperty groupedValueProperty() { + return this.groupedValue; + } + + public final Object getGroupedValue() { + return this.groupedValueProperty().get(); + } + + public final void setGroupedValue(final Object groupedValue) { + this.groupedValueProperty().set(groupedValue); + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/events/JFXAutoCompleteEvent.java b/HMCL/src/main/java/com/jfoenix/controls/events/JFXAutoCompleteEvent.java new file mode 100644 index 0000000000..b9d029fee7 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/events/JFXAutoCompleteEvent.java @@ -0,0 +1,45 @@ +/* + * 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.events; + +import javafx.event.Event; +import javafx.event.EventType; + +/** + * @author Shadi Shaheen + * @version 1.0.0 + * @since 2018-02-01 + */ +public class JFXAutoCompleteEvent extends Event { + + private final T object; + + public JFXAutoCompleteEvent(EventType eventType, T object) { + super(eventType); + this.object = object; + } + + public T getObject() { + return object; + } + + //TODO: more events to be added + public static final EventType> SELECTION = new EventType<>(Event.ANY, "JFX_AUTOCOMPLETE_SELECTION"); +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/events/JFXDialogEvent.java b/HMCL/src/main/java/com/jfoenix/controls/events/JFXDialogEvent.java new file mode 100644 index 0000000000..0439e4287d --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/events/JFXDialogEvent.java @@ -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.controls.events; + +import com.jfoenix.controls.JFXDialog; +import javafx.event.Event; +import javafx.event.EventType; + +/** + * JFXDialog events, used exclusively by the following methods: + *
    + *
  • {@code JFXDialog#close()} + *
  • {@code JFXDialog#getShowAnimation()} + *
+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDialogEvent extends Event { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new JFXDialog {@code Event} with the specified event type + * + * @param eventType the event type + */ + public JFXDialogEvent(EventType eventType) { + super(eventType); + } + + /** + * This event occurs when a JFXDialog is closed, no longer visible to the user + * ( after the exit animation ends ) + */ + public static final EventType CLOSED = + new EventType<>(Event.ANY, "JFX_DIALOG_CLOSED"); + + /** + * This event occurs when a JFXDialog is opened, visible to the user + * ( after the entrance animation ends ) + */ + public static final EventType OPENED = + new EventType<>(Event.ANY, "JFX_DIALOG_OPENED"); + +} diff --git a/HMCL/src/main/java/com/jfoenix/controls/events/JFXDrawerEvent.java b/HMCL/src/main/java/com/jfoenix/controls/events/JFXDrawerEvent.java new file mode 100644 index 0000000000..a1d2f8cf21 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/controls/events/JFXDrawerEvent.java @@ -0,0 +1,80 @@ +/* + * 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.events; + +import javafx.event.Event; +import javafx.event.EventType; + +/** + * JFXDrawer events, used exclusively by the following methods: + *
    + *
  • JFXDrawer#open() + *
  • JFXDrawer#close() + *
+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDrawerEvent extends Event { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new JFXDrawer {@code Event} with the specified event type + * + * @param eventType the event type + */ + public JFXDrawerEvent(EventType eventType) { + super(eventType); + } + + /** + * This event occurs when a JFXDrawer is closed, no longer visible to the user + * ( after the exit animation ends ) + */ + public static final EventType CLOSED = + new EventType<>(Event.ANY, "JFX_DRAWER_CLOSED"); + + /** + * This event occurs when a JFXDrawer is drawn, visible to the user + * ( after the entrance animation ends ) + */ + public static final EventType OPENED = + new EventType<>(Event.ANY, "JFX_DRAWER_OPENED"); + + /** + * This event occurs when a JFXDrawer is being drawn, visible to the user + * ( after the entrance animation ends ) + */ + public static final EventType OPENING = + new EventType<>(Event.ANY, "JFX_DRAWER_OPENING"); + + + /** + * This event occurs when a JFXDrawer is being closed, will become invisible to the user + * at the end of the animation + * ( after the entrance animation ends ) + */ + public static final EventType CLOSING = + new EventType<>(Event.ANY, "JFX_DRAWER_CLOSING"); + + +} diff --git a/HMCL/src/main/java/com/jfoenix/converters/ButtonTypeConverter.java b/HMCL/src/main/java/com/jfoenix/converters/ButtonTypeConverter.java new file mode 100644 index 0000000000..2185112a4e --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/converters/ButtonTypeConverter.java @@ -0,0 +1,72 @@ +/* + * 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.converters; + +import com.jfoenix.controls.JFXButton.ButtonType; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * Converts the CSS for -fx-button-type items into ButtonType. + * it's used in JFXButton + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class ButtonTypeConverter extends StyleConverter { + + private ButtonTypeConverter() { + super(); + } + + // lazy, thread-safe instantiation + private static class Holder { + static final ButtonTypeConverter INSTANCE = new ButtonTypeConverter(); + + private Holder() { + throw new IllegalAccessError("Holder class"); + } + } + + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + + + @Override + public ButtonType convert(ParsedValue value, Font notUsedFont) { + String string = value.getValue(); + try { + return ButtonType.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + LOG.warning("Invalid button type value '" + string + "'"); + return ButtonType.FLAT; + } + } + + @Override + public String toString() { + return "ButtonTypeConverter"; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/converters/DialogTransitionConverter.java b/HMCL/src/main/java/com/jfoenix/converters/DialogTransitionConverter.java new file mode 100644 index 0000000000..9475a5f316 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/converters/DialogTransitionConverter.java @@ -0,0 +1,62 @@ +/* + * 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.converters; + +import com.jfoenix.controls.JFXDialog.DialogTransition; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +/** + * Converts the CSS for -fx-dialog-transition items into DialogTransition. + * it's used in JFXDialog. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class DialogTransitionConverter extends StyleConverter { + // lazy, thread-safe instatiation + private static class Holder { + static final DialogTransitionConverter INSTANCE = new DialogTransitionConverter(); + } + + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + + private DialogTransitionConverter() { + } + + @Override + public DialogTransition convert(ParsedValue value, Font not_used) { + String string = value.getValue(); + try { + return DialogTransition.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + return DialogTransition.CENTER; + } + } + + @Override + public String toString() { + return "DialogTransitionConverter"; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/converters/IndicatorPositionConverter.java b/HMCL/src/main/java/com/jfoenix/converters/IndicatorPositionConverter.java new file mode 100644 index 0000000000..30736a77df --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/converters/IndicatorPositionConverter.java @@ -0,0 +1,63 @@ +/* + * 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.converters; + +import com.jfoenix.controls.JFXSlider.IndicatorPosition; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +/** + * Converts the CSS for -fx-indicator-position items into IndicatorPosition. + * it's used in JFXSlider. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class IndicatorPositionConverter extends StyleConverter { + // lazy, thread-safe instatiation + private static class Holder { + static final IndicatorPositionConverter INSTANCE = new IndicatorPositionConverter(); + } + + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + + private IndicatorPositionConverter() { + } + + @Override + public IndicatorPosition convert(ParsedValue value, Font not_used) { + String string = value.getValue().toUpperCase(); + try { + return IndicatorPosition.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + return IndicatorPosition.LEFT; + } + } + + @Override + public String toString() { + return "IndicatorPositionConverter"; + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/converters/RipplerMaskTypeConverter.java b/HMCL/src/main/java/com/jfoenix/converters/RipplerMaskTypeConverter.java new file mode 100644 index 0000000000..f3eec1f095 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/converters/RipplerMaskTypeConverter.java @@ -0,0 +1,62 @@ +/* + * 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.converters; + +import com.jfoenix.controls.JFXRippler.RipplerMask; +import javafx.css.ParsedValue; +import javafx.css.StyleConverter; +import javafx.scene.text.Font; + +/** + * Converts the CSS for -fx-mask-type items into RipplerMask. + * it's used in JFXRippler. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public final class RipplerMaskTypeConverter extends StyleConverter { + // lazy, thread-safe instatiation + private static class Holder { + static final RipplerMaskTypeConverter INSTANCE = new RipplerMaskTypeConverter(); + } + + public static StyleConverter getInstance() { + return Holder.INSTANCE; + } + + private RipplerMaskTypeConverter() { + } + + @Override + public RipplerMask convert(ParsedValue value, Font notUsed) { + String string = value.getValue(); + try { + return RipplerMask.valueOf(string); + } catch (IllegalArgumentException | NullPointerException exception) { + return RipplerMask.RECT; + } + } + + @Override + public String toString() { + return "RipplerMaskTypeConverter"; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/converters/base/NodeConverter.java b/HMCL/src/main/java/com/jfoenix/converters/base/NodeConverter.java new file mode 100644 index 0000000000..28763f2ed3 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/converters/base/NodeConverter.java @@ -0,0 +1,57 @@ +/* + * 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.converters.base; + +import javafx.scene.Node; + +/** + * Converter defines conversion behavior between Nodes and Objects. + * The type of Objects are defined by the subclasses of Converter. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public abstract class NodeConverter { + /** + * Converts the object provided into its node form. + * Styling of the returned node is defined by the specific converter. + * + * @return a node representation of the object passed in. + */ + public abstract Node toNode(T object); + + /** + * Converts the node provided into an object defined by the specific converter. + * Format of the node and type of the resulting object is defined by the specific converter. + * + * @return an object representation of the node passed in. + */ + public abstract T fromNode(Node node); + + /** + * Converts the object provided into a String defined by the specific converter. + * Format of the String is defined by the specific converter. + * + * @return a String representation of the node passed in. + */ + public abstract String toString(T object); + +} diff --git a/HMCL/src/main/java/com/jfoenix/effects/JFXDepthManager.java b/HMCL/src/main/java/com/jfoenix/effects/JFXDepthManager.java new file mode 100644 index 0000000000..3bc38fb5d6 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/effects/JFXDepthManager.java @@ -0,0 +1,115 @@ +/* + * 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.effects; + +import javafx.scene.Node; +import javafx.scene.effect.BlurType; +import javafx.scene.effect.DropShadow; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; + +/** + * it will create a shadow effect for a given node and a specified depth level. + * depth levels are {0,1,2,3,4,5} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXDepthManager { + + private static final DropShadow[] depth = new DropShadow[] { + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0), 0, 0, 0, 0), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 10, 0.12, -1, 2), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 15, 0.16, 0, 4), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 20, 0.19, 0, 6), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 25, 0.25, 0, 8), + new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 30, 0.30, 0, 10)}; + + /** + * this method is used to add shadow effect to the node, + * however the shadow is not real ( gets affected with node transformations) + *

+ * use {@link #createMaterialNode(Node, int)} instead to generate a real shadow + */ + public static void setDepth(Node control, int level) { + level = Math.max(level, 0); + level = Math.min(level, 5); + control.setEffect(new DropShadow(BlurType.GAUSSIAN, + depth[level].getColor(), + depth[level].getRadius(), + depth[level].getSpread(), + depth[level].getOffsetX(), + depth[level].getOffsetY())); + } + + public static int getLevels() { + return depth.length; + } + + public static DropShadow getShadowAt(int level) { + return depth[level]; + } + + /** + * this method will generate a new container node that prevent + * control transformation to be applied to the shadow effect + * (which makes it looks as a real shadow) + */ + public static Node createMaterialNode(Node control, int level) { + Node container = new Pane(control){ + @Override + protected double computeMaxWidth(double height) { + return computePrefWidth(height); + } + + @Override + protected double computeMaxHeight(double width) { + return computePrefHeight(width); + } + + @Override + protected double computePrefWidth(double height) { + return control.prefWidth(height); + } + + @Override + protected double computePrefHeight(double width) { + return control.prefHeight(width); + } + }; + container.getStyleClass().add("depth-container"); + container.setPickOnBounds(false); + level = Math.max(level, 0); + level = Math.min(level, 5); + container.setEffect(new DropShadow(BlurType.GAUSSIAN, + depth[level].getColor(), + depth[level].getRadius(), + depth[level].getSpread(), + depth[level].getOffsetX(), + depth[level].getOffsetY())); + return container; + } + + public static void pop(Node control) { + control.setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5, 0.05, 0, 1)); + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java new file mode 100644 index 0000000000..5491aacb2b --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java @@ -0,0 +1,228 @@ +/* + * 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.skins.ButtonSkinAdapter; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXButton.ButtonType; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.CachedTransition; +import com.jfoenix.utils.JFXNodeUtils; +import javafx.animation.*; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.effect.DropShadow; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import javafx.util.Duration; + +/** + *

Material Design Button Skin

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXButtonSkin extends ButtonSkinAdapter { + + private Transition clickedAnimation; + private final JFXRippler buttonRippler; + private Runnable releaseManualRippler = null; + private boolean invalid = true; + private boolean mousePressed = false; + + public JFXButtonSkin(JFXButton button) { + super(button); + + buttonRippler = new JFXRippler(getSkinnable()) { + @Override + protected Node getMask() { + StackPane mask = new StackPane(); + mask.shapeProperty().bind(getSkinnable().shapeProperty()); + JFXNodeUtils.updateBackground(getSkinnable().getBackground(), mask); + mask.resize(getWidth() - snappedRightInset() - snappedLeftInset(), + getHeight() - snappedBottomInset() - snappedTopInset()); + return mask; + } + + @Override + protected void positionControl(Node control) { + // do nothing as the controls is not inside the ripple + } + }; + + // add listeners to the button and bind properties + button.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> playClickAnimation(1)); +// button.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> playClickAnimation(-1)); + button.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> mousePressed = true); + button.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> mousePressed = false); + button.addEventFilter(MouseEvent.MOUSE_DRAGGED, e -> mousePressed = false); + + button.ripplerFillProperty().addListener((o, oldVal, newVal) -> buttonRippler.setRipplerFill(newVal)); + + button.armedProperty().addListener((o, oldVal, newVal) -> { + if (newVal) { + if (!mousePressed) { + releaseManualRippler = buttonRippler.createManualRipple(); + playClickAnimation(1); + } + } else { + if (releaseManualRippler != null) { + releaseManualRippler.run(); + releaseManualRippler = null; + } + playClickAnimation(-1); + } + }); + + // show focused state + button.focusedProperty().addListener((o, oldVal, newVal) -> { + if (!button.disableVisualFocusProperty().get()) { + if (newVal) { + if (!getSkinnable().isPressed()) { + buttonRippler.setOverlayVisible(true); + } + } else { + buttonRippler.setOverlayVisible(false); + } + } + }); + + button.buttonTypeProperty().addListener((o, oldVal, newVal) -> updateButtonType(newVal)); + + updateButtonType(button.getButtonType()); + + updateChildren(); + } + + @Override + protected void updateChildren() { + super.updateChildren(); + if (buttonRippler != null) { + getChildren().add(0, buttonRippler); + } + for (int i = 1; i < getChildren().size(); i++) { + final Node child = getChildren().get(i); + if (child instanceof Text) { + child.setMouseTransparent(true); + } + } + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + if (invalid) { + if (((JFXButton) getSkinnable()).getRipplerFill() == null) { + // change rippler fill according to the last LabeledText/Label child + for (int i = getChildren().size() - 1; i >= 1; i--) { + if (getChildren().get(i) instanceof Text) { + buttonRippler.setRipplerFill(((Text) getChildren().get(i)).getFill()); + ((Text) getChildren().get(i)).fillProperty() + .addListener((o, oldVal, newVal) -> buttonRippler.setRipplerFill( + newVal)); + break; + } else if (getChildren().get(i) instanceof Label) { + buttonRippler.setRipplerFill(((Label) getChildren().get(i)).getTextFill()); + ((Label) getChildren().get(i)).textFillProperty() + .addListener((o, oldVal, newVal) -> buttonRippler.setRipplerFill( + newVal)); + break; + } + } + } else { + buttonRippler.setRipplerFill(((JFXButton) getSkinnable()).getRipplerFill()); + } + invalid = false; + } + buttonRippler.resizeRelocate( + getSkinnable().getLayoutBounds().getMinX(), + getSkinnable().getLayoutBounds().getMinY(), + getSkinnable().getWidth(), getSkinnable().getHeight()); + layoutLabelInArea(x, y, w, h); + } + + + private void updateButtonType(ButtonType type) { + switch (type) { + case RAISED: + JFXDepthManager.setDepth(getSkinnable(), 2); + clickedAnimation = new ButtonClickTransition(getSkinnable(), (DropShadow) getSkinnable().getEffect()); + /* + * disable action when clicking on the button shadow + */ + getSkinnable().setPickOnBounds(false); + break; + default: + getSkinnable().setEffect(null); + getSkinnable().setPickOnBounds(true); + break; + } + } + + private void playClickAnimation(double rate) { + if (clickedAnimation != null) { + if (!clickedAnimation.getCurrentTime().equals(clickedAnimation.getCycleDuration()) || rate != 1) { + clickedAnimation.setRate(rate); + clickedAnimation.play(); + } + } + } + + private static class ButtonClickTransition extends CachedTransition { + ButtonClickTransition(Node node, DropShadow shadowEffect) { + super(node, new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(shadowEffect.radiusProperty(), + JFXDepthManager.getShadowAt(2).radiusProperty().get(), + Interpolator.EASE_BOTH), + new KeyValue(shadowEffect.spreadProperty(), + JFXDepthManager.getShadowAt(2).spreadProperty().get(), + Interpolator.EASE_BOTH), + new KeyValue(shadowEffect.offsetXProperty(), + JFXDepthManager.getShadowAt(2).offsetXProperty().get(), + Interpolator.EASE_BOTH), + new KeyValue(shadowEffect.offsetYProperty(), + JFXDepthManager.getShadowAt(2).offsetYProperty().get(), + Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(shadowEffect.radiusProperty(), + JFXDepthManager.getShadowAt(5).radiusProperty().get(), + Interpolator.EASE_BOTH), + new KeyValue(shadowEffect.spreadProperty(), + JFXDepthManager.getShadowAt(5).spreadProperty().get(), + Interpolator.EASE_BOTH), + new KeyValue(shadowEffect.offsetXProperty(), + JFXDepthManager.getShadowAt(5).offsetXProperty().get(), + Interpolator.EASE_BOTH), + new KeyValue(shadowEffect.offsetYProperty(), + JFXDepthManager.getShadowAt(5).offsetYProperty().get(), + Interpolator.EASE_BOTH) + ) + ) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.2)); + setDelay(Duration.seconds(0)); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java new file mode 100644 index 0000000000..751ad7115b --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java @@ -0,0 +1,361 @@ +/* + * 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.skins.CheckBoxSkinAdapter; +import com.jfoenix.controls.JFXCheckBox; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXRippler.RipplerMask; +import com.jfoenix.transitions.CachedTransition; +import com.jfoenix.transitions.JFXFillTransition; +import com.jfoenix.utils.JFXNodeUtils; +import javafx.animation.*; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.NodeOrientation; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.CheckBox; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.util.Duration; + +/** + *

Material Design CheckBox Skin v1.1

+ * the old skin is still supported using JFXCheckBoxOldSkin + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-09-06 + */ +public class JFXCheckBoxSkin extends CheckBoxSkinAdapter { + + private final StackPane box = new StackPane(); + private final StackPane mark = new StackPane(); + private final StackPane indeterminateMark = new StackPane(); + private final JFXRippler rippler; + + private final Transition transition; + private final Transition indeterminateTransition; + + private boolean invalid = true; + private JFXFillTransition select; + private final StackPane boxContainer; + + public JFXCheckBoxSkin(JFXCheckBox control) { + super(control); + + indeterminateMark.getStyleClass().setAll("indeterminate-mark"); + indeterminateMark.setOpacity(0); + indeterminateMark.setScaleX(0); + indeterminateMark.setScaleY(0); + + mark.getStyleClass().setAll("mark"); + mark.setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); + mark.setOpacity(0); + mark.setScaleX(0); + mark.setScaleY(0); + + box.getStyleClass().setAll("box"); + box.setBorder(new Border(new BorderStroke(control.getUnCheckedColor(), + BorderStrokeStyle.SOLID, + new CornerRadii(2), + new BorderWidths(2)))); + box.getChildren().setAll(indeterminateMark, mark); + + boxContainer = new StackPane(); + boxContainer.getChildren().add(box); + boxContainer.getStyleClass().add("box-container"); + rippler = new JFXRippler(boxContainer, RipplerMask.CIRCLE, JFXRippler.RipplerPos.BACK); + + updateRippleColor(); + + // add listeners + control.selectedProperty().addListener(observable -> { + updateRippleColor(); + playSelectAnimation(control.isSelected(), true); + }); + control.indeterminateProperty().addListener(observable -> { + updateRippleColor(); + playIndeterminateAnimation(control.isIndeterminate(), true); + }); + + // show focused state + control.focusedProperty().addListener((o, oldVal, newVal) -> { + if (!control.isDisableVisualFocus()) { + if (newVal) { + if (!getSkinnable().isPressed()) { + rippler.setOverlayVisible(true); + } + } else { + rippler.setOverlayVisible(false); + } + } + }); + control.pressedProperty().addListener((o, oldVal, newVal) -> rippler.setOverlayVisible(false)); + updateChildren(); + + // create animation + transition = new CheckBoxTransition(mark); + indeterminateTransition = new CheckBoxTransition(indeterminateMark); + createFillTransition(); + + __registerChangeListener(control.checkedColorProperty(), "CHECKED_COLOR"); + __registerChangeListener(control.unCheckedColorProperty(), "UNCHECKED_COLOR"); + } + + @Override + protected void __handleControlPropertyChanged(String key) { + if ("CHECKED_COLOR".equals(key)) { + select.stop(); + createFillTransition(); + updateColors(); + } else if ("UNCHECKED_COLOR".equals(key)) { + updateColors(); + } + } + + private void updateRippleColor() { + rippler.setRipplerFill(getSkinnable().isSelected() ? + ((JFXCheckBox) getSkinnable()).getCheckedColor() : ((JFXCheckBox) getSkinnable()).getUnCheckedColor()); + } + + private void updateColors() { + final Paint color = getSkinnable().isSelected() ? ((JFXCheckBox) getSkinnable()).getCheckedColor() : ((JFXCheckBox) getSkinnable()).getUnCheckedColor(); + JFXNodeUtils.updateBackground(indeterminateMark.getBackground(), indeterminateMark, ((JFXCheckBox) getSkinnable()).getCheckedColor()); + JFXNodeUtils.updateBackground(box.getBackground(), box, getSkinnable().isSelected() ? ((JFXCheckBox) getSkinnable()).getCheckedColor() : Color.TRANSPARENT); + rippler.setRipplerFill(color); + final BorderStroke borderStroke = box.getBorder().getStrokes().get(0); + box.setBorder(new Border(new BorderStroke(color, + borderStroke.getTopStyle(), + borderStroke.getRadii(), + borderStroke.getWidths()))); + } + + protected void updateChildren() { + super.updateChildren(); + getChildren().removeIf(node -> node.getStyleClass().contains("box")); + if (rippler != null) { + getChildren().add(rippler); + } + } + + @Override + protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + + snapSize(box.minWidth(-1)) + getLabelOffset(); + } + + @Override + protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + + snapSize(box.prefWidth(-1)) + getLabelOffset(); + } + + @Override + protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return Math.max(super.computeMinHeight(width - box.minWidth(-1), topInset, rightInset, bottomInset, leftInset), + topInset + box.minHeight(-1) + bottomInset); + } + + @Override + protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return Math.max(super.computePrefHeight(width - box.prefWidth(-1), topInset, rightInset, bottomInset, leftInset), + topInset + box.prefHeight(-1) + bottomInset); + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + final double labelOffset = getLabelOffset(); + final CheckBox checkBox = getSkinnable(); + final double boxWidth = snapSize(box.prefWidth(-1)); + final double boxHeight = snapSize(box.prefHeight(-1)); + final double computeWidth = Math.max(checkBox.prefWidth(-1), checkBox.minWidth(-1)); + final double labelWidth = Math.min(computeWidth - boxWidth, w - snapSize(boxWidth)) + labelOffset; + final double labelHeight = Math.min(checkBox.prefHeight(labelWidth), h); + final double maxHeight = Math.max(boxHeight, labelHeight); + final double xOffset = computeXOffset(w, labelWidth + boxWidth, checkBox.getAlignment().getHpos()) + x; + final double yOffset = computeYOffset(h, maxHeight, checkBox.getAlignment().getVpos()) + x; + + if (invalid) { + if (checkBox.isIndeterminate()) { + playIndeterminateAnimation(true, false); + } else if (checkBox.isSelected()) { + playSelectAnimation(true, false); + } + invalid = false; + } + + layoutLabelInArea(xOffset + boxWidth + labelOffset, yOffset, labelWidth, maxHeight, checkBox.getAlignment()); + rippler.resize(boxWidth, boxHeight); + positionInArea(rippler, + xOffset, yOffset, + boxWidth, maxHeight, 0, + checkBox.getAlignment().getHpos(), + checkBox.getAlignment().getVpos()); + + } + + private double getLabelOffset() { + return 0.2 * boxContainer.snappedRightInset(); + } + + static double computeXOffset(double width, double contentWidth, HPos hpos) { + switch (hpos) { + case LEFT: + return 0; + case CENTER: + return (width - contentWidth) / 2; + case RIGHT: + return width - contentWidth; + } + return 0; + } + + static double computeYOffset(double height, double contentHeight, VPos vpos) { + switch (vpos) { + case TOP: + return 0; + case CENTER: + return (height - contentHeight) / 2; + case BOTTOM: + return height - contentHeight; + default: + return 0; + } + } + + private void playSelectAnimation(Boolean selection, boolean playAnimation) { + if (selection == null) { + selection = false; + } + transition.setRate(selection ? 1 : -1); + select.setRate(selection ? 1 : -1); + if (playAnimation) { + transition.play(); + select.play(); + } else { + CornerRadii radii = box.getBackground() == null ? + null : box.getBackground().getFills().get(0).getRadii(); + Insets insets = box.getBackground() == null ? + null : box.getBackground().getFills().get(0).getInsets(); + if (selection) { + mark.setScaleY(1); + mark.setScaleX(1); + mark.setOpacity(1); + box.setBackground(new Background(new BackgroundFill(((JFXCheckBox) getSkinnable()).getCheckedColor(), radii, insets))); + select.playFrom(select.getCycleDuration()); + transition.playFrom(transition.getCycleDuration()); + } else { + mark.setScaleY(0); + mark.setScaleX(0); + mark.setOpacity(0); + box.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, radii, insets))); + select.playFrom(Duration.ZERO); + transition.playFrom(Duration.ZERO); + } + } + box.setBorder(new Border(new BorderStroke(selection ? ((JFXCheckBox) getSkinnable()).getCheckedColor() : ((JFXCheckBox) getSkinnable()).getUnCheckedColor(), + BorderStrokeStyle.SOLID, + new CornerRadii(2), + new BorderWidths(2)))); + } + + private void playIndeterminateAnimation(Boolean indeterminate, boolean playAnimation) { + if (indeterminate == null) { + indeterminate = false; + } + indeterminateTransition.setRate(indeterminate ? 1 : -1); + if (playAnimation) { + indeterminateTransition.play(); + } else { + if (indeterminate) { + CornerRadii radii = indeterminateMark.getBackground() == null ? + null : indeterminateMark.getBackground().getFills().get(0).getRadii(); + Insets insets = indeterminateMark.getBackground() == null ? + null : indeterminateMark.getBackground().getFills().get(0).getInsets(); + indeterminateMark.setOpacity(1); + indeterminateMark.setScaleY(1); + indeterminateMark.setScaleX(1); + indeterminateMark.setBackground(new Background(new BackgroundFill(((JFXCheckBox) getSkinnable()).getCheckedColor(), radii, insets))); + indeterminateTransition.playFrom(indeterminateTransition.getCycleDuration()); + } else { + indeterminateMark.setOpacity(0); + indeterminateMark.setScaleY(0); + indeterminateMark.setScaleX(0); + indeterminateTransition.playFrom(Duration.ZERO); + } + } + + if (getSkinnable().isSelected()) { + playSelectAnimation(!indeterminate, playAnimation); + } + } + + private void createFillTransition() { + select = new JFXFillTransition(Duration.millis(120), + box, + Color.TRANSPARENT, + (Color) ((JFXCheckBox) getSkinnable()).getCheckedColor()); + select.setInterpolator(Interpolator.EASE_OUT); + } + + private final static class CheckBoxTransition extends CachedTransition { + private final Node mark; + + CheckBoxTransition(Node mark) { + super(null, new Timeline( + new KeyFrame( + Duration.ZERO, + new KeyValue(mark.opacityProperty(), 0, Interpolator.EASE_OUT), + new KeyValue(mark.scaleXProperty(), 0.5, Interpolator.EASE_OUT), + new KeyValue(mark.scaleYProperty(), 0.5, Interpolator.EASE_OUT) + ), + new KeyFrame(Duration.millis(400), + new KeyValue(mark.opacityProperty(), 1, Interpolator.EASE_OUT), + new KeyValue(mark.scaleXProperty(), 0.5, Interpolator.EASE_OUT), + new KeyValue(mark.scaleYProperty(), 0.5, Interpolator.EASE_OUT) + ), + new KeyFrame( + Duration.millis(1000), + new KeyValue(mark.scaleXProperty(), 1, Interpolator.EASE_OUT), + new KeyValue(mark.scaleYProperty(), 1, Interpolator.EASE_OUT) + ) + ) + ); + // reduce the number to increase the shifting , increase number to reduce shifting + setCycleDuration(Duration.seconds(0.12)); + setDelay(Duration.seconds(0.05)); + this.mark = mark; + } + + @Override + protected void starting() { + super.starting(); + } + + @Override + protected void stopping() { + super.stopping(); + mark.setOpacity(getRate() == 1 ? 1 : 0); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXComboBoxListViewSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXComboBoxListViewSkin.java new file mode 100644 index 0000000000..70b9533250 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXComboBoxListViewSkin.java @@ -0,0 +1,227 @@ +/* + * 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.skins.ComboBoxListViewSkinAdapter; +import com.jfoenix.controls.JFXComboBox; +import javafx.beans.property.ObjectProperty; +import javafx.css.*; +import javafx.scene.Node; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + *

Material Design ComboBox Skin

+ * + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-01-25 + */ + +public class JFXComboBoxListViewSkin extends ComboBoxListViewSkinAdapter { + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + private boolean invalid = true; + + private Text promptText; + private final ValidationPane> errorContainer; + private final PromptLinesWrapper> linesWrapper; + + protected final ObjectProperty promptTextFill = new StyleableObjectProperty(Color.GRAY) { + @Override + public Object getBean() { + return JFXComboBoxListViewSkin.this; + } + + @Override + public String getName() { + return "promptTextFill"; + } + + @Override + public CssMetaData, Paint> getCssMetaData() { + return StyleableProperties.PROMPT_TEXT_FILL; + } + }; + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + public JFXComboBoxListViewSkin(final JFXComboBox comboBox) { + super(comboBox); + + linesWrapper = new PromptLinesWrapper<>( + comboBox, + promptTextFill, + comboBox.valueProperty(), + comboBox.promptTextProperty(), + () -> promptText); + + linesWrapper.init(this::createPromptNode); + Pane arrowButton = null; + for (Node node : getChildren()) { + if (node.getId().equals("arrow-button")) { + arrowButton = (Pane) node; + break; + } + } + if (arrowButton != null) { + linesWrapper.clip.widthProperty().bind(linesWrapper.promptContainer.widthProperty().subtract(arrowButton.widthProperty())); + } + + errorContainer = new ValidationPane<>(comboBox); + + getChildren().addAll(linesWrapper.line, linesWrapper.focusedLine, linesWrapper.promptContainer, errorContainer); + + if (comboBox.isEditable()) { + comboBox.getEditor().setStyle("-fx-background-color:TRANSPARENT;-fx-padding: 0.333333em 0em;"); + comboBox.getEditor().promptTextProperty().unbind(); + comboBox.getEditor().setPromptText(null); + comboBox.getEditor().textProperty().addListener((o, oldVal, newVal) -> linesWrapper.usePromptText.invalidate()); + } + + __registerChangeListener(comboBox.disableProperty(), "DISABLE_NODE"); + __registerChangeListener(comboBox.focusColorProperty(), "FOCUS_COLOR"); + __registerChangeListener(comboBox.unFocusColorProperty(), "UNFOCUS_COLOR"); + __registerChangeListener(comboBox.disableAnimationProperty(), "DISABLE_ANIMATION"); + } + + @Override + protected void __handleControlPropertyChanged(String key) { + if ("DISABLE_NODE".equals(key)) { + linesWrapper.updateDisabled(); + } else if ("FOCUS_COLOR".equals(key)) { + linesWrapper.updateFocusColor(); + } else if ("UNFOCUS_COLOR".equals(key)) { + linesWrapper.updateUnfocusColor(); + } else if ("DISABLE_ANIMATION".equals(key)) { + // remove error clip if animation is disabled + errorContainer.updateClip(); + } + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + @Override + protected void layoutChildren(final double x, final double y, + final double w, final double h) { + super.layoutChildren(x, y, w, h); + final double height = getSkinnable().getHeight(); + linesWrapper.layoutLines(x, y, w, h, height, + promptText == null ? 0 : __snapPositionX(promptText.getBaselineOffset() + promptText.getLayoutBounds().getHeight() * .36)); + errorContainer.layoutPane(x, height + linesWrapper.focusedLine.getHeight(), w, h); + + linesWrapper.updateLabelFloatLayout(); + + if (invalid) { + invalid = false; + // update validation container + errorContainer.invalid(w); + // focus + linesWrapper.invalid(); + } + } + + private void createPromptNode() { + if (promptText != null || !linesWrapper.usePromptText.get()) { + return; + } + promptText = new Text(); + // create my custom pane for the prompt node + promptText.textProperty().bind(getSkinnable().promptTextProperty()); + promptText.fillProperty().bind(linesWrapper.animatedPromptTextFill); + promptText.getStyleClass().addAll("text"); + promptText.getTransforms().add(linesWrapper.promptTextScale); + promptText.visibleProperty().bind(linesWrapper.usePromptText); + promptText.setTranslateX(1); + linesWrapper.promptContainer.getChildren().add(promptText); + + if (getSkinnable().isFocused() && ((JFXComboBox) getSkinnable()).isLabelFloat()) { + promptText.setTranslateY(-__snapPositionY(promptText.getBaselineOffset() + promptText.getLayoutBounds().getHeight() * .36)); + linesWrapper.promptTextScale.setX(0.85); + linesWrapper.promptTextScale.setY(0.85); + } + } + + private static class StyleableProperties { + private static final CssMetaData, Paint> PROMPT_TEXT_FILL = + new CssMetaData, Paint>("-fx-prompt-text-fill", + StyleConverter.getPaintConverter(), Color.GRAY) { + + @Override + public boolean isSettable(JFXComboBox n) { + final JFXComboBoxListViewSkin skin = (JFXComboBoxListViewSkin) n.getSkin(); + return !skin.promptTextFill.isBound(); + } + + @Override + @SuppressWarnings("unchecked") + public StyleableProperty getStyleableProperty(JFXComboBox n) { + final JFXComboBoxListViewSkin skin = (JFXComboBoxListViewSkin) n.getSkin(); + return (StyleableProperty) skin.promptTextFill; + } + }; + + private static final List> STYLEABLES; + + static { + List> styleables = + new ArrayList<>(SkinBase.getClassCssMetaData()); + styleables.add(PROMPT_TEXT_FILL); + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + + /** + * @return The CssMetaData associated with this class, which may include the + * CssMetaData of its super classes. + */ + public static List> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } + + /** + * {@inheritDoc} + */ + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXListViewSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXListViewSkin.java new file mode 100644 index 0000000000..e695aa310f --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXListViewSkin.java @@ -0,0 +1,96 @@ +/* + * 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.VirtualFlowAdapter; +import com.jfoenix.adapters.skins.ListViewSkinAdapter; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.effects.JFXDepthManager; +import javafx.scene.control.ListCell; +import javafx.scene.layout.Region; + +/** + *

Material Design ListView Skin

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXListViewSkin extends ListViewSkinAdapter { + + private final VirtualFlowAdapter> flow; + + public JFXListViewSkin(final JFXListView listView) { + super(listView); + flow = __getFlow(); + JFXDepthManager.setDepth(flow.getFlow(), listView.depthProperty().get()); + listView.depthProperty().addListener((o, oldVal, newVal) -> JFXDepthManager.setDepth(flow.getFlow(), newVal)); + } + + @Override + protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return 200; + } + + @Override + protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + final int itemsCount = getSkinnable().getItems().size(); + if (getSkinnable().maxHeightProperty().isBound() || itemsCount <= 0) { + return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset); + } + + final double fixedCellSize = getSkinnable().getFixedCellSize(); + double computedHeight = fixedCellSize != Region.USE_COMPUTED_SIZE ? + fixedCellSize * itemsCount + snapVerticalInsets() : estimateHeight(); + double height = super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset); + if (height > computedHeight) { + height = computedHeight; + } + + if (getSkinnable().getMaxHeight() > 0 && computedHeight > getSkinnable().getMaxHeight()) { + return getSkinnable().getMaxHeight(); + } + + return height; + } + + private double estimateHeight() { + // compute the border/padding for the list + double borderWidth = snapVerticalInsets(); + // compute the gap between list cells + + JFXListView listview = (JFXListView) getSkinnable(); + double gap = listview.isExpanded() ? ((JFXListView) getSkinnable()).getVerticalGap() * (getSkinnable().getItems() + .size()) : 0; + // compute the height of each list cell + double cellsHeight = 0; + + for (int i = 0; i < flow.getCellCount(); i++) { + ListCell cell = flow.getCell(i); + cellsHeight += cell.getHeight(); + } + return cellsHeight + gap + borderWidth; + } + + private double snapVerticalInsets() { + return getSkinnable().snappedBottomInset() + getSkinnable().snappedTopInset(); + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java new file mode 100644 index 0000000000..2d32ede037 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java @@ -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.skins; + +import com.jfoenix.controls.JFXPopup; +import com.jfoenix.controls.JFXPopup.PopupHPosition; +import com.jfoenix.controls.JFXPopup.PopupVPosition; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.CacheMemento; +import com.jfoenix.transitions.CachedTransition; +import javafx.animation.*; +import javafx.animation.Animation.Status; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Skin; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.transform.Scale; +import javafx.util.Duration; + +/** + *

Material Design Popup Skin

+ * TODO: REWORK + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-03-01 + */ +public class JFXPopupSkin implements Skin { + + protected JFXPopup control; + protected StackPane container = new StackPane(); + protected Region popupContent; + protected Node root; + + private Animation animation; + protected Scale scale; + + public JFXPopupSkin(JFXPopup control) { + this.control = control; + // set scale y to 0.01 instead of 0 to allow layout of the content, + // otherwise it will cause exception in traverse engine, when focusing the 1st node + scale = new Scale(1, 0.01, 0, 0); + popupContent = control.getPopupContent(); + container.getStyleClass().add("jfx-popup-container"); + container.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); + container.getChildren().add(popupContent); + container.getTransforms().add(scale); + container.setOpacity(0); + root = JFXDepthManager.createMaterialNode(container, 4); + animation = getAnimation(); +} + + + public void reset(PopupVPosition vAlign, PopupHPosition hAlign, double offsetX, double offsetY) { + // postion the popup according to its animation + scale.setPivotX(hAlign == PopupHPosition.RIGHT ? container.getWidth() : 0); + scale.setPivotY(vAlign == PopupVPosition.BOTTOM ? container.getHeight() : 0); + root.setTranslateX(hAlign == PopupHPosition.RIGHT ? -container.getWidth() + offsetX : offsetX); + root.setTranslateY(vAlign == PopupVPosition.BOTTOM ? -container.getHeight() + offsetY : offsetY); + } + + public final void animate() { + if (animation.getStatus() == Status.STOPPED) { + animation.play(); + } + } + + @Override + public JFXPopup getSkinnable() { + return control; + } + + @Override + public Node getNode() { + return root; + } + + @Override + public void dispose() { + animation.stop(); + animation = null; + container = null; + control = null; + popupContent = null; + root = null; + } + + protected Animation getAnimation() { + return new PopupTransition(); + } + + private final class PopupTransition extends CachedTransition { + PopupTransition() { + super(root, new Timeline( + new KeyFrame( + Duration.ZERO, + new KeyValue(popupContent.opacityProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(scale.xProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(scale.yProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(700), + new KeyValue(scale.xProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(popupContent.opacityProperty(), 0, Interpolator.EASE_BOTH) + ), + new KeyFrame(Duration.millis(1000), + new KeyValue(popupContent.opacityProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(scale.yProperty(), 1, Interpolator.EASE_BOTH) + ) + ) + , new CacheMemento(popupContent)); + setCycleDuration(Duration.seconds(.4)); + setDelay(Duration.seconds(0)); + } + + @Override + protected void starting() { + container.setOpacity(1); + super.starting(); + } + } + + public void init() { + animation.stop(); + container.setOpacity(0); + scale.setX(1); + scale.setY(0.1); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java new file mode 100644 index 0000000000..6146f31d99 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java @@ -0,0 +1,90 @@ +/* + * 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.skins.ProgressBarSkinAdapter; +import com.jfoenix.controls.JFXProgressBar; +import javafx.animation.*; +import javafx.geometry.Insets; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +// Old +public class JFXProgressBarSkin extends ProgressBarSkinAdapter { + private static final Color indicatorColor = Color.valueOf("#0F9D58"); + private static final Color trackColor = Color.valueOf("#E0E0E0"); + private StackPane bar; + private Region clip; + + public JFXProgressBarSkin(JFXProgressBar progressBar) { + super(progressBar); + this.init(); + this.__registerChangeListener(this.getSkinnable().indeterminateProperty(), "INDETERMINATE"); + } + + @Override + protected void __handleControlPropertyChanged(String p) { + if ("INDETERMINATE".equals(p)) { + this.init(); + } + } + + protected void init() { + this.bar = (StackPane) this.getChildren().get(1); + this.bar.setBackground(new Background(new BackgroundFill(indicatorColor, CornerRadii.EMPTY, Insets.EMPTY))); + this.bar.setPadding(new Insets(1.5)); + StackPane track = (StackPane) this.getChildren().get(0); + track.setBackground(new Background(new BackgroundFill(trackColor, CornerRadii.EMPTY, Insets.EMPTY))); + this.clip = new Region(); + this.clip.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))); + this.getSkinnable().setClip(this.clip); + this.getSkinnable().requestLayout(); + } + + protected void layoutChildren(double x, double y, double W, double h) { + super.layoutChildren(x, y, W, h); + this.clip.resizeRelocate(x, y, W, h); + if (this.getSkinnable().isIndeterminate()) { + if (this.__getIndeterminateTransition() != null) { + this.__getIndeterminateTransition().stop(); + } + + ProgressIndicator control = this.getSkinnable(); + double w = control.getWidth() - (this.snappedLeftInset() + this.snappedRightInset()); + double bWidth = this.bar.getWidth(); + this.__setIndeterminateTransition(new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(this.bar.scaleXProperty(), 0, Interpolator.EASE_IN), + new KeyValue(this.bar.translateXProperty(), -bWidth, Interpolator.LINEAR)), + new KeyFrame(Duration.seconds(0.5), + new KeyValue(this.bar.scaleXProperty(), 3, Interpolator.LINEAR), + new KeyValue(this.bar.translateXProperty(), w / 2.0, Interpolator.LINEAR)), + new KeyFrame(Duration.seconds(1.0), + new KeyValue(this.bar.scaleXProperty(), 0, Interpolator.EASE_OUT), + new KeyValue(this.bar.translateXProperty(), w, Interpolator.LINEAR)))); + this.__getIndeterminateTransition().setCycleCount(-1); + this.__getIndeterminateTransition().play(); + } + + } +} + diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java new file mode 100644 index 0000000000..6e80a4cf07 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java @@ -0,0 +1,294 @@ +/* + * 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.skins.RadioButtonSkinAdapter; +import com.jfoenix.controls.JFXRadioButton; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXRippler.RipplerMask; +import com.jfoenix.transitions.JFXAnimationTimer; +import com.jfoenix.transitions.JFXKeyFrame; +import com.jfoenix.transitions.JFXKeyValue; +import javafx.animation.Interpolator; +import javafx.geometry.HPos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.RadioButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; + +/** + *

Material Design Radio Button Skin

+ * + * @author Shadi Shaheen + * @version 2.0 + * @since 2016-09-29 + */ +public class JFXRadioButtonSkin extends RadioButtonSkinAdapter { + private static final double padding = 12; + + private final JFXRippler rippler; + + private final Circle radio; + private final Circle dot; + private final StackPane container; + + private JFXAnimationTimer timer; + + public JFXRadioButtonSkin(JFXRadioButton control) { + super(control); + + final double radioRadius = 8; + radio = new Circle(radioRadius); + radio.getStyleClass().setAll("radio"); + radio.setStrokeWidth(2); + radio.setFill(Color.TRANSPARENT); + radio.setSmooth(true); + + dot = new Circle(radioRadius); + dot.getStyleClass().setAll("dot"); + dot.fillProperty().bind(control.selectedColorProperty()); + dot.setScaleX(0); + dot.setScaleY(0); + dot.setSmooth(true); + + container = new StackPane(radio, dot); + container.getStyleClass().add("radio-container"); + + rippler = new JFXRippler(container, RipplerMask.CIRCLE) { + @Override + protected double computeRippleRadius() { + double width = ripplerPane.getWidth(); + double width2 = width * width; + return Math.min(Math.sqrt(width2 + width2), RIPPLE_MAX_RADIUS) * 1.1 + 5; + } + + @Override + protected void setOverLayBounds(Rectangle overlay) { + overlay.setWidth(ripplerPane.getWidth()); + overlay.setHeight(ripplerPane.getHeight()); + } + + 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() + padding, event.getY() + padding)); + // create fade out transition for the ripple + control.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> releaseRipple()); + } + + @Override + protected Node getMask() { + double radius = ripplerPane.getWidth() / 2; + return new Circle(radius, radius, radius); + } + + @Override + protected void positionControl(Node control) { + + } + }; + + updateChildren(); + + // show focused state + control.focusedProperty().addListener((o, oldVal, newVal) -> { + if (!control.disableVisualFocusProperty().get()) { + if (newVal) { + if (!getSkinnable().isPressed()) { + rippler.setOverlayVisible(true); + } + } else { + rippler.setOverlayVisible(false); + } + } + }); + + control.pressedProperty().addListener((o, oldVal, newVal) -> rippler.setOverlayVisible(false)); + + timer = new JFXAnimationTimer( + new JFXKeyFrame(Duration.millis(200), + JFXKeyValue.builder() + .setTarget(dot.scaleXProperty()) + .setEndValueSupplier(() -> getSkinnable().isSelected() ? 0.55 : 0) + .setInterpolator(Interpolator.EASE_BOTH) + .build(), + JFXKeyValue.builder() + .setTarget(dot.scaleYProperty()) + .setEndValueSupplier(() -> getSkinnable().isSelected() ? 0.55 : 0) + .setInterpolator(Interpolator.EASE_BOTH) + .build(), + JFXKeyValue.builder() + .setTarget(radio.strokeProperty()) + .setEndValueSupplier(() -> getSkinnable().isSelected() ? ((JFXRadioButton) getSkinnable()).getSelectedColor() : ((JFXRadioButton) getSkinnable()).getUnSelectedColor()) + .setInterpolator(Interpolator.EASE_BOTH) + .build() + )); + + + __registerChangeListener(control.selectedColorProperty(), "SELECTED_COLOR"); + __registerChangeListener(control.unSelectedColorProperty(), "SELECTED_COLOR"); + __registerChangeListener(control.selectedProperty(), "SELECTED"); + + updateColors(); + timer.applyEndValues(); + } + + @Override + protected void __handleControlPropertyChanged(String p) { + if ("SELECTED_COLOR".equals(p)) { + // update current colors + updateColors(); + } else if ("UNSELECTED_COLOR".equals(p)) { + // update current colors + updateColors(); + } else if ("SELECTED".equals(p)) { + // update ripple color + boolean isSelected = getSkinnable().isSelected(); + Color unSelectedColor = ((JFXRadioButton) getSkinnable()).getUnSelectedColor(); + Color selectedColor = ((JFXRadioButton) getSkinnable()).getSelectedColor(); + rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor); + if (((JFXRadioButton) getSkinnable()).isDisableAnimation()) { + // apply end values + timer.applyEndValues(); + } else { + // play selection animation + timer.reverseAndContinue(); + } + } + } + + @Override + protected void updateChildren() { + super.updateChildren(); + if (radio != null) { + removeRadio(); + getChildren().addAll(container, rippler); + } + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + final RadioButton radioButton = getSkinnable(); + final double contWidth = __snapSizeX(container.prefWidth(-1)); + final double contHeight = __snapSizeY(container.prefHeight(-1)); + final double computeWidth = Math.max(radioButton.prefWidth(-1), radioButton.minWidth(-1)); + final double width = __snapSizeX(contWidth); + final double height = __snapSizeY(contHeight); + + final double labelWidth = Math.min(computeWidth - contWidth, w - width); + final double labelHeight = Math.min(radioButton.prefHeight(labelWidth), h); + final double maxHeight = Math.max(contHeight, labelHeight); + final double xOffset = computeXOffset(w, labelWidth + contWidth, radioButton.getAlignment().getHpos()) + x; + final double yOffset = computeYOffset(h, maxHeight, radioButton.getAlignment().getVpos()) + x; + + container.resize(width, height); + layoutLabelInArea(xOffset + contWidth + padding / 3, yOffset, labelWidth, maxHeight, radioButton.getAlignment()); + positionInArea(container, + xOffset, + yOffset, + contWidth, + maxHeight, + 0, + radioButton.getAlignment().getHpos(), + radioButton.getAlignment().getVpos()); + final double ripplerWidth = width + 2 * padding; + final double ripplerHeight = height + 2 * padding; + rippler.resizeRelocate((width / 2 + xOffset) - ripplerWidth / 2, + (height / 2 + xOffset) - ripplerHeight / 2, + ripplerWidth, ripplerHeight); + } + + private void removeRadio() { + // TODO: replace with removeIf + for (int i = 0; i < getChildren().size(); i++) { + if (getChildren().get(i).getStyleClass().contains("radio")) { + getChildren().remove(i); + break; + } + } + } + + private void updateColors() { + boolean isSelected = getSkinnable().isSelected(); + Color unSelectedColor = ((JFXRadioButton) getSkinnable()).getUnSelectedColor(); + Color selectedColor = ((JFXRadioButton) getSkinnable()).getSelectedColor(); + rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor); + radio.setStroke(isSelected ? selectedColor : unSelectedColor); + } + + @Override + protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computeMinWidth(height, + topInset, + rightInset, + bottomInset, + leftInset) + __snapSizeX(radio.minWidth(-1)) + padding / 3; + } + + @Override + protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, + topInset, + rightInset, + bottomInset, + leftInset) + __snapSizeX(radio.prefWidth(-1)) + padding / 3; + } + + private static double computeXOffset(double width, double contentWidth, HPos hpos) { + switch (hpos) { + case LEFT: + return 0; + case CENTER: + return (width - contentWidth) / 2; + case RIGHT: + return width - contentWidth; + } + return 0; + } + + private static double computeYOffset(double height, double contentHeight, VPos vpos) { + switch (vpos) { + case TOP: + return 0; + case CENTER: + return (height - contentHeight) / 2; + case BOTTOM: + return height - contentHeight; + default: + return 0; + } + } + + @Override + public void dispose() { + super.dispose(); + timer.dispose(); + timer = null; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXSliderSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXSliderSkin.java new file mode 100644 index 0000000000..e09f3b41ed --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXSliderSkin.java @@ -0,0 +1,257 @@ +/* + * 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.skins.SliderSkinAdapter; +import com.jfoenix.controls.JFXSlider; +import com.jfoenix.controls.JFXSlider.IndicatorPosition; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.css.PseudoClass; +import javafx.geometry.Orientation; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import javafx.util.Duration; + +/** + *

Material Design Slider Skin

+ *

+ * rework of JFXSliderSkin by extending Java SliderSkin + * this solves padding and resizing issues + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXSliderSkin extends SliderSkinAdapter { + + private static final PseudoClass MIN_VALUE = PseudoClass.getPseudoClass("min"); + private static final PseudoClass MAX_VALUE = PseudoClass.getPseudoClass("max"); + + private final Pane mouseHandlerPane = new Pane(); + + private final Text sliderValue; + private final StackPane coloredTrack; + private final StackPane thumb; + private final StackPane track; + private final StackPane animatedThumb; + + private Timeline timeline; + + private double indicatorRotation; + private double horizontalRotation; + private double shifting; + private boolean isValid = false; + + public JFXSliderSkin(JFXSlider slider) { + super(slider); + + track = (StackPane) getSkinnable().lookup(".track"); + thumb = (StackPane) getSkinnable().lookup(".thumb"); + + coloredTrack = new StackPane(); + coloredTrack.getStyleClass().add("colored-track"); + coloredTrack.setMouseTransparent(true); + + sliderValue = new Text(); + sliderValue.getStyleClass().setAll("slider-value"); + + animatedThumb = new StackPane(); + animatedThumb.getStyleClass().add("animated-thumb"); + animatedThumb.getChildren().add(sliderValue); + animatedThumb.setMouseTransparent(true); + animatedThumb.setScaleX(0); + animatedThumb.setScaleY(0); + + getChildren().add(getChildren().indexOf(thumb), coloredTrack); + getChildren().add(getChildren().indexOf(thumb), animatedThumb); + getChildren().add(0, mouseHandlerPane); + registerChangeListener(slider.valueFactoryProperty(), obs->refreshSliderValueBinding()); + + initListeners(); + } + + private void refreshSliderValueBinding() { + sliderValue.textProperty().unbind(); + if (((JFXSlider) getSkinnable()).getValueFactory() != null) { + sliderValue.textProperty() + .bind(((JFXSlider) getSkinnable()).getValueFactory().call((JFXSlider) getSkinnable())); + } else { + sliderValue.textProperty().bind(Bindings.createStringBinding(() -> { + if (getSkinnable().getLabelFormatter() != null) { + return getSkinnable().getLabelFormatter().toString(getSkinnable().getValue()); + } else { + return String.valueOf(Math.round(getSkinnable().getValue())); + } + }, getSkinnable().valueProperty())); + } + } + + @Override + protected void layoutChildren(double x, double y, double w, double h) { + super.layoutChildren(x, y, w, h); + + if (!isValid) { + initializeVariables(); + initAnimation(getSkinnable().getOrientation()); + isValid = true; + } + + double prefWidth = animatedThumb.prefWidth(-1); + animatedThumb.resize(prefWidth, animatedThumb.prefHeight(prefWidth)); + + boolean horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL; + double width, height, layoutX, layoutY; + if (horizontal) { + width = thumb.getLayoutX() - snappedLeftInset(); + height = track.getHeight(); + layoutX = track.getLayoutX(); + layoutY = track.getLayoutY(); + animatedThumb.setLayoutX(thumb.getLayoutX() + thumb.getWidth() / 2 - animatedThumb.getWidth() / 2); + } else { + height = track.getLayoutBounds().getMaxY() + track.getLayoutY() - thumb.getLayoutY() - snappedBottomInset(); + width = track.getWidth(); + layoutX = track.getLayoutX(); + layoutY = thumb.getLayoutY(); + animatedThumb.setLayoutY(thumb.getLayoutY() + thumb.getHeight() / 2 - animatedThumb.getHeight() / 2); + } + + coloredTrack.resizeRelocate(layoutX, layoutY, width, height); + mouseHandlerPane.resizeRelocate(x, y, w, h); + } + + private void initializeVariables() { + shifting = 30 + thumb.getWidth(); + if (getSkinnable().getOrientation() != Orientation.HORIZONTAL) { + horizontalRotation = -90; + } + if (((JFXSlider) getSkinnable()).getIndicatorPosition() != IndicatorPosition.LEFT) { + indicatorRotation = 180; + shifting = -shifting; + } + final double rotationAngle = 45; + sliderValue.setRotate(rotationAngle + indicatorRotation + 3 * horizontalRotation); + animatedThumb.setRotate(-rotationAngle + indicatorRotation + horizontalRotation); + } + + private void initListeners() { + // delegate slider mouse events to track node + mouseHandlerPane.setOnMousePressed(this::delegateToTrack); + mouseHandlerPane.setOnMouseReleased(this::delegateToTrack); + mouseHandlerPane.setOnMouseDragged(this::delegateToTrack); + + // animate value node + track.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> { + timeline.setRate(1); + timeline.play(); + }); + track.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> { + timeline.setRate(-1); + timeline.play(); + }); + thumb.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> { + timeline.setRate(1); + timeline.play(); + }); + thumb.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> { + timeline.setRate(-1); + timeline.play(); + }); + + refreshSliderValueBinding(); + updateValueStyleClass(); + + getSkinnable().valueProperty().addListener(observable -> updateValueStyleClass()); + getSkinnable().orientationProperty().addListener(observable -> initAnimation(getSkinnable().getOrientation())); + } + + private void delegateToTrack(MouseEvent event) { + if (!event.isConsumed()) { + event.consume(); + track.fireEvent(event); + } + } + + private void updateValueStyleClass() { + getSkinnable().pseudoClassStateChanged(MIN_VALUE, getSkinnable().getMin() == getSkinnable().getValue()); + getSkinnable().pseudoClassStateChanged(MAX_VALUE, getSkinnable().getMax() == getSkinnable().getValue()); + } + + private void initAnimation(Orientation orientation) { + double thumbPos, thumbNewPos; + DoubleProperty layoutProperty; + + if (orientation == Orientation.HORIZONTAL) { + if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) { + thumbPos = thumb.getLayoutY() - thumb.getHeight(); + thumbNewPos = thumbPos - shifting; + } else { + double height = animatedThumb.prefHeight(animatedThumb.prefWidth(-1)); + thumbPos = thumb.getLayoutY() - height / 2; + thumbNewPos = thumb.getLayoutY() - height - thumb.getHeight(); + } + layoutProperty = animatedThumb.translateYProperty(); + } else { + if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) { + thumbPos = thumb.getLayoutX() - thumb.getWidth(); + thumbNewPos = thumbPos - shifting; + } else { + double width = animatedThumb.prefWidth(-1); + thumbPos = thumb.getLayoutX() - width / 2; + thumbNewPos = thumb.getLayoutX() - width - thumb.getWidth(); + } + layoutProperty = animatedThumb.translateXProperty(); + } + + clearAnimation(); + + timeline = new Timeline( + new KeyFrame( + Duration.ZERO, + new KeyValue(animatedThumb.scaleXProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(animatedThumb.scaleYProperty(), 0, Interpolator.EASE_BOTH), + new KeyValue(layoutProperty, thumbPos, Interpolator.EASE_BOTH)), + new KeyFrame( + Duration.seconds(0.2), + new KeyValue(animatedThumb.scaleXProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(animatedThumb.scaleYProperty(), 1, Interpolator.EASE_BOTH), + new KeyValue(layoutProperty, thumbNewPos, Interpolator.EASE_BOTH))); + } + + @Override + public void dispose() { + super.dispose(); + clearAnimation(); + } + + private void clearAnimation() { + if (timeline != null) { + timeline.stop(); + timeline.getKeyFrames().clear(); + timeline = null; + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXTextAreaSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXTextAreaSkin.java new file mode 100644 index 0000000000..3798aa32ab --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXTextAreaSkin.java @@ -0,0 +1,142 @@ +/* + * 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.skins.TextAreaSkinAdapter; +import com.jfoenix.controls.JFXTextArea; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; + +import java.util.Collections; + +/** + *

Material Design TextArea Skin

+ * + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-01-25 + */ +public class JFXTextAreaSkin extends TextAreaSkinAdapter { + + private boolean invalid = true; + + private final ScrollPane scrollPane; + private Text promptText; + + private final ValidationPane errorContainer; + private final PromptLinesWrapper linesWrapper; + + public JFXTextAreaSkin(JFXTextArea textArea) { + super(textArea); + // init text area properties + scrollPane = (ScrollPane) getChildren().get(0); + textArea.setWrapText(true); + + linesWrapper = new PromptLinesWrapper<>( + textArea, + __promptTextFillProperty(), + textArea.textProperty(), + textArea.promptTextProperty(), + () -> promptText); + + linesWrapper.init(this::createPromptNode, scrollPane); + errorContainer = new ValidationPane<>(textArea); + getChildren().addAll(linesWrapper.line, linesWrapper.focusedLine, linesWrapper.promptContainer, errorContainer); + + __registerChangeListener(textArea.disableProperty(), "DISABLE_NODE"); + __registerChangeListener(textArea.focusColorProperty(), "FOCUS_COLOR"); + __registerChangeListener(textArea.unFocusColorProperty(), "UNFOCUS_COLOR"); + __registerChangeListener(textArea.disableAnimationProperty(), "DISABLE_ANIMATION"); + } + + @Override + protected void __handleControlPropertyChanged(String key) { + if ("DISABLE_NODE".equals(key)) { + linesWrapper.updateDisabled(); + } else if ("FOCUS_COLOR".equals(key)) { + linesWrapper.updateFocusColor(); + } else if ("UNFOCUS_COLOR".equals(key)) { + linesWrapper.updateUnfocusColor(); + } else if ("DISABLE_ANIMATION".equals(key)) { + // remove error clip if animation is disabled + errorContainer.updateClip(); + } + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + super.layoutChildren(x, y, w, h); + + final double height = getSkinnable().getHeight(); + linesWrapper.layoutLines(x, y, w, h, height, promptText == null ? 0 : promptText.getLayoutBounds().getHeight() + 3); + errorContainer.layoutPane(x, height + linesWrapper.focusedLine.getHeight(), w, h); + linesWrapper.updateLabelFloatLayout(); + + if (invalid) { + invalid = false; + // set the default background of text area viewport to white + Region viewPort = (Region) scrollPane.getChildrenUnmodifiable().get(0); + viewPort.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, + CornerRadii.EMPTY, + Insets.EMPTY))); + // reapply css of scroll pane in case set by the user + viewPort.applyCss(); + errorContainer.invalid(w); + // focus + linesWrapper.invalid(); + } + } + + private void createPromptNode() { + if (promptText != null || !linesWrapper.usePromptText.get()) { + return; + } + promptText = new Text(); + promptText.setManaged(false); + promptText.getStyleClass().add("text"); + promptText.visibleProperty().bind(linesWrapper.usePromptText); + promptText.fontProperty().bind(getSkinnable().fontProperty()); + promptText.textProperty().bind(getSkinnable().promptTextProperty()); + promptText.fillProperty().bind(linesWrapper.animatedPromptTextFill); + promptText.setLayoutX(1); + promptText.setTranslateX(1); + promptText.getTransforms().add(linesWrapper.promptTextScale); + linesWrapper.promptContainer.getChildren().add(promptText); + if (getSkinnable().isFocused() && ((JFXTextArea) getSkinnable()).isLabelFloat()) { + promptText.setTranslateY(-Math.floor(scrollPane.getHeight())); + linesWrapper.promptTextScale.setX(0.85); + linesWrapper.promptTextScale.setY(0.85); + } + + Text oldValue = __getPromptNode(); + if (oldValue != null) { + removeHighlight(Collections.singletonList(((Node) oldValue))); + } + + __setPromptNode(promptText); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXTextFieldSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXTextFieldSkin.java new file mode 100644 index 0000000000..a872f0f9c8 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXTextFieldSkin.java @@ -0,0 +1,170 @@ +/* + * 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.skins.TextFieldSkinAdapter; +import com.jfoenix.controls.base.IFXLabelFloatControl; +import javafx.beans.property.DoubleProperty; +import javafx.beans.value.ObservableDoubleValue; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import javafx.scene.layout.Pane; +import javafx.scene.text.Text; + +/** + *

Material Design Text input control Skin, used for both JFXTextField/JFXPasswordField

+ * + * @author Shadi Shaheen + * @version 2.0 + * @since 2017-01-25 + */ +public class JFXTextFieldSkin extends TextFieldSkinAdapter { + + private boolean invalid = true; + + private Text promptText; + private final Pane textPane; + private final Node textNode; + private final ObservableDoubleValue textRight; + private final DoubleProperty textTranslateX; + + private final ValidationPane errorContainer; + private final PromptLinesWrapper linesWrapper; + + public JFXTextFieldSkin(T textField) { + super(textField); + textPane = (Pane) this.getChildren().get(0); + + // get parent fields + textNode = __getTextNode(); + textTranslateX = __getTextTranslateX(); + textRight = __getTextRightHandle(); + + linesWrapper = new PromptLinesWrapper<>( + textField, + __promptTextFillProperty(), + textField.textProperty(), + textField.promptTextProperty(), + () -> promptText + ); + + linesWrapper.init(this::createPromptNode, textPane); + + __setUsePromptText(linesWrapper.usePromptText); + + errorContainer = new ValidationPane<>(textField); + + getChildren().addAll(linesWrapper.line, linesWrapper.focusedLine, linesWrapper.promptContainer, errorContainer); + + __registerChangeListener(textField.disableProperty(), "DISABLE_ANIMATION"); + __registerChangeListener(textField.focusColorProperty(), "FOCUS_COLOR"); + __registerChangeListener(textField.unFocusColorProperty(), "UNFOCUS_COLOR"); + __registerChangeListener(textField.disableAnimationProperty(), "DISABLE_ANIMATION"); + } + + protected void __handleControlPropertyChanged(String key) { + if ("LEADING_GRAPHIC".equals(key)) { + // fixme: updateGraphic(((JFXTextField) getSkinnable()).getLeadingGraphic(), "leading"); + } else if ("TRAILING_GRAPHIC".equals(key)) { + // fixme: updateGraphic(((JFXTextField) getSkinnable()).getTrailingGraphic(), "trailing"); + } else if ("DISABLE_NODE".equals(key)) { + linesWrapper.updateDisabled(); + } else if ("FOCUS_COLOR".equals(key)) { + linesWrapper.updateFocusColor(); + } else if ("UNFOCUS_COLOR".equals(key)) { + linesWrapper.updateUnfocusColor(); + } else if ("DISABLE_ANIMATION".equals(key)) { + // remove error clip if animation is disabled + errorContainer.updateClip(); + } + } + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + super.layoutChildren(x, y, w, h); + + final double height = getSkinnable().getHeight(); + linesWrapper.layoutLines(x, y, w, h, height, Math.floor(h)); + errorContainer.layoutPane(x, height + linesWrapper.focusedLine.getHeight(), w, h); + + if (getSkinnable().getWidth() > 0) { + updateTextPos(); + } + + linesWrapper.updateLabelFloatLayout(); + + if (invalid) { + invalid = false; + // update validation container + errorContainer.invalid(w); + // focus + linesWrapper.invalid(); + } + } + + private void updateTextPos() { + double textWidth = textNode.getLayoutBounds().getWidth(); + final double promptWidth = promptText == null ? 0 : promptText.getLayoutBounds().getWidth(); + switch (getSkinnable().getAlignment().getHpos()) { + case CENTER: + linesWrapper.promptTextScale.setPivotX(promptWidth / 2); + double midPoint = textRight.get() / 2; + double newX = midPoint - textWidth / 2; + if (newX + textWidth <= textRight.get()) { + textTranslateX.set(newX); + } + break; + case LEFT: + linesWrapper.promptTextScale.setPivotX(0); + break; + case RIGHT: + linesWrapper.promptTextScale.setPivotX(promptWidth); + break; + } + + } + + private void createPromptNode() { + if (promptText != null || !linesWrapper.usePromptText.get()) { + return; + } + promptText = new Text(); + promptText.setManaged(false); + promptText.getStyleClass().add("text"); + promptText.visibleProperty().bind(linesWrapper.usePromptText); + promptText.fontProperty().bind(getSkinnable().fontProperty()); + promptText.textProperty().bind(getSkinnable().promptTextProperty()); + promptText.fillProperty().bind(linesWrapper.animatedPromptTextFill); + promptText.setLayoutX(1); + promptText.getTransforms().add(linesWrapper.promptTextScale); + linesWrapper.promptContainer.getChildren().add(promptText); + if (getSkinnable().isFocused() && ((IFXLabelFloatControl) getSkinnable()).isLabelFloat()) { + promptText.setTranslateY(-Math.floor(textPane.getHeight())); + linesWrapper.promptTextScale.setX(0.85); + linesWrapper.promptTextScale.setY(0.85); + } + + Text oldValue = __getPromptNode(); + if (oldValue != null) { + textPane.getChildren().remove(oldValue); + } + __setPromptNode(promptText); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java new file mode 100644 index 0000000000..b7aecb198c --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java @@ -0,0 +1,184 @@ +/* + * 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.skins.ToggleButtonSkinAdapter; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXRippler.RipplerMask; +import com.jfoenix.controls.JFXRippler.RipplerPos; +import com.jfoenix.controls.JFXToggleButton; +import com.jfoenix.effects.JFXDepthManager; +import com.jfoenix.transitions.JFXAnimationTimer; +import com.jfoenix.transitions.JFXKeyFrame; +import com.jfoenix.transitions.JFXKeyValue; +import javafx.animation.Interpolator; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.StrokeLineCap; +import javafx.util.Duration; + +/** + *

Material Design ToggleButton Skin

+ * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class JFXToggleButtonSkin extends ToggleButtonSkinAdapter { + private Runnable releaseManualRippler = null; + + private JFXAnimationTimer timer; + private final Circle circle; + private final Line line; + + public JFXToggleButtonSkin(JFXToggleButton toggleButton) { + super(toggleButton); + + double circleRadius = toggleButton.getSize(); + + line = new Line(); + line.setStroke(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); + line.setStartX(0); + line.setStartY(0); + line.setEndX(circleRadius * 2 + 2); + line.setEndY(0); + line.setStrokeWidth(circleRadius * 1.5); + line.setStrokeLineCap(StrokeLineCap.ROUND); + line.setSmooth(true); + + circle = new Circle(); + circle.setFill(getSkinnable().isSelected() ? toggleButton.getToggleColor() : toggleButton.getUnToggleColor()); + circle.setCenterX(-circleRadius); + circle.setCenterY(0); + circle.setRadius(circleRadius); + circle.setSmooth(true); + JFXDepthManager.setDepth(circle, 1); + + StackPane circlePane = new StackPane(); + circlePane.getChildren().add(circle); + circlePane.setPadding(new Insets(circleRadius * 1.5)); + + JFXRippler rippler = new JFXRippler(circlePane, RipplerMask.CIRCLE, RipplerPos.BACK); + rippler.setRipplerFill(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); + rippler.setTranslateX(computeTranslation(circleRadius, line)); + + final StackPane main = new StackPane(); + main.getChildren().setAll(line, rippler); + main.setCursor(Cursor.HAND); + + // show focus traversal effect + getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> { + if (newVal) { + releaseManualRippler = rippler.createManualRipple(); + } else if (releaseManualRippler != null) { + releaseManualRippler.run(); + } + }); + toggleButton.focusedProperty().addListener((o, oldVal, newVal) -> { + if (!toggleButton.isDisableVisualFocus()) { + if (newVal) { + if (!getSkinnable().isPressed()) { + rippler.setOverlayVisible(true); + } + } else { + rippler.setOverlayVisible(false); + } + } + }); + toggleButton.pressedProperty().addListener(observable -> rippler.setOverlayVisible(false)); + + // add change listener to selected property + getSkinnable().selectedProperty().addListener(observable -> { + rippler.setRipplerFill(toggleButton.isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); + if (!toggleButton.isDisableAnimation()) { + timer.reverseAndContinue(); + } else { + rippler.setTranslateX(computeTranslation(circleRadius, line)); + } + }); + + getSkinnable().setGraphic(main); + + timer = new JFXAnimationTimer( + new JFXKeyFrame(Duration.millis(100), + JFXKeyValue.builder() + .setTarget(rippler.translateXProperty()) + .setEndValueSupplier(() -> computeTranslation(circleRadius, line)) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) + .build(), + + JFXKeyValue.builder() + .setTarget(line.strokeProperty()) + .setEndValueSupplier(() -> getSkinnable().isSelected() ? + ((JFXToggleButton) getSkinnable()).getToggleLineColor() + : ((JFXToggleButton) getSkinnable()).getUnToggleLineColor()) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) + .build(), + + JFXKeyValue.builder() + .setTarget(circle.fillProperty()) + .setEndValueSupplier(() -> getSkinnable().isSelected() ? + ((JFXToggleButton) getSkinnable()).getToggleColor() + : ((JFXToggleButton) getSkinnable()).getUnToggleColor()) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) + .build() + ) + ); + timer.setCacheNodes(circle, line); + + registerChangeListener(toggleButton.toggleColorProperty(), observableValue -> { + if (getSkinnable().isSelected()) { + circle.setFill(((JFXToggleButton) getSkinnable()).getToggleColor()); + } + }); + registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> { + if (!getSkinnable().isSelected()) { + circle.setFill(((JFXToggleButton) getSkinnable()).getUnToggleColor()); + } + }); + registerChangeListener(toggleButton.toggleLineColorProperty(), observableValue -> { + if (getSkinnable().isSelected()) { + line.setStroke(((JFXToggleButton) getSkinnable()).getToggleLineColor()); + } + }); + registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> { + if (!getSkinnable().isSelected()) { + line.setStroke(((JFXToggleButton) getSkinnable()).getUnToggleLineColor()); + } + }); + } + + private double computeTranslation(double circleRadius, Line line) { + return (getSkinnable().isSelected() ? 1 : -1) * ((line.getLayoutBounds().getWidth() / 2) - circleRadius + 2); + } + + @Override + public void dispose() { + super.dispose(); + timer.dispose(); + timer = null; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/PromptLinesWrapper.java b/HMCL/src/main/java/com/jfoenix/skins/PromptLinesWrapper.java new file mode 100644 index 0000000000..ab5a6eeba9 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/PromptLinesWrapper.java @@ -0,0 +1,344 @@ +/* + * 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.base.IFXLabelFloatControl; +import com.jfoenix.transitions.JFXAnimationTimer; +import com.jfoenix.transitions.JFXKeyFrame; +import com.jfoenix.transitions.JFXKeyValue; +import javafx.animation.Interpolator; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WritableValue; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Control; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Text; +import javafx.scene.transform.Scale; +import javafx.util.Duration; + +import java.util.function.Supplier; + +/** + * this class used to create label-float/focus-lines for all {@link IFXLabelFloatControl} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2018-07-19 + */ +public class PromptLinesWrapper { + + private final Supplier promptTextSupplier; + private final T control; + + public StackPane line = new StackPane(); + public StackPane focusedLine = new StackPane(); + public StackPane promptContainer = new StackPane(); + + private JFXAnimationTimer focusTimer; + private JFXAnimationTimer unfocusTimer; + + private final double initScale = 0.05; + public final Scale promptTextScale = new Scale(1, 1, 0, 0); + private final Scale scale = new Scale(initScale, 1); + public final Rectangle clip = new Rectangle(); + + public ObjectProperty animatedPromptTextFill; + public BooleanBinding usePromptText; + private final ObjectProperty promptTextFill; + private final ObservableValue valueProperty; + private final ObservableValue promptTextProperty; + + private boolean animating = false; + private double contentHeight = 0; + + public PromptLinesWrapper(T control, ObjectProperty promptTextFill, + ObservableValue valueProperty, + ObservableValue promptTextProperty, + Supplier promptTextSupplier) { + this.control = control; + this.promptTextSupplier = promptTextSupplier; + this.promptTextFill = promptTextFill; + this.valueProperty = valueProperty; + this.promptTextProperty = promptTextProperty; + } + + public void init(Runnable createPromptNodeRunnable, Node... cachedNodes) { + animatedPromptTextFill = new SimpleObjectProperty<>(promptTextFill.get()); + usePromptText = Bindings.createBooleanBinding(this::usePromptText, + valueProperty, + promptTextProperty, + control.labelFloatProperty(), + promptTextFill); + + // draw lines + line.setManaged(false); + line.getStyleClass().add("input-line"); + line.setBackground(new Background( + new BackgroundFill(control.getUnFocusColor(), CornerRadii.EMPTY, Insets.EMPTY))); + + // focused line + focusedLine.setManaged(false); + focusedLine.getStyleClass().add("input-focused-line"); + focusedLine.setBackground(new Background( + new BackgroundFill(control.getFocusColor(), CornerRadii.EMPTY, Insets.EMPTY))); + focusedLine.setOpacity(0); + focusedLine.getTransforms().add(scale); + + + if (usePromptText.get()) { + createPromptNodeRunnable.run(); + } + usePromptText.addListener(observable -> { + createPromptNodeRunnable.run(); + control.requestLayout(); + }); + + final Supplier> promptTargetSupplier = + () -> promptTextSupplier.get() == null ? null : promptTextSupplier.get().translateYProperty(); + + focusTimer = new JFXAnimationTimer( + new JFXKeyFrame(Duration.millis(1), + JFXKeyValue.builder() + .setTarget(focusedLine.opacityProperty()) + .setEndValue(1) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(control::isFocused).build()), + + new JFXKeyFrame(Duration.millis(160), + JFXKeyValue.builder() + .setTarget(scale.xProperty()) + .setEndValue(1) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(control::isFocused).build(), + JFXKeyValue.builder() + .setTarget(animatedPromptTextFill) + .setEndValueSupplier(control::getFocusColor) + .setInterpolator(Interpolator.EASE_BOTH) + .setAnimateCondition(() -> control.isFocused() && control.isLabelFloat()).build(), + JFXKeyValue.builder() + .setTargetSupplier(promptTargetSupplier) + .setEndValueSupplier(() -> -contentHeight) + .setAnimateCondition(control::isLabelFloat) + .setInterpolator(Interpolator.EASE_BOTH).build(), + JFXKeyValue.builder() + .setTarget(promptTextScale.xProperty()) + .setEndValue(0.85) + .setAnimateCondition(control::isLabelFloat) + .setInterpolator(Interpolator.EASE_BOTH).build(), + JFXKeyValue.builder() + .setTarget(promptTextScale.yProperty()) + .setEndValue(0.85) + .setAnimateCondition(control::isLabelFloat) + .setInterpolator(Interpolator.EASE_BOTH).build()) + ); + + unfocusTimer = new JFXAnimationTimer( + new JFXKeyFrame(Duration.millis(160), + JFXKeyValue.builder() + .setTargetSupplier(promptTargetSupplier) + .setEndValue(0) + .setInterpolator(Interpolator.EASE_BOTH).build(), + JFXKeyValue.builder() + .setTarget(promptTextScale.xProperty()) + .setEndValue(1) + .setInterpolator(Interpolator.EASE_BOTH).build(), + JFXKeyValue.builder() + .setTarget(promptTextScale.yProperty()) + .setEndValue(1) + .setInterpolator(Interpolator.EASE_BOTH).build()) + ); + + promptContainer.getStyleClass().add("prompt-container"); + promptContainer.setManaged(false); + promptContainer.setMouseTransparent(true); + + // clip prompt container + clip.setSmooth(false); + clip.setX(0); + clip.widthProperty().bind(promptContainer.widthProperty()); + promptContainer.setClip(clip); + + focusTimer.setOnFinished(() -> animating = false); + unfocusTimer.setOnFinished(() -> animating = false); + focusTimer.setCacheNodes(cachedNodes); + unfocusTimer.setCacheNodes(cachedNodes); + + // handle animation on focus gained/lost event + control.focusedProperty().addListener(observable -> { + if (control.isFocused()) { + focus(); + } else { + unFocus(); + } + }); + + promptTextFill.addListener(observable -> { + if (!control.isLabelFloat() || !control.isFocused()) { + animatedPromptTextFill.set(promptTextFill.get()); + } + }); + + updateDisabled(); + } + + private Object getControlValue() { + Object text = valueProperty.getValue(); + text = validateComboBox(text); + return text; + } + + private Object validateComboBox(Object text) { + if (control instanceof ComboBox && ((ComboBox) control).isEditable()) { + final String editorText = ((ComboBox) control).getEditor().getText(); + text = editorText == null || editorText.isEmpty() ? null : text; + } + return text; + } + + private void focus() { + unfocusTimer.stop(); + animating = true; + runTimer(focusTimer, true); + } + + private void unFocus() { + focusTimer.stop(); + scale.setX(initScale); + focusedLine.setOpacity(0); + if (control.isLabelFloat()) { + animatedPromptTextFill.set(promptTextFill.get()); + Object text = getControlValue(); + if (text == null || text.toString().isEmpty()) { + animating = true; + runTimer(unfocusTimer, true); + } + } + } + + public void updateFocusColor() { + Paint paint = control.getFocusColor(); + focusedLine.setBackground(paint == null ? Background.EMPTY + : new Background(new BackgroundFill(paint, CornerRadii.EMPTY, Insets.EMPTY))); + } + + public void updateUnfocusColor() { + Paint paint = control.getUnFocusColor(); + line.setBackground(paint == null ? Background.EMPTY + : new Background(new BackgroundFill(paint, CornerRadii.EMPTY, Insets.EMPTY))); + } + + private void updateLabelFloat(boolean animation) { + if (control.isLabelFloat()) { + if (control.isFocused()) { + animateFloatingLabel(true, animation); + } else { + Object text = getControlValue(); + animateFloatingLabel(!(text == null || text.toString().isEmpty()), animation); + } + } + } + + /** + * this method is called when the text property is changed when the + * field is not focused (changed in code) + * + * @param up direction of the prompt label + */ + private void animateFloatingLabel(boolean up, boolean animation) { + if (promptTextSupplier.get() == null) { + return; + } + if (up) { + if (promptTextSupplier.get().getTranslateY() != -contentHeight) { + unfocusTimer.stop(); + runTimer(focusTimer, animation); + } + } else { + if (promptTextSupplier.get().getTranslateY() != 0) { + focusTimer.stop(); + runTimer(unfocusTimer, animation); + } + } + } + + private void runTimer(JFXAnimationTimer timer, boolean animation) { + if (animation) { + if (!timer.isRunning()) { + timer.start(); + } + } else { + timer.applyEndValues(); + } + } + + private boolean usePromptText() { + Object txt = getControlValue(); + String promptTxt = promptTextProperty.getValue(); + boolean isLabelFloat = control.isLabelFloat(); + return isLabelFloat || (promptTxt != null + && (txt == null || txt.toString().isEmpty()) + && !promptTxt.isEmpty() + && !promptTextFill.get().equals(Color.TRANSPARENT)); + } + + public void layoutLines(double x, double y, double w, double h, double controlHeight, double translateY) { + this.contentHeight = translateY; + clip.setY(-contentHeight); + clip.setHeight(controlHeight + contentHeight); + focusedLine.resizeRelocate(x, controlHeight, w, focusedLine.prefHeight(-1)); + line.resizeRelocate(x, controlHeight, w, line.prefHeight(-1)); + promptContainer.resizeRelocate(x, y, w, h); + scale.setPivotX(w / 2); + } + + public void updateLabelFloatLayout() { + if (!animating) { + updateLabelFloat(false); + } else if (unfocusTimer.isRunning()) { + // handle the case when changing the control value when losing focus + unfocusTimer.stop(); + updateLabelFloat(true); + } + } + + public void invalid() { + if (control.isFocused()) { + focus(); + } + } + + public void updateDisabled() { + final boolean disabled = control.isDisable(); + line.setBorder(!disabled ? Border.EMPTY : + new Border(new BorderStroke(control.getUnFocusColor(), + BorderStrokeStyle.DASHED, CornerRadii.EMPTY, new BorderWidths(1)))); + line.setBackground(new Background( + new BackgroundFill(disabled ? Color.TRANSPARENT : control.getUnFocusColor(), CornerRadii.EMPTY, Insets.EMPTY))); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/skins/ValidationPane.java b/HMCL/src/main/java/com/jfoenix/skins/ValidationPane.java new file mode 100644 index 0000000000..438facbe3d --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/skins/ValidationPane.java @@ -0,0 +1,244 @@ +/* + * 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.base.IFXStaticControl; +import com.jfoenix.controls.base.IFXValidatableControl; +import com.jfoenix.validation.base.ValidatorBase; +import javafx.animation.*; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; +import javafx.scene.transform.Scale; +import javafx.util.Duration; +import org.jackhuang.hmcl.ui.FXUtils; + +/** + * this class used to create validation ui for all {@link IFXValidatableControl} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2018-07-19 + */ +public class ValidationPane extends HBox { + + private final Label errorLabel = new Label(); + private final StackPane errorIcon = new StackPane(); + + private final Rectangle errorContainerClip = new Rectangle(); + private final Scale errorClipScale = new Scale(1, 0, 0, 0); + private final Timeline errorHideTransition = new Timeline(new KeyFrame(Duration.millis(80), + new KeyValue(opacityProperty(), 0, Interpolator.LINEAR))); + private final Timeline errorShowTransition = new Timeline(new KeyFrame(Duration.millis(80), + new KeyValue(opacityProperty(), 1, Interpolator.EASE_OUT))); + private final Timeline scale1 = new Timeline(); + private final Timeline scaleLess1 = new Timeline(); + + private final T control; + + public ValidationPane(T control) { + this.control = control; + setManaged(false); + + errorLabel.getStyleClass().add("error-label"); + + final StackPane labelContainer = new StackPane(errorLabel); + labelContainer.getStyleClass().add("error-label-container"); + labelContainer.setAlignment(Pos.TOP_LEFT); + getChildren().setAll(labelContainer, errorIcon); + HBox.setHgrow(labelContainer, Priority.ALWAYS); + + setSpacing(8); + setAlignment(Pos.CENTER_LEFT); + setPadding(new Insets(4, 0, 0, 0)); + setVisible(false); + setOpacity(0); + + errorContainerClip.getTransforms().add(errorClipScale); + setClip(control.isDisableAnimation() ? null : errorContainerClip); + + + control.activeValidatorProperty().addListener((ObservableValue o, ValidatorBase oldVal, ValidatorBase newVal) -> { + if (!control.isDisableAnimation()) { + if (newVal != null) { + errorHideTransition.setOnFinished(finish -> { + showError(newVal); + final double w = control.getWidth(); + double errorContainerHeight = computeErrorHeight(computeErrorWidth(w)); + if (errorLabel.isWrapText()) { + // animate opacity + scale + if (errorContainerHeight < getHeight()) { + // update animation frames + scaleLess1.getKeyFrames().setAll(createSmallerScaleFrame(errorContainerHeight)); + scaleLess1.setOnFinished(event -> { + updateErrorContainerSize(w, errorContainerHeight); + errorClipScale.setY(1); + }); + SequentialTransition transition = new SequentialTransition(scaleLess1, + errorShowTransition); + transition.play(); + } else { + errorClipScale.setY(oldVal == null ? 0 : getHeight() / errorContainerHeight); + updateErrorContainerSize(w, errorContainerHeight); + // update animation frames + scale1.getKeyFrames().setAll(createScaleToOneFrames()); + // play animation + ParallelTransition parallelTransition = new ParallelTransition(); + parallelTransition.getChildren().addAll(scale1, errorShowTransition); + parallelTransition.play(); + } + } else { + // animate opacity only + errorClipScale.setY(1); + updateErrorContainerSize(w, errorContainerHeight); + ParallelTransition parallelTransition = new ParallelTransition(errorShowTransition); + parallelTransition.play(); + } + }); + errorHideTransition.play(); + } else { + errorHideTransition.setOnFinished(null); + if (errorLabel.isWrapText()) { + // animate scale only + scaleLess1.getKeyFrames().setAll(new KeyFrame(Duration.millis(100), + new KeyValue(errorClipScale.yProperty(), 0, Interpolator.EASE_BOTH))); + scaleLess1.setOnFinished(event -> { + hideError(); + errorClipScale.setY(0); + }); + SequentialTransition transition = new SequentialTransition(scaleLess1); + transition.play(); + } else { + errorClipScale.setY(0); + } + // animate opacity only + errorHideTransition.play(); + } + } else { + if (newVal != null) { + FXUtils.runInFXAndWait(() -> showError(newVal)); + } else { + FXUtils.runInFXAndWait(this::hideError); + } + } + }); + } + + public void layoutPane(final double x, final double y, final double w, final double h) { + relocate(x, y); + // resize error container if animation is disabled + if (control.isDisableAnimation() || isErrorVisible()) { + resize(w, computeErrorHeight(computeErrorWidth(w))); + errorContainerClip.setWidth(w); + } + } + + public void invalid(double w) { + final ValidatorBase activeValidator = control.getActiveValidator(); + if (activeValidator != null) { + showError(activeValidator); + final double errorContainerWidth = w - errorIcon.prefWidth(-1); + setOpacity(1); + resize(w, computeErrorHeight(errorContainerWidth)); + errorContainerClip.setWidth(w); + errorContainerClip.setHeight(getHeight()); + errorClipScale.setY(1); + } + } + + public void updateClip() { + setClip(control.isDisableAnimation() ? null : errorContainerClip); + } + + private boolean isErrorVisible() { + return isVisible() + && errorShowTransition.getStatus().equals(Animation.Status.STOPPED) + && errorHideTransition.getStatus().equals(Animation.Status.STOPPED); + } + + private double computeErrorWidth(double w) { + return w - errorIcon.prefWidth(-1); + } + + private double computeErrorHeight(double errorContainerWidth) { + return errorLabel.prefHeight(errorContainerWidth) + + snappedBottomInset() + + snappedTopInset(); + } + + /** + * update the size of error container and its clip + */ + private void updateErrorContainerSize(double w, double errorContainerHeight) { + errorContainerClip.setWidth(w); + errorContainerClip.setHeight(errorContainerHeight); + resize(w, errorContainerHeight); + } + + /** + * creates error animation frames when moving from large -> small error container + */ + private KeyFrame createSmallerScaleFrame(double errorContainerHeight) { + return new KeyFrame(Duration.millis(100), + new KeyValue(errorClipScale.yProperty(), + errorContainerHeight / getHeight(), + Interpolator.EASE_BOTH)); + } + + /** + * creates error animation frames when moving from small -> large error container + */ + private KeyFrame createScaleToOneFrames() { + return new KeyFrame(Duration.millis(100), new + KeyValue(errorClipScale.yProperty(), 1, Interpolator.EASE_BOTH)); + } + + + private void showError(ValidatorBase validator) { + // set text in error label + errorLabel.setText(validator.getMessage()); + // show error icon + Node icon = validator.getIcon(); + errorIcon.getChildren().clear(); + if (icon != null) { + errorIcon.getChildren().setAll(icon); + StackPane.setAlignment(icon, Pos.CENTER_RIGHT); + } + setVisible(true); + } + + private void hideError() { + // clear error label text + errorLabel.setText(null); + // clear error icon + errorIcon.getChildren().clear(); + // reset the height of the text field + // hide error container + setVisible(false); + } + +} diff --git a/HMCL/src/main/java/com/jfoenix/svg/SVGGlyph.java b/HMCL/src/main/java/com/jfoenix/svg/SVGGlyph.java new file mode 100644 index 0000000000..d37d007013 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/svg/SVGGlyph.java @@ -0,0 +1,236 @@ +/* + * 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.svg; + +import javafx.beans.NamedArg; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.*; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.SVGPath; +import javafx.scene.shape.Shape; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Node that is used to show svg images + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class SVGGlyph extends Pane { + private static final String DEFAULT_STYLE_CLASS = "jfx-svg-glyph"; + + private final int glyphId; + private final String name; + private static final int DEFAULT_PREF_SIZE = 64; + private double widthHeightRatio = 1; + private final ObjectProperty fill = new SimpleObjectProperty<>(); + + public SVGGlyph() { + this(null); + } + + public SVGGlyph(@NamedArg("svgPathContent") String svgPathContent) { + this(-1, "UNNAMED", svgPathContent, Color.BLACK); + } + + public SVGGlyph(@NamedArg("svgPathContent") String svgPathContent, @NamedArg("fill") Paint fill) { + this(-1, "UNNAMED", svgPathContent, fill); + } + + /** + * Constructs SVGGlyph node for a specified svg content and color + * Note: name and glyphId is not needed when creating a single SVG image, + * they have been used in {@link SVGGlyphLoader} to load icomoon svg font. + * + * @param glyphId integer represents the glyph id + * @param name glyph name + * @param svgPathContent svg content + * @param fill svg color + */ + public SVGGlyph(int glyphId, String name, String svgPathContent, Paint fill) { + this.glyphId = glyphId; + this.name = name; + getStyleClass().add(DEFAULT_STYLE_CLASS); + this.fill.addListener((observable) -> setBackground(new Background( + new BackgroundFill(getFill() == null ? Color.BLACK : getFill(), null, null)))); + + shapeProperty().addListener(observable -> { + Shape shape = getShape(); + if (getShape() != null) { + widthHeightRatio = shape.prefWidth(-1) / shape.prefHeight(-1); + if (getSize() != Region.USE_COMPUTED_SIZE) { + setSizeRatio(getSize()); + } + } + }); + + if (svgPathContent != null && !svgPathContent.isEmpty()) { + SVGPath shape = new SVGPath(); + shape.setContent(svgPathContent); + setShape(shape); + setFill(fill); + } + + setPrefSize(DEFAULT_PREF_SIZE, DEFAULT_PREF_SIZE); + } + + /** + * @return current svg id + */ + public int getGlyphId() { + return glyphId; + } + + /** + * @return current svg name + */ + public String getName() { + return name; + } + + /** + * svg color property + */ + public void setFill(Paint fill) { + this.fill.setValue(fill); + } + + public ObjectProperty fillProperty() { + return fill; + } + + public Paint getFill() { + return fill.getValue(); + } + + /** + * resize the svg to a certain width and height + */ + public void setSize(double width, double height) { + this.setMinSize(StackPane.USE_PREF_SIZE, StackPane.USE_PREF_SIZE); + this.setPrefSize(width, height); + this.setMaxSize(StackPane.USE_PREF_SIZE, StackPane.USE_PREF_SIZE); + } + + /** + * resize the svg to this size while keeping the width/height ratio + * + * @param size in pixel + */ + private void setSizeRatio(double size) { + double width = widthHeightRatio * size; + double height = size / widthHeightRatio; + if (width <= size) { + setSize(width, size); + } else if (height <= size) { + setSize(size, height); + } else { + setSize(size, size); + } + } + + /** + * resize the svg to certain width while keeping the width/height ratio + * + * @param width in pixel + */ + public void setSizeForWidth(double width) { + double height = width / widthHeightRatio; + setSize(width, height); + } + + /** + * resize the svg to certain width while keeping the width/height ratio + * + * @param height in pixel + */ + public void setSizeForHeight(double height) { + double width = height * widthHeightRatio; + setSize(width, height); + } + + /** + * specifies the radius of the spinner node, by default it's set to -1 (USE_COMPUTED_SIZE) + */ + private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty(StyleableProperties.SIZE, + SVGGlyph.this, + "size", + Region.USE_COMPUTED_SIZE) { + @Override + public void invalidated() { + setSizeRatio(getSize()); + } + }; + + public double getSize() { + return size.get(); + } + + public DoubleProperty sizeProperty() { + return size; + } + + public void setSize(double size) { + this.size.set(size); + } + + private static class StyleableProperties { + private static final CssMetaData SIZE = + new CssMetaData("-jfx-size", StyleConverter.getSizeConverter(), Region.USE_COMPUTED_SIZE) { + @Override + public boolean isSettable(SVGGlyph control) { + return !control.size.isBound(); + } + + @Override + public StyleableDoubleProperty getStyleableProperty(SVGGlyph control) { + return control.size; + } + }; + + private static final List> CHILD_STYLEABLES; + + static { + final List> styleables = + new ArrayList<>(Pane.getClassCssMetaData()); + Collections.addAll(styleables, + SIZE + ); + CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return StyleableProperties.CHILD_STYLEABLES; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/svg/SVGGlyphLoader.java b/HMCL/src/main/java/com/jfoenix/svg/SVGGlyphLoader.java new file mode 100644 index 0000000000..37bacae418 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/svg/SVGGlyphLoader.java @@ -0,0 +1,243 @@ +/* + * 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.svg; + +import javafx.beans.binding.Bindings; +import javafx.scene.paint.Color; +import javafx.scene.transform.Scale; +import javafx.scene.transform.Translate; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.*; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashMap; +import java.util.Set; + +/** + * will load icomoon svg font file, it will create a map of the + * available svg glyphs. the user can retrieve the svg glyph using its name. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class SVGGlyphLoader { + + private static final HashMap glyphsMap = new HashMap<>(); + + public static SVGGlyph getGlyph(String glyphName) { + return glyphsMap.get(glyphName).build(); + } + + /** + * will retrieve icons from the glyphs map for a certain glyphName + * + * @param glyphName the glyph name + * @return SVGGlyph node + */ + public static SVGGlyph getIcoMoonGlyph(String glyphName) throws Exception{ + SVGGlyphBuilder builder = glyphsMap.get(glyphName); + if(builder == null) throw new Exception("Glyph '" + glyphName + "' not found!"); + SVGGlyph glyph = builder.build(); + // we need to apply transformation to correct the icon since + // its being inverted after importing from icomoon + glyph.getTransforms().add(new Scale(1, -1)); + Translate height = new Translate(); + height.yProperty().bind(Bindings.createDoubleBinding(() -> -glyph.getHeight(), glyph.heightProperty())); + glyph.getTransforms().add(height); + return glyph; + } + + /** + * @return a set of all loaded svg IDs (names) + */ + public static Set getAllGlyphsIDs() { + return glyphsMap.keySet(); + } + + /** + * will load SVG icons from icomoon font file (e.g font.svg) + * + * @param url of the svg font file + */ + public static void loadGlyphsFont(URL url) throws IOException { + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + + docBuilder.setEntityResolver((publicId, systemId) -> { + // disable dtd entites at runtime + return new InputSource(new StringReader("")); + }); + + File svgFontFile = new File(url.toURI()); + Document doc = docBuilder.parse(svgFontFile); + doc.getDocumentElement().normalize(); + + NodeList glyphsList = doc.getElementsByTagName("glyph"); + for (int i = 0; i < glyphsList.getLength(); i++) { + Node glyph = glyphsList.item(i); + Node glyphName = glyph.getAttributes().getNamedItem("glyph-name"); + if (glyphName == null) { + continue; + } + + String glyphId = glyphName.getNodeValue(); + SVGGlyphBuilder glyphPane = new SVGGlyphBuilder(i, + glyphId, + glyph.getAttributes() + .getNamedItem("d") + .getNodeValue()); + glyphsMap.put(svgFontFile.getName() + "." + glyphId, glyphPane); + } + } catch (ParserConfigurationException | SAXException | URISyntaxException e) { + e.printStackTrace(); + } + } + + /** + * will load SVG icons from input stream + * + * @param stream input stream of svg font file + * @param keyPrefix will be used as a prefix when storing SVG icons in the map + */ + public static void loadGlyphsFont(InputStream stream, String keyPrefix) throws IOException { + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + + docBuilder.setEntityResolver((publicId, systemId) -> { + // disable dtd entites at runtime + return new InputSource(new StringReader("")); + }); + + Document doc = docBuilder.parse(stream); + doc.getDocumentElement().normalize(); + + NodeList glyphsList = doc.getElementsByTagName("glyph"); + for (int i = 0; i < glyphsList.getLength(); i++) { + Node glyph = glyphsList.item(i); + Node glyphName = glyph.getAttributes().getNamedItem("glyph-name"); + if (glyphName == null) { + continue; + } + + String glyphId = glyphName.getNodeValue(); + SVGGlyphBuilder glyphPane = new SVGGlyphBuilder(i, + glyphId, + glyph.getAttributes() + .getNamedItem("d") + .getNodeValue()); + glyphsMap.put(keyPrefix + "." + glyphId, glyphPane); + } + stream.close(); + } catch (ParserConfigurationException | SAXException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * load a single svg icon from a file + * + * @param url of the svg icon + * @return SVGGLyph node + * @throws IOException + */ + public static SVGGlyph loadGlyph(URL url) throws IOException { + String urlString = url.toString(); + String filename = urlString.substring(urlString.lastIndexOf('/') + 1); + + int startPos = 0; + int endPos = 0; + while (endPos < filename.length() && filename.charAt(endPos) != '-') { + endPos++; + } + int id = Integer.parseInt(filename.substring(startPos, endPos)); + startPos = endPos + 1; + + while (endPos < filename.length() && filename.charAt(endPos) != '.') { + endPos++; + } + String name = filename.substring(startPos, endPos); + + return new SVGGlyph(id, name, extractSvgPath(getStringFromInputStream(url.openStream())), Color.BLACK); + } + + /** + * clear all loaded svg icons + */ + public static void clear() { + glyphsMap.clear(); + } + + private static String extractSvgPath(String svgString) { + return svgString.replaceFirst(".*d=\"", "").replaceFirst("\".*", ""); + } + + private static String getStringFromInputStream(InputStream is) { + BufferedReader br = null; + StringBuilder sb = new StringBuilder(); + + String line; + try { + br = new BufferedReader(new InputStreamReader(is)); + while ((line = br.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return sb.toString(); + } + + private static final class SVGGlyphBuilder { + private final int glyphId; + private final String name; + private final String svgPathContent; + + SVGGlyphBuilder(int glyphId, String name, String svgPathContent) { + this.glyphId = glyphId; + this.name = name; + this.svgPathContent = svgPathContent; + } + + SVGGlyph build() { + return new SVGGlyph(glyphId, name, svgPathContent, Color.BLACK); + } + } +} + diff --git a/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java b/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java new file mode 100644 index 0000000000..96d27766a6 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java @@ -0,0 +1,68 @@ +/* + * 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.transitions; + +import javafx.scene.CacheHint; +import javafx.scene.Node; +import javafx.scene.layout.Region; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class CacheMemento { + private boolean cache; + private boolean cacheShape; + private boolean snapToPixel; + private CacheHint cacheHint = CacheHint.DEFAULT; + private final Node node; + private final AtomicBoolean isCached = new AtomicBoolean(false); + + public CacheMemento(Node node) { + this.node = node; + } + + /** + * this method will cache the node only if it wasn't cached before + */ + public void cache() { + if (!isCached.getAndSet(true)) { + this.cache = node.isCache(); + this.cacheHint = node.getCacheHint(); + node.setCache(true); + node.setCacheHint(CacheHint.SPEED); + if (node instanceof Region) { + this.cacheShape = ((Region) node).isCacheShape(); + this.snapToPixel = ((Region) node).isSnapToPixel(); + ((Region) node).setCacheShape(true); + ((Region) node).setSnapToPixel(true); + } + } + } + + public void restore() { + if (isCached.getAndSet(false)) { + node.setCache(cache); + node.setCacheHint(cacheHint); + if (node instanceof Region) { + ((Region) node).setCacheShape(cacheShape); + ((Region) node).setSnapToPixel(snapToPixel); + } + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/CachedTransition.java b/HMCL/src/main/java/com/jfoenix/transitions/CachedTransition.java new file mode 100644 index 0000000000..2f0a070bf8 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/CachedTransition.java @@ -0,0 +1,107 @@ +/* + * 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.transitions; + +import javafx.animation.Timeline; +import javafx.animation.Transition; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Node; +import javafx.util.Duration; + +/** + * applies animation on a cached node to improve the performance + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public class CachedTransition extends Transition { + protected final Node node; + protected ObjectProperty timeline = new SimpleObjectProperty<>(); + private CacheMemento[] mementos = new CacheMemento[0]; + + public CachedTransition(final Node node, final Timeline timeline) { + this.node = node; + this.timeline.set(timeline); + mementos = node == null ? mementos : new CacheMemento[]{new CacheMemento(node)}; + statusProperty().addListener(observable -> { + switch (getStatus()) { + case RUNNING: + starting(); + break; + default: + stopping(); + break; + } + }); + } + + public CachedTransition(final Node node, final Timeline timeline, CacheMemento... cacheMomentos) { + this.node = node; + this.timeline.set(timeline); + mementos = new CacheMemento[(node == null ? 0 : 1) + cacheMomentos.length]; + if (node != null) { + mementos[0] = new CacheMemento(node); + } + System.arraycopy(cacheMomentos, 0, mementos, node == null ? 0 : 1, cacheMomentos.length); + statusProperty().addListener(observable -> { + switch (getStatus()) { + case RUNNING: + starting(); + break; + default: + stopping(); + break; + } + }); + } + + /** + * Called when the animation is starting + */ + protected void starting() { + if (mementos != null) { + for (CacheMemento memento : mementos) { + memento.cache(); + } + } + } + + /** + * Called when the animation is stopping + */ + protected void stopping() { + if (mementos != null) { + for (CacheMemento memento : mementos) { + memento.restore(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void interpolate(double d) { + timeline.get().playFrom(Duration.seconds(d)); + timeline.get().stop(); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java new file mode 100644 index 0000000000..f6e5207194 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java @@ -0,0 +1,283 @@ +/* + * 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.transitions; + +import javafx.animation.AnimationTimer; +import javafx.beans.value.WritableValue; +import javafx.scene.Node; +import javafx.util.Duration; + +import java.util.*; +import java.util.function.Supplier; + +/** + * Custom AnimationTimer that can be created the same way as a timeline, + * however it doesn't behave the same yet. it only animates in one direction, + * it doesn't support animation 0 -> 1 -> 0.5 + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-09-21 + */ + +public class JFXAnimationTimer extends AnimationTimer { + + private final Set animationHandlers = new HashSet<>(); + private long startTime = -1; + private boolean running = false; + private final List caches = new ArrayList<>(); + private double totalElapsedMilliseconds; + + + public JFXAnimationTimer(JFXKeyFrame... keyFrames) { + for (JFXKeyFrame keyFrame : keyFrames) { + Duration duration = keyFrame.getDuration(); + final Set> keyValuesSet = keyFrame.getValues(); + if (!keyValuesSet.isEmpty()) { + animationHandlers.add(new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues())); + } + } + } + + private final HashMap mutableFrames = new HashMap<>(); + + public void addKeyFrame(JFXKeyFrame keyFrame) throws Exception { + if (isRunning()) { + throw new Exception("Can't update animation timer while running"); + } + Duration duration = keyFrame.getDuration(); + final Set> keyValuesSet = keyFrame.getValues(); + if (!keyValuesSet.isEmpty()) { + final AnimationHandler handler = new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues()); + animationHandlers.add(handler); + mutableFrames.put(keyFrame, handler); + } + } + + public void removeKeyFrame(JFXKeyFrame keyFrame) throws Exception { + if (isRunning()) { + throw new Exception("Can't update animation timer while running"); + } + AnimationHandler handler = mutableFrames.get(keyFrame); + animationHandlers.remove(handler); + } + + @Override + public void start() { + super.start(); + running = true; + startTime = -1; + for (AnimationHandler animationHandler : animationHandlers) { + animationHandler.init(); + } + for (CacheMemento cache : caches) { + cache.cache(); + } + } + + @Override + public void handle(long now) { + startTime = startTime == -1 ? now : startTime; + totalElapsedMilliseconds = (now - startTime) / 1000000.0; + boolean stop = true; + for (AnimationHandler handler : animationHandlers) { + handler.animate(totalElapsedMilliseconds); + if (!handler.finished) { + stop = false; + } + } + if (stop) { + this.stop(); + } + } + + /** + * this method will pause the timer and reverse the animation if the timer already + * started otherwise it will start the animation. + */ + public void reverseAndContinue() { + if (isRunning()) { + super.stop(); + for (AnimationHandler handler : animationHandlers) { + handler.reverse(totalElapsedMilliseconds); + } + startTime = -1; + super.start(); + } else { + start(); + } + } + + @Override + public void stop() { + super.stop(); + running = false; + for (AnimationHandler handler : animationHandlers) { + handler.clear(); + } + for (CacheMemento cache : caches) { + cache.restore(); + } + if (onFinished != null) { + onFinished.run(); + } + } + + public void applyEndValues() { + if (isRunning()) { + super.stop(); + } + for (AnimationHandler handler : animationHandlers) { + handler.applyEndValues(); + } + startTime = -1; + } + + public boolean isRunning() { + return running; + } + + private Runnable onFinished = null; + + public void setOnFinished(Runnable onFinished) { + this.onFinished = onFinished; + } + + public void setCacheNodes(Node... nodesToCache) { + caches.clear(); + if (nodesToCache != null) { + for (Node node : nodesToCache) { + caches.add(new CacheMemento(node)); + } + } + } + + public void dispose() { + caches.clear(); + for (AnimationHandler handler : animationHandlers) { + handler.dispose(); + } + animationHandlers.clear(); + } + + static class AnimationHandler { + private final double duration; + private double currentDuration; + private final Set> keyValues; + private final Supplier animationCondition; + private boolean finished = false; + + private final HashMap, Object> initialValuesMap = new HashMap<>(); + private final HashMap, Object> endValuesMap = new HashMap<>(); + + AnimationHandler(Duration duration, Supplier animationCondition, Set> keyValues) { + this.duration = duration.toMillis(); + currentDuration = this.duration; + this.keyValues = keyValues; + this.animationCondition = animationCondition; + } + + public void init() { + finished = animationCondition != null && !animationCondition.get(); + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.getTarget() != null) { + // replaced putIfAbsent for mobile compatibility + if (!initialValuesMap.containsKey(keyValue.getTarget())) { + initialValuesMap.put(keyValue.getTarget(), keyValue.getTarget().getValue()); + } + if (!endValuesMap.containsKey(keyValue.getTarget())) { + endValuesMap.put(keyValue.getTarget(), keyValue.getEndValue()); + } + } + } + } + + void reverse(double now) { + finished = animationCondition != null && !animationCondition.get(); + currentDuration = duration - (currentDuration - now); + // update initial values + for (JFXKeyValue keyValue : keyValues) { + final WritableValue target = keyValue.getTarget(); + if (target != null) { + initialValuesMap.put(target, target.getValue()); + endValuesMap.put(target, keyValue.getEndValue()); + } + } + } + + // now in milliseconds + public void animate(double now) { + // if animate condition for the key frame is not met then do nothing + if (finished) { + return; + } + if (now <= currentDuration) { + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.isValid()) { + final WritableValue target = keyValue.getTarget(); + final Object endValue = endValuesMap.get(target); + if (endValue != null && target != null && !target.getValue().equals(endValue)) { + target.setValue(keyValue.getInterpolator().interpolate(initialValuesMap.get(target), endValue, now / currentDuration)); + } + } + } + } else { + finished = true; + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.isValid()) { + final WritableValue target = keyValue.getTarget(); + if (target != null) { + // set updated end value instead of cached + final Object endValue = keyValue.getEndValue(); + if (endValue != null) { + target.setValue(endValue); + } + } + } + } + currentDuration = duration; + } + } + + public void applyEndValues() { + for (JFXKeyValue keyValue : keyValues) { + if (keyValue.isValid()) { + final WritableValue target = keyValue.getTarget(); + if (target != null) { + final Object endValue = keyValue.getEndValue(); + if (endValue != null && !target.getValue().equals(endValue)) { + target.setValue(endValue); + } + } + } + } + } + + public void clear() { + initialValuesMap.clear(); + endValuesMap.clear(); + } + + void dispose() { + clear(); + keyValues.clear(); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXDrawerKeyValue.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXDrawerKeyValue.java new file mode 100644 index 0000000000..810dc6d106 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXDrawerKeyValue.java @@ -0,0 +1,129 @@ +/* + * 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.transitions; + +import javafx.animation.Interpolator; +import javafx.beans.value.WritableValue; + +import java.util.function.Supplier; + +/** + * Wrapper for JFXDrawer animation key value + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2018-05-03 + */ +public class JFXDrawerKeyValue { + + private WritableValue target; + private Supplier closeValueSupplier; + private Supplier openValueSupplier; + private Interpolator interpolator; + private Supplier animateCondition = () -> true; + + public WritableValue getTarget() { + return target; + } + + public Supplier getCloseValueSupplier() { + return closeValueSupplier; + } + + public Supplier getOpenValueSupplier() { + return openValueSupplier; + } + + public Interpolator getInterpolator() { + return interpolator; + } + + public boolean isValid() { + return animateCondition == null || animateCondition.get(); + } + + public static JFXDrawerKeyValueBuilder builder() { + return new JFXDrawerKeyValueBuilder<>(); + } + + public void applyOpenValues() { + target.setValue(getOpenValueSupplier().get()); + } + + public void applyCloseValues(){ + target.setValue(getCloseValueSupplier().get()); + } + + public static final class JFXDrawerKeyValueBuilder { + private WritableValue target; + private Interpolator interpolator = Interpolator.EASE_BOTH; + private Supplier animateCondition = () -> true; + private Supplier closeValueSupplier; + private Supplier openValueSupplier; + + private JFXDrawerKeyValueBuilder() { + } + + public JFXDrawerKeyValueBuilder setTarget(WritableValue target) { + this.target = target; + return this; + } + + public JFXDrawerKeyValueBuilder setInterpolator(Interpolator interpolator) { + this.interpolator = interpolator; + return this; + } + + public JFXDrawerKeyValueBuilder setAnimateCondition(Supplier animateCondition) { + this.animateCondition = animateCondition; + return this; + } + + public JFXDrawerKeyValueBuilder setCloseValue(T closeValue) { + this.closeValueSupplier = () -> closeValue; + return this; + } + + public JFXDrawerKeyValueBuilder setCloseValueSupplier(Supplier closeValueSupplier) { + this.closeValueSupplier = closeValueSupplier; + return this; + } + + public JFXDrawerKeyValueBuilder setOpenValueSupplier(Supplier openValueSupplier) { + this.openValueSupplier = openValueSupplier; + return this; + } + + public JFXDrawerKeyValueBuilder setOpenValue(T openValue) { + this.openValueSupplier = () -> openValue; + return this; + } + + public JFXDrawerKeyValue build() { + JFXDrawerKeyValue jFXDrawerKeyValue = new JFXDrawerKeyValue<>(); + jFXDrawerKeyValue.openValueSupplier = this.openValueSupplier; + jFXDrawerKeyValue.closeValueSupplier = this.closeValueSupplier; + jFXDrawerKeyValue.target = this.target; + jFXDrawerKeyValue.interpolator = this.interpolator; + jFXDrawerKeyValue.animateCondition = this.animateCondition; + return jFXDrawerKeyValue; + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXFillTransition.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXFillTransition.java new file mode 100644 index 0000000000..eedd5b23d4 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXFillTransition.java @@ -0,0 +1,310 @@ +/* + * 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.transitions; + +import javafx.animation.Transition; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Insets; +import javafx.scene.CacheHint; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +/** + * This {@code Transition} creates an animation, that changes the filling of a + * pane over a {@code duration}. This is done by updating the {@code background} + * property of the {@code pane} at regular intervals. + *

+ * It starts from the {@code fromValue}. + *

+ * It stops at the {@code toValue} value. + *

+ * It's similar to JavaFX FillTransition, however it can be applied on Region + * instead of shape + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ + +public final class JFXFillTransition extends Transition { + + private Color start; + private Color end; + private CacheHint oldCacheHint = CacheHint.DEFAULT; + private boolean oldCache = false; + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * The target region of this {@code JFXFillTransition}. + */ + private ObjectProperty region; + + public void setRegion(Region value) { + if ((region != null) || (value != null /* DEFAULT_SHAPE */)) { + regionProperty().set(value); + } + } + + public Region getRegion() { + return (region == null) ? null : region.get(); + } + + public ObjectProperty regionProperty() { + if (region == null) { + region = new SimpleObjectProperty<>(this, "region", null); + } + return region; + } + + /** + * The duration of this {@code JFXFillTransition}. + *

+ * Note: While the unit of {@code duration} is a millisecond, the + * granularity depends on the underlying operating system and will in + * general be larger. For example animations on desktop systems usually run + * with a maximum of 60fps which gives a granularity of ~17 ms. + *

+ * Setting duration to value lower than {@link Duration#ZERO} will result + * in {@link IllegalArgumentException}. + * + * @defaultValue 400ms + */ + private ObjectProperty duration; + private static final Duration DEFAULT_DURATION = Duration.millis(400); + + public void setDuration(Duration value) { + if ((duration != null) || (!DEFAULT_DURATION.equals(value))) { + durationProperty().set(value); + } + } + + public Duration getDuration() { + return (duration == null) ? DEFAULT_DURATION : duration.get(); + } + + public ObjectProperty durationProperty() { + if (duration == null) { + duration = new ObjectPropertyBase(DEFAULT_DURATION) { + + @Override + public void invalidated() { + try { + setCycleDuration(getDuration()); + } catch (IllegalArgumentException e) { + if (isBound()) { + unbind(); + } + set(getCycleDuration()); + throw e; + } + } + + @Override + public Object getBean() { + return JFXFillTransition.this; + } + + @Override + public String getName() { + return "duration"; + } + }; + } + return duration; + } + + /** + * Specifies the start color value for this {@code JFXFillTransition}. + * + * @defaultValue {@code null} + */ + private ObjectProperty fromValue; + private static final Color DEFAULT_FROM_VALUE = null; + + public void setFromValue(Color value) { + if ((fromValue != null) || (value != null /* DEFAULT_FROM_VALUE */)) { + fromValueProperty().set(value); + } + } + + public Color getFromValue() { + return (fromValue == null) ? DEFAULT_FROM_VALUE : fromValue.get(); + } + + public ObjectProperty fromValueProperty() { + if (fromValue == null) { + fromValue = new SimpleObjectProperty<>(this, "fromValue", DEFAULT_FROM_VALUE); + } + return fromValue; + } + + /** + * Specifies the stop color value for this {@code JFXFillTransition}. + * + * @defaultValue {@code null} + */ + private ObjectProperty toValue; + private static final Color DEFAULT_TO_VALUE = null; + + public void setToValue(Color value) { + if ((toValue != null) || (value != null /* DEFAULT_TO_VALUE */)) { + toValueProperty().set(value); + } + } + + public Color getToValue() { + return (toValue == null) ? DEFAULT_TO_VALUE : toValue.get(); + } + + public ObjectProperty toValueProperty() { + if (toValue == null) { + toValue = new SimpleObjectProperty<>(this, "toValue", DEFAULT_TO_VALUE); + } + return toValue; + } + + /** + * The constructor of {@code JFXFillTransition} + * + * @param duration The duration of the {@code JFXFillTransition} + * @param region The {@code region} which filling will be animated + * @param fromValue The start value of the color-animation + * @param toValue The end value of the color-animation + */ + public JFXFillTransition(Duration duration, Region region, Color fromValue, + Color toValue) { + setDuration(duration); + setRegion(region); + setFromValue(fromValue); + setToValue(toValue); + setCycleDuration(duration); + statusProperty().addListener((ov, t, newStatus) -> { + switch (newStatus) { + case RUNNING: + starting(); + break; + default: + stopping(); + break; + } + }); + } + + /** + * The constructor of {@code JFXFillTransition} + * + * @param duration The duration of the {@code JFXFillTransition} + * @param fromValue The start value of the color-animation + * @param toValue The end value of the color-animation + */ + public JFXFillTransition(Duration duration, Color fromValue, Color toValue) { + this(duration, null, fromValue, toValue); + } + + /** + * The constructor of {@code JFXFillTransition} + * + * @param duration The duration of the {@code JFXFillTransition} + * @param region The {@code region} which filling will be animated + */ + public JFXFillTransition(Duration duration, Region region) { + this(duration, region, null, null); + } + + /** + * The constructor of {@code JFXFillTransition} + * + * @param duration The duration of the {@code FadeTransition} + */ + public JFXFillTransition(Duration duration) { + this(duration, null, null, null); + } + + /** + * The constructor of {@code JFXFillTransition} + */ + public JFXFillTransition() { + this(DEFAULT_DURATION, null); + } + + /** + * Called when the animation is starting + */ + private CornerRadii radii; + private Insets insets; + + private void starting() { + // init animation values + if (start == null) { + oldCache = region.get().isCache(); + oldCacheHint = region.get().getCacheHint(); + radii = region.get().getBackground() == null ? null : region.get() + .getBackground() + .getFills() + .get(0) + .getRadii(); + insets = region.get().getBackground() == null ? null : region.get() + .getBackground() + .getFills() + .get(0) + .getInsets(); + start = fromValue.get(); + end = toValue.get(); + region.get().setCache(true); + region.get().setCacheHint(CacheHint.SPEED); + } + } + + /** + * Called when the animation is stopping + */ + private void stopping() { + region.get().setCache(oldCache); + region.get().setCacheHint(oldCacheHint); + } + + /** + * {@inheritDoc} + */ + @Override + protected void interpolate(double frac) { + if (start == null) { + starting(); + } + Color newColor = start.interpolate(end, frac); + if (Color.TRANSPARENT.equals(start)) { + newColor = new Color(end.getRed(), end.getGreen(), end.getBlue(), newColor.getOpacity()); + } + region.get().setBackground(new Background(new BackgroundFill(newColor, radii, insets))); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java new file mode 100644 index 0000000000..8c46455c15 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java @@ -0,0 +1,104 @@ +/* + * 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.transitions; + +import javafx.util.Duration; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Supplier; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-09-21 + */ + +public class JFXKeyFrame { + + private Duration duration; + private Set> keyValues = new CopyOnWriteArraySet<>(); + private Supplier animateCondition = null; + + public JFXKeyFrame(Duration duration, JFXKeyValue... keyValues) { + this.duration = duration; + for (final JFXKeyValue keyValue : keyValues) { + if (keyValue != null) { + this.keyValues.add(keyValue); + } + } + } + + private JFXKeyFrame() { + + } + + public final Duration getDuration() { + return duration; + } + + public final Set> getValues() { + return keyValues; + } + + public Supplier getAnimateCondition() { + return animateCondition; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Duration duration; + private final Set> keyValues = new CopyOnWriteArraySet<>(); + private Supplier animateCondition = null; + + private Builder() { + } + + public Builder setDuration(Duration duration) { + this.duration = duration; + return this; + } + + public Builder setKeyValues(JFXKeyValue... keyValues) { + for (final JFXKeyValue keyValue : keyValues) { + if (keyValue != null) { + this.keyValues.add(keyValue); + } + } + return this; + } + + public Builder setAnimateCondition(Supplier animateCondition) { + this.animateCondition = animateCondition; + return this; + } + + public JFXKeyFrame build() { + JFXKeyFrame jFXKeyFrame = new JFXKeyFrame(); + jFXKeyFrame.duration = this.duration; + jFXKeyFrame.keyValues = this.keyValues; + jFXKeyFrame.animateCondition = this.animateCondition; + return jFXKeyFrame; + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java new file mode 100644 index 0000000000..d217d0a82e --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java @@ -0,0 +1,157 @@ +/* + * 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.transitions; + +import javafx.animation.Interpolator; +import javafx.beans.value.WritableValue; + +import java.util.function.Supplier; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-09-21 + */ + +public class JFXKeyValue { + + private WritableValue target; + private Supplier> targetSupplier; + private Supplier endValueSupplier; + private T endValue; + private Supplier animateCondition = () -> true; + private Interpolator interpolator; + + private JFXKeyValue() { + } + + // this builder is created to ensure type inference from method arguments + public static Builder builder() { + return new Builder(); + } + + public T getEndValue() { + return endValue == null ? endValueSupplier.get() : endValue; + } + + public WritableValue getTarget() { + return target == null ? targetSupplier.get() : target; + } + + public Interpolator getInterpolator() { + return interpolator; + } + + public boolean isValid() { + return animateCondition == null || animateCondition.get(); + } + + public static final class Builder{ + public JFXKeyValueBuilder setTarget(WritableValue target) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setTarget(target); + return builder; + } + public JFXKeyValueBuilder setTargetSupplier(Supplier> targetSupplier) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setTargetSupplier(targetSupplier); + return builder; + } + + public JFXKeyValueBuilder setEndValueSupplier(Supplier endValueSupplier) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setEndValueSupplier(endValueSupplier); + return builder; + } + + public JFXKeyValueBuilder setEndValue(T endValue) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setEndValue(endValue); + return builder; + } + + public JFXKeyValueBuilder setAnimateCondition(Supplier animateCondition) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setAnimateCondition(animateCondition); + return builder; + } + + public JFXKeyValueBuilder setInterpolator(Interpolator interpolator) { + JFXKeyValueBuilder builder = new JFXKeyValueBuilder<>(); + builder.setInterpolator(interpolator); + return builder; + } + } + + + public static final class JFXKeyValueBuilder { + + private WritableValue target; + private Supplier> targetSupplier; + private Supplier endValueSupplier; + private T endValue; + private Supplier animateCondition = () -> true; + private Interpolator interpolator = Interpolator.EASE_BOTH; + + private JFXKeyValueBuilder() { + } + + public JFXKeyValueBuilder setTarget(WritableValue target) { + this.target = target; + return this; + } + + public JFXKeyValueBuilder setTargetSupplier(Supplier> targetSupplier) { + this.targetSupplier = targetSupplier; + return this; + } + + public JFXKeyValueBuilder setEndValueSupplier(Supplier endValueSupplier) { + this.endValueSupplier = endValueSupplier; + return this; + } + + public JFXKeyValueBuilder setEndValue(T endValue) { + this.endValue = endValue; + return this; + } + + public JFXKeyValueBuilder setAnimateCondition(Supplier animateCondition) { + this.animateCondition = animateCondition; + return this; + } + + public JFXKeyValueBuilder setInterpolator(Interpolator interpolator) { + this.interpolator = interpolator; + return this; + } + + public JFXKeyValue build() { + JFXKeyValue jFXKeyValue = new JFXKeyValue<>(); + jFXKeyValue.target = this.target; + jFXKeyValue.interpolator = this.interpolator; + jFXKeyValue.targetSupplier = this.targetSupplier; + jFXKeyValue.endValue = this.endValue; + jFXKeyValue.endValueSupplier = this.endValueSupplier; + jFXKeyValue.animateCondition = this.animateCondition; + return jFXKeyValue; + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/utils/JFXNodeUtils.java b/HMCL/src/main/java/com/jfoenix/utils/JFXNodeUtils.java new file mode 100644 index 0000000000..c8e4471819 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/utils/JFXNodeUtils.java @@ -0,0 +1,163 @@ +/* + * 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.animation.PauseTransition; +import javafx.beans.InvalidationListener; +import javafx.beans.value.ObservableValue; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +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 javafx.util.Duration; + +import java.util.Locale; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * @author Shadi Shaheen + * @version 1.0 + * @since 2017-02-11 + */ +public 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)).toUpperCase(); + } else { + return null; + } + } + + public static void addPressAndHoldHandler(Node node, Duration holdTime, + EventHandler handler) { + Wrapper eventWrapper = new Wrapper<>(); + PauseTransition holdTimer = new PauseTransition(holdTime); + holdTimer.setOnFinished(event -> handler.handle(eventWrapper.content)); + node.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> { + eventWrapper.content = event; + holdTimer.playFromStart(); + }); + node.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> holdTimer.stop()); + node.addEventHandler(MouseEvent.DRAG_DETECTED, event -> holdTimer.stop()); + } + + public static void addPressAndHoldFilter(Node node, Duration holdTime, + EventHandler handler) { + Wrapper eventWrapper = new Wrapper<>(); + PauseTransition holdTimer = new PauseTransition(holdTime); + holdTimer.setOnFinished(event -> handler.handle(eventWrapper.content)); + node.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { + eventWrapper.content = event; + holdTimer.playFromStart(); + }); + node.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> holdTimer.stop()); + node.addEventFilter(MouseEvent.DRAG_DETECTED, event -> holdTimer.stop()); + } + + public static InvalidationListener addDelayedPropertyInvalidationListener(ObservableValue property, + Duration delayTime, + Consumer consumer) { + Wrapper eventWrapper = new Wrapper<>(); + PauseTransition holdTimer = new PauseTransition(delayTime); + holdTimer.setOnFinished(event -> consumer.accept(eventWrapper.content)); + final InvalidationListener invalidationListener = observable -> { + eventWrapper.content = property.getValue(); + holdTimer.playFromStart(); + }; + property.addListener(invalidationListener); + return invalidationListener; + } + + public static InvalidationListener addDelayedPropertyInvalidationListener(ObservableValue property, + Duration delayTime, + BiConsumer consumer) { + Wrapper eventWrapper = new Wrapper<>(); + PauseTransition holdTimer = new PauseTransition(delayTime); + final InvalidationListener invalidationListener = observable -> { + eventWrapper.content = property.getValue(); + holdTimer.playFromStart(); + }; + holdTimer.setOnFinished(event -> consumer.accept(eventWrapper.content, invalidationListener)); + property.addListener(invalidationListener); + return invalidationListener; + } + + + public static InvalidationListener addDelayedPropertyInvalidationListener(ObservableValue property, + Duration delayTime, + Consumer justInTimeConsumer, + Consumer delayedConsumer) { + Wrapper eventWrapper = new Wrapper<>(); + PauseTransition holdTimer = new PauseTransition(delayTime); + holdTimer.setOnFinished(event -> delayedConsumer.accept(eventWrapper.content)); + final InvalidationListener invalidationListener = observable -> { + eventWrapper.content = property.getValue(); + justInTimeConsumer.accept(eventWrapper.content); + holdTimer.playFromStart(); + }; + property.addListener(invalidationListener); + return invalidationListener; + } + + + public static EventHandler addDelayedEventHandler(Node control, Duration delayTime, + final EventType eventType, + final EventHandler eventHandler) { + Wrapper eventWrapper = new Wrapper<>(); + PauseTransition holdTimer = new PauseTransition(delayTime); + holdTimer.setOnFinished(finish -> eventHandler.handle(eventWrapper.content)); + final EventHandler eventEventHandler = event -> { + eventWrapper.content = event; + holdTimer.playFromStart(); + }; + control.addEventHandler(eventType, eventEventHandler); + return eventEventHandler; + } + + private static class Wrapper { + T content; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/validation/DoubleValidator.java b/HMCL/src/main/java/com/jfoenix/validation/DoubleValidator.java new file mode 100644 index 0000000000..9943f6ee2a --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/validation/DoubleValidator.java @@ -0,0 +1,66 @@ +/* + * 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.validation; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.DefaultProperty; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputControl; + +/** + * Double field validation, that is applied on text input controls + * such as {@link TextField} and {@link TextArea} + * + * @author eralpsahin + * @version 1.0 + * @since 2017-01-27 + */ +@DefaultProperty(value = "icon") +public class DoubleValidator extends ValidatorBase { + + public DoubleValidator() { + setMessage("Value must be a rational number"); + } + + public DoubleValidator(String message) { + super(message); + } + + /** + * {@inheritDoc} + */ + @Override + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + evalTextInputField(); + } + } + + private void evalTextInputField() { + TextInputControl textField = (TextInputControl) srcControl.get(); + try { + Double.parseDouble(textField.getText()); + hasErrors.set(false); + } catch (Exception e) { + hasErrors.set(true); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/validation/IntegerValidator.java b/HMCL/src/main/java/com/jfoenix/validation/IntegerValidator.java new file mode 100644 index 0000000000..35cb5eae89 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/validation/IntegerValidator.java @@ -0,0 +1,70 @@ +/* + * 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.validation; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.DefaultProperty; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputControl; + +/** + * An example of Number field validation, that is applied on text input controls + * such as {@link TextField} and {@link TextArea} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value = "icon") +public class IntegerValidator extends ValidatorBase { + + public IntegerValidator() { + setMessage("Value must be a number"); + } + + public IntegerValidator(String message) { + super(message); + } + + /** + * {@inheritDoc} + */ + @Override + + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + evalTextInputField(); + } + } + + private void evalTextInputField() { + TextInputControl textField = (TextInputControl) srcControl.get(); + String text = textField.getText(); + try { + hasErrors.set(false); + if (!text.isEmpty()) { + Integer.parseInt(text); + } + } catch (Exception e) { + hasErrors.set(true); + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/validation/NumberValidator.java b/HMCL/src/main/java/com/jfoenix/validation/NumberValidator.java new file mode 100644 index 0000000000..8bb76e417e --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/validation/NumberValidator.java @@ -0,0 +1,114 @@ +/* + * 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.validation; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.DefaultProperty; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputControl; +import javafx.util.converter.NumberStringConverter; + +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; + +/** + * An example of Number field validation, that is applied on text input controls + * such as {@link TextField} and {@link TextArea} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value = "icon") +public class NumberValidator extends ValidatorBase { + + private NumberStringConverter numberStringConverter = new NumberStringConverter(){ + @Override + public Number fromString(String string) { + try { + if (string == null) { + return null; + } + string = string.trim(); + if (string.isEmpty()) { + return null; + } + // Create and configure the parser to be used + NumberFormat parser = getNumberFormat(); + ParsePosition parsePosition = new ParsePosition(0); + Number result = parser.parse(string, parsePosition); + final int index = parsePosition.getIndex(); + if (index == 0 || index < string.length()) { + throw new ParseException("Unparseable number: \"" + string + "\"", parsePosition.getErrorIndex()); + } + return result; + } catch (ParseException ex) { + throw new RuntimeException(ex); + } + } + }; + + public NumberValidator() { } + + public NumberValidator(String message) { + super(message); + } + + public NumberValidator(NumberStringConverter numberStringConverter) { + this.numberStringConverter = numberStringConverter; + } + + public NumberValidator(String message, NumberStringConverter numberStringConverter) { + super(message); + this.numberStringConverter = numberStringConverter; + } + + /** + * {@inheritDoc} + */ + @Override + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + evalTextInputField(); + } + } + + private void evalTextInputField() { + TextInputControl textField = (TextInputControl) srcControl.get(); + String text = textField.getText(); + try { + hasErrors.set(false); + if (!text.isEmpty()) + numberStringConverter.fromString(text); + } catch (Exception e) { + hasErrors.set(true); + } + } + + public NumberStringConverter getNumberStringConverter() { + return numberStringConverter; + } + + public void setNumberStringConverter(NumberStringConverter numberStringConverter) { + this.numberStringConverter = numberStringConverter; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/validation/RegexValidator.java b/HMCL/src/main/java/com/jfoenix/validation/RegexValidator.java new file mode 100644 index 0000000000..1fa78ae380 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/validation/RegexValidator.java @@ -0,0 +1,86 @@ +/* + * 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.validation; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.DefaultProperty; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputControl; + +import java.util.regex.Pattern; + + +/** + * Regex validation, that is applied on text input controls + * such as {@link TextField} and {@link TextArea}. + * + * @version 1.0 + * @since 2018-08-06 + */ +@DefaultProperty(value = "icon") +public class RegexValidator extends ValidatorBase { + + private String regexPattern; + + public RegexValidator(String message) { + super(message); + } + + public RegexValidator() { + + } + + + private Pattern regexPatternCompiled; + + /** + * {@inheritDoc} + */ + @Override + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + evalTextInputField(); + } + } + + private void evalTextInputField() { + TextInputControl textField = (TextInputControl) srcControl.get(); + String text = (textField.getText() == null) ? "" : textField.getText(); // Treat null like empty string + + if (regexPatternCompiled.matcher(text).matches()) { + hasErrors.set(false); + } else { + hasErrors.set(true); + } + } + + /* + * GETTER AND SETTER + */ + public void setRegexPattern(String regexPattern) { + this.regexPattern = regexPattern; + this.regexPatternCompiled = Pattern.compile(regexPattern); + } + + public String getRegexPattern() { + return regexPattern; + } +} diff --git a/HMCL/src/main/java/com/jfoenix/validation/RequiredFieldValidator.java b/HMCL/src/main/java/com/jfoenix/validation/RequiredFieldValidator.java new file mode 100644 index 0000000000..4ca39fa518 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/validation/RequiredFieldValidator.java @@ -0,0 +1,73 @@ +/* + * 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.validation; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.DefaultProperty; +import javafx.scene.control.ComboBoxBase; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputControl; + +/** + * An example of required field validation, that is applied on text input + * controls such as {@link TextField} and {@link TextArea} + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +@DefaultProperty(value = "icon") +public class RequiredFieldValidator extends ValidatorBase { + + public RequiredFieldValidator(String message) { + super(message); + } + + public RequiredFieldValidator() { + } + + /** + * {@inheritDoc} + */ + @Override + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + evalTextInputField(); + } + if (srcControl.get() instanceof ComboBoxBase) { + evalComboBoxField(); + } + } + + private void evalTextInputField() { + TextInputControl textField = (TextInputControl) srcControl.get(); + if (textField.getText() == null || textField.getText().isEmpty()) { + hasErrors.set(true); + } else { + hasErrors.set(false); + } + } + + private void evalComboBoxField() { + ComboBoxBase comboField = (ComboBoxBase) srcControl.get(); + Object value = comboField.getValue(); + hasErrors.set(value == null || value.toString().isEmpty()); + } +} diff --git a/HMCL/src/main/java/com/jfoenix/validation/StringLengthValidator.java b/HMCL/src/main/java/com/jfoenix/validation/StringLengthValidator.java new file mode 100644 index 0000000000..ec07b9bfd7 --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/validation/StringLengthValidator.java @@ -0,0 +1,101 @@ +/* + * 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.validation; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.scene.control.TextInputControl; + +/** + * @author Victor Espino + * @version 1.0 + * @since 2019-08-10 + */ +public class StringLengthValidator extends ValidatorBase { + + int StringLength; + + /** + * Basic constructor with Default message this way: + * "Max length is " + StringLength +" character(s) " + * + * @param StringLengh Length of the string in the input field to validate. + */ + public StringLengthValidator(int StringLengh) { + super("Max length is " + StringLengh + " character(s) "); + this.StringLength = StringLengh + 1; + } + + + /** + * The displayed message shown will be concatenated by the message with StringLength + * this way "message" + StringLength. + * + * @param StringLength Length of the string in the input field to validate. + * @param message Message to show. + */ + public StringLengthValidator(int StringLength, String message) { + this.StringLength = StringLength + 1; + setMessage(message + StringLength); + } + + /** + * The displayed message will be personalized, + * but still need to indicate the StringLength to validate. + * + * @param StringLength Length of the string in the input field to validate. + * @param message Message to show. + */ + public StringLengthValidator(String message, int StringLength) { + super(message); + this.StringLength = StringLength + 1; + } + + public void changeStringLength(int newLength) { + this.StringLength = newLength + 1; + } + + public int getStringLength() { + return StringLength - 1; + } + + /** + * {@inheritDoc} + */ + @Override + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + evalTextInputField(); + } + } + + private void evalTextInputField() { + TextInputControl textField = (TextInputControl) srcControl.get(); + String text = textField.getText(); + hasErrors.set(false); + + if (!text.isEmpty()) { + if (text.length() > StringLength - 1) { + hasErrors.set(true); + // textField.textProperty().set(text.substring(0, 19)); + + } + } + } +} diff --git a/HMCL/src/main/java/com/jfoenix/validation/base/ValidatorBase.java b/HMCL/src/main/java/com/jfoenix/validation/base/ValidatorBase.java new file mode 100644 index 0000000000..be79529ded --- /dev/null +++ b/HMCL/src/main/java/com/jfoenix/validation/base/ValidatorBase.java @@ -0,0 +1,317 @@ +/* + * 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.validation.base; + +import com.jfoenix.validation.RegexValidator; +import com.jfoenix.validation.RequiredFieldValidator; +import javafx.beans.property.*; +import javafx.css.PseudoClass; +import javafx.scene.Node; +import javafx.scene.control.Control; +import javafx.scene.control.Tooltip; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +/** + * An abstract class that defines the basic validation functionality for a certain control. + * + * @author Shadi Shaheen + * @version 1.0 + * @since 2016-03-09 + */ +public abstract class ValidatorBase { + + /** + * This {@link PseudoClass} will be activated when a validation error occurs. + *

+ * Some components have default styling for this pseudo class. See {@code jfx-text-field.css} + * and {@code jfx-combo-box.css} for examples. + */ + public static final PseudoClass PSEUDO_CLASS_ERROR = PseudoClass.getPseudoClass("error"); + + /** + * When using {@code Tooltip.install(node, tooltip)}, the given tooltip is stored in the Node's properties + * under this key. + * + * @see Tooltip#install(Node, Tooltip) + */ + private static final String TOOLTIP_PROP_KEY = "javafx.scene.control.Tooltip"; + + /** + * Default error tooltip style class + */ + public static final String ERROR_TOOLTIP_STYLE_CLASS = "error-tooltip"; + + /** + * Key used to stash control tooltip upon validation + */ + private static final String TEMP_TOOLTIP_KEY = "stashed-tootlip"; + + /** + * supported tooltips keys + */ + private static final Set supportedTooltipKeys = new HashSet<>( + Arrays.asList( + "javafx.scene.control.Tooltip", + "jfoenix-tooltip" + ) + ); + + /** + * @param message will be set as the validator's {@link #message}. + * @see #ValidatorBase() + */ + public ValidatorBase(String message) { + this(); + this.setMessage(message); + } + + /** + * When creating a new validator you need to define the validation condition by implementing {@link #eval()}. + *

+ * For examples of how you might implement it, see {@link RequiredFieldValidator} and + * {@link RegexValidator}. + */ + public ValidatorBase() { + + } + + /////////////////////////////////////////////////////////////////////////// + // Methods + /////////////////////////////////////////////////////////////////////////// + + /** + * Will validate the source control. + *

+ * Calls {@link #eval()} and then {@link #onEval()}. + */ + public void validate() { + eval(); + onEval(); + } + + /** + * Should evaluate the validation condition and set {@link #hasErrors} to true or false. It should + * be true when the value is invalid (it has errors) and false when the value is valid (no errors). + *

+ * This method is fired once {@link #validate()} is called. + */ + protected abstract void eval(); + + /** + * This method will update the source control after evaluating the validation condition (see {@link #eval()}). + *

+ * If the validator isn't "passing" the {@link #PSEUDO_CLASS_ERROR :error} pseudoclass is applied to the + * {@link #srcControl}. + *

+ * Applies the {@link #PSEUDO_CLASS_ERROR :error} pseudo class and the errorTooltip to + * the {@link #srcControl}. + */ + protected void onEval() { + Node control = getSrcControl(); + boolean invalid = hasErrors.get(); + control.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, invalid); + Tooltip activeTooltip = getActiveTooltip(control); + if (invalid) { + Tooltip errorTooltip = errorTooltipSupplier.get(); + errorTooltip.getStyleClass().add(ERROR_TOOLTIP_STYLE_CLASS); + errorTooltip.setText(getMessage()); + install(control, activeTooltip, errorTooltip); + } else { + Tooltip orgTooltip = (Tooltip) control.getProperties().remove(TEMP_TOOLTIP_KEY); + install(control, activeTooltip, orgTooltip); + } + } + + private Tooltip getActiveTooltip(Node node) { + Tooltip tooltip = null; + for (String key : supportedTooltipKeys) { + tooltip = (Tooltip) node.getProperties().get(key); + if (tooltip != null) { + break; + } + } + return tooltip; + } + + private void install(Node node, Tooltip oldVal, Tooltip newVal) { + // stash old tooltip if it's not error tooltip + if (oldVal != null && !oldVal.getStyleClass().contains(ERROR_TOOLTIP_STYLE_CLASS)) { + node.getProperties().put(TEMP_TOOLTIP_KEY, oldVal); + } + if (node instanceof Control) { + // uninstall + if (oldVal != null) { + ((Control) node).setTooltip(newVal); + return; + } + // install + ((Control) node).setTooltip(newVal); + } else { + uninstall(node, oldVal); + install(node, newVal); + } + } + + private void uninstall(Node node, Tooltip tooltip) { + Tooltip.uninstall(node, tooltip); + } + + private void install(Node node, Tooltip tooltip) { + if (tooltip == null) { + return; + } + Tooltip.install(node, tooltip); + } + + /////////////////////////////////////////////////////////////////////////// + // Properties + /////////////////////////////////////////////////////////////////////////// + + /** + * The {@link Control}/{@link Node} that the validator is checking the value of. + *

+ * Supports {@link Node}s because not all things that need validating are {@link Control}s. + */ + protected SimpleObjectProperty srcControl = new SimpleObjectProperty<>(); + + /** + * @see #srcControl + */ + public void setSrcControl(Node srcControl) { + this.srcControl.set(srcControl); + } + + /** + * @see #srcControl + */ + public Node getSrcControl() { + return this.srcControl.get(); + } + + /** + * @see #srcControl + */ + public ObjectProperty srcControlProperty() { + return this.srcControl; + } + + /** + * Tells whether the validator is "passing" or not. + *

+ * In a validator's implementation of {@link #eval()}, if the value the validator is checking is invalid, it should + * set this to true. If the value is valid, it should set this to false. + *

+ * When hasErrors is true, the validator will automatically apply the {@link #PSEUDO_CLASS_ERROR :error} + * pseudoclass to the {@link #srcControl}; the {@link #srcControl} will also have a {@link Tooltip} containing the + * {@link #message} applied to it. + */ + protected ReadOnlyBooleanWrapper hasErrors = new ReadOnlyBooleanWrapper(false); + + /** + * @see #hasErrors + */ + public boolean getHasErrors() { + return hasErrors.get(); + } + + /** + * @see #hasErrors + */ + public ReadOnlyBooleanProperty hasErrorsProperty() { + return hasErrors.getReadOnlyProperty(); + } + + private Supplier errorTooltipSupplier = Tooltip::new; + + public Supplier getErrorTooltipSupplier() { + return errorTooltipSupplier; + } + + public void setErrorTooltipSupplier(Supplier errorTooltipSupplier) { + this.errorTooltipSupplier = errorTooltipSupplier; + } + + /** + * The error message to display when the validator is not "passing." + *

+ * When {@link #hasErrors} is true, this message is displayed near the {@link #srcControl} (usually below); + * it's also displayed in a {@link Tooltip} applied to the {@link #srcControl}. + */ + protected SimpleStringProperty message = new SimpleStringProperty(); + + /** + * @see #message + */ + public void setMessage(String msg) { + this.message.set(msg); + } + + /** + * @see #message + */ + public String getMessage() { + return this.message.get(); + } + + /** + * @see #message + */ + public StringProperty messageProperty() { + return this.message; + } + + + /***** Icon *****/ + protected SimpleObjectProperty> iconSupplier = new SimpleObjectProperty<>(); + + public void setIconSupplier(Supplier icon) { + this.iconSupplier.set(icon); + } + + public SimpleObjectProperty> iconSupplierProperty() { + return this.iconSupplier; + } + + public Supplier getIconSupplier() { + return iconSupplier.get(); + } + + /** + * allow setting icon in FXML + */ + public void setIcon(Node icon) { + iconSupplier.set(() -> icon); + } + + public Node getIcon() { + if (iconSupplier.get() == null) { + return null; + } + Node icon = iconSupplier.get().get(); + if (icon != null) { + icon.getStyleClass().add("error-icon"); + } + return icon; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 7b6692823d..a255347b06 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -87,6 +87,7 @@ import java.util.List; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; @@ -148,6 +149,26 @@ public static void runInFX(Runnable runnable) { } } + public static void runInFXAndWait(Runnable runnable) { + if (Platform.isFxApplicationThread()) { + runnable.run(); + return; + } + final CountDownLatch doneLatch = new CountDownLatch(1); + Platform.runLater(() -> { + try { + runnable.run(); + } finally { + doneLatch.countDown(); + } + }); + try { + doneLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + public static void checkFxUserThread() { if (!Platform.isFxApplicationThread()) { throw new IllegalStateException("Not on FX application thread; currentThread = " diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index 7510126ac8..da50c5e973 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXRadioButton; import com.jfoenix.effects.JFXDepthManager; import javafx.beans.binding.Bindings; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; import javafx.scene.canvas.Canvas; @@ -185,7 +186,7 @@ public AccountListItemSkin(AccountListItem skinnable) { root.setRight(right); root.getStyleClass().add("card"); - root.setStyle("-fx-padding: 8 8 8 0;"); + root.setPadding(new Insets(8)); JFXDepthManager.setDepth(root, 1); getChildren().setAll(root); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java index 8e5fb183a5..4dd327473f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java @@ -17,12 +17,12 @@ */ package org.jackhuang.hmcl.ui.construct; -import com.jfoenix.controls.JFXColorPicker; import com.jfoenix.controls.JFXRadioButton; import javafx.beans.NamedArg; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.scene.Node; +import javafx.scene.control.ColorPicker; import javafx.scene.control.Label; import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; @@ -43,7 +43,7 @@ public class MultiColorItem extends ComponentList { private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file")); private final ToggleGroup group = new ToggleGroup(); - private final JFXColorPicker colorPicker = new JFXColorPicker(); + private final ColorPicker colorPicker = new ColorPicker(); private final JFXRadioButton radioCustom = new JFXRadioButton(); private final BorderPane custom = new BorderPane(); private final VBox pane = new VBox(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java index c8d0ec9205..44981a82d6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java @@ -113,9 +113,8 @@ protected SkinBase createDefaultSkin() { } private static final class Skin extends SkinBase { - private final JFXSpinner spinner = new JFXSpinner(); private final StackPane contentPane = new StackPane(); - private final StackPane topPane = new StackPane(); + private final StackPane topPane = new StackPane(new JFXSpinner()); private final TransitionPane root = new TransitionPane(); private final StackPane failedPane = new StackPane(); private final Label failedReasonLabel = new Label(); @@ -125,7 +124,6 @@ private static final class Skin extends SkinBase { Skin(SpinnerPane control) { super(control); - topPane.getChildren().setAll(spinner); topPane.getStyleClass().add("notice-pane"); failedPane.getStyleClass().add("notice-pane"); failedPane.getChildren().setAll(failedReasonLabel); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java index 6b3a65a711..3d1aee281a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java @@ -42,13 +42,14 @@ public static Consumer> addTo(JFXTextField control, String mes return predicate -> { Validator validator = new Validator(message, predicate); InvalidationListener listener = any -> control.validate(); - validator.getProperties().put(validator, listener); + validator.holder = listener; control.textProperty().addListener(new WeakInvalidationListener(listener)); control.getValidators().add(validator); }; } private final Predicate validator; + private InvalidationListener holder; /** * @param validator return true if the input string is valid. diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index b1a1fb3dd7..d54d80c42d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -37,6 +37,7 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.paint.Color; +import javafx.scene.text.Text; import javafx.stage.Stage; import javafx.util.Duration; import org.jackhuang.hmcl.Launcher; @@ -446,7 +447,7 @@ private void closeDialog(Node node) { // ==== Toast ==== public void showToast(String content) { - decorator.getSnackbar().fireEvent(new JFXSnackbar.SnackbarEvent(content, null, 2000L, false, null)); + decorator.getSnackbar().fireEvent(new JFXSnackbar.SnackbarEvent(new Text(content), Duration.millis(2000L))); } // ==== Wizard ==== diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index ea0f1c68fe..3b0485d933 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -126,12 +126,11 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow centrePane = new ComponentList(); centrePane.getStyleClass().add("no-padding"); { - HBox checkPane = new HBox(); - checkPane.setSpacing(10); + HBox checkPane = new HBox(10); { chkRelease = new JFXCheckBox(i18n("version.game.releases")); chkRelease.setSelected(true); - HBox.setMargin(chkRelease, new Insets(10, 0, 10, 0)); + HBox.setMargin(chkRelease, new Insets(10, 0, 10, 10)); chkSnapshot = new JFXCheckBox(i18n("version.game.snapshots")); HBox.setMargin(chkSnapshot, new Insets(10, 0, 10, 0)); diff --git a/HMCL/src/main/java11/org/jackhuang/hmcl/util/JavaFXPatcher.java b/HMCL/src/main/java9/org/jackhuang/hmcl/util/JavaFXPatcher.java similarity index 100% rename from HMCL/src/main/java11/org/jackhuang/hmcl/util/JavaFXPatcher.java rename to HMCL/src/main/java9/org/jackhuang/hmcl/util/JavaFXPatcher.java diff --git a/HMCL/src/main/java11/org/jackhuang/hmcl/util/ModuleHelper.java b/HMCL/src/main/java9/org/jackhuang/hmcl/util/ModuleHelper.java similarity index 100% rename from HMCL/src/main/java11/org/jackhuang/hmcl/util/ModuleHelper.java rename to HMCL/src/main/java9/org/jackhuang/hmcl/util/ModuleHelper.java diff --git a/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java b/HMCL/src/main/java9/org/jackhuang/hmcl/util/logging/CallerFinder.java similarity index 100% rename from HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java rename to HMCL/src/main/java9/org/jackhuang/hmcl/util/logging/CallerFinder.java diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index e836437e54..1faa0413ae 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -33,8 +33,8 @@ } .title-label { - -fx-font-size: 16.0px; - -fx-padding: 14.0px; + -fx-font-size: 16px; + -fx-padding: 14px; -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); } @@ -421,16 +421,16 @@ .dialog-trigger { -fx-background-color: WHITE; -jfx-button-type: RAISED; - -fx-font-size: 14.0px; + -fx-font-size: 14px; } .jfx-dialog-layout { - -fx-padding: 24.0px 24.0px 16.0px 24.0px; + -fx-padding: 24px 24px 16px 24px; -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); } .jfx-layout-heading { - -fx-font-size: 20.0px; + -fx-font-size: 20px; -fx-alignment: center-left; -fx-padding: 5.0 0.0 5.0 0.0; } @@ -442,7 +442,7 @@ .jfx-layout-actions { -fx-pref-width: 400; - -fx-padding: 10.0px 0.0 0.0 0.0; + -fx-padding: 10px 0.0 0.0 0.0; -fx-alignment: center-right; } @@ -476,7 +476,7 @@ } .popup-list-view { - -fx-pref-width: 150.0px; + -fx-pref-width: 150px; } .jfx-snackbar-content { @@ -634,7 +634,7 @@ } .jfx-radio-button .radio { - -fx-stroke-width: 2.0px; + -fx-stroke-width: 2px; -fx-fill: transparent; } @@ -671,6 +671,10 @@ -fx-background-color: -fx-base-color; } +.jfx-slider .colored-track { + -fx-background-color: -fx-base-color; +} + /******************************************************************************* * * * JFX Rippler * @@ -970,7 +974,7 @@ } .toggle-label { - -fx-font-size: 14.0px; + -fx-font-size: 14px; } .toggle-icon1 .icon { diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-alert.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-alert.css new file mode 100644 index 0000000000..3eb4814ad8 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-alert.css @@ -0,0 +1,28 @@ +/** + * 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. + */ + +.jfx-alert-overlay{ + -fx-background-color: #00000011; + -fx-padding: 0; +} + +.jfx-alert-overlay > .depth-container > .jfx-alert-content-container, +.jfx-alert-overlay > .jfx-alert-content-container{ + -fx-background-color: WHITE; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-button.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-button.css new file mode 100644 index 0000000000..ca01318eca --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-button.css @@ -0,0 +1,86 @@ +/** + * 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. + */ + +.jfx-button:armed, +.jfx-button:hover, +.jfx-button:focused, +.jfx-button { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-background-radius: 3px; + -fx-background-insets: 0px; +} + + +/******************************************************************************* + * * + * JFXChipView * + * * + ******************************************************************************/ + +.jfx-chip-view .jfx-chip .close-button { + -fx-background-color: #A6A6A6; + -fx-pref-width: 20; + -fx-min-width: -fx-pref-width; + -fx-max-width: -fx-pref-width; + -fx-pref-height: 20; + -fx-min-height: -fx-pref-height; + -fx-max-height: -fx-pref-height; + -fx-background-radius: 20; +} + +.jfx-chip-view .jfx-chip .close-button .jfx-rippler { + -jfx-rippler-fill: BLACK; +} + +.jfx-chip-view .jfx-chip .close-button .jfx-svg-glyph { + -fx-shape: "M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"; + -fx-background-color: #E0E0E0; + -jfx-size: 8; +} + + +/******************************************************************************* + * * + * JFXTabPane * + * * + ******************************************************************************/ + +.jfx-tab-pane .headers-region .tab .tab-container .tab-close-button { + -fx-background-color: TRANSPARENT; + -fx-pref-width: 24; + -fx-pref-height: 24; + -fx-min-width: -fx-pref-width; + -fx-max-width: -fx-pref-width; + -fx-min-height: -fx-pref-height; + -fx-max-height: -fx-pref-height; +} + +.jfx-tab-pane .headers-region .tab .tab-container .tab-close-button .jfx-rippler{ + -jfx-rippler-fill: WHITE; +} + +.jfx-tab-pane .headers-region .tab .tab-container .tab-close-button > .jfx-svg-glyph { + -fx-shape: "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z"; + -jfx-size: 12; + -fx-background-color: rgba(255, 255, 255, .87); +} + +.jfx-tab-pane .headers-region .tab:selected .tab-container .tab-close-button > .jfx-svg-glyph { + -fx-background-color: WHITE; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-check-box.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-check-box.css new file mode 100644 index 0000000000..ad523ad850 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-check-box.css @@ -0,0 +1,72 @@ +/** + * 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. + */ + +.jfx-check-box { + -jfx-unchecked-color: #5A5A5A; + -jfx-checked-color: #0F9D58; +} + +.jfx-check-box .box-container { + -fx-shape: "M 400 100 L 400 100 A 50 50 0 1 1 400 250 A 50 50 0 1 1 400 100 "; + -fx-padding: 10; +} + +.jfx-check-box .box, +.jfx-check-box:indeterminate .box, +.jfx-check-box:indeterminate:selected .box{ + -fx-pref-width: 18; + -fx-pref-height: 18; + + -fx-min-width: -fx-pref-width; + -fx-max-width: -fx-pref-width; + -fx-min-height: -fx-pref-height; + -fx-max-height: -fx-pref-height; + + -fx-background-color: TRANSPARENT; + -fx-background-radius: 2; + + -fx-border-color: -jfx-unchecked-color; + -fx-border-style: solid; + -fx-border-radius: 2; + -fx-border-width: 2; +} + +.jfx-check-box:selected .box { + -fx-border-color: -jfx-checked-color; +} + +.jfx-check-box .mark { + -fx-shape: "M384 690l452-452 60 60-512 512-238-238 60-60z"; + -fx-max-width: 15; + -fx-max-height: 12; + + -fx-background-color: WHITE; + + -fx-border-color: WHITE; + -fx-border-width: 2; + -fx-border-radius: 2; +} + +.jfx-check-box .indeterminate-mark{ + -fx-background-color: -jfx-checked-color; + -fx-background-radius: 2; + -fx-border-width: 0; + -fx-max-width: 10; + -fx-max-height: 10; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-chip-view.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-chip-view.css new file mode 100644 index 0000000000..a5590335ad --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-chip-view.css @@ -0,0 +1,110 @@ +/** + * 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. + */ + +.jfx-chip-view { + -fx-background-color: TRANSPARENT; + -fx-pref-width: 100px; + -fx-pref-height: 100px; +} + +.jfx-chip-view .chips-pane { + -fx-background-color: TRANSPARENT; + -fx-vgap: 4; + -fx-hgap: 6; +} + +.jfx-chip-view .chips-pane > .text-area, +.jfx-chip-view .chips-pane > .text-area .scroll-pane, +.jfx-chip-view .chips-pane > .text-area .scroll-pane > .viewport, +.jfx-chip-view .chips-pane > .text-area:focused, +.jfx-chip-view .chips-pane > .text-area:focused .scroll-pane { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; +} + +.jfx-chip-view .chips-pane > .text-area { + -fx-padding: 0.333333em 0 0.333333em 0; + -fx-min-width: 100px; + -fx-pref-height: 42; +} + +.jfx-chip-view .chips-pane > .text-area .content, +.jfx-chip-view .chips-pane > .text-area:focused .content { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-padding: 0 0 0 0; +} + +.jfx-chip-view .jfx-chip > HBox { + -fx-font-family: "Roboto Medium"; + -fx-font-size: 14.0; + -fx-background-color: #E0E0E0; + -fx-background-radius: 40px; + -fx-padding: 5px 8px 5px 12px; + -fx-pref-height: 32px; + -fx-alignment: center-left; + -fx-spacing: 8; +} + +.jfx-chip-view > .scroll-pane, +.jfx-chip-view > .scroll-pane > .viewport { + -fx-background-color: TRANSPARENT; +} + +.jfx-chip-view > .scroll-pane > .scroll-bar:horizontal, +.jfx-chip-view > .scroll-pane > .scroll-bar:vertical { + -fx-background-color: TRANSPARENT; +} + +.jfx-chip-view > .scroll-pane > .scroll-bar:horizontal .track, +.jfx-chip-view > .scroll-pane > .scroll-bar:vertical .track { + -fx-background-color: TRANSPARENT; + -fx-border-color: TRANSPARENT; + -fx-background-radius: 0em; + -fx-border-radius: 2em; +} + +.jfx-chip-view > .scroll-pane > .scroll-bar:horizontal .thumb, +.jfx-chip-view > .scroll-pane > .scroll-bar:vertical .thumb { + -fx-background-color: #898989; + -fx-background-insets: 2, 0, 0; + -fx-background-radius: 2em; +} + +.jfx-chip-view > .scroll-pane > .corner { + -fx-background-color: TRANSPARENT; +} + +.jfx-chip-view > .scroll-pane > .scroll-bar:horizontal .increment-button, +.jfx-chip-view > .scroll-pane > .scroll-bar:horizontal .decrement-button { + -fx-background-color: transparent; + -fx-background-radius: 0em; + -fx-padding: 0 0 10 0; +} + +.jfx-chip-view > .scroll-pane > .scroll-bar:vertical .increment-button, +.jfx-chip-view > .scroll-pane > .scroll-bar:vertical .decrement-button { + -fx-background-color: transparent; + -fx-background-radius: 0em; + -fx-padding: 0 10 0 0; +} + +.jfx-chip-view > .scroll-pane > .scroll-bar .increment-arrow, +.jfx-chip-view > .scroll-pane > .scroll-bar .decrement-arrow { + -fx-shape: " "; + -fx-padding: 0; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-color-picker.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-color-picker.css new file mode 100644 index 0000000000..e13643526f --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-color-picker.css @@ -0,0 +1,34 @@ +/** + * 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. + */ + +.jfx-color-picker:armed, +.jfx-color-picker:hover, +.jfx-color-picker:focused, +.jfx-color-picker { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-background-radius: 3px; + -fx-background-insets: 0px; + -fx-min-height: 25px; +} + +.jfx-color-picker > .jfx-rippler > .color-box { + -fx-background-color: #fafafa; + -fx-background-radius: 3px; + -fx-background-insets: 0px; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-combo-box.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-combo-box.css new file mode 100644 index 0000000000..3b0718c94a --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-combo-box.css @@ -0,0 +1,71 @@ +/** + * 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. + */ + +.jfx-combo-box { + -jfx-focus-color: #4059A9; + -jfx-unfocus-color: #4d4d4d; + -fx-prompt-text-fill: #4d4d4d; +} + +.jfx-combo-box > .input-line { + -fx-background-color: -jfx-unfocus-color; + -fx-pref-height: 1px; + -fx-translate-y: 1px; +} + +.jfx-combo-box > .input-focused-line { + -fx-background-color: -jfx-focus-color; + -fx-pref-height: 2px; +} + +.jfx-combo-box > .prompt-container { + -fx-alignment: center-left; +} + +.jfx-combo-box, +.jfx-combo-box:focused, +.jfx-combo-box:editable, +.jfx-combo-box:editable:focused { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-background-radius: 3px; + -fx-background-insets: 0px; +} + +.jfx-combo-box .combo-box-button-container { + -fx-background-color: TRANSPARENT; +} + +.jfx-combo-box > .arrow-button, +.jfx-combo-box:editable > .arrow-button, +.jfx-combo-box:editable:focused > .arrow-button { + -fx-background-color: TRANSPARENT; +} + +.jfx-combo-box:error { + -jfx-focus-color: #D34336; + -jfx-unfocus-color: #D34336; +} + +.jfx-combo-box .error-label { + -fx-text-fill: #D34336; +} + +.jfx-combo-box .error-icon { + -fx-fill: #D34336; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-date-picker.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-date-picker.css new file mode 100644 index 0000000000..cd8b443068 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-date-picker.css @@ -0,0 +1,84 @@ +/** + * 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. + */ + +.jfx-date-picker { + -jfx-default-color: #009688; +} + +.jfx-date-picker, +.jfx-date-picker:focused, +.jfx-date-picker:focused:editable { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; +} + +.jfx-date-picker > .arrow-button, +.jfx-date-picker:editable > .arrow-button, +.jfx-date-picker:focused > .arrow-button, +.jfx-date-picker:editable:focused > .arrow-button { + -fx-background-color: TRANSPARENT; +} + + +.jfx-date-picker > .arrow-button > .arrow{ + -fx-shape: "M320 384h128v128h-128zM512 384h128v128h-128zM704 384h128v128h-128zM128 768h128v128h-128zM320 768h128v128h-128zM512 768h128v128h-128zM320 576h128v128h-128zM512 576h128v128h-128zM704 576h128v128h-128zM128 576h128v128h-128zM832 0v64h-128v-64h-448v64h-128v-64h-128v1024h960v-1024h-128zM896 960h-832v-704h832v704z"; + -fx-background-color: -jfx-default-color; + -fx-background-insets: 0; + -fx-padding: 10; +} + +.jfx-date-picker > .arrow-button > .jfx-svg-glyph { + -fx-background-color: -jfx-default-color; +} + +.date-picker-popup > .month-year-pane { + -fx-background-color: -jfx-default-color; +} + +.date-picker-popup > * > .spinner { + -fx-spacing: 0.25em; /* 3 */ + -fx-alignment: CENTER; + -fx-fill-height: false; + -fx-background-color: transparent; +} + +.date-picker-popup > * > .spinner > .label { + -fx-alignment: CENTER; +} + +.date-picker-popup > * > .date-picker-list-view > .virtual-flow > .clipped-container > .sheet > .data-picker-list-cell { + -fx-font-size: 16; + -fx-font-weight: NORMAL; + -fx-background-color: WHITE; + -fx-alignment: center; + -fx-text-fill: #313131; +} + +.date-picker-popup > * > .date-picker-list-view > .virtual-flow > .clipped-container > .sheet > .data-picker-list-cell > .jfx-rippler { + -jfx-rippler-fill: grey; +} + +.date-picker-popup > * > .date-picker-list-view > .virtual-flow > .clipped-container > .sheet > .data-picker-list-cell:hover { + -fx-background-color: #EDEDED; +} + +.date-picker-popup > * > .date-picker-list-view > .virtual-flow > .clipped-container > .sheet > .data-picker-list-cell:selected-year { + -fx-font-size: 24; + -fx-font-weight: BOLD; + -fx-text-fill: -jfx-default-color; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-drawer.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-drawer.css new file mode 100644 index 0000000000..56ed99d12d --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-drawer.css @@ -0,0 +1,24 @@ +/** + * 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. + */ + +.jfx-drawer > .jfx-drawer-side-pane { + -fx-border-color: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.1) 20%, rgba(0, 0, 0, 0) 100%); + -fx-border-width: 0px 7px 0px 0px; + -fx-border-insets: 0px -7px 0px 0px; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-password-field.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-password-field.css new file mode 100644 index 0000000000..98aa2f9bf9 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-password-field.css @@ -0,0 +1,59 @@ +/** + * 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. + */ + +.jfx-password-field { + -jfx-focus-color: #4059A9; + -jfx-unfocus-color: #4d4d4d; + -fx-padding: 0.333333em 0 0.333333em 0; +} + +.jfx-password-field > .input-line { + -fx-background-color: -jfx-unfocus-color; + -fx-pref-height: 1px; + -fx-translate-y: 1px; +} + +.jfx-password-field > .input-focused-line{ + -fx-background-color: -jfx-focus-color; + -fx-pref-height: 2px; +} + +.jfx-password-field > .input-focused-line{ + -fx-pref-height: 2px; + -fx-background-color: -jfx-focus-color; +} + +.jfx-password-field, +.jfx-password-field:focused { + -fx-prompt-text-fill: #4D4D4D; + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT; +} + +.jfx-password-field:error { + -jfx-focus-color: #D34336; + -jfx-unfocus-color: #D34336; +} + +.jfx-password-field .error-label { + -fx-text-fill: #D34336; +} + +.jfx-password-field .error-icon { + -fx-fill: #D34336; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-progress-bar.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-progress-bar.css new file mode 100644 index 0000000000..7882baff08 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-progress-bar.css @@ -0,0 +1,39 @@ +/** + * 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. + */ + +.jfx-progress-bar > .track{ + -fx-background-color: #E0E0E0; +} + +.jfx-progress-bar > .bar, +.jfx-progress-bar:indeterminate > .bar{ + -fx-background-color: #0F9D58; + -fx-padding: 1.5; +} + +.jfx-progress-bar > .secondary-bar, +.jfx-progress-bar:indeterminate > .secondary-bar{ + -fx-background-color: #CDE1B7; +} + +.jfx-progress-bar > .track, +.jfx-progress-bar > .bar { + -fx-background-radius: 0; + -fx-background-insets: 0; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-radio-button.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-radio-button.css new file mode 100644 index 0000000000..2dde1f1fa5 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-radio-button.css @@ -0,0 +1,27 @@ +/** + * 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. + */ + +.jfx-radio-button{ + -jfx-selected-color: #0F9D58; + -jfx-unselected-color: #5A5A5A; +} + +.jfx-radio-button .radio-container:hover{ + -fx-cursor: hand; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-slider.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-slider.css new file mode 100644 index 0000000000..3f476c232f --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-slider.css @@ -0,0 +1,71 @@ +/** + * 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. + */ + +.jfx-slider { + -jfx-default-thumb: #0F9D58; + -jfx-default-track: #CCCCCC; +} + +.jfx-slider .track, +.jfx-slider:vertical .track { + -fx-background-color: -jfx-default-track; + -fx-background-radius: 5; + -fx-background-insets: 0; + -fx-pref-width: 2px; + -fx-pref-height: 2px; +} + +.jfx-slider .thumb, +.jfx-slider:focused .thumb { + -fx-background-color: -jfx-default-thumb; + -fx-background-radius: 20; + -fx-background-insets: 0; +} + +.jfx-slider .colored-track { + -fx-background-color: -jfx-default-thumb; + -fx-background-radius: 5; + -fx-background-insets: 0; +} + +.jfx-slider .slider-value { + -fx-stroke: WHITE; + -fx-font-size: 10; +} + +.jfx-slider .animated-thumb { + -fx-pref-width: 30px; + -fx-pref-height: 30px; + -fx-background-color: -jfx-default-thumb; + -fx-background-radius: 50% 50% 50% 0%; + -fx-background-insets: 0; +} + +.jfx-slider:min .thumb, +.jfx-slider:min:focused .thumb { + -fx-background-color: -jfx-default-track; + -fx-background-radius: 20; + -fx-background-insets: 0; +} + +.jfx-slider:min .animated-thumb { + -fx-background-color: -jfx-default-track; + -fx-background-radius: 50% 50% 50% 0%; + -fx-background-insets: 0; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-spinner.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-spinner.css new file mode 100644 index 0000000000..45caea8ec4 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-spinner.css @@ -0,0 +1,26 @@ +/** + * 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. + */ + +.jfx-spinner:determinate .arc{ + -fx-stroke: #0F9D58; +} + +.jfx-spinner:determinate .percentage{ + -fx-fill: #4d4d4d; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tab-pane.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tab-pane.css new file mode 100644 index 0000000000..5a1a3ef361 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tab-pane.css @@ -0,0 +1,82 @@ +/** + * 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. + */ + +.jfx-tab-pane .tab-header-background { + -fx-background-color: #00BCD4; +} + +.jfx-tab-pane .tab-header-area .tab-down-button { + -fx-background-color: WHITE; + -fx-min-width: 0.8em; + -fx-max-width: 0.8em; + -fx-min-height: 1.3em; + -fx-max-height: 1.3em; +} + +.jfx-tab-pane .tab-header-area .control-buttons-tab .jfx-rippler { + -jfx-rippler-fill: WHITE; +} + +.jfx-tab-pane .tab-header-area .tab-down-button:left { + -fx-shape: "M 742,-37 90,614 Q 53,651 53,704.5 53,758 90,795 l 652,651 q 37,37 90.5,37 53.5,0 90.5,-37 l 75,-75 q 37,-37 37,-90.5 0,-53.5 -37,-90.5 L 512,704 998,219 q 37,-38 37,-91 0,-53 -37,-90 L 923,-37 Q 886,-74 832.5,-74 779,-74 742,-37 z"; + +} + +.jfx-tab-pane .tab-header-area .tab-down-button:right { + -fx-shape: "m 1099,704 q 0,-52 -37,-91 L 410,-38 q -37,-37 -90,-37 -53,0 -90,37 l -76,75 q -37,39 -37,91 0,53 37,90 l 486,486 -486,485 q -37,39 -37,91 0,53 37,90 l 76,75 q 36,38 90,38 54,0 90,-38 l 652,-651 q 37,-37 37,-90 z" +} + + +.jfx-tab-pane .headers-region > .tab-selected-line{ + -fx-pref-width: 1px !important; + -fx-pref-height: 2px; +} + +.jfx-tab-pane .headers-region > .tab > .jfx-rippler { + -jfx-rippler-fill: #FFFF8D; +} + +.jfx-tab-pane .headers-region .tab-selected-line { + -fx-background-color: #FFFF8D;; +} + + +.jfx-tab-pane .headers-region .tab:closable { + -fx-border-color: rgba(255,255,255, .87); + -fx-border-width: 0 2 0 0; + -fx-border-style: dotted; + -fx-border-insets: 8 0 8 0; + -fx-padding: 0 8 0 8; +} + +.jfx-tab-pane .headers-region .tab .tab-container .tab-label { + -fx-text-fill: rgba(255,255,255, .87); + -fx-font-weight: BOLD; + -fx-padding: 6 10 6 10; + -fx-font-size: 16; +} +.jfx-tab-pane .headers-region .tab:closable .tab-container .tab-label{ + -fx-padding: 0 8 0 6; +} + + + +.jfx-tab-pane .headers-region .tab:selected .tab-container .tab-label { + -fx-text-fill: WHITE; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-text-area.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-text-area.css new file mode 100644 index 0000000000..8d9559e8ba --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-text-area.css @@ -0,0 +1,66 @@ +/** + * 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. + */ + +.jfx-text-area { + -jfx-focus-color: #4059A9; + -jfx-unfocus-color: #4d4d4d; + -fx-padding: 0.333333em 0 0 0 ; +} + +.jfx-text-area > .input-line { + -fx-background-color: -jfx-unfocus-color; + -fx-pref-height: 1px; + -fx-translate-y: 1px; +} + +.jfx-text-area > .input-focused-line { + -fx-background-color: -jfx-focus-color; + -fx-pref-height: 2px; +} + +.jfx-text-area, +.jfx-text-area:focused { + -fx-prompt-text-fill: #4D4D4D; +} + +.jfx-text-area, +.jfx-text-area .scroll-pane, +.jfx-text-area:focused, +.jfx-text-area:focused .scroll-pane{ + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; +} + +.jfx-text-area .content, +.jfx-text-area:focused .content{ + -fx-background-color:TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-padding: 0 0 0 0; +} + +.jfx-text-area:error { + -jfx-focus-color: #D34336; + -jfx-unfocus-color: #D34336; +} + +.jfx-text-area .error-label { + -fx-text-fill: #D34336; +} + +.jfx-text-area .error-icon { + -fx-fill: #D34336; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-text-field.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-text-field.css new file mode 100644 index 0000000000..5fe6472733 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-text-field.css @@ -0,0 +1,77 @@ +/** + * 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. + */ + +.jfx-text-field { + -jfx-focus-color: #4059A9; + -jfx-unfocus-color: #4d4d4d; + -fx-padding: 0.333333em 0 0.333333em 0; +} + +.jfx-text-field > .input-line { + -fx-background-color: -jfx-unfocus-color; + -fx-pref-height: 1px; + -fx-translate-y: 1px; +} + +.jfx-text-field > .input-focused-line { + -fx-background-color: -jfx-focus-color; + -fx-pref-height: 2px; +} + +.jfx-text-field, +.jfx-text-field:focused { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-prompt-text-fill: #4D4D4D; +} + +.jfx-text-field:error { + -jfx-focus-color: #D34336; + -jfx-unfocus-color: #D34336; +} + +.jfx-text-field .error-label { + -fx-text-fill: #D34336; +} + +.jfx-text-field .error-icon { + -fx-fill: #D34336; +} + +/* -------------- STYLES FOR THE DEFAULT JFXTEXTFIELD-BASED COMBOBOX ------------- */ + +.combo-box-base:editable > .jfx-text-field, +.date-picker > .jfx-text-field, +.jfx-date-picker > .jfx-text-field, +.combo-box-base:editable:focused > .text-field, +.combo-box-base:editable > .text-field:focused, +.date-picker:focused > .text-field, +.date-picker > .text-field:focused { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT; +} + +.jfx-date-picker > .jfx-text-field, +.jfx-time-picker > .jfx-text-field{ + -jfx-focus-color: -jfx-default-color; +} + +.combo-box-base:error > .jfx-text-field, +.combo-box-base:error > .jfx-text-field { + -jfx-focus-color: #D34336; + -jfx-unfocus-color: #D34336; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-time-picker.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-time-picker.css new file mode 100644 index 0000000000..7ae108d3f5 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-time-picker.css @@ -0,0 +1,57 @@ +/** + * 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. + */ + +.jfx-time-picker{ + -jfx-default-color: #009688; +} + +.jfx-time-picker, +.jfx-time-picker:focused, +.jfx-time-picker:focused:editable { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; +} + +.jfx-time-picker > .arrow-button, +.jfx-time-picker:editable > .arrow-button, +.jfx-time-picker:focused > .arrow-button, +.jfx-time-picker:editable:focused > .arrow-button{ + -fx-background-color: TRANSPARENT; +} + +.jfx-time-picker > .arrow-button > .arrow{ + -fx-shape: "M512 310.857v256q0 8-5.143 13.143t-13.143 5.143h-182.857q-8 0-13.143-5.143t-5.143-13.143v-36.571q0-8 5.143-13.143t13.143-5.143h128v-201.143q0-8 5.143-13.143t13.143-5.143h36.571q8 0 13.143 5.143t5.143 13.143zM749.714 512q0-84.571-41.714-156t-113.143-113.143-156-41.714-156 41.714-113.143 113.143-41.714 156 41.714 156 113.143 113.143 156 41.714 156-41.714 113.143-113.143 41.714-156zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"; + -fx-background-color: -jfx-default-color; + -fx-background-insets: 0; + -fx-padding: 10; +} + +.jfx-time-picker > .arrow-button > .jfx-svg-glyph{ + -fx-background-color: -jfx-default-color; +} + +.date-picker-popup > * > .spinner { + -fx-spacing: 0.25em; /* 3 */ + -fx-alignment: CENTER; + -fx-fill-height: false; + -fx-background-color: transparent; +} + +.date-picker-popup > * > .spinner > .label { + -fx-alignment: CENTER; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-toggle-button.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-toggle-button.css new file mode 100644 index 0000000000..a6fb7f1c79 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-toggle-button.css @@ -0,0 +1,59 @@ +/** + * 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. + */ + +.jfx-toggle-button, +.jfx-toggle-button:armed, +.jfx-toggle-button:hover, +.jfx-toggle-button:focused, +.jfx-toggle-button:selected, +.jfx-toggle-button:focused:selected { + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-background-radius: 3px; + -fx-background-insets: 0px; + + -jfx-toggle-color: #009688; + -jfx-untoggle-color: #FAFAFA; + -jfx-toggle-line-color: derive(-jfx-toggle-color, 65%); + -jfx-untoggle-line-color: #999999; + -jfx-size: 10; +} + + +.jfx-toggle-button Line { + -fx-stroke: -jfx-untoggle-line-color; +} +.jfx-toggle-button:selected Line{ + -fx-stroke: -jfx-toggle-line-color; +} + +.jfx-toggle-button Circle{ + -fx-fill: -jfx-untoggle-color; +} + +.jfx-toggle-button:selected Circle{ + -fx-fill: -jfx-toggle-color; +} + +.jfx-toggle-button .jfx-rippler { + -jfx-rippler-fill: -jfx-untoggle-line-color; +} + +.jfx-toggle-button:selected .jfx-rippler { + -jfx-rippler-fill: -jfx-toggle-line-color; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-toggle-node.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-toggle-node.css new file mode 100644 index 0000000000..43f34d62c1 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-toggle-node.css @@ -0,0 +1,29 @@ +/** + * 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. + */ + +.jfx-toggle-node, +.jfx-toggle-node:armed, +.jfx-toggle-node:hover, +.jfx-toggle-node:focused, +.jfx-toggle-node:selected, +.jfx-toggle-node:focused:selected{ + -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -fx-background-radius: 3px; + -fx-background-insets: 0px; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tree-table-view.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tree-table-view.css new file mode 100644 index 0000000000..2e31c0e958 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tree-table-view.css @@ -0,0 +1,130 @@ +/** + * 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. + */ + +.jfx-tree-table-view { + -fx-tree-table-color: rgba(82, 100, 174, 0.4); + -fx-tree-table-rippler-color: rgba(82, 100, 174, 0.6); + -jfx-header-color: #949494; +} +.jfx-tree-table-view:focused { + -fx-background-color: TRANSPARENT, -fx-box-border, -fx-control-inner-background; + -fx-background-insets: -1.4, 0, 1; + -fx-background-radius: 1.4, 0, 0; + -fx-padding: 1; /* 0.083333em; */ +} + +.jfx-tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:selected { + -fx-background-color: -fx-tree-table-color; + -fx-table-cell-border-color: -fx-tree-table-color; + -fx-text-fill: BLACK; +} + +.jfx-tree-table-view:focused .tree-table-row-cell:selected .tree-table-cell { + -fx-text-fill: BLACK; +} + +.jfx-tree-table-view .column-header, +.jfx-tree-table-view .column-header-background, +.jfx-tree-table-view .column-header-background .filler { + -fx-background-color: TRANSPARENT; +} + +.jfx-tree-table-view .column-header { + -fx-border-width: 0 1 0 1; + -fx-border-color: #F3F3F3; +} + +.jfx-tree-table-view .column-header .label { + -fx-text-fill: -jfx-header-color; + -fx-padding: 16 0 16 0; +} + +.jfx-tree-table-view .column-header .arrow, +.jfx-tree-table-view .column-header .sort-order-dot { + -fx-background-color: -jfx-header-color; +} + +.jfx-tree-table-view .column-header:last-visible { + -fx-border-width: 0 2 0 1; +} + +.jfx-tree-table-view .column-header-background { + -fx-border-width: 0 0.0 1 0; + -fx-border-color: #F3F3F3; +} + +.jfx-tree-table-view .tree-table-cell { + -fx-border-width: 0 0 0 0; + -fx-pref-height: 48px; + -fx-alignment: center-left; +} + +.jfx-tree-table-view .column-overlay { + -fx-background-color: -fx-tree-table-color; +} + +.jfx-tree-table-view .column-resize-line, .jfx-tree-table-view .column-drag-header { + -fx-background-color: -fx-tree-table-rippler-color; +} + + +.tree-table-row-cell .tree-table-cell > .tree-disclosure-node > .arrow { + -fx-background-color: -fx-text-fill; + -fx-padding: 0.333333em 0.229em 0.333333em 0.229em; /* 4 */ + -fx-shape: "M 0 -3.5 L 4 0 L 0 3.5 z"; +} + +.tree-table-row-cell:expanded .tree-table-cell > .tree-disclosure-node > .arrow { + -fx-rotate: 90; +} + +.tree-table-row-cell .jfx-text-field, +.tree-table-row-cell .jfx-password-field, +.tree-table-row-cell .jfx-text-area { + -jfx-focus-color: rgb(82, 100, 174); + -fx-background-color: transparent; +} + +.tree-table-row-cell .jfx-text-field:error { + -jfx-focus-color: #D34336; + -jfx-unfocus-color: #D34336; +} + +.tree-table-row-cell .jfx-text-field .error-label { + -fx-text-fill: #D34336; + -fx-font-size: 0.75em; +} + +.tree-table-row-cell .jfx-text-field .error-icon { + -fx-fill: #D34336; + -fx-font-size: 1.0em; +} + +.tree-table-row-cell:grouped { + -fx-background-color: rgb(230, 230, 230); +} + +.jfx-tree-table-view .menu-item:focused { + -fx-background-color: -fx-tree-table-color; + +} + +.jfx-tree-table-view .menu-item .label { + -fx-padding: 5 0 5 0; +} diff --git a/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tree-view-path.css b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tree-view-path.css new file mode 100644 index 0000000000..6f4426e766 --- /dev/null +++ b/HMCL/src/main/resources/com/jfoenix/assets/css/controls/jfx-tree-view-path.css @@ -0,0 +1,38 @@ +/** + * 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. + */ + +.jfx-tree-view-path, +.jfx-tree-view-path:focused{ + -jfx-offset: 10; + -fx-background-insets: 0; + -fx-border-width: 0; + -fx-container-color: WHITE; + -fx-padding: 0; +} + +.jfx-tree-view-path > .viewport, +.jfx-tree-view-path:focused > .viewport{ + -fx-background-color: -fx-container-color; + -fx-background-insets: 0; + -fx-padding: 0; +} +.jfx-tree-view-path .buttons-container{ + -fx-background-color: -fx-container-color; +} + diff --git a/build.gradle.kts b/build.gradle.kts index 2317dbc93c..4e687d9f41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,10 +14,6 @@ subprojects { } repositories { - flatDir { - name = "libs" - dirs = setOf(rootProject.file("lib")) - } mavenCentral() maven(url = "https://jitpack.io") maven(url = "https://libraries.minecraft.net") diff --git a/lib/JFoenix.jar b/lib/JFoenix.jar deleted file mode 100644 index f08adcfacb..0000000000 Binary files a/lib/JFoenix.jar and /dev/null differ