From a6d7ee11d4b1356eeca56758b658787cbd8a3988 Mon Sep 17 00:00:00 2001 From: palexdev Date: Tue, 9 Nov 2021 12:44:01 +0100 Subject: [PATCH] :boom: MFXCheckbox has been reworked Controls Package :sparkles: Added new features to MFXCheckbox :fire: MFXCheckbox, removed all styleable properties as there's no need (see MFXCheckBox.css to learn how to style the icon) :sparkles: LabeledControlWrapper, this new control will soon be used in all eligible MaterialFX controls. It's basically a Label that has all its properties bound to a labeled Control. This way the control's text is fully customizable with CSS. :recycle: MFXIconWrapper is now capable of auto-sizing itself, just use -1 as size Font Package :bug: MFXFontIcon, fixed a very annoying bug that prevented icons from being styled with CSS if properties were set with code (via setters/constructors). To be precise rather than a bug it's the normal behavior of JavaFX's styleable properties, but it's annoying so I added a workaround to make it always work :sparkles: Added new resources Skin Package :boom: MFXCheckboxSkin has been reworked MFXCheckBox.css has been reworked too, since now most of the MFXCheckbox appearance is determined by its stylesheet Other changed files that are not mentioned here are a consequence of the above changes Signed-off-by: palexdev --- .gitattributes | 0 .../controllers/StepperDemoController.java | 2 - .../controllers/TableViewsDemoController.java | 4 +- demo/src/test/java/CheckboxTest.java | 25 ++ demo/src/test/java/Launcher.java | 2 +- .../functional/ComparatorProperty.java | 0 .../controls/LabeledControlWrapper.java | 39 +++ .../materialfx/controls/MFXCheckbox.java | 152 +++------ .../materialfx/controls/MFXDatePicker.java | 6 +- .../materialfx/controls/MFXIconWrapper.java | 15 + .../controls/cell/MFXCheckTreeCell.java | 2 - .../controls/cell/MFXNotificationCell.java | 1 - .../materialfx/font/FontResources.java | 97 +++--- .../palexdev/materialfx/font/MFXFontIcon.java | 121 ++++--- .../materialfx/skins/MFXCheckboxSkin.java | 316 +++++++++--------- .../palexdev/materialfx/css/MFXCheckBox.css | 50 ++- .../palexdev/materialfx/css/MFXTreeCell.css | 5 + .../materialfx/fonts/MFXResources.ttf | Bin 20080 -> 20124 bytes 18 files changed, 443 insertions(+), 394 deletions(-) mode change 100644 => 100755 .gitattributes create mode 100755 demo/src/test/java/CheckboxTest.java mode change 100644 => 100755 materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/ComparatorProperty.java create mode 100755 materialfx/src/main/java/io/github/palexdev/materialfx/controls/LabeledControlWrapper.java diff --git a/.gitattributes b/.gitattributes old mode 100644 new mode 100755 diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java index 3f7fc12e..8c936275 100755 --- a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java +++ b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java @@ -77,8 +77,6 @@ public class StepperDemoController implements Initializable { lastNameField.setPromptText("Last Name..."); genderCombo.setItems(FXCollections.observableArrayList("Male", "Female", "Other")); - checkbox.setMarkType("mfx-variant7-mark"); - List stepperToggles = createSteps(); stepper.getStepperToggles().addAll(stepperToggles); diff --git a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TableViewsDemoController.java b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TableViewsDemoController.java index 85374fe5..85be9527 100755 --- a/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TableViewsDemoController.java +++ b/demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TableViewsDemoController.java @@ -41,7 +41,6 @@ import javafx.scene.control.TableColumn; import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; import javafx.stage.Modality; import javafx.stage.Stage; @@ -49,7 +48,6 @@ import java.net.URL; import java.util.Comparator; import java.util.List; import java.util.ResourceBundle; -import java.util.concurrent.Callable; import static io.github.palexdev.materialfx.demo.model.Machine.State.OFFLINE; import static io.github.palexdev.materialfx.demo.model.Machine.State.ONLINE; @@ -166,7 +164,7 @@ public class TableViewsDemoController implements Initializable { rowCell.setGraphicTextGap(4); MFXFontIcon icon = new MFXFontIcon("mfx-circle", 6); icon.colorProperty().bind(Bindings.createObjectBinding( - (Callable) () -> machine.getState() == ONLINE ? Color.LIMEGREEN : Color.SALMON, + () -> machine.getState() == ONLINE ? Color.LIMEGREEN : Color.SALMON, machine.stateProperty()) ); rowCell.setLeadingGraphic(icon); diff --git a/demo/src/test/java/CheckboxTest.java b/demo/src/test/java/CheckboxTest.java new file mode 100755 index 00000000..b0ffa774 --- /dev/null +++ b/demo/src/test/java/CheckboxTest.java @@ -0,0 +1,25 @@ +import io.github.palexdev.materialfx.controls.MFXCheckbox; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Font; +import javafx.stage.Stage; +import org.scenicview.ScenicView; + +public class CheckboxTest extends Application { + + @Override + public void start(Stage primaryStage) { + StackPane stackPane = new StackPane(); + + MFXCheckbox checkbox = new MFXCheckbox("Checkbox"); + checkbox.setFont(Font.font("Roboto Medium", 14)); + stackPane.getChildren().add(checkbox); + + Scene scene = new Scene(stackPane, 800, 600); + primaryStage.setScene(scene); + primaryStage.show(); + + ScenicView.show(scene); + } +} diff --git a/demo/src/test/java/Launcher.java b/demo/src/test/java/Launcher.java index b38a9f21..aec87bb0 100755 --- a/demo/src/test/java/Launcher.java +++ b/demo/src/test/java/Launcher.java @@ -3,6 +3,6 @@ import javafx.application.Application; public class Launcher { public static void main(String[] args) { - Application.launch(NotificationsTest.class, args); + Application.launch(CheckboxTest.class, args); } } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/ComparatorProperty.java b/materialfx/src/main/java/io/github/palexdev/materialfx/beans/properties/functional/ComparatorProperty.java old mode 100644 new mode 100755 diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/LabeledControlWrapper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/LabeledControlWrapper.java new file mode 100755 index 00000000..c14d7e48 --- /dev/null +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/LabeledControlWrapper.java @@ -0,0 +1,39 @@ +package io.github.palexdev.materialfx.controls; + +import javafx.scene.control.Label; +import javafx.scene.control.Labeled; + +public class LabeledControlWrapper extends Label { + + public LabeledControlWrapper(Labeled labeled) { + super(); + + // Init + setText(labeled.getText()); + setFont(labeled.getFont()); + setTextFill(labeled.getTextFill()); + setWrapText(labeled.isWrapText()); + setTextAlignment(labeled.getTextAlignment()); + setTextOverrun(labeled.getTextOverrun()); + setEllipsisString(labeled.getEllipsisString()); + setUnderline(labeled.isUnderline()); + setLineSpacing(labeled.getLineSpacing()); + setGraphicTextGap(labeled.getGraphicTextGap()); + setContentDisplay(labeled.getContentDisplay()); + setGraphic(labeled.getGraphic()); + + // Bindings + textProperty().bind(labeled.textProperty()); + fontProperty().bind(labeled.fontProperty()); + textFillProperty().bind(labeled.textFillProperty()); + wrapTextProperty().bind(labeled.wrapTextProperty()); + textAlignmentProperty().bind(labeled.textAlignmentProperty()); + textOverrunProperty().bind(labeled.textOverrunProperty()); + ellipsisStringProperty().bind(labeled.ellipsisStringProperty()); + underlineProperty().bind(labeled.underlineProperty()); + lineSpacingProperty().bind(labeled.lineSpacingProperty()); + graphicTextGapProperty().bind(labeled.graphicTextGapProperty()); + contentDisplayProperty().bind(labeled.contentDisplayProperty()); + graphicProperty().bind(labeled.graphicProperty()); + } +} diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java index fcfd90fe..657e35e7 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java @@ -20,11 +20,12 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; import io.github.palexdev.materialfx.skins.MFXCheckboxSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.css.*; import javafx.scene.control.CheckBox; +import javafx.scene.control.ContentDisplay; import javafx.scene.control.Skin; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; import java.util.ArrayList; import java.util.Collections; @@ -44,12 +45,13 @@ public class MFXCheckbox extends CheckBox { private final String STYLE_CLASS = "mfx-checkbox"; private final String STYLESHEET = MFXResourcesLoader.load("css/MFXCheckBox.css"); + private final ObjectProperty contentDisposition = new SimpleObjectProperty<>(ContentDisplay.LEFT); + //================================================================================ // Constructors //================================================================================ public MFXCheckbox() { - setText("CheckBox"); - initialize(); + this(""); } public MFXCheckbox(String text) { @@ -64,102 +66,45 @@ public class MFXCheckbox extends CheckBox { getStyleClass().add(STYLE_CLASS); } + public ContentDisplay getContentDisposition() { + return contentDisposition.get(); + } + + /** + * Specifies how the checkbox is positioned relative to the text. + */ + public ObjectProperty contentDispositionProperty() { + return contentDisposition; + } + + public void setContentDisposition(ContentDisplay contentDisposition) { + this.contentDisposition.set(contentDisposition); + } + //================================================================================ // Stylesheet properties //================================================================================ - /** - * Specifies the color of the box when it's checked. - * - * @see Color - */ - private final StyleableObjectProperty checkedColor = new SimpleStyleableObjectProperty<>( - StyleableProperties.CHECKED_COLOR, + private final StyleableDoubleProperty gap = new SimpleStyleableDoubleProperty( + StyleableProperties.GAP, this, - "checkedColor", - Color.rgb(15, 157, 88) + "gap", + 8.0 ); - /** - * Specifies the color of the box when it's unchecked. - * - * @see Color - */ - private final StyleableObjectProperty uncheckedColor = new SimpleStyleableObjectProperty<>( - StyleableProperties.UNCHECKED_COLOR, - this, - "uncheckedColor", - Color.rgb(90, 90, 90) - ); + public double getGap() { + return gap.get(); + } /** - * Specifies the shape of the mark from a predefined set. - * - * @see javafx.scene.shape.SVGPath + * Specifies the spacing between the checkbox and the text. */ - private final StyleableStringProperty markType = new SimpleStyleableStringProperty( - StyleableProperties.MARK_TYPE, - 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 StyleableDoubleProperty gapProperty() { + return gap; } - public StyleableObjectProperty checkedColorProperty() { - return checkedColor; - } - - public void setCheckedColor(Paint checkedColor) { - this.checkedColor.set(checkedColor); - } - - public Paint getUncheckedColor() { - return uncheckedColor.get(); - } - - public StyleableObjectProperty 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); + public void setGap(double gap) { + this.gap.set(gap); } //================================================================================ @@ -168,37 +113,16 @@ public class MFXCheckbox extends CheckBox { private static class StyleableProperties { private static final List> cssMetaDataList; - private static final CssMetaData CHECKED_COLOR = - FACTORY.createPaintCssMetaData( - "-mfx-checked-color", - MFXCheckbox::checkedColorProperty, - Color.rgb(15, 157, 88) - ); - - private static final CssMetaData UNCHECKED_COLOR = - FACTORY.createPaintCssMetaData( - "-mfx-unchecked-color", - MFXCheckbox::uncheckedColorProperty, - Color.rgb(90, 90, 90) - ); - - private static final CssMetaData MARK_TYPE = - FACTORY.createStringCssMetaData( - "-mfx-mark-type", - MFXCheckbox::markTypeProperty, - "mfx-modena-mark" - ); - - private static final CssMetaData MARK_SIZE = + private static final CssMetaData GAP = FACTORY.createSizeCssMetaData( - "-mfx-mark-size", - MFXCheckbox::markSizeProperty, - 12 + "-mfx-gap", + MFXCheckbox::gapProperty, + 8.0 ); static { List> ckbCssMetaData = new ArrayList<>(CheckBox.getClassCssMetaData()); - Collections.addAll(ckbCssMetaData, CHECKED_COLOR, UNCHECKED_COLOR, MARK_TYPE, MARK_SIZE); + Collections.addAll(ckbCssMetaData, GAP); cssMetaDataList = Collections.unmodifiableList(ckbCssMetaData); } } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java index 2d4fd03d..895caf85 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java @@ -19,8 +19,8 @@ package io.github.palexdev.materialfx.controls; import io.github.palexdev.materialfx.MFXResourcesLoader; -import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; import io.github.palexdev.materialfx.beans.PositionBean; +import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; import io.github.palexdev.materialfx.font.MFXFontIcon; import io.github.palexdev.materialfx.skins.MFXDatePickerContent; import io.github.palexdev.materialfx.utils.NodeUtils; @@ -108,7 +108,7 @@ public class MFXDatePicker extends VBox { value.setMinWidth(64); calendar = new MFXFontIcon("mfx-calendar-semi-black"); calendar.getStyleClass().add("calendar-icon"); - calendar.setColor(getPickerColor()); + calendar.setColor((Color) getPickerColor()); calendar.setSize(20); stackPane = new StackPane(value, calendar); stackPane.setPadding(new Insets(5, -2.5, 5, 5)); @@ -247,7 +247,7 @@ public class MFXDatePicker extends VBox { calendar.setColor(Color.LIGHTGRAY); } else { line.setStroke(getLineColor()); - calendar.setColor(getPickerColor()); + calendar.setColor((Color) getPickerColor()); } }); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java index 5ddad181..82a0d4dc 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXIconWrapper.java @@ -28,6 +28,7 @@ import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; @@ -222,4 +223,18 @@ public class MFXIconWrapper extends StackPane { public ObservableList 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); + } + } } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java index 25d38901..646b6871 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java @@ -75,8 +75,6 @@ public class MFXCheckTreeCell extends MFXSimpleTreeCell { addListeners(); checked.bind(item.checkedProperty()); indeterminate.bind(item.indeterminateProperty()); - checkbox.setMarkType("mfx-variant3-mark"); - checkbox.setMarkSize(8); } /** diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXNotificationCell.java b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXNotificationCell.java index 36fd2227..e3dea835 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXNotificationCell.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXNotificationCell.java @@ -48,7 +48,6 @@ public class MFXNotificationCell extends HBox implements Cell { checkbox = new MFXCheckbox(""); checkbox.setId("check"); - checkbox.setMarkType("mfx-variant7-mark"); container = new StackPane(checkbox); container.setMinWidth(USE_PREF_SIZE); diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java b/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java index c4146397..12bc6008 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java @@ -19,7 +19,7 @@ package io.github.palexdev.materialfx.font; /** - * Enumerator class for MaterialFX font resources. (Count: 101) + * Enumerator class for MaterialFX font resources. (Count: 102) */ public enum FontResources { ANGLE_DOWN("mfx-angle-down", '\uE900'), @@ -77,53 +77,54 @@ public enum FontResources { GOOGLE_SCRIPT("mfx-google-script", '\uE934'), GOOGLE_SITES("mfx-google-sites", '\uE935'), HOME("mfx-home", '\uE936'), - IMAGE("mfx-image", '\uE937'), - INFO("mfx-info", '\uE938'), - INFO_CIRCLE("mfx-info-circle", '\uE939'), - LAST_PAGE("mfx-last-page", '\uE93A'), - LEVEL_UP("mfx-level-up", '\uE93B'), - LOCK("mfx-lock", '\uE93C'), - LOCK_OPEN("mfx-lock-open", '\uE93D'), - MAP("mfx-map", '\uE93E'), - MINUS("mfx-minus", '\uE93F'), - MINUS_CIRCLE("mfx-minus-circle", '\uE940'), - MODENA_MARK("mfx-modena-mark", '\uE941'), - MUSIC("mfx-music", '\uE942'), - NEXT("mfx-next", '\uE943'), - REDO("mfx-redo", '\uE944'), - RESTORE("mfx-restore", '\uE945'), - SEARCH("mfx-search", '\uE946'), - SEARCH_PLUS("mfx-search-plus", '\uE947'), - SELECT_ALL("mfx-select-all", '\uE948'), - SHORTCUT("mfx-shortcut", '\uE949'), - SLIDERS("mfx-sliders", '\uE94A'), - SPREADSHEET("mfx-spreadsheet", '\uE94B'), - STEP_BACKWARD("mfx-step-backward", '\uE94C'), - STEP_FORWARD("mfx-step-forward", '\uE94D'), - SYNC("mfx-sync", '\uE94E'), - SYNC_LIGHT("mfx-sync-light", '\uE94F'), - UNDO("mfx-undo", '\uE950'), - USER("mfx-user", '\uE951'), - USERS("mfx-users", '\uE952'), - VARIANT10_MARK("mfx-variant10-mark", '\uE953'), - VARIANT11_MARK("mfx-variant11-mark", '\uE954'), - VARIANT12_MARK("mfx-variant12-mark", '\uE955'), - VARIANT13_MARK("mfx-variant13-mark", '\uE956'), - VARIANT14_MARK("mfx-variant14-mark", '\uE957'), - VARIANT3_MARK("mfx-variant3-mark", '\uE958'), - VARIANT4_MARK("mfx-variant4-mark", '\uE959'), - VARIANT5_MARK("mfx-variant5-mark", '\uE95A'), - VARIANT6_MARK("mfx-variant6-mark", '\uE95B'), - VARIANT7_MARK("mfx-variant7-mark", '\uE95C'), - VARIANT8_MARK("mfx-variant8-mark", '\uE95D'), - VARIANT9_MARK("mfx-variant9-mark", '\uE95E'), - VIDEO("mfx-video", '\uE95F'), - X("mfx-x", '\uE960'), - X_ALT("mfx-x-alt", '\uE961'), - X_CIRCLE("mfx-x-circle", '\uE962'), - X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE963'), - X_LIGHT("mfx-x-light", '\uE964') - ; + HYPHEN("mfx-hyphen", '\uE937'), + IMAGE("mfx-image", '\uE938'), + INFO("mfx-info", '\uE939'), + INFO_CIRCLE("mfx-info-circle", '\uE93A'), + LAST_PAGE("mfx-last-page", '\uE93B'), + LEVEL_UP("mfx-level-up", '\uE93C'), + LOCK("mfx-lock", '\uE93D'), + LOCK_OPEN("mfx-lock-open", '\uE93E'), + MAP("mfx-map", '\uE93F'), + MINUS("mfx-minus", '\uE940'), + MINUS_CIRCLE("mfx-minus-circle", '\uE941'), + MODENA_MARK("mfx-modena-mark", '\uE942'), + MUSIC("mfx-music", '\uE943'), + NEXT("mfx-next", '\uE944'), + REDO("mfx-redo", '\uE945'), + RESTORE("mfx-restore", '\uE946'), + SEARCH("mfx-search", '\uE947'), + SEARCH_PLUS("mfx-search-plus", '\uE948'), + SELECT_ALL("mfx-select-all", '\uE949'), + SHORTCUT("mfx-shortcut", '\uE94A'), + SLIDERS("mfx-sliders", '\uE94B'), + SPREADSHEET("mfx-spreadsheet", '\uE94C'), + STEP_BACKWARD("mfx-step-backward", '\uE94D'), + STEP_FORWARD("mfx-step-forward", '\uE94E'), + SYNC("mfx-sync", '\uE94F'), + SYNC_LIGHT("mfx-sync-light", '\uE950'), + UNDO("mfx-undo", '\uE951'), + USER("mfx-user", '\uE952'), + USERS("mfx-users", '\uE953'), + VARIANT10_MARK("mfx-variant10-mark", '\uE954'), + VARIANT11_MARK("mfx-variant11-mark", '\uE955'), + VARIANT12_MARK("mfx-variant12-mark", '\uE956'), + VARIANT13_MARK("mfx-variant13-mark", '\uE957'), + VARIANT14_MARK("mfx-variant14-mark", '\uE958'), + VARIANT3_MARK("mfx-variant3-mark", '\uE959'), + VARIANT4_MARK("mfx-variant4-mark", '\uE95A'), + VARIANT5_MARK("mfx-variant5-mark", '\uE95B'), + VARIANT6_MARK("mfx-variant6-mark", '\uE95C'), + VARIANT7_MARK("mfx-variant7-mark", '\uE95D'), + VARIANT8_MARK("mfx-variant8-mark", '\uE95E'), + VARIANT9_MARK("mfx-variant9-mark", '\uE95F'), + VIDEO("mfx-video", '\uE960'), + X("mfx-x", '\uE961'), + X_ALT("mfx-x-alt", '\uE962'), + X_CIRCLE("mfx-x-circle", '\uE963'), + X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE964'), + X_LIGHT("mfx-x-light", '\uE965'); + public static FontResources findByDescription(String description) { for (FontResources font : values()) { diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/font/MFXFontIcon.java b/materialfx/src/main/java/io/github/palexdev/materialfx/font/MFXFontIcon.java index ad8fe3a0..3b502ab0 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/font/MFXFontIcon.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/font/MFXFontIcon.java @@ -18,13 +18,15 @@ package io.github.palexdev.materialfx.font; +import javafx.beans.binding.Bindings; import javafx.css.*; import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; import javafx.scene.text.Font; import javafx.scene.text.FontSmoothingType; import javafx.scene.text.Text; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -41,7 +43,7 @@ public class MFXFontIcon extends Text { // Constructors //================================================================================ public MFXFontIcon() { - initialize(); + this(null); } public MFXFontIcon(String description) { @@ -53,17 +55,14 @@ public class MFXFontIcon extends Text { } public MFXFontIcon(String description, double size) { - this(description, size, Color.rgb(117, 117, 117)); + this(description, size, Color.web("#454545")); } public MFXFontIcon(String description, double size, Color color) { initialize(); - setDescription(description); - setSize(size); + setFont(Font.font(getFont().getFamily(), size)); setColor(color); - - setText(String.valueOf(FontHandler.getCode(description))); } //================================================================================ @@ -74,19 +73,24 @@ public class MFXFontIcon extends Text { setFont(FontHandler.getResources()); setFontSmoothingType(FontSmoothingType.GRAY); - sizeProperty().addListener((observable, oldValue, newValue) -> { - Font font = getFont(); - setFont(Font.font(font.getFamily(), newValue.doubleValue())); - }); + textProperty().bind(Bindings.createStringBinding( + () -> { + String desc = getDescription(); + return desc != null && !desc.isBlank() ? descriptionToString(desc) : ""; + }, description + )); - colorProperty().addListener((observable, oldValue, newValue) -> setFill(newValue)); + fillProperty().bind(colorProperty()); + sizeProperty().addListener((observable, oldValue, newValue) -> setFontSize(newValue.doubleValue())); + } - descriptionProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - final char character = FontHandler.getCode(newValue); - setText(String.valueOf(character)); - } - }); + private String descriptionToString(String desc) { + return String.valueOf(FontHandler.getCode(desc)); + } + + private void setFontSize(double size) { + String fontFamily = getFont().getFamily(); + setFont(Font.font(fontFamily, size)); } /** @@ -102,25 +106,55 @@ public class MFXFontIcon extends Text { //================================================================================ // Styleable Properties //================================================================================ + private final StyleableObjectProperty color = new SimpleStyleableObjectProperty<>( + StyleableProperties.COLOR, + this, + "color", + Color.web("#454545") + ) { + @Override + public StyleOrigin getStyleOrigin() { + return StyleOrigin.USER_AGENT; + } + }; + private final StyleableStringProperty description = new SimpleStyleableStringProperty( StyleableProperties.DESCRIPTION, this, "description" - ); + ) { + @Override + public StyleOrigin getStyleOrigin() { + return StyleOrigin.USER_AGENT; + } + }; private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty( StyleableProperties.SIZE, this, "size", 10.0 - ); + ) { + @Override + public StyleOrigin getStyleOrigin() { + return StyleOrigin.USER_AGENT; + } + }; - private final StyleableObjectProperty color = new SimpleStyleableObjectProperty<>( - StyleableProperties.COLOR, - this, - "color", - Color.rgb(117, 117, 117) - ); + public Color getColor() { + return color.get(); + } + + /** + * Specifies the color of the icon. + */ + public StyleableObjectProperty colorProperty() { + return color; + } + + public void setColor(Color color) { + this.color.set(color); + } public String getDescription() { return description.get(); @@ -152,30 +186,22 @@ public class MFXFontIcon extends Text { this.size.set(size); } - public Paint getColor() { - return color.get(); - } - - /** - * Specifies the color of the icon. - */ - public StyleableObjectProperty colorProperty() { - return color; - } - - public void setColor(Paint color) { - this.color.set(color); - } - //================================================================================ // CssMetaData //================================================================================ public static class StyleableProperties { private static final List> cssMetaDataList; + private static final CssMetaData COLOR = + FACTORY.createColorCssMetaData( + "-mfx-color", + MFXFontIcon::colorProperty, + Color.web("#454545") + ); + private static final CssMetaData DESCRIPTION = FACTORY.createStringCssMetaData( - "-mfx-icon-code", + "-mfx-description", MFXFontIcon::descriptionProperty ); @@ -186,15 +212,10 @@ public class MFXFontIcon extends Text { 10 ); - private static final CssMetaData COLOR = - FACTORY.createPaintCssMetaData( - "-mfx-color", - MFXFontIcon::colorProperty, - Color.rgb(117, 117, 117) - ); - static { - cssMetaDataList = List.of(DESCRIPTION, SIZE, COLOR); + List> txtCssMetaData = new ArrayList<>(Text.getClassCssMetaData()); + Collections.addAll(txtCssMetaData, COLOR, DESCRIPTION, SIZE); + cssMetaDataList = Collections.unmodifiableList(txtCssMetaData); } } diff --git a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java index ea2a5fc6..0f84a18e 100755 --- a/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java +++ b/materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXCheckboxSkin.java @@ -18,20 +18,18 @@ package io.github.palexdev.materialfx.skins; +import io.github.palexdev.materialfx.beans.PositionBean; +import io.github.palexdev.materialfx.controls.LabeledControlWrapper; import io.github.palexdev.materialfx.controls.MFXCheckbox; import io.github.palexdev.materialfx.controls.MFXIconWrapper; import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator; -import io.github.palexdev.materialfx.beans.PositionBean; import io.github.palexdev.materialfx.font.MFXFontIcon; -import io.github.palexdev.materialfx.utils.NodeUtils; import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.control.Label; +import javafx.scene.control.ContentDisplay; import javafx.scene.control.SkinBase; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; /** * This is the implementation of the {@code Skin} associated with every {@link MFXCheckbox}. @@ -40,13 +38,11 @@ public class MFXCheckboxSkin extends SkinBase { //================================================================================ // Properties //================================================================================ - private final HBox container; - private final Label label; private final MFXIconWrapper box; - private final double boxSize = 27; + private final LabeledControlWrapper text; - private final AnchorPane rippleContainer; - private final double rippleContainerSize = 31; + private final StackPane rippleContainer; + private final Circle rippleContainerClip; private final MFXCircleRippleGenerator rippleGenerator; //================================================================================ @@ -55,16 +51,15 @@ public class MFXCheckboxSkin extends SkinBase { public MFXCheckboxSkin(MFXCheckbox checkbox) { super(checkbox); - // Contains the ripple generator and the box - rippleContainer = new AnchorPane(); - rippleContainer.setPrefSize(rippleContainerSize, rippleContainerSize); - rippleContainer.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - rippleContainer.getStyleClass().setAll("ripple-container"); - NodeUtils.makeRegionCircular(rippleContainer); + MFXFontIcon mark = new MFXFontIcon(); + mark.getStyleClass().add("mark"); + box = new MFXIconWrapper(mark, -1); + box.getStyleClass().add("box"); + rippleContainer = new StackPane(); rippleGenerator = new MFXCircleRippleGenerator(rippleContainer); + rippleGenerator.setManaged(false); rippleGenerator.setAnimateBackground(false); - rippleGenerator.setAnimationSpeed(1.5); rippleGenerator.setCheckBounds(false); rippleGenerator.setClipSupplier(() -> null); rippleGenerator.setRipplePositionFunction(event -> { @@ -73,180 +68,173 @@ public class MFXCheckboxSkin extends SkinBase { position.setY(Math.min(event.getY(), rippleContainer.getHeight())); return position; }); - rippleGenerator.setRippleRadius(16); - - // Contains the mark - MFXFontIcon icon = new MFXFontIcon(checkbox.getMarkType(), checkbox.getMarkSize(), Color.WHITE); - icon.getStyleClass().add("mark"); - box = new MFXIconWrapper(icon, boxSize); - box.getStyleClass().add("box"); - - box.setBorder(new Border(new BorderStroke( - checkbox.getUncheckedColor(), - BorderStrokeStyle.SOLID, - new CornerRadii(2.5), - new BorderWidths(1.8) - ))); - box.setBackground(new Background(new BackgroundFill( - Color.TRANSPARENT, - new CornerRadii(2.5), - Insets.EMPTY - ))); - - label = new Label(); - label.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - label.textProperty().bind(checkbox.textProperty()); - rippleContainer.getChildren().addAll(rippleGenerator, box); - container = new HBox(10, rippleContainer, label); - container.setAlignment(Pos.CENTER_LEFT); - getChildren().add(container); + rippleContainer.getStyleClass().add("ripple-container"); + rippleContainer.setManaged(false); - updateMarkType(); - setListeners(); + rippleContainerClip = new Circle(); + 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 //================================================================================ + private void addListeners() { + MFXCheckbox checkbox = getSkinnable(); - /** - * Adds listeners for: markType, selected, indeterminate, checked and unchecked coloros properties. - */ - private void setListeners() { - MFXCheckbox checkBox = getSkinnable(); - - checkBox.markTypeProperty().addListener( - (observable, oldValue, newValue) -> updateMarkType() - ); - - checkBox.markSizeProperty().addListener( - (observable, oldValue, newValue) -> ((MFXFontIcon) box.getIcon()).setFont(Font.font(newValue.doubleValue()))); - - checkBox.selectedProperty().addListener( - (observable, oldValue, newValue) -> updateColors() - ); - - checkBox.indeterminateProperty().addListener( - (observable, oldValue, newValue) -> updateColors() - ); - - checkBox.checkedColorProperty().addListener( - (observable, oldValue, newValue) -> updateColors() - ); - - checkBox.uncheckedColorProperty().addListener( - (observable, oldValue, newValue) -> updateColors() - ); - - /* Listener on control but if the coordinates of the event are greater than then ripple container size - * then the center of the ripple is set to the width and/or height of container - */ - checkBox.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> { - if (!NodeUtils.inHierarchy(event, checkBox)) { - return; - } - + checkbox.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> { rippleGenerator.generateRipple(event); - checkBox.fire(); + checkbox.fire(); }); - /* - * Workaround - * When the control is created the Skin is still null, so if the CheckBox is set - * to be selected/indeterminate the animation won't be played. To fix this add a listener to the - * control's skinProperty, when the skin is not null and the CheckBox isSelected/isIndeterminate, - * play the animation. - */ - NodeUtils.waitForSkin(checkBox, this::updateColors, true, false); - } - - - /** - * 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); + checkbox.contentDispositionProperty().addListener(invalidated -> checkbox.requestLayout()); + checkbox.gapProperty().addListener(invalidated -> checkbox.requestLayout()); } //================================================================================ - // Override Methods + // Overridden Methods //================================================================================ - @Override - protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { - return rippleContainer.getHeight(); - } - @Override 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 - protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { - return rippleContainer.getHeight(); + protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + MFXCheckbox checkbox = getSkinnable(); + ContentDisplay disposition = checkbox.getContentDisposition(); + double minH; + switch (disposition) { + case LEFT: + case RIGHT: + case TEXT_ONLY: + minH = topInset + Math.max(rippleContainer.prefHeight(-1), text.prefHeight(-1)) + bottomInset; + break; + case TOP: + case BOTTOM: + minH = topInset + rippleContainer.prefHeight(-1) + getSkinnable().getGap() + text.prefHeight(-1) + bottomInset; + break; + case CENTER: + case GRAPHIC_ONLY: + minH = leftInset + rippleContainer.prefHeight(-1) + rightInset; + break; + default: + minH = super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset); + } + return minH; } @Override protected double 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 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); } } diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckBox.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckBox.css index e3ccf35a..d878f560 100755 --- a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckBox.css +++ b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXCheckBox.css @@ -16,15 +16,53 @@ * along with MaterialFX. If not, see . */ -/*================================================================================*/ -/* Ripple -/*================================================================================*/ .mfx-checkbox { - -fx-border-insets: -1 -3 -1 -1; - -fx-background-insets: -1 -3 -1 -1; + -mfx-main: #6200EE; + -mfx-gray: #424242; +} + +.mfx-checkbox .box { + -fx-background-color: transparent; + -fx-background-radius: 2; + + -fx-border-color: -mfx-gray; + -fx-border-radius: 2; + -fx-border-width: 1.5; + + -fx-padding: 4; +} + +.mfx-checkbox .box .mark { + visibility: hidden; + -mfx-color: white; + -mfx-description: "mfx-variant7-mark"; + -mfx-size: 12; } .mfx-checkbox .ripple-container .mfx-ripple-generator { - -mfx-ripple-color: rgb(190, 190, 190); + -mfx-animation-speed: 1.5; + -mfx-ripple-color: derive(-mfx-main, 110%); + -mfx-ripple-radius: 16; +} + +.mfx-checkbox:selected .box, +.mfx-checkbox:indeterminate .box { + -fx-background-color: -mfx-main; + -fx-border-color: -mfx-main; +} + +.mfx-checkbox:selected .box .mark { + visibility: visible; +} + +.mfx-checkbox:selected:disabled .box, +.mfx-checkbox:indeterminate:disabled .box { + -fx-background-color: -mfx-gray; + -fx-border-color: -mfx-gray; +} + +.mfx-checkbox:indeterminate .box .mark { + visibility: visible; + -mfx-description: "mfx-minus"; } diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeCell.css b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeCell.css index 6098c242..88a61cb2 100755 --- a/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeCell.css +++ b/materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTreeCell.css @@ -21,6 +21,11 @@ -fx-background-radius: 7px; } +.mfx-tree-cell .mfx-checkbox .box .mark { + -mfx-description: "mfx-variant3-mark"; + -mfx-size: 8; +} + .mfx-tree-cell .mfx-ripple-generator { -mfx-ripple-color: rgba(0, 190, 0, 0.3); } diff --git a/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf b/materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf index f1afd55110595a5335a9e35fd4ce26c68b9c5cd7..11b05f04da5919a46e718eb1772949475e6b2b75 100755 GIT binary patch delta 563 zcmew`hjGqa#(D-u1_lOhh6V;^1_S?KeItG$wlJW`9w1Ig&P^;354-e+fq_v5$PYJ>JzYbt+ z2T--u>nO^GRCKnkj_0@e%D~M6au>th4P56D^#22Fx0ycy`5X*DlfeM!KIQ}e4=^yW z-DY6;-vA^*F@OxRCV%mK!^kxGu9q@1a|6R>W^X>m`ZdCA!Xd(cM8rgVMCOTn6Ri=Q zCMF{0BQ`;7kvNxlhWImyIg%2RPLl7W!lZhn4oN4-Fv(=e9Fc946O+r5n9vt1|hIZV2*`5Yjcm!6Gi~KntqA^ delta 496 zcmbO;m+`|K#(D-u1_lOhh6V;^1_S?KeItG$wjQ9!9w1Ig&P^;354-f5fq_v5$PYRFPLpX`{J+F9Phz~Bwia3wdfqJW`>;T!{lPY#f;ke8U7YV=>o z1jq+Eit$iEesKxVp+Mjl0Fq~5X8t$vfIVZ%Bt~z>l+8YjfvTG?sx4%7Sm=YSo8{(pe?0rMvypM!x3Bnkw;z+^t~{{RDn1d#K;f%gG8 zctJ8SkU3ex>&;{iZv|!v2CmK8-h7OSZ-lLc9|`{vF%oGJxg(k++9JjzW+hf9HbLx% zc$oMVi7rVtNdw6{QZ7