Browse Source

Version 11.10.1

LoaderUtils, MFXHLoader, MFXVLoader, MFXLoaderBean: migrated type from Node to Parent.
Introduced caching mechanism which vastly improves the performance of the views switching.

MFXComboBoxSkin, MFXFilterComboBoxSkin: fixed occasional NullPointerException

MFXFlowlessCheckListCell: the pseudo class should be accessible to sub classes.

MFXFlowlessListView, MFXFlowlessCheckListView (css): fixed scrollbars' thumb not showing properly in case of many cells

TreeCheckModel: fixed scanTree
Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
palexdev 4 years ago
parent
commit
86ce432ac4

+ 2 - 2
README.md

@@ -101,7 +101,7 @@ repositories {
 }
 
 dependencies {
-implementation 'io.github.palexdev:materialfx:11.10.0'
+implementation 'io.github.palexdev:materialfx:11.10.1'
 }
 ```
 
@@ -111,7 +111,7 @@ implementation 'io.github.palexdev:materialfx:11.10.0'
 <dependency>
   <groupId>io.github.palexdev</groupId>
   <artifactId>materialfx</artifactId>
-  <version>11.10.0</version>
+  <version>11.10.1</version>
 </dependency>
 ```
 

+ 1 - 1
build.gradle

@@ -4,7 +4,7 @@ plugins {
 }
 
 group 'io.github.palexdev'
-version '11.10.0'
+version '11.10.1'
 
 repositories {
     mavenCentral()

+ 1 - 1
materialfx/gradle.properties

@@ -1,6 +1,6 @@
 GROUP=io.github.palexdev
 POM_ARTIFACT_ID=materialfx
-VERSION_NAME=11.10.0
+VERSION_NAME=11.10.1
 
 POM_NAME=materialfx
 POM_DESCRIPTION=Material Desgin components for JavaFX

+ 4 - 4
materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXLoaderBean.java

@@ -18,7 +18,7 @@
 
 package io.github.palexdev.materialfx.beans;
 
-import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.ToggleButton;
 import javafx.util.Callback;
 
@@ -38,7 +38,7 @@ public class MFXLoaderBean {
     //================================================================================
     // Properties
     //================================================================================
-    private Node root;
+    private Parent root;
     private final boolean defaultRoot;
     private final Callback<Class<?>, Object> controllerFactory;
     private final ToggleButton button;
@@ -61,7 +61,7 @@ public class MFXLoaderBean {
     //================================================================================
     // Methods
     //================================================================================
-    public Node getRoot() {
+    public Parent getRoot() {
         return root;
     }
 
@@ -73,7 +73,7 @@ public class MFXLoaderBean {
         return controllerFactory;
     }
 
-    public void setRoot(Node root) {
+    public void setRoot(Parent root) {
         this.root = root;
     }
 

+ 79 - 21
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXHLoader.java

@@ -19,6 +19,7 @@
 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.utils.LoaderUtils;
 import io.github.palexdev.materialfx.utils.ToggleButtonsUtil;
@@ -26,11 +27,14 @@ import javafx.application.Platform;
 import javafx.beans.property.*;
 import javafx.fxml.FXMLLoader;
 import javafx.geometry.Pos;
-import javafx.scene.Node;
+import javafx.scene.CacheHint;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
 import javafx.scene.control.ToggleGroup;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Pane;
 import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
 
 import java.net.URL;
 import java.util.ArrayList;
@@ -38,6 +42,8 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -56,6 +62,10 @@ import java.util.stream.Collectors;
  * Once everything is loaded: {@link ToggleButtonsUtil#addAlwaysOneSelectedSupport(ToggleGroup)} is called, the loaded
  * toggles are added to the children list and {@link #setDefault()} is called.
  *
+ * <b>NOTE: the cache level must be set before invoking the {@link #start()} method.</b>
+ * <p>
+ * By default it is set to: {@link LoaderCacheLevel#SCENE_CACHE}
+ *
  * @see LoaderUtils
  * @see MFXLoaderBean
  * @see ToggleButtonsUtil
@@ -68,7 +78,7 @@ public class MFXHLoader extends HBox {
     private Pane contentPane;
     private final ToggleGroup toggleGroup;
 
-    private final BooleanProperty isAnimated = new SimpleBooleanProperty(false);
+    private final BooleanProperty animated = new SimpleBooleanProperty(false);
     private final DoubleProperty animationMillis = new SimpleDoubleProperty(800);
     private MFXAnimationFactory animationType = MFXAnimationFactory.FADE_IN;
 
@@ -76,6 +86,8 @@ public class MFXHLoader extends HBox {
     private final Map<String, MFXLoaderBean> idViewMap;
     private Supplier<FXMLLoader> fxmlLoaderSupplier;
 
+    private LoaderCacheLevel cacheLevel = LoaderCacheLevel.SCENE_CACHE;
+
     //================================================================================
     // Constructors
     //================================================================================
@@ -167,9 +179,8 @@ public class MFXHLoader extends HBox {
     /**
      * Starts the loading process.
      * <p></p>
-     * Retrieves the {@link MFXLoaderBean}s in the idViewMa, for each of them
-     * checks if the node has been already loaded, if not then calls {@link #buildLoadCallable(MFXLoaderBean)},
-     * submit the callable to {@link LoaderUtils#submit(Callable)}, then increments the loadedItems counter.
+     * Retrieves the {@link MFXLoaderBean}s in the idViewMap, for each of them
+     * checks if the node has been already loaded, if not then calls {@link #load(MFXLoaderBean)}
      */
     public void start() {
         if (contentPane == null) {
@@ -179,20 +190,63 @@ public class MFXHLoader extends HBox {
         List<MFXLoaderBean> loaderBeans = new ArrayList<>(idViewMap.values());
         for (MFXLoaderBean loaderBean : loaderBeans) {
             if (loaderBean.getRoot() == null) {
-                LoaderUtils.submit(buildLoadCallable(loaderBean));
+                load(loaderBean);
+            }
+        }
+    }
+
+    /**
+     * Loads the root node of a {@link MFXLoaderBean}. The load process depends
+     * on the set cache level. If the level is set to {@link LoaderCacheLevel#NONE}
+     * the load task built by {@link #buildLoadCallable(MFXLoaderBean)} is submitted
+     * to the loader executor {@link LoaderUtils#submit(Callable)}.
+     * In the other cases the task is submitted to the executor but the {@link Future#get()} method
+     * is invoked which causes the thread to wait until the fxml is loaded.
+     *
+     * @see #cacheParent(Parent)
+     */
+    private void load(MFXLoaderBean loaderBean) {
+        if (cacheLevel != LoaderCacheLevel.NONE) {
+            try {
+                Parent loaded = LoaderUtils.submit(buildLoadCallable(loaderBean)).get();
+                cacheParent(loaded);
+                loadedItems.set(loadedItems.get() + 1);
+            } catch (InterruptedException | ExecutionException ex) {
                 loadedItems.set(loadedItems.get() + 1);
+                ex.printStackTrace();
             }
+        } else {
+            LoaderUtils.submit(buildLoadCallable(loaderBean));
+            loadedItems.set(loadedItems.get() + 1);
         }
     }
 
+    /**
+     * Called if the cache level is not set to {@link LoaderCacheLevel#NONE}.
+     *
+     * @see LoaderCacheLevel
+     */
+    private void cacheParent(Parent parent) {
+        if (cacheLevel == LoaderCacheLevel.SCENE_JAVAFX_CACHE) {
+            parent.setCache(true);
+            parent.setCacheHint(CacheHint.SPEED);
+        }
+
+        StackPane pane = new StackPane();
+        pane.getChildren().setAll(parent);
+        Scene scene = new Scene(pane);
+        pane.applyCss();
+        pane.layout();
+    }
+
     /**
      * Builds the callable which loads the fxmlFile with {@link LoaderUtils#fxmlLoad(FXMLLoader, MFXLoaderBean)}
      * or {@link LoaderUtils#fxmlLoad(MFXLoaderBean)} if the FXMLLoader supplier is null.
      * When the file is loaded adds a listener to the toggle's selected property to handle the view switching.
      */
-    private Callable<Node> buildLoadCallable(MFXLoaderBean loaderBean) {
+    private Callable<Parent> buildLoadCallable(MFXLoaderBean loaderBean) {
         return () -> {
-            Node root;
+            Parent root;
             if (fxmlLoaderSupplier != null) {
                 root = LoaderUtils.fxmlLoad(fxmlLoaderSupplier.get(), loaderBean);
             } else {
@@ -201,15 +255,11 @@ public class MFXHLoader extends HBox {
             loaderBean.setRoot(root);
 
             loaderBean.getButton().selectedProperty().addListener((observable, oldValue, newValue) -> {
-                if (isIsAnimated()) {
-                    getAnimationType().build(loaderBean.getRoot(), getAnimationMillis()).play();
+                if (isAnimated()) {
+                    animationType.build(loaderBean.getRoot(), animationMillis.doubleValue()).play();
                 }
                 if (newValue) {
-                    try {
-                        contentPane.getChildren().set(0, loaderBean.getRoot());
-                    } catch (IndexOutOfBoundsException ex) {
-                        contentPane.getChildren().add(0, loaderBean.getRoot());
-                    }
+                    contentPane.getChildren().setAll(loaderBean.getRoot());
                 }
             });
             return root;
@@ -250,19 +300,19 @@ public class MFXHLoader extends HBox {
         }
     }
 
-    public boolean isIsAnimated() {
-        return isAnimated.get();
+    public boolean isAnimated() {
+        return animated.get();
     }
 
     /**
      * Specifies if the view switching is animated.
      */
-    public BooleanProperty isAnimatedProperty() {
-        return isAnimated;
+    public BooleanProperty animatedProperty() {
+        return animated;
     }
 
-    public void setIsAnimated(boolean isAnimated) {
-        this.isAnimated.set(isAnimated);
+    public void setAnimated(boolean animated) {
+        this.animated.set(animated);
     }
 
     public double getAnimationMillis() {
@@ -290,4 +340,12 @@ public class MFXHLoader extends HBox {
     public void setAnimationType(MFXAnimationFactory animationType) {
         this.animationType = animationType;
     }
+
+    public LoaderCacheLevel getCacheLevel() {
+        return cacheLevel;
+    }
+
+    public void setCacheLevel(LoaderCacheLevel cacheLevel) {
+        this.cacheLevel = cacheLevel;
+    }
 }

+ 79 - 20
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXVLoader.java

@@ -19,6 +19,7 @@
 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.utils.LoaderUtils;
 import io.github.palexdev.materialfx.utils.ToggleButtonsUtil;
@@ -26,10 +27,13 @@ import javafx.application.Platform;
 import javafx.beans.property.*;
 import javafx.fxml.FXMLLoader;
 import javafx.geometry.Pos;
-import javafx.scene.Node;
+import javafx.scene.CacheHint;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
 import javafx.scene.control.ToggleGroup;
 import javafx.scene.layout.Pane;
 import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
 import javafx.scene.layout.VBox;
 
 import java.net.URL;
@@ -38,6 +42,8 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -55,6 +61,11 @@ import java.util.stream.Collectors;
  * <p></p>
  * Once everything is loaded: {@link ToggleButtonsUtil#addAlwaysOneSelectedSupport(ToggleGroup)} is called, the loaded
  * toggles are added to the children list and {@link #setDefault()} is called.
+ * <p></p>
+ *
+ * <b>NOTE: the cache level must be set before invoking the {@link #start()} method.</b>
+ * <p>
+ * By default it is set to: {@link LoaderCacheLevel#SCENE_CACHE}
  *
  * @see LoaderUtils
  * @see MFXLoaderBean
@@ -68,7 +79,7 @@ public class MFXVLoader extends VBox {
     private Pane contentPane;
     private final ToggleGroup toggleGroup;
 
-    private final BooleanProperty isAnimated = new SimpleBooleanProperty(false);
+    private final BooleanProperty animated = new SimpleBooleanProperty(false);
     private final DoubleProperty animationMillis = new SimpleDoubleProperty(800);
     private MFXAnimationFactory animationType = MFXAnimationFactory.FADE_IN;
 
@@ -76,6 +87,8 @@ public class MFXVLoader extends VBox {
     private final Map<String, MFXLoaderBean> idViewMap;
     private Supplier<FXMLLoader> fxmlLoaderSupplier;
 
+    private LoaderCacheLevel cacheLevel = LoaderCacheLevel.SCENE_CACHE;
+
     //================================================================================
     // Constructors
     //================================================================================
@@ -167,9 +180,8 @@ public class MFXVLoader extends VBox {
     /**
      * Starts the loading process.
      * <p></p>
-     * Retrieves the {@link MFXLoaderBean}s in the idViewMa, for each of them
-     * checks if the node has been already loaded, if not then calls {@link #buildLoadCallable(MFXLoaderBean)},
-     * submit the callable to {@link LoaderUtils#submit(Callable)}, then increments the loadedItems counter.
+     * Retrieves the {@link MFXLoaderBean}s in the idViewMap, for each of them
+     * checks if the node has been already loaded, if not then calls {@link #load(MFXLoaderBean)}
      */
     public void start() {
         if (contentPane == null) {
@@ -179,20 +191,63 @@ public class MFXVLoader extends VBox {
         List<MFXLoaderBean> loaderBeans = new ArrayList<>(idViewMap.values());
         for (MFXLoaderBean loaderBean : loaderBeans) {
             if (loaderBean.getRoot() == null) {
-                LoaderUtils.submit(buildLoadCallable(loaderBean));
+                load(loaderBean);
+            }
+        }
+    }
+
+    /**
+     * Loads the root node of a {@link MFXLoaderBean}. The load process depends
+     * on the set cache level. If the level is set to {@link LoaderCacheLevel#NONE}
+     * the load task built by {@link #buildLoadCallable(MFXLoaderBean)} is submitted
+     * to the loader executor {@link LoaderUtils#submit(Callable)}.
+     * In the other cases the task is submitted to the executor but the {@link Future#get()} method
+     * is invoked which causes the thread to wait until the fxml is loaded.
+     *
+     * @see #cacheParent(Parent)
+     */
+    private void load(MFXLoaderBean loaderBean) {
+        if (cacheLevel != LoaderCacheLevel.NONE) {
+            try {
+                Parent loaded = LoaderUtils.submit(buildLoadCallable(loaderBean)).get();
+                cacheParent(loaded);
+                loadedItems.set(loadedItems.get() + 1);
+            } catch (InterruptedException | ExecutionException ex) {
                 loadedItems.set(loadedItems.get() + 1);
+                ex.printStackTrace();
             }
+        } else {
+            LoaderUtils.submit(buildLoadCallable(loaderBean));
+            loadedItems.set(loadedItems.get() + 1);
         }
     }
 
+    /**
+     * Called if the cache level is not set to {@link LoaderCacheLevel#NONE}.
+     *
+     * @see LoaderCacheLevel
+     */
+    private void cacheParent(Parent parent) {
+        if (cacheLevel == LoaderCacheLevel.SCENE_JAVAFX_CACHE) {
+            parent.setCache(true);
+            parent.setCacheHint(CacheHint.SPEED);
+        }
+
+        StackPane pane = new StackPane();
+        pane.getChildren().setAll(parent);
+        Scene scene = new Scene(pane);
+        pane.applyCss();
+        pane.layout();
+    }
+
     /**
      * Builds the callable which loads the fxmlFile with {@link LoaderUtils#fxmlLoad(FXMLLoader, MFXLoaderBean)}
      * or {@link LoaderUtils#fxmlLoad(MFXLoaderBean)} if the FXMLLoader supplier is null.
      * When the file is loaded adds a listener to the toggle's selected property to handle the view switching.
      */
-    private Callable<Node> buildLoadCallable(MFXLoaderBean loaderBean) {
+    private Callable<Parent> buildLoadCallable(MFXLoaderBean loaderBean) {
         return () -> {
-            Node root;
+            Parent root;
             if (fxmlLoaderSupplier != null) {
                 root = LoaderUtils.fxmlLoad(fxmlLoaderSupplier.get(), loaderBean);
             } else {
@@ -201,15 +256,11 @@ public class MFXVLoader extends VBox {
             loaderBean.setRoot(root);
 
             loaderBean.getButton().selectedProperty().addListener((observable, oldValue, newValue) -> {
-                if (isAnimated.get()) {
+                if (isAnimated()) {
                     animationType.build(loaderBean.getRoot(), animationMillis.doubleValue()).play();
                 }
                 if (newValue) {
-                    try {
-                        contentPane.getChildren().set(0, loaderBean.getRoot());
-                    } catch (IndexOutOfBoundsException ex) {
-                        contentPane.getChildren().add(0, loaderBean.getRoot());
-                    }
+                        contentPane.getChildren().setAll(loaderBean.getRoot());
                 }
             });
             return root;
@@ -250,19 +301,19 @@ public class MFXVLoader extends VBox {
         }
     }
 
-    public boolean isIsAnimated() {
-        return isAnimated.get();
+    public boolean isAnimated() {
+        return animated.get();
     }
 
     /**
      * Specifies if the view switching is animated.
      */
-    public BooleanProperty isAnimatedProperty() {
-        return isAnimated;
+    public BooleanProperty animatedProperty() {
+        return animated;
     }
 
-    public void setIsAnimated(boolean isAnimated) {
-        this.isAnimated.set(isAnimated);
+    public void setAnimated(boolean animated) {
+        this.animated.set(animated);
     }
 
     public double getAnimationMillis() {
@@ -290,4 +341,12 @@ public class MFXVLoader extends VBox {
     public void setAnimationType(MFXAnimationFactory animationType) {
         this.animationType = animationType;
     }
+
+    public LoaderCacheLevel getCacheLevel() {
+        return cacheLevel;
+    }
+
+    public void setCacheLevel(LoaderCacheLevel cacheLevel) {
+        this.cacheLevel = cacheLevel;
+    }
 }

+ 1 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXFlowlessCheckListCell.java

@@ -51,7 +51,7 @@ public class MFXFlowlessCheckListCell<T> extends AbstractMFXFlowlessListCell<T>
     protected final MFXCheckbox checkbox;
 
     private final ReadOnlyBooleanWrapper checked = new ReadOnlyBooleanWrapper();
-    private static final PseudoClass CHECKED_PSEUDO_CLASS = PseudoClass.getPseudoClass("checked");
+    protected static final PseudoClass CHECKED_PSEUDO_CLASS = PseudoClass.getPseudoClass("checked");
 
     private boolean clearSelectionOnCheck = true;
 

+ 52 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/LoaderCacheLevel.java

@@ -0,0 +1,52 @@
+/*
+ *     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 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 General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package io.github.palexdev.materialfx.controls.enums;
+
+import io.github.palexdev.materialfx.controls.MFXHLoader;
+import io.github.palexdev.materialfx.controls.MFXVLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+
+/**
+ * Enumerator to define the level of caching used by {@link MFXHLoader}
+ * and {@link MFXVLoader}. By enabling cache the switch performance vastly improves
+ * but the views cannot be loaded in parallel (quite acceptable loss in some cases).
+ */
+public enum LoaderCacheLevel {
+    /**
+     * No caching, may lag a little in some occasions
+     * (when the root contains a huge amount of nodes for example).
+     */
+    NONE,
+
+    /**
+     * The root node is added to a dummy pane and {@link Scene},
+     * then {@link Parent#applyCss()} and {@link Parent#layout()} are called.
+     * This causes all nodes in the scene to create their skin and layout
+     * thus "caching" the scenegraph. Vastly improves view switching performance.
+     */
+    SCENE_CACHE,
+
+    /**
+     * Does what SCENE_CACHE does, plus sets the JavaFX's properties cache to true
+     * and the cache hint to SPEED on the loaded root node.
+     * (To be honest I don't know if this truly improves performance since I didn't notice any effective improvement)
+     */
+    SCENE_JAVAFX_CACHE
+}

+ 14 - 8
materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java

@@ -51,13 +51,14 @@ public enum FontResources {
     FILTER("mfx-filter", '\uE929'),
     FILTER_CLEAR("mfx-filter-clear", '\uE92b'),
     FIRST_PAGE("mfx-first-page", '\uE927'),
+    GEAR("mfx-gear", '\uE900'),
     GOOGLE("mfx-google", '\uE90a'),
     GOOGLE_DRIVE("mfx-google-drive", '\uE931'),
     HOME("mfx-home", '\uE932'),
     INFO("mfx-info", '\uE933'),
     INFO_CIRCLE("mfx-info-circle", '\uE91a'),
     LAST_PAGE("mfx-last-page", '\uE928'),
-    LEVEL_UP("mfx-level-up", '\uE937'),
+    LEVEL_UP("mfx-level-up", '\uE936'),
     MINUS("mfx-minus", '\uE901'),
     MINUS_CIRCLE("mfx-minus-circle", '\uE90c'),
     MODENA_MARK("mfx-modena-mark", '\uE90d'),
@@ -65,17 +66,22 @@ public enum FontResources {
     SEARCH_PLUS("mfx-search-plus", '\uE92a'),
     STEP_BACKWARD("mfx-step-backward", '\uE923'),
     STEP_FORWARD("mfx-step-forward", '\uE924'),
-    SYNC("mfx-sync", '\uE936'),
+    SYNC("mfx-sync", '\uE937'),
+    SYNC_LIGHT("mfx-sync-light", '\uE938'),
     USER("mfx-user", '\uE92c'),
     USERS("mfx-users", '\uE92d'),
-    VARIANT3_MARK("mfx-variant3-mark", '\uE90e'),
-    VARIANT4_MARK("mfx-variant4-mark", '\uE90f'),
-    VARIANT5_MARK("mfx-variant5-mark", '\uE910'),
-    VARIANT6_MARK("mfx-variant6-mark", '\uE911'),
+    VARIANT3_MARK("mfx-variant3-mark", '\uE90f'),
+    VARIANT4_MARK("mfx-variant4-mark", '\uE90e'),
+    VARIANT5_MARK("mfx-variant5-mark", '\uE911'),
+    VARIANT6_MARK("mfx-variant6-mark", '\uE910'),
     VARIANT7_MARK("mfx-variant7-mark", '\uE912'),
-    VARIANT8_MARK("mfx-variant8-mark", '\uE913'),
-    VARIANT9_MARK("mfx-variant9-mark", '\uE914'),
+    VARIANT8_MARK("mfx-variant8-mark", '\uE93a'),
+    VARIANT9_MARK("mfx-variant9-mark", '\uE913'),
+    VARIANT10_MARK("mfx-variant10-mark", '\uE93b'),
+    VARIANT11_MARK("mfx-variant11-mark", '\uE93c'),
+    VARIANT12_MARK("mfx-variant12-mark", '\uE914'),
     X("mfx-x", '\uE916'),
+    X_ALT("mfx-x-alt", '\uE93d'),
     X_CIRCLE("mfx-x-circle", '\uE915'),
     X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE900'),
     ;

+ 4 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/selection/TreeCheckModel.java

@@ -151,9 +151,11 @@ public class TreeCheckModel<T> extends TreeSelectionModel<T> implements ITreeChe
      */
     @Override
     public void scanTree(MFXCheckTreeItem<T> item) {
-        clearChecked();
         TreeItemStream.flattenTree(item).forEach(treeItem -> {
-            if (((MFXCheckTreeItem<T>) treeItem).isChecked()) check(item, null);
+            MFXCheckTreeItem<T> cItem = (MFXCheckTreeItem<T>) treeItem;
+            if (cItem.isChecked() && !checkedItems.contains(cItem)) {
+                check(cItem, null);
+            }
         });
     }
 

+ 1 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java

@@ -246,7 +246,7 @@ public class MFXComboBoxSkin<T> extends SkinBase<MFXComboBox<T>> {
 
         comboBox.skinProperty().addListener((observable, oldValue, newValue) -> comboBox.getScene().addEventFilter(MouseEvent.MOUSE_PRESSED, popupHandler));
         comboBox.sceneProperty().addListener((observable, oldValue, newValue) -> {
-            if (newValue != oldValue) {
+            if (oldValue != null && newValue != oldValue) {
                 oldValue.removeEventFilter(MouseEvent.MOUSE_PRESSED, popupHandler);
             }
             if (newValue != null) {

+ 1 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java

@@ -240,7 +240,7 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
 
         comboBox.skinProperty().addListener((observable, oldValue, newValue) -> comboBox.getScene().addEventFilter(MouseEvent.MOUSE_PRESSED, popupHandler));
         comboBox.sceneProperty().addListener((observable, oldValue, newValue) -> {
-            if (newValue != oldValue) {
+            if (oldValue != null && newValue != oldValue) {
                 oldValue.removeEventFilter(MouseEvent.MOUSE_PRESSED, popupHandler);
             }
             if (newValue != null) {
@@ -598,7 +598,6 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
         public FilterListCell(MFXFilterComboBox<T> comboBox, T data, double fixedHeight) {
             super(null, data, fixedHeight);
             this.comboBox = comboBox;
-            initialize();
 
             if (comboBox.getSelectionModel().getSelectedItem() == getData() && !isSelected()) {
                 setSelected(true);

+ 6 - 6
materialfx/src/main/java/io/github/palexdev/materialfx/utils/LoaderUtils.java

@@ -22,7 +22,7 @@ import io.github.palexdev.materialfx.beans.MFXLoaderBean;
 import io.github.palexdev.materialfx.controls.MFXHLoader;
 import io.github.palexdev.materialfx.controls.MFXVLoader;
 import javafx.fxml.FXMLLoader;
-import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.util.Callback;
 
 import java.io.IOException;
@@ -77,7 +77,7 @@ public class LoaderUtils {
      *                                    scheduled for execution
      * @throws NullPointerException       if the task is null
      */
-    public static Future<Node> submit(Callable<Node> task) {
+    public static Future<Parent> submit(Callable<Parent> task) {
         return executor.submit(task);
     }
 
@@ -89,7 +89,7 @@ public class LoaderUtils {
      * @see     #fxmlLoad(FXMLLoader, URL)
      * @see     #fxmlLoad(FXMLLoader, URL, Callback)
      */
-    public static Node fxmlLoad(MFXLoaderBean loaderBean) throws IOException {
+    public static Parent fxmlLoad(MFXLoaderBean loaderBean) throws IOException {
         FXMLLoader fxmlLoader = new FXMLLoader();
         if (loaderBean.getControllerFactory() != null) {
             return fxmlLoad(fxmlLoader, loaderBean.getFxmlURL(), loaderBean.getControllerFactory());
@@ -109,7 +109,7 @@ public class LoaderUtils {
      * @see     #fxmlLoad(FXMLLoader, URL)
      * @see     #fxmlLoad(FXMLLoader, URL, Callback)
      */
-    public static Node fxmlLoad(FXMLLoader fxmlLoader, MFXLoaderBean loaderBean) throws IOException {
+    public static Parent fxmlLoad(FXMLLoader fxmlLoader, MFXLoaderBean loaderBean) throws IOException {
         if (loaderBean.getControllerFactory() != null) {
             return fxmlLoad(fxmlLoader, loaderBean.getFxmlURL(), loaderBean.getControllerFactory());
         }
@@ -122,7 +122,7 @@ public class LoaderUtils {
      * @param   fxmlURL the fxml file to load
      * @return  the loaded object hierarchy from the fxml
      */
-    private static Node fxmlLoad(FXMLLoader fxmlLoader, URL fxmlURL) throws IOException {
+    private static Parent fxmlLoad(FXMLLoader fxmlLoader, URL fxmlURL) throws IOException {
         fxmlLoader.setLocation(fxmlURL);
         return fxmlLoader.load();
     }
@@ -134,7 +134,7 @@ public class LoaderUtils {
      * @param   controllerFactory the controller object to set
      * @return  the loaded object hierarchy from the fxml
      */
-    private static Node fxmlLoad(FXMLLoader fxmlLoader, URL fxmlURL, Callback<Class<?>, Object> controllerFactory) throws IOException {
+    private static Parent fxmlLoad(FXMLLoader fxmlLoader, URL fxmlURL, Callback<Class<?>, Object> controllerFactory) throws IOException {
         fxmlLoader.setLocation(fxmlURL);
         fxmlLoader.setControllerFactory(controllerFactory);
         return fxmlLoader.load();

+ 1 - 7
materialfx/src/main/resources/io/github/palexdev/materialfx/css/mfx-flowless-check-listview.css

@@ -22,16 +22,10 @@
     -mfx-thumb-hover-color: rgb(89, 88, 91);
 }
 
-.mfx-check-list-view .virtual-flow {
-    -fx-padding: 5;
-}
-
 /* Remove JavaFX crap */
 .scroll-bar,
 .scroll-bar .decrement-arrow,
-.scroll-bar .increment-arrow,
-.scroll-bar .decrement-button,
-.scroll-bar .increment-button {
+.scroll-bar .increment-arrow {
     -fx-pref-width: 0;
     -fx-pref-height: 0;
 }

+ 1 - 7
materialfx/src/main/resources/io/github/palexdev/materialfx/css/mfx-flowless-listview.css

@@ -22,16 +22,10 @@
     -mfx-thumb-hover-color: rgb(89, 88, 91);
 }
 
-.mfx-list-view .virtual-flow {
-    -fx-padding: 5;
-}
-
 /* Remove JavaFX crap */
 .scroll-bar,
 .scroll-bar .decrement-arrow,
-.scroll-bar .increment-arrow,
-.scroll-bar .decrement-button,
-.scroll-bar .increment-button {
+.scroll-bar .increment-arrow {
     -fx-pref-width: 0;
     -fx-pref-height: 0;
 }

BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/materialfx-resources.ttf