// Canvas widget - view over infinite virtual space.
//
// Canvas allows you to draw your widgets anywhere over infinite space and provide
// view over it with support for panning and scaling.
//
// When you enter a canvas ImGui is moved to virtual space which mean:
//   - ImGui::GetCursorScreenPos() return (0, 0) and which correspond to top left corner
//     of the canvas on the screen (this can be changed usign CanvasView()).
//   - Mouse input is brought to canvas space, so widgets works as usual.
//   - Everything you draw with ImDrawList will be in virtual space.
//
// By default origin point is on top left corner of canvas widget. It can be
// changed with call to CanvasView() where you can specify what part of space
// should be viewed by setting viewport origin point and scale. Current state
// can be queried with CanvasViewOrigin() and CanvasViewScale().
//
// Viewport size is controlled by 'size' parameter in BeginCanvas(). You can query
// it using CanvasContentMin/Max/Size functions. They are useful if you to not specify
// canvas size in which case all free space is used.
//
// Bounds of visible region of infinite space can be queried using CanvasViewMin/Max/Size
// functions. Everything that is drawn outside of this region will be clipped
// as usual in ImGui.
//
// While drawing inside canvas you can translate position from world (usual ImGui space)
// to virtual space and back usign CanvasFromWorld()/CanvasToWorld().
//
// Canvas can be nested in each other (they are regular widgets after all). There
// is a way to transform position between current and parent canvas with
// CanvasFromParent()/CanvasToParent().
//
// Sometimes in more elaborate scenarios you want to move out canvas virtual space,
// do something and came back. You can do that with SuspendCanvas() and ResumeCanvas().
//
// Note:
//     It is not valid to call canvas API outside of BeginCanvas() / EndCanvas() scope.
//
// LICENSE
//   This software is dual-licensed to the public domain and under the following
//   license: you are granted a perpetual, irrevocable license to copy, modify,
//   publish, and distribute this file as you see fit.
//
// CREDITS
//   Written by Michal Cichon
# ifndef __IMGUI_EX_CANVAS_H__
# define __IMGUI_EX_CANVAS_H__
# pragma once

# include <imgui.h>
# include <imgui_internal.h> // ImRect, ImFloor

namespace ImGuiEx {

struct CanvasView
{
    ImVec2 Origin;
    float  Scale  = 1.0f;
    float  InvScale = 1.0f;

    CanvasView() = default;
    CanvasView(const ImVec2& origin, float scale)
        : Origin(origin)
        , Scale(scale)
        , InvScale(scale ? 1.0f / scale : 0.0f)
    {
    }

    void Set(const ImVec2& origin, float scale)
    {
        *this = CanvasView(origin, scale);
    }
};

// Canvas widget represent view over infinite plane.
//
// It acts like a child window without scroll bars with
// ability to zoom to specific part of canvas plane.
//
// Widgets are clipped according to current view exactly
// same way ImGui do. To avoid `missing widgets` artifacts first
// setup visible region with SetView() then draw content.
//
// Everything drawn with ImDrawList betwen calls to Begin()/End()
// will be drawn on canvas plane. This behavior can be suspended
// by calling Suspend() and resumed by calling Resume().
//
// Warning:
//     Please do not interleave canvas with use of channel splitter.
//     Keep channel splitter contained inside canvas or always
//     call canvas functions from same channel.
struct Canvas
{
    // Begins drawing content of canvas plane.
    //
    // When false is returned that mean canvas is not visible to the
    // user can drawing should be skipped and End() not called.
    // When true is returned drawing must be ended with call to End().
    //
    // If any size component is equal to zero or less canvas will
    // automatically expand to all available area on that axis.
    // So (0, 300) will take horizontal space and have height
    // of 300 points. (0, 0) will take all remaining space of
    // the window.
    //
    // You can query size of the canvas while it is being drawn
    // by calling Rect().
    bool Begin(const char* id, const ImVec2& size);
    bool Begin(ImGuiID id, const ImVec2& size);

    // Ends interaction with canvas plane.
    //
    // Must be called only when Begin() retuned true.
    void End();

    // Sets visible region of canvas plane.
    //
    // Origin is an offset of infinite plane origin from top left
    // corner of the canvas.
    //
    // Scale greater than 1 make canvas content be bigger, less than 1 smaller.
    void SetView(const ImVec2& origin, float scale);
    void SetView(const CanvasView& view);

    // Centers view over specific point on canvas plane.
    //
    // View will be centered on specific point by changing origin
    // but not scale.
    void CenterView(const ImVec2& canvasPoint);

    // Calculates view over specific point on canvas plane.
    CanvasView CalcCenterView(const ImVec2& canvasPoint) const;

    // Centers view over specific rectangle on canvas plane.
    //
    // Whole rectangle will fit in canvas view. This will affect both
    // origin and scale.
    void CenterView(const ImRect& canvasRect);

    // Calculates view over specific rectangle on canvas plane.
    CanvasView CalcCenterView(const ImRect& canvasRect) const;

    // Suspends canvas by returning to normal ImGui transformation space.
    // While suspended UI will not be drawn on canvas plane.
    //
    // Calls to Suspend()/Resume() are symetrical. Each call to Suspend()
    // must be matched with call to Resume().
    void Suspend();
    void Resume();

    // Transforms point from canvas plane to ImGui.
    ImVec2 FromLocal(const ImVec2& point) const;
    ImVec2 FromLocal(const ImVec2& point, const CanvasView& view) const;

    // Transforms vector from canvas plant to ImGui.
    ImVec2 FromLocalV(const ImVec2& vector) const;
    ImVec2 FromLocalV(const ImVec2& vector, const CanvasView& view) const;

    // Transforms point from ImGui to canvas plane.
    ImVec2 ToLocal(const ImVec2& point) const;
    ImVec2 ToLocal(const ImVec2& point, const CanvasView& view) const;

    // Transforms vector from ImGui to canvas plane.
    ImVec2 ToLocalV(const ImVec2& vector) const;
    ImVec2 ToLocalV(const ImVec2& vector, const CanvasView& view) const;

    // Returns widget bounds.
    //
    // Note:
    //     Rect is valid after call to Begin().
    const ImRect& Rect() const { return m_WidgetRect; }

    // Returns visible region on canvas plane (in canvas plane coordinates).
    const ImRect& ViewRect() const { return m_ViewRect; }

    // Calculates visible region for view.
    ImRect CalcViewRect(const CanvasView& view) const;

    // Returns current view.
    const CanvasView& View() const { return m_View; }

    // Returns origin of the view.
    //
    // Origin is an offset of infinite plane origin from top left
    // corner of the canvas.
    const ImVec2& ViewOrigin()  const { return m_View.Origin; }

    // Returns scale of the view.
    float ViewScale() const { return m_View.Scale; }

    // Returns true if canvas is suspended.
    //
    // See: Suspend()/Resume()
    bool IsSuspended() const { return m_SuspendCounter > 0; }

private:
# define IMGUI_EX_CANVAS_DEFERED() 0

# if IMGUI_EX_CANVAS_DEFERED()
    struct Range
    {
        int BeginVertexIndex = 0;
        int EndVertexIndex   = 0;
        int BeginComandIndex = 0;
        int EndCommandIndex  = 0;
    };
# endif

    void UpdateViewTransformPosition();

    void SaveInputState();
    void RestoreInputState();

    void SaveViewportState();
    void RestoreViewportState();

    void EnterLocalSpace();
    void LeaveLocalSpace();

    bool m_InBeginEnd = false;

    ImVec2 m_WidgetPosition;
    ImVec2 m_WidgetSize;
    ImRect m_WidgetRect;

    ImDrawList* m_DrawList = nullptr;
    int m_ExpectedChannel = 0;

# if IMGUI_EX_CANVAS_DEFERED()
    ImVector<Range> m_Ranges;
    Range* m_CurrentRange = nullptr;
# endif

    int m_DrawListCommadBufferSize = 0;
    int m_DrawListStartVertexIndex = 0;

    CanvasView  m_View;
    ImRect      m_ViewRect;

    ImVec2 m_ViewTransformPosition;

    int m_SuspendCounter = 0;

    float m_LastFringeScale = 1.0f;

    ImVec2 m_MousePosBackup;
    ImVec2 m_MousePosPrevBackup;
    ImVec2 m_MouseClickedPosBackup[IM_ARRAYSIZE(ImGuiIO::MouseClickedPos)];
    ImVec2 m_WindowCursorMaxBackup;

# if defined(IMGUI_HAS_VIEWPORT)
    ImVec2 m_ViewportPosBackup;
    ImVec2 m_ViewportSizeBackup;
# endif
};

} // namespace ImGuiEx

# endif // __IMGUI_EX_CANVAS_H__