Compare commits

...

5 Commits

Author SHA1 Message Date
palexdev
0a04912529 Merge remote-tracking branch 'origin/main' into staging
# Conflicts:
#	build.gradle
#	demo/src/test/java/Playground.java
#	materialfx/gradle.properties
2022-04-12 16:32:47 +02:00
palexdev
aa22e3e03f ♻️ Minor changes
♻️ MFXScrollPane: added method to compute the full size of a scroll pane (including scroll bars)

♻️ Renamed ToggleButtonsUtil to ToggleUtils, and added a utility method to quickly add several toggles to a group

🐛 MFXTitledPaneSkin: minSize of the content pane should be set according to the position

🐛 PositionUtils: fix compute position methods, as sometimes "getLayoutBounds().getHeight()" can return 0. Added a parameter to specify whether the sizes must be computed instead of using the layoutBounds

 Added some new resources

📝 TODO: documentation will be added in a future commit

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
2022-03-29 15:59:49 +02:00
palexdev
a7148073e3 ♻️ Minor changes
♻️ Allow controls using MFXLabeledSkinBase to display only the graphic node

♻️ Improve width/height computation for MFXRectangleToggleNode

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
2022-03-21 14:10:42 +01:00
palexdev
8ae31667ea 🎨 Added/Updated some font resources
Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
2022-03-21 12:28:43 +01:00
palexdev
ef67779103 🔖 Version 11.4.0-EA1
 Added new control, MFXTitledPane

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
2022-03-21 11:32:03 +01:00
34 changed files with 1212 additions and 168 deletions

View File

@ -14,7 +14,26 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **Removed** for now removed features.
- **Fixed** for any bug fixes.
[//]: ##[Unreleased]
## [Unreleased] - 21-03-2022
### Added
- New control: MFXTitledPane
- Added some new resources
### Changes
- Added/Updated some font resources
- Allow controls using MFXLabeledSkinBase to display only the graphic node
- Improve width/height computation for MFXRectangleToggleNode
- MFXScrollPane: added method to compute the full size of a scroll pane (including scroll bars)
- Renamed ToggleButtonsUtil to ToggleUtils, and added a utility method to quickly add several toggles to a group
### Fixed
- MFXTitledPaneSkin: minSize of the content pane should be set according to the position
- PositionUtils: fix compute position methods, as sometimes "getLayoutBounds().getHeight()" can return 0. Added a
parameter to specify whether the sizes must be computed instead of using the layoutBounds
## [11.13.5] - 11-04-2022

View File

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

View File

@ -23,7 +23,7 @@ import io.github.palexdev.materialfx.controls.MFXRectangleToggleNode;
import io.github.palexdev.materialfx.controls.MFXScrollPane;
import io.github.palexdev.materialfx.font.MFXFontIcon;
import io.github.palexdev.materialfx.utils.ScrollUtils;
import io.github.palexdev.materialfx.utils.ToggleButtonsUtil;
import io.github.palexdev.materialfx.utils.ToggleUtils;
import io.github.palexdev.materialfx.utils.others.loader.MFXLoader;
import io.github.palexdev.materialfx.utils.others.loader.MFXLoaderBean;
import javafx.application.Platform;
@ -79,7 +79,7 @@ public class DemoController implements Initializable {
public DemoController(Stage stage) {
this.stage = stage;
this.toggleGroup = new ToggleGroup();
ToggleButtonsUtil.addAlwaysOneSelectedSupport(toggleGroup);
ToggleUtils.addAlwaysOneSelectedSupport(toggleGroup);
}
@Override

View File

@ -71,7 +71,7 @@ public class DialogsController {
@FXML
private void openInfo(ActionEvent event) {
MFXFontIcon infoIcon = new MFXFontIcon("mfx-info-circle-filled", 18);
MFXFontIcon infoIcon = new MFXFontIcon("mfx-info-circle", 18);
dialogContent.setHeaderIcon(infoIcon);
dialogContent.setHeaderText("This is a generic info dialog");
convertDialogTo("mfx-info-dialog");

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'MFXColors.css';
.header-label {

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'MFXColors.css';
/**************************************************

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -16,7 +16,7 @@
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
@import '../fonts/Fonts.css';
@import 'Fonts.css';
@import 'Common.css';
@import 'MFXColors.css';

View File

@ -1,42 +1,25 @@
import io.github.palexdev.materialfx.controls.MFXTextField;
import io.github.palexdev.materialfx.controls.MFXTitledPane;
import io.github.palexdev.materialfx.enums.HeaderPosition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.VBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import org.scenicview.ScenicView;
import java.text.DecimalFormat;
import java.text.ParsePosition;
public class Playground extends Application {
private final double w = 445;
private final double h = 270;
@Override
public void start(Stage primaryStage) {
VBox vBox = new VBox(10);
vBox.setAlignment(Pos.CENTER);
BorderPane bp = new BorderPane();
DecimalFormat format = new DecimalFormat("#.0");
MFXTextField field = new MFXTextField("", "", "Numbers");
field.delegateSetTextFormatter(new TextFormatter<>(c ->
{
if (c.getControlNewText().isEmpty()) {
return c;
}
MFXTitledPane tp = new MFXTitledPane("SideBar", new Rectangle(200, 1200));
tp.setHeaderPos(HeaderPosition.LEFT);
bp.setRight(tp);
ParsePosition parsePosition = new ParsePosition(0);
Object object = format.parse(c.getControlNewText(), parsePosition);
if (object == null || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
} else {
return c;
}
}));
vBox.getChildren().addAll(field);
Scene scene = new Scene(vBox, 800, 800);
Scene scene = new Scene(bp, 1440, 900);
primaryStage.setScene(scene);
primaryStage.show();

View File

@ -1,6 +1,6 @@
GROUP=io.github.palexdev
POM_ARTIFACT_ID=materialfx
VERSION_NAME=11.13.5
VERSION_NAME=11.14.0-EA3
POM_NAME=materialfx
POM_DESCRIPTION=Material Desgin components for JavaFX
@ -10,7 +10,7 @@ POM_URL=https://github.com/palexdev/MaterialFX
POM_SCM_URL=https://github.com/palexdev/MaterialFX
POM_LICENCE_NAME=GNU LGPLv3
POM_LICENCE_URL=https://www.gnu.org/licenses/gpl-3.0.html
POM_LICENCE_URL=https://www.gnu.org/licenses/lgpl-3.0.html
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=palexdev

View File

@ -0,0 +1,115 @@
package io.github.palexdev.materialfx.controls;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.ToggleGroup;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This class acts as a {@link ToggleGroup} but for {@link MFXTitledPane}s.
*/
public class ExpandGroup {
private Set<Node> prevPanes = new HashSet<>();
private final ReadOnlyObjectWrapper<MFXTitledPane> expandedPane = new ReadOnlyObjectWrapper<>() {
@Override
public void set(MFXTitledPane newValue) {
if (isBound()) {
throw new RuntimeException("A bound value cannot be set.");
}
MFXTitledPane old = get();
if (old == newValue) return;
if (setExpanded(newValue, true) ||
(newValue != null && newValue.getExpandGroup() == ExpandGroup.this) ||
(newValue == null)) {
if (old == null || old.getExpandGroup() == ExpandGroup.this || !old.isExpanded())
setExpanded(old, false);
super.set(newValue);
}
}
};
private final ObservableList<MFXTitledPane> panes = FXCollections.observableArrayList();
public ExpandGroup() {
panes.addListener((ListChangeListener<? super MFXTitledPane>) c -> {
while (c.next()) {
List<? extends MFXTitledPane> addedList = c.getAddedSubList();
for (MFXTitledPane removed : c.getRemoved()) {
if (removed.isExpanded()) {
setExpandedPane(null);
}
if (!addedList.contains(removed)) {
removed.setExpandGroup(null);
}
}
for (MFXTitledPane added : addedList) {
if (prevPanes.contains(added))
throw new IllegalArgumentException("Duplicate panes are not allowed!");
if (!this.equals(added.getExpandGroup())) {
if (added.getExpandGroup() != null) added.getExpandGroup().getPanes().remove(added);
added.setExpandGroup(this);
}
}
for (MFXTitledPane added : addedList) {
if (added.isExpanded()) {
setExpandedPane(added);
break;
}
}
}
prevPanes = new HashSet<>(c.getList());
});
}
private boolean setExpanded(MFXTitledPane pane, boolean expanded) {
if (pane != null &&
pane.getExpandGroup() == this &&
!pane.expandedProperty().isBound()) {
pane.setExpanded(expanded);
return true;
}
return false;
}
public final void clearExpandedPane() {
if (!getExpandedPane().isExpanded()) {
for (MFXTitledPane pane : getPanes()) {
if (pane.isExpanded()) return;
}
}
setExpandedPane(null);
}
public MFXTitledPane getExpandedPane() {
return expandedPane.get();
}
public ReadOnlyObjectProperty<MFXTitledPane> expandedPaneProperty() {
return expandedPane.getReadOnlyProperty();
}
public final void setExpandedPane(MFXTitledPane expandedPane) {
this.expandedPane.set(expandedPane);
}
public ObservableList<MFXTitledPane> getPanes() {
return FXCollections.unmodifiableObservableList(panes);
}
public static void addToGroup(ExpandGroup group, MFXTitledPane... panes) {
for (MFXTitledPane pane : panes) {
pane.setExpandGroup(group);
}
}
}

View File

@ -19,11 +19,13 @@
package io.github.palexdev.materialfx.controls;
import io.github.palexdev.materialfx.MFXResourcesLoader;
import io.github.palexdev.materialfx.beans.SizeBean;
import io.github.palexdev.materialfx.skins.MFXScrollPaneSkin;
import io.github.palexdev.materialfx.utils.ColorUtils;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Skin;
import javafx.scene.paint.Color;
@ -40,6 +42,8 @@ public class MFXScrollPane extends ScrollPane {
//================================================================================
private final String STYLE_CLASS = "mfx-scroll-pane";
private final String STYLESHEET = MFXResourcesLoader.load("css/MFXScrollPane.css");
private ScrollBar vBar;
private ScrollBar hBar;
//================================================================================
// Constructors
@ -61,6 +65,28 @@ public class MFXScrollPane extends ScrollPane {
addListeners();
}
public SizeBean getFullSizes() {
ScrollBar vBar = (this.vBar != null) ? this.vBar : (ScrollBar) lookup(".vBar");
ScrollBar hBar = (this.hBar != null) ? this.hBar : (ScrollBar) lookup(".hBar");
this.vBar = vBar;
this.hBar = hBar;
double vBarW = (vBar != null) ?
vBar.snappedLeftInset() + vBar.prefWidth(-1) + vBar.snappedRightInset() :
0.0;
double hBarH = (hBar != null) ?
hBar.snappedTopInset() + hBar.prefHeight(-1) + hBar.snappedBottomInset() :
0.0;
double contentW = (getContent() != null) ? getContent().prefWidth(-1) : 0.0;
double contentH = (getContent() != null) ? getContent().prefHeight(-1) : 0.0;
double fullW = snappedLeftInset() + contentW + snappedRightInset() + vBarW;
double fullH = snappedTopInset() + contentH + snappedBottomInset() + hBarH;
return SizeBean.of(fullW, fullH);
}
//================================================================================
// Style Properties
//================================================================================

View File

@ -0,0 +1,482 @@
/*
* Copyright (C) 2022 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 Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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.beans.properties.functional.SupplierProperty;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableBooleanProperty;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
import io.github.palexdev.materialfx.effects.Interpolators;
import io.github.palexdev.materialfx.enums.HeaderPosition;
import io.github.palexdev.materialfx.font.MFXFontIcon;
import io.github.palexdev.materialfx.skins.MFXTitledPaneSkin;
import io.github.palexdev.materialfx.utils.AnimationUtils;
import io.github.palexdev.materialfx.utils.NodeUtils;
import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.control.TitledPane;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
import java.util.List;
import java.util.function.Supplier;
/**
* This is the implementation of a JavaFX's {@link TitledPane} remade from scratch to give it
* more features, flexibility and of course a modern look.
* <p></p>
* Unlike the original pane, this one allows you to set whatever you want as header by setting
* the {@link #headerSupplierProperty()}, just keep in mind that a {@code null} supplier or a {@code null} return value
* won't be accepted. When using this constructor, {@link MFXTitledPane#MFXTitledPane(String, Node)}, the supplier
* will be set to build a new {@link DefaultHeader} pane.
* <p>
* So, the {@link #titleProperty()}, is only relevant when using the default header supplier, or (of course) if
* by making your own header you decide to use it in some way (a Label bound to the title for example).
* <p></p>
* There are also three other new features:
* <p> - You can set the header position wherever you like, TOP/RIGHT/BOTTOM/LEFT
* <p> - There's no need to use an accordion anymore, you can simply arrange multiple {@code MFXTitledPanes} in a
* container, like a VBox or HBox, and use an {@link ExpandGroup} to achieve the same behavior
* <p> - Unlike the original one, you can specify the duration of the expand/collapse animation
*/
public class MFXTitledPane extends Control {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-titled-pane";
private final String STYLESHEET = MFXResourcesLoader.load("css/MFXTitledPane.css");
private final StringProperty title = new SimpleStringProperty();
private final SupplierProperty<Node> headerSupplier = new SupplierProperty<>();
private final ObjectProperty<Node> content = new SimpleObjectProperty<>();
private final BooleanProperty expanded = new SimpleBooleanProperty() {
@Override
protected void invalidated() {
boolean state = get();
pseudoClassStateChanged(EXPANDED_PSEUDO_CLASS, state);
pseudoClassStateChanged(COLLAPSED_PSEUDO_CLASS, !state);
}
};
private final ObjectProperty<ExpandGroup> expandGroup = new SimpleObjectProperty<>();
protected static final PseudoClass EXPANDED_PSEUDO_CLASS = PseudoClass.getPseudoClass("expanded");
protected static final PseudoClass COLLAPSED_PSEUDO_CLASS = PseudoClass.getPseudoClass("collapsed");
//================================================================================
// Constructors
//================================================================================
public MFXTitledPane() {
this("Title", null);
}
public MFXTitledPane(String title, Node content) {
defaultHeaderSupplier();
setTitle(title);
setContent(content);
initialize();
}
public MFXTitledPane(Supplier<Node> headerSupplier, Node content) {
setHeaderSupplier(headerSupplier);
setContent(content);
initialize();
}
//================================================================================
// Methods
//================================================================================
private void initialize() {
getStyleClass().add(STYLE_CLASS);
expandGroup.addListener((observable, oldGroup, newGroup) -> {
if (newGroup != null && newGroup.getPanes().contains(this)) {
if (oldGroup != null) oldGroup.getPanes().remove(this);
newGroup.getPanes().add(this);
} else if (newGroup == null) {
oldGroup.getPanes().remove(this);
}
});
expanded.addListener(invalidated -> {
ExpandGroup eg = getExpandGroup();
if (eg != null) {
if (isExpanded()) {
eg.setExpandedPane(this);
} else if (eg.getExpandedPane() == this) {
eg.clearExpandedPane();
}
}
});
}
/**
* Resets the {@link #headerSupplierProperty()} to the default supplier.
*/
public void defaultHeaderSupplier() {
setHeaderSupplier(DefaultHeader::new);
}
//================================================================================
// Styleable Properties
//================================================================================
private final StyleableBooleanProperty animated = new StyleableBooleanProperty(
StyleableProperties.ANIMATED,
this,
"animated",
true
);
private final StyleableObjectProperty<Duration> animationDuration = new StyleableObjectProperty<>(
StyleableProperties.ANIMATION_DURATION,
this,
"animationDuration",
Duration.millis(300)
);
private final StyleableBooleanProperty collapsible = new StyleableBooleanProperty(
StyleableProperties.COLLAPSIBLE,
this,
"collapsible",
true
);
private final StyleableObjectProperty<HeaderPosition> headerPos = new StyleableObjectProperty<>(
StyleableProperties.HEADER_POS,
this,
"headerPos",
HeaderPosition.TOP
);
public boolean isAnimated() {
return animated.get();
}
/**
* Specifies whether to animate the expand/collapse transition.
*/
public StyleableBooleanProperty animatedProperty() {
return animated;
}
public void setAnimated(boolean animated) {
this.animated.set(animated);
}
public Duration getAnimationDuration() {
return animationDuration.get();
}
/**
* Specifies the duration of the expand/collapse animation.
*/
public StyleableObjectProperty<Duration> animationDurationProperty() {
return animationDuration;
}
public void setAnimationDuration(Duration animationDuration) {
this.animationDuration.set(animationDuration);
}
public boolean isCollapsible() {
return collapsible.get();
}
/**
* Specifies whether the pane can be collapsed.
*/
public StyleableBooleanProperty collapsibleProperty() {
return collapsible;
}
public void setCollapsible(boolean collapsible) {
this.collapsible.set(collapsible);
}
public HeaderPosition getHeaderPos() {
return headerPos.get();
}
/**
* Specifies the position of the header node.
*/
public StyleableObjectProperty<HeaderPosition> headerPosProperty() {
return headerPos;
}
public void setHeaderPos(HeaderPosition headerPos) {
this.headerPos.set(headerPos);
}
//================================================================================
// CSSMetaData
//================================================================================
private static class StyleableProperties {
private static final StyleablePropertyFactory<MFXTitledPane> FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData());
private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
private static final CssMetaData<MFXTitledPane, Boolean> ANIMATED =
FACTORY.createBooleanCssMetaData(
"-mfx-animated",
MFXTitledPane::animatedProperty,
true
);
private static final CssMetaData<MFXTitledPane, Duration> ANIMATION_DURATION =
FACTORY.createDurationCssMetaData(
"-mfx-animation-duration",
MFXTitledPane::animationDurationProperty,
Duration.millis(300)
);
private static final CssMetaData<MFXTitledPane, Boolean> COLLAPSIBLE =
FACTORY.createBooleanCssMetaData(
"-mfx-collapsible",
MFXTitledPane::collapsibleProperty,
true
);
private static final CssMetaData<MFXTitledPane, HeaderPosition> HEADER_POS =
FACTORY.createEnumCssMetaData(
HeaderPosition.class,
"-mfx-pos",
MFXTitledPane::headerPosProperty,
HeaderPosition.TOP
);
static {
cssMetaDataList = StyleablePropertiesUtils.cssMetaDataList(
Control.getClassCssMetaData(),
ANIMATED, ANIMATION_DURATION, COLLAPSIBLE, HEADER_POS
);
}
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.cssMetaDataList;
}
@Override
protected List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected Skin<?> createDefaultSkin() {
return new MFXTitledPaneSkin(this);
}
@Override
public String getUserAgentStylesheet() {
return STYLESHEET;
}
//================================================================================
// Getters/Setters
//================================================================================
public String getTitle() {
return title.get();
}
/**
* Specifies the pane's title.
*/
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public Supplier<Node> getHeaderSupplier() {
return headerSupplier.get();
}
/**
* Specifies the {@link Supplier} used to build the header node.
* <p></p>
* The default one builds a new {@link DefaultHeader}.
*/
public SupplierProperty<Node> headerSupplierProperty() {
return headerSupplier;
}
public void setHeaderSupplier(Supplier<Node> headerSupplier) {
this.headerSupplier.set(headerSupplier);
}
public Node getContent() {
return content.get();
}
/**
* Specifies the pane's content, can be null and changed at runtime.
*/
public ObjectProperty<Node> contentProperty() {
return content;
}
public void setContent(Node content) {
this.content.set(content);
}
public boolean isExpanded() {
return expanded.get();
}
/**
* Specifies the expand state of the pane.
*/
public BooleanProperty expandedProperty() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded.set(expanded);
}
public ExpandGroup getExpandGroup() {
return expandGroup.get();
}
/**
* Specifies the {@link ExpandGroup} this pane belongs to.
*/
public ObjectProperty<ExpandGroup> expandGroupProperty() {
return expandGroup;
}
public void setExpandGroup(ExpandGroup expandGroup) {
this.expandGroup.set(expandGroup);
}
//================================================================================
// Default Header Class
//================================================================================
/**
* Default header used by {@link MFXTitledPane}s.
* <p></p>
* It basically consists in a {@link Label} which has its text bound to the pane's {@link #titleProperty()},
* and a {@link MFXFontIcon} (wrapped in a {@link MFXIconWrapper}) for the arrow, which is responsible
* for expanding/collapsing the pane.
* <p></p>
* This header is capable of rearranging itself when the {@link #headerPosProperty()}, to make things easier
* the layout is not manual but automatically managed, see {@link #initializeContainer()} for more info.
* <p>
* The icon also depends on the {@link #headerPosProperty()}, see {@link #iconForPosition()}.
*/
public class DefaultHeader extends StackPane {
private final Label label;
private final MFXIconWrapper wrapped;
public DefaultHeader() {
label = new Label();
label.textProperty().bind(titleProperty());
label.getStyleClass().add("header-label");
label.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(label, Priority.ALWAYS);
MFXFontIcon icon = new MFXFontIcon("mfx-chevron-left", 14);
icon.descriptionProperty().bind(Bindings.createStringBinding(this::iconForPosition, headerPosProperty()));
wrapped = new MFXIconWrapper(icon, 20).defaultRippleGeneratorBehavior();
wrapped.setRotate(isExpanded() ? -180 : 0);
NodeUtils.makeRegionCircular(wrapped);
wrapped.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getButton() != MouseButton.PRIMARY || (isExpanded() && !isCollapsible())) return;
setExpanded(!isExpanded());
});
expanded.addListener((observable, oldValue, newValue) -> {
double rotate = newValue ? -180 : 0;
AnimationUtils.TimelineBuilder.build()
.add(AnimationUtils.KeyFrames.of(200, wrapped.rotateProperty(), rotate, Interpolators.INTERPOLATOR_V1))
.getAnimation()
.play();
});
headerPos.addListener(invalidated -> initializeContainer());
initializeContainer();
getStyleClass().add("header-pane");
}
/**
* Responsible for rearranging the header's content when the {@link #headerPosProperty()} changes.
* <p></p>
* When it is LEFT or RIGHT, both the label and icon will be contained by a VBox,
* otherwise they will be contained by a HBox.
*/
private void initializeContainer() {
HeaderPosition position = getHeaderPos();
Node container;
if (position == HeaderPosition.LEFT || position == HeaderPosition.RIGHT) {
container = new VBox(label, wrapped);
((VBox) container).setAlignment(Pos.CENTER);
} else {
container = new HBox(label, wrapped);
((HBox) container).setAlignment(Pos.CENTER);
}
getChildren().setAll(container);
}
/**
* Responsible for changing the icon when {@link #headerPosProperty()} changes.
* <p>
* <p> - Case RIGHT: "mfx-chevron-left"
* <p> - Case BOTTOM: "mfx-chevron-down"
* <p> - Case LEFT: "mfx-chevron-right"
* <p> - Case TOP: "mfx-chevron-up"
*/
protected String iconForPosition() {
HeaderPosition position = getHeaderPos();
switch (position) {
case RIGHT:
return "mfx-chevron-left";
case BOTTOM:
return "mfx-chevron-down";
case LEFT:
return "mfx-chevron-right";
case TOP:
default:
return "mfx-chevron-up";
}
}
}
}

View File

@ -24,7 +24,7 @@ import io.github.palexdev.materialfx.controls.base.MFXLabeled;
import io.github.palexdev.materialfx.skins.MFXToggleButtonSkin;
import io.github.palexdev.materialfx.utils.ColorUtils;
import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
import io.github.palexdev.materialfx.utils.ToggleButtonsUtil;
import io.github.palexdev.materialfx.utils.ToggleUtils;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -114,7 +114,7 @@ public class MFXToggleButton extends Labeled implements Toggle, MFXLabeled {
if (isSelected()) {
tg.selectToggle(this);
} else if (tg.getSelectedToggle() == this) {
ToggleButtonsUtil.clearSelectedToggle(tg);
ToggleUtils.clearSelectedToggle(tg);
}
}
});

View File

@ -27,7 +27,7 @@ import javafx.util.StringConverter;
* <p>
* {@code SpinnerModel} is basically an helper class to allow the spinner to work on any object
* type as long as a model exists for it. The model is responsible for changing the spinner's value by
* going forward or backwards ({@link #next() or {@link #previous()}}.
* going forward or backwards ({@link #next()}, or {@link #previous()}).
* <p>
* Along this core functionality the model also specifies a {@link StringConverter} which will be
* used to convert the T value to a String, which will be the spinner's text.

View File

@ -30,7 +30,7 @@ public class MFXDialogs {
}
public static MFXGenericDialogBuilder info() {
MFXFontIcon infoIcon = new MFXFontIcon("mfx-info-circle-filled", 18);
MFXFontIcon infoIcon = new MFXFontIcon("mfx-info-circle", 18);
return MFXGenericDialogBuilder.build().addStyleClasses("mfx-info-dialog").setHeaderIcon(infoIcon);
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2022 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 Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.palexdev.materialfx.enums;
import io.github.palexdev.materialfx.controls.MFXTitledPane;
/**
* Enumeration used by {@link MFXTitledPane} to specify the header's position.
*/
public enum HeaderPosition {
TOP, RIGHT, BOTTOM, LEFT
}

View File

@ -19,7 +19,7 @@
package io.github.palexdev.materialfx.font;
/**
* Enumerator class for MaterialFX font resources. (Count: 132)
* Enumerator class for MaterialFX font resources. (Count: 167)
*/
public enum FontResources {
ANGLE_DOWN("mfx-angle-down", '\uE900'),
@ -53,108 +53,142 @@ public enum FontResources {
CHEVRON_RIGHT("mfx-chevron-right", '\uE91C'),
CHEVRON_UP("mfx-chevron-up", '\uE91D'),
CIRCLE("mfx-circle", '\uE91E'),
CIRCLE_DOT("mfx-circle-dot", '\uE91F'),
CIRCLE_EMPTY("mfx-circle-empty", '\uE920'),
CONTENT_COPY("mfx-content-copy", '\uE921'),
CONTENT_CUT("mfx-content-cut", '\uE922'),
CONTENT_PASTE("mfx-content-paste", '\uE923'),
DASHBOARD("mfx-dashboard", '\uE924'),
DEBUG("mfx-debug", '\uE925'),
DELETE("mfx-delete", '\uE926'),
DELETE_ALT("mfx-delete-alt", '\uE927'),
DO_NOT_ENTER_CIRCLE("mfx-do-not-enter-circle", '\uE928'),
ELLIPSIS_VERTICAL("mfx-ellipsis-vertical", '\uE929'),
EXCLAMATION_CIRCLE("mfx-exclamation-circle", '\uE92A'),
EXCLAMATION_CIRCLE_FILLED("mfx-exclamation-circle-filled", '\uE92B'),
EXCLAMATION_TRIANGLE("mfx-exclamation-triangle", '\uE92C'),
EXPAND("mfx-expand", '\uE92D'),
EYE("mfx-eye", '\uE92E'),
EYE_SLASH("mfx-eye-slash", '\uE92F'),
FILE("mfx-file", '\uE930'),
FILTER("mfx-filter", '\uE931'),
FILTER_ALT("mfx-filter-alt", '\uE932'),
FILTER_ALT_CLEAR("mfx-filter-alt-clear", '\uE933'),
FIRST_PAGE("mfx-first-page", '\uE934'),
FIT("mfx-fit", '\uE935'),
FOLDER("mfx-folder", '\uE936'),
FONTICONS("mfx-fonticons", '\uE937'),
GEAR("mfx-gear", '\uE938'),
GOOGLE("mfx-google", '\uE939'),
GOOGLE_DRAWING("mfx-google-drawing", '\uE93A'),
GOOGLE_DRIVE("mfx-google-drive", '\uE93B'),
GOOGLE_FORMS("mfx-google-forms", '\uE93C'),
GOOGLE_FUSION_TABLES("mfx-google-fusion-tables", '\uE93D'),
GOOGLE_PRESENTATION("mfx-google-presentation", '\uE93E'),
GOOGLE_SCRIPT("mfx-google-script", '\uE93F'),
GOOGLE_SITES("mfx-google-sites", '\uE940'),
HOME("mfx-home", '\uE941'),
HYPHEN("mfx-hyphen", '\uE942'),
IMAGE("mfx-image", '\uE943'),
INFO("mfx-info", '\uE944'),
INFO_CIRCLE("mfx-info-circle", '\uE945'),
INFO_CIRCLE_FILLED("mfx-info-circle-filled", '\uE946'),
INPUT_PIPE("mfx-input-pipe", '\uE947'),
INPUT_PIPE_ALT("mfx-input-pipe-alt", '\uE948'),
LAST_PAGE("mfx-last-page", '\uE949'),
LEVEL_UP("mfx-level-up", '\uE94A'),
LIST_DROPDOWN("mfx-list-dropdown", '\uE94B'),
LOCK("mfx-lock", '\uE94C'),
LOCK_OPEN("mfx-lock-open", '\uE94D'),
LOGO("mfx-logo", '\uE94E'),
LOGO_ALT("mfx-logo-alt", '\uE94F'),
MAP("mfx-map", '\uE950'),
MESSAGE("mfx-message", '\uE951'),
MESSAGES("mfx-messages", '\uE952'),
MINUS("mfx-minus", '\uE953'),
MINUS_CIRCLE("mfx-minus-circle", '\uE954'),
MODENA_MARK("mfx-modena-mark", '\uE955'),
MUSIC("mfx-music", '\uE956'),
NEXT("mfx-next", '\uE957'),
PLUS("mfx-plus", '\uE958'),
PROGRESS_BARS("mfx-progress-bars", '\uE959'),
PROGRESS_BARS_ALT("mfx-progress-bars-alt", '\uE95A'),
REDO("mfx-redo", '\uE95B'),
RESTORE("mfx-restore", '\uE95C'),
SCROLL_BAR("mfx-scroll-bar", '\uE95D'),
SEARCH("mfx-search", '\uE95E'),
SEARCH_PLUS("mfx-search-plus", '\uE95F'),
SELECT_ALL("mfx-select-all", '\uE960'),
SHORTCUT("mfx-shortcut", '\uE961'),
SIDEBAR_CLOSE("mfx-sidebar-close", '\uE962'),
SIDEBAR_OPEN("mfx-sidebar-open", '\uE963'),
SLIDERS("mfx-sliders", '\uE964'),
SPREADSHEET("mfx-spreadsheet", '\uE965'),
SQUARE_LIST("mfx-square-list", '\uE966'),
STEP_BACKWARD("mfx-step-backward", '\uE967'),
STEP_FORWARD("mfx-step-forward", '\uE968'),
STEPPER("mfx-stepper", '\uE969'),
SYNC("mfx-sync", '\uE96A'),
SYNC_LIGHT("mfx-sync-light", '\uE96B'),
TABLE("mfx-table", '\uE96C'),
TABLE_ALT("mfx-table-alt", '\uE96D'),
TOGGLE_OFF("mfx-toggle-off", '\uE96E'),
TOGGLE_ON("mfx-toggle-on", '\uE96F'),
UNDO("mfx-undo", '\uE970'),
USER("mfx-user", '\uE971'),
USERS("mfx-users", '\uE972'),
VARIANT10_MARK("mfx-variant10-mark", '\uE973'),
VARIANT11_MARK("mfx-variant11-mark", '\uE974'),
VARIANT12_MARK("mfx-variant12-mark", '\uE975'),
VARIANT13_MARK("mfx-variant13-mark", '\uE976'),
VARIANT14_MARK("mfx-variant14-mark", '\uE977'),
VARIANT3_MARK("mfx-variant3-mark", '\uE978'),
VARIANT4_MARK("mfx-variant4-mark", '\uE979'),
VARIANT5_MARK("mfx-variant5-mark", '\uE97A'),
VARIANT6_MARK("mfx-variant6-mark", '\uE97B'),
VARIANT7_MARK("mfx-variant7-mark", '\uE97C'),
VARIANT8_MARK("mfx-variant8-mark", '\uE97D'),
VARIANT9_MARK("mfx-variant9-mark", '\uE97E'),
VIDEO("mfx-video", '\uE97F'),
X("mfx-x", '\uE980'),
X_ALT("mfx-x-alt", '\uE981'),
X_CIRCLE("mfx-x-circle", '\uE982'),
X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE983'),
X_LIGHT("mfx-x-light", '\uE984');
CIRCLE_CHEVRON_DOWN("mfx-circle-chevron-down", '\uE91F'),
CIRCLE_CHEVRON_LEFT("mfx-circle-chevron-left", '\uE920'),
CIRCLE_CHEVRON_RIGHT("mfx-circle-chevron-right", '\uE921'),
CIRCLE_CHEVRON_UP("mfx-circle-chevron-up", '\uE922'),
CIRCLE_DOT("mfx-circle-dot", '\uE923'),
CIRCLE_EMPTY("mfx-circle-empty", '\uE924'),
CONTENT_COPY("mfx-content-copy", '\uE925'),
CONTENT_CUT("mfx-content-cut", '\uE926'),
CONTENT_PASTE("mfx-content-paste", '\uE927'),
CSS("mfx-css", '\uE928'),
CSS_ALT("mfx-css-alt", '\uE929'),
DEBUG("mfx-debug", '\uE92A'),
DELETE("mfx-delete", '\uE92B'),
DELETE_ALT("mfx-delete-alt", '\uE92C'),
DO_NOT_ENTER_CIRCLE("mfx-do-not-enter-circle", '\uE92D'),
ELLIPSIS_VERTICAL("mfx-ellipsis-vertical", '\uE92E'),
EXCLAMATION_CIRCLE("mfx-exclamation-circle", '\uE92F'),
EXCLAMATION_CIRCLE_FILLED("mfx-exclamation-circle-filled", '\uE930'),
EXCLAMATION_TRIANGLE("mfx-exclamation-triangle", '\uE931'),
EXPAND("mfx-expand", '\uE932'),
EYE("mfx-eye", '\uE933'),
EYE_SLASH("mfx-eye-slash", '\uE934'),
FILE("mfx-file", '\uE935'),
FILES("mfx-files", '\uE936'),
FILTER("mfx-filter", '\uE937'),
FILTER_ALT("mfx-filter-alt", '\uE938'),
FILTER_ALT_CLEAR("mfx-filter-alt-clear", '\uE939'),
FIRST_PAGE("mfx-first-page", '\uE93A'),
FIT("mfx-fit", '\uE93B'),
FOLDER("mfx-folder", '\uE93C'),
FONTICONS("mfx-fonticons", '\uE93D'),
FUNCTION("mfx-function", '\uE93E'),
FUNCTION_LIGHT("mfx-function-light", '\uE93F'),
GEAR("mfx-gear", '\uE940'),
GEAR_ALT("mfx-gear-alt", '\uE941'),
GEARS("mfx-gears", '\uE942'),
GOOGLE("mfx-google", '\uE943'),
GOOGLE_DRAWING("mfx-google-drawing", '\uE944'),
GOOGLE_DRIVE("mfx-google-drive", '\uE945'),
GOOGLE_FORMS("mfx-google-forms", '\uE946'),
GOOGLE_FUSION_TABLES("mfx-google-fusion-tables", '\uE947'),
GOOGLE_PRESENTATION("mfx-google-presentation", '\uE948'),
GOOGLE_SCRIPT("mfx-google-script", '\uE949'),
GOOGLE_SITES("mfx-google-sites", '\uE94A'),
HOME("mfx-home", '\uE94B'),
HYPHEN("mfx-hyphen", '\uE94C'),
IMAGE("mfx-image", '\uE94D'),
INFO("mfx-info", '\uE94E'),
INFO_CIRCLE("mfx-info-circle", '\uE94F'),
INFO_CIRCLE_LIGHT("mfx-info-circle-light", '\uE950'),
INFO_SQUARE("mfx-info-square", '\uE951'),
INFO_SQUARE_LIGHT("mfx-info-square-light", '\uE952'),
INPUT_PIPE("mfx-input-pipe", '\uE953'),
INPUT_PIPE_ALT("mfx-input-pipe-alt", '\uE954'),
LAST_PAGE("mfx-last-page", '\uE955'),
LAYER_GROUP("mfx-layer-group", '\uE956'),
LAYER_GROUP_LIGHT("mfx-layer-group-light", '\uE957'),
LAYOUTS_ALT("mfx-layouts-alt", '\uE958'),
LAYOUTS_ALT_LIGHT("mfx-layouts-alt-light", '\uE959'),
LEVEL_UP("mfx-level-up", '\uE95A'),
LIST_DROPDOWN("mfx-list-dropdown", '\uE95B'),
LOCK("mfx-lock", '\uE95C'),
LOCK_OPEN("mfx-lock-open", '\uE95D'),
LOGO("mfx-logo", '\uE95E'),
LOGO_ALT("mfx-logo-alt", '\uE95F'),
MAGNIFYING_GLASS("mfx-magnifying-glass", '\uE960'),
MAGNIFYING_GLASS_LIGHT("mfx-magnifying-glass-light", '\uE961'),
MAGNIFYING_GLASS_MINUS("mfx-magnifying-glass-minus", '\uE962'),
MAGNIFYING_GLASS_MINUS_LIGHT("mfx-magnifying-glass-minus-light", '\uE963'),
MAGNIFYING_GLASS_PLUS("mfx-magnifying-glass-plus", '\uE964'),
MAGNIFYING_GLASS_PLUS_LIGHT("mfx-magnifying-glass-plus-light", '\uE965'),
MAP("mfx-map", '\uE966'),
MENU_V1("mfx-menu-v1", '\uE967'),
MENU_V2("mfx-menu-v2", '\uE968'),
MENU_V3("mfx-menu-v3", '\uE969'),
MENU_V3_LIGHT("mfx-menu-v3-light", '\uE96A'),
MESSAGE("mfx-message", '\uE96B'),
MESSAGES("mfx-messages", '\uE96C'),
MINUS("mfx-minus", '\uE96D'),
MINUS_CIRCLE("mfx-minus-circle", '\uE96E'),
MODENA_MARK("mfx-modena-mark", '\uE96F'),
MUSIC("mfx-music", '\uE970'),
NEXT("mfx-next", '\uE971'),
PEN_FIELD("mfx-pen-field", '\uE972'),
PEN_FIELD_LIGHT("mfx-pen-field-light", '\uE973'),
PLUS("mfx-plus", '\uE974'),
PROGRESS_BARS("mfx-progress-bars", '\uE975'),
PROGRESS_BARS_ALT("mfx-progress-bars-alt", '\uE976'),
REDO("mfx-redo", '\uE977'),
RESTORE("mfx-restore", '\uE978'),
SCROLL_BAR("mfx-scroll-bar", '\uE979'),
SELECT_ALL("mfx-select-all", '\uE97A'),
SHORTCUT("mfx-shortcut", '\uE97B'),
SIDEBAR_CLOSE("mfx-sidebar-close", '\uE97C'),
SIDEBAR_OPEN("mfx-sidebar-open", '\uE97D'),
SLIDERS("mfx-sliders", '\uE97E'),
SPARKLES("mfx-sparkles", '\uE97F'),
SPARKLES_LIGHT("mfx-sparkles-light", '\uE980'),
SPREADSHEET("mfx-spreadsheet", '\uE981'),
SQUARE_CHEVRON_DOWN("mfx-square-chevron-down", '\uE982'),
SQUARE_CHEVRON_LEFT("mfx-square-chevron-left", '\uE983'),
SQUARE_CHEVRON_RIGHT("mfx-square-chevron-right", '\uE984'),
SQUARE_CHEVRON_UP("mfx-square-chevron-up", '\uE985'),
SQUARE_LIST("mfx-square-list", '\uE986'),
SQUARE_PEN("mfx-square-pen", '\uE987'),
SQUARE_PLUS("mfx-square-plus", '\uE988'),
STEP_BACKWARD("mfx-step-backward", '\uE989'),
STEP_FORWARD("mfx-step-forward", '\uE98A'),
STEPPER("mfx-stepper", '\uE98B'),
SYNC("mfx-sync", '\uE98C'),
SYNC_LIGHT("mfx-sync-light", '\uE98D'),
TABLE("mfx-table", '\uE98E'),
TABLE_ALT("mfx-table-alt", '\uE98F'),
TOGGLE_OFF("mfx-toggle-off", '\uE990'),
TOGGLE_ON("mfx-toggle-on", '\uE991'),
UNDO("mfx-undo", '\uE992'),
USER("mfx-user", '\uE993'),
USERS("mfx-users", '\uE994'),
VARIANT10_MARK("mfx-variant10-mark", '\uE995'),
VARIANT11_MARK("mfx-variant11-mark", '\uE996'),
VARIANT12_MARK("mfx-variant12-mark", '\uE997'),
VARIANT13_MARK("mfx-variant13-mark", '\uE998'),
VARIANT14_MARK("mfx-variant14-mark", '\uE999'),
VARIANT3_MARK("mfx-variant3-mark", '\uE99A'),
VARIANT4_MARK("mfx-variant4-mark", '\uE99B'),
VARIANT5_MARK("mfx-variant5-mark", '\uE99C'),
VARIANT6_MARK("mfx-variant6-mark", '\uE99D'),
VARIANT7_MARK("mfx-variant7-mark", '\uE99E'),
VARIANT8_MARK("mfx-variant8-mark", '\uE99F'),
VARIANT9_MARK("mfx-variant9-mark", '\uE9A0'),
VIDEO("mfx-video", '\uE9A1'),
X("mfx-x", '\uE9A2'),
X_ALT("mfx-x-alt", '\uE9A3'),
X_CIRCLE("mfx-x-circle", '\uE9A4'),
X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE9A5'),
X_LIGHT("mfx-x-light", '\uE9A6');
public static FontResources findByDescription(String description) {
for (FontResources font : values()) {

View File

@ -173,7 +173,7 @@ public class MFXRectangleToggleNodeSkin extends SkinBase<MFXRectangleToggleNode>
// Override Methods
//================================================================================
@Override
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
MFXRectangleToggleNode toggleNode = getSkinnable();
Node graphic = toggleNode.getGraphic();
if (graphic != null) {
@ -192,6 +192,16 @@ public class MFXRectangleToggleNodeSkin extends SkinBase<MFXRectangleToggleNode>
rightInset;
}
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
MFXRectangleToggleNode toggleNode = getSkinnable();
Node graphic = toggleNode.getGraphic();
if (graphic != null) {
return topInset + graphic.prefHeight(-1) + bottomInset;
}
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
@Override
protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
return getSkinnable().prefWidth(-1);

View File

@ -19,9 +19,14 @@
package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.controls.MFXScrollPane;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.skin.ScrollPaneSkin;
import javafx.scene.layout.StackPane;
import java.util.Set;
/**
* Skin used for {@link MFXScrollPane}, this class' purpose is to
* fix a bug of ScrollPanes' viewport which makes the content blurry.
@ -34,5 +39,13 @@ public class MFXScrollPaneSkin extends ScrollPaneSkin {
super(scrollPane);
StackPane viewPort = (StackPane) scrollPane.lookup(".viewport");
viewPort.setCache(false);
Set<Node> nodes = scrollPane.lookupAll(".scroll-bar");
nodes.forEach(node -> {
if (node instanceof ScrollBar) {
ScrollBar sb = ((ScrollBar) node);
sb.getStyleClass().add(sb.getOrientation() == Orientation.VERTICAL ? "vBar" : "hBar");
}
});
}
}

View File

@ -0,0 +1,282 @@
/*
* Copyright (C) 2022 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 Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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.MFXTitledPane;
import io.github.palexdev.materialfx.effects.Interpolators;
import io.github.palexdev.materialfx.enums.HeaderPosition;
import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames;
import io.github.palexdev.materialfx.utils.AnimationUtils.TimelineBuilder;
import io.github.palexdev.materialfx.utils.ExecutionUtils;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Default skin implementation used by {@link MFXTitledPane}.
* <p></p>
* It consists in three main nodes:
* <p> - A {@link BorderPane} which is the top container, automatically manages the layout
* for use making things way easier. It is also very convenient for the {@link MFXTitledPane#headerPosProperty()} feature,
* see {@link #updatePane()}.
* <p> - A {@link StackPane} which contains the {@link MFXTitledPane#contentProperty()}
* <p> - A {@link Rectangle} to clip the above {@code StackPane}
* <p></p>
* Since the header position can change, the whole system needs to change as well. What I mean, is:
* if the header is at the TOP or BOTTOM then we want to work with the content pane's prefHeight,
* otherwise we want to work with the content pane's prefWidth.
* This deeply influences both the clip and the expand/collapse code.
* <p></p>
* For this reason we use a smart system with three functions:
* <p> - A {@link Supplier} which gives us the content pane's prefWidth/prefHeight property (sizeSupplier)
* <p> - A {@link Supplier} which gives us the content's prefWidth/prefHeight (targetSizeSupplier)
* <p> - A {@link Consumer} which accepts a target prefWidth/prefHeight and sets it on the content pane (setter)
*/
public class MFXTitledPaneSkin extends SkinBase<MFXTitledPane> {
//================================================================================
// Properties
//================================================================================
private final BorderPane bp;
private final StackPane contentPane;
private final Rectangle clip;
private Node header;
private Node content;
private Supplier<DoubleProperty> sizeSupplier;
private Supplier<Double> targetSizeSupplier;
private Consumer<Double> setter;
//================================================================================
// Constructors
//================================================================================
public MFXTitledPaneSkin(MFXTitledPane pane) {
super(pane);
header = pane.getHeaderSupplier().get();
content = pane.getContent();
contentPane = new StackPane();
contentPane.getStyleClass().add("content-pane");
if (content != null) contentPane.getChildren().add(content);
clip = new Rectangle();
clip();
bp = new BorderPane();
bp.setCenter(contentPane);
updateSuppliers();
updatePane();
getChildren().setAll(bp);
addListeners();
}
//================================================================================
// Methods
//================================================================================
/**
* Adds the following listeners/handlers:
* <p> - A listener to the {@link MFXTitledPane#expandedProperty()} to call {@link #expandCollapse()}
* <p> - A listener to the {@link MFXTitledPane#collapsibleProperty()} to call {@link #expandCollapse()}
* <p> - A listener to the {@link MFXTitledPane#headerSupplierProperty()} to call {@link #updatePane()}
* <p> - A listener to the {@link MFXTitledPane#contentProperty()} to update the content pane and call {@link #expandCollapse()}
* <p> - A listener to the {@link MFXTitledPane#headerPosProperty()} to call {@link #updateSuppliers()}, {@link #clip()}, {@link #updatePane()} and {@link #expandCollapse()}
* <p> - A MouseEvent.MOUSE_PRESSED event handler to acquire focus
* <p></p>
* There's also a call to {@link ExecutionUtils#executeWhen(ObservableValue, BiConsumer, boolean, BiFunction, boolean)},
* which triggers when the content pane is laid out and calls {@link #expandCollapse()} to initialize the control.
*/
private void addListeners() {
MFXTitledPane pane = getSkinnable();
pane.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> pane.requestFocus());
pane.expandedProperty().addListener(invalidated -> expandCollapse());
pane.collapsibleProperty().addListener(invalidated -> expandCollapse());
pane.headerSupplierProperty().addListener((observable, oldValue, newValue) -> {
bp.getChildren().remove(header);
header = newValue.get();
updatePane();
});
pane.contentProperty().addListener((observable, oldValue, newValue) -> {
content = newValue;
if (newValue != null) contentPane.getChildren().setAll(newValue);
expandCollapse();
});
pane.headerPosProperty().addListener(invalidated -> {
updateSuppliers();
clip();
updatePane();
expandCollapse();
});
ExecutionUtils.executeWhen(
contentPane.layoutBoundsProperty(),
(oldValue, newValue) -> expandCollapse(),
false,
(oldValue, newValue) -> newValue.getWidth() != 0 || newValue.getHeight() != 0,
true
);
}
/**
* Responsible for updating the sizes' functions when {@link MFXTitledPane#headerPosProperty()} changes.
* <p></p>
* Case RIGHT, LEFT:
* <p> - Pref Height set to {@link Region#USE_COMPUTED_SIZE}, sizeSupplier set to {@code contentPane::prefWidthProperty},
* targetSizeSupplier set to {@code content.prefWidth(-1)} (plus insets), setter set to {@code contentPane::setPrefWidth}
* Case TOP, BOTTOM:
* <p> - Pref Width set to {@link Region#USE_COMPUTED_SIZE}, sizeSupplier set to {@code contentPane::prefHeightProperty},
* targetSizeSupplier set to {@code content.prefHeight(-1)} (plus insets), setter set to {@code contentPane::setPrefHeight}
*/
private void updateSuppliers() {
MFXTitledPane pane = getSkinnable();
HeaderPosition position = pane.getHeaderPos();
if (position == HeaderPosition.RIGHT || position == HeaderPosition.LEFT) {
contentPane.setPrefHeight(Region.USE_COMPUTED_SIZE);
sizeSupplier = contentPane::prefWidthProperty;
targetSizeSupplier = () -> contentPane.snappedLeftInset() + content.prefWidth(-1) + contentPane.snappedRightInset();
setter = contentPane::setPrefWidth;
} else {
contentPane.setPrefWidth(Region.USE_COMPUTED_SIZE);
sizeSupplier = contentPane::prefHeightProperty;
targetSizeSupplier = () -> contentPane.snappedTopInset() + content.prefHeight(-1) + contentPane.snappedBottomInset();
setter = contentPane::setPrefHeight;
}
}
/**
* Responsible for updating the header position in the {@link BorderPane}
* according to {@link MFXTitledPane#headerPosProperty()}.
*/
private void updatePane() {
MFXTitledPane pane = getSkinnable();
HeaderPosition position = pane.getHeaderPos();
switch (position) {
case RIGHT:
bp.setRight(header);
contentPane.setMinSize(Region.USE_PREF_SIZE, Region.USE_COMPUTED_SIZE);
break;
case BOTTOM:
bp.setBottom(header);
contentPane.setMinSize(Region.USE_COMPUTED_SIZE, Region.USE_PREF_SIZE);
break;
case LEFT:
bp.setLeft(header);
contentPane.setMinSize(Region.USE_PREF_SIZE, Region.USE_COMPUTED_SIZE);
break;
case TOP:
default:
bp.setTop(header);
contentPane.setMinSize(Region.USE_COMPUTED_SIZE, Region.USE_PREF_SIZE);
}
}
/**
* Responsible for expanding/collapsing the content pane according to:
* {@link MFXTitledPane#expandedProperty()}, {@link MFXTitledPane#collapsibleProperty()}, {@link MFXTitledPane#animatedProperty()}.
* <p></p>
* If the content is null the size is set to 0
* <p>
* If it's not expanded and it's not collapsible the size is set to {@code targetSizeSupplier.get()} and returns immediately.
* <p></p>
* Otherwise, the target size is computed according to the expand state, and the size is then set directly or by an animation,
* the opacity is also animated.
*/
private void expandCollapse() {
if (content == null) {
setter.accept(0.0);
return;
}
MFXTitledPane pane = getSkinnable();
boolean isExpanded = pane.isExpanded();
if (!isExpanded && !pane.isCollapsible()) {
setter.accept(targetSizeSupplier.get());
return;
}
DoubleProperty property = sizeSupplier.get();
double targetSize = isExpanded ? targetSizeSupplier.get() : 0;
double targetOp = isExpanded ? 1.0 : 0.0;
if (pane.isAnimated()) {
TimelineBuilder.build()
.add(KeyFrames.of(pane.getAnimationDuration(), contentPane.opacityProperty(), targetOp, Interpolators.INTERPOLATOR_V1))
.add(KeyFrames.of(pane.getAnimationDuration(), property, targetSize, Interpolators.INTERPOLATOR_V1))
.getAnimation()
.play();
} else {
contentPane.setOpacity(targetOp);
setter.accept(targetSize);
}
}
/**
* Responsible for clipping the content pane according to the
* {@link MFXTitledPane#headerPosProperty()}.
*/
private void clip() {
MFXTitledPane pane = getSkinnable();
HeaderPosition position = pane.getHeaderPos();
contentPane.setClip(null);
if (position == HeaderPosition.RIGHT || position == HeaderPosition.LEFT) {
clip.widthProperty().bind(contentPane.prefWidthProperty());
clip.heightProperty().bind(pane.heightProperty());
} else {
clip.widthProperty().bind(pane.widthProperty());
clip.heightProperty().bind(contentPane.prefHeightProperty());
}
contentPane.setClip(clip);
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
HeaderPosition position = getSkinnable().getHeaderPos();
if (position == HeaderPosition.RIGHT || position == HeaderPosition.LEFT) {
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
}
return super.computeMaxWidth(height, topInset, rightInset, bottomInset, leftInset);
}
@Override
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
HeaderPosition position = getSkinnable().getHeaderPos();
if (position == HeaderPosition.TOP || position == HeaderPosition.BOTTOM) {
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset);
}
}

View File

@ -128,7 +128,9 @@ public abstract class MFXLabeledSkinBase<C extends Labeled & MFXLabeled> extends
BorderPane.setMargin(text, InsetsFactory.left(gap));
break;
}
case GRAPHIC_ONLY:
case GRAPHIC_ONLY: {
break;
}
case CENTER: {
topContainer.setCenter(controlContainer);
BorderPane.setMargin(text, InsetsFactory.none());
@ -175,8 +177,12 @@ public abstract class MFXLabeledSkinBase<C extends Labeled & MFXLabeled> extends
minW = Math.max(getControlContainer().prefWidth(-1), text.prefWidth(-1));
break;
}
case CENTER:
case GRAPHIC_ONLY: {
Node graphic = labeled.getGraphic();
minW = (graphic != null) ? graphic.prefWidth(-1) : 0.0;
break;
}
case CENTER: {
minW = getControlContainer().prefWidth(-1);
}
}

View File

@ -79,11 +79,11 @@ public class PositionUtils {
//================================================================================
public static PositionBean computePosition(Region parent, Node child, double areaX, double areaY, double areaWidth, double areaHeight,
double areaBaselineOffset, Insets margin, HPos hAlignment, VPos vAlignment) {
return computePosition(parent, child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, hAlignment, vAlignment, true);
return computePosition(parent, child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, hAlignment, vAlignment, true, true);
}
public static PositionBean computePosition(Region parent, Node child, double areaX, double areaY, double areaWidth, double areaHeight,
double areaBaselineOffset, Insets margin, HPos hAlignment, VPos vAlignment, boolean snapToPixel) {
double areaBaselineOffset, Insets margin, HPos hAlignment, VPos vAlignment, boolean snapToPixel, boolean computeSizes) {
Insets snappedMargin = margin == null ? Insets.EMPTY : margin;
if (snapToPixel) {
@ -95,12 +95,12 @@ public class PositionUtils {
);
}
double xPosition = computeXPosition(parent, child, areaX, areaWidth, snappedMargin, false, hAlignment, snapToPixel);
double yPosition = computeYPosition(parent, child, areaY, areaHeight, areaBaselineOffset, snappedMargin, false, vAlignment, snapToPixel);
double xPosition = computeXPosition(parent, child, areaX, areaWidth, snappedMargin, false, hAlignment, snapToPixel, computeSizes);
double yPosition = computeYPosition(parent, child, areaY, areaHeight, areaBaselineOffset, snappedMargin, false, vAlignment, snapToPixel, computeSizes);
return PositionBean.of(xPosition, yPosition);
}
public static double computeXPosition(Region parent, Node child, double areaX, double areaWidth, Insets margin, boolean snapMargin, HPos hAlignment, boolean snapToPixel) {
public static double computeXPosition(Region parent, Node child, double areaX, double areaWidth, Insets margin, boolean snapMargin, HPos hAlignment, boolean snapToPixel, boolean computeSizes) {
Insets snappedMargin = margin == null ? Insets.EMPTY : margin;
if (snapMargin) {
snappedMargin = InsetsFactory.of(
@ -113,12 +113,12 @@ public class PositionUtils {
final double leftMargin = snappedMargin.getLeft();
final double rightMargin = snappedMargin.getRight();
final double xOffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin, child.getLayoutBounds().getWidth(), hAlignment);
final double xOffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin, computeSizes ? child.prefWidth(-1) : child.getLayoutBounds().getWidth(), hAlignment);
final double xPosition = areaX + xOffset;
return snapToPixel ? parent.snapPositionX(xPosition) : xPosition;
}
public static double computeYPosition(Region parent, Node child, double areaY, double areaHeight, double areaBaselineOffset, Insets margin, boolean snapMargin, VPos vAlignment, boolean snapToPixel) {
public static double computeYPosition(Region parent, Node child, double areaY, double areaHeight, double areaBaselineOffset, Insets margin, boolean snapMargin, VPos vAlignment, boolean snapToPixel, boolean computeSizes) {
Insets snappedMargin = margin == null ? Insets.EMPTY : margin;
if (snapMargin) {
snappedMargin = InsetsFactory.of(
@ -135,13 +135,12 @@ public class PositionUtils {
if (vAlignment == VPos.BASELINE) {
double bo = child.getBaselineOffset();
if (bo == Node.BASELINE_OFFSET_SAME_AS_HEIGHT) {
// We already know the layout bounds at this stage, so we can use them
yOffset = areaBaselineOffset - child.getLayoutBounds().getHeight();
yOffset = areaBaselineOffset - (computeSizes ? child.prefHeight(-1) : child.getLayoutBounds().getHeight());
} else {
yOffset = areaBaselineOffset - bo;
}
} else {
yOffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin, child.getLayoutBounds().getHeight(), vAlignment);
yOffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin, computeSizes ? child.prefHeight(-1) : child.getLayoutBounds().getHeight(), vAlignment);
}
final double yPosition = areaY + yOffset;
return snapToPixel ? parent.snapPositionY(yPosition) : yPosition;

View File

@ -28,7 +28,7 @@ import javafx.scene.input.MouseEvent;
/**
* Utils class for {@code ToggleButtons}.
*/
public class ToggleButtonsUtil {
public class ToggleUtils {
private static final EventHandler<MouseEvent> consumeMouseEventFilter = (MouseEvent mouseEvent) -> {
if (((Toggle) mouseEvent.getSource()).isSelected()) {
@ -56,7 +56,7 @@ public class ToggleButtonsUtil {
}
}
});
toggleGroup.getToggles().forEach(ToggleButtonsUtil::addConsumeMouseEventFilter);
toggleGroup.getToggles().forEach(ToggleUtils::addConsumeMouseEventFilter);
}
/**
@ -74,4 +74,10 @@ public class ToggleButtonsUtil {
}
toggleGroup.selectToggle(null);
}
public static void addTogglesTo(ToggleGroup tg, Toggle... toggles) {
for (Toggle toggle : toggles) {
toggle.setToggleGroup(tg);
}
}
}

View File

@ -0,0 +1,41 @@
@import "Fonts.css";
@import "MFXColors.css";
@import "MFXScrollPane.css";
.mfx-titled-pane {
-fx-background-color: white;
-fx-background-radius: 6;
-fx-border-color: lightgray;
-fx-border-radius: 6;
}
.mfx-titled-pane:focused {
-fx-border-color: -mfx-purple;
}
.mfx-titled-pane .header-pane {
-fx-padding: 5;
}
.mfx-titled-pane .header-pane .header-label {
-fx-font-family: "Open Sans SemiBold";
-fx-text-fill: -mfx-text-he;
}
.mfx-titled-pane:focused .header-pane .mfx-icon-wrapper .mfx-font-icon {
-mfx-color: -mfx-purple;
}
.mfx-titled-pane .header-pane .mfx-icon-wrapper .mfx-ripple-generator {
-mfx-ripple-color: derive(-mfx-purple, 145%);
}
.mfx-titled-pane .content-pane {
-fx-background-color: transparent;
-fx-border-color: lightgray transparent transparent transparent;
-fx-padding: 10 5 10 5;
}
.mfx-titled-pane:focused .content-pane {
-fx-border-color: -mfx-purple transparent transparent transparent;
}