Version 11 (modified by peterc, 9 years ago)
--

Drag Tutorial

libRocket has a few ways of implementing dragging of elements, such as:

  • the <handle> tag, as used by the documents in the sample applications
  • setting an element's 'drag' property to 'drag' or 'drag-drop' and listening to the raw drag events ('dragstart', 'dragend', etc) and animating element positions manually
  • setting an element's 'drag' property to 'clone'

This tutorial shows how to use the third type, cloning, to implement dragging items between multiple inventory windows.

Step 1: Taking a look

Compile the drag tutorial (at /samples/tutorials/drag/) and run the program; it should end up looking like this:

Take a look at the source code. As you can see, the application creates two Inventory objects, each of which loads a document from the 'inventory.rml' file. The application then creates four inventory objects in one of the inventories; each of these objects is a libRocket element with a tag of 'icon'. At the bottom of the 'tutorial.rcss' file you can see the properties applied to 'icon'. It is sized to 100px x 100px with a margin to separate it from its neighbour icons and a decorator for its background image. It is floated left so icons will stack from left to right in the inventory windows.

Step 2: Adding a drag property

If you try dragging the icons now, nothing much happens. In the tutorial's, RCSS file add the line:

	drag: clone;

to the rule for 'icon' elements. Now try dragging the icons again; success! A clone of the icons now follows the cursor when you drag them around. We'll need to add code to listen to the end of the drag and respond accordingly, but before we get to that I'll explain how the 'drag' property works.

The 'drag' property can take several different values depending on how you want libRocket to inform you about dragging. The possible values are:

  • none: The element does not send any drag messages. This is the default.
  • block: The element does not send any drag messages, and prevents any elements 'underneath' the element from being dragged as well. This is useful for buttons on a window's title bar, for example.
  • drag: If the left mouse button is pressed while over the element and dragged, the element will trigger a 'dragstart' event. Every subsequent time the mouse is moved, the element will trigger a 'drag' event. When the button is released, the element will trigger a 'dragend' event.
  • dragdrop: As drag, but as the mouse moves over other elements 'dragover' and 'dragout' events will be triggered (similarly to the 'mouseover' and 'mouseout' events). When the button is released, the element the mouse is hovering over will trigger the 'dragdrop' message.
  • clone: As dragdrop, but a clone of the element is attached to the mouse cursor during dragging. The clone has the pseudo-class 'drag' set on it to allow it to be differentiated from the original element.

So both drag and dragdrop only send messages; they don't actually drag any elements anywhere automatically. Very useful for complicated dragging operations or dragging multiple elements.

The clone value, however, takes care of almost everything if all you need to do is drag single elements.

Step 3: Listening to the events

Now that the items can be visibly dragged around, we need to actually change their parenting when they're dropped. Create a class which inherits from Rocket::Core::EventListener and give it a static method for registering the containers. Override the ProcessEvent() function as well so we can process the 'dragdrop' event.

#ifndef DRAGLISTENER_H
#define DRAGLISTENER_H

#include <Rocket/Core/EventListener.h>
#include <Rocket/Core/Types.h>

class DragListener : public Rocket::Core::EventListener
{
public:
	/// Registers an elemenet as being a container of draggable elements.
	static void RegisterDraggableContainer(Rocket::Core::Element* element);

protected:
	virtual void ProcessEvent(Rocket::Core::Event& event);
};

#endif

The RegisterDraggableContainer() function simply needs to attach the listener object to the 'dragdrop' event:

#include "DragListener.h"
#include <Rocket/Core/Element.h>

static DragListener drag_listener;

// Registers an element as being a container of draggable elements.
void DragListener::RegisterDraggableContainer(Rocket::Core::Element* element)
{
	element->AddEventListener("dragdrop", &drag_listener);
}

The DragListener object will now receive a call to ProcessEvent() whenever an item is dropped on the registered elements or any of their children.

The 'dragdrop' event

The event we'll be processing is the 'dragdrop' event. This event is sent to the element that the dragged element was dropped onto. The dragged element itself can be queried from the event as the parameter 'drag_element'.

We can now write a simple handler that will move dragged elements between the two containers:

void DragListener::ProcessEvent(Rocket::Core::Event& event)
{
	if (event == "dragdrop")
	{
		Rocket::Core::Element* container = event.GetCurrentElement();
		Rocket::Core::Element* drag_element = static_cast< Rocket::Core::Element* >(event.GetParameter< void* >("drag_element", NULL));

		drag_element->GetParentNode()->RemoveChild(drag_element);
		container->AppendChild(drag_element);
	}
}

The container that received the drop is determined by event.GetCurrentElement(). Events have both a target and a current element. The target element is the element the event was actually targetted at. The current element is the element the processing listener is observing; in our case, the container.

The dragged element is simply removed from its old parent and attached to the container it was dropped onto.

Registering the containers

All that's left to do before we can try out the dragging is to register the containers. In the constructor of the Inventory object (top of Inventory.cpp), we need to register the inventory window as a draggable container; add the following line at the end of the constructor:

	DragListener::RegisterDraggableContainer(document->GetElementById("content"));

Fire it up, start dragging and see what happens.