Drag & Drop

There are two ways of handling drag and drop. The simplest and most strait-forward way is using the On Drag property. The second is using the drag arbiter system, which gives you fine control over drag sources, targets, and how they interact with each other. The two systems can be used together. It's typical to use the Drag Control Node connected to the On Drag to handle moving a widget around with the pointer performing the drag, while an arbiter handles drop logic.

Handling "On Drag"#

The On Drag property provides an impulse that will fire with event information related to drag and drop when the view is dragged with a pointer event.

The following is the script for an example lua node to handle the On Drag property:

-- Drag position updater
if drag then
  for i=1,#drag do
    local ev = drag[i]
    if ev.action == INK_EVENT_CLICK_UP then
      dragging = false
      end_drag = true
    elseif ev.action == INK_EVENT_CLICK_DOWN then
      x = drag_x
      y = drag_y
      start_drag = true
    elseif ev.action == INK_EVENT_MOTION then
      x = x + ev.dx
      y = y + ev.dy
      drag_x = x
      drag_y = y
      dragging = true
    end
  end
else
  dragging = false
end

The lua node would have the following outputs:

And these could be either inputs or outputs, depending on how you wish to use their values:

drag_x and drag_y could then be connected to the Translate X and Translate Y properties on a view for example.

Drag Arbiters#

Drag arbiters work by categorizing views into draggable views, and drag targetable views. You can make a view draggable by settings the Drag Id property, or make a view a drag target by settings the Drag Target property.

A drag arbiter's job is to handle dragging views to potential targets. Here is an example lua script that sets up an arbiter for dragging "items" to "slots", and handles drag events as they happen:

-- Register with ink that we will be handling when a view with the drag id
-- "item" interacts with views that have a target id "slot"
ink.event_register_drag_arbiter("item", "slot")

ink.before_step(function(dt)
    repeat
        -- Loop through all drag events generated by the event system.
        local ev = ink.event_next_drag_arbiter()
        if ev ~= nil then
            local r = false
            if ev.drag_type_id == "item" and ev.target_type_id == "slot" then
                if ev.info.action == INK_EVENT_ARBITER_QUERY_ACCEPT then
                    -- When a drag is started, arbiters are first queried
                    -- to see if the dragging view and the target view are
                    -- compatible. This way, you could for example have
                    -- item XYZ only draggable to a slot ABC, but not
                    -- some other slot.

                    -- dragging_id is the "Id" value of the property on the
                    -- view we're currently dragging.
                    local item = items[ev.dragging_id]

                    -- target_id is the "Id" value of the property on the
                    -- target view we're currently checking.
                    local target_slot = slots[ev.target_id]

                    -- If we report `false`, no further arbiter events will
                    -- be created for this dragging_id, target_id pair.
                    r = item:is_valid_drop_target(target_slot)

                elseif ev.info.action == INK_EVENT_ARBITER_ENTER then
                    -- If you were to report false here, the drag system
                    -- will ignore this pairing on a drop event.
                    -- This will be queried again if the drag intersects
                    -- the target at a later point.
                    r = true

                elseif ev.info.action == INK_EVENT_ARBITER_EXIT then
                    -- Reporting `false` here will cause drop events to
                    -- interact with this pairing even after the user
                    -- moved their pointer out of the this target view.
                    -- If you report `false`, this will be queried again
                    -- each frame until it is re-entered, or you report
                    -- `true`.
                    r = true

                elseif ev.info.action == INK_EVENT_ARBITER_DRAG then
                    -- Here we're updating some members on a data handle
                    -- to visualize the drag. (For example to keep the
                    -- "item" view under the pointer.) You could instead
                    -- respond to the "On Drag" property like in the
                    -- example in "Handling On Drag".
                    local target_slot = slots[ev.target_id]
                    target_slot.data_handle:set_int("drag_x", ev.info.x)
                    target_slot.data_handle:set_int("drag_y", ev.info.y)

                elseif ev.info.action == INK_EVENT_ARBITER_DROP then
                    -- The drag arbiter system will go through each
                    -- pairing that reported `true` to the ENTER event
                    -- until one of them reports `true` here.
                    move_item(ev.dragging_id, ev.target_id)
                    r = true
                end
            end

            -- It is very important to always call `event_report_drag_arbiter`
            -- after each call to `event_next_drag_arbiter`.
            ink.event_report_drag_arbiter(r)
        end
    until ev == nil
end)

Drag arbiters are typically used in conjunction with the On Drag system handling updating drag visualization, but arbiters can be used on their own.