UIForScrollPanels.java

  1. package swingtree;

  2. import org.jspecify.annotations.Nullable;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import sprouts.*;
  6. import sprouts.impl.SequenceDiff;
  7. import sprouts.impl.SequenceDiffOwner;
  8. import swingtree.api.mvvm.EntryViewModel;
  9. import swingtree.api.mvvm.ViewSupplier;
  10. import swingtree.components.JScrollPanels;
  11. import swingtree.layout.AddConstraint;

  12. import javax.swing.*;
  13. import java.util.List;
  14. import java.util.Objects;
  15. import java.util.concurrent.atomic.AtomicReference;
  16. import java.util.function.BiConsumer;
  17. import java.util.function.Function;
  18. import java.util.stream.Collectors;
  19. import java.util.stream.StreamSupport;

  20. /**
  21.  *  A builder node for {@link JScrollPanels}, a custom SwingTree component,
  22.  *  which is similar to a {@link JList} but with the ability to interact with
  23.  *  the individual components in the list.
  24.  *
  25.  * @param <P> The type of the component which this builder node wraps.
  26.  * @author Daniel Nepp
  27.  */
  28. public class UIForScrollPanels<P extends JScrollPanels> extends UIForAnyScrollPane<UIForScrollPanels<P>, P>
  29. {
  30.     private static final Logger log = LoggerFactory.getLogger(UIForScrollPanels.class);
  31.     private final BuilderState<P> _state;

  32.     /**
  33.      * Extensions of the {@link  UIForAnySwing} always wrap
  34.      * a single component for which they are responsible.
  35.      *
  36.      * @param state The {@link BuilderState} modelling how the underlying component is build.
  37.      */
  38.     protected UIForScrollPanels( BuilderState<P> state ) {
  39.         Objects.requireNonNull(state);
  40.         _state = state;
  41.     }

  42.     @Override
  43.     protected BuilderState<P> _state() {
  44.         return _state;
  45.     }

  46.     @Override
  47.     protected UIForScrollPanels<P> _newBuilderWithState( BuilderState<P> newState ) {
  48.         return new UIForScrollPanels<>(newState);
  49.     }

  50.     @Override
  51.     protected void _addComponentTo(P thisComponent, JComponent addedComponent, @Nullable AddConstraint constraints) {
  52.         Objects.requireNonNull(addedComponent);

  53.         EntryViewModel entry = _entryModel();
  54.         if ( constraints == null )
  55.             thisComponent.addEntry( entry, m -> UI.of(addedComponent) );
  56.         else
  57.             thisComponent.addEntry( constraints, entry, m -> UI.of(addedComponent) );
  58.     }

  59.     private static EntryViewModel _entryModel() {
  60.         Var<Boolean> selected = Var.of(false);
  61.         Var<Integer> position = Var.of(0);
  62.         return new EntryViewModel() {
  63.             @Override public Var<Boolean> isSelected() { return selected; }
  64.             @Override public Var<Integer> position() { return position; }
  65.         };
  66.     }

  67.     private static <M> M _modelFetcher(int i, Vals<M> vals) {
  68.         M v = vals.at(i).get();
  69.         if ( v instanceof EntryViewModel ) ((EntryViewModel) v).position().set(i);
  70.         return v;
  71.     }

  72.     private static <M> M _entryFetcher(int i, Vals<M> vals) {
  73.         M v = _modelFetcher(i, vals);
  74.         return ( v != null ? (M) v : (M)_entryModel() );
  75.     }

  76.     @Override
  77.     protected <M> void _addViewableProps(
  78.             Vals<M> models, @Nullable AddConstraint attr, ModelToViewConverter<M> viewSupplier, P thisComponent
  79.     ) {
  80.         BiConsumer<Integer, Vals<M>> addAllAt = (index, vals) -> {
  81.             boolean allAreEntries = vals.stream().allMatch( v -> v instanceof EntryViewModel );
  82.             if ( allAreEntries ) {
  83.                 List<EntryViewModel> entries = (List) vals.toList();
  84.                 thisComponent.addAllEntriesAt(index, attr, entries, (ViewSupplier<EntryViewModel>) viewSupplier);
  85.             }
  86.             else
  87.                 for ( int i = 0; i< vals.size(); i++ ) {
  88.                     int finalI = i;
  89.                     thisComponent.addEntryAt(
  90.                             finalI + index, attr,
  91.                             _entryModel(),
  92.                             m -> viewSupplier.createViewFor(_entryFetcher(finalI,vals))
  93.                         );
  94.                 }
  95.         };

  96.         _onShow( models, thisComponent, (c, delegate) -> {
  97.             viewSupplier.rememberCurrentViewsForReuse();
  98.             Tuple<M> tupleOfModels = Tuple.of(delegate.currentValues().type(), delegate.currentValues());
  99.             int delegateIndex = delegate.index().orElse(-1);
  100.             SequenceChange changeType = delegate.change();
  101.             int removeCount = delegate.oldValues().size();
  102.             int addCount = delegate.newValues().size();
  103.             int maxChange = Math.max(removeCount, addCount);
  104.             _update(c, attr, changeType, delegateIndex, maxChange, tupleOfModels, viewSupplier);
  105.             viewSupplier.clearCurrentViews();
  106.         });
  107.         addAllAt.accept(0,models);
  108.     }

  109.     private static <M> M _modelFetcher(int i, Tuple<M> tuple) {
  110.         M v = tuple.get(i);
  111.         if ( v instanceof EntryViewModel ) ((EntryViewModel) v).position().set(i);
  112.         return v;
  113.     }

  114.     private static <M> M _entryFetcher(int i, Tuple<M> tuple) {
  115.         M v = _modelFetcher(i, tuple);
  116.         return ( v != null ? (M) v : (M)_entryModel() );
  117.     }

  118.     private <M> void _addAllEntriesAt(
  119.             @Nullable AddConstraint attr,
  120.             JScrollPanels thisComponent,
  121.             int index,
  122.             Iterable<M> iterable,
  123.             ViewSupplier<M> viewSupplier
  124.     ) {
  125.         boolean allAreEntries = StreamSupport.stream(iterable.spliterator(), false).allMatch( v -> v instanceof EntryViewModel );
  126.         if ( allAreEntries ) {
  127.             List<EntryViewModel> entries = StreamSupport.stream(iterable.spliterator(), false).map(v -> (EntryViewModel)v).collect(Collectors.toList());
  128.             thisComponent.addAllEntriesAt(index, attr, entries, (ViewSupplier<EntryViewModel>) viewSupplier);
  129.         }
  130.         else {
  131.             Tuple<M> tuple = (iterable instanceof Tuple) ? (Tuple<M>) iterable : (Tuple<M>) Tuple.of(Object.class, (Iterable<Object>) iterable);
  132.             for ( int i = 0; i< tuple.size(); i++ ) {
  133.                 int finalI = i;
  134.                 thisComponent.addEntryAt(
  135.                     i + index, attr,
  136.                     _entryModel(),
  137.                     m -> viewSupplier.createViewFor(_entryFetcher(finalI,tuple))
  138.                 );
  139.             }
  140.         }
  141.     }

  142.     private <M> void _addAllEntriesAt(
  143.             @Nullable AddConstraint attr,
  144.             JScrollPanels thisComponent,
  145.             int index,
  146.             Tuple<M> models,
  147.             Function<Integer, ViewHandle<M>> lensSupplier,
  148.             ViewSupplier<ViewHandle<M>> viewSupplier
  149.     ) {
  150.         for ( int i = 0; i< models.size(); i++ ) {
  151.             ViewHandle<M> viewable = lensSupplier.apply(index+i);
  152.             thisComponent.addEntryAt(
  153.                     i + index, attr,
  154.                     _entryModel(),
  155.                     m -> viewSupplier.createViewFor(viewable)
  156.             );
  157.         }
  158.     }

  159.     private <M> void _setAllEntriesAt(
  160.         @Nullable AddConstraint attr,
  161.         JScrollPanels thisComponent,
  162.         int index,
  163.         Iterable<M> iterable,
  164.         ViewSupplier<M> viewSupplier
  165.     ) {
  166.         boolean allAreEntries = StreamSupport.stream(iterable.spliterator(), false).allMatch( v -> v instanceof EntryViewModel );
  167.         if ( allAreEntries ) {
  168.             List<EntryViewModel> entries = StreamSupport.stream(iterable.spliterator(), false).map(v -> (EntryViewModel)v).collect(Collectors.toList());
  169.             thisComponent.setAllEntriesAt(index, attr, entries, (ViewSupplier<EntryViewModel>) viewSupplier);
  170.         }
  171.         else {
  172.             Tuple<M> tuple = (iterable instanceof Tuple) ? (Tuple<M>) iterable : (Tuple<M>) Tuple.of(Object.class, (Iterable<Object>) iterable);
  173.             for (int i = 0; i < tuple.size(); i++) {
  174.                 int finalI = i;
  175.                 thisComponent.setEntryAt(
  176.                     i + index, attr,
  177.                     _entryModel(),
  178.                     m -> viewSupplier.createViewFor(_entryFetcher(finalI, tuple))
  179.                 );
  180.             }
  181.         }
  182.     }

  183.     private <M> void _setAllEntriesAt(
  184.             @Nullable AddConstraint attr,
  185.             JScrollPanels thisComponent,
  186.             int index,
  187.             int size,
  188.             Function<Integer, ViewHandle<M>> lensSupplier,
  189.             ViewSupplier<ViewHandle<M>> viewSupplier
  190.     ) {
  191.         for (int i = 0; i < size; i++) {
  192.             ViewHandle<M> viewable = lensSupplier.apply(index+i);
  193.             thisComponent.setEntryAt(
  194.                     i + index, attr,
  195.                     _entryModel(),
  196.                     m -> viewSupplier.createViewFor(viewable)
  197.             );
  198.         }
  199.     }

  200.     @Override
  201.     protected <M> void _addViewableProps(
  202.             Val<Tuple<M>> models,
  203.             @Nullable AddConstraint attr,
  204.             ModelToViewConverter<M> viewSupplier,
  205.             P thisComponent
  206.     ) {
  207.         AtomicReference<@Nullable SequenceDiff> lastDiffRef = new AtomicReference<>(null);
  208.         if (models.get() instanceof SequenceDiffOwner)
  209.             lastDiffRef.set(((SequenceDiffOwner)models.get()).differenceFromPrevious().orElse(null));
  210.         _onShow( models, thisComponent, (c, tupleOfModels) -> {
  211.             viewSupplier.rememberCurrentViewsForReuse();
  212.             SequenceDiff diff = null;
  213.             SequenceDiff lastDiff = lastDiffRef.get();
  214.             if (tupleOfModels instanceof SequenceDiffOwner)
  215.                 diff = ((SequenceDiffOwner)tupleOfModels).differenceFromPrevious().orElse(null);
  216.             lastDiffRef.set(diff);

  217.             if ( diff == null || ( lastDiff == null || !diff.isDirectSuccessorOf(lastDiff) ) ) {
  218.                 c.removeAllEntries();
  219.                 _addAllEntriesAt(attr, c, 0, tupleOfModels, viewSupplier);
  220.             } else {
  221.                 int index = diff.index().orElse(-1);
  222.                 int count = diff.size();
  223.                 _update(c, attr, diff.change(), index, count, tupleOfModels, viewSupplier);
  224.             }
  225.             viewSupplier.clearCurrentViews();
  226.         });
  227.         models.ifPresent( (tupleOfModels) -> {
  228.             thisComponent.removeAllEntries();
  229.             _addAllEntriesAt(attr, thisComponent, 0, tupleOfModels, viewSupplier);
  230.         });
  231.     }

  232.     @Override
  233.     protected <M> void _addViewableProps(
  234.             Var<Tuple<M>> propertyOfModels,
  235.             @Nullable AddConstraint attr,
  236.             ModelToViewConverter<ViewHandle<M>> viewSupplier,
  237.             P scrollPanels
  238.     ) {
  239.         Function<Integer, ViewHandle<M>> lensSupplier = index -> ViewHandle.of(propertyOfModels, index, scrollPanels);
  240.         AtomicReference<@Nullable SequenceDiff> lastDiffRef = new AtomicReference<>(null);
  241.         if (propertyOfModels.get() instanceof SequenceDiffOwner)
  242.             lastDiffRef.set(((SequenceDiffOwner)propertyOfModels.get()).differenceFromPrevious().orElse(null));
  243.         _onShowDelegated( propertyOfModels, scrollPanels, (thisComponent, delegate) -> {
  244.             Tuple<M> oldModels = delegate.oldValue().orElseThrowUnchecked();
  245.             Tuple<M> newModels = delegate.currentValue().orElseThrowUnchecked();
  246.             viewSupplier.rememberCurrentViewsForReuse();
  247.             SequenceDiff diff = null;
  248.             SequenceDiff lastDiff = lastDiffRef.get();
  249.             if (newModels instanceof SequenceDiffOwner && oldModels instanceof SequenceDiffOwner) {
  250.                 diff = ((SequenceDiffOwner)newModels).differenceFromPrevious().orElse(null);
  251.             }
  252.             lastDiffRef.set(diff);

  253.             if ( diff == null || ( lastDiff == null || !diff.isDirectSuccessorOf(lastDiff) ) ) {
  254.                 thisComponent.removeAllEntries();
  255.                 _addAllEntriesAt(attr, thisComponent, 0, newModels, lensSupplier, viewSupplier);
  256.             } else {
  257.                 int index = diff.index().orElse(-1);
  258.                 int count = diff.size();
  259.                 _update(thisComponent, attr, diff.change(), index, count, newModels, lensSupplier, viewSupplier);
  260.             }
  261.             viewSupplier.clearCurrentViews();
  262.         });
  263.         propertyOfModels.ifPresent( (tupleOfModels) -> {
  264.             scrollPanels.removeAllEntries();
  265.             _addAllEntriesAt(attr, scrollPanels, 0, tupleOfModels, lensSupplier, viewSupplier);
  266.         });
  267.     }

  268.     private <M> void _update(
  269.             P c,
  270.             @Nullable AddConstraint attr,
  271.             SequenceChange change,
  272.             int index,
  273.             int count,
  274.             Tuple<M> tupleOfModels,
  275.             ModelToViewConverter<M> viewSupplier
  276.     ) {
  277.         if ( index < 0 ) {
  278.             // We do a simple re-build
  279.             c.removeAllEntries();
  280.             _addAllEntriesAt(attr, c, 0, tupleOfModels, viewSupplier);
  281.         } else {
  282.             switch (change) {
  283.                 case SET:
  284.                     Tuple<M> slice = tupleOfModels.sliceAt(index, count);
  285.                     _setAllEntriesAt(attr, c, index, slice, viewSupplier);
  286.                     break;
  287.                 case ADD:
  288.                     _addAllEntriesAt(attr, c, index, tupleOfModels.sliceAt(index, count), viewSupplier);
  289.                     break;
  290.                 case REMOVE:
  291.                     c.removeEntriesAt(index, count);
  292.                     break;
  293.                 case RETAIN: // Only keep the elements in the range.
  294.                     // Remove trailing components:
  295.                     c.removeEntriesAt(index + count, c.getNumberOfEntries() - (index + count));
  296.                     // Remove leading components:
  297.                     c.removeEntriesAt(0, index);
  298.                     break;
  299.                 case CLEAR:
  300.                     c.removeAllEntries();
  301.                     break;
  302.                 case NONE:
  303.                     break;
  304.                 default:
  305.                     log.error("Unknown change type: {}", change, new Throwable());
  306.                     // We do a simple rebuild:
  307.                     c.removeAllEntries();
  308.                     _addAllEntriesAt(attr, c, 0, tupleOfModels, viewSupplier);
  309.             }
  310.         }
  311.     }

  312.     private <M> void _update(
  313.             P c,
  314.             @Nullable AddConstraint attr,
  315.             SequenceChange change,
  316.             int index,
  317.             int count,
  318.             Tuple<M> newModels,
  319.             Function<Integer, ViewHandle<M>> lensSupplier,
  320.             ModelToViewConverter<ViewHandle<M>> viewSupplier
  321.     ) {
  322.         if ( index < 0 ) {
  323.             // We do a simple re-build
  324.             c.removeAllEntries();
  325.             _addAllEntriesAt(attr, c, 0, newModels, lensSupplier, viewSupplier);
  326.         } else {
  327.             switch (change) {
  328.                 case SET:
  329.                     _setAllEntriesAt(attr, c, index, count, lensSupplier, viewSupplier);
  330.                     break;
  331.                 case ADD:
  332.                     _addAllEntriesAt(attr, c, index, newModels.sliceAt(index, count), lensSupplier, viewSupplier);
  333.                     break;
  334.                 case REMOVE:
  335.                     c.removeEntriesAt(index, count);
  336.                     break;
  337.                 case RETAIN: // Only keep the elements in the range.
  338.                     // Remove trailing components:
  339.                     c.removeEntriesAt(index + count, c.getNumberOfEntries() - (index + count));
  340.                     // Remove leading components:
  341.                     c.removeEntriesAt(0, index);
  342.                     break;
  343.                 case CLEAR:
  344.                     c.removeAllEntries();
  345.                     break;
  346.                 case NONE:
  347.                     break;
  348.                 default:
  349.                     log.error("Unknown change type: {}", change, new Throwable());
  350.                     // We do a simple rebuild:
  351.                     c.removeAllEntries();
  352.                     _addAllEntriesAt(attr, c, 0, newModels, lensSupplier, viewSupplier);
  353.             }
  354.         }
  355.     }

  356. }