All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.lessvoid.nifty.controls.dragndrop.DraggableControl Maven / Gradle / Ivy

There is a newer version: 1.4.3
Show newest version
package de.lessvoid.nifty.controls.dragndrop;

import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import de.lessvoid.nifty.EndNotify;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.controls.*;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.input.NiftyInputEvent;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.tools.SizeValue;

/**
 * The controller class for a draggable element.
 *
 * @author void
 * @author Martin Karing <[email protected]>
 * @deprecated Internal control class. Use {@link de.lessvoid.nifty.controls.Draggable}
 */
@Deprecated
public class DraggableControl extends AbstractController implements Draggable {
  @Nonnull
  private static final String POPUP = "draggablePopup";
  @Nonnull
  private static final Logger log = Logger.getLogger(DraggableControl.class.getName());
  @Nullable
  private Nifty nifty;
  @Nullable
  private Screen screen;
  @Nullable
  private Element originalParent;
  @Nullable
  private Element popup;
  @Nullable
  private Element handle;
  private boolean revert;
  private boolean dropEnabled;
  private boolean dragged = false;
  private boolean triedDragging = false;
  private int originalPositionX;
  private int originalPositionY;
  @Nonnull
  private SizeValue originalConstraintX;
  @Nonnull
  private SizeValue originalConstraintY;
  private int dragStartX;
  private int dragStartY;
  @Nullable
  private Droppable droppable;

  /**
   * This flag stores if the draggable is supposed to be disabled once the dragging operation is done.
   */
  private boolean disableAtEndOfDrag;

  @Override
  public void bind(
      @Nonnull final Nifty nifty,
      @Nonnull final Screen screen,
      @Nonnull final Element element,
      @Nonnull final Parameters parameter) {
    super.bind(element);
    this.nifty = nifty;
    this.screen = screen;

    String handleId = parameter.get("handle");
    handle = element.findElementById(handleId);
    if (handle == null) {
      handle = element;
    }
    revert = parameter.getAsBoolean("revert", true);
    dropEnabled = parameter.getAsBoolean("drop", true);
  }

  @Override
  public void onStartScreen() {
  }

  @Override
  public boolean inputEvent(@Nonnull final NiftyInputEvent inputEvent) {
    return false;
  }

  /**
   * Called by 
   */
  public void bringToFront() {
    if (!dragged) {
      moveToFront();
    }
  }

  /**
   * Called by the dragging function to start a dragging operation.
   *
   * @param mouseX mouse x
   * @param mouseY mouse y
   */
  private void dragStart(final int mouseX, final int mouseY) {
    if (dragged || triedDragging) {
      return;
    }

    triedDragging = true;

    Element draggable = getElement();
    if (draggable == null) {
      return;
    }

    originalParent = draggable.getParent();
    originalPositionX = draggable.getX();
    originalPositionY = draggable.getY();
    originalConstraintX = draggable.getConstraintX();
    originalConstraintY = draggable.getConstraintY();
    dragStartX = mouseX;
    dragStartY = mouseY;

    if (handle != null && handle.isMouseInsideElement(mouseX, mouseY)) {
      moveDraggableToPopup();
      dragged = true;
      notifyObserversDragStarted();
    } else {
      moveToFront();
    }
  }

  /**
   * Called by 
   */
  public void dragStop() {
    triedDragging = false;
    if (!dragged) {
      moveToFront();
      return;
    }
    Element newDroppable = findDroppableAtCurrentCoordinates();
    if (newDroppable == null || newDroppable == originalParent) {
      dragCancel();
    } else {
      DroppableControl droppableControl = newDroppable.getControl(DroppableControl.class);
      if (droppableControl == null) {
        log.warning("Droppable has no Droppable control. Corrupted controls! Canceling drag.");
        dragCancel();
      } else if (droppableControl.accept(droppable, this)) {
        droppableControl.drop(this, closePopup());
      } else {
        dragCancel();
      }
    }
    dragged = false;
    postDragActions();
  }

  private void moveDraggableToPopup() {
    if (nifty == null || screen == null) {
      return;
    }
    final Element draggable = getElement();
    if (draggable == null) {
      return;
    }
    popup = nifty.createPopup(screen, POPUP);
    String popupId = popup.getId();
    if (popupId == null) {
      log.severe("Newly created popup did not get a ID. Critical internal error. Popup creation failed.");
      return;
    }
    nifty.showPopup(screen, popupId, null);

    draggable.markForMove(popup, new EndNotify() {
      @Override
      public void perform() {
        draggable.getFocusHandler().requestExclusiveMouseFocus(draggable);
        draggable.setConstraintX(SizeValue.px(originalPositionX));
        draggable.setConstraintY(SizeValue.px(originalPositionY));
      }
    });
  }

  @Override
  public void moveToFront() {
    final Element draggable = getElement();
    if (draggable == null) {
      return;
    }
    final Element parent = draggable.getParent();
    final List siblings = parent.getChildren();
    //noinspection ObjectEquality
    if (siblings.get(siblings.size() - 1) == draggable) {
      return;
    }
    draggable.markForMove(parent, new EndNotify() {
      @Override
      public void perform() {
        draggable.reactivate();
      }
    });
  }

  /**
   * Overwritten default disable function to ensure the proper deactivation of a currently dragged draggable.
   */
  @SuppressWarnings("RefusedBequest")
  @Override
  public void disable() {
    disable(true);
  }

  @Override
  public void disable(final boolean cancelCurrentDrag) {
    if (dragged) {
      if (cancelCurrentDrag) {
        dragCancel();
        dragged = false;
        super.disable();
      } else {
        disableAtEndOfDrag = true;
      }
    } else {
      super.disable();
    }
  }

  private void postDragActions() {
    if (disableAtEndOfDrag) {
      disableAtEndOfDrag = false;
      super.disable();
    }
  }

  private void dragCancel() {
    final Element draggable = getElement();
    if (draggable == null) {
      return;
    }
    if (revert) {
      draggable.setConstraintX(originalConstraintX);
      draggable.setConstraintY(originalConstraintY);
    } else {
      draggable.setConstraintX(SizeValue.px(draggable.getX()));
      draggable.setConstraintY(SizeValue.px(draggable.getY()));
    }
    moveDraggableBackToOriginalParent();
    notifyObserversDragCanceled();
  }

  @Nullable
  private EndNotify closePopup() {
    return new EndNotify() {
      @Override
      public void perform() {
        if (popup == null || nifty == null) {
          return;
        }
        final Element draggable = getElement();
        if (draggable == null) {
          return;
        }
        String popupId = popup.getId();
        if (popupId == null) {
          return;
        }
        nifty.closePopup(popupId, new EndNotify() {
          @Override
          public void perform() {
            draggable.reactivate();
            popup.markForRemoval(new EndNotify() {
              @Override
              public void perform() {
                popup = null;
              }
            });
          }
        });
      }
    };
  }

  private void moveDraggableBackToOriginalParent() {
    if (originalParent == null) {
      return;
    }
    final Element draggable = getElement();
    if (draggable == null) {
      return;
    }
    draggable.markForMove(originalParent, closePopup());
  }

  @Nullable
  private Element findDroppableAtCurrentCoordinates() {
    if (screen == null) {
      return null;
    }
    if (dropEnabled) {
      final Element draggable = getElement();
      if (draggable == null) {
        return null;
      }
      int dragAnkerX = draggable.getX() + draggable.getWidth() / 2;
      int dragAnkerY = draggable.getY() + draggable.getHeight() / 2;
      List layers = screen.getLayerElements();
      final int layerCount = layers.size();
      for (int i = layerCount - 1; i >= 0; i--) {
        Element layer = layers.get(i);
        if ((popup != null) && (layer != popup)) {
          DroppableSearchResult droppableSearchResult = findDroppableAtCoordinates(layer, dragAnkerX, dragAnkerY);
          if (droppableSearchResult != null) {
            if (droppableSearchResult.getFoundDroppable() != null) {
              // found a droppable!
              return droppableSearchResult.getFoundDroppable();
            }
            // found no droppable, but the mouse was blocked.
            break;
          }
        }
      }
    }
    return originalParent;
  }

  /**
   * This function looks for a valid foundDroppable target on a specified screen position.
   *
   * @param context the element that is the root element to the search
   * @param x the x component of the screen coordinate
   * @param y the y component of the screen coordinate
   * @return {@code null} in case nothing was found and the search should continue with the next element if any or a
   * search result, in that case the search is supposed to be stopped.
   */
  @Nullable
  private DroppableSearchResult findDroppableAtCoordinates(@Nonnull final Element context, final int x, final int y) {
    List elements = context.getChildren();
    final int childCount = elements.size();
    for (int i = childCount - 1; i >= 0; i--) {
      Element element = elements.get(i);

      // Check if the element we are testing is visible and in covers the tested location
      boolean mouseInsideAndVisible = element.isVisibleWithParent() && element.isMouseInsideElement(x, y);
      if (mouseInsideAndVisible && isDroppable(element)) {
        // its also a droppable. Our search is over.
        return new DroppableSearchResult(element);
      }

      // nothing found for this element check it's child elements
      DroppableSearchResult searchResult = findDroppableAtCoordinates(element, x, y);
      if (searchResult != null) {
        // search at the child returned a result. We are done.
        return searchResult;
      }
      if (mouseInsideAndVisible && element.isVisibleToMouseEvents()) {
        // we did not find a result, how ever we found a element that blocks the mouse. Stop the search without result.
        return new DroppableSearchResult();
      }
    }
    // No results at all. Continue the search with the other elements/layers.
    return null;
  }

  private boolean isDroppable(@Nonnull final Element element) {
    NiftyInputControl control = element.getAttachedInputControl();
    if (control != null) {
      return control.getController() instanceof DroppableControl;
    }
    return false;
  }

  @Nullable
  public Droppable getDroppable() {
    return droppable;
  }

  @Override
  public void setDroppable(@Nullable final Droppable droppable) {
    this.droppable = droppable;
  }

  private void notifyObserversDragStarted() {
    if (nifty == null || droppable == null) {
      return;
    }
    String id = getId();
    if (id != null) {
      nifty.publishEvent(id, new DraggableDragStartedEvent(droppable, this));
    }
  }

  private void notifyObserversDragCanceled() {
    if (nifty == null || droppable == null) {
      return;
    }
    String id = getId();
    if (id != null) {
      nifty.publishEvent(id, new DraggableDragCanceledEvent(droppable, this));
    }
  }

  private class DroppableSearchResult {
    @Nullable
    private final Element foundDroppable;

    DroppableSearchResult() {
      this(null);
    }

    DroppableSearchResult(@Nullable Element foundDroppable) {
      this.foundDroppable = foundDroppable;
    }

    @Nullable
    public Element getFoundDroppable() {
      return foundDroppable;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy