// Aseprite UI Library
// Copyright (C) 2019-2025  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifndef UI_GRAPHICS_H_INCLUDED
#define UI_GRAPHICS_H_INCLUDED
#pragma once

#include "base/disable_copying.h"
#include "base/string.h"
#include "gfx/color.h"
#include "gfx/point.h"
#include "gfx/rect.h"
#include "gfx/size.h"
#include "os/surface.h"
#include "text/font.h"
#include "text/fwd.h"
#include "text/shaper_features.h"
#include "ui/layer.h"
#include "ui/paint.h"

#include <memory>
#include <string>

namespace gfx {
class Matrix;
class Path;
class Region;
} // namespace gfx

namespace os {
struct Sampling;
}

namespace ui {
class Display;

// Class to render a widget in the screen.
//
// The gfx::Color parameter is a color in the sRGB color space
// (e.g. used to paint theme elements on widgets). If you want to
// paint a color from other color space, use the Paint version of each
// function.
class Graphics {
public:
  Graphics(Display* display, const os::SurfaceRef& surface, int dx, int dy);
  Graphics(Display* display);
  Graphics(const os::SurfaceRef& surface);
  ~Graphics();

  int width() const;
  int height() const;

  Display* display() const { return m_display; }
  os::Surface* getInternalSurface() { return m_surface.get(); }
  os::ColorSpace* colorSpace() { return m_surface->colorSpace().get(); }

  int getInternalDeltaX() { return m_dx; }
  int getInternalDeltaY() { return m_dy; }

  int getSaveCount() const;
  gfx::Rect getClipBounds() const;
  void saveClip();
  void restoreClip();
  bool clipRect(const gfx::Rect& rc);
  void clipRegion(const gfx::Region& rgn);

  void save();
  void concat(const gfx::Matrix& matrix);
  void setMatrix(const gfx::Matrix& matrix);
  void resetMatrix();
  void restore();
  gfx::Matrix matrix() const;

  gfx::Color getPixel(int x, int y);
  void putPixel(gfx::Color color, int x, int y);

  void drawHLine(int x, int y, int w, const Paint& paint);
  void drawHLine(gfx::Color color, int x, int y, int w);
  void drawVLine(int x, int y, int h, const Paint& paint);
  void drawVLine(gfx::Color color, int x, int y, int h);
  void drawLine(gfx::Color color, const gfx::Point& a, const gfx::Point& b);
  void drawLine(const gfx::PointF& a, const gfx::PointF& b, const Paint& paint);
  void drawPath(gfx::Path& path, const Paint& paint);

  void drawRect(const gfx::Rect& rc, const Paint& paint);
  void drawRect(const gfx::RectF& rc, const Paint& paint);
  void drawRect(gfx::Color color, const gfx::Rect& rc);
  void fillRect(gfx::Color color, const gfx::Rect& rc);
  void fillRegion(gfx::Color color, const gfx::Region& rgn);
  void fillAreaBetweenRects(gfx::Color color, const gfx::Rect& outer, const gfx::Rect& inner);

  void drawSurface(os::Surface* surface, int x, int y);
  void drawSurface(os::Surface* surface,
                   const gfx::Rect& srcRect,
                   const gfx::Rect& dstRect,
                   const os::Sampling& sampling,
                   const Paint* paint);
  void drawRgbaSurface(os::Surface* surface, int x, int y);
  void drawRgbaSurface(os::Surface* surface, int srcx, int srcy, int dstx, int dsty, int w, int h);
  void drawColoredRgbaSurface(os::Surface* surface, gfx::Color color, int x, int y);
  void drawColoredRgbaSurface(os::Surface* surface,
                              gfx::Color color,
                              int srcx,
                              int srcy,
                              int dstx,
                              int dsty,
                              int w,
                              int h);
  void drawSurfaceNine(os::Surface* surface,
                       const gfx::Rect& src,
                       const gfx::Rect& center,
                       const gfx::Rect& dst,
                       const bool drawCenter,
                       const Paint* paint = nullptr);

  // ======================================================================
  // FONT & TEXT
  // ======================================================================

  const text::FontRef& font() { return m_font; }
  void setFont(const text::FontRef& font);

  [[deprecated]]
  void drawTextWithDelegate(const std::string& str,
                            gfx::Color fg,
                            gfx::Color bg,
                            const gfx::Point& pt,
                            text::DrawTextDelegate* delegate = nullptr,
                            text::ShaperFeatures features = {});

  void drawTextBlob(const text::TextBlobRef& textBlob, const gfx::PointF& pt, const Paint& paint);

  void drawText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt);
  void drawUIText(const std::string& str,
                  gfx::Color fg,
                  gfx::Color bg,
                  const gfx::Point& pt,
                  const int mnemonic);
  void drawAlignedUIText(const std::string& str,
                         gfx::Color fg,
                         gfx::Color bg,
                         const gfx::Rect& rc,
                         const int align);

  gfx::Size measureText(const std::string& str);
  gfx::Size fitString(const std::string& str, int maxWidth, int align);

  // Can be used in case that you've accessed/changed the
  // getInternalSurface() directly and need to specify which area
  // was modified.
  void invalidate(const gfx::Rect& bounds);

private:
  gfx::Size doUIStringAlgorithm(const std::string& str,
                                gfx::Color fg,
                                gfx::Color bg,
                                const gfx::Rect& rc,
                                int align,
                                bool draw);
  void dirty(const gfx::Rect& bounds);

  Display* m_display;
  os::SurfaceRef m_surface;
  int m_dx;
  int m_dy;
  gfx::Rect m_clipBounds;
  text::FontRef m_font;
  gfx::Rect m_dirtyBounds;
};

// Class to temporary set the Graphics' clip region to the full
// extend.
class SetClip {
public:
  SetClip(Graphics* g) : m_graphics(g), m_oldClip(g->getClipBounds()), m_oldCount(g->getSaveCount())
  {
    if (m_oldCount > 1)
      m_graphics->restoreClip();
  }

  ~SetClip()
  {
    if (m_oldCount > 1) {
      m_graphics->saveClip();
      m_graphics->clipRect(m_oldClip);
    }
  }

private:
  Graphics* m_graphics;
  gfx::Rect m_oldClip;
  int m_oldCount;

  DISABLE_COPYING(SetClip);
};

// Class to temporary set the Graphics' clip region to a sub-rectangle
// (in the life-time of the IntersectClip instance).
class IntersectClip {
public:
  IntersectClip(Graphics* g, const gfx::Rect& rc) : m_graphics(g)
  {
    m_graphics->saveClip();
    m_notEmpty = m_graphics->clipRect(rc);
  }

  ~IntersectClip() { m_graphics->restoreClip(); }

  operator bool() const { return m_notEmpty; }

private:
  Graphics* m_graphics;
  bool m_notEmpty;

  DISABLE_COPYING(IntersectClip);
};

typedef std::shared_ptr<Graphics> GraphicsPtr;

} // namespace ui

#endif
