// Path: src/main/java/tornadofx/control/skin/ExpandableTableRowSkin.java
// public class ExpandableTableRowSkin<S> extends TableRowSkin<S> {
//     private final TableRow<S> tableRow;
//     private TableRowExpanderColumn<S> expander;
//     private Double tableRowPrefHeight = -1D;
// 
//     /**
//      * Create the ExpandableTableRowSkin and listen to changes for the item this table row represents. When the
//      * item is changed, the old expanded node, if any, is removed from the children list of the TableRow.
//      *
//      * @param tableRow The table row to apply this skin for
//      * @param expander The expander column, used to retrieve the expanded node when this row is expanded
//      */
//     public ExpandableTableRowSkin(TableRow<S> tableRow, TableRowExpanderColumn<S> expander) {
//         super(tableRow);
//         this.tableRow = tableRow;
//         this.expander = expander;
//         tableRow.itemProperty().addListener((observable, oldValue, newValue) -> {
//             if (oldValue != null) {
//                 Node expandedNode = this.expander.getExpandedNode(oldValue);
//                 if (expandedNode != null) getChildren().remove(expandedNode);
//             }
//         });
//     }
// 
//     /**
//      * Create the expanded content node that should represent the current table row.
//      *
//      * If the expanded content node is not currently in the children list of the TableRow it is automatically added.
//      *
//      * @return The expanded content Node
//      */
//     private Node getContent() {
//         Node node = expander.getOrCreateExpandedNode(tableRow);
//         if (!getChildren().contains(node)) getChildren().add(node);
//         return node;
//     }
// 
//     /**
//      * Check if the current node is expanded. This is done by checking that there is an item for the current row,
//      * and that the expanded property for the row is true.
//      *
//      * @return A boolean indicating the expanded state of this row
//      */
//     private Boolean isExpanded() {
//         return getSkinnable().getItem() != null && expander.getCellData(getSkinnable().getIndex());
//     }
// 
//     /**
//      * Add the preferred height of the expanded Node whenever the expanded flag is true.
//      *
//      * @return The preferred height of the TableRow, appended with the preferred height of the expanded node
//      * if this row is currently expanded.
//      */
//     @Override
//     protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
//         tableRowPrefHeight = super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
//         return isExpanded() ? tableRowPrefHeight + getContent().prefHeight(width) : tableRowPrefHeight;
//     }
// 
//     /**
//      * Lay out the columns of the TableRow, then add the expanded content node below if this row is currently expanded.
//      */
//     @Override
//     protected void layoutChildren(double x, double y, double w, double h) {
//         super.layoutChildren(x, y, w, h);
//         if (isExpanded()) getContent().resizeRelocate(0.0, tableRowPrefHeight, w, h - tableRowPrefHeight);
//     }
// }


import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.util.Callback;
import tornadofx.control.skin.ExpandableTableRowSkin;
import java.util.HashMap;
import java.util.Map;
     * The expandedNodeCallback is expected to return a Node representing the editor that should appear below the
     * table row when the toggle button within the expander column is clicked.
     *
     * Once this column is assigned to a TableView, it will automatically install a custom row factory for the TableView
     * so that it can configure a TableRow with the {@link ExpandableTableRowSkin}. It is within the skin that the actual
     * rendering of the expanded node occurs.
     *
     * @see TableRowExpanderColumn
     * @see TableRowDataFeatures
     *
     * @param expandedNodeCallback
     */
    public TableRowExpanderColumn(Callback<TableRowDataFeatures<S>, Node> expandedNodeCallback) {
        this.expandedNodeCallback = expandedNodeCallback;

        getStyleClass().add(STYLE_CLASS);
        setCellValueFactory(param -> getExpandedProperty(param.getValue()));
        setCellFactory(param -> new ToggleCell());
        installRowFactoryOnTableViewAssignment();
    }

    /**
     * Install the row factory on the TableView when this column is assigned to a TableView.
     */
    private void installRowFactoryOnTableViewAssignment() {
        tableViewProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                getTableView().setRowFactory(param -> new TableRow<S>() {
                    @Override
                    protected Skin<?> createDefaultSkin() {
// Path: src/main/java/tornadofx/control/skin/ExpandableTableRowSkin.java
// public class ExpandableTableRowSkin<S> extends TableRowSkin<S> {
//     private final TableRow<S> tableRow;
//     private TableRowExpanderColumn<S> expander;
//     private Double tableRowPrefHeight = -1D;
// 
//     /**
//      * Create the ExpandableTableRowSkin and listen to changes for the item this table row represents. When the
//      * item is changed, the old expanded node, if any, is removed from the children list of the TableRow.
//      *
//      * @param tableRow The table row to apply this skin for
//      * @param expander The expander column, used to retrieve the expanded node when this row is expanded
//      */
//     public ExpandableTableRowSkin(TableRow<S> tableRow, TableRowExpanderColumn<S> expander) {
//         super(tableRow);
//         this.tableRow = tableRow;
//         this.expander = expander;
//         tableRow.itemProperty().addListener((observable, oldValue, newValue) -> {
//             if (oldValue != null) {
//                 Node expandedNode = this.expander.getExpandedNode(oldValue);
//                 if (expandedNode != null) getChildren().remove(expandedNode);
//             }
//         });
//     }
// 
//     /**
//      * Create the expanded content node that should represent the current table row.
//      *
//      * If the expanded content node is not currently in the children list of the TableRow it is automatically added.
//      *
//      * @return The expanded content Node
//      */
//     private Node getContent() {
//         Node node = expander.getOrCreateExpandedNode(tableRow);
//         if (!getChildren().contains(node)) getChildren().add(node);
//         return node;
//     }
// 
//     /**
//      * Check if the current node is expanded. This is done by checking that there is an item for the current row,
//      * and that the expanded property for the row is true.
//      *
//      * @return A boolean indicating the expanded state of this row
//      */
//     private Boolean isExpanded() {
//         return getSkinnable().getItem() != null && expander.getCellData(getSkinnable().getIndex());
//     }
// 
//     /**
//      * Add the preferred height of the expanded Node whenever the expanded flag is true.
//      *
//      * @return The preferred height of the TableRow, appended with the preferred height of the expanded node
//      * if this row is currently expanded.
//      */
//     @Override
//     protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
//         tableRowPrefHeight = super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
//         return isExpanded() ? tableRowPrefHeight + getContent().prefHeight(width) : tableRowPrefHeight;
//     }
// 
//     /**
//      * Lay out the columns of the TableRow, then add the expanded content node below if this row is currently expanded.
//      */
//     @Override
//     protected void layoutChildren(double x, double y, double w, double h) {
//         super.layoutChildren(x, y, w, h);
//         if (isExpanded()) getContent().resizeRelocate(0.0, tableRowPrefHeight, w, h - tableRowPrefHeight);
//     }
// }


// Path: src/main/java/tornadofx/control/TableRowExpanderColumn.java
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.util.Callback;
import tornadofx.control.skin.ExpandableTableRowSkin;
import java.util.HashMap;
import java.util.Map;

     * The expandedNodeCallback is expected to return a Node representing the editor that should appear below the
     * table row when the toggle button within the expander column is clicked.
     *
     * Once this column is assigned to a TableView, it will automatically install a custom row factory for the TableView
     * so that it can configure a TableRow with the {@link ExpandableTableRowSkin}. It is within the skin that the actual
     * rendering of the expanded node occurs.
     *
     * @see TableRowExpanderColumn
     * @see TableRowDataFeatures
     *
     * @param expandedNodeCallback
     */
    public TableRowExpanderColumn(Callback<TableRowDataFeatures<S>, Node> expandedNodeCallback) {
        this.expandedNodeCallback = expandedNodeCallback;

        getStyleClass().add(STYLE_CLASS);
        setCellValueFactory(param -> getExpandedProperty(param.getValue()));
        setCellFactory(param -> new ToggleCell());
        installRowFactoryOnTableViewAssignment();
    }

    /**
     * Install the row factory on the TableView when this column is assigned to a TableView.
     */
    private void installRowFactoryOnTableViewAssignment() {
        tableViewProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                getTableView().setRowFactory(param -> new TableRow<S>() {
                    @Override
                    protected Skin<?> createDefaultSkin() {
                        return new ExpandableTableRowSkin<>(this, TableRowExpanderColumn.this);