Kaynağa Gözat

:boom: Huge update [Part 5]

Demo
module-info.java:
:bug: jdk.localedata module is required to get the correct Locale in DatePickers

ComboBoxesDemoController:
:recycle: Minor change to show the force search field focus feature

MaterialFX
FontResources:
:sparkles: Added new resources

MFXComboBox:
:sparkles: Added icons to the context menu

MFXContextMenu:
:boom: The MFXContextMenu has been completely remade, now it's simpler than ever
:boom: To build a MFXContextMenu now you must specify its owner, this is needed to open the popup. Since the owner is a property it's easy to change the change the context menu owner.
:boom: Instead of having a list containing all the separators, the context menu now has a list containing all the context menu items and the separators. To change the context menu items you must use the setItems() method as the getChildren() method has been overridden to return an unmodifiable list.
:boom: The Builder has been reworked as well. Now it must be built by invoking the static methods "build(...)" thus removing the necessity of using "new"

MFXContextMenuItem:
:boom: The MFXContextMenuItem has been completely remade. It is not a simple bean anymore. It extends Control and defines its own skin. It offers several features such as: set an icon, text, accelerator, text and accelerator alignment, text and accelerator padding, tooltip and a property to specify the action to perform on mouse pressed.
:boom: Since it now extends Control it has its own stylesheet
:boom: Since it now extends Control it has its own skin. The skin is a made using a GridPane which contains a MFXIconWrapper to contain the item's icon, and two labels for the text and the accelerator text. The GridPane ensures that all the items in the context menu are perfectly aligned.
:boom: The control is completely made with fluent design in mind. Every setter returns the instance of the item.

MFXDatePickerContent:
:recycle: Use getters and setters
:recycle: Simplify firstDayIndex() and createDayNameCells()

MFXFilterComboBoxSkin:
:bug: The control should reset when the popup is closed, fixed
:bug: Remove the context menu from the search field

MFXIconWrapper:
:recycle: Generate ripple only if the pressed mouse button is the PRIMARY

MFXLabelSkin:
:bug: Remove the context menu from the text field

MFXPasswordFieldSkin:
:sparkles: Added icons to context menu

MFXTableColumnSkin:
:recycle: Lock icon execute action only if the pressed mouse button is the PRIMARY
:recycle: Update the icon according to the column's resizable property

MFXTableViewSkin:
:sparkles: Added icons to context menu

MFXTextField:
:sparkles: Added icons to context menu

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
palexdev 4 yıl önce
ebeveyn
işleme
ebc3bf56e0
25 değiştirilmiş dosya ile 1006 ekleme ve 593 silme
  1. 2 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ComboBoxesDemoController.java
  2. 0 6
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TextFieldsDemoController.java
  3. 2 0
      demo/src/main/java/module-info.java
  4. 13 16
      demo/src/main/resources/io/github/palexdev/materialfx/demo/LabelsDemo.fxml
  5. 9 5
      demo/src/main/resources/io/github/palexdev/materialfx/demo/ListViewsDemo.fxml
  6. 20 36
      demo/src/main/resources/io/github/palexdev/materialfx/demo/TextFieldsDemo.fxml
  7. 0 89
      materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXContextMenuItem.java
  8. 27 30
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java
  9. 218 189
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXContextMenu.java
  10. 336 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXContextMenuItem.java
  11. 11 2
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java
  12. 38 36
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java
  13. 1 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXDialogFactory.java
  14. 74 64
      materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java
  15. 2 1
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java
  16. 96 0
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXContextMenuItemSkin.java
  17. 43 39
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java
  18. 8 2
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java
  19. 1 0
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXLabelSkin.java
  20. 32 26
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPasswordFieldSkin.java
  21. 10 3
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableColumnSkin.java
  22. 33 34
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java
  23. 5 14
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenu.css
  24. 25 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenuItem.css
  25. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf

+ 2 - 0
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ComboBoxesDemoController.java

@@ -160,6 +160,8 @@ public class ComboBoxesDemoController implements Initializable {
         filtersValidated.setValidated(true);
         filtersValidated.getValidator().add(BindingUtils.toProperty(filtersValidated.getSelectionModel().selectedIndexProperty().isNotEqualTo(-1)), "A value must be selected");
         filtersValidated.getValidator().add(checkbox.selectedProperty(), "Checkbox must be selected");
+
+        filters3.setForceFieldFocusOnShow(true);
     }
 
     private FontIcon createIcon(String s) {

+ 0 - 6
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TextFieldsDemoController.java

@@ -29,7 +29,6 @@ import javafx.beans.binding.Bindings;
 import javafx.beans.property.BooleanProperty;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
-import javafx.scene.control.Label;
 import javafx.scene.paint.Color;
 
 import java.net.URL;
@@ -48,9 +47,6 @@ public class TextFieldsDemoController implements Initializable {
     @FXML
     private MFXDatePicker picker;
 
-    @FXML
-    private Label label;
-
     @FXML
     private MFXPasswordField passwordValidated;
 
@@ -81,8 +77,6 @@ public class TextFieldsDemoController implements Initializable {
         validated.setIcon(new MFXFontIcon("mfx-variant7-mark", 16, Color.web("#8FF7A7")));
         validated.getIcon().visibleProperty().bind(validated.getValidator().validProperty());
 
-        label.visibleProperty().bind(validated.getValidator().validProperty());
-
         passwordValidated.setValidated(true);
         passwordValidated.getValidator().add(
                 BindingUtils.toProperty(passwordValidated.passwordProperty().length().greaterThanOrEqualTo(8)),

+ 2 - 0
demo/src/main/java/module-info.java

@@ -1,6 +1,8 @@
 module MaterialFX.Demo {
     requires MaterialFX;
 
+    requires jdk.localedata;
+
     requires javafx.controls;
     requires javafx.fxml;
     requires javafx.graphics;

+ 13 - 16
demo/src/main/resources/io/github/palexdev/materialfx/demo/LabelsDemo.fxml

@@ -4,35 +4,32 @@
 <?import javafx.geometry.*?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.*?>
-<StackPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/15.0.1"
-           xmlns:fx="http://javafx.com/fxml/1"
-           fx:controller="io.github.palexdev.materialfx.demo.controllers.LabelsDemoController">
-   <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Labels"
-          StackPane.alignment="TOP_CENTER">
+<StackPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.LabelsDemoController">
+   <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Labels" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets top="20.0"/>
+         <Insets top="20.0" />
       </StackPane.margin>
    </Label>
    <HBox maxHeight="-Infinity" maxWidth="-Infinity" spacing="50.0" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets top="60.0"/>
+         <Insets top="60.0" />
       </StackPane.margin>
       <padding>
-         <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
+         <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
       </padding>
-      <MFXLabel promptText="Prompt Text"/>
-      <MFXLabel text="Style 1"/>
-      <MFXLabel labelStyle="STYLE2" text="Style 2"/>
+      <MFXLabel promptText="Prompt Text" />
+      <MFXLabel text="Style 1" />
+      <MFXLabel labelStyle="STYLE2" text="Style 2" />
+      <MFXLabel text="Style 3" />
    </HBox>
    <HBox maxHeight="-Infinity" maxWidth="-Infinity" spacing="50.0" StackPane.alignment="TOP_CENTER">
       <padding>
-         <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
+         <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
       </padding>
       <StackPane.margin>
-         <Insets top="120.0"/>
+         <Insets top="120.0" />
       </StackPane.margin>
-      <MFXLabel editable="true" promptText="Double Click Me!!"/>
-      <MFXLabel fx:id="custom" editable="true" lineColor="#eb4400" promptText="Double Click Me!! Custom"
-                unfocusedLineColor="#0caf00"/>
+      <MFXLabel editable="true" promptText="Double Click Me!!" />
+      <MFXLabel fx:id="custom" editable="true" lineColor="#eb4400" promptText="Double Click Me!! Custom" unfocusedLineColor="#0caf00" />
    </HBox>
 </StackPane>

+ 9 - 5
demo/src/main/resources/io/github/palexdev/materialfx/demo/ListViewsDemo.fxml

@@ -4,7 +4,7 @@
 <?import javafx.geometry.*?>
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
-<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="920.0" stylesheets="@css/ListViewsDemo.css" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.ListViewDemoController">
+<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="920.0" stylesheets="@css/ListViewsDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.ListViewDemoController">
     <padding>
         <Insets left="20.0" right="20.0" />
     </padding>
@@ -50,10 +50,9 @@
             <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="HBoxes" />
             <MFXFlowlessListView fx:id="hBoxViewNew" />
         </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="135.0"
-              spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="125.0" text="CheckListView"/>
-            <MFXFlowlessCheckListView fx:id="checkList"/>
+        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="135.0" spacing="10.0">
+            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="125.0" text="CheckListView" />
+            <MFXFlowlessCheckListView fx:id="checkList" />
         </VBox>
         <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="135.0" spacing="10.0">
             <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="125.0" text="Customized and CSS" />
@@ -72,4 +71,9 @@
          <Insets top="75.0" />
       </StackPane.margin>
    </MFXButton>
+   <Label text="Multiple Selection On" StackPane.alignment="TOP_LEFT">
+      <StackPane.margin>
+         <Insets left="6.0" top="115.0" />
+      </StackPane.margin>
+   </Label>
 </StackPane>

+ 20 - 36
demo/src/main/resources/io/github/palexdev/materialfx/demo/TextFieldsDemo.fxml

@@ -4,53 +4,37 @@
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.*?>
-<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="450.0"
-            prefWidth="650.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1"
-            fx:controller="io.github.palexdev.materialfx.demo.controllers.TextFieldsDemoController">
-   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="14.0" prefHeight="26.0" prefWidth="266.0"
-          text="TextFields" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="20.0"/>
-   <HBox alignment="CENTER" layoutX="200.0" layoutY="46.0" prefWidth="200.0" spacing="60.0" AnchorPane.leftAnchor="20.0"
-         AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="60.0">
+<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="450.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.TextFieldsDemoController">
+   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="14.0" prefHeight="26.0" prefWidth="266.0" text="TextFields" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="20.0" />
+   <HBox alignment="CENTER" layoutX="200.0" layoutY="46.0" prefWidth="200.0" spacing="60.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="60.0">
       <padding>
-         <Insets bottom="20.0" top="20.0"/>
+         <Insets bottom="20.0" top="20.0" />
       </padding>
-      <MFXTextField alignment="CENTER" maxWidth="-Infinity" prefWidth="120.0" text="MFXTextField"/>
-      <MFXTextField alignment="CENTER" maxWidth="-Infinity" prefWidth="120.0" promptText="Text Limit" textLimit="5"/>
-      <MFXTextField alignment="CENTER" disable="true" maxWidth="-Infinity" prefWidth="120.0" text="Disabled"/>
+      <MFXTextField alignment="CENTER" maxWidth="-Infinity" prefWidth="120.0" text="MFXTextField" />
+      <MFXTextField alignment="CENTER" maxWidth="-Infinity" prefWidth="120.0" promptText="Text Limit" textLimit="5" />
+      <MFXTextField alignment="CENTER" disable="true" maxWidth="-Infinity" prefWidth="120.0" text="Disabled" />
    </HBox>
-   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="125.0" prefHeight="26.0" prefWidth="266.0"
-          text="Customization" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0"
-          AnchorPane.topAnchor="130.0"/>
-   <HBox alignment="CENTER" layoutX="60.0" layoutY="156.0" prefWidth="200.0" spacing="120.0"
-         AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="170.0">
+   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="125.0" prefHeight="26.0" prefWidth="266.0" text="Customization" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="130.0" />
+   <HBox alignment="CENTER" layoutX="60.0" layoutY="156.0" prefWidth="200.0" spacing="120.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="170.0">
       <padding>
-         <Insets bottom="20.0" top="20.0"/>
+         <Insets bottom="20.0" top="20.0" />
       </padding>
-      <MFXTextField id="colors" alignment="CENTER" lineColor="#52db32" maxWidth="-Infinity" prefWidth="120.0"
-                    promptText="Colors"/>
-      <MFXTextField fx:id="validated" lineColor="#52db32" maxWidth="-Infinity" prefWidth="120.0" text="Validation"/>
+      <MFXTextField id="colors" alignment="CENTER" lineColor="#52db32" maxWidth="-Infinity" prefWidth="120.0" promptText="Colors" />
+      <MFXTextField fx:id="validated" lineColor="#52db32" maxWidth="-Infinity" prefWidth="120.0" text="Validation" />
    </HBox>
    <HBox layoutX="15.0" layoutY="348.0" spacing="50.0" AnchorPane.bottomAnchor="15.0" AnchorPane.leftAnchor="15.0">
       <padding>
-         <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+         <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
       </padding>
-      <MFXCheckbox fx:id="checkbox" checkedColor="#00e240" markSize="9.0" markType="mfx-variant9-mark"
-                   text="CheckBox Validation"/>
-      <MFXDatePicker fx:id="picker" colorText="true" lineColor="#00c133b2" maxHeight="-Infinity" maxWidth="-Infinity"
-                     pickerColor="#00c133" prefHeight="30.0" prefWidth="120.0"/>
-      <Label fx:id="label" alignment="CENTER" minHeight="-Infinity" minWidth="-Infinity" prefHeight="25.0"
-             prefWidth="100.0" text="VALID" textFill="#00c133" visible="false"/>
+      <MFXCheckbox fx:id="checkbox" checkedColor="#00e240" markSize="9.0" markType="mfx-variant9-mark" text="CheckBox Validation" />
+      <MFXDatePicker fx:id="picker" colorText="true" lineColor="#00c133b2" maxHeight="-Infinity" maxWidth="-Infinity" pickerColor="#00c133" prefHeight="30.0" prefWidth="120.0" />
    </HBox>
-   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="235.0" prefHeight="26.0" prefWidth="266.0"
-          text="Customization" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0"
-          AnchorPane.topAnchor="240.0"/>
-   <HBox alignment="CENTER" layoutX="120.0" layoutY="266.0" prefWidth="200.0" spacing="60.0"
-         AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="275.0">
+   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="235.0" prefHeight="26.0" prefWidth="266.0" text="Customization" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="240.0" />
+   <HBox alignment="CENTER" layoutX="120.0" layoutY="266.0" prefWidth="200.0" spacing="60.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="275.0">
       <padding>
-         <Insets bottom="20.0" top="20.0"/>
+         <Insets bottom="20.0" top="20.0" />
       </padding>
-      <MFXPasswordField lineColor="#52db32" promptText="Prompt Text"/>
-      <MFXPasswordField fx:id="passwordValidated" lineColor="#52db32" promptText="Enter Password..."
-                        showPassword="true"/>
+      <MFXPasswordField lineColor="#52db32" promptText="Prompt Text" />
+      <MFXPasswordField fx:id="passwordValidated" lineColor="#52db32" promptText="Enter Password..." showPassword="true" />
    </HBox>
 </AnchorPane>

+ 0 - 89
materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXContextMenuItem.java

@@ -1,89 +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 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.beans;
-
-import javafx.event.EventHandler;
-import javafx.scene.Node;
-import javafx.scene.control.Label;
-import javafx.scene.input.MouseEvent;
-
-/**
- * Bean used in {@link io.github.palexdev.materialfx.controls.MFXContextMenu.Builder}.
- * Contains the node reference to add to the context menu and the action automatically added to the node
- * when is pressed.
- */
-public class MFXContextMenuItem {
-    private final Node node;
-    private EventHandler<MouseEvent> action;
-
-    /**
-     * Creates a nre context menu item with the specified node and null action.
-     */
-    public MFXContextMenuItem(Node node) {
-        this.node = node;
-    }
-
-    /**
-     * Created a new menu item with the specified node and action(added automatically to the node).
-     */
-    public MFXContextMenuItem(Node node, EventHandler<MouseEvent> action) {
-        this.node = node;
-        this.action = action;
-        node.addEventHandler(MouseEvent.MOUSE_PRESSED, action);
-    }
-
-    /**
-     * Creates a new menu item with a Label that has the specified text set.
-     */
-    public MFXContextMenuItem(String text) {
-        Label label = new Label(text);
-        label.setMaxWidth(Double.MAX_VALUE);
-        node = label;
-    }
-
-    /**
-     * Creates a new menu item with a Label that has the specified text set and the
-     * specified action on mouse pressed.
-     */
-    public MFXContextMenuItem(String text, EventHandler<MouseEvent> action) {
-        Label label = new Label(text);
-        label.setMaxWidth(Double.MAX_VALUE);
-        node = label;
-        node.addEventHandler(MouseEvent.MOUSE_PRESSED, action);
-    }
-
-    public Node getNode() {
-        return node;
-    }
-
-    public EventHandler<MouseEvent> getAction() {
-        return action;
-    }
-
-    /**
-     * Sets or replace the node action on mouse pressed.
-     */
-    public void setAction(EventHandler<MouseEvent> action) {
-        if (this.action != null) {
-            node.removeEventHandler(MouseEvent.MOUSE_PRESSED, this.action);
-        }
-        node.addEventHandler(MouseEvent.MOUSE_PRESSED, action);
-        this.action = action;
-    }
-}

+ 27 - 30
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java

@@ -19,8 +19,8 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
-import io.github.palexdev.materialfx.beans.MFXContextMenuItem;
 import io.github.palexdev.materialfx.controls.enums.DialogType;
+import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.selection.ComboSelectionModelMock;
 import io.github.palexdev.materialfx.skins.MFXComboBoxSkin;
 import io.github.palexdev.materialfx.validation.MFXDialogValidator;
@@ -203,9 +203,6 @@ public class MFXComboBox<T> extends Control implements Validated<MFXDialogValida
             if (oldValue != null) {
                 oldValue.dispose();
             }
-            if (newValue != null) {
-                newValue.install(this);
-            }
         });
 
         setupValidator();
@@ -213,40 +210,40 @@ public class MFXComboBox<T> extends Control implements Validated<MFXDialogValida
     }
 
     protected void defaultContextMenu() {
-        MFXContextMenuItem selectFirst = new MFXContextMenuItem(
-                "Select First",
-                event -> mockSelection.selectFirst()
-        );
-
-        MFXContextMenuItem selectNext = new MFXContextMenuItem(
-                "Select Next",
-                event -> mockSelection.selectNext()
-        );
-
-        MFXContextMenuItem selectPrevious = new MFXContextMenuItem(
-                "Select Previous",
-                event -> mockSelection.selectPrevious()
-        );
-
-        MFXContextMenuItem selectLast = new MFXContextMenuItem(
-                "Select Last",
-                event -> mockSelection.selectLast()
-        );
-
-        MFXContextMenuItem resetSelection = new MFXContextMenuItem(
-                "Clear Selection",
-                event -> mockSelection.clearSelection()
-        );
+        MFXContextMenuItem selectFirst = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-first-page", 16))
+                .setText("Select First")
+                .setAction(event -> mockSelection.selectFirst());
+
+        MFXContextMenuItem selectNext = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-next", 18))
+                .setText("Select Next")
+                .setAction(event -> mockSelection.selectNext());
+
+        MFXContextMenuItem selectPrevious = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-back", 18))
+                .setText("Select Previous")
+                .setAction(event -> mockSelection.selectPrevious());
+
+        MFXContextMenuItem selectLast = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-last-page", 16))
+                .setText("Select Last")
+                .setAction(event -> mockSelection.selectLast());
+
+        MFXContextMenuItem resetSelection = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-x", 16))
+                .setText("Clear Selection")
+                .setAction(event -> mockSelection.clearSelection());
 
         setMFXContextMenu(
-                new MFXContextMenu.Builder()
+                MFXContextMenu.Builder.build(this)
                         .addMenuItem(selectFirst)
                         .addMenuItem(selectNext)
                         .addMenuItem(selectPrevious)
                         .addMenuItem(selectLast)
                         .addSeparator()
                         .addMenuItem(resetSelection)
-                        .get()
+                        .install()
         );
     }
 

+ 218 - 189
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXContextMenu.java

@@ -19,36 +19,33 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
-import io.github.palexdev.materialfx.beans.MFXContextMenuItem;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
 import javafx.event.EventHandler;
-import javafx.event.EventType;
 import javafx.geometry.Insets;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
-import javafx.scene.Scene;
 import javafx.scene.control.PopupControl;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.VBox;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.Line;
-import javafx.stage.Window;
 import javafx.stage.WindowEvent;
 
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * This control is a context menu built from scratch which extends {@code VBox}.
  * <p>
  * It easily styleable and allows to add separators between the context menu nodes.
  * The context menu is shown in a {@code PopupControl}.
+ * <p>
+ * It also allows to easily change owner and menu items even at runtime.
  * <p></p>
- * It is <b>highly recommended</b> to use the {@link Builder} class to create a context menu.
+ * It is <b>highly recommended</b> to use the {@link MFXContextMenu.Builder} class to create a context menu.
  */
 public class MFXContextMenu extends VBox {
     //================================================================================
@@ -57,290 +54,322 @@ public class MFXContextMenu extends VBox {
     private final String STYLE_CLASS = "mfx-context-menu";
     private final String STYLESHEET = MFXResourcesLoader.load("css/MFXContextMenu.css");
 
-    private final List<Line> separators = new ArrayList<>();
-
-    private WeakReference<Node> nodeReference;
-    private final PopupControl popupControl;
-    private final ChangeListener<Scene> sceneListener;
-    private final ChangeListener<Boolean> windowFocusListener;
-    private final EventHandler<MouseEvent> sceneHandler;
-    private final EventHandler<MouseEvent> openHandler;
-    private final EventHandler<KeyEvent> keyHandler;
+    private final ObjectProperty<ObservableList<Node>> items = new SimpleObjectProperty<>(FXCollections.observableArrayList());
+    private final ObjectProperty<Node> owner = new SimpleObjectProperty<>();
+    private final PopupControl popup;
+    private EventHandler<MouseEvent> openHandler;
+    private ChangeListener<Boolean> ownerFocus;
 
     //================================================================================
     // Constructors
     //================================================================================
-    public MFXContextMenu() {
-        this(0);
+    public MFXContextMenu(Node owner) {
+        this(0, owner);
     }
 
-    public MFXContextMenu(double spacing) {
+    public MFXContextMenu(double spacing, Node owner) {
         super(spacing);
-        setMinWidth(100);
-        setAlignment(Pos.TOP_CENTER);
-        getStyleClass().setAll(STYLE_CLASS);
-        popupControl = new PopupControl();
-        popupControl.getScene().setFill(Color.WHITE);
-        popupControl.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> hide());
 
-        sceneHandler = event -> {
-            if (popupControl.isShowing()) {
-                hide();
-            }
-        };
-        sceneListener = (observable, oldValue, newValue) -> {
-            if (oldValue != null) {
-                oldValue.removeEventFilter(MouseEvent.MOUSE_PRESSED, sceneHandler);
-            }
-            if (newValue != null) {
-                newValue.addEventFilter(MouseEvent.MOUSE_PRESSED, sceneHandler);
-            }
-        };
-        windowFocusListener = (observable, oldValue, newValue) -> {
-            if (!newValue && popupControl.isShowing()) {
-                hide();
-            }
-        };
+        popup = new PopupControl();
+        popup.getScene().setRoot(this);
+        popup.getScene().setFill(Color.TRANSPARENT);
+        popup.setAutoHide(true);
+        popup.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> hide());
+
         openHandler = event -> {
             if (event.getButton() == MouseButton.SECONDARY) {
-                popupControl.getScene().setRoot(MFXContextMenu.this);
-                show(nodeReference.get(), event.getScreenX(), event.getScreenY());
+                show(event);
             }
         };
-        keyHandler = event -> {
-            if (event.getEventType() != KeyEvent.KEY_PRESSED) return;
-
-            final KeyCode code = event.getCode();
-            if (code == KeyCode.ENTER || code == KeyCode.SPACE) {
+        ownerFocus = (observable, oldValue, newValue) -> {
+            if (!newValue && isShowing()) {
                 hide();
             }
         };
+
+        initialize();
+        setOwner(owner);
     }
 
     //================================================================================
     // Methods
     //================================================================================
+    private void initialize() {
+        getStyleClass().add(STYLE_CLASS);
+        setStyle("-fx-background-color: white");
+        setMinWidth(100);
+        setAlignment(Pos.TOP_CENTER);
 
-    /**
-     * Installs the context menu to the given node.
-     */
-    public void install(Node node) {
-        if (node.getScene() != null) {
-            Scene scene = node.getScene();
-            Window window = scene.getWindow();
-            scene.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> hide());
-            if (window != null) {
-                window.focusedProperty().addListener(windowFocusListener);
-            }
-        }
 
-        node.sceneProperty().addListener((observable, oldValue, newValue) -> {
+        items.addListener((observable, oldValue, newValue) -> {
+            if (oldValue != null && !oldValue.isEmpty()) {
+                oldValue.clear();
+            }
             if (newValue != null) {
-                newValue.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> hide());
-                Window window = newValue.getWindow();
-                if (window != null) {
-                    window.focusedProperty().addListener(windowFocusListener);
-                }
+                super.getChildren().setAll(newValue);
             }
         });
 
-        Node nR = nodeReference != null ? nodeReference.get() : null;
-        if (nR != null && nR == node) {
-            return;
-        }
+        owner.addListener((observable, oldValue, newValue) -> {
+            if (oldValue != null) {
+                oldValue.removeEventFilter(MouseEvent.MOUSE_PRESSED, openHandler);
+                oldValue.focusedProperty().removeListener(ownerFocus);
+            }
+            if (newValue != null) {
+                newValue.addEventFilter(MouseEvent.MOUSE_PRESSED, openHandler);
+                newValue.focusedProperty().addListener(ownerFocus);
+            }
+        });
+    }
 
-        dispose();
-        nodeReference = new WeakReference<>(node);
-        installBehavior();
+    /**
+     * Shows the context menu' popup.
+     */
+    public void show(MouseEvent event) {
+        popup.show(getOwner(), event.getScreenX(), event.getScreenY());
     }
 
     /**
-     * Installs the context menu behavior to the given node.
-     * Used in {@link #install(Node)}.
+     * Hides the context menu' popup.
      */
-    private void installBehavior() {
-        if (nodeReference != null) {
-            Node node = nodeReference.get();
-            if (node != null) {
-                node.sceneProperty().addListener(sceneListener);
-                node.addEventHandler(MouseEvent.MOUSE_PRESSED, openHandler);
-                node.addEventHandler(KeyEvent.KEY_PRESSED, keyHandler);
-            }
-        }
+    public void hide() {
+        popup.hide();
     }
 
     /**
-     * If the node reference is not null and {@link #install(Node)} is called again, this method is called to
-     * remove the context menu from the previous node.
+     * Removes the popup handler from the current set owner (if not null) and then
+     * sets it to null.
      */
     public void dispose() {
-        if (nodeReference != null) {
-            Node node = nodeReference.get();
-            if (node != null) {
-                node.sceneProperty().removeListener(sceneListener);
-                node.getScene().removeEventFilter(MouseEvent.MOUSE_PRESSED, sceneHandler);
-                Window window = node.getScene().getWindow();
-                if (window != null) {
-                    window.focusedProperty().removeListener(windowFocusListener);
-                }
-                node.removeEventHandler(MouseEvent.MOUSE_PRESSED, openHandler);
-                node.removeEventHandler(KeyEvent.KEY_PRESSED, keyHandler);
-            }
+        if (getOwner() != null) {
+            getOwner().removeEventFilter(MouseEvent.MOUSE_PRESSED, openHandler);
+            getOwner().focusedProperty().removeListener(ownerFocus);
         }
+        openHandler = null;
+        ownerFocus = null;
     }
 
-    void addSeparator(Line line) {
-        separators.add(line);
+    /**
+     * @return the item's list of this context menu, separators included
+     */
+    public ObservableList<Node> getItems() {
+        return items.get();
     }
 
-    @Override
-    public String getUserAgentStylesheet() {
-        return STYLESHEET;
+    /**
+     * Sets the item's list of this context menu with the given list.
+     */
+    public void setItems(ObservableList<Node> items) {
+        this.items.set(items);
     }
 
-    @Override
-    protected void layoutChildren() {
-        super.layoutChildren();
-
-        separators.forEach(line -> {
-            line.setStartX(0);
-            line.setEndX(getWidth() - (snappedRightInset() + snappedLeftInset()));
-        });
+    public Node getOwner() {
+        return owner.get();
     }
 
     /**
-     * Shows the context menu' popup.
+     * Specifies the popup's owner. This is needed to invoke {@link PopupControl#show(Node, double, double)}.
      */
-    public void show(Node ownerNode, double anchorX, double anchorY) {
-        popupControl.show(ownerNode, anchorX, anchorY);
+    public ObjectProperty<Node> ownerProperty() {
+        return owner;
     }
 
-    /**
-     * Hides the context menu' popup.
-     */
-    public void hide() {
-        popupControl.hide();
+    public void setOwner(Node owner) {
+        this.owner.set(owner);
+    }
+
+    //================================================================================
+    // Delegate Methods
+    //================================================================================
+    public void setOnCloseRequest(EventHandler<WindowEvent> value) {
+        popup.setOnCloseRequest(value);
+    }
+
+    public void setOnShowing(EventHandler<WindowEvent> value) {
+        popup.setOnShowing(value);
+    }
+
+    public void setOnShown(EventHandler<WindowEvent> value) {
+        popup.setOnShown(value);
     }
 
-    public void addPopupEventHandler(EventType<WindowEvent> eventType, EventHandler<WindowEvent> eventHandler) {
-        popupControl.addEventHandler(eventType, eventHandler);
+    public void setOnHiding(EventHandler<WindowEvent> value) {
+        popup.setOnHiding(value);
     }
 
-    public void addPopupEventFilter(EventType<WindowEvent> eventType, EventHandler<WindowEvent> eventHandler) {
-        popupControl.addEventFilter(eventType, eventHandler);
+    public void setOnHidden(EventHandler<WindowEvent> value) {
+        popup.setOnHidden(value);
     }
 
-    public void removePopupEventHandler(EventType<WindowEvent> eventType, EventHandler<WindowEvent> eventHandler) {
-        popupControl.removeEventHandler(eventType, eventHandler);
+    public boolean isShowing() {
+        return popup.isShowing();
+    }
+
+    public ReadOnlyBooleanProperty showingProperty() {
+        return popup.showingProperty();
+    }
+
+    //================================================================================
+    // Override Methods
+    //================================================================================
+
+    /**
+     * @return an unmodifiable list containing the control's children
+     */
+    @Override
+    public ObservableList<Node> getChildren() {
+        return super.getChildrenUnmodifiable();
+    }
+
+    @Override
+    public String getUserAgentStylesheet() {
+        return STYLESHEET;
     }
 
-    public void removePopupEventFilter(EventType<WindowEvent> eventType, EventHandler<WindowEvent> eventHandler) {
-        popupControl.removeEventFilter(eventType, eventHandler);
+    @Override
+    protected void layoutChildren() {
+        super.layoutChildren();
+
+        getItems().stream()
+                .filter(node -> node instanceof Line)
+                .map(node -> (Line) node)
+                .forEach(line -> {
+                    line.setStartX(0);
+                    line.setEndX(getWidth() - (snappedRightInset() + snappedLeftInset()));
+                });
     }
 
+    //================================================================================
+    // Builder
+    //================================================================================
+
     /**
-     * Utils class that facilitates the creation of context menus with fluent api.
+     * Builder class that facilitates the creation of context menus with fluent api.
      * <p>
-     * Example from {@code MFXTableView Skin}:
+     * An example:
      * <p></p>
      * <pre>
      * {@code
-     * MFXContextMenuItem restoreWidthThis = new MFXContextMenuItem(
-     *      "Restore this column width",
-     *      event -> column.setMinWidth(column.getInitialWidth())
-     * );
+     * MFXContextMenuItem item1 = new MFXContextMenuItem()
+     *         .setText("A")
+     *         .setAccelerator("Shift + A")
+     *         .setTooltipSupplier(() -> new Tooltip("A"))
+     *         .setAction(event -> System.out.println("Action A"));
+     *
+     * MFXContextMenuItem item2 = new MFXContextMenuItem()
+     *         .setText("B")
+     *         .setAccelerator("Shift + B")
+     *         .setTooltipSupplier(() -> new Tooltip("B"))
+     *         .setAction(event -> System.out.println("Action B"));
      *
-     * MFXContextMenuItem restoreWidthAll = new MFXContextMenuItem(
-     *      "Restore all columns width",
-     *      event -> columnsContainer.getChildren().stream()
-     *          .filter(node -> node instanceof MFXTableColumnCell)
-     *          .map(node -> (MFXTableColumnCell<T>) node)
-     *          .forEach(c -> c.setMinWidth(c.getInitialWidth()))
-     * );
+     * MFXContextMenuItem item3 = new MFXContextMenuItem()
+     *         .setText("C")
+     *         .setAccelerator("Shift + C")
+     *         .setTooltipSupplier(() -> new Tooltip("C"))
+     *         .setAction(event -> System.out.println("Action C"));
      *
-     * MFXContextMenuItem autoSizeThis = new MFXContextMenuItem(
-     *      "Autosize this column",
-     *      event -> autoSizeColumn(column)
-     *  );
+     * MFXContextMenuItem item4 = new MFXContextMenuItem()
+     *         .setText("D")
+     *         .setAccelerator("Shift + D")
+     *         .setTooltipSupplier(() -> new Tooltip("D"))
+     *         .setAction(event -> System.out.println("Action D"));
      *
-     * MFXContextMenuItem autoSizeAll = new MFXContextMenuItem(
-     *      "Autosize all columns",
-     *      event -> columnsContainer.getChildren().stream()
-     *          .filter(node -> node instanceof MFXTableColumnCell)
-     *          .map(node -> (MFXTableColumnCell<T>) node)
-     *          .forEach(this::autoSizeColumn)
-     * );
+     * MFXContextMenuItem item5 = new MFXContextMenuItem()
+     *         .setText("E")
+     *         .setAccelerator("Shift + E")
+     *         .setTooltipSupplier(() -> new Tooltip("E"))
+     *         .setAction(event -> System.out.println("Action E"));
      *
-     * new MFXContextMenu.Builder()
-     *      .addMenuItem(autoSizeAll)
-     *      .addMenuItem(autoSizeThis)
-     *      .addSeparator()
-     *      .addMenuItem(restoreWidthAll)
-     *      .addMenuItem(restoreWidthThis)
-     *      .install(column);
+     * MFXContextMenuItem item6 = new MFXContextMenuItem()
+     *         .setText("F")
+     *         .setAccelerator("Shift + F")
+     *         .setTooltipSupplier(() -> new Tooltip("F"))
+     *         .setAction(event -> System.out.println("Action F"));
+     *
+     * MFXContextMenu.Builder.build(owner)
+     *         .addMenuItem(item1)
+     *         .addMenuItem(item2)
+     *         .addSeparator()
+     *         .addMenuItem(item3)
+     *         .addMenuItem(item4)
+     *         .addSeparator()
+     *         .addMenuItem(item5)
+     *         .addMenuItem(item6)
+     *         .install();
      * }
      * </pre>
+     *
+     * @see MFXContextMenuItem
      */
     public static class Builder {
         private final MFXContextMenu contextMenu;
+        private final ObservableList<Node> items = FXCollections.observableArrayList();
+
+        private Builder(Node owner) {
+            this(0, owner);
+        }
+
+        private Builder(double spacing, Node owner) {
+            contextMenu = new MFXContextMenu(spacing, owner);
+        }
 
         /**
-         * Creates a new Builder with a new MFXContextMenu instance.
+         * @return a new Builder instance with the given owner for the MFXContextMenu
          */
-        public Builder() {
-            contextMenu = new MFXContextMenu();
+        public static Builder build(Node owner) {
+            return new Builder(owner);
         }
 
         /**
-         * Creates a new Builder with a new MFXContextMenu instance that has
-         * the spacing set to the given value.
+         * @return a new Builder instance with the given owner for the MFXContextMenu
+         * and the given spacing
          */
-        public Builder(double spacing) {
-            contextMenu = new MFXContextMenu(spacing);
+        public static Builder build(double spacing, Node owner) {
+            return new Builder(spacing, owner);
         }
 
         /**
-         * Adds a new node to the context menu with the specified action on mouse pressed.
+         * Adds the specifies node to the items list.
+         */
+        public Builder addMenuItem(Node node) {
+            items.add(node);
+            return this;
+        }
+
+        /**
+         * Adds the specified action to the specified node by adding an event handler
+         * for MOUSE_PRESSED to the node and then adds the node to the items list.
          */
         public Builder addMenuItem(Node node, EventHandler<MouseEvent> action) {
             node.addEventHandler(MouseEvent.MOUSE_PRESSED, action);
-            contextMenu.getChildren().add(node);
+            items.add(node);
             return this;
         }
 
         /**
-         * Adds the specified {@link MFXContextMenuItem} to the context menu.
+         * Adds the specified {@link MFXContextMenuItem} to the items list.
          */
         public Builder addMenuItem(MFXContextMenuItem item) {
-            contextMenu.getChildren().add(item.getNode());
+            items.add(item);
             return this;
         }
 
         /**
-         * Adds a separator to the context menu.
+         * Adds a separator to the items list.
          */
         public Builder addSeparator() {
             Line separator = new Line();
             separator.getStyleClass().add("separator");
             VBox.setMargin(separator, new Insets(4, 0, 3, 0));
-            contextMenu.addSeparator(separator);
-            contextMenu.getChildren().add(separator);
+            items.add(separator);
             return this;
         }
 
         /**
-         * @return the built context menu instance
-         */
-        public MFXContextMenu get() {
-            return contextMenu;
-        }
-
-        /**
-         * Installs the context menu to the given node and returns the
-         * context menu instance.
+         * Installs the added items in the context menu.
          */
-        public MFXContextMenu install(Node node) {
-            contextMenu.install(node);
+        public MFXContextMenu install() {
+            contextMenu.setItems(items);
             return contextMenu;
         }
     }

+ 336 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXContextMenuItem.java

@@ -0,0 +1,336 @@
+/*
+ *     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;
+
+import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.skins.MFXContextMenuItemSkin;
+import javafx.beans.property.*;
+import javafx.event.EventHandler;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Control;
+import javafx.scene.control.Skin;
+import javafx.scene.control.Tooltip;
+import javafx.scene.input.MouseEvent;
+
+import java.util.function.Supplier;
+
+/**
+ * Even if the {@link MFXContextMenu} builder allows to add any node to the items list, this
+ * control is the recommended item to use when building {@code MFXContextMenus}.
+ * <p></p>
+ * Extends {@code Control} and defines its own skin. It allows to specify not only the text but also
+ * the accelerator string and an icon. Each of them has separate properties like: alignment, width and insets/padding.
+ * <p></p>
+ * Defines a property, {@link #actionProperty()} to allow the user to specify what to do when the mouse is pressed
+ * on the item.
+ * <p>
+ * It also allows to specify a {@link Tooltip} by defining a tooltip supplier property.
+ * <p></p>
+ * An no-arg constructor is defines because this control is design with fluent api in mind, all the setters
+ * return the context menu item.
+ * <p></p>
+ * An example:
+ * <p></p>
+ * <pre>
+ * {@code
+ * MFXContextMenuItem item1 = new MFXContextMenuItem()
+ *     .setText("Context Menu Item 1")
+ *     .setAccelerator("Shift + C")
+ *     .setTooltipSupplier(() -> new Tooltip("Item 1"))
+ *     .setAction(event -> System.out.println("Action 1 Executed"));
+ * }
+ * </pre>
+ *
+ * <p></p>
+ * A little note on the icon, please please don't use big nodes like buttons, it won't work,
+ * the context menu item is not made to support such things. The icon should be a small node like
+ * font icons.
+ */
+public class MFXContextMenuItem extends Control {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private final String STYLE_CLASS = "mfx-context-menu-item";
+    private final String STYLESHEET = MFXResourcesLoader.load("css/MFXContextMenuItem.css");
+
+    private final StringProperty text = new SimpleStringProperty("");
+    private final StringProperty accelerator = new SimpleStringProperty("");
+    private final ObjectProperty<Node> icon = new SimpleObjectProperty<>();
+    private final DoubleProperty spacing = new SimpleDoubleProperty(0);
+    private final DoubleProperty textWidth = new SimpleDoubleProperty(80);
+    private final DoubleProperty acceleratorWidth = new SimpleDoubleProperty(50);
+    private final ObjectProperty<Pos> textAlignment = new SimpleObjectProperty<>(Pos.CENTER_LEFT);
+    private final ObjectProperty<Pos> acceleratorAlignment = new SimpleObjectProperty<>(Pos.CENTER_RIGHT);
+    private final ObjectProperty<Insets> textInsets = new SimpleObjectProperty<>(new Insets(5, 0, 5, 10));
+    private final ObjectProperty<Insets> acceleratorInsets = new SimpleObjectProperty<>(new Insets(5, 10, 5, 0));
+    private final ObjectProperty<Supplier<Tooltip>> tooltipSupplier = new SimpleObjectProperty<>();
+    private final ObjectProperty<EventHandler<MouseEvent>> action = new SimpleObjectProperty<>() {
+        @Override
+        protected void invalidated() {
+            setEventHandler(MouseEvent.MOUSE_PRESSED, get());
+        }
+    };
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public MFXContextMenuItem() {
+        this("", "");
+    }
+
+    public MFXContextMenuItem(String text) {
+        this(text, "");
+    }
+
+    public MFXContextMenuItem(String text, String accelerator) {
+        setText(text);
+        setAccelerator(accelerator);
+        initialize();
+    }
+
+    //================================================================================
+    // Methods
+    //================================================================================
+    private void initialize() {
+        getStyleClass().add(STYLE_CLASS);
+
+        tooltipSupplier.addListener((observable, oldValue, newValue) -> {
+            if (oldValue != null) {
+                setTooltip(null);
+            }
+            if (newValue != null) {
+                setTooltip(newValue.get());
+            }
+        });
+    }
+
+    public String getText() {
+        return text.get();
+    }
+
+    /**
+     * Specifies the item's text.
+     */
+    public StringProperty textProperty() {
+        return text;
+    }
+
+    public MFXContextMenuItem setText(String text) {
+        this.text.set(text);
+        return this;
+    }
+
+    public String getAccelerator() {
+        return accelerator.get();
+    }
+
+    /**
+     * Specifies the item's accelerator.
+     */
+    public StringProperty acceleratorProperty() {
+        return accelerator;
+    }
+
+    public MFXContextMenuItem setAccelerator(String accelerator) {
+        this.accelerator.set(accelerator);
+        return this;
+    }
+
+    public Node getIcon() {
+        return icon.get();
+    }
+
+    /**
+     * Specifies the item's icon.
+     */
+    public ObjectProperty<Node> iconProperty() {
+        return icon;
+    }
+
+    public MFXContextMenuItem setIcon(Node icon) {
+        this.icon.set(icon);
+        return this;
+    }
+
+    public MFXContextMenuItem setIcon(Supplier<Node> icon) {
+        this.icon.set(icon.get());
+        return this;
+    }
+
+    public double getSpacing() {
+        return spacing.get();
+    }
+
+    /**
+     * Specifies the spacing between the text and the accelerator.
+     */
+    public DoubleProperty spacingProperty() {
+        return spacing;
+    }
+
+    public MFXContextMenuItem setSpacing(double spacing) {
+        this.spacing.set(spacing);
+        return this;
+    }
+
+    public double getTextWidth() {
+        return textWidth.get();
+    }
+
+    /**
+     * Specifies the text label width.
+     */
+    public DoubleProperty textWidthProperty() {
+        return textWidth;
+    }
+
+    public MFXContextMenuItem setTextWidth(double textWidth) {
+        this.textWidth.set(textWidth);
+        return this;
+    }
+
+    public double getAcceleratorWidth() {
+        return acceleratorWidth.get();
+    }
+
+    /**
+     * Specifies the accelerator label width.
+     */
+    public DoubleProperty acceleratorWidthProperty() {
+        return acceleratorWidth;
+    }
+
+    public MFXContextMenuItem setAcceleratorWidth(double acceleratorTextWidth) {
+        this.acceleratorWidth.set(acceleratorTextWidth);
+        return this;
+    }
+
+    public Pos getTextAlignment() {
+        return textAlignment.get();
+    }
+
+    /**
+     * Specifies the text alignment.
+     */
+    public ObjectProperty<Pos> textAlignmentProperty() {
+        return textAlignment;
+    }
+
+    public MFXContextMenuItem setTextAlignment(Pos textAlignment) {
+        this.textAlignment.set(textAlignment);
+        return this;
+    }
+
+    public Pos getAcceleratorAlignment() {
+        return acceleratorAlignment.get();
+    }
+
+    /**
+     * Specifies the accelerator text alignment.
+     */
+    public ObjectProperty<Pos> acceleratorAlignmentProperty() {
+        return acceleratorAlignment;
+    }
+
+    public MFXContextMenuItem setAcceleratorAlignment(Pos acceleratorAlignment) {
+        this.acceleratorAlignment.set(acceleratorAlignment);
+        return this;
+    }
+
+    public Insets getTextInsets() {
+        return textInsets.get();
+    }
+
+    /**
+     * Specifies the text label padding.
+     */
+    public ObjectProperty<Insets> textInsetsProperty() {
+        return textInsets;
+    }
+
+    public MFXContextMenuItem setTextInsets(Insets textInsets) {
+        this.textInsets.set(textInsets);
+        return this;
+    }
+
+    public Insets getAcceleratorInsets() {
+        return acceleratorInsets.get();
+    }
+
+    /**
+     * Specifies the accelerator label padding.
+     */
+    public ObjectProperty<Insets> acceleratorInsetsProperty() {
+        return acceleratorInsets;
+    }
+
+    public MFXContextMenuItem setAcceleratorInsets(Insets acceleratorInsets) {
+        this.acceleratorInsets.set(acceleratorInsets);
+        return this;
+    }
+
+    public Supplier<Tooltip> getTooltipSupplier() {
+        return tooltipSupplier.get();
+    }
+
+    /**
+     * Specifies the supplier used to build the item's tooltip.
+     * <p>
+     * Set it to null to disable it.
+     */
+    public ObjectProperty<Supplier<Tooltip>> tooltipSupplierProperty() {
+        return tooltipSupplier;
+    }
+
+    public MFXContextMenuItem setTooltipSupplier(Supplier<Tooltip> tooltipSupplier) {
+        this.tooltipSupplier.set(tooltipSupplier);
+        return this;
+    }
+
+    public EventHandler<MouseEvent> getAction() {
+        return action.get();
+    }
+
+    /**
+     * Specifies the action to perform when the mouse is pressed on the item.
+     */
+    public ObjectProperty<EventHandler<MouseEvent>> actionProperty() {
+        return action;
+    }
+
+    public MFXContextMenuItem setAction(EventHandler<MouseEvent> action) {
+        this.action.set(action);
+        return this;
+    }
+
+    //================================================================================
+    // Override Methods
+    //================================================================================
+    @Override
+    public String getUserAgentStylesheet() {
+        return STYLESHEET;
+    }
+
+    @Override
+    protected Skin<?> createDefaultSkin() {
+        return new MFXContextMenuItemSkin(this);
+    }
+}

+ 11 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java

@@ -28,6 +28,7 @@ import javafx.beans.property.SimpleObjectProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.scene.Node;
+import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.StackPane;
 
@@ -87,7 +88,11 @@ public class MFXIconWrapper extends StackPane {
     public MFXIconWrapper defaultRippleGeneratorBehavior() {
         addRippleGenerator();
         rippleGenerator.setRipplePositionFunction(event -> new RipplePosition(event.getX(), event.getY()));
-        addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple);
+        addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
+            if (event.getButton() == MouseButton.PRIMARY) {
+                rippleGenerator.generateRipple(event);
+            }
+        });
         return this;
     }
 
@@ -101,7 +106,11 @@ public class MFXIconWrapper extends StackPane {
     public MFXIconWrapper rippleGeneratorBehavior(Function<MouseEvent, RipplePosition> positionFunction) {
         addRippleGenerator();
         rippleGenerator.setRipplePositionFunction(positionFunction);
-        addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple);
+        addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
+            if (event.getButton() == MouseButton.PRIMARY) {
+                rippleGenerator.generateRipple(event);
+            }
+        });
         return this;
     }
 

+ 38 - 36
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java

@@ -19,8 +19,8 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
-import io.github.palexdev.materialfx.beans.MFXContextMenuItem;
 import io.github.palexdev.materialfx.controls.enums.DialogType;
+import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.skins.MFXTextFieldSkin;
 import io.github.palexdev.materialfx.validation.MFXDialogValidator;
 import io.github.palexdev.materialfx.validation.base.AbstractMFXValidator;
@@ -189,9 +189,6 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
             if (oldValue != null) {
                 oldValue.dispose();
             }
-            if (newValue != null) {
-                newValue.install(this);
-            }
         });
 
         textProperty().addListener((observable, oldValue, newValue) -> {
@@ -212,40 +209,45 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
      * Installs the default {@link MFXContextMenu}.
      */
     protected void defaultContextMenu() {
-        MFXContextMenuItem copy = new MFXContextMenuItem(
-                "Copy",
-                event -> copy()
-        );
-
-        MFXContextMenuItem cut = new MFXContextMenuItem(
-                "Cut",
-                event -> cut()
-        );
-
-        MFXContextMenuItem paste = new MFXContextMenuItem(
-                "Paste",
-                event -> paste()
-        );
-
-        MFXContextMenuItem delete = new MFXContextMenuItem(
-                "Delete",
-                event -> deleteText(getSelection())
-        );
-
-        MFXContextMenuItem selectAll = new MFXContextMenuItem(
-                "Select All",
-                event -> selectAll()
-        );
+        MFXContextMenuItem copy = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-content-copy", 14))
+                .setText("Copy")
+                .setAccelerator("Ctrl + C")
+                .setAction(event -> copy());
+
+        MFXContextMenuItem cut = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-content-cut", 14))
+                .setText("Cut")
+                .setAccelerator("Ctrl + X")
+                .setAction(event -> cut());
+
+        MFXContextMenuItem paste = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-content-paste", 14))
+                .setText("Paste")
+                .setAccelerator("Ctrl + V")
+                .setAction(event -> paste());
+
+        MFXContextMenuItem delete = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-delete-alt", 16))
+                .setText("Delete")
+                .setAccelerator("Ctrl + D")
+                .setAction(event -> deleteText(getSelection()));
+
+        MFXContextMenuItem selectAll = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-select-all", 16))
+                .setText("Select All")
+                .setAccelerator("Ctrl + A")
+                .setAction(event -> selectAll());
 
         setMFXContextMenu(
-                new MFXContextMenu.Builder()
-                .addMenuItem(copy)
-                .addMenuItem(cut)
-                .addMenuItem(paste)
-                .addMenuItem(delete)
-                .addSeparator()
-                .addMenuItem(selectAll)
-                .get()
+                MFXContextMenu.Builder.build(this)
+                        .addMenuItem(copy)
+                        .addMenuItem(cut)
+                        .addMenuItem(paste)
+                        .addMenuItem(delete)
+                        .addSeparator()
+                        .addMenuItem(selectAll)
+                        .install()
         );
     }
 

+ 1 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXDialogFactory.java

@@ -165,7 +165,7 @@ public class MFXDialogFactory {
         headerNode.getStyleClass().add("header-node");
         headerNode.setStyle("-fx-background-color: " + color + ";\n");
 
-        MFXFontIcon closeIcon = new MFXFontIcon("mfx-x", Color.WHITE);
+        MFXFontIcon closeIcon = new MFXFontIcon("mfx-x-alt", 16, Color.WHITE);
 
         if (dialog.getType() != null && dialog.getType().equals(DialogType.GENERIC)) {
             dialog.setCloseButtons(new MFXButton(""));

+ 74 - 64
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: 69)
+ * Enumerator class for MaterialFX font resources. (Count: 79)
  */
 public enum FontResources {
     ANGLE_DOWN("mfx-angle-down", '\uE900'),
@@ -28,69 +28,79 @@ public enum FontResources {
     ANGLE_UP("mfx-angle-up", '\uE903'),
     ARROW_BACK("mfx-arrow-back", '\uE904'),
     ARROW_FORWARD("mfx-arrow-forward", '\uE905'),
-    CALENDAR_BLACK("mfx-calendar-black", '\uE906'),
-    CALENDAR_SEMI_BLACK("mfx-calendar-semi-black", '\uE907'),
-    CALENDAR_WHITE("mfx-calendar-white", '\uE908'),
-    CARET_DOWN("mfx-caret-down", '\uE909'),
-    CARET_LEFT("mfx-caret-left", '\uE90A'),
-    CARET_RIGHT("mfx-caret-right", '\uE90B'),
-    CARET_UP("mfx-caret-up", '\uE90C'),
-    CASPIAN_MARK("mfx-caspian-mark", '\uE90D'),
-    CHART_PIE("mfx-chart-pie", '\uE90E'),
-    CHECK_CIRCLE("mfx-check-circle", '\uE90F'),
-    CHEVRON_DOWN("mfx-chevron-down", '\uE910'),
-    CHEVRON_LEFT("mfx-chevron-left", '\uE911'),
-    CHEVRON_RIGHT("mfx-chevron-right", '\uE912'),
-    CHEVRON_UP("mfx-chevron-up", '\uE913'),
-    CIRCLE("mfx-circle", '\uE914'),
-    CONTENT_COPY("mfx-content-copy", '\uE915'),
-    DASHBOARD("mfx-dashboard", '\uE916'),
-    DEBUG("mfx-debug", '\uE917'),
-    EXCLAMATION_CIRCLE("mfx-exclamation-circle", '\uE918'),
-    EXCLAMATION_TRIANGLE("mfx-exclamation-triangle", '\uE919'),
-    EXPAND("mfx-expand", '\uE91A'),
-    EYE("mfx-eye", '\uE91B'),
-    EYE_SLASH("mfx-eye-slash", '\uE91C'),
-    FILTER("mfx-filter", '\uE91D'),
-    FILTER_ALT("mfx-filter-alt", '\uE91E'),
-    FILTER_ALT_CLEAR("mfx-filter-alt-clear", '\uE91F'),
-    FIRST_PAGE("mfx-first-page", '\uE920'),
-    GEAR("mfx-gear", '\uE921'),
-    GOOGLE("mfx-google", '\uE922'),
-    GOOGLE_DRIVE("mfx-google-drive", '\uE923'),
-    HOME("mfx-home", '\uE924'),
-    INFO("mfx-info", '\uE925'),
-    INFO_CIRCLE("mfx-info-circle", '\uE926'),
-    LAST_PAGE("mfx-last-page", '\uE927'),
-    LEVEL_UP("mfx-level-up", '\uE928'),
-    LOCK("mfx-lock", '\uE929'),
-    LOCK_OPEN("mfx-lock-open", '\uE92A'),
-    MINUS("mfx-minus", '\uE92B'),
-    MINUS_CIRCLE("mfx-minus-circle", '\uE92C'),
-    MODENA_MARK("mfx-modena-mark", '\uE92D'),
-    SEARCH("mfx-search", '\uE92E'),
-    SEARCH_PLUS("mfx-search-plus", '\uE92F'),
-    SLIDERS("mfx-sliders", '\uE930'),
-    STEP_BACKWARD("mfx-step-backward", '\uE931'),
-    STEP_FORWARD("mfx-step-forward", '\uE932'),
-    SYNC("mfx-sync", '\uE933'),
-    SYNC_LIGHT("mfx-sync-light", '\uE934'),
-    USER("mfx-user", '\uE935'),
-    USERS("mfx-users", '\uE936'),
-    VARIANT10_MARK("mfx-variant10-mark", '\uE937'),
-    VARIANT11_MARK("mfx-variant11-mark", '\uE938'),
-    VARIANT12_MARK("mfx-variant12-mark", '\uE939'),
-    VARIANT3_MARK("mfx-variant3-mark", '\uE93A'),
-    VARIANT4_MARK("mfx-variant4-mark", '\uE93B'),
-    VARIANT5_MARK("mfx-variant5-mark", '\uE93C'),
-    VARIANT6_MARK("mfx-variant6-mark", '\uE93D'),
-    VARIANT7_MARK("mfx-variant7-mark", '\uE93E'),
-    VARIANT8_MARK("mfx-variant8-mark", '\uE93F'),
-    VARIANT9_MARK("mfx-variant9-mark", '\uE940'),
-    X("mfx-x", '\uE941'),
-    X_ALT("mfx-x-alt", '\uE942'),
-    X_CIRCLE("mfx-x-circle", '\uE943'),
-    X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE944'),
+    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'),
+    FILTER("mfx-filter", '\uE922'),
+    FILTER_ALT("mfx-filter-alt", '\uE923'),
+    FILTER_ALT_CLEAR("mfx-filter-alt-clear", '\uE924'),
+    FIRST_PAGE("mfx-first-page", '\uE925'),
+    FIT("mfx-fit", '\uE926'),
+    GEAR("mfx-gear", '\uE927'),
+    GOOGLE("mfx-google", '\uE928'),
+    GOOGLE_DRIVE("mfx-google-drive", '\uE929'),
+    HOME("mfx-home", '\uE92A'),
+    INFO("mfx-info", '\uE92B'),
+    INFO_CIRCLE("mfx-info-circle", '\uE92C'),
+    LAST_PAGE("mfx-last-page", '\uE92D'),
+    LEVEL_UP("mfx-level-up", '\uE92E'),
+    LOCK("mfx-lock", '\uE92F'),
+    LOCK_OPEN("mfx-lock-open", '\uE930'),
+    MINUS("mfx-minus", '\uE931'),
+    MINUS_CIRCLE("mfx-minus-circle", '\uE932'),
+    MODENA_MARK("mfx-modena-mark", '\uE933'),
+    NEXT("mfx-next", '\uE934'),
+    RESTORE("mfx-restore", '\uE935'),
+    SEARCH("mfx-search", '\uE936'),
+    SEARCH_PLUS("mfx-search-plus", '\uE937'),
+    SELECT_ALL("mfx-select-all", '\uE938'),
+    SLIDERS("mfx-sliders", '\uE939'),
+    STEP_BACKWARD("mfx-step-backward", '\uE93A'),
+    STEP_FORWARD("mfx-step-forward", '\uE93B'),
+    SYNC("mfx-sync", '\uE93C'),
+    SYNC_LIGHT("mfx-sync-light", '\uE93D'),
+    USER("mfx-user", '\uE93E'),
+    USERS("mfx-users", '\uE93F'),
+    VARIANT10_MARK("mfx-variant10-mark", '\uE940'),
+    VARIANT11_MARK("mfx-variant11-mark", '\uE941'),
+    VARIANT12_MARK("mfx-variant12-mark", '\uE942'),
+    VARIANT3_MARK("mfx-variant3-mark", '\uE943'),
+    VARIANT4_MARK("mfx-variant4-mark", '\uE944'),
+    VARIANT5_MARK("mfx-variant5-mark", '\uE945'),
+    VARIANT6_MARK("mfx-variant6-mark", '\uE946'),
+    VARIANT7_MARK("mfx-variant7-mark", '\uE947'),
+    VARIANT8_MARK("mfx-variant8-mark", '\uE948'),
+    VARIANT9_MARK("mfx-variant9-mark", '\uE949'),
+    X("mfx-x", '\uE94A'),
+    X_ALT("mfx-x-alt", '\uE94B'),
+    X_CIRCLE("mfx-x-circle", '\uE94C'),
+    X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE94D'),
+    X_LIGHT("mfx-x-light", '\uE94E'),
     ;
 
     public static FontResources findByDescription(String description) {

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

@@ -562,10 +562,11 @@ public class MFXComboBoxSkin<T> extends SkinBase<MFXComboBox<T>> {
 
         validate.resizeRelocate(lx, ly, lw, lh);
 
+        double extraX = getSkinnable().getComboStyle() == Styles.ComboBoxStyles.STYLE3 ? 5 : 3;
         double iconWidth = icon.getPrefWidth();
         double iconHeight = icon.getPrefHeight();
         double center = ((snappedTopInset() + snappedBottomInset()) / 2.0) + ((h - iconHeight) / 2.0);
-        icon.resizeRelocate(w - iconWidth, center, iconWidth, iconHeight);
+        icon.resizeRelocate(w - iconWidth + extraX, center, iconWidth, iconHeight);
         focusedLine.relocate(0, h);
         unfocusedLine.relocate(0, h);
     }

+ 96 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXContextMenuItemSkin.java

@@ -0,0 +1,96 @@
+/*
+ *     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.skins;
+import io.github.palexdev.materialfx.controls.MFXContextMenuItem;
+import io.github.palexdev.materialfx.controls.MFXIconWrapper;
+import javafx.geometry.VPos;
+import javafx.scene.control.Label;
+import javafx.scene.control.SkinBase;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.ColumnConstraints;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.RowConstraints;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a basic implementation of a {@code Skin} used by every {@link MFXContextMenuItem}.
+ * <p>
+ * It is basically an HBox which contains two labels to show the text and the accelerator.
+ */
+public class MFXContextMenuItemSkin extends SkinBase<MFXContextMenuItem> {
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public MFXContextMenuItemSkin(MFXContextMenuItem item) {
+        super(item);
+
+        MFXIconWrapper iconWrapper = new MFXIconWrapper(null, 24);
+        iconWrapper.iconProperty().bind(item.iconProperty());
+
+        Label text = new Label();
+        text.textProperty().bind(item.textProperty());
+        text.minWidthProperty().bind(item.textWidthProperty());
+        text.alignmentProperty().bind(item.textAlignmentProperty());
+        text.paddingProperty().bind(item.textInsetsProperty());
+
+        Label accelerator = new Label();
+        accelerator.getStyleClass().add("accelerator");
+        accelerator.textProperty().bind(item.acceleratorProperty());
+        accelerator.minWidthProperty().bind(item.acceleratorWidthProperty());
+        accelerator.alignmentProperty().bind(item.acceleratorAlignmentProperty());
+        accelerator.paddingProperty().bind(item.acceleratorInsetsProperty());
+
+        GridPane gridPane = new GridPane();
+        gridPane.vgapProperty().bind(item.spacingProperty());
+        gridPane.setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);
+
+        List<ColumnConstraints> columnConstraints = new ArrayList<>();
+        for (int i = 0; i < 3; i++) {
+            ColumnConstraints cc = new ColumnConstraints();
+            cc.setPrefWidth(Region.USE_COMPUTED_SIZE);
+
+            columnConstraints.add(cc);
+        }
+        gridPane.getColumnConstraints().setAll(columnConstraints);
+
+        RowConstraints rowConstraints = new RowConstraints();
+        rowConstraints.setMinHeight(27);
+        rowConstraints.setPrefHeight(Region.USE_COMPUTED_SIZE);
+        rowConstraints.setValignment(VPos.CENTER);
+        gridPane.getRowConstraints().setAll(rowConstraints);
+
+        gridPane.add(iconWrapper, 0, 0);
+        gridPane.add(text, 1, 0);
+        gridPane.add(accelerator, 2, 0);
+
+        getChildren().setAll(gridPane);
+        setListeners();
+
+        item.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> text.requestFocus());
+    }
+
+    private void setListeners() {
+        MFXContextMenuItem item = getSkinnable();
+
+    }
+}

+ 43 - 39
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java

@@ -63,7 +63,6 @@ import java.time.format.TextStyle;
 import java.time.temporal.WeekFields;
 import java.util.*;
 
-import static java.time.temporal.ChronoUnit.DAYS;
 import static java.time.temporal.ChronoUnit.MONTHS;
 
 /**
@@ -131,7 +130,6 @@ public class MFXDatePickerContent extends VBox {
 
     // Date formatters
     private final ObjectProperty<DateTimeFormatter> dateFormatter = new SimpleObjectProperty<>(DateTimeFormatter.ofPattern("d/M/yyyy"));
-    private final DateTimeFormatter weekDayNameFormatter = DateTimeFormatter.ofPattern("ccc");
 
     private final BooleanProperty animateCalendar = new SimpleBooleanProperty();
 
@@ -173,13 +171,13 @@ public class MFXDatePickerContent extends VBox {
         if (localDate != null) {
             setCurrentDate(localDate);
             setYearMonth(YearMonth.of(getCurrentDate().getYear(), getCurrentDate().getMonth()));
-            lastSelectedDayCell.set(
+            setLastSelectedDayCell(
                     days.stream()
                             .filter(day -> day.getText().equals(Integer.toString(getCurrentDate().getDayOfMonth())))
                             .findFirst()
                             .orElse(null)
             );
-            lastSelectedDayCell.get().setSelectedDate(true);
+            getLastSelectedDayCell().setSelectedDate(true);
             lastSelectedYearCell = yearsList.stream()
                     .filter(year -> year.getText().equals(Integer.toString(getYearMonth().getYear())))
                     .findFirst()
@@ -248,7 +246,7 @@ public class MFXDatePickerContent extends VBox {
 
                 setYearMonth(getYearMonth().withYear(Integer.parseInt(lastSelectedYearCell.getText())));
 
-                if (lastSelectedDayCell.get() == null) {
+                if (getLastSelectedDayCell() == null) {
                     selectDay();
                 }
             });
@@ -264,24 +262,27 @@ public class MFXDatePickerContent extends VBox {
     private void createDayNameCells() {
         dayNameCells.clear();
 
-        int firstDayOfWeek = WeekFields.of(getLocale()).getFirstDayOfWeek().getValue();
-        LocalDate date = LocalDate.of(2009, 7, 12 + firstDayOfWeek);
+        Locale locale = getLocale();
+        WeekFields wf = WeekFields.of(locale);
+        DayOfWeek day = wf.getFirstDayOfWeek();
 
         int i;
-        for (i = 0; i < daysPerWeek; i++) {
-            MFXDateCell cell = new MFXDateCell();
-            cell.getStyleClass().add("day-name-cell");
-            cell.setAlignment(Pos.CENTER);
-
-            String name = weekDayNameFormatter.withLocale(getLocale()).format(date.plus(i, DAYS));
+        for (i = 0; i < DayOfWeek.values().length; i++) {
+            String name = day.getDisplayName(TextStyle.SHORT, locale);
             dayNameMap.put(name, i);
-            if (weekDayNameFormatter.getLocale() == java.util.Locale.CHINA) {
+
+            if (locale == java.util.Locale.CHINA) {
                 name = name.substring(name.length() - 1).toUpperCase();
             } else {
                 name = name.substring(0, 1).toUpperCase();
             }
-            cell.setText(name);
 
+            day = day.plus(1);
+
+            MFXDateCell cell = new MFXDateCell();
+            cell.getStyleClass().add("day-name-cell");
+            cell.setAlignment(Pos.CENTER);
+            cell.setText(name);
             dayNameCells.add(cell);
         }
     }
@@ -307,11 +308,11 @@ public class MFXDatePickerContent extends VBox {
             NodeUtils.makeRegionCircular(cell, 13);
 
             cell.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
-                if (lastSelectedDayCell.get() != null) {
-                    lastSelectedDayCell.get().setSelectedDate(false);
+                if (getLastSelectedDayCell() != null) {
+                    getLastSelectedDayCell().setSelectedDate(false);
                 }
-                lastSelectedDayCell.set(cell);
-                lastSelectedDayCell.get().setSelectedDate(true);
+                setLastSelectedDayCell(cell);
+                getLastSelectedDayCell().setSelectedDate(true);
 
                 if (lastSelectedYearCell == null) {
                     selectYear();
@@ -328,11 +329,9 @@ public class MFXDatePickerContent extends VBox {
             MFXDateCell cell = days.get(i);
             cell.setText(Integer.toString(cnt));
 
-            if (day == cnt &&
+            cell.setCurrent(day == cnt &&
                     LocalDate.now().getMonth().equals(getYearMonth().getMonth()) &&
-                    LocalDate.now().getYear() == getYearMonth().getYear()) {
-                cell.setCurrent(true);
-            }
+                    LocalDate.now().getYear() == getYearMonth().getYear());
 
             cnt++;
         }
@@ -420,13 +419,13 @@ public class MFXDatePickerContent extends VBox {
 
             refresh();
 
-            if (lastSelectedDayCell.get() != null) {
+            if (getLastSelectedDayCell() != null) {
                 MFXDateCell day = days.stream()
-                        .filter(cell -> cell.getText().equals(lastSelectedDayCell.get().getText()))
+                        .filter(cell -> cell.getText().equals(getLastSelectedDayCell().getText()))
                         .findFirst()
                         .orElse(null);
                 if (day != null) {
-                    lastSelectedDayCell.set(day);
+                    setLastSelectedDayCell(day);
                     day.setSelectedDate(true);
                 }
             }
@@ -655,7 +654,7 @@ public class MFXDatePickerContent extends VBox {
                     setCurrentDate(LocalDate.parse(selectedDate.getText(), getDateFormatter()));
                 } catch (DateTimeParseException ex) {
                     ex.printStackTrace();
-                    inputField.getValidator().add(validInput, ex.getMessage());
+                    inputField.getValidator().add(validInput, "Invalid at index " + ex.getErrorIndex());
                     validInput.set(false);
                 }
             }
@@ -746,9 +745,10 @@ public class MFXDatePickerContent extends VBox {
      * Finds the index of the first day of the week from {@link #yearMonth}.
      */
     private int firstDayIndex() {
-        DayOfWeek fd = getYearMonth().atDay(1).getDayOfWeek();
-        LocalDate date = LocalDate.of(2009, 7, 12 + fd.getValue());
-        String name = weekDayNameFormatter.withLocale(getLocale()).format(date.plus(0, DAYS));
+        Locale locale = getLocale();
+        LocalDate firstDay = getYearMonth().atDay(1);
+        DayOfWeek day = firstDay.getDayOfWeek();
+        String name = day.getDisplayName(TextStyle.SHORT, locale);
         return dayNameMap.get(name);
     }
 
@@ -770,7 +770,7 @@ public class MFXDatePickerContent extends VBox {
             setYearMonth(getYearMonth().minus(1, MONTHS));
         }
 
-        if (lastSelectedDayCell.get() == null) {
+        if (getLastSelectedDayCell() == null) {
             selectDay();
         }
         if (lastSelectedYearCell == null) {
@@ -778,11 +778,11 @@ public class MFXDatePickerContent extends VBox {
         }
 
         if (
-                lastSelectedDayCell.get() != null &&
+                getLastSelectedDayCell() != null &&
                         getYearMonth().getMonth().length(getYearMonth().isLeapYear()) <
-                                Integer.parseInt(lastSelectedDayCell.get().getText())
+                                Integer.parseInt(getLastSelectedDayCell().getText())
         ) {
-            lastSelectedDayCell.get().setSelectedDate(false);
+            getLastSelectedDayCell().setSelectedDate(false);
 
             MFXDateCell cell = days.stream()
                     .filter(dayCell -> dayCell.getText().equals("1"))
@@ -791,7 +791,7 @@ public class MFXDatePickerContent extends VBox {
 
             if (cell != null) {
                 cell.setSelectedDate(true);
-                lastSelectedDayCell.set(cell);
+                setLastSelectedDayCell(cell);
             }
         }
     }
@@ -812,16 +812,16 @@ public class MFXDatePickerContent extends VBox {
                     .filter(day -> day.getText().equals("1"))
                     .findFirst()
                     .ifPresent(day -> {
-                        lastSelectedDayCell.set(day);
-                        lastSelectedDayCell.get().setSelectedDate(true);
+                        setLastSelectedDayCell(day);
+                        getLastSelectedDayCell().setSelectedDate(true);
                     });
         } else {
             days.stream()
                     .filter(day -> day.getText().equals(Integer.toString(getCurrentDate().getDayOfMonth())))
                     .findFirst()
                     .ifPresent(day -> {
-                        lastSelectedDayCell.set(day);
-                        lastSelectedDayCell.get().setSelectedDate(true);
+                        setLastSelectedDayCell(day);
+                        getLastSelectedDayCell().setSelectedDate(true);
                     });
         }
     }
@@ -883,6 +883,10 @@ public class MFXDatePickerContent extends VBox {
         return lastSelectedDayCell;
     }
 
+    public void setLastSelectedDayCell(MFXDateCell lastSelectedDayCell) {
+        this.lastSelectedDayCell.set(lastSelectedDayCell);
+    }
+
     public DateTimeFormatter getDateFormatter() {
         return dateFormatter.get();
     }

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

@@ -318,7 +318,9 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
     /**
      * Specifies the behavior for maxPopupHeight and maxPopupWidth properties, also adds the
      * {@link #popupHandler} to the scene to close the popup in case it is open and the mouse is not
-     * pressed on the combo box.
+     * pressed on the combo box. And resets the control when the popup is hidden.
+     *
+     * @see #reset()
      */
     private void popupBehavior() {
         MFXFilterComboBox<T> comboBox = getSkinnable();
@@ -355,6 +357,8 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
                 listSelectionModel.select(selectionModelMock.getSelectedIndex(), selectionModelMock.getSelectedItem(), null);
             }
         });
+
+        popup.addEventHandler(WindowEvent.WINDOW_HIDDEN, event -> reset());
     }
 
     /**
@@ -523,6 +527,7 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
 
         valueLabel.setVisible(false);
         searchField = new MFXTextField("");
+        searchField.setMFXContextMenu(null);
         comboBox.editorFocusedProperty().bind(searchField.focusedProperty());
         searchField.setPromptText("Search...");
         searchField.setId("search-field");
@@ -709,10 +714,11 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
 
         validate.resizeRelocate(lx, ly, lw, lh);
 
+        double extraX = getSkinnable().getComboStyle() == Styles.ComboBoxStyles.STYLE3 ? 5 : 3;
         double iconWidth = icon.getPrefWidth();
         double iconHeight = icon.getPrefHeight();
         double center = ((snappedTopInset() + snappedBottomInset()) / 2.0) + ((h - iconHeight) / 2.0);
-        icon.resizeRelocate(w - iconWidth, center, iconWidth, iconHeight);
+        icon.resizeRelocate(w - iconWidth + extraX, center, iconWidth, iconHeight);
         focusedLine.relocate(0, h);
         unfocusedLine.relocate(0, h);
     }

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

@@ -250,6 +250,7 @@ public class MFXLabelSkin extends SkinBase<MFXLabel> {
 
         textNode.setVisible(false);
         MFXTextField textField = new MFXTextField(label.getText());
+        textField.setMFXContextMenu(null);
         label.editorFocusedProperty().bind(textField.focusedProperty());
         textField.setId("editor-node");
         textField.setManaged(false);

+ 32 - 26
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPasswordFieldSkin.java

@@ -18,9 +18,10 @@
 
 package io.github.palexdev.materialfx.skins;
 
-import io.github.palexdev.materialfx.beans.MFXContextMenuItem;
 import io.github.palexdev.materialfx.controls.MFXContextMenu;
+import io.github.palexdev.materialfx.controls.MFXContextMenuItem;
 import io.github.palexdev.materialfx.controls.MFXPasswordField;
+import io.github.palexdev.materialfx.font.MFXFontIcon;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.StringProperty;
@@ -257,53 +258,58 @@ public class MFXPasswordFieldSkin extends MFXTextFieldSkin {
     protected void setContextMenu() {
         MFXPasswordField passwordField = (MFXPasswordField) getSkinnable();
 
-        MFXContextMenuItem copy = new MFXContextMenuItem(
-                "Copy",
-                event -> {
+        MFXContextMenuItem copy = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-content-copy", 14))
+                .setText("Copy")
+                .setAccelerator("Ctrl + C")
+                .setAction(event -> {
                     if (passwordField.isAllowCopy()) {
                         passwordField.copy();
                     }
-                }
-        );
+                });
 
-        MFXContextMenuItem cut = new MFXContextMenuItem(
-                "Cut",
-                event -> {
+        MFXContextMenuItem cut = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-content-cut", 14))
+                .setText("Cut")
+                .setAccelerator("Ctrl + X")
+                .setAction(event -> {
                     if (passwordField.isAllowCut()) {
                         passwordField.cut();
                         handleDeletion(passwordField.getText().length());
                     }
-                }
-        );
+                });
 
-        MFXContextMenuItem paste = new MFXContextMenuItem(
-                "Paste",
-                event -> {
+        MFXContextMenuItem paste = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-content-paste", 14))
+                .setText("Paste")
+                .setAccelerator("Ctrl + V")
+                .setAction(event -> {
                     if (passwordField.isAllowPaste()) {
                         handlePaste();
                     }
-                }
-        );
+                });
 
-        MFXContextMenuItem delete = new MFXContextMenuItem(
-                "Delete",
-                event -> handleDeletion(passwordField.getText().length())
-        );
+        MFXContextMenuItem delete = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-delete-alt", 16))
+                .setText("Delete")
+                .setAccelerator("Ctrl + D")
+                .setAction(event -> handleDeletion(passwordField.getText().length()));
 
-        MFXContextMenuItem selectAll = new MFXContextMenuItem(
-                "Select All",
-                event -> passwordField.selectAll()
-        );
+        MFXContextMenuItem selectAll = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-select-all", 16))
+                .setText("Select All")
+                .setAccelerator("Ctrl + A")
+                .setAction(event -> passwordField.selectAll());
 
         passwordField.setMFXContextMenu(
-                new MFXContextMenu.Builder()
+                MFXContextMenu.Builder.build(passwordField)
                         .addMenuItem(copy)
                         .addMenuItem(cut)
                         .addMenuItem(paste)
                         .addMenuItem(delete)
                         .addSeparator()
                         .addMenuItem(selectAll)
-                        .get()
+                        .install()
         );
     }
 

+ 10 - 3
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableColumnSkin.java

@@ -28,6 +28,7 @@ import javafx.geometry.Insets;
 import javafx.geometry.Pos;
 import javafx.scene.control.Label;
 import javafx.scene.control.SkinBase;
+import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Region;
@@ -58,10 +59,16 @@ public class MFXTableColumnSkin<T> extends SkinBase<MFXTableColumn<T>> {
         label = new Label();
         label.textProperty().bind(column.textProperty());
 
-        MFXFontIcon icon = new MFXFontIcon(column.isResizable() ? "mfx-lock" : "mfx-lock-open", 14);
-        lockIcon = new MFXIconWrapper(icon, 18).defaultRippleGeneratorBehavior();
+        MFXFontIcon icon = new MFXFontIcon(column.isResizable() ? "mfx-lock" : "mfx-lock-open", 12);
+        icon.descriptionProperty().bind(Bindings.createStringBinding(
+                () -> column.isResizable() ? "mfx-lock" : "mfx-lock-open",
+                column.resizableProperty()
+        ));
+        lockIcon = new MFXIconWrapper(icon, 20).defaultRippleGeneratorBehavior();
         lockIcon.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
-            column.setResizable(!column.isResizable());
+            if (event.getButton() == MouseButton.PRIMARY) {
+                column.setResizable(!column.isResizable());
+            }
             event.consume();
         });
         lockIcon.visibleProperty().bind(Bindings.createBooleanBinding(

+ 33 - 34
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java

@@ -18,7 +18,6 @@
 
 package io.github.palexdev.materialfx.skins;
 
-import io.github.palexdev.materialfx.beans.MFXContextMenuItem;
 import io.github.palexdev.materialfx.controls.*;
 import io.github.palexdev.materialfx.controls.MFXTableView.MFXTableViewEvent;
 import io.github.palexdev.materialfx.controls.cell.MFXTableColumn;
@@ -625,37 +624,37 @@ public class MFXTableViewSkin<T> extends SkinBase<MFXTableView<T>> {
     protected void addContextMenu(MFXTableColumn<T> column) {
         MFXTableView<T> tableView = getSkinnable();
 
-        MFXContextMenuItem restoreWidthThis = new MFXContextMenuItem(
-                "Restore this column width",
-                event -> column.setMinWidth(column.getInitialWidth())
-        );
-
-        MFXContextMenuItem restoreWidthAll = new MFXContextMenuItem(
-                "Restore all columns width",
-                event -> tableView.getTableColumns().forEach(c -> c.setMinWidth(c.getInitialWidth()))
-        );
-
-        MFXContextMenuItem autoSizeThis = new MFXContextMenuItem(
-                "Autosize this column",
-                event -> autoSizeColumn(column)
-        );
-
-        MFXContextMenuItem autoSizeAll = new MFXContextMenuItem(
-                "Autosize all columns",
-                event -> tableView.getTableColumns().forEach(this::autoSizeColumn)
-        );
-
-        MFXContextMenuItem lockSize = new MFXContextMenuItem(
-                "Lock this column size",
-                event -> column.setResizable(false)
-        );
-
-        MFXContextMenuItem unlockSize = new MFXContextMenuItem(
-                "Unlock this column size",
-                event -> column.setResizable(true)
-        );
-
-        new MFXContextMenu.Builder()
+        MFXContextMenuItem restoreWidthThis = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-restore", 16))
+                .setText("Restore this column width")
+                .setAction(event -> column.setMinWidth(column.getInitialWidth()));
+
+        MFXContextMenuItem restoreWidthAll = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-restore", 16))
+                .setText("Restore all columns width")
+                .setAction(event -> tableView.getTableColumns().forEach(c -> c.setMinWidth(c.getInitialWidth())));
+
+        MFXContextMenuItem autoSizeThis = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-fit", 16))
+                .setText("Autosize this column")
+                .setAction(event -> autoSizeColumn(column));
+
+        MFXContextMenuItem autoSizeAll = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-fit", 16))
+                .setText("Autosize all columns")
+                .setAction(event -> tableView.getTableColumns().forEach(this::autoSizeColumn));
+
+        MFXContextMenuItem lockSize = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-lock", 12))
+                .setText("Lock this column size")
+                .setAction(event -> column.setResizable(false));
+
+        MFXContextMenuItem unlockSize = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-lock-open", 12))
+                .setText("Unlock this column size")
+                .setAction(event -> column.setResizable(true));
+
+        MFXContextMenu.Builder.build(column)
                 .addMenuItem(autoSizeAll)
                 .addMenuItem(autoSizeThis)
                 .addSeparator()
@@ -664,7 +663,7 @@ public class MFXTableViewSkin<T> extends SkinBase<MFXTableView<T>> {
                 .addSeparator()
                 .addMenuItem(lockSize)
                 .addMenuItem(unlockSize)
-                .install(column);
+                .install();
     }
 
     /**
@@ -684,7 +683,7 @@ public class MFXTableViewSkin<T> extends SkinBase<MFXTableView<T>> {
                     .collect(Collectors.toList());
             List<MFXTableRowCell> rowCells = new ArrayList<>();
             tableRows.forEach(row -> {
-                MFXTableRowCell rowCell = (MFXTableRowCell) row.getChildren().get(index);
+                MFXTableRowCell rowCell = (MFXTableRowCell) row.getChildren().get(index + 1);
                 rowCell.requestLayout();
                 if (rowCell.isTruncated()) {
                     rowCells.add(rowCell);

+ 5 - 14
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenu.css

@@ -17,24 +17,15 @@
  */
 
 .mfx-context-menu {
-    -fx-background-radius: 4;
-    -fx-border-color: #A4B1B6;
-    -fx-border-radius: 4;
-    -fx-padding: 3 0 3 0;
-}
-
-.label {
-    -fx-min-height: 27;
-    -fx-padding: 8;
     -fx-background-color: white;
-}
-
-.label:hover {
-    -fx-background-color: rgb(120, 150, 255);
+    -fx-background-radius: 5;
+    -fx-border-color: #ADB5BD;
+    -fx-border-radius: 5;
+    -fx-padding: 1;
 }
 
 .separator {
-    -fx-border-color: #A4B1B6;
+    -fx-border-color: #ADB5BD;
     -fx-stroke-width: 0.5;
     -fx-opacity: 0.5;
 }

+ 25 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXContextMenuItem.css

@@ -0,0 +1,25 @@
+/*
+ *     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/>.
+ */
+
+.mfx-context-menu-item {
+    -fx-background-color: white;
+}
+
+.mfx-context-menu-item:hover {
+    -fx-background-color: #ECF9FD;
+}

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