/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Copyright (C) 2010  Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Author:
 *   Emmanuele Bassi <ebassi@linux.intel.com>
 */

/**
 * SECTION:clutter-constraint
 * @Title: ClutterConstraint
 * @Short_Description: Abstract class for constraints on position or size
 * @See_Also: #ClutterAction
 *
 * #ClutterConstraint is a base abstract class for modifiers of a #ClutterActor
 * position or size.
 *
 * A #ClutterConstraint sub-class should contain the logic for modifying
 * the position or size of the #ClutterActor to which it is applied, by
 * updating the actor's allocation. Each #ClutterConstraint can change the
 * allocation of the actor to which they are applied by overriding the
 * #ClutterConstraintClass.update_allocation() virtual function.
 *
 * #ClutterConstraint is available since Clutter 1.4
 *
 * ## Using Constraints
 *
 * Constraints can be used with fixed layout managers, like
 * #ClutterFixedLayout, or with actors implicitly using a fixed layout
 * manager, like #ClutterGroup and #ClutterStage.
 *
 * Constraints provide a way to build user interfaces by using
 * relations between #ClutterActors, without explicit fixed
 * positioning and sizing, similarly to how fluid layout managers like
 * #ClutterBoxLayout and #ClutterTableLayout lay out their children.
 *
 * Constraints are attached to a #ClutterActor, and are available
 * for inspection using clutter_actor_get_constraints().
 *
 * Clutter provides different implementation of the #ClutterConstraint
 * abstract class, for instance:
 *
 *  - #ClutterAlignConstraint, a constraint that can be used to align
 *  an actor to another one on either the horizontal or the vertical
 *  axis, using a normalized value between 0 and 1.
 *  - #ClutterBindConstraint, a constraint binds the X, Y, width or height
 *  of an actor to the corresponding position or size of a source actor,
 *  with or without an offset.
 *  - #ClutterSnapConstraint, a constraint that "snaps" together the edges
 *  of two #ClutterActors; if an actor uses two constraints on both its
 *  horizontal or vertical edges then it can also expand to fit the empty
 *  space.
 *
 * The [constraints example](https://git.gnome.org/browse/clutter/tree/examples/constraints.c?h=clutter-1.18)
 * uses various types of #ClutterConstraints to lay out three actors on a
 * resizable stage. Only the central actor has an explicit size, and no
 * actor has an explicit position.
 *
 *  - The #ClutterActor with #ClutterActor:name `layerA` is explicitly
 *  sized to 100 pixels by 25 pixels, and it's added to the #ClutterStage
 *  - two #ClutterAlignConstraints are used to anchor `layerA` to the
 *  center of the stage, by using 0.5 as the alignment #ClutterAlignConstraint:factor on
 *  both the X and Y axis
 *  - the #ClutterActor with #ClutterActor:name `layerB` is added to the
 *  #ClutterStage with no explicit size
 *  - the #ClutterActor:x and #ClutterActor:width of `layerB` are bound
 *  to the same properties of `layerA` using two #ClutterBindConstraint
 *  objects, thus keeping `layerB` aligned to `layerA`
 *  - the top edge of `layerB` is snapped together with the bottom edge
 *  of `layerA`; the bottom edge of `layerB` is also snapped together with
 *  the bottom edge of the #ClutterStage; an offset is  given to the two
 *  #ClutterSnapConstraintss to allow for some padding; since `layerB` is
 *  snapped between two different #ClutterActors, its height is stretched
 *  to match the gap
 *  - the #ClutterActor with #ClutterActor:name `layerC` mirrors `layerB`,
 *  snapping the top edge of the #ClutterStage to the top edge of `layerC`
 *  and the top edge of `layerA` to the bottom edge of `layerC`
 *
 * You can try resizing interactively the #ClutterStage and verify
 * that the three #ClutterActors maintain the same position and
 * size relative to each other, and to the #ClutterStage.
 *
 * It is important to note that Clutter does not avoid loops or
 * competing constraints; if two or more #ClutterConstraints
 * are operating on the same positional or dimensional attributes of an
 * actor, or if the constraints on two different actors depend on each
 * other, then the behavior is undefined.
 *
 * ## Implementing a ClutterConstraint
 *
 * Creating a sub-class of #ClutterConstraint requires the
 * implementation of the #ClutterConstraintClass.update_allocation()
 * virtual function.
 *
 * The `update_allocation()` virtual function is called during the
 * allocation sequence of a #ClutterActor, and allows any #ClutterConstraint
 * attached to that actor to modify the allocation before it is passed to
 * the actor's #ClutterActorClass.allocate() implementation.
 *
 * The #ClutterActorBox passed to the `update_allocation()` implementation
 * contains the original allocation of the #ClutterActor, plus the eventual
 * modifications applied by the other #ClutterConstraints, in the same order
 * the constraints have been applied to the actor.
 *
 * It is not necessary for a #ClutterConstraint sub-class to chain
 * up to the parent's implementation.
 *
 * If a #ClutterConstraint is parametrized - i.e. if it contains
 * properties that affect the way the constraint is implemented - it should
 * call clutter_actor_queue_relayout() on the actor to which it is attached
 * to whenever any parameter is changed. The actor to which it is attached
 * can be recovered at any point using clutter_actor_meta_get_actor().
 */

#ifdef HAVE_CONFIG_H
#include "clutter-build-config.h"
#endif

#include <string.h>

#include "clutter-constraint-private.h"

#include "clutter-actor.h"
#include "clutter-actor-meta-private.h"
#include "clutter-private.h"

G_DEFINE_ABSTRACT_TYPE (ClutterConstraint,
                        clutter_constraint,
                        CLUTTER_TYPE_ACTOR_META);

static void
constraint_update_allocation (ClutterConstraint *constraint,
                              ClutterActor      *actor,
                              ClutterActorBox   *allocation)
{
}

static void
constraint_update_preferred_size (ClutterConstraint  *constraint,
                                  ClutterActor       *actor,
                                  ClutterOrientation  direction,
                                  float               for_size,
                                  float              *minimum_size,
                                  float              *natural_size)
{
}

static void
clutter_constraint_notify (GObject    *gobject,
                           GParamSpec *pspec)
{
  if (strcmp (pspec->name, "enabled") == 0)
    {
      ClutterActorMeta *meta = CLUTTER_ACTOR_META (gobject);
      ClutterActor *actor = clutter_actor_meta_get_actor (meta);

      if (actor != NULL)
        clutter_actor_queue_relayout (actor);
    }

  if (G_OBJECT_CLASS (clutter_constraint_parent_class)->notify != NULL)
    G_OBJECT_CLASS (clutter_constraint_parent_class)->notify (gobject, pspec);
}

static void
clutter_constraint_class_init (ClutterConstraintClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->notify = clutter_constraint_notify;

  klass->update_allocation = constraint_update_allocation;
  klass->update_preferred_size = constraint_update_preferred_size;
}

static void
clutter_constraint_init (ClutterConstraint *self)
{
}

/*< private >
 * clutter_constraint_update_allocation:
 * @constraint: a #ClutterConstraint
 * @actor: a #ClutterActor
 * @allocation: (inout): the allocation to modify
 *
 * Asks the @constraint to update the @allocation of a #ClutterActor.
 *
 * Returns: %TRUE if the allocation was updated
 */
gboolean
clutter_constraint_update_allocation (ClutterConstraint *constraint,
                                      ClutterActor      *actor,
                                      ClutterActorBox   *allocation)
{
  ClutterActorBox old_alloc;

  g_return_val_if_fail (CLUTTER_IS_CONSTRAINT (constraint), FALSE);
  g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), FALSE);
  g_return_val_if_fail (allocation != NULL, FALSE);

  old_alloc = *allocation;

  CLUTTER_CONSTRAINT_GET_CLASS (constraint)->update_allocation (constraint,
                                                                actor,
                                                                allocation);

  return !clutter_actor_box_equal (allocation, &old_alloc);
}

void
clutter_constraint_update_preferred_size (ClutterConstraint  *constraint,
                                          ClutterActor       *actor,
                                          ClutterOrientation  direction,
                                          float               for_size,
                                          float              *minimum_size,
                                          float              *natural_size)
{
  g_return_if_fail (CLUTTER_IS_CONSTRAINT (constraint));
  g_return_if_fail (CLUTTER_IS_ACTOR (actor));

  CLUTTER_CONSTRAINT_GET_CLASS (constraint)->update_preferred_size (constraint, actor,
                                                                    direction,
                                                                    for_size,
                                                                    minimum_size,
                                                                    natural_size);
}
