2014-04-08 16:51:06 +04:00
|
|
|
//
|
2013-06-26 16:07:05 +04:00
|
|
|
// Copyright (C) 2013 Tom Beckmann
|
2014-04-08 16:51:06 +04:00
|
|
|
//
|
2013-06-26 16:07:05 +04:00
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
2014-04-08 16:51:06 +04:00
|
|
|
//
|
2013-06-26 16:07:05 +04:00
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
2014-04-08 16:51:06 +04:00
|
|
|
//
|
2013-06-26 16:07:05 +04:00
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2014-04-08 16:51:06 +04:00
|
|
|
//
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
namespace Gala
|
2013-06-26 16:07:05 +04:00
|
|
|
{
|
2013-06-27 16:12:51 +04:00
|
|
|
public enum DragDropActionType
|
2013-06-26 16:07:05 +04:00
|
|
|
{
|
2013-06-27 16:12:51 +04:00
|
|
|
SOURCE = 0,
|
|
|
|
DESTINATION
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
public class DragDropAction : Clutter.Action
|
2013-06-26 16:07:05 +04:00
|
|
|
{
|
2013-06-27 16:12:51 +04:00
|
|
|
/**
|
2014-04-29 23:35:49 +04:00
|
|
|
* A drag has been started. You have to connect to this signal and
|
|
|
|
* return an actor that is transformed during the drag operation.
|
|
|
|
*
|
|
|
|
* @param x The global x coordinate where the action was activated
|
|
|
|
* @param y The global y coordinate where the action was activated
|
|
|
|
* @return A ClutterActor that serves as handle
|
2013-06-27 21:39:41 +04:00
|
|
|
*/
|
2014-04-29 23:35:49 +04:00
|
|
|
public signal Clutter.Actor drag_begin (float x, float y);
|
2013-06-27 16:12:51 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A drag has been canceled. You may want to consider cleaning up
|
|
|
|
* your handle.
|
2013-06-27 21:39:41 +04:00
|
|
|
*/
|
2013-06-27 17:41:18 +04:00
|
|
|
public signal void drag_canceled ();
|
2013-06-27 16:12:51 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A drag action has successfully been finished.
|
2014-04-08 16:51:06 +04:00
|
|
|
*
|
2013-06-27 16:12:51 +04:00
|
|
|
* @param actor The actor on which the drag finished
|
2013-06-27 21:39:41 +04:00
|
|
|
*/
|
2013-06-27 17:41:18 +04:00
|
|
|
public signal void drag_end (Clutter.Actor actor);
|
2013-06-27 16:12:51 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The destination has been crossed
|
2013-06-27 21:39:41 +04:00
|
|
|
*
|
2013-06-27 16:12:51 +04:00
|
|
|
* @param hovered indicates whether the actor is now hovered or not
|
2013-06-27 21:39:41 +04:00
|
|
|
*/
|
2013-06-27 16:12:51 +04:00
|
|
|
public signal void crossed (bool hovered);
|
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
/**
|
|
|
|
* Emitted on the source when a destination is crossed.
|
|
|
|
*
|
|
|
|
* @param destination The destination actor that has been crossed
|
|
|
|
* @param hovered Whether the actor is now hovered or has just been left
|
|
|
|
*/
|
|
|
|
public signal void destination_crossed (Clutter.Actor destination, bool hovered);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The source has been clicked, but the movement was not larger than
|
|
|
|
* the drag threshold. Useful if the source is also activable.
|
|
|
|
*/
|
|
|
|
public signal void actor_clicked ();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The type of the action
|
|
|
|
*/
|
|
|
|
public DragDropActionType drag_type { get; construct; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The unique id given to this drag-drop-group
|
|
|
|
*/
|
|
|
|
public string drag_id { get; construct; }
|
|
|
|
|
|
|
|
public Clutter.Actor handle { get; private set; }
|
|
|
|
/**
|
|
|
|
* Indicates whether a drag action is currently active
|
|
|
|
*/
|
|
|
|
public bool dragging { get; private set; default = false; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allow checking the parents of reactive children if they are valid destinations
|
|
|
|
* if the child is none
|
|
|
|
*/
|
|
|
|
public bool allow_bubbling { get; set; default = true; }
|
|
|
|
|
|
|
|
Clutter.Actor? hovered = null;
|
|
|
|
bool clicked = false;
|
|
|
|
bool actor_was_reactive = false;
|
|
|
|
float last_x;
|
|
|
|
float last_y;
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
/**
|
|
|
|
* Create a new DragDropAction
|
2013-06-27 21:39:41 +04:00
|
|
|
*
|
2013-06-27 16:12:51 +04:00
|
|
|
* @param type The type of this actor
|
2014-04-29 23:35:49 +04:00
|
|
|
* @param id An ID that marks which sources can be dragged on
|
|
|
|
* which destinations. It has to be the same for all actors that
|
|
|
|
* should be compatible with each other.
|
2013-06-27 21:39:41 +04:00
|
|
|
*/
|
2013-06-27 16:12:51 +04:00
|
|
|
public DragDropAction (DragDropActionType type, string id)
|
|
|
|
{
|
2013-06-27 16:52:44 +04:00
|
|
|
Object (drag_type : type, drag_id : id);
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
~DragDropAction ()
|
|
|
|
{
|
|
|
|
if (actor != null)
|
|
|
|
release_actor (actor);
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
public override void set_actor (Clutter.Actor? new_actor)
|
|
|
|
{
|
|
|
|
if (actor != null) {
|
|
|
|
release_actor (actor);
|
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
if (new_actor != null) {
|
|
|
|
connect_actor (new_actor);
|
|
|
|
}
|
|
|
|
|
|
|
|
base.set_actor (new_actor);
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
void release_actor (Clutter.Actor actor)
|
|
|
|
{
|
|
|
|
if (drag_type == DragDropActionType.SOURCE) {
|
|
|
|
actor.button_press_event.disconnect (source_clicked);
|
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
void connect_actor (Clutter.Actor actor)
|
|
|
|
{
|
|
|
|
if (drag_type == DragDropActionType.SOURCE) {
|
|
|
|
actor.button_press_event.connect (source_clicked);
|
|
|
|
}
|
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
void emit_crossed (Clutter.Actor destination, bool hovered)
|
|
|
|
{
|
|
|
|
get_drag_drop_action (destination).crossed (hovered);
|
|
|
|
destination_crossed (destination, hovered);
|
|
|
|
}
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
bool source_clicked (Clutter.ButtonEvent event)
|
|
|
|
{
|
|
|
|
if (event.button != 1)
|
|
|
|
return false;
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
actor.get_stage ().captured_event.connect (follow_move);
|
2013-06-27 16:12:51 +04:00
|
|
|
clicked = true;
|
2014-04-29 23:35:49 +04:00
|
|
|
last_x = event.x;
|
|
|
|
last_y = event.y;
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
return true;
|
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
bool follow_move (Clutter.Event event)
|
2013-06-27 16:12:51 +04:00
|
|
|
{
|
2014-04-29 23:35:49 +04:00
|
|
|
// still determining if we actually want to start a drag action
|
|
|
|
if (!dragging) {
|
|
|
|
switch (event.get_type ()) {
|
|
|
|
case Clutter.EventType.MOTION:
|
|
|
|
float x, y;
|
|
|
|
event.get_coords (out x, out y);
|
|
|
|
|
|
|
|
var drag_threshold = Clutter.Settings.get_default ().dnd_drag_threshold;
|
|
|
|
if (Math.fabsf (last_x - x) > drag_threshold || Math.fabsf (last_y - y) > drag_threshold) {
|
|
|
|
handle = drag_begin (x, y);
|
|
|
|
if (handle == null) {
|
|
|
|
actor.get_stage ().captured_event.disconnect (follow_move);
|
|
|
|
critical ("No handle has been returned by the started signal, aborting drag.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
actor_was_reactive = handle.reactive;
|
|
|
|
handle.reactive = false;
|
|
|
|
|
|
|
|
clicked = false;
|
|
|
|
dragging = true;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case Clutter.EventType.BUTTON_RELEASE:
|
|
|
|
float x, y, ex, ey;
|
|
|
|
event.get_coords (out ex, out ey);
|
|
|
|
actor.get_transformed_position (out x, out y);
|
|
|
|
|
|
|
|
// release has happened within bounds of actor
|
|
|
|
if (x < ex && x + actor.width > ex && y < ey && y + actor.height > ey) {
|
|
|
|
actor_clicked ();
|
|
|
|
}
|
2013-06-27 16:12:51 +04:00
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
actor.get_stage ().captured_event.disconnect (follow_move);
|
|
|
|
clicked = false;
|
|
|
|
dragging = false;
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return true;
|
|
|
|
}
|
2013-06-27 16:12:51 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (event.get_type ()) {
|
|
|
|
case Clutter.EventType.KEY_PRESS:
|
|
|
|
if (event.get_key_code () == Clutter.Key.Escape) {
|
|
|
|
cancel ();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case Clutter.EventType.MOTION:
|
|
|
|
float x, y;
|
|
|
|
event.get_coords (out x, out y);
|
2014-04-29 23:35:49 +04:00
|
|
|
handle.x -= last_x - x;
|
|
|
|
handle.y -= last_y - y;
|
|
|
|
last_x = x;
|
|
|
|
last_y = y;
|
2013-06-27 16:12:51 +04:00
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
var stage = actor.get_stage ();
|
2013-06-27 16:12:51 +04:00
|
|
|
var actor = actor.get_stage ().get_actor_at_pos (Clutter.PickMode.REACTIVE, (int)x, (int)y);
|
|
|
|
DragDropAction action = null;
|
2014-04-29 23:35:49 +04:00
|
|
|
// if we're allowed to bubble and we this actor is not a destination, check its parents
|
|
|
|
if (actor != null && (action = get_drag_drop_action (actor)) == null && allow_bubbling) {
|
|
|
|
while ((actor = actor.get_parent ()) != stage) {
|
|
|
|
if ((action = get_drag_drop_action (actor)) != null)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// didn't change, no need to do anything
|
|
|
|
if (actor == hovered)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (action == null) {
|
|
|
|
// apparently we left ours if we had one before
|
2013-06-27 16:12:51 +04:00
|
|
|
if (hovered != null) {
|
2014-04-29 23:35:49 +04:00
|
|
|
emit_crossed (hovered, false);
|
2013-06-27 16:12:51 +04:00
|
|
|
hovered = null;
|
|
|
|
}
|
2014-04-29 23:35:49 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
return true;
|
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
// signal the previous one that we left it
|
2013-06-26 16:07:05 +04:00
|
|
|
if (hovered != null) {
|
2014-04-29 23:35:49 +04:00
|
|
|
emit_crossed (hovered, false);
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2014-04-29 23:35:49 +04:00
|
|
|
// tell the new one that it is hovered
|
2013-06-27 16:12:51 +04:00
|
|
|
hovered = actor;
|
2014-04-29 23:35:49 +04:00
|
|
|
emit_crossed (hovered, true);
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
return true;
|
|
|
|
case Clutter.EventType.BUTTON_RELEASE:
|
|
|
|
if (hovered != null) {
|
|
|
|
finish ();
|
|
|
|
} else {
|
|
|
|
cancel ();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case Clutter.EventType.ENTER:
|
|
|
|
case Clutter.EventType.LEAVE:
|
|
|
|
return true;
|
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
return false;
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2013-06-27 21:39:41 +04:00
|
|
|
/**
|
|
|
|
* Looks for a DragDropAction instance if this actor has one or NULL.
|
|
|
|
* It also checks if it is a DESTINATION and if the id matches
|
|
|
|
*
|
|
|
|
* @return the DragDropAction instance on this actor or NULL
|
|
|
|
*/
|
2013-06-27 16:12:51 +04:00
|
|
|
DragDropAction? get_drag_drop_action (Clutter.Actor actor)
|
|
|
|
{
|
2013-06-27 16:52:44 +04:00
|
|
|
DragDropAction? drop_action = null;
|
2013-06-27 16:12:51 +04:00
|
|
|
|
|
|
|
foreach (var action in actor.get_actions ()) {
|
2013-06-27 16:52:44 +04:00
|
|
|
drop_action = action as DragDropAction;
|
|
|
|
if (drop_action == null
|
|
|
|
|| drop_action.drag_type != DragDropActionType.DESTINATION
|
|
|
|
|| drop_action.drag_id != drag_id)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
return drop_action;
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
2013-06-27 16:52:44 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
return null;
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
/**
|
|
|
|
* Abort the drag
|
2013-06-27 21:39:41 +04:00
|
|
|
*/
|
2013-06-27 16:12:51 +04:00
|
|
|
public void cancel ()
|
|
|
|
{
|
|
|
|
if (dragging) {
|
|
|
|
actor.get_stage ().captured_event.disconnect (follow_move);
|
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
dragging = false;
|
2014-04-29 23:35:49 +04:00
|
|
|
actor.reactive = actor_was_reactive;
|
|
|
|
|
|
|
|
drag_canceled ();
|
2013-06-27 16:12:51 +04:00
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
void finish ()
|
|
|
|
{
|
|
|
|
// make sure they reset the style or whatever they changed when hovered
|
2014-04-29 23:35:49 +04:00
|
|
|
emit_crossed (hovered, false);
|
2013-06-26 16:07:05 +04:00
|
|
|
|
2013-06-27 16:12:51 +04:00
|
|
|
actor.get_stage ().captured_event.disconnect (follow_move);
|
2014-04-29 23:35:49 +04:00
|
|
|
|
|
|
|
dragging = false;
|
|
|
|
actor.reactive = actor_was_reactive;
|
|
|
|
|
2013-06-27 17:41:18 +04:00
|
|
|
drag_end (hovered);
|
2013-06-27 16:12:51 +04:00
|
|
|
}
|
2013-06-26 16:07:05 +04:00
|
|
|
}
|
|
|
|
}
|