💥 MFXCheckbox has been reworked

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

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

Skin Package
💥 MFXCheckboxSkin has been reworked

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

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

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
This commit is contained in:
palexdev 2021-11-09 12:44:01 +01:00
parent e9c7fe4afd
commit a6d7ee11d4
18 changed files with 443 additions and 394 deletions

0
.gitattributes vendored Normal file → Executable file
View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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