diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f6b1c4ce --- /dev/null +++ b/.gitattributes @@ -0,0 +1,34 @@ +# Java sources +*.java text diff=java +*.kt text diff=java +*.groovy text diff=java +*.scala text diff=java +*.gradle text diff=java +*.gradle.kts text diff=java + +# These files are text and should be normalized (Convert crlf => lf) +*.css text diff=css +*.scss text diff=css +*.sass text +*.df text +*.htm text diff=html +*.html text diff=html +*.js text +*.jsp text +*.jspf text +*.jspx text +*.properties text +*.tld text +*.tag text +*.tagx text +*.xml text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.dll binary +*.ear binary +*.jar binary +*.so binary +*.war binary +*.jks binary \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.run/MaterialFX [build].run.xml b/.run/MaterialFX [build].run.xml old mode 100644 new mode 100755 diff --git a/.run/MaterialFX [clean].run.xml b/.run/MaterialFX [clean].run.xml old mode 100644 new mode 100755 diff --git a/.run/MaterialFX [jlinkZip].run.xml b/.run/MaterialFX [jlinkZip].run.xml old mode 100644 new mode 100755 diff --git a/.run/MaterialFX [materialfx_uploadArchives].run.xml b/.run/MaterialFX [materialfx_uploadArchives].run.xml old mode 100644 new mode 100755 diff --git a/.run/MaterialFX [run].run.xml b/.run/MaterialFX [run].run.xml old mode 100644 new mode 100755 diff --git a/.run/MaterialFX [testrun].run.xml b/.run/MaterialFX [testrun].run.xml old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/ROADMAP.md b/ROADMAP.md old mode 100644 new mode 100755 diff --git a/build.gradle b/build.gradle old mode 100644 new mode 100755 diff --git a/demo/build.gradle b/demo/build.gradle old mode 100644 new mode 100755 index 922bcd73..12c9521d --- a/demo/build.gradle +++ b/demo/build.gradle @@ -24,7 +24,7 @@ dependencies { implementation 'org.kordamp.ikonli:ikonli-core:12.2.0' implementation 'org.kordamp.ikonli:ikonli-javafx:12.2.0' implementation 'org.kordamp.ikonli:ikonli-fontawesome5-pack:12.2.0' - implementation 'io.github.palexdev:virtualizedfx:11.1.3' + implementation 'io.github.palexdev:virtualizedfx:11.2.1' implementation project(':materialfx') } diff --git a/demo/libs/scenicview.jar b/demo/libs/scenicview.jar old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/Demo.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/Demo.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/MFXDemoResourcesLoader.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/MFXDemoResourcesLoader.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/TestDemo.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/TestDemo.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ComboBoxesDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ComboBoxesDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DatePickersDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DatePickersDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DialogsController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DialogsController.java old mode 100644 new mode 100755 index 5537917f..85dc9a9c --- a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DialogsController.java +++ b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DialogsController.java @@ -19,17 +19,13 @@ package io.github.palexdev.materialfx.demo.controllers; import io.github.palexdev.materialfx.controls.MFXButton; -import io.github.palexdev.materialfx.controls.MFXNotification; import io.github.palexdev.materialfx.controls.MFXStageDialog; -import io.github.palexdev.materialfx.controls.SimpleMFXNotificationPane; import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog; -import io.github.palexdev.materialfx.controls.enums.ButtonType; -import io.github.palexdev.materialfx.controls.enums.DialogType; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; -import io.github.palexdev.materialfx.controls.factories.MFXDialogFactory; import io.github.palexdev.materialfx.effects.DepthLevel; -import io.github.palexdev.materialfx.notifications.NotificationPos; -import io.github.palexdev.materialfx.notifications.NotificationsManager; +import io.github.palexdev.materialfx.enums.ButtonType; +import io.github.palexdev.materialfx.enums.DialogType; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXDialogFactory; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -37,9 +33,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; -import javafx.scene.layout.Region; import javafx.stage.Modality; -import javafx.util.Duration; import java.net.URL; import java.util.ResourceBundle; @@ -270,9 +264,10 @@ public class DialogsController implements Initializable { action3.setDepthLevel(DepthLevel.LEVEL1); close.setDepthLevel(DepthLevel.LEVEL1); - action1.setOnAction(event -> NotificationsManager.send(NotificationPos.BOTTOM_RIGHT, createNotification("Action 1 Performed"))); + // TODO remake +/* action1.setOnAction(event -> NotificationsManager.send(NotificationPos.BOTTOM_RIGHT, createNotification("Action 1 Performed"))); action2.setOnAction(event -> NotificationsManager.send(NotificationPos.BOTTOM_RIGHT, createNotification("Action 2 Performed"))); - action3.setOnAction(event -> NotificationsManager.send(NotificationPos.BOTTOM_RIGHT, createNotification("Action 3 Performed"))); + action3.setOnAction(event -> NotificationsManager.send(NotificationPos.BOTTOM_RIGHT, createNotification("Action 3 Performed")));*/ dialog.addCloseButton(close); HBox box = new HBox(20, action1, action2, action3, close); @@ -280,15 +275,4 @@ public class DialogsController implements Initializable { box.setPadding(new Insets(20, 5, 20, 5)); return box; } - - private MFXNotification createNotification(String text) { - Region notificationPane = new SimpleMFXNotificationPane( - "Dialogs Actions Test", - "", - text - ); - MFXNotification notification = new MFXNotification(notificationPane, true, true); - notification.setHideAfterDuration(Duration.seconds(3)); - return notification; - } } diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/FontResourcesDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/FontResourcesDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/InfoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/InfoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/LabelsDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/LabelsDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ListViewsDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ListViewsDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/NotificationsController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/NotificationsController.java old mode 100644 new mode 100755 index bacca117..3c87258b --- a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/NotificationsController.java +++ b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/NotificationsController.java @@ -18,19 +18,8 @@ package io.github.palexdev.materialfx.demo.controllers; -import io.github.palexdev.materialfx.controls.MFXDialog; -import io.github.palexdev.materialfx.controls.MFXNotification; -import io.github.palexdev.materialfx.controls.SimpleMFXNotificationPane; -import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog; -import io.github.palexdev.materialfx.controls.enums.DialogType; -import io.github.palexdev.materialfx.controls.factories.MFXDialogFactory; -import io.github.palexdev.materialfx.notifications.NotificationPos; -import io.github.palexdev.materialfx.notifications.NotificationsManager; +import io.github.palexdev.materialfx.enums.NotificationPos; import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.paint.Color; -import javafx.util.Duration; -import org.kordamp.ikonli.javafx.FontIcon; import java.util.Random; @@ -83,27 +72,10 @@ public class NotificationsController { } private void showNotification(NotificationPos pos) { - MFXNotification notification = buildNotification(); - NotificationsManager.send(pos, notification); + // TODO remake } - private MFXNotification buildNotification() { - Region template = getRandomTemplate(); - MFXNotification notification = new MFXNotification(template, true, true); - notification.setHideAfterDuration(Duration.seconds(3)); - - if (template instanceof SimpleMFXNotificationPane) { - SimpleMFXNotificationPane pane = (SimpleMFXNotificationPane) template; - pane.setCloseHandler(closeEvent -> notification.hideNotification()); - } else { - MFXDialog dialog = (MFXDialog) template; - dialog.setCloseHandler(closeEvent -> notification.hideNotification()); - } - - return notification; - } - - private Region getRandomTemplate() { +/* private Region getRandomTemplate() { final int rand = random.nextInt(4); switch (rand) { @@ -144,5 +116,5 @@ public class NotificationsController { default: return null; } - } + }*/ } diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressBarsDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressBarsDemoController.java old mode 100644 new mode 100755 index faa89b6b..e762d712 --- a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressBarsDemoController.java +++ b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressBarsDemoController.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.demo.controllers; import io.github.palexdev.materialfx.beans.NumberRange; import io.github.palexdev.materialfx.controls.MFXProgressBar; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.utils.AnimationUtils; import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; import javafx.animation.Animation; diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressSpinnersDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressSpinnersDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ScrollPaneDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ScrollPaneDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/SlidersDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/SlidersDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TableViewsDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TableViewsDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TextFieldsDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TextFieldsDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TogglesController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TogglesController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TreeviewsDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TreeviewsDemoController.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/model/FilterablePerson.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/model/FilterablePerson.java old mode 100644 new mode 100755 index c598f4eb..324251f2 --- a/demo/src/main/java/io/github/palexdev/materialfx/demo/model/FilterablePerson.java +++ b/demo/src/main/java/io/github/palexdev/materialfx/demo/model/FilterablePerson.java @@ -18,13 +18,13 @@ package io.github.palexdev.materialfx.demo.model; -import io.github.palexdev.materialfx.filter.IFilterable; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -public class FilterablePerson implements IFilterable { +// TODO remove? +public class FilterablePerson { private final StringProperty firstName = new SimpleStringProperty(); private final StringProperty lastName = new SimpleStringProperty(); private final StringProperty address = new SimpleStringProperty(); @@ -85,11 +85,6 @@ public class FilterablePerson implements IFilterable { this.age.set(age); } - @Override - public String toFilterString() { - return getFirstName() + getLastName() + getAddress() + getAge(); - } - @Override public String toString() { return getFirstName() + " " + getLastName(); diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/model/Machine.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/model/Machine.java old mode 100644 new mode 100755 index 9799f153..30bbfe4a --- a/demo/src/main/java/io/github/palexdev/materialfx/demo/model/Machine.java +++ b/demo/src/main/java/io/github/palexdev/materialfx/demo/model/Machine.java @@ -18,13 +18,14 @@ package io.github.palexdev.materialfx.demo.model; -import io.github.palexdev.materialfx.filter.IFilterable; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -public class Machine implements IFilterable { + +// TODO review? +public class Machine { public enum State { ONLINE, OFFLINE @@ -89,9 +90,4 @@ public class Machine implements IFilterable { public void setState(State state) { this.state.set(state); } - - @Override - public String toFilterString() { - return getName() + " " + getIp() + " " + getOwner() + " " + getState().name(); - } } diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/model/Person.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/model/Person.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/model/SimplePerson.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/model/SimplePerson.java old mode 100644 new mode 100755 diff --git a/demo/src/main/java/module-info.java b/demo/src/main/java/module-info.java old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/ButtonsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/ButtonsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/CheckBoxesDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/CheckBoxesDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/ComboBoxesDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/ComboBoxesDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/DatePickersDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/DatePickersDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/Demo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/Demo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/DialogsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/DialogsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/FontResourcesDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/FontResourcesDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/InfoDialog.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/InfoDialog.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/LabelsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/LabelsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/ListViewsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/ListViewsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/NotificationsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/NotificationsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressBarsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressBarsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressSpinnersDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressSpinnersDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/RadioButtonsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/RadioButtonsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/ScrollPanesDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/ScrollPanesDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/SlidersDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/SlidersDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/StepperDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/StepperDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/TableViewsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/TableViewsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/TextFieldsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/TextFieldsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/ToggleButtonsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/ToggleButtonsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/TreeViewsDemo.fxml b/demo/src/main/resources/io/github/palexdev/materialfx/demo/TreeViewsDemo.fxml old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/logo.png b/demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/logo.png old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome1.wav b/demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome1.wav old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome2.wav b/demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome2.wav old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ButtonsDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ButtonsDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/CheckBoxesDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/CheckBoxesDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ComboBoxesDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ComboBoxesDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Common.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Common.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/CustomDatePicker.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/CustomDatePicker.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/DatePickersDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/DatePickersDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Demo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Demo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/InfoDialog.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/InfoDialog.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ListViewsDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ListViewsDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/MFXColors.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/MFXColors.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressBarDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressBarDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressSpinnersDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressSpinnersDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/RadioButtonsDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/RadioButtonsDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ScrollPanesDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ScrollPanesDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/SlidersDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/SlidersDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/StepperDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/StepperDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TestDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TestDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TextFieldsDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TextFieldsDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ToggleButtonsDemo.css b/demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ToggleButtonsDemo.css old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Bold.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Bold.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Light.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Light.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Medium.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Medium.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Regular.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-Regular.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-SemiBold.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-SemiBold.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-VariableFont_wght.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Comfortaa/Comfortaa-VariableFont_wght.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Bold.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Bold.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-BoldItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-BoldItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBold.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBold.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Italic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Italic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Light.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Light.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-LightItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-LightItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Regular.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Regular.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBold.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBold.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Black.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Black.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-BlackItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-BlackItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Bold.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Bold.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-BoldItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-BoldItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Italic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Italic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Light.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Light.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-LightItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-LightItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Medium.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Medium.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-MediumItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-MediumItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Regular.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Regular.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Thin.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-Thin.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-ThinItalic.ttf b/demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/Roboto/Roboto-ThinItalic.ttf old mode 100644 new mode 100755 diff --git a/demo/src/main/resources/logo.ico b/demo/src/main/resources/logo.ico old mode 100644 new mode 100755 diff --git a/demo/src/test/java/Launcher.java b/demo/src/test/java/Launcher.java old mode 100644 new mode 100755 index 8c9ed24a..b38a9f21 --- a/demo/src/test/java/Launcher.java +++ b/demo/src/test/java/Launcher.java @@ -1,9 +1,8 @@ -import combobox.ComboBoxTest; import javafx.application.Application; public class Launcher { public static void main(String[] args) { - Application.launch(ComboBoxTest.class, args); + Application.launch(NotificationsTest.class, args); } } diff --git a/demo/src/test/java/NotificationsTest.java b/demo/src/test/java/NotificationsTest.java new file mode 100755 index 00000000..e3b34c1d --- /dev/null +++ b/demo/src/test/java/NotificationsTest.java @@ -0,0 +1,84 @@ +import io.github.palexdev.materialfx.controls.MFXLabel; +import io.github.palexdev.materialfx.controls.MFXNotificationCenter; +import io.github.palexdev.materialfx.enums.NotificationPos; +import io.github.palexdev.materialfx.font.MFXFontIcon; +import io.github.palexdev.materialfx.notifications.MFXNotificationCenterSystem; +import io.github.palexdev.materialfx.notifications.MFXNotificationSystem; +import io.github.palexdev.materialfx.controls.MFXSimpleNotification; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.utils.ColorUtils; +import io.github.palexdev.materialfx.utils.RandomInstance; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import org.scenicview.ScenicView; + +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +public class NotificationsTest extends Application { + + @Override + public void start(Stage primaryStage) throws Exception { + StackPane stackPane = new StackPane(); + + MFXNotificationCenter notificationCenter = new MFXNotificationCenter(); + IntStream.range(0, 100).forEach(i -> notificationCenter.getNotifications().add(createDummyNotification())); + stackPane.getChildren().add(notificationCenter); + + MFXNotificationCenterSystem.instance() + .initOwner(primaryStage) + .setOpenOnNew(false) + .setCloseAutomatically(true) + .setPosition(NotificationPos.TOP_RIGHT); + MFXNotificationSystem.instance() + .initOwner(primaryStage) + .setPosition(NotificationPos.TOP_RIGHT); + stackPane.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + switch (event.getCode()) { + case A -> MFXNotificationCenterSystem.instance().publish(createDummyNotification()); + case C -> notificationCenter.stopNotificationsUpdater(); + case S -> notificationCenter.startNotificationsUpdater(60, TimeUnit.SECONDS); + case T -> MFXNotificationSystem.instance().publish(createDummyNotification()); + case P -> MFXNotificationCenterSystem.instance().delaySetPosition(NotificationPos.TOP_LEFT); + } + }); + + Scene scene = new Scene(stackPane, 800, 600); + primaryStage.setScene(scene); + primaryStage.show(); + + ScenicView.show(scene); + } + + private INotification createDummyNotification() { + MFXLabel label = new MFXLabel("Random Label n." + RandomInstance.random.nextInt()); + label.setLeadingIcon(MFXFontIcon.getRandomIcon(32, ColorUtils.getRandomColor())); + label.setAlignment(Pos.CENTER_LEFT); + label.setLineColor(Color.TRANSPARENT); + label.setUnfocusedLineColor(Color.TRANSPARENT); + label.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(label, Priority.ALWAYS); + + MFXLabel time = new MFXLabel(); + time.setAlignment(Pos.CENTER_RIGHT); + time.setLineColor(Color.TRANSPARENT); + time.setUnfocusedLineColor(Color.TRANSPARENT); + + HBox box = new HBox(label, time); + box.setMinSize(450, 100); + box.setStyle("-fx-background-color: white"); + box.setAlignment(Pos.CENTER_LEFT); + MFXSimpleNotification notification = new MFXSimpleNotification(box); + notification.setOnUpdateElapsed((longElapsed, stringElapsed) -> Platform.runLater(() -> time.setText(stringElapsed))); + time.setText(notification.getTimeToStringConverter().apply(notification.getElapsedTime())); + return notification; + } +} diff --git a/demo/src/test/java/PopupTest.java b/demo/src/test/java/PopupTest.java new file mode 100755 index 00000000..acb59511 --- /dev/null +++ b/demo/src/test/java/PopupTest.java @@ -0,0 +1,73 @@ +import io.github.palexdev.materialfx.controls.MFXButton; +import io.github.palexdev.materialfx.controls.MFXLabel; +import io.github.palexdev.materialfx.controls.MFXPopup; +import io.github.palexdev.materialfx.enums.ButtonType; +import io.github.palexdev.materialfx.effects.DepthLevel; +import io.github.palexdev.materialfx.font.MFXFontIcon; +import io.github.palexdev.materialfx.controls.MFXSimpleNotification; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.utils.ColorUtils; +import io.github.palexdev.materialfx.utils.RandomInstance; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.geometry.HPos; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Scene; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +public class PopupTest extends Application { + + @Override + public void start(Stage primaryStage) throws Exception { + StackPane stackPane = new StackPane(); + + MFXButton button = new MFXButton("SHOW"); + button.setPrefSize(180, 36); + button.setButtonType(ButtonType.RAISED); + button.setDepthLevel(DepthLevel.LEVEL1); + + button.setOnAction(event -> { + Region content = createDummyNotification().getContent(); + MFXPopup popup = new MFXPopup(content); + popup.show(button, HPos.LEFT, VPos.BOTTOM, -0, -0); + }); + + stackPane.getChildren().add(button); + Scene scene = new Scene(stackPane, 800, 800); + primaryStage.setScene(scene); + primaryStage.show(); + } + + private INotification createDummyNotification() { + MFXLabel label = new MFXLabel("Random Label n." + RandomInstance.random.nextInt()); + label.setLeadingIcon(MFXFontIcon.getRandomIcon(32, ColorUtils.getRandomColor())); + label.setAlignment(Pos.CENTER_LEFT); + label.setLineColor(Color.TRANSPARENT); + label.setUnfocusedLineColor(Color.TRANSPARENT); + label.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(label, Priority.ALWAYS); + + MFXLabel time = new MFXLabel(); + time.setAlignment(Pos.CENTER_RIGHT); + time.setLineColor(Color.TRANSPARENT); + time.setUnfocusedLineColor(Color.TRANSPARENT); + + HBox box = new HBox(label, time); + box.setMinSize(450, 100); + box.setStyle("-fx-background-color: white"); + box.setAlignment(Pos.CENTER_LEFT); + MFXSimpleNotification notification = new MFXSimpleNotification(box); + notification.setOnUpdateElapsed((longElapsed, stringElapsed) -> Platform.runLater(() -> time.setText(stringElapsed))); + time.setText(notification.getTimeToStringConverter().apply(notification.getElapsedTime())); + box.setStyle("" + + "-fx-background-color: transparent;\n" + + "-fx-border-color: red"); + return notification; + } +} diff --git a/demo/src/test/java/binding/BindingManagerTests.java b/demo/src/test/java/binding/BindingManagerTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/collections/TransformableListTest.java b/demo/src/test/java/collections/TransformableListTest.java new file mode 100755 index 00000000..c758d4bc --- /dev/null +++ b/demo/src/test/java/collections/TransformableListTest.java @@ -0,0 +1,69 @@ +package collections; + +import io.github.palexdev.materialfx.collections.TransformableList; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testfx.framework.junit5.ApplicationExtension; + +import java.util.Comparator; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(ApplicationExtension.class) +public class TransformableListTest { + private final ObservableList source = FXCollections.observableArrayList("A", "B", "C", "D", "E"); + + @Test + public void sortTest1() { + TransformableList transformed = new TransformableList<>(source); + transformed.setComparator(Comparator.reverseOrder(), true); + + assertEquals(transformed.get(4), "A"); + assertEquals(transformed.indexOf("E"), 0); + assertEquals(transformed.viewToSource(0), 4); + assertEquals(transformed.sourceToView(0), 4); + } + + @Test + public void sortAndFilterTest1() { + TransformableList transformed = new TransformableList<>(source); + transformed.setComparator(Comparator.reverseOrder(), true); + transformed.setPredicate(s -> s.equals("A") || s.equals("C") || s.equals("E")); + + assertThrows(IndexOutOfBoundsException.class, () -> transformed.get(4)); + assertEquals(transformed.get(1), "C"); + assertEquals(transformed.indexOf("E"), 0); + assertEquals(transformed.viewToSource(1), 2); + assertEquals(transformed.sourceToView(1), -1); + } + + @Test + public void testJavaFX1() { + SortedList sorted = new SortedList<>(source); + sorted.setComparator(Comparator.reverseOrder()); + + assertEquals(sorted.get(4), "A"); + assertEquals(sorted.indexOf("E"), 0); + assertEquals(sorted.getSourceIndex(0), 4); + assertEquals(sorted.getViewIndex(0), 4); + } + + @Test + public void testJavaFX2() { + SortedList sorted = new SortedList<>(source); + sorted.setComparator(Comparator.reverseOrder()); + + FilteredList filtered = new FilteredList<>(sorted); + filtered.setPredicate(s -> s.equals("A") || s.equals("C") || s.equals("E")); + + assertThrows(IndexOutOfBoundsException.class, () -> filtered.get(4)); + assertEquals(filtered.get(1), "C"); + assertEquals(filtered.indexOf("E"), 0); + assertEquals(filtered.getSourceIndex(1), 2); + assertTrue(filtered.getViewIndex(1) < 0); + } +} diff --git a/demo/src/test/java/combobox/ComboBoxTest.java b/demo/src/test/java/combobox/ComboBoxTest.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/properties/SynchronizedBooleanTests.java b/demo/src/test/java/properties/SynchronizedBooleanTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/properties/SynchronizedDoubleTests.java b/demo/src/test/java/properties/SynchronizedDoubleTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/properties/SynchronizedFloatTests.java b/demo/src/test/java/properties/SynchronizedFloatTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/properties/SynchronizedIntegerTests.java b/demo/src/test/java/properties/SynchronizedIntegerTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/properties/SynchronizedLongTests.java b/demo/src/test/java/properties/SynchronizedLongTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/properties/SynchronizedObjectTests.java b/demo/src/test/java/properties/SynchronizedObjectTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/properties/SynchronizedStringTests.java b/demo/src/test/java/properties/SynchronizedStringTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/selection/SingleSelectionModelTests.java b/demo/src/test/java/selection/SingleSelectionModelTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/treeview/NumberUtilsTests.java b/demo/src/test/java/treeview/NumberUtilsTests.java old mode 100644 new mode 100755 diff --git a/demo/src/test/java/treeview/TreeViewTests.java b/demo/src/test/java/treeview/TreeViewTests.java old mode 100644 new mode 100755 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar old mode 100644 new mode 100755 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties old mode 100644 new mode 100755 diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 diff --git a/materialfx/build.gradle b/materialfx/build.gradle old mode 100644 new mode 100755 index 647d2d88..1886a7d4 --- a/materialfx/build.gradle +++ b/materialfx/build.gradle @@ -23,7 +23,7 @@ dependencies { testImplementation('junit:junit:4.13.2') implementation 'com.vanniktech:gradle-maven-publish-plugin:0.18.0' - implementation 'io.github.palexdev:virtualizedfx:11.1.3' + implementation 'io.github.palexdev:virtualizedfx:11.2.1' } javadoc { @@ -70,7 +70,7 @@ jar { shadowJar { mergeServiceFiles() dependencies { - include(dependency('io.github.palexdev:virtualizedfx:11.1.3')) + include(dependency('io.github.palexdev:virtualizedfx:11.2.1')) } } diff --git a/materialfx/gradle.properties b/materialfx/gradle.properties old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/MFXResourcesLoader.java b/materialfx/src/main/java/io/github/palexdev/materialfx/MFXResourcesLoader.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/AnimationsData.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/AnimationsData.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/BiPredicateBean.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/BiPredicateBean.java new file mode 100755 index 00000000..939dcc04 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/BiPredicateBean.java @@ -0,0 +1,42 @@ +package io.github.palexdev.materialfx.beans; + +import java.util.function.BiPredicate; + +/** + * A simple bean that wraps a {@link BiPredicate} and s String that represents + * the name for the predicate. + * + * @param the type of the first argument to the predicate + * @param the type of the second argument the predicate + */ +public class BiPredicateBean { + //================================================================================ + // Properties + //================================================================================ + private final String name; + private final BiPredicate predicate; + + //================================================================================ + // Constructors + //================================================================================ + public BiPredicateBean(String name, BiPredicate predicate) { + this.name = name; + this.predicate = predicate; + } + + //================================================================================ + // Getters + //================================================================================ + public String name() { + return name; + } + + public BiPredicate predicate() { + return predicate; + } + + @Override + public String toString() { + return name; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/CustomBounds.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/CustomBounds.java new file mode 100755 index 00000000..7388951a --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/CustomBounds.java @@ -0,0 +1,98 @@ +package io.github.palexdev.materialfx.beans; + +import io.github.palexdev.materialfx.notifications.MFXNotificationCenterSystem; +import javafx.geometry.BoundingBox; +import javafx.geometry.Bounds; + +/** + * JavaFX allows you to create custom {@code Bounds} objects, see {@link BoundingBox}, the thing is + * that it automatically computes the max X/Y/Z values. This can be quite unfortunate in some rare + * cases because maybe you need some kind of special bounds, this bean is specifically for those + * cases, it allows creating custom bounds. + *

+ * An example of that is in the {@link MFXNotificationCenterSystem} class, there custom bounds + * are created to take into account the coordinates of the bell icon and the entire width/height of the + * notification center. Like I said tough, cases like that are quite rare. + */ +public class CustomBounds { + //================================================================================ + // Properties + //================================================================================ + private final double minX; + private final double minY; + private final double minZ; + private final double maxX; + private final double maxY; + private final double maxZ; + private final double width; + private final double height; + + //================================================================================ + // Constructors + //================================================================================ + public CustomBounds(double minX, double minY, double maxX, double maxY, double width, double height) { + this(minX, minY, 0, maxX, maxY, 0, width, height); + } + + public CustomBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, double width, double height) { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + this.width = width; + this.height = height; + } + + //================================================================================ + // Static Methods + //================================================================================ + public static CustomBounds from(Bounds bounds) { + return new CustomBounds( + bounds.getMinX(), + bounds.getMinY(), + bounds.getMinZ(), + bounds.getMaxX(), + bounds.getMaxY(), + bounds.getMaxY(), + bounds.getWidth(), + bounds.getHeight() + ); + } + + //================================================================================ + // Getters/Setters + //================================================================================ + public double getMinX() { + return minX; + } + + public double getMinY() { + return minY; + } + + public double getMinZ() { + return minZ; + } + + public double getMaxX() { + return maxX; + } + + public double getMaxY() { + return maxY; + } + + public double getMaxZ() { + return maxZ; + } + + public double getWidth() { + return width; + } + + public double getHeight() { + return height; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/FilterBean.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/FilterBean.java new file mode 100755 index 00000000..97963d2d --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/FilterBean.java @@ -0,0 +1,104 @@ +package io.github.palexdev.materialfx.beans; + +import io.github.palexdev.materialfx.enums.ChainMode; +import io.github.palexdev.materialfx.filter.base.AbstractFilter; + +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +/** + * A simple bean that has all the necessary information to produce a {@link Predicate} + * for a given T object type. + *

+ * It wraps the following data: + *

- A String which is the query + *

- An object of type {@link AbstractFilter}, which is effectively responsible for producing the {@link Predicate} + *

- A {@link BiPredicateBean}, which is used by {@link AbstractFilter}, see {@link AbstractFilter#predicateFor(String)} or {@link AbstractFilter#predicateFor(String, BiPredicate)} + *

- A {@link ChainMode} enumeration to specify how this filter should be combined with other filters + * + * @param the type of objects to filter + * @param the type of objects on which the {@link BiPredicate} operates + */ +public class FilterBean { + //================================================================================ + // Properties + //================================================================================ + private final String query; + private final AbstractFilter filter; + private final BiPredicateBean predicateBean; + private ChainMode mode; + + //================================================================================ + // Constructors + //================================================================================ + public FilterBean(String query, AbstractFilter filter, BiPredicateBean predicateBean) { + this(query, filter, predicateBean, ChainMode.OR); + } + + public FilterBean(String query, AbstractFilter filter, BiPredicateBean predicateBean, ChainMode mode) { + this.query = query; + this.filter = filter; + this.predicateBean = predicateBean; + this.mode = mode; + } + + //================================================================================ + // Methods + //================================================================================ + + /** + * Calls {@link AbstractFilter#predicateFor(String)} with the query specified by this bean. + */ + public Predicate predicate() { + return filter.predicateFor(query); + } + + /** + * @return the query, see {@link AbstractFilter} documentation for more info about the query + */ + public String getQuery() { + return query; + } + + /** + * @return the {@link AbstractFilter} specified by this bean + */ + public AbstractFilter getFilter() { + return filter; + } + + /** + * Delegate for {@link AbstractFilter#name()}. + */ + public String getFilterName() { + return filter.name(); + } + + /** + * @return the {@link BiPredicateBean} specified by this bean + */ + public BiPredicateBean getPredicateBean() { + return predicateBean; + } + + /** + * Delegate for {@link BiPredicateBean#name()}. + */ + public String getPredicateName() { + return predicateBean.name(); + } + + /** + * @return the {@link ChainMode} enumeration that specifies how this filter should be chained with other filters. + */ + public ChainMode getMode() { + return mode; + } + + /** + * Sets the chain mode to the specified one. + */ + public void setMode(ChainMode mode) { + this.mode = mode; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXLoaderBean.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXLoaderBean.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXSnapshotWrapper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXSnapshotWrapper.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/NumberRange.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/NumberRange.java old mode 100644 new mode 100755 index aa485e92..606902f0 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/NumberRange.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/NumberRange.java @@ -18,7 +18,8 @@ package io.github.palexdev.materialfx.beans; -import java.util.List; +import java.util.*; +import java.util.stream.IntStream; /** * Simple bean to represent a range of values from min to max. @@ -43,14 +44,38 @@ public class NumberRange { //================================================================================ // Methods //================================================================================ + + /** + * @return the lower bound + */ public T getMin() { return min; } + /** + * @return the upper bound + */ public T getMax() { return max; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NumberRange that = (NumberRange) o; + return Objects.equals(min, that.min) && Objects.equals(max, that.max); + } + + @Override + public int hashCode() { + return Objects.hash(min, max); + } + + @Override + public String toString() { + return "Min[" + min + "], Max[" + max + "]"; + } //================================================================================ // Static Methods //================================================================================ @@ -124,4 +149,18 @@ public class NumberRange { public static boolean inRangeOf(long val, List> ranges) { return ranges.stream().anyMatch(range -> inRangeOf(val, range)); } + + /** + * Expands a range of integers to a List of integers. + */ + public static List expandRange(NumberRange range) { + return IntStream.rangeClosed(range.getMin(), range.getMax()).collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + } + + /** + * Expands a range of integers to a Set of integers. + */ + public static Set expandRangeToSet(NumberRange range) { + return IntStream.rangeClosed(range.getMin(), range.getMax()).collect(HashSet::new, HashSet::add, HashSet::addAll); + } } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/PopupPositionBean.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/PopupPositionBean.java new file mode 100755 index 00000000..aa8f7e41 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/PopupPositionBean.java @@ -0,0 +1,121 @@ +package io.github.palexdev.materialfx.beans; + +import io.github.palexdev.materialfx.controls.MFXPopup; +import javafx.geometry.Bounds; +import javafx.geometry.HPos; +import javafx.geometry.VPos; +import javafx.scene.Node; + +/** + * A useful bean which gives info about a {@link MFXPopup}'s position and owner. + *

+ * The purpose of this bean is to provide a way to communicate between the popup and its skin. + * The precise location of a popup cannot be computed when the show methods are called because the content + * has not been laid out yet, thus its sizes/bounds are 0. This changes when the skin is created, at that moment + * all info about the content are available so this bean is necessary to properly reposition and animate the popup. + */ +public class PopupPositionBean { + //================================================================================ + // Properties + //================================================================================ + private final Node owner; + private final Bounds ownerBounds; + private final PositionBean positionBean; + private final HPos hPos; + private final VPos vPos; + private final double xOffset; + private final double yOffset; + + //================================================================================ + // Constructors + //================================================================================ + public PopupPositionBean(Node owner, PositionBean positionBean, HPos hPos, VPos vPos, double xOffset, double yOffset) { + this.owner = owner; + this.ownerBounds = owner.getLayoutBounds(); + this.positionBean = positionBean; + this.hPos = hPos; + this.vPos = vPos; + this.xOffset = xOffset; + this.yOffset = yOffset; + } + + /** + * @return the popup's owner + */ + public Node getOwner() { + return owner; + } + + /** + * @return the popup owner's bounds + */ + public Bounds getOwnerBounds() { + return ownerBounds; + } + + /** + * @return the popup owner's width + */ + public double getOwnerWidth() { + return ownerBounds.getWidth(); + } + + /** + * @return the popup owner's height + */ + public double getOwnerHeight() { + return ownerBounds.getHeight(); + } + + /** + * You should NOT rely on these coordinates since as of now + * they do not take into account the translations made by the skin. + * + * @return the initial computed coordinates of the popup + */ + public PositionBean getPositionBean() { + return positionBean; + } + + /** + * Delegate for {@link #getPositionBean()}.getX(). + */ + public double getX() { + return positionBean.getX(); + } + + /** + * Delegate for {@link #getPositionBean()}.getY(). + */ + public double getY() { + return positionBean.getY(); + } + + /** + * @return the specified {@link HPos} + */ + public HPos getHPos() { + return hPos; + } + + /** + * @return the specified {@link VPos} + */ + public VPos getVPos() { + return vPos; + } + + /** + * @return the specified x offset + */ + public double getXOffset() { + return xOffset; + } + + /** + * @return the specified y offset + */ + public double getYOffset() { + return yOffset; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/PositionBean.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/PositionBean.java new file mode 100755 index 00000000..a70f7b23 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/PositionBean.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 Parisi Alessandro + * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). + * + * MaterialFX is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MaterialFX 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MaterialFX. If not, see . + */ + +package io.github.palexdev.materialfx.beans; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; + +/** + * Simple bean that keeps track of two coordinates, x and y. + *

+ * Both are JavaFX properties to allow dynamic uses. + */ +public class PositionBean { + //================================================================================ + // Properties + //================================================================================ + private final DoubleProperty x = new SimpleDoubleProperty(0); + private final DoubleProperty y = new SimpleDoubleProperty(0); + + //================================================================================ + // Constructors + //================================================================================ + public PositionBean() { + } + + public PositionBean(double x, double y) { + setX(x); + setY(y); + } + + //================================================================================ + // Static Methods + //================================================================================ + public static PositionBean of(double x, double y) { + return new PositionBean(x, y); + } + + //================================================================================ + // Methods + //================================================================================ + public double getX() { + return x.get(); + } + + /** + * The x coordinate property. + */ + public DoubleProperty xProperty() { + return x; + } + + public void setX(double xPosition) { + this.x.set(xPosition); + } + + public double getY() { + return y.get(); + } + + /** + * The y coordinate property + */ + public DoubleProperty yProperty() { + return y; + } + + public void setY(double yPosition) { + this.y.set(yPosition); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/TransitionPositionBean.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/TransitionPositionBean.java new file mode 100755 index 00000000..07b314e9 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/TransitionPositionBean.java @@ -0,0 +1,85 @@ +package io.github.palexdev.materialfx.beans; + +import javafx.animation.Transition; + +// TODO documentation +/** + * This is an extension of {@link PositionBean} to be used + * with {@link Transition}s that start from a point P(x, y) and + * end at a point P1(endX, endY). + *

+ * A very basic example: + *

+ * Let's say I want to move a point P from (x, y) to the left + * (x1, y) with an animation. The transition would probably look like this: + *

+ * {@code
+ *     double startX = ...;
+ *     double startY = ...;
+ *     double endX = ...;
+ *     double endY = startY; // The y coordinate doesn't change so it is equal to the start one
+ *     TransitionPositionBean position = TransitionPositionBean.of(startX, startY, endX, endY);
+ *     Transition move = new Transition() {
+ *             @Override
+ *             protected void interpolate(double frac) {
+ *                 p.setX(x - position.deltaX() * frac);
+ *             }
+ *      }
+ * }
+ * 
+ */ +public class TransitionPositionBean extends PositionBean { + //================================================================================ + // Properties + //================================================================================ + private final double endX; + private final double endY; + + //================================================================================ + // Constructors + //================================================================================ + public TransitionPositionBean(double x, double y, double endX, double endY) { + super(x, y); + this.endX = endX; + this.endY = endY; + } + + //================================================================================ + // Static Methods + //================================================================================ + public static TransitionPositionBean of(double x, double y, double endX, double endY) { + return new TransitionPositionBean(x, y, endX, endY); + } + + //================================================================================ + // Getters/Setters + //================================================================================ + + /** + * @return the end x coordinate + */ + public double getEndX() { + return endX; + } + + /** + * @return the end y coordinate + */ + public double getEndY() { + return endY; + } + + /** + * @return the difference between the star x and end x coordinates + */ + public double deltaX() { + return getX() - getEndX(); + } + + /** + * @return the difference between the start y and end y coordinates + */ + public double deltaY() { + return getY() - getEndY(); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/NumberRangeProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/NumberRangeProperty.java new file mode 100755 index 00000000..896bd44d --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/NumberRangeProperty.java @@ -0,0 +1,62 @@ +package io.github.palexdev.materialfx.beans.properties; + +import io.github.palexdev.materialfx.beans.NumberRange; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link NumberRange}. + * + * @param the range's number type + */ +public class NumberRangeProperty extends SimpleObjectProperty> { + + + //================================================================================ + // Methods + //================================================================================ + + /** + * Convenience method to get the range's lower bound. + * Null if the range is null. + */ + public T getMin() { + return get() == null ? null : get().getMin(); + } + + /** + * Convenience method to get the range's upper bound. + * Null if the range is null. + */ + public T getMax() { + return get() == null ? null : get().getMin(); + } + + /** + * Convenience method to set a range with both min and max equal. + */ + public void setRange(T value) { + set(NumberRange.of(value)); + } + + /** + * Convenience method to set a range with the given min and max values. + */ + public void setRange(T min, T max) { + set(NumberRange.of(min, max)); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + /** + * Overridden to check equality between ranges and return in case ranges are the same. + */ + @Override + public void set(NumberRange newValue) { + NumberRange oldValue = get(); + if (newValue.equals(oldValue)) return; + super.set(newValue); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/base/ResettableProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/base/ResettableProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/base/SynchronizedProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/base/SynchronizedProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiConsumerProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiConsumerProperty.java new file mode 100755 index 00000000..15f24ebe --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiConsumerProperty.java @@ -0,0 +1,14 @@ +package io.github.palexdev.materialfx.beans.properties.functional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.function.BiConsumer; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link BiConsumer}. + * + * @param the consumer's first argument + * @param the consumer's second argument + */ +public class BiConsumerProperty extends SimpleObjectProperty> {} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiFunctionProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiFunctionProperty.java new file mode 100755 index 00000000..4f6d2c49 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiFunctionProperty.java @@ -0,0 +1,15 @@ +package io.github.palexdev.materialfx.beans.properties.functional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.function.BiFunction; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link BiFunction}. + * + * @param the function's first argument + * @param the function's second argument + * @param the function's return type + */ +public class BiFunctionProperty extends SimpleObjectProperty> {} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiPredicateProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiPredicateProperty.java new file mode 100755 index 00000000..2f06708e --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/BiPredicateProperty.java @@ -0,0 +1,14 @@ +package io.github.palexdev.materialfx.beans.properties.functional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.function.BiPredicate; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link BiPredicate}. + * + * @param the predicate's first argument + * @param the predicate's second argument + */ +public class BiPredicateProperty extends SimpleObjectProperty> {} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/ConsumerProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/ConsumerProperty.java new file mode 100755 index 00000000..60c7d646 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/ConsumerProperty.java @@ -0,0 +1,13 @@ +package io.github.palexdev.materialfx.beans.properties.functional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.function.Consumer; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link Consumer}. + * + * @param the consumer's input type + */ +public class ConsumerProperty extends SimpleObjectProperty> {} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/FunctionProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/FunctionProperty.java new file mode 100755 index 00000000..faa004ab --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/FunctionProperty.java @@ -0,0 +1,14 @@ +package io.github.palexdev.materialfx.beans.properties.functional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.function.Function; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link Function}. + * + * @param the function's input type + * @param the function's return type + */ +public class FunctionProperty extends SimpleObjectProperty> {} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/PredicateProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/PredicateProperty.java new file mode 100755 index 00000000..5da3f9c1 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/PredicateProperty.java @@ -0,0 +1,13 @@ +package io.github.palexdev.materialfx.beans.properties.functional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.function.Predicate; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link Predicate}. + * + * @param the predicate's input type + */ +public class PredicateProperty extends SimpleObjectProperty> {} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/SupplierProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/SupplierProperty.java new file mode 100755 index 00000000..86c51509 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/SupplierProperty.java @@ -0,0 +1,13 @@ +package io.github.palexdev.materialfx.beans.properties.functional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.function.Supplier; + +/** + * Simply an {@link ObjectProperty} that wraps a {@link Supplier}. + * + * @param the supplier's return type + */ +public class SupplierProperty extends SimpleObjectProperty> {} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableBooleanProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableBooleanProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableDoubleProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableDoubleProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableFloatProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableFloatProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableIntegerProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableIntegerProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableLongProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableLongProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableObjectProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableObjectProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableStringProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/resettable/ResettableStringProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedBooleanProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedBooleanProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedDoubleProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedDoubleProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedFloatProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedFloatProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedIntegerProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedIntegerProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedLongProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedLongProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedObjectProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedObjectProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedStringProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/synced/SynchronizedStringProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BidirectionalBindingHelper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BidirectionalBindingHelper.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BindingHelper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BindingHelper.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BindingManager.java b/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BindingManager.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BooleanListBinding.java b/materialfx/src/main/java/io/github/palexdev/materialfx/bindings/BooleanListBinding.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/collections/ChangeHelper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/ChangeHelper.java new file mode 100755 index 00000000..8779e8df --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/ChangeHelper.java @@ -0,0 +1,35 @@ +package io.github.palexdev.materialfx.collections; + +import java.util.Arrays; +import java.util.List; + +class ChangeHelper { + ChangeHelper() {} + + public static String addRemoveChangeToString(int from, int to, List list, List removed) { + StringBuilder sb = new StringBuilder(); + if (removed.isEmpty()) { + sb.append(list.subList(from, to)); + sb.append(" added at ").append(from); + } else { + sb.append(removed); + if (from == to) { + sb.append(" removed at ").append(from); + } else { + sb.append(" replaced by "); + sb.append(list.subList(from, to)); + sb.append(" at ").append(from); + } + } + + return sb.toString(); + } + + public static String permChangeToString(int[] permutation) { + return "permutated by " + Arrays.toString(permutation); + } + + public static String updateChangeToString(int from, int to) { + return "updated at range [" + from + ", " + to + ")"; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/collections/CircularQueue.java b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/CircularQueue.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/collections/GenericAddRemoveChange.java b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/GenericAddRemoveChange.java new file mode 100755 index 00000000..adb5dbcc --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/GenericAddRemoveChange.java @@ -0,0 +1,156 @@ +package io.github.palexdev.materialfx.collections; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +import java.util.Collections; +import java.util.List; + +abstract class NonIterableChange extends ListChangeListener.Change { + private final int from; + private final int to; + private boolean invalid = true; + private static final int[] EMPTY_PERM = new int[0]; + + protected NonIterableChange(int from, int to, ObservableList list) { + super(list); + this.from = from; + this.to = to; + } + + public int getFrom() { + checkState(); + return from; + } + + public int getTo() { + checkState(); + return to; + } + + protected int[] getPermutation() { + checkState(); + return EMPTY_PERM; + } + + public boolean next() { + if (invalid) { + invalid = false; + return true; + } else { + return false; + } + } + + public void reset() { + invalid = true; + } + + public void checkState() { + if (invalid) { + throw new IllegalStateException("Invalid Change state: next() must be called before inspecting the Change."); + } + } + + public String toString() { + boolean oldInvalid = invalid; + invalid = false; + String ret; + if (wasPermutated()) { + ret = ChangeHelper.permChangeToString(getPermutation()); + } else if (wasUpdated()) { + ret = ChangeHelper.updateChangeToString(from, to); + } else { + ret = ChangeHelper.addRemoveChangeToString(from, to, getList(), getRemoved()); + } + + invalid = oldInvalid; + return "{ " + ret + " }"; + } + + public static class SimpleUpdateChange extends NonIterableChange { + public SimpleUpdateChange(int position, ObservableList list) { + this(position, position + 1, list); + } + + public SimpleUpdateChange(int from, int to, ObservableList list) { + super(from, to, list); + } + + public List getRemoved() { + return Collections.emptyList(); + } + + public boolean wasUpdated() { + return true; + } + } + + public static class SimplePermutationChange extends NonIterableChange { + private final int[] permutation; + + public SimplePermutationChange(int from, int to, int[] permutation, ObservableList list) { + super(from, to, list); + this.permutation = permutation; + } + + public List getRemoved() { + checkState(); + return Collections.emptyList(); + } + + protected int[] getPermutation() { + checkState(); + return permutation; + } + } + + public static class SimpleAddChange extends NonIterableChange { + public SimpleAddChange(int from, int to, ObservableList list) { + super(from, to, list); + } + + public boolean wasRemoved() { + checkState(); + return false; + } + + public List getRemoved() { + checkState(); + return Collections.emptyList(); + } + } + + public static class SimpleRemovedChange extends NonIterableChange { + private final List removed; + + public SimpleRemovedChange(int from, int to, E removed, ObservableList list) { + super(from, to, list); + this.removed = Collections.singletonList(removed); + } + + public boolean wasRemoved() { + checkState(); + return true; + } + + public List getRemoved() { + checkState(); + return removed; + } + } + + public static class GenericAddRemoveChange extends NonIterableChange { + private final List removed; + + public GenericAddRemoveChange(int from, int to, List removed, ObservableList list) { + super(from, to, list); + this.removed = removed; + } + + public List getRemoved() { + checkState(); + return removed; + } + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/collections/ObservableStack.java b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/ObservableStack.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/collections/TransformableList.java b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/TransformableList.java new file mode 100755 index 00000000..cfe66fa6 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/TransformableList.java @@ -0,0 +1,269 @@ +package io.github.palexdev.materialfx.collections; + +import io.github.palexdev.materialfx.collections.NonIterableChange.GenericAddRemoveChange; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import javafx.collections.transformation.TransformationList; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * A {@code TransformableList} is a particular type of List which wraps another + * List called "source" and allows manipulations such as: filter and sort, retaining + * the original items' index. + *

+ * Extends {@link TransformationList}, it's basically the same thing of a {@link FilteredList} + * and a {@link SortedList} but combined into one. + *

+ * A more detailed (and hopefully more clear) explanation about the "indexes retention mentioned above": + *

+ * Think of this List as a View for the source list. The underlying data provided by the source is + * not guaranteed to be what the user sees but the items' properties are maintained. + * Let's see a brief example: + *

+ * {@code
+ *     // Let's say I have this ObservableList
+ *     ObservableList source = FXCollections.observableArrayList("A", "B", "C"):
+ *
+ *     // Now let's say I want to sort this list in reverse order (CBA) and that
+ *     // for some reason I still want A to be the element at index 0, B-1 and C-2
+ *     // This is exactly the purpose of the TransformableList...
+ *     TransformableList transformed = new TransformableList<>(source);
+ *     transformed.setSorter(Comparator.reverseOrder());
+ *
+ *     // Now that the order is (CBA) let's see how the list behaves:
+ *     transformed.get(0); // Returns C
+ *     transformed.indexOf("C"); // Returns 2, the index is retrieved in the source list
+ *     transformed.viewToSource(0); // Returns 2, it maps an index of the transformed list to the index of the source list, at 0 we have C which is at index 2 in the source list
+ *     transformed.sourceToView(0); // Also returns 2, it maps an index of the source list to the index of the transformed list, at 0 we have C which is at index 2 in the transformed list
+ *
+ *     // To better see its behavior try to sort and filter the list at the same time.
+ *     // You'll notice that sometimes sourceToView will return a negative index because the item is not in the transformed list (after a filter operation)
+ * }
+ * 
+ * + * Check {@link #computeIndexes()} documentation to see how indexes are calculated. + *

+ * IMPORTANT: If using a reversed comparator please use {@link #setComparator(Comparator, boolean)} with 'true' as argument, + * as {@link #setComparator(Comparator)} will always assume it is a natural order comparator. This is needed to make {@link #sourceToView(int)} + * properly work as it uses a binary search algorithm to find the right index. + * + * @param the items' type + */ +public class TransformableList extends TransformationList { + //================================================================================ + // Constructors + //================================================================================ + private final List indexes = new ArrayList<>(); + private boolean reversed = false; + + private final ObjectProperty> predicate = new SimpleObjectProperty<>() { + @Override + protected void invalidated() { + update(); + } + }; + + private final ObjectProperty> comparator = new SimpleObjectProperty<>() { + @Override + protected void invalidated() { + update(); + } + }; + + //================================================================================ + // Constructors + //================================================================================ + public TransformableList(ObservableList source) { + this(source, null); + } + + public TransformableList(ObservableList source, Predicate predicate) { + this(source, predicate, null); + } + + public TransformableList(ObservableList source, Predicate predicate, Comparator comparator) { + super(source); + setPredicate(predicate); + setComparator(comparator); + } + + //================================================================================ + // Methods + //================================================================================ + + /** + * Calls {@link #getSourceIndex(int)}, just with a different name to be more clear. + *

+ * Maps an index of the transformed list, to the index of the source list. + */ + public int viewToSource(int index) { + return getSourceIndex(index); + } + + /** + * Calls {@link #getViewIndex(int)}, just with a different name to be more clear. + *

+ * Maps an index of the source list, to the index of the transformed list. + */ + public int sourceToView(int index) { + return getViewIndex(index); + } + + /** + * Responsible for updating the transformed indexes when the + * predicate or the comparator change. + */ + private void update() { + indexes.clear(); + indexes.addAll(computeIndexes()); + if (this.hasListeners()) { + this.fireChange(new GenericAddRemoveChange<>(0, size(), new ArrayList<>(this), this)); + } + } + + /** + * Core method of TransformableLists. This is responsible for computing + * the transformed indexes by creating a {@link SortedMap} and mapping every index from 0 to source size + * to its item. Before mapping, items are filtered with the given predicate, {@link #predicateProperty()}. + * Before returning, the map's entry set is sorted by its values with the given comparator, {@link #comparatorProperty()}. + * Finally, returns the map's key set, this set contains the transformed indexes, filtered and sorted. + */ + private Collection computeIndexes() { + Predicate filter = this.getPredicate(); + Comparator sorter = this.getComparator(); + SortedMap sourceMap; + if (filter != null) { + sourceMap = IntStream.range(0, getSource().size()) + .filter((index) -> filter.test(getSource().get(index))) + .collect(TreeMap::new, (map, index) -> map.put(index, getSource().get(index)), TreeMap::putAll); + } else { + sourceMap = IntStream.range(0, getSource().size()) + .collect(TreeMap::new, (map, index) -> map.put(index, getSource().get(index)), TreeMap::putAll); + } + + return sorter != null ? sourceMap.entrySet().stream() + .sorted((o1, o2) -> sorter.compare(o1.getValue(), o2.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()) : sourceMap.keySet(); + } + + public Predicate getPredicate() { + return this.predicate.get(); + } + + /** + * Specifies the predicate used to filter the source list. + */ + public ObjectProperty> predicateProperty() { + return this.predicate; + } + + public void setPredicate(Predicate predicate) { + this.predicate.set(predicate); + } + + public Comparator getComparator() { + return this.comparator.get(); + } + + /** + * Specifies the comparator used to sort the source list. + * + * @see #setComparator(Comparator, boolean) + */ + public ObjectProperty> comparatorProperty() { + return this.comparator; + } + + public void setComparator(Comparator comparator) { + this.reversed = false; + this.comparator.set(comparator); + } + + /** + * This method is NECESSARY if using a reversed comparator, + * a special flag is set to true and {@link #sourceToView(int)} behaves accordingly. + */ + public void setComparator(Comparator sorter, boolean reversed) { + this.reversed = reversed; + this.comparator.set(sorter); + } + + /** + * Specifies if a reversed comparator is being used. + */ + public boolean isReversed() { + return reversed; + } + + /** + * Communicates to the transformed list, specifically to {@link #getViewIndex(int)}, + * if the list is sorted in reversed order. + */ + public void setReversed(boolean reversed) { + this.reversed = reversed; + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + /** + * {@inheritDoc} + *

+ * Calls {@link #update()}. + */ + @Override + protected void sourceChanged(ListChangeListener.Change c) { + beginChange(); + update(); + endChange(); + } + + /** + * @return the number of items in the transformable list + */ + @Override + public int size() { + return indexes.size(); + } + + /** + * Retrieves and return the item at the given index in the transformable list. + * This means transformations due to {@link #predicateProperty()} or {@link #comparatorProperty()} + * are taken into account. + */ + @Override + public T get(int index) { + if (index > size()) { + throw new IndexOutOfBoundsException(index); + } else { + return getSource().get(indexes.get(index)); + } + } + + @Override + public int getSourceIndex(int index) { + if (index > size()) { + throw new IndexOutOfBoundsException(index); + } else { + return indexes.get(index); + } + } + + @Override + public int getViewIndex(int index) { + int viewIndex = reversed ? + Collections.binarySearch(indexes, index, Collections.reverseOrder()) : + Collections.binarySearch(indexes, index); + return viewIndex < 0 ? -1 : viewIndex; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/collections/TransformableListWrapper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/TransformableListWrapper.java new file mode 100755 index 00000000..4ab96b70 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/collections/TransformableListWrapper.java @@ -0,0 +1,168 @@ +package io.github.palexdev.materialfx.collections; + +import javafx.beans.InvalidationListener; +import javafx.beans.property.ObjectProperty; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +import java.util.*; +import java.util.function.Predicate; + +@SuppressWarnings({"unchecked", "NullableProblems"}) +public class TransformableListWrapper extends AbstractList implements ObservableList { + private final ObservableList source; + private final TransformableList transformableList; + + public TransformableListWrapper(ObservableList source) { + this.source = source; + this.transformableList = new TransformableList<>(source); + } + + @Override + public void addListener(ListChangeListener listener) { + transformableList.addListener(listener); + } + + @Override + public void removeListener(ListChangeListener listener) { + transformableList.removeListener(listener); + } + + @Override + public boolean add(T t) { + return source.add(t); + } + + @Override + public T set(int index, T element) { + return source.set(index, element); + } + + @Override + public void add(int index, T element) { + source.add(index, element); + } + + @Override + public T remove(int index) { + return source.remove(index); + } + + @Override + public int indexOf(Object o) { + return transformableList.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return transformableList.lastIndexOf(o); + } + + @Override + public void clear() { + source.clear(); + } + + @Override + public boolean addAll(int index, Collection c) { + return source.addAll(index, c); + } + + @Override + public boolean addAll(T... elements) { + return source.addAll(elements); + } + + @Override + public boolean setAll(T... elements) { + return source.setAll(elements); + } + + @Override + public boolean setAll(Collection col) { + return source.setAll(col); + } + + @Override + public boolean removeAll(T... elements) { + return source.removeAll(elements); + } + + @Override + public boolean retainAll(T... elements) { + return source.retainAll(elements); + } + + @Override + public void remove(int from, int to) { + source.remove(from, to); + } + + @Override + public void addListener(InvalidationListener listener) { + transformableList.addListener(listener); + } + + @Override + public void removeListener(InvalidationListener listener) { + transformableList.removeListener(listener); + } + + @Override + public T get(int index) { + return transformableList.get(index); + } + + @Override + public int size() { + return transformableList.size(); + } + + public ObservableList getSource() { + return transformableList.getSource(); + } + + public int viewToSource(int index) { + return transformableList.viewToSource(index); + } + + public int sourceToView(int index) { + return transformableList.sourceToView(index); + } + + public Predicate getPredicate() { + return transformableList.getPredicate(); + } + + public ObjectProperty> predicateProperty() { + return transformableList.predicateProperty(); + } + + public void setPredicate(Predicate predicate) { + transformableList.setPredicate(predicate); + } + + public Comparator getComparator() { + return transformableList.getComparator(); + } + + public ObjectProperty> comparatorProperty() { + return transformableList.comparatorProperty(); + } + + public void setComparator(Comparator comparator) { + transformableList.setComparator(comparator); + } + + public void setComparator(Comparator sorter, boolean reversed) { + transformableList.setComparator(sorter, reversed); + } + + public boolean isReversed() { + return transformableList.isReversed(); + } + + public void setReversed(boolean reversed) { + transformableList.setReversed(reversed); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java old mode 100644 new mode 100755 index 216f9c3d..321496ce --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java @@ -19,10 +19,10 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; -import io.github.palexdev.materialfx.controls.enums.ButtonType; +import io.github.palexdev.materialfx.enums.ButtonType; import io.github.palexdev.materialfx.effects.DepthLevel; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.skins.MFXButtonSkin; import javafx.beans.property.*; import javafx.css.*; @@ -119,7 +119,7 @@ public class MFXButton extends Button { setRippleColor(Color.rgb(190, 190, 190)); setRippleRadius(25); setComputeRadiusMultiplier(true); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY())); } public boolean isComputeRadiusMultiplier() { diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckListView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckListView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckTreeItem.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckTreeItem.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckTreeView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckTreeView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCircleToggleNode.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCircleToggleNode.java old mode 100644 new mode 100755 index 3cfd89e1..de138444 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCircleToggleNode.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCircleToggleNode.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.controls.base.AbstractMFXToggleNode; -import io.github.palexdev.materialfx.controls.enums.TextPosition; +import io.github.palexdev.materialfx.enums.TextPosition; import io.github.palexdev.materialfx.skins.MFXCircleToggleNodeSkin; import javafx.css.*; import javafx.scene.Node; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java old mode 100644 new mode 100755 index f1b67961..4227856a --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.beans.MFXSnapshotWrapper; -import io.github.palexdev.materialfx.controls.enums.DialogType; +import io.github.palexdev.materialfx.enums.DialogType; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel; import io.github.palexdev.materialfx.skins.MFXComboBoxSkin; @@ -41,7 +41,7 @@ import javafx.scene.paint.Paint; import java.util.List; import java.util.function.Supplier; -import static io.github.palexdev.materialfx.controls.enums.Styles.ComboBoxStyles; +import static io.github.palexdev.materialfx.enums.Styles.ComboBoxStyles; /** * This is the implementation of a combo box following Google's material design guidelines in JavaFX. @@ -229,7 +229,7 @@ public class MFXComboBox extends Control implements Validated null); rippleGenerator.setRadiusMultiplier(1.7); rippleGenerator.setRippleColor(Color.rgb(98, 0, 238, 0.3)); - rippleGenerator.setRipplePositionFunction(event -> { - RipplePosition ripplePosition = new RipplePosition(); - ripplePosition.setXPosition(calendar.getBoundsInParent().getCenterX()); - ripplePosition.setYPosition(calendar.getBoundsInParent().getCenterY()); - return ripplePosition; - }); + rippleGenerator.setRipplePositionFunction(event -> PositionBean.of( + calendar.getBoundsInParent().getCenterX(), + calendar.getBoundsInParent().getCenterY() + )); getChildren().add(0, rippleGenerator); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDialog.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDialog.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXExceptionDialog.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXExceptionDialog.java old mode 100644 new mode 100755 index 3cfb90ac..d1f33bd0 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXExceptionDialog.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXExceptionDialog.java @@ -18,7 +18,7 @@ package io.github.palexdev.materialfx.controls; -import io.github.palexdev.materialfx.controls.enums.ButtonType; +import io.github.palexdev.materialfx.enums.ButtonType; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.ExceptionUtils; import io.github.palexdev.materialfx.utils.NodeUtils; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXFilterComboBox.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXFilterComboBox.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXFilterPane.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXFilterPane.java new file mode 100755 index 00000000..b5bc3172 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXFilterPane.java @@ -0,0 +1,240 @@ +package io.github.palexdev.materialfx.controls; + +import io.github.palexdev.materialfx.MFXResourcesLoader; +import io.github.palexdev.materialfx.enums.ChainMode; +import io.github.palexdev.materialfx.filter.base.AbstractFilter; +import io.github.palexdev.materialfx.beans.FilterBean; +import io.github.palexdev.materialfx.skins.MFXFilterPaneSkin; +import io.github.palexdev.materialfx.utils.PredicateUtils; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.input.MouseEvent; + +import java.util.function.Predicate; + +/** + * This control allows to produce a {@link Predicate} for a given object type + * interactively, meaning that the filter is assembled from the user choices. + * To produce a filter the user must choose the object's field, input/choose a query + * and a way to evaluate the object's field against the query. + *

+ * From now on all code examples to better understand the functionalities of this control + * will use these POJO classes: + *
+ * {@code
+ *      public enum Gender {
+ *          MALE, FEMALE
+ *      }
+ *
+ *      public class Person {
+ *          private final String name;
+ *          private final int age;
+ *          private final Gender gender;
+ *          private final City city;
+ *
+ *          public Person(String name, int age, Gender gender, City city) {
+ *              this.name = name;
+ *              this.age = age;
+ *              this.gender = gender;
+ *              this.city = city;
+ *          }
+ *
+ *          public String name() {
+ *              return name;
+ *          }
+ *
+ *          public int age() {
+ *              return age;
+ *          }
+ *
+ *          public Gender gender() {
+ *              return gender;
+ *          }
+ *
+ *          public City city() {
+ *              return city;
+ *          }
+ *      }
+ *
+ *      public class City {
+ *          private final String name;
+ *          private final long population;
+ *
+ *          public City(String name, long population) {
+ *              this.name = name;
+ *              this.population = population;
+ *          }
+ *
+ *          public String name() {
+ *              return name;
+ *          }
+ *
+ *          public long population() {
+ *              return population;
+ *          }
+ *      }
+ * }
+ * 
+ *

+ * To specify on which fields to operate the filters must be added like this: + *
+ * {@code
+ *      MFXFilterPane fp = new MFXFilterPane<>();
+ *      AbstractFilter nameFilter = new StringFilter<>("Name", Person::name)
+ *      AbstractFilter ageFilter = new IntegerFilter<>("Age", Person:.age);
+ *
+ *      // MFXFilterPane is so powerful and versatile that you can also filter by nested objects, something like this for example...
+ *      AbstractFilter populationFilter = new LongFilter<>("City Population", person -> person.city().population());
+ *
+ *      // It even works for enumerators...
+ *      AbstractFilter> genderFilter = new EnumFilter<>("Gender", Person::gender, Gender.class); // Note that the type is necessary
+ *
+ *      // Finally...
+ *      fp.getFilters().addAll(nameFilter, ageFilter, populationFilter, genderFilter);
+ * }
+ * 
+ * + *

+ * When a filter is created through the add button, a {@link FilterBean} is created and added to a list + * which holds the "active filters", {@link #getActiveFilters()}. + *

+ * Note that the list is not unmodifiable, potentially, you could even add your own custom filters, the UI will be updated anyway. + *

+ * As you can read in the {@link FilterBean} documentation, they can be chained according to the specified {@link FilterBean#getMode()}, + * this is also interactive, meaning that when you build more than one filter, a node will appear between them, that node specifies + * the {@link ChainMode}, by clicking on it, you can switch between modes. + *

+ * Once filters you have finished you can produce a filter by calling {@link #filter()}. + *

+ * The control also offers to icons that are intended to produce a filter or reset the control, + * to set their behavior use {@link #setOnFilter(EventHandler)} and {@link #setOnReset(EventHandler)}. + * + * @param + */ +public class MFXFilterPane extends Control { + //================================================================================ + // Properties + //================================================================================ + private final String STYLE_CLASS = "mfx-filter-pane"; + private final String STYLESHEET = MFXResourcesLoader.load("css/MFXFilterPane.css"); + private final StringProperty headerText = new SimpleStringProperty("Filters"); + private final ObservableList> filters = FXCollections.observableArrayList(); + private final ObservableList> activeFilters = FXCollections.observableArrayList(); + + private EventHandler onFilter = event -> {}; + private EventHandler onReset = event -> {}; + + //================================================================================ + // Constructors + //================================================================================ + public MFXFilterPane() { + initialize(); + } + + //================================================================================ + // Methods + //================================================================================ + private void initialize() { + getStyleClass().add(STYLE_CLASS); + getStylesheets().add(STYLESHEET); + } + + /** + * Builds a predicate from the list of built filters (active filters). + *

+ * The {@link FilterBean} are chained by using {@link PredicateUtils#chain(Predicate, Predicate, ChainMode)}. + *

+ * If the list is empty by default a predicate that always returns true is built. + */ + public Predicate filter() { + Predicate filter = null; + ChainMode mode = null; + + for (FilterBean activeFilter : activeFilters) { + if (filter == null) { + filter = activeFilter.predicate(); + mode = activeFilter.getMode(); + continue; + } + + filter = PredicateUtils.chain(filter, activeFilter.predicate(), mode); + mode = activeFilter.getMode(); + } + + return filter != null ? filter : t -> true; + } + + //================================================================================ + // Getters/Setters + //================================================================================ + public String getHeaderText() { + return headerText.get(); + } + + /** + * Specifies the text of the header. + */ + public StringProperty headerTextProperty() { + return headerText; + } + + public void setHeaderText(String headerText) { + this.headerText.set(headerText); + } + + /** + * @return the list of {@link AbstractFilter}s. Each of them + * represents an object's field o which the filter operates + */ + public ObservableList> getFilters() { + return filters; + } + + /** + * @return the list of built filters + */ + public ObservableList> getActiveFilters() { + return activeFilters; + } + + /** + * @return the action invoked when clicking on the filter icon + */ + public EventHandler getOnFilter() { + return onFilter; + } + + /** + * Sets the action to perform when the filter icon is clicked. + */ + public void setOnFilter(EventHandler onFilter) { + this.onFilter = onFilter; + } + + /** + * @return the action invoked when clicking on the reset icon + */ + public EventHandler getOnReset() { + return onReset; + } + + /** + * Sets the action to perform when the reset icon is clicked. + */ + public void setOnReset(EventHandler onReset) { + this.onReset = onReset; + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected Skin createDefaultSkin() { + return new MFXFilterPaneSkin<>(this); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXHLoader.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXHLoader.java old mode 100644 new mode 100755 index cf1ff679..7dac8925 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXHLoader.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXHLoader.java @@ -19,8 +19,8 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.beans.MFXLoaderBean; -import io.github.palexdev.materialfx.controls.enums.LoaderCacheLevel; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.enums.LoaderCacheLevel; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.utils.LoaderUtils; import io.github.palexdev.materialfx.utils.ToggleButtonsUtil; import javafx.application.Platform; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java old mode 100644 new mode 100755 index 55942d91..7705a967 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java @@ -19,7 +19,7 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.effects.ripple.base.IRippleGenerator; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -63,6 +63,8 @@ public class MFXIconWrapper extends StackPane { setSize(size); } + // TODO add constructor with MFXFontIcon description and replace everywhere + //================================================================================ // Methods //================================================================================ @@ -87,7 +89,7 @@ public class MFXIconWrapper extends StackPane { */ public MFXIconWrapper defaultRippleGeneratorBehavior() { addRippleGenerator(); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY())); addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { if (event.getButton() == MouseButton.PRIMARY) { rippleGenerator.generateRipple(event); @@ -103,7 +105,7 @@ public class MFXIconWrapper extends StackPane { * @see IRippleGenerator * @see MFXCircleRippleGenerator */ - public MFXIconWrapper rippleGeneratorBehavior(Function positionFunction) { + public MFXIconWrapper rippleGeneratorBehavior(Function positionFunction) { addRippleGenerator(); rippleGenerator.setRipplePositionFunction(positionFunction); addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXLabel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXLabel.java old mode 100644 new mode 100755 index 5c0b1ec8..e9f28203 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXLabel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXLabel.java @@ -34,7 +34,7 @@ import javafx.scene.text.Font; import java.util.List; -import static io.github.palexdev.materialfx.controls.enums.Styles.LabelStyles; +import static io.github.palexdev.materialfx.enums.Styles.LabelStyles; /** * This is the implementation of a label following Google's material design guidelines in JavaFX. diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXListView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXListView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotification.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotification.java deleted file mode 100644 index 7a6f3267..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotification.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.controls; - -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; -import javafx.animation.Animation; -import javafx.animation.PauseTransition; -import javafx.animation.Timeline; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.scene.layout.Region; -import javafx.stage.Popup; -import javafx.stage.Window; -import javafx.util.Duration; - -/** - * This is the implementation of a popup notification in JavaFX. - *

- * Extends {@code Popup}, provides animations for showing and closing and allows to - * close automatically the notification after some specified time. - */ -public class MFXNotification extends Popup { - //================================================================================ - // Properties - //================================================================================ - private Region content; - private boolean animate = false; - private final BooleanProperty hideAfter = new SimpleBooleanProperty(false); - - private Timeline inAnimation; - private Timeline outAnimation; - private PauseTransition hideAfterTransition; - - //================================================================================ - // Constructors - //================================================================================ - public MFXNotification(Region content) { - setAutoFix(false); - this.content = content; - this.getContent().add(content); - initialize(); - } - - public MFXNotification(Region content, boolean animate) { - this(content); - this.animate = animate; - } - - public MFXNotification(Region content, boolean animate, boolean hideAfter) { - this(content, animate); - this.hideAfter.set(hideAfter); - } - - //================================================================================ - // Methods - //================================================================================ - private void initialize() { - this.inAnimation = MFXAnimationFactory.FADE_IN.build(content, 600); - this.outAnimation = MFXAnimationFactory.FADE_OUT.build(content, 600); - this.hideAfterTransition = new PauseTransition(Duration.seconds(4)); - - this.hideAfter.addListener((observable, oldValue, newValue) -> { - if (newValue) { - addMouseHandlers(); - } else { - removeMouseHandlers(); - } - }); - } - - /** - * Closes the notification, plays out animation if requested. - *

- * Note: this method should be used rather than Popup's hide() method - */ - public void hideNotification() { - if (animate) { - outAnimation.setOnFinished(event -> super.hide()); - outAnimation.play(); - } else { - super.hide(); - } - } - - /** - * If the notification is set to hide automatically this method is called. - *

- * Adds MouseEntered and MouseExited handlers to stop/restart the - * close countdown when mouse is on/off the notification content. - */ - private void addMouseHandlers() { - this.content.setOnMouseEntered(event -> { - outAnimation.stop(); - hideAfterTransition.stop(); - this.content.setOpacity(1.0); - }); - this.content.setOnMouseExited(event -> { - if (hideAfterTransition.getStatus().equals(Animation.Status.STOPPED)) { - hideAfterTransition.playFromStart(); - } - }); - } - - /** - * If the notification is set to not hide automatically this method is called. - * Removes the handlers for MouseEntered and MouseExited - */ - private void removeMouseHandlers() { - this.content.setOnMouseEntered(null); - this.content.setOnMouseExited(null); - } - - /** - * Returns the notification's content - */ - public Region getNotificationContent() { - return content; - } - - /** - * Sets the notification's content and re-initializes the object. - */ - public void setContent(Region content) { - this.content = content; - this.getContent().add(content); - initialize(); - } - - public void setAnimate(boolean animate) { - this.animate = animate; - } - - public void setHideAfter(boolean hideAfter) { - this.hideAfter.set(hideAfter); - } - - public void setHideAfterDuration(Duration hideAfterDuration) { - this.hideAfterTransition.setDuration(hideAfterDuration); - } - - public void setInAnimation(Timeline inAnimation) { - this.inAnimation = inAnimation; - } - - public void setOutAnimation(Timeline outAnimation) { - this.outAnimation = outAnimation; - } - - //================================================================================ - // Override Methods - //================================================================================ - - /** - * Shows the notification on screen, plays in animation if requested, - * starts the close countdown if it's set to hide automatically. - * - * @param ownerWindow The owner of the popup. This must not be null. - * @param anchorX The x position of the popup anchor in screen coordinates - * @param anchorY The y position of the popup anchor in screen coordinates - * @throws NullPointerException if content is null - */ - @Override - public void show(Window ownerWindow, double anchorX, double anchorY) { - if (content == null) { - throw new NullPointerException("Notification content is null!"); - } - - if (animate) { - inAnimation.play(); - } - super.show(ownerWindow, anchorX, anchorY); - - if (hideAfter.get()) { - hideAfterTransition.setOnFinished(event -> hideNotification()); - hideAfterTransition.play(); - } - } -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotificationCenter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotificationCenter.java new file mode 100755 index 00000000..82ad9f21 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotificationCenter.java @@ -0,0 +1,721 @@ +package io.github.palexdev.materialfx.controls; + +import io.github.palexdev.materialfx.MFXResourcesLoader; +import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty; +import io.github.palexdev.materialfx.collections.TransformableListWrapper; +import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell; +import io.github.palexdev.materialfx.enums.NotificationCounterStyle; +import io.github.palexdev.materialfx.enums.NotificationState; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.selection.MultipleSelectionModel; +import io.github.palexdev.materialfx.skins.MFXNotificationCenterSkin; +import io.github.palexdev.materialfx.utils.ExecutionUtils; +import io.github.palexdev.materialfx.utils.ListChangeProcessor; +import io.github.palexdev.materialfx.utils.others.ReusableScheduledExecutor; +import io.github.palexdev.virtualizedfx.beans.NumberRange; +import io.github.palexdev.virtualizedfx.flow.simple.SimpleVirtualFlow; +import io.github.palexdev.virtualizedfx.utils.ListChangeHelper; +import io.github.palexdev.virtualizedfx.utils.ListChangeHelper.Change; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.LongBinding; +import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.event.EventHandler; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.input.MouseEvent; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.IntStream; + +import static io.github.palexdev.materialfx.enums.NotificationCounterStyle.NUMBER; + +/** + * A quite complex but easy to use implementation of a modern notification center. + *

+ * For the notifications it uses {@link TransformableListWrapper} as list implementation, this allows + * not only basic operations such additions, removals and replacements, but also filter and sort operations. + *

+ * It's composed by an icon and a popup that contains the list of notifications. + *

+ * A complete list of the features the notification center offers: + *

- Uses a virtual flow to show the notifications and have high performance + *

- Uses {@link MFXNotificationCell} as cells to contain the notifications, those special + * cells perfectly integrate with the selection mode feature of the notification center to show a checkbox when needed + *

- Has a {@link MultipleSelectionModel} to keep track of the selected notifications + *

- Has a property that keeps track of the number of unread notifications + *

- Has two styles for the unread counter: as a DOT, or as a dot with the NUMBER + *

- Allows to change the header's text + *

- Allows to toggle a "Do not disturb" mode + *

- Has a property that specifies whether the popup is showing or not + *

- Has a property that specifies whether the mouse is on the popup (it's bound, cannot be set, nor unbound) + *

- Allows to specify the space between the bell icon and the popup + *

- Allows to specify the popup's size. Must be greater than 0 and should always be larger than the bell's icon + *

- Has a context menu that opens when right-clicking on the virtual flow, by default it contains + * items to perform selection, filter and sort actions. It can also be disabled by setting a null factory, {@link #contextMenuFactoryProperty()} + *

- Has a flag to specify whether the notification center is animated or not + *

- Has a flag to specify whether visible notifications should be set as READ when the popup is shown + *

- Has a flag to specify whether notifications should be set as READ when they are dismissed + *

- Allows specifying the action to perform when the bell icon is pressed, by default inverts the value of the {@link #showingProperty()} + * to inform the popup that it should open/hide + *

Has an executor that runs every 60 seconds (by default, can be changed) that updates all the notifications. + * By update, I mean that {@link INotification#updateElapsed()} is called. The service can be stopped and started + * whenever desired, by default it is always started. + *

+ * As you can see it as a LOT to offer, and to be honest it's not everything I wanted to implement, but I decided to + * restrain myself for now, as adding any other feature would add more and more complexity. + */ +public class MFXNotificationCenter extends Control { + //================================================================================ + // Properties + //================================================================================ + private final String STYLE_CLASS = "mfx-notification-center"; + private final String STYLESHEET = MFXResourcesLoader.load("css/MFXNotificationCenter.css"); + + private final TransformableListWrapper notifications = new TransformableListWrapper<>(FXCollections.observableArrayList()); + + private final SimpleVirtualFlow virtualFlow; + private final MultipleSelectionModel selectionModel = new MultipleSelectionModel<>(notifications); + + private final BooleanProperty selectionMode = new SimpleBooleanProperty(false); + private final ReadOnlyLongWrapper unreadCount = new ReadOnlyLongWrapper(0); + private final LongBinding unreadCountBinding; + + private final ObjectProperty counterStyle = new SimpleObjectProperty<>(NUMBER); + private final StringProperty headerTextProperty = new SimpleStringProperty("Notifications"); + private final BooleanProperty doNotDisturb = new SimpleBooleanProperty(false); + private final BooleanProperty showing = new SimpleBooleanProperty(false); + + private final BooleanProperty popupHover = new SimpleBooleanProperty(false) { + @Override + public void unbind() {} + }; + private final DoubleProperty popupSpacing = new SimpleDoubleProperty(10); + private final DoubleProperty popupWidth = new SimpleDoubleProperty(450); + private final DoubleProperty popupHeight = new SimpleDoubleProperty(550); + + private MFXContextMenu contextMenu; + private final FunctionProperty contextMenuFactory = new FunctionProperty<>() { + @Override + public void set(Function newValue) { + if (contextMenu != null) { + contextMenu.dispose(); + contextMenu = null; + } + if (newValue != null) { + contextMenu = newValue.apply(virtualFlow); + } + super.set(newValue); + } + }; + + private boolean animated = true; + private boolean markAsReadOnShow = false; + private boolean markAsReadOnDismiss = false; + + private EventHandler onIconClicked = event -> setShowing(!isShowing()); + + private final ReusableScheduledExecutor notificationsUpdater; + + //================================================================================ + // Constructors + //================================================================================ + public MFXNotificationCenter() { + virtualFlow = SimpleVirtualFlow.Builder.create( + notifications, + notification -> new MFXNotificationCell(this, notification), + Orientation.VERTICAL + ); + + unreadCountBinding = Bindings.createLongBinding(() -> + notifications.stream() + .filter(notification -> notification.getState() == NotificationState.UNREAD) + .count(), + notifications + ); + + notificationsUpdater = new ReusableScheduledExecutor(Executors.newScheduledThreadPool( + 1, + r -> { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + } + )); + initialize(); + } + + //================================================================================ + // Methods + //================================================================================ + private void initialize() { + getStyleClass().add(STYLE_CLASS); + getStylesheets().add(STYLESHEET); + setPrefSize(400, 550); + defaultContextMenu(); + + unreadCount.bind(unreadCountBinding); + notifications.addListener((ListChangeListener) change -> { + if (!selectionModel.getSelection().isEmpty()) { + if (change.getList().isEmpty()) { + selectionModel.clearSelection(); + } else { + Change c = ListChangeHelper.processChange(change, NumberRange.of(0, Integer.MAX_VALUE)); + ListChangeProcessor updater = new ListChangeProcessor(selectionModel.getSelection().keySet()); + c.processReplacement((changed, removed) -> selectionModel.replaceSelection(changed.toArray(new Integer[0]))); + c.processAddition((from, to, added) -> { + updater.computeAddition(added.size(), from); + selectionModel.replaceSelection(updater.getIndexes().toArray(new Integer[0])); + }); + c.processRemoval((from, to, removed) -> { + updater.computeRemoval(removed, from); + selectionModel.replaceSelection(updater.getIndexes().toArray(new Integer[0])); + }); + } + } + }); + + notifications.predicateProperty().addListener(invalidated -> { + setSelectionMode(false); + selectionModel.clearSelection(); + }); + notifications.comparatorProperty().addListener(invalidated -> { + setSelectionMode(false); + selectionModel.clearSelection(); + }); + + showing.addListener((observable, oldValue, newValue) -> { + if (newValue && markAsReadOnShow) markVisibleNotificationsAs(NotificationState.READ); + }); + + startNotificationsUpdater(60, TimeUnit.SECONDS); + } + + /** + * Responsible for building and setting the default context menu. + */ + protected void defaultContextMenu() { + setContextMenuFactory(owner -> { + MFXContextMenuItem selectAll = new MFXContextMenuItem("Select All"); + selectAll.setAction(event -> { + if (notifications.isEmpty()) return; + NumberRange indexes = NumberRange.of(0, notifications.size() - 1); + selectionModel.replaceSelection(NumberRange.expandRange(indexes).toArray(Integer[]::new)); + } + ); + + MFXContextMenuItem selectRead = new MFXContextMenuItem("Select Read"); + selectRead.setAction(event -> { + if (notifications.isEmpty()) return; + Integer[] indexes = IntStream.range(0, notifications.size()) + .filter(i -> notifications.get(i).getState() == NotificationState.READ) + .boxed() + .toArray(Integer[]::new); + selectionModel.replaceSelection(indexes); + }); + + MFXContextMenuItem selectUnread = new MFXContextMenuItem("Select Unread"); + selectUnread.setAction(event -> { + if (notifications.isEmpty()) return; + Integer[] indexes = IntStream.range(0, notifications.size()) + .filter(i -> notifications.get(i).getState() == NotificationState.UNREAD) + .boxed() + .toArray(Integer[]::new); + selectionModel.replaceSelection(indexes); + }); + + MFXContextMenuItem clearSelection = new MFXContextMenuItem("Clear Selection"); + clearSelection.setAction(event -> selectionModel.clearSelection()); + + MFXContextMenuItem sortByState = new MFXContextMenuItem("Sort By State"); + sortByState.setAction(event -> notifications.setComparator(Comparator.comparing(INotification::getState))); + + MFXContextMenuItem sortByTime = new MFXContextMenuItem("Sort By Time"); + sortByTime.setAction(event -> notifications.setComparator(Comparator.comparing(INotification::getTime))); + + MFXContextMenuItem reverseSort = new MFXContextMenuItem("Reverse Sort"); + reverseSort.setAction(event -> { + if (notifications.getComparator() == null) return; + Comparator comparator = notifications.getComparator(); + notifications.setComparator(comparator.reversed()); + }); + + MFXContextMenuItem filterRead = new MFXContextMenuItem("Filter By Read"); + filterRead.setAction(event -> notifications.setPredicate(notification -> notification.getState() == NotificationState.READ)); + + MFXContextMenuItem filterUnread = new MFXContextMenuItem("Filter By Unread"); + filterUnread.setAction(event -> notifications.setPredicate(notification -> notification.getState() == NotificationState.UNREAD)); + + MFXContextMenuItem clearFilter = new MFXContextMenuItem("Clear Filter"); + clearFilter.setAction(event -> notifications.setPredicate(null)); + + MFXContextMenuItem clearSort = new MFXContextMenuItem("Clear Sort"); + clearSort.setAction(event -> notifications.setComparator(null)); + + + MFXContextMenu contextMenu = MFXContextMenu.Builder.build(owner) + .addMenuItem(selectAll) + .addMenuItem(selectRead) + .addMenuItem(selectUnread) + .addMenuItem(clearSelection) + .addSeparator() + .addMenuItem(sortByState) + .addMenuItem(sortByTime) + .addMenuItem(reverseSort) + .addSeparator() + .addMenuItem(filterRead) + .addMenuItem(filterUnread) + .addSeparator() + .addMenuItem(clearSort) + .addMenuItem(clearFilter) + .installAndGet(); + + ExecutionUtils.executeWhen( + getStylesheets(), + () -> { + String base = contextMenu.getUserAgentStylesheet(); // TODO change if making THAT change + List stylesheets = new ArrayList<>(List.of(base)); + stylesheets.addAll(getStylesheets()); + contextMenu.getStylesheets().setAll(stylesheets); + }, + true, + () -> true, + false + ); + return contextMenu; + }); + } + + /** + * Starts the notifications updater service to run the update task + * periodically, according to the given period and time unit. + * + * @see INotification#updateElapsed() + */ + public void startNotificationsUpdater(long period, TimeUnit timeUnit) { + notificationsUpdater.scheduleAtFixedRate( + () -> notifications.forEach(INotification::updateElapsed), + 0, + period, + timeUnit + ); + } + + /** + * Immediately stops the notifications updater service. + */ + public void stopNotificationsUpdater() { + notificationsUpdater.cancelNow(); + } + + /** + * Sets all the given notifications' state to the given state. + *

+ * At the end recomputes the number of unread notifications. + */ + public void markNotificationsAs(NotificationState state, INotification... notifications) { + for (INotification notification : notifications) { + notification.setNotificationState(state); + } + unreadCountBinding.invalidate(); + } + + /** + * Sets all the visible notifications' state to the given state. + */ + public void markVisibleNotificationsAs(NotificationState state) { + markNotificationsAs( + state, + getCells().values().stream() + .map(MFXNotificationCell::getNotification) + .toArray(INotification[]::new) + ); + } + + /** + * Sets all the selected notifications' state to the given state. + */ + public void markSelectedNotificationsAs(NotificationState state) { + markNotificationsAs(state, selectionModel.getSelection().values().toArray(INotification[]::new)); + } + + /** + * Sets all the notifications' state to the given state. + */ + public void markAllNotificationsAs(NotificationState state) { + markNotificationsAs(state, notifications.toArray(INotification[]::new)); + } + + /** + * Sets all the given notifications' state to READ, then removes them from the notifications list. + */ + public void dismiss(INotification... notifications) { + if (markAsReadOnDismiss) { + markNotificationsAs(NotificationState.READ, notifications); + } + this.notifications.removeAll(notifications); + } + + /** + * Sets all the visible notifications' state to READ, then removes them from the notifications list. + */ + public void dismissVisible() { + dismiss(getCells().values().stream().map(MFXNotificationCell::getNotification).toArray(INotification[]::new)); + } + + /** + * Sets all the selected notifications' state to READ, then removes them from the notifications list. + */ + public void dismissSelected() { + dismiss(getSelectionModel().getSelection().values().toArray(INotification[]::new)); + } + + /** + * Sets all the notifications' state to READ, then removes them from the notifications list. + */ + public void dismissAll() { + dismiss(notifications.toArray(INotification[]::new)); + } + + //================================================================================ + // Getters/Setters + //================================================================================ + + /** + * @return the list of notifications + */ + public TransformableListWrapper getNotifications() { + return notifications; + } + + /** + * @return the selection model instance used to keep track of selected notifications + */ + public MultipleSelectionModel getSelectionModel() { + return selectionModel; + } + + public boolean isSelectionMode() { + return selectionMode.get(); + } + + /** + * Specifies if the notification center is in selection mode. + *

+ * By default this mode triggers {@link MFXNotificationCell} to show a checkbox for selection + */ + public BooleanProperty selectionModeProperty() { + return selectionMode; + } + + public void setSelectionMode(boolean selectionMode) { + this.selectionMode.set(selectionMode); + } + + public long getUnreadCount() { + return unreadCount.get(); + } + + /** + * Specifies the number of unread notifications. + */ + public ReadOnlyLongProperty unreadCountProperty() { + return unreadCount.getReadOnlyProperty(); + } + + public NotificationCounterStyle getCounterStyle() { + return counterStyle.get(); + } + + /** + * Specifies the style of the unread counter. + */ + public ObjectProperty counterStyleProperty() { + return counterStyle; + } + + public void setCounterStyle(NotificationCounterStyle counterStyle) { + this.counterStyle.set(counterStyle); + } + + public String getHeaderTextProperty() { + return headerTextProperty.get(); + } + + /** + * Specifies the header's text. + */ + public StringProperty headerTextPropertyProperty() { + return headerTextProperty; + } + + public void setHeaderTextProperty(String headerTextProperty) { + this.headerTextProperty.set(headerTextProperty); + } + + public boolean isDoNotDisturb() { + return doNotDisturb.get(); + } + + /** + * Specifies if the notification center is in "Do not disturb" mode. + */ + public BooleanProperty doNotDisturbProperty() { + return doNotDisturb; + } + + public void setDoNotDisturb(boolean doNotDisturb) { + this.doNotDisturb.set(doNotDisturb); + } + + public boolean isShowing() { + return showing.get(); + } + + /** + * Specifies if the popup is shown/hidden. + *

+ * Can also be used to control the popup. + */ + public BooleanProperty showingProperty() { + return showing; + } + + public void setShowing(boolean showing) { + this.showing.set(showing); + } + + public boolean isPopupHover() { + return popupHover.get(); + } + + /** + * Specifies if the mouse is on the popup. + *

+ * Despite being a Read-Write property, it is bound to the popup's hover property + * and cannot be unbound. Attempts to set this will always fail with an exception. + */ + public BooleanProperty popupHoverProperty() { + return popupHover; + } + + public double getPopupSpacing() { + return popupSpacing.get(); + } + + /** + * Specifies the space between the bell icon and the popup + */ + public DoubleProperty popupSpacingProperty() { + return popupSpacing; + } + + public void setPopupSpacing(double popupSpacing) { + this.popupSpacing.set(popupSpacing); + } + + public double getPopupWidth() { + return popupWidth.get(); + } + + /** + * Specifies the popups' width. + */ + public DoubleProperty popupWidthProperty() { + return popupWidth; + } + + public void setPopupWidth(double popupWidth) { + this.popupWidth.set(popupWidth); + } + + public double getPopupHeight() { + return popupHeight.get(); + } + + /** + * Specifies the popup's height. + */ + public DoubleProperty popupHeightProperty() { + return popupHeight; + } + + public void setPopupHeight(double popupHeight) { + this.popupHeight.set(popupHeight); + } + + public Function getContextMenuFactory() { + return contextMenuFactory.get(); + } + + /** + * Specifies the function used to produce a {@link MFXContextMenu}. + *

+ * Setting this to null will remove the context menu. + */ + public FunctionProperty contextMenuFactoryProperty() { + return contextMenuFactory; + } + + public void setContextMenuFactory(Function contextMenuFactory) { + this.contextMenuFactory.set(contextMenuFactory); + } + + /** + * Specifies whether the notification center is animated. + */ + public boolean isAnimated() { + return animated; + } + + public void setAnimated(boolean animated) { + this.animated = animated; + } + + /** + * Specifies whether visible notification should be marked as read when the popup is shown. + */ + public boolean isMarkAsReadOnShow() { + return markAsReadOnShow; + } + + public void setMarkAsReadOnShow(boolean markAsReadOnShow) { + this.markAsReadOnShow = markAsReadOnShow; + } + + /** + * Specifies whether dismissed notifications should be set as READ. + *

+ * For this to work use one of the dismiss methods offered by the control. + */ + public boolean isMarkAsReadOnDismiss() { + return markAsReadOnDismiss; + } + + public void setMarkAsReadOnDismiss(boolean markAsReadOnDismiss) { + this.markAsReadOnDismiss = markAsReadOnDismiss; + } + + /** + * Specifies the action to perform when the bell icon is clicked. + */ + public EventHandler getOnIconClicked() { + return onIconClicked; + } + + public void setOnIconClicked(EventHandler onIconClicked) { + this.onIconClicked = onIconClicked; + } + + //================================================================================ + // Delegate Methods + //================================================================================ + + /** + * Delegate method for {@link SimpleVirtualFlow#getCell(int)}. + */ + public MFXNotificationCell getCell(int index) { + return virtualFlow.getCell(index); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#getCells()}. + */ + public Map getCells() { + return virtualFlow.getCells(); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#scrollBy(double)}. + */ + public void scrollBy(double pixels) { + virtualFlow.scrollBy(pixels); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#scrollTo(int)}. + */ + public void scrollTo(int index) { + virtualFlow.scrollTo(index); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#scrollToFirst()}. + */ + public void scrollToFirst() { + virtualFlow.scrollToFirst(); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#scrollToLast()}. + */ + public void scrollToLast() { + virtualFlow.scrollToLast(); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#scrollToPixel(double)}. + */ + public void scrollToPixel(double pixel) { + virtualFlow.scrollToPixel(pixel); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#setHSpeed(double, double)}. + */ + public void setHSpeed(double unit, double block) { + virtualFlow.setHSpeed(unit, block); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#setVSpeed(double, double)}. + */ + public void setVSpeed(double unit, double block) { + virtualFlow.setVSpeed(unit, block); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#getVerticalPosition()}. + */ + public double getVerticalPosition() { + return virtualFlow.getVerticalPosition(); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#getHorizontalPosition()}. + */ + public double getHorizontalPosition() { + return virtualFlow.getHorizontalPosition(); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#setCellFactory(Function)}. + */ + public void setCellFactory(Function cellFactory) { + virtualFlow.setCellFactory(cellFactory); + } + + /** + * Delegate method for {@link SimpleVirtualFlow#features()}. + */ + public SimpleVirtualFlow.Features features() { + return virtualFlow.features(); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected Skin createDefaultSkin() { + return new MFXNotificationCenterSkin(this, virtualFlow); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java old mode 100644 new mode 100755 index 7e0625d4..865fd378 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java @@ -52,7 +52,6 @@ import javafx.scene.paint.Color; *

* Side notes: *

- the context menu is redefined in the skin since some methods are private in the skin. - *

- see {@link #enableContextMenuTextSelectionFix(boolean)}. */ public class MFXPasswordField extends MFXTextField { //================================================================================ @@ -128,7 +127,7 @@ public class MFXPasswordField extends MFXTextField { */ @Override protected void defaultContextMenu() { - setMFXContextMenu(MFXContextMenu.Builder.build(this).install()); + setMFXContextMenu(MFXContextMenu.Builder.build(this).installAndGet()); } public String getPassword() { diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPopup.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPopup.java new file mode 100755 index 00000000..a4f1d377 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPopup.java @@ -0,0 +1,357 @@ +package io.github.palexdev.materialfx.controls; + +import io.github.palexdev.materialfx.beans.PopupPositionBean; +import io.github.palexdev.materialfx.beans.PositionBean; +import io.github.palexdev.materialfx.effects.Interpolators; +import io.github.palexdev.materialfx.skins.MFXPopupSkin; +import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; +import io.github.palexdev.materialfx.utils.AnimationUtils.TimelineBuilder; +import javafx.animation.Animation; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.css.PseudoClass; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.geometry.HPos; +import javafx.geometry.Point2D; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.PopupControl; +import javafx.scene.control.Skin; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.transform.Scale; +import javafx.stage.Window; + +import java.util.function.BiFunction; + +/** + * Custom and better implementation of a {@link PopupControl}. + *

+ * Setting the popup's content is now easier, it is animated (can be disabled), the animation + * can be changed but the most important features are the hover property and PseudoClass ("popup-hover" in css) + * that specifies when the mouse is on the content, and the new show methods that make use of {@link HPos} + * and {@link VPos} to compute the position for you, no more x and y computation, leave it to {@code MFXPopup}. + *

+ * Of course if needed JavaFX's methods are still available. + *

+ * Also allows to reposition the popup on demand by calling {@link #reposition()} and also + * offers a new {@link EventType}. + */ +public class MFXPopup extends PopupControl { + //================================================================================ + // Properties + //================================================================================ + private PopupPositionBean position; + private final ObjectProperty content = new SimpleObjectProperty<>() { + @Override + public void set(Node newValue) { + Node oldValue = get(); + if (oldValue != null) { + oldValue.removeEventFilter(MouseEvent.MOUSE_ENTERED, entered); + oldValue.removeEventFilter(MouseEvent.MOUSE_EXITED, exited); + } + newValue.addEventFilter(MouseEvent.MOUSE_ENTERED, entered); + newValue.addEventFilter(MouseEvent.MOUSE_EXITED, exited); + super.set(newValue); + } + }; + private BiFunction animationProvider; + private boolean animated = true; + + private final PseudoClass HOVER_PSEUDO_CLASS = PseudoClass.getPseudoClass("popup-hover"); + private final BooleanProperty hover = new SimpleBooleanProperty(false); + private final EventHandler entered = event -> setHover(true); + private final EventHandler exited = event -> setHover(false); + + //================================================================================ + // Constructors + //================================================================================ + public MFXPopup() { + animationProvider = (node, scale) -> TimelineBuilder.build() + .show(150, node) + .add(KeyFrames.of(150, + scale.xProperty(), 1, Interpolators.INTERPOLATOR_V2 + )) + .add(KeyFrames.of(200, + scale.yProperty(), 1, Interpolators.INTERPOLATOR_V2 + + )) + .getAnimation(); + initialize(); + } + + public MFXPopup(Node content) { + setContent(content); + animationProvider = (node, scale) -> TimelineBuilder.build() + .show(150, node) + .add(KeyFrames.of(150, + scale.xProperty(), 1, Interpolators.INTERPOLATOR_V2 + )) + .add(KeyFrames.of(200, + scale.yProperty(), 1, Interpolators.INTERPOLATOR_V2 + + )) + .getAnimation(); + initialize(); + } + + //================================================================================ + // Methods + //================================================================================ + private void initialize() { + setAutoFix(false); + setAutoHide(true); + setHideOnEscape(true); + + hover.addListener(invalidated -> pseudoClassStateChanged(HOVER_PSEUDO_CLASS, hover.get())); + } + + /** + * Shows the popup at the BOTTOM_RIGHT of the specified node. + *

+ * Calls {@link #show(Node, HPos, VPos, double, double)}. + */ + public void show(Node node) { + show(node, HPos.RIGHT, VPos.BOTTOM, 0, 0); + } + + /** + * Shows the popup at the given positions. + *

+ * Calls {@link #show(Node, HPos, VPos, double, double)}. + */ + public void show(Node node, HPos hPos, VPos vPos) { + show(node, hPos, vPos, 0, 0); + } + + /** + * Shows the popup at the given positions, then shifts the computed coordinates + * by the given offsets. + *

+ * Once the position is computed, it is stored in a {@link PopupPositionBean} alongside other + * useful info. These info are important because before the popup is actually shown, its skin + * is created. The {@link MFXPopupSkin} uses these info to properly position and animate the popup. + * This is needed because before creating the skin the content is not laid out so its sizes/bounds are 0 + * and the "real" coordinates cannot be computed. For this reason, the coordinates stored in the + * position bean are not reliable, because they do not take into account the adjustments applied + * by the skin. (as of now, maybe will be improved in the future) + */ + public void show(Node node, HPos hPos, VPos vPos, double xOffset, double yOffset) { + if (node.getScene() == null || node.getScene().getWindow() == null) { + throw new IllegalStateException("Cannot show the popup. The node must be attached to a scene/window!"); + } + + Window window = node.getScene().getWindow(); + PositionBean position = computePosition(node, window, hPos, vPos); + this.position = new PopupPositionBean(node, position, hPos, vPos, xOffset, yOffset); + show(window, position.getX(), position.getY()); + } + + // TODO add show Window + + /** + * Repositions the popup by recomputing the position from + * the previous stored info. + *

+ * This should be called when the owner's position changes. + * + * @see #show(Node, HPos, VPos, double, double) + * @see PopupPositionBean + */ + public void reposition() { + if (!isShowing() || position == null) return; + position = computeReposition(); + double containerW = getContent().prefWidth(-1); + double containerH = getContent().prefHeight(-1); + HPos hPos = position.getHPos(); + VPos vPos = position.getVPos(); + double xOffset = position.getXOffset(); + double yOffset = position.getYOffset(); + + double tx = 0; + double ty = 0; + switch (hPos) { + case CENTER: { + tx = -(Math.abs(containerW - position.getOwnerWidth()) / 2) + xOffset; + break; + } + case LEFT: { + tx = -containerW + xOffset; + break; + } + case RIGHT: { + tx = xOffset; + break; + } + } + switch (vPos) { + case BOTTOM: { + ty = yOffset; + break; + } + case CENTER: { + ty = -(Math.abs(containerH - position.getOwnerHeight()) / 2) + yOffset; + break; + } + case TOP: { + ty = -containerH + yOffset; + break; + } + } + + setX(position.getX() + tx); + setY(position.getY() + ty); + } + + /** + * Computes the initial (x, y) coordinates for the given parameters. + * These will be "refined" in the skin. + */ + private PositionBean computePosition(Node node, Window window, HPos hPos, VPos vPos) { + Point2D origin = node.localToScene(0, 0); + + double nodeWidth = node.prefWidth(-1); + double nodeHeight = node.prefHeight(-1); + double x = window.getX() + origin.getX() + node.getScene().getX() + (hPos == HPos.LEFT ? nodeWidth : 0); + double y = window.getY() + origin.getY() + node.getScene().getY() + (vPos == VPos.BOTTOM ? nodeHeight : 0); + return PositionBean.of(x, y); + } + + /** + * Used to compute the new position of the popup when repositioning. + */ + private PopupPositionBean computeReposition() { + Node node = position.getOwner(); + Window window = node.getScene().getWindow(); + HPos hPos = position.getHPos(); + VPos vPos = position.getVPos(); + PositionBean positionBean = computePosition(node, window, hPos, vPos); + return new PopupPositionBean(node, positionBean, hPos, vPos, position.getXOffset(), position.getYOffset()); + } + + //================================================================================ + // Getters/Setters + //================================================================================ + + /** + * @return the instance of the {@link PopupPositionBean} computed when showing + * or repositioning the popup. Note that it will return null if the popup is not showing. + */ + public PopupPositionBean getPosition() { + return position; + } + + public Node getContent() { + return content.get(); + } + + /** + * Specifies the popup's content. + *

+ * As of now changing the content while the popup is shown won't have any effect. + * For it to work, the popup must be closed and reopened again. + * As of now, there's no plan to improve this because such use case would introduce + * an unnecessary layer of complexity and in my opinion it's also a discouraged practice to change + * the popup's content while open. It would be better to use a container Pane and change its content. + *

+ * This is a property because when the content changes it's needed to add the necessary handlers to it + * to make the "hover" feature work. + *

+ * The content cannot be null. + */ + public ObjectProperty contentProperty() { + return content; + } + + public void setContent(Node content) { + this.content.set(content); + } + + /** + * @return the function used by the skin to produce the popup's animation + */ + public BiFunction getAnimationProvider() { + return animationProvider; + } + + /** + * Sets the function used by the skin to produce the popup's animation. + *

+ * The input parameters are the popup's container (it's a {@link StackPane}, and + * the {@link Scale} transform applied to the container. + */ + public void setAnimationProvider(BiFunction animationProvider) { + this.animationProvider = animationProvider; + } + + /** + * Specifies whether tha popup's is animated. + */ + public boolean isAnimated() { + return animated; + } + + public void setAnimated(boolean animated) { + this.animated = animated; + } + + public boolean isHover() { + return hover.get(); + } + + /** + * Specifies if the mouse is on the popup's content. + */ + public BooleanProperty hoverProperty() { + return hover; + } + + public void setHover(boolean hover) { + this.hover.set(hover); + } + + //================================================================================ + // Override Methods + //================================================================================ + + /** + * {@inheritDoc} + *

+ * Overridden to set the stored {@link PopupPositionBean} to null. + */ + @Override + public void hide() { + position = null; + super.hide(); + } + + @Override + protected Skin createDefaultSkin() { + return new MFXPopupSkin(this); + } + + //================================================================================ + // Events + //================================================================================ + + /** + * Events class for {@link MFXPopup}s. + *

+ * Introduces a new event type: "REPOSITION_EVENT". It can be used to inform a popup that it should + * reposition. The typical use of this event is in case of Control/Skin or in general MVC pattern. + * If you don't have a reference to the popup, fire this event and capture it in the class that has the popup's reference + * then call, {@link #reposition()}. + */ + public static class MFXPopupEvent extends Event { + + public static final EventType REPOSITION_EVENT = new EventType<>(ANY, "Reposition Event"); + + public MFXPopupEvent(EventType eventType) { + super(eventType); + } + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressBar.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressBar.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressSpinner.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressSpinner.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRadioButton.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRadioButton.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRectangleToggleNode.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRectangleToggleNode.java old mode 100644 new mode 100755 index cacfde16..dc83461c --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRectangleToggleNode.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRectangleToggleNode.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.controls.base.AbstractMFXToggleNode; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; import io.github.palexdev.materialfx.effects.ripple.RippleClipType; import io.github.palexdev.materialfx.skins.MFXRectangleToggleNodeSkin; import javafx.beans.property.ObjectProperty; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXScrollPane.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXScrollPane.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSimpleNotification.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSimpleNotification.java new file mode 100755 index 00000000..09f3a7f2 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSimpleNotification.java @@ -0,0 +1,147 @@ +package io.github.palexdev.materialfx.controls; + +import io.github.palexdev.materialfx.enums.NotificationState; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.utils.StringUtils; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; + +import java.time.Instant; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Simple implementation of {@link INotification}. + *

+ * By default the {@link INotification#getTimeToStringConverter()} function is set to use {@link StringUtils#timeToHumanReadable(long)}. + * By default the {@link INotification#setOnUpdateElapsed(BiConsumer)} function is set to do nothing. + *

+ * Offers a Builder to build a notification with fluent design. + */ +public class MFXSimpleNotification implements INotification { + //================================================================================ + // Properties + //================================================================================ + private Region content; + + private final ObjectProperty state = new SimpleObjectProperty<>(NotificationState.UNREAD); + private final long createdTime; + private Function timeToStringConverter = StringUtils::timeToHumanReadable; + private BiConsumer onUpdate = (elapsedLong, elapsedString) -> {}; + + //================================================================================ + // Constructors + //================================================================================ + protected MFXSimpleNotification() { + this(new AnchorPane()); + } + + public MFXSimpleNotification(Region content) { + createdTime = Instant.now().getEpochSecond(); + if (content == null) { + throw new IllegalArgumentException("Content cannot be null!"); + } + this.content = content; + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + @Override + public Region getContent() { + return content; + } + + protected void setContent(Region content) { + this.content = content; + } + + @Override + public NotificationState getState() { + return state.get(); + } + + @Override + public ObjectProperty notificationStateProperty() { + return state; + } + + @Override + public void setNotificationState(NotificationState state) { + this.state.set(state); + } + + @Override + public long getTime() { + return createdTime; + } + + @Override + public long getElapsedTime() { + return Instant.now().getEpochSecond() - createdTime; + } + + @Override + public Function getTimeToStringConverter() { + return timeToStringConverter; + } + + @Override + public void setTimeToStringConverter(Function converter) { + this.timeToStringConverter = converter; + } + + @Override + public void updateElapsed() { + long elapsedTime = getElapsedTime(); + onUpdate.accept(elapsedTime, timeToStringConverter.apply(elapsedTime)); + } + + @Override + public void setOnUpdateElapsed(BiConsumer elapsedConsumer) { + this.onUpdate = elapsedConsumer; + } + + //================================================================================ + // Builder + //================================================================================ + public static class Builder { + private final MFXSimpleNotification notification = new MFXSimpleNotification(); + + protected Builder() {} + + public static Builder build() { + return new Builder(); + } + + public Builder setContent(Region content) { + if (content == null) { + throw new IllegalArgumentException("Content cannot be null!"); + } + notification.setContent(content); + return this; + } + + public Builder setState(NotificationState state) { + notification.setNotificationState(state); + return this; + } + + public Builder setTimeToStringConverter(Function converter) { + notification.setTimeToStringConverter(converter); + return this; + } + + public Builder setOnUpdateElapsed(BiConsumer elapsedConsumer) { + notification.setOnUpdateElapsed(elapsedConsumer); + return this; + } + + public MFXSimpleNotification get() { + return notification; + } + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java old mode 100644 new mode 100755 index 75c44ba3..19159fe8 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java @@ -20,10 +20,10 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.beans.NumberRange; -import io.github.palexdev.materialfx.controls.enums.SliderEnums.SliderMode; -import io.github.palexdev.materialfx.controls.enums.SliderEnums.SliderPopupSide; +import io.github.palexdev.materialfx.enums.SliderEnums.SliderMode; +import io.github.palexdev.materialfx.enums.SliderEnums.SliderPopupSide; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.skins.MFXSliderSkin; import io.github.palexdev.materialfx.utils.NodeUtils; @@ -264,7 +264,7 @@ public class MFXSlider extends Control { rippleGenerator.setMouseTransparent(true); rippleGenerator.setRadiusMultiplier(2.5); rippleGenerator.setRippleRadius(6); - rippleGenerator.setRipplePositionFunction(mouseEvent -> new RipplePosition(stackPane.getWidth() / 2, stackPane.getHeight() / 2)); + rippleGenerator.setRipplePositionFunction(mouseEvent -> new PositionBean(stackPane.getWidth() / 2, stackPane.getHeight() / 2)); stackPane.addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple); stackPane.getChildren().add(rippleGenerator); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStageDialog.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStageDialog.java old mode 100644 new mode 100755 index dc558410..e52d565b --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStageDialog.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStageDialog.java @@ -19,9 +19,9 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog; -import io.github.palexdev.materialfx.controls.enums.DialogType; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; -import io.github.palexdev.materialfx.controls.factories.MFXStageDialogFactory; +import io.github.palexdev.materialfx.enums.DialogType; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXStageDialogFactory; import io.github.palexdev.materialfx.effects.MFXScrimEffect; import io.github.palexdev.materialfx.utils.AnimationUtils; import javafx.animation.KeyFrame; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepper.java old mode 100644 new mode 100755 index b6086963..9da9a413 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepper.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepper.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.controls.MFXStepperToggle.MFXStepperToggleEvent; -import io.github.palexdev.materialfx.controls.enums.StepperToggleState; +import io.github.palexdev.materialfx.enums.StepperToggleState; import io.github.palexdev.materialfx.skins.MFXStepperSkin; import io.github.palexdev.materialfx.utils.NodeUtils; import io.github.palexdev.materialfx.validation.base.AbstractMFXValidator; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepperToggle.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepperToggle.java old mode 100644 new mode 100755 index 79209c98..30ff5049 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepperToggle.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepperToggle.java @@ -19,8 +19,8 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; -import io.github.palexdev.materialfx.controls.enums.StepperToggleState; -import io.github.palexdev.materialfx.controls.enums.TextPosition; +import io.github.palexdev.materialfx.enums.StepperToggleState; +import io.github.palexdev.materialfx.enums.TextPosition; import io.github.palexdev.materialfx.skins.MFXStepperSkin; import io.github.palexdev.materialfx.skins.MFXStepperToggleSkin; import io.github.palexdev.materialfx.validation.MFXDialogValidator; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableRow.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableRow.java old mode 100644 new mode 100755 index 0e801eb7..c365a85a --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableRow.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableRow.java @@ -19,10 +19,10 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; import io.github.palexdev.materialfx.effects.ripple.RippleClipType; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.css.PseudoClass; @@ -89,7 +89,7 @@ public class MFXTableRow extends HBox { rippleGenerator.setClipSupplier(() -> new RippleClipTypeFactory(RippleClipType.RECTANGLE).setOffsetW(10).build(this)); rippleGenerator.setComputeRadiusMultiplier(true); rippleGenerator.setManaged(false); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> new PositionBean(event.getX(), event.getY())); rippleGenerator.setTranslateX(-5); rippleGenerator.rippleRadiusProperty().bind(widthProperty().divide(2.0)); addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableSortModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableSortModel.java old mode 100644 new mode 100755 index f1d17cf5..7bcefd86 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableSortModel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableSortModel.java @@ -19,7 +19,7 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.controls.cell.MFXTableColumn; -import io.github.palexdev.materialfx.controls.enums.SortState; +import io.github.palexdev.materialfx.enums.SortState; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.ListChangeListener; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java old mode 100644 new mode 100755 index 594ca047..cac61219 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java @@ -19,7 +19,7 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; -import io.github.palexdev.materialfx.controls.enums.DialogType; +import io.github.palexdev.materialfx.enums.DialogType; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.skins.MFXTextFieldSkin; import io.github.palexdev.materialfx.utils.ColorUtils; @@ -262,7 +262,7 @@ public class MFXTextField extends TextField implements Validated. - */ - -package io.github.palexdev.materialfx.controls; - -import io.github.palexdev.materialfx.controls.base.AbstractMFXNotificationPane; -import io.github.palexdev.materialfx.font.MFXFontIcon; -import io.github.palexdev.materialfx.utils.NodeUtils; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; - -/** - * This class extends {@code AbstractMFXNotificationPane} and it serves as an - * example of a basic pane for a {@code MFXNotification}. - */ -public class SimpleMFXNotificationPane extends AbstractMFXNotificationPane { - //================================================================================ - // Properties - //================================================================================ - private final StackPane headerNode; - private final Label headerLabel; - private final Label titleLabel; - private final MFXScrollPane contentScroll; - private final Label contentLabel; - - private final MFXButton closeButton; - private final MFXButton okButton; - private final HBox buttonsBox; - - private EventHandler closeHandler; - - //================================================================================ - // Constructors - //================================================================================ - public SimpleMFXNotificationPane(String header, String title, String content) { - this(null, header, title, content); - } - - public SimpleMFXNotificationPane(Node icon, String header, String title, String content) { - // Header - headerNode = new StackPane(); - headerLabel = new Label(); - headerLabel.textProperty().bind(headerProperty); - headerLabel.getStyleClass().add("header-label"); - headerLabel.setGraphic(icon); - headerLabel.setGraphicTextGap(7); - headerLabel.setPadding(new Insets(15, 15, 0, 15)); - headerProperty.set(header); - - closeButton = new MFXButton(""); - closeButton.setPrefSize(18, 18); - closeButton.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - MFXFontIcon xIcon = new MFXFontIcon("mfx-x-circle", 14); - closeButton.setGraphic(xIcon); - closeButton.setRippleRadius(12); - closeButton.setRippleColor(Color.rgb(255, 0, 0, 0.1)); - - NodeUtils.makeRegionCircular(closeButton); - - headerNode.getChildren().addAll(headerLabel, closeButton); - StackPane.setMargin(headerLabel, new Insets(0, 0, 10, 0)); - StackPane.setAlignment(headerLabel, Pos.CENTER_LEFT); - StackPane.setAlignment(closeButton, Pos.TOP_RIGHT); - StackPane.setMargin(closeButton, new Insets(6, 6, 8, 8)); - - // Title - titleLabel = new Label(); - titleLabel.textProperty().bind(titleProperty); - titleLabel.getStyleClass().add("title-label"); - titleLabel.setPadding(new Insets(0, 0, 0, 15)); - titleProperty.set(title); - - // Content - contentScroll = new MFXScrollPane(); - contentScroll.setFitToWidth(true); - contentScroll.setPrefSize(200, 200); - - contentLabel = new Label(); - contentLabel.textProperty().bind(contentProperty); - contentLabel.getStyleClass().add("content-label"); - contentLabel.setWrapText(true); - contentProperty.set(content); - - contentScroll.setContent(contentLabel); - contentScroll.setPadding(new Insets(5, 10, 5, 10)); - VBox.setMargin(contentScroll, new Insets(5, 10, 5, 10)); - - // Buttons - buttonsBox = new HBox(); - buttonsBox.getStyleClass().add("buttons-box"); - buttonsBox.setAlignment(Pos.CENTER_RIGHT); - buttonsBox.setSpacing(20); - okButton = new MFXButton("OK"); - okButton.setPrefWidth(50); - buttonsBox.getChildren().add(okButton); - VBox.setMargin(buttonsBox, new Insets(2, 10, 2, 0)); - - getChildren().addAll(headerNode, titleLabel, contentScroll, buttonsBox); - initialize(); - } - - //================================================================================ - // Methods - //================================================================================ - private void initialize() { - getStyleClass().add(STYLE_CLASS); - setPrefSize(360, 160); - setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - } - - /** - * Adds the specified button the the HBox at the bottom of the VBox. - */ - public void addButton(Button button) { - this.buttonsBox.getChildren().add(button); - } - - /** - * Adds the specified button to the HBox at the bottom of the VBox at the specified index. - */ - public void addButton(int index, Button button) { - try { - this.buttonsBox.getChildren().add(index, button); - } catch (IndexOutOfBoundsException ex) { - throw new IndexOutOfBoundsException("Could not add button at index:" + index + - ", list size is:" + this.buttonsBox.getChildren().size()); - } - } - - /** - * Since this class has no references to {@code MFXNotification} because they are two distinct and separate concepts, - * the close button action must be set after instantiating a {@code MFXNotification}. - */ - public void setCloseHandler(EventHandler closeHandler) { - if (this.closeHandler != null) { - this.closeButton.removeEventHandler(MouseEvent.MOUSE_PRESSED, this.closeHandler); - } - - this.closeHandler = closeHandler; - this.closeButton.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler); - } - - public StackPane getHeaderNode() { - return headerNode; - } - - public Label getHeaderLabel() { - return headerLabel; - } - - public Label getTitleLabel() { - return titleLabel; - } - - public MFXScrollPane getContentScroll() { - return contentScroll; - } - - public Label getContentLabel() { - return contentLabel; - } - - public MFXButton getCloseButton() { - return closeButton; - } - - public MFXButton getOkButton() { - return okButton; - } - - public HBox getButtonsBox() { - return buttonsBox; - } - - /* - * Unused code. - * Before using the scroll pane for the content label the header had an extra button, - * an expand button similar to Android's notifications, that button was set to be visible - * only if the content was truncated and on click the prefHeight was incremented by the specified value with - * a Transition, however the problem with this approach was the PositionManager system because as you can see in the following code, - * if the content was still truncated at the end of the transition the method was executed again and again until the isTruncated property - * was false. The PositionManager had two extra methods, repositionNotifications and buildRepositionAnimation with the expandValue as parameter, - * the reposition method had to be recalled every time too with the same frequency as the expandNotificationMethod but as you can see this class - * has no references to PositionManager because of course they are two distinct and separate concepts. - * - * I don't want to delete this code because I still believe it can be implemented in some way, but in the end I opted for a scroll pane - * because it was way easier. - */ - /* - public void expandNotification(double expandValue) { - final double currHeight = getPrefHeight(); - Transition expand = new Transition() { - { - setCycleDuration(Duration.millis(0.1)); - } - - @Override - protected void interpolate(double frac) { - setPrefHeight(currHeight + (expandValue * frac)); - } - }; - expand.setOnFinished(event -> { - if (isTruncated.get()) { - expandNotification(expandValue); - } - }); - expand.play(); - } - */ -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXDialog.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXDialog.java old mode 100644 new mode 100755 index 469c11bb..46fdf8d4 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXDialog.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXDialog.java @@ -20,8 +20,8 @@ package io.github.palexdev.materialfx.controls.base; import io.github.palexdev.materialfx.controls.MFXButton; import io.github.palexdev.materialfx.controls.MFXDialog; -import io.github.palexdev.materialfx.controls.enums.DialogType; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.enums.DialogType; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.effects.MFXScrimEffect; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.animation.ParallelTransition; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXListView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXListView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXNotificationPane.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXNotificationPane.java deleted file mode 100644 index 163be3fe..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXNotificationPane.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.controls.base; - -import io.github.palexdev.materialfx.MFXResourcesLoader; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.scene.layout.VBox; - -/** - * Base class for a material notification content pane. - *

- * Extends {@code VBox} and redefines the style class to "mfx-notification" for usage in CSS. - */ -public abstract class AbstractMFXNotificationPane extends VBox { - //================================================================================ - // Properties - //================================================================================ - protected final String STYLE_CLASS = "mfx-notification"; - protected final String STYLESHEET = MFXResourcesLoader.load("css/MFXNotification.css"); - - protected final StringProperty headerProperty = new SimpleStringProperty(""); - protected final StringProperty titleProperty = new SimpleStringProperty(""); - protected final StringProperty contentProperty = new SimpleStringProperty(""); - - //================================================================================ - // Methods - //================================================================================ - public String getHeaderProperty() { - return headerProperty.get(); - } - - public StringProperty headerPropertyProperty() { - return headerProperty; - } - - public void setHeaderProperty(String headerProperty) { - this.headerProperty.set(headerProperty); - } - - public String getTitleProperty() { - return titleProperty.get(); - } - - public StringProperty titlePropertyProperty() { - return titleProperty; - } - - public void setTitleProperty(String titleProperty) { - this.titleProperty.set(titleProperty); - } - - public String getContentProperty() { - return contentProperty.get(); - } - - public StringProperty contentPropertyProperty() { - return contentProperty; - } - - public void setContentProperty(String contentProperty) { - this.contentProperty.set(contentProperty); - } - - //================================================================================ - // Override Methods - //================================================================================ - @Override - public String getUserAgentStylesheet() { - return STYLESHEET; - } -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXToggleNode.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXToggleNode.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeCell.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeItem.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeItem.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/IListView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/IListView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckListCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckListCell.java old mode 100644 new mode 100755 index 6f4f53cc..c9b5db49 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckListCell.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckListCell.java @@ -19,11 +19,11 @@ package io.github.palexdev.materialfx.controls.cell; import io.github.palexdev.materialfx.MFXResourcesLoader; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.controls.MFXCheckListView; import io.github.palexdev.materialfx.controls.MFXCheckbox; import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.beans.binding.Bindings; import javafx.scene.Node; @@ -90,7 +90,7 @@ public class MFXCheckListCell extends AbstractMFXListCell { */ protected void setupRippleGenerator() { rippleGenerator.setManaged(false); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY())); rippleGenerator.rippleRadiusProperty().bind(widthProperty().divide(2.0)); addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { if (NodeUtils.inHierarchy(event, checkbox)) { diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXDateCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXDateCell.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXListCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXListCell.java old mode 100644 new mode 100755 index 00b86de3..854a2ae5 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXListCell.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXListCell.java @@ -22,7 +22,7 @@ import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.controls.MFXListView; import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.input.MouseButton; @@ -83,7 +83,7 @@ public class MFXListCell extends AbstractMFXListCell { */ protected void setupRippleGenerator() { rippleGenerator.setManaged(false); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY())); rippleGenerator.rippleRadiusProperty().bind(widthProperty().divide(2.0)); addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { if (event.getButton() == MouseButton.PRIMARY) { diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXNotificationCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXNotificationCell.java new file mode 100755 index 00000000..36fd2227 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXNotificationCell.java @@ -0,0 +1,224 @@ +package io.github.palexdev.materialfx.controls.cell; + +import io.github.palexdev.materialfx.controls.MFXCheckbox; +import io.github.palexdev.materialfx.controls.MFXNotificationCenter; +import io.github.palexdev.materialfx.effects.Interpolators; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; +import io.github.palexdev.materialfx.utils.AnimationUtils.ParallelBuilder; +import io.github.palexdev.virtualizedfx.cell.Cell; +import javafx.beans.binding.Bindings; +import javafx.beans.property.*; +import javafx.css.PseudoClass; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; + +/** + * Implementation of a {@link Cell} for usage with {@link MFXNotificationCenter}. + *

+ * Includes a checkbox to allow selecting notifications. + */ +public class MFXNotificationCell extends HBox implements Cell { + //================================================================================ + // Properties + //================================================================================ + private final String STYLE_CLASS = "mfx-notification-cell"; + private final MFXNotificationCenter notificationCenter; + private final ReadOnlyObjectWrapper notification = new ReadOnlyObjectWrapper<>(); + private final ReadOnlyIntegerWrapper index = new ReadOnlyIntegerWrapper(); + private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(); + + protected final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected"); + protected final StackPane container; + protected final MFXCheckbox checkbox; + + //================================================================================ + // Constructors + //================================================================================ + public MFXNotificationCell(MFXNotificationCenter notificationCenter, INotification notification) { + this.notificationCenter = notificationCenter; + setNotification(notification); + + setPrefHeight(65); + setMaxHeight(USE_PREF_SIZE); + setAlignment(Pos.CENTER_LEFT); + + checkbox = new MFXCheckbox(""); + checkbox.setId("check"); + checkbox.setMarkType("mfx-variant7-mark"); + + container = new StackPane(checkbox); + container.setMinWidth(USE_PREF_SIZE); + container.setPrefWidth(0); + container.setMaxWidth(USE_PREF_SIZE); + + Rectangle clip = new Rectangle(); + clip.widthProperty().bind(container.widthProperty()); + clip.heightProperty().bind(container.heightProperty()); + container.setClip(clip); + + initialize(); + } + + //================================================================================ + // Methods + //================================================================================ + + /** + * Adds the style class, calls {@link #setBehavior()} then {@link #render(INotification)} + * for the first time. + */ + private void initialize() { + getStyleClass().add(STYLE_CLASS); + setBehavior(); + render(getNotification()); + } + + /** + * Sets the following behaviors: + *

+ * - Binds the selected property to the notification center' selection model (checks for index).

+ * - Updates the selected PseudoClass state when selected property changes.

+ * - Adds a listener to the checkbox' selection state to call {@link #updateSelection(boolean)}.

+ * - Adds a listener to the notification center's {@link MFXNotificationCenter#selectionModeProperty()} to call {@link #expand(boolean)}. + */ + protected void setBehavior() { + selected.addListener(invalidated -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get())); + selected.bind(Bindings.createBooleanBinding(() -> { + boolean contained = notificationCenter.getSelectionModel().getSelection().containsKey(getIndex()); + checkbox.setSelected(contained); + return contained; + }, notificationCenter.getSelectionModel().selectionProperty(), index)); + + checkbox.selectedProperty().addListener((observable, oldValue, newValue) -> updateSelection(newValue)); + notificationCenter.selectionModeProperty().addListener((observable, oldValue, newValue) -> expand(newValue)); + } + + /** + * Responsible for rendering the cell's content. + */ + protected void render(INotification notification) { + if (notificationCenter.isSelectionMode()) { + checkbox.setOpacity(1.0); + checkbox.setPrefWidth(45); + } + getChildren().setAll(container, notification.getContent()); + } + + /** + * Responsible for updating the selection state according to the checkbox' state. + *

+ * If checked is true then the cell should be selected, otherwise it is deselected. + */ + protected void updateSelection(boolean checked) { + int index = getIndex(); + if (checked) { + notificationCenter.getSelectionModel().selectIndex(index); + } else { + notificationCenter.getSelectionModel().deselectIndex(index); + } + } + + /** + * Responsible for showing/hiding the checkbox. + */ + protected void expand(boolean selectionMode) { + double width = selectionMode ? 45 : 0; + double opacity = selectionMode ? 1 : 0; + if (notificationCenter.isAnimated()) { + ParallelBuilder.build() + .add( + KeyFrames.of(150, checkbox.opacityProperty(), opacity, Interpolators.EASE_OUT), + KeyFrames.of(250, container.prefWidthProperty(), width, Interpolators.EASE_OUT_SINE) + ).getAnimation().play(); + } else { + container.setPrefWidth(width); + checkbox.setOpacity(opacity); + } + if (!selectionMode) { + notificationCenter.getSelectionModel().clearSelection(); + } + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + @Override + public Node getNode() { + return this; + } + + /** + * Updates the notification property of the cell, then calls {@link #render(INotification)}. + *

+ * This is called after {@link #updateIndex(int)}. + */ + @Override + public void updateItem(INotification notification) { + setNotification(notification); + render(notification); + } + + /** + * Updates the index property of the cell. + *

+ * This is called before {@link #updateItem(INotification)}. + */ + @Override + public void updateIndex(int index) { + setIndex(index); + } + + //================================================================================ + // Getters/Setters + //================================================================================ + + public INotification getNotification() { + return this.notification.get(); + } + + /** + * Specifies the current shown notification (in other words the cell's content). + */ + public ReadOnlyObjectProperty notificationProperty() { + return this.notification.getReadOnlyProperty(); + } + + protected void setNotification(INotification notification) { + this.notification.set(notification); + } + + public int getIndex() { + return this.index.get(); + } + + /** + * Specifies the cell's index. + */ + protected ReadOnlyIntegerProperty indexProperty() { + return this.index.getReadOnlyProperty(); + } + + protected void setIndex(int index) { + this.index.set(index); + } + + public boolean isSelected() { + return this.selected.get(); + } + + /** + * Specifies the selection state of the cell. + */ + public ReadOnlyBooleanProperty selectedProperty() { + return this.selected.getReadOnlyProperty(); + } + + protected void setSelected(boolean selected) { + this.selected.set(selected); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXSimpleTreeCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXSimpleTreeCell.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXTableColumn.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXTableColumn.java old mode 100644 new mode 100755 index fd40cb96..24b935da --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXTableColumn.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXTableColumn.java @@ -21,7 +21,7 @@ package io.github.palexdev.materialfx.controls.cell; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.controls.MFXIconWrapper; import io.github.palexdev.materialfx.controls.MFXTableView; -import io.github.palexdev.materialfx.controls.enums.SortState; +import io.github.palexdev.materialfx.enums.SortState; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.skins.MFXTableColumnSkin; import io.github.palexdev.materialfx.skins.MFXTableViewSkin; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXTableRowCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXTableRowCell.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/base/AbstractMFXListCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/base/AbstractMFXListCell.java old mode 100644 new mode 100755 index 95ac1509..9d19894a --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/base/AbstractMFXListCell.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/base/AbstractMFXListCell.java @@ -166,7 +166,7 @@ public abstract class AbstractMFXListCell extends HBox implements Cell { } /** - * Index property of the cell. + * Specifies the cell's index. */ public ReadOnlyIntegerProperty indexProperty() { return index.getReadOnlyProperty(); @@ -181,7 +181,7 @@ public abstract class AbstractMFXListCell extends HBox implements Cell { } /** - * The selection state property of the cell. + * Specifies the selection state of the cell. */ public ReadOnlyBooleanProperty selectedProperty() { return selected.getReadOnlyProperty(); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyComboBox.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyComboBox.java old mode 100644 new mode 100755 index 5df45540..62aa0d49 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyComboBox.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyComboBox.java @@ -21,7 +21,7 @@ package io.github.palexdev.materialfx.controls.legacy; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.beans.MFXSnapshotWrapper; import io.github.palexdev.materialfx.controls.MFXComboBox; -import io.github.palexdev.materialfx.controls.enums.DialogType; +import io.github.palexdev.materialfx.enums.DialogType; import io.github.palexdev.materialfx.skins.legacy.MFXLegacyComboBoxSkin; import io.github.palexdev.materialfx.utils.NodeUtils; import io.github.palexdev.materialfx.validation.MFXDialogValidator; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyListCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyListCell.java old mode 100644 new mode 100755 index 7458744f..c92f97f2 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyListCell.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyListCell.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.controls.legacy; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.css.*; import javafx.geometry.Insets; @@ -127,7 +127,7 @@ public class MFXLegacyListCell extends ListCell { protected void setupRippleGenerator() { rippleGenerator.setRippleColor(Color.rgb(50, 150, 255)); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY())); addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyListView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyListView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyTableRow.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyTableRow.java old mode 100644 new mode 100755 index f14792c4..d1df17ea --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyTableRow.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyTableRow.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.controls.legacy; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.css.*; import javafx.geometry.Insets; @@ -68,7 +68,7 @@ public class MFXLegacyTableRow extends TableRow { private void setupRippleGenerator() { rippleGenerator.setRippleColor(Color.rgb(50, 150, 255)); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY())); addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyTableView.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/legacy/MFXLegacyTableView.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ConsumerTransition.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ConsumerTransition.java new file mode 100755 index 00000000..16e3bb40 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ConsumerTransition.java @@ -0,0 +1,138 @@ +package io.github.palexdev.materialfx.effects; + +import javafx.animation.Interpolator; +import javafx.animation.Transition; +import javafx.util.Duration; + +import java.util.function.Consumer; + +/** + * A simple implementation of {@link Transition} that allows to specify + * what to do when the {@link #interpolate(double)} method is called by using + * a {@link Consumer}. + */ +public class ConsumerTransition extends Transition { + //================================================================================ + // Properties + //================================================================================ + private Consumer interpolateConsumer; + + //================================================================================ + // Methods + //================================================================================ + + /** + * Sets the transition duration. + */ + public ConsumerTransition setDuration(Duration duration) { + this.setCycleDuration(duration); + return this; + } + + /** + * Sets the transition duration in milliseconds. + */ + public ConsumerTransition setDuration(double millis) { + this.setCycleDuration(Duration.millis(millis)); + return this; + } + + /** + * Sets the consumer used by the {@link #interpolate(double)} method. + */ + public ConsumerTransition setInterpolateConsumer(Consumer interpolateConsumer) { + this.interpolateConsumer = interpolateConsumer; + return this; + } + + /** + * Sets the transition's interpolator. + */ + public ConsumerTransition setInterpolatorFluent(Interpolator interpolator) { + this.setInterpolator(interpolator); + return this; + } + + /** + * Sets the transition's delay. + */ + public ConsumerTransition setDelayFluent(Duration duration) { + this.setDelay(duration); + return this; + } + + /** + * Calls {@link #setInterpolateConsumer(Consumer)} and then starts the animation. + */ + public void playWithConsumer(Consumer interpolateConsumer) { + setInterpolateConsumer(interpolateConsumer); + this.play(); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + /** + * {@inheritDoc} + *

+ * Implementation to make use of a {@link Consumer}. + */ + @Override + protected void interpolate(double frac) { + this.interpolateConsumer.accept(frac); + } + + //================================================================================ + // Static Methods + //================================================================================ + + /** + * Creates a new {@code ConsumerTransition} with the given consumer. + */ + public static ConsumerTransition of(Consumer interpolateConsumer) { + return (new ConsumerTransition()).setInterpolateConsumer(interpolateConsumer); + } + + /** + * Creates a new {@code ConsumerTransition} with the given consumer and duration. + */ + public static ConsumerTransition of(Consumer interpolateConsumer, Duration duration) { + return (new ConsumerTransition()).setInterpolateConsumer(interpolateConsumer).setDuration(duration); + } + + /** + * Creates a new {@code ConsumerTransition} with the given consumer and duration in milliseconds. + */ + public static ConsumerTransition of(Consumer interpolateConsumer, double duration) { + return (new ConsumerTransition()).setInterpolateConsumer(interpolateConsumer).setDuration(duration); + } + + /** + * Creates a new {@code ConsumerTransition} with the given consumer, duration and interpolator. + */ + public static ConsumerTransition of(Consumer interpolateConsumer, Duration duration, Interpolator interpolator) { + return (new ConsumerTransition()).setInterpolateConsumer(interpolateConsumer).setDuration(duration).setInterpolatorFluent(interpolator); + } + + /** + * Creates a new {@code ConsumerTransition} with the given consumer, duration in milliseconds and interpolator. + */ + public static ConsumerTransition of(Consumer interpolateConsumer, double duration, Interpolator interpolator) { + return (new ConsumerTransition()).setInterpolateConsumer(interpolateConsumer).setDuration(duration).setInterpolatorFluent(interpolator); + } + + /** + * Creates a new {@code ConsumerTransition} with the given consumer, duration and interpolator. + */ + public static ConsumerTransition of(Consumer interpolateConsumer, Duration duration, Interpolators interpolator) { + return (new ConsumerTransition()).setInterpolateConsumer(interpolateConsumer).setDuration(duration).setInterpolatorFluent(interpolator.toInterpolator()); + } + + /** + * Creates a new {@code ConsumerTransition} with the given consumer, duration in milliseconds and interpolator. + */ + public static ConsumerTransition of(Consumer interpolateConsumer, double duration, Interpolators interpolator) { + return (new ConsumerTransition()).setInterpolateConsumer(interpolateConsumer).setDuration(duration).setInterpolatorFluent(interpolator.toInterpolator()); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/DepthLevel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/DepthLevel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/Interpolators.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/Interpolators.java new file mode 100755 index 00000000..9c66a948 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/Interpolators.java @@ -0,0 +1,101 @@ +package io.github.palexdev.materialfx.effects; + +import javafx.animation.Interpolator; + +import java.util.function.Function; + +/** + * Enumerator that offers some new {@link Interpolator}s for JavaFX's animations. + */ +public enum Interpolators { + INTERPOLATOR_V1(null) { + public Interpolator toInterpolator() { + return Interpolator.SPLINE(0.25D, 0.1D, 0.25D, 1.0D); + } + }, + INTERPOLATOR_V2(null) { + public Interpolator toInterpolator() { + return Interpolator.SPLINE(0.0825D, 0.3025D, 0.0875D, 0.9975D); + } + }, + LINEAR((t) -> t) { + public Interpolator toInterpolator() { + return new Interpolator() { + protected double curve(double t) { + return getCurve().apply(t); + } + }; + } + }, + EASE_IN((t) -> t * t * t) { + public Interpolator toInterpolator() { + return new Interpolator() { + protected double curve(double t) { + return getCurve().apply(t); + } + }; + } + }, + EASE_IN_SINE((t) -> 1.0D - Math.cos(t * 3.141592653589793D / 2.0D)) { + public Interpolator toInterpolator() { + return new Interpolator() { + protected double curve(double t) { + return getCurve().apply(t); + } + }; + } + }, + EASE_OUT((t) -> 1.0D - (1.0D - t) * (1.0D - t) * (1.0D - t)) { + public Interpolator toInterpolator() { + return new Interpolator() { + protected double curve(double t) { + return getCurve().apply(t); + } + }; + } + }, + EASE_OUT_SINE((t) -> { + return Math.sin(t * 3.141592653589793D / 2.0D); + }) { + public Interpolator toInterpolator() { + return new Interpolator() { + protected double curve(double t) { + return getCurve().apply(t); + } + }; + } + }, + EASE_IN_OUT((t) -> t < 0.5D ? 4.0D * t * t * t : 1.0D - Math.pow(-2.0D * t + 2.0D, 3.0D) / 2.0D) { + public Interpolator toInterpolator() { + return new Interpolator() { + protected double curve(double t) { + return getCurve().apply(t); + } + }; + } + }, + EASE_IN_OUT_SINE((t) -> -(Math.cos(3.141592653589793D * t) - 1.0D) / 2.0D) { + public Interpolator toInterpolator() { + return new Interpolator() { + protected double curve(double t) { + return getCurve().apply(t); + } + }; + } + }; + + private final Function curve; + + Interpolators(Function curve) { + this.curve = curve; + } + + /** + * Converts a Function to a JavaFX's {@link Interpolator}. + */ + public abstract Interpolator toInterpolator(); + + public Function getCurve() { + return this.curve; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/MFXDepthManager.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/MFXDepthManager.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/MFXScrimEffect.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/MFXScrimEffect.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/MFXCircleRippleGenerator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/MFXCircleRippleGenerator.java old mode 100644 new mode 100755 index 2bca45f9..09ce45d2 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/MFXCircleRippleGenerator.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/MFXCircleRippleGenerator.java @@ -18,8 +18,9 @@ package io.github.palexdev.materialfx.effects.ripple; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.beans.PositionBean; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; import io.github.palexdev.materialfx.effects.DepthLevel; import io.github.palexdev.materialfx.effects.MFXDepthManager; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator.CircleRipple; @@ -123,12 +124,12 @@ public class MFXCircleRippleGenerator extends AbstractMFXRippleGenerator new RipplePosition()); + setRipplePositionFunction(event -> new PositionBean()); } @Override - public Function getRipplePositionFunction() { + public Function getRipplePositionFunction() { return positionFunction; } @Override - public void setRipplePositionFunction(Function positionFunction) { + public void setRipplePositionFunction(Function positionFunction) { super.positionFunction = positionFunction; } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RippleClipType.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RippleClipType.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RippleGenerator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RippleGenerator.java old mode 100644 new mode 100755 index 760191f0..63986eaa --- a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RippleGenerator.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RippleGenerator.java @@ -18,8 +18,8 @@ package io.github.palexdev.materialfx.effects.ripple; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; import io.github.palexdev.materialfx.effects.DepthLevel; import javafx.animation.*; import javafx.beans.property.ObjectProperty; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RipplePosition.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RipplePosition.java deleted file mode 100644 index 4b1e8512..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/RipplePosition.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.effects.ripple; - -import io.github.palexdev.materialfx.effects.ripple.base.IRippleGenerator; -import io.github.palexdev.materialfx.skins.MFXToggleButtonSkin; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; - -import java.util.function.Function; - -/** - * Simple bean to wrap the coordinates of generated ripples. - *

- * This is used by the ripple generator's position function as the return type, - * {@link IRippleGenerator#setRipplePositionFunction(Function)}. - *

- * Note that both the positions are JavaFX properties, this allows to change the ripple position during - * its animation, an example can be seen in the {@link MFXToggleButtonSkin} - *

- * In {@link MFXCircleRippleGenerator} the ripple center properties are already bound to these values. - */ -public class RipplePosition { - private final DoubleProperty xPosition = new SimpleDoubleProperty(0); - private final DoubleProperty yPosition = new SimpleDoubleProperty(0); - - public RipplePosition() { - } - - public RipplePosition(double xPosition, double yPosition) { - setXPosition(xPosition); - setYPosition(yPosition); - } - - public double getXPosition() { - return xPosition.get(); - } - - public DoubleProperty xPositionProperty() { - return xPosition; - } - - public void setXPosition(double xPosition) { - this.xPosition.set(xPosition); - } - - public double getYPosition() { - return yPosition.get(); - } - - public DoubleProperty yPositionProperty() { - return yPosition; - } - - public void setYPosition(double yPosition) { - this.yPosition.set(yPosition); - } -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/AbstractMFXRippleGenerator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/AbstractMFXRippleGenerator.java old mode 100644 new mode 100755 index f9056f7a..1721efa6 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/AbstractMFXRippleGenerator.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/AbstractMFXRippleGenerator.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.effects.ripple.base; import io.github.palexdev.materialfx.collections.ObservableStack; import io.github.palexdev.materialfx.effects.DepthLevel; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.skins.MFXCheckboxSkin; import javafx.animation.Animation; import javafx.beans.property.*; @@ -60,7 +60,7 @@ public abstract class AbstractMFXRippleGenerator extends Regi protected final Region region; protected Supplier clipSupplier; protected Supplier rippleSupplier; - protected Function positionFunction; + protected Function positionFunction; protected final BooleanProperty animateBackground = new SimpleBooleanProperty(true); protected final BooleanProperty animateShadow = new SimpleBooleanProperty(false); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/IRipple.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/IRipple.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/IRippleGenerator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/IRippleGenerator.java old mode 100644 new mode 100755 index 60f94b36..f456d129 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/IRippleGenerator.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/effects/ripple/base/IRippleGenerator.java @@ -18,8 +18,8 @@ package io.github.palexdev.materialfx.effects.ripple.base; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.beans.PositionBean; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; import javafx.scene.shape.Shape; @@ -68,7 +68,7 @@ public interface IRippleGenerator { /** * @return the current generator's position function */ - Function getRipplePositionFunction(); + Function getRipplePositionFunction(); /** * Sets the generator's ripple position function to the specified one. @@ -76,9 +76,9 @@ public interface IRippleGenerator { * This {@link Function} is responsible for computing the ripple's x and y * coordinates before the animation is played. The function takes a MouseEvent as the input * (since in most controls the coordinates are the x and y coordinates of the mouse event) - * and returns a {@link RipplePosition} bean. + * and returns a {@link PositionBean} bean. */ - void setRipplePositionFunction(Function positionFunction); + void setRipplePositionFunction(Function positionFunction); /** * Every ripple generator should have a default ripple supplier. diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/ButtonType.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/ButtonType.java old mode 100644 new mode 100755 similarity index 93% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/ButtonType.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/ButtonType.java index c6ed9e7b..ceef7e5a --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/ButtonType.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/ButtonType.java @@ -16,7 +16,7 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; public enum ButtonType { FLAT, diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/enums/ChainMode.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/ChainMode.java new file mode 100755 index 00000000..6a103090 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/ChainMode.java @@ -0,0 +1,22 @@ +package io.github.palexdev.materialfx.enums; + +/** + * Enumeration to specify how two predicates should be chained. + * Also specify how a ChainMode enumeration should be represented in UI. + */ +public enum ChainMode { + AND("&"), + OR("or"); + + public static boolean useAlternativeAnd = false; + private final String text; + + ChainMode(String text) { + this.text = text; + } + + public String text() { + return this == AND && useAlternativeAnd ? "and" : this.text; + } + +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/DialogType.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/DialogType.java old mode 100644 new mode 100755 similarity index 94% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/DialogType.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/DialogType.java index 420152ab..5a859f06 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/DialogType.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/DialogType.java @@ -16,7 +16,7 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; public enum DialogType { ERROR, diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/LoaderCacheLevel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/LoaderCacheLevel.java old mode 100644 new mode 100755 similarity index 97% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/LoaderCacheLevel.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/LoaderCacheLevel.java index 77afd229..bbd00ad4 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/LoaderCacheLevel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/LoaderCacheLevel.java @@ -16,7 +16,7 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; import io.github.palexdev.materialfx.controls.MFXHLoader; import io.github.palexdev.materialfx.controls.MFXVLoader; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationCounterStyle.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationCounterStyle.java new file mode 100755 index 00000000..abf0a3be --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationCounterStyle.java @@ -0,0 +1,10 @@ +package io.github.palexdev.materialfx.enums; + +import io.github.palexdev.materialfx.controls.MFXNotificationCenter; + +/** + * Enumeration to specify the style of a {@link MFXNotificationCenter}'s counter. + */ +public enum NotificationCounterStyle { + DOT, NUMBER +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationPos.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationPos.java new file mode 100755 index 00000000..5ccae29b --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationPos.java @@ -0,0 +1,30 @@ +package io.github.palexdev.materialfx.enums; + +import io.github.palexdev.materialfx.notifications.MFXNotificationCenterSystem; +import io.github.palexdev.materialfx.notifications.MFXNotificationSystem; + +/** + * Enumeration to specify where a notification has to be shown. + *

+ * Used by {@link MFXNotificationCenterSystem} and {@link MFXNotificationSystem}. + */ +public enum NotificationPos { + TOP_CENTER, + TOP_LEFT, + TOP_RIGHT, + BOTTOM_CENTER, + BOTTOM_LEFT, + BOTTOM_RIGHT; + + public boolean isTop() { + return this == TOP_LEFT || this == TOP_CENTER || this == TOP_RIGHT; + } + + public boolean isCenter() { + return this == TOP_CENTER || this == BOTTOM_CENTER; + } + + public boolean isRight() { + return this == TOP_RIGHT || this == BOTTOM_RIGHT; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationState.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationState.java new file mode 100755 index 00000000..f2c749ac --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/NotificationState.java @@ -0,0 +1,8 @@ +package io.github.palexdev.materialfx.enums; + +/** + * Enumeration to represent the read state of a notification. + */ +public enum NotificationState { + READ, UNREAD; +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/SliderEnums.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/SliderEnums.java old mode 100644 new mode 100755 similarity index 72% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/SliderEnums.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/SliderEnums.java index 2f2e9102..a5f83780 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/SliderEnums.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/SliderEnums.java @@ -16,16 +16,27 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; +import io.github.palexdev.materialfx.controls.MFXSlider; + +/** + * Class that contains some enumerators to be used with {@link MFXSlider}. + */ public class SliderEnums { private SliderEnums() {} + /** + * Enumeration to specify the snap behavior of {@link MFXSlider}. + */ public enum SliderMode { DEFAULT, SNAP_TO_TICKS } + /** + * Enumeration to specify on which side to show the {@link MFXSlider}'s popup. + */ public enum SliderPopupSide { DEFAULT, OTHER_SIDE } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/SortState.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/SortState.java old mode 100644 new mode 100755 similarity index 92% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/SortState.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/SortState.java index 1b05d8a0..3d57eddc --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/SortState.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/SortState.java @@ -16,8 +16,11 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; +/** + * Enumerations to represent sorting. + */ public enum SortState { ASCENDING, DESCENDING, diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/StepperToggleState.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/StepperToggleState.java old mode 100644 new mode 100755 similarity index 88% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/StepperToggleState.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/StepperToggleState.java index 72b540a4..39f1b4a3 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/StepperToggleState.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/StepperToggleState.java @@ -16,12 +16,12 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; import io.github.palexdev.materialfx.controls.MFXStepperToggle; /** - * Enumerator to represent the state of a {@link MFXStepperToggle} + * Enumerator to represent the states of a {@link MFXStepperToggle} */ public enum StepperToggleState { SELECTED, diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/Styles.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/Styles.java old mode 100644 new mode 100755 similarity index 97% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/Styles.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/Styles.java index 279d095a..0faedc7e --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/Styles.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/Styles.java @@ -16,7 +16,7 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; /** * This class contains various enumerators used in MaterialFX controls which diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/TextPosition.java b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/TextPosition.java old mode 100644 new mode 100755 similarity index 93% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/TextPosition.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/enums/TextPosition.java index 94360945..61c9c005 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/TextPosition.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/enums/TextPosition.java @@ -16,7 +16,7 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.enums; +package io.github.palexdev.materialfx.enums; public enum TextPosition { TOP, diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/factories/InsetsFactory.java b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/InsetsFactory.java new file mode 100755 index 00000000..ce129059 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/InsetsFactory.java @@ -0,0 +1,53 @@ +package io.github.palexdev.materialfx.factories; + +import javafx.geometry.Insets; + +/** + * Convenience class to build {@link Insets} objects. + */ +public class InsetsFactory { + + //================================================================================ + // Constructors + //================================================================================ + private InsetsFactory() {} + + //================================================================================ + // Static Methods + //================================================================================ + public static Insets all(double topRightBottomLeft) { + return new Insets(topRightBottomLeft); + } + + public static Insets none() { + return Insets.EMPTY; + } + + public static Insets top(double top) { + return new Insets(top, 0, 0, 0); + } + + public static Insets right(double right) { + return new Insets(0, right, 0, 0); + } + + public static Insets bottom(double bottom) { + return new Insets(0, 0, bottom, 0); + } + + public static Insets left(double left) { + return new Insets(0, 0, 0, left); + } + + public static Insets of(double top, double right) { + return new Insets(top, right, 0, 0); + } + + public static Insets of(double top, double right, double bottom) { + return new Insets(top, right, bottom, 0); + } + + public static Insets of(double top, double right, double bottom, double left) { + return new Insets(top, right, bottom, left); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXAnimationFactory.java b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXAnimationFactory.java old mode 100644 new mode 100755 similarity index 99% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXAnimationFactory.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXAnimationFactory.java index 478d6432..2e3a6739 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXAnimationFactory.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXAnimationFactory.java @@ -16,7 +16,7 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.factories; +package io.github.palexdev.materialfx.factories; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXDialogFactory.java b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXDialogFactory.java old mode 100644 new mode 100755 similarity index 98% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXDialogFactory.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXDialogFactory.java index 771a4e17..15f68022 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXDialogFactory.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXDialogFactory.java @@ -16,13 +16,13 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.factories; +package io.github.palexdev.materialfx.factories; import io.github.palexdev.materialfx.controls.MFXButton; import io.github.palexdev.materialfx.controls.MFXDialog; import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog; -import io.github.palexdev.materialfx.controls.enums.ButtonType; -import io.github.palexdev.materialfx.controls.enums.DialogType; +import io.github.palexdev.materialfx.enums.ButtonType; +import io.github.palexdev.materialfx.enums.DialogType; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.geometry.Insets; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXStageDialogFactory.java b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXStageDialogFactory.java old mode 100644 new mode 100755 similarity index 96% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXStageDialogFactory.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXStageDialogFactory.java index 86010b4c..2fb21ae4 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXStageDialogFactory.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/MFXStageDialogFactory.java @@ -16,10 +16,10 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.factories; +package io.github.palexdev.materialfx.factories; import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog; -import io.github.palexdev.materialfx.controls.enums.DialogType; +import io.github.palexdev.materialfx.enums.DialogType; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/RippleClipTypeFactory.java b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/RippleClipTypeFactory.java old mode 100644 new mode 100755 similarity index 98% rename from materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/RippleClipTypeFactory.java rename to materialfx/src/main/java/io/github/palexdev/materialfx/factories/RippleClipTypeFactory.java index ef878499..048e3552 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/RippleClipTypeFactory.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/factories/RippleClipTypeFactory.java @@ -16,7 +16,7 @@ * along with MaterialFX. If not, see . */ -package io.github.palexdev.materialfx.controls.factories; +package io.github.palexdev.materialfx.factories; import io.github.palexdev.materialfx.effects.ripple.RippleClipType; import javafx.scene.layout.Region; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/BooleanFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/BooleanFilter.java new file mode 100755 index 00000000..448849d3 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/BooleanFilter.java @@ -0,0 +1,51 @@ +package io.github.palexdev.materialfx.filter; + +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.filter.base.AbstractFilter; +import io.github.palexdev.materialfx.utils.FXCollectors; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; +import javafx.util.converter.BooleanStringConverter; + +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Extension of {@link AbstractFilter} for boolean fields. + *

+ * Offers the following default {@link BiPredicateBean}s: + *

- "is": checks for booleans equality + *

- "is not": checks for booleans inequality + */ +public class BooleanFilter extends AbstractFilter { + + //================================================================================ + // Constructors + //================================================================================ + public BooleanFilter(String name, Function extractor) { + this(name, extractor, new BooleanStringConverter()); + } + + public BooleanFilter(String name, Function extractor, StringConverter converter) { + super(name, extractor, converter); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected ObservableList> defaultPredicates() { + return Stream.>of( + new BiPredicateBean<>("is", Boolean::equals), + new BiPredicateBean<>("is not", (aBoolean, aBoolean2) -> !aBoolean.equals(aBoolean2)) + ).collect(FXCollectors.toList()); + } + + @SafeVarargs + @Override + protected final BooleanFilter extend(BiPredicateBean... predicateBeans) { + Collections.addAll(super.predicates, predicateBeans); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/DoubleFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/DoubleFilter.java new file mode 100755 index 00000000..a3253201 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/DoubleFilter.java @@ -0,0 +1,59 @@ +package io.github.palexdev.materialfx.filter; + +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.filter.base.NumberFilter; +import io.github.palexdev.materialfx.utils.FXCollectors; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; +import javafx.util.converter.DoubleStringConverter; + +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Extension of {@link NumberFilter} for double fields. + *

+ * Offers the following default {@link BiPredicateBean}s: + *

- "is": checks for doubles equality + *

- "is not": checks for doubles inequality + *

- "greater than": checks if a double is greater than another double + *

- "greater or equal to": checks if a double is greater or equal to another double + *

- "lesser than": checks if a double is lesser than another double + *

- "lesser or equal to": checks if a double is lesser or equal to another double + */ +public class DoubleFilter extends NumberFilter { + + //================================================================================ + // Constructors + //================================================================================ + public DoubleFilter(String name, Function extractor) { + this(name, extractor, new DoubleStringConverter()); + } + + public DoubleFilter(String name, Function extractor, StringConverter converter) { + super(name, extractor, converter); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected ObservableList> defaultPredicates() { + return Stream.>of( + new BiPredicateBean<>("is", Double::equals), + new BiPredicateBean<>("is not", (aDouble, aDouble2) -> !aDouble.equals(aDouble2)), + new BiPredicateBean<>("greater than", (aDouble, aDouble2) -> aDouble > aDouble2), + new BiPredicateBean<>("greater or equal to", (aDouble, aDouble2) -> aDouble >= aDouble2), + new BiPredicateBean<>("lesser than", (aDouble, aDouble2) -> aDouble < aDouble2), + new BiPredicateBean<>("lesser or equal to", (aDouble, aDouble2) -> aDouble <= aDouble2) + ).collect(FXCollectors.toList()); + } + + @SafeVarargs + @Override + protected final DoubleFilter extend(BiPredicateBean... predicateBeans) { + Collections.addAll(super.predicates, predicateBeans); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/EnumFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/EnumFilter.java new file mode 100755 index 00000000..18817480 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/EnumFilter.java @@ -0,0 +1,66 @@ +package io.github.palexdev.materialfx.filter; + +import io.github.palexdev.materialfx.filter.base.AbstractFilter; +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.utils.EnumStringConverter; +import io.github.palexdev.materialfx.utils.FXCollectors; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; + +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Extension of {@link AbstractFilter} for {@link Enum} fields. + *

+ * Offers the following default {@link BiPredicateBean}s: + *

- "is": checks for enums equality + *

- "is not": checks for enums inequality + *

+ * This filter is special because to extract the enumerations of a given E enum, it's + * needed to also pass the type to the constructor. This is necessary for the {@link EnumStringConverter}. + */ +public class EnumFilter> extends AbstractFilter { + //================================================================================ + // Properties + //================================================================================ + private final Class enumType; + + //================================================================================ + // Constructors + //================================================================================ + public EnumFilter(String name, Function extractor, Class enumType) { + this(name, extractor, enumType, new EnumStringConverter<>(enumType)); + } + + public EnumFilter(String name, Function extractor, Class enumType, StringConverter converter) { + super(name, extractor, converter); + this.enumType = enumType; + } + + //================================================================================ + // Getters + //================================================================================ + public Class getEnumType() { + return enumType; + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected ObservableList> defaultPredicates() { + return Stream.>of( + new BiPredicateBean<>("is", Enum::equals), + new BiPredicateBean<>("is not", (anEnum, anEnum2) -> !anEnum.equals(anEnum2)) + ).collect(FXCollectors.toList()); + } + + @SafeVarargs + @Override + protected final EnumFilter extend(BiPredicateBean... predicateBeans) { + Collections.addAll(super.predicates, predicateBeans); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/EvaluationMode.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/EvaluationMode.java deleted file mode 100644 index 166d8a18..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/EvaluationMode.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.filter; - -public enum EvaluationMode { - AND, OR -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/FloatFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/FloatFilter.java new file mode 100755 index 00000000..43bb95e3 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/FloatFilter.java @@ -0,0 +1,59 @@ +package io.github.palexdev.materialfx.filter; + +import io.github.palexdev.materialfx.filter.base.NumberFilter; +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.utils.FXCollectors; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; +import javafx.util.converter.FloatStringConverter; + +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Extension of {@link NumberFilter} for float fields. + *

+ * Offers the following default {@link BiPredicateBean}s: + *

- "is": checks for floats equality + *

- "is not": checks for floats inequality + *

- "greater than": checks if a float is greater than another float + *

- "greater or equal to": checks if a float is greater or equal to another float + *

- "lesser than": checks if a float is lesser than another float + *

- "lesser or equal to": checks if a float is lesser or equal to another float + */ +public class FloatFilter extends NumberFilter { + + //================================================================================ + // Constructors + //================================================================================ + public FloatFilter(String name, Function extractor) { + this(name, extractor, new FloatStringConverter()); + } + + public FloatFilter(String name, Function extractor, StringConverter converter) { + super(name, extractor, converter); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected ObservableList> defaultPredicates() { + return Stream.>of( + new BiPredicateBean<>("is", Float::equals), + new BiPredicateBean<>("is not", (aFloat, aFloat2) -> !aFloat.equals(aFloat2)), + new BiPredicateBean<>("greater than", (aFloat, aFloat2) -> aFloat > aFloat2), + new BiPredicateBean<>("greater or equal to", (aFloat, aFloat2) -> aFloat >= aFloat2), + new BiPredicateBean<>("lesser than", (aFloat, aFloat2) -> aFloat < aFloat2), + new BiPredicateBean<>("lesser or equal to", (aFloat, aFloat2) -> aFloat <= aFloat2) + ).collect(FXCollectors.toList()); + } + + @SafeVarargs + @Override + protected final FloatFilter extend(BiPredicateBean... predicateBeans) { + Collections.addAll(super.predicates, predicateBeans); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/IFilterable.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/IFilterable.java deleted file mode 100644 index b5b8986f..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/IFilterable.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.filter; - -/** - * This interface allows filtering a {@link io.github.palexdev.materialfx.controls.MFXTableView} without - * using an object {@code toString()} method but rather using a specific method. - */ -public interface IFilterable { - String toFilterString(); -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/IntegerFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/IntegerFilter.java new file mode 100755 index 00000000..86841ec0 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/IntegerFilter.java @@ -0,0 +1,59 @@ +package io.github.palexdev.materialfx.filter; + +import io.github.palexdev.materialfx.filter.base.NumberFilter; +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.utils.FXCollectors; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; +import javafx.util.converter.IntegerStringConverter; + +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Extension of {@link NumberFilter} for integer fields. + *

+ * Offers the following default {@link BiPredicateBean}s: + *

- "is": checks for integers equality + *

- "is not": checks for integers inequality + *

- "greater than": checks if a integer is greater than another integer + *

- "greater or equal to": checks if a integer is greater or equal to another integer + *

- "lesser than": checks if a integer is lesser than another integer + *

- "lesser or equal to": checks if a integer is lesser or equal to another integer + */ +public class IntegerFilter extends NumberFilter { + + //================================================================================ + // Constructors + //================================================================================ + public IntegerFilter(String name, Function extractor) { + this(name, extractor, new IntegerStringConverter()); + } + + public IntegerFilter(String name, Function extractor, StringConverter converter) { + super(name, extractor, converter); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected ObservableList> defaultPredicates() { + return Stream.>of( + new BiPredicateBean<>("is", Integer::equals), + new BiPredicateBean<>("is not", (anInteger, anInteger2) -> !anInteger.equals(anInteger2)), + new BiPredicateBean<>("greater than", (anInteger, anInteger2) -> anInteger > anInteger2), + new BiPredicateBean<>("greater or equal to", (anInteger, anInteger2) -> anInteger >= anInteger2), + new BiPredicateBean<>("lesser than", (anInteger, anInteger2) -> anInteger < anInteger2), + new BiPredicateBean<>("lesser or equal to", (anInteger, anInteger2) -> anInteger <= anInteger2) + ).collect(FXCollectors.toList()); + } + + @SafeVarargs + @Override + protected final IntegerFilter extend(BiPredicateBean... predicateBeans) { + Collections.addAll(super.predicates, predicateBeans); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/LongFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/LongFilter.java new file mode 100755 index 00000000..4042625f --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/LongFilter.java @@ -0,0 +1,59 @@ +package io.github.palexdev.materialfx.filter; + +import io.github.palexdev.materialfx.filter.base.NumberFilter; +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.utils.FXCollectors; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; +import javafx.util.converter.LongStringConverter; + +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Extension of {@link NumberFilter} for long fields. + *

+ * Offers the following default {@link BiPredicateBean}s: + *

- "is": checks for longs equality + *

- "is not": checks for longs inequality + *

- "greater than": checks if a long is greater than another long + *

- "greater or equal to": checks if a long is greater or equal to another long + *

- "lesser than": checks if a long is lesser than another long + *

- "lesser or equal to": checks if a long is lesser or equal to another long + */ +public class LongFilter extends NumberFilter { + + //================================================================================ + // Constructors + //================================================================================ + public LongFilter(String name, Function extractor) { + this(name, extractor, new LongStringConverter()); + } + + public LongFilter(String name, Function extractor, StringConverter converter) { + super(name, extractor, converter); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected ObservableList> defaultPredicates() { + return Stream.>of( + new BiPredicateBean<>("is", Long::equals), + new BiPredicateBean<>("is not", (aLong, aLong2) -> !aLong.equals(aLong2)), + new BiPredicateBean<>("greater than", (aLong, aLong2) -> aLong > aLong2), + new BiPredicateBean<>("greater or equal to", (aLong, aLong2) -> aLong >= aLong2), + new BiPredicateBean<>("lesser than", (aLong, aLong2) -> aLong < aLong2), + new BiPredicateBean<>("lesser or equal to", (aLong, aLong2) -> aLong <= aLong2) + ).collect(FXCollectors.toList()); + } + + @SafeVarargs + @Override + protected final LongFilter extend(BiPredicateBean... predicateBeans) { + Collections.addAll(super.predicates, predicateBeans); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/MFXEvaluationBox.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/MFXEvaluationBox.java deleted file mode 100644 index 0b172361..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/MFXEvaluationBox.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.filter; - -import io.github.palexdev.materialfx.MFXResourcesLoader; -import io.github.palexdev.materialfx.controls.MFXComboBox; -import io.github.palexdev.materialfx.controls.MFXTextField; -import io.github.palexdev.materialfx.controls.enums.Styles; -import io.github.palexdev.materialfx.font.MFXFontIcon; -import io.github.palexdev.materialfx.utils.StringUtils; -import javafx.beans.binding.Bindings; -import javafx.collections.FXCollections; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.control.Label; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.paint.Color; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.function.BiPredicate; - -/** - * This little control provides a graphical way of evaluating a condition on - * a given string with a specific predicate. The computed boolean can also be chained with - * other conditions as an AND or an OR, specified by {@link EvaluationMode}. - *

- * The control is made of: - *

- An icon that should be used by the control's parent to remove it from the children list - *

- A text field that provides one of the strings to test - *

- A combo box that contains the predicate to apply - *

- * An usage example would be: - *

- * Let's say I have a string {@code s1 = AbcdE} and a string {@code s2 = "cde"} provided by the text field. - *

- * If the selected predicate is "Contains" then {@link #test(String)} will check if s1 contains s2 and will return false. - *

- * If the selected predicate is "Contains Ignore Case" then {@link #test(String)} will check if s1 contains s2 ignoring case and will return true. - * - *

- * N.B: Since "Contains Any" and "Contains All" are advanced functions the field text is cleared when one of those functions is selected - * in the combo box and a prompt text that shows a small example on how to format the string is set. - * - * @see BiPredicate - */ -public class MFXEvaluationBox extends HBox { - //================================================================================ - // Properties - //================================================================================ - private final String STYLE_CLASS = "mfx/evaluation-box"; - private final String STYLESHEET = MFXResourcesLoader.load("css/MFXEvaluationBox.css"); - - private final EvaluationMode mode; - private final Map> biPredicates = new LinkedHashMap<>(); - private final MFXFontIcon removeIcon; - private final MFXTextField inputField; - private final MFXComboBox predicatesCombo; - - //================================================================================ - // Constructor - //================================================================================ - public MFXEvaluationBox(EvaluationMode mode) { - setPrefWidth(600); - setAlignment(Pos.CENTER_LEFT); - setSpacing(20); - setPadding(new Insets(15, 10, 15, 10)); - - this.mode = mode; - - Label modeLabel = new Label(mode.name()); - modeLabel.setId("modeLabel"); - modeLabel.setPrefSize(40, 40); - modeLabel.setMaxHeight(Double.MAX_VALUE); - modeLabel.setPadding(new Insets(3)); - modeLabel.setAlignment(Pos.CENTER); - - HBox box = new HBox(5); - box.setAlignment(Pos.CENTER); - - Label predicateLabel = new Label("Evaluation Predicate:"); - predicatesCombo = new MFXComboBox<>(); - predicatesCombo.setComboStyle(Styles.ComboBoxStyles.STYLE2); - predicatesCombo.setPrefSize(180, 27); - predicatesCombo.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE); - - inputField = new MFXTextField(); - inputField.setPromptText("Input String..."); - inputField.setAnimateLines(false); - inputField.setLineColor(Color.web("#4d4d4d")); - inputField.setLineStrokeWidth(1); - inputField.setMaxWidth(Double.MAX_VALUE); - HBox.setHgrow(inputField, Priority.ALWAYS); - HBox.setMargin(inputField, new Insets(0, 10, 2, 0)); - inputField.getStylesheets().add(STYLESHEET); - - removeIcon = new MFXFontIcon("mfx-x-circle", 16, Color.web("#4D4D4D")); - removeIcon.colorProperty().bind(Bindings.createObjectBinding( - () -> removeIcon.isHover() ? Color.web("#EF6E6B") : Color.web("#4D4D4D"), - removeIcon.hoverProperty() - )); - - box.getChildren().addAll(predicateLabel, predicatesCombo); - getChildren().addAll(removeIcon, modeLabel, box, inputField); - - initialize(); - } - - //================================================================================ - // Methods - //================================================================================ - private void initialize() { - getStyleClass().add(STYLE_CLASS); - - biPredicates.put("Contains", String::contains); - biPredicates.put("Contains Ignore Case", StringUtils::containsIgnoreCase); - biPredicates.put("Contains Any", StringUtils::containsAny); - biPredicates.put("Contains All", StringUtils::containsAll); - biPredicates.put("Starts With", String::startsWith); - biPredicates.put("Start With Ignore Case", StringUtils::startsWithIgnoreCase); - biPredicates.put("Ends With", String::endsWith); - biPredicates.put("Ends With Ignore Case", StringUtils::endsWithIgnoreCase); - biPredicates.put("Equals", String::equals); - biPredicates.put("Equals Ignore Case", String::equalsIgnoreCase); - - predicatesCombo.selectedValueProperty().addListener((observable, oldValue, newValue) -> { - if (newValue.equals("Contains Any") || newValue.equals("Contains All")) { - inputField.setPromptText("Eg. \"A, B, C, DEF GHI, E, F...\""); - inputField.clear(); - } else { - inputField.setPromptText(""); - } - }); - - predicatesCombo.setItems(FXCollections.observableArrayList(biPredicates.keySet())); - predicatesCombo.getSelectionModel().selectFirst(); - } - - /** - * Applies the selected predicate (provided by the combo box) to the given - * string and the text provided by the text field. - */ - public Boolean test(String testString) { - if (getPredicate() != null) { - return biPredicates.get(getPredicate()).test(testString, inputField.getText()); - } - return false; - } - - /** - * @return the evaluation mode of this control - */ - public EvaluationMode getMode() { - return mode; - } - - /** - * @return the currently selected predicate - */ - public String getPredicate() { - return predicatesCombo.getSelectedValue(); - } - - /** - * @return the remove icon instance - */ - public MFXFontIcon getRemoveIcon() { - return removeIcon; - } - - //================================================================================ - // Override Methods - //================================================================================ - @Override - public String getUserAgentStylesheet() { - return STYLESHEET; - } -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/MFXFilterDialog.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/MFXFilterDialog.java old mode 100644 new mode 100755 index 036ed604..ff582d77 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/MFXFilterDialog.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/MFXFilterDialog.java @@ -18,35 +18,14 @@ package io.github.palexdev.materialfx.filter; -import io.github.palexdev.materialfx.MFXResourcesLoader; -import io.github.palexdev.materialfx.controls.*; -import io.github.palexdev.materialfx.controls.cell.MFXListCell; -import io.github.palexdev.materialfx.controls.enums.Styles; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; -import io.github.palexdev.materialfx.effects.DepthLevel; -import io.github.palexdev.materialfx.effects.ripple.RippleClipType; -import io.github.palexdev.materialfx.font.MFXFontIcon; -import javafx.beans.binding.Bindings; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.event.Event; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; - -import java.util.List; -import java.util.stream.Collectors; - +// TODO remake? /** * This dialog provides a graphical way of filtering a given list of T items based * on the conditions specified by the added evaluation boxes. * * @see MFXEvaluationBox */ +/* public class MFXFilterDialog extends MFXDialog { //================================================================================ // Properties @@ -154,9 +133,11 @@ public class MFXFilterDialog extends MFXDialog { setBehavior(); } - /** + */ +/** * Sets the buttons behavior - */ + *//* + private void setBehavior() { addEventFilter(MouseEvent.MOUSE_PRESSED, event -> requestFocus()); @@ -165,7 +146,8 @@ public class MFXFilterDialog extends MFXDialog { clear.setOnAction(event -> evaluationBoxes.clear()); } - /** + */ +/** * Filters the given list and returns an observable filtered list. *

* Calls {@link #filter(String)} on each item for filtering. @@ -173,7 +155,8 @@ public class MFXFilterDialog extends MFXDialog { * N.B: The evaluation is done by calling the item's toString method or, if the item implements {@link IFilterable}, * by calling {@link IFilterable#toFilterString()}. If the toString method is not overridden * or does not contain any useful information for filtering it won't work. - */ + *//* + public ObservableList filter(List list) { return list.stream() .filter(item -> { @@ -187,9 +170,11 @@ public class MFXFilterDialog extends MFXDialog { .collect(Collectors.toCollection(FXCollections::observableArrayList)); } - /** + */ +/** * Tests all the evaluation boxes conditions on the given string. - */ + *//* + private boolean filter(String filterString) { Boolean expression = null; for (MFXEvaluationBox box : evaluationBoxes) { @@ -210,9 +195,11 @@ public class MFXFilterDialog extends MFXDialog { return expression != null ? expression : false; } - /** + */ +/** * Adds a new {@link MFXEvaluationBox} with the specified {@link EvaluationMode} to the dialog. - */ + *//* + private void addFilterBox(EvaluationMode mode) { MFXEvaluationBox evaluationBox = new MFXEvaluationBox(mode); HBox.setHgrow(evaluationBox, Priority.ALWAYS); @@ -220,9 +207,11 @@ public class MFXFilterDialog extends MFXDialog { evaluationBoxes.add(evaluationBox); } - /** + */ +/** * @return the filter button instance - */ + *//* + public MFXButton getFilterButton() { return filterButton; } @@ -245,3 +234,4 @@ public class MFXFilterDialog extends MFXDialog { closeIcon.resizeRelocate(ciX, ciY, ciSize, ciSize); } } +*/ diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/StringFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/StringFilter.java new file mode 100755 index 00000000..40f8ebb9 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/StringFilter.java @@ -0,0 +1,81 @@ +package io.github.palexdev.materialfx.filter; + +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.filter.base.AbstractFilter; +import io.github.palexdev.materialfx.utils.FXCollectors; +import io.github.palexdev.materialfx.utils.StringUtils; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; + +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Extension of {@link AbstractFilter} for String fields. + *

+ * Offers the following default {@link BiPredicateBean}s: + *

- "contains": checks if a String is contained in another String + *

- "contains ignore case": checks if a String is contained in another String, case insensitive + *

- "contains any": checks if any of the given words are contained in a String. Words are specified as a + * single String split by ", ". Like this: "A, B, C, DEF GHI, E, F" + *

- "contains all": checks if all the given words are contained in a String. Words are specified as a + * single String split by ", ". Like this: "A, B, C, DEF GHI, E, F" + *

- "ends with": checks if a String ends with another String + *

- "ends with ignore case": checks if a String ends with another String, case insensitive + *

- "starts with": checks if a String starts with another String + *

- "starts with ignore case": checks if a String starts with another String, case insensitive + *

- "equals": checks for Strings equality + *

- "equals ignore case": checks for Strings equality, case insensitive + *

- "is not equal to": checks for Strings inequality + */ +public class StringFilter extends AbstractFilter { + + //================================================================================ + // Constructors + //================================================================================ + public StringFilter(String name, Function extractor) { + this(name, extractor, new StringConverter<>() { + @Override + public String toString(String object) { + return object; + } + + @Override + public String fromString(String string) { + return string; + } + }); + } + + public StringFilter(String name, Function extractor, StringConverter converter) { + super(name, extractor, converter); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected ObservableList> defaultPredicates() { + return Stream.>of( + new BiPredicateBean<>("contains", String::contains), + new BiPredicateBean<>("contains ignore case", StringUtils::containsIgnoreCase), + new BiPredicateBean<>("contains any", StringUtils::containsAny), + new BiPredicateBean<>("contains all", StringUtils::containsAll), + new BiPredicateBean<>("ends with", String::endsWith), + new BiPredicateBean<>("ends with ignore case", StringUtils::endsWithIgnoreCase), + new BiPredicateBean<>("equals", String::equals), + new BiPredicateBean<>("equals ignore case", String::equalsIgnoreCase), + new BiPredicateBean<>("is not equal to", (aString, aString2) -> !aString.equals(aString2)), + new BiPredicateBean<>("starts with", String::startsWith), + new BiPredicateBean<>("starts with ignore case", StringUtils::startsWithIgnoreCase) + ).collect(FXCollectors.toList()); + } + + @SafeVarargs + @Override + protected final StringFilter extend(BiPredicateBean... predicateBeans) { + Collections.addAll(super.predicates, predicateBeans); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/base/AbstractFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/base/AbstractFilter.java new file mode 100755 index 00000000..1bb6b9f4 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/base/AbstractFilter.java @@ -0,0 +1,246 @@ +package io.github.palexdev.materialfx.filter.base; + +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.controls.MFXFilterPane; +import io.github.palexdev.materialfx.enums.ChainMode; +import io.github.palexdev.materialfx.beans.FilterBean; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.ObservableList; +import javafx.util.StringConverter; + +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Base class for all filters. + *

+ * A filter is a class capable of operating on a given T object type for + * a given U field of that object. + *

+ * In other words, it is capable of extracting a field U from an object T (this is the extractor function) + * and producing a {@link Predicate} given a certain input (also called query) and it's a String. + *

+ * To make the filter system flexible and yet highly specialized, every implementation must specify a + * {@link StringConverter} which is used to convert the query to an object of type U. + *

+ * At this point we have all the basic elements to describe how the {@link Predicate} is predicate is produced. + * Every implementation of this base class has some predefined {@link BiPredicate} which operate on U objects. + * The query is converted to an object of type U, and the extractor gets the U field from a T object, both U + * objects are fed to the {@link BiPredicate}. In code: + *
+ * {@code
+ *      // We have the query...
+ *      String query = ...;
+ *      U convertedQuery = converter.fromString(query);
+ *
+ *      // We can build a Predicate by doing this...
+ *      Predicate predicate = t -> biPredicate.test(extractor.apply(t), convertedQuery);
+ * }
+ * 
+ *

+ * Filters are intended to be used with UI controls, they provide an interactive way to build a {@link Predicate} + * and filter a collection with generics, however you can also use them without an UI, however some other + * aspects needs to be discussed because they are strictly related to UI usage: + *

Every filter has a name, see {@link MFXFilterPane} documentation for an example + *

{@link BiPredicate}s are wrapped in a {@link BiPredicateBean} + *

The BiPredicate to use is "selected" with an index property (ideal for comboboxes), see {@link #predicateFor(String)}. + * + * @param the type of objects to filter + * @param the objects' field on which to operate + */ +public abstract class AbstractFilter { + //================================================================================ + // Properties + //================================================================================ + private final String name; + private final Function extractor; + protected final ObservableList> predicates; + protected final IntegerProperty selectedPredicateIndex = new SimpleIntegerProperty(-1); + protected final StringConverter converter; + + //================================================================================ + // Constructors + //================================================================================ + public AbstractFilter(String name, Function extractor, StringConverter converter) { + this.name = name; + this.extractor = extractor; + this.converter = converter; + this.predicates = defaultPredicates(); + } + + //================================================================================ + // Abstract Methods + //================================================================================ + + /** + * Every implementation of {@link AbstractFilter} must define some default {@link BiPredicate}s. + */ + protected abstract ObservableList> defaultPredicates(); + + /** + * Allows to add some extra {@link BiPredicateBean}s alongside the default ones. + */ + protected abstract AbstractFilter extend(BiPredicateBean... predicateBeans); + + //================================================================================ + // Methods + //================================================================================ + + /** + * Converts a given input String to an object of type U using + * the {@link StringConverter} specified by this filter. + */ + public U getValue(String input) { + return getConverter().fromString(input); + } + + /** + * Produces a {@link Predicate} from the given input. + *

+ * First checks if a {@link BiPredicate} is selected by checking + * the selected index property, see {@link #checkIndex()}. + *

+ * Then converts the input to an object of type U by using {@link #getValue(String)}, + * and then returns a Predicate that applies the selected BiPredicate to the extracted U field of T + * and the converted U input. + *

+ * In code: + *
+     * {@code
+     *      return t -> biPredicate.test(extractor.apply(t), convertedQuery);
+     * }
+     * 
+ */ + public Predicate predicateFor(String input) { + checkIndex(); + int index = getSelectedPredicateIndex(); + U convertedInput = getValue(input); + return t -> predicates.get(index).predicate().test(extractor.apply(t), convertedInput); + } + + // TODO can be used but warn + /** + * Produces a {@link Predicate} from the given input and {@link BiPredicate}. + *

+ * First converts the input to an object of type U by using {@link #getValue(String)}, + * and then returns a Predicate that applies the given BiPredicate to the extracted U field of T + * and the converted U input. + *

+ * In code: + *
+     * {@code
+     *      return t -> biPredicate.test(extractor.apply(t), convertedQuery);
+     * }
+     * 
+ *

+ * WARN: to be honest this method should have been removed but I wanted to keep it + * since it adds some flexibility to the filter system. Note that using this method may lead + * to inconsistencies in UI controls since the given argument is not a {@link BiPredicateBean}, + * which means that it won't be added to the predicates list of this filter, and the selected predicate index + * property won't be updated. This also means that any other method that relies on that index will fail. + */ + public Predicate predicateFor(String input, BiPredicate biPredicate) { + U convertedInput = getValue(input); + return t -> biPredicate.test(extractor.apply(t), convertedInput); + } + + /** + * Converts this filter to a {@link FilterBean} from the given input. + *

+ * Checks for the selected BiPredicate, see {@link #checkIndex()}. + */ + public FilterBean toFilterBean(String input) { + checkIndex(); + int index = getSelectedPredicateIndex(); + BiPredicateBean bean = predicates.get(index); + return new FilterBean<>(input, this, bean); + } + + /** + * Converts this filter to a {@link FilterBean} from the given input and {@link ChainMode}. + *

+ * Checks for the selected BiPredicate, see {@link #checkIndex()}. + */ + public FilterBean toFilterBean(String input, ChainMode mode) { + checkIndex(); + int index = getSelectedPredicateIndex(); + BiPredicateBean bean = predicates.get(index); + return new FilterBean<>(input, this, bean, mode); + } + + /** + * Converts this filter to a {@link FilterBean} from the given input, {@link BiPredicateBean} and {@link ChainMode}. + */ + public FilterBean toFilterBean(String input, BiPredicateBean bean, ChainMode mode) { + return new FilterBean<>(input, this, bean, mode); + } + + /** + * Used in methods which rely on a selected {@link BiPredicateBean}. + * + * @throws IllegalStateException if the selected index is not valid + */ + private void checkIndex() throws IllegalStateException{ + int index = getSelectedPredicateIndex(); + if (index < 0) { + throw new IllegalStateException("No predicate selected for filter: " + name); + } + } + + //================================================================================ + // Getters/Setters + //================================================================================ + + /** + * @return the filter's name + */ + public String name() { + return name; + } + + /** + * @return the function used to extract a field of type U from an object of type T + */ + public Function getExtractor() { + return extractor; + } + + /** + * @return the list of usable {@link BiPredicate}s, each wrapped in a {@link BiPredicateBean} + */ + public ObservableList> getPredicates() { + return predicates; + } + + public int getSelectedPredicateIndex() { + return selectedPredicateIndex.get(); + } + + /** + * Used to specify the selected {@link BiPredicateBean}. + */ + public IntegerProperty selectedPredicateIndexProperty() { + return selectedPredicateIndex; + } + + public void setSelectedPredicateIndex(int selectedPredicateIndex) { + this.selectedPredicateIndex.set(selectedPredicateIndex); + } + + /** + * @return the {@link StringConverter} used to convert the input String to an object of type U + */ + public StringConverter getConverter() { + return converter; + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + public String toString() { + return name; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/filter/base/NumberFilter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/base/NumberFilter.java new file mode 100755 index 00000000..98348003 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/filter/base/NumberFilter.java @@ -0,0 +1,18 @@ +package io.github.palexdev.materialfx.filter.base; + +import javafx.util.StringConverter; + +import java.util.function.Function; + +/** + * Extension of {@link AbstractFilter}, still abstract, limits the U parameter to {@link Number}s. + */ +public abstract class NumberFilter extends AbstractFilter { + + //================================================================================ + // Constructors + //================================================================================ + public NumberFilter(String name, Function extractor, StringConverter converter) { + super(name, extractor, converter); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontHandler.java b/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontHandler.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java b/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java old mode 100644 new mode 100755 index 61680d88..c4146397 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java @@ -19,7 +19,7 @@ package io.github.palexdev.materialfx.font; /** - * Enumerator class for MaterialFX font resources. (Count: 95) + * Enumerator class for MaterialFX font resources. (Count: 101) */ public enum FontResources { ANGLE_DOWN("mfx-angle-down", '\uE900'), @@ -29,94 +29,100 @@ public enum FontResources { ARROW_BACK("mfx-arrow-back", '\uE904'), ARROW_FORWARD("mfx-arrow-forward", '\uE905'), BACK("mfx-back", '\uE906'), - CALENDAR_BLACK("mfx-calendar-black", '\uE907'), - CALENDAR_SEMI_BLACK("mfx-calendar-semi-black", '\uE908'), - CALENDAR_WHITE("mfx-calendar-white", '\uE909'), - CARET_DOWN("mfx-caret-down", '\uE90A'), - CARET_LEFT("mfx-caret-left", '\uE90B'), - CARET_RIGHT("mfx-caret-right", '\uE90C'), - CARET_UP("mfx-caret-up", '\uE90D'), - CASPIAN_MARK("mfx-caspian-mark", '\uE90E'), - CHART_PIE("mfx-chart-pie", '\uE90F'), - CHECK_CIRCLE("mfx-check-circle", '\uE910'), - CHEVRON_DOWN("mfx-chevron-down", '\uE911'), - CHEVRON_LEFT("mfx-chevron-left", '\uE912'), - CHEVRON_RIGHT("mfx-chevron-right", '\uE913'), - CHEVRON_UP("mfx-chevron-up", '\uE914'), - CIRCLE("mfx-circle", '\uE915'), - CONTENT_COPY("mfx-content-copy", '\uE916'), - CONTENT_CUT("mfx-content-cut", '\uE917'), - CONTENT_PASTE("mfx-content-paste", '\uE918'), - DASHBOARD("mfx-dashboard", '\uE919'), - DEBUG("mfx-debug", '\uE91A'), - DELETE("mfx-delete", '\uE91B'), - DELETE_ALT("mfx-delete-alt", '\uE91C'), - EXCLAMATION_CIRCLE("mfx-exclamation-circle", '\uE91D'), - EXCLAMATION_TRIANGLE("mfx-exclamation-triangle", '\uE91E'), - EXPAND("mfx-expand", '\uE91F'), - EYE("mfx-eye", '\uE920'), - EYE_SLASH("mfx-eye-slash", '\uE921'), - FILE("mfx-file", '\uE922'), - FILTER("mfx-filter", '\uE923'), - FILTER_ALT("mfx-filter-alt", '\uE924'), - FILTER_ALT_CLEAR("mfx-filter-alt-clear", '\uE925'), - FIRST_PAGE("mfx-first-page", '\uE926'), - FIT("mfx-fit", '\uE927'), - FOLDER("mfx-folder", '\uE928'), - GEAR("mfx-gear", '\uE929'), - GOOGLE("mfx-google", '\uE92A'), - GOOGLE_DRAWING("mfx-google-drawing", '\uE92B'), - GOOGLE_DRIVE("mfx-google-drive", '\uE92C'), - GOOGLE_FORMS("mfx-google-forms", '\uE92D'), - GOOGLE_FUSION_TABLES("mfx-google-fusion-tables", '\uE92E'), - GOOGLE_PRESENTATION("mfx-google-presentation", '\uE92F'), - GOOGLE_SCRIPT("mfx-google-script", '\uE930'), - GOOGLE_SITES("mfx-google-sites", '\uE931'), - HOME("mfx-home", '\uE932'), - IMAGE("mfx-image", '\uE933'), - INFO("mfx-info", '\uE934'), - INFO_CIRCLE("mfx-info-circle", '\uE935'), - LAST_PAGE("mfx-last-page", '\uE936'), - LEVEL_UP("mfx-level-up", '\uE937'), - LOCK("mfx-lock", '\uE938'), - LOCK_OPEN("mfx-lock-open", '\uE939'), - MAP("mfx-map", '\uE93A'), - MINUS("mfx-minus", '\uE93B'), - MINUS_CIRCLE("mfx-minus-circle", '\uE93C'), - MODENA_MARK("mfx-modena-mark", '\uE93D'), - MUSIC("mfx-music", '\uE93E'), - NEXT("mfx-next", '\uE93F'), - REDO("mfx-redo", '\uE940'), - RESTORE("mfx-restore", '\uE941'), - SEARCH("mfx-search", '\uE942'), - SEARCH_PLUS("mfx-search-plus", '\uE943'), - SELECT_ALL("mfx-select-all", '\uE944'), - SHORTCUT("mfx-shortcut", '\uE945'), - SLIDERS("mfx-sliders", '\uE946'), - SPREADSHEET("mfx-spreadsheet", '\uE947'), - STEP_BACKWARD("mfx-step-backward", '\uE948'), - STEP_FORWARD("mfx-step-forward", '\uE949'), - SYNC("mfx-sync", '\uE94A'), - SYNC_LIGHT("mfx-sync-light", '\uE94B'), - UNDO("mfx-undo", '\uE94C'), - USER("mfx-user", '\uE94D'), - USERS("mfx-users", '\uE94E'), - VARIANT10_MARK("mfx-variant10-mark", '\uE94F'), - VARIANT11_MARK("mfx-variant11-mark", '\uE950'), - VARIANT12_MARK("mfx-variant12-mark", '\uE951'), - VARIANT3_MARK("mfx-variant3-mark", '\uE952'), - VARIANT4_MARK("mfx-variant4-mark", '\uE953'), - VARIANT5_MARK("mfx-variant5-mark", '\uE954'), - VARIANT6_MARK("mfx-variant6-mark", '\uE955'), - VARIANT7_MARK("mfx-variant7-mark", '\uE956'), - VARIANT8_MARK("mfx-variant8-mark", '\uE957'), - VARIANT9_MARK("mfx-variant9-mark", '\uE958'), - VIDEO("mfx-video", '\uE959'), - X("mfx-x", '\uE95A'), - X_ALT("mfx-x-alt", '\uE95B'), - X_CIRCLE("mfx-x-circle", '\uE95C'), - X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE95D'), - X_LIGHT("mfx-x-light", '\uE95E'), + BELL("mfx-bell", '\uE907'), + BELL_ALT("mfx-bell-alt", '\uE908'), + CALENDAR_BLACK("mfx-calendar-black", '\uE909'), + CALENDAR_SEMI_BLACK("mfx-calendar-semi-black", '\uE90A'), + CALENDAR_WHITE("mfx-calendar-white", '\uE90B'), + CARET_DOWN("mfx-caret-down", '\uE90C'), + CARET_LEFT("mfx-caret-left", '\uE90D'), + CARET_RIGHT("mfx-caret-right", '\uE90E'), + CARET_UP("mfx-caret-up", '\uE90F'), + CASPIAN_MARK("mfx-caspian-mark", '\uE910'), + CHART_PIE("mfx-chart-pie", '\uE911'), + CHECK_CIRCLE("mfx-check-circle", '\uE912'), + CHECK_CIRCLE_EMPTY("mfx-check-circle-empty", '\uE913'), + CHEVRON_DOWN("mfx-chevron-down", '\uE914'), + CHEVRON_LEFT("mfx-chevron-left", '\uE915'), + CHEVRON_RIGHT("mfx-chevron-right", '\uE916'), + CHEVRON_UP("mfx-chevron-up", '\uE917'), + CIRCLE("mfx-circle", '\uE918'), + CIRCLE_EMPTY("mfx-circle-empty", '\uE919'), + CONTENT_COPY("mfx-content-copy", '\uE91A'), + CONTENT_CUT("mfx-content-cut", '\uE91B'), + CONTENT_PASTE("mfx-content-paste", '\uE91C'), + DASHBOARD("mfx-dashboard", '\uE91D'), + DEBUG("mfx-debug", '\uE91E'), + DELETE("mfx-delete", '\uE91F'), + DELETE_ALT("mfx-delete-alt", '\uE920'), + EXCLAMATION_CIRCLE("mfx-exclamation-circle", '\uE921'), + EXCLAMATION_TRIANGLE("mfx-exclamation-triangle", '\uE922'), + EXPAND("mfx-expand", '\uE923'), + EYE("mfx-eye", '\uE924'), + EYE_SLASH("mfx-eye-slash", '\uE925'), + FILE("mfx-file", '\uE926'), + FILTER("mfx-filter", '\uE927'), + FILTER_ALT("mfx-filter-alt", '\uE928'), + FILTER_ALT_CLEAR("mfx-filter-alt-clear", '\uE929'), + FIRST_PAGE("mfx-first-page", '\uE92A'), + FIT("mfx-fit", '\uE92B'), + FOLDER("mfx-folder", '\uE92C'), + GEAR("mfx-gear", '\uE92D'), + GOOGLE("mfx-google", '\uE92E'), + GOOGLE_DRAWING("mfx-google-drawing", '\uE92F'), + GOOGLE_DRIVE("mfx-google-drive", '\uE930'), + GOOGLE_FORMS("mfx-google-forms", '\uE931'), + GOOGLE_FUSION_TABLES("mfx-google-fusion-tables", '\uE932'), + GOOGLE_PRESENTATION("mfx-google-presentation", '\uE933'), + GOOGLE_SCRIPT("mfx-google-script", '\uE934'), + GOOGLE_SITES("mfx-google-sites", '\uE935'), + HOME("mfx-home", '\uE936'), + IMAGE("mfx-image", '\uE937'), + INFO("mfx-info", '\uE938'), + INFO_CIRCLE("mfx-info-circle", '\uE939'), + LAST_PAGE("mfx-last-page", '\uE93A'), + LEVEL_UP("mfx-level-up", '\uE93B'), + LOCK("mfx-lock", '\uE93C'), + LOCK_OPEN("mfx-lock-open", '\uE93D'), + MAP("mfx-map", '\uE93E'), + MINUS("mfx-minus", '\uE93F'), + MINUS_CIRCLE("mfx-minus-circle", '\uE940'), + MODENA_MARK("mfx-modena-mark", '\uE941'), + MUSIC("mfx-music", '\uE942'), + NEXT("mfx-next", '\uE943'), + REDO("mfx-redo", '\uE944'), + RESTORE("mfx-restore", '\uE945'), + SEARCH("mfx-search", '\uE946'), + SEARCH_PLUS("mfx-search-plus", '\uE947'), + SELECT_ALL("mfx-select-all", '\uE948'), + SHORTCUT("mfx-shortcut", '\uE949'), + SLIDERS("mfx-sliders", '\uE94A'), + SPREADSHEET("mfx-spreadsheet", '\uE94B'), + STEP_BACKWARD("mfx-step-backward", '\uE94C'), + STEP_FORWARD("mfx-step-forward", '\uE94D'), + SYNC("mfx-sync", '\uE94E'), + SYNC_LIGHT("mfx-sync-light", '\uE94F'), + UNDO("mfx-undo", '\uE950'), + USER("mfx-user", '\uE951'), + USERS("mfx-users", '\uE952'), + VARIANT10_MARK("mfx-variant10-mark", '\uE953'), + VARIANT11_MARK("mfx-variant11-mark", '\uE954'), + VARIANT12_MARK("mfx-variant12-mark", '\uE955'), + VARIANT13_MARK("mfx-variant13-mark", '\uE956'), + VARIANT14_MARK("mfx-variant14-mark", '\uE957'), + VARIANT3_MARK("mfx-variant3-mark", '\uE958'), + VARIANT4_MARK("mfx-variant4-mark", '\uE959'), + VARIANT5_MARK("mfx-variant5-mark", '\uE95A'), + VARIANT6_MARK("mfx-variant6-mark", '\uE95B'), + VARIANT7_MARK("mfx-variant7-mark", '\uE95C'), + VARIANT8_MARK("mfx-variant8-mark", '\uE95D'), + VARIANT9_MARK("mfx-variant9-mark", '\uE95E'), + VIDEO("mfx-video", '\uE95F'), + X("mfx-x", '\uE960'), + X_ALT("mfx-x-alt", '\uE961'), + X_CIRCLE("mfx-x-circle", '\uE962'), + X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE963'), + X_LIGHT("mfx-x-light", '\uE964') ; public static FontResources findByDescription(String description) { diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/font/MFXFontIcon.java b/materialfx/src/main/java/io/github/palexdev/materialfx/font/MFXFontIcon.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/MFXNotificationCenterSystem.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/MFXNotificationCenterSystem.java new file mode 100755 index 00000000..730dbbc1 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/MFXNotificationCenterSystem.java @@ -0,0 +1,342 @@ +package io.github.palexdev.materialfx.notifications; + +import io.github.palexdev.materialfx.beans.CustomBounds; +import io.github.palexdev.materialfx.beans.PositionBean; +import io.github.palexdev.materialfx.beans.TransitionPositionBean; +import io.github.palexdev.materialfx.controls.MFXNotificationCenter; +import io.github.palexdev.materialfx.effects.ConsumerTransition; +import io.github.palexdev.materialfx.effects.Interpolators; +import io.github.palexdev.materialfx.enums.NotificationPos; +import io.github.palexdev.materialfx.notifications.base.AbstractMFXNotificationSystem; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.notifications.base.INotificationSystem; +import io.github.palexdev.materialfx.utils.AnimationUtils.ParallelBuilder; +import io.github.palexdev.materialfx.utils.AnimationUtils.PauseBuilder; +import io.github.palexdev.materialfx.utils.AnimationUtils.TimelineBuilder; +import io.github.palexdev.materialfx.utils.ExecutionUtils; +import javafx.animation.PauseTransition; +import javafx.geometry.Bounds; +import javafx.geometry.Rectangle2D; +import javafx.scene.input.MouseEvent; +import javafx.stage.Window; +import javafx.stage.WindowEvent; + +/** + * Implementation of an {@link AbstractMFXNotificationSystem} which makes use of a {@link MFXNotificationCenter} + * to show the notifications. + */ +public class MFXNotificationCenterSystem extends AbstractMFXNotificationSystem { + //================================================================================ + // Instance + //================================================================================ + private final static MFXNotificationCenterSystem instance = new MFXNotificationCenterSystem(); + + public static MFXNotificationCenterSystem instance() { + return instance; + } + + //================================================================================ + // Properties + //================================================================================ + private final MFXNotificationCenter center; + private boolean openOnNew = true; + + //================================================================================ + // Constructors + //================================================================================ + private MFXNotificationCenterSystem() { + super(); + center = new MFXNotificationCenter(); + center.setOnIconClicked(event -> { + if (!isShowing() && !center.getNotifications().isEmpty()) { + show(); + } else { + close(); + } + }); + + center.popupHoverProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + closeAfterTransition.stop(); + } else if (closeAutomatically) { + closeAfterTransition.playFromStart(); + } + }); + center.addEventFilter(MouseEvent.MOUSE_ENTERED, event -> closeAfterTransition.stop()); + center.addEventFilter(MouseEvent.MOUSE_EXITED, event -> { + if (closeAutomatically) closeAfterTransition.playFromStart(); + }); + + popup.setContent(center); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + /** + * {@inheritDoc} + *

+ * This method must be called before the notification system can be used. + *

+ * Also calls {@link #dispose()} before initializing. + */ + @Override + public MFXNotificationCenterSystem initOwner(Window owner) { + dispose(); + super.owner = owner; + owner.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, onClose); + center.setOpacity(0.0); + if (!dummyStage.isShowing()) dummyStage.show(); + popup.show(dummyStage); + return this; + } + + /** + * Initializes the popup's position for the specified {@link NotificationPos}. + */ + @Override + protected void init() { + PositionBean positionBean = computePosition(); + popup.setX(positionBean.getX()); + popup.setY(positionBean.getY()); + init = true; // TODO remove? + } + + /** + * If the notification system is closing, exits and {@link #scheduleReopen(INotification)} is called. + *

+ * Adds the notification to the {@link MFXNotificationCenter} then if the notification system is set + * to close automatically starts the close {@link PauseTransition}. + *

+ * If the notification center is in "Do not disturb mode" exits immediately otherwise + * shows the popup (the content is still hidden tough so it's not really open), then shown + * the bell icon and if {@link #isOpenOnNew()} is true, {@link #show()} is called. + * + * @throws IllegalStateException if the notification system has not been initialized + */ + @Override + public INotificationSystem publish(INotification notification) { + if (notification == null) return this; + + if (owner == null) { + throw new IllegalStateException("The NotificationSystem has not been initialized!"); + } else if (isClosing()) { + scheduleReopen(notification); + } else { + center.getNotifications().add(notification); + if (closeAutomatically) { + closeAfterTransition.playFromStart(); + } + + if (!isShowing() && !center.isDoNotDisturb()) { + init(); + popup.show(dummyStage); + + if (animated) { + TimelineBuilder.build().show(250, center).getAnimation().play(); + } else { + center.setOpacity(1.0); + } + if (openOnNew) { + show(); + } + } + } + return this; + } + + /** + * Sets the showing property to true, computes the popup position as a {@link TransitionPositionBean}, + * then positions the popup (animated or not), and tells the notification center to open. + */ + @Override + protected void show() { + showing.set(true); + TransitionPositionBean positionBean = computePosition(); + double x = positionBean.getX(); + double y = positionBean.getY(); + double deltaX = positionBean.deltaX(); + double deltaY = positionBean.deltaY(); + + if (animated) { + ParallelBuilder.build() + .add(ConsumerTransition.of(frac -> popup.setX(x - deltaX * frac), 250, Interpolators.INTERPOLATOR_V2)) + .add(ConsumerTransition.of(frac -> popup.setY(y - deltaY * frac), 250, Interpolators.INTERPOLATOR_V2)) + .setOnFinished(event -> center.setShowing(true)) + .getAnimation() + .play(); + } else { + popup.setX(x - deltaX); + popup.setY(y - deltaY); + center.setShowing(true); + } + } + + /** + * Sets the closing property to true, immediately closes the notification center, + * computes the popup's position as a {@link TransitionPositionBean} and then sets the popup's + * coordinates (animated or not). + * At the end always hides the popup, and resets the showing/closing properties. + */ + @Override + protected void close() { + closing.set(true); + center.setShowing(false); + + TransitionPositionBean positionBean = computePosition(); + double x = popup.getX(); + double y = popup.getY(); + double deltaX = positionBean.deltaX(); + double deltaY = positionBean.deltaY(); + + if (animated) { + ParallelBuilder.build() + .setDelay(100) + .hide(400, center) + .add(ConsumerTransition.of(frac -> popup.setX(x + deltaX * frac), 250, Interpolators.INTERPOLATOR_V2)) + .add(ConsumerTransition.of(frac -> popup.setY(y + deltaY * frac), 250, Interpolators.INTERPOLATOR_V2)) + .setOnFinished(event -> { + popup.hide(); + closing.reset(); + showing.reset(); + }) + .getAnimation().play(); + } else { + PauseBuilder.build().setDuration(30).setOnFinished((event) -> { + popup.setX(x + deltaX); + popup.setY(y + deltaY); + center.setOpacity(0); + popup.hide(); + showing.reset(); + closing.reset(); + }).getAnimation().play(); + } + } + + /** + * Adds a one-time listener to the closing property so that when it becomes + * false the notification that could not be shown will be sent to {@link #publish(INotification)} again. + */ + @Override + protected void scheduleReopen(INotification notification) { + ExecutionUtils.executeWhen( + closing, + (oldValue, newValue) -> publish(notification), + false, + (oldValue, newValue) -> !newValue, + true + ); + } + + /** + * Computes the position of the popup as a {@link TransitionPositionBean} to be used in animations too. + */ + @Override + protected TransitionPositionBean computePosition() { + double x; + double y; + double endX; + double endY; + + Rectangle2D screenBounds = screen.getVisualBounds(); + CustomBounds centerBounds = getBounds(); + double counterWidth = centerBounds.getMaxX() - centerBounds.getMinX(); + + if (position.isTop()) { + y = screenBounds.getMinY() + spacing.getTop(); + endY = y; + } else { + y = screenBounds.getMaxY() - centerBounds.getMaxY() - spacing.getBottom(); + endY = screenBounds.getMaxY() - centerBounds.getHeight() - spacing.getBottom(); + } + + if (position.isCenter()) { + x = (screenBounds.getMaxX() / 2) - (counterWidth / 2); + endX = x; + } else if (position.isRight()) { + x = screenBounds.getMaxX() - counterWidth - spacing.getRight(); + endX = screenBounds.getMaxX() - centerBounds.getMaxX() - spacing.getRight(); + } else { + x = screenBounds.getMinX() + spacing.getLeft(); + endX = centerBounds.getMinX() + spacing.getLeft(); + } + return TransitionPositionBean.of(x, y, endX, endY); + } + + @Override + public INotificationSystem dispose() { + if (super.owner != null) { + super.owner.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, onClose); + super.owner = null; + } + if (dummyStage.isShowing()) { + dummyStage.close(); + } + return this; + } + + //================================================================================ + // Methods + //================================================================================ + + /** + * Computes the bounds of the notification center. + * Custom bounds are needed since we need to take into account the bell icon AND the popup. + */ + private CustomBounds getBounds() { + Bounds bounds = center.getLayoutBounds(); + double width = center.getPopupWidth(); + double height = bounds.getHeight() + center.getPopupHeight() + center.getPopupSpacing(); + double minX = (width / 2) - (bounds.getWidth() / 2); + double maxX = minX + bounds.getWidth(); + return new CustomBounds( + minX, + bounds.getMinY(), + maxX, + bounds.getMaxY(), + width, + height + ); + } + + //================================================================================ + // Getters/Setters + //================================================================================ + + /** + * Returns the instance of the notification center used by this notification system. + *

+ * WARN: do not mess around too much with it since the notification system is highly + * dependant on the notification center. For this reason I also warn you that + * this method could be deprecated and removed in the future, maybe replaced by delegates methods + * to expose only a few features. + */ + public MFXNotificationCenter getCenter() { + return center; + } + + /** + * {@inheritDoc} + *

+ * Overridden to also enable/disable the notification center's animations. + */ + @Override + public AbstractMFXNotificationSystem setAnimated(boolean animated) { + this.animated = animated; + center.setAnimated(animated); + return this; + } + + /** + * Specifies if the notification center should be opened when a new notification is sent. + */ + public boolean isOpenOnNew() { + return openOnNew; + } + + public MFXNotificationCenterSystem setOpenOnNew(boolean openOnNew) { + this.openOnNew = openOnNew; + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/MFXNotificationSystem.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/MFXNotificationSystem.java new file mode 100755 index 00000000..185217eb --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/MFXNotificationSystem.java @@ -0,0 +1,295 @@ +package io.github.palexdev.materialfx.notifications; + +import io.github.palexdev.materialfx.beans.CustomBounds; +import io.github.palexdev.materialfx.beans.PositionBean; +import io.github.palexdev.materialfx.beans.TransitionPositionBean; +import io.github.palexdev.materialfx.collections.CircularQueue; +import io.github.palexdev.materialfx.effects.ConsumerTransition; +import io.github.palexdev.materialfx.effects.Interpolators; +import io.github.palexdev.materialfx.enums.NotificationPos; +import io.github.palexdev.materialfx.notifications.base.AbstractMFXNotificationSystem; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.notifications.base.INotificationSystem; +import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; +import io.github.palexdev.materialfx.utils.AnimationUtils.ParallelBuilder; +import io.github.palexdev.materialfx.utils.AnimationUtils.PauseBuilder; +import io.github.palexdev.materialfx.utils.AnimationUtils.TimelineBuilder; +import io.github.palexdev.materialfx.utils.ExecutionUtils; +import javafx.animation.PauseTransition; +import javafx.geometry.Bounds; +import javafx.geometry.Rectangle2D; +import javafx.scene.Group; +import javafx.stage.Window; +import javafx.stage.WindowEvent; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple implementation of an {@link AbstractMFXNotificationSystem} which makes use of + * a {@link CircularQueue} to keep an history of the shown notifications (by default max size is 100), + * and a list to keep a reference to queued notifications that can't be shown at the moment of {@link #publish(INotification)} + * and that will be sent to {@link #scheduleReopen(INotification)} instead. + */ +public class MFXNotificationSystem extends AbstractMFXNotificationSystem { + //================================================================================ + // Instance + //================================================================================ + private static final MFXNotificationSystem instance = new MFXNotificationSystem(); + + public static MFXNotificationSystem instance() { + return instance; + } + + //================================================================================ + // Properties + //================================================================================ + private final CircularQueue notifications = new CircularQueue<>(100); + private final List queued = new ArrayList<>(); + private final Group notificationContainer; + + //================================================================================ + // Constructors + //================================================================================ + private MFXNotificationSystem() { + super(); + notificationContainer = new Group(); + notificationContainer.setOpacity(0.0); + + popup.setContent(notificationContainer); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + /** + * {@inheritDoc} + *

+ * This method must be called before the notification system can be used. + *

+ * Also calls {@link #dispose()} before initializing. + */ + @Override + public MFXNotificationSystem initOwner(Window owner) { + dispose(); + super.owner = owner; + owner.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, onClose); + popup.show(dummyStage); + return this; + } + + /** + * Initializes the popup's position for the specified {@link NotificationPos}. + */ + @Override + protected void init() { + PositionBean position = computePosition(); + popup.show(dummyStage, position.getX(), position.getY()); + init = true; // TODO remove? + } + + /** + * If the notification system is showing/closing, exits and {@link #scheduleReopen(INotification)} is called. + *

+ * Adds the notification to the notifications queue then if the notification system is set + * to close automatically starts the close {@link PauseTransition}. + *

+ * Shows the popup (the content is still hidden tough so it's not really open), then forces + * the notification container to compute its bounds (should not be necessary though), shown the content + * and calls {@link #show()}. + * + * @throws IllegalStateException if the notification system has not been initialized + */ + @Override + public MFXNotificationSystem publish(INotification notification) { + if (notification == null) return this; + + if (owner == null) { + throw new IllegalStateException("The NotificationSystem has not been initialized!"); + } else if (isClosing() || isShowing()) { + scheduleReopen(notification); + } else { + notifications.add(notification); + if (closeAutomatically) { + closeAfterTransition.playFromStart(); + } + + if (!isShowing()) { + init(); + popup.show(dummyStage); + + notificationContainer.getChildren().setAll(notification.getContent()); + notificationContainer.applyCss(); + notificationContainer.layout(); + + //popup.setY(screen.getBounds().getWidth()); + if (animated) { + TimelineBuilder.build().show(400, notificationContainer).getAnimation().play(); + } else { + notificationContainer.setOpacity(1.0); + } + show(); + } + } + return this; + } + + /** + * Sets the showing property to true, computes the popup position as a {@link TransitionPositionBean}, + * then positions the popup (animated or not). + */ + @Override + protected void show() { + showing.set(true); + TransitionPositionBean positionBean = computePosition(); + double x = positionBean.getX(); + double y = positionBean.getY(); + double deltaX = positionBean.deltaX(); + double deltaY = positionBean.deltaY(); + + if (animated) { + ParallelBuilder.build() + .add(ConsumerTransition.of(frac -> popup.setY(y - deltaY * frac), Duration.millis(400), Interpolators.INTERPOLATOR_V2.toInterpolator())) + .add(KeyFrames.of(1, event -> popup.setX(x - deltaX))) + .getAnimation().play(); + } else { + popup.setX(x - deltaX); + popup.setY(y - deltaY); + } + } + + /** + * Sets the closing property to true and hides the notification. + * At the end always hides the popup, and resets the showing/closing properties. + */ + @Override + protected void close() { + closing.set(true); + if (animated) { + TimelineBuilder.build() + .hide(400, notificationContainer) + .setOnFinished(event -> { + popup.hide(); + showing.reset(); + closing.reset(); + }) + .getAnimation() + .play(); + } else { + PauseBuilder.build().setDuration(30).setOnFinished(event -> { + notificationContainer.setOpacity(0); + popup.hide(); + showing.reset(); + closing.reset(); + }).getAnimation().play(); + } + } + + /** + * Adds the notification to the queued notifications list, then adds a one-time listener to the closing property + * so that when it becomes false a notification is removed from the queue and then + * sent to {@link #publish(INotification)} again. + */ + @Override + protected void scheduleReopen(INotification notification) { + queued.add(notification); + ExecutionUtils.executeWhen( + closing, + (oldValue, newValue) -> { + if (!queued.isEmpty()) { + PauseBuilder.build() + .setDuration(300) + .setOnFinished(event -> publish(queued.remove(0))) + .getAnimation().play(); + } + }, + false, + (oldValue, newValue) -> !newValue, + true + ); + } + + /** + * Computes the position of the popup as a {@link TransitionPositionBean} to be used in animations too. + */ + @Override + protected TransitionPositionBean computePosition() { + double x; + double y; + double endX; + double endY; + + Rectangle2D screenBounds = screen.getVisualBounds(); + Bounds containerBounds = notificationContainer.getLayoutBounds(); + + if (position.isTop()) { + y = -containerBounds.getMaxY() - spacing.getTop(); + endY = screenBounds.getMinY() + spacing.getTop(); + } else { + y = screenBounds.getMaxY() + spacing.getBottom(); + endY = screenBounds.getMaxY() - containerBounds.getMaxY() - spacing.getBottom(); + } + + if (position.isCenter()) { + x = (screenBounds.getMaxX() / 2) - (containerBounds.getMaxX() / 2); + endX = x; + } else if (position.isRight()) { + x = screenBounds.getMaxX() + spacing.getRight(); + endX = screenBounds.getMaxX() - containerBounds.getMaxX() - spacing.getRight(); + } else { + x = -containerBounds.getMaxX() - spacing.getLeft(); + endX = screenBounds.getMinX() + spacing.getLeft(); + } + return TransitionPositionBean.of(x, y, endX, endY); + } + + @Override + public INotificationSystem dispose() { + if (super.owner != null) { + super.owner.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, onClose); + super.owner = null; + } + return this; + } + + //================================================================================ + // Methods + //================================================================================ + + /** + * Computes the bounds of the notification container. + * These bounds always have the maxY 0. + */ + private CustomBounds getBounds() { + Bounds bounds = notificationContainer.getBoundsInParent(); + return new CustomBounds( + bounds.getMinX(), + bounds.getMinY(), + bounds.getMaxX(), + 0, + bounds.getWidth(), + bounds.getHeight() + ); + } + + //================================================================================ + // Getters/Setters + //================================================================================ + + /** + * Returns the list of shown notifications. + */ + public CircularQueue history() { + return notifications; + } + + /** + * Delegate to {@link CircularQueue#setSize(int)}. + */ + public MFXNotificationSystem setHistoryLimit(int size) { + notifications.setSize(size); + return this; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/NotificationPos.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/NotificationPos.java deleted file mode 100644 index 324ff521..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/NotificationPos.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.notifications; - -public enum NotificationPos { - TOP_LEFT, TOP_CENTER, TOP_RIGHT, - BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/NotificationsManager.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/NotificationsManager.java deleted file mode 100644 index 5d589746..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/NotificationsManager.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.notifications; - -import io.github.palexdev.materialfx.collections.CircularQueue; -import io.github.palexdev.materialfx.controls.MFXNotification; -import javafx.geometry.Rectangle2D; -import javafx.stage.Screen; -import javafx.stage.Window; - -import java.util.HashMap; -import java.util.Map; - -/** - * This class is a notification system, its job is to manage the incoming notifications - * by sending them to the correct position. It also keeps track of the sent notifications - * by storing them in a {@link CircularQueue} with the default size of 20. - */ -public class NotificationsManager { - //================================================================================ - // Properties - //================================================================================ - private static final Rectangle2D screenBounds; - private static final Window window; - private static final Map notifications = new HashMap<>(); - private static final CircularQueue notificationsHistory = new CircularQueue<>(20); - - //================================================================================ - // Init - //================================================================================ - static { - screenBounds = Screen.getPrimary().getVisualBounds(); - window = getWindow(); - } - - //================================================================================ - // Methods - //================================================================================ - - /** - * Sends a {@code MFXNotification} to the designated {@code PositionManager} - * - * @param pos The notifications' position on screen - * @param notification The notification - */ - public static void send(NotificationPos pos, MFXNotification notification) { - notifications.computeIfAbsent(pos, notificationPos -> new PositionManager(screenBounds, window, notificationPos)); - notifications.get(pos).show(notification); - notificationsHistory.add(notification); - } - - /** - * Sends a {@code MFXNotification} to the designated {@code PositionManager} with the specified spacing. - * - * @param pos The notifications' position on screen - * @param notification The notification - * @param spacing The number of pixels between each shown notification and from screen's left and right borders - */ - public static void send(NotificationPos pos, MFXNotification notification, double spacing) { - notifications.computeIfAbsent(pos, notificationPos -> new PositionManager(screenBounds, window, notificationPos)); - notifications.get(pos).setSpacing(spacing).show(notification); - notificationsHistory.add(notification); - } - - /** - * Sends a {@code MFXNotification} to the designated {@code PositionManager} with the specified limit. - * - * @param pos The notifications' position on screen - * @param notification The notification - * @param limit The maximum number of notifications to show, if limit is exceeded they will be queued - */ - public static void send(NotificationPos pos, MFXNotification notification, int limit) { - notifications.computeIfAbsent(pos, notificationPos -> new PositionManager(screenBounds, window, notificationPos)); - notifications.get(pos).setLimit(limit).show(notification); - notificationsHistory.add(notification); - } - - /** - * Sends a {@code MFXNotification} to the designated {@code PositionManager} with the specified spacing and limit. - * - * @param pos The notifications' position on screen - * @param notification The notification - * @param spacing The number of pixels between each shown notification and from screen's left and right borders - * @param limit The maximum number of notifications to show, if limit is exceeded they will be queued - */ - public static void send(NotificationPos pos, MFXNotification notification, double spacing, int limit) { - notifications.computeIfAbsent(pos, notificationPos -> new PositionManager(screenBounds, window, notificationPos)); - notifications.get(pos).setSpacing(spacing).setLimit(limit).show(notification); - notificationsHistory.add(notification); - } - - public static PositionManager getPositionManager(NotificationPos pos) { - return notifications.get(pos); - } - - public CircularQueue getNotificationsHistory() { - return notificationsHistory; - } - - public static void setHistoryLimit(int size) { - notificationsHistory.setSize(size); - } - - private static Window getWindow() { - for (Window w : Window.getWindows()) { - if (w.isFocused()) { - return w; - } - } - return null; - } -} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/PositionManager.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/PositionManager.java deleted file mode 100644 index 9ddfdf7f..00000000 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/PositionManager.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2021 Parisi Alessandro - * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX). - * - * MaterialFX is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MaterialFX 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with MaterialFX. If not, see . - */ - -package io.github.palexdev.materialfx.notifications; - -import io.github.palexdev.materialfx.controls.MFXNotification; -import javafx.animation.Interpolator; -import javafx.animation.Transition; -import javafx.application.Platform; -import javafx.concurrent.Task; -import javafx.geometry.Rectangle2D; -import javafx.stage.Window; -import javafx.util.Duration; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; - -/** - * Support class for the {@code NotificationManager}. - *

- * Each {@code PositionManager} is responsible for showing the incoming notifications according to its position ({@link #pos}), - * and according to its limit ({@link #limit}), which by default is 3. Each notification is distant from each other and from - * the left and right borders according to the {@link #spacing} property which by default is 15. - *

- * To simulate the behavior of a queue, a {@link ThreadPoolExecutor} and a {@link Semaphore} are used, - * each notification show is committed to a JavaFX's {@link Task} and before any other operation a permit is acquired - * from the semaphore. The semaphore has a number of permits equals to the {@link #limit} property of this class, - * when the limit is reached and there are no more permits available all other sent notifications are queued and - * waiting for a semaphore {@code release()}. The semaphore is released every time a notification is being hidden. - */ -public class PositionManager { - //================================================================================ - // Properties - //================================================================================ - private final ThreadPoolExecutor service; - private final Semaphore semaphore; - private final Rectangle2D screenBounds; - private final Window owner; - - private final List notifications = new ArrayList<>(); - private double spacing = 15; - private int limit = 3; - - private final NotificationPos pos; - private double anchorX; - private double anchorY; - - //================================================================================ - // Constructors - //================================================================================ - public PositionManager(Rectangle2D screenBounds, Window owner, NotificationPos pos) { - this.service = new ThreadPoolExecutor( - 1, - 2, - 2, - TimeUnit.SECONDS, - new LinkedBlockingDeque<>(), - runnable -> { - Thread t = Executors.defaultThreadFactory().newThread(runnable); - t.setName("MFXNotificationsThread - " + pos.name()); - t.setDaemon(true); - return t; - } - ); - this.service.allowCoreThreadTimeOut(true); - this.semaphore = new Semaphore(limit); - - this.screenBounds = screenBounds; - this.owner = owner; - this.pos = pos; - } - - //================================================================================ - // Methods - //================================================================================ - - /** - * Shows the specified notification on screen. - *

- * The show mechanism uses a {@link ThreadPoolExecutor} with JavaFX's {@code Tasks} and - * a {@link Semaphore} to make threads wait when the notifications limit is reached, - * in a sense it simulates the operation of a queue. - *

- * After the new notification has been added to the list, all others notifications are repositioned, - * then anchorX and anchorY are calculated according to the {@link #pos} property. - *

- * After that the notification's show method is called on the JavaFX's thread with the specified owner and anchors. - * On hidden the notification is removed from the list and the semaphore is released. - *

- * On failed task, logs the exception. - * - * @param newNotification The notification to show - * @see Task - */ - public void show(MFXNotification newNotification) { - Task showTask = new Task<>() { - @Override - protected Void call() throws Exception { - semaphore.acquire(); - - notifications.add(newNotification); - repositionNotifications(newNotification); - - newNotification.setOnHidden(event -> { - notifications.remove(newNotification); - semaphore.release(); - }); - computePosition(newNotification); - Platform.runLater(() -> newNotification.show(owner, anchorX, anchorY)); - - return null; - } - }; - showTask.setOnFailed(event -> { - if (showTask.getException() != null) { - showTask.getException().printStackTrace(); - } - }); - service.submit(showTask); - } - - public PositionManager setSpacing(double spacing) { - this.spacing = spacing; - return this; - } - - public PositionManager setLimit(int limit) { - this.limit = limit; - this.semaphore.release(limit); - return this; - } - - /** - * Repositions every notification in the list, except the most recent one, with a {@code Transition} animation. - */ - private void repositionNotifications(MFXNotification newNotification) { - for (int i = 0; i < notifications.indexOf(newNotification); i++) { - MFXNotification oldNotification = notifications.get(i); - buildRepositionAnimation(newNotification, oldNotification).play(); - } - } - - /** - * Builds the repositioning animation. - *

- * The new anchorY is calculated using the current value and the new notification's content prefHeight. - * Note: this works only if the notification's content has it's pref height set - * - * @param newNotification The new notification - * @param oldNotification The already showing notification - * @return The animation - */ - private Transition buildRepositionAnimation(MFXNotification newNotification, MFXNotification oldNotification) { - final double notificationHeight = newNotification.getNotificationContent().getPrefHeight(); - final double oldAnchorY = oldNotification.getAnchorY(); - - switch (pos) { - case BOTTOM_LEFT: - case BOTTOM_CENTER: - case BOTTOM_RIGHT: - return new Transition() { - { - setCycleDuration(Duration.millis(350)); - setInterpolator(Interpolator.EASE_BOTH); - } - - @Override - protected void interpolate(double frac) { - final double newAnchorY = (oldAnchorY - notificationHeight) - (spacing * frac); - oldNotification.setAnchorY(newAnchorY); - } - }; - default: - return new Transition() { - { - setCycleDuration(Duration.millis(350)); - setInterpolator(Interpolator.EASE_BOTH); - } - - @Override - protected void interpolate(double frac) { - final double newAnchorY = (oldAnchorY + notificationHeight) + (spacing * frac); - oldNotification.setAnchorY(newAnchorY); - } - }; - } - } - - /** - * Computes the notification coordinates according to the {@link #pos} property. - */ - private void computePosition(MFXNotification notification) { - switch (pos) { - case TOP_LEFT: - case TOP_CENTER: - case TOP_RIGHT: - anchorY = spacing; - break; - case BOTTOM_LEFT: - case BOTTOM_CENTER: - case BOTTOM_RIGHT: - anchorY = screenBounds.getHeight() - notification.getNotificationContent().getPrefHeight() - spacing; - break; - } - - switch (pos) { - case TOP_LEFT: - case BOTTOM_LEFT: - anchorX = spacing; - break; - case TOP_RIGHT: - case BOTTOM_RIGHT: - anchorX = screenBounds.getWidth() - notification.getNotificationContent().getPrefWidth() - spacing; - break; - default: - anchorX = (screenBounds.getWidth() / 2) - (notification.getNotificationContent().getPrefWidth() / 2); - } - } -} \ No newline at end of file diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/AbstractMFXNotificationSystem.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/AbstractMFXNotificationSystem.java new file mode 100755 index 00000000..71c150b2 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/AbstractMFXNotificationSystem.java @@ -0,0 +1,268 @@ +package io.github.palexdev.materialfx.notifications.base; + +import io.github.palexdev.materialfx.beans.TransitionPositionBean; +import io.github.palexdev.materialfx.beans.properties.resettable.ResettableBooleanProperty; +import io.github.palexdev.materialfx.controls.MFXPopup; +import io.github.palexdev.materialfx.enums.NotificationPos; +import io.github.palexdev.materialfx.factories.InsetsFactory; +import io.github.palexdev.materialfx.utils.AnimationUtils.PauseBuilder; +import io.github.palexdev.materialfx.utils.ExecutionUtils; +import javafx.animation.PauseTransition; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.stage.*; +import javafx.util.Duration; + +/** + * Base class to define a notification system. + *

+ * A notification system has the following info and features: + *

- The {@link Screen} on which to show the notification + *

- The {@link Window} on which the notification system's stage depends (more on that later) + *

- A {@link MFXPopup} which contains the notification + *

- The {@link NotificationPos} to compute the shown notifications' position + *

- Allows to specify some extra spacing between the screen's borders and the notification + *

- By default is animated but it can also be disabled + *

- By default notifications will close automatically after 3 seconds (by default) + *

+ * To fix issue #80 and similar, the notification system do not uses the focused window anymore but + * it has its own "dummy" stage. It is a UTILITY stage, so that no icon appear in the taskbar, and its opacity is 0. + * While this is a great improvement over the old design there's also a downside. + * When the JavaFX's primary stage is closed the app won't close because the "dummy" stage is still open, + * to fix this a notification system must be initialized with an owner window, so that when owner is closed + * the "dummy" stage is closed too. + *

+ * Any notification system ideally must implement the following behaviors: + *

- publish: the method that accepts and manages new notifications + *

- show: ideally called by the publish method, serves to specify how to show the new notification + *

- close: serves to specify how a notification should be closed + *

- scheduleReopen: this method specifies how the notification system behaves when + * a new notification is published but it already is in a showing/closing state. Ideally this method + * should instruct the notification system to ignore the new notification and recall publish as soon as the current + * notification is being closed. + *

- computePosition: computes the position of the notification as a {@link TransitionPositionBean} for use + * with animations too. (for non animated systems just subtract the deltas from their respective coordinates) + *

+ * Side note on the close method: it's highly recommended to also close the popup since it is not transparent + * and mouse won't work through. (may block OS windows) + *

+ * Side notes on the scheduleReopen mechanism: this base class offers two {@link ResettableBooleanProperty} to + * specify that the notification is showing/closing a notification. Show and hide methods should set these properties + * to true as the first operation, and then reset them once the notification is closed (see implementations source code for examples). + * The scheduleReopen should instruct the system to call publish as soon as the closing property becomes false. + *

+ * Last note: notification systems ideally should be singletons. + */ +public abstract class AbstractMFXNotificationSystem implements INotificationSystem { + //================================================================================ + // Properties + //================================================================================ + protected Screen screen = Screen.getPrimary(); + protected Window owner; + protected final Stage dummyStage; + protected final EventHandler onClose; + protected final MFXPopup popup; + protected NotificationPos position; + protected Insets spacing; + + protected boolean animated = true; + protected boolean closeAutomatically = true; + protected Duration closeAfter = Duration.seconds(3); + protected final PauseTransition closeAfterTransition; + + protected final ResettableBooleanProperty showing = new ResettableBooleanProperty(false, false); + protected final ResettableBooleanProperty closing = new ResettableBooleanProperty(false, false); + protected boolean init = false; + + //================================================================================ + // Constructors + //================================================================================ + protected AbstractMFXNotificationSystem() { + position = NotificationPos.BOTTOM_RIGHT; + spacing = InsetsFactory.all(15); + + dummyStage = new Stage(); + dummyStage.initStyle(StageStyle.UTILITY); + dummyStage.setOpacity(0.0); + dummyStage.show(); + onClose = event -> dummyStage.close(); + + popup = new MFXPopup(); + popup.setAnimated(false); + + closeAfterTransition = PauseBuilder.build() + .setDelay(100) + .setDuration(closeAfter) + .setOnFinished(event -> close()) + .getAnimation(); + + closing.setFireChangeOnReset(true); + } + + //================================================================================ + // Abstract Methods + //================================================================================ + + /** + * Shows a notification by manipulating the popup's coordinates and content. + */ + protected abstract void show(); + + /** + * Closes a notification by manipulating the popup's coordinates and content. + *

+ * The popup should be closed as well! + */ + protected abstract void close(); + + /** + * Instructs the notification system to shown the specified notification when possible. + */ + protected abstract void scheduleReopen(INotification notification); + + /** + * Responsible for computing the popup's coordinates. + */ + protected abstract TransitionPositionBean computePosition(); + + //================================================================================ + // Methods + //================================================================================ + + /** + * Default implementation is empty. + */ + protected void init() {} + + //================================================================================ + // Getters/Setters + //================================================================================ + + /** + * @return the screen on which to show the notifications + */ + public Screen getScreen() { + return screen; + } + + /** + * Sets the screen on which to show the notifications. + */ + public AbstractMFXNotificationSystem setScreen(Screen screen) { + this.screen = screen; + return this; + } + + /** + * @return the position at which notifications will be shown + */ + public NotificationPos getPosition() { + return position; + } + + /** + * Sets the position at which notifications will be shown. + */ + public AbstractMFXNotificationSystem setPosition(NotificationPos position) { + this.position = position; + return this; + } + + /** + * Safer version of {@link #setPosition(NotificationPos)}. If the notification system is currently showing + * it's not a good idea to change the position as the close method could then misbehave, this method + * changes the position as soon as the notification system has been closed. If it's already closed then + * the position is set immediately. + */ + public AbstractMFXNotificationSystem delaySetPosition(NotificationPos position) { + if (isShowing()) { + ExecutionUtils.executeWhen( + closing, + (oldValue, newValue) -> setPosition(position), + false, + (oldValue, newValue) -> !newValue, + true + ); + } else { + setPosition(position); + } + return this; + } + + /** + * @return the Insets object that specifies the spacing between notifications and the screen borders + */ + public Insets getSpacing() { + return spacing; + } + + /** + * Sets the Insets object that specifies the spacing between notifications and the screen borders. + */ + public AbstractMFXNotificationSystem setSpacing(Insets spacing) { + this.spacing = spacing; + return this; + } + + /** + * @return whether the notification system is animated + */ + public boolean isAnimated() { + return animated; + } + + /** + * Enables/Disables animations. + */ + public AbstractMFXNotificationSystem setAnimated(boolean animated) { + this.animated = animated; + return this; + } + + /** + * @return whether notifications should close automatically + */ + public boolean isCloseAutomatically() { + return closeAutomatically; + } + + /** + * Enables/Disables notifications automatic close. + */ + public AbstractMFXNotificationSystem setCloseAutomatically(boolean closeAutomatically) { + this.closeAutomatically = closeAutomatically; + return this; + } + + /** + * @return the duration of time after which the notifications are automatically closed + * if {@link #isCloseAutomatically()} is true + */ + public Duration getCloseAfter() { + return closeAfter; + } + + /** + * Sets the duration of time after which the notifications are automatically closed + * if {@link #isCloseAutomatically()} is true + */ + public AbstractMFXNotificationSystem setCloseAfter(Duration closeAfter) { + this.closeAfter = closeAfter; + closeAfterTransition.setDuration(closeAfter); + return this; + } + + /** + * @return whether the notification system is showing a notification + */ + public boolean isShowing() { + return showing.get(); + } + + /** + * @return whether the notification system is closing + */ + public boolean isClosing() { + return closing.get(); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/INotification.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/INotification.java new file mode 100755 index 00000000..cc017694 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/INotification.java @@ -0,0 +1,67 @@ +package io.github.palexdev.materialfx.notifications.base; + +import io.github.palexdev.materialfx.enums.NotificationState; +import javafx.beans.property.ObjectProperty; +import javafx.scene.layout.Region; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Interface which specifies the features of a notification rather than its content. + *

+ * Base features of a notification are: + *

- the content, specified as a generic {@link Region} + *

- the read state property, {@link NotificationState} + *

- a way to tell when the notification was created, how much time has passed since creation, + * a way to convert the time from a long value to a String, a way to tell that the elapsed time should change + * and an action to run when this happens + */ +public interface INotification { + + /** + * @return the notification's content + */ + Region getContent(); + + NotificationState getState(); + + /** + * Specifies the notification's read state. + */ + ObjectProperty notificationStateProperty(); + + void setNotificationState(NotificationState state); + + /** + * @return the created time as a long value, the number of seconds from the Java epoch + */ + long getTime(); + + /** + * @return the difference between the current number of seconds from the Java epoch and the created time + */ + long getElapsedTime(); + + /** + * @return the function used to convert a time in seconds to String + */ + Function getTimeToStringConverter(); + + /** + * Sets the function used to convert a time in seconds to String. + */ + void setTimeToStringConverter(Function converter); + + /** + * Should be called by a periodic task to inform "someone" that the elapsed time should be updated + */ + void updateElapsed(); + + /** + * This action is automatically called by {@link #updateElapsed()}, use this to inform "someone" that + * the elapsed time should be updated. The action is a {@link BiConsumer} and the inputs are the elapsed seconds + * as a long value and the elapsed seconds converted to a String by using the {@link #getTimeToStringConverter()}. + */ + void setOnUpdateElapsed(BiConsumer elapsedConsumer); +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/INotificationSystem.java b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/INotificationSystem.java new file mode 100755 index 00000000..1dcb2fd2 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/notifications/base/INotificationSystem.java @@ -0,0 +1,26 @@ +package io.github.palexdev.materialfx.notifications.base; + +import javafx.stage.Window; + +/** + * Defines the public API of every notification system. + */ +public interface INotificationSystem { + + /** + * A notification system should be initialized to honor the behavior of a owner Window. + * (for example if the owner closes that the notification system closes too) + */ + INotificationSystem initOwner(Window owner); + + /** + * Method to send a new notification to be shown, it's up to an implementation + * to decide how to manage and show it. + */ + INotificationSystem publish(INotification notification); + + /** + * Closes and disposes the notification system if not needed anymore. + */ + INotificationSystem dispose(); +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/ComboBoxSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/ComboBoxSelectionModel.java old mode 100644 new mode 100755 index 41b213c5..9863e93f --- a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/ComboBoxSelectionModel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/ComboBoxSelectionModel.java @@ -18,7 +18,7 @@ package io.github.palexdev.materialfx.selection; -import javafx.beans.value.ObservableValue; +import javafx.beans.property.ObjectProperty; import javafx.collections.ObservableList; /** @@ -29,7 +29,11 @@ public class ComboBoxSelectionModel extends SingleSelectionModel { //================================================================================ // Constructors //================================================================================ - public ComboBoxSelectionModel(ObservableValue> items) { + public ComboBoxSelectionModel(ObservableList items) { + super(items); + } + + public ComboBoxSelectionModel(ObjectProperty> items) { super(items); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/MultipleSelectionManager.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/MultipleSelectionManager.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/MultipleSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/MultipleSelectionModel.java old mode 100644 new mode 100755 index 9a2edc28..c935fc97 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/MultipleSelectionModel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/MultipleSelectionModel.java @@ -39,6 +39,10 @@ public class MultipleSelectionModel extends AbstractMultipleSelectionModel //================================================================================ // Constructors //================================================================================ + public MultipleSelectionModel(ObservableList items) { + super(items); + } + public MultipleSelectionModel(ObjectProperty> items) { super(items); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/SingleSelectionManager.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/SingleSelectionManager.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/SingleSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/SingleSelectionModel.java old mode 100644 new mode 100755 index 1fa92c83..bc83cc75 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/SingleSelectionModel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/SingleSelectionModel.java @@ -21,6 +21,7 @@ package io.github.palexdev.materialfx.selection; import io.github.palexdev.materialfx.selection.base.AbstractSingleSelectionModel; import io.github.palexdev.materialfx.selection.base.ISingleSelectionModel; import io.github.palexdev.materialfx.utils.others.TriConsumer; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.Property; import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyObjectProperty; @@ -40,7 +41,11 @@ public class SingleSelectionModel extends AbstractSingleSelectionModel { //================================================================================ // Constructors //================================================================================ - public SingleSelectionModel(ObservableValue> items) { + public SingleSelectionModel(ObservableList items) { + super(items); + } + + public SingleSelectionModel(ObjectProperty> items) { super(items); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/TableSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/TableSelectionModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/TreeCheckModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/TreeCheckModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/TreeSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/TreeSelectionModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractMultipleSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractMultipleSelectionModel.java old mode 100644 new mode 100755 index 3200642e..4176d7a7 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractMultipleSelectionModel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractMultipleSelectionModel.java @@ -44,6 +44,10 @@ public abstract class AbstractMultipleSelectionModel implements IMultipleSele //================================================================================ // Constructors //================================================================================ + protected AbstractMultipleSelectionModel(ObservableList items) { + this.items.set(items); + } + protected AbstractMultipleSelectionModel(ObservableValue> items) { this.items.bind(items); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractSingleSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractSingleSelectionModel.java old mode 100644 new mode 100755 index 361af32c..087bc0b8 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractSingleSelectionModel.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/AbstractSingleSelectionModel.java @@ -44,6 +44,10 @@ public abstract class AbstractSingleSelectionModel implements ISingleSelectio //================================================================================ // Constructors //================================================================================ + protected AbstractSingleSelectionModel(ObservableList items) { + this.items.set(items); + } + protected AbstractSingleSelectionModel(ObservableValue> items) { this.items.bind(items); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/IMultipleSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/IMultipleSelectionModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ISingleSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ISingleSelectionModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ITableSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ITableSelectionModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ITreeCheckModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ITreeCheckModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ITreeSelectionModel.java b/materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/ITreeSelectionModel.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXButtonSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXButtonSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckTreeItemSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckTreeItemSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java old mode 100644 new mode 100755 index 93337ee0..ea2a5fc6 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java @@ -21,7 +21,7 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.MFXCheckbox; import io.github.palexdev.materialfx.controls.MFXIconWrapper; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.geometry.Insets; @@ -68,9 +68,9 @@ public class MFXCheckboxSkin extends SkinBase { rippleGenerator.setCheckBounds(false); rippleGenerator.setClipSupplier(() -> null); rippleGenerator.setRipplePositionFunction(event -> { - RipplePosition position = new RipplePosition(); - position.setXPosition(Math.min(event.getX(), rippleContainer.getWidth())); - position.setYPosition(Math.min(event.getY(), rippleContainer.getHeight())); + PositionBean position = new PositionBean(); + position.setX(Math.min(event.getX(), rippleContainer.getWidth())); + position.setY(Math.min(event.getY(), rippleContainer.getHeight())); return position; }); rippleGenerator.setRippleRadius(16); @@ -144,7 +144,7 @@ public class MFXCheckboxSkin extends SkinBase { * then the center of the ripple is set to the width and/or height of container */ checkBox.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> { - if (!event, checkBox)) { + if (!NodeUtils.inHierarchy(event, checkBox)) { return; } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCircleToggleNodeSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCircleToggleNodeSkin.java old mode 100644 new mode 100755 index 6d4d35d4..797c7e81 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCircleToggleNodeSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCircleToggleNodeSkin.java @@ -20,9 +20,9 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.MFXCircleToggleNode; import io.github.palexdev.materialfx.controls.MFXLabel; -import io.github.palexdev.materialfx.controls.enums.TextPosition; +import io.github.palexdev.materialfx.enums.TextPosition; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.utils.LabelUtils; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.beans.binding.Bindings; @@ -112,7 +112,7 @@ public class MFXCircleToggleNodeSkin extends SkinBase { )); return clip; }); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> new PositionBean(event.getX(), event.getY())); rippleGenerator.rippleRadiusProperty().bind(circle.radiusProperty().add(5)); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java old mode 100644 new mode 100755 index 60c5577a..2e987f5b --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java @@ -24,10 +24,10 @@ import io.github.palexdev.materialfx.controls.MFXIconWrapper; import io.github.palexdev.materialfx.controls.MFXLabel; import io.github.palexdev.materialfx.controls.MFXListView; import io.github.palexdev.materialfx.controls.cell.MFXListCell; -import io.github.palexdev.materialfx.controls.enums.Styles; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.enums.Styles; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel; import io.github.palexdev.materialfx.utils.AnimationUtils; @@ -364,7 +364,7 @@ public class MFXComboBoxSkin extends SkinBase> { */ private void iconBehavior() { icon.rippleGeneratorBehavior(event -> - new RipplePosition(icon.getWidth() / 2, icon.getHeight() / 2) + new PositionBean(icon.getWidth() / 2, icon.getHeight() / 2) ); MFXCircleRippleGenerator rg = icon.getRippleGenerator(); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXContextMenuItemSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXContextMenuItemSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDateCellSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDateCellSkin.java old mode 100644 new mode 100755 index 4867ec0d..ce624947 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDateCellSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDateCellSkin.java @@ -19,10 +19,10 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.cell.MFXDateCell; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; import io.github.palexdev.materialfx.effects.ripple.RippleClipType; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import javafx.scene.control.skin.DateCellSkin; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; @@ -47,7 +47,7 @@ public class MFXDateCellSkin extends DateCellSkin { rippleGenerator = new MFXCircleRippleGenerator(dateCell); rippleGenerator.setClipSupplier(() -> new RippleClipTypeFactory(RippleClipType.ROUNDED_RECTANGLE).setArcs(15).build(dateCell)); rippleGenerator.setRippleColor(Color.rgb(220, 220, 220, 0.6)); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> new PositionBean(event.getX(), event.getY())); dateCell.addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple); updateChildren(); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java old mode 100644 new mode 100755 index cdf9df74..d49ca1f6 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java @@ -26,7 +26,7 @@ import io.github.palexdev.materialfx.controls.MFXScrollPane; import io.github.palexdev.materialfx.controls.MFXTextField; import io.github.palexdev.materialfx.controls.cell.MFXDateCell; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.*; import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; @@ -489,7 +489,7 @@ public class MFXDatePickerContent extends VBox { private void buildButtons() { MFXFontIcon chevronDown = new MFXFontIcon("mfx-chevron-down", 13); yearsButton = new MFXIconWrapper(chevronDown, 20).rippleGeneratorBehavior(event -> - new RipplePosition(yearsButton.getWidth() / 2, yearsButton.getHeight() / 2) + new PositionBean(yearsButton.getWidth() / 2, yearsButton.getHeight() / 2) ); yearsButton.getStyleClass().add("years-button"); NodeUtils.makeRegionCircular(yearsButton); @@ -501,7 +501,7 @@ public class MFXDatePickerContent extends VBox { MFXFontIcon chevronLeft = new MFXFontIcon("mfx-chevron-left", 13); monthBackButton = new MFXIconWrapper(chevronLeft, 20).rippleGeneratorBehavior(event -> - new RipplePosition(monthBackButton.getWidth() / 2, monthBackButton.getHeight() / 2) + new PositionBean(monthBackButton.getWidth() / 2, monthBackButton.getHeight() / 2) ); monthBackButton.getStyleClass().add("month-back-button"); NodeUtils.makeRegionCircular(monthBackButton); @@ -509,7 +509,7 @@ public class MFXDatePickerContent extends VBox { MFXFontIcon chevronRight = new MFXFontIcon("mfx-chevron-right", 13); monthForwardButton = new MFXIconWrapper(chevronRight, 20).rippleGeneratorBehavior(event -> - new RipplePosition(monthForwardButton.getWidth() / 2, monthForwardButton.getHeight() / 2) + new PositionBean(monthForwardButton.getWidth() / 2, monthForwardButton.getHeight() / 2) ); monthForwardButton.getStyleClass().add("month-forward-button"); NodeUtils.makeRegionCircular(monthForwardButton); @@ -517,7 +517,7 @@ public class MFXDatePickerContent extends VBox { MFXFontIcon calendar = new MFXFontIcon("mfx-calendar-semi-black", 17); inputButton = new MFXIconWrapper(calendar, 35).rippleGeneratorBehavior(event -> - new RipplePosition(inputButton.getWidth() / 2, inputButton.getHeight() / 2) + new PositionBean(inputButton.getWidth() / 2, inputButton.getHeight() / 2) ); inputButton.getStyleClass().add("change-input-button"); Tooltip tooltip = new Tooltip("Switches between mouse input and keyboard input"); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java old mode 100644 new mode 100755 index ce48dc8d..7660e756 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java @@ -21,10 +21,10 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.beans.MFXSnapshotWrapper; import io.github.palexdev.materialfx.controls.*; import io.github.palexdev.materialfx.controls.cell.MFXListCell; -import io.github.palexdev.materialfx.controls.enums.Styles; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.enums.Styles; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel; import io.github.palexdev.materialfx.utils.AnimationUtils; @@ -379,7 +379,7 @@ public class MFXFilterComboBoxSkin extends SkinBase> { */ private void iconBehavior() { icon.rippleGeneratorBehavior(event -> - new RipplePosition(icon.getWidth() / 2, icon.getHeight() / 2) + new PositionBean(icon.getWidth() / 2, icon.getHeight() / 2) ); MFXCircleRippleGenerator rg = icon.getRippleGenerator(); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterPaneSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterPaneSkin.java new file mode 100755 index 00000000..d9b9bf02 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterPaneSkin.java @@ -0,0 +1,342 @@ +package io.github.palexdev.materialfx.skins; + +import io.github.palexdev.materialfx.controls.*; +import io.github.palexdev.materialfx.factories.InsetsFactory; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.effects.ripple.RippleClipType; +import io.github.palexdev.materialfx.filter.BooleanFilter; +import io.github.palexdev.materialfx.enums.ChainMode; +import io.github.palexdev.materialfx.filter.EnumFilter; +import io.github.palexdev.materialfx.filter.base.AbstractFilter; +import io.github.palexdev.materialfx.filter.base.NumberFilter; +import io.github.palexdev.materialfx.beans.BiPredicateBean; +import io.github.palexdev.materialfx.beans.FilterBean; +import io.github.palexdev.materialfx.font.MFXFontIcon; +import io.github.palexdev.materialfx.utils.NodeUtils; +import javafx.beans.InvalidationListener; +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.SkinBase; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.text.Text; +import javafx.stage.Modality; + +/** + * This is the skin associated with every {@link MFXFilterPane}. + *

+ * The control is made of four parts: + *

- The header which contains a label, and two icons to confirm the filter or reset the filter pane + *

- The filter builder which contains the necessary controls to produce active filters + *

- A label which acts as a separator + *

- A {@link FlowPane} to show the currently built active filters + */ +public class MFXFilterPaneSkin extends SkinBase> { + //================================================================================ + // Properties + //================================================================================ + private final VBox container; + private final Region filterBuilder; + private final FlowPane activeFiltersPane; + + private final MFXExceptionDialog exceptionDialog = new MFXExceptionDialog(); + private final MFXStageDialog errorDialog = new MFXStageDialog(exceptionDialog); + private final StringProperty query = new SimpleStringProperty(); + + //================================================================================ + // Constructors + //================================================================================ + public MFXFilterPaneSkin(MFXFilterPane filterPane) { + super(filterPane); + + // TODO review exception dialog + exceptionDialog.setPrefSize(600, 500); + errorDialog.setModality(Modality.WINDOW_MODAL); + + Region header = buildHeader(); + + Label filtersLabel = new Label("Active filters"); + filtersLabel.getStyleClass().add("header-label"); + VBox.setMargin(filtersLabel, InsetsFactory.top(15)); + + filterBuilder = buildFilterBuilder(); + activeFiltersPane = buildActiveFilters(); + + container = new VBox(10, header, filterBuilder, filtersLabel, activeFiltersPane); + getChildren().setAll(container); + addListeners(); + } + + //================================================================================ + // Methods + //================================================================================ + private void addListeners() { + MFXFilterPane filterPane = getSkinnable(); + filterPane.getActiveFilters().addListener((InvalidationListener) invalidated -> updateFilters()); + } + + /** + * For each filter in the {@link MFXFilterPane#getActiveFilters()} list builds + * a node that represents the filter. Filters are spaced by another node + * which represents how to filters are chained, by clicking on this icon the + * chain mode can be changed., + * + * @see #buildFilter(FilterBean) + * @see #buildAndOrIcon(FilterBean) + */ + protected void updateFilters() { + MFXFilterPane filterPane = getSkinnable(); + ObservableList> filters = filterPane.getActiveFilters(); + ObservableList children = FXCollections.observableArrayList(); + + for (int i = 0; i < filters.size(); i++) { + int previous = i - 1; + FilterBean filter; + if (previous >= 0) { + filter = filters.get(previous); + children.add(buildAndOrIcon(filter)); + } + + filter = filters.get(i); + children.add(buildFilter(filter)); + } + + activeFiltersPane.getChildren().setAll(children); + } + + /** + * Builds a node that represents the given {@link FilterBean}. + *

+ * It is composed by: + *

- A label that shows the object's data type/name + *

- A label that shows which function is used to compare the input with the object's field value + *

- A label that shows which is the input + *

- An icon to remove the filter + */ + protected Region buildFilter(FilterBean filter) { + Label filterLabel = new Label(filter.getFilterName()); + Label functionLabel = new Label(filter.getPredicateName()); + Label queryLabel = new Label(filter.getQuery()); + functionLabel.getStyleClass().add("function-text"); + MFXFontIcon remove = new MFXFontIcon("mfx-x-alt", 12); + remove.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> getSkinnable().getActiveFilters().remove(filter)); + HBox.setMargin(remove, InsetsFactory.top(2)); + + HBox container = new HBox(15, filterLabel, functionLabel, queryLabel, remove); + container.getStyleClass().add("active-filter"); + container.setAlignment(Pos.CENTER); + return container; + } + + /** + * Builds an icon that represents how two {@link FilterBean}s should be chained. + * Clicking on the icon switches the chain mode. + */ + protected Node buildAndOrIcon(FilterBean filter) { + Text modeText = new Text(filter.getMode().text()); + modeText.getStyleClass().add("and-or-text"); + modeText.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> { + if (filter.getMode() == ChainMode.AND) { + filter.setMode(ChainMode.OR); + modeText.setText(ChainMode.OR.text()); + } else { + filter.setMode(ChainMode.AND); + modeText.setText(ChainMode.AND.text()); + } + }); + return modeText; + } + + /** + * Builds the header. + */ + protected Region buildHeader() { + MFXFilterPane filterPane = getSkinnable(); + + Label headerLabel = new Label(); + headerLabel.getStyleClass().add("header-label"); + headerLabel.textProperty().bind(filterPane.headerTextProperty()); + headerLabel.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(headerLabel, Priority.ALWAYS); + + MFXIconWrapper filter = new MFXIconWrapper(new MFXFontIcon("mfx-variant7-mark", 16), 28).defaultRippleGeneratorBehavior(); + MFXIconWrapper reset = new MFXIconWrapper(new MFXFontIcon("mfx-undo", 16), 28).defaultRippleGeneratorBehavior(); + + filter.setId("filterIcon"); + reset.setId("resetIcon"); + + NodeUtils.makeRegionCircular(filter); + NodeUtils.makeRegionCircular(reset); + + filter.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> filterPane.getOnFilter().handle(event)); // TODO action + reset.addEventHandler(MouseEvent.MOUSE_CLICKED, this::reset); + + HBox box = new HBox(5, headerLabel, filter, reset); + box.setAlignment(Pos.CENTER_LEFT); + box.getStyleClass().add("header"); + return box; + } + + /** + * Builds the filter builder region. + */ + protected Region buildFilterBuilder() { + MFXFilterPane filterPane = getSkinnable(); + + ListProperty> predicates = new SimpleListProperty<>(FXCollections.observableArrayList()); + + MFXTextField searchField = new MFXTextField(); + searchField.setPromptText("Type in your filter value..."); + searchField.textProperty().bindBidirectional(query); + + MFXComboBox enumsCombo = new MFXComboBox<>(); + enumsCombo.selectedValueProperty().addListener((observable, oldValue, newValue) -> setQuery(newValue.toString())); + enumsCombo.setManaged(false); + enumsCombo.setVisible(false); + + MFXComboBox booleansCombo = new MFXComboBox<>(FXCollections.observableArrayList(true, false)); + booleansCombo.selectedValueProperty().addListener((observable, oldValue, newValue) -> setQuery(newValue.toString())); + booleansCombo.setManaged(false); + booleansCombo.setVisible(false); + + MFXComboBox> filterCombo = new MFXComboBox<>(filterPane.getFilters()); + filterCombo.getSelectionModel().selectFirst(); + filterCombo.getStyleClass().add("filter-combo"); + filterCombo.selectedValueProperty().addListener((observable, oldValue, newValue) -> { + setQuery(""); + predicates.setAll(newValue.getPredicates()); + + if (newValue instanceof EnumFilter) { + EnumFilter enumFilter = (EnumFilter) filterCombo.getSelectedValue(); + enumsCombo.setItems(FXCollections.observableArrayList(enumFilter.getEnumType().getEnumConstants())); + enumsCombo.setVisible(true); + booleansCombo.setVisible(false); + searchField.setVisible(false); + } else if (newValue instanceof BooleanFilter) { + booleansCombo.setVisible(true); + enumsCombo.setVisible(false); + searchField.setVisible(false); + } else { + enumsCombo.setVisible(false); + booleansCombo.setVisible(false); + searchField.setVisible(true); + } + }); + + // TODO FIX COMBO BOX ICON EMPTY LIST + // TODO FIX SELECTION + MFXComboBox> predicatesCombo = new MFXComboBox<>(predicates); + predicatesCombo.getStyleClass().add("predicates-combo"); + + MFXButton addButton = new MFXButton("Add filter"); + addButton.setOnAction(event -> { + if (filterCombo.getSelectionModel().getSelectedItem() != null + && predicatesCombo.getSelectionModel().getSelectedItem() != null + && !searchField.getText().isEmpty() + ) { + AbstractFilter selected = filterCombo.getSelectedValue(); + selected.setSelectedPredicateIndex(predicatesCombo.getSelectionModel().getSelectedIndex()); + FilterBean predicate = selected.toFilterBean(getQuery()); + + if (queryValidation(selected)) { + filterPane.getActiveFilters().add(predicate); + }; + } + }); + addButton.getRippleGenerator().setClipSupplier(() -> new RippleClipTypeFactory(RippleClipType.ROUNDED_RECTANGLE).setArcs(30).build(addButton)); + + HBox container = new HBox(10, filterCombo, predicatesCombo, searchField, enumsCombo, booleansCombo, addButton) { + @Override + protected void layoutChildren() { + super.layoutChildren(); + + double w = searchField.getBoundsInParent().getWidth(); + double h = searchField.getBoundsInParent().getHeight(); + double x = searchField.getBoundsInParent().getMinX(); + double y = searchField.getBoundsInParent().getMinY(); + enumsCombo.resizeRelocate(x, y, w, h); + booleansCombo.resizeRelocate(x, y, w, h); + } + }; + container.setAlignment(Pos.CENTER_LEFT); + return container; + } + + /** + * Builds tha active filters flow pane. + */ + protected FlowPane buildActiveFilters() { + MFXFilterPane filterPane = getSkinnable(); + FlowPane flowPane = new FlowPane(Orientation.HORIZONTAL, 10, 10); + flowPane.setAlignment(Pos.TOP_LEFT); + flowPane.prefWrapLengthProperty().bind(filterPane.widthProperty()); + flowPane.setPadding(InsetsFactory.bottom(7)); + return flowPane; + } + + /** + * Validates the given query. This is needed for Numbers since the input is a + * String and there's no guarantee that the typed text represents a number. + *

+ * Returns true if the input is valid otherwise shows an {@link MFXExceptionDialog} and returns false. + */ + protected boolean queryValidation(AbstractFilter filter) { + String type = ""; + String name = ""; + + try { + if (filter instanceof NumberFilter) { + NumberFilter numberFilter = (NumberFilter) filter; + type = "Number"; + name = numberFilter.name(); + Number parsed = numberFilter.getValue(getQuery()); + } + return true; + } catch (Exception ex) { + String title = "Attempted to parse " + name + " of type: " + type; + exceptionDialog.setTitle(title); + exceptionDialog.setException(ex); + errorDialog.show(); + return false; + } + } + + /** + * Resets the filter pane. + */ + protected void reset(MouseEvent event) { + MFXFilterPane filterPane = getSkinnable(); + setQuery(""); + filterPane.getActiveFilters().clear(); + filterPane.getOnReset().handle(event); + } + + public String getQuery() { + return query.get(); + } + + public void setQuery(String query) { + this.query.set(query); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().prefWidth(-1); + } + + @Override + protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().prefHeight(-1); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXLabelSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXLabelSkin.java old mode 100644 new mode 100755 index 7a8b8f35..168a4b07 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXLabelSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXLabelSkin.java @@ -20,8 +20,8 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.MFXLabel; import io.github.palexdev.materialfx.controls.MFXTextField; -import io.github.palexdev.materialfx.controls.enums.Styles; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.enums.Styles; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.utils.LabelUtils; import javafx.animation.ScaleTransition; import javafx.beans.binding.Bindings; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXListViewSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXListViewSkin.java old mode 100644 new mode 100755 index ba92f823..1f2b6fdc --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXListViewSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXListViewSkin.java @@ -19,7 +19,7 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.base.AbstractMFXListView; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.effects.MFXDepthManager; import io.github.palexdev.materialfx.utils.AnimationUtils; import io.github.palexdev.virtualizedfx.flow.simple.SimpleVirtualFlow; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXNotificationCenterSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXNotificationCenterSkin.java new file mode 100755 index 00000000..28a0d444 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXNotificationCenterSkin.java @@ -0,0 +1,213 @@ +package io.github.palexdev.materialfx.skins; + +import io.github.palexdev.materialfx.controls.*; +import io.github.palexdev.materialfx.controls.MFXPopup.MFXPopupEvent; +import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell; +import io.github.palexdev.materialfx.factories.InsetsFactory; +import io.github.palexdev.materialfx.enums.NotificationCounterStyle; +import io.github.palexdev.materialfx.enums.NotificationState; +import io.github.palexdev.materialfx.font.MFXFontIcon; +import io.github.palexdev.materialfx.notifications.base.INotification; +import io.github.palexdev.materialfx.utils.NodeUtils; +import io.github.palexdev.virtualizedfx.flow.simple.SimpleVirtualFlow; +import javafx.beans.binding.Bindings; +import javafx.css.Styleable; +import javafx.geometry.HPos; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.SkinBase; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.text.Text; + +/** + * This is the skin associated with every {@link MFXNotificationCenter}. + *

+ * It is composed by an icon, which is a bell, that opens a {@link MFXPopup} to show the notification center. + *

+ * As of now there's a bug that I can't fix because of JavaFX internal API, if the popup is open and the + * icon has been clicked again the popup won't open but will stay open. I'll see what I can do... // TODO fix + */ +public class MFXNotificationCenterSkin extends SkinBase { + //================================================================================ + // Properties + //================================================================================ + private final MFXIconWrapper bellWrapped; + private final NotificationsCounter counter; + private final MFXPopup popup; + + //================================================================================ + // Constructors + //================================================================================ + public MFXNotificationCenterSkin(MFXNotificationCenter notificationCenter, SimpleVirtualFlow virtualFlow) { + super(notificationCenter); + + MFXFontIcon bell = new MFXFontIcon("mfx-bell-alt", 36); + bellWrapped = new MFXIconWrapper(bell, 56); + bellWrapped.getStyleClass().add("notifications-icon"); + + counter = new NotificationsCounter(); + counter.setManaged(false); + + MFXLabel headerLabel = new MFXLabel(); + headerLabel.textProperty().bind(notificationCenter.headerTextPropertyProperty()); + headerLabel.setAlignment(Pos.CENTER_LEFT); + headerLabel.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(headerLabel, Priority.ALWAYS); + + MFXToggleButton dndToggle = new MFXToggleButton("Do not disturb"); + dndToggle.setContentDisplay(ContentDisplay.RIGHT); + dndToggle.setGraphicTextGap(15); + notificationCenter.doNotDisturbProperty().bindBidirectional(dndToggle.selectedProperty()); + + HBox header = new HBox(headerLabel, dndToggle); + header.getStyleClass().add("header"); + header.setAlignment(Pos.CENTER_LEFT); + + MFXIconWrapper select = new MFXIconWrapper(new MFXFontIcon("mfx-variant13-mark", 24), 36).defaultRippleGeneratorBehavior(); + MFXIconWrapper markAsRead = new MFXIconWrapper(new MFXFontIcon("mfx-eye", 20), 36).defaultRippleGeneratorBehavior(); + MFXIconWrapper markAsUnread = new MFXIconWrapper(new MFXFontIcon("mfx-eye-slash", 20), 36).defaultRippleGeneratorBehavior(); + MFXIconWrapper dismiss = new MFXIconWrapper(new MFXFontIcon("mfx-delete", 20), 36).defaultRippleGeneratorBehavior(); + + select.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> notificationCenter.setSelectionMode(!notificationCenter.isSelectionMode())); + markAsRead.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> notificationCenter.markSelectedNotificationsAs(NotificationState.READ)); + markAsUnread.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> notificationCenter.markSelectedNotificationsAs(NotificationState.UNREAD)); + dismiss.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> notificationCenter.dismissSelected()); + + NodeUtils.makeRegionCircular(select); + NodeUtils.makeRegionCircular(markAsRead); + NodeUtils.makeRegionCircular(markAsUnread); + NodeUtils.makeRegionCircular(dismiss); + + HBox actions = new HBox(50, select, markAsRead, markAsUnread, dismiss); + actions.getStyleClass().add("actions"); + actions.setAlignment(Pos.CENTER); + + BorderPane borderPane = new BorderPane(); + borderPane.setTop(header); + borderPane.setCenter(virtualFlow); + borderPane.setBottom(actions); + borderPane.getStyleClass().add("notifications-container"); + VBox.setVgrow(borderPane, Priority.ALWAYS); + + VBox popupContent = new VBox(borderPane); + popupContent.setAlignment(Pos.TOP_CENTER); + popupContent.paddingProperty().bind(Bindings.createObjectBinding( + () -> InsetsFactory.top(notificationCenter.getPopupSpacing()), + notificationCenter.popupSpacingProperty() + )); + popupContent.setMinHeight(Region.USE_PREF_SIZE); + popupContent.setMaxHeight(Region.USE_PREF_SIZE); + popupContent.prefWidthProperty().bind(notificationCenter.popupWidthProperty()); + popupContent.prefHeightProperty().bind(notificationCenter.popupHeightProperty()); + + popup = new MFXPopup(popupContent) { + @Override + public Styleable getStyleableParent() { + return MFXNotificationCenterSkin.this.getSkinnable(); + } + }; + popup.setAnimated(false); + popup.setConsumeAutoHidingEvents(false); // TODO can't fix as of now, JavaFX 17.0.1 + popup.getStyleClass().addAll(getSkinnable().getStyleClass()); + + getChildren().setAll(bellWrapped, counter); + addListeners(); + } + + //================================================================================ + // Methods + //================================================================================ + private void addListeners() { + MFXNotificationCenter notificationCenter = getSkinnable(); + + popup.showingProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue && notificationCenter.isShowing()) notificationCenter.setShowing(false); + }); + bellWrapped.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { + bellWrapped.requestFocus(); + notificationCenter.getOnIconClicked().handle(event); + }); + notificationCenter.showingProperty().addListener((observable, oldValue, newValue) -> managePopup(newValue)); + notificationCenter.addEventFilter(MFXPopupEvent.REPOSITION_EVENT, event -> popup.reposition()); + notificationCenter.popupHoverProperty().bind(popup.hoverProperty()); + } + + /** + * Shows/Hides the popup. + */ + protected void managePopup(boolean showing) { + if (!showing) { + popup.hide(); + return; + } + popup.show(bellWrapped, HPos.CENTER, VPos.BOTTOM); + } + + //================================================================================ + // Overridden Methods + //================================================================================ + @Override + protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return bellWrapped.prefWidth(-1); + } + + @Override + protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return bellWrapped.prefHeight(-1); + } + + @Override + protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { + super.layoutChildren(contentX, contentY, contentWidth, contentHeight); + + // That one time I really need math, I don't understand how it works, but it works :) + double bellCenterX = bellWrapped.getBoundsInParent().getCenterX(); + double bellCenterY = bellWrapped.getBoundsInParent().getCenterY(); + double radius = bellWrapped.getSize() / 2; + double angle = 45; + double angleToRadians = Math.PI / 180; + double size = counter.getSize(); + double counterX = bellCenterX + radius * Math.cos(angle * angleToRadians) - (size / 2); + double counterY = bellCenterY - radius * Math.sin(angle * angleToRadians) - (size / 2); + counter.resizeRelocate(counterX, counterY, size, size); + } + + /** + * To keep things clean and organized the {@link MFXNotificationCenter}'s counter has been + * written to a separate class. + */ + private class NotificationsCounter extends MFXIconWrapper { + + public NotificationsCounter() { + MFXNotificationCenter notificationCenter = getSkinnable(); + + Text counterText = new Text(); + counterText.getStyleClass().add("text"); + + counterText.textProperty().addListener(invalidated -> { + double padding = notificationCenter.getCounterStyle() == NotificationCounterStyle.DOT ? 3 : 7; + double textW = counterText.prefWidth(-1); + double textH = counterText.prefHeight(-1); + double size = textW > textH ? snapSizeX(textW + padding) : snapSizeY(textH + padding); + setSize(size); + notificationCenter.requestLayout(); + requestLayout(); + }); + counterText.visibleProperty().bind(Bindings.createBooleanBinding( + () -> notificationCenter.getCounterStyle() == NotificationCounterStyle.NUMBER, + notificationCenter.counterStyleProperty() + )); + counterText.textProperty().bind(notificationCenter.unreadCountProperty().asString()); + counterText.textProperty().bind(Bindings.createStringBinding( + () -> notificationCenter.getCounterStyle() == NotificationCounterStyle.NUMBER ? Long.toString(notificationCenter.getUnreadCount()) : "", + notificationCenter.unreadCountProperty(), notificationCenter.counterStyleProperty() + )); + + getStyleClass().add("counter"); + visibleProperty().bind(notificationCenter.unreadCountProperty().greaterThan(0)); + setIcon(counterText); + } + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPasswordFieldSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPasswordFieldSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPopupSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPopupSkin.java new file mode 100755 index 00000000..44bd5ffe --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPopupSkin.java @@ -0,0 +1,125 @@ +package io.github.palexdev.materialfx.skins; + +import io.github.palexdev.materialfx.beans.PopupPositionBean; +import io.github.palexdev.materialfx.controls.MFXPopup; +import javafx.animation.Animation; +import javafx.geometry.HPos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.Skin; +import javafx.scene.layout.StackPane; +import javafx.scene.transform.Scale; + +/** + * This is the skin associated with every {@link MFXPopup}. + *

+ * The popup's content is shown in a container (a {@link StackPane}). + */ +public class MFXPopupSkin implements Skin { + //================================================================================ + // Properties + //================================================================================ + private MFXPopup popup; + private final StackPane container; + private final Scale scale; + + private Animation animation; + + //================================================================================ + // Constructors + //================================================================================ + public MFXPopupSkin(MFXPopup popup) { + this.popup = popup; + + container = new StackPane(popup.getContent()); + scale = new Scale(0.1, 0.1, 0, 0); + container.getTransforms().add(scale); + init(); + + if (popup.isAnimated()) { + animation = popup.getAnimationProvider().apply(container, scale); + animation.play(); + } else { + scale.setX(1); + scale.setY(1); + container.setOpacity(1); + } + } + + //================================================================================ + // Methods + //================================================================================ + + /** + * Positions the popup. + */ + protected void init() { + container.setOpacity(0.0); + + PopupPositionBean position = popup.getPosition(); + if (position == null) return; + + double containerW = container.prefWidth(-1); + double containerH = container.prefHeight(-1); + HPos hPos = position.getHPos(); + VPos vPos = position.getVPos(); + double xOffset = position.getXOffset(); + double yOffset = position.getYOffset(); + + double tx = 0; + double ty = 0; + double px = hPos == HPos.RIGHT ? xOffset : containerW + xOffset; + double py = vPos == VPos.BOTTOM ? yOffset : containerH + yOffset; + + switch (hPos) { + case CENTER: { + tx = -(Math.abs(containerW - position.getOwnerWidth()) / 2) + xOffset; + break; + } + case LEFT: { + tx = -containerW + xOffset; + break; + } + case RIGHT: { + tx = xOffset; + break; + } + } + switch (vPos) { + case BOTTOM: { + ty = yOffset; + break; + } + case CENTER: { + ty = -(Math.abs(containerH - position.getOwnerHeight()) / 2) + yOffset; + break; + } + case TOP: { + ty = -containerH + yOffset; + break; + } + } + + scale.setPivotX(px); + scale.setPivotY(py); + container.setTranslateX(tx); + container.setTranslateY(ty); + } + + @Override + public MFXPopup getSkinnable() { + return popup; + } + + @Override + public Node getNode() { + return container; + } + + @Override + public void dispose() { + animation.stop(); + animation = null; + popup = null; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXProgressBarSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXProgressBarSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXProgressSpinnerSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXProgressSpinnerSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRadioButtonSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRadioButtonSkin.java old mode 100644 new mode 100755 index 640bf20d..341b1454 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRadioButtonSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRadioButtonSkin.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.MFXRadioButton; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.utils.AnimationUtils; import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; import io.github.palexdev.materialfx.utils.ColorUtils; @@ -78,9 +78,9 @@ public class MFXRadioButtonSkin extends RadioButtonSkin { rippleGenerator.setAnimationSpeed(2); rippleGenerator.setClipSupplier(() -> null); rippleGenerator.setRipplePositionFunction(event -> { - RipplePosition position = new RipplePosition(); - position.setXPosition(dot.getBoundsInParent().getCenterX()); - position.setYPosition(dot.getBoundsInParent().getCenterY()); + PositionBean position = new PositionBean(); + position.setX(dot.getBoundsInParent().getCenterX()); + position.setY(dot.getBoundsInParent().getCenterY()); return position; }); rippleGenerator.setRippleRadius(radius); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRectangleToggleNodeSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRectangleToggleNodeSkin.java old mode 100644 new mode 100755 index 9d2cb1c4..ae426408 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRectangleToggleNodeSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXRectangleToggleNodeSkin.java @@ -21,7 +21,7 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.MFXLabel; import io.github.palexdev.materialfx.controls.MFXRectangleToggleNode; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.utils.LabelUtils; import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.event.Event; @@ -90,7 +90,7 @@ public class MFXRectangleToggleNodeSkin extends SkinBase rippleGenerator.setAnimateBackground(false); rippleGenerator.setClipSupplier(() -> toggleNode.getRippleClipTypeFactory().build(container)); - rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY())); + rippleGenerator.setRipplePositionFunction(event -> new PositionBean(event.getX(), event.getY())); rippleGenerator.rippleRadiusProperty().bind(toggleNode.widthProperty().divide(2.0)); } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXScrollPaneSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXScrollPaneSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXSliderSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXSliderSkin.java old mode 100644 new mode 100755 index 2c8d9de2..2d88f7d5 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXSliderSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXSliderSkin.java @@ -20,9 +20,9 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.beans.NumberRange; import io.github.palexdev.materialfx.controls.MFXSlider; -import io.github.palexdev.materialfx.controls.enums.SliderEnums.SliderMode; -import io.github.palexdev.materialfx.controls.enums.SliderEnums.SliderPopupSide; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.enums.SliderEnums.SliderMode; +import io.github.palexdev.materialfx.enums.SliderEnums.SliderPopupSide; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.AnimationUtils; import io.github.palexdev.materialfx.utils.AnimationUtils.PauseBuilder; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperSkin.java old mode 100644 new mode 100755 index 035757b0..29bdabd9 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperSkin.java @@ -23,8 +23,8 @@ import io.github.palexdev.materialfx.controls.MFXStepper; import io.github.palexdev.materialfx.controls.MFXStepper.MFXStepperEvent; import io.github.palexdev.materialfx.controls.MFXStepperToggle; import io.github.palexdev.materialfx.controls.MFXStepperToggle.MFXStepperToggleEvent; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; -import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.RippleClipTypeFactory; import io.github.palexdev.materialfx.effects.ripple.RippleClipType; import io.github.palexdev.materialfx.utils.AnimationUtils; import io.github.palexdev.materialfx.utils.NodeUtils; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperToggleSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperToggleSkin.java old mode 100644 new mode 100755 index 09559de0..634b17ff --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperToggleSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperToggleSkin.java @@ -22,9 +22,9 @@ import io.github.palexdev.materialfx.controls.MFXIconWrapper; import io.github.palexdev.materialfx.controls.MFXLabel; import io.github.palexdev.materialfx.controls.MFXStageDialog; import io.github.palexdev.materialfx.controls.MFXStepperToggle; -import io.github.palexdev.materialfx.controls.enums.DialogType; -import io.github.palexdev.materialfx.controls.enums.StepperToggleState; -import io.github.palexdev.materialfx.controls.enums.TextPosition; +import io.github.palexdev.materialfx.enums.DialogType; +import io.github.palexdev.materialfx.enums.StepperToggleState; +import io.github.palexdev.materialfx.enums.TextPosition; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.DialogUtils; import io.github.palexdev.materialfx.utils.LabelUtils; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableColumnSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableColumnSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableRowCellSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableRowCellSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java old mode 100644 new mode 100755 index 5eb4a4a2..29f8525b --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java @@ -22,11 +22,9 @@ import io.github.palexdev.materialfx.controls.*; import io.github.palexdev.materialfx.controls.MFXTableView.MFXTableViewEvent; import io.github.palexdev.materialfx.controls.cell.MFXTableColumn; import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell; -import io.github.palexdev.materialfx.controls.enums.SortState; -import io.github.palexdev.materialfx.controls.enums.Styles; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; -import io.github.palexdev.materialfx.filter.IFilterable; -import io.github.palexdev.materialfx.filter.MFXFilterDialog; +import io.github.palexdev.materialfx.enums.SortState; +import io.github.palexdev.materialfx.enums.Styles; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.selection.TableSelectionModel; import io.github.palexdev.materialfx.selection.base.ITableSelectionModel; @@ -61,7 +59,6 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; -import javafx.stage.Modality; import javafx.util.Duration; import javafx.util.Pair; @@ -70,6 +67,7 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +// TODO review /** * This is the implementation of the {@code Skin} associated with every {@link MFXTableView}. *

@@ -125,8 +123,8 @@ public class MFXTableViewSkin extends SkinBase> { private SortedList sortedList; private SortedList filteredList; - private final MFXFilterDialog filterDialog; - private final MFXStageDialog filterStageDialog; + //private final MFXFilterDialog filterDialog; + //private final MFXStageDialog filterStageDialog; private final BooleanProperty tableFiltered = new SimpleBooleanProperty(false); //================================================================================ @@ -178,12 +176,12 @@ public class MFXTableViewSkin extends SkinBase> { pgcBox = buildPaginationControls(); - filterDialog = new MFXFilterDialog<>(); +/* filterDialog = new MFXFilterDialog<>(); filterDialog.getFilterButton().setOnAction(event -> filterTable()); filterStageDialog = new MFXStageDialog(filterDialog); filterStageDialog.setOwner(tableView.getScene() != null ? tableView.getScene().getWindow() : null); filterStageDialog.setCenterInOwner(true); - filterStageDialog.setModality(Modality.WINDOW_MODAL); + filterStageDialog.setModality(Modality.WINDOW_MODAL);*/ container.getChildren().setAll(header, columnsBox, rowsBox, pgcBox); getChildren().setAll(container); @@ -233,7 +231,7 @@ public class MFXTableViewSkin extends SkinBase> { } }); - NodeUtils.waitForScene(tableView, () -> filterStageDialog.setOwner(tableView.getScene().getWindow()), true, false); + //NodeUtils.waitForScene(tableView, () -> filterStageDialog.setOwner(tableView.getScene().getWindow()), true, false); tableView.getItems().addListener((InvalidationListener) listInvalidated -> reset(false)); tableView.itemsProperty().addListener(propertyInvalidated -> { @@ -428,7 +426,7 @@ public class MFXTableViewSkin extends SkinBase> { ); filterIcon.disableProperty().bind(tableFiltered.or(listEmpty)); clearFilterIcon.disableProperty().bind(tableFiltered.not().or(listEmpty)); - filterIcon.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> filterStageDialog.show()); + //filterIcon.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> filterStageDialog.show()); clearFilterIcon.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { if (tableFiltered.get()) { tableFiltered.set(false); @@ -600,12 +598,12 @@ public class MFXTableViewSkin extends SkinBase> { * N.B: if the toString method is not overridden or does not contain any useful information for filtering it won't work. */ protected void filterTable() { - ObservableList list = filterDialog.filter(sortedList); +/* ObservableList list = filterDialog.filter(sortedList); filteredList = new SortedList<>(list); filteredList.comparatorProperty().bind(sortedList.comparatorProperty()); tableFiltered.set(true); buildRows(); - filterStageDialog.close(); + filterStageDialog.close();*/ } /** @@ -661,7 +659,7 @@ public class MFXTableViewSkin extends SkinBase> { .addSeparator() .addMenuItem(lockSize) .addMenuItem(unlockSize) - .install(); + .installAndGet(); } /** diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTextFieldSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTextFieldSkin.java old mode 100644 new mode 100755 index 75ef9b63..bd90a7fd --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTextFieldSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTextFieldSkin.java @@ -20,7 +20,7 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.MFXIconWrapper; import io.github.palexdev.materialfx.controls.MFXTextField; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.LabelUtils; import io.github.palexdev.materialfx.validation.MFXDialogValidator; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXToggleButtonSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXToggleButtonSkin.java old mode 100644 new mode 100755 index 2ee7d2c2..228c7c1f --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXToggleButtonSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXToggleButtonSkin.java @@ -22,7 +22,7 @@ import io.github.palexdev.materialfx.controls.MFXToggleButton; import io.github.palexdev.materialfx.effects.DepthLevel; import io.github.palexdev.materialfx.effects.MFXDepthManager; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.effects.ripple.RipplePosition; +import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.utils.AnimationUtils; import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; import io.github.palexdev.materialfx.utils.NodeUtils; @@ -144,12 +144,12 @@ public class MFXToggleButtonSkin extends ToggleButtonSkin { rippleGenerator.setClipSupplier(() -> null); rippleGenerator.setRippleColor(toggleButton.isSelected() ? toggleButton.getUnToggleLineColor() : toggleButton.getToggleLineColor()); rippleGenerator.setRipplePositionFunction(mouseEvent -> { - RipplePosition position = new RipplePosition(); - position.xPositionProperty().bind(Bindings.createDoubleBinding( + PositionBean position = new PositionBean(); + position.xProperty().bind(Bindings.createDoubleBinding( () -> circle.getBoundsInParent().getCenterX(), circle.boundsInParentProperty() )); - position.yPositionProperty().bind(Bindings.createDoubleBinding( + position.yProperty().bind(Bindings.createDoubleBinding( () -> circle.localToParent(circle.getLayoutBounds()).getCenterY(), circle.layoutBoundsProperty() )); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTreeItemSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTreeItemSkin.java old mode 100644 new mode 100755 index 8bbab588..51f063bc --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTreeItemSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTreeItemSkin.java @@ -21,7 +21,7 @@ package io.github.palexdev.materialfx.skins; import io.github.palexdev.materialfx.controls.MFXTreeItem; import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeCell; import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.selection.TreeSelectionModel; import io.github.palexdev.materialfx.utils.AnimationUtils; import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyComboBoxSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyComboBoxSkin.java old mode 100644 new mode 100755 index 16bdcb25..e9d0e4ac --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyComboBoxSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyComboBoxSkin.java @@ -19,7 +19,7 @@ package io.github.palexdev.materialfx.skins.legacy; import io.github.palexdev.materialfx.controls.MFXIconWrapper; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.controls.legacy.MFXLegacyComboBox; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.utils.LabelUtils; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyListViewSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyListViewSkin.java old mode 100644 new mode 100755 index a4204cb8..b3ac951a --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyListViewSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyListViewSkin.java @@ -18,7 +18,7 @@ package io.github.palexdev.materialfx.skins.legacy; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import io.github.palexdev.materialfx.controls.legacy.MFXLegacyListView; import io.github.palexdev.materialfx.effects.MFXDepthManager; import io.github.palexdev.materialfx.utils.AnimationUtils; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyTableViewSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/legacy/MFXLegacyTableViewSkin.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/AnimationUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/AnimationUtils.java old mode 100644 new mode 100755 index fb0c9c74..b2703b8c --- a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/AnimationUtils.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/AnimationUtils.java @@ -19,7 +19,8 @@ package io.github.palexdev.materialfx.utils; import io.github.palexdev.materialfx.beans.AnimationsData; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.effects.Interpolators; import javafx.animation.*; import javafx.animation.Animation.Status; import javafx.beans.binding.BooleanExpression; @@ -145,6 +146,10 @@ public class AnimationUtils { return animation.getStatus() == Status.PAUSED; } + public static boolean isStopped(Animation animation) { + return animation.getStatus() == Status.STOPPED; + } + //================================================================================ // Builders //================================================================================ @@ -705,6 +710,16 @@ public class AnimationUtils { // Methods //================================================================================ + public AnimationUtils.PauseBuilder setDelay(Duration duration) { + pauseTransition.setDelay(duration); + return this; + } + + public AnimationUtils.PauseBuilder setDelay(double millis) { + pauseTransition.setDelay(Duration.millis(millis)); + return this; + } + /** * Sets the pause transition duration. */ @@ -859,5 +874,21 @@ public class AnimationUtils { public static KeyFrame of(double millis, WritableValue writableValue, T endValue, Interpolator interpolator) { return of(Duration.millis(millis), writableValue, endValue, interpolator); } + + /** + * Returns a new KeyFrame with the given duration and builds a new KeyValue for it + * with the given writable property, endValue and interpolator. + */ + public static KeyFrame of(Duration duration, WritableValue writableValue, T endValue, Interpolators interpolator) { + return of(duration, new KeyValue(writableValue, endValue, interpolator.toInterpolator())); + } + + /** + * Calls {@link #of(Duration, WritableValue, Object, Interpolators)} by converting the given millis value + * with {@link Duration#millis(double)}. + */ + public static KeyFrame of(double millis, WritableValue writableValue, T endValue, Interpolators interpolator) { + return of(Duration.millis(millis), writableValue, endValue, interpolator.toInterpolator()); + } } } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/BindingUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/BindingUtils.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ColorUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ColorUtils.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/DialogUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/DialogUtils.java old mode 100644 new mode 100755 index ea49f38c..35d31635 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/DialogUtils.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/DialogUtils.java @@ -21,8 +21,8 @@ package io.github.palexdev.materialfx.utils; import io.github.palexdev.materialfx.controls.MFXDialog; import io.github.palexdev.materialfx.controls.MFXExceptionDialog; import io.github.palexdev.materialfx.controls.MFXStageDialog; -import io.github.palexdev.materialfx.controls.enums.DialogType; -import io.github.palexdev.materialfx.controls.factories.MFXDialogFactory; +import io.github.palexdev.materialfx.enums.DialogType; +import io.github.palexdev.materialfx.factories.MFXDialogFactory; import javafx.stage.Modality; import javafx.stage.Window; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/DragResizer.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/DragResizer.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/EnumStringConverter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/EnumStringConverter.java new file mode 100755 index 00000000..78558fb9 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/EnumStringConverter.java @@ -0,0 +1,42 @@ +package io.github.palexdev.materialfx.utils; + +import javafx.util.StringConverter; + +/** + * Implementation of {@link StringConverter} to work with a generic {@link Enum}. + *

+ * For this to work, it's necessary to specify the enumerator class, see {@link Enum#valueOf(Class, String)}. + */ +public class EnumStringConverter> extends StringConverter { + //================================================================================ + // Properties + //================================================================================ + private final Class type; + + //================================================================================ + // Constructors + //================================================================================ + public EnumStringConverter(Class type) { + this.type = type; + } + + //================================================================================ + // Overridden Methods + //================================================================================ + + /** + * Calls toString() on the given enumeration. + */ + @Override + public String toString(E e) { + return e.toString(); + } + + /** + * Uses {@link Enum#valueOf(Class, String)} to convert the given String to an enumeration. + */ + @Override + public E fromString(String string) { + return E.valueOf(type, string); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExceptionUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExceptionUtils.java old mode 100644 new mode 100755 index 6782d7d9..d9b156d4 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExceptionUtils.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExceptionUtils.java @@ -25,7 +25,6 @@ import java.io.StringWriter; * Little utils class to convert a throwable stack trace to a String. */ public class ExceptionUtils { - private static final StringWriter sw = new StringWriter(); private ExceptionUtils() {} @@ -34,6 +33,7 @@ public class ExceptionUtils { * by using a {@link StringWriter} and a {@link PrintWriter}. */ public static String getStackTraceString(Throwable ex) { + StringWriter sw = new StringWriter(); sw.flush(); ex.printStackTrace(new PrintWriter(sw)); return sw.toString(); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExecutionUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExecutionUtils.java old mode 100644 new mode 100755 index 9d53de6c..e1cc3046 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExecutionUtils.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ExecutionUtils.java @@ -19,6 +19,8 @@ package io.github.palexdev.materialfx.utils; import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.beans.binding.BooleanExpression; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -30,6 +32,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.Supplier; /** * Utils class to help with concurrency and callables. @@ -160,6 +163,38 @@ public class ExecutionUtils { }); } + /** + * Executes the given action when the given {@link Observable} changes. + *

+ * If executeNow is true the action is immediately executed. + *

+ * Adds a listener to the observable and executes the given action every time the observable changes and the + * execution condition is met or just once if the isOneShot parameter is true. + * + * @param observable the observable to listen to + * @param action the action to execute when the observable changes + * @param executeNow to specify if the given action should be immediately executed + * @param executionCondition to specify on what conditions the action should be executed + * @param isOneShot to specify if the added listener should be removed after the first time the observable changes + */ + public static void executeWhen(Observable observable, Runnable action, boolean executeNow, Supplier executionCondition, boolean isOneShot) { + if (executeNow) { + action.run(); + } + + observable.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if (executionCondition.get()) { + action.run(); + if (isOneShot) { + observable.removeListener(this); + } + } + } + }); + } + /** * Executes the given action if the given expression and the executeNow parameter are true. *

diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/FXCollectors.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/FXCollectors.java new file mode 100755 index 00000000..3cfc55c8 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/FXCollectors.java @@ -0,0 +1,32 @@ +package io.github.palexdev.materialfx.utils; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; + +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Convenience class that offers some methods useful on combination with Java {@link Stream} + * to collect to JavaFX's collections. + */ +public class FXCollectors { + + private FXCollectors() {} + + /** + * @return a collector that returns an {@link ObservableSet} + */ + public static Collector> toSet() { + return Collectors.collectingAndThen(Collectors.toSet(), FXCollections::observableSet); + } + + /** + * @return a collector that returns an {@link ObservableList} + */ + public static Collector> toList() { + return Collectors.collectingAndThen(Collectors.toList(), FXCollections::observableArrayList); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/LabelUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/LabelUtils.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ListChangeProcessor.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ListChangeProcessor.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/LoaderUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/LoaderUtils.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/NodeUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/NodeUtils.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/NumberUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/NumberUtils.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/PredicateUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/PredicateUtils.java new file mode 100755 index 00000000..3f05b28f --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/PredicateUtils.java @@ -0,0 +1,21 @@ +package io.github.palexdev.materialfx.utils; + +import io.github.palexdev.materialfx.enums.ChainMode; + +import java.util.function.Predicate; + +/** + * Convenience methods for predicates. + */ +public class PredicateUtils { + + private PredicateUtils() {} + + /** + * @return a new predicate that is the combination of the original predicate + * with the given one according to the specified {@link ChainMode}. + */ + public static Predicate chain(Predicate original, Predicate other, ChainMode mode) { + return mode == ChainMode.AND ? original.and(other) : original.or(other); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/RandomInstance.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/RandomInstance.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ScrollUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ScrollUtils.java old mode 100644 new mode 100755 index 331e080f..944e36da --- a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ScrollUtils.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ScrollUtils.java @@ -18,7 +18,7 @@ package io.github.palexdev.materialfx.utils; -import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory; +import io.github.palexdev.materialfx.factories.MFXAnimationFactory; import javafx.animation.Animation; import javafx.animation.Animation.Status; import javafx.animation.KeyFrame; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/StringUtils.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/StringUtils.java old mode 100644 new mode 100755 index 736f99c0..b4dd28d2 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/StringUtils.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/StringUtils.java @@ -167,6 +167,29 @@ public class StringUtils { return inputStringList.containsAll(wordsList); } + /** + * A useful method to convert a given elapsed time in seconds to a + * String. + *

+ *

- "Just now" if elapsed is less than 60 seconds + *

- minutes + " minutes ago" if the elapsed seconds is greater than 60 seconds + *

- hours + " minutes ago" if the elapsed minutes are greater than 60 minutes + *

- days + " days ago" if the elapsed hours are greater than 24 + */ + public static String timeToHumanReadable(long elapsedSeconds) { + if (elapsedSeconds < 60) { + return "Just now"; + } else { + long minutes = elapsedSeconds / 60; + if (minutes < 60) { + return minutes + " min ago"; + } else { + long hours = minutes / 60; + return hours < 24 ? hours + " hours ago" : hours / 24 + " days ago"; + } + } + } + /** * Generates a random alphabetic string of given length */ diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ToggleButtonsUtil.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ToggleButtonsUtil.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/TreeItemIterator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/TreeItemIterator.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/TreeItemStream.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/TreeItemStream.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/FunctionalStringConverter.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/FunctionalStringConverter.java new file mode 100755 index 00000000..81942c26 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/FunctionalStringConverter.java @@ -0,0 +1,13 @@ +package io.github.palexdev.materialfx.utils.others; + +/** + * A functional alternative to {@link javafx.util.StringConverter}. + */ +@FunctionalInterface +public interface FunctionalStringConverter { + T fromString(String s); + + default String toString(T t) { + throw new UnsupportedOperationException(); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/ReusableScheduledExecutor.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/ReusableScheduledExecutor.java new file mode 100755 index 00000000..bcaaf6f4 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/ReusableScheduledExecutor.java @@ -0,0 +1,70 @@ +package io.github.palexdev.materialfx.utils.others; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * This class wraps a {@link ScheduledExecutorService} to make it reusable by keeping + * a reference to the {@link ScheduledFuture}. + */ +public class ReusableScheduledExecutor { + //================================================================================ + // Properties + //================================================================================ + private final ScheduledExecutorService service; + private ScheduledFuture task; + + //================================================================================ + // Constructors + //================================================================================ + public ReusableScheduledExecutor(ScheduledExecutorService service) { + this.service = service; + } + + //================================================================================ + // Delegate Methods + //================================================================================ + + /** + * Cancels the task by calling {@link ScheduledFuture#cancel(boolean)} with false as argument. + */ + public void cancel() { + task.cancel(false); + } + + /** + * Cancels the task by calling {@link ScheduledFuture#cancel(boolean)} with true as argument. + */ + public void cancelNow() { + task.cancel(true); + } + + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + task = service.schedule(command, delay, unit); + return task; + } + + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + task = service.schedule(callable, delay, unit); + return (ScheduledFuture) task; + } + + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + task = service.scheduleAtFixedRate(command, initialDelay, period, unit); + return task; + } + + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + task = service.scheduleWithFixedDelay(command, initialDelay, delay, unit); + return task; + } + + //================================================================================ + // Getter/Setters + //================================================================================ + public ScheduledExecutorService getService() { + return service; + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/TriConsumer.java b/materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/TriConsumer.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/validation/MFXDialogValidator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/validation/MFXDialogValidator.java old mode 100644 new mode 100755 index d6f7e06f..a053ec20 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/validation/MFXDialogValidator.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/validation/MFXDialogValidator.java @@ -19,8 +19,8 @@ package io.github.palexdev.materialfx.validation; import io.github.palexdev.materialfx.controls.MFXStageDialog; -import io.github.palexdev.materialfx.controls.enums.DialogType; -import io.github.palexdev.materialfx.controls.factories.MFXDialogFactory; +import io.github.palexdev.materialfx.enums.DialogType; +import io.github.palexdev.materialfx.factories.MFXDialogFactory; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/validation/MFXPriorityValidator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/validation/MFXPriorityValidator.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/validation/base/AbstractMFXValidator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/validation/base/AbstractMFXValidator.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/validation/base/IMFXValidator.java b/materialfx/src/main/java/io/github/palexdev/materialfx/validation/base/IMFXValidator.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/validation/base/Validated.java b/materialfx/src/main/java/io/github/palexdev/materialfx/validation/base/Validated.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/module-info.java b/materialfx/src/main/java/module-info.java old mode 100644 new mode 100755 index 2c9389ce..acbebbed --- a/materialfx/src/main/java/module-info.java +++ b/materialfx/src/main/java/module-info.java @@ -9,6 +9,7 @@ module MaterialFX { exports io.github.palexdev.materialfx; exports io.github.palexdev.materialfx.beans; exports io.github.palexdev.materialfx.beans.properties.base; + exports io.github.palexdev.materialfx.beans.properties.functional; exports io.github.palexdev.materialfx.beans.properties.resettable; exports io.github.palexdev.materialfx.beans.properties.synced; exports io.github.palexdev.materialfx.bindings; @@ -16,15 +17,17 @@ module MaterialFX { exports io.github.palexdev.materialfx.controls; exports io.github.palexdev.materialfx.controls.base; exports io.github.palexdev.materialfx.controls.cell; - exports io.github.palexdev.materialfx.controls.enums; - exports io.github.palexdev.materialfx.controls.factories; exports io.github.palexdev.materialfx.controls.legacy; exports io.github.palexdev.materialfx.effects; exports io.github.palexdev.materialfx.effects.ripple; exports io.github.palexdev.materialfx.effects.ripple.base; + exports io.github.palexdev.materialfx.enums; + exports io.github.palexdev.materialfx.factories; exports io.github.palexdev.materialfx.filter; + exports io.github.palexdev.materialfx.filter.base; exports io.github.palexdev.materialfx.font; exports io.github.palexdev.materialfx.notifications; + exports io.github.palexdev.materialfx.notifications.base; exports io.github.palexdev.materialfx.selection; exports io.github.palexdev.materialfx.selection.base; exports io.github.palexdev.materialfx.skins; diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/Fonts.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/Fonts.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXButton.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXButton.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckBox.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckBox.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckListCell.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckListCell.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckListView.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckListView.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckTreeCell.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckTreeCell.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCircleToggleNode.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCircleToggleNode.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXColors.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXColors.css old mode 100644 new mode 100755 index 6afd20c0..a0da8355 --- a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXColors.css +++ b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXColors.css @@ -20,6 +20,7 @@ -mfx-blue: #2196f3; -mfx-charcoal: #445055; -mfx-green: #4caf50; + -mfx-onyx: #353935; -mfx-purple: #673AB7; -mfx-red: #EF6E6B; } diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle1.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle1.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle2.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle2.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle3.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle3.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenu.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenu.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenuItem.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenuItem.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXDatePicker.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXDatePicker.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXDatePickerContent.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXDatePickerContent.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXDialog.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXDialog.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXEvaluationBox.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXEvaluationBox.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterComboBox.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterComboBox.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterDialog.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterDialog.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterPane.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterPane.css new file mode 100755 index 00000000..3d0d8b4e --- /dev/null +++ b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterPane.css @@ -0,0 +1,172 @@ +@import "MFXColors.css"; +@import "Fonts.css"; + +.mfx-filter-pane { + -mfx-main: #7A0ED9; + -mfx-main-light: #B47BC2; + -mfx-main-lighter: #F5F2F7; + -mfx-gray: #6b6b6b; + + -fx-background-color: white; + -fx-background-radius: 10; + -fx-padding: 7 14 7 14; +} + +.mfx-filter-pane .header { + -fx-padding: 0 0 7 0; +} + +.mfx-filter-pane .header .header-label { + -fx-font-family: "Open Sans SemiBold"; + -fx-font-size: 14; + -fx-text-fill: -mfx-gray; + -fx-font-smoothing-type: gray; + -fx-padding: 5; +} + +.mfx-filter-pane .header #filterIcon .mfx-ripple-generator { + -mfx-ripple-radius: 20; + -mfx-ripple-color: derive(-mfx-main, 120%); +} + +.mfx-filter-pane .header #resetIcon .mfx-ripple-generator { + -mfx-ripple-radius: 20; + -mfx-ripple-color: derive(-mfx-red, 60%); +} + +.mfx-filter-pane .header #filterIcon .mfx-font-icon, +.mfx-filter-pane .header #resetIcon .mfx-font-icon { + -mfx-color: -mfx-gray; +} + +.mfx-filter-pane .header #filterIcon:hover { + -fx-background-color: derive(-mfx-main, 150%); +} + +.mfx-filter-pane .header #resetIcon:hover { + -fx-background-color: derive(-mfx-red, 90%); +} + +.mfx-filter-pane .header #filterIcon:hover .mfx-font-icon { + -mfx-color: -mfx-main; +} + +.mfx-filter-pane .header #resetIcon:hover .mfx-font-icon { + -mfx-color: -mfx-red +} + +.mfx-filter-pane .mfx-combo-box .focused-line, +.mfx-filter-pane .mfx-combo-box .unfocused-line { + -fx-stroke: transparent; +} + +.mfx-filter-pane .mfx-combo-box { + -mfx-animate-lines: false; + + -fx-background-color: -mfx-main-lighter; + -fx-background-radius: 30; + -fx-border-color: lightgray; + -fx-border-width: 0.5; + -fx-border-radius: 30; + -fx-padding: 0 3 0 5; /* TODO FIX */ +} + +.mfx-filter-pane .filter-combo { + -fx-min-height: 36; + -fx-min-width: 120; +} + +.mfx-filter-pane .predicates-combo { + -fx-min-height: 36; + -fx-min-width: 180; +} + +.mfx-filter-pane .mfx-combo-box:focused { + -fx-background-color: -mfx-main; +} + +.mfx-filter-pane .mfx-combo-box .mfx-label { + -mfx-font-family: "Open Sans SemiBold"; + -mfx-text-fill: -mfx-charcoal; +} + +.mfx-filter-pane .mfx-combo-box:focused .mfx-label { + -mfx-text-fill: white; +} + +.mfx-filter-pane .mfx-combo-box .mfx-icon-wrapper .mfx-font-icon { + -mfx-color: gray; +} + +.mfx-filter-pane .mfx-combo-box:focused .mfx-icon-wrapper .mfx-font-icon { + -mfx-color: white; +} + +.mfx-filter-pane .mfx-combo-box .mfx-icon-wrapper .mfx-ripple-generator { + -mfx-ripple-color: -mfx-main-lighter; +} + +/* TODO fix */ +.mfx-filter-pane .mfx-combo-box .mfx-list-view { + -fx-background-color: red; +} + +.mfx-filter-pane .mfx-combo-box .mfx-list-view .mfx-list-cell:selected { + -fx-background-color: transparent; +} + +.mfx-filter-pane .mfx-text-field { + -fx-min-width: 180; + -fx-min-height: 36; + -mfx-animate-lines: false; + -fx-background-color: transparent; + -fx-border-color: lightgray; + -fx-border-width: 0.7; + -fx-border-radius: 100%; + -fx-padding: 7 8 7 8; +} + +.mfx-filter-pane .mfx-text-field .focused-line, +.mfx-filter-pane .mfx-text-field .unfocused-line { + -fx-stroke: transparent; +} + +.mfx-filter-pane .mfx-button { + -fx-pref-width: 135; + -fx-pref-height: 36; + -fx-background-color: #7915D9; + -fx-background-radius: 30; + -fx-text-fill: white; + -fx-font-weight: bold; +} + +.mfx-filter-pane .mfx-button .mfx-ripple-generator { + -mfx-ripple-color: rgba(250, 250, 250, 0.6); + -mfx-ripple-radius: 45; +} + +.mfx-filter-pane .active-filter { + -fx-min-height: 30; + -fx-background-color: #B379C1; + -fx-background-radius: 30; + -fx-padding: 0 20 0 20; +} + +.mfx-filter-pane .active-filter .mfx-font-icon { + -mfx-color: white; +} + +.mfx-filter-pane .active-filter .label { + -fx-font-family: "Open Sans Regular"; + -fx-text-fill: white; +} + +.mfx-filter-pane .active-filter .function-text { + -fx-font-family: "Open Sans Bold"; +} + +.mfx-filter-pane .and-or-text { + -fx-font-family: "Open Sans Bold"; + -fx-font-size: 14; + -fx-fill: -mfx-gray; +} \ No newline at end of file diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle1.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle1.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle2.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle2.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle3.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle3.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXListCell.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXListCell.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXListView.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXListView.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXNotification.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXNotification.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXNotificationCenter.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXNotificationCenter.css new file mode 100755 index 00000000..0d4125fe --- /dev/null +++ b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXNotificationCenter.css @@ -0,0 +1,88 @@ +@import "MFXColors.css"; +@import "Fonts.css"; + +.mfx-notification-center { + -mfx-main: #7A0ED9; + -fx-background-color: transparent; +} + +.mfx-notification-center .notifications-icon { + -fx-background-color: white; + -fx-background-radius: 100%; + -fx-border-color: lightgray; + -fx-border-radius: 100%; +} + +.mfx-notification-center .counter { + -fx-background-color: -mfx-main; + -fx-background-radius: 100%; +} + +.mfx-notification-center .counter .text { + -fx-fill: white; + -fx-font-family: "Open Sans Bold"; + -fx-font-smoothing-type: gray; +} + +.mfx-notification-center .header { + -fx-border-color: transparent transparent lightgray transparent; + -fx-border-width: 0.7; + -fx-padding: 5 0 5 0; +} + +.mfx-notification-center .header .mfx-label { + -mfx-line-color: transparent; + -mfx-unfocused-line-color: transparent; + -mfx-animate-lines: false; + + -mfx-font-family: "Open Sans SemiBold"; + -mfx-font-size: 14; + -mfx-text-fill: -mfx-onyx; +} + +.mfx-notification-center .header .mfx-toggle-button { + -mfx-toggle-color: -mfx-main; + -mfx-untoggle-line-color: derive(-mfx-onyx, 30%); + + -fx-font-family: "Open Sans SemiBold"; + -fx-font-size: 12; + -fx-text-fill: -mfx-onyx; + -fx-font-smoothing-type: gray; +} + +.mfx-notification-center .notifications-container { + -fx-background-color: white; + -fx-background-radius: 5; + -fx-border-color: lightgray; + -fx-border-radius: 5; + -fx-border-width: 0.7; + -fx-padding: 5 1 1 1; +} + +.mfx-notification-center .notifications-container .virtual-flow .mfx-notification-cell #check { + -mfx-checked-color: -mfx-main; + -mfx-unchecked-color: -mfx-onyx; +} + +.mfx-notification-center .notifications-container .virtual-flow .mfx-notification-cell #check .ripple-container .mfx-ripple-generator { + -mfx-ripple-color: derive(-mfx-main, 125%); +} + +.mfx-notification-center .actions { + -fx-border-color: lightgray transparent transparent transparent; + -fx-border-width: 0.7; + -fx-padding: 10; +} + +.mfx-notification-center .actions .mfx-font-icon { + -mfx-color: -mfx-onyx; +} + +.mfx-notification-center .notifications-container .actions .mfx-icon-wrapper .mfx-ripple-generator { + -mfx-ripple-radius: 24; + -mfx-ripple-color: derive(-mfx-main, 150%) +} + +.mfx-context-menu .mfx-context-menu-item:hover { + -fx-background-color: derive(#7A0ED9, 150%); +} diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXPasswordField.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXPasswordField.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXProgressBar.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXProgressBar.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXProgressSpinner.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXProgressSpinner.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXRadioButton.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXRadioButton.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXRectangleToggleNode.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXRectangleToggleNode.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXScrollPane.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXScrollPane.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXSlider.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXSlider.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXStepper.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXStepper.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXStepperToggle.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXStepperToggle.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableColumn.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableColumn.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableRow.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableRow.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableRowCell.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableRowCell.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableView.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTableView.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTextField.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTextField.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXToggleButton.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXToggleButton.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeCell.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeCell.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeItem.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeItem.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeView.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeView.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXComboBox.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXComboBox.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXLegacyListCell.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXLegacyListCell.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXLegacyListView.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXLegacyListView.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXTableRow.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXTableRow.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXTableView.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/legacy/MFXTableView.css old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Bold.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Bold.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Light.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Light.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Medium.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Medium.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Regular.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Regular.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-SemiBold.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-SemiBold.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-VariableFont_wght.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-VariableFont_wght.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf old mode 100644 new mode 100755 index 47a41873..f1afd551 Binary files a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf and b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf differ diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Bold.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Bold.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-BoldItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-BoldItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBold.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBold.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Italic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Italic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Light.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Light.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-LightItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-LightItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Regular.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Regular.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBold.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBold.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Black.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Black.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BlackItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BlackItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Bold.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Bold.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BoldItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BoldItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Italic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Italic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Light.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Light.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-LightItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-LightItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Medium.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Medium.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-MediumItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-MediumItalic.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Regular.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Regular.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Thin.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Thin.ttf old mode 100644 new mode 100755 diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-ThinItalic.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-ThinItalic.ttf old mode 100644 new mode 100755 diff --git a/settings.gradle b/settings.gradle old mode 100644 new mode 100755