Browse Source

:boom: MFXCheckbox has been reworked

Controls Package
:sparkles: Added new features to MFXCheckbox
:fire: MFXCheckbox, removed all styleable properties as there's no need (see MFXCheckBox.css to learn how to style the icon)
:sparkles: LabeledControlWrapper, this new control will soon be used in all eligible MaterialFX controls. It's basically a Label that has all its properties bound to a labeled Control. This way the control's text is fully customizable with CSS.
:recycle: MFXIconWrapper is now capable of auto-sizing itself, just use -1 as size

Font Package
:bug: MFXFontIcon, fixed a very annoying bug that prevented icons from being styled with CSS if properties were set with code (via setters/constructors). To be precise rather than a bug it's the normal behavior of JavaFX's styleable properties, but it's annoying so I added a workaround to make it always work
:sparkles: Added new resources

Skin Package
:boom: MFXCheckboxSkin has been reworked

MFXCheckBox.css has been reworked too, since now most of the MFXCheckbox appearance is determined by its stylesheet

Other changed files that are not mentioned here are a consequence of the above changes

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
palexdev 3 years ago
parent
commit
a6d7ee11d4

+ 0 - 2
.gitattributes

@@ -77,8 +77,6 @@ public class StepperDemoController implements Initializable {
         lastNameField.setPromptText("Last Name...");
         genderCombo.setItems(FXCollections.observableArrayList("Male", "Female", "Other"));
 
-        checkbox.setMarkType("mfx-variant7-mark");
-
         List<MFXStepperToggle> stepperToggles = createSteps();
         stepper.getStepperToggles().addAll(stepperToggles);
 

+ 1 - 3
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TableViewsDemoController.java

@@ -41,7 +41,6 @@ import javafx.scene.control.TableColumn;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.*;
 import javafx.scene.paint.Color;
-import javafx.scene.paint.Paint;
 import javafx.stage.Modality;
 import javafx.stage.Stage;
 
@@ -49,7 +48,6 @@ import java.net.URL;
 import java.util.Comparator;
 import java.util.List;
 import java.util.ResourceBundle;
-import java.util.concurrent.Callable;
 
 import static io.github.palexdev.materialfx.demo.model.Machine.State.OFFLINE;
 import static io.github.palexdev.materialfx.demo.model.Machine.State.ONLINE;
@@ -166,7 +164,7 @@ public class TableViewsDemoController implements Initializable {
             rowCell.setGraphicTextGap(4);
             MFXFontIcon icon = new MFXFontIcon("mfx-circle", 6);
             icon.colorProperty().bind(Bindings.createObjectBinding(
-                    (Callable<Paint>) () -> machine.getState() == ONLINE ? Color.LIMEGREEN : Color.SALMON,
+                    () -> machine.getState() == ONLINE ? Color.LIMEGREEN : Color.SALMON,
                     machine.stateProperty())
             );
             rowCell.setLeadingGraphic(icon);

+ 25 - 0
demo/src/test/java/CheckboxTest.java

@@ -0,0 +1,25 @@
+import io.github.palexdev.materialfx.controls.MFXCheckbox;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.layout.StackPane;
+import javafx.scene.text.Font;
+import javafx.stage.Stage;
+import org.scenicview.ScenicView;
+
+public class CheckboxTest extends Application {
+
+    @Override
+    public void start(Stage primaryStage) {
+        StackPane stackPane = new StackPane();
+
+        MFXCheckbox checkbox = new MFXCheckbox("Checkbox");
+        checkbox.setFont(Font.font("Roboto Medium", 14));
+        stackPane.getChildren().add(checkbox);
+
+        Scene scene = new Scene(stackPane, 800, 600);
+        primaryStage.setScene(scene);
+        primaryStage.show();
+
+        ScenicView.show(scene);
+    }
+}

+ 1 - 1
demo/src/test/java/Launcher.java

@@ -3,6 +3,6 @@ import javafx.application.Application;
 public class Launcher {
 
     public static void main(String[] args) {
-        Application.launch(NotificationsTest.class, args);
+        Application.launch(CheckboxTest.class, args);
     }
 }

+ 39 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/ComparatorProperty.java

@@ -0,0 +1,39 @@
+package io.github.palexdev.materialfx.controls;
+
+import javafx.scene.control.Label;
+import javafx.scene.control.Labeled;
+
+public class LabeledControlWrapper extends Label {
+
+    public LabeledControlWrapper(Labeled labeled) {
+        super();
+
+        // Init
+        setText(labeled.getText());
+        setFont(labeled.getFont());
+        setTextFill(labeled.getTextFill());
+        setWrapText(labeled.isWrapText());
+        setTextAlignment(labeled.getTextAlignment());
+        setTextOverrun(labeled.getTextOverrun());
+        setEllipsisString(labeled.getEllipsisString());
+        setUnderline(labeled.isUnderline());
+        setLineSpacing(labeled.getLineSpacing());
+        setGraphicTextGap(labeled.getGraphicTextGap());
+        setContentDisplay(labeled.getContentDisplay());
+        setGraphic(labeled.getGraphic());
+
+        // Bindings
+        textProperty().bind(labeled.textProperty());
+        fontProperty().bind(labeled.fontProperty());
+        textFillProperty().bind(labeled.textFillProperty());
+        wrapTextProperty().bind(labeled.wrapTextProperty());
+        textAlignmentProperty().bind(labeled.textAlignmentProperty());
+        textOverrunProperty().bind(labeled.textOverrunProperty());
+        ellipsisStringProperty().bind(labeled.ellipsisStringProperty());
+        underlineProperty().bind(labeled.underlineProperty());
+        lineSpacingProperty().bind(labeled.lineSpacingProperty());
+        graphicTextGapProperty().bind(labeled.graphicTextGapProperty());
+        contentDisplayProperty().bind(labeled.contentDisplayProperty());
+        graphicProperty().bind(labeled.graphicProperty());
+    }
+}

+ 37 - 113
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java

@@ -20,11 +20,12 @@ package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.skins.MFXCheckboxSkin;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.css.*;
 import javafx.scene.control.CheckBox;
+import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.Skin;
-import javafx.scene.paint.Color;
-import javafx.scene.paint.Paint;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -44,12 +45,13 @@ public class MFXCheckbox extends CheckBox {
     private final String STYLE_CLASS = "mfx-checkbox";
     private final String STYLESHEET = MFXResourcesLoader.load("css/MFXCheckBox.css");
 
+    private final ObjectProperty<ContentDisplay> contentDisposition = new SimpleObjectProperty<>(ContentDisplay.LEFT);
+
     //================================================================================
     // Constructors
     //================================================================================
     public MFXCheckbox() {
-        setText("CheckBox");
-        initialize();
+        this("");
     }
 
     public MFXCheckbox(String text) {
@@ -64,102 +66,45 @@ public class MFXCheckbox extends CheckBox {
         getStyleClass().add(STYLE_CLASS);
     }
 
-    //================================================================================
-    // Stylesheet properties
-    //================================================================================
-
-    /**
-     * Specifies the color of the box when it's checked.
-     *
-     * @see Color
-     */
-    private final StyleableObjectProperty<Paint> checkedColor = new SimpleStyleableObjectProperty<>(
-            StyleableProperties.CHECKED_COLOR,
-            this,
-            "checkedColor",
-            Color.rgb(15, 157, 88)
-    );
-
-    /**
-     * Specifies the color of the box when it's unchecked.
-     *
-     * @see Color
-     */
-    private final StyleableObjectProperty<Paint> uncheckedColor = new SimpleStyleableObjectProperty<>(
-            StyleableProperties.UNCHECKED_COLOR,
-            this,
-            "uncheckedColor",
-            Color.rgb(90, 90, 90)
-    );
-
-    /**
-     * Specifies the shape of the mark from a predefined set.
-     *
-     * @see javafx.scene.shape.SVGPath
-     */
-    private final StyleableStringProperty markType = new SimpleStyleableStringProperty(
-            StyleableProperties.MARK_TYPE,
-            this,
-            "markType",
-            "mfx-modena-mark"
-    );
+    public ContentDisplay getContentDisposition() {
+        return contentDisposition.get();
+    }
 
     /**
-     * Specifies the size of the mark.
+     * Specifies how the checkbox is positioned relative to the text.
      */
-    private final StyleableDoubleProperty markSize = new SimpleStyleableDoubleProperty(
-            StyleableProperties.MARK_SIZE,
-            this,
-            "markSize",
-            12.0
-    );
-
-    public Paint getCheckedColor() {
-        return checkedColor.get();
+    public ObjectProperty<ContentDisplay> contentDispositionProperty() {
+        return contentDisposition;
     }
 
-    public StyleableObjectProperty<Paint> checkedColorProperty() {
-        return checkedColor;
+    public void setContentDisposition(ContentDisplay contentDisposition) {
+        this.contentDisposition.set(contentDisposition);
     }
 
-    public void setCheckedColor(Paint checkedColor) {
-        this.checkedColor.set(checkedColor);
-    }
-
-    public Paint getUncheckedColor() {
-        return uncheckedColor.get();
-    }
-
-    public StyleableObjectProperty<Paint> uncheckedColorProperty() {
-        return uncheckedColor;
-    }
-
-    public void setUncheckedColor(Paint uncheckedColor) {
-        this.uncheckedColor.set(uncheckedColor);
-    }
-
-    public String getMarkType() {
-        return markType.get();
-    }
-
-    public StyleableStringProperty markTypeProperty() {
-        return markType;
-    }
+    //================================================================================
+    // Stylesheet properties
+    //================================================================================
 
-    public void setMarkType(String markType) {
-        this.markType.set(markType);
-    }
+    private final StyleableDoubleProperty gap = new SimpleStyleableDoubleProperty(
+            StyleableProperties.GAP,
+            this,
+            "gap",
+            8.0
+    );
 
-    public double getMarkSize() {
-        return markSize.get();
+    public double getGap() {
+        return gap.get();
     }
 
-    public StyleableDoubleProperty markSizeProperty() {
-        return markSize;
+    /**
+     * Specifies the spacing between the checkbox and the text.
+     */
+    public StyleableDoubleProperty gapProperty() {
+        return gap;
     }
 
-    public void setMarkSize(double markSize) {
-        this.markSize.set(markSize);
+    public void setGap(double gap) {
+        this.gap.set(gap);
     }
 
     //================================================================================
@@ -168,37 +113,16 @@ public class MFXCheckbox extends CheckBox {
     private static class StyleableProperties {
         private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
 
-        private static final CssMetaData<MFXCheckbox, Paint> CHECKED_COLOR =
-                FACTORY.createPaintCssMetaData(
-                        "-mfx-checked-color",
-                        MFXCheckbox::checkedColorProperty,
-                        Color.rgb(15, 157, 88)
-                );
-
-        private static final CssMetaData<MFXCheckbox, Paint> UNCHECKED_COLOR =
-                FACTORY.createPaintCssMetaData(
-                        "-mfx-unchecked-color",
-                        MFXCheckbox::uncheckedColorProperty,
-                        Color.rgb(90, 90, 90)
-                );
-
-        private static final CssMetaData<MFXCheckbox, String> MARK_TYPE =
-                FACTORY.createStringCssMetaData(
-                        "-mfx-mark-type",
-                        MFXCheckbox::markTypeProperty,
-                        "mfx-modena-mark"
-                );
-
-        private static final CssMetaData<MFXCheckbox, Number> MARK_SIZE =
+        private static final CssMetaData<MFXCheckbox, Number> GAP =
                 FACTORY.createSizeCssMetaData(
-                        "-mfx-mark-size",
-                        MFXCheckbox::markSizeProperty,
-                        12
+                        "-mfx-gap",
+                        MFXCheckbox::gapProperty,
+                        8.0
                 );
 
         static {
             List<CssMetaData<? extends Styleable, ?>> ckbCssMetaData = new ArrayList<>(CheckBox.getClassCssMetaData());
-            Collections.addAll(ckbCssMetaData, CHECKED_COLOR, UNCHECKED_COLOR, MARK_TYPE, MARK_SIZE);
+            Collections.addAll(ckbCssMetaData, GAP);
             cssMetaDataList = Collections.unmodifiableList(ckbCssMetaData);
         }
     }

+ 3 - 3
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java

@@ -19,8 +19,8 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
-import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.beans.PositionBean;
+import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.skins.MFXDatePickerContent;
 import io.github.palexdev.materialfx.utils.NodeUtils;
@@ -108,7 +108,7 @@ public class MFXDatePicker extends VBox {
         value.setMinWidth(64);
         calendar = new MFXFontIcon("mfx-calendar-semi-black");
         calendar.getStyleClass().add("calendar-icon");
-        calendar.setColor(getPickerColor());
+        calendar.setColor((Color) getPickerColor());
         calendar.setSize(20);
         stackPane = new StackPane(value, calendar);
         stackPane.setPadding(new Insets(5, -2.5, 5, 5));
@@ -247,7 +247,7 @@ public class MFXDatePicker extends VBox {
                 calendar.setColor(Color.LIGHTGRAY);
             } else {
                 line.setStroke(getLineColor());
-                calendar.setColor(getPickerColor());
+                calendar.setColor((Color) getPickerColor());
             }
         });
 

+ 15 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java

@@ -28,6 +28,7 @@ import javafx.beans.property.SimpleDoubleProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.geometry.Insets;
 import javafx.scene.Node;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
@@ -222,4 +223,18 @@ public class MFXIconWrapper extends StackPane {
     public ObservableList<Node> getChildren() {
         return FXCollections.unmodifiableObservableList(super.getChildren());
     }
+
+    @Override
+    protected void layoutChildren() {
+        super.layoutChildren();
+
+        if (getSize() == -1) {
+            Node icon = getIcon();
+            double iW = icon.prefWidth(-1);
+            double iH = icon.prefHeight(-1);
+            Insets padding = getPadding();
+            double size = Math.max(padding.getLeft() + iW + padding.getRight(), padding.getTop() + iH + padding.getBottom());
+            setSize(size);
+        }
+    }
 }

+ 0 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java

@@ -75,8 +75,6 @@ public class MFXCheckTreeCell<T> extends MFXSimpleTreeCell<T> {
         addListeners();
         checked.bind(item.checkedProperty());
         indeterminate.bind(item.indeterminateProperty());
-        checkbox.setMarkType("mfx-variant3-mark");
-        checkbox.setMarkSize(8);
     }
 
     /**

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

@@ -48,7 +48,6 @@ public class MFXNotificationCell extends HBox implements Cell<INotification> {
 
         checkbox = new MFXCheckbox("");
         checkbox.setId("check");
-        checkbox.setMarkType("mfx-variant7-mark");
 
         container = new StackPane(checkbox);
         container.setMinWidth(USE_PREF_SIZE);

+ 49 - 48
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: 101)
+ * Enumerator class for MaterialFX font resources. (Count: 102)
  */
 public enum FontResources {
     ANGLE_DOWN("mfx-angle-down", '\uE900'),
@@ -77,53 +77,54 @@ public enum FontResources {
     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')
-    ;
+    HYPHEN("mfx-hyphen", '\uE937'),
+    IMAGE("mfx-image", '\uE938'),
+    INFO("mfx-info", '\uE939'),
+    INFO_CIRCLE("mfx-info-circle", '\uE93A'),
+    LAST_PAGE("mfx-last-page", '\uE93B'),
+    LEVEL_UP("mfx-level-up", '\uE93C'),
+    LOCK("mfx-lock", '\uE93D'),
+    LOCK_OPEN("mfx-lock-open", '\uE93E'),
+    MAP("mfx-map", '\uE93F'),
+    MINUS("mfx-minus", '\uE940'),
+    MINUS_CIRCLE("mfx-minus-circle", '\uE941'),
+    MODENA_MARK("mfx-modena-mark", '\uE942'),
+    MUSIC("mfx-music", '\uE943'),
+    NEXT("mfx-next", '\uE944'),
+    REDO("mfx-redo", '\uE945'),
+    RESTORE("mfx-restore", '\uE946'),
+    SEARCH("mfx-search", '\uE947'),
+    SEARCH_PLUS("mfx-search-plus", '\uE948'),
+    SELECT_ALL("mfx-select-all", '\uE949'),
+    SHORTCUT("mfx-shortcut", '\uE94A'),
+    SLIDERS("mfx-sliders", '\uE94B'),
+    SPREADSHEET("mfx-spreadsheet", '\uE94C'),
+    STEP_BACKWARD("mfx-step-backward", '\uE94D'),
+    STEP_FORWARD("mfx-step-forward", '\uE94E'),
+    SYNC("mfx-sync", '\uE94F'),
+    SYNC_LIGHT("mfx-sync-light", '\uE950'),
+    UNDO("mfx-undo", '\uE951'),
+    USER("mfx-user", '\uE952'),
+    USERS("mfx-users", '\uE953'),
+    VARIANT10_MARK("mfx-variant10-mark", '\uE954'),
+    VARIANT11_MARK("mfx-variant11-mark", '\uE955'),
+    VARIANT12_MARK("mfx-variant12-mark", '\uE956'),
+    VARIANT13_MARK("mfx-variant13-mark", '\uE957'),
+    VARIANT14_MARK("mfx-variant14-mark", '\uE958'),
+    VARIANT3_MARK("mfx-variant3-mark", '\uE959'),
+    VARIANT4_MARK("mfx-variant4-mark", '\uE95A'),
+    VARIANT5_MARK("mfx-variant5-mark", '\uE95B'),
+    VARIANT6_MARK("mfx-variant6-mark", '\uE95C'),
+    VARIANT7_MARK("mfx-variant7-mark", '\uE95D'),
+    VARIANT8_MARK("mfx-variant8-mark", '\uE95E'),
+    VARIANT9_MARK("mfx-variant9-mark", '\uE95F'),
+    VIDEO("mfx-video", '\uE960'),
+    X("mfx-x", '\uE961'),
+    X_ALT("mfx-x-alt", '\uE962'),
+    X_CIRCLE("mfx-x-circle", '\uE963'),
+    X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE964'),
+    X_LIGHT("mfx-x-light", '\uE965');
+
 
     public static FontResources findByDescription(String description) {
         for (FontResources font : values()) {

+ 71 - 50
materialfx/src/main/java/io/github/palexdev/materialfx/font/MFXFontIcon.java

@@ -18,13 +18,15 @@
 
 package io.github.palexdev.materialfx.font;
 
+import javafx.beans.binding.Bindings;
 import javafx.css.*;
 import javafx.scene.paint.Color;
-import javafx.scene.paint.Paint;
 import javafx.scene.text.Font;
 import javafx.scene.text.FontSmoothingType;
 import javafx.scene.text.Text;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -41,7 +43,7 @@ public class MFXFontIcon extends Text {
     // Constructors
     //================================================================================
     public MFXFontIcon() {
-        initialize();
+        this(null);
     }
 
     public MFXFontIcon(String description) {
@@ -53,17 +55,14 @@ public class MFXFontIcon extends Text {
     }
 
     public MFXFontIcon(String description, double size) {
-        this(description, size, Color.rgb(117, 117, 117));
+        this(description, size, Color.web("#454545"));
     }
 
     public MFXFontIcon(String description, double size, Color color) {
         initialize();
-
         setDescription(description);
-        setSize(size);
+        setFont(Font.font(getFont().getFamily(), size));
         setColor(color);
-
-        setText(String.valueOf(FontHandler.getCode(description)));
     }
 
     //================================================================================
@@ -74,19 +73,24 @@ public class MFXFontIcon extends Text {
         setFont(FontHandler.getResources());
         setFontSmoothingType(FontSmoothingType.GRAY);
 
-        sizeProperty().addListener((observable, oldValue, newValue) -> {
-            Font font = getFont();
-            setFont(Font.font(font.getFamily(), newValue.doubleValue()));
-        });
+        textProperty().bind(Bindings.createStringBinding(
+                () -> {
+                    String desc = getDescription();
+                    return desc != null && !desc.isBlank() ? descriptionToString(desc) : "";
+                }, description
+        ));
 
-        colorProperty().addListener((observable, oldValue, newValue) -> setFill(newValue));
+        fillProperty().bind(colorProperty());
+        sizeProperty().addListener((observable, oldValue, newValue) -> setFontSize(newValue.doubleValue()));
+    }
+
+    private String descriptionToString(String desc) {
+        return String.valueOf(FontHandler.getCode(desc));
+    }
 
-        descriptionProperty().addListener((observable, oldValue, newValue) -> {
-            if (newValue != null) {
-                final char character = FontHandler.getCode(newValue);
-                setText(String.valueOf(character));
-            }
-        });
+    private void setFontSize(double size) {
+        String fontFamily = getFont().getFamily();
+        setFont(Font.font(fontFamily, size));
     }
 
     /**
@@ -102,25 +106,55 @@ public class MFXFontIcon extends Text {
     //================================================================================
     // Styleable Properties
     //================================================================================
+    private final StyleableObjectProperty<Color> color = new SimpleStyleableObjectProperty<>(
+            StyleableProperties.COLOR,
+            this,
+            "color",
+            Color.web("#454545")
+    ) {
+        @Override
+        public StyleOrigin getStyleOrigin() {
+            return StyleOrigin.USER_AGENT;
+        }
+    };
+
     private final StyleableStringProperty description = new SimpleStyleableStringProperty(
             StyleableProperties.DESCRIPTION,
             this,
             "description"
-    );
+    ) {
+        @Override
+        public StyleOrigin getStyleOrigin() {
+            return StyleOrigin.USER_AGENT;
+        }
+    };
 
     private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty(
             StyleableProperties.SIZE,
             this,
             "size",
             10.0
-    );
+    ) {
+        @Override
+        public StyleOrigin getStyleOrigin() {
+            return StyleOrigin.USER_AGENT;
+        }
+    };
 
-    private final StyleableObjectProperty<Paint> color = new SimpleStyleableObjectProperty<>(
-            StyleableProperties.COLOR,
-            this,
-            "color",
-            Color.rgb(117, 117, 117)
-    );
+    public Color getColor() {
+        return color.get();
+    }
+
+    /**
+     * Specifies the color of the icon.
+     */
+    public StyleableObjectProperty<Color> colorProperty() {
+        return color;
+    }
+
+    public void setColor(Color color) {
+        this.color.set(color);
+    }
 
     public String getDescription() {
         return description.get();
@@ -152,30 +186,22 @@ public class MFXFontIcon extends Text {
         this.size.set(size);
     }
 
-    public Paint getColor() {
-        return color.get();
-    }
-
-    /**
-     * Specifies the color of the icon.
-     */
-    public StyleableObjectProperty<Paint> colorProperty() {
-        return color;
-    }
-
-    public void setColor(Paint color) {
-        this.color.set(color);
-    }
-
     //================================================================================
     // CssMetaData
     //================================================================================
     public static class StyleableProperties {
         private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
 
+        private static final CssMetaData<MFXFontIcon, Color> COLOR =
+                FACTORY.createColorCssMetaData(
+                        "-mfx-color",
+                        MFXFontIcon::colorProperty,
+                        Color.web("#454545")
+                );
+
         private static final CssMetaData<MFXFontIcon, String> DESCRIPTION =
                 FACTORY.createStringCssMetaData(
-                        "-mfx-icon-code",
+                        "-mfx-description",
                         MFXFontIcon::descriptionProperty
                 );
 
@@ -186,15 +212,10 @@ public class MFXFontIcon extends Text {
                         10
                 );
 
-        private static final CssMetaData<MFXFontIcon, Paint> COLOR =
-                FACTORY.createPaintCssMetaData(
-                        "-mfx-color",
-                        MFXFontIcon::colorProperty,
-                        Color.rgb(117, 117, 117)
-                );
-
         static {
-            cssMetaDataList = List.of(DESCRIPTION, SIZE, COLOR);
+            List<CssMetaData<? extends Styleable, ?>> txtCssMetaData = new ArrayList<>(Text.getClassCssMetaData());
+            Collections.addAll(txtCssMetaData, COLOR, DESCRIPTION, SIZE);
+            cssMetaDataList = Collections.unmodifiableList(txtCssMetaData);
         }
     }
 

+ 149 - 161
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java

@@ -18,20 +18,18 @@
 
 package io.github.palexdev.materialfx.skins;
 
+import io.github.palexdev.materialfx.beans.PositionBean;
+import io.github.palexdev.materialfx.controls.LabeledControlWrapper;
 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.beans.PositionBean;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
-import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.geometry.Insets;
-import javafx.geometry.Pos;
-import javafx.scene.control.Label;
+import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.SkinBase;
 import javafx.scene.input.MouseEvent;
-import javafx.scene.layout.*;
-import javafx.scene.paint.Color;
-import javafx.scene.text.Font;
+import javafx.scene.layout.StackPane;
+import javafx.scene.shape.Circle;
 
 /**
  * This is the implementation of the {@code Skin} associated with every {@link MFXCheckbox}.
@@ -40,13 +38,11 @@ public class MFXCheckboxSkin extends SkinBase<MFXCheckbox> {
     //================================================================================
     // Properties
     //================================================================================
-    private final HBox container;
-    private final Label label;
     private final MFXIconWrapper box;
-    private final double boxSize = 27;
+    private final LabeledControlWrapper text;
 
-    private final AnchorPane rippleContainer;
-    private final double rippleContainerSize = 31;
+    private final StackPane rippleContainer;
+    private final Circle rippleContainerClip;
     private final MFXCircleRippleGenerator rippleGenerator;
 
     //================================================================================
@@ -55,16 +51,15 @@ public class MFXCheckboxSkin extends SkinBase<MFXCheckbox> {
     public MFXCheckboxSkin(MFXCheckbox checkbox) {
         super(checkbox);
 
-        // Contains the ripple generator and the box
-        rippleContainer = new AnchorPane();
-        rippleContainer.setPrefSize(rippleContainerSize, rippleContainerSize);
-        rippleContainer.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
-        rippleContainer.getStyleClass().setAll("ripple-container");
-        NodeUtils.makeRegionCircular(rippleContainer);
+        MFXFontIcon mark = new MFXFontIcon();
+        mark.getStyleClass().add("mark");
+        box = new MFXIconWrapper(mark, -1);
+        box.getStyleClass().add("box");
 
+        rippleContainer = new StackPane();
         rippleGenerator = new MFXCircleRippleGenerator(rippleContainer);
+        rippleGenerator.setManaged(false);
         rippleGenerator.setAnimateBackground(false);
-        rippleGenerator.setAnimationSpeed(1.5);
         rippleGenerator.setCheckBounds(false);
         rippleGenerator.setClipSupplier(() -> null);
         rippleGenerator.setRipplePositionFunction(event -> {
@@ -73,180 +68,173 @@ public class MFXCheckboxSkin extends SkinBase<MFXCheckbox> {
             position.setY(Math.min(event.getY(), rippleContainer.getHeight()));
             return position;
         });
-        rippleGenerator.setRippleRadius(16);
 
-        // Contains the mark
-        MFXFontIcon icon = new MFXFontIcon(checkbox.getMarkType(), checkbox.getMarkSize(), Color.WHITE);
-        icon.getStyleClass().add("mark");
-        box = new MFXIconWrapper(icon, boxSize);
-        box.getStyleClass().add("box");
-
-        box.setBorder(new Border(new BorderStroke(
-                checkbox.getUncheckedColor(),
-                BorderStrokeStyle.SOLID,
-                new CornerRadii(2.5),
-                new BorderWidths(1.8)
-        )));
-        box.setBackground(new Background(new BackgroundFill(
-                Color.TRANSPARENT,
-                new CornerRadii(2.5),
-                Insets.EMPTY
-        )));
-
-        label = new Label();
-        label.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
-        label.textProperty().bind(checkbox.textProperty());
+        rippleContainer.getChildren().addAll(rippleGenerator, box);
+        rippleContainer.getStyleClass().add("ripple-container");
+        rippleContainer.setManaged(false);
 
+        rippleContainerClip = new Circle();
+        rippleContainerClip.centerXProperty().bind(rippleContainer.widthProperty().divide(2.0));
+        rippleContainerClip.centerYProperty().bind(rippleContainer.heightProperty().divide(2.0));
+        rippleContainer.setClip(rippleContainerClip);
 
-        rippleContainer.getChildren().addAll(rippleGenerator, box);
-        container = new HBox(10, rippleContainer, label);
-        container.setAlignment(Pos.CENTER_LEFT);
-        getChildren().add(container);
+        text = new LabeledControlWrapper(checkbox);
 
-        updateMarkType();
-        setListeners();
+        getChildren().setAll(rippleContainer, text);
+        addListeners();
     }
 
     //================================================================================
     // Methods
     //================================================================================
+    private void addListeners() {
+        MFXCheckbox checkbox = getSkinnable();
 
-    /**
-     * Adds listeners for: markType, selected, indeterminate, checked and unchecked coloros properties.
-     */
-    private void setListeners() {
-        MFXCheckbox checkBox = getSkinnable();
-
-        checkBox.markTypeProperty().addListener(
-                (observable, oldValue, newValue) -> updateMarkType()
-        );
-
-        checkBox.markSizeProperty().addListener(
-                (observable, oldValue, newValue) -> ((MFXFontIcon) box.getIcon()).setFont(Font.font(newValue.doubleValue())));
-
-        checkBox.selectedProperty().addListener(
-                (observable, oldValue, newValue) -> updateColors()
-        );
-
-        checkBox.indeterminateProperty().addListener(
-                (observable, oldValue, newValue) -> updateColors()
-        );
-
-        checkBox.checkedColorProperty().addListener(
-                (observable, oldValue, newValue) -> updateColors()
-        );
-
-        checkBox.uncheckedColorProperty().addListener(
-                (observable, oldValue, newValue) -> updateColors()
-        );
-
-        /* Listener on control but if the coordinates of the event are greater than then ripple container size
-         * then the center of the ripple is set to the width and/or height of container
-         */
-        checkBox.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
-            if (!NodeUtils.inHierarchy(event, checkBox)) {
-                return;
-            }
-
+        checkbox.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
             rippleGenerator.generateRipple(event);
-            checkBox.fire();
+            checkbox.fire();
         });
 
-        /*
-         * Workaround
-         * When the control is created the Skin is still null, so if the CheckBox is set
-         * to be selected/indeterminate the animation won't be played. To fix this add a listener to the
-         * control's skinProperty, when the skin is not null and the CheckBox isSelected/isIndeterminate,
-         * play the animation.
-         */
-        NodeUtils.waitForSkin(checkBox, this::updateColors, true, false);
+        checkbox.contentDispositionProperty().addListener(invalidated -> checkbox.requestLayout());
+        checkbox.gapProperty().addListener(invalidated -> checkbox.requestLayout());
     }
 
-
-    /**
-     * This method is called whenever one of the following properties changes:
-     * {@code selectedProperty}, {@code indeterminateProperty}, {@code checkedColor} and {@code uncheckedColor} properties
-     *
-     * @see NodeUtils
-     */
-    private void updateColors() {
+    //================================================================================
+    // Overridden Methods
+    //================================================================================
+    @Override
+    protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
         MFXCheckbox checkbox = getSkinnable();
-
-        final BorderStroke borderStroke = box.getBorder().getStrokes().get(0);
-        if (checkbox.isIndeterminate()) {
-            NodeUtils.updateBackground(box, checkbox.getCheckedColor(), new Insets(3.2));
-            box.getIcon().setVisible(false);
-        } else if (checkbox.isSelected()) {
-            NodeUtils.updateBackground(box, checkbox.getCheckedColor(), Insets.EMPTY);
-            box.getIcon().setVisible(true);
-            box.setBorder(new Border(new BorderStroke(
-                    checkbox.getCheckedColor(),
-                    borderStroke.getTopStyle(),
-                    borderStroke.getRadii(),
-                    borderStroke.getWidths()
-            )));
-        } else {
-            NodeUtils.updateBackground(box, Color.TRANSPARENT);
-            box.getIcon().setVisible(false);
-            box.setBorder(new Border(new BorderStroke(
-                    checkbox.getUncheckedColor(),
-                    borderStroke.getTopStyle(),
-                    borderStroke.getRadii(),
-                    borderStroke.getWidths()
-            )));
+        ContentDisplay disposition = checkbox.getContentDisposition();
+        double minW;
+        switch (disposition) {
+            case LEFT:
+            case RIGHT:
+            case TEXT_ONLY:
+                minW = leftInset + rippleContainer.prefWidth(-1) + getSkinnable().getGap() + text.prefWidth(-1) + rightInset;
+                break;
+            case TOP:
+            case BOTTOM:
+                minW = leftInset + Math.max(rippleContainer.prefWidth(-1), text.prefWidth(-1)) + rightInset;
+                break;
+            case CENTER:
+            case GRAPHIC_ONLY:
+                minW = leftInset + rippleContainer.prefWidth(-1) + rightInset;
+                break;
+            default:
+                minW = super.computeMinWidth(height, topInset, rightInset, bottomInset, leftInset);
         }
+        return minW;
     }
 
-    /**
-     * This method is called whenever the {@code markType} property changes.
-     */
-    private void updateMarkType() {
-        MFXCheckbox checkbox = getSkinnable();
-
-        MFXFontIcon icon = new MFXFontIcon(checkbox.getMarkType(), checkbox.getMarkSize(), Color.WHITE);
-        box.setIcon(icon);
-    }
-
-    /**
-     * Centers the box in the ripple container
-     */
-    private void centerBox() {
-        final double offsetPercentage = 3;
-        final double vInset = ((rippleContainerSize - boxSize) / 2) * offsetPercentage;
-        final double hInset = ((rippleContainerSize - boxSize) / 2) * offsetPercentage;
-        AnchorPane.setTopAnchor(box, vInset);
-        AnchorPane.setRightAnchor(box, hInset);
-        AnchorPane.setBottomAnchor(box, vInset);
-        AnchorPane.setLeftAnchor(box, hInset);
-    }
-
-    //================================================================================
-    // Override Methods
-    //================================================================================
     @Override
     protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
-        return rippleContainer.getHeight();
+        MFXCheckbox checkbox = getSkinnable();
+        ContentDisplay disposition = checkbox.getContentDisposition();
+        double minH;
+        switch (disposition) {
+            case LEFT:
+            case RIGHT:
+            case TEXT_ONLY:
+                minH = topInset + Math.max(rippleContainer.prefHeight(-1), text.prefHeight(-1)) + bottomInset;
+                break;
+            case TOP:
+            case BOTTOM:
+                minH = topInset + rippleContainer.prefHeight(-1) + getSkinnable().getGap() + text.prefHeight(-1) + bottomInset;
+                break;
+            case CENTER:
+            case GRAPHIC_ONLY:
+                minH = leftInset + rippleContainer.prefHeight(-1) + rightInset;
+                break;
+            default:
+                minH = super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
+        }
+        return minH;
     }
 
     @Override
-    protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
-        return rippleContainer.getWidth() + label.getWidth() + container.getSpacing();
+    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 rippleContainer.getHeight();
-    }
-
-    @Override
-    protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
-        return getSkinnable().prefWidth(height);
+        return getSkinnable().prefHeight(-1);
     }
 
     @Override
     protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
-        super.layoutChildren(contentX, contentY, contentWidth, contentHeight);
+        MFXCheckbox checkbox = getSkinnable();
+        ContentDisplay disposition = checkbox.getContentDisposition();
+        Insets padding = checkbox.getPadding();
+        double gap = checkbox.getGap();
+
+        double rcW = rippleContainer.prefWidth(-1);
+        double rcH = rippleContainer.prefHeight(-1);
+        double rcX = 0;
+        double rcY = 0;
+
+        double txW = text.prefWidth(-1);
+        double txH = text.prefHeight(-1);
+        double txX = 0;
+        double txY = 0;
+
+        switch (disposition) {
+            case TOP: {
+                rcX = (contentWidth / 2) - (rcW / 2);
+                rcY = 0;
+                txX = (contentWidth / 2) - (txW / 2);
+                txY = rcH + gap;
+                break;
+            }
+            case RIGHT: {
+                rcX = contentWidth - rcW;
+                rcY = (contentHeight / 2) - (rcH / 2);
+                txX = rcX - txW - gap;
+                txY = (contentHeight / 2) - (txH / 2);
+                break;
+            }
+            case BOTTOM: {
+                txX = (contentWidth / 2) - (txW / 2);
+                txY = 0;
+                rcX = (contentWidth / 2) - (rcW / 2);
+                rcY = txH + gap;
+                break;
+            }
+            case TEXT_ONLY:
+            case LEFT: {
+                rcX = 0;
+                rcY = (contentHeight / 2) - (rcH / 2);
+                txX = rcW + gap;
+                txY = (contentHeight / 2) - (txH / 2);
+                break;
+            }
+            case CENTER:
+            case GRAPHIC_ONLY: {
+                rcX = (contentWidth / 2) - (rcW / 2);
+                rcY = (contentHeight / 2) - (rcH / 2);
+                txW = 0;
+                txH = 0;
+                break;
+            }
+        }
+
+        rippleContainer.resizeRelocate(
+                snapPositionX(rcX + padding.getLeft()),
+                snapPositionY(rcY + padding.getTop()),
+                rcW,
+                rcH
+        );
+        text.resizeRelocate(
+                snapPositionX(txX + padding.getLeft()),
+                snapPositionY(txY + padding.getTop()),
+                txW,
+                txH
+        );
 
-        centerBox();
+        double boxSize = box.getSize();
+        Insets boxPadding = box.getPadding();
+        double boxClipRadius = boxPadding.getLeft() + boxSize / 2 + boxPadding.getRight();
+        rippleContainerClip.setRadius(boxClipRadius);
     }
 }

+ 44 - 6
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckBox.css

@@ -16,15 +16,53 @@
  * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/*================================================================================*/
-/* Ripple
-/*================================================================================*/
 .mfx-checkbox {
-    -fx-border-insets: -1 -3 -1 -1;
-    -fx-background-insets: -1 -3 -1 -1;
+    -mfx-main: #6200EE;
+    -mfx-gray: #424242;
+}
+
+.mfx-checkbox .box {
+    -fx-background-color: transparent;
+    -fx-background-radius: 2;
+
+    -fx-border-color: -mfx-gray;
+    -fx-border-radius: 2;
+    -fx-border-width: 1.5;
+
+    -fx-padding: 4;
+}
+
+.mfx-checkbox .box .mark {
+    visibility: hidden;
+    -mfx-color: white;
+    -mfx-description: "mfx-variant7-mark";
+    -mfx-size: 12;
 }
 
 .mfx-checkbox .ripple-container .mfx-ripple-generator {
-    -mfx-ripple-color: rgb(190, 190, 190);
+    -mfx-animation-speed: 1.5;
+    -mfx-ripple-color: derive(-mfx-main, 110%);
+    -mfx-ripple-radius: 16;
+}
+
+.mfx-checkbox:selected .box,
+.mfx-checkbox:indeterminate .box {
+    -fx-background-color: -mfx-main;
+    -fx-border-color: -mfx-main;
+}
+
+.mfx-checkbox:selected .box .mark {
+    visibility: visible;
+}
+
+.mfx-checkbox:selected:disabled .box,
+.mfx-checkbox:indeterminate:disabled .box {
+    -fx-background-color: -mfx-gray;
+    -fx-border-color: -mfx-gray;
+}
+
+.mfx-checkbox:indeterminate .box .mark {
+    visibility: visible;
+    -mfx-description: "mfx-minus";
 }
 

+ 5 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeCell.css

@@ -21,6 +21,11 @@
     -fx-background-radius: 7px;
 }
 
+.mfx-tree-cell .mfx-checkbox .box .mark {
+    -mfx-description: "mfx-variant3-mark";
+    -mfx-size: 8;
+}
+
 .mfx-tree-cell .mfx-ripple-generator {
     -mfx-ripple-color: rgba(0, 190, 0, 0.3);
 }

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