1
0
Fork 0

[alpha] Allow registering alpha functions

In order to unify alpha functions and animation modes in ClutterAlpha
we should be able to register alpha functions and get a logical id
for them; the logical id will then be available to be used by
clutter_alpha_set_mode().

The registration requires API changes in ClutterAlpha constructors
and methods. It also provides the chance to shift ClutterAlpha
towards the use of animations modes only, and to alpha functions
as a convenience API for language bindings alone.
This commit is contained in:
Emmanuele Bassi 2009-01-16 13:42:06 +00:00
parent 9339334a43
commit 74213e0ee3
15 changed files with 265 additions and 123 deletions

View file

@ -40,11 +40,15 @@
* alpha value into something meaningful for a #ClutterActor.
*
* You should provide a #ClutterTimeline and bind it to the #ClutterAlpha
* instance using clutter_alpha_set_timeline(); you should also provide a
* function returning the alpha value depending on the progress of the
* timeline, using clutter_alpha_set_func() or clutter_alpha_set_closure().
* The alpha function will be executed each time a new frame in the
* #ClutterTimeline is reached.
* instance using clutter_alpha_set_timeline(). You should also set an
* "animation mode", either by using the #ClutterAnimatioMode values that
* Clutter itself provides or by registering custom functions.
*
* Instead of a #ClutterAnimationMode you may provide a function returning
* the alpha value depending on the progress of the timeline, using
* clutter_alpha_set_func() or clutter_alpha_set_closure(). The alpha
* function will be executed each time a new frame in the #ClutterTimeline
* is reached.
*
* Since the alpha function is controlled by the timeline instance, you can
* pause, stop or resume the #ClutterAlpha from calling the alpha function by
@ -85,7 +89,7 @@ struct _ClutterAlphaPrivate
GClosure *closure;
ClutterAnimationMode mode;
gulong mode;
};
enum
@ -126,9 +130,11 @@ clutter_alpha_set_property (GObject *object,
case PROP_TIMELINE:
clutter_alpha_set_timeline (alpha, g_value_get_object (value));
break;
case PROP_MODE:
clutter_alpha_set_mode (alpha, g_value_get_enum (value));
clutter_alpha_set_mode (alpha, g_value_get_ulong (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -152,12 +158,15 @@ clutter_alpha_get_property (GObject *object,
case PROP_TIMELINE:
g_value_set_object (value, priv->timeline);
break;
case PROP_ALPHA:
g_value_set_uint (value, priv->alpha);
break;
case PROP_MODE:
g_value_set_enum (value, priv->mode);
g_value_set_ulong (value, priv->mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -231,22 +240,25 @@ clutter_alpha_class_init (ClutterAlphaClass *klass)
/**
* ClutterAlpha:mode:
*
* The progress function as a #ClutterAnimationMode enumeration
* value. If %CLUTTER_CUSTOM_MODE is used then the function set
* using clutter_alpha_set_closure() or clutter_alpha_set_func()
* The progress function logical id - either a value from the
* #ClutterAnimationMode enumeration or a value returned by
* clutter_alpha_register_func().
*
* If %CLUTTER_CUSTOM_MODE is used then the function set using
* clutter_alpha_set_closure() or clutter_alpha_set_func()
* will be used.
*
* Since: 1.0
*/
g_object_class_install_property (object_class,
PROP_MODE,
g_param_spec_enum ("mode",
"Mode",
"Progress mode",
CLUTTER_TYPE_ANIMATION_MODE,
CLUTTER_CUSTOM_MODE,
G_PARAM_CONSTRUCT |
CLUTTER_PARAM_READWRITE));
g_param_spec_ulong ("mode",
"Mode",
"Progress mode",
0, G_MAXULONG,
CLUTTER_CUSTOM_MODE,
G_PARAM_CONSTRUCT |
CLUTTER_PARAM_READWRITE));
}
static void
@ -462,22 +474,49 @@ clutter_alpha_new (void)
/**
* clutter_alpha_new_full:
* @timeline: #ClutterTimeline timeline
* @func: #ClutterAlphaFunc alpha function
* @data: data to be passed to the alpha function
* @destroy: notify to be called when removing the alpha function
* @mode: animation mode
*
* Creates a new #ClutterAlpha instance and sets the timeline
* and alpha function.
* and animation mode.
*
* See also clutter_alpha_set_timeline() and clutter_alpha_set_mode().
*
* Return Value: the newly created #ClutterAlpha
*
* Since: 0.2
*/
ClutterAlpha *
clutter_alpha_new_full (ClutterTimeline *timeline,
ClutterAlphaFunc func,
gpointer data,
GDestroyNotify destroy)
clutter_alpha_new_full (ClutterTimeline *timeline,
gulong mode)
{
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), NULL);
g_return_val_if_fail (mode != CLUTTER_ANIMATION_LAST, NULL);
return g_object_new (CLUTTER_TYPE_ALPHA,
"timeline", timeline,
"mode", mode,
NULL);
}
/**
* clutter_alpha_new_with_func:
* @timeline: a #ClutterTimeline
* @func: a #ClutterAlphaFunc
* @data: data to pass to the function, or %NULL
* @destroy: function to call when removing the alpha function, or %NULL
*
* Creates a new #ClutterAlpha instances and sets the timeline
* and the alpha function.
*
* Return value: the newly created #ClutterAlpha
*
* Since: 1.0
*/
ClutterAlpha *
clutter_alpha_new_with_func (ClutterTimeline *timeline,
ClutterAlphaFunc func,
gpointer data,
GDestroyNotify destroy)
{
ClutterAlpha *retval;
@ -485,32 +524,12 @@ clutter_alpha_new_full (ClutterTimeline *timeline,
g_return_val_if_fail (func != NULL, NULL);
retval = clutter_alpha_new ();
clutter_alpha_set_timeline (retval, timeline);
clutter_alpha_set_func (retval, func, data, destroy);
return retval;
}
/**
* clutter_alpha_new_for_mode:
* @mode: a #ClutterAnimationMode value
*
* Creates a new #ClutterAlpha using @mode to set the
* progress function using its symbolic name.
*
* Return value: the newly created #ClutterAlpha
*
* Since: 1.0
*/
ClutterAlpha *
clutter_alpha_new_for_mode (ClutterAnimationMode mode)
{
return g_object_new (CLUTTER_TYPE_ALPHA,
"mode", mode,
NULL);
}
/**
* clutter_alpha_get_mode:
* @alpha: a #ClutterAlpha
@ -521,7 +540,7 @@ clutter_alpha_new_for_mode (ClutterAnimationMode mode)
*
* Since: 1.0
*/
ClutterAnimationMode
gulong
clutter_alpha_get_mode (ClutterAlpha *alpha)
{
g_return_val_if_fail (CLUTTER_IS_ALPHA (alpha), CLUTTER_CUSTOM_MODE);
@ -529,9 +548,13 @@ clutter_alpha_get_mode (ClutterAlpha *alpha)
return alpha->priv->mode;
}
/* XXX - keep in sync with ClutterAnimationMode */
/* static enum/function mapping table for the animation modes
* we provide internally
*
* XXX - keep in sync with ClutterAnimationMode
*/
static const struct {
ClutterAnimationMode mode;
gulong mode;
ClutterAlphaFunc func;
} animation_modes[] = {
{ CLUTTER_CUSTOM_MODE, NULL },
@ -545,46 +568,161 @@ static const struct {
{ CLUTTER_EXPO_IN, clutter_exp_in_func },
{ CLUTTER_EXPO_OUT, clutter_exp_out_func },
{ CLUTTER_EXPO_IN_OUT, clutter_exp_in_out_func },
{ CLUTTER_SMOOTH_IN_OUT, clutter_smoothstep_inc_func }
{ CLUTTER_SMOOTH_IN_OUT, clutter_smoothstep_inc_func },
{ CLUTTER_ANIMATION_LAST, NULL },
};
typedef struct _AlphaData {
guint closure_set : 1;
ClutterAlphaFunc func;
gpointer data;
GClosure *closure;
} AlphaData;
static GPtrArray *clutter_alphas = NULL;
/**
* clutter_alpha_set_mode:
* @alpha: a #ClutterAlpha
* @mode: a #ClutterAnimationMode
*
* Sets the progress function of @alpha using the symbolic value
* of @mode, as taken by the #ClutterAnimationMode enumeration
* of @mode, as taken by the #ClutterAnimationMode enumeration or
* using the value returned by clutter_alpha_register_func().
*
* Since: 1.0
*/
void
clutter_alpha_set_mode (ClutterAlpha *alpha,
ClutterAnimationMode mode)
clutter_alpha_set_mode (ClutterAlpha *alpha,
gulong mode)
{
ClutterAlphaPrivate *priv;
g_return_if_fail (CLUTTER_IS_ALPHA (alpha));
g_return_if_fail (mode != CLUTTER_ANIMATION_LAST);
priv = alpha->priv;
/* sanity check to avoid getting an out of sync enum/function mapping */
g_assert (animation_modes[mode].mode == mode);
if (G_LIKELY (animation_modes[mode].func != NULL))
clutter_alpha_set_func (alpha, animation_modes[mode].func, NULL, NULL);
if (mode < CLUTTER_ANIMATION_LAST)
{
/* sanity check to avoid getting an out of sync
* enum/function mapping
*/
g_assert (animation_modes[mode].mode == mode);
priv->mode = mode;
if (G_LIKELY (animation_modes[mode].func != NULL))
clutter_alpha_set_func (alpha, animation_modes[mode].func, NULL, NULL);
priv->mode = mode;
}
else if (mode > CLUTTER_ANIMATION_LAST)
{
AlphaData *alpha_data = NULL;
gulong real_index = 0;
if (G_UNLIKELY (clutter_alphas == NULL))
{
g_warning ("No alpha functions defined for ClutterAlpha to use. "
"Use clutter_alpha_register_func() to register an "
"alpha function.");
return;
}
real_index = mode - CLUTTER_ANIMATION_LAST - 1;
alpha_data = g_ptr_array_index (clutter_alphas, real_index);
if (G_UNLIKELY (alpha_data == NULL))
{
g_warning ("No alpha function registered for mode %lu.",
mode);
return;
}
if (alpha_data->closure_set)
clutter_alpha_set_closure (alpha, alpha_data->closure);
else
clutter_alpha_set_func (alpha, alpha_data->func,
alpha_data->data,
NULL);
priv->mode = mode;
}
else
g_assert_not_reached ();
g_object_notify (G_OBJECT (alpha), "mode");
}
/**
* CLUTTER_ALPHA_RAMP_INC:
* clutter_alpha_register_func:
* @func: a #ClutterAlphaFunc
* @data: user data to pass to @func, or %NULL
*
* Convenience symbol for clutter_ramp_inc_func().
* Registers a global alpha function and returns its logical id
* to be used by clutter_alpha_set_mode() or by #ClutterAnimation.
*
* Since: 0.2
* The logical id is always greater than %CLUTTER_ANIMATION_LAST.
*
* Return value: the logical id of the alpha function
*
* Since: 1.0
*/
gulong
clutter_alpha_register_func (ClutterAlphaFunc func,
gpointer data)
{
AlphaData *alpha_data;
g_return_val_if_fail (func != NULL, 0);
alpha_data = g_slice_new (AlphaData);
alpha_data->closure_set = FALSE;
alpha_data->func = func;
alpha_data->data = data;
if (G_UNLIKELY (clutter_alphas == NULL))
clutter_alphas = g_ptr_array_new ();
g_ptr_array_add (clutter_alphas, alpha_data);
return clutter_alphas->len + CLUTTER_ANIMATION_LAST;
}
/**
* clutter_alpha_register_closure:
* @closure: a #GClosure
*
* #GClosure variant of clutter_alpha_register_func().
*
* Registers a global alpha function and returns its logical id
* to be used by clutter_alpha_set_mode() or by #ClutterAnimation.
*
* The logical id is always greater than %CLUTTER_ANIMATION_LAST.
*
* Return value: the logical id of the alpha function
*
* Since: 1.0
*/
gulong
clutter_alpha_register_closure (GClosure *closure)
{
AlphaData *data;
g_return_val_if_fail (closure != NULL, 0);
data = g_slice_new (AlphaData);
data->closure_set = TRUE;
data->closure = closure;
if (G_UNLIKELY (clutter_alphas == NULL))
clutter_alphas = g_ptr_array_new ();
g_ptr_array_add (clutter_alphas, data);
return clutter_alphas->len + CLUTTER_ANIMATION_LAST;
}
/**
* clutter_ramp_inc_func:

View file

@ -31,7 +31,6 @@
#ifndef __CLUTTER_ALPHA_H__
#define __CLUTTER_ALPHA_H__
#include <clutter/clutter-fixed.h>
#include <clutter/clutter-timeline.h>
#include <clutter/clutter-types.h>
@ -110,27 +109,31 @@ struct _ClutterAlphaClass
GType clutter_alpha_get_type (void) G_GNUC_CONST;
ClutterAlpha * clutter_alpha_new (void);
ClutterAlpha * clutter_alpha_new_full (ClutterTimeline *timeline,
ClutterAlphaFunc func,
gpointer data,
GDestroyNotify destroy);
ClutterAlpha * clutter_alpha_new (void);
ClutterAlpha * clutter_alpha_new_full (ClutterTimeline *timeline,
gulong mode);
ClutterAlpha * clutter_alpha_new_with_func (ClutterTimeline *timeline,
ClutterAlphaFunc func,
gpointer data,
GDestroyNotify destroy);
ClutterAlpha * clutter_alpha_new_for_mode (ClutterAnimationMode mode);
guint32 clutter_alpha_get_alpha (ClutterAlpha *alpha);
void clutter_alpha_set_func (ClutterAlpha *alpha,
ClutterAlphaFunc func,
gpointer data,
GDestroyNotify destroy);
void clutter_alpha_set_closure (ClutterAlpha *alpha,
GClosure *closure);
void clutter_alpha_set_timeline (ClutterAlpha *alpha,
ClutterTimeline *timeline);
ClutterTimeline *clutter_alpha_get_timeline (ClutterAlpha *alpha);
void clutter_alpha_set_mode (ClutterAlpha *alpha,
gulong mode);
gulong clutter_alpha_get_mode (ClutterAlpha *alpha);
guint32 clutter_alpha_get_alpha (ClutterAlpha *alpha);
void clutter_alpha_set_func (ClutterAlpha *alpha,
ClutterAlphaFunc func,
gpointer data,
GDestroyNotify destroy);
void clutter_alpha_set_closure (ClutterAlpha *alpha,
GClosure *closure);
void clutter_alpha_set_timeline (ClutterAlpha *alpha,
ClutterTimeline *timeline);
ClutterTimeline * clutter_alpha_get_timeline (ClutterAlpha *alpha);
void clutter_alpha_set_mode (ClutterAlpha *alpha,
ClutterAnimationMode mode);
ClutterAnimationMode clutter_alpha_get_mode (ClutterAlpha *alpha);
gulong clutter_alpha_register_func (ClutterAlphaFunc func,
gpointer data);
gulong clutter_alpha_register_closure (GClosure *closure);
/* convenience functions */
guint32 clutter_ramp_inc_func (ClutterAlpha *alpha,

View file

@ -200,6 +200,7 @@ typedef enum {
* @CLUTTER_EXPO_OUT: exponential out progress
* @CLUTTER_EXPO_IN_OUT: exponential in-out progress
* @CLUTTER_SMOOTH_IN_OUT: smoothstep in-out progress
* @CLUTTER_ANIMATION_LAST: last animation mode
*
* The animation modes used by #ClutterAlpha and #ClutterAnimation. This
* enumeration can be expanded in later versions of Clutter.
@ -207,7 +208,7 @@ typedef enum {
* Since: 1.0
*/
typedef enum {
CLUTTER_CUSTOM_MODE,
CLUTTER_CUSTOM_MODE = 0,
CLUTTER_LINEAR,
CLUTTER_SINE_IN,
@ -220,6 +221,8 @@ typedef enum {
CLUTTER_EXPO_OUT,
CLUTTER_EXPO_IN_OUT,
CLUTTER_SMOOTH_IN_OUT,
CLUTTER_ANIMATION_LAST
} ClutterAnimationMode;
G_END_DECLS

View file

@ -102,7 +102,7 @@ ClutterAlpha
ClutterAlphaClass
clutter_alpha_new
clutter_alpha_new_full
clutter_alpha_new_for_mode
clutter_alpha_new_with_func
clutter_alpha_get_alpha
CLUTTER_ALPHA_MAX_ALPHA
ClutterAlphaFunc
@ -113,6 +113,10 @@ clutter_alpha_get_timeline
clutter_alpha_set_mode
clutter_alpha_get_mode
<SUBSECTION>
clutter_alpha_register_closure
clutter_alpha_register_func
<SUBSECTION>
clutter_ramp_inc_func
clutter_ramp_dec_func

View file

@ -168,7 +168,8 @@ test_actors_main (int argc, char *argv[])
g_signal_connect (timeline, "new-frame", G_CALLBACK (frame_cb), oh);
/* Set up some behaviours to handle scaling */
alpha = clutter_alpha_new_full (timeline, clutter_sine_func, NULL, NULL);
alpha = clutter_alpha_new_with_func (timeline, clutter_sine_func,
NULL, NULL);
scaler_1 = clutter_behaviour_scale_new (alpha,
0.5, 0.5,

View file

@ -171,8 +171,7 @@ test_behave_main (int argc, char *argv[])
NULL);
/* Set an alpha func to power behaviour - ramp is constant rise */
alpha = clutter_alpha_new_for_mode (CLUTTER_LINEAR);
clutter_alpha_set_timeline (alpha, timeline);
alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
/* Create a behaviour for that alpha */
o_behave = clutter_behaviour_opacity_new (alpha, 0X33, 0xff);

View file

@ -159,10 +159,10 @@ test_depth_main (int argc, char *argv[])
"completed", G_CALLBACK (timeline_completed),
NULL);
d_behave = clutter_behaviour_depth_new (clutter_alpha_new_full (timeline,
clutter_ramp_inc_func,
NULL, NULL),
-100, 100);
d_behave =
clutter_behaviour_depth_new (clutter_alpha_new_full (timeline,
CLUTTER_LINEAR),
-100, 100);
clutter_behaviour_apply (d_behave, label);
/* add two faced actor */
@ -170,12 +170,12 @@ test_depth_main (int argc, char *argv[])
clutter_container_add_actor (CLUTTER_CONTAINER (stage), janus);
clutter_actor_set_position (janus, 300, 350);
r_behave = clutter_behaviour_rotate_new (clutter_alpha_new_full (timeline,
clutter_ramp_inc_func,
NULL, NULL),
CLUTTER_Y_AXIS,
CLUTTER_ROTATE_CW,
0, 360);
r_behave =
clutter_behaviour_rotate_new (clutter_alpha_new_full (timeline,
CLUTTER_LINEAR),
CLUTTER_Y_AXIS,
CLUTTER_ROTATE_CW,
0, 360);
clutter_behaviour_apply (r_behave, janus);
@ -187,12 +187,12 @@ test_depth_main (int argc, char *argv[])
clutter_actor_set_rotation (box, CLUTTER_X_AXIS, 45, 0, 0, 0);
clutter_actor_set_opacity (box, 0x44);
r_behave = clutter_behaviour_rotate_new (clutter_alpha_new_full (timeline,
clutter_ramp_inc_func,
NULL, NULL),
CLUTTER_Y_AXIS,
CLUTTER_ROTATE_CW,
0, 360);
r_behave =
clutter_behaviour_rotate_new (clutter_alpha_new_full (timeline,
CLUTTER_LINEAR),
CLUTTER_Y_AXIS,
CLUTTER_ROTATE_CW,
0, 360);
clutter_behaviour_apply (r_behave, box);

View file

@ -748,6 +748,7 @@ G_MODULE_EXPORT int
test_layout_main (int argc, char *argv[])
{
ClutterActor *stage, *instructions;
ClutterAlpha *alpha;
gint i;
GError *error = NULL;
@ -764,10 +765,8 @@ test_layout_main (int argc, char *argv[])
G_CALLBACK (relayout_on_frame),
NULL);
behaviour = clutter_behaviour_scale_new (clutter_alpha_new_full (main_timeline,
clutter_sine_func,
NULL, NULL),
1.0, 1.0, 2.0, 2.0);
alpha = clutter_alpha_new_full (main_timeline, CLUTTER_SINE_IN_OUT);
behaviour = clutter_behaviour_scale_new (alpha, 1.0, 1.0, 2.0, 2.0);
box = my_thing_new (10, 10);

View file

@ -75,8 +75,7 @@ on_button_press (ClutterActor *actor,
timeline = clutter_timeline_new_for_duration (2000);
g_object_set (timeline, "loop", TRUE, NULL);
alpha = clutter_alpha_new_for_mode (CLUTTER_LINEAR);
clutter_alpha_set_timeline (alpha, timeline);
alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
r_behave = clutter_behaviour_rotate_new (alpha,
CLUTTER_Y_AXIS,
CLUTTER_ROTATE_CW,

View file

@ -205,7 +205,7 @@ test_paint_wrapper_main (int argc, char *argv[])
g_signal_connect (timeline, "new-frame", G_CALLBACK (frame_cb), oh);
/* Set up some behaviours to handle scaling */
alpha = clutter_alpha_new_full (timeline, clutter_sine_func, NULL, NULL);
alpha = clutter_alpha_new_full (timeline, CLUTTER_SINE_IN_OUT);
scaler_1 = clutter_behaviour_scale_new (alpha,
0.5, 0.5,

View file

@ -45,8 +45,7 @@ test_rotate_main (int argc, char *argv[])
g_object_set (timeline, "loop", TRUE, NULL);
/* Set an alpha func to power behaviour */
alpha = clutter_alpha_new_for_mode (CLUTTER_LINEAR);
clutter_alpha_set_timeline (alpha, timeline);
alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
/* Create a behaviour for that alpha */
r_behave = clutter_behaviour_rotate_new (alpha,

View file

@ -78,9 +78,9 @@ test_scale_main (int argc, char *argv[])
clutter_group_add (CLUTTER_GROUP (stage), rect);
timeline = clutter_timeline_new_for_duration (750);
alpha = clutter_alpha_new_full (timeline,
clutter_ramp_func,
NULL, NULL);
alpha = clutter_alpha_new_with_func (timeline,
clutter_ramp_func,
NULL, NULL);
behave = clutter_behaviour_scale_new (alpha,
0.0, 0.0, /* scale start */

View file

@ -81,8 +81,7 @@ test_texture_quality_main (int argc, char *argv[])
"completed", G_CALLBACK (timeline_completed),
NULL);
alpha = clutter_alpha_new_for_mode (CLUTTER_LINEAR);
clutter_alpha_set_timeline (alpha, timeline);
alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
depth_behavior = clutter_behaviour_depth_new (alpha, -2500, 400);
clutter_behaviour_apply (depth_behavior, image);

View file

@ -213,16 +213,15 @@ test_threads_main (int argc, char *argv[])
timeline = clutter_timeline_new (150, 50);
clutter_timeline_set_loop (timeline, TRUE);
r_behaviour = clutter_behaviour_rotate_new (clutter_alpha_new_full (timeline,
clutter_ramp_inc_func,
NULL, NULL),
alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
r_behaviour = clutter_behaviour_rotate_new (alpha,
CLUTTER_Z_AXIS,
CLUTTER_ROTATE_CW,
0.0, 360.0);
clutter_behaviour_apply (r_behaviour, rect);
alpha = clutter_alpha_new_full (timeline, clutter_ramp_inc_func,
NULL, NULL);
alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
p_behaviour = clutter_behaviour_path_new_with_knots (alpha,
knots,
G_N_ELEMENTS (knots));

View file

@ -40,8 +40,7 @@ test_viewport_main (int argc, char *argv[])
clutter_timeline_set_loop (timeline, TRUE);
/* Set an alpha func to power behaviour */
alpha = clutter_alpha_new_for_mode (CLUTTER_LINEAR);
clutter_alpha_set_timeline (alpha, timeline);
alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
/* Create a behaviour for that alpha */
r_behave = clutter_behaviour_rotate_new (alpha,