root/src/compositor/mpeg4_layout.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. layout_reset_lines
  2. new_line_info
  3. get_justify
  4. get_lines_info
  5. layout_justify
  6. layout_setup_scroll_bounds
  7. layout_scroll
  8. TraverseLayout
  9. OnLayout
  10. layout_is_enabled
  11. compositor_init_layout
  12. compositor_layout_modified
  13. compositor_mpeg4_layout_get_sensor_handler

/*
 *                      GPAC - Multimedia Framework C SDK
 *
 *                      Authors: Jean Le Feuvre
 *                      Copyright (c) Telecom ParisTech 2000-2012
 *                                      All rights reserved
 *
 *  This file is part of GPAC / Scene Compositor sub-project
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *
 *  GPAC 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; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "nodes_stacks.h"
#include "mpeg4_grouping.h"
#include "visual_manager.h"

#ifndef GPAC_DISABLE_VRML

typedef struct
{
        PARENT_MPEG4_STACK_2D

        Bool is_scrolling;
        u32 start_scroll_type;
        Double start_time, pause_time;
        GF_List *lines;
        GF_Rect clip;
        Fixed last_scroll, prev_rate, scroll_rate, scale_scroll, scroll_len, scroll_min, scroll_max;

        /*for keyboard navigation*/
        GF_SensorHandler hdl;
        s32 key_scroll;
        Bool keys_active;
} LayoutStack;

typedef struct
{
        Fixed width, height, ascent, descent;
        u32 first_child, nb_children;
        Bool line_break;
} LineInfo;

static void layout_reset_lines(LayoutStack *st)
{
        while (gf_list_count(st->lines)) {
                LineInfo *li = (LineInfo *)gf_list_get(st->lines, 0);
                gf_list_rem(st->lines, 0);
                gf_free(li);
        }
}

static LineInfo *new_line_info(LayoutStack *st)
{
        LineInfo *li;
        GF_SAFEALLOC(li, LineInfo);

        gf_list_add(st->lines, li);
        return li;
}


enum
{
        L_FIRST,
        L_BEGIN,
        L_MIDDLE,
        L_END,
        L_JUSTIFY,
};

static u32 get_justify(M_Layout *l, u32 i)
{
        if (l->justify.count <= i) return L_BEGIN;
        if (!strcmp(l->justify.vals[i], "END")) return L_END;
        if (!strcmp(l->justify.vals[i], "MIDDLE")) return L_MIDDLE;
        if (!strcmp(l->justify.vals[i], "FIRST")) return L_FIRST;
        if (!strcmp(l->justify.vals[i], "SPREAD")) return L_JUSTIFY;
        if (!strcmp(l->justify.vals[i], "JUSTIFY")) return L_JUSTIFY;
        return L_BEGIN;
}

static void get_lines_info(LayoutStack *st, M_Layout *l)
{
        Fixed prev_discard_width;
        u32 i, count;
        LineInfo *li;
        Fixed max_w, max_h;

        max_w = st->clip.width;
        max_h = st->clip.height;
        layout_reset_lines(st);

        count = gf_list_count(st->groups);
        if (!count) return;

        li = new_line_info(st);
        li->first_child = 0;
        prev_discard_width = 0;

        for (i=0; i<count; i++) {
                ChildGroup *cg = (ChildGroup *)gf_list_get(st->groups, i);
                if (!l->horizontal) {
                        /*check if exceed column size or not - if so, move to next column or clip given wrap mode*/
                        if (cg->final.height + li->height > max_h) {
                                if (l->wrap) {
                                        li = new_line_info(st);
                                        li->first_child = i;
                                }
                        }
                        if (cg->final.width > li->width) li->width = cg->final.width;
                        li->height += cg->final.height;
                        li->nb_children ++;
                } else {
                        if ((cg->text_type==2) || (i && (cg->final.width + li->width> max_w))) {
                                if (cg->text_type==2) li->line_break = 1;
                                if (l->wrap) {
                                        if (!li->ascent) {
                                                li->ascent = li->height;
                                                li->descent = 0;
                                        }
                                        /*previous word is discardable (' ')*/
                                        if (prev_discard_width) {
                                                li->width -= prev_discard_width;
                                                li->nb_children--;
                                        }
                                        if ((cg->text_type==1) && (i+1==count)) break;

                                        li = new_line_info(st);
                                        li->first_child = i;
                                        if (cg->text_type) {
                                                li->first_child++;
                                                continue;
                                        }
                                }
                        }

                        /*get ascent/descent for text or height for non-text*/
                        if (cg->ascent) {
                                if (li->ascent < cg->ascent) li->ascent = cg->ascent;
                                if (li->descent < cg->descent) li->descent = cg->descent;
                                if (li->height < li->ascent + li->descent) li->height = li->ascent + li->descent;
                        } else if (cg->final.height > li->height) {
                                li->height = cg->final.height;
                        }
                        li->width += cg->final.width;
                        li->nb_children ++;

                        prev_discard_width = (cg->text_type==1) ? cg->final.width : 0;

                }
        }
}


static void layout_justify(LayoutStack *st, M_Layout *l)
{
        u32 first, minor, major, i, k, nbLines;
        Fixed current_top, current_left, h;
        LineInfo *li;
        ChildGroup *cg, *prev;
        get_lines_info(st, l);
        major = get_justify(l, 0);
        minor = get_justify(l, 1);

        st->scroll_len = 0;
        nbLines = gf_list_count(st->lines);
        if (l->horizontal) {
                if (l->wrap && !l->topToBottom) {
                        li = (LineInfo*)gf_list_get(st->lines, 0);
                        current_top = st->clip.y - st->clip.height;
                        if (li) current_top += li->height;
                } else {
                        current_top = st->clip.y;
                }

                /*for each line perform adjustment*/
                for (k=0; k<nbLines; k++) {
                        Fixed spacing = 0;
                        li = (LineInfo*)gf_list_get(st->lines, k);
                        first = li->first_child;
                        if (!l->leftToRight) first += li->nb_children - 1;

                        if (!l->topToBottom && k) current_top += li->height;

                        /*set major alignment (X) */
                        cg = (ChildGroup *)gf_list_get(st->groups, first);
                        if (!cg) continue;
                        switch (major) {
                        case L_END:
                                cg->final.x = st->clip.x + st->clip.width - li->width;
                                break;
                        case L_MIDDLE:
                                cg->final.x = st->clip.x + (st->clip.width - li->width)/2;
                                break;
                        case L_FIRST:
                        case L_BEGIN:
                                cg->final.x = st->clip.x;
                                break;
                        case L_JUSTIFY:
                                cg->final.x = st->clip.x;
                                if (li->nb_children>1) {
                                        cg = (ChildGroup *)gf_list_get(st->groups, li->nb_children-1);
                                        spacing = (st->clip.width - li->width) / (li->nb_children-1) ;
                                        if (spacing<0) spacing = 0;
                                        else if (cg->ascent) {
                                                /*disable spacing for last text line and line breaks*/
                                                if ( (k+1==nbLines) || li->line_break) {
                                                        spacing = 0;
                                                }
                                        }
                                }
                                break;
                        }


                        /*for each in the run */
                        i = first;
                        while (1) {
                                cg = (ChildGroup *)gf_list_get(st->groups, i);
                                if (!cg) break;
                                h = MAX(li->ascent, li->height);
                                switch (minor) {
                                case L_FIRST:
                                        cg->final.y = current_top - h;
                                        if (cg->ascent) {
                                                cg->final.y += cg->ascent;
                                        } else {
                                                cg->final.y += cg->final.height;
                                        }
                                        break;
                                case L_MIDDLE:
                                        cg->final.y = current_top - (h - cg->final.height)/2;
                                        break;
                                case L_END:
                                        cg->final.y = current_top;
                                        break;
                                case L_BEGIN:
                                default:
                                        cg->final.y = current_top - h + cg->final.height;
                                        break;
                                }
                                /*update left for non-first children in line*/
                                if (i != first) {
                                        if (l->leftToRight) {
                                                prev = (ChildGroup *)gf_list_get(st->groups, i-1);
                                        } else {
                                                prev = (ChildGroup *)gf_list_get(st->groups, i+1);
                                        }
                                        cg->final.x = prev->final.x + prev->final.width + spacing;
                                }
                                i += l->leftToRight ? +1 : -1;
                                if (l->leftToRight && (i==li->first_child + li->nb_children))
                                        break;
                                else if (!l->leftToRight && (i==li->first_child - 1))
                                        break;
                        }
                        if (l->topToBottom) {
                                current_top -= gf_mulfix(l->spacing, li->height);
                        } else {
                                current_top += gf_mulfix(l->spacing - FIX_ONE, li->height);
                        }
                        if (l->scrollVertical) {
                                st->scroll_len += li->height;
                        } else {
                                if (st->scroll_len < li->width) st->scroll_len = li->width;
                        }
                }
                return;
        }

        /*Vertical aligment*/
        li = (LineInfo*)gf_list_get(st->lines, 0);
        if (l->wrap && !l->leftToRight) {
                current_left = st->clip.x + st->clip.width;
                if (li) current_left -= li->width;
        } else {
                current_left = st->clip.x;
        }

        /*for all columns in run*/
        for (k=0; k<nbLines; k++) {
                Fixed spacing = 0;
                li = (LineInfo*)gf_list_get(st->lines, k);

                first = li->first_child;
                if (!l->topToBottom) first += li->nb_children - 1;

                /*set major alignment (Y) */
                cg = (ChildGroup *)gf_list_get(st->groups, first);
                switch (major) {
                case L_END:
                        cg->final.y = st->clip.y - st->clip.height + li->height;
                        break;
                case L_MIDDLE:
                        cg->final.y = st->clip.y - st->clip.height/2 + li->height/2;
                        break;
                case L_FIRST:
                case L_BEGIN:
                        cg->final.y = st->clip.y;
                        break;
                case L_JUSTIFY:
                        cg->final.y = st->clip.y;
                        if (li->nb_children>1) {
                                spacing = (st->clip.height - li->height) / (li->nb_children-1) ;
                                if (spacing<0) spacing = 0;
                        }
                        break;
                }

                /*for each in the run */
                i = first;
                while (1) {
                        cg = (ChildGroup *)gf_list_get(st->groups, i);
                        switch (minor) {
                        case L_MIDDLE:
                                cg->final.x = current_left + li->width/2 - cg->final.width/2;
                                break;
                        case L_END:
                                cg->final.x = current_left + li->width - cg->final.width;
                                break;
                        case L_BEGIN:
                        case L_FIRST:
                        default:
                                cg->final.x = current_left;
                                break;
                        }
                        /*update top for non-first children in line*/
                        if (i != first) {
                                if (l->topToBottom) {
                                        prev = (ChildGroup *)gf_list_get(st->groups, i-1);
                                } else {
                                        prev = (ChildGroup *)gf_list_get(st->groups, i+1);
                                }
                                cg->final.y = prev->final.y - prev->final.height + spacing;
                        }
                        i += l->topToBottom ? +1 : -1;
                        if (l->topToBottom && (i==li->first_child + li->nb_children))
                                break;
                        else if (!l->topToBottom && (i==li->first_child - 1))
                                break;
                }
                if (l->leftToRight) {
                        current_left += gf_mulfix(l->spacing, li->width);
                } else if (k < nbLines - 1) {
                        li = (LineInfo*)gf_list_get(st->lines, k+1);
                        current_left -= gf_mulfix(l->spacing, li->width);
                }
                if (l->scrollVertical) {
                        if (st->scroll_len < li->height) st->scroll_len = li->height;
                } else {
                        st->scroll_len += li->width;
                }
        }
}

static void layout_setup_scroll_bounds(LayoutStack *st, M_Layout *l)
{
        u32 minor_justify = 0;

        st->scroll_min = st->scroll_max = 0;

        if (l->horizontal) minor_justify = l->scrollVertical ? 1 : 0;
        else minor_justify = l->scrollVertical ? 0 : 1;

        /*update scroll-out max limit*/
        if (l->scrollMode != -1) {
                /*set max limit*/
                switch( get_justify(l, minor_justify)) {
                case L_END:
                        if (l->scrollVertical) {
                                if (st->scale_scroll<0) st->scroll_max = - st->scroll_len;
                                else st->scroll_max = st->clip.height;
                        } else {
                                if (st->scale_scroll<0) st->scroll_max = - st->clip.width;
                                else st->scroll_max = st->scroll_len;
                        }
                        break;
                case L_MIDDLE:
                        if (l->scrollVertical) {
                                if (st->scale_scroll<0) st->scroll_max = - (st->clip.height + st->scroll_len)/2;
                                else st->scroll_max = (st->clip.height + st->scroll_len)/2;
                        } else {
                                if (st->scale_scroll<0) st->scroll_max = - (st->clip.width + st->scroll_len)/2;
                                else st->scroll_max = (st->clip.width + st->scroll_len)/2;
                        }
                        break;
                default:
                        if (l->scrollVertical) {
                                if (st->scale_scroll<0) st->scroll_max = - st->clip.height;
                                else st->scroll_max = st->scroll_len;
                        } else {
                                if (st->scale_scroll<0) st->scroll_max = - st->scroll_len;
                                else st->scroll_max = st->clip.width;
                        }
                        break;
                }
        }
        /*scroll-in only*/
        else {
                st->scroll_max = 0;
        }

        /*scroll-out only*/
        if (l->scrollMode==1) {
                st->scroll_min = 0;
                return;
        }

        /*when vertically scrolling an horizontal layout, don't use vertical justification, only justify top/bottom lines*/
        if (l->horizontal && l->scrollVertical) {
                if (st->scale_scroll<0) {
                        st->scroll_min = st->scroll_len;
                } else {
                        st->scroll_min = - st->clip.height;
                }
                return;
        }

        /*update scroll-in offset*/
        switch( get_justify(l, minor_justify)) {
        case L_END:
                if (l->scrollVertical) {
                        if (st->scale_scroll<0) st->scroll_min = st->clip.height;
                        else st->scroll_min = - st->scroll_len;
                } else {
                        if (st->scale_scroll<0) st->scroll_min = st->scroll_len;
                        else st->scroll_min = -st->clip.width;
                }
                break;
        case L_MIDDLE:
                if (l->scrollVertical) {
                        if (st->scale_scroll<0) st->scroll_min = (st->clip.height + st->scroll_len)/2;
                        else st->scroll_min = - (st->clip.height + st->scroll_len)/2;
                } else {
                        if (st->scale_scroll<0) st->scroll_min = (st->clip.width + st->scroll_len)/2;
                        else st->scroll_min = - (st->clip.width + st->scroll_len)/2;
                }
                break;
        default:
                if (l->scrollVertical) {
                        if (st->scale_scroll<0) st->scroll_min = st->scroll_len;
                        else st->scroll_min = - st->clip.height;
                } else {
                        if (st->scale_scroll<0) st->scroll_min = st->clip.width;
                        else st->scroll_min = - st->scroll_len;
                }
                break;
        }
}


static void layout_scroll(GF_TraverseState *tr_state, LayoutStack *st, M_Layout *l)
{
        u32 i, nb_lines;
        Fixed scrolled, rate, elapsed, scroll_diff;
        Bool smooth, do_scroll, stop_anim;
        Double time = 0;
        ChildGroup *cg;

        /*not scrolling*/
        if (!st->scale_scroll && !st->is_scrolling && !st->key_scroll) return;

        if (st->key_scroll) {
                if (!st->is_scrolling) {
                        layout_setup_scroll_bounds(st, l);
                        st->is_scrolling = 1;
                }

                scrolled = st->last_scroll + INT2FIX(st->key_scroll);
        } else {

                time = gf_node_get_scene_time((GF_Node *)l);

                //      if (st->scale_scroll && (st->prev_rate!=st->scale_scroll)) st->start_scroll_type = 1;

                /*if scroll rate changed to previous non-zero value, this is a
                scroll restart, don't re-update bounds*/
                if ((st->start_scroll_type==2) && (st->prev_rate==st->scale_scroll)) st->start_scroll_type = 0;

                if (st->start_scroll_type) {
                        st->start_time = time;
                        st->is_scrolling = 1;
                        st->prev_rate = st->scale_scroll;

                        /*continuous restart: use last scroll to update the start time. We must recompute scroll bounds
                        since switching from scroll_rate >0 to <0 changes the bounds !*/
                        if ((st->start_scroll_type==2) && st->scale_scroll) {
                                Fixed cur_pos = st->scroll_min + st->last_scroll;
                                layout_setup_scroll_bounds(st, l);
                                cur_pos -= st->scroll_min;
                                st->start_time = time - FIX2FLT(gf_divfix(cur_pos, st->scale_scroll));
                        } else {
                                layout_setup_scroll_bounds(st, l);
                        }
                        st->last_scroll = 0;
                        st->start_scroll_type = 0;
                }

                /*handle pause/resume*/
                rate = st->scale_scroll;
                if (!rate) {
                        if (!st->pause_time) {
                                st->pause_time = time;
                        } else {
                                time = st->pause_time;
                        }
                        rate = st->prev_rate;
                } else if (st->pause_time) {
                        st->start_time += (time - st->pause_time);
                        st->pause_time = 0;
                }

                /*compute advance in pixels for smooth scroll*/
                elapsed = FLT2FIX((Float) (time - st->start_time));
                scrolled = gf_mulfix(elapsed, rate);
        }

        smooth = l->smoothScroll;
        /*if the scroll is in the same direction as the layout, there is no notion of line or column to scroll
        so move to smooth mode*/
        if (!l->horizontal && l->scrollVertical) smooth = 1;
        else if (l->horizontal && !l->scrollVertical) smooth = 1;


        stop_anim = 0;
        /*compute scroll diff for non-smooth mode*/
        if (smooth) {
                do_scroll = 1;
        } else {
                Fixed dim;
                scroll_diff = scrolled - st->last_scroll;
                do_scroll = 0;

                nb_lines = gf_list_count(st->lines);
                for (i=0; i < nb_lines; i++) {
                        LineInfo *li = (LineInfo*)gf_list_get(st->lines, i);
                        dim = l->scrollVertical ? li->height : li->width;
                        if (st->key_scroll) {
                                if (st->key_scroll<0) dim = -dim;
                                scrolled = dim + st->last_scroll;
                                /*in key mode we must handle the min ourselves since we can go below the scroll limit*/
                                if (st->scroll_min > st->scroll_len + scrolled) {
                                        scrolled = st->scroll_min - st->scroll_len;
                                } else {
                                        do_scroll = 1;
                                }
                                break;
                        } else if (ABS(scroll_diff) >= dim) {
//                              if (scroll_diff<0) scroll_diff = -dim;
//                              else scroll_diff = dim;
                                do_scroll = 1;
                                break;
                        }
                }
        }

        scroll_diff = st->scroll_max - st->scroll_min;
        if ((scroll_diff<0) && (scrolled<=scroll_diff)) {
                stop_anim = 1;
                scrolled = scroll_diff;
        }
        else if ((scroll_diff>0) && (scrolled>=scroll_diff)) {
                stop_anim = 1;
                scrolled = scroll_diff;
        }

        if (do_scroll)
                st->last_scroll = scrolled;
        else
                scrolled = st->last_scroll;

        i=0;
        while ((cg = (ChildGroup *)gf_list_enum(st->groups, &i))) {
                if (l->scrollVertical) {
                        cg->scroll_y = st->scroll_min + scrolled;
                        cg->scroll_x = 0;
                } else {
                        cg->scroll_x = st->scroll_min + scrolled;
                        cg->scroll_y = 0;
                }
        }

        if (st->key_scroll) {
                st->key_scroll = 0;
                return;
        }
        /*draw next frame*/
        if (!stop_anim) {
                tr_state->visual->compositor->force_next_frame_redraw = 1;
                return;
        }

        /*done*/
        if (!l->loop) return;

        /*restart*/
        st->start_time = time;
        gf_sc_invalidate(tr_state->visual->compositor, NULL);
}


static void TraverseLayout(GF_Node *node, void *rs, Bool is_destroy)
{
        Bool recompute_layout;
        u32 i;
        ChildGroup *cg;
        GF_IRect prev_clip;
        Bool mode_bckup, had_clip=GF_FALSE;
        ParentNode2D *parent_bck;
        GF_Rect prev_clipper;
        M_Layout *l = (M_Layout *)node;
        LayoutStack *st = (LayoutStack *) gf_node_get_private(node);
        GF_TraverseState *tr_state = (GF_TraverseState *)rs;

        if (is_destroy) {
                layout_reset_lines(st);
                parent_node_predestroy((ParentNode2D *)st);
                gf_list_del(st->lines);
                gf_free(st);
                return;
        }

        /*note we don't clear dirty flag, this is done in traversing*/
        if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {

                /*TO CHANGE IN BIFS - scroll_rate is quite unusable*/
                st->scale_scroll = st->scroll_rate = l->scrollRate;
                /*move to pixel metrics*/
                if (visual_get_size_info(tr_state, &st->clip.width, &st->clip.height)) {
                        st->scale_scroll = gf_mulfix(st->scale_scroll, l->scrollVertical ? st->clip.height : st->clip.width);
                }
                /*setup bounds in local coord system*/
                if (l->size.x>=0) st->clip.width = l->size.x;
                if (l->size.y>=0) st->clip.height = l->size.y;
                st->bounds = st->clip = gf_rect_center(st->clip.width, st->clip.height);

                if (st->scale_scroll && !st->start_scroll_type) st->start_scroll_type = 1;

                drawable_reset_group_highlight(tr_state, node);
        }

        /*don't waste time traversing is pick ray not in clipper*/
        if ((tr_state->traversing_mode==TRAVERSE_PICK) && !gf_sc_pick_in_clipper(tr_state, &st->clip))
                goto layout_exit;

        if ((tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) && !tr_state->for_node) {
                tr_state->bounds = st->clip;
#ifndef GPAC_DISABLE_3D
                gf_bbox_from_rect(&tr_state->bbox, &st->clip);
#endif
                goto layout_exit;
        }

        recompute_layout = 0;
        if (gf_node_dirty_get(node))
                recompute_layout = 1;

        /*setup clipping*/
        prev_clip = tr_state->visual->top_clipper;
        if (tr_state->traversing_mode==TRAVERSE_SORT) {
                compositor_2d_update_clipper(tr_state, st->clip, &had_clip, &prev_clipper, 0);
                if (tr_state->has_clip) {
                        tr_state->visual->top_clipper = gf_rect_pixelize(&tr_state->clipper);
                        gf_irect_intersect(&tr_state->visual->top_clipper, &prev_clip);
                }
        }
        if (recompute_layout) {
                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Layout] recomputing positions\n"));

                parent_node_reset((ParentNode2D*)st);

                /*setup traversing state*/
                parent_bck = tr_state->parent;
                mode_bckup = tr_state->traversing_mode;
                tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
                tr_state->parent = (ParentNode2D *) st;

                if (l->wrap) tr_state->text_split_mode = 1;
                parent_node_traverse(node, (ParentNode2D *)st, tr_state);
                /*restore traversing state*/
                tr_state->parent = parent_bck;
                tr_state->traversing_mode = mode_bckup;
                if (l->wrap) tr_state->text_split_mode = 0;

                /*center all nodes*/
                i=0;
                while ((cg = (ChildGroup *)gf_list_enum(st->groups, &i))) {
                        cg->final.x = - cg->final.width/2;
                        cg->final.y = cg->final.height/2;
                }

                /*apply justification*/
                layout_justify(st, l);

                /*if scrolling, update bounds*/
                if (l->scrollRate && st->is_scrolling) {
                        layout_setup_scroll_bounds(st, l);
                }
        }


        if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS)  {
                tr_state->bounds = st->bounds;
                if (l->scrollVertical) {
                        tr_state->bounds.height = st->scroll_len;
                } else {
                        tr_state->bounds.width = st->scroll_len;
                }
#ifndef GPAC_DISABLE_3D
                gf_bbox_from_rect(&tr_state->bbox, &tr_state->bounds);
#endif
                goto layout_exit;
        }


        /*scroll*/
        layout_scroll(tr_state, st, l);

        i=0;
        while ((cg = (ChildGroup *)gf_list_enum(st->groups, &i))) {
                parent_node_child_traverse(cg, tr_state);
        }
        tr_state->visual->top_clipper = prev_clip;
        if (tr_state->traversing_mode==TRAVERSE_SORT)  {
                if (had_clip) tr_state->clipper = prev_clipper;
                tr_state->has_clip = had_clip;

                drawable_check_focus_highlight(node, tr_state, &st->clip);
        }

layout_exit:
        tr_state->text_split_mode = 0;
}

static Bool OnLayout(GF_SensorHandler *sh, Bool is_over, Bool is_cancel, GF_Event *ev, GF_Compositor *compositor)
{
        Bool vertical = ((M_Layout *)sh->sensor)->scrollVertical;
        LayoutStack *st = (LayoutStack *) gf_node_get_private(sh->sensor);

        if (!is_over) {
                st->is_scrolling = 0;
                st->key_scroll = 0;
                return 0;
        }
        if (ev->type!=GF_EVENT_KEYDOWN) {
                st->is_scrolling = 0;
                st->key_scroll = 0;
                return 0;
        }

        switch (ev->key.key_code) {
        case GF_KEY_LEFT:
                if (!st->keys_active) return 0;

                if (vertical) return 0;
                st->key_scroll = -1;
                break;
        case GF_KEY_RIGHT:
                if (!st->keys_active) return 0;

                if (vertical) return 0;
                st->key_scroll = +1;
                break;
        case GF_KEY_UP:
                if (!st->keys_active) return 0;

                if (!vertical) return 0;
                st->key_scroll = +1;
                break;
        case GF_KEY_DOWN:
                if (!st->keys_active) return 0;

                if (!vertical) return 0;
                st->key_scroll = -1;
                break;
        case GF_KEY_ENTER:
                st->keys_active = !st->keys_active;
                break;
        default:
                st->key_scroll = 0;
                return 0;
        }
        gf_sc_invalidate(compositor, NULL);
        return 1;
}

static Bool layout_is_enabled(GF_Node *node)
{
        M_Layout *l = (M_Layout *)node;
        if (l->scrollRate != 0) return 0;
        return 1;
}

void compositor_init_layout(GF_Compositor *compositor, GF_Node *node)
{
        LayoutStack *stack;
        GF_SAFEALLOC(stack, LayoutStack);
        if (!stack) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate layout stack\n"));
                return;
        }

        parent_node_setup((ParentNode2D*)stack);
        stack->lines = gf_list_new();
        gf_node_set_private(node, stack);
        gf_node_set_callback_function(node, TraverseLayout);
        stack->hdl.sensor = node;
        stack->hdl.IsEnabled = layout_is_enabled;
        stack->hdl.OnUserEvent = OnLayout;
}

void compositor_layout_modified(GF_Compositor *compositor, GF_Node *node)
{
        LayoutStack *st = (LayoutStack *) gf_node_get_private(node);
        /*if modif other than scrollrate restart scroll*/
        if (st->scroll_rate == ((M_Layout*)node)->scrollRate) {
                st->start_scroll_type = 1;
        }
        /*if modif on scroll rate only, indicate continous restart*/
        else if (((M_Layout*)node)->scrollRate) {
                st->start_scroll_type = 2;
        }
        gf_node_dirty_set(node, GF_SG_NODE_DIRTY, 0);
        gf_sc_invalidate(compositor, NULL);
}

GF_SensorHandler *compositor_mpeg4_layout_get_sensor_handler(GF_Node *node)
{
        LayoutStack *st = (LayoutStack *) gf_node_get_private(node);
        return &st->hdl;
}


#endif /*GPAC_DISABLE_VRML*/

/* [<][>][^][v][top][bottom][index][help] */