/*
 * This file is part of vimix - video live mixer
 *
 * **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
 *
 * 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.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
**/

#include "IconsFontAwesome5.h"
#include <glad/glad.h>
#include <glm/common.hpp>
#include <glm/ext/vector_float2.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/vector_angle.hpp>

#include <string>
#include <sstream>
#include <iomanip>

#include "Toolkit/ImGuiToolkit.h"

#include "defines.h"
#include "Mixer.h"
#include "Source/Source.h"
#include "Settings.h"
#include "Resource.h"
#include "Visitor/PickingVisitor.h"
#include "Visitor/DrawVisitor.h"
#include "Scene/Decorations.h"
#include "UserInterfaceManager.h"
#include "ActionManager.h"
#include "Toolkit/DialogToolkit.h"
#include "MousePointer.h"
#include "TabletInput.h"

enum TabletInputFlags_
{
    TabletInput_none            = 0,
    TabletInput_brush_size      = 1 << 1,
    TabletInput_brush_pressure  = 1 << 2
};

#include "TextureView.h"

TextureView::TextureView() : View(TEXTURE), edit_source_(nullptr), need_edit_update_(true)
{
    scene.root()->scale_ = glm::vec3(APPEARANCE_DEFAULT_SCALE, APPEARANCE_DEFAULT_SCALE, 1.0f);
    scene.root()->translation_ = glm::vec3(0.8f, 0.f, 0.0f);
    // read default settings
    if ( Settings::application.views[mode_].name.empty() )
        // no settings found: store application default
        saveSettings();
    else
        restoreSettings();
    Settings::application.views[mode_].name = "Texturing";

    //
    // Scene background
    //
    // global dark
    Surface *tmp = new Surface( new Shader);
    tmp->scale_ = glm::vec3(20.f, 20.f, 1.f);
    tmp->shader()->color = glm::vec4( 0.1f, 0.1f, 0.1f, 0.6f );
    scene.bg()->attach(tmp);
    // frame showing the source original shape
    background_surface_= new Surface( new Shader);
    background_surface_->scale_ = glm::vec3(20.f, 20.f, 1.f);
    background_surface_->shader()->color = glm::vec4( COLOR_BGROUND, 1.0f );
    scene.bg()->attach(background_surface_);
    background_frame_ = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
    background_frame_->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.6f );
    scene.bg()->attach(background_frame_);
    // frame with checkerboard background to show cropped preview
    preview_checker_ = new ImageSurface("images/checker.dds");
    static glm::mat4 Tra = glm::scale(glm::translate(glm::identity<glm::mat4>(), glm::vec3( -32.f, -32.f, 0.f)), glm::vec3( 64.f, 64.f, 1.f));
    preview_checker_->shader()->iTransform = Tra;
    scene.bg()->attach(preview_checker_);
    preview_frame_ = new Frame(Frame::SHARP, Frame::THIN, Frame::GLOW);
    preview_frame_->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f );
    scene.bg()->attach(preview_frame_);
    // marks on the frame to show scale
    horizontal_mark_ = new Mesh("mesh/h_mark.ply");
    horizontal_mark_->translation_ = glm::vec3(0.f, -1.f, 0.0f);
    horizontal_mark_->scale_ = glm::vec3(2.5f, -2.5f, 0.0f);
    horizontal_mark_->rotation_.z = M_PI;
    horizontal_mark_->shader()->color = glm::vec4( COLOR_TRANSITION_LINES, 0.9f );
    scene.bg()->attach(horizontal_mark_);
    vertical_mark_  = new Mesh("mesh/h_mark.ply");
    vertical_mark_->translation_ = glm::vec3(-1.0f, 0.0f, 0.0f);
    vertical_mark_->scale_ = glm::vec3(2.5f, -2.5f, 0.0f);
    vertical_mark_->rotation_.z = M_PI_2;
    vertical_mark_->shader()->color = glm::vec4( COLOR_TRANSITION_LINES, 0.9f );
    scene.bg()->attach(vertical_mark_);

    //
    // surface to show the texture of the source
    //
    preview_shader_ = new ImageShader;
    preview_surface_ = new Surface(preview_shader_); // to attach source preview
    preview_surface_->translation_.z = 0.002f;
    scene.bg()->attach(preview_surface_);

    //
    // User interface foreground
    //
    // Mask manipulation
    mask_node_ = new Group;
    mask_square_ = new Frame(Frame::SHARP, Frame::LARGE, Frame::NONE);
    mask_square_->color = glm::vec4( COLOR_APPEARANCE_MASK, 1.f );
    mask_node_->attach(mask_square_);
    mask_circle_ = new Mesh("mesh/circle.ply");
    mask_circle_->shader()->color = glm::vec4( COLOR_APPEARANCE_MASK, 1.f );
    mask_node_->attach(mask_circle_);
    mask_horizontal_ = new Mesh("mesh/h_line.ply");
    mask_horizontal_->shader()->color = glm::vec4( COLOR_APPEARANCE_MASK, 1.f );
    mask_horizontal_->scale_.x = 1.0f;
    mask_horizontal_->scale_.y = 3.0f;
    mask_node_->attach(mask_horizontal_);
    mask_vertical_ = new Group;
    Mesh *line = new Mesh("mesh/h_line.ply");
    line->shader()->color = glm::vec4( COLOR_APPEARANCE_MASK, 1.f );
    line->scale_.x = 1.0f;
    line->scale_.y = 3.0f;
    line->rotation_.z = M_PI_2;
    mask_vertical_->attach(line);
    mask_node_->attach(mask_vertical_);
    scene.fg()->attach(mask_node_);

    // Source manipulation (texture coordinates)
    //
    // point to show POSITION
    overlay_position_ = new Symbol(Symbol::SQUARE_POINT);
    overlay_position_->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    overlay_position_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
    scene.fg()->attach(overlay_position_);
    overlay_position_->visible_ = false;
    // cross to show the axis for POSITION
    overlay_position_cross_ = new Symbol(Symbol::GRID);
    overlay_position_cross_->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    overlay_position_cross_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
    scene.fg()->attach(overlay_position_cross_);
    overlay_position_cross_->visible_ = false;
    // 'grid' : tic marks every 0.1 step for SCALING
    // with dark background
    Group *g = new Group;
    Symbol *s = new Symbol(Symbol::GRID);
    s->scale_ = glm::vec3(1.655f, 1.655f, 1.f);
    s->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    g->attach(s);
    s = new Symbol(Symbol::SQUARE_POINT);
    s->color = glm::vec4(0.f, 0.f, 0.f, 0.2f);
    s->scale_ = glm::vec3(18.f, 18.f, 1.f);
    s->translation_.z = -0.1;
    g->attach(s);
    overlay_scaling_grid_ = g;
    overlay_scaling_grid_->scale_ = glm::vec3(0.3f, 0.3f, 1.f);
    scene.fg()->attach(overlay_scaling_grid_);
    overlay_scaling_grid_->visible_ = false;
    // cross in the square for proportional SCALING
    overlay_scaling_cross_ = new Symbol(Symbol::CROSS);
    overlay_scaling_cross_->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    overlay_scaling_cross_->scale_ = glm::vec3(0.3f, 0.3f, 1.f);
    scene.fg()->attach(overlay_scaling_cross_);
    overlay_scaling_cross_->visible_ = false;
    // square to show the center of SCALING
    overlay_scaling_ = new Symbol(Symbol::SQUARE);
    overlay_scaling_->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    overlay_scaling_->scale_ = glm::vec3(0.3f, 0.3f, 1.f);
    scene.fg()->attach(overlay_scaling_);
    overlay_scaling_->visible_ = false;
    // 'clock' : tic marks every 10 degrees for ROTATION
    // with dark background
    g = new Group;
    s = new Symbol(Symbol::CLOCK);
    s->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    g->attach(s);
    s = new Symbol(Symbol::CIRCLE_POINT);
    s->color = glm::vec4(0.f, 0.f, 0.f, 0.25f);
    s->scale_ = glm::vec3(28.f, 28.f, 1.f);
    s->translation_.z = -0.1;
    g->attach(s);
    overlay_rotation_clock_ = g;
    overlay_rotation_clock_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_clock_);
    overlay_rotation_clock_->visible_ = false;
    // circle to show fixed-size  ROTATION
    overlay_rotation_clock_hand_ = new Symbol(Symbol::CLOCK_H);
    overlay_rotation_clock_hand_->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    overlay_rotation_clock_hand_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_clock_hand_);
    overlay_rotation_clock_hand_->visible_ = false;
    overlay_rotation_fix_ = new Symbol(Symbol::SQUARE);
    overlay_rotation_fix_->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    overlay_rotation_fix_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_fix_);
    overlay_rotation_fix_->visible_ = false;
    // circle to show the center of ROTATION
    overlay_rotation_ = new Symbol(Symbol::CIRCLE);
    overlay_rotation_->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f );
    overlay_rotation_->scale_ = glm::vec3(0.25f, 0.25f, 1.f);
    scene.fg()->attach(overlay_rotation_);
    overlay_rotation_->visible_ = false;

    // Mask draw
    mask_cursor_paint_ = 1;
    mask_cursor_shape_ = 1;
    stored_mask_size_ = glm::vec3(0.f);
    mask_cursor_circle_ = new Mesh("mesh/icon_circle.ply");
    mask_cursor_circle_->scale_ = glm::vec3(0.2f, 0.2f, 1.f);
    mask_cursor_circle_->shader()->color = glm::vec4( COLOR_APPEARANCE_MASK, 0.8f );
    mask_cursor_circle_->visible_ = false;
    scene.fg()->attach(mask_cursor_circle_);
    mask_cursor_square_ = new Mesh("mesh/icon_square.ply");
    mask_cursor_square_->scale_ = glm::vec3(0.2f, 0.2f, 1.f);
    mask_cursor_square_->shader()->color = glm::vec4( COLOR_APPEARANCE_MASK, 0.8f );
    mask_cursor_square_->visible_ = false;
    scene.fg()->attach(mask_cursor_square_);
    mask_cursor_crop_ = new Mesh("mesh/icon_crop.ply");
    mask_cursor_crop_->scale_ = glm::vec3(1.4f, 1.4f, 1.f);
    mask_cursor_crop_->shader()->color = glm::vec4( COLOR_APPEARANCE_MASK, 0.9f );
    mask_cursor_crop_->visible_ = false;
    scene.fg()->attach(mask_cursor_crop_);

    stored_mask_size_ = glm::vec3(0.f);
    show_cursor_forced_ = false;
    scene_brush_pos = glm::vec3(100.f);
    previous_scene_brush_pos = glm::vec3(0.f);

    // replace grid with appropriate one
    translation_grid_ = new TranslationGrid(scene.root());
    translation_grid_->root()->visible_ = false;
    rotation_grid_ = new RotationGrid(scene.root());
    rotation_grid_->root()->visible_ = false;
    if (grid) delete grid;
    grid = translation_grid_;
}

void TextureView::update(float dt)
{
    View::update(dt);

    // a more complete update is requested (e.g. after switching to view)
    if  (View::need_deep_update_ > 0 ||
        (Mixer::manager().currentSource() != nullptr && edit_source_ != Mixer::manager().currentSource() )) {
        // request update
        need_edit_update_ = true;
        // change grid color
        const ImVec4 c = ImGuiToolkit::HighlightColor();
        translation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) );
        rotation_grid_->setColor( glm::vec4(c.x, c.y, c.z, 0.3) );
    }

}

void TextureView::resize ( int scale )
{
    float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
    z *= z;
    z *= APPEARANCE_MAX_SCALE - APPEARANCE_MIN_SCALE;
    z += APPEARANCE_MIN_SCALE;
    scene.root()->scale_.x = z;
    scene.root()->scale_.y = z;

    // Clamp translation to acceptable area
    glm::vec3 border(2.f * Mixer::manager().session()->frame()->aspectRatio(), 2.f, 0.f);
    scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border);
}

int TextureView::size ()
{
    float z = (scene.root()->scale_.x - APPEARANCE_MIN_SCALE) / (APPEARANCE_MAX_SCALE - APPEARANCE_MIN_SCALE);
    return (int) ( sqrt(z) * 100.f);
}

void  TextureView::recenter ()
{
    // restore default view
    restoreSettings();
}

void TextureView::select(glm::vec2 A, glm::vec2 B)
{
    // unproject mouse coordinate into scene coordinates
    glm::vec3 scene_point_A = Rendering::manager().unProject(A);
    glm::vec3 scene_point_B = Rendering::manager().unProject(B);

    // picking visitor traverses the scene
    PickingVisitor pv(scene_point_A, scene_point_B, true); // here is the difference
    scene.accept(pv);

    // picking visitor found nodes in the area?
    if ( !pv.empty()) {

        // create a list of source matching the list of picked nodes
        SourceList selection;
        // loop over the nodes and add all sources found.
         for(std::vector< std::pair<Node *, glm::vec2> >::const_reverse_iterator p = pv.rbegin(); p != pv.rend(); ++p){
            Source *s = Mixer::manager().findSource( p->first );
            if (canSelect(s))
                selection.push_back( s );
        }
        // set the selection with list of picked (overlaped) sources
        Mixer::selection().set(selection);
    }
    else
        // reset selection
        Mixer::selection().clear();
}

bool TextureView::canSelect(Source *s) {

    return ( s!=nullptr && ( s == Mixer::manager().currentSource() || s == edit_source_ ));
}

View::Cursor TextureView::over (glm::vec2 pos)
{
    mask_cursor_circle_->visible_ = false;
    mask_cursor_square_->visible_ = false;
    mask_cursor_crop_->visible_   = false;

    if (edit_source_ != nullptr && edit_source_->ready())
    {
        // coordinates of mouse in scene reference frame
        glm::vec3 scene_pos = Rendering::manager().unProject(pos, scene.root()->transform_);

        // if the mouse button is down, use the grabbing coordinates instead
        if (current_action_ongoing_)
            scene_pos = scene_brush_pos;

        // put the cursor at the coordinates of the mouse / cursor
        mask_cursor_circle_->translation_ = glm::vec3(glm::vec2(scene_pos), 0.f);
        mask_cursor_square_->translation_ = glm::vec3(glm::vec2(scene_pos), 0.f);
        mask_cursor_crop_->translation_ = glm::vec3(glm::vec2(scene_pos), 0.f);

        ImGuiIO& io = ImGui::GetIO();
        if (!io.WantCaptureMouse || show_cursor_forced_)
        {
            // show paint brush cursor
            if (edit_source_->maskShader()->mode == MaskShader::PAINT) {
                if (mask_cursor_paint_ > 0) {
                    glm::vec2 S(preview_surface_->scale_);
                    S += glm::vec2(Settings::application.brush.x);
                    if ( ABS(scene_pos.x) < S.x  && ABS(scene_pos.y) < S.y ) {
                        mask_cursor_circle_->visible_ = Settings::application.brush.z < 1.0;
                        mask_cursor_square_->visible_ = Settings::application.brush.z > 0.0;
                        edit_source_->maskShader()->option = mask_cursor_paint_;
                        if (mask_cursor_paint_ > 1) {
                            ImVec4 c = ImGuiToolkit::HighlightColor(false);
                            mask_cursor_circle_->shader()->color = glm::vec4(c.x, c.y, c.z, 0.8f );
                            mask_cursor_square_->shader()->color = glm::vec4(c.x, c.y, c.z, 0.8f );
                        } else {
                            ImVec4 c = ImGuiToolkit::HighlightColor();
                            mask_cursor_circle_->shader()->color = glm::vec4(c.x, c.y, c.z, 0.8f );
                            mask_cursor_square_->shader()->color = glm::vec4(c.x, c.y, c.z, 0.8f );
                        }
                    }
                    else {
                        edit_source_->maskShader()->option = 0;
                    }
                }
            }
            // show crop cursor
            else if (edit_source_->maskShader()->mode == MaskShader::SHAPE) {
                if (mask_cursor_shape_ > 0) {
                    mask_cursor_crop_->visible_   = true;
                    mask_cursor_crop_->scale_ = glm::vec3(1.4f /scene.root()->scale_.x, 1.4f / scene.root()->scale_.x, 1.f);
                }
            }
        }
    }

    return Cursor();
}

void TextureView::adaptGridToSource(Source *s, Node *picked)
{
    // remember active state
    bool _active = grid->active();

    // Reset
    grid = translation_grid_;
    rotation_grid_->setActive( false );
    translation_grid_->setActive( false );
    rotation_grid_->root()->translation_ = glm::vec3(0.f);
    rotation_grid_->root()->scale_ = glm::vec3(1.f);
    translation_grid_->root()->translation_ = glm::vec3(0.f);
    translation_grid_->root()->rotation_ = glm::vec3(0.f);

    if (s != nullptr) {

        if ( picked == s->handles_[mode_][Handles::ROTATE] ) {
            // shift grid at center of source
            rotation_grid_->root()->translation_ = s->group(mode_)->translation_;
            rotation_grid_->root()->scale_.x = glm::length(
                        glm::vec2(s->frame()->aspectRatio() * s->group(mode_)->scale_.x,
                                  s->group(mode_)->scale_.y) );
            rotation_grid_->root()->scale_.y = rotation_grid_->root()->scale_.x;
            // Swap grid to rotation grid
            grid = rotation_grid_;
        }
        else if ( picked == s->handles_[mode_][Handles::RESIZE] ||
                  picked == s->handles_[mode_][Handles::RESIZE_V] ||
                  picked == s->handles_[mode_][Handles::RESIZE_H] ){
            translation_grid_->root()->translation_ = glm::vec3(0.f);
            translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z;
        }
        else if ( picked == s->handles_[mode_][Handles::SCALE] ){
            translation_grid_->root()->translation_ = s->group(mode_)->translation_;
            translation_grid_->root()->rotation_.z = s->group(mode_)->rotation_.z;
        }

        // set grid aspect ratio
        if (Settings::application.proportional_grid)
            translation_grid_->setAspectRatio( s->frame()->aspectRatio() );
        else
            translation_grid_->setAspectRatio( 1.f );
    }

    // restore active state
    grid->setActive( _active );

    // only the active source is visible
    rotation_grid_->root()->visible_ = rotation_grid_->active();
    translation_grid_->root()->visible_ = translation_grid_->active();
}

std::pair<Node *, glm::vec2> TextureView::pick(glm::vec2 P)
{
    // prepare empty return value
    std::pair<Node *, glm::vec2> pick = { nullptr, glm::vec2(0.f) };

    // unproject mouse coordinate into scene coordinates
    glm::vec3 scene_point_ = Rendering::manager().unProject(P);

    // picking visitor traverses the scene (force to find source)
    PickingVisitor pv(scene_point_, true);
    scene.accept(pv);

    // picking visitor found nodes?
    if ( !pv.empty()) {
        // keep edit source active if it is clicked
        // AND if the cursor is not for drawing
        Source *current = edit_source_;
        if (current != nullptr) {

            // special case for drawing in the mask
            if ( current->maskShader()->mode == MaskShader::PAINT && mask_cursor_paint_ > 0) {
                pick = { mask_cursor_circle_, P };
                // adapt grid to prepare grab action
                adaptGridToSource(current);
                // reset previous brush position
                previous_scene_brush_pos = glm::zero<glm::vec3>();
                return pick;
            }
            // special case for cropping the mask shape
            else if ( current->maskShader()->mode == MaskShader::SHAPE && mask_cursor_shape_ > 0) {
                pick = { mask_cursor_crop_, P };
                // adapt grid to prepare grab action
                adaptGridToSource(current);
                return pick;
            }

            // find if the edit source was picked
            auto itp = pv.rbegin();
            for (; itp != pv.rend(); itp++){
                // test if source contains this node
                Source::hasNode is_in_source( (*itp).first );
                if ( is_in_source( current ) ){
                    // a node in the current source was clicked !
                    pick = *itp;
                    // adapt grid to prepare grab action
                    adaptGridToSource(current, pick.first);
                    break;
                }
            }
            // not found: the edit source was not clicked
            if ( itp == pv.rend() ) {
                current = nullptr;
            }
            // picking on the menu handle
            else if ( !current->locked() && pick.first == current->handles_[mode_][Handles::MENU] ) {
                // show context menu
                openContextMenu(MENU_SOURCE);
            }
            // pick on the lock icon; unlock source
            else if ( UserInterface::manager().ctrlModifier() && pick.first == current->lock_ ) {
                lock(current, false);
                pick = { current->locker_,  pick.second };
            }
            // pick on the open lock icon; lock source and cancel pick
            else if ( UserInterface::manager().ctrlModifier() && pick.first == current->unlock_ ) {
                lock(current, true);
                current = nullptr;
            }
        }

        // not the edit source (or no edit source)
        if (current == nullptr) {
            // cancel pick
            pick = { nullptr, glm::vec2(0.f) };
        }

    }

    return pick;
}

bool TextureView::adjustBackground()
{
    bool ret = false;

    // by default consider edit source is null
    mask_node_->visible_ = false;
    float image_original_width = 1.f;
    scale_crop_ = glm::vec3(1.f);
    shift_crop_ = glm::vec3(0.f);
    preview_surface_->setTextureIndex( Resource::getTextureTransparent() );

    // if its a valid index
    if (edit_source_ != nullptr) {
        if (edit_source_->ready()) {
            // update rendering frame to match edit source AR
            image_original_width = edit_source_->frame()->aspectRatio();
            scale_crop_.x = (edit_source_->group(View::GEOMETRY)->crop_[1]
                             - edit_source_->group(View::GEOMETRY)->crop_[0])
                            * 0.5f;
            scale_crop_.y = (edit_source_->group(View::GEOMETRY)->crop_[2]
                             - edit_source_->group(View::GEOMETRY)->crop_[3])
                            * 0.5f;
            shift_crop_.x = edit_source_->group(View::GEOMETRY)->crop_[1] - scale_crop_.x;
            shift_crop_.y = edit_source_->group(View::GEOMETRY)->crop_[3] + scale_crop_.y;
            scale_crop_.x *= edit_source_->frame()->aspectRatio();
            shift_crop_.x *= edit_source_->frame()->aspectRatio();

            preview_surface_->setTextureIndex(edit_source_->frame()->texture());
            preview_shader_->secondary_texture = edit_source_->blendingShader()->secondary_texture;
            preview_surface_->scale_ = scale_crop_;
            preview_surface_->translation_ = shift_crop_;

            // mask appearance
            mask_node_->visible_ = edit_source_->maskShader()->mode == MaskShader::SHAPE
                                   && mask_cursor_shape_ > 0;

            int shape = edit_source_->maskShader()->shape;
            mask_circle_->visible_ = shape == MaskShader::ELLIPSE;
            mask_square_->visible_ = shape == MaskShader::OBLONG || shape == MaskShader::RECTANGLE;
            mask_horizontal_->visible_ = shape == MaskShader::HORIZONTAL;
            mask_vertical_->visible_ = shape == MaskShader::VERTICAL;

            // symetrical shapes
            if (shape < MaskShader::HORIZONTAL) {
                mask_node_->scale_ = scale_crop_ * glm::vec3(edit_source_->maskShader()->size, 1.f);
                mask_node_->translation_ = glm::vec3(0.f);
            }
            // vertical
            else if (shape > MaskShader::HORIZONTAL) {
                mask_node_->scale_ = glm::vec3(1.f, scale_crop_.y, 1.f);
                mask_node_->translation_ = glm::vec3(edit_source_->maskShader()->size.x
                                                         * scale_crop_.x,
                                                     0.f,
                                                     0.f);
            }
            // horizontal
            else {
                mask_node_->scale_ = glm::vec3(scale_crop_.x, 1.f, 1.f);
                mask_node_->translation_ = glm::vec3(0.f,
                                                     edit_source_->maskShader()->size.y
                                                         * scale_crop_.y,
                                                     0.f);
            }

            mask_node_->translation_ += shift_crop_;
        }
        else
            ret = true;
    }

    // background scene
    background_surface_->scale_.x = image_original_width;
    background_surface_->scale_.y = 1.f;
    background_frame_->scale_.x = image_original_width;
    vertical_mark_->translation_.x = -image_original_width;
    preview_frame_->scale_ = scale_crop_;
    preview_frame_->translation_ = shift_crop_;
    preview_checker_->scale_ = scale_crop_;
    preview_checker_->translation_ = shift_crop_;
    glm::mat4 Ar  = glm::scale(glm::identity<glm::mat4>(), scale_crop_ );
    static glm::mat4 Tra = glm::scale(glm::translate(glm::identity<glm::mat4>(), glm::vec3( -32.f, -32.f, 0.f)), glm::vec3( 64.f, 64.f, 1.f));
    preview_checker_->shader()->iTransform = Ar * Tra;

    return ret;
}

Source *TextureView::getEditOrCurrentSource()
{
    // get current source
    Source *_source = Mixer::manager().currentSource();

    // no current source but possible source
    if (_source == nullptr && Mixer::manager().numSource() > 0) {
        // then get the source previously active
        // _source = UserInterface::manager().sourceInPanel();
        _source = Mixer::manager().previousCurrentSource();
        // nothing yet?
        if (_source == nullptr && edit_source_ != nullptr) {
            // then keep the current edit source ( if still available )
            _source = Mixer::manager().findSource( edit_source_->id() );
        }
        // ensures the source is selected
        Mixer::selection().set(_source);
    }

    // do not work on a failed source
    if (_source != nullptr && _source->failed() ) {
        _source = nullptr;
    }

    return _source;
}

void TextureView::draw()
{
    // edit view needs to be updated (source changed)
    if (need_edit_update_) {
        // now, follow the change of current source
        // & remember source to edit
        edit_source_ = getEditOrCurrentSource();

        // update background and frame to match editsource
        // & return true if still needs to edit update
        need_edit_update_ = adjustBackground();
    }

    // Display grid
    grid->root()->visible_ = (grid->active() && current_action_ongoing_);

    // draw general view
    Shader::force_blending_opacity = true;
    View::draw();
    Shader::force_blending_opacity = false;

    // if source active
    if (edit_source_ != nullptr){

        // force to redraw the frame of the edit source (even if source is not visible)
        DrawVisitor dv(edit_source_->groups_[mode_], Rendering::manager().Projection(), true);
        scene.accept(dv);

        // display interface
        // Locate window at upper left corner
        glm::vec2 P(-background_frame_->scale_.x, background_frame_->scale_.y + 0.01f);
        P = Rendering::manager().project(glm::vec3(P, 0.f), scene.root()->transform_, false);
        // Set window position depending on icons size
        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
        ImGui::SetNextWindowPos(ImVec2(P.x, P.y - 2.f * ImGui::GetFrameHeight() ), ImGuiCond_Always);
        if (ImGui::Begin("##AppearanceMaskOptions", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground
                         | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings
                         | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus ))
        {
            // style grey
            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f));  // 1
            ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
            ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.15f, 0.5f));
            ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.16f, 0.16f, 0.16f, 0.99f));
            ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.85f, 0.85f, 0.85f, 0.86f));
            ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.95f, 0.95f, 0.95f, 1.00f));
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.22f, 0.22f, 0.22f, 0.99f));

            int maskmode = edit_source_->maskShader()->mode;
            ImGui::SetNextItemWidth( ImGui::GetTextLineHeightWithSpacing() * 2.6);
            if (ImGui::Button( std::string(std::string(MaskShader::mask_icons[maskmode])+" "+ICON_FA_SORT_DOWN ).c_str()))
                ImGui::OpenPopup( "Mask_menu_popup" );
            if (ImGui::IsItemHovered())
                ImGuiToolkit::ToolTip(MaskShader::mask_names[maskmode]);
            if (ImGui::BeginPopup( "Mask_menu_popup" ))
            {
                ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
                for (int m = MaskShader::NONE; m <= MaskShader::SOURCE; ++m){
                    if (ImGui::Selectable( std::string(std::string(MaskShader::mask_icons[m])
                                                      + " " + MaskShader::mask_names[m]).c_str()) ) {
                        // on change of mode
                        if (maskmode != m) {
                            // cancel previous source mask
                            if (maskmode == MaskShader::SOURCE) {
                                // store source image as mask before painting
                                if (edit_source_->maskSource()->connected() && m == MaskShader::PAINT)
                                    edit_source_->setMask( edit_source_->maskSource()->source()->frame()->image() );
                                // cancel source mask
                                edit_source_->maskSource()->disconnect();
                            }
                            // store current mask before painting
                            else if (m == MaskShader::PAINT)
                                edit_source_->storeMask();
                            // set new mode
                            maskmode = m;
                            edit_source_->maskShader()->mode = maskmode;
                            // force update source and view
                            edit_source_->touch(Source::SourceUpdate_Mask);
                            need_edit_update_ = true;
                            // store action history
                            std::ostringstream oss;
                            oss << edit_source_->name() << ": " << MaskShader::mask_names[maskmode];
                            Action::manager().store(oss.str());
                            // select source depending on mode and tool
                            if ((maskmode == MaskShader::SHAPE && mask_cursor_shape_) ||
                                (maskmode == MaskShader::PAINT && mask_cursor_paint_))
                                Mixer::manager().unsetCurrentSource();
                            else
                                Mixer::manager().setCurrentSource(edit_source_);
                        }
                    }
                }
                ImGui::PopFont();
                ImGui::EndPopup();
            }

            // GUI for selecting source mask
            if (maskmode == MaskShader::SOURCE) {

                ImGui::SameLine(0, 60);
                bool on = true;
                if (ImGuiToolkit::ButtonToggle(ICON_FA_MOUSE_POINTER, &on, "Edit texture"))
                    Mixer::manager().setCurrentSource(edit_source_);

                // List of sources
                ImGui::SameLine(0, 60);
                std::string label = "Select source";
                Source *ref_source = nullptr;
                if (edit_source_->maskSource()->connected()) {
                    ref_source = edit_source_->maskSource()->source();
                    if (ref_source != nullptr)
                        label = std::string("Source ") + ref_source->initials() + " - " + ref_source->name();
                }
                if (ImGui::BeginCombo("##SourceMask", label.c_str())) {
                    SourceList::iterator iter;
                    for (iter = Mixer::manager().session()->begin(); iter != Mixer::manager().session()->end(); ++iter)
                    {
                        label = std::string("Source ") + (*iter)->initials() + " - " + (*iter)->name();
                        if (ImGui::Selectable( label.c_str(), *iter == ref_source )) {
                            edit_source_->maskSource()->connect( *iter );
                            edit_source_->touch(Source::SourceUpdate_Mask);
                            need_edit_update_ = true;
                            // store action history
                            std::ostringstream oss;
                            oss << edit_source_->name() << ": " << MaskShader::mask_names[maskmode];
                            Action::manager().store(oss.str());
                        }
                    }
                    ImGui::EndCombo();
                }
                if (ImGui::IsItemHovered())
                    ImGuiToolkit::ToolTip("Source used as mask");
                // reset button
                if (ref_source){
                    ImGui::SameLine();
                    if (ImGui::Button(ICON_FA_BACKSPACE)){
                        edit_source_->maskSource()->disconnect();
                        edit_source_->touch(Source::SourceUpdate_Mask);
                        need_edit_update_ = true;
                    }
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Reset");
                }

            }
            // GUI for drawing mask
            else if (maskmode == MaskShader::PAINT) {

                // select cursor
                static bool on = true;
                ImGui::SameLine(0, 60);
                on = mask_cursor_paint_ == 0;
                if (ImGuiToolkit::ButtonToggle(ICON_FA_MOUSE_POINTER, &on, "Edit texture")) {
                    Mixer::manager().setCurrentSource(edit_source_);
                    mask_cursor_paint_ = 0;
                }

                ImGui::SameLine();
                on = mask_cursor_paint_ == 1;
                if (ImGuiToolkit::ButtonToggle(ICON_FA_PAINT_BRUSH, &on, "Brush")) {
                    Mixer::manager().unsetCurrentSource();
                    mask_cursor_paint_ = 1;
                }

                ImGui::SameLine();
                on = mask_cursor_paint_ == 2;
                if (ImGuiToolkit::ButtonToggle(ICON_FA_ERASER, &on, "Eraser")) {
                    Mixer::manager().unsetCurrentSource();
                    mask_cursor_paint_ = 2;
                }

                if (mask_cursor_paint_ > 0) {

                    ImGui::SameLine(0, 50);
                    if (ImGui::Button(ICON_FA_PEN_NIB ICON_FA_SORT_DOWN ))
                        ImGui::OpenPopup("brush_shape_popup");
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Shape");
                    if (ImGui::BeginPopup( "brush_shape_popup" ))
                    {
                        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
                        if (ImGui::Selectable( ICON_FA_CIRCLE "  Circle"))
                            Settings::application.brush.z = 0.f;
                        if (ImGui::Selectable( ICON_FA_SQUARE "   Square"))
                            Settings::application.brush.z = 1.f;
                        ImGui::PopFont();
                        ImGui::EndPopup();
                    }

                    ImGui::SameLine();
                    show_cursor_forced_ = false;
                    if (ImGui::Button(ICON_FA_DOT_CIRCLE ICON_FA_SORT_DOWN ))
                        ImGui::OpenPopup("brush_size_popup");
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Size");
                    if (ImGui::BeginPopup("brush_size_popup", ImGuiWindowFlags_NoMove))
                    {
                        int pixel_size_min = int(BRUSH_MIN_SIZE * edit_source_->frame()->height() );
                        int pixel_size_max = int(BRUSH_MAX_SIZE * edit_source_->frame()->height() );
                        int pixel_size = int(Settings::application.brush.x * edit_source_->frame()->height() );
                        show_cursor_forced_ = true;
                        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
                        // toggle to enable tablet input on brush size
                        if(TabletInput::instance().isEnabled() && TabletInput::instance().hasPressure()) {
                            static bool enable_tablet_input = false;
                            enable_tablet_input = Settings::application.brush_pressure_mode & TabletInput_brush_size;
                            ImGuiToolkit::ButtonIconToggle(13, 0, &enable_tablet_input, "Tablet pressure sensitive");
                            if (enable_tablet_input)
                                Settings::application.brush_pressure_mode |= TabletInput_brush_size;
                            else 
                                Settings::application.brush_pressure_mode &= ~TabletInput_brush_size;
                        }
                        // max brush size
                        ImGuiToolkit::Indication("Large  ", 16, 1);
                        if (ImGui::VSliderInt("##BrushSize", ImVec2(30,260), &pixel_size, pixel_size_min, pixel_size_max, "") ){
                            Settings::application.brush.x = CLAMP(float(pixel_size) / edit_source_->frame()->height(), BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
                        }
                        if (ImGui::IsItemHovered() || ImGui::IsItemActive() )  {
                            ImGui::BeginTooltip();
                            ImGui::Text("%d px", pixel_size);
                            ImGui::EndTooltip();
                        }
                        ImGuiToolkit::Indication("Small  ", 15, 1);
                        ImGui::PopFont();
                        ImGui::EndPopup();
                    }
                    // make sure the visual brush is up to date
                    glm::vec2 s = glm::vec2(Settings::application.brush.x);
                    mask_cursor_circle_->scale_ = glm::vec3(s * 1.16f, 1.f);
                    mask_cursor_square_->scale_ = glm::vec3(s * 1.75f, 1.f);

                    ImGui::SameLine();
                    if (ImGui::Button(ICON_FA_FEATHER_ALT ICON_FA_SORT_DOWN ))
                        ImGui::OpenPopup("brush_pressure_popup");
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Pressure");
                    if (ImGui::BeginPopup("brush_pressure_popup", ImGuiWindowFlags_NoMove))
                    {
                        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
                        // toggle to enable tablet input on brush pressure
                        if(TabletInput::instance().isEnabled() && TabletInput::instance().hasPressure()) {
                            static bool enable_tablet_input = false;
                            enable_tablet_input = Settings::application.brush_pressure_mode & TabletInput_brush_pressure;
                            ImGuiToolkit::ButtonIconToggle(13, 0, &enable_tablet_input, "Tablet pressure sensitive");
                            if (enable_tablet_input)
                                Settings::application.brush_pressure_mode |= TabletInput_brush_pressure;
                            else 
                                Settings::application.brush_pressure_mode &= ~TabletInput_brush_pressure;
                        }
                        // max brush pressure
                        ImGuiToolkit::Indication("Light  ", ICON_FA_FEATHER_ALT);
                        ImGui::VSliderFloat("##BrushPressure", ImVec2(30,260), &Settings::application.brush.y, BRUSH_MAX_PRESS, BRUSH_MIN_PRESS, "", 0.3f);
                        if (ImGui::IsItemHovered() || ImGui::IsItemActive() )  {
                            ImGui::BeginTooltip();
                            ImGui::Text("%.1f%%", Settings::application.brush.y * 100.0);
                            ImGui::EndTooltip();
                        }
                        ImGuiToolkit::Indication("Heavy  ", ICON_FA_WEIGHT_HANGING);
                        ImGui::PopFont();
                        ImGui::EndPopup();
                    }

                    // store mask if changed, reset after applied
                    if (edit_source_->maskShader()->effect > 0)
                        edit_source_->storeMask();
                    edit_source_->maskShader()->effect = 0;

                    // menu for effects
                    ImGui::SameLine(0, 60);
                    if (ImGui::Button(ICON_FA_PAINT_ROLLER ICON_FA_SORT_DOWN ))
                        ImGui::OpenPopup( "brush_menu_popup" );
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Operations");
                    if (ImGui::BeginPopup( "brush_menu_popup" ))
                    {
                        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
                        std::ostringstream oss;
                        oss << edit_source_->name() << ": " << MaskShader::mask_names[MaskShader::PAINT];
                        int e = 0;
                        if (ImGui::Selectable( ICON_FA_BACKSPACE "  Clear")) {
                            e = 1;
                            oss << " cleared";
                        }
                        if (ImGui::Selectable( ICON_FA_THEATER_MASKS "   Invert")) {
                            e = 2;
                            oss << " inverted";
                        }
                        if (ImGui::Selectable( ICON_FA_WAVE_SQUARE "  Edge")) {
                            e = 3;
                            oss << " edged";
                        }
                        if (e>0) {
                            edit_source_->maskShader()->effect = e;
                            edit_source_->maskShader()->cursor = glm::vec4(100.0, 100.0,100.f, 100.f);
                            edit_source_->touch(Source::SourceUpdate_Mask);
                            Action::manager().store(oss.str());
                        }
                        ImGui::PopFont();
                        ImGui::EndPopup();
                    }

                    static DialogToolkit::OpenFileDialog maskdialog("Select Image",
                                                                    IMAGES_FILES_TYPE,
                                                                    IMAGES_FILES_PATTERN );

                    ImGui::SameLine();
                    if (ImGui::Button(ICON_FA_FOLDER_OPEN))
                        maskdialog.open();
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Open image");
                    if (maskdialog.closed() && !maskdialog.path().empty())
                    {
                        FrameBufferImage *img = new FrameBufferImage(maskdialog.path());
                        if (edit_source_->maskbuffer_->fill( img )) {
                            // apply mask filled
                            edit_source_->storeMask();
                            // store history
                            std::ostringstream oss;
                            oss << edit_source_->name() << ": " << MaskShader::mask_names[MaskShader::PAINT];
                            oss << " filled with image";
                            Action::manager().store(oss.str());
                        }
                    }

                }
                // disabled info
                else {
                    ImGui::SameLine(0, 60);
                    ImGui::TextDisabled( "Paint mask" );
                }

            }
            // GUI for shape masks
            else if (maskmode == MaskShader::SHAPE) {

                // select cursor
                static bool on = true;
                ImGui::SameLine(0, 60);
                on = mask_cursor_shape_ == 0;
                if (ImGuiToolkit::ButtonToggle(ICON_FA_MOUSE_POINTER, &on, "Edit texture")) {
                    Mixer::manager().setCurrentSource(edit_source_);
                    need_edit_update_ = true;
                    mask_cursor_shape_ = 0;
                }

                ImGui::SameLine();
                on = mask_cursor_shape_ == 1;
                if (ImGuiToolkit::ButtonToggle(ICON_FA_CROP_ALT, &on, "Edit shape")) {
                    Mixer::manager().unsetCurrentSource();
                    need_edit_update_ = true;
                    mask_cursor_shape_ = 1;
                }

                int shape = edit_source_->maskShader()->shape;
                int blur_percent = int(edit_source_->maskShader()->blur * 100.f);

                if (mask_cursor_shape_ > 0) {

                    std::ostringstream oss;
                    oss << edit_source_->name() << ": " << MaskShader::mask_names[MaskShader::SHAPE];

                    ImGui::SameLine(0, 50);
                    ImGui::SetNextItemWidth( ImGui::GetTextLineHeight() * 6.5f);
                    if ( ImGui::Combo("##MaskShape", &shape, MaskShader::mask_shapes, IM_ARRAYSIZE(MaskShader::mask_shapes) ) ) {
                        edit_source_->maskShader()->shape = shape;
                        edit_source_->touch(Source::SourceUpdate_Mask);
                        need_edit_update_ = true;
                        // store action history
                        oss << " " << MaskShader::mask_shapes[shape];
                        Action::manager().store(oss.str());
                    }
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Select shape");

                    ImGui::SameLine(0, 20);

                    char buf[128];
                    snprintf(buf, 128, "%d%%" ICON_FA_SORT_DOWN, blur_percent);
                    if (ImGui::Button(buf))
                        ImGui::OpenPopup("shape_smooth_popup");
                    if (ImGui::IsItemHovered())
                        ImGuiToolkit::ToolTip("Blur");
                    if (ImGui::BeginPopup("shape_smooth_popup", ImGuiWindowFlags_NoMove))
                    {
                        static bool smoothchanged = false;
                        ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
                        ImGuiToolkit::Indication("Blurry ", 7, 16);
                        if (ImGui::VSliderInt("##shapeblur", ImVec2(30,260), &blur_percent, 0, 100, "") ){
                            edit_source_->maskShader()->blur = float(blur_percent) / 100.f;
                            edit_source_->touch(Source::SourceUpdate_Mask);
                            need_edit_update_ = true;
                            smoothchanged = true;
                        }
                        else if (smoothchanged && ImGui::IsMouseReleased(ImGuiMouseButton_Left)){
                            // store action history
                            oss << " Blur " << blur_percent << "%";
                            Action::manager().store(oss.str());
                            smoothchanged = false;
                        }
                        if (ImGui::IsItemHovered() || ImGui::IsItemActive() )  {
                            ImGui::BeginTooltip();
                            ImGui::Text("%.d%% blur", blur_percent);
                            ImGui::EndTooltip();
                        }
                        ImGuiToolkit::Indication("Sharp ", 8, 16);
                        ImGui::PopFont();
                        ImGui::EndPopup();
                    }

                }
                // disabled info
                else {
                    ImGui::SameLine(0, 60);
                    ImGui::TextDisabled( "%s", MaskShader::mask_shapes[shape] );
                    ImGui::SameLine();
                    ImGui::TextDisabled( "mask");
                }
            }
            else {// maskmode == MaskShader::NONE
                // always active mouse pointer
                ImGui::SameLine(0, 60);
                bool on = true;
                if (ImGuiToolkit::ButtonToggle(ICON_FA_MOUSE_POINTER, &on, "Edit texture"))
                    Mixer::manager().setCurrentSource(edit_source_);
                ImGui::SameLine(0, 60);
                ImGui::TextDisabled( "No mask" );
            }

            ImGui::PopStyleColor(8);  // colors
            ImGui::End();
        }
        ImGui::PopFont();

    }

    // display popup menu
    if (show_context_menu_ == MENU_SOURCE) {
        ImGui::OpenPopup( "AppearanceSourceContextMenu" );
        show_context_menu_ = MENU_NONE;
    }
    if (ImGui::BeginPopup("AppearanceSourceContextMenu", ImGuiWindowFlags_NoMove)) {
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(COLOR_APPEARANCE_SOURCE, 1.f));
        ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(COLOR_MENU_HOVERED, 0.8f));
        Source *s = Mixer::manager().currentSource();
        if (s != nullptr) {

            if (s->textureMirrored()) {
                if (ImGui::Selectable( ICON_FA_TH_LARGE "  Repeat " )){
                    s->setTextureMirrored(false);
                    Action::manager().store(s->name() + std::string(": Texture Repeat "));
                }
            } else {
                if (ImGui::Selectable( ICON_FA_TH_LARGE "  Mirror " )){
                    s->setTextureMirrored(true);
                    Action::manager().store(s->name() + std::string(": Texture Mirror "));
                }
            }
            ImGui::Separator();

            if (ImGui::Selectable( ICON_FA_VECTOR_SQUARE "  Reset" )){
                s->group(mode_)->scale_ = glm::vec3(1.f);
                s->group(mode_)->rotation_.z = 0;
                s->group(mode_)->translation_ = glm::vec3(0.f);
                s->touch();
                Action::manager().store(s->name() + std::string(": Texture Reset "));
            }
            if (ImGui::Selectable( ICON_FA_CROSSHAIRS "  Reset position" )){
                s->group(mode_)->translation_ = glm::vec3(0.f);
                s->touch();
                Action::manager().store(s->name() + std::string(": Texture Reset position "));
            }
            if (ImGui::Selectable( ICON_FA_CIRCLE_NOTCH "  Reset rotation" )){
                s->group(mode_)->rotation_.z = 0;
                s->touch();
                Action::manager().store(s->name() + std::string(": Texture Reset rotation "));
            }
            if (ImGui::Selectable( ICON_FA_EXPAND_ALT "  Reset aspect ratio" )){
                s->group(mode_)->scale_.x = s->group(mode_)->scale_.y;
                s->group(mode_)->scale_.x *= (s->group(mode_)->crop_[1] - s->group(mode_)->crop_[0]) /
                                             (s->group(mode_)->crop_[2] - s->group(mode_)->crop_[3]);
                s->touch();
                Action::manager().store(s->name() + std::string(": Texture Reset aspect ratio "));
            }
        }
        ImGui::PopStyleColor(2);
        ImGui::EndPopup();
    }

}



View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
{
    std::ostringstream info;
    View::Cursor ret = Cursor();

    // grab coordinates in scene-View reference frame
    glm::vec3 scene_from = Rendering::manager().unProject(from, scene.root()->transform_);
    glm::vec3 scene_to   = Rendering::manager().unProject(to, scene.root()->transform_);

    // Not grabbing a source
    if (!s) {
        // work on the edited source
        if ( edit_source_ != nullptr ) {
            info << edit_source_->name() << ": ";

            // set brush coordinates (used in mouse over)
            scene_brush_pos = scene_to;

            // no previous brush position : restart at same coordinates
            if ( glm::length( previous_scene_brush_pos ) < EPSILON) 
                previous_scene_brush_pos = scene_brush_pos;

            if ( pick.first == mask_cursor_circle_ ) {
                // snap prush coordinates if grid is active
                if (grid->active())
                    scene_brush_pos = grid->snap(scene_brush_pos);
                // inform shader of a cursor action : coordinates and crop scaling
                edit_source_->maskShader()->size = edit_source_->mixingsurface_->scale_;

                // Apply tablet pressure to brush if available
                if (TabletInput::instance().hasPressure() && TabletInput::instance().isPressed()) {
                    float tablet_pressure = TabletInput::instance().getPressure();
                    // apply pressure depending on mode    
                    // scale size of brush
                    if (Settings::application.brush_pressure_mode & TabletInput_brush_size)
                        edit_source_->maskShader()->brush.x = CLAMP( Settings::application.brush.x * tablet_pressure, BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
                    // transparency pressure
                    if (Settings::application.brush_pressure_mode & TabletInput_brush_pressure)
                        edit_source_->maskShader()->brush.y = CLAMP( Settings::application.brush.x * tablet_pressure, BRUSH_MIN_PRESS, BRUSH_MAX_PRESS);
                }

                // inform shader of a cursor action : coordinates and crop scaling
                edit_source_->maskShader()->cursor = glm::vec4(scene_brush_pos.x - shift_crop_.x,
                                                               scene_brush_pos.y - shift_crop_.y,
                                                               previous_scene_brush_pos.x - shift_crop_.x,
                                                               previous_scene_brush_pos.y - shift_crop_.y);
                edit_source_->touch(Source::SourceUpdate_Mask);

                previous_scene_brush_pos = scene_brush_pos;

                // action label
                info << MaskShader::mask_names[MaskShader::PAINT] << " changed";
                // cursor indication - no info, just cursor
                ret.type = Cursor_Hand;
                // store action in history
                current_action_ = info.str();
            }
            else if ( pick.first == mask_cursor_crop_ ) {

                // special case for horizontal and vertical Shapes
                bool hv = edit_source_->maskShader()->shape > MaskShader::RECTANGLE;
                // match edit source AR
                glm::vec3 val = edit_source_->mixingsurface_->scale_;
                // use cursor translation to scale by quadrant
                glm::vec3 scene_translation = scene_to - scene_from;
                val = glm::sign( hv ? glm::vec3(1.f) : scene_from) * glm::vec3(scene_translation / val);
                // relative change of stored mask size
                val += stored_mask_size_;
                // snap to grid following its aspect ratio
                if (grid->active()) {
                    val.x *= grid->aspectRatio();
                    val = grid->snap(val);
                    val.x *= 1.f / grid->aspectRatio();
                }
                // Clamp | val | < 2.0
                val = glm::sign(val) * glm::min( glm::abs(val), glm::vec3(2.f));
                // clamp values for correct effect
                if (edit_source_->maskShader()->shape == MaskShader::HORIZONTAL)
                    edit_source_->maskShader()->size.y = val.y;
                else if (edit_source_->maskShader()->shape == MaskShader::VERTICAL)
                    edit_source_->maskShader()->size.x = val.x;
                else
                    edit_source_->maskShader()->size = glm::max(glm::abs(glm::vec2(val)), glm::vec2(0.2));
                // update
                edit_source_->touch(Source::SourceUpdate_Mask);
                need_edit_update_ = true;
                // action label
                info << MaskShader::mask_names[MaskShader::SHAPE] << " "
                     << std::fixed << std::setprecision(3) << edit_source_->maskShader()->size.x;
                info << " x "  << edit_source_->maskShader()->size.y;
                // cursor indication - no info, just cursor
                ret.type = Cursor_Hand;
                // store action in history
                current_action_ = info.str();
            }

        }
        return ret;
    }

    // do not operate on locked source
    if (s->locked())
        return ret;

    Group *sourceNode = s->group(mode_);

    // make sure matrix transform of stored status is updated
    s->stored_status_->update(0);

    // grab coordinates in source-root reference frame
    const glm::mat4 scene_to_source_transform = glm::inverse(s->stored_status_->transform_);
    const glm::mat4 source_to_scene_transform = s->stored_status_->transform_;

    // which manipulation to perform?
    if (pick.first)  {
        // which corner was picked ?
        glm::vec2 corner = glm::round(pick.second);

        // transform from source center to corner
        glm::mat4 source_to_corner_transform = GlmToolkit::transform(glm::vec3(corner, 0.f), glm::vec3(0.f, 0.f, 0.f),
                                            glm::vec3(1.f / s->frame()->aspectRatio(), 1.f, 1.f));

        // transformation from scene to corner:
        glm::mat4 scene_to_corner_transform = source_to_corner_transform * scene_to_source_transform;
        glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform);

        // picking on the resizing handles in the corners
        if ( pick.first == s->handles_[mode_][Handles::RESIZE] ) {
            // hide all other grips
            s->handles_[mode_][Handles::SCALE]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_H]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_V]->visible_ = false;
            s->handles_[mode_][Handles::ROTATE]->visible_ = false;
            s->handles_[mode_][Handles::MENU]->visible_ = false;
            // inform on which corner should be overlayed (opposite)
            s->handles_[mode_][Handles::RESIZE]->overlayActiveCorner(-corner);

            //
            // Manipulate the handle in the SCENE coordinates to apply grid snap
            //
            glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f );
            // move the corner we hold by the mouse translation (in scene reference frame)
            handle = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * handle;
            // snap handle coordinates to grid (if active)
            if ( grid->active() )
                handle = grid->snap(handle);
            // Compute coordinates coordinates back in CORNER reference frame
            handle = scene_to_corner_transform * handle;
            // The scaling factor is computed by dividing new handle coordinates with the ones before transform
            glm::vec2 corner_scaling = glm::vec2(handle) / glm::vec2(corner * 2.f);

            // proportional SCALING with SHIFT
            if (UserInterface::manager().shiftModifier())
                corner_scaling = glm::vec2(glm::compMax(corner_scaling));

            // Apply scaling to the source
            sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f);

            //
            // Adjust translation
            //
            // The center of the source in CORNER reference frame
            glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f);
            // scale center of source in CORNER reference frame
            corner_center = glm::scale(glm::identity<glm::mat4>(), glm::vec3(corner_scaling, 1.f)) * corner_center;
            // convert center back into scene reference frame
            corner_center = corner_to_scene_transform * corner_center;
            // Apply scaling to the source
            sourceNode->translation_ = glm::vec3(corner_center);

            // show cursor depending on diagonal (corner picked)
            glm::mat4 T = glm::rotate(glm::identity<glm::mat4>(), s->stored_status_->rotation_.z, glm::vec3(0.f, 0.f, 1.f));
            T = glm::scale(T, s->stored_status_->scale_);
            corner = T * glm::vec4( corner, 0.f, 0.f );
            ret.type = corner.x * corner.y > 0.f ? Cursor_ResizeNESW : Cursor_ResizeNWSE;
            info << "Texture Scale " << std::fixed << std::setprecision(3) << sourceNode->scale_.x;
            info << " x "  << sourceNode->scale_.y;
        }
        // picking on the BORDER RESIZING handles left or right
        else if ( pick.first == s->handles_[mode_][Handles::RESIZE_H] ) {

            // hide all other grips
            s->handles_[mode_][Handles::RESIZE]->visible_ = false;
            s->handles_[mode_][Handles::SCALE]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_V]->visible_ = false;
            s->handles_[mode_][Handles::ROTATE]->visible_ = false;
            s->handles_[mode_][Handles::MENU]->visible_ = false;

            // inform on which corner should be overlayed (opposite)
            s->handles_[mode_][Handles::RESIZE_H]->overlayActiveCorner(-corner);

            //
            // Manipulate the handle in the SCENE coordinates to apply grid snap
            //
            glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f );
            // move the corner we hold by the mouse translation (in scene reference frame)
            handle = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * handle;
            // snap handle coordinates to grid (if active)
            if ( grid->active() )
                handle = grid->snap(handle);
            // Compute coordinates coordinates back in CORNER reference frame
            handle = scene_to_corner_transform * handle;
            // The scaling factor is computed by dividing new handle coordinates with the ones before transform
            glm::vec2 corner_scaling =  glm::vec2(handle.x, 1.f) / glm::vec2(corner.x * 2.f, 1.f);

            // Apply scaling to the source
            sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f);

            // SHIFT: restore previous aspect ratio
            if (UserInterface::manager().shiftModifier()) {
                float ar = s->stored_status_->scale_.y / s->stored_status_->scale_.x;
                sourceNode->scale_.y = ar * sourceNode->scale_.x;
            }

            //
            // Adjust translation
            //
            // The center of the source in CORNER reference frame
            glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f);
            // scale center of source in CORNER reference frame
            corner_center = glm::scale(glm::identity<glm::mat4>(), glm::vec3(corner_scaling, 1.f)) * corner_center;
            // convert center back into scene reference frame
            corner_center = corner_to_scene_transform * corner_center;
            // Apply scaling to the source
            sourceNode->translation_ = glm::vec3(corner_center);

            // show cursor depending on angle
            float c = tan(sourceNode->rotation_.z);
            ret.type = ABS(c) > 1.f ? Cursor_ResizeNS : Cursor_ResizeEW;
            info << "Texture Scale " << std::fixed << std::setprecision(3) << sourceNode->scale_.x;
            info << " x "  << sourceNode->scale_.y;
        }
        // picking on the BORDER RESIZING handles top or bottom
        else if ( pick.first == s->handles_[mode_][Handles::RESIZE_V] ) {

            // hide all other grips
            s->handles_[mode_][Handles::RESIZE]->visible_ = false;
            s->handles_[mode_][Handles::SCALE]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_H]->visible_ = false;
            s->handles_[mode_][Handles::ROTATE]->visible_ = false;
            s->handles_[mode_][Handles::MENU]->visible_ = false;

            // inform on which corner should be overlayed (opposite)
            s->handles_[mode_][Handles::RESIZE_V]->overlayActiveCorner(-corner);

            //
            // Manipulate the handle in the SCENE coordinates to apply grid snap
            //
            glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f );
            // move the corner we hold by the mouse translation (in scene reference frame)
            handle = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * handle;
            // snap handle coordinates to grid (if active)
            if ( grid->active() )
                handle = grid->snap(handle);
            // Compute coordinates coordinates back in CORNER reference frame
            handle = scene_to_corner_transform * handle;
            // The scaling factor is computed by dividing new handle coordinates with the ones before transform
            glm::vec2 corner_scaling =  glm::vec2(1.f, handle.y) / glm::vec2(1.f, corner.y * 2.f);

            // Apply scaling to the source
            sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(corner_scaling, 1.f);

            // SHIFT: restore previous aspect ratio
            if (UserInterface::manager().shiftModifier()) {
                float ar = s->stored_status_->scale_.x / s->stored_status_->scale_.y;
                sourceNode->scale_.x = ar * sourceNode->scale_.y;
            }

            //
            // Adjust translation
            //
            // The center of the source in CORNER reference frame
            glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f);
            // scale center of source in CORNER reference frame
            corner_center = glm::scale(glm::identity<glm::mat4>(), glm::vec3(corner_scaling, 1.f)) * corner_center;
            // convert center back into scene reference frame
            corner_center = corner_to_scene_transform * corner_center;
            // Apply scaling to the source
            sourceNode->translation_ = glm::vec3(corner_center);

            // show cursor depending on angle
            float c = tan(sourceNode->rotation_.z);
            ret.type = ABS(c) > 1.f ? Cursor_ResizeEW : Cursor_ResizeNS;
            info << "Texture Scale " << std::fixed << std::setprecision(3) << sourceNode->scale_.x;
            info << " x "  << sourceNode->scale_.y;
        }
        // picking on the CENTRER SCALING handle
        else if ( pick.first == s->handles_[mode_][Handles::SCALE] ) {
            // hide all other grips
            s->handles_[mode_][Handles::RESIZE]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_H]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_V]->visible_ = false;
            s->handles_[mode_][Handles::ROTATE]->visible_ = false;
            s->handles_[mode_][Handles::MENU]->visible_ = false;

            // prepare overlay
            overlay_scaling_cross_->visible_ = false;
            overlay_scaling_grid_->visible_ = false;
            overlay_scaling_->visible_ = true;
            overlay_scaling_->translation_.x = s->stored_status_->translation_.x;
            overlay_scaling_->translation_.y = s->stored_status_->translation_.y;
            overlay_scaling_->rotation_.z = s->stored_status_->rotation_.z;
            overlay_scaling_->update(0);

            // Transform scene to reference frame at the center, but scaled to match aspect ratio
            const glm::mat4 center_scale = glm::scale(glm::identity<glm::mat4>(),
                                                      glm::vec3(1.f / s->frame()->aspectRatio(), 1.f, 1.f));

            glm::mat4 scene_to_center_transform = center_scale * scene_to_source_transform;
            glm::mat4 center_to_scene_transform = glm::inverse(scene_to_center_transform);

            //
            // Manipulate the scaling handle in the SCENE coordinates to apply grid snap
            //
            glm::vec3 handle = glm::vec3( glm::round(pick.second), 0.f);
            // Compute handle coordinates into SCENE reference frame
            handle = center_to_scene_transform * glm::vec4( handle, 1.f );
            // move the handle we hold by the mouse translation (in scene reference frame)
            handle = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * glm::vec4( handle, 1.f );
            // snap handle coordinates to grid (if active)
            if ( grid->active() )
                handle = grid->snap(handle);
            // Compute handle coordinates back in SOURCE CENTER reference frame
            handle = scene_to_center_transform * glm::vec4( handle,  1.f );
            // The scaling factor is computed by dividing new handle coordinates with the ones before transform
            glm::vec2 handle_scaling = glm::vec2(handle) / glm::round(pick.second);

            // proportional SCALING with SHIFT
            if (UserInterface::manager().shiftModifier()) {
                handle_scaling = glm::vec2(glm::compMax(handle_scaling));
                overlay_scaling_cross_->visible_ = true;
                overlay_scaling_cross_->copyTransform(overlay_scaling_);
            }
            // Apply scaling to the source
            sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(handle_scaling, 1.f);

            // show cursor depending on diagonal
            corner = glm::sign(sourceNode->scale_);
            ret.type = (corner.x * corner.y) > 0.f ? Cursor_ResizeNWSE : Cursor_ResizeNESW;
            info << "Texture Scale " << std::fixed << std::setprecision(3) << sourceNode->scale_.x;
            info << " x "  << sourceNode->scale_.y;
        }
        // picking on the rotating handle
        else if ( pick.first == s->handles_[mode_][Handles::ROTATE] ) {

            // hide all other grips
            s->handles_[mode_][Handles::RESIZE]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_H]->visible_ = false;
            s->handles_[mode_][Handles::RESIZE_V]->visible_ = false;
            s->handles_[mode_][Handles::SCALE]->visible_ = false;
            s->handles_[mode_][Handles::MENU]->visible_ = false;
            // ROTATION on CENTER
            overlay_rotation_->visible_ = true;
            overlay_rotation_->translation_.x = s->stored_status_->translation_.x;
            overlay_rotation_->translation_.y = s->stored_status_->translation_.y;
            overlay_rotation_->update(0);
            overlay_rotation_fix_->visible_ = false;
            overlay_rotation_fix_->copyTransform(overlay_rotation_);
            overlay_rotation_clock_->visible_ = false;
            overlay_rotation_clock_hand_->visible_ = true;
            overlay_rotation_clock_hand_->translation_.x = s->stored_status_->translation_.x;
            overlay_rotation_clock_hand_->translation_.y = s->stored_status_->translation_.y;

            // prepare variables
            const float diagonal = glm::length( glm::vec2(s->frame()->aspectRatio() * s->stored_status_->scale_.x, s->stored_status_->scale_.y));
            glm::vec2 handle_polar = glm::vec2( diagonal, 0.f);

            // rotation center to center of source (disregarding scale)
            glm::mat4 M = glm::translate(glm::identity<glm::mat4>(), s->stored_status_->translation_);
            glm::vec3 source_from = glm::inverse(M) * glm::vec4( scene_from,  1.f );
            glm::vec3 source_to   = glm::inverse(M) * glm::vec4( scene_to,  1.f );

            // compute rotation angle on Z axis
            float angle = glm::orientedAngle( glm::normalize(glm::vec2(source_from)), glm::normalize(glm::vec2(source_to)));
            handle_polar.y = s->stored_status_->rotation_.z + angle;
            info << "Texture Angle " << std::fixed << std::setprecision(1) << glm::degrees(sourceNode->rotation_.z) << UNICODE_DEGREE;

            // compute scaling of diagonal to reach new coordinates
            handle_polar.x *= glm::length( glm::vec2(source_to) ) / glm::length( glm::vec2(source_from) );

            // snap polar coordiantes (diagonal lenght, angle)
            if ( grid->active() ) {
                handle_polar = glm::round( handle_polar / grid->step() ) * grid->step();
                // prevent null size
                handle_polar.x = glm::max( grid->step().x,  handle_polar.x );
            }

            // Cancel scaling diagonal with SHIFT
            if (UserInterface::manager().shiftModifier()) {
                handle_polar.x = diagonal;
                overlay_rotation_fix_->visible_ = true;
            } else {
                info << std::endl << "   Size " << std::fixed << std::setprecision(3) << sourceNode->scale_.x;
                info << " x "  << sourceNode->scale_.y ;
            }

            // apply after snap
            sourceNode->rotation_ = glm::vec3(0.f, 0.f, handle_polar.y);
            handle_polar.x /= diagonal ;
            sourceNode->scale_ = s->stored_status_->scale_ * glm::vec3(handle_polar.x, handle_polar.x, 1.f);

            // update overlay
            overlay_rotation_clock_hand_->rotation_.z = sourceNode->rotation_.z;
            overlay_rotation_clock_hand_->update(0);

            // show cursor for rotation
            ret.type = Cursor_Hand;
        }
        // picking anywhere but on a handle: user wants to move the source
        else {
            // Default is to grab the center (0,0) of the source
            glm::vec3 handle(0.f);
            glm::vec3 offset(0.f);

            // Snap corner with SHIFT
            if (UserInterface::manager().shiftModifier()) {
                // get corner closest representative of the quadrant of the picking point
                handle = glm::vec3( glm::sign(pick.second), 0.f);
                // remember the offset for adjustment of translation to this corner
                offset = source_to_scene_transform * glm::vec4(handle, 0.f);
            }

            // Compute target coordinates of manipulated handle into SCENE reference frame
            glm::vec3 source_target = source_to_scene_transform * glm::vec4(handle, 1.f);
            source_target.z = 0.f;

            // apply translation of target in SCENE
            source_target = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * glm::vec4(source_target, 1.f);

            // snap coordinates to grid (if active)
            if ( grid->active() )
                source_target = grid->snap(source_target);

            // Apply translation to the source
            sourceNode->translation_ = source_target - offset;

            // Show center overlay for POSITION
            overlay_position_->visible_ = true;
            overlay_position_->translation_.x = sourceNode->translation_.x;
            overlay_position_->translation_.y = sourceNode->translation_.y;
            overlay_position_->update(0);

            // Show move cursor
            ret.type = Cursor_ResizeAll;
            info << "Texture Shift " << std::fixed << std::setprecision(3) << sourceNode->translation_.x;
            info << ", "  << sourceNode->translation_.y ;
        }
    }

    // request update
    s->touch();

    // store action in history
    current_action_ = s->name() + ": " + info.str();

    // update cursor
    ret.info = info.str();
    return ret;
}


void TextureView::initiate()
{
    // View default initiation of action
    View::initiate();

    if ( edit_source_ != nullptr ) {
        stored_mask_size_ = glm::vec3(edit_source_->maskShader()->size, 0.0);

        // apply mask settings
        edit_source_->maskShader()->brush = Settings::application.brush;
    }
    else
        stored_mask_size_ = glm::vec3(0.f);
}

void TextureView::terminate(bool force)
{
    // special case for texture paint: store image on mouse release (end of action PAINT)
    if ( edit_source_ != nullptr && current_action_.find(MaskShader::mask_names[MaskShader::PAINT]) != std::string::npos ) {
        edit_source_->storeMask();
        edit_source_->maskShader()->cursor = glm::vec4(100.0, 100.0, 100.f, 100.f);
        edit_source_->maskShader()->size = glm::vec2(stored_mask_size_);
    }

    // View default termination of action
    View::terminate(force);

    // hide all overlays
    overlay_position_->visible_       = false;
    overlay_position_cross_->visible_ = false;
    overlay_scaling_grid_->visible_   = false;
    overlay_scaling_cross_->visible_  = false;
    overlay_scaling_->visible_        = false;
    overlay_rotation_clock_->visible_ = false;
    overlay_rotation_clock_hand_->visible_ = false;
    overlay_rotation_fix_->visible_   = false;
    overlay_rotation_->visible_       = false;

    // cancel of all handles overlays
    const glm::vec2 c(0.f, 0.f);
    for (auto sit = Mixer::manager().session()->begin();
         sit != Mixer::manager().session()->end(); sit++){

        (*sit)->handles_[mode_][Handles::RESIZE]->overlayActiveCorner(c);
        (*sit)->handles_[mode_][Handles::RESIZE_H]->overlayActiveCorner(c);
        (*sit)->handles_[mode_][Handles::RESIZE_V]->overlayActiveCorner(c);
        (*sit)->handles_[mode_][Handles::RESIZE]->visible_ = true;
        (*sit)->handles_[mode_][Handles::RESIZE_H]->visible_ = true;
        (*sit)->handles_[mode_][Handles::RESIZE_V]->visible_ = true;
        (*sit)->handles_[mode_][Handles::SCALE]->visible_ = true;
        (*sit)->handles_[mode_][Handles::ROTATE]->visible_ = true;
        (*sit)->handles_[mode_][Handles::MENU]->visible_ = true;
    }

    // reset grid
    adaptGridToSource();
}

#define MAX_DURATION 1000.f
#define MIN_SPEED_A 0.005f
#define MAX_SPEED_A 0.5f

void TextureView::arrow (glm::vec2 movement)
{
    static float _duration = 0.f;
    static glm::vec2 _from(0.f);
    static glm::vec2 _displacement(0.f);

    Source *current = Mixer::manager().currentSource();

    if (!current && !Mixer::selection().empty())
        Mixer::manager().setCurrentSource( Mixer::selection().back() );

    if (current) {

        if (current_action_ongoing_) {

            // add movement to displacement
            _duration += dt_;
            const float speed = MIN_SPEED_A + (MAX_SPEED_A - MIN_SPEED_A) * glm::min(1.f,_duration / MAX_DURATION);
            _displacement += movement * dt_ * speed;

            // set coordinates of target
            glm::vec2 _to  = _from + _displacement;

            // update mouse pointer action
            MousePointer::manager().active()->update(_to, dt_ / 1000.f);

            // simulate mouse grab
            grab(current, _from, MousePointer::manager().active()->target(),
                 std::make_pair(current->group(mode_), glm::vec2(0.f) ) );

            // draw mouse pointer effect
            MousePointer::manager().active()->draw();
        }
        else {

            if (UserInterface::manager().altModifier() || Settings::application.mouse_pointer_lock)
                MousePointer::manager().setActiveMode( (Pointer::Mode) Settings::application.mouse_pointer );
            else
                MousePointer::manager().setActiveMode( Pointer::POINTER_DEFAULT );

            // initiate view action and store status of source
            initiate();

            // get coordinates of source and set this as start of mouse position
            _from = glm::vec2( Rendering::manager().project(current->group(mode_)->translation_, scene.root()->transform_) );
            _displacement = glm::vec2(0.f);

            // Initiate mouse pointer action
            MousePointer::manager().active()->initiate(_from);
        }

    }
    else {

        terminate(true);

        _from = glm::vec2(0.f);
        _displacement = glm::vec2(0.f);

    }

//    Source *s = Mixer::manager().currentSource();
//    if (s) {
//        static float accumulator = 0.f;
//        accumulator += dt_;

//        glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
//        glm::vec3 gl_Position_to   = Rendering::manager().unProject(movement, scene.root()->transform_);
//        glm::vec3 gl_delta = gl_Position_to - gl_Position_from;

//        Group *sourceNode = s->group(mode_);
//        glm::vec3 alt_move_ = sourceNode->translation_;
//        if (UserInterface::manager().altModifier()) {
//            if (accumulator > 100.f)
//            {
//                // precise movement with SHIFT
//                if ( UserInterface::manager().shiftModifier() ) {
//                    alt_move_ += glm::sign(gl_delta) * 0.0011f;
//                    sourceNode->translation_.x = ROUND(alt_move_.x, 1000.f);
//                    sourceNode->translation_.y = ROUND(alt_move_.y, 1000.f);
//                }
//                else {
//                    alt_move_ += glm::sign(gl_delta) * 0.11f;
//                    sourceNode->translation_.x = ROUND(alt_move_.x, 10.f);
//                    sourceNode->translation_.y = ROUND(alt_move_.y, 10.f);
//                }
//                accumulator = 0.f;
//            }
//        }
//        else {
//            sourceNode->translation_ += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
//            accumulator = 0.f;
//            alt_move_ = sourceNode->translation_;
//        }

//        // store action in history
//        std::ostringstream info;
//        info << "Texture Shift " << std::fixed << std::setprecision(3) << sourceNode->translation_.x;
//        info << ", "  << sourceNode->translation_.y ;
//        current_action_ = s->name() + ": " + info.str();

//        // request update
//        s->touch();
//    }
//    else if (edit_source_) {
//        if (edit_source_->maskShader()->mode == MaskShader::PAINT) {
//            if (mask_cursor_paint_ > 0) {
//                glm::vec2 b = 0.02f * movement;
//                Settings::application.brush.x = CLAMP(Settings::application.brush.x+b.x, BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
//                Settings::application.brush.y = CLAMP(Settings::application.brush.y+b.y, BRUSH_MIN_PRESS, BRUSH_MAX_PRESS);
//            }
//        }
//        else if (edit_source_->maskShader()->mode == MaskShader::SHAPE) {
//            if (mask_cursor_shape_ > 0) {
//                float b = -0.02f * movement.y;
//                edit_source_->maskShader()->blur = CLAMP(edit_source_->maskShader()->blur+b, SHAPE_MIN_BLUR, SHAPE_MAX_BLUR);
//                edit_source_->touch(Source::SourceUpdate_Mask);
//            }
//        }
//    }
}
