Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Drag & Drop Support via Draggables & Slots #55

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

Kulltero
Copy link
Contributor

Implementing Drag & Drop into our UIs

drag & drop allows us to mark a panel as draggable & listening to its movements when dragged by the player, opening up a whole new way of player interaction

The Breakdown

this PR adds 2 components, a Draggable component and a Slot component. the Draggable Component is responsible for the dragging interaction, sending RPCs to the server, & keeping itself within the constraints. the Slot Component's purpose is to allow draggables to be attached to the panel, with an optional primitive string filter to only allow draggables with a matching filter in.

Demo:

Drag-n-Drop.mp4

Receiving RPCs

Draggable's will send 1 of 2 RPC calls on the CommunityEntity depending on which action is performed:

  • Swapping Draggables or Attaching Draggables to slots
DropRPC(string draggedName, string draggedSlot, string swappedName, string swappedSlot)
  • Dragging a panel to a new position without swapping or attaching to a panel
DragRPC(string draggedName, Vector2 position, byte positionType)

the position Type

the Draggable Component comes with 4 seperate position send types, you might prefer one over the other depending on your scenario:

public enum PositionSendType : byte{
   // the Position normalized to the Screen resolution
   NormalizedScreen,
   // the Position normalized to the panel's limitParent (even if the setting is not used)*
   NormalizedParent,
   // the offset from the Draggable's last position
   Relative,
   // the offset from the anchor
   RelativeAnchor
}

* if the limitParent setting is not used, it still calculates the parent to use for the NormalizedParent setting. dragging the panel outside of the panel will produce values greater or lower than 0 - 1. the limitParentIndex is also supported for this usecase

the JSON Implementation - Draggable

{
  "type":"Draggable",  // All thats required to make the panel draggable

   "maxDistance": -1, // how far the draggable is allowed to be dragged from its parent
   "anchorOffset": "0 0", // if the anchor should be offset from the draggable's initial position the offset can be specified here

   "limitToParent": false, // Limits the Draggable to the closest non-slot parent, does not work combined with maxDistance
   "parentLimitIndex": 1, // if limitToParent is true, this index allows you to specify how many non slot parents it should traverse before finding the limit
   "parentPadding": "0 0", // if limitToParent is true, specifies additional padding that the draggable should be contained. can be negative to allow the draggable to slightly travel outside the parent

   "dropAnywhere": true, // if false, the draggable will return to its last valid position (its initial position by default) unless swapped or attached to a slot
   "keepOnTop": false, // if true the draggable will stay detached from its parent, reccomended for unrestricted draggables
   "allowSwapping": false, // if true the draggable can be swapped with other draggables if dragged ontop of eachother
   "dragAlpha": 1, // if the alpha of the draggable's graphics should be lowered while being dragged.
   "positionRPC": "NormalizedScreen", // how the position should be processed before being sent via RPCs*

   "filter": null // the filter string this draggable should carry, see #Slots & Filtering
}

* Depending on other settings the default value for the positionRPC field may change.

  • if maxDistance is used, the default will be RelativeAnchor
  • if limitToParent is used, the default will be NormalizedParent

these are only defaults, you can still override this by specifying the field in the json

the JSON Implementation - Slot

{
  "type":"Draggable",  // All thats required to make the panel a slot
   "filter": null, // if not null it will only allow draggables with a matching filter string
}

Slots & Filtering

Slots come with a built in filter system that allows you to specify what draggables can be attached.

this is how the filtering is evaluated:

public static bool FitsIntoSlot(Draggable drag, Slot slot){
   if(slot.filter == null)
      return true;

   return drag.filter == slot.filter;
}

if the slot has no filter, any draggable can be inserted. if the slot has a filter it only allows Draggables with a matching filter property. in the demo video this is shown with the green Draggable & Slots, the gray slots will accept any draggable while the green slot will only accept the matching Draggable. this is accomplished by setting the filter string

when building a system arround slots & the swapping of Draggables, its reccomended to set the dropAnywhere setting to false to make Draggables return to their last position if a

Kulltero added 9 commits May 11, 2023 09:30
Added the JSON API required to setup & update Draggables & Slots.
Also added the ability to reparent & order a transform if called via an update call, this is needed to allow reparenting a Draggable
Fix a potential illegal swap when a panel inside a restricted slot was swapped with a non-slotted panel.

Changed DragRPC to not send when a swap/attach operation fails & the panel will reset to its last position,
the change was made because in those situations, a player didnt intend to drag the panel, they intended to attach it.
fixed attachement fail sending a DragRPC, also properly sets the position of panels that can be dropped anywhere
Kulltero added 8 commits May 19, 2023 20:55
the previous approach used offsets, only modifying the position if the new position would be within bounds. this caused awkward behaviour where the dragged object would stop long before its actual bounds.

this was also framerate dependant as it would make bigger leaps on lower framerates, making it more likely that the new position would have been out of bounds and increasing the apparent distance from its bounds

after actually testing the PR in game i've realized it is safe to just use the absolute position (anchor + offset) and to constrain that to the rect with respect to the padding.
adding default sprites and changed some images to use Image instead
the draggable now consider's both the parent's position and scale instead of just its scale when determining if its bounds need to be recached - this solves an issue with animated elements having cached old bounds

the anchor now also gets parented based on the parentLimitIndex, previously it was parented to the draggable's initial parent which brought with it unwanted behaviour when that parent was moving
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant