Compare commits

...

3 Commits

Author SHA1 Message Date
palexdev
4cb62db297 🔖 Bump and release **alpha** version 21.18.0-alpha
Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
2025-02-27 12:55:22 +01:00
palexdev
75d507e20c 💥 Begin transition to JDK21 and VirtualizedFX 21.x.x
Currently ported controls (that rely on virtualization):
- Combo Box
- Filter Combo Box
- List

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
2025-02-27 12:33:51 +01:00
palexdev
e159bbd3c4 💥 Update configuration and prepare for first 21.x.x release
Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
2025-01-15 14:59:34 +01:00
109 changed files with 2904 additions and 7213 deletions

View File

@ -15,10 +15,18 @@ repositories {
}
subprojects {
apply plugin: 'java-library'
apply plugin: 'org.openjfx.javafxplugin'
javafx {
version = "$jfx"
modules = ['javafx.controls', 'javafx.fxml', 'javafx.media', 'javafx.swing', 'javafx.web']
}
test {
// Because the new TestFX is garbage :)
jvmArgs += [
'--add-opens', 'javafx.graphics/com.sun.javafx.application=ALL-UNNAMED'
]
useJUnitPlatform()
}
}

View File

@ -1,5 +1,7 @@
import org.apache.tools.ant.taskdefs.condition.Os
import java.nio.file.Paths
plugins {
id 'application'
id 'org.beryx.jlink' version "$jlink"
@ -13,49 +15,43 @@ repositories {
}
}
compileJava {
sourceCompatibility = "$jdk"
targetCompatibility = "$jdk"
}
dependencies {
implementation project(':materialfx')
implementation("io.github.palexdev:scenicview:$scenicView") {
exclude group: 'org.openjfx'
}
implementation("fr.brouillard.oss:cssfx:$cssfx") { exclude group: 'org.openjfx' }
implementation "org.kordamp.ikonli:ikonli-core:$ikonli"
implementation "org.kordamp.ikonli:ikonli-javafx:$ikonli"
implementation "org.kordamp.ikonli:ikonli-fontawesome5-pack:$ikonli"
implementation "io.github.palexdev:virtualizedfx:$vfx"
testImplementation "org.testfx:testfx-core:$testfx"
testImplementation "org.testfx:testfx-junit5:$testfx"
testImplementation "org.junit.jupiter:junit-jupiter-api:$junit"
testImplementation "org.junit.platform:junit-platform-suite-api:$junitSuite"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit"
implementation "io.github.palexdev:scenicview:$scenicView"
implementation("fr.brouillard.oss:cssfx:$cssfx") { exclude group: 'org.openjfx' }
implementation "org.kordamp.ikonli:ikonli-core:$ikonli"
implementation "org.kordamp.ikonli:ikonli-javafx:$ikonli"
implementation "org.kordamp.ikonli:ikonli-fontawesome5-pack:$ikonli"
implementation "io.github.palexdev:virtualizedfx:$vfx"
implementation project(':materialfx')
}
compileJava {
sourceCompatibility = "$testJdk"
targetCompatibility = "$testJdk"
}
compileTestJava {
moduleOptions {
compileOnClasspath = true
}
}
test {
useJUnitPlatform()
moduleOptions {
runOnClasspath = true
}
javafx {
modules = ['javafx.controls', 'javafx.fxml', 'javafx.swing']
configurations = ["implementation", "testImplementation"]
}
application {
setMainModule("MaterialFX.Demo")
mainModule = "MaterialFX.Demo"
String main = project.findProperty("chooseMain").toString()
if (main != "null" && !main.trim().isEmpty()) {
setMainClassName(main)
mainClass = main
} else {
setMainClassName("io.github.palexdev.materialfx.demo.Demo")
mainClass = "io.github.palexdev.materialfx.demo.Demo"
}
applicationDefaultJvmArgs = ["-Dglass.disableGrab=true"]
}
@ -66,26 +62,40 @@ jlink {
mainClass = "io.github.palexdev.materialfx.demo.Demo"
name = 'MaterialFX Demo'
}
addExtraDependencies("javafx")
jpackage {
imageOptions = ['--icon', 'src/main/resources/logo.ico']
imageOptions = ['--icon', 'src/main/resources/io/github/palexdev/materialfx/demo/logo.ico']
}
targetPlatform("linux-x64") {
jdkHome = jdkDownload("https://cdn.azul.com/zulu/bin/zulu19.32.13-ca-jdk19.0.2-linux_x64.tar.gz")
addExtraModulePath("/home/palexdev/Documents/JavaFX_jmods/linux_x64")
def jmodBasePath = Paths.get(rootProject.projectDir.getAbsolutePath()).resolve("jfx-jmods").toString()
targetPlatform("linux") {
jdkHome = jdkDownload("https://cdn.azul.com/zulu/bin/zulu21.38.21-ca-jdk21.0.5-linux_x64.tar.gz")
addExtraModulePath("$jmodBasePath/linux-$jfx")
jpackage {
targetPlatformName = "linux"
}
}
targetPlatform("win") {
jdkHome = jdkDownload("https://cdn.azul.com/zulu/bin/zulu19.32.13-ca-jdk19.0.2-win_x64.zip")
addExtraModulePath("/home/palexdev/Documents/JavaFX_jmods/win_x64")
jdkHome = jdkDownload("https://cdn.azul.com/zulu/bin/zulu21.38.21-ca-jdk21.0.5-win_x64.zip")
addExtraModulePath("$jmodBasePath/win-$jfx")
jpackage {
targetPlatformName = "win"
}
}
targetPlatform("mac") {
jdkHome = jdkDownload("https://cdn.azul.com/zulu/bin/zulu19.32.13-ca-jdk19.0.2-macosx_x64.tar.gz")
addExtraModulePath("/home/palexdev/Documents/JavaFX_jmods/mac_x64")
}
jdkHome = jdkDownload("https://cdn.azul.com/zulu/bin/zulu21.38.21-ca-jdk21.0.5-macosx_x64.tar.gz")
addExtraModulePath("$jmodBasePath/mac-$jfx")
addExtraDependencies('javafx')
jpackage {
targetPlatformName = "mac"
}
}
}
tasks.register('doPackageAll') {

View File

@ -18,8 +18,10 @@
package io.github.palexdev.materialfx.demo.controllers;
import java.net.URL;
import java.util.*;
import io.github.palexdev.materialfx.controls.MFXTableColumn;
import io.github.palexdev.materialfx.controls.MFXTableRow;
import io.github.palexdev.materialfx.controls.MFXTableView;
import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
import io.github.palexdev.materialfx.filter.StringFilter;
@ -35,9 +37,6 @@ import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import java.net.URL;
import java.util.*;
public class FontResourcesController implements Initializable {
private final ObservableList<IconDescriptor> fontResources;
@ -67,12 +66,13 @@ public class FontResourcesController implements Initializable {
private void handleProvider(IconDescriptor desc) {
if (desc.getClass() == current) return;
if (desc instanceof FontAwesomeSolid) {
icon.setIconsProvider(IconsProviders.FONTAWESOME_SOLID);
} else if (desc instanceof FontAwesomeRegular) {
icon.setIconsProvider(IconsProviders.FONTAWESOME_REGULAR);
} else if (desc instanceof FontAwesomeBrands) {
icon.setIconsProvider(IconsProviders.FONTAWESOME_BRANDS);
switch (desc) {
case FontAwesomeSolid fontAwesomeSolid -> icon.setIconsProvider(IconsProviders.FONTAWESOME_SOLID);
case FontAwesomeRegular fontAwesomeRegular ->
icon.setIconsProvider(IconsProviders.FONTAWESOME_REGULAR);
case FontAwesomeBrands fontAwesomeBrands ->
icon.setIconsProvider(IconsProviders.FONTAWESOME_BRANDS);
default -> {}
}
current = desc.getClass();
}
@ -92,15 +92,10 @@ public class FontResourcesController implements Initializable {
});
codeColumn.setRowCellFactory(resource -> new MFXTableRowCell<>(IconDescriptor::getCode, character -> Integer.toHexString(character | 0x10000).substring(1).toUpperCase()));
tableView.setTableRowFactory(resource -> new MFXTableRow<>(tableView, resource) {{
setPrefHeight(48);
}});
tableView.getTableColumns().addAll(iconColumn, descriptionColumn, codeColumn);
tableView.setRowsHeight(48.0);
//tableView.getColumns().addAll(iconColumn, descriptionColumn, codeColumn);
tableView.getFilters().add(new StringFilter<>("Description", IconDescriptor::getDescription));
tableView.setItems(fontResources);
tableView.features().enableBounceEffect();
tableView.features().enableSmoothScrolling(0.7);
tableView.autosizeColumnsOnInitialization();
tableView.getItems().setAll(fontResources);
header.setText("MaterialFX Font Resources (" + fontResources.size() + " in total)");
}

View File

@ -18,37 +18,40 @@
package io.github.palexdev.materialfx.demo.controllers;
import java.net.URL;
import java.util.ResourceBundle;
import io.github.palexdev.materialfx.controls.MFXCheckListView;
import io.github.palexdev.materialfx.controls.MFXListView;
import io.github.palexdev.materialfx.controls.cell.MFXCheckListCell;
import io.github.palexdev.materialfx.controls.cell.MFXListCell;
import io.github.palexdev.materialfx.controls.legacy.MFXLegacyListCell;
import io.github.palexdev.materialfx.controls.legacy.MFXLegacyListView;
import io.github.palexdev.materialfx.demo.model.Model;
import io.github.palexdev.materialfx.demo.model.Person;
import io.github.palexdev.materialfx.effects.DepthLevel;
import io.github.palexdev.mfxresources.fonts.MFXFontIcon;
import io.github.palexdev.materialfx.utils.ColorUtils;
import io.github.palexdev.materialfx.utils.others.FunctionalStringConverter;
import io.github.palexdev.mfxeffects.enums.ElevationLevel;
import io.github.palexdev.mfxresources.fonts.MFXFontIcon;
import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.util.StringConverter;
import java.net.URL;
import java.util.ResourceBundle;
public class ListViewsController implements Initializable {
@FXML
private MFXListView<String> list;
private MFXListView<String, VFXCell<String>> list;
@FXML
private MFXListView<Person> custList;
private MFXListView<Person, VFXCell<Person>> custList;
@FXML
private MFXCheckListView<String> checkList;
private MFXCheckListView<String, VFXCell<String>> checkList;
@FXML
private MFXListView<Person> legacyList;
private MFXLegacyListView<Person> legacyList;
@Override
public void initialize(URL location, ResourceBundle resources) {
@ -57,45 +60,42 @@ public class ListViewsController implements Initializable {
StringConverter<Person> converter = FunctionalStringConverter.to(person -> (person == null) ? "" : person.getName() + " " + person.getSurname());
list.setItems(strings);
list.setCellFactory(MFXListCell::new);
custList.setItems(people);
custList.setCellFactory(p -> new PersonCellFactory(p).setConverter(converter));
checkList.setItems(strings);
custList.setConverter(converter);
custList.setCellFactory(person -> new PersonCellFactory(custList, person));
custList.features().enableBounceEffect();
custList.features().enableSmoothScrolling(0.5);
checkList.setCellFactory(MFXCheckListCell::new);
legacyList.setItems(people);
legacyList.setConverter(converter);
}
@FXML
void changeColors(ActionEvent event) {
custList.setTrackColor(ColorUtils.getRandomColor());
custList.setThumbColor(ColorUtils.getRandomColor());
custList.setThumbHoverColor(ColorUtils.getRandomColor());
legacyList.setCellFactory(p -> new MFXLegacyListCell<>() {
@Override
protected void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
setText(item != null ? converter.toString(item) : "");
}
});
}
@FXML
void changeDepth(ActionEvent event) {
DepthLevel newLevel = (custList.getDepthLevel() == DepthLevel.LEVEL0) ? DepthLevel.LEVEL2 : DepthLevel.LEVEL0;
custList.setDepthLevel(newLevel);
ElevationLevel depth = list.getDepth();
ElevationLevel next = depth == ElevationLevel.LEVEL0 ? ElevationLevel.LEVEL3 : ElevationLevel.LEVEL0;
list.setDepth(next);
custList.setDepth(next);
checkList.setDepth(next);
}
private static class PersonCellFactory extends MFXListCell<Person> {
private final MFXFontIcon userIcon;
public PersonCellFactory(MFXListView<Person> listView, Person data) {
super(listView, data);
public PersonCellFactory(Person data) {
super(data);
userIcon = new MFXFontIcon("fas-user", 18);
MFXFontIcon userIcon = new MFXFontIcon("fas-user", 18);
userIcon.getStyleClass().add("user-icon");
render(data);
}
@Override
protected void render(Person data) {
super.render(data);
if (userIcon != null) getChildren().add(0, userIcon);
setGraphic(userIcon);
}
}
}

View File

@ -22,7 +22,6 @@ import io.github.palexdev.materialfx.controls.MFXButton;
import io.github.palexdev.materialfx.controls.MFXIconWrapper;
import io.github.palexdev.materialfx.controls.MFXNotificationCenter;
import io.github.palexdev.materialfx.controls.MFXSimpleNotification;
import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell;
import io.github.palexdev.materialfx.demo.MFXDemoResourcesLoader;
import io.github.palexdev.materialfx.demo.model.Model;
import io.github.palexdev.materialfx.enums.NotificationPos;
@ -59,11 +58,7 @@ public class NotificationsController {
MFXNotificationCenterSystem.instance().initOwner(stage);
MFXNotificationCenter center = MFXNotificationCenterSystem.instance().getCenter();
center.setCellFactory(notification -> new MFXNotificationCell(center, notification) {
{
setPrefHeight(400);
}
});
center.setCellHeight(400.0);
});
}

View File

@ -18,24 +18,15 @@
package io.github.palexdev.materialfx.demo.controllers;
import java.net.URL;
import java.util.ResourceBundle;
import io.github.palexdev.materialfx.controls.MFXPaginatedTableView;
import io.github.palexdev.materialfx.controls.MFXTableColumn;
import io.github.palexdev.materialfx.controls.MFXTableView;
import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
import io.github.palexdev.materialfx.demo.model.Device;
import io.github.palexdev.materialfx.demo.model.Model;
import io.github.palexdev.materialfx.demo.model.Person;
import io.github.palexdev.materialfx.filter.EnumFilter;
import io.github.palexdev.materialfx.filter.IntegerFilter;
import io.github.palexdev.materialfx.filter.StringFilter;
import io.github.palexdev.materialfx.utils.others.observables.When;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import java.net.URL;
import java.util.Comparator;
import java.util.ResourceBundle;
public class TableViewsController implements Initializable {
@ -47,7 +38,7 @@ public class TableViewsController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
setupTable();
/* setupTable();
setupPaginated();
table.autosizeColumnsOnInitialization();
@ -55,10 +46,10 @@ public class TableViewsController implements Initializable {
When.onChanged(paginated.currentPageProperty())
.then((oldValue, newValue) -> paginated.autosizeColumns())
.listen();
.listen();*/
}
private void setupTable() {
/* private void setupTable() {
MFXTableColumn<Person> nameColumn = new MFXTableColumn<>("Name", true, Comparator.comparing(Person::getName));
MFXTableColumn<Person> surnameColumn = new MFXTableColumn<>("Surname", true, Comparator.comparing(Person::getSurname));
MFXTableColumn<Person> ageColumn = new MFXTableColumn<>("Age", true, Comparator.comparing(Person::getAge));
@ -104,5 +95,5 @@ public class TableViewsController implements Initializable {
new EnumFilter<>("State", Device::getState, Device.State.class)
);
paginated.setItems(Model.devices);
}
}*/
}

View File

@ -7,7 +7,6 @@ module MaterialFX.Demo {
requires javafx.controls;
requires javafx.fxml;
requires javafx.graphics;
requires javafx.media;
requires fr.brouillard.oss.cssfx;
requires org.kordamp.ikonli.javafx;

View File

@ -20,19 +20,28 @@
@import 'Common.css';
@import 'MFXColors.css';
.mfx-list-view .virtual-flow .mfx-list-cell .data-label {
.vfx-scroll-pane {
-vfx-smooth-scroll: true;
-vfx-layout-mode: COMPACT;
}
.vfx-scroll-pane .viewport {
-fx-padding: 4px;
}
.mfx-list-view .mfx-list-cell .data-label {
-fx-font-family: 'Open Sans Regular';
}
.mfx-list-view .virtual-flow .mfx-list-cell:selected .data-label {
.mfx-list-view .mfx-list-cell:selected .data-label {
-fx-font-family: 'Open Sans SemiBold';
}
.mfx-check-list-view .virtual-flow .mfx-check-list-cell .data-label {
.mfx-check-list-view .mfx-check-list-cell .data-label {
-fx-font-family: 'Open Sans Regular';
}
.mfx-check-list-view .virtual-flow .mfx-check-list-cell:selected .data-label {
.mfx-check-list-view .mfx-check-list-cell:selected .data-label {
-fx-font-family: 'Open Sans SemiBold';
}
@ -41,43 +50,34 @@
-fx-background-radius: 10;
-fx-border-color: -mfx-purple;
-fx-border-radius: 10;
-fx-padding: 4;
}
#custList .virtual-flow {
-fx-background-color: transparent;
}
#custList:focused {
-fx-border-color: -mfx-purple;
}
#custList .virtual-flow .mfx-list-cell {
#custList .mfx-list-cell {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-background-radius: 10;
-fx-border-radius: 10;
}
#custList .virtual-flow .mfx-list-cell .data-label {
#custList .mfx-list-cell .label {
-fx-text-fill: white;
-fx-padding: 2px;
}
#custList .virtual-flow .mfx-list-cell .user-icon {
#custList .mfx-list-cell .user-icon {
-mfx-color: white;
}
#custList .virtual-flow .mfx-list-cell:hover,
#custList .virtual-flow .mfx-list-cell:selected {
#custList .mfx-list-cell:hover,
#custList .mfx-list-cell:selected {
-fx-background-color: rgba(255, 255, 255, 0.3);
}
#custList .virtual-flow .mfx-list-cell .mfx-ripple-generator {
-mfx-auto-clip: true;
}
#custList .virtual-flow .mfx-list-cell:selected .mfx-ripple-generator {
-mfx-paused: true;
#custList .mfx-list-cell .mfx-ripple-generator {
-mfx-ripple-color: rgba(255, 255, 255, 0.15);
}
.mfx-button {

View File

@ -18,7 +18,11 @@
~ along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.github.palexdev.materialfx.controls.*?>
<?import io.github.palexdev.materialfx.controls.legacy.MFXLegacyListView?>
<?import io.github.palexdev.materialfx.controls.MFXButton?>
<?import io.github.palexdev.materialfx.controls.MFXCheckListView?>
<?import io.github.palexdev.materialfx.controls.MFXListView?>
<?import io.github.palexdev.virtualizedfx.controls.VFXScrollPane?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
@ -54,31 +58,34 @@
<Label alignment="CENTER" maxWidth="1.7976931348623157E308" styleClass="header-label" text="Lists"
GridPane.columnSpan="3"/>
<Label styleClass="sub-header-label" text="Default" GridPane.rowIndex="1"/>
<MFXListView fx:id="list" prefWidth="170.0" GridPane.rowIndex="2" GridPane.rowSpan="3">
<VFXScrollPane prefWidth="170.0" GridPane.rowIndex="2" GridPane.rowSpan="3">
<content>
<MFXListView fx:id="list"/>
</content>
<GridPane.margin>
<Insets bottom="15.0"/>
</GridPane.margin>
</MFXListView>
</VFXScrollPane>
<Label styleClass="sub-header-label" text="Custom" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<MFXListView id="custList" fx:id="custList" prefWidth="170.0" GridPane.columnIndex="1" GridPane.rowIndex="2"
GridPane.rowSpan="3">
<VFXScrollPane prefWidth="170.0" GridPane.columnIndex="1" GridPane.rowIndex="2"
GridPane.rowSpan="3">
<content>
<MFXListView id="custList" fx:id="custList"/>
</content>
<GridPane.margin>
<Insets bottom="15.0"/>
</GridPane.margin>
</MFXListView>
</VFXScrollPane>
<Label styleClass="sub-header-label" text="Check List" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
<MFXCheckListView fx:id="checkList" prefWidth="170.0" GridPane.columnIndex="2" GridPane.rowIndex="2"
GridPane.rowSpan="3">
<VFXScrollPane prefWidth="170.0" GridPane.columnIndex="2" GridPane.rowIndex="2"
GridPane.rowSpan="3">
<content>
<MFXCheckListView fx:id="checkList"/>
</content>
<GridPane.margin>
<Insets bottom="15.0"/>
</GridPane.margin>
</MFXCheckListView>
<MFXButton minHeight="32.0" onAction="#changeColors" text="Change Scrollbar Colors" GridPane.columnIndex="1"
GridPane.rowIndex="5">
<GridPane.margin>
<Insets bottom="20.0"/>
</GridPane.margin>
</MFXButton>
</VFXScrollPane>
<StackPane prefHeight="150.0" prefWidth="200.0" styleClass="grid-background" GridPane.columnIndex="4"
GridPane.rowSpan="2147483647">
<GridPane.margin>
@ -86,12 +93,12 @@
</GridPane.margin>
</StackPane>
<Label styleClass="header-label" text="Legacy" GridPane.columnIndex="4"/>
<MFXListView fx:id="legacyList" prefWidth="170.0" GridPane.columnIndex="4" GridPane.rowIndex="2"
GridPane.rowSpan="3">
<MFXLegacyListView fx:id="legacyList" prefWidth="170.0" GridPane.columnIndex="4" GridPane.rowIndex="2"
GridPane.rowSpan="3">
<GridPane.margin>
<Insets bottom="15.0"/>
</GridPane.margin>
</MFXListView>
</MFXLegacyListView>
<MFXButton minHeight="32.0" onAction="#changeDepth" text="3D/2D" GridPane.columnIndex="1" GridPane.rowIndex="6">
<GridPane.margin>
<Insets bottom="20.0"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

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

View File

@ -1,59 +0,0 @@
import io.github.palexdev.materialfx.controls.MFXTableColumn;
import io.github.palexdev.materialfx.controls.MFXTableView;
import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
import io.github.palexdev.materialfx.demo.model.Model;
import io.github.palexdev.materialfx.demo.model.Person;
import io.github.palexdev.materialfx.filter.IntegerFilter;
import io.github.palexdev.materialfx.filter.StringFilter;
import io.github.palexdev.materialfx.theming.CSSFragment;
import io.github.palexdev.materialfx.theming.JavaFXThemes;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.UserAgentBuilder;
import io.github.palexdev.mfxcore.builders.InsetsBuilder;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Reproducer extends Application {
@Override
public void start(Stage stage) throws Exception {
StackPane pane = new StackPane();
pane.setPadding(InsetsBuilder.all(10));
MFXTableView<Person> table = new MFXTableView<>(Model.people);
MFXTableColumn<Person> name = new MFXTableColumn<>("Name");
name.setRowCellFactory(p -> new MFXTableRowCell<>(Person::getName));
MFXTableColumn<Person> surname = new MFXTableColumn<>("Surname");
surname.setRowCellFactory(p -> new MFXTableRowCell<>(Person::getSurname));
MFXTableColumn<Person> age = new MFXTableColumn<>("Age");
age.setRowCellFactory(p -> new MFXTableRowCell<>(Person::getAge));
table.getTableColumns().addAll(name, surname, age);
pane.getChildren().add(table);
table.setMinSize(400.0, 400.0);
table.getFilters().addAll(
new StringFilter<>("Name", Person::getName),
new StringFilter<>("Surname", Person::getSurname),
new IntegerFilter<>("Age", Person::getAge)
);
CSSFragment.Builder.build()
.addSelector(".mfx-filter-pane")
.addStyle("-mfx-main: blue")
.closeSelector()
.applyOn(table);
UserAgentBuilder.builder()
.themes(JavaFXThemes.MODENA)
.themes(MaterialFXStylesheets.forAssemble(false))
.setResolveAssets(true)
.setDeploy(true)
.build()
.setGlobal();
Scene scene = new Scene(pane, 600, 600);
stage.setScene(scene);
stage.show();
}
}

View File

@ -1,6 +1,8 @@
package collections;
import io.github.palexdev.materialfx.collections.TransformableList;
import java.util.Comparator;
import io.github.palexdev.materialfx.collections.RefineList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@ -9,36 +11,34 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testfx.framework.junit5.ApplicationExtension;
import java.util.Comparator;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(ApplicationExtension.class)
public class TransformableListTest {
public class RefineListTest {
private final ObservableList<String> source = FXCollections.observableArrayList("A", "B", "C", "D", "E");
@Test
public void sortTest1() {
TransformableList<String> transformed = new TransformableList<>(source);
transformed.setComparator(Comparator.reverseOrder(), true);
RefineList<String> transformed = new RefineList<>(source);
transformed.setComparator(Comparator.reverseOrder());
assertEquals(transformed.get(4), "A");
assertEquals(transformed.indexOf("E"), 0);
assertEquals(transformed.viewToSource(0), 4);
assertEquals(transformed.sourceToView(0), 4);
assertEquals("A", transformed.getView().get(4));
assertEquals(0, transformed.getView().indexOf("E"));
assertEquals(4, transformed.viewToSource(0));
assertEquals(4, transformed.sourceToView(0));
}
@Test
public void sortAndFilterTest1() {
TransformableList<String> transformed = new TransformableList<>(source);
transformed.setComparator(Comparator.reverseOrder(), true);
RefineList<String> transformed = new RefineList<>(source);
transformed.setComparator(Comparator.reverseOrder());
transformed.setPredicate(s -> s.equals("A") || s.equals("C") || s.equals("E"));
assertThrows(IndexOutOfBoundsException.class, () -> transformed.get(4));
assertEquals(transformed.get(1), "C");
assertEquals(transformed.indexOf("E"), 0);
assertEquals(transformed.viewToSource(1), 2);
assertEquals(transformed.sourceToView(1), -1);
assertThrows(IndexOutOfBoundsException.class, () -> transformed.getView().get(4));
assertEquals("C", transformed.getView().get(1));
assertEquals(0, transformed.getView().indexOf("E"));
assertEquals(2, transformed.viewToSource(1));
assertTrue(transformed.sourceToView(1) < 0);
}
@Test
@ -46,10 +46,10 @@ public class TransformableListTest {
SortedList<String> sorted = new SortedList<>(source);
sorted.setComparator(Comparator.reverseOrder());
assertEquals(sorted.get(4), "A");
assertEquals(sorted.indexOf("E"), 0);
assertEquals(sorted.getSourceIndex(0), 4);
assertEquals(sorted.getViewIndex(0), 4);
assertEquals("A", sorted.get(4));
assertEquals(0, sorted.indexOf("E"));
assertEquals(4, sorted.getSourceIndex(0));
assertEquals(4, sorted.getViewIndex(0));
}
@Test
@ -61,9 +61,9 @@ public class TransformableListTest {
filtered.setPredicate(s -> s.equals("A") || s.equals("C") || s.equals("E"));
assertThrows(IndexOutOfBoundsException.class, () -> filtered.get(4));
assertEquals(filtered.get(1), "C");
assertEquals(filtered.indexOf("E"), 0);
assertEquals(filtered.getSourceIndex(1), 2);
assertEquals("C", filtered.get(1));
assertEquals(0, filtered.indexOf("E"));
assertEquals(2, filtered.getSourceIndex(1));
assertTrue(filtered.getViewIndex(1) < 0);
}
}

View File

@ -1,5 +1,7 @@
package combobox;
import java.util.stream.IntStream;
import io.github.palexdev.materialfx.controls.MFXFilterComboBox;
import javafx.application.Application;
import javafx.collections.FXCollections;
@ -11,8 +13,6 @@ import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import org.scenicview.ScenicView;
import java.util.stream.IntStream;
public class ComboBoxTest extends Application {
@Override
@ -34,7 +34,7 @@ public class ComboBoxTest extends Application {
MFXFilterComboBox<String> c3 = new MFXFilterComboBox<>(s3);
c2.valueProperty().bind(c1.valueProperty());
c3.getSelectionModel().bindIndexBidirectional(c1.getSelectionModel());
c3.selection().bindBidirectional(c1.selection());
hBox.getChildren().addAll(c1, c2, c3);
Scene scene = new Scene(hBox, 800, 800);

View File

@ -18,39 +18,41 @@
package selection;
import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import io.github.palexdev.materialfx.selection.SelectionModel;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.utils.FXCollectors;
import javafx.collections.ObservableList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MultipleSelectionModelTests {
private final ObservableList<String> strings = IntStream.rangeClosed(0, 30)
.mapToObj(i -> "String " + i)
.collect(FXCollectors.toList());
private final MultipleSelectionModel<String> selectionModel = new MultipleSelectionModel<String>(strings);
private final ISelectionModel<String> selectionModel = new SelectionModel<>(strings);
@BeforeEach
public void setUp() {
selectionModel.setAllowsMultipleSelection(true);
selectionModel.clearSelection();
}
@Test
public void testOrder() {
Integer[] toSelect = {0, 6, 3, 9, 6};
selectionModel.selectIndexes(List.of(toSelect));
selectionModel.selectIndexes(toSelect);
assertEquals(4, selectionModel.getSelection().size());
assertEquals(4, selectionModel.selection().size());
// Indexes
int i = 0;
Set<Integer> indexes = selectionModel.getSelection().keySet();
Set<Integer> indexes = selectionModel.selection().keySet();
for (int val : indexes) {
assertEquals(toSelect[i], val);
i++;
@ -64,7 +66,7 @@ public class MultipleSelectionModelTests {
"String 3",
"String 9"
};
List<String> values = selectionModel.getSelectedValues();
List<String> values = selectionModel.getSelectedItems();
for (String value : values) {
assertEquals(expected[i], value);
i++;

View File

@ -3,8 +3,10 @@ package selection;
import io.github.palexdev.materialfx.bindings.BiBindingManager;
import io.github.palexdev.materialfx.bindings.BindingManager;
import io.github.palexdev.materialfx.demo.model.Person;
import io.github.palexdev.materialfx.selection.SingleSelectionModel;
import javafx.beans.property.*;
import io.github.palexdev.materialfx.selection.SelectionModel;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@ -12,8 +14,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testfx.framework.junit5.ApplicationExtension;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(ApplicationExtension.class)
@ -43,8 +43,8 @@ public class SingleSelectionModelTests {
new Person("Phil"))
);
private final SingleSelectionModel<Person> model1 = new SingleSelectionModel<>(people1);
private final SingleSelectionModel<Person> model2 = new SingleSelectionModel<>(people2);
private final ISelectionModel<Person> model1 = new SelectionModel<>(people1);
private final ISelectionModel<Person> model2 = new SelectionModel<>(people2);
@AfterEach
public void checkManagers() {
@ -61,26 +61,26 @@ public class SingleSelectionModelTests {
@Test
public void testIndexSelection1() {
model1.selectIndex(0);
assertEquals(0, model1.getSelectedIndex());
assertEquals(0, model1.getSelectedEntry().getKey());
assertEquals("Jack", model1.getSelectedItem().getName());
}
@Test
public void testIndexSelection2() {
assertThrows(IndexOutOfBoundsException.class, () -> model1.selectIndex(10));
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedEntry());
}
@Test
public void testIndexSelection3() {
assertThrows(IndexOutOfBoundsException.class, () -> model1.selectIndex(-1));
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedEntry());
}
@Test
public void testItemSelection1() {
model1.selectItem(new Person("Mark"));
assertEquals(1, model1.getSelectedIndex());
assertEquals(1, model1.getSelectedEntry().getKey());
assertEquals("Mark", model1.getSelectedItem().getName());
}
@ -90,441 +90,10 @@ public class SingleSelectionModelTests {
}
@Test
public void testBindIndexProperty1() {
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndex(property, i -> model1.getUnmodifiableItems().get(i));
property.set(2);
assertEquals(2, model1.getSelectedIndex());
assertEquals("Linda", model1.getSelectedItem().getName());
}
@Test
public void testBindIndexProperty2() {
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndex(property, i -> model1.getUnmodifiableItems().get(i));
assertThrows(IllegalStateException.class, () -> model1.selectIndex(2));
}
@Test
public void testBindIndexProperty3() {
AtomicReference<Throwable> ex = new AtomicReference<>();
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndex(property, i -> {
try {
return model1.getUnmodifiableItems().get(i);
} catch (Exception exception) {
ex.set(exception);
}
return null;
});
property.set(6);
assertNotNull(ex.get());
}
@Test
public void testBindIndexProperty4() {
model1.selectIndex(0);
AtomicReference<Throwable> ex = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> ex.set(e));
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndex(property, i -> model1.getUnmodifiableItems().get(i));
property.set(-1);
assertNotNull(ex.get());
assertEquals(0, model1.getSelectedIndex());
assertEquals("Jack", model1.getSelectedItem().getName());
}
@Test
public void testBindIndex1() {
model1.bindIndex(model2);
model2.selectIndex(2);
assertEquals(2, model1.getSelectedIndex());
assertEquals(2, model2.getSelectedIndex());
assertEquals("Linda", model1.getSelectedItem().getName());
assertEquals("Alex", model2.getSelectedItem().getName());
}
@Test
public void testBindIndex2() {
model1.bindIndex(model2);
assertThrows(IllegalStateException.class, () -> model1.selectIndex(2));
}
@Test
public void testBindIndex3() {
AtomicReference<Throwable> ex = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> ex.set(e));
model1.bindIndex(model2);
model2.selectIndex(8);
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
assertEquals(8, model2.getSelectedIndex());
assertEquals("Alex", model2.getSelectedItem().getName());
assertNotNull(ex.get());
}
@Test
public void testBindIndex4() {
model1.bindIndex(model2);
model2.selectIndex(0);
assertThrows(IndexOutOfBoundsException.class, () -> model2.selectIndex(-1));
assertEquals(0, model1.getSelectedIndex());
assertEquals("Jack", model1.getSelectedItem().getName());
assertEquals(0, model2.getSelectedIndex());
assertEquals("Mark", model2.getSelectedItem().getName());
}
@Test
public void testBindIndexBidirectionalProperty1() {
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndexBidirectional(property, i -> model1.getUnmodifiableItems().get(i), (clearing, i, other) -> other.setValue(i));
property.set(1);
assertEquals(1, model1.getSelectedIndex());
assertEquals("Mark", model1.getSelectedItem().getName());
model1.selectIndex(5);
assertEquals("Sam", model1.getSelectedItem().getName());
assertEquals(5, property.get());
model1.selectItem(new Person("Lily"));
assertEquals(4, model1.getSelectedIndex());
assertEquals(4, property.get());
}
@Test
public void testBindIndexBidirectionalProperty2() {
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndexBidirectional(property, i -> model1.getUnmodifiableItems().get(i), (clearing, i, other) -> other.setValue(i));
property.set(1);
assertEquals(1, model1.getSelectedIndex());
assertEquals("Mark", model1.getSelectedItem().getName());
property.set(-1);
assertEquals(-1, property.get());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
}
@Test
public void testBindIndexBidirectionalProperty3() {
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndexBidirectional(property, i -> model1.getUnmodifiableItems().get(i), (clearing, i, other) -> other.setValue(i));
assertThrows(IndexOutOfBoundsException.class, () -> model1.selectIndex(8));
assertEquals(0, property.get());
assertEquals(0, model1.getSelectedIndex());
assertEquals("Jack", model1.getSelectedItem().getName());
}
@Test
public void testBindIndexBidirectional1() {
model1.bindIndexBidirectional(model2);
model2.selectIndex(1);
assertEquals("Mark", model1.getSelectedItem().getName());
assertEquals("Roberto", model2.getSelectedItem().getName());
model1.selectIndex(3);
assertEquals("Marty", model1.getSelectedItem().getName());
assertEquals("Samantha", model2.getSelectedItem().getName());
}
@Test
public void testBindIndexBidirectional2() {
AtomicReference<Throwable> reference = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> reference.set(e));
model1.bindIndexBidirectional(model2);
model2.selectIndex(8);
assertNotNull(reference.get());
assertEquals("Alex", model2.getSelectedItem().getName());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
model1.selectIndex(0);
assertEquals("Jack", model1.getSelectedItem().getName());
assertEquals("Mark", model2.getSelectedItem().getName());
}
@Test
public void testBindIndexBidirectional3() {
AtomicReference<Throwable> reference = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> reference.set(e));
model2.bindIndexBidirectional(model1);
model1.selectIndex(0);
assertEquals("Jack", model1.getSelectedItem().getName());
assertEquals("Mark", model2.getSelectedItem().getName());
model2.selectIndex(8);
assertNotNull(reference.get());
assertEquals("Alex", model2.getSelectedItem().getName());
assertEquals(0, model1.getSelectedIndex());
assertEquals("Jack", model1.getSelectedItem().getName());
}
@Test
public void testBindItemProperty1() {
ObjectProperty<Person> property = new SimpleObjectProperty<>();
model1.bindItem(property, Person -> model1.getUnmodifiableItems().indexOf(Person));
property.set(new Person("Mark"));
assertEquals(1, model1.getSelectedIndex());
assertEquals("Mark", model1.getSelectedItem().getName());
}
@Test
public void testBindItemProperty2() {
ObjectProperty<Person> property = new SimpleObjectProperty<>();
model2.bindItem(property, Person -> model2.getUnmodifiableItems().indexOf(Person));
property.set(new Person("Alex"));
assertEquals(2, model2.getSelectedIndex());
assertEquals("Alex", model2.getSelectedItem().getName());
}
@Test
public void testBindItemProperty3() {
AtomicReference<Throwable> reference = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> reference.set(e));
ObjectProperty<Person> property = new SimpleObjectProperty<>();
model2.bindItem(property, Person -> model2.getUnmodifiableItems().indexOf(Person));
property.set(new Person("Unexisting"));
assertEquals("Unexisting", property.get().getName());
assertEquals(-1, model2.getSelectedIndex());
assertNull(model2.getSelectedItem());
assertNotNull(reference.get());
}
@Test
public void testBindItem1() {
model1.bindItem(model2);
model2.selectItem(new Person("Mark"));
assertEquals(1, model1.getSelectedIndex());
assertEquals(0, model2.getSelectedIndex());
}
@Test
public void testBindItem2() {
model1.bindItem(model2);
assertThrows(IllegalStateException.class, () -> model1.selectItem(new Person("Mark")));
}
@Test
public void testBindItem3() {
AtomicReference<Throwable> reference = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> reference.set(e));
model1.bindItem(model2);
model2.selectItem(new Person("Alex"));
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
assertEquals(2, model2.getSelectedIndex());
assertEquals("Alex", model2.getSelectedItem().getName());
assertNotNull(reference.get());
}
@Test
public void testBindItemPropertyBidirectional1() {
ObjectProperty<Person> property = new SimpleObjectProperty<>();
model1.bindItemBidirectional(property, Person -> model1.getUnmodifiableItems().indexOf(Person), (clearing, Person, other) -> other.setValue(Person));
property.set(new Person("Linda"));
assertEquals(2, model1.getSelectedIndex());
assertEquals("Linda", model1.getSelectedItem().getName());
}
@Test
public void testBindItemPropertyBidirectional2() {
AtomicReference<Throwable> reference = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> reference.set(e));
ObjectProperty<Person> property = new SimpleObjectProperty<>();
model1.bindItemBidirectional(property, Person -> model1.getUnmodifiableItems().indexOf(Person), (clearing, Person, other) -> other.setValue(Person));
property.set(new Person("Unexisting"));
assertNotNull(reference.get());
}
@Test
public void testBindItemPropertyBidirectional3() {
ObjectProperty<Person> property = new SimpleObjectProperty<>();
model1.bindItemBidirectional(property, Person -> model1.getUnmodifiableItems().indexOf(Person), (clearing, Person, other) -> other.setValue(Person));
property.set(new Person("Mark"));
assertEquals(1, model1.getSelectedIndex());
model1.selectItem(new Person("Linda"));
assertEquals(2, model1.getSelectedIndex());
assertEquals("Linda", model1.getSelectedItem().getName());
assertEquals("Linda", property.get().getName());
}
@Test
public void testBindItemBidirectional1() {
model1.bindItemBidirectional(model2);
model2.selectItem(new Person("Mark"));
assertEquals(1, model1.getSelectedIndex());
assertEquals(0, model2.getSelectedIndex());
model1.selectItem(new Person("Sam"));
assertEquals(5, model1.getSelectedIndex());
assertEquals(6, model2.getSelectedIndex());
}
@Test
public void testBindItemBidirectional2() {
AtomicReference<Throwable> reference = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> reference.set(e));
model1.bindItemBidirectional(model2);
model1.selectItem(new Person("Sam"));
assertEquals(5, model1.getSelectedIndex());
assertEquals(6, model2.getSelectedIndex());
model2.selectItem(new Person("Alex"));
assertNotNull(reference.get());
assertEquals(5, model1.getSelectedIndex());
assertEquals("Sam", model1.getSelectedItem().getName());
assertEquals(2, model2.getSelectedIndex());
assertEquals("Alex", model2.getSelectedItem().getName());
}
@Test
public void testBindItemBidirectional3() {
AtomicReference<Throwable> reference = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> reference.set(e));
model1.bindItemBidirectional(model2);
model2.selectItem(new Person("Alex"));
assertNotNull(reference.get());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
assertEquals(2, model2.getSelectedIndex());
assertEquals("Alex", model2.getSelectedItem().getName());
model1.selectItem(new Person("Sam"));
assertEquals(5, model1.getSelectedIndex());
assertEquals(6, model2.getSelectedIndex());
}
@Test
public void testClearSelection1() {
public void testClearSelection() {
model1.selectIndex(0);
model1.clearSelection();
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
}
@Test
public void testClearSelection2() {
model1.bindIndexBidirectional(model2);
model1.selectIndex(0);
model1.clearSelection();
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
assertEquals(-1, model2.getSelectedIndex());
assertNull(model2.getSelectedItem());
}
@Test
public void testClearSelection3() {
model1.bindIndex(model2);
model2.selectIndex(0);
assertThrows(IllegalStateException.class, model1::clearSelection);
}
@Test
public void testClearSelection4() {
model1.bindItemBidirectional(model2);
model2.selectItem(new Person("Mark"));
assertEquals(1, model1.getSelectedIndex());
assertEquals("Mark", model1.getSelectedItem().getName());
assertEquals(0, model2.getSelectedIndex());
assertEquals("Mark", model2.getSelectedItem().getName());
model1.clearSelection();
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
assertEquals(-1, model2.getSelectedIndex());
assertNull(model2.getSelectedItem());
}
@Test
public void testClearSelection5() {
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndexBidirectional(property, i -> model1.getUnmodifiableItems().get(i), (clearing, i, otherProperty) -> property.set(i));
model1.selectIndex(1);
assertEquals(1, property.get());
assertEquals("Mark", model1.getSelectedItem().getName());
model1.clearSelection();
assertEquals(-1, property.get());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
model1.selectIndex(1);
property.set(-1);
assertEquals(-1, property.get());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
}
@Test
public void testClearSelection6() {
ObjectProperty<Person> property = new SimpleObjectProperty<>();
model1.bindItemBidirectional(property, v -> model1.getUnmodifiableItems().indexOf(v), (clearing, v, otherProperty) -> property.set(v));
model1.selectIndex(1);
assertEquals("Mark", property.get().getName());
assertEquals(1, model1.getSelectedIndex());
model1.clearSelection();
assertNull(property.get());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
model1.selectIndex(1);
property.set(null);
assertNull(property.get());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedItem());
}
@Test
public void testClearSelection7() {
IntegerProperty property = new SimpleIntegerProperty();
model1.bindIndexBidirectional(
property,
i -> model1.getUnmodifiableItems().get(i),
(clearing, i, otherProperty) -> property.set(i)
);
model1.selectIndex(1);
assertEquals(1, property.get());
assertEquals("Mark", model1.getSelectedItem().getName());
property.set(-1);
assertEquals(-1, property.get());
assertEquals(-1, model1.getSelectedIndex());
assertNull(model1.getSelectedEntry());
assertNull(model1.getSelectedItem());
}
}

View File

@ -1,27 +1,25 @@
#--------------------------------------#
# Versions #
#--------------------------------------#
jdk=11
testJdk=17
materialfx=11.17.0
jdk=21
materialfx=21.18.0-alpha
# Plugins
jfxPlugin=0.0.13
jlink=2.26.0
bnd=6.2.0
mavenPublish=0.19.0
shadowJarPlugin=7.1.2
jfxPlugin=0.1.0
jlink=3.1.1
mavenPublish=0.28.0
shadowJarPlugin=9.0.0-beta4
# Dependencies
jfx=21
mfxcore=11.8.0
mfxresources=11.9.1
vfx=11.9.6
jfx=23.0.1
mfxcore=11.11.4
mfxresources=11.12.2
vfx=21.7.9
cssfx=11+
ikonli=12.3.1
# Test Dependencies
junit=5.9.1
junit=5.11.4
junitSuite=1.8.1
testfx=4.0.16-alpha
testfx=4.0.17
scenicView=17.0.2

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,10 +1,9 @@
import org.apache.tools.ant.taskdefs.condition.Os
import com.vanniktech.maven.publish.SonatypeHost
plugins {
id 'java-library'
id 'biz.aQute.bnd.builder' version "$bnd"
id 'com.vanniktech.maven.publish' version "$mavenPublish"
id 'com.github.johnrengelman.shadow' version "$shadowJarPlugin"
id 'com.gradleup.shadow' version "$shadowJarPlugin"
}
// Cross-platform scripts
@ -19,54 +18,31 @@ repositories {
}
}
compileJava {
java {
sourceCompatibility = "$jdk"
targetCompatibility = "$jdk"
withJavadocJar()
withSourcesJar()
tasks.withType(Jar).each { it.archiveBaseName.set("materialfx") }
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junit"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit"
api "io.github.palexdev:mfxcore:$mfxcore"
api "io.github.palexdev:mfxresources:$mfxresources"
api "io.github.palexdev:virtualizedfx:$vfx"
testImplementation platform("org.junit:junit-bom:$junit")
testImplementation 'org.junit.jupiter:junit-jupiter'
}
javafx {
configuration = 'compileOnly'
}
javadoc {
excludes = ['**/*.html', 'META-INF/**']
options.use = true
options.splitIndex = true
options.encoding = 'UTF-8'
options.author = true
options.version = true
options.windowTitle = "$project.name $project.version API"
options.docTitle = "$project.name $project.version API"
options.links = ['https://docs.oracle.com/en/java/javase/11/docs/api',
'https://openjfx.io/javadoc/17']
}
tasks.register('javadocJar', Jar) {
dependsOn javadoc
archiveClassifier.set('javadoc')
from javadoc.destinationDir
}
tasks.register('sourcesJarBuild', Jar) {
dependsOn classes
archiveClassifier.set('sources')
from sourceSets.main.allSource
modules = ['javafx.controls', 'javafx.fxml']
configurations = ["compileOnly", "testImplementation"]
}
artifacts {
archives javadocJar
archives sourcesJarBuild
archives jar
archives shadowJar
}
jar {
@ -89,27 +65,16 @@ shadowJar {
}
}
tasks.register('copyJar', Copy) {
from jar
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
into System.getenv("APPDATA") + '/Scene Builder/Library'
} else if (Os.isFamily(Os.FAMILY_MAC)) {
into System.getProperty("user.home") + '/Library/Application Support' + '/Scene Builder/Library'
} else if (Os.isFamily(Os.FAMILY_UNIX)) {
into System.getProperty("user.home") + '/.scenebuilder/Library'
mavenPublishing {
publishToMavenCentral(SonatypeHost.S01)
signAllPublications()
}
configurations {
// Remove vanniktech non-sense
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(plainJavadocJar)) {
plainJavadocJar.enabled = false
}
}
}
tasks.register('removeBnd', Delete) {
delete fileTree(project.buildDir) {
include '**/*.bnd'
}
}
build {
dependsOn shadowJar, copyJar, removeBnd
}
mavenPublish {
sonatypeHost = "S01"
}

View File

@ -1,6 +1,6 @@
GROUP=io.github.palexdev
POM_ARTIFACT_ID=materialfx
VERSION_NAME=11.17.0
VERSION_NAME=21.18.0-alpha
POM_NAME=materialfx
POM_DESCRIPTION=Material Desgin components for JavaFX

View File

@ -1,85 +0,0 @@
/*
* 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.builders.base;
import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
import io.github.palexdev.materialfx.effects.DepthLevel;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.collections.ObservableList;
import javafx.scene.paint.Paint;
import javafx.util.Duration;
import javafx.util.StringConverter;
public class BaseListViewBuilder<T, C extends Cell<T>, L extends AbstractMFXListView<T, C>> extends ControlBuilder<L> {
//================================================================================
// Constructors
//================================================================================
public BaseListViewBuilder(L control) {
super(control);
}
public static <T, C extends Cell<T>> BaseListViewBuilder<T, C, AbstractMFXListView<T, C>> baseList(AbstractMFXListView<T, C> list) {
return new BaseListViewBuilder<>(list);
}
//================================================================================
// Delegate Methods
//================================================================================
public BaseListViewBuilder<T, C, L> setTrackColor(Paint trackColor) {
node.setTrackColor(trackColor);
return this;
}
public BaseListViewBuilder<T, C, L> setThumbColor(Paint thumbColor) {
node.setThumbColor(thumbColor);
return this;
}
public BaseListViewBuilder<T, C, L> setThumbHoverColor(Paint thumbHoverColor) {
node.setThumbHoverColor(thumbHoverColor);
return this;
}
public BaseListViewBuilder<T, C, L> setHideAfter(Duration hideAfter) {
node.setHideAfter(hideAfter);
return this;
}
public BaseListViewBuilder<T, C, L> setItems(ObservableList<T> items) {
node.setItems(items);
return this;
}
public BaseListViewBuilder<T, C, L> setConverter(StringConverter<T> converter) {
node.setConverter(converter);
return this;
}
public BaseListViewBuilder<T, C, L> setHideScrollBars(boolean hideScrollBars) {
node.setHideScrollBars(hideScrollBars);
return this;
}
public BaseListViewBuilder<T, C, L> setDepthLevel(DepthLevel depthLevel) {
node.setDepthLevel(depthLevel);
return this;
}
}

View File

@ -1,116 +0,0 @@
/*
* 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.builders.control;
import io.github.palexdev.materialfx.builders.base.BaseListViewBuilder;
import io.github.palexdev.materialfx.controls.MFXCheckListView;
import io.github.palexdev.materialfx.controls.cell.MFXCheckListCell;
import java.util.function.Function;
public class CheckListBuilder<T> extends BaseListViewBuilder<T, MFXCheckListCell<T>, MFXCheckListView<T>> {
//================================================================================
// Constructors
//================================================================================
public CheckListBuilder() {
this(new MFXCheckListView<>());
}
public CheckListBuilder(MFXCheckListView<T> listView) {
super(listView);
}
public static <T> CheckListBuilder<T> checkList() {
return new CheckListBuilder<>();
}
public static <T> CheckListBuilder<T> checkList(MFXCheckListView<T> checkListView) {
return new CheckListBuilder<>(checkListView);
}
//================================================================================
// Delegate Methods
//================================================================================
public CheckListBuilder<T> scrollBy(double pixels) {
node.scrollBy(pixels);
return this;
}
public CheckListBuilder<T> scrollTo(int index) {
node.scrollTo(index);
return this;
}
public CheckListBuilder<T> scrollToFirst() {
node.scrollToFirst();
return this;
}
public CheckListBuilder<T> scrollToLast() {
node.scrollToLast();
return this;
}
public CheckListBuilder<T> scrollToPixel(double pixel) {
node.scrollToPixel(pixel);
return this;
}
public CheckListBuilder<T> setHSpeed(double unit, double block) {
node.setHSpeed(unit, block);
return this;
}
public CheckListBuilder<T> setVSpeed(double unit, double block) {
node.setVSpeed(unit, block);
return this;
}
public CheckListBuilder<T> setCellFactory(Function<T, MFXCheckListCell<T>> cellFactory) {
node.setCellFactory(cellFactory);
return this;
}
public CheckListBuilder<T> enableSmoothScrolling(double speed) {
node.features().enableSmoothScrolling(speed);
return this;
}
public CheckListBuilder<T> enableSmoothScrolling(double speed, double trackPadAdjustment) {
node.features().enableSmoothScrolling(speed, trackPadAdjustment);
return this;
}
public CheckListBuilder<T> enableSmoothScrolling(double speed, double trackPadAdjustment, double scrollThreshold) {
node.features().enableSmoothScrolling(speed, trackPadAdjustment, scrollThreshold);
return this;
}
public CheckListBuilder<T> enableBounceEffect() {
node.features().enableBounceEffect();
return this;
}
public CheckListBuilder<T> enableBounceEffect(double strength, double maxOverscroll) {
node.features().enableBounceEffect(strength, maxOverscroll);
return this;
}
}

View File

@ -18,9 +18,13 @@
package io.github.palexdev.materialfx.builders.control;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import io.github.palexdev.materialfx.beans.Alignment;
import io.github.palexdev.materialfx.controls.MFXComboBox;
import io.github.palexdev.virtualizedfx.cell.Cell;
import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import javafx.animation.Animation;
import javafx.collections.ObservableList;
import javafx.event.Event;
@ -28,10 +32,6 @@ import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.util.StringConverter;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public class ComboBuilder<T, C extends MFXComboBox<T>> extends TextFieldBuilder<C> {
//================================================================================
@ -143,7 +143,7 @@ public class ComboBuilder<T, C extends MFXComboBox<T>> extends TextFieldBuilder<
return this;
}
public ComboBuilder<T, C> setCellFactory(Function<T, Cell<T>> cellFactory) {
public ComboBuilder<T, C> setCellFactory(Function<T, VFXCell<T>> cellFactory) {
node.setCellFactory(cellFactory);
return this;
}

View File

@ -18,12 +18,12 @@
package io.github.palexdev.materialfx.builders.control;
import io.github.palexdev.materialfx.controls.MFXFilterComboBox;
import java.util.Comparator;
import java.util.function.Function;
import java.util.function.Predicate;
import io.github.palexdev.materialfx.controls.MFXFilterComboBox;
public class FilterComboBuilder<T> extends ComboBuilder<T, MFXFilterComboBox<T>> {
//================================================================================
@ -73,9 +73,4 @@ public class FilterComboBuilder<T> extends ComboBuilder<T, MFXFilterComboBox<T>>
node.getFilterList().setComparator(comparator);
return this;
}
public FilterComboBuilder<T> setComparator(Comparator<T> comparator, boolean isReverse) {
node.getFilterList().setComparator(comparator, isReverse);
return this;
}
}

View File

@ -1,116 +0,0 @@
/*
* 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.builders.control;
import io.github.palexdev.materialfx.builders.base.BaseListViewBuilder;
import io.github.palexdev.materialfx.controls.MFXListView;
import io.github.palexdev.materialfx.controls.cell.MFXListCell;
import java.util.function.Function;
public class ListBuilder<T> extends BaseListViewBuilder<T, MFXListCell<T>, MFXListView<T>> {
//================================================================================
// Constructors
//================================================================================
public ListBuilder() {
this(new MFXListView<>());
}
public ListBuilder(MFXListView<T> listView) {
super(listView);
}
public static <T> ListBuilder<T> list() {
return new ListBuilder<>();
}
public static <T> ListBuilder<T> list(MFXListView<T> listView) {
return new ListBuilder<>(listView);
}
//================================================================================
// Delegate Methods
//================================================================================
public ListBuilder<T> scrollBy(double pixels) {
node.scrollBy(pixels);
return this;
}
public ListBuilder<T> scrollTo(int index) {
node.scrollTo(index);
return this;
}
public ListBuilder<T> scrollToFirst() {
node.scrollToFirst();
return this;
}
public ListBuilder<T> scrollToLast() {
node.scrollToLast();
return this;
}
public ListBuilder<T> scrollToPixel(double pixel) {
node.scrollToPixel(pixel);
return this;
}
public ListBuilder<T> setHSpeed(double unit, double block) {
node.setHSpeed(unit, block);
return this;
}
public ListBuilder<T> setVSpeed(double unit, double block) {
node.setVSpeed(unit, block);
return this;
}
public ListBuilder<T> setCellFactory(Function<T, MFXListCell<T>> cellFactory) {
node.setCellFactory(cellFactory);
return this;
}
public ListBuilder<T> enableSmoothScrolling(double speed) {
node.features().enableSmoothScrolling(speed);
return this;
}
public ListBuilder<T> enableSmoothScrolling(double speed, double trackPadAdjustment) {
node.features().enableSmoothScrolling(speed, trackPadAdjustment);
return this;
}
public ListBuilder<T> enableSmoothScrolling(double speed, double trackPadAdjustment, double scrollThreshold) {
node.features().enableSmoothScrolling(speed, trackPadAdjustment, scrollThreshold);
return this;
}
public ListBuilder<T> enableBounceEffect() {
node.features().enableBounceEffect();
return this;
}
public ListBuilder<T> enableBounceEffect(double strength, double maxOverscroll) {
node.features().enableBounceEffect(strength, maxOverscroll);
return this;
}
}

View File

@ -18,6 +18,9 @@
package io.github.palexdev.materialfx.builders.control;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import io.github.palexdev.materialfx.builders.base.ControlBuilder;
import io.github.palexdev.materialfx.controls.MFXNotificationCenter;
import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell;
@ -27,9 +30,6 @@ import io.github.palexdev.materialfx.notifications.base.INotification;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
public class NotificationCenterBuilder extends ControlBuilder<MFXNotificationCenter> {
//================================================================================
@ -190,7 +190,8 @@ public class NotificationCenterBuilder extends ControlBuilder<MFXNotificationCen
return this;
}
public NotificationCenterBuilder setHSpeed(double unit, double block) {
// TODO fix these
/* public NotificationCenterBuilder setHSpeed(double unit, double block) {
node.setHSpeed(unit, block);
return this;
}
@ -198,14 +199,15 @@ public class NotificationCenterBuilder extends ControlBuilder<MFXNotificationCen
public NotificationCenterBuilder setVSpeed(double unit, double block) {
node.setVSpeed(unit, block);
return this;
}
}*/
public NotificationCenterBuilder setCellFactory(Function<INotification, MFXNotificationCell> cellFactory) {
node.setCellFactory(cellFactory);
return this;
}
public NotificationCenterBuilder enableSmoothScrolling(double speed) {
// TODO and these
/* public NotificationCenterBuilder enableSmoothScrolling(double speed) {
node.features().enableSmoothScrolling(speed);
return this;
}
@ -228,5 +230,5 @@ public class NotificationCenterBuilder extends ControlBuilder<MFXNotificationCen
public NotificationCenterBuilder enableBounceEffect(double strength, double maxOverscroll) {
node.features().enableBounceEffect(strength, maxOverscroll);
return this;
}
}*/
}

View File

@ -1,67 +0,0 @@
/*
* 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.builders.control;
import io.github.palexdev.materialfx.controls.MFXPaginatedTableView;
public class PaginatedTableBuilder<T> extends TableBuilder<T, MFXPaginatedTableView<T>> {
//================================================================================
// Constructors
//================================================================================
public PaginatedTableBuilder() {
this(new MFXPaginatedTableView<>());
}
public PaginatedTableBuilder(MFXPaginatedTableView<T> tableView) {
super(tableView);
}
public static <T> PaginatedTableBuilder<T> paginatedTable() {
return new PaginatedTableBuilder<>();
}
public static <T> PaginatedTableBuilder<T> paginatedTable(MFXPaginatedTableView<T> tableView) {
return new PaginatedTableBuilder<>(tableView);
}
//================================================================================
// Delegate Methods
//================================================================================
public PaginatedTableBuilder<T> goToPage(int index) {
node.goToPage(index);
return this;
}
public PaginatedTableBuilder<T> setCurrentPage(int currentPage) {
node.setCurrentPage(currentPage);
return this;
}
public PaginatedTableBuilder<T> setPagesToShow(int pagesToShow) {
node.setPagesToShow(pagesToShow);
return this;
}
public PaginatedTableBuilder<T> setRowsPerPage(int rowsPerPage) {
node.setRowsPerPage(rowsPerPage);
return this;
}
}

View File

@ -18,15 +18,15 @@
package io.github.palexdev.materialfx.builders.control;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import io.github.palexdev.materialfx.builders.base.ControlBuilder;
import io.github.palexdev.materialfx.controls.MFXPagination;
import io.github.palexdev.materialfx.controls.cell.MFXPage;
import javafx.geometry.Orientation;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
public class PaginationBuilder extends ControlBuilder<MFXPagination> {
//================================================================================

View File

@ -1,176 +0,0 @@
/*
* 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.builders.control;
import io.github.palexdev.materialfx.builders.base.ControlBuilder;
import io.github.palexdev.materialfx.controls.MFXTableColumn;
import io.github.palexdev.materialfx.controls.MFXTableRow;
import io.github.palexdev.materialfx.controls.MFXTableView;
import io.github.palexdev.materialfx.filter.base.AbstractFilter;
import javafx.collections.ObservableList;
import java.util.Comparator;
import java.util.function.Function;
import java.util.function.Predicate;
public class TableBuilder<T, V extends MFXTableView<T>> extends ControlBuilder<V> {
//================================================================================
// Constructors
//================================================================================
@SuppressWarnings("unchecked")
public TableBuilder() {
this((V) new MFXTableView<T>());
}
public TableBuilder(V tableView) {
super(tableView);
}
public static <T> TableBuilder<T, MFXTableView<T>> table() {
return new TableBuilder<>();
}
public static <T> TableBuilder<T, MFXTableView<T>> table(MFXTableView<T> tableView) {
return new TableBuilder<>(tableView);
}
//================================================================================
// Delegate Methods
//================================================================================
public TableBuilder<T, V> autosizeColumnsOnInitialization() {
node.autosizeColumnsOnInitialization();
return this;
}
public TableBuilder<T, V> scrollBy(double pixels) {
node.scrollBy(pixels);
return this;
}
public TableBuilder<T, V> scrollTo(int index) {
node.scrollTo(index);
return this;
}
public TableBuilder<T, V> scrollToFirst() {
node.scrollToFirst();
return this;
}
public TableBuilder<T, V> scrollToLast() {
node.scrollToLast();
return this;
}
public TableBuilder<T, V> scrollToPixel(double pixel) {
node.scrollToPixel(pixel);
return this;
}
public TableBuilder<T, V> setHSpeed(double unit, double block) {
node.setHSpeed(unit, block);
return this;
}
public TableBuilder<T, V> setVSpeed(double unit, double block) {
node.setVSpeed(unit, block);
return this;
}
public TableBuilder<T, V> enableSmoothScrolling(double speed) {
node.features().enableSmoothScrolling(speed);
return this;
}
public TableBuilder<T, V> enableSmoothScrolling(double speed, double trackPadAdjustment) {
node.features().enableSmoothScrolling(speed, trackPadAdjustment);
return this;
}
public TableBuilder<T, V> enableSmoothScrolling(double speed, double trackPadAdjustment, double scrollThreshold) {
node.features().enableSmoothScrolling(speed, trackPadAdjustment, scrollThreshold);
return this;
}
public TableBuilder<T, V> enableBounceEffect() {
node.features().enableBounceEffect();
return this;
}
public TableBuilder<T, V> enableBounceEffect(double strength, double maxOverscroll) {
node.features().enableBounceEffect(strength, maxOverscroll);
return this;
}
public TableBuilder<T, V> setItems(ObservableList<T> items) {
node.setItems(items);
return this;
}
@SuppressWarnings("unchecked")
public TableBuilder<T, V> addColumns(MFXTableColumn<T>... columns) {
node.getTableColumns().addAll(columns);
return this;
}
@SuppressWarnings("unchecked")
public TableBuilder<T, V> setColumns(MFXTableColumn<T>... columns) {
node.getTableColumns().setAll(columns);
return this;
}
public TableBuilder<T, V> setTableRowFactory(Function<T, MFXTableRow<T>> tableRowFactory) {
node.setTableRowFactory(tableRowFactory);
return this;
}
public TableBuilder<T, V> setFilter(Predicate<T> filter) {
node.getTransformableList().setPredicate(filter);
return this;
}
public TableBuilder<T, V> setComparator(Comparator<T> comparator) {
node.getTransformableList().setComparator(comparator);
return this;
}
public TableBuilder<T, V> setComparator(Comparator<T> comparator, boolean isReverse) {
node.getTransformableList().setComparator(comparator, isReverse);
return this;
}
@SuppressWarnings("unchecked")
public TableBuilder<T, V> addFilters(AbstractFilter<T, ?>... filters) {
node.getFilters().addAll(filters);
return this;
}
@SuppressWarnings("unchecked")
public TableBuilder<T, V> setFilters(AbstractFilter<T, ?>... filters) {
node.getFilters().setAll(filters);
return this;
}
public TableBuilder<T, V> setFooterVisible(boolean footerVisible) {
node.setFooterVisible(footerVisible);
return this;
}
}

View File

@ -1,73 +0,0 @@
/*
* 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.builders.control;
import io.github.palexdev.materialfx.builders.base.LabeledBuilder;
import io.github.palexdev.materialfx.controls.MFXTableColumn;
import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
import io.github.palexdev.materialfx.enums.SortState;
import java.util.Comparator;
import java.util.function.Function;
public class TableColumnBuilder<T> extends LabeledBuilder<MFXTableColumn<T>> {
//================================================================================
// Constructors
//================================================================================
public TableColumnBuilder() {
this(new MFXTableColumn<>());
}
public TableColumnBuilder(MFXTableColumn<T> column) {
super(column);
}
public static <T> TableColumnBuilder<T> tableColumn() {
return new TableColumnBuilder<>();
}
public static <T> TableColumnBuilder<T> tableColumn(MFXTableColumn<T> column) {
return new TableColumnBuilder<>(column);
}
//================================================================================
// Delegate Methods
//================================================================================
public TableColumnBuilder<T> setRowCellFactory(Function<T, MFXTableRowCell<T, ?>> rowCellFactory) {
node.setRowCellFactory(rowCellFactory);
return this;
}
public TableColumnBuilder<T> setSortState(SortState sortState) {
node.setSortState(sortState);
return this;
}
public TableColumnBuilder<T> setComparator(Comparator<T> comparator) {
node.setComparator(comparator);
return this;
}
public TableColumnBuilder<T> setColumnResizable(boolean columnResizable) {
node.setColumnResizable(columnResizable);
return this;
}
}

View File

@ -1,54 +0,0 @@
/*
* 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.collections;
import java.util.Arrays;
import java.util.List;
class ChangeHelper {
ChangeHelper() {
}
public static String addRemoveChangeToString(int from, int to, List<?> list, List<?> removed) {
StringBuilder sb = new StringBuilder();
if (removed.isEmpty()) {
sb.append(list.subList(from, to));
sb.append(" added at ").append(from);
} else {
sb.append(removed);
if (from == to) {
sb.append(" removed at ").append(from);
} else {
sb.append(" replaced by ");
sb.append(list.subList(from, to));
sb.append(" at ").append(from);
}
}
return sb.toString();
}
public static String permChangeToString(int[] permutation) {
return "permutated by " + Arrays.toString(permutation);
}
public static String updateChangeToString(int from, int to) {
return "updated at range [" + from + ", " + to + ")";
}
}

View File

@ -1,174 +0,0 @@
/*
* 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.collections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.Collections;
import java.util.List;
abstract class NonIterableChange<E> extends ListChangeListener.Change<E> {
private final int from;
private final int to;
private boolean invalid = true;
private static final int[] EMPTY_PERM = new int[0];
protected NonIterableChange(int from, int to, ObservableList<E> list) {
super(list);
this.from = from;
this.to = to;
}
public int getFrom() {
checkState();
return from;
}
public int getTo() {
checkState();
return to;
}
protected int[] getPermutation() {
checkState();
return EMPTY_PERM;
}
public boolean next() {
if (invalid) {
invalid = false;
return true;
} else {
return false;
}
}
public void reset() {
invalid = true;
}
public void checkState() {
if (invalid) {
throw new IllegalStateException("Invalid Change state: next() must be called before inspecting the Change.");
}
}
public String toString() {
boolean oldInvalid = invalid;
invalid = false;
String ret;
if (wasPermutated()) {
ret = ChangeHelper.permChangeToString(getPermutation());
} else if (wasUpdated()) {
ret = ChangeHelper.updateChangeToString(from, to);
} else {
ret = ChangeHelper.addRemoveChangeToString(from, to, getList(), getRemoved());
}
invalid = oldInvalid;
return "{ " + ret + " }";
}
public static class SimpleUpdateChange<E> extends NonIterableChange<E> {
public SimpleUpdateChange(int position, ObservableList<E> list) {
this(position, position + 1, list);
}
public SimpleUpdateChange(int from, int to, ObservableList<E> list) {
super(from, to, list);
}
public List<E> getRemoved() {
return Collections.emptyList();
}
public boolean wasUpdated() {
return true;
}
}
public static class SimplePermutationChange<E> extends NonIterableChange<E> {
private final int[] permutation;
public SimplePermutationChange(int from, int to, int[] permutation, ObservableList<E> list) {
super(from, to, list);
this.permutation = permutation;
}
public List<E> getRemoved() {
checkState();
return Collections.emptyList();
}
protected int[] getPermutation() {
checkState();
return permutation;
}
}
public static class SimpleAddChange<E> extends NonIterableChange<E> {
public SimpleAddChange(int from, int to, ObservableList<E> list) {
super(from, to, list);
}
public boolean wasRemoved() {
checkState();
return false;
}
public List<E> getRemoved() {
checkState();
return Collections.emptyList();
}
}
public static class SimpleRemovedChange<E> extends NonIterableChange<E> {
private final List<E> removed;
public SimpleRemovedChange(int from, int to, E removed, ObservableList<E> list) {
super(from, to, list);
this.removed = Collections.singletonList(removed);
}
public boolean wasRemoved() {
checkState();
return true;
}
public List<E> getRemoved() {
checkState();
return removed;
}
}
public static class GenericAddRemoveChange<E> extends NonIterableChange<E> {
private final List<E> removed;
public GenericAddRemoveChange(int from, int to, List<E> removed, ObservableList<E> list) {
super(from, to, list);
this.removed = removed;
}
public List<E> getRemoved() {
checkState();
return removed;
}
}
}

View File

@ -0,0 +1,272 @@
/*
* Copyright (C) 2024 Parisi Alessandro - alessandro.parisi406@gmail.com
* This file is part of ArchitectFX (https://github.com/palexdev/ArchitectFX)
*
* ArchitectFX 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.
*
* ArchitectFX 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 ArchitectFX. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.palexdev.materialfx.collections;
import java.util.*;
import java.util.function.Predicate;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
public class RefineList<T> implements ObservableList<T> {
//================================================================================
// Properties
//================================================================================
private final ObservableList<T> src;
private final FilteredList<T> filtered;
private final SortedList<T> sorted;
//================================================================================
// Constructors
//================================================================================
public RefineList(ObservableList<T> src) {
this.src = src;
filtered = new FilteredList<>(src);
sorted = new SortedList<>(filtered);
}
//================================================================================
// Methods
//================================================================================
public int sourceToView(int index) {
return (getPredicate() != null) ?
filtered.getViewIndex(index) :
sorted.getViewIndex(index);
}
public int viewToSource(int index) {
return sorted.getSourceIndexFor(src, index);
}
//================================================================================
// Delegate Methods
//================================================================================
public Predicate<T> getPredicate() {
return (Predicate<T>) filtered.getPredicate();
}
public ObjectProperty<Predicate<? super T>> predicateProperty() {
return filtered.predicateProperty();
}
public void setPredicate(Predicate<T> predicate) {
filtered.setPredicate(predicate);
}
public Comparator<T> getComparator() {
return (Comparator<T>) sorted.getComparator();
}
public ObjectProperty<Comparator<? super T>> comparatorProperty() {
return sorted.comparatorProperty();
}
public void setComparator(Comparator<T> comparator) {
sorted.setComparator(comparator);
}
// Observability (on the "last view")
@Override
public void addListener(ListChangeListener<? super T> listener) {
sorted.addListener(listener);
}
@Override
public void removeListener(ListChangeListener<? super T> listener) {
sorted.removeListener(listener);
}
@Override
public void addListener(InvalidationListener listener) {
sorted.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
sorted.removeListener(listener);
}
// Basic List Operations
@Override
public boolean add(T t) {
return src.add(t);
}
@Override
public boolean remove(Object o) {
return src.remove(o);
}
@Override
public void clear() {
src.clear();
}
@Override
public int size() {
return src.size();
}
@Override
public boolean isEmpty() {
return src.isEmpty();
}
@Override
public boolean contains(Object o) {
return src.contains(o);
}
// Bulk Operations
@Override
public boolean addAll(T... elements) {
return src.addAll(elements);
}
@Override
public boolean addAll(Collection<? extends T> c) {
return src.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
return src.addAll(index, c);
}
@Override
public boolean setAll(T... elements) {
return src.setAll(elements);
}
@Override
public boolean setAll(Collection<? extends T> col) {
return src.setAll(col);
}
@Override
public boolean removeAll(T... elements) {
return src.removeAll(elements);
}
@Override
public boolean removeAll(Collection<?> c) {
return src.removeAll(c);
}
@Override
public boolean retainAll(T... elements) {
return src.retainAll(elements);
}
@Override
public boolean retainAll(Collection<?> c) {
return src.retainAll(c);
}
@Override
public boolean containsAll(Collection<?> c) {
return src.containsAll(c);
}
// Indexed Operations
@Override
public T get(int index) {
return src.get(index);
}
@Override
public T set(int index, T element) {
return src.set(index, element);
}
@Override
public void add(int index, T element) {
src.add(index, element);
}
@Override
public T remove(int index) {
return src.remove(index);
}
@Override
public void remove(int from, int to) {
src.remove(from, to);
}
// Positional Search
@Override
public int indexOf(Object o) {
return src.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return src.lastIndexOf(o);
}
// Iterators
@Override
public Iterator<T> iterator() {
return src.iterator();
}
@Override
public ListIterator<T> listIterator() {
return src.listIterator();
}
@Override
public ListIterator<T> listIterator(int index) {
return src.listIterator(index);
}
// Sublist Operations
@Override
public List<T> subList(int fromIndex, int toIndex) {
return src.subList(fromIndex, toIndex);
}
// Array Conversions
@Override
public Object[] toArray() {
return src.toArray();
}
@Override
public <T1> T1[] toArray(T1[] a) {
return src.toArray(a);
}
//================================================================================
// Getters
//================================================================================
public ObservableList<T> getSrc() {
return src;
}
public ObservableList<T> getView() {
return sorted;
}
}

View File

@ -1,288 +0,0 @@
/*
* 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.collections;
import io.github.palexdev.materialfx.beans.properties.functional.ComparatorProperty;
import io.github.palexdev.materialfx.beans.properties.functional.PredicateProperty;
import io.github.palexdev.materialfx.collections.NonIterableChange.GenericAddRemoveChange;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.collections.transformation.TransformationList;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* A {@code TransformableList} is a particular type of List which wraps another
* List called "source" and allows manipulations such as: filter and sort, retaining
* the original items' index.
* <p></p>
* Extends {@link TransformationList}, it's basically the same thing of a {@link FilteredList}
* and a {@link SortedList} but combined into one.
* <p></p>
* A more detailed (and hopefully more clear) explanation about the "indexes retention mentioned above":
* <p>
* Think of this List as a View for the source list. The underlying data provided by the source is
* not guaranteed to be what the user sees but the items' properties are maintained.
* Let's see a brief example:
* <pre>
* {@code
* // Let's say I have this ObservableList
* ObservableList<String> source = FXCollections.observableArrayList("A", "B", "C"):
*
* // Now let's say I want to sort this list in reverse order (CBA) and that
* // for some reason I still want A to be the element at index 0, B-1 and C-2
* // This is exactly the purpose of the TransformableList...
* TransformableList<String> transformed = new TransformableList<>(source);
* transformed.setSorter(Comparator.reverseOrder());
*
* // Now that the order is (CBA) let's see how the list behaves:
* transformed.get(0); // Returns C
* transformed.indexOf("C"); // Returns 2, the index is retrieved in the source list
* transformed.viewToSource(0); // Returns 2, it maps an index of the transformed list to the index of the source list, at 0 we have C which is at index 2 in the source list
* transformed.sourceToView(0); // Also returns 2, it maps an index of the source list to the index of the transformed list, at 0 we have C which is at index 2 in the transformed list
*
* // To better see its behavior try to sort and filter the list at the same time.
* // You'll notice that sometimes sourceToView will return a negative index because the item is not in the transformed list (after a filter operation)
* }
* </pre>
* <p>
* Check {@link #computeIndexes()} documentation to see how indexes are calculated.
* <p></p>
* <b>IMPORTANT:</b> If using a reversed comparator please use {@link #setComparator(Comparator, boolean)} with 'true' as argument,
* as {@link #setComparator(Comparator)} will always assume it is a natural order comparator. This is needed to make {@link #sourceToView(int)}
* properly work as it uses a binary search algorithm to find the right index.
*
* @param <T> the items' type
*/
public class TransformableList<T> extends TransformationList<T, T> {
//================================================================================
// Constructors
//================================================================================
private final List<Integer> indexes = new ArrayList<>();
private boolean reversed = false;
private final PredicateProperty<T> predicate = new PredicateProperty<>() {
@Override
protected void invalidated() {
update();
}
};
private final ComparatorProperty<T> comparator = new ComparatorProperty<>() {
@Override
protected void invalidated() {
update();
}
};
//================================================================================
// Constructors
//================================================================================
public TransformableList(ObservableList<? extends T> source) {
this(source, null);
}
public TransformableList(ObservableList<? extends T> source, Predicate<T> predicate) {
this(source, predicate, null);
}
public TransformableList(ObservableList<? extends T> source, Predicate<T> predicate, Comparator<T> comparator) {
super(source);
setPredicate(predicate);
setComparator(comparator);
update();
}
//================================================================================
// Methods
//================================================================================
/**
* Calls {@link #getSourceIndex(int)}, just with a different name to be more clear.
* <p></p>
* Maps an index of the transformed list, to the index of the source list.
*/
public int viewToSource(int index) {
return getSourceIndex(index);
}
/**
* Calls {@link #getViewIndex(int)}, just with a different name to be more clear.
* <p></p>
* Maps an index of the source list, to the index of the transformed list.
*/
public int sourceToView(int index) {
return getViewIndex(index);
}
/**
* Responsible for updating the transformed indexes when the
* predicate or the comparator change.
*/
private void update() {
indexes.clear();
indexes.addAll(computeIndexes());
if (this.hasListeners()) {
this.fireChange(new GenericAddRemoveChange<>(0, size(), new ArrayList<>(this), this));
}
}
/**
* Core method of TransformableLists. This is responsible for computing
* the transformed indexes by creating a {@link SortedMap} and mapping every index from 0 to source size
* to its item. Before mapping, items are filtered with the given predicate, {@link #predicateProperty()}.
* Before returning, the map's entry set is sorted by its values with the given comparator, {@link #comparatorProperty()}.
* Finally, returns the map's key set, this set contains the transformed indexes, filtered and sorted.
*/
private Collection<Integer> computeIndexes() {
Predicate<? super T> filter = this.getPredicate();
Comparator<? super T> sorter = this.getComparator();
SortedMap<Integer, T> sourceMap;
if (filter != null) {
sourceMap = IntStream.range(0, getSource().size())
.filter((index) -> filter.test(getSource().get(index)))
.collect(TreeMap::new, (map, index) -> map.put(index, getSource().get(index)), TreeMap::putAll);
} else {
sourceMap = IntStream.range(0, getSource().size())
.collect(TreeMap::new, (map, index) -> map.put(index, getSource().get(index)), TreeMap::putAll);
}
return sorter != null ? sourceMap.entrySet().stream()
.sorted((o1, o2) -> sorter.compare(o1.getValue(), o2.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toList()) : sourceMap.keySet();
}
public Predicate<? super T> getPredicate() {
return this.predicate.get();
}
/**
* Specifies the predicate used to filter the source list.
*/
public PredicateProperty<T> predicateProperty() {
return this.predicate;
}
public void setPredicate(Predicate<T> predicate) {
this.predicate.set(predicate);
}
public Comparator<T> getComparator() {
return this.comparator.get();
}
/**
* Specifies the comparator used to sort the source list.
*
* @see #setComparator(Comparator, boolean)
*/
public ComparatorProperty<T> comparatorProperty() {
return this.comparator;
}
public void setComparator(Comparator<T> comparator) {
this.reversed = false;
this.comparator.set(comparator);
}
/**
* This method is NECESSARY if using a reversed comparator,
* a special flag is set to true and {@link #sourceToView(int)} behaves accordingly.
*/
public void setComparator(Comparator<T> comparator, boolean reversed) {
this.reversed = reversed;
this.comparator.set(comparator);
}
/**
* Specifies if a reversed comparator is being used.
*/
public boolean isReversed() {
return reversed;
}
/**
* Communicates to the transformed list, specifically to {@link #getViewIndex(int)},
* if the list is sorted in reversed order.
*/
public void setReversed(boolean reversed) {
this.reversed = reversed;
}
//================================================================================
// Overridden Methods
//================================================================================
/**
* {@inheritDoc}
* <p></p>
* Calls {@link #update()}.
*/
@Override
protected void sourceChanged(ListChangeListener.Change<? extends T> c) {
beginChange();
update();
endChange();
}
/**
* @return the number of items in the transformable list
*/
@Override
public int size() {
return indexes.size();
}
/**
* Retrieves and return the item at the given index in the transformable list.
* This means transformations due to {@link #predicateProperty()} or {@link #comparatorProperty()}
* are taken into account.
*/
@Override
public T get(int index) {
if (index > size()) {
throw new IndexOutOfBoundsException(index);
} else {
return getSource().get(indexes.get(index));
}
}
@Override
public int getSourceIndex(int index) {
if (index > size()) {
throw new IndexOutOfBoundsException(index);
} else {
return indexes.get(index);
}
}
@Override
public int getViewIndex(int index) {
int viewIndex = reversed ?
Collections.binarySearch(indexes, index, Collections.reverseOrder()) :
Collections.binarySearch(indexes, index);
return viewIndex < 0 ? -1 : viewIndex;
}
}

View File

@ -1,341 +0,0 @@
/*
* 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.collections;
import io.github.palexdev.materialfx.beans.properties.functional.ComparatorProperty;
import io.github.palexdev.materialfx.beans.properties.functional.PredicateProperty;
import javafx.beans.InvalidationListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList;
import java.util.AbstractList;
import java.util.Collection;
import java.util.Comparator;
import java.util.function.Predicate;
/**
* For some idiot reason JavaFX's {@link TransformationList}s do not allow modifying the
* source list.
* <p>
* This class fixes that. It wraps an {@link ObservableList} which is the source list and
* the new {@link TransformableList} which is built from that source.
* <p>
* This way you can benefit of the futures of the new {@link TransformableList} (sorting and filtering)
* while also being able to directly modify the source list.
*/
@SuppressWarnings({"unchecked", "NullableProblems"})
public class TransformableListWrapper<T> extends AbstractList<T> implements ObservableList<T> {
//================================================================================
// Properties
//================================================================================
private final ObservableList<T> source;
private final TransformableList<T> transformableList;
//================================================================================
// Constructors
//================================================================================
public TransformableListWrapper(ObservableList<T> source) {
this.source = source;
this.transformableList = new TransformableList<>(source);
}
//================================================================================
// Methods
//================================================================================
/**
* {@inheritDoc}
* <p></p>
* Added to the {@link TransformableList}.
*/
@Override
public void addListener(ListChangeListener<? super T> listener) {
transformableList.addListener(listener);
}
/**
* {@inheritDoc}
* <p></p>
* Removed from the {@link TransformableList}.
*/
@Override
public void removeListener(ListChangeListener<? super T> listener) {
transformableList.removeListener(listener);
}
/**
* {@inheritDoc}
* <p></p>
* Added to the source list.
*/
@Override
public boolean add(T t) {
return source.add(t);
}
/**
* {@inheritDoc}
* <p></p>
* Set on the source list.
*/
@Override
public T set(int index, T element) {
return source.set(index, element);
}
/**
* {@inheritDoc}
* <p></p>
* Added to the source list.
*/
@Override
public void add(int index, T element) {
source.add(index, element);
}
/**
* {@inheritDoc}
* <p></p>
* Removed from the source list.
*/
@Override
public T remove(int index) {
return source.remove(index);
}
/**
* {@inheritDoc}
* <p></p>
* Retrieved from the {@link TransformableList}.
*/
@Override
public int indexOf(Object o) {
return transformableList.indexOf(o);
}
/**
* {@inheritDoc}
* <p></p>
* Retrieved from the {@link TransformableList}.
*/
@Override
public int lastIndexOf(Object o) {
return transformableList.lastIndexOf(o);
}
/**
* {@inheritDoc}
* <p></p>
* The source list is cleared.
*/
@Override
public void clear() {
source.clear();
}
/**
* {@inheritDoc}
* <p></p>
* Added to the source list.
*/
@Override
public boolean addAll(int index, Collection<? extends T> c) {
return source.addAll(index, c);
}
/**
* {@inheritDoc}
* <p></p>
* Added to the source list.
*/
@Override
public boolean addAll(T... elements) {
return source.addAll(elements);
}
/**
* {@inheritDoc}
* <p></p>
* Set on the source list.
*/
@Override
public boolean setAll(T... elements) {
return source.setAll(elements);
}
/**
* {@inheritDoc}
* <p></p>
* Set on the source list.
*/
@Override
public boolean setAll(Collection<? extends T> col) {
return source.setAll(col);
}
/**
* {@inheritDoc}
* <p></p>
* Removed from the source list.
*/
@Override
public boolean removeAll(T... elements) {
return source.removeAll(elements);
}
/**
* {@inheritDoc}
* <p></p>
* Retained on the source list.
*/
@Override
public boolean retainAll(T... elements) {
return source.retainAll(elements);
}
/**
* {@inheritDoc}
* <p></p>
* Removed from the source list.
*/
@Override
public void remove(int from, int to) {
source.remove(from, to);
}
/**
* {@inheritDoc}
* <p></p>
* Added to the {@link TransformableList}.
*/
@Override
public void addListener(InvalidationListener listener) {
transformableList.addListener(listener);
}
/**
* {@inheritDoc}
* <p></p>
* Removed from the {@link TransformableList}.
*/
@Override
public void removeListener(InvalidationListener listener) {
transformableList.removeListener(listener);
}
/**
* {@inheritDoc}
* <p></p>
* Retrieved from the {@link TransformableList}.
*/
@Override
public T get(int index) {
return transformableList.get(index);
}
/**
* {@inheritDoc}
* <p></p>
* Size of the {@link TransformableList}.
*/
@Override
public int size() {
return transformableList.size();
}
/**
* @return the source observable list
*/
public ObservableList<? extends T> getSource() {
return transformableList.getSource();
}
/**
* Delegate for {@link TransformableList#viewToSource(int)}.
*/
public int viewToSource(int index) {
return transformableList.viewToSource(index);
}
/**
* Delegate for {@link TransformableList#sourceToView(int)}.
*/
public int sourceToView(int index) {
return transformableList.sourceToView(index);
}
public Predicate<? super T> getPredicate() {
return transformableList.getPredicate();
}
/**
* Delegate for {@link TransformableList#predicateProperty()}.
*/
public PredicateProperty<T> predicateProperty() {
return transformableList.predicateProperty();
}
public void setPredicate(Predicate<T> predicate) {
transformableList.setPredicate(predicate);
}
public Comparator<T> getComparator() {
return transformableList.getComparator();
}
/**
* Delegate for {@link TransformableList#comparatorProperty()}.
*/
public ComparatorProperty<T> comparatorProperty() {
return transformableList.comparatorProperty();
}
public void setComparator(Comparator<T> comparator) {
transformableList.setComparator(comparator);
}
/**
* Delegate for {@link TransformableList#setComparator(Comparator, boolean)}.
*/
public void setComparator(Comparator<T> sorter, boolean reversed) {
transformableList.setComparator(sorter, reversed);
}
/**
* Delegate for {@link TransformableList#isReversed()}.
*/
public boolean isReversed() {
return transformableList.isReversed();
}
/**
* Delegate for {@link TransformableList#setReversed(boolean)}.
*/
public void setReversed(boolean reversed) {
transformableList.setReversed(reversed);
}
/**
* @return the wrapped {@link TransformableList}
*/
public TransformableList<T> getTransformableList() {
return transformableList;
}
}

View File

@ -1,250 +1,41 @@
/*
* 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.controls.base.AbstractMFXListView;
import io.github.palexdev.materialfx.controls.cell.MFXCheckListCell;
import io.github.palexdev.materialfx.skins.MFXListViewSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import io.github.palexdev.materialfx.utils.ListChangeHelper;
import io.github.palexdev.materialfx.utils.ListChangeProcessor;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.control.Skin;
import java.util.HashSet;
import java.util.Map;
import java.util.List;
import java.util.function.Function;
/**
* Implementation of a check listview based on VirtualizedFX.
* <p>
* Extends {@link AbstractMFXListView}.
* <p></p>
* Default cell: {@link MFXCheckListCell}.
* <p>
* Default skin: {@link MFXListViewSkin}.
* <p></p>
* Holds the reference to the VirtualFlow used by the list, this allows to expose some
* methods from it as delegate method thus allowing to:
* <p> - Manually scroll by a certain amount of pixels
* <p> - Manually scroll to a given index (also first and last)
* <p> - Manually scroll to the given pixel value
* <p> - Set the scrollbar's speed
* <p> - Get the vertical or horizontal position of the list
* <p> - Configure extra features of the VirtualFlow, {@link #features()}
* <p> - Get the currently shown cells, or a specific cell by index
* <p></p>
* It's also responsible for updating the selection model in case the items list property
* changes, or changes occur in the items list.
*/
public class MFXCheckListView<T> extends AbstractMFXListView<T, MFXCheckListCell<T>> {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-check-list-view";
private final SimpleVirtualFlow<T, MFXCheckListCell<T>> virtualFlow;
private final ListChangeListener<? super T> itemsChanged = this::itemsChanged;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.mfxcore.utils.fx.SceneBuilderIntegration;
import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
public class MFXCheckListView<T, C extends VFXCell<T>> extends MFXListView<T, C> {
//================================================================================
// Constructors
//================================================================================
public MFXCheckListView() {
virtualFlow = new SimpleVirtualFlow<>(
itemsProperty(),
null,
Orientation.VERTICAL
);
initialize();
}
public MFXCheckListView(ObservableList<T> items, Function<T, C> cellFactory) {
super(items, cellFactory);
}
public MFXCheckListView(ObservableList<T> items) {
super(items);
virtualFlow = new SimpleVirtualFlow<>(
itemsProperty(),
null,
Orientation.VERTICAL
);
initialize();
public MFXCheckListView(ObservableList<T> items, Function<T, C> cellFactory, Orientation orientation) {
super(items, cellFactory, orientation);
}
//================================================================================
// Methods
//================================================================================
@Override
protected void initialize() {
super.initialize();
getStyleClass().add(STYLE_CLASS);
items.addListener((observable, oldValue, newValue) -> {
oldValue.removeListener(itemsChanged);
newValue.addListener(itemsChanged);
});
getItems().addListener(this::itemsChanged);
}
protected void itemsChanged(ListChangeListener.Change<? extends T> change) {
if (getSelectionModel().getSelection().isEmpty()) return;
if (change.getList().isEmpty()) {
getSelectionModel().clearSelection();
return;
}
ListChangeHelper.Change c = ListChangeHelper.processChange(change, IntegerRange.of(0, Integer.MAX_VALUE));
ListChangeProcessor updater = new ListChangeProcessor(new HashSet<>(getSelectionModel().getSelection().keySet()));
c.processReplacement((changed, removed) -> getSelectionModel().replaceSelection(changed.toArray(new Integer[0])));
c.processAddition((from, to, added) -> {
updater.computeAddition(added.size(), from);
getSelectionModel().replaceSelection(updater.getIndexes().toArray(new Integer[0]));
});
c.processRemoval((from, to, removed) -> {
updater.computeRemoval(removed, from);
getSelectionModel().replaceSelection(updater.getIndexes().toArray(new Integer[0]));
});
}
//================================================================================
// Delegate Methods
// Overridden Methods
//================================================================================
/**
* Delegate method for {@link SimpleVirtualFlow#getCell(int)}.
*/
public MFXCheckListCell<T> getCell(int index) {
return virtualFlow.getCell(index);
}
/**
* Delegate method for {@link SimpleVirtualFlow#getCells()}.
*/
public Map<Integer, MFXCheckListCell<T>> getCells() {
return virtualFlow.getCells();
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollBy(double)}.
*/
public void scrollBy(double pixels) {
virtualFlow.scrollBy(pixels);
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollTo(int)}.
*/
public void scrollTo(int index) {
virtualFlow.scrollTo(index);
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToFirst()}.
*/
public void scrollToFirst() {
virtualFlow.scrollToFirst();
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToLast()}.
*/
public void scrollToLast() {
virtualFlow.scrollToLast();
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToPixel(double)}.
*/
public void scrollToPixel(double pixel) {
virtualFlow.scrollToPixel(pixel);
}
/**
* Delegate method for {@link SimpleVirtualFlow#setHSpeed(double, double)}.
*/
public void setHSpeed(double unit, double block) {
virtualFlow.setHSpeed(unit, block);
}
/**
* Delegate method for {@link SimpleVirtualFlow#setVSpeed(double, double)}.
*/
public void setVSpeed(double unit, double block) {
virtualFlow.setVSpeed(unit, block);
}
/**
* Delegate method for {@link SimpleVirtualFlow#getVerticalPosition()}.
*/
public double getVerticalPosition() {
return virtualFlow.getVerticalPosition();
}
/**
* Delegate method for {@link SimpleVirtualFlow#getHorizontalPosition()}.
*/
public double getHorizontalPosition() {
return virtualFlow.getHorizontalPosition();
}
/**
* Delegate method for {@link SimpleVirtualFlow#features()}.
*/
public SimpleVirtualFlow<T, MFXCheckListCell<T>>.Features features() {
return virtualFlow.features();
}
//================================================================================
// Override Methods
//================================================================================
@Override
public Theme getTheme() {
return MaterialFXStylesheets.CHECK_LIST_VIEW;
}
/**
* Sets the default factory for the cells.
*/
@Override
protected void setDefaultCellFactory() {
setCellFactory(item -> new MFXCheckListCell<>(this, item));
public void sceneBuilderIntegration() {
SceneBuilderIntegration.ifInSceneBuilder(() -> getStylesheets().add(MaterialFXStylesheets.CHECK_LIST_VIEW.toData()));
}
@Override
public Function<T, MFXCheckListCell<T>> getCellFactory() {
return virtualFlow.getCellFactory();
public List<String> defaultStyleClasses() {
return List.of("mfx-check-list");
}
@Override
public ObjectProperty<Function<T, MFXCheckListCell<T>>> cellFactoryProperty() {
return virtualFlow.cellFactoryProperty();
}
@Override
public void setCellFactory(Function<T, MFXCheckListCell<T>> cellFactory) {
virtualFlow.setCellFactory(cellFactory);
}
@Override
protected Skin<?> createDefaultSkin() {
return new MFXListViewSkin<>(this, virtualFlow);
}
}
}

View File

@ -18,6 +18,11 @@
package io.github.palexdev.materialfx.controls;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import io.github.palexdev.materialfx.beans.Alignment;
import io.github.palexdev.materialfx.beans.PositionBean;
import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
@ -33,18 +38,17 @@ import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel;
import io.github.palexdev.materialfx.skins.MFXComboBoxSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import io.github.palexdev.materialfx.utils.*;
import io.github.palexdev.materialfx.utils.NodeUtils;
import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
import io.github.palexdev.materialfx.utils.others.FunctionalStringConverter;
import io.github.palexdev.materialfx.validation.MFXValidator;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.mfxresources.fonts.MFXFontIcon;
import io.github.palexdev.virtualizedfx.cell.Cell;
import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import io.github.palexdev.virtualizedfx.events.VFXContainerEvent;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
@ -60,13 +64,6 @@ import javafx.scene.control.Skin;
import javafx.util.Duration;
import javafx.util.StringConverter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A new, completely remade from scratch {@code ComboBox} for JavaFX.
* <p>
@ -97,11 +94,17 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
private final BiFunctionProperty<Node, Boolean, Animation> animationProvider = new BiFunctionProperty<>();
private final ObjectProperty<T> value = new SimpleObjectProperty<>();
private final ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<>();
private final ObjectProperty<ObservableList<T>> items = new SimpleObjectProperty<>();
private final ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<>() {
@Override
protected void invalidated() {
// VFX cells already have the converter but in this case we want to use the one from the combo box
// Since the cells do not have it as a property we must send an update request to the cells when this changes
fireEvent(new VFXContainerEvent(null, MFXComboBox.this, VFXContainerEvent.UPDATE));
}
};
private final ListProperty<T> items = new SimpleListProperty<>();
private final ComboBoxSelectionModel<T> selectionModel = new ComboBoxSelectionModel<>(items);
private final FunctionProperty<T, Cell<T>> cellFactory = new FunctionProperty<>(t -> new MFXComboBoxCell<>(this, t));
private final ListChangeListener<? super T> itemsChanged = this::itemsChanged;
private final FunctionProperty<T, VFXCell<T>> cellFactory = new FunctionProperty<>(t -> new MFXComboBoxCell<>(this, t));
private final ConsumerProperty<String> onCommit = new ConsumerProperty<>();
private final ConsumerProperty<String> onCancel = new ConsumerProperty<>();
@ -155,12 +158,6 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
setConverter(FunctionalStringConverter.to(t -> t != null ? t.toString() : ""));
showing.addListener(invalidated -> pseudoClassStateChanged(POPUP_OPEN_PSEUDO_CLASS, showing.get()));
items.addListener((observable, oldValue, newValue) -> {
oldValue.removeListener(itemsChanged);
newValue.addListener(itemsChanged);
});
getItems().addListener(this::itemsChanged);
}
@Override
@ -240,40 +237,6 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
}
}
/**
* Responsible for updating the selection when the items list changes.
*/
protected void itemsChanged(ListChangeListener.Change<? extends T> change) {
if (getSelectedIndex() == -1) return;
if (change.getList().isEmpty()) {
clearSelection();
return;
}
ListChangeHelper.Change c = ListChangeHelper.processChange(change, IntegerRange.of(0, Integer.MAX_VALUE));
Set<Integer> indexes = new HashSet<>();
indexes.add(getSelectedIndex());
ListChangeProcessor updater = new ListChangeProcessor(indexes);
c.processReplacement((changed, removed) -> {
int selected = getSelectedIndex();
if (changed.contains(selected) || removed.contains(selected)) {
selectItem(getItems().get(selected));
}
});
c.processAddition((from, to, added) -> {
updater.computeAddition(added.size(), from);
selectIndex(updater.getIndexes().toArray(new Integer[0])[0]);
});
c.processRemoval((from, to, removed) -> {
updater.computeRemoval(removed, from);
int index = NumberUtils.clamp(updater.getIndexes().toArray(new Integer[0])[0], 0, getItems().size() - 1);
selectIndex(index);
});
setValue(getSelectedItem());
}
//================================================================================
// Overridden Methods
//================================================================================
@ -353,13 +316,6 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
return selectionModel.getSelectedIndex();
}
/**
* Delegate for {@link ComboBoxSelectionModel#selectedIndexProperty()}.
*/
public ReadOnlyIntegerProperty selectedIndexProperty() {
return selectionModel.selectedIndexProperty();
}
/**
* Delegate for {@link ComboBoxSelectionModel#getSelectedItem()}.
*/
@ -368,18 +324,10 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
}
/**
* Delegate for {@link ComboBoxSelectionModel#selectedItemProperty()}.
* Delegate for {@link ComboBoxSelectionModel#selection()}.
*/
public ReadOnlyObjectProperty<T> selectedItemProperty() {
return selectionModel.selectedItemProperty();
}
//================================================================================
// Validation
//================================================================================
@Override
public MFXValidator getValidator() {
return validator;
public MapProperty<Integer, T> selection() {
return selectionModel.selection();
}
//================================================================================
@ -606,7 +554,7 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
}
@Override
public ObjectProperty<ObservableList<T>> itemsProperty() {
public ListProperty<T> itemsProperty() {
return items;
}
@ -616,17 +564,17 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
}
@Override
public Function<T, Cell<T>> getCellFactory() {
public Function<T, VFXCell<T>> getCellFactory() {
return cellFactory.get();
}
@Override
public ObjectProperty<Function<T, Cell<T>>> cellFactoryProperty() {
public ObjectProperty<Function<T, VFXCell<T>>> cellFactoryProperty() {
return cellFactory;
}
@Override
public void setCellFactory(Function<T, Cell<T>> cellFactory) {
public void setCellFactory(Function<T, VFXCell<T>> cellFactory) {
this.cellFactory.set(cellFactory);
}

View File

@ -18,9 +18,11 @@
package io.github.palexdev.materialfx.controls;
import java.util.function.Function;
import java.util.function.Predicate;
import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
import io.github.palexdev.materialfx.collections.TransformableList;
import io.github.palexdev.materialfx.collections.TransformableListWrapper;
import io.github.palexdev.materialfx.collections.RefineList;
import io.github.palexdev.materialfx.controls.cell.MFXComboBoxCell;
import io.github.palexdev.materialfx.controls.cell.MFXFilterComboBoxCell;
import io.github.palexdev.materialfx.skins.MFXFilterComboBoxSkin;
@ -34,9 +36,6 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Skin;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Extends {@link MFXComboBox} and changes the popup's content slightly to
* allow filtering the items list.
@ -52,6 +51,7 @@ import java.util.function.Predicate;
* Note: this combo box do not use {@link MFXComboBoxCell} and while it does allow it it should never be used.
* Use {@link MFXFilterComboBoxCell} instead for consistent selection behavior.
*/
// TODO check this mess
public class MFXFilterComboBox<T> extends MFXComboBox<T> {
//================================================================================
// Properties
@ -59,11 +59,11 @@ public class MFXFilterComboBox<T> extends MFXComboBox<T> {
private final String STYLECLASS = "mfx-filter-combo-box";
private final StringProperty searchText = new SimpleStringProperty();
private final TransformableListWrapper<T> filterList = new TransformableListWrapper<>(FXCollections.observableArrayList());
private final RefineList<T> refineList = new RefineList<>(FXCollections.observableArrayList());
private final FunctionProperty<String, Predicate<T>> filterFunction = new FunctionProperty<>(s -> t -> StringUtils.containsIgnoreCase(t.toString(), s));
private boolean resetOnPopupHidden = true;
private final InvalidationListener itemsChanged = invalidated -> filterList.setAll(getItems());
private final InvalidationListener itemsChanged = invalidated -> refineList.setAll(getItems());
//================================================================================
// Constructors
@ -85,12 +85,12 @@ public class MFXFilterComboBox<T> extends MFXComboBox<T> {
getStyleClass().add(STYLECLASS);
setCellFactory(t -> new MFXFilterComboBoxCell<>(this, getFilterList(), t));
filterList.setAll(getItems());
refineList.setAll(getItems());
itemsProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue != null) oldValue.removeListener(itemsChanged);
if (newValue != null) {
newValue.addListener(itemsChanged);
filterList.setAll(newValue);
refineList.setAll(newValue);
}
});
getItems().addListener(itemsChanged);
@ -117,8 +117,8 @@ public class MFXFilterComboBox<T> extends MFXComboBox<T> {
this.searchText.set(searchText);
}
public TransformableList<T> getFilterList() {
return filterList.getTransformableList();
public RefineList<T> getFilterList() {
return refineList;
}
public Function<String, Predicate<T>> getFilterFunction() {

View File

@ -1,248 +1,152 @@
/*
* 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.controls.base.AbstractMFXListView;
import io.github.palexdev.materialfx.controls.cell.MFXListCell;
import io.github.palexdev.materialfx.skins.MFXListViewSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import io.github.palexdev.materialfx.utils.ListChangeHelper;
import io.github.palexdev.materialfx.utils.ListChangeProcessor;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.control.Skin;
import java.util.HashSet;
import java.util.Map;
import java.util.List;
import java.util.function.Function;
/**
* Implementation of a check listview based on VirtualizedFX.
* <p>
* Extends {@link AbstractMFXListView}.
* <p></p>
* Default cell: {@link MFXListCell}.
* <p>
* Default skin: {@link MFXListViewSkin}.
* <p></p>
* Holds the reference to the VirtualFlow used by the list, this allows to expose some
* methods from it as delegate method thus allowing to:
* <p> - Manually scroll by a certain amount of pixels
* <p> - Manually scroll to a given index (also first and last)
* <p> - Manually scroll to the given pixel value
* <p> - Set the scrollbar's speed
* <p> - Get the vertical or horizontal position of the list
* <p> - Configure extra features of the VirtualFlow, {@link #features()}
* <p> - Get the currently shown cells, or a specific cell by index
* <p></p>
* It's also responsible for updating the selection model in case the items list property
* changes, or changes occur in the items list.
*/
public class MFXListView<T> extends AbstractMFXListView<T, MFXListCell<T>> {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-list-view";
private final SimpleVirtualFlow<T, MFXListCell<T>> virtualFlow;
private final ListChangeListener<? super T> itemsChanged = this::itemsChanged;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.selection.SelectionModel;
import io.github.palexdev.materialfx.selection.base.WithSelectionModel;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.mfxcore.utils.fx.SceneBuilderIntegration;
import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
import io.github.palexdev.mfxeffects.enums.ElevationLevel;
import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import io.github.palexdev.virtualizedfx.controls.VFXScrollPane;
import io.github.palexdev.virtualizedfx.list.VFXList;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.Orientation;
import javafx.scene.Parent;
import javafx.scene.effect.DropShadow;
//================================================================================
// Constructors
//================================================================================
public MFXListView() {
virtualFlow = new SimpleVirtualFlow<>(
itemsProperty(),
null,
Orientation.VERTICAL
);
initialize();
}
public class MFXListView<T, C extends VFXCell<T>> extends VFXList<T, C> implements WithSelectionModel<T> {
//================================================================================
// Properties
//================================================================================
private final ISelectionModel<T> selectionModel = new SelectionModel<>(itemsProperty());
public MFXListView(ObservableList<T> items) {
super(items);
virtualFlow = new SimpleVirtualFlow<>(
itemsProperty(),
null,
Orientation.VERTICAL
);
initialize();
}
private VFXScrollPane vsp;
//================================================================================
// Methods
//================================================================================
@Override
protected void initialize() {
super.initialize();
getStyleClass().setAll(STYLE_CLASS);
items.addListener((observable, oldValue, newValue) -> {
if (oldValue != null) oldValue.removeListener(itemsChanged);
if (newValue != null) newValue.removeListener(itemsChanged);
});
getItems().addListener(itemsChanged);
}
//================================================================================
// Constructors
//================================================================================
public MFXListView() {
initialize();
}
protected void itemsChanged(ListChangeListener.Change<? extends T> change) {
if (getSelectionModel().getSelection().isEmpty()) return;
public MFXListView(ObservableList<T> items, Function<T, C> cellFactory) {
super(items, cellFactory);
initialize();
}
if (change.getList().isEmpty()) {
getSelectionModel().clearSelection();
return;
}
public MFXListView(ObservableList<T> items, Function<T, C> cellFactory, Orientation orientation) {
super(items, cellFactory, orientation);
initialize();
}
ListChangeHelper.Change c = ListChangeHelper.processChange(change, IntegerRange.of(0, Integer.MAX_VALUE));
ListChangeProcessor updater = new ListChangeProcessor(new HashSet<>(getSelectionModel().getSelection().keySet()));
c.processReplacement((changed, removed) -> getSelectionModel().replaceSelection(changed.toArray(new Integer[0])));
c.processAddition((from, to, added) -> {
updater.computeAddition(added.size(), from);
getSelectionModel().replaceSelection(updater.getIndexes().toArray(new Integer[0]));
});
c.processRemoval((from, to, removed) -> {
updater.computeRemoval(removed, from);
getSelectionModel().replaceSelection(updater.getIndexes().toArray(new Integer[0]));
});
}
//================================================================================
// Methods
//================================================================================
private void initialize() {
sceneBuilderIntegration();
}
//================================================================================
// Delegate Methods
//================================================================================
protected void findVsp() {
Parent parent = getParent();
while (parent != null) {
if (parent instanceof VFXScrollPane p) {
vsp = p;
onDepthChanged();
break;
}
parent = parent.getParent();
}
}
/**
* Delegate method for {@link SimpleVirtualFlow#getCell(int)}.
*/
public MFXListCell<T> getCell(int index) {
return virtualFlow.getCell(index);
}
protected void onDepthChanged() {
ElevationLevel level = getDepth();
DropShadow effect = (level == null || level == ElevationLevel.LEVEL0) ? null : level.toShadow();
if (vsp == null) findVsp();
if (vsp != null) vsp.setEffect(effect);
}
/**
* Delegate method for {@link SimpleVirtualFlow#getCells()}.
*/
public Map<Integer, MFXListCell<T>> getCells() {
return virtualFlow.getCells();
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public ISelectionModel<T> getSelectionModel() {
return selectionModel;
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollBy(double)}.
*/
public void scrollBy(double pixels) {
virtualFlow.scrollBy(pixels);
}
@Override
public void sceneBuilderIntegration() {
SceneBuilderIntegration.ifInSceneBuilder(() -> getStylesheets().add(MaterialFXStylesheets.LIST_VIEW.toData()));
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollTo(int)}.
*/
public void scrollTo(int index) {
virtualFlow.scrollTo(index);
}
@Override
public List<String> defaultStyleClasses() {
return List.of("mfx-list-view");
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToFirst()}.
*/
public void scrollToFirst() {
virtualFlow.scrollToFirst();
}
//================================================================================
// Styleable Properties
//================================================================================
private final StyleableObjectProperty<ElevationLevel> depth = new StyleableObjectProperty<>(
StyleableProperties.DEPTH,
this,
"depth",
ElevationLevel.LEVEL0
) {
@Override
protected void invalidated() {
onDepthChanged();
}
};
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToLast()}.
*/
public void scrollToLast() {
virtualFlow.scrollToLast();
}
public ElevationLevel getDepth() {
return depth.get();
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToPixel(double)}.
*/
public void scrollToPixel(double pixel) {
virtualFlow.scrollToPixel(pixel);
}
public StyleableObjectProperty<ElevationLevel> depthProperty() {
return depth;
}
/**
* Delegate method for {@link SimpleVirtualFlow#setHSpeed(double, double)}.
*/
public void setHSpeed(double unit, double block) {
virtualFlow.setHSpeed(unit, block);
}
public void setDepth(ElevationLevel depth) {
this.depth.set(depth);
}
/**
* Delegate method for {@link SimpleVirtualFlow#setVSpeed(double, double)}.
*/
public void setVSpeed(double unit, double block) {
virtualFlow.setVSpeed(unit, block);
}
//================================================================================
// CssMetaData
//================================================================================
private static class StyleableProperties {
private static final StyleablePropertyFactory<MFXListView<?, ?>> FACTORY = new StyleablePropertyFactory<>(VFXList.getClassCssMetaData());
private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
/**
* Delegate method for {@link SimpleVirtualFlow#getVerticalPosition()}.
*/
public double getVerticalPosition() {
return virtualFlow.getVerticalPosition();
}
private static final CssMetaData<MFXListView<?, ?>, ElevationLevel> DEPTH =
FACTORY.createEnumCssMetaData(
ElevationLevel.class,
"-mfx-depth",
MFXListView::depthProperty,
ElevationLevel.LEVEL0
);
/**
* Delegate method for {@link SimpleVirtualFlow#getHorizontalPosition()}.
*/
public double getHorizontalPosition() {
return virtualFlow.getHorizontalPosition();
}
static {
cssMetaDataList = StyleUtils.cssMetaDataList(
VFXList.getClassCssMetaData(),
DEPTH
);
}
}
/**
* Delegate method for {@link SimpleVirtualFlow#features()}.
*/
public SimpleVirtualFlow<T, MFXListCell<T>>.Features features() {
return virtualFlow.features();
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.cssMetaDataList;
}
//================================================================================
// Override Methods
//================================================================================
@Override
public Theme getTheme() {
return MaterialFXStylesheets.LIST_VIEW;
}
@Override
protected void setDefaultCellFactory() {
setCellFactory(item -> new MFXListCell<>(this, item));
}
@Override
public Function<T, MFXListCell<T>> getCellFactory() {
return virtualFlow.getCellFactory();
}
@Override
public ObjectProperty<Function<T, MFXListCell<T>>> cellFactoryProperty() {
return virtualFlow.cellFactoryProperty();
}
@Override
public void setCellFactory(Function<T, MFXListCell<T>> cellFactory) {
virtualFlow.setCellFactory(cellFactory);
}
@Override
protected Skin<?> createDefaultSkin() {
return new MFXListViewSkin<>(this, virtualFlow);
}
@Override
protected List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
}

View File

@ -18,15 +18,23 @@
package io.github.palexdev.materialfx.controls;
import io.github.palexdev.materialfx.collections.TransformableListWrapper;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.IntStream;
import io.github.palexdev.materialfx.collections.RefineList;
import io.github.palexdev.materialfx.controls.base.MFXMenuControl;
import io.github.palexdev.materialfx.controls.base.Themable;
import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell;
import io.github.palexdev.materialfx.enums.NotificationCounterStyle;
import io.github.palexdev.materialfx.enums.NotificationState;
import io.github.palexdev.materialfx.i18n.I18N;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.selection.SelectionModel;
import io.github.palexdev.materialfx.notifications.base.INotification;
import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
import io.github.palexdev.materialfx.skins.MFXNotificationCenterSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
@ -35,7 +43,6 @@ import io.github.palexdev.materialfx.utils.ListChangeHelper.Change;
import io.github.palexdev.materialfx.utils.ListChangeProcessor;
import io.github.palexdev.materialfx.utils.others.ReusableScheduledExecutor;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.LongBinding;
import javafx.beans.property.*;
@ -49,19 +56,12 @@ import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.input.MouseEvent;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.IntStream;
import static io.github.palexdev.materialfx.enums.NotificationCounterStyle.NUMBER;
/**
* A quite complex but easy to use implementation of a modern notification center.
* <p></p>
* For the notifications it uses {@link TransformableListWrapper} as list implementation, this allows
* For the notifications it uses {@link RefineList} as list implementation, this allows
* not only basic operations such additions, removals and replacements, but also filter and sort operations.
* <p></p>
* It's composed by an icon and a popup that contains the list of notifications.
@ -99,10 +99,10 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
//================================================================================
private final String STYLE_CLASS = "mfx-notification-center";
private final TransformableListWrapper<INotification> notifications = new TransformableListWrapper<>(FXCollections.observableArrayList());
private final RefineList<INotification> notifications = new RefineList<>(FXCollections.observableArrayList());
private final SimpleVirtualFlow<INotification, MFXNotificationCell> virtualFlow;
private final MultipleSelectionModel<INotification> selectionModel = new MultipleSelectionModel<>(notifications);
private final MFXListView<INotification, MFXNotificationCell> listView;
private final ISelectionModel<INotification> selectionModel = new SelectionModel<>(notifications);
private final BooleanProperty selectionMode = new SimpleBooleanProperty(false);
private final ReadOnlyLongWrapper unreadCount = new ReadOnlyLongWrapper(0);
@ -136,7 +136,7 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
// Constructors
//================================================================================
public MFXNotificationCenter() {
virtualFlow = new SimpleVirtualFlow<>(
listView = new MFXListView<>(
notifications,
notification -> new MFXNotificationCell(this, notification),
Orientation.VERTICAL
@ -170,12 +170,12 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
unreadCount.bind(unreadCountBinding);
notifications.addListener((ListChangeListener<? super INotification>) change -> {
if (!selectionModel.getSelection().isEmpty()) {
if (!selectionModel.isEmpty()) {
if (change.getList().isEmpty()) {
selectionModel.clearSelection();
} else {
Change c = ListChangeHelper.processChange(change, IntegerRange.of(0, Integer.MAX_VALUE));
ListChangeProcessor updater = new ListChangeProcessor(selectionModel.getSelection().keySet());
ListChangeProcessor updater = new ListChangeProcessor(selectionModel.selection().keySet());
c.processReplacement((changed, removed) -> selectionModel.replaceSelection(changed.toArray(new Integer[0])));
c.processAddition((from, to, added) -> {
updater.computeAddition(added.size(), from);
@ -288,7 +288,7 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
.get();
contextMenu = MFXContextMenu.Builder.build(virtualFlow)
contextMenu = MFXContextMenu.Builder.build(listView)
.addSeparator(new Label(I18N.getOrDefault("notificationCenter.contextMenu.selectionSeparator")))
.addItems(selectAll, selectRead, selectUnread, clearSelection)
.addSeparator(new Label(I18N.getOrDefault("notificationCenter.contextMenu.sortingSeparator")))
@ -340,7 +340,7 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
markNotificationsAs(
state,
getCells().values().stream()
.map(MFXNotificationCell::getNotification)
.map(MFXNotificationCell::getItem)
.toArray(INotification[]::new)
);
}
@ -349,7 +349,7 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
* Sets all the selected notifications' state to the given state.
*/
public void markSelectedNotificationsAs(NotificationState state) {
markNotificationsAs(state, selectionModel.getSelection().values().toArray(INotification[]::new));
markNotificationsAs(state, selectionModel.selection().values().toArray(INotification[]::new));
}
/**
@ -373,14 +373,14 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
* Sets all the visible notifications' state to READ, then removes them from the notifications list.
*/
public void dismissVisible() {
dismiss(getCells().values().stream().map(MFXNotificationCell::getNotification).toArray(INotification[]::new));
dismiss(getCells().values().stream().map(MFXNotificationCell::getItem).toArray(INotification[]::new));
}
/**
* Sets all the selected notifications' state to READ, then removes them from the notifications list.
*/
public void dismissSelected() {
dismiss(getSelectionModel().getSelection().values().toArray(INotification[]::new));
dismiss(getSelectionModel().selection().values().toArray(INotification[]::new));
}
/**
@ -397,14 +397,14 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
/**
* @return the list of notifications
*/
public TransformableListWrapper<INotification> getNotifications() {
public RefineList<INotification> getNotifications() {
return notifications;
}
/**
* @return the selection model instance used to keep track of selected notifications
*/
public MultipleSelectionModel<INotification> getSelectionModel() {
public ISelectionModel<INotification> getSelectionModel() {
return selectionModel;
}
@ -615,95 +615,40 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
// Delegate Methods
//================================================================================
/**
* Delegate method for {@link SimpleVirtualFlow#getCell(int)}.
*/
public MFXNotificationCell getCell(int index) {
return virtualFlow.getCell(index);
return listView.getCellsByIndexUnmodifiable().get(index);
}
/**
* Delegate method for {@link SimpleVirtualFlow#getCells()}.
*/
public Map<Integer, MFXNotificationCell> getCells() {
return virtualFlow.getCells();
return listView.getCellsByIndexUnmodifiable();
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollBy(double)}.
*/
public void scrollBy(double pixels) {
virtualFlow.scrollBy(pixels);
listView.scrollBy(pixels);
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollTo(int)}.
*/
public void scrollTo(int index) {
virtualFlow.scrollTo(index);
listView.scrollToIndex(index);
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToFirst()}.
*/
public void scrollToFirst() {
virtualFlow.scrollToFirst();
listView.scrollToFirst();
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToLast()}.
*/
public void scrollToLast() {
virtualFlow.scrollToLast();
listView.scrollToLast();
}
/**
* Delegate method for {@link SimpleVirtualFlow#scrollToPixel(double)}.
*/
public void scrollToPixel(double pixel) {
virtualFlow.scrollToPixel(pixel);
listView.scrollToPixel(pixel);
}
/**
* Delegate method for {@link SimpleVirtualFlow#setHSpeed(double, double)}.
*/
public void setHSpeed(double unit, double block) {
virtualFlow.setHSpeed(unit, block);
}
/**
* Delegate method for {@link SimpleVirtualFlow#setVSpeed(double, double)}.
*/
public void setVSpeed(double unit, double block) {
virtualFlow.setVSpeed(unit, block);
}
/**
* Delegate method for {@link SimpleVirtualFlow#getVerticalPosition()}.
*/
public double getVerticalPosition() {
return virtualFlow.getVerticalPosition();
}
/**
* Delegate method for {@link SimpleVirtualFlow#getHorizontalPosition()}.
*/
public double getHorizontalPosition() {
return virtualFlow.getHorizontalPosition();
}
/**
* Delegate method for {@link SimpleVirtualFlow#setCellFactory(Function)}.
*/
public void setCellFactory(Function<INotification, MFXNotificationCell> cellFactory) {
virtualFlow.setCellFactory(cellFactory);
listView.setCellFactory(cellFactory);
}
/**
* Delegate method for {@link SimpleVirtualFlow#features()}.
*/
public SimpleVirtualFlow<INotification, MFXNotificationCell>.Features features() {
return virtualFlow.features();
public void setCellHeight(double height) {
listView.setCellSize(height);
}
//================================================================================
@ -722,6 +667,6 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl, Th
@Override
protected Skin<?> createDefaultSkin() {
return new MFXNotificationCenterSkin(this, virtualFlow);
return new MFXNotificationCenterSkin(this, listView);
}
}

View File

@ -18,19 +18,6 @@
package io.github.palexdev.materialfx.controls;
import io.github.palexdev.materialfx.skins.MFXPaginatedTableViewSkin;
import io.github.palexdev.materialfx.utils.NumberUtils;
import javafx.beans.InvalidationListener;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.scene.control.Skin;
import javafx.scene.input.ScrollEvent;
/**
* This is the implementation of a paginated {@link MFXTableView}.
* <p>
@ -47,7 +34,7 @@ import javafx.scene.input.ScrollEvent;
* @param <T> The type of the data within the table.
*/
public class MFXPaginatedTableView<T> extends MFXTableView<T> {
//================================================================================
/* //================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-paginated-table-view";
@ -96,20 +83,20 @@ public class MFXPaginatedTableView<T> extends MFXTableView<T> {
currentPageProperty().addListener(invalidated -> goToPage(getCurrentPage()));
}
/**
*//**
* Goes to the given page index.
* <p>
* The given integer is clamped between 1 and the max page index.
*/
*//*
public void goToPage(int index) {
int page = NumberUtils.clamp(index, 1, getMaxPage());
double pos = (page - 1) * getRowsPerPage() * rowsFlow.getCellHeight();
rowsFlow.getVBar().setValue(pos);
}
/**
*//**
* Responsible for updating the max page index when needed.
*/
*//*
private void updateMaxPages() {
int size = getTransformableList().size();
int rowsPerPage = getRowsPerPage();
@ -121,41 +108,41 @@ public class MFXPaginatedTableView<T> extends MFXTableView<T> {
// Overridden Methods
//================================================================================
/**
*//**
* Unsupported by the paginated table view.
*/
*//*
@Override
public void scrollBy(double pixels) {
throw new UnsupportedOperationException("The paginated table view cannot scroll ny pixels");
}
/**
*//**
* Calls {@link #goToPage(int)}.
*/
*//*
@Override
public void scrollTo(int index) {
goToPage(index);
}
/**
*//**
* Goes to the first page.
*/
*//*
@Override
public void scrollToFirst() {
goToPage(1);
}
/**
*//**
* Goes to the last page.
*/
*//*
@Override
public void scrollToLast() {
goToPage(getMaxPage());
}
/**
*//**
* Unsupported by the paginated table view.
*/
*//*
@Override
public void scrollToPixel(double pixel) {
throw new UnsupportedOperationException("The paginated table view cannot scroll to pixel");
@ -173,9 +160,9 @@ public class MFXPaginatedTableView<T> extends MFXTableView<T> {
return currentPage.get();
}
/**
*//**
* Specifies the current shown page.
*/
*//*
public IntegerProperty currentPageProperty() {
return currentPage;
}
@ -188,9 +175,9 @@ public class MFXPaginatedTableView<T> extends MFXTableView<T> {
return maxPage.get();
}
/**
*//**
* Specifies the last page index.
*/
*//*
public ReadOnlyIntegerProperty maxPageProperty() {
return maxPage.getReadOnlyProperty();
}
@ -203,10 +190,10 @@ public class MFXPaginatedTableView<T> extends MFXTableView<T> {
return pagesToShow.get();
}
/**
*//**
* Specifies how many pages can be shown at a time by the
* {@link MFXPagination} control used in the skin.
*/
*//*
public IntegerProperty pagesToShowProperty() {
return pagesToShow;
}
@ -219,9 +206,9 @@ public class MFXPaginatedTableView<T> extends MFXTableView<T> {
return rowsPerPage.get();
}
/**
*//**
* Specifies how many rows the table can show per page.
*/
*//*
public IntegerProperty rowsPerPageProperty() {
return rowsPerPage;
}
@ -229,4 +216,5 @@ public class MFXPaginatedTableView<T> extends MFXTableView<T> {
public void setRowsPerPage(int rowsPerPage) {
this.rowsPerPage.set(rowsPerPage);
}
*/
}

View File

@ -18,6 +18,12 @@
package io.github.palexdev.materialfx.controls;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
import io.github.palexdev.materialfx.controls.base.Themable;
@ -32,12 +38,6 @@ import javafx.scene.Parent;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* This is the implementation of a smart, material pagination control in JavaFX.
* <p>
@ -187,7 +187,7 @@ public class MFXPagination extends Control implements Themable {
lastMax++;
after = false;
} else {
indexes.add(0, lastMin);
indexes.addFirst(lastMin);
lastMin--;
after = true;
}

View File

@ -1,285 +0,0 @@
/*
* 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.beans.PositionBean;
import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
import io.github.palexdev.virtualizedfx.cell.Cell;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import java.util.LinkedList;
import java.util.List;
/**
* This is the HBox that contains the table row cells built by each column.
* <p></p>
* The new implementation of the table view makes use of {@link SimpleVirtualFlow} to contain
* the rows, this makes the table view super efficient, that's also why the new {@code MFXTableRow}
* implements {@link Cell}.
* <p></p>
* {@link MFXTableRowCell} though, are not reusable {@link Cell}s. So, to keep things efficient table rows
* now build the cells only once (or when needed by the table view) and simply updates them when the {@link #dataProperty()} changes,
* by using {@link #updateCells(Object)}. This mechanism should also simplify working with non JavaFX models (which do not use observables).
*/
public class MFXTableRow<T> extends HBox implements Cell<T> {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-table-row";
private final MFXTableView<T> tableView;
private final ObservableList<MFXTableRowCell<T, ?>> cells = FXCollections.observableArrayList();
private final ReadOnlyIntegerWrapper index = new ReadOnlyIntegerWrapper();
private final ReadOnlyObjectWrapper<T> data = new ReadOnlyObjectWrapper<>();
protected final MFXCircleRippleGenerator rippleGenerator = new MFXCircleRippleGenerator(this);
private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
protected static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
//================================================================================
// Constructors
//================================================================================
public MFXTableRow(MFXTableView<T> tableView, T data) {
this.tableView = tableView;
setData(data);
setMinHeight(USE_PREF_SIZE);
setPrefHeight(32);
setMaxHeight(USE_PREF_SIZE);
initialize();
buildCells();
}
//================================================================================
// Methods
//================================================================================
private void initialize() {
getStyleClass().add(STYLE_CLASS);
setBehavior();
setupRippleGenerator();
}
/**
* Adds the needed listeners/handlers to manage the selection state.
*
* @see #updateSelection(MouseEvent).
*/
private void setBehavior() {
selected.addListener(invalidated -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get()));
selected.bind(Bindings.createBooleanBinding(
() -> tableView.getSelectionModel().getSelection().containsKey(getIndex()),
tableView.getSelectionModel().selectionProperty(), index
));
addEventFilter(MouseEvent.MOUSE_CLICKED, this::updateSelection);
}
/**
* Initializes the ripple generator.
*/
protected void setupRippleGenerator() {
rippleGenerator.setManaged(false);
rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY()));
rippleGenerator.rippleRadiusProperty().bind(widthProperty().divide(2.0));
addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getButton() == MouseButton.PRIMARY) {
rippleGenerator.generateRipple(event);
}
});
}
/**
* Public API to update the row's cells.
* <p>
* Simply calls {@link #updateRow(Object)} given that
* the current {@link #dataProperty()} is not null.
*/
public void updateRow() {
T data = getData();
if (data == null) return;
updateRow(data);
}
/**
* Called by the {@link #updateItem(Object)} method.
* Responsible for updating the cells, {@link #updateCells(Object)}, or building them
* if not yet done, {@link #buildCells()}.
*/
protected void updateRow(T data) {
if (cells.isEmpty()) {
buildCells();
} else {
updateCells(data);
}
}
/**
* Responsible for updating the row cells by calling {@link MFXTableRowCell#update(Object)}.
*/
protected void updateCells(T data) {
cells.forEach(cell -> cell.update(data));
}
/**
* Responsible for building the row's cells when needed.
* <p>
* For each column specified by the table view, {@link MFXTableView#getTableColumns()}, retrieves the
* {@link MFXTableColumn#rowCellFactoryProperty()}, build the cell with the row's data, {@link #dataProperty()},
* updates the cell, {@link MFXTableRowCell#update(Object)}, then adds to the list.
* At the end calls {@link #updateChildren(List)} with the built cells list.
* <p></p>
* If the row's data is null, exits immediately.
*/
public void buildCells() {
T data = getData();
if (data == null) return;
if (!cells.isEmpty()) cells.clear();
ObservableList<MFXTableColumn<T>> columns = tableView.getTableColumns();
for (MFXTableColumn<T> column : columns) {
MFXTableRowCell<T, ?> cell = column.getRowCellFactory().apply(data);
cell.update(data);
cells.add(cell);
}
updateChildren(cells);
}
/**
* Responsible for populating the row with the given children list.
* Since the row also has a ripple generator, this is added at the start of the given list.
*/
private void updateChildren(List<MFXTableRowCell<T, ?>> children) {
List<Node> finalList = new LinkedList<>(children);
finalList.add(0, rippleGenerator);
getChildren().setAll(finalList);
}
/**
* Responsible for handling the selection triggered by a {@link MouseEvent}.
* <p>
* According to the index and the selection state this can: deselect the row,
* add the row to the selection model, replace the selection with only this row.
*/
private void updateSelection(MouseEvent event) {
if (event.getButton() != MouseButton.PRIMARY) return;
int index = getIndex();
if (event.isControlDown()) {
if (isSelected()) {
tableView.getSelectionModel().deselectIndex(index);
} else {
tableView.getSelectionModel().selectIndex(index);
}
return;
}
if (event.isShiftDown()) {
tableView.getSelectionModel().expandSelection(index);
return;
}
tableView.getSelectionModel().replaceSelection(index);
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public Node getNode() {
return this;
}
@Override
public void updateItem(T data) {
setData(data);
updateRow(data);
}
@Override
public void updateIndex(int index) {
setIndex(tableView.getTransformableList().viewToSource(index));
}
//================================================================================
// Getters/Setters
//================================================================================
/**
* @return the row's cells as an unmodifiable observable list
*/
public ObservableList<MFXTableRowCell<T, ?>> getCells() {
return FXCollections.unmodifiableObservableList(cells);
}
public int getIndex() {
return index.get();
}
/**
* Specifies the row's index.
*/
public ReadOnlyIntegerProperty indexProperty() {
return index.getReadOnlyProperty();
}
protected void setIndex(int index) {
this.index.set(index);
}
public T getData() {
return data.get();
}
/**
* Specifies the item represented by the row.
*/
public ReadOnlyObjectProperty<T> dataProperty() {
return data.getReadOnlyProperty();
}
protected void setData(T data) {
this.data.set(data);
}
public boolean isSelected() {
return selected.get();
}
/**
* Specifies the selection state of the row.
*/
public ReadOnlyBooleanProperty selectedProperty() {
return selected.getReadOnlyProperty();
}
protected void setSelected(boolean selected) {
this.selected.set(selected);
}
}

View File

@ -1,412 +1,446 @@
/*
* 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.beans.properties.functional.FunctionProperty;
import io.github.palexdev.materialfx.collections.TransformableList;
import io.github.palexdev.materialfx.collections.TransformableListWrapper;
import java.util.List;
import java.util.Map;
import java.util.SequencedMap;
import java.util.function.Function;
import io.github.palexdev.materialfx.collections.RefineList;
import io.github.palexdev.materialfx.controls.base.Themable;
import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
import io.github.palexdev.materialfx.filter.base.AbstractFilter;
import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.selection.SelectionModel;
import io.github.palexdev.materialfx.selection.base.WithSelectionModel;
import io.github.palexdev.materialfx.controls.cell.MFXTableRow;
import io.github.palexdev.materialfx.skins.MFXTableViewSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import io.github.palexdev.materialfx.utils.ListChangeHelper;
import io.github.palexdev.materialfx.utils.ListChangeProcessor;
import io.github.palexdev.materialfx.utils.others.observables.When;
import io.github.palexdev.mfxcore.base.beans.Size;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import javafx.beans.InvalidationListener;
import javafx.beans.property.*;
import io.github.palexdev.mfxcore.base.properties.functional.FunctionProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableIntegerProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableObjectProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableSizeProperty;
import io.github.palexdev.virtualizedfx.base.VFXStyleable;
import io.github.palexdev.virtualizedfx.cells.base.VFXTableCell;
import io.github.palexdev.virtualizedfx.enums.BufferSize;
import io.github.palexdev.virtualizedfx.enums.ColumnsLayoutMode;
import io.github.palexdev.virtualizedfx.properties.CellFactory;
import io.github.palexdev.virtualizedfx.table.*;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Parent;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import java.util.*;
import java.util.function.Function;
// TODO prefer composition over inheritance for virtualized components
public class MFXTableView<T> extends Control implements WithSelectionModel<T>, Themable, VFXStyleable {
//================================================================================
// Properties
//================================================================================
protected final VFXTable<T> vfxTable;
/**
* This is the implementation of a table view following Google's material design guidelines in JavaFX.
* <p>
* Extends {@code Control} and provides a new skin since it is built from scratch.
*
* @param <T> The type of the data within the table.
* @see MFXTableViewSkin
*/
public class MFXTableView<T> extends Control implements Themable {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-table-view";
protected final SimpleVirtualFlow<T, MFXTableRow<T>> rowsFlow;
protected final ReadOnlyBooleanWrapper virtualFlowInitialized = new ReadOnlyBooleanWrapper();
private final RefineList<T> refineList;
private final ObservableList<AbstractFilter<T, ?>> filters = FXCollections.observableArrayList();
private final ObjectProperty<ObservableList<T>> items = new SimpleObjectProperty<>();
private final ListChangeListener<? super T> itemsChanged = this::itemsChanged;
private final ISelectionModel<T> selectionModel;
private final IMultipleSelectionModel<T> selectionModel = new MultipleSelectionModel<>(items);
private final ObservableList<MFXTableColumn<T>> tableColumns = FXCollections.observableArrayList();
private final FunctionProperty<T, MFXTableRow<T>> tableRowFactory = new FunctionProperty<>(item -> new MFXTableRow<>(this, item));
//================================================================================
// Constructors
//================================================================================
public MFXTableView() {
this(FXCollections.observableArrayList());
}
private final TransformableListWrapper<T> transformableList = new TransformableListWrapper<>(FXCollections.observableArrayList());
private final ObservableList<AbstractFilter<T, ?>> filters = FXCollections.observableArrayList();
private final InvalidationListener itemsInvalid = invalidated -> transformableList.setAll(getItems());
private final BooleanProperty footerVisible = new SimpleBooleanProperty(true);
public MFXTableView(ObservableList<T> items) {
refineList = new RefineList<>(items);
selectionModel = new SelectionModel<>(refineList);
vfxTable = new VFXTable<>(refineList) {
@Override
protected Function<T, VFXTableRow<T>> defaultRowFactory() {
return MFXTableRow::new;
}
};
initialize();
}
//================================================================================
// Constructors
//================================================================================
public MFXTableView() {
this(FXCollections.observableArrayList());
}
//================================================================================
// Methods
//================================================================================
private void initialize() {
getStyleClass().setAll(defaultStyleClasses());
sceneBuilderIntegration();
autosizeColumns();
}
public MFXTableView(ObservableList<T> items) {
setItems(items);
rowsFlow = new SimpleVirtualFlow<>(
transformableList,
getTableRowFactory(),
Orientation.VERTICAL
);
rowsFlow.cellFactoryProperty().bind(tableRowFactoryProperty());
VBox.setVgrow(rowsFlow, Priority.ALWAYS);
//================================================================================
// Overridden Methods
//================================================================================
@Override
public ISelectionModel<T> getSelectionModel() {
return selectionModel;
}
initialize();
}
@Override
public Parent toParent() {
return this;
}
//================================================================================
// Methods
//================================================================================
private void initialize() {
getStyleClass().add(STYLE_CLASS);
@Override
public Theme getTheme() {
return MaterialFXStylesheets.TABLE_VIEW;
}
transformableList.setAll(getItems());
itemsProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.removeListener(itemsChanged);
oldValue.removeListener(itemsInvalid);
}
if (newValue != null) {
newValue.addListener(itemsChanged);
newValue.addListener(itemsInvalid);
transformableList.setAll(newValue);
}
});
@Override
public List<String> defaultStyleClasses() {
return List.of("mfx-table-view");
}
getItems().addListener(itemsChanged);
getItems().addListener(itemsInvalid);
sceneBuilderIntegration();
}
@Override
protected Skin<?> createDefaultSkin() {
return new MFXTableViewSkin<>(this);
}
/**
* Responsible for updating the selection when the items list changes.
*/
protected void itemsChanged(ListChangeListener.Change<? extends T> change) {
IMultipleSelectionModel<T> selectionModel = getSelectionModel();
if (selectionModel.getSelection().isEmpty()) return;
//================================================================================
// Delegate Methods
//================================================================================
public VFXTable<T> getVirtualizedContainer() {
return vfxTable;
}
if (change.getList().isEmpty()) {
selectionModel.clearSelection();
return;
}
public void autosizeColumn(int index) {
vfxTable.autosizeColumn(index);
}
ListChangeHelper.Change c = ListChangeHelper.processChange(change, IntegerRange.of(0, Integer.MAX_VALUE));
ListChangeProcessor updater = new ListChangeProcessor(new HashSet<>(selectionModel.getSelection().keySet()));
c.processReplacement((changed, removed) -> selectionModel.replaceSelection(changed.toArray(Integer[]::new)));
c.processAddition((from, to, added) -> {
updater.computeAddition(added.size(), from);
selectionModel.replaceSelection(updater.getIndexes().toArray(Integer[]::new));
});
c.processRemoval((from, to, removed) -> {
updater.computeRemoval(removed, from);
getSelectionModel().replaceSelection(updater.getIndexes().toArray(Integer[]::new));
});
}
public void setVPos(double vPos) {
vfxTable.setVPos(vPos);
}
/**
* Allows to programmatically update the table.
* <p>
* Uses {@link MFXTableRow#updateRow()} on the currently built rows, {@link SimpleVirtualFlow#getCells()}.
*/
public void update() {
rowsFlow.getCells().values().forEach(MFXTableRow::updateRow);
}
public ReadOnlyDoubleProperty virtualMaxYProperty() {
return vfxTable.virtualMaxYProperty();
}
/**
* Autosize all the table columns.
*/
public void autosizeColumns() {
tableColumns.forEach(this::autosizeColumn);
}
public BufferSize getRowsBufferSize() {
return vfxTable.getRowsBufferSize();
}
/**
* Autosizes the column at the given index.
* <p>
* This method fails silently if it can not get the column at index.
*/
public void autosizeColumn(int index) {
try {
MFXTableColumn<T> column = tableColumns.get(index);
autosizeColumn(column);
} catch (Exception ignored) {
}
}
public void scrollToLastColumn() {
vfxTable.scrollToLastColumn();
}
/**
* Autosizes the given column.
*/
public void autosizeColumn(MFXTableColumn<T> column) {
int index = tableColumns.indexOf(column);
if (index == -1) return;
public void autosizeColumns() {
vfxTable.autosizeColumns();
}
Collection<MFXTableRow<T>> rows = rowsFlow.getCells().values();
List<Double> minSizes = new ArrayList<>();
minSizes.add(column.getWidth());
rows.forEach(row -> {
ObservableList<MFXTableRowCell<T, ?>> rowCells = row.getCells();
if (rowCells.isEmpty()) return;
MFXTableRowCell<T, ?> rowCell = rowCells.get(index);
rowCell.requestLayout();
minSizes.add(rowCell.computePrefWidth(-1));
});
double max = minSizes.stream().max(Double::compareTo).orElse(-1.0);
if (max != -1.0) {
column.setMinWidth(max);
}
}
public Function<ColumnsLayoutMode, VFXTableHelper<T>> getHelperFactory() {
return vfxTable.getHelperFactory();
}
/**
* This should be called only if you need to autosize the columns
* before the table is laid out/initialized.
* <p>
* Calling this afterwards won't have any effect.
*/
public void autosizeColumnsOnInitialization() {
if (isVirtualFlowInitialized()) return;
When.onChanged(virtualFlowInitialized)
.then((oldValue, newValue) -> autosizeColumns())
.oneShot()
.listen();
}
public StyleableObjectProperty<BufferSize> columnsBufferSizeProperty() {
return vfxTable.columnsBufferSizeProperty();
}
//================================================================================
// Delegate Methods
//================================================================================
public ReadOnlyObjectWrapper<VFXTableHelper<T>> helperProperty() {
return vfxTable.helperProperty();
}
/**
* Delegate for {@link SimpleVirtualFlow#getCell(int)}.
*/
public MFXTableRow<T> getCell(int index) {
return rowsFlow.getCell(index);
}
public ViewportLayoutRequest<T> getViewportLayoutRequest() {
return vfxTable.getViewportLayoutRequest();
}
/**
* Delegate for {@link SimpleVirtualFlow#getCells()}.
*/
public Map<Integer, MFXTableRow<T>> getCells() {
return rowsFlow.getCells();
}
public void scrollToPixelHorizontal(double pixel) {
vfxTable.scrollToPixelHorizontal(pixel);
}
/**
* Delegate for {@link SimpleVirtualFlow#scrollBy(double)}.
*/
public void scrollBy(double pixels) {
rowsFlow.scrollBy(pixels);
}
public int indexOf(VFXTableColumn<T, ?> column) {
return vfxTable.indexOf(column);
}
/**
* Delegate for {@link SimpleVirtualFlow#scrollTo(int)}.
*/
public void scrollTo(int index) {
rowsFlow.scrollTo(index);
}
public IntegerRange getColumnsRange() {
return vfxTable.getColumnsRange();
}
/**
* Delegate for {@link SimpleVirtualFlow#scrollToFirst()}.
*/
public void scrollToFirst() {
rowsFlow.scrollToFirst();
}
public void setExtraAutosizeWidth(double extraAutosizeWidth) {
vfxTable.setExtraAutosizeWidth(extraAutosizeWidth);
}
/**
* Delegate for {@link SimpleVirtualFlow#scrollToLast()}.
*/
public void scrollToLast() {
rowsFlow.scrollToLast();
}
public BufferSize getBufferSize() {
return vfxTable.getBufferSize();
}
/**
* Delegate for {@link SimpleVirtualFlow#scrollToPixel(double)}.
*/
public void scrollToPixel(double pixel) {
rowsFlow.scrollToPixel(pixel);
}
public double getClipBorderRadius() {
return vfxTable.getClipBorderRadius();
}
/**
* Delegate for {@link SimpleVirtualFlow#setHSpeed(double, double)}.
*/
public void setHSpeed(double unit, double block) {
rowsFlow.setHSpeed(unit, block);
}
public double getRowsHeight() {
return vfxTable.getRowsHeight();
}
/**
* Delegate for {@link SimpleVirtualFlow#setVSpeed(double, double)}.
*/
public void setVSpeed(double unit, double block) {
rowsFlow.setVSpeed(unit, block);
}
public VFXTable<T> populateCacheAll() {
return vfxTable.populateCacheAll();
}
/**
* Delegate for {@link SimpleVirtualFlow#features()}.
*/
public SimpleVirtualFlow<T, MFXTableRow<T>>.Features features() {
return rowsFlow.features();
}
public void scrollToFirstColumn() {
vfxTable.scrollToFirstColumn();
}
//================================================================================
// Overridden Methods
//================================================================================
public VFXTableState<T> getState() {
return vfxTable.getState();
}
@Override
public Parent toParent() {
return this;
}
public void scrollToRow(int index) {
vfxTable.scrollToRow(index);
}
@Override
public Theme getTheme() {
return MaterialFXStylesheets.TABLE_VIEW;
}
public void setRowsCacheCapacity(int rowsCacheCapacity) {
vfxTable.setRowsCacheCapacity(rowsCacheCapacity);
}
@Override
protected Skin<?> createDefaultSkin() {
return new MFXTableViewSkin<>(this, rowsFlow);
}
public BufferSize getColumnsBufferSize() {
return vfxTable.getColumnsBufferSize();
}
@Override
protected void layoutChildren() {
super.layoutChildren();
if (!isVirtualFlowInitialized() && rowsFlow.getCellHeight() > 0) virtualFlowInitialized.set(true);
}
public double getHPos() {
return vfxTable.getHPos();
}
//================================================================================
// Getters/Setters
//================================================================================
public ObservableList<T> getItems() {
return items.get();
}
public ReadOnlyDoubleProperty virtualMaxXProperty() {
return vfxTable.virtualMaxXProperty();
}
/**
* Specifies the table's {@link ObservableList} containing the items.
*/
public ObjectProperty<ObservableList<T>> itemsProperty() {
return items;
}
public void setColumnsWidth(double w) {
vfxTable.setColumnsWidth(w);
}
public void setItems(ObservableList<T> items) {
this.items.set(items);
}
public VFXTableHelper<T> getHelper() {
return vfxTable.getHelper();
}
/**
* @return the selection model used by the table to handle row selection
*/
public IMultipleSelectionModel<T> getSelectionModel() {
return selectionModel;
}
public IntegerRange getRowsRange() {
return vfxTable.getRowsRange();
}
/**
* @return the list containing the table's columns
*/
public ObservableList<MFXTableColumn<T>> getTableColumns() {
return tableColumns;
}
public StyleableDoubleProperty extraAutosizeWidthProperty() {
return vfxTable.extraAutosizeWidthProperty();
}
public Function<T, MFXTableRow<T>> getTableRowFactory() {
return tableRowFactory.get();
}
public void setColumnsSize(Size columnsSize) {
vfxTable.setColumnsSize(columnsSize);
}
/**
* Specifies the {@link Function} used to generate the table rows.
*/
public FunctionProperty<T, MFXTableRow<T>> tableRowFactoryProperty() {
return tableRowFactory;
}
public void setRowFactory(Function<T, VFXTableRow<T>> rowFactory) {
vfxTable.setRowFactory(rowFactory);
}
public void setTableRowFactory(Function<T, MFXTableRow<T>> tableRowFactory) {
this.tableRowFactory.set(tableRowFactory);
}
public VFXTable<T> populateCache() {
return vfxTable.populateCache();
}
/**
* @return the list that is effectively used by the {@link SimpleVirtualFlow} (which contains the table rows).
* This list is capable of filtering and sorting.
* @see TransformableListWrapper
* @see TransformableList
*/
public TransformableListWrapper<T> getTransformableList() {
return transformableList;
}
public ReadOnlyObjectProperty<VFXTableState<T>> stateProperty() {
return vfxTable.stateProperty();
}
/**
* @return the list containing the filters' information used by the
* {@link MFXFilterPane} to filter the table
*/
public ObservableList<AbstractFilter<T, ?>> getFilters() {
return filters;
}
public void scrollVerticalBy(double pixels) {
vfxTable.scrollVerticalBy(pixels);
}
public boolean isFooterVisible() {
return footerVisible.get();
}
public boolean isNeedsViewportLayout() {
return vfxTable.isNeedsViewportLayout();
}
/**
* Specifies whether the table's footer is visible
*/
public BooleanProperty footerVisibleProperty() {
return footerVisible;
}
public void scrollToLastRow() {
vfxTable.scrollToLastRow();
}
public void setFooterVisible(boolean footerVisible) {
this.footerVisible.set(footerVisible);
}
public void setColumnsHeight(double h) {
vfxTable.setColumnsHeight(h);
}
public boolean isVirtualFlowInitialized() {
return virtualFlowInitialized.get();
}
public Function<T, VFXTableRow<T>> getRowFactory() {
return vfxTable.getRowFactory();
}
/**
* Useful property to inform that the table layout
* has been initialized/is ready.
* <p>
* For example it is used by {@link #autosizeColumnsOnInitialization()}
* to autosize the columns before the table is even laid out by using a
* listener.
* <p>
* It is considered initialized as soon as the {@link SimpleVirtualFlow}
* retrieves the cells' height.
*/
public ReadOnlyBooleanProperty virtualFlowInitializedProperty() {
return virtualFlowInitialized.getReadOnlyProperty();
}
public double getVPos() {
return vfxTable.getVPos();
}
public void setRowsBufferSize(BufferSize rowsBufferSize) {
vfxTable.setRowsBufferSize(rowsBufferSize);
}
public DoubleProperty hPosProperty() {
return vfxTable.hPosProperty();
}
public ReadOnlyDoubleProperty maxHScrollProperty() {
return vfxTable.maxHScrollProperty();
}
public void setColumnsSize(double w, double h) {
vfxTable.setColumnsSize(w, h);
}
public List<Map.Entry<T, VFXTableRow<T>>> getRowsByItemUnmodifiable() {
return vfxTable.getRowsByItemUnmodifiable();
}
public void scrollToColumn(int index) {
vfxTable.scrollToColumn(index);
}
public void setHelperFactory(Function<ColumnsLayoutMode, VFXTableHelper<T>> helperFactory) {
vfxTable.setHelperFactory(helperFactory);
}
public double getExtraAutosizeWidth() {
return vfxTable.getExtraAutosizeWidth();
}
public void setBufferSize(BufferSize bufferSize) {
vfxTable.setBufferSize(bufferSize);
}
public int cellsCacheSize() {
return vfxTable.cellsCacheSize();
}
public void setClipBorderRadius(double clipBorderRadius) {
vfxTable.setClipBorderRadius(clipBorderRadius);
}
public StyleableSizeProperty columnsSizeProperty() {
return vfxTable.columnsSizeProperty();
}
public StyleableIntegerProperty rowsCacheCapacityProperty() {
return vfxTable.rowsCacheCapacityProperty();
}
public StyleableObjectProperty<BufferSize> bufferSizeProperty() {
return vfxTable.bufferSizeProperty();
}
public void setColumnsLayoutMode(ColumnsLayoutMode columnsLayoutMode) {
vfxTable.setColumnsLayoutMode(columnsLayoutMode);
}
public CellFactory<T, VFXTableRow<T>> rowFactoryProperty() {
return vfxTable.rowFactoryProperty();
}
public ObservableList<VFXTableColumn<T, ? extends VFXTableCell<T>>> getColumns() {
return vfxTable.getColumns();
}
public void scrollHorizontalBy(double pixels) {
vfxTable.scrollHorizontalBy(pixels);
}
public void setRowsHeight(double rowsHeight) {
vfxTable.setRowsHeight(rowsHeight);
}
public void scrollToFirstRow() {
vfxTable.scrollToFirstRow();
}
public DoubleProperty vPosProperty() {
return vfxTable.vPosProperty();
}
public void update(int... indexes) {
vfxTable.update(indexes);
}
public ReadOnlyDoubleProperty maxVScrollProperty() {
return vfxTable.maxVScrollProperty();
}
public StyleableObjectProperty<BufferSize> rowsBufferSizeProperty() {
return vfxTable.rowsBufferSizeProperty();
}
public ColumnsLayoutMode getColumnsLayoutMode() {
return vfxTable.getColumnsLayoutMode();
}
public SequencedMap<Integer, VFXTableRow<T>> getRowsByIndexUnmodifiable() {
return vfxTable.getRowsByIndexUnmodifiable();
}
public void setHPos(double hPos) {
vfxTable.setHPos(hPos);
}
public void autosizeColumn(VFXTableColumn<T, ?> column) {
vfxTable.autosizeColumn(column);
}
public void setColumnsBufferSize(BufferSize columnsBufferSize) {
vfxTable.setColumnsBufferSize(columnsBufferSize);
}
public FunctionProperty<ColumnsLayoutMode, VFXTableHelper<T>> helperFactoryProperty() {
return vfxTable.helperFactoryProperty();
}
public int getRowsCacheCapacity() {
return vfxTable.getRowsCacheCapacity();
}
public void scrollToPixelVertical(double pixel) {
vfxTable.scrollToPixelVertical(pixel);
}
public Size getColumnsSize() {
return vfxTable.getColumnsSize();
}
public void setHelper(VFXTableHelper<T> helper) {
vfxTable.setHelper(helper);
}
public ReadOnlyObjectProperty<ViewportLayoutRequest<T>> needsViewportLayoutProperty() {
return vfxTable.needsViewportLayoutProperty();
}
public StyleableObjectProperty<ColumnsLayoutMode> columnsLayoutModeProperty() {
return vfxTable.columnsLayoutModeProperty();
}
public void requestViewportLayout() {
vfxTable.requestViewportLayout();
}
public StyleableDoubleProperty rowsHeightProperty() {
return vfxTable.rowsHeightProperty();
}
public void switchColumnsLayoutMode() {
vfxTable.switchColumnsLayoutMode();
}
public int rowsCacheSize() {
return vfxTable.rowsCacheSize();
}
public StyleableDoubleProperty clipBorderRadiusProperty() {
return vfxTable.clipBorderRadiusProperty();
}
//================================================================================
// Getters
//================================================================================
public ObservableList<T> getItems() {
return refineList;
}
public ObservableList<AbstractFilter<T, ?>> getFilters() {
return filters;
}
}

View File

@ -1,308 +0,0 @@
/*
* 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.base;
import io.github.palexdev.materialfx.effects.DepthLevel;
import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
import io.github.palexdev.materialfx.utils.ColorUtils;
import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.*;
import javafx.scene.Parent;
import javafx.scene.control.Control;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.util.Duration;
import javafx.util.StringConverter;
import java.util.List;
/**
* Base class for all list views based on VirtualizedFX, defines common properties and behavior.
*
* @param <T> the type of data within the ListView
*/
public abstract class AbstractMFXListView<T, C extends Cell<T>> extends Control implements IListView<T, C>, Themable {
//================================================================================
// Properties
//================================================================================
protected final ObjectProperty<ObservableList<T>> items = new SimpleObjectProperty<>(FXCollections.observableArrayList());
protected final ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<>();
protected final IMultipleSelectionModel<T> selectionModel = new MultipleSelectionModel<>(items);
//================================================================================
// Constructors
//================================================================================
public AbstractMFXListView() {}
public AbstractMFXListView(ObservableList<T> items) {
setItems(items);
}
//================================================================================
// Abstract Methods
//================================================================================
/**
* Abstract method called automatically to set a default factory for the cells.
*/
protected abstract void setDefaultCellFactory();
//================================================================================
// Methods
//================================================================================
protected void initialize() {
setDefaultCellFactory();
addBarsListeners();
sceneBuilderIntegration();
}
protected void addBarsListeners() {
this.trackColor.addListener((observable, oldValue, newValue) -> {
if (!newValue.equals(oldValue)) {
setColors();
}
});
this.thumbColor.addListener((observable, oldValue, newValue) -> {
if (!newValue.equals(oldValue)) {
setColors();
}
});
this.thumbHoverColor.addListener((observable, oldValue, newValue) -> {
if (!newValue.equals(oldValue)) {
setColors();
}
});
}
/**
* Sets the CSS looked-up colors
*/
protected void setColors() {
StringBuilder sb = new StringBuilder();
sb.append("-mfx-track-color: ").append(ColorUtils.toCss(trackColor.get()))
.append(";\n-mfx-thumb-color: ").append(ColorUtils.toCss(thumbColor.get()))
.append(";\n-mfx-thumb-hover-color: ").append(ColorUtils.toCss(thumbHoverColor.get()))
.append(";");
setStyle(sb.toString());
}
//================================================================================
// ScrollBars Properties
//================================================================================
private final ObjectProperty<Paint> trackColor = new SimpleObjectProperty<>(Color.rgb(230, 230, 230));
private final ObjectProperty<Paint> thumbColor = new SimpleObjectProperty<>(Color.rgb(137, 137, 137));
private final ObjectProperty<Paint> thumbHoverColor = new SimpleObjectProperty<>(Color.rgb(89, 88, 91));
private final ObjectProperty<Duration> hideAfter = new SimpleObjectProperty<>(Duration.seconds(1));
public Paint getTrackColor() {
return trackColor.get();
}
/**
* Specifies the color of the scrollbars' track.
*/
public ObjectProperty<Paint> trackColorProperty() {
return trackColor;
}
public void setTrackColor(Paint trackColor) {
this.trackColor.set(trackColor);
}
public Paint getThumbColor() {
return thumbColor.get();
}
/**
* Specifies the color of the scrollbars' thumb.
*/
public ObjectProperty<Paint> thumbColorProperty() {
return thumbColor;
}
public void setThumbColor(Paint thumbColor) {
this.thumbColor.set(thumbColor);
}
public Paint getThumbHoverColor() {
return thumbHoverColor.get();
}
/**
* Specifies the color of the scrollbars' thumb when mouse hover.
*/
public ObjectProperty<Paint> thumbHoverColorProperty() {
return thumbHoverColor;
}
public void setThumbHoverColor(Paint thumbHoverColor) {
this.thumbHoverColor.set(thumbHoverColor);
}
public Duration getHideAfter() {
return hideAfter.get();
}
/**
* Specifies the time after which the scrollbars are hidden.
*/
public ObjectProperty<Duration> hideAfterProperty() {
return hideAfter;
}
public void setHideAfter(Duration hideAfter) {
this.hideAfter.set(hideAfter);
}
//================================================================================
// Getters/Setters
//================================================================================
@Override
public Parent toParent() {
return this;
}
@Override
public ObservableList<T> getItems() {
return items.get();
}
@Override
public ObjectProperty<ObservableList<T>> itemsProperty() {
return items;
}
@Override
public void setItems(ObservableList<T> items) {
this.items.set(items);
}
@Override
public StringConverter<T> getConverter() {
return converter.get();
}
@Override
public ObjectProperty<StringConverter<T>> converterProperty() {
return converter;
}
@Override
public void setConverter(StringConverter<T> converter) {
this.converter.set(converter);
}
@Override
public IMultipleSelectionModel<T> getSelectionModel() {
return selectionModel;
}
//================================================================================
// Styleable Properties
//================================================================================
private final StyleableBooleanProperty hideScrollBars = new SimpleStyleableBooleanProperty(
StyleableProperties.HIDE_SCROLLBARS,
this,
"hideScrollBars",
false
);
private final StyleableObjectProperty<DepthLevel> depthLevel = new SimpleStyleableObjectProperty<>(
StyleableProperties.DEPTH_LEVEL,
this,
"depthLevel",
DepthLevel.LEVEL2
);
public boolean isHideScrollBars() {
return hideScrollBars.get();
}
/**
* Specifies if the scrollbars should be hidden when the mouse is not on the list.
*/
public StyleableBooleanProperty hideScrollBarsProperty() {
return hideScrollBars;
}
public void setHideScrollBars(boolean hideScrollBars) {
this.hideScrollBars.set(hideScrollBars);
}
public DepthLevel getDepthLevel() {
return depthLevel.get();
}
/**
* Specifies the shadow strength around the control.
*/
public StyleableObjectProperty<DepthLevel> depthLevelProperty() {
return depthLevel;
}
public void setDepthLevel(DepthLevel depthLevel) {
this.depthLevel.set(depthLevel);
}
private static class StyleableProperties {
private static final StyleablePropertyFactory<AbstractMFXListView<?, ?>> FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData());
private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
private static final CssMetaData<AbstractMFXListView<?, ?>, Boolean> HIDE_SCROLLBARS =
FACTORY.createBooleanCssMetaData(
"-mfx-hide-scrollbars",
AbstractMFXListView::hideScrollBarsProperty,
false
);
private static final CssMetaData<AbstractMFXListView<?, ?>, DepthLevel> DEPTH_LEVEL =
FACTORY.createEnumCssMetaData(
DepthLevel.class,
"-mfx-depth-level",
AbstractMFXListView::depthLevelProperty,
DepthLevel.LEVEL2
);
static {
cssMetaDataList = StyleablePropertiesUtils.cssMetaDataList(
Control.getClassCssMetaData(),
HIDE_SCROLLBARS, DEPTH_LEVEL
);
}
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.cssMetaDataList;
}
@Override
protected List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return AbstractMFXListView.getClassCssMetaData();
}
}

View File

@ -1,81 +0,0 @@
/*
* 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.base;
import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import java.util.function.Function;
/**
* Interface that defines the public API for all the listviews based on VirtualizedFX.
*
* @param <T> the type of data within the ListView
* @param <C> the type of cells that will be used
*/
public interface IListView<T, C extends Cell<T>> {
/**
* @return the items observable list
*/
ObservableList<T> getItems();
/**
* The items list property.
*/
ObjectProperty<ObservableList<T>> itemsProperty();
/**
* Replaces the items list with the given one.
*/
void setItems(ObservableList<T> items);
StringConverter<T> getConverter();
/**
* Specifies the {@link StringConverter} used to convert a generic
* item to a String. It is used by the list cells.
*/
ObjectProperty<StringConverter<T>> converterProperty();
void setConverter(StringConverter<T> converter);
/**
* @return the function used to build the list cells
*/
Function<T, C> getCellFactory();
/**
* @return the cell factory property
*/
ObjectProperty<Function<T, C>> cellFactoryProperty();
/**
* Replaces the cell factory with the given one
*/
void setCellFactory(Function<T, C> cellFactory);
/**
* @return the listview selection model
*/
IMultipleSelectionModel<T> getSelectionModel();
}

View File

@ -18,11 +18,15 @@
package io.github.palexdev.materialfx.controls.base;
import java.util.function.Consumer;
import java.util.function.Function;
import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
import io.github.palexdev.materialfx.beans.properties.functional.ConsumerProperty;
import io.github.palexdev.materialfx.selection.base.ISingleSelectionModel;
import io.github.palexdev.virtualizedfx.cell.Cell;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
@ -31,9 +35,6 @@ import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.util.StringConverter;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Public API every combo box must implement.
*/
@ -123,24 +124,24 @@ public interface MFXCombo<T> {
/**
* Specifies the combo box's items list.
*/
ObjectProperty<ObservableList<T>> itemsProperty();
ListProperty<T> itemsProperty();
void setItems(ObservableList<T> items);
Function<T, Cell<T>> getCellFactory();
Function<T, VFXCell<T>> getCellFactory();
/**
* Specifies the function used to create the items cells
* in the popup.
*/
ObjectProperty<Function<T, Cell<T>>> cellFactoryProperty();
ObjectProperty<Function<T, VFXCell<T>>> cellFactoryProperty();
void setCellFactory(Function<T, Cell<T>> cellFactory);
void setCellFactory(Function<T, VFXCell<T>> cellFactory);
/**
* @return the combo box' selection model
*/
ISingleSelectionModel<T> getSelectionModel();
ISelectionModel<T> getSelectionModel();
EventHandler<ActionEvent> getOnAction();

View File

@ -1,177 +1,42 @@
/*
* 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.cell;
import io.github.palexdev.materialfx.beans.PositionBean;
import io.github.palexdev.materialfx.controls.MFXCheckListView;
import io.github.palexdev.materialfx.controls.MFXCheckbox;
import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell;
import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
import java.util.List;
import io.github.palexdev.materialfx.skins.MFXCheckListCellSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import io.github.palexdev.materialfx.utils.NodeUtils;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectExpression;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import io.github.palexdev.mfxcore.controls.SkinBase;
import io.github.palexdev.mfxcore.utils.fx.SceneBuilderIntegration;
import javafx.util.StringConverter;
/**
* Implementation of an {@link AbstractMFXListCell} which has a combo box
* for usage in {@link MFXCheckListView}.
* <p></p>
* The label used to display the data is built in the constructor
* only if the given T data is not a Node, otherwise it's null.
* <p></p>
* The label's text is bound to the data property and converted to a String
* using {@link ObjectExpression#asString()}.
*/
public class MFXCheckListCell<T> extends AbstractMFXListCell<T> {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-check-list-cell";
protected final MFXCircleRippleGenerator rippleGenerator = new MFXCircleRippleGenerator(this);
public class MFXCheckListCell<T> extends MFXListCell<T> {
private final MFXCheckListView<T> listView;
protected final MFXCheckbox checkbox;
protected final Label label;
//================================================================================
// Constructors
//================================================================================
public MFXCheckListCell(T item) {
super(item);
}
//================================================================================
// Constructors
//================================================================================
public MFXCheckListCell(MFXCheckListView<T> listView, T data) {
super(listView, data);
this.listView = listView;
checkbox = new MFXCheckbox("");
public MFXCheckListCell(T item, StringConverter<T> converter) {
super(item, converter);
}
if (!(data instanceof Node)) {
label = new Label();
label.textProperty().bind(Bindings.createStringBinding(
() -> listView.getConverter() != null ? listView.getConverter().toString(getData()) : getData().toString(),
dataProperty(), listView.converterProperty()
));
label.getStyleClass().add("data-label");
} else {
label = null;
}
//================================================================================
// Overridden Methods
//================================================================================
initialize();
}
@Override
protected SkinBase<?, ?> buildSkin() {
return new MFXCheckListCellSkin<>(this);
}
//================================================================================
// Methods
//================================================================================
@Override
protected void sceneBuilderIntegration() {
SceneBuilderIntegration.ifInSceneBuilder(() -> getStylesheets().add(MaterialFXStylesheets.CHECK_LIST_CELL.toData()));
}
/**
* Overridden to add the style class, setup the ripple generator and call {@link #render(Object)}
* for the first time.
*/
@Override
protected void initialize() {
super.initialize();
getStyleClass().add(STYLE_CLASS);
setupRippleGenerator();
render(getData());
sceneBuilderIntegration();
}
/**
* Sets up the properties of the ripple generator and adds the mouse pressed filter.
*/
protected void setupRippleGenerator() {
rippleGenerator.setManaged(false);
rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY()));
rippleGenerator.rippleRadiusProperty().bind(widthProperty().divide(2.0));
addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
if (NodeUtils.inHierarchy(event, checkbox)) {
rippleGenerator.generateRipple(event);
}
});
}
/**
* Responsible for updating the selection state according to the checkbox' state.
* <p>
* If checked is true then the cell should be selected, otherwise it is deselected.
*/
private void updateSelection(boolean checked) {
int index = getIndex();
if (checked) {
listView.getSelectionModel().selectIndex(index);
} else {
listView.getSelectionModel().deselectIndex(index);
}
}
//================================================================================
// Overridden/Implemented Methods
//================================================================================
/**
* Overridden as the selected state depends on the checkbox' state.
*/
@Override
protected void setBehavior() {
selected.addListener(invalidated -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get()));
selected.bind(Bindings.createBooleanBinding(
() -> {
boolean contained = listView.getSelectionModel().getSelection().containsKey(index.get());
checkbox.setSelected(contained);
return contained;
},
listView.getSelectionModel().selectionProperty(), index
));
checkbox.selectedProperty().addListener((observable, oldValue, newValue) -> updateSelection(newValue));
}
/**
* Responsible for rendering the cell's content.
* <p>
* If the given data type is a Node, it is added to the children list,
* otherwise a label is used to display the data.
* <p>
* At the end adds a ripple generator at index 0.
*/
@Override
protected void render(T data) {
if (data instanceof Node) {
getChildren().setAll(rippleGenerator, checkbox, (Node) data);
} else {
getChildren().setAll(rippleGenerator, checkbox, label);
}
}
/**
* Updates the data property of the cell. If the data is a Node
* {@link #render(Object)} is called.
* <p>
* This is called after {@link #updateIndex(int)}.
*/
@Override
public void updateItem(T item) {
super.updateItem(item);
if (item instanceof Node) render(item);
}
@Override
public Theme getTheme() {
return MaterialFXStylesheets.CHECK_LIST_CELL;
}
@Override
public List<String> defaultStyleClasses() {
return List.of("cell-base", "cell", "mfx-list-cell", "mfx-check-list-cell");
}
}

View File

@ -1,231 +1,61 @@
/*
* 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.cell;
import io.github.palexdev.materialfx.controls.MFXComboBox;
import java.util.List;
import java.util.Optional;
import io.github.palexdev.materialfx.controls.base.MFXCombo;
import io.github.palexdev.materialfx.controls.base.Themable;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import io.github.palexdev.mfxcore.utils.fx.SceneBuilderIntegration;
import io.github.palexdev.virtualizedfx.cells.VFXSimpleCell;
import javafx.util.StringConverter;
/**
* Cells used by default by {@link MFXComboBox}
* <p></p>
* The cell's structure is pretty similar to the {@link MFXListCell} one, but doesn't include
* a ripple generator as it is not necessary (the popup is closed on selection, the ripple effect is
* barely noticeable).
* <p></p>
* The label used to display the data (in case it's not a Node), uses the combo box's
* {@link StringConverter} to convert the data to a String. In case it's null, toString() is
* called on the data.
*/
public class MFXComboBoxCell<T> extends HBox implements Cell<T>, Themable {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-combo-box-cell";
public class MFXComboBoxCell<T> extends MFXListCell<T> {
//================================================================================
// Properties
//================================================================================
private MFXCombo<T> combo;
protected final ReadOnlyObjectWrapper<T> data = new ReadOnlyObjectWrapper<>();
protected final ReadOnlyIntegerWrapper index = new ReadOnlyIntegerWrapper();
//================================================================================
// Constructors
//================================================================================
public MFXComboBoxCell(MFXCombo<T> combo, T item) {
super(item);
this.combo = combo;
}
protected final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
protected final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
//================================================================================
// Overridden Methods
//================================================================================
@Override
public Optional<ISelectionModel<T>> getSelectionModel() {
return Optional.ofNullable(combo.getSelectionModel());
}
protected final MFXCombo<T> comboBox;
private final Label label;
@Override
protected void sceneBuilderIntegration() {
SceneBuilderIntegration.ifInSceneBuilder(() -> getStylesheets().add(MaterialFXStylesheets.COMBO_BOX_CELL.toData()));
}
//================================================================================
// Constructors
//================================================================================
public MFXComboBoxCell(MFXCombo<T> combo, T data) {
this.comboBox = combo;
@Override
public List<String> defaultStyleClasses() {
return List.of("mfx-combo-box-cell");
}
setPrefHeight(32);
setMaxHeight(USE_PREF_SIZE);
setAlignment(Pos.CENTER_LEFT);
setSpacing(5);
@Override
public VFXSimpleCell<T> setConverter(StringConverter<T> converter) {
combo.setConverter(converter);
return this;
}
if (!(data instanceof Node)) {
label = new Label();
label.getStyleClass().add("data-label");
label.textProperty().bind(Bindings.createStringBinding(
() -> {
StringConverter<T> converter = combo.getConverter();
return converter != null ? converter.toString(getData()) : getData().toString();
}, dataProperty(), combo.converterProperty()
));
} else {
label = null;
}
@Override
public StringConverter<T> getConverter() {
return combo.getConverter();
}
initialize();
}
//================================================================================
// Methods
//================================================================================
protected void initialize() {
getStyleClass().add(STYLE_CLASS);
setBehavior();
render(getData());
sceneBuilderIntegration();
}
/**
* Sets the following behaviors:
* <p>
* - Binds the selected property to the combo' selection model (checks for index). <p>
* - Updates the selected PseudoClass state when selected property changes.<p>
* - Adds and handler for MOUSE_PRESSED events to call {@link #updateSelection(MouseEvent)}.
*/
protected void setBehavior() {
selected.addListener(invalidated -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get()));
selected.bind(Bindings.createBooleanBinding(
() -> comboBox.getSelectionModel().getSelectedIndex() == index.get(),
comboBox.getSelectionModel().selectedIndexProperty(), index
));
addEventFilter(MouseEvent.MOUSE_PRESSED, this::updateSelection);
}
/**
* Responsible for rendering the cell's content.
* <p>
* If the given data type is a Node, it is added to the children list,
* otherwise a label is used to display the data.
*/
protected void render(T data) {
if (data instanceof Node) {
getChildren().setAll((Node) data);
} else {
getChildren().setAll(label);
}
}
/**
* If the pressed mouse button is not the primary, exits immediately.
* <p>
* Orders the combo's selection model to select the index of this cell.
*/
protected void updateSelection(MouseEvent event) {
if (event.getButton() != MouseButton.PRIMARY) {
return;
}
int index = getIndex();
comboBox.getSelectionModel().selectIndex(index);
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public Parent toParent() {
return this;
}
@Override
public Theme getTheme() {
return MaterialFXStylesheets.COMBO_BOX_CELL;
}
@Override
public Node getNode() {
return this;
}
/**
* Updates the index property of the cell.
* <p>
* This is called before {@link #updateItem(Object)}.
*/
@Override
public void updateIndex(int index) {
setIndex(index);
}
/**
* Updates the data property of the cell.
* <p>
* This is called after {@link #updateIndex(int)}.
*/
@Override
public void updateItem(T item) {
setData(item);
}
//================================================================================
// Getters/Setters
//================================================================================
public T getData() {
return data.get();
}
/**
* Data property of the cell.
*/
public ReadOnlyObjectProperty<T> dataProperty() {
return data.getReadOnlyProperty();
}
protected void setData(T data) {
this.data.set(data);
}
public int getIndex() {
return index.get();
}
/**
* Specifies the cell's index.
*/
public ReadOnlyIntegerProperty indexProperty() {
return index.getReadOnlyProperty();
}
protected void setIndex(int index) {
this.index.set(index);
}
public boolean isSelected() {
return selected.get();
}
/**
* Specifies the selection state of the cell.
*/
public ReadOnlyBooleanProperty selectedProperty() {
return selected.getReadOnlyProperty();
}
protected void setSelected(boolean selected) {
this.selected.set(selected);
}
@Override
public void dispose() {
combo = null;
super.dispose();
}
}

View File

@ -1,168 +1,112 @@
/*
* 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.cell;
import io.github.palexdev.materialfx.controls.MFXDatePicker;
import io.github.palexdev.materialfx.controls.base.Themable;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import java.time.LocalDate;
import java.util.List;
import java.util.function.Supplier;
/**
* Simple implementation of a {@link Cell} capable of representing {@link LocalDate} values.
* <p></p>
* It has three main states:
* <p> - selected: when the cell's value is equal to {@link MFXDatePicker#valueProperty()}
* <p> - current: when the cell's value is equal to {@link MFXDatePicker#currentDateProperty()}
* <p> - extra: to mark this cells as belonging to a different month
*/
public class MFXDateCell extends Label implements Cell<LocalDate>, Themable {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-date-cell";
import io.github.palexdev.materialfx.controls.MFXDatePicker;
import io.github.palexdev.materialfx.skins.MFXListCellSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.PseudoClasses;
import io.github.palexdev.mfxcore.controls.SkinBase;
import io.github.palexdev.mfxcore.utils.fx.SceneBuilderIntegration;
import io.github.palexdev.virtualizedfx.cells.CellBaseBehavior;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.util.StringConverter;
private final MFXDatePicker datePicker;
private final ReadOnlyObjectWrapper<LocalDate> date = new ReadOnlyObjectWrapper<>();
public class MFXDateCell extends MFXListCell<LocalDate> {
//================================================================================
// Properties
//================================================================================
private MFXDatePicker datePicker;
private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
protected static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
private final ReadOnlyBooleanWrapper current = new ReadOnlyBooleanWrapper(false) {
@Override
protected void invalidated() {
PseudoClasses.setOn(MFXDateCell.this, PseudoClasses.CURRENT, get());
}
};
private final ReadOnlyBooleanWrapper current = new ReadOnlyBooleanWrapper();
protected static final PseudoClass CURRENT_PSEUDO_CLASS = PseudoClass.getPseudoClass("current");
private boolean extra = false;
private boolean extra = false;
protected static final PseudoClass EXTRA_PSEUDO_CLASS = PseudoClass.getPseudoClass("extra");
//================================================================================
// Constructors
//================================================================================
public MFXDateCell(MFXDatePicker datePicker, LocalDate item) {
super(item);
this.datePicker = datePicker;
initialize();
}
//================================================================================
// Constructors
//================================================================================
public MFXDateCell(MFXDatePicker datePicker, LocalDate date) {
this.datePicker = datePicker;
updateItem(date);
initialize();
}
public MFXDateCell(MFXDatePicker datePicker, LocalDate item, StringConverter<LocalDate> converter) {
super(item, converter);
this.datePicker = datePicker;
initialize();
}
//================================================================================
// Methods
//================================================================================
private void initialize() {
getStyleClass().add(STYLE_CLASS);
setAlignment(Pos.CENTER);
setBehavior();
sceneBuilderIntegration();
}
//================================================================================
// Methods
//================================================================================
private void initialize() {
selected.bind(datePicker.valueProperty().isEqualTo(itemProperty()));
current.bind(datePicker.currentDateProperty().isEqualTo(itemProperty()));
}
/**
* Sets the behavior for selected and current states. Binds the text to {@link LocalDate#getDayOfMonth()} (from the current value),
* binds the visible property to the cell's text (hidden if text is empty, visible if text is not empty)
* <p>
* Also handles MOUSE_PRESSED events to change the date picker's value.
*/
protected void setBehavior() {
selected.addListener((observable, oldValue, newValue) -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get()));
current.addListener((observable, oldValue, newValue) -> pseudoClassStateChanged(CURRENT_PSEUDO_CLASS, current.get()));
/**
* Marks/unmarks this cell as an extra cell.
*/
public void setExtra(boolean isExtra) {
extra = isExtra;
PseudoClasses.setOn(this, PseudoClasses.EXTRA, isExtra);
}
textProperty().bind(Bindings.createStringBinding(
() -> getDate() != null ? String.valueOf(getDate().getDayOfMonth()) : "",
dateProperty()
));
visibleProperty().bind(textProperty().isNotEmpty());
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected void sceneBuilderIntegration() {
SceneBuilderIntegration.ifInSceneBuilder(() -> getStylesheets().add(MaterialFXStylesheets.DATE_CELL.toData()));
}
selected.bind(datePicker.valueProperty().isEqualTo(dateProperty()));
current.bind(datePicker.currentDateProperty().isEqualTo(dateProperty()));
addEventHandler(MouseEvent.MOUSE_PRESSED, event -> datePicker.setValue(getDate()));
}
@Override
protected SkinBase<?, ?> buildSkin() {
return new MFXListCellSkin<>(this) {
{
visibleProperty().bind(label.textProperty().isNotEmpty());
}
};
}
/**
* Marks this cell as an extra cell.
*/
public void markAsExtra() {
extra = true;
pseudoClassStateChanged(EXTRA_PSEUDO_CLASS, true);
}
@Override
public Supplier<CellBaseBehavior<LocalDate>> defaultBehaviorProvider() {
return () -> new MFXListCellBehavior<>(this) {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseButton.PRIMARY) {
datePicker.setValue(getItem());
}
mouseClicked(e, null);
}
};
}
/**
* Un-marks this cell as extra.
*/
public void unmarkAsExtra() {
extra = false;
pseudoClassStateChanged(EXTRA_PSEUDO_CLASS, false);
}
@Override
public List<String> defaultStyleClasses() {
return List.of("mfx-date-cell");
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public Parent toParent() {
return this;
}
@Override
public void dispose() {
datePicker = null;
super.dispose();
}
@Override
public Theme getTheme() {
return MaterialFXStylesheets.DATE_CELL;
}
@Override
public Node getNode() {
return this;
}
@Override
public void updateItem(LocalDate date) {
setDate(date);
}
//================================================================================
// Getters/Setters
//================================================================================
public LocalDate getDate() {
return date.get();
}
/**
* Specifies the cell's represented date.
*/
public ReadOnlyObjectProperty<LocalDate> dateProperty() {
return date.getReadOnlyProperty();
}
protected void setDate(LocalDate date) {
this.date.set(date);
}
/**
* @return whether the cell is an extra cell
*/
public boolean isExtra() {
return extra;
}
//================================================================================
// Getters
//================================================================================
public boolean isExtra() {
return extra;
}
}

View File

@ -18,39 +18,55 @@
package io.github.palexdev.materialfx.controls.cell;
import io.github.palexdev.materialfx.collections.TransformableList;
import io.github.palexdev.materialfx.collections.RefineList;
import io.github.palexdev.materialfx.controls.base.MFXCombo;
import io.github.palexdev.mfxcore.builders.bindings.BooleanBindingBuilder;
import io.github.palexdev.virtualizedfx.base.VFXContainer;
/**
* Extends {@link MFXComboBoxCell} to modify the {@link #updateIndex(int)} method.
*/
public class MFXFilterComboBoxCell<T> extends MFXComboBoxCell<T> {
//================================================================================
// Properties
//================================================================================
private final TransformableList<T> filterList;
//================================================================================
// Properties
//================================================================================
private final RefineList<T> refineList;
//================================================================================
// Constructors
//================================================================================
public MFXFilterComboBoxCell(MFXCombo<T> combo, TransformableList<T> filterList, T data) {
super(combo, data);
this.filterList = filterList;
}
//================================================================================
// Constructors
//================================================================================
public MFXFilterComboBoxCell(MFXCombo<T> combo, RefineList<T> refineList, T data) {
super(combo, data);
this.refineList = refineList;
}
//================================================================================
// Overridden Methods
//================================================================================
//================================================================================
// Overridden Methods
//================================================================================
/**
* {@inheritDoc}
* <p></p>
* A filter combo box uses a {@link TransformableList} to display the filtered items
* in the list. The thing is, when items are filtered their index changes as well. For
* selection to work properly the index must be converted using {@link TransformableList#viewToSource(int)}.
*/
@Override
public void updateIndex(int index) {
super.updateIndex(filterList.viewToSource(index));
}
@Override
public void onCreated(VFXContainer<T> container) {
super.onCreated(container);
getSelectionModel().ifPresent(sm ->
selected.bind(BooleanBindingBuilder.build()
.setMapper(() -> {
int index = getIndex();
if (refineList.getPredicate() != null) {
try {
int toSource = refineList.viewToSource(index);
return sm.contains(toSource);
} catch (IndexOutOfBoundsException ex) {
return false;
}
} else {
return sm.contains(index);
}
})
.addSources(sm.selection(), indexProperty(), refineList.getView())
.get()
)
);
}
}

View File

@ -1,164 +1,231 @@
/*
* 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.cell;
import io.github.palexdev.materialfx.beans.PositionBean;
import io.github.palexdev.materialfx.controls.MFXListView;
import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell;
import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.selection.base.WithSelectionModel;
import io.github.palexdev.materialfx.skins.MFXListCellSkin;
import io.github.palexdev.materialfx.theming.MaterialFXStylesheets;
import io.github.palexdev.materialfx.theming.base.Theme;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectExpression;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import io.github.palexdev.materialfx.theming.PseudoClasses;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
import io.github.palexdev.mfxcore.builders.bindings.BooleanBindingBuilder;
import io.github.palexdev.mfxcore.controls.SkinBase;
import io.github.palexdev.mfxcore.utils.fx.SceneBuilderIntegration;
import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
import io.github.palexdev.virtualizedfx.base.VFXContainer;
import io.github.palexdev.virtualizedfx.cells.CellBaseBehavior;
import io.github.palexdev.virtualizedfx.cells.VFXSimpleCell;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleablePropertyFactory;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.util.StringConverter;
/**
* Simple implementation of {@link AbstractMFXListCell},
* includes a ripple generator for ripple effects on mouse pressed.
* <p></p>
* The label used to display the data is built in the constructor
* only if the given T data is not a Node, otherwise it's null.
* <p></p>
* The label's text is bound to the data property and converted to a String
* using {@link ObjectExpression#asString()}.
*/
public class MFXListCell<T> extends AbstractMFXListCell<T> {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-list-cell";
protected final MFXCircleRippleGenerator rippleGenerator = new MFXCircleRippleGenerator(this);
public class MFXListCell<T> extends VFXSimpleCell<T> {
//================================================================================
// Properties
//================================================================================
protected final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(false) {
@Override
protected void invalidated() {
PseudoClasses.setOn(MFXListCell.this, PseudoClasses.SELECTED, get());
}
};
private final Label label;
//================================================================================
// Constructors
//================================================================================
public MFXListCell(T item) {
super(item);
}
//================================================================================
// Constructors
//================================================================================
public MFXListCell(MFXListView<T> listView, T data) {
super(listView, data);
public MFXListCell(T item, StringConverter<T> converter) {
super(item, converter);
}
if (!(data instanceof Node)) {
label = new Label();
label.textProperty().bind(Bindings.createStringBinding(
() -> listView.getConverter() != null ? listView.getConverter().toString(getData()) : getData().toString(),
dataProperty(), listView.converterProperty()
));
label.getStyleClass().add("data-label");
} else {
label = null;
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public void onCreated(VFXContainer<T> container) {
super.onCreated(container);
initialize();
}
getSelectionModel().ifPresent(sm ->
selected.bind(BooleanBindingBuilder.build()
.setMapper(() -> sm.contains(getIndex()))
.addSources(sm.selection(), indexProperty())
.get()
)
);
//================================================================================
// Methods
//================================================================================
sceneBuilderIntegration();
}
/**
* Overridden to add the style class, setup the ripple generator and call {@link #render(Object)}
* for the first time.
*/
@Override
protected void initialize() {
super.initialize();
getStyleClass().add(STYLE_CLASS);
setupRippleGenerator();
render(getData());
sceneBuilderIntegration();
}
@Override
protected SkinBase<?, ?> buildSkin() {
return new MFXListCellSkin<>(this);
}
/**
* Sets up the properties of the ripple generator and adds the mouse pressed filter.
*/
protected void setupRippleGenerator() {
rippleGenerator.setManaged(false);
rippleGenerator.setRipplePositionFunction(event -> PositionBean.of(event.getX(), event.getY()));
rippleGenerator.rippleRadiusProperty().bind(widthProperty().divide(2.0));
addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
if (event.getButton() == MouseButton.PRIMARY) {
rippleGenerator.generateRipple(event);
}
});
}
@Override
public Supplier<CellBaseBehavior<T>> defaultBehaviorProvider() {
return () -> new MFXListCellBehavior<>(this);
}
//================================================================================
// Overridden/Implemented Methods
//================================================================================
@Override
public List<String> defaultStyleClasses() {
return List.of("cell-base", "cell", "mfx-list-cell");
}
/**
* Responsible for rendering the cell's content.
* <p>
* If the given data type is a Node, it is added to the children list,
* otherwise a label is used to display the data.
* <p>
* At the end adds a ripple generator at index 0.
*/
@Override
protected void render(T data) {
if (data instanceof Node) {
getChildren().setAll(rippleGenerator, (Node) data);
} else {
getChildren().setAll(rippleGenerator, label);
}
}
@Override
protected void sceneBuilderIntegration() {
SceneBuilderIntegration.ifInSceneBuilder(() -> getStylesheets().add(MaterialFXStylesheets.LIST_CELL.toData()));
}
/**
* Updates the data property of the cell. If the data is a Node
* {@link #render(Object)} is called.
* <p>
* This is called after {@link #updateIndex(int)}.
*/
@Override
public void updateItem(T item) {
super.updateItem(item);
if (item instanceof Node) render(item);
}
//================================================================================
// Styleable Properties
//================================================================================
private final StyleableDoubleProperty hGap = new StyleableDoubleProperty(
StyleableProperties.HGAP,
this,
"hGap",
10.0
);
@Override
public Parent toParent() {
return this;
}
public double getHGap() {
return hGap.get();
}
@Override
public Theme getTheme() {
return MaterialFXStylesheets.LIST_CELL;
}
public StyleableDoubleProperty hGapProperty() {
return hGap;
}
@Override
public String toString() {
String className = getClass().getName();
String simpleName = className.substring(className.lastIndexOf('.') + 1);
StringBuilder sb = new StringBuilder();
sb.append("[").append(simpleName);
sb.append('@');
sb.append(Integer.toHexString(hashCode()));
sb.append("]");
sb.append("[Data:").append(getData()).append("]");
if (getId() != null) {
sb.append("[id:").append(getId()).append("]");
}
public void setHGap(double hGap) {
this.hGap.set(hGap);
}
return sb.toString();
}
//================================================================================
// CssMetaData
//================================================================================
private static class StyleableProperties {
private static final StyleablePropertyFactory<MFXListCell<?>> FACTORY = new StyleablePropertyFactory<>(VFXSimpleCell.getClassCssMetaData());
private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
private static final CssMetaData<MFXListCell<?>, Number> HGAP =
FACTORY.createSizeCssMetaData(
"-fx-hgap",
MFXListCell::hGapProperty,
10.0
);
static {
cssMetaDataList = StyleUtils.cssMetaDataList(
VFXSimpleCell.getClassCssMetaData(),
HGAP
);
}
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.cssMetaDataList;
}
@Override
protected List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
//================================================================================
// Getters
//================================================================================
@SuppressWarnings("unchecked")
public Optional<ISelectionModel<T>> getSelectionModel() {
if (getContainer() instanceof WithSelectionModel<?>) {
return Optional.of(((WithSelectionModel<T>) getContainer()).getSelectionModel());
}
return Optional.empty();
}
public boolean isSelected() {
return selected.get();
}
public ReadOnlyBooleanProperty selectedProperty() {
return selected.getReadOnlyProperty();
}
//================================================================================
// Inner Classes
//================================================================================
public static class MFXListCellBehavior<T> extends CellBaseBehavior<T> {
//================================================================================
// Constructors
//================================================================================
public MFXListCellBehavior(MFXListCell<T> cell) {
super(cell);
}
//================================================================================
// Methods
//================================================================================
protected void updateSelection(SelectionMode mode) {
MFXListCell<T> cell = getNode();
cell.getSelectionModel().ifPresent(sm -> {
int index = cell.getIndex();
switch (mode) {
case STANDARD -> {
if (cell.isSelected()) {
sm.deselectIndex(index);
} else {
sm.selectIndex(index);
}
}
case EXTEND -> sm.expandSelection(index, true);
case REPLACE -> sm.replaceSelection(index);
}
});
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public void mouseClicked(MouseEvent e, Consumer<MouseEvent> callback) {
// We use a null event to signal the selection update comes from another node
if (e == null) {
updateSelection(SelectionMode.STANDARD);
return;
} else if (e.getButton() == MouseButton.PRIMARY) {
SelectionMode sm = SelectionMode.forEvent(e);
updateSelection(sm);
}
if (callback != null) callback.accept(e);
}
@Override
public MFXListCell<T> getNode() {
return (MFXListCell<T>) super.getNode();
}
public enum SelectionMode {
STANDARD,
EXTEND,
REPLACE,
;
public static SelectionMode forEvent(MouseEvent me) {
if (me.isControlDown())
return SelectionMode.STANDARD;
if (me.isShiftDown())
return EXTEND;
return REPLACE;
}
}
}
}

View File

@ -1,251 +1,54 @@
/*
* 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.cell;
import io.github.palexdev.materialfx.controls.MFXCheckbox;
import java.util.List;
import java.util.Optional;
import io.github.palexdev.materialfx.controls.MFXNotificationCenter;
import io.github.palexdev.materialfx.effects.Interpolators;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.notifications.base.INotification;
import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames;
import io.github.palexdev.materialfx.utils.AnimationUtils.ParallelBuilder;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
/**
* Implementation of a {@link Cell} for usage with {@link MFXNotificationCenter}.
* <p></p>
* Includes a checkbox to allow selecting notifications.
*/
public class MFXNotificationCell extends HBox implements Cell<INotification> {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-notification-cell";
private final MFXNotificationCenter notificationCenter;
private final ReadOnlyObjectWrapper<INotification> notification = new ReadOnlyObjectWrapper<>();
private final ReadOnlyIntegerWrapper index = new ReadOnlyIntegerWrapper();
private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
public class MFXNotificationCell extends MFXCheckListCell<INotification> {
//================================================================================
// Properties
//================================================================================
private MFXNotificationCenter notificationCenter;
protected final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
protected final StackPane container;
protected final MFXCheckbox checkbox;
//================================================================================
// Constructors
//================================================================================
public MFXNotificationCell(MFXNotificationCenter notificationCenter, INotification item) {
super(item);
this.notificationCenter = notificationCenter;
}
//================================================================================
// Constructors
//================================================================================
public MFXNotificationCell(MFXNotificationCenter notificationCenter, INotification notification) {
this.notificationCenter = notificationCenter;
setNotification(notification);
//================================================================================
// Overridden Methods
//================================================================================
@Override
public Optional<ISelectionModel<INotification>> getSelectionModel() {
return Optional.ofNullable(notificationCenter.getSelectionModel());
}
setPrefHeight(80);
setMaxHeight(USE_PREF_SIZE);
setAlignment(Pos.CENTER_LEFT);
@Override
protected void sceneBuilderIntegration() {
}
checkbox = new MFXCheckbox("");
checkbox.setId("check");
@Override
public List<String> defaultStyleClasses() {
return List.of("mfx-notification-cell");
}
container = new StackPane(checkbox);
container.setMinWidth(USE_PREF_SIZE);
container.setPrefWidth(0);
container.setMaxWidth(USE_PREF_SIZE);
@Override
public void dispose() {
this.notificationCenter = null;
super.dispose();
}
Rectangle clip = new Rectangle();
clip.widthProperty().bind(container.widthProperty());
clip.heightProperty().bind(container.heightProperty());
container.setClip(clip);
initialize();
}
//================================================================================
// Methods
//================================================================================
/**
* Adds the style class, calls {@link #setBehavior()} then {@link #render(INotification)}
* for the first time.
*/
private void initialize() {
getStyleClass().add(STYLE_CLASS);
setBehavior();
render(getNotification());
if (notificationCenter.isSelectionMode()) expand(true);
}
/**
* Sets the following behaviors:
* <p>
* - Binds the selected property to the notification center' selection model (checks for index). <p>
* - Updates the selected PseudoClass state when selected property changes. <p>
* - Adds a listener to the checkbox' selection state to call {@link #updateSelection(boolean)}. <p>
* - Adds a listener to the notification center's {@link MFXNotificationCenter#selectionModeProperty()} to call {@link #expand(boolean)}.
*/
protected void setBehavior() {
selected.addListener(invalidated -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get()));
selected.bind(Bindings.createBooleanBinding(() -> {
boolean contained = notificationCenter.getSelectionModel().getSelection().containsKey(getIndex());
checkbox.setSelected(contained);
return contained;
}, notificationCenter.getSelectionModel().selectionProperty(), index));
checkbox.selectedProperty().addListener((observable, oldValue, newValue) -> updateSelection(newValue));
notificationCenter.selectionModeProperty().addListener((observable, oldValue, newValue) -> expand(newValue));
}
/**
* Responsible for rendering the cell's content.
*/
protected void render(INotification notification) {
if (notificationCenter.isSelectionMode()) {
checkbox.setOpacity(1.0);
checkbox.setPrefWidth(45);
}
getChildren().setAll(container, notification.getContent());
}
/**
* Responsible for updating the selection state according to the checkbox' state.
* <p>
* If checked is true then the cell should be selected, otherwise it is deselected.
*/
protected void updateSelection(boolean checked) {
int index = getIndex();
if (checked) {
notificationCenter.getSelectionModel().selectIndex(index);
} else {
notificationCenter.getSelectionModel().deselectIndex(index);
}
}
/**
* Responsible for showing/hiding the checkbox.
*/
protected void expand(boolean selectionMode) {
double width = selectionMode ? 45 : 0;
double opacity = selectionMode ? 1 : 0;
if (notificationCenter.isAnimated()) {
ParallelBuilder.build()
.add(
KeyFrames.of(150, checkbox.opacityProperty(), opacity, Interpolators.EASE_OUT),
KeyFrames.of(250, container.prefWidthProperty(), width, Interpolators.EASE_OUT_SINE)
).getAnimation().play();
} else {
container.setPrefWidth(width);
checkbox.setOpacity(opacity);
}
if (!selectionMode) {
notificationCenter.getSelectionModel().clearSelection();
}
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public Node getNode() {
return this;
}
/**
* Updates the notification property of the cell, then calls {@link #render(INotification)}.
* <p>
* This is called after {@link #updateIndex(int)}.
*/
@Override
public void updateItem(INotification notification) {
setNotification(notification);
render(notification);
}
/**
* Updates the index property of the cell.
* <p>
* This is called before {@link #updateItem(INotification)}.
*/
@Override
public void updateIndex(int index) {
setIndex(index);
}
/**
* Ensures that the combobox container is properly expanded
* after the cell has been laid out.
*/
@Override
public void afterLayout() {
expand(notificationCenter.isSelectionMode());
}
//================================================================================
// Getters/Setters
//================================================================================
public INotification getNotification() {
return this.notification.get();
}
/**
* Specifies the current shown notification (in other words the cell's content).
*/
public ReadOnlyObjectProperty<INotification> notificationProperty() {
return this.notification.getReadOnlyProperty();
}
protected void setNotification(INotification notification) {
this.notification.set(notification);
}
public int getIndex() {
return this.index.get();
}
/**
* Specifies the cell's index.
*/
protected ReadOnlyIntegerProperty indexProperty() {
return this.index.getReadOnlyProperty();
}
protected void setIndex(int index) {
this.index.set(index);
}
public boolean isSelected() {
return this.selected.get();
}
/**
* Specifies the selection state of the cell.
*/
public ReadOnlyBooleanProperty selectedProperty() {
return this.selected.getReadOnlyProperty();
}
protected void setSelected(boolean selected) {
this.selected.set(selected);
}
//================================================================================
// Getters
//================================================================================
public MFXNotificationCenter getNotificationCenter() {
return notificationCenter;
}
}

View File

@ -1,191 +1,106 @@
/*
* 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.cell;
import io.github.palexdev.materialfx.beans.Alignment;
import io.github.palexdev.materialfx.beans.NumberRange;
import io.github.palexdev.materialfx.controls.MFXListView;
import java.util.function.Consumer;
import io.github.palexdev.materialfx.controls.MFXPagination;
import io.github.palexdev.materialfx.controls.MFXPopup;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import io.github.palexdev.materialfx.skins.MFXPageSkin;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.mfxcore.builders.bindings.BooleanBindingBuilder;
import io.github.palexdev.mfxcore.controls.SkinBase;
import io.github.palexdev.virtualizedfx.base.VFXContainer;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
/**
* This is the default cell used by {@link MFXPagination} to show the page indexes.
* <p></p>
* It's a very basic cell that show's the page's index as text (since it extends {@link Label}),
* handles the selection state (according to {@link MFXPagination#currentPageProperty()}).
* <p>
* If the cells represents a truncated page, the text is specified by {@link MFXPagination#ellipseStringProperty()}.
* In this case the cell also allows to show a popup containing the hidden pages, for faster and easier navigation.
*/
public class MFXPage extends Label implements Cell<Integer> {
//================================================================================
// Properties
//================================================================================
private final String STYLE_CLASS = "mfx-page";
public class MFXPage extends MFXListCell<Integer> {
//================================================================================
// Properties
//================================================================================
private MFXPagination pagination;
private IntegerRange between;
private final MFXPagination pagination;
private final ReadOnlyIntegerWrapper index = new ReadOnlyIntegerWrapper();
private NumberRange<Integer> between;
//================================================================================
// Constructors
//================================================================================
public MFXPage(MFXPagination pagination, int item) {
super(item);
this.pagination = pagination;
setDefaultConverter();
}
private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
protected static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
//================================================================================
// Methods
//================================================================================
public void setDefaultConverter() {
setConverter(i -> i == -1 ? pagination.getEllipseString() : String.valueOf(i));
}
//================================================================================
// Constructors
//================================================================================
public MFXPage(MFXPagination pagination, int index) {
this.pagination = pagination;
updateItem(index);
initialize();
addListeners();
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public void onCreated(VFXContainer<Integer> container) {
super.onCreated(container);
//================================================================================
// Methods
//================================================================================
private void initialize() {
getStyleClass().add(STYLE_CLASS);
setAlignment(Pos.CENTER);
}
selected.bind(BooleanBindingBuilder.build()
.setMapper(() -> pagination.getCurrentPage() == getItem())
.addSources(itemProperty(), pagination.currentPageProperty())
.get()
);
}
/**
* Handles selection, text property and updates the {@link MFXPagination#currentPageProperty()} on click.
* <p>
* If truncated and enabled, {@link #showPopup()} on click.
*/
private void addListeners() {
selected.addListener(invalidated -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get()));
selected.bind(Bindings.createBooleanBinding(
() -> pagination.getCurrentPage() == getIndex(),
indexProperty(), pagination.currentPageProperty()
));
@Override
protected SkinBase<?, ?> buildSkin() {
return new MFXPageSkin(this);
}
textProperty().bind(Bindings.createStringBinding(
() -> getIndex() == -1 ? pagination.getEllipseString() : String.valueOf(getIndex()),
indexProperty()
));
@Override
public void dispose() {
this.pagination = null;
super.dispose();
}
addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getButton() != MouseButton.PRIMARY) return;
if (getIndex() != -1) {
pagination.setCurrentPage(getIndex());
} else {
showPopup();
}
});
}
//================================================================================
// Getters/Setters
//================================================================================
public MFXPagination getPagination() {
return pagination;
}
/**
* If the page is truncated, shows a popup containing the hidden pages' indexes.
*/
protected void showPopup() {
if (!pagination.isShowPopupForTruncatedPages() || between == null) return;
public IntegerRange getBetween() {
return between;
}
ObservableList<Integer> indexes = FXCollections.observableArrayList(NumberRange.expandRange(between));
MFXListView<Integer> listView = new MFXListView<>(indexes);
public void setBetween(IntegerRange between) {
this.between = between;
}
MFXPopup popup = new MFXPopup(listView);
popup.getStyleClass().add("pages-popup");
popup.setPopupStyleableParent(pagination);
//================================================================================
// Inner Classes
//================================================================================
public static class MFXPageBehavior extends MFXListCellBehavior<Integer> {
listView.setCellFactory(integer -> {
MFXListCell<Integer> cell = new MFXListCell<>(listView, integer);
cell.setOnMouseClicked(event -> {
pagination.setCurrentPage(cell.getData());
popup.hide();
});
return cell;
});
public MFXPageBehavior(MFXPage cell) {
super(cell);
}
popup.show(this, Alignment.of(HPos.CENTER, VPos.BOTTOM), 0, 5);
}
@Override
public void mouseClicked(MouseEvent e, Consumer<MouseEvent> callback) {
MFXPage cell = getNode();
if (e.getButton() == MouseButton.PRIMARY) {
MFXPagination pagination = cell.getPagination();
Integer item = cell.getItem();
if (item != -1) {
pagination.setCurrentPage(item);
} else {
super.mouseClicked(e, callback);
}
}
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public Node getNode() {
return this;
}
@Override
public void updateItem(Integer index) {
setIndex(index);
}
//================================================================================
// Getters/Setters
//================================================================================
public int getIndex() {
return index.get();
}
/**
* Specifies the page's index.
*/
public ReadOnlyIntegerProperty indexProperty() {
return index.getReadOnlyProperty();
}
protected void setIndex(int index) {
this.index.set(index);
}
/**
* @return the range of hidden pages, if truncated otherwise null
*/
public NumberRange<Integer> getBetween() {
return between;
}
public void setBetween(NumberRange<Integer> between) {
this.between = between;
}
public boolean isSelected() {
return selected.get();
}
/**
* Specifies the selection state of the page.
*/
public ReadOnlyBooleanProperty selectedProperty() {
return selected.getReadOnlyProperty();
}
protected void setSelected(boolean selected) {
this.selected.set(selected);
}
@Override
public MFXPage getNode() {
return (MFXPage) super.getNode();
}
}
}

View File

@ -0,0 +1,137 @@
package io.github.palexdev.materialfx.controls.cell;
import java.util.Optional;
import io.github.palexdev.materialfx.collections.RefineList;
import io.github.palexdev.materialfx.controls.cell.MFXListCell.MFXListCellBehavior.SelectionMode;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.materialfx.selection.base.WithSelectionModel;
import io.github.palexdev.materialfx.theming.PseudoClasses;
import io.github.palexdev.mfxcore.builders.bindings.BooleanBindingBuilder;
import io.github.palexdev.mfxcore.events.WhenEvent;
import io.github.palexdev.mfxeffects.ripple.MFXRippleGenerator;
import io.github.palexdev.virtualizedfx.base.VFXContainer;
import io.github.palexdev.virtualizedfx.table.VFXTable;
import io.github.palexdev.virtualizedfx.table.defaults.VFXDefaultTableRow;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.collections.ObservableList;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
public class MFXTableRow<T> extends VFXDefaultTableRow<T> {
//================================================================================
// Properties
//================================================================================
protected final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(false) {
@Override
protected void invalidated() {
PseudoClasses.setOn(MFXTableRow.this, PseudoClasses.SELECTED, get());
}
};
private final MFXRippleGenerator rg = new MFXRippleGenerator(this);
private WhenEvent<?> wMouseClicked;
//================================================================================
// Constructors
//================================================================================
public MFXTableRow(T item) {
super(item);
rg.enable();
wMouseClicked = WhenEvent.intercept(this, MouseEvent.MOUSE_CLICKED)
.condition(e -> e.getButton() == MouseButton.PRIMARY)
.process(e -> updateSelection(SelectionMode.forEvent(e)))
.register();
}
//================================================================================
// Methods
//================================================================================
protected void updateSelection(SelectionMode mode) {
getSelectionModel().ifPresent(sm -> {
int index = getIndex();
switch (mode) {
case STANDARD -> {
if (isSelected()) {
sm.deselectIndex(index);
} else {
sm.selectIndex(index);
}
}
case EXTEND -> sm.expandSelection(index, true);
case REPLACE -> sm.replaceSelection(index);
}
});
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected void onCellsChanged() {
super.onCellsChanged();
getChildren().addFirst(rg);
}
@Override
public void onCreated(VFXContainer<T> container) {
super.onCreated(container);
getSelectionModel().ifPresent(sm ->
selected.bind(BooleanBindingBuilder.build()
.setMapper(() -> sm.contains(getIndex()))
.addSources(sm.selection(), indexProperty())
.get()
));
}
@Override
public void updateIndex(int index) {
ObservableList<T> items = getTable().getItems();
if (items instanceof RefineList<T> rl) {
setIndex(rl.viewToSource(index));
return;
}
super.updateIndex(index);
}
@Override
protected void layoutChildren() {
super.layoutChildren();
rg.resizeRelocate(0, 0, getWidth(), getHeight());
}
@Override
public void dispose() {
if (wMouseClicked != null) {
wMouseClicked.dispose();
wMouseClicked = null;
}
rg.dispose();
super.dispose();
}
//================================================================================
// Getters
//================================================================================
@SuppressWarnings("unchecked")
public Optional<ISelectionModel<T>> getSelectionModel() {
VFXTable<T> table = getTable();
if (table instanceof WithSelectionModel<?>) {
return Optional.of(((WithSelectionModel<T>) table).getSelectionModel());
}
return Optional.empty();
}
public boolean isSelected() {
return selected.get();
}
public ReadOnlyBooleanProperty selectedProperty() {
return selected.getReadOnlyProperty();
}
}

View File

@ -19,8 +19,6 @@
package io.github.palexdev.materialfx.controls.cell;
import io.github.palexdev.materialfx.controls.MFXTableColumn;
import io.github.palexdev.materialfx.controls.MFXTableRow;
import io.github.palexdev.materialfx.controls.MFXTableView;
import io.github.palexdev.materialfx.skins.MFXTableRowCellSkin;
import io.github.palexdev.materialfx.utils.others.FunctionalStringConverter;
import javafx.beans.property.ObjectProperty;

View File

@ -1,210 +0,0 @@
/*
* 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.cell.base;
import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
import io.github.palexdev.materialfx.controls.base.Themable;
import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
import io.github.palexdev.virtualizedfx.cell.Cell;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
/**
* Base class for all cells used in listviews based on VirtualizedFX,
* defines common properties and behavior (e.g. selection), has the selected property
* and PseudoClass ":selected" for usage in CSS.
* <p>
* Extends {@link HBox} and implements {@link Cell}.
*
* @param <T> the type of data within the ListView
*/
public abstract class AbstractMFXListCell<T> extends HBox implements Cell<T>, Themable {
//================================================================================
// Properties
//================================================================================
protected final AbstractMFXListView<T, ?> listView;
protected final ReadOnlyObjectWrapper<T> data = new ReadOnlyObjectWrapper<>();
protected final ReadOnlyIntegerWrapper index = new ReadOnlyIntegerWrapper();
protected final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
protected final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
//================================================================================
// Constructors
//================================================================================
public AbstractMFXListCell(AbstractMFXListView<T, ?> listView, T data) {
this.listView = listView;
setData(data);
setPrefHeight(32);
setMaxHeight(USE_PREF_SIZE);
setAlignment(Pos.CENTER_LEFT);
setSpacing(5);
}
//================================================================================
// Abstract Methods
//================================================================================
/**
* Abstract method which defines how the cell should process and show the given data.
*/
protected abstract void render(T data);
//================================================================================
// Methods
//================================================================================
protected void initialize() {
setBehavior();
}
/**
* Sets the following behaviors:
* <p>
* - Binds the selected property to the list' selection model (checks for index). <p>
* - Updates the selected PseudoClass state when selected property changes.<p>
* - Adds and handler for MOUSE_PRESSED events to call {@link #updateSelection(MouseEvent)}.
*/
protected void setBehavior() {
selected.addListener(invalidated -> pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, selected.get()));
selected.bind(Bindings.createBooleanBinding(
() -> listView.getSelectionModel().getSelection().containsKey(index.get()),
listView.getSelectionModel().selectionProperty(), index
));
addEventFilter(MouseEvent.MOUSE_PRESSED, this::updateSelection);
}
/**
* If the pressed mouse button is not the primary, exits immediately.
* <p>
* If the cell is already deselected calls {@link MultipleSelectionModel#deselectIndex(int)},
* otherwise checks if Shift or Ctrl are pressed and updates the selection accordingly,
* adds if they were pressed, replaces if not (acting like single selection),
* see {@link MultipleSelectionModel#selectIndex(int)}, {@link MultipleSelectionModel#replaceSelection(Integer...)}.
*/
protected void updateSelection(MouseEvent event) {
if (event.getButton() != MouseButton.PRIMARY) return;
int index = getIndex();
if (event.isControlDown()) {
if (isSelected()) {
listView.getSelectionModel().deselectIndex(index);
} else {
listView.getSelectionModel().selectIndex(index);
}
return;
}
if (event.isShiftDown()) {
listView.getSelectionModel().expandSelection(index);
return;
}
listView.getSelectionModel().replaceSelection(index);
}
//================================================================================
// Override Methods
//================================================================================
@Override
public Parent toParent() {
return this;
}
@Override
public Node getNode() {
return this;
}
/**
* Updates the index property of the cell.
* <p>
* This is called before {@link #updateItem(Object)}.
*/
@Override
public void updateIndex(int index) {
setIndex(index);
}
/**
* Updates the data property of the cell.
* <p>
* This is called after {@link #updateIndex(int)}.
*/
@Override
public void updateItem(T item) {
setData(item);
}
//================================================================================
// Getters/Setters
//================================================================================
public T getData() {
return data.get();
}
/**
* Data property of the cell.
*/
public ReadOnlyObjectProperty<T> dataProperty() {
return data.getReadOnlyProperty();
}
protected void setData(T data) {
this.data.set(data);
}
public int getIndex() {
return index.get();
}
/**
* Specifies the cell's index.
*/
public ReadOnlyIntegerProperty indexProperty() {
return index.getReadOnlyProperty();
}
protected void setIndex(int index) {
this.index.set(index);
}
public boolean isSelected() {
return selected.get();
}
/**
* Specifies the selection state of the cell.
*/
public ReadOnlyBooleanProperty selectedProperty() {
return selected.getReadOnlyProperty();
}
protected void setSelected(boolean selected) {
this.selected.set(selected);
}
}

View File

@ -18,13 +18,14 @@
package io.github.palexdev.materialfx.selection;
import javafx.beans.property.ObjectProperty;
import java.util.Map;
import javafx.collections.ObservableList;
/**
* Extension of {@link SingleSelectionModel} to implement a few more methods for comboboxes.
* Extension of {@link SelectionModel} to implement a few more methods for combo boxes.
*/
public class ComboBoxSelectionModel<T> extends SingleSelectionModel<T> {
public class ComboBoxSelectionModel<T> extends SelectionModel<T> {
//================================================================================
// Constructors
@ -33,10 +34,6 @@ public class ComboBoxSelectionModel<T> extends SingleSelectionModel<T> {
super(items);
}
public ComboBoxSelectionModel(ObjectProperty<ObservableList<T>> items) {
super(items);
}
//================================================================================
// Methods
//================================================================================
@ -78,17 +75,22 @@ public class ComboBoxSelectionModel<T> extends SingleSelectionModel<T> {
selectIndex(index);
}
public int getSelectedIndex() {
Map.Entry<Integer, T> e = getSelectedEntry();
return (e == null) ? -1 : e.getKey();
}
/**
* Convenience method to get the items list size.
*/
private int itemsSize() {
return items.get().size();
return getItems().size();
}
/**
* Convenience method to check if the items list is empty.
*/
private boolean itemsEmpty() {
return items.get().isEmpty();
return getItems().isEmpty();
}
}

View File

@ -1,329 +0,0 @@
/*
* 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.selection;
import io.github.palexdev.materialfx.beans.NumberRange;
import io.github.palexdev.materialfx.selection.base.AbstractMultipleSelectionModel;
import javafx.beans.property.MapProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import java.util.*;
import java.util.stream.Collectors;
// TODO introduce bindings eventually
/**
* Helper class that is capable of managing/update MultipleSelectionModels.
*/
@SuppressWarnings("unchecked")
public class MultipleSelectionManager<T> {
//================================================================================
// Properties
//================================================================================
private final AbstractMultipleSelectionModel<T> selectionModel;
private final MapProperty<Integer, T> selection = new SimpleMapProperty<>(getMap());
private boolean allowsMultipleSelection = true;
//================================================================================
// Constructors
//================================================================================
public MultipleSelectionManager(AbstractMultipleSelectionModel<T> selectionModel) {
this.selectionModel = selectionModel;
}
//================================================================================
// Methods
//================================================================================
/**
* Clears the selection by setting it to an empty map.
*/
public void clearSelection() {
selection.set(getMap());
}
/**
* Removes the given index from the selection map.
*/
public void deselectIndex(int index) {
selection.remove(index);
}
/**
* Retrieves the index of the given item from the items list and if it's not -1
* removes it from the selection map.
*/
public void deselectItem(T item) {
int index = selectionModel.getItems().indexOf(item);
if (index >= 0) {
selection.remove(index);
}
}
/**
* Removes all the specified indexes from the selection map, done
* by creating a tmp map, updating the tmp map and then replacing the
* selection with this new map.
*/
public void deselectIndexes(int... indexes) {
ObservableMap<Integer, T> tmp = getMap(selection);
for (int index : indexes) {
tmp.remove(index);
}
selection.set(tmp);
}
/**
* Filters the items list to check if the given items exist, then retrieves
* their index and collect them to a tmp map, then replaces the
* selection with this new map.
*/
public void deselectItems(T... items) {
Map<Integer, T> tmp = Arrays.stream(items)
.filter(item -> selectionModel.getItems().contains(item))
.collect(Collectors.toMap(
item -> selectionModel.getItems().indexOf(item),
item -> item
));
ObservableMap<Integer, T> newSelection = getMap(tmp);
selection.set(newSelection);
}
/**
* If multiple selection is allowed adds the given index (and the retrieved item) to the selection map,
* otherwise creates a new tmp map containing only the given index-item entry and replaces the selection.
*/
public void updateSelection(int index) {
T item = selectionModel.getItems().get(index);
if (allowsMultipleSelection) {
selection.put(index, item);
} else {
ObservableMap<Integer, T> map = getMap();
map.put(index, item);
selection.set(map);
}
}
/**
* If multiple selection is allowed adds the given item (and the retrieved index) to the selection map,
* otherwise creates a new tmp map containing only the given index-item entry and replaces the selection.
*/
public void updateSelection(T item) {
int index = selectionModel.getItems().indexOf(item);
if (allowsMultipleSelection) {
selection.put(index, item);
} else {
ObservableMap<Integer, T> map = getMap();
map.put(index, item);
selection.set(map);
}
}
/**
* If multiple selection is allowed adds all the given indexes to the selection
* (and the retrieved items), otherwise replaces the selection with the first index given in the list.
*/
public void updateSelectionByIndexes(List<Integer> indexes) {
if (indexes.isEmpty()) return;
if (allowsMultipleSelection) {
Set<Integer> indexesSet = new LinkedHashSet<>(indexes);
Map<Integer, T> newSelection = indexesSet.stream().collect(Collectors.toMap(
i -> i,
i -> selectionModel.getItems().get(i),
(t, t2) -> t2,
LinkedHashMap::new
));
selection.putAll(newSelection);
} else {
int index = indexes.get(0);
T item = selectionModel.getItems().get(index);
ObservableMap<Integer, T> map = getMap();
map.put(index, item);
selection.set(map);
}
}
/**
* If multiple selection is allowed adds all the given items to the selection
* (and the retrieved indexes), otherwise replaces the selection with the first item given in the list.
*/
public void updateSelectionByItems(List<T> items) {
if (items.isEmpty()) return;
if (allowsMultipleSelection) {
Set<Integer> indexesSet = items.stream()
.mapToInt(item -> selectionModel.getItems().indexOf(item))
.boxed()
.collect(Collectors.toSet());
Map<Integer, T> newSelection = indexesSet.stream().collect(Collectors.toMap(
i -> i,
items::get
));
selection.putAll(newSelection);
} else {
T item = items.get(0);
int index = selectionModel.getItems().indexOf(item);
ObservableMap<Integer, T> map = getMap();
map.put(index, item);
selection.set(map);
}
}
/**
* This is responsible for expanding the selection in the given index direction.
* There are 4 cases to consider:
* <p> 1) The selection is empty: the new selection will go from [0 to index]
* <p> 2) The minimum selected index is equal to the given index: the new selection will just be [index]
* <p> 3) The given index is lesser than the minimum index: the new selection will go from [index to min]
* <p> 4) The given index is greater than the minimum index: the new selection will go from [min to index]
*/
public void expandSelection(int index) {
if (selection.isEmpty()) {
replaceSelection(NumberRange.expandRangeToArray(0, index));
return;
}
int min = selection.keySet().stream().min(Integer::compareTo).orElse(-1);
if (index == min) {
replaceSelection(index);
return;
}
if (index < min) {
replaceSelection(NumberRange.expandRangeToArray(index, min));
} else {
replaceSelection(NumberRange.expandRangeToArray(min, index));
}
}
/**
* If multiple selection is allowed replaces the selection with all the given indexes
* (and the retrieved items), otherwise replaces the selection with the first given index.
*/
public void replaceSelection(Integer... indexes) {
ObservableMap<Integer, T> newSelection = getMap();
if (allowsMultipleSelection) {
newSelection.putAll(
Arrays.stream(indexes).collect(Collectors.toMap(
i -> i,
i -> selectionModel.getItems().get(i)
))
);
} else {
int index = indexes[0];
newSelection.put(index, selectionModel.getItems().get(index));
}
selection.set(newSelection);
}
/**
* If multiple selection is allowed replaces the selection with all the given items
* (and the retrieved indexes), otherwise replaces the selection with the first given item.
*/
public void replaceSelection(T... items) {
ObservableMap<Integer, T> newSelection = getMap();
if (allowsMultipleSelection) {
newSelection.putAll(
Arrays.stream(items).collect(Collectors.toMap(
item -> selectionModel.getItems().indexOf(item),
item -> item
))
);
} else {
T item = items[0];
newSelection.put(selectionModel.getItems().indexOf(item), item);
}
selection.set(newSelection);
}
/**
* Builds a new observable hash map backed by a {@link LinkedHashMap}.
*/
protected ObservableMap<Integer, T> getMap() {
return FXCollections.observableMap(new LinkedHashMap<>());
}
/**
* Builds a new observable hash map backed by a {@link LinkedHashMap}, initialized with the given map.
*/
protected ObservableMap<Integer, T> getMap(Map<Integer, T> map) {
return FXCollections.observableMap(new LinkedHashMap<>(map));
}
//================================================================================
// Getters/Setters
//================================================================================
/**
* @return the selection {@link ObservableMap}
*/
public ObservableMap<Integer, T> getSelection() {
return selection.get();
}
/**
* The {@link MapProperty} used to keep track of multiple selection.
* <p></p>
* We use a {@link MapProperty} to represent multiple selection because this way
* we can always update it "atomically", meaning that when the selected indexes changes
* the selected items are updated as well (also true viceversa).
*/
public MapProperty<Integer, T> selectionProperty() {
return selection;
}
/**
* Replaces the selection with the given {@link ObservableMap}.
*/
public void setSelection(ObservableMap<Integer, T> selection) {
this.selection.set(selection);
}
/**
* Returns an unmodifiable {@link List} containing all the selected values extracted from
* {@link Map#values()}.
* The values order is kept since the selection is backed by a {@link LinkedHashMap}.
*/
public List<T> getSelectedValues() {
return List.copyOf(selection.values());
}
/**
* Specifies if this model allows multiple selection or should act like
* a SingleSelectionModel.
*/
public boolean allowsMultipleSelection() {
return allowsMultipleSelection;
}
/**
* Sets the selection behavior of this model to be multiple (true) or
* single (false).
* <p>
* If it's set to false the selection is cleared.
*/
public void setAllowsMultipleSelection(boolean allowsMultipleSelection) {
if (!allowsMultipleSelection) clearSelection();
this.allowsMultipleSelection = allowsMultipleSelection;
}
}

View File

@ -1,197 +0,0 @@
/*
* 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.selection;
import io.github.palexdev.materialfx.selection.base.AbstractMultipleSelectionModel;
import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
import javafx.beans.property.MapProperty;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import java.util.List;
/**
* Implementation of {@link AbstractMultipleSelectionModel} to implement the API
* specified by {@link IMultipleSelectionModel}.
* <p></p>
* The logic is handled by {@link MultipleSelectionManager}, in fact all methods are just delegates.
*/
@SuppressWarnings("unchecked")
public class MultipleSelectionModel<T> extends AbstractMultipleSelectionModel<T> {
//================================================================================
// Constructors
//================================================================================
public MultipleSelectionModel(ObservableList<T> items) {
super(items);
}
public MultipleSelectionModel(ObjectProperty<ObservableList<T>> items) {
super(items);
}
//================================================================================
// Override Methods
//================================================================================
/**
* Delegate method for {@link MultipleSelectionManager#clearSelection()}.
*/
@Override
public void clearSelection() {
selectionManager.clearSelection();
}
/**
* Delegate method for {@link MultipleSelectionManager#deselectIndex(int)}.
*/
@Override
public void deselectIndex(int index) {
selectionManager.deselectIndex(index);
}
/**
* Delegate method for {@link MultipleSelectionManager#deselectItem(Object)}.
*/
@Override
public void deselectItem(T item) {
selectionManager.deselectItem(item);
}
/**
* Delegate method for {@link MultipleSelectionManager#deselectIndexes(int...)}.
*/
@Override
public void deselectIndexes(int... indexes) {
selectionManager.deselectIndexes(indexes);
}
/**
* Delegate method for {@link MultipleSelectionManager#deselectItems(Object[])}.
*/
@Override
public void deselectItems(T... items) {
selectionManager.deselectItems(items);
}
/**
* Delegate method for {@link MultipleSelectionManager#updateSelection(int)}.
*/
@Override
public void selectIndex(int index) {
selectionManager.updateSelection(index);
}
/**
* Delegate method for {@link MultipleSelectionManager#updateSelection(Object)}.
*/
@Override
public void selectItem(T item) {
selectionManager.updateSelection(item);
}
/**
* Delegate method for {@link MultipleSelectionManager#updateSelectionByIndexes(List)}.
*/
@Override
public void selectIndexes(List<Integer> indexes) {
selectionManager.updateSelectionByIndexes(indexes);
}
/**
* Delegate method for {@link MultipleSelectionManager#updateSelectionByItems(List)}.
*/
@Override
public void selectItems(List<T> items) {
selectionManager.updateSelectionByItems(items);
}
/**
* Delegate method for {@link MultipleSelectionManager#expandSelection(int)}.
*/
@Override
public void expandSelection(int index) {
selectionManager.expandSelection(index);
}
/**
* Delegate method for {@link MultipleSelectionManager#replaceSelection(Integer...)}.
*/
@Override
public void replaceSelection(Integer... indexes) {
selectionManager.replaceSelection(indexes);
}
/**
* Delegate method for {@link MultipleSelectionManager#replaceSelection(Object[])}.
*/
@Override
public void replaceSelection(T... items) {
selectionManager.replaceSelection(items);
}
/**
* Delegate method for {@link MultipleSelectionManager#getSelection()}.
*/
@Override
public ObservableMap<Integer, T> getSelection() {
return selectionManager.getSelection();
}
/**
* Delegate method for {@link MultipleSelectionManager#selectionProperty()}.
*/
@Override
public MapProperty<Integer, T> selectionProperty() {
return selectionManager.selectionProperty();
}
/**
* Delegate method for {@link MultipleSelectionManager#setSelection(ObservableMap)}.
*/
@Override
public void setSelection(ObservableMap<Integer, T> newSelection) {
selectionManager.setSelection(newSelection);
}
/**
* Delegate method for {@link MultipleSelectionManager#getSelectedValues()}.
*/
@Override
public List<T> getSelectedValues() {
return selectionManager.getSelectedValues();
}
/**
* Delegate method for {@link MultipleSelectionManager#allowsMultipleSelection()}.
*/
@Override
public boolean allowsMultipleSelection() {
return selectionManager.allowsMultipleSelection();
}
/**
* Delegate method for {@link MultipleSelectionManager#setAllowsMultipleSelection(boolean)}.
*/
@Override
public void setAllowsMultipleSelection(boolean allowsMultipleSelection) {
selectionManager.setAllowsMultipleSelection(allowsMultipleSelection);
}
}

View File

@ -0,0 +1,390 @@
package io.github.palexdev.materialfx.selection;
import java.util.*;
import java.util.stream.Collectors;
import io.github.palexdev.materialfx.selection.base.ISelectionModel;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.mfxcore.utils.fx.ListChangeHelper;
import io.github.palexdev.virtualizedfx.utils.Utils;
import javafx.beans.property.ListProperty;
import javafx.beans.property.MapProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import static java.util.function.Function.identity;
public class SelectionModel<T> implements ISelectionModel<T> {
//================================================================================
// Properties
//================================================================================
private final ListProperty<T> items = new SimpleListProperty<>();
private final MapProperty<Integer, T> selection = new SimpleMapProperty<>(newMap());
private SequencedMap<Integer, T> backingMap;
private boolean allowsMultipleSelection = true;
private ListChangeHelper<T> lch;
//================================================================================
// Constructors
//================================================================================
public SelectionModel(ObservableList<T> items) {
if (items instanceof ListProperty<T> lp) {
this.items.bind(lp);
} else {
this.items.set(items);
}
init();
}
//================================================================================
// Methods
//================================================================================
protected void init() {
lch = new ListChangeHelper<>(items)
.setOnClear(selection::clear)
.setOnPermutation(p -> replaceSelection(selection.keySet()
.stream()
.map(p::get)
.toArray(Integer[]::new))
)
.setOnReplace(rep -> {
if (!selection.containsKey(rep)) {
return;
}
selection.put(rep, items.get(rep));
})
.setOnRemoved(rem -> {
List<Integer> updated = ListChangeHelper.shiftOnRemove(selection.keySet(), rem, rem.first());
replaceSelection(updated.toArray(Integer[]::new));
})
.setOnAdded(add -> {
List<Integer> updated = ListChangeHelper.shiftOnAdd(selection.keySet(), add);
replaceSelection(updated.toArray(Integer[]::new));
})
.init();
}
protected ObservableMap<Integer, T> newMap() {
this.backingMap = new LinkedHashMap<>();
return FXCollections.observableMap(backingMap);
}
protected ObservableMap<Integer, T> newMap(Map<Integer, T> map) {
this.backingMap = new LinkedHashMap<>(map);
return FXCollections.observableMap(backingMap);
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
public boolean contains(int index) {
return selection.containsKey(index);
}
@Override
public boolean contains(T element) {
return selection.containsValue(element);
}
@Override
public void clearSelection() {
selection.set(newMap());
}
@Override
public void deselectIndex(int index) {
selection.remove(index);
}
@Override
public void deselectItem(T item) {
int index = items.indexOf(item);
if (index != -1) {
selection.remove(index);
}
}
@Override
public void deselectIndexes(int... indexes) {
ObservableMap<Integer, T> tmp = newMap(selection);
for (int index : indexes) {
tmp.remove(index);
}
selection.set(tmp);
}
@Override
public void deselectIndexes(IntegerRange range) {
ObservableMap<Integer, T> tmp = newMap(selection);
for (Integer index : range) {
tmp.remove(index);
}
selection.set(tmp);
}
@Override
public void deselectItems(T... items) {
Map<Integer, T> tmp = Arrays.stream(items)
.mapToInt(this.items::indexOf)
.filter(i -> i >= 0)
.boxed()
.collect(Collectors.toMap(
identity(),
this.items::get
));
selection.set(newMap(tmp));
}
@Override
public void selectIndex(int index) {
T item = items.get(index);
if (allowsMultipleSelection) {
selection.put(index, item);
} else {
ObservableMap<Integer, T> map = newMap();
map.put(index, item);
selection.set(map);
}
}
@Override
public void selectItem(T item) {
int index = items.indexOf(item);
if (index < 0)
throw new IllegalArgumentException("Item %s is not part of the dataset".formatted(item));
if (allowsMultipleSelection) {
selection.put(index, item);
} else {
ObservableMap<Integer, T> map = newMap();
map.put(index, item);
selection.set(map);
}
}
@Override
public void selectIndexes(Integer... indexes) {
if (indexes.length == 0) {
return;
}
if (allowsMultipleSelection) {
Set<Integer> indexesSet = new LinkedHashSet<>(List.of(indexes));
Map<Integer, T> newSelection = indexesSet.stream()
.collect(Collectors.toMap(
identity(),
items::get,
(t, t2) -> t2,
LinkedHashMap::new
));
selection.putAll(newSelection);
} else {
int index = indexes[indexes.length - 1];
T item = items.get(index);
ObservableMap<Integer, T> map = newMap();
map.put(index, item);
selection.set(map);
}
}
@Override
public void selectIndexes(IntegerRange range) {
if (Utils.INVALID_RANGE.equals(range)) {
return;
}
if (allowsMultipleSelection) {
Map<Integer, T> newSelection = range.stream().collect(Collectors.toMap(
identity(),
items::get,
(t1, t2) -> t2,
LinkedHashMap::new
));
selection.putAll(newSelection);
} else {
int index = range.getMax();
T item = items.get(index);
ObservableMap<Integer, T> map = newMap();
map.put(index, item);
selection.set(map);
}
}
@Override
public void selectItems(T... items) {
if (items.length == 0) {
return;
}
if (allowsMultipleSelection) {
Set<Integer> indexesSet = Arrays.stream(items)
.mapToInt(this.items::indexOf)
.filter(i -> i >= 0)
.boxed()
.collect(Collectors.toSet());
Map<Integer, T> newSelection = indexesSet.stream()
.collect(Collectors.toMap(
identity(),
i -> items[i]
));
selection.putAll(newSelection);
} else {
T item = items[items.length - 1];
int index = this.items.indexOf(item);
if (index < 0)
throw new IllegalArgumentException("Item %s is not part of the dataset".formatted(item));
ObservableMap<Integer, T> map = newMap();
map.put(index, item);
selection.set(map);
}
}
@Override
public void expandSelection(int index, boolean fromLast) {
if (selection.isEmpty()) {
replaceSelection(IntegerRange.of(0, index));
return;
}
if (fromLast) {
Map.Entry<Integer, T> last = backingMap.lastEntry();
Integer lastIndex = last.getKey();
int min = Math.min(lastIndex, index);
int max = Math.max(lastIndex, index);
selectIndexes(IntegerRange.of(min, max));
return;
}
int min = selection.keySet().stream()
.min(Integer::compareTo)
.orElse(-1);
if (index == min) {
replaceSelection(index);
return;
}
IntegerRange range = (index < min) ?
IntegerRange.of(index, min) :
IntegerRange.of(min, index);
replaceSelection(range);
}
@Override
public void replaceSelection(Integer... indexes) {
if (indexes.length == 0) {
selection.set(newMap());
return;
}
ObservableMap<Integer, T> newSelection = newMap();
if (allowsMultipleSelection) {
newSelection.putAll(
Arrays.stream(indexes)
.collect(Collectors.toMap(
identity(),
items::get)
)
);
} else {
Integer index = indexes[indexes.length - 1];
T item = items.get(index);
newSelection.put(index, item);
}
selection.set(newSelection);
}
@Override
public void replaceSelection(IntegerRange range) {
if (Utils.INVALID_RANGE.equals(range)) {
return;
}
ObservableMap<Integer, T> newSelection;
if (allowsMultipleSelection) {
newSelection = range.stream().collect(Collectors.toMap(
identity(),
items::get,
(t1, t2) -> t2,
this::newMap
));
} else {
newSelection = newMap();
int index = range.getMax();
T item = items.get(index);
newSelection.put(index, item);
}
selection.set(newSelection);
}
@Override
public void replaceSelection(T... items) {
ObservableMap<Integer, T> newSelection = newMap();
if (allowsMultipleSelection) {
newSelection.putAll(
Arrays.stream(items)
.mapToInt(this.items::indexOf)
.filter(i -> i >= 0)
.boxed()
.collect(Collectors.toMap(
identity(),
this.items::get
)
)
);
} else {
T item = items[items.length - 1];
int index = this.items.indexOf(item);
if (index < 0)
throw new IllegalArgumentException("Item %s is not part of the dataset".formatted(item));
newSelection.put(index, item);
}
selection.set(newSelection);
}
@Override
public MapProperty<Integer, T> selection() {
return selection;
}
@Override
public List<T> getSelectedItems() {
return List.copyOf(selection.values());
}
@Override
public boolean allowsMultipleSelection() {
return allowsMultipleSelection;
}
@Override
public void setAllowsMultipleSelection(boolean allowsMultipleSelection) {
// Clear selection when switching modes
if (this.allowsMultipleSelection != allowsMultipleSelection) {
if (!allowsMultipleSelection && size() > 1)
selection.clear();
}
this.allowsMultipleSelection = allowsMultipleSelection;
}
@Override
public Map.Entry<Integer, T> getSelectedEntry() {
return (size() == 0 || backingMap == null) ? null : backingMap.lastEntry();
}
@Override
public void dispose() {
lch.dispose();
lch = null;
items.unbind();
items.clear();
selection.clear();
}
//================================================================================
// Getters
//================================================================================
public ObservableList<T> getItems() {
return FXCollections.unmodifiableObservableList(items);
}
}

View File

@ -1,390 +0,0 @@
/*
* 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.selection;
import io.github.palexdev.materialfx.beans.properties.base.SynchronizedProperty;
import io.github.palexdev.materialfx.beans.properties.synced.SynchronizedIntegerProperty;
import io.github.palexdev.materialfx.beans.properties.synced.SynchronizedObjectProperty;
import io.github.palexdev.materialfx.bindings.BiBindingManager;
import io.github.palexdev.materialfx.bindings.BindingManager;
import io.github.palexdev.materialfx.selection.base.AbstractSingleSelectionModel;
import io.github.palexdev.materialfx.utils.others.TriConsumer;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import java.util.function.Function;
/**
* Helper class for {@link AbstractSingleSelectionModel} models to properly handle the selection
* and the bindings with properties or other models.
* <p></p>
* Both the selectedIndex and selectedItem properties are SynchronizedProperties, see {@link SynchronizedProperty}.
* <p>
* So when you select an index the item will be automatically updated and only then a change event will be fired,
* the same thing happens if you select an item.
* <p></p>
* Invalid values, like -1 for the index or null for the item, will throw an exception. To clear the selection
* use {@link #clearSelection()}, a boolean flag will be set to true thus allowing setting the aforementioned values.
*/
public class SingleSelectionManager<T> {
//================================================================================
// Properties
//================================================================================
private final AbstractSingleSelectionModel<T> selectionModel;
private final SynchronizedIntegerProperty selectedIndex = new SynchronizedIntegerProperty(-1);
private final SynchronizedObjectProperty<T> selectedItem = new SynchronizedObjectProperty<>(null);
private boolean clearing;
//================================================================================
// Constructors
//================================================================================
public SingleSelectionManager(AbstractSingleSelectionModel<T> selectionModel) {
this.selectionModel = selectionModel;
}
//================================================================================
// Methods
//================================================================================
/**
* Sets the index to -1 and item to null by using {@link SynchronizedProperty#setAndWait(Object, ObservableValue)}.
*
* @throws IllegalStateException if the selection model is bound, {@link #isBound()}
*/
public void clearSelection() {
if (isBound()) {
throw new IllegalStateException("Cannot clear the selection as this selection model is bound to some other property");
}
clearing = true;
selectedIndex.setAndWait(-1, selectedItem);
selectedItem.set(null);
selectedIndex.awake();
clearing = false;
}
/**
* Updates the selection with the given index (and the retrieved item) by using
* {@link SynchronizedProperty#setAndWait(Object, ObservableValue)}.
*
* @throws IllegalStateException if the selection model is bound, {@link #isBound()}
*/
public void updateSelection(int index) {
if (isBound()) {
throw new IllegalStateException("Cannot set the selected index as this selection model is bound to some other property");
}
if (clearing) {
clearSelection();
return;
}
T item = selectionModel.getUnmodifiableItems().get(index);
selectedIndex.setAndWait(index, selectedItem);
selectedItem.set(item);
if (selectedIndex.isWaiting()) selectedIndex.awake();
}
/**
* Updates the selection with the given item (and the retrieved index) by using
* {@link SynchronizedProperty#setAndWait(Object, ObservableValue)}.
*
* @throws IllegalStateException if the selection model is bound, {@link #isBound()}
*/
public void updateSelection(T item) {
if (isBound()) {
throw new IllegalStateException("Cannot set the selected item as this selection model is bound to some other property");
}
if (clearing) {
clearSelection();
return;
}
int index = selectionModel.getUnmodifiableItems().indexOf(item);
if (index == -1) {
throw new IllegalArgumentException("The given item is not present is this selection model's list");
}
selectedItem.setAndWait(item, selectedIndex);
selectedIndex.set(index);
if (selectedItem.isWaiting()) selectedItem.awake();
}
//================================================================================
// Getters/Setters
//================================================================================
/**
* @return the current selected index
*/
public int getSelectedIndex() {
return selectedIndex.get();
}
/**
* The selected index property.
*/
public SynchronizedIntegerProperty selectedIndexProperty() {
return selectedIndex;
}
/**
* @return the current selected item
*/
public T getSelectedItem() {
return selectedItem.get();
}
/**
* The selected item property.
*/
public SynchronizedObjectProperty<T> selectedItemProperty() {
return selectedItem;
}
/**
* Flag to specify that updateSelection should be ignored as {@link #clearSelection()} was invoked.
*/
public void setClearing(boolean clearing) {
this.clearing = clearing;
}
//================================================================================
// Bindings
//================================================================================
/**
* Binds the index property to given source {@link ObservableValue}.
* The indexConverter function is used to convert the index values to an item
* of the selection model.
* <p></p>
* By default creates this binding:
* <pre>
* {@code
* BindingManager.instance().bind(selectedIndex)
* .with((oldValue, newValue) -> {
* T item = indexConverter.apply(newValue.intValue());
* selectedIndex.setAndWait(newValue.intValue(), selectedItem);
* selectedItem.set(item);
* })
* .to(source)
* .create();
* }
* </pre>
* To change it you should override the {@link SingleSelectionModel} method.
*/
public void bindIndex(ObservableValue<? extends Number> source, Function<Integer, T> indexConverter) {
if (selectedIndex.isBound()) selectedIndex.unbind();
BindingManager.instance().bind(selectedIndex)
.with((oldValue, newValue) -> {
T item = indexConverter.apply(newValue.intValue());
selectedIndex.setAndWait(newValue.intValue(), selectedItem);
selectedItem.set(item);
})
.to(source)
.create();
}
/**
* Binds the index property bidirectionally to given other {@link Property}.
* The indexConverter function is used to convert the index from the other property
* to an item of the selection model.
* <p>
* The updateOther {@link TriConsumer} is used to customize the way the other
* property is updated, the first parameter is the clearing flag of the selection manager,
* the second parameter is the new index, the third parameter is the other property reference.
* <p></p>
* By default creates this binding:
* <pre>
* {@code
* BiBindingManager.instance().bindBidirectional(selectedIndex)
* .with((oldValue, newValue) -> {
* if (newValue.intValue() == -1) {
* clearSelection();
* return;
* }
*
* if (newValue.intValue() == selectedIndex.getValue()) {
* return;
* }
* T item = indexConverter.apply(newValue.intValue());
* selectedIndex.setAndWait(newValue.intValue(), selectedItem);
* selectedItem.set(item);
* })
* .to(other, (oldValue, newValue) -> updateOther.accept(clearing, newValue.intValue(), other))
* .create();
* }
* </pre>
* To change it you should override the {@link SingleSelectionModel} method.
*/
public void bindIndexBidirectional(Property<Number> other, Function<Integer, T> indexConverter, TriConsumer<Boolean, Integer, Property<Number>> updateOther) {
if (selectedIndex.isBound()) selectedIndex.unbind();
BiBindingManager.instance().bindBidirectional(selectedIndex)
.with((oldValue, newValue) -> {
if (newValue.intValue() == -1) {
clearSelection();
return;
}
if (newValue.intValue() == selectedIndex.getValue()) {
return;
}
T item = indexConverter.apply(newValue.intValue());
selectedIndex.setAndWait(newValue.intValue(), selectedItem);
selectedItem.set(item);
})
.to(other, (oldValue, newValue) -> updateOther.accept(clearing, newValue.intValue(), other))
.create();
}
/**
* Binds the item property to given source {@link ObservableValue}.
* The itemConverter function is used to convert the item values to an index
* of the selection model.
* <p></p>
* By default creates this binding:
* <pre>
* {@code
* BindingManager.instance().bind(selectedItem)
* .with((oldValue, newValue) -> {
* if (!selectionModel.getUnmodifiableItems().contains(newValue)) {
* throw new IllegalArgumentException("The given item is not present is this selection model's list");
* }
* int index = itemConverter.apply(newValue);
* selectedItem.setAndWait(newValue, selectedIndex);
* selectedIndex.set(index);
* })
* .to(source)
* .create();
* }
* </pre>
* To change it you should override the {@link SingleSelectionModel} method.
*/
public void bindItem(ObservableValue<? extends T> source, Function<T, Integer> itemConverter) {
if (selectedItem.isBound()) selectedItem.unbind();
BindingManager.instance().bind(selectedItem)
.with((oldValue, newValue) -> {
if (!selectionModel.getUnmodifiableItems().contains(newValue)) {
throw new IllegalArgumentException("The given item is not present is this selection model's list");
}
int index = itemConverter.apply(newValue);
selectedItem.setAndWait(newValue, selectedIndex);
selectedIndex.set(index);
})
.to(source)
.create();
}
/**
* Binds the item property bidirectionally to given other {@link Property}.
* The itemConverter function is used to convert the item from the other property
* to an index of the selection model.
* <p>
* The updateOther {@link TriConsumer} is used to customize the way the other
* property is updated, the first parameter is the clearing flag of the selection manager,
* the second parameter is the new item, the third parameter is the other property reference.
* <p></p>
* By default creates this binding:
* <pre>
* {@code
* BiBindingManager.instance().bindBidirectional(selectedItem)
* .with((oldValue, newValue) -> {
* if (newValue == null) {
* clearSelection();
* return;
* }
*
* if (!selectionModel.getUnmodifiableItems().contains(newValue)) {
* throw new IllegalArgumentException("The given item is not present is this selection model's list");
* }
*
* int index = itemConverter.apply(newValue);
* selectedItem.setAndWait(newValue, selectedIndex);
* selectedIndex.set(index);
* })
* .to(other, (oldValue, newValue) -> updateOther.accept(clearing, newValue, other))
* .create();
* }
* </pre>
* To change it you should override the {@link SingleSelectionModel} method.
*/
public void bindItemBidirectional(Property<T> other, Function<T, Integer> itemConverter, TriConsumer<Boolean, T, Property<T>> updateOther) {
if (selectedItem.isBound()) selectedItem.unbind();
BiBindingManager.instance().bindBidirectional(selectedItem)
.with((oldValue, newValue) -> {
if (newValue == null) {
clearSelection();
return;
}
if (!selectionModel.getUnmodifiableItems().contains(newValue)) {
throw new IllegalArgumentException("The given item is not present is this selection model's list");
}
int index = itemConverter.apply(newValue);
selectedItem.setAndWait(newValue, selectedIndex);
selectedIndex.set(index);
})
.to(other, (oldValue, newValue) -> updateOther.accept(clearing, newValue, other))
.create();
}
/**
* Unbinds the selection.
*/
public void unbind() {
if (selectedIndex.isBound()) {
selectedIndex.unbind();
}
if (selectedItem.isBound()) {
selectedItem.unbind();
}
}
/**
* Removes the bidirectional binding between the index property and the given other property.
*/
public void unbindIndexBidirectional(Property<Number> other) {
selectedIndex.unbindBidirectional(other);
}
/**
* Removes the bidirectional binding between the item property and the given other property.
*/
public void unbindItemBidirectional(Property<T> other) {
selectedItem.unbindBidirectional(other);
}
/**
* Removes all bidirectional bindings.
*/
public void unbindBidirectional() {
selectedIndex.clearBidirectional();
selectedItem.clearBidirectional();
}
/**
* Returns true if the selected index or item properties are bound
* unidirectionally.
*/
public boolean isBound() {
return selectedIndex.isBound() || selectedItem.isBound();
}
}

View File

@ -1,268 +0,0 @@
/*
* 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.selection;
import io.github.palexdev.materialfx.selection.base.AbstractSingleSelectionModel;
import io.github.palexdev.materialfx.selection.base.ISingleSelectionModel;
import io.github.palexdev.materialfx.utils.others.TriConsumer;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import java.util.function.Function;
/**
* Implementation of {@link AbstractSingleSelectionModel} to implement the API
* specified by {@link ISingleSelectionModel}.
* <p></p>
* The logic is handled by {@link SingleSelectionManager}, in fact all methods are just delegates.
*/
public class SingleSelectionModel<T> extends AbstractSingleSelectionModel<T> {
//================================================================================
// Constructors
//================================================================================
public SingleSelectionModel(ObservableList<T> items) {
super(items);
}
public SingleSelectionModel(ObjectProperty<ObservableList<T>> items) {
super(items);
}
//================================================================================
// Delegate Methods
//================================================================================
/**
* Delegate method for {@link SingleSelectionManager#clearSelection()}.
*/
@Override
public void clearSelection() {
selectionManager.clearSelection();
}
/**
* Delegate method for {@link SingleSelectionManager#updateSelection(int)}.
*/
@Override
public void selectIndex(int index) {
selectionManager.updateSelection(index);
}
/**
* Delegate method for {@link SingleSelectionManager#updateSelection(Object)}.
*/
@Override
public void selectItem(T item) {
selectionManager.updateSelection(item);
}
/**
* Delegate method for {@link SingleSelectionManager#getSelectedIndex()}}.
*/
@Override
public int getSelectedIndex() {
return selectionManager.getSelectedIndex();
}
/**
* Delegate method for {@link SingleSelectionManager#selectedIndexProperty()}, but
* a read-only property is returned.
*/
@Override
public ReadOnlyIntegerProperty selectedIndexProperty() {
return selectionManager.selectedIndexProperty().getReadOnlyProperty();
}
/**
* Delegate method for {@link SingleSelectionManager#getSelectedItem()}.
*/
@Override
public T getSelectedItem() {
return selectionManager.getSelectedItem();
}
/**
* Delegate method for {@link SingleSelectionManager#selectedItemProperty()}, but
* a read-only property is returned.
*/
@Override
public ReadOnlyObjectProperty<T> selectedItemProperty() {
return selectionManager.selectedItemProperty().getReadOnlyProperty();
}
//================================================================================
// Bindings
//================================================================================
/**
* Binds this selection model's index to the given selection model's index,
* calls {@link SingleSelectionManager#bindIndex(ObservableValue, Function)}.
* <p></p>
* Default implementation:
* <pre>
* {@code
* selectionManager.bindIndex(selectionModel.selectionManager.selectedIndexProperty(), getItems()::get);
* }
* </pre>
*/
public void bindIndex(SingleSelectionModel<T> selectionModel) {
selectionManager.bindIndex(selectionModel.selectionManager.selectedIndexProperty(), getItems()::get);
}
/**
* Binds this selection model's index bidirectionally to the given selection model's index,
* calls {@link SingleSelectionManager#bindIndexBidirectional(Property, Function, TriConsumer)}.
* <p></p>
* Default implementation:
* <pre>
* {@code
* selectionManager.bindIndexBidirectional(
* selectionModel.selectionManager.selectedIndexProperty(),
* getItems()::get,
* (clearing, i, other) -> {
* selectionModel.selectionManager.setClearing(clearing);
* selectionModel.selectionManager.updateSelection(i);
* }
* );
* }
* </pre>
*/
public void bindIndexBidirectional(SingleSelectionModel<T> selectionModel) {
selectionManager.bindIndexBidirectional(
selectionModel.selectionManager.selectedIndexProperty(),
getItems()::get,
(clearing, i, other) -> {
selectionModel.selectionManager.setClearing(clearing);
selectionModel.selectionManager.updateSelection(i);
}
);
}
/**
* Binds this selection model's item to the given selection model's item,
* calls {@link SingleSelectionManager#bindItem(ObservableValue, Function)}.
* <p></p>
* Default implementation:
* <pre>
* {@code
* selectionManager.bindItem(selectionModel.selectionManager.selectedItemProperty(), getItems()::indexOf);
* }
* </pre>
*/
public void bindItem(SingleSelectionModel<T> selectionModel) {
selectionManager.bindItem(selectionModel.selectionManager.selectedItemProperty(), getItems()::indexOf);
}
/**
* Binds this selection model's item bidirectionally to the given selection model's item,
* calls {@link SingleSelectionManager#bindItemBidirectional(Property, Function, TriConsumer)}.
* <p></p>
* Default implementation:
* <pre>
* {@code
* selectionManager.bindItemBidirectional(
* selectionModel.selectionManager.selectedItemProperty(),
* getItems()::indexOf,
* (clearing, item, other) -> {
* selectionModel.selectionManager.setClearing(clearing);
* selectionModel.selectionManager.updateSelection(item);
* }
* );
* }
* </pre>
*/
public void bindItemBidirectional(SingleSelectionModel<T> selectionModel) {
selectionManager.bindItemBidirectional(
selectionModel.selectionManager.selectedItemProperty(),
getItems()::indexOf,
(clearing, item, other) -> {
selectionModel.selectionManager.setClearing(clearing);
selectionModel.selectionManager.updateSelection(item);
}
);
}
/**
* Delegate method for {@link SingleSelectionManager#bindIndex(ObservableValue, Function)}.
*/
public void bindIndex(ObservableValue<? extends Number> source, Function<Integer, T> indexConverter) {
selectionManager.bindIndex(source, indexConverter);
}
/**
* Delegate method for {@link SingleSelectionManager#bindIndexBidirectional(Property, Function, TriConsumer)}.
*/
public void bindIndexBidirectional(Property<Number> other, Function<Integer, T> indexConverter, TriConsumer<Boolean, Integer, Property<Number>> updateOther) {
selectionManager.bindIndexBidirectional(other, indexConverter, updateOther);
}
/**
* Delegate method for {@link SingleSelectionManager#bindItem(ObservableValue, Function)}.
*/
public void bindItem(ObservableValue<? extends T> source, Function<T, Integer> itemConverter) {
selectionManager.bindItem(source, itemConverter);
}
/**
* Delegate method for {@link SingleSelectionManager#bindItemBidirectional(Property, Function, TriConsumer)}.
*/
public void bindItemBidirectional(Property<T> other, Function<T, Integer> itemConverter, TriConsumer<Boolean, T, Property<T>> updateOther) {
selectionManager.bindItemBidirectional(other, itemConverter, updateOther);
}
/**
* Delegate method for {@link SingleSelectionManager#unbind()}.
*/
public void unbind() {
selectionManager.unbind();
}
/**
* Delegate method for {@link SingleSelectionManager#unbindIndexBidirectional(Property)}.
*/
public void unbindIndexBidirectional(Property<Number> other) {
selectionManager.unbindIndexBidirectional(other);
}
/**
* Delegate method for {@link SingleSelectionManager#unbindItemBidirectional(Property)}.
*/
public void unbindItemBidirectional(Property<T> other) {
selectionManager.unbindItemBidirectional(other);
}
/**
* Delegate method for {@link SingleSelectionManager#unbindBidirectional()}.
*/
public void unbindBidirectional() {
selectionManager.unbindBidirectional();
}
/**
* Delegate method for {@link SingleSelectionManager#isBound()}.
*/
public boolean isBound() {
return selectionManager.isBound();
}
}

View File

@ -1,65 +0,0 @@
/*
* 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.selection.base;
import io.github.palexdev.materialfx.selection.MultipleSelectionManager;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
/**
* Abstract base class for all MultipleSelectionModels.
* <p>
* This class holds a property for the items list. Controls that uses this selection model are
* responsible for changes in the source list, so if anything changes there be sure to keep the
* selection model in a consistent state.
* Also holds a reference for {@link MultipleSelectionManager}, the class that is effectively
* responsible for updating/managing the selection model' state.
*/
public abstract class AbstractMultipleSelectionModel<T> implements IMultipleSelectionModel<T> {
//================================================================================
// Properties
//================================================================================
protected final ObjectProperty<ObservableList<T>> items = new SimpleObjectProperty<>();
protected final MultipleSelectionManager<T> selectionManager = new MultipleSelectionManager<>(this);
//================================================================================
// Constructors
//================================================================================
protected AbstractMultipleSelectionModel(ObservableList<T> items) {
this.items.set(items);
}
protected AbstractMultipleSelectionModel(ObservableValue<? extends ObservableList<T>> items) {
this.items.bind(items);
}
//================================================================================
// Getters/Setters
//================================================================================
/**
* @return an unmodifiable copy of the items list
*/
public ObservableList<T> getItems() {
return FXCollections.unmodifiableObservableList(items.get());
}
}

View File

@ -1,72 +0,0 @@
/*
* 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.selection.base;
import io.github.palexdev.materialfx.selection.SingleSelectionManager;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
/**
* Abstract base class for all SingleSelectionModels.
* <p>
* This class holds a property for the items list. Controls that uses this selection model are
* responsible for changes in the source list, so if anything changes there be sure to keep the
* selection model in a consistent state.
* Also holds a reference for {@link SingleSelectionManager}, the class that is effectively
* responsible for updating/managing the selection model' state.
*/
public abstract class AbstractSingleSelectionModel<T> implements ISingleSelectionModel<T> {
//================================================================================
// Properties
//================================================================================
protected final ObjectProperty<ObservableList<T>> items = new SimpleObjectProperty<>();
protected final SingleSelectionManager<T> selectionManager = new SingleSelectionManager<>(this);
//================================================================================
// Constructors
//================================================================================
protected AbstractSingleSelectionModel(ObservableList<T> items) {
this.items.set(items);
}
protected AbstractSingleSelectionModel(ObservableValue<? extends ObservableList<T>> items) {
this.items.bind(items);
}
//================================================================================
// Getters/Setters
//================================================================================
/**
* @return an unmodifiable copy of the items list
*/
public ObservableList<T> getUnmodifiableItems() {
return FXCollections.unmodifiableObservableList(items.get());
}
/**
* @return the items list
*/
final protected ObservableList<T> getItems() {
return items.get();
}
}

View File

@ -1,145 +0,0 @@
/*
* 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.selection.base;
import javafx.beans.property.MapProperty;
import javafx.collections.ObservableMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Public API that every MultipleSelectionModel must implement.
*/
@SuppressWarnings("unchecked")
public interface IMultipleSelectionModel<T> {
/**
* Clears the selection.
*/
void clearSelection();
/**
* Deselects the given index.
*/
void deselectIndex(int index);
/**
* Deselects the given item.
*/
void deselectItem(T item);
/**
* Deselects the given indexes.
*/
void deselectIndexes(int... indexes);
/**
* Deselects the given items.
*/
void deselectItems(T... items);
/**
* Selects the given index.
*/
void selectIndex(int index);
/**
* Selects the given item.
*/
void selectItem(T item);
/**
* Selects the given indexes list.
*/
void selectIndexes(List<Integer> indexes);
/**
* Selects the given items list.
*/
void selectItems(List<T> items);
/**
* Expands the selection in the given index direction.
*/
void expandSelection(int index);
/**
* Clears the selection and replaces it with the given indexes.
*/
void replaceSelection(Integer... indexes);
/**
* Clears the selection and replaces it with the given items.
*/
void replaceSelection(T... items);
/**
* @return the selection {@link ObservableMap}
*/
ObservableMap<Integer, T> getSelection();
/**
* The {@link MapProperty} used to keep track of multiple selection.
* <p></p>
* We use a {@link MapProperty} to represent multiple selection because this way
* we can always update it "atomically", meaning that when the selected indexes changes
* the selected items are updated as well (also true viceversa).
*/
MapProperty<Integer, T> selectionProperty();
/**
* Replaces the selection with the given {@link ObservableMap}.
*/
void setSelection(ObservableMap<Integer, T> newSelection);
/**
* Returns an unmodifiable {@link List} containing all the selected values extracted from
* {@link Map#values()}.
* The values order is kept since the selection is backed by a {@link LinkedHashMap}.
*/
List<T> getSelectedValues();
/**
* @return the first selected item or null if selection is empty
*/
default T getSelectedValue() {
return getSelectedValues().isEmpty() ? null : getSelectedValues().get(0);
}
/**
* @return the last selected item or null if selection is empty
*/
default T getLastSelectedValue() {
return getSelectedValues().isEmpty() ? null : getSelectedValues().get(getSelectedValues().size() - 1);
}
/**
* Specifies if this model allows multiple selection or should act like
* a SingleSelectionModel.
*/
boolean allowsMultipleSelection();
/**
* Sets the selection behavior of this model to be multiple (true) or
* single (false).
*/
void setAllowsMultipleSelection(boolean allowsMultipleSelection);
}

View File

@ -0,0 +1,88 @@
package io.github.palexdev.materialfx.selection.base;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import javafx.beans.property.MapProperty;
@SuppressWarnings("unchecked")
public interface ISelectionModel<T> {
boolean contains(int index);
boolean contains(T element);
void clearSelection();
void deselectIndex(int index);
void deselectItem(T item);
void deselectIndexes(int... indexes);
void deselectIndexes(IntegerRange range);
void deselectItems(T... items);
void selectIndex(int index);
void selectItem(T item);
void selectIndexes(Integer... indexes);
void selectIndexes(IntegerRange range);
void selectItems(T... items);
void expandSelection(int index, boolean fromLast);
void replaceSelection(Integer... indexes);
void replaceSelection(IntegerRange range);
void replaceSelection(T... items);
MapProperty<Integer, T> selection();
List<T> getSelectedItems();
default int size() {
return selection().size();
}
default boolean isEmpty() {
return selection().isEmpty();
}
default T getSelectedItem() {
return (size() == 0) ? null : getSelectedItems().getFirst();
}
default Optional<T> getSelectedItemOpt() {
return Optional.ofNullable(getSelectedItem());
}
default T getLastSelectedItem() {
int size = size();
return (size == 0) ? null : getSelectedItems().get(size - 1);
}
default Optional<T> getLastSelectedItemOpt() {
return Optional.ofNullable(getLastSelectedItem());
}
default Map.Entry<Integer, T> getSelectedEntry() {
return (size() == 0) ? null : selection().entrySet().iterator().next();
}
default Optional<Map.Entry<Integer, T>> getSelectedEntryOpt() {
return Optional.ofNullable(getSelectedEntry());
}
boolean allowsMultipleSelection();
void setAllowsMultipleSelection(boolean allowsMultipleSelection);
void dispose();
}

View File

@ -1,66 +0,0 @@
/*
* 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.selection.base;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
/**
* Public API that every SingleSelectionModel must implement.
*/
public interface ISingleSelectionModel<T> {
/**
* Clears the selection.
*/
void clearSelection();
/**
* Selects the given index.
*/
void selectIndex(int index);
/**
* Selects the given item.
*/
void selectItem(T item);
/**
* @return the current selected index
*/
int getSelectedIndex();
/**
* The selected index property as a read-only property.
* Selection should always be updated with the dedicated methods.
*/
ReadOnlyIntegerProperty selectedIndexProperty();
/**
* @return the current selected item
*/
T getSelectedItem();
/**
* The selected item property as a read-only property.
* Selection should always be updated with the dedicated methods.
*/
ReadOnlyObjectProperty<T> selectedItemProperty();
}

View File

@ -0,0 +1,5 @@
package io.github.palexdev.materialfx.selection.base;
public interface WithSelectionModel<T> {
ISelectionModel<T> getSelectionModel();
}

View File

@ -0,0 +1,89 @@
package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.controls.MFXCheckbox;
import io.github.palexdev.materialfx.controls.cell.MFXCheckListCell;
import io.github.palexdev.mfxcore.base.beans.Position;
import io.github.palexdev.mfxcore.events.WhenEvent;
import io.github.palexdev.mfxcore.utils.fx.LayoutUtils;
import io.github.palexdev.virtualizedfx.cells.CellBaseBehavior;
import io.github.palexdev.virtualizedfx.cells.VFXCellBase;
import io.github.palexdev.virtualizedfx.events.VFXContainerEvent;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.ContentDisplay;
import javafx.scene.input.MouseEvent;
public class MFXCheckListCellSkin<T> extends MFXListCellSkin<T> {
//================================================================================
// Properties
//================================================================================
private final MFXCheckbox checkbox;
//================================================================================
// Constructors
//================================================================================
public MFXCheckListCellSkin(MFXCheckListCell<T> cell) {
super(cell);
checkbox = new MFXCheckbox() {
@Override
public void fire() {
}
};
checkbox.setContentDisposition(ContentDisplay.GRAPHIC_ONLY);
checkbox.selectedProperty().bind(cell.selectedProperty());
getChildren().add(checkbox);
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected MFXCheckListCell<T> getCell() {
return (MFXCheckListCell<T>) super.getCell();
}
@Override
protected void initBehavior(CellBaseBehavior<T> behavior) {
VFXCellBase<T> cell = getSkinnable();
behavior.init();
events(
WhenEvent.intercept(cell, VFXContainerEvent.UPDATE)
.process(e -> {
update();
e.consume();
}),
WhenEvent.intercept(checkbox, MouseEvent.MOUSE_CLICKED)
.process(e -> {
behavior.mouseClicked(null);
e.consume();
})
);
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
MFXCheckListCell<T> cell = getCell();
// Checkbox
checkbox.autosize();
Position cPos = LayoutUtils.computePosition(
cell, checkbox,
x, y, w, h, 0, Insets.EMPTY,
HPos.LEFT, VPos.CENTER
);
checkbox.relocate(cPos.getX(), cPos.getY());
// Label
double gap = cell.getHGap();
layoutInArea(
label,
x + gap + checkbox.getWidth(), y, w, h, 0,
HPos.LEFT, VPos.CENTER
);
// Ripple
rg.resizeRelocate(0, 0, cell.getWidth(), cell.getHeight());
}
}

View File

@ -21,8 +21,9 @@ package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.controls.*;
import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel;
import io.github.palexdev.materialfx.utils.AnimationUtils;
import io.github.palexdev.virtualizedfx.cell.Cell;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import io.github.palexdev.virtualizedfx.list.VFXList;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -55,7 +56,7 @@ public class MFXComboBoxSkin<T> extends MFXTextFieldSkin {
private EventHandler<MouseEvent> popupManager;
protected final BooleanProperty vfInitialized = new SimpleBooleanProperty(false);
protected SimpleVirtualFlow<T, Cell<T>> virtualFlow;
protected VFXList<T, VFXCell<T>> vfxList;
//================================================================================
// Constructors
@ -125,8 +126,7 @@ public class MFXComboBoxSkin<T> extends MFXTextFieldSkin {
private void selectionBehavior() {
MFXComboBox<T> comboBox = getComboBox();
ComboBoxSelectionModel<T> selectionModel = comboBox.getSelectionModel();
selectionModel.selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
selectionModel.selection().addListener((InvalidationListener) i -> {
if (!comboBox.valueProperty().isBound()) {
comboBox.setValue(selectionModel.getSelectedItem());
}
@ -155,8 +155,7 @@ public class MFXComboBoxSkin<T> extends MFXTextFieldSkin {
popup.showingProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
comboBox.hide();
if (trailingIcon instanceof MFXIconWrapper) {
MFXIconWrapper icon = (MFXIconWrapper) trailingIcon;
if (trailingIcon instanceof MFXIconWrapper icon) {
icon.getRippleGenerator().generateRipple(null);
}
animateIcon(comboBox.getTrailingIcon(), false);
@ -187,7 +186,7 @@ public class MFXComboBoxSkin<T> extends MFXTextFieldSkin {
.setOnFinished(end -> {
if (comboBox.isScrollOnOpen()) {
int selectedIndex = comboBox.getSelectedIndex();
if (selectedIndex >= 0) virtualFlow.scrollTo(selectedIndex);
if (selectedIndex >= 0) vfxList.scrollToIndex(selectedIndex);
}
})
.getAnimation()
@ -245,26 +244,26 @@ public class MFXComboBoxSkin<T> extends MFXTextFieldSkin {
*/
protected Node createPopupContent() {
MFXComboBox<T> comboBox = getComboBox();
if (virtualFlow == null) {
virtualFlow = new SimpleVirtualFlow<>(
if (vfxList == null) {
vfxList = new VFXList<>(
comboBox.itemsProperty(),
comboBox.getCellFactory(),
Orientation.VERTICAL
);
virtualFlow.cellFactoryProperty().bind(comboBox.cellFactoryProperty());
virtualFlow.prefWidthProperty().bind(comboBox.widthProperty());
vfxList.getCellFactory().bind(comboBox.cellFactoryProperty());
vfxList.prefWidthProperty().bind(comboBox.widthProperty());
Runnable createBinding = () ->
virtualFlow.prefHeightProperty().bind(Bindings.createDoubleBinding(
() -> Math.min(comboBox.getRowsCount(), comboBox.getItems().size()) * virtualFlow.getCellHeight(),
comboBox.rowsCountProperty(), comboBox.getItems(), virtualFlow.cellFactoryProperty(), vfInitialized
vfxList.prefHeightProperty().bind(Bindings.createDoubleBinding(
() -> Math.min(comboBox.getRowsCount(), comboBox.getItems().size()) * vfxList.getCellSize(),
comboBox.rowsCountProperty(), comboBox.getItems(), vfxList.cellSizeProperty(), vfInitialized
));
virtualFlow.itemsProperty().addListener((observable, oldValue, newValue) -> {
vfxList.itemsProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) createBinding.run();
});
createBinding.run();
}
return virtualFlow;
return vfxList.makeScrollable();
}
/**
@ -283,7 +282,7 @@ public class MFXComboBoxSkin<T> extends MFXTextFieldSkin {
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
if (virtualFlow != null && !vfInitialized.get() && virtualFlow.getCellHeight() != 0)
if (vfxList != null && !vfInitialized.get() && vfxList.getCellSize() != 0)
vfInitialized.set(true);
}
@ -295,6 +294,6 @@ public class MFXComboBoxSkin<T> extends MFXTextFieldSkin {
comboBox.getTrailingIcon().removeEventHandler(MouseEvent.MOUSE_PRESSED, popupManager);
}
popupManager = null;
virtualFlow = null;
vfxList = null;
}
}

View File

@ -18,6 +18,18 @@
package io.github.palexdev.materialfx.skins;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import io.github.palexdev.materialfx.beans.NumberRange;
import io.github.palexdev.materialfx.controls.*;
import io.github.palexdev.materialfx.controls.cell.MFXDateCell;
@ -40,18 +52,6 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.util.StringConverter;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Skin associated with every {@link MFXDatePicker} by default.
* <p>
@ -239,8 +239,7 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
popup.showingProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
datePicker.hide();
if (trailingIcon instanceof MFXIconWrapper) {
MFXIconWrapper icon = (MFXIconWrapper) trailingIcon;
if (trailingIcon instanceof MFXIconWrapper icon) {
icon.getRippleGenerator().generateRipple(null);
}
}
@ -325,7 +324,7 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
});
leftArrow.disableProperty().bind(Bindings.createBooleanBinding(
() -> Objects.equals(yearCombo.getSelectedItem(), datePicker.getYearsRange().getMin()) && currentYearMonth.getMonth() == Month.JANUARY,
datePicker.yearsRangeProperty(), yearCombo.selectedItemProperty(), monthCombo.selectedItemProperty()
datePicker.yearsRangeProperty(), yearCombo.selection(), monthCombo.selection()
));
rightArrow.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
@ -336,7 +335,7 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
});
rightArrow.disableProperty().bind(Bindings.createBooleanBinding(
() -> Objects.equals(yearCombo.getSelectedItem(), datePicker.getYearsRange().getMax()) && currentYearMonth.getMonth() == Month.DECEMBER,
datePicker.yearsRangeProperty(), yearCombo.selectedItemProperty(), monthCombo.selectedItemProperty()
datePicker.yearsRangeProperty(), yearCombo.selection(), monthCombo.selection()
));
NodeUtils.makeRegionCircular(leftArrow);
@ -402,8 +401,8 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
* Responsible for updating the days grid, also builds the cells cache or
* updates it if a reset was not needed.
* <p></p>
* This is also responsible for marking/un-marking some cells as "extra" cells, {@link MFXDateCell#markAsExtra()},
* {@link MFXDateCell#unmarkAsExtra()}. Extra cells are those cells that contains days belonging to the previous/next month.
* This is also responsible for marking/un-marking some cells as "extra" cells, {@link MFXDateCell#setExtra(boolean)}.
* Extra cells are those cells that contains days belonging to the previous/next month.
*/
private void updateGrid() {
MFXDatePicker datePicker = getDatePicker();
@ -433,7 +432,7 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
cell = getCell(i, j, null);
cell.updateItem(null);
cells[i][j] = cell;
children.add(cell.toParent());
children.add(cell.toNode());
continue;
}
@ -441,23 +440,23 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
YearMonth previous = currentYearMonth.plusMonths(-1);
date = LocalDate.of(previous.getYear(), previous.getMonth(), day);
cell = getCell(i, j, date);
cell.markAsExtra();
cell.setExtra(true);
} else if (index > endIndex) {
YearMonth next = currentYearMonth.plusMonths(1);
date = LocalDate.of(next.getYear(), next.getMonth(), day);
cell = getCell(i, j, date);
cell.markAsExtra();
cell.setExtra(true);
} else {
date = LocalDate.of(currentYearMonth.getYear(), currentYearMonth.getMonth(), day);
cell = getCell(i, j, date);
cell.unmarkAsExtra();
cell.setExtra(false);
}
if (!cellsInitialized) {
cells[i][j] = cell;
}
cell.updateItem(date);
children.add(cell.toParent());
children.add(cell.toNode());
}
if (!cellsInitialized) {

View File

@ -18,12 +18,15 @@
package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.collections.TransformableList;
import java.util.function.Function;
import java.util.function.Predicate;
import io.github.palexdev.materialfx.collections.RefineList;
import io.github.palexdev.materialfx.controls.BoundTextField;
import io.github.palexdev.materialfx.controls.MFXFilterComboBox;
import io.github.palexdev.materialfx.controls.MFXTextField;
import io.github.palexdev.materialfx.i18n.I18N;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import io.github.palexdev.mfxlocalization.I18N;
import io.github.palexdev.virtualizedfx.list.VFXList;
import javafx.beans.binding.Bindings;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
@ -31,9 +34,6 @@ import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Skin associated with every {@link MFXFilterComboBox} by default.
* <p>
@ -101,37 +101,37 @@ public class MFXFilterComboBoxSkin<T> extends MFXComboBoxSkin<T> {
@Override
protected Node createPopupContent() {
MFXFilterComboBox<T> comboBox = getComboBox();
TransformableList<T> filterList = comboBox.getFilterList();
RefineList<T> list = comboBox.getFilterList();
MFXTextField searchField = new MFXTextField("", I18N.getOrDefault("filterCombo.search"));
searchField.getStyleClass().add("search-field");
searchField.textProperty().bindBidirectional(comboBox.searchTextProperty());
searchField.setMaxWidth(Double.MAX_VALUE);
virtualFlow = new SimpleVirtualFlow<>(
filterList,
comboBox.getCellFactory(),
Orientation.VERTICAL
vfxList = new VFXList<>(
list.getView(),
comboBox.getCellFactory(),
Orientation.VERTICAL
);
virtualFlow.cellFactoryProperty().bind(comboBox.cellFactoryProperty());
virtualFlow.prefWidthProperty().bind(comboBox.widthProperty());
virtualFlow.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
vfxList.getCellFactory().bind(comboBox.cellFactoryProperty());
vfxList.prefWidthProperty().bind(comboBox.widthProperty());
vfxList.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
if (popup.isShowing()) {
popup.hide();
}
});
Runnable createBinding = () ->
virtualFlow.minHeightProperty().bind(Bindings.createDoubleBinding(
() -> Math.min(comboBox.getRowsCount(), comboBox.getItems().size()) * virtualFlow.getCellHeight(),
comboBox.rowsCountProperty(), comboBox.getItems(), virtualFlow.cellFactoryProperty(), vfInitialized
));
virtualFlow.itemsProperty().addListener((observable, oldValue, newValue) -> {
vfxList.minHeightProperty().bind(Bindings.createDoubleBinding(
() -> Math.min(comboBox.getRowsCount(), comboBox.getItems().size()) * vfxList.getCellSize(),
comboBox.rowsCountProperty(), comboBox.getItems(), vfxList.cellSizeProperty(), vfInitialized
));
vfxList.itemsProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) createBinding.run();
});
createBinding.run();
VBox container = new VBox(10, searchField, virtualFlow);
VBox container = new VBox(10, searchField, vfxList.makeScrollable());
container.getStyleClass().add("search-container");
container.setAlignment(Pos.TOP_CENTER);
return container;

View File

@ -0,0 +1,71 @@
package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.controls.cell.MFXListCell;
import io.github.palexdev.mfxcore.events.WhenEvent;
import io.github.palexdev.mfxeffects.ripple.MFXRippleGenerator;
import io.github.palexdev.virtualizedfx.cells.CellBaseBehavior;
import io.github.palexdev.virtualizedfx.cells.VFXLabeledCellSkin;
import javafx.scene.input.MouseEvent;
import javafx.util.StringConverter;
public class MFXListCellSkin<T> extends VFXLabeledCellSkin<T> {
//================================================================================
// Properties
//================================================================================
protected final MFXRippleGenerator rg;
//================================================================================
// Constructors
//================================================================================
public MFXListCellSkin(MFXListCell<T> cell) {
super(cell);
rg = new MFXRippleGenerator(cell);
getChildren().addFirst(rg);
}
//================================================================================
// Methods
//================================================================================
protected MFXListCell<T> getCell() {
return ((MFXListCell<T>) getSkinnable());
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected void initBehavior(CellBaseBehavior<T> behavior) {
super.initBehavior(behavior);
MFXListCell<T> cell = getCell();
events(
WhenEvent.intercept(cell, MouseEvent.MOUSE_PRESSED)
.process(rg::generate),
WhenEvent.intercept(cell, MouseEvent.MOUSE_CLICKED)
.process(e -> behavior.mouseClicked(e, c -> rg.release()))
);
}
@Override
protected void update() {
MFXListCell<T> cell = getCell();
T item = cell.getItem();
StringConverter<T> converter = cell.getConverter();
label.setText(converter.toString(item));
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
MFXListCell<T> cell = getCell();
rg.resizeRelocate(0, 0, cell.getWidth(), cell.getHeight());
}
@Override
public void dispose() {
rg.dispose();
super.dispose();
}
}

View File

@ -1,196 +0,0 @@
/*
* 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.base.AbstractMFXListView;
import io.github.palexdev.materialfx.effects.MFXDepthManager;
import io.github.palexdev.materialfx.factories.MFXAnimationFactory;
import io.github.palexdev.materialfx.utils.AnimationUtils;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import javafx.animation.Animation;
import javafx.animation.KeyValue;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseEvent;
import javafx.util.Duration;
/**
* Implementation of the {@code Skin} used by all list views based on VirtualizedFX.
*/
public class MFXListViewSkin<T> extends SkinBase<AbstractMFXListView<T, ?>> {
//================================================================================
// Properties
//================================================================================
private final ScrollBar hBar;
private final ScrollBar vBar;
private Animation hideBars;
private Animation showBars;
//================================================================================
// Constructors
//================================================================================
public MFXListViewSkin(AbstractMFXListView<T, ?> listView, SimpleVirtualFlow<T, ?> virtualFlow) {
super(listView);
hBar = virtualFlow.getHBar();
vBar = virtualFlow.getVBar();
hideBars = AnimationUtils.TimelineBuilder.build()
.add(
AnimationUtils.KeyFrames.of(Duration.millis(400),
new KeyValue(vBar.opacityProperty(), 0.0, MFXAnimationFactory.INTERPOLATOR_V1),
new KeyValue(hBar.opacityProperty(), 0.0, MFXAnimationFactory.INTERPOLATOR_V1))
)
.getAnimation();
showBars = AnimationUtils.TimelineBuilder.build()
.add(
AnimationUtils.KeyFrames.of(Duration.millis(400),
new KeyValue(vBar.opacityProperty(), 1.0, MFXAnimationFactory.INTERPOLATOR_V1),
new KeyValue(hBar.opacityProperty(), 1.0, MFXAnimationFactory.INTERPOLATOR_V1))
)
.getAnimation();
if (listView.isHideScrollBars()) {
vBar.setOpacity(0.0);
hBar.setOpacity(0.0);
}
listView.setEffect(MFXDepthManager.shadowOf(listView.getDepthLevel()));
getChildren().setAll(virtualFlow);
setListeners();
}
//================================================================================
// Methods
//================================================================================
/**
* Calls {@link #setScrollBarHandlers()}, adds a listener to the list view's depth property.
*/
private void setListeners() {
AbstractMFXListView<T, ?> listView = getSkinnable();
setScrollBarHandlers();
listView.depthLevelProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue.equals(oldValue)) {
listView.setEffect(MFXDepthManager.shadowOf(listView.getDepthLevel()));
}
});
listView.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> listView.requestFocus());
}
/**
* Sets up the scroll bars behavior.
*/
private void setScrollBarHandlers() {
AbstractMFXListView<T, ?> listView = getSkinnable();
listView.setOnMouseExited(event -> {
if (listView.isHideScrollBars()) {
hideBars.setDelay(listView.getHideAfter());
if (hBar.isPressed()) {
hBar.pressedProperty().addListener(new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue) {
hideBars.play();
}
hBar.pressedProperty().removeListener(this);
}
});
return;
}
if (vBar.isPressed()) {
vBar.pressedProperty().addListener(new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue) {
hideBars.play();
}
vBar.pressedProperty().removeListener(this);
}
});
return;
}
hideBars.play();
}
});
listView.setOnMouseEntered(event -> {
if (hideBars.getStatus().equals(Animation.Status.RUNNING)) {
hideBars.stop();
}
showBars.play();
});
listView.hideScrollBarsProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
hideBars.play();
} else {
showBars.play();
}
if (newValue &&
hideBars.getStatus() != Animation.Status.RUNNING ||
vBar.getOpacity() != 0 ||
hBar.getOpacity() != 0
) {
vBar.setOpacity(0.0);
hBar.setOpacity(0.0);
}
});
}
//================================================================================
// Override Methods
//================================================================================
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
return topInset + 350 + bottomInset;
}
@Override
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
return leftInset + 200 + rightInset;
}
@Override
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
return getSkinnable().prefHeight(width);
}
@Override
protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
return getSkinnable().prefWidth(height);
}
@Override
public void dispose() {
super.dispose();
if (hideBars != null) {
hideBars = null;
}
if (showBars != null) {
showBars = null;
}
}
}

View File

@ -0,0 +1,144 @@
package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.controls.MFXCheckbox;
import io.github.palexdev.materialfx.controls.MFXNotificationCenter;
import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell;
import io.github.palexdev.materialfx.effects.Interpolators;
import io.github.palexdev.materialfx.notifications.base.INotification;
import io.github.palexdev.materialfx.utils.AnimationUtils;
import io.github.palexdev.mfxcore.base.beans.Position;
import io.github.palexdev.mfxcore.controls.SkinBase;
import io.github.palexdev.mfxcore.events.WhenEvent;
import io.github.palexdev.mfxcore.observables.When;
import io.github.palexdev.mfxcore.utils.fx.LayoutUtils;
import io.github.palexdev.virtualizedfx.cells.CellBaseBehavior;
import io.github.palexdev.virtualizedfx.cells.VFXCellBase;
import io.github.palexdev.virtualizedfx.events.VFXContainerEvent;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import static javafx.scene.layout.Region.USE_PREF_SIZE;
public class MFXNotificationCellSkin extends SkinBase<VFXCellBase<INotification>, CellBaseBehavior<INotification>> {
//================================================================================
// Properties
//================================================================================
private final MFXCheckbox checkbox;
private final StackPane container;
public MFXNotificationCellSkin(MFXNotificationCell cell) {
super(cell);
checkbox = new MFXCheckbox("");
checkbox.setId("check");
container = new StackPane(checkbox);
container.setMinWidth(USE_PREF_SIZE);
container.setPrefWidth(0);
container.setMaxWidth(USE_PREF_SIZE);
Rectangle clip = new Rectangle();
clip.widthProperty().bind(container.widthProperty());
clip.heightProperty().bind(container.heightProperty());
container.setClip(clip);
addListeners();
}
//================================================================================
// Methods
//================================================================================
protected void addListeners() {
MFXNotificationCell cell = getCell();
listeners(
When.onInvalidated(cell.itemProperty())
.then(t -> update()),
When.onInvalidated(cell.getNotificationCenter().selectionModeProperty())
.then(this::expand)
.executeNow()
);
}
protected void update() {
MFXNotificationCell cell = getCell();
MFXNotificationCenter notificationCenter = cell.getNotificationCenter();
INotification notification = cell.getItem();
if (notificationCenter.isSelectionMode()) {
checkbox.setOpacity(1.0);
checkbox.setPrefWidth(45);
}
getChildren().setAll(container, notification.getContent());
}
protected void expand(boolean selectionMode) {
MFXNotificationCell cell = getCell();
MFXNotificationCenter notificationCenter = cell.getNotificationCenter();
double width = selectionMode ? 45 : 0;
double opacity = selectionMode ? 1 : 0;
if (notificationCenter.isAnimated()) {
AnimationUtils.ParallelBuilder.build()
.add(
AnimationUtils.KeyFrames.of(150, checkbox.opacityProperty(), opacity, Interpolators.EASE_OUT),
AnimationUtils.KeyFrames.of(250, container.prefWidthProperty(), width, Interpolators.EASE_OUT_SINE)
).getAnimation().play();
} else {
container.setPrefWidth(width);
checkbox.setOpacity(opacity);
}
if (!selectionMode) {
notificationCenter.getSelectionModel().clearSelection();
}
}
protected MFXNotificationCell getCell() {
return (MFXNotificationCell) getSkinnable();
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected void initBehavior(CellBaseBehavior<INotification> behavior) {
MFXNotificationCell cell = getCell();
behavior.init();
events(
WhenEvent.intercept(cell, VFXContainerEvent.UPDATE)
.process(e -> {
update();
e.consume();
})
);
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
MFXNotificationCell cell = getCell();
// Checkbox
checkbox.autosize();
Position cPos = LayoutUtils.computePosition(
cell, checkbox,
x, y, w, h, 0, Insets.EMPTY,
HPos.LEFT, VPos.CENTER
);
checkbox.relocate(cPos.getX(), cPos.getY());
// Content
INotification notification = cell.getItem();
if (notification == null) return;
Region content = notification.getContent();
double gap = cell.getHGap();
layoutInArea(
content,
x + gap + checkbox.getLayoutX(), y, w, h, 0,
HPos.LEFT, VPos.CENTER
);
}
}

View File

@ -28,7 +28,6 @@ import io.github.palexdev.materialfx.factories.InsetsFactory;
import io.github.palexdev.materialfx.i18n.I18N;
import io.github.palexdev.materialfx.notifications.base.INotification;
import io.github.palexdev.materialfx.utils.NodeUtils;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import javafx.beans.binding.Bindings;
import javafx.css.Styleable;
import javafx.geometry.Bounds;
@ -60,7 +59,7 @@ public class MFXNotificationCenterSkin extends SkinBase<MFXNotificationCenter> {
//================================================================================
// Constructors
//================================================================================
public MFXNotificationCenterSkin(MFXNotificationCenter notificationCenter, SimpleVirtualFlow<INotification, MFXNotificationCell> virtualFlow) {
public MFXNotificationCenterSkin(MFXNotificationCenter notificationCenter, MFXListView<INotification, MFXNotificationCell> listView) {
super(notificationCenter);
bellWrapped = new MFXIconWrapper("fas-bell", 36, 56);
@ -113,7 +112,7 @@ public class MFXNotificationCenterSkin extends SkinBase<MFXNotificationCenter> {
BorderPane borderPane = new BorderPane();
borderPane.setTop(header);
borderPane.setCenter(virtualFlow);
borderPane.setCenter(listView);
borderPane.setBottom(actions);
borderPane.getStyleClass().add("notifications-container");
@ -121,7 +120,7 @@ public class MFXNotificationCenterSkin extends SkinBase<MFXNotificationCenter> {
borderPane.setMaxHeight(Region.USE_PREF_SIZE);
borderPane.prefWidthProperty().bind(notificationCenter.popupWidthProperty());
borderPane.prefHeightProperty().bind(notificationCenter.popupHeightProperty());
BorderPane.setMargin(virtualFlow, InsetsFactory.all(5));
BorderPane.setMargin(listView, InsetsFactory.all(5));
popup = new MFXPopup(borderPane) {
@Override

View File

@ -0,0 +1,78 @@
package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.beans.Alignment;
import io.github.palexdev.materialfx.controls.MFXListView;
import io.github.palexdev.materialfx.controls.MFXPagination;
import io.github.palexdev.materialfx.controls.MFXPopup;
import io.github.palexdev.materialfx.controls.cell.MFXListCell;
import io.github.palexdev.materialfx.controls.cell.MFXPage;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.mfxcore.events.WhenEvent;
import io.github.palexdev.virtualizedfx.cells.CellBaseBehavior;
import io.github.palexdev.virtualizedfx.events.VFXContainerEvent;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.input.MouseEvent;
public class MFXPageSkin extends MFXListCellSkin<Integer> {
//================================================================================
// Constructors
//================================================================================
public MFXPageSkin(MFXPage cell) {
super(cell);
}
//================================================================================
// Methods
//================================================================================
protected void showPopup() {
MFXPage cell = getCell();
MFXPagination pagination = cell.getPagination();
IntegerRange between = cell.getBetween();
if (!pagination.isShowPopupForTruncatedPages() || between == null) return;
ObservableList<Integer> indexes = FXCollections.observableArrayList(IntegerRange.expandRangeToArray(between));
MFXListView<Integer, MFXListCell<Integer>> listView = new MFXListView<>(indexes, null);
MFXPopup popup = new MFXPopup(listView);
popup.getStyleClass().add("pages-popup");
popup.setPopupStyleableParent(pagination);
listView.setCellFactory(i -> {
MFXListCell<Integer> c = new MFXListCell<>(i);
c.setOnMouseClicked(e -> {
pagination.setCurrentPage(c.getItem());
popup.hide();
});
return c;
});
popup.show(cell, Alignment.of(HPos.CENTER, VPos.BOTTOM), 0, 5);
}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected void initBehavior(CellBaseBehavior<Integer> behavior) {
MFXPage cell = getCell();
behavior.init();
events(
WhenEvent.intercept(cell, VFXContainerEvent.UPDATE)
.process(e -> {
update();
e.consume();
}),
WhenEvent.intercept(cell, MouseEvent.MOUSE_CLICKED)
.process(e -> behavior.mouseClicked(e, c -> showPopup()))
);
}
@Override
protected MFXPage getCell() {
return (MFXPage) super.getCell();
}
}

View File

@ -20,14 +20,8 @@ package io.github.palexdev.materialfx.skins;
import io.github.palexdev.materialfx.controls.MFXPaginatedTableView;
import io.github.palexdev.materialfx.controls.MFXPagination;
import io.github.palexdev.materialfx.controls.MFXTableRow;
import io.github.palexdev.materialfx.utils.AnimationUtils.PauseBuilder;
import io.github.palexdev.virtualizedfx.unused.simple.SimpleVirtualFlow;
import io.github.palexdev.materialfx.controls.MFXTableView;
import javafx.animation.PauseTransition;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
/**
* This is the default skin implementation for {@link MFXPaginatedTableView}.
@ -44,7 +38,12 @@ import javafx.scene.layout.StackPane;
* is thrown.
*/
public class MFXPaginatedTableViewSkin<T> extends MFXTableViewSkin<T> {
//================================================================================
public MFXPaginatedTableViewSkin(MFXTableView<T> tableView) {
super(tableView);
}
/* //================================================================================
// Properties
//================================================================================
private final MFXPagination pagination;
@ -53,7 +52,7 @@ public class MFXPaginatedTableViewSkin<T> extends MFXTableViewSkin<T> {
//================================================================================
// Constructors
//================================================================================
public MFXPaginatedTableViewSkin(MFXPaginatedTableView<T> tableView, SimpleVirtualFlow<T, MFXTableRow<T>> rowsFlow) {
public MFXPaginatedTableViewSkin(MFXPaginatedTableView<T> tableView, VFXList<T, MFXTableRow<T>> rowsFlow) {
super(tableView, rowsFlow);
rowsFlow.setMinHeight(Region.USE_PREF_SIZE);
@ -112,5 +111,5 @@ public class MFXPaginatedTableViewSkin<T> extends MFXTableViewSkin<T> {
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
double footerWidth = leftInset + footer.prefWidth(-1) + pagination.prefWidth(-1) * 2 + 10 + rightInset;
return Math.max(footerWidth, super.computeMinWidth(height, topInset, rightInset, bottomInset, leftInset));
}
}*/
}

Some files were not shown because too many files have changed in this diff Show More