///////////////////////////////////////////////////////////////////////////////
// Name:        src/univ/stdrend.cpp
// Purpose:     implementation of wxStdRenderer
// Author:      Vadim Zeitlin
// Created:     2006-09-16
// Copyright:   (c) 2006 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#ifndef WX_PRECOMP
    #include "wx/settings.h"
    #include "wx/brush.h"
    #include "wx/dc.h"
    #include "wx/statusbr.h"
    #include "wx/toplevel.h"
#endif //WX_PRECOMP

#include "wx/univ/stdrend.h"
#include "wx/univ/colschem.h"

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

static const int FRAME_TITLEBAR_HEIGHT             = 18;
static const int FRAME_BUTTON_WIDTH                = 16;
static const int FRAME_BUTTON_HEIGHT               = 14;

// the margin between listbox item text and its rectangle
static const int ITEM_MARGIN = 1;

// ============================================================================
// wxStdRenderer implementation
// ============================================================================

// ----------------------------------------------------------------------------
// ctor
// ----------------------------------------------------------------------------

wxStdRenderer::wxStdRenderer(const wxColourScheme *scheme)
             : m_scheme(scheme)
{
    m_penBlack = wxPen(wxSCHEME_COLOUR(scheme, SHADOW_DARK));
    m_penDarkGrey = wxPen(wxSCHEME_COLOUR(scheme, SHADOW_OUT));
    m_penLightGrey = wxPen(wxSCHEME_COLOUR(scheme, SHADOW_IN));
    m_penHighlight = wxPen(wxSCHEME_COLOUR(scheme, SHADOW_HIGHLIGHT));

    m_titlebarFont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
    m_titlebarFont.SetWeight(wxFONTWEIGHT_BOLD);
}

// ----------------------------------------------------------------------------
// helper functions
// ----------------------------------------------------------------------------

void
wxStdRenderer::DrawSolidRect(wxDC& dc, const wxColour& col, const wxRect& rect)
{
    dc.SetBrush(col);
    dc.SetPen(*wxTRANSPARENT_PEN);
    dc.DrawRectangle(rect);
}

void wxStdRenderer::DrawRect(wxDC& dc, wxRect *rect, const wxPen& pen)
{
    // draw
    dc.SetPen(pen);
    dc.SetBrush(*wxTRANSPARENT_BRUSH);
    dc.DrawRectangle(*rect);

    // adjust the rect
    rect->Inflate(-1);
}

void wxStdRenderer::DrawShadedRect(wxDC& dc, wxRect *rect,
                                   const wxPen& pen1, const wxPen& pen2)
{
    // draw the rectangle
    dc.SetPen(pen1);
    dc.DrawLine(rect->GetLeft(), rect->GetTop(),
                rect->GetLeft(), rect->GetBottom());
    dc.DrawLine(rect->GetLeft() + 1, rect->GetTop(),
                rect->GetRight(), rect->GetTop());
    dc.SetPen(pen2);
    dc.DrawLine(rect->GetRight(), rect->GetTop(),
                rect->GetRight(), rect->GetBottom());
    dc.DrawLine(rect->GetLeft(), rect->GetBottom(),
                rect->GetRight() + 1, rect->GetBottom());

    // adjust the rect
    rect->Inflate(-1);
}

// ----------------------------------------------------------------------------
// translate various flags into corresponding renderer constants
// ----------------------------------------------------------------------------

/* static */
void wxStdRenderer::GetIndicatorsFromFlags(int flags,
                                           IndicatorState& state,
                                           IndicatorStatus& status)
{
    if ( flags & wxCONTROL_SELECTED )
        state = flags & wxCONTROL_DISABLED ? IndicatorState_SelectedDisabled
                                           : IndicatorState_Selected;
    else if ( flags & wxCONTROL_DISABLED )
        state = IndicatorState_Disabled;
    else if ( flags & wxCONTROL_PRESSED )
        state = IndicatorState_Pressed;
    else
        state = IndicatorState_Normal;

    status = flags & wxCONTROL_CHECKED ? IndicatorStatus_Checked
                                       : flags & wxCONTROL_UNDETERMINED
                                            ? IndicatorStatus_Undetermined
                                            : IndicatorStatus_Unchecked;
}

/* static */
wxStdRenderer::ArrowDirection wxStdRenderer::GetArrowDirection(wxDirection dir)
{
    switch ( dir )
    {
        case wxLEFT:
            return Arrow_Left;

        case wxRIGHT:
            return Arrow_Right;

        case wxUP:
            return Arrow_Up;

        case wxDOWN:
            return Arrow_Down;

        default:
            wxFAIL_MSG(wxT("unknown arrow direction"));
    }

    return Arrow_Max;
}

// ----------------------------------------------------------------------------
// background
// ----------------------------------------------------------------------------

void wxStdRenderer::DrawBackground(wxDC& dc,
                                   const wxColour& col,
                                   const wxRect& rect,
                                   int WXUNUSED(flags),
                                   wxWindow *window)
{
    wxColour colBg;

    if (col.IsOk())
    {
        colBg = col;
    }
    else if (window)
    {
        colBg = m_scheme->GetBackground(window);
    }
    else
    {
        colBg = wxSCHEME_COLOUR(m_scheme, CONTROL);
    }

    DrawSolidRect(dc, colBg, rect);
}


void wxStdRenderer::DrawButtonSurface(wxDC& dc,
                                      const wxColour& col,
                                      const wxRect& rect,
                                      int flags)
{
    DrawBackground(dc, col, rect, flags);
}

// ----------------------------------------------------------------------------
// text
// ----------------------------------------------------------------------------

void
wxStdRenderer::DrawFocusRect(wxWindow* WXUNUSED(win), wxDC& dc, const wxRect& rect, int WXUNUSED(flags))
{
    // draw the pixels manually because the "dots" in wxPen with wxPENSTYLE_DOT style
    // may be short traits and not really dots
    //
    // note that to behave in the same manner as DrawRect(), we must exclude
    // the bottom and right borders from the rectangle
    wxCoord x1 = rect.GetLeft(),
            y1 = rect.GetTop(),
            x2 = rect.GetRight(),
            y2 = rect.GetBottom();

    dc.SetPen(m_penBlack);

    // this seems to be closer than what Windows does than wxINVERT although
    // I'm still not sure if it's correct
    dc.SetLogicalFunction(wxAND_REVERSE);

    wxCoord z;
    for ( z = x1 + 1; z < x2; z += 2 )
        dc.DrawPoint(z, rect.GetTop());

    wxCoord shift = z == x2 ? 0 : 1;
    for ( z = y1 + shift; z < y2; z += 2 )
        dc.DrawPoint(x2, z);

    shift = z == y2 ? 0 : 1;
    for ( z = x2 - shift; z > x1; z -= 2 )
        dc.DrawPoint(z, y2);

    shift = z == x1 ? 0 : 1;
    for ( z = y2 - shift; z > y1; z -= 2 )
        dc.DrawPoint(x1, z);

    dc.SetLogicalFunction(wxCOPY);
}

void wxStdRenderer::DrawLabel(wxDC& dc,
                              const wxString& label,
                              const wxRect& rect,
                              int flags,
                              int alignment,
                              int indexAccel,
                              wxRect *rectBounds)
{
    DrawButtonLabel(dc, label, wxNullBitmap, rect, flags,
                    alignment, indexAccel, rectBounds);
}

void wxStdRenderer::DrawButtonLabel(wxDC& dc,
                                    const wxString& label,
                                    const wxBitmap& image,
                                    const wxRect& rect,
                                    int flags,
                                    int alignment,
                                    int indexAccel,
                                    wxRect *rectBounds)
{
    wxDCTextColourChanger clrChanger(dc);

    wxRect rectLabel = rect;
    if ( !label.empty() && (flags & wxCONTROL_DISABLED) )
    {
        if ( flags & wxCONTROL_PRESSED )
        {
            // shift the label if a button is pressed
            rectLabel.Offset(1, 1);
        }

        // draw shadow of the text
        clrChanger.Set(m_penHighlight.GetColour());
        wxRect rectShadow = rect;
        rectShadow.Offset(1, 1);
        dc.DrawLabel(label, rectShadow, alignment, indexAccel);

        // make the main label text grey
        clrChanger.Set(m_penDarkGrey.GetColour());

        if ( flags & wxCONTROL_FOCUSED )
        {
            // leave enough space for the focus rect
            rectLabel.Inflate(-2);
        }
    }

    dc.DrawLabel(label, image, rectLabel, alignment, indexAccel, rectBounds);

    if ( !label.empty() && (flags & wxCONTROL_FOCUSED) )
    {
        rectLabel.Inflate(-1);

        DrawFocusRect(nullptr, dc, rectLabel);
    }
}

// ----------------------------------------------------------------------------
// borders
// ----------------------------------------------------------------------------

/*
   We implement standard-looking 3D borders which have the following appearance:

   The raised border:

   WWWWWWWWWWWWWWWWWWWWWWB
   WHHHHHHHHHHHHHHHHHHHHGB
   WH                   GB  W = white       (HILIGHT)
   WH                   GB  H = light grey  (LIGHT)
   WH                   GB  G = dark grey   (SHADOI)
   WH                   GB  B = black       (DKSHADOI)
   WH                   GB
   WH                   GB
   WGGGGGGGGGGGGGGGGGGGGGB
   BBBBBBBBBBBBBBBBBBBBBBB

   The sunken border looks like this:

   GGGGGGGGGGGGGGGGGGGGGGW
   GBBBBBBBBBBBBBBBBBBBBHW
   GB                   HW
   GB                   HW
   GB                   HW
   GB                   HW
   GB                   HW
   GB                   HW
   GHHHHHHHHHHHHHHHHHHHHHW
   WWWWWWWWWWWWWWWWWWWWWWW

   The static border (used for the controls which don't get focus) is like
   this:

   GGGGGGGGGGGGGGGGGGGGGGW
   G                     W
   G                     W
   G                     W
   G                     W
   G                     W
   G                     W
   G                     W
   WWWWWWWWWWWWWWWWWWWWWWW

   The most complicated is the double border which is a combination of special
   "anti-sunken" border and an extra border inside it:

   HHHHHHHHHHHHHHHHHHHHHHB
   HWWWWWWWWWWWWWWWWWWWWGB
   HWHHHHHHHHHHHHHHHHHHHGB
   HWH                 HGB
   HWH                 HGB
   HWH                 HGB
   HWH                 HGB
   HWHHHHHHHHHHHHHHHHHHHGB
   HGGGGGGGGGGGGGGGGGGGGGB
   BBBBBBBBBBBBBBBBBBBBBBB

   And the simple border is, well, simple:

   BBBBBBBBBBBBBBBBBBBBBBB
   B                     B
   B                     B
   B                     B
   B                     B
   B                     B
   B                     B
   B                     B
   B                     B
   BBBBBBBBBBBBBBBBBBBBBBB
*/

void wxStdRenderer::DrawRaisedBorder(wxDC& dc, wxRect *rect)
{
    DrawShadedRect(dc, rect, m_penHighlight, m_penBlack);
    DrawShadedRect(dc, rect, m_penLightGrey, m_penDarkGrey);
}

void wxStdRenderer::DrawSunkenBorder(wxDC& dc, wxRect *rect)
{
    DrawShadedRect(dc, rect, m_penDarkGrey, m_penHighlight);
    DrawShadedRect(dc, rect, m_penBlack, m_penLightGrey);
}

void wxStdRenderer::DrawAntiSunkenBorder(wxDC& dc, wxRect *rect)
{
    DrawShadedRect(dc, rect, m_penLightGrey, m_penBlack);
    DrawShadedRect(dc, rect, m_penHighlight, m_penDarkGrey);
}

void wxStdRenderer::DrawBoxBorder(wxDC& dc, wxRect *rect)
{
    DrawShadedRect(dc, rect, m_penDarkGrey, m_penHighlight);
    DrawShadedRect(dc, rect, m_penHighlight, m_penDarkGrey);
}

void wxStdRenderer::DrawStaticBorder(wxDC& dc, wxRect *rect)
{
    DrawShadedRect(dc, rect, m_penDarkGrey, m_penHighlight);
}

void wxStdRenderer::DrawExtraBorder(wxDC& dc, wxRect *rect)
{
    DrawRect(dc, rect, m_penLightGrey);
}

void wxStdRenderer::DrawBorder(wxDC& dc,
                               wxBorder border,
                               const wxRect& rectTotal,
                               int WXUNUSED(flags),
                               wxRect *rectIn)
{
    wxRect rect = rectTotal;

    switch ( border )
    {
        case wxBORDER_SUNKEN:
        case wxBORDER_THEME:
            DrawSunkenBorder(dc, &rect);
            break;

        // wxBORDER_DOUBLE and wxBORDER_THEME are currently the same value.
#if 0
        case wxBORDER_DOUBLE:
            DrawAntiSunkenBorder(dc, &rect);
            DrawExtraBorder(dc, &rect);
            break;
#endif

        case wxBORDER_STATIC:
            DrawStaticBorder(dc, &rect);
            break;

        case wxBORDER_RAISED:
            DrawRaisedBorder(dc, &rect);
            break;

        case wxBORDER_SIMPLE:
            DrawRect(dc, &rect, m_penBlack);
            break;

        default:
            wxFAIL_MSG(wxT("unknown border type"));
            // fall through

        case wxBORDER_DEFAULT:
        case wxBORDER_NONE:
            break;
    }

    if ( rectIn )
        *rectIn = rect;
}

wxRect wxStdRenderer::GetBorderDimensions(wxBorder border) const
{
    wxCoord width;
    switch ( border )
    {
        case wxBORDER_SIMPLE:
        case wxBORDER_STATIC:
            width = 1;
            break;

        case wxBORDER_RAISED:
        case wxBORDER_SUNKEN:
        case wxBORDER_THEME:
            width = 2;
            break;
#if 0
        case wxBORDER_DOUBLE:
            width = 3;
            break;
#endif
        default:
            wxFAIL_MSG(wxT("unknown border type"));
            // fall through

        case wxBORDER_DEFAULT:
        case wxBORDER_NONE:
            width = 0;
            break;
    }

    wxRect rect;
    rect.x =
    rect.y =
    rect.width =
    rect.height = width;

    return rect;
}

void wxStdRenderer::AdjustSize(wxSize *size, const wxWindow *window)
{
    // take into account the border width
    wxRect rectBorder = GetBorderDimensions(window->GetBorder());
    size->x += rectBorder.x + rectBorder.width;
    size->y += rectBorder.y + rectBorder.height;
}

bool wxStdRenderer::AreScrollbarsInsideBorder() const
{
    return false;
}

wxCoord wxStdRenderer::GetListboxItemHeight(wxCoord fontHeight)
{
    return fontHeight + 2*ITEM_MARGIN;
}

void wxStdRenderer::DrawTextBorder(wxDC& dc,
                                   wxBorder border,
                                   const wxRect& rect,
                                   int flags,
                                   wxRect *rectIn)
{
    DrawBorder(dc, border, rect, flags, rectIn);
}

// ----------------------------------------------------------------------------
// lines and boxes
// ----------------------------------------------------------------------------

void
wxStdRenderer::DrawHorizontalLine(wxDC& dc, wxCoord y, wxCoord x1, wxCoord x2)
{
    dc.SetPen(m_penDarkGrey);
    dc.DrawLine(x1, y, x2 + 1, y);

    dc.SetPen(m_penHighlight);
    y++;
    dc.DrawLine(x1, y, x2 + 1, y);
}

void
wxStdRenderer::DrawVerticalLine(wxDC& dc, wxCoord x, wxCoord y1, wxCoord y2)
{
    dc.SetPen(m_penDarkGrey);
    dc.DrawLine(x, y1, x, y2 + 1);

    dc.SetPen(m_penHighlight);
    x++;
    dc.DrawLine(x, y1, x, y2 + 1);
}

void wxStdRenderer::DrawFrameWithoutLabel(wxDC& dc,
                                          const wxRect& rectFrame,
                                          const wxRect& rectLabel)
{
    // draw left, bottom and right lines entirely
    DrawVerticalLine(dc, rectFrame.GetLeft(),
                     rectFrame.GetTop(), rectFrame.GetBottom() - 2);
    DrawHorizontalLine(dc, rectFrame.GetBottom() - 1,
                       rectFrame.GetLeft(), rectFrame.GetRight());
    DrawVerticalLine(dc, rectFrame.GetRight() - 1,
                     rectFrame.GetTop(), rectFrame.GetBottom() - 1);

    // and 2 parts of the top line
    DrawHorizontalLine(dc, rectFrame.GetTop(),
                       rectFrame.GetLeft() + 1, rectLabel.GetLeft());
    DrawHorizontalLine(dc, rectFrame.GetTop(),
                       rectLabel.GetRight(), rectFrame.GetRight() - 2);
}

void wxStdRenderer::DrawFrameWithLabel(wxDC& dc,
                                       const wxString& label,
                                       const wxRect& rectFrame,
                                       const wxRect& rectText,
                                       int flags,
                                       int alignment,
                                       int indexAccel)
{
    wxRect rectLabel;
    DrawLabel(dc, label, rectText, flags, alignment, indexAccel, &rectLabel);

    DrawFrameWithoutLabel(dc, rectFrame, rectLabel);
}

void wxStdRenderer::DrawFrame(wxDC& dc,
                              const wxString& label,
                              const wxRect& rect,
                              int flags,
                              int alignment,
                              int indexAccel)
{
    wxCoord height = 0; // of the label
    wxRect rectFrame = rect;
    if ( !label.empty() )
    {
        // the text should touch the top border of the rect, so the frame
        // itself should be lower
        dc.GetTextExtent(label, nullptr, &height);
        rectFrame.y += height / 2;
        rectFrame.height -= height / 2;

        // we have to draw each part of the frame individually as we can't
        // erase the background beyond the label as it might contain some
        // pixmap already, so drawing everything and then overwriting part of
        // the frame with label doesn't work

        // TODO: the +5 shouldn't be hard coded
        wxRect rectText;
        rectText.x = rectFrame.x + 5;
        rectText.y = rect.y;
        rectText.width = rectFrame.width - 7; // +2 border width
        rectText.height = height;

        DrawFrameWithLabel(dc, label, rectFrame, rectText, flags,
                           alignment, indexAccel);
    }
    else // no label
    {
        DrawBoxBorder(dc, &rectFrame);
    }
}

void wxStdRenderer::DrawItem(wxDC& dc,
                             const wxString& label,
                             const wxRect& rect,
                             int flags)
{
    wxDCTextColourChanger colChanger(dc);

    if ( flags & wxCONTROL_SELECTED )
    {
        colChanger.Set(wxSCHEME_COLOUR(m_scheme, HIGHLIGHT_TEXT));

        const wxColour colBg = wxSCHEME_COLOUR(m_scheme, HIGHLIGHT);
        dc.SetBrush(colBg);
        dc.SetPen(colBg);
        dc.DrawRectangle(rect);
    }

    // horizontal adjustment is arbitrary
    wxRect rectText = rect;
    rectText.Deflate(2, ITEM_MARGIN);
    dc.DrawLabel(label, wxNullBitmap, rectText);

    if ( flags & wxCONTROL_FOCUSED )
    {
        DrawFocusRect(nullptr, dc, rect, flags);
    }
}

void wxStdRenderer::DrawCheckItemBitmap(wxDC& dc,
                                        const wxBitmap& bitmap,
                                        const wxRect& rect,
                                        int flags)
{
    DrawCheckButton(dc, wxEmptyString, bitmap, rect, flags);
}

void wxStdRenderer::DrawCheckItem(wxDC& dc,
                                  const wxString& label,
                                  const wxBitmap& bitmap,
                                  const wxRect& rect,
                                  int flags)
{
    wxRect rectBitmap = rect;
    rectBitmap.width = GetCheckBitmapSize().x;
    DrawCheckItemBitmap(dc, bitmap, rectBitmap, flags);

    wxRect rectLabel = rect;
    wxCoord shift = rectBitmap.width + 2*GetCheckItemMargin();
    rectLabel.x += shift;
    rectLabel.width -= shift;
    DrawItem(dc, label, rectLabel, flags);
}

// ----------------------------------------------------------------------------
// check and radio bitmaps
// ----------------------------------------------------------------------------

void wxStdRenderer::DrawCheckButton(wxDC& dc,
                                    const wxString& label,
                                    const wxBitmap& bitmap,
                                    const wxRect& rect,
                                    int flags,
                                    wxAlignment align,
                                    int indexAccel)
{
    if (bitmap.IsOk())
        DrawCheckOrRadioButton(dc, label, bitmap, rect, flags, align, indexAccel);
    else
        DrawCheckOrRadioButton(dc, label, GetCheckBitmap(flags), rect, flags, align, indexAccel);
}

void wxStdRenderer::DrawRadioButton(wxDC& dc,
                                    const wxString& label,
                                    const wxBitmap& bitmap,
                                    const wxRect& rect,
                                    int flags,
                                    wxAlignment align,
                                    int indexAccel)
{
    if (bitmap.IsOk())
        DrawCheckOrRadioButton(dc, label, bitmap, rect, flags, align, indexAccel);
    else
        DrawCheckOrRadioButton(dc, label, GetRadioBitmap(flags), rect, flags, align, indexAccel);

}

void wxStdRenderer::DrawCheckOrRadioButton(wxDC& dc,
                                           const wxString& label,
                                           const wxBitmap& bitmap,
                                           const wxRect& rect,
                                           int flags,
                                           wxAlignment align,
                                           int indexAccel)
{
    // calculate the position of the bitmap and of the label
    wxCoord heightBmp = bitmap.GetHeight();
    wxCoord xBmp,
            yBmp = rect.y + (rect.height - heightBmp) / 2;

    wxRect rectLabel;
    dc.GetMultiLineTextExtent(label, nullptr, &rectLabel.height);
    rectLabel.y = rect.y + (rect.height - rectLabel.height) / 2;

    // align label vertically with the bitmap - looks nicer like this
    rectLabel.y -= (rectLabel.height - heightBmp) % 2;

    // calc horz position
    if ( align == wxALIGN_RIGHT )
    {
        xBmp = rect.GetRight() - bitmap.GetWidth();
        rectLabel.x = rect.x + 3;
        rectLabel.SetRight(xBmp);
    }
    else // normal (checkbox to the left of the text) case
    {
        xBmp = rect.x;
        rectLabel.x = xBmp + bitmap.GetWidth() + 5;
        rectLabel.SetRight(rect.GetRight());
    }

    dc.DrawBitmap(bitmap, xBmp, yBmp, true /* use mask */);

    DrawLabel(dc, label, rectLabel, flags,
              wxALIGN_LEFT | wxALIGN_TOP, indexAccel);
}

#if wxUSE_TEXTCTRL

void wxStdRenderer::DrawTextLine(wxDC& dc,
                                 const wxString& text,
                                 const wxRect& rect,
                                 int selStart,
                                 int selEnd,
                                 int flags)
{
    if ( (selStart == -1) || !(flags & wxCONTROL_FOCUSED) )
    {
        // just draw it as is
        dc.DrawText(text, rect.x, rect.y);
    }
    else // we have selection
    {
        wxCoord width,
                x = rect.x;

        // draw the part before selection
        wxString s(text, (size_t)selStart);
        if ( !s.empty() )
        {
            dc.DrawText(s, x, rect.y);

            dc.GetTextExtent(s, &width, nullptr);
            x += width;
        }

        // draw the selection itself
        s = wxString(text.c_str() + selStart, text.c_str() + selEnd);
        if ( !s.empty() )
        {
            wxColour colFg = dc.GetTextForeground(),
                     colBg = dc.GetTextBackground();
            dc.SetTextForeground(wxSCHEME_COLOUR(m_scheme, HIGHLIGHT_TEXT));
            dc.SetTextBackground(wxSCHEME_COLOUR(m_scheme, HIGHLIGHT));
            dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);

            dc.DrawText(s, x, rect.y);
            dc.GetTextExtent(s, &width, nullptr);
            x += width;

            dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
            dc.SetTextBackground(colBg);
            dc.SetTextForeground(colFg);
        }

        // draw the final part
        s = text.c_str() + selEnd;
        if ( !s.empty() )
        {
            dc.DrawText(s, x, rect.y);
        }
    }
}

void wxStdRenderer::DrawLineWrapMark(wxDC& WXUNUSED(dc),
                                     const wxRect& WXUNUSED(rect))
{
    // nothing by default
}

int wxStdRenderer::GetTextBorderWidth(const wxTextCtrl * WXUNUSED(text)) const
{
    return 1;
}

wxRect
wxStdRenderer::GetTextTotalArea(const wxTextCtrl *text, const wxRect& rect) const
{
    wxRect rectTotal = rect;
    rectTotal.Inflate(GetTextBorderWidth(text));
    return rectTotal;
}

wxRect wxStdRenderer::GetTextClientArea(const wxTextCtrl *text,
                                        const wxRect& rect,
                                        wxCoord *extraSpaceBeyond) const
{
    wxRect rectText = rect;
    rectText.Deflate(GetTextBorderWidth(text));

    if ( extraSpaceBeyond )
        *extraSpaceBeyond = 0;

    return rectText;
}

#endif // wxUSE_TEXTCTRL

// ----------------------------------------------------------------------------
// scrollbars drawing
// ----------------------------------------------------------------------------

void wxStdRenderer::DrawScrollbarArrow(wxDC& dc,
                                       wxDirection dir,
                                       const wxRect& rect,
                                       int flags)
{
    DrawArrow(dc, dir, rect, flags);
}

void wxStdRenderer::DrawScrollCorner(wxDC& dc, const wxRect& rect)
{
    DrawSolidRect(dc, wxSCHEME_COLOUR(m_scheme, CONTROL), rect);
}

// ----------------------------------------------------------------------------
// status bar
// ----------------------------------------------------------------------------

#if wxUSE_STATUSBAR

wxSize wxStdRenderer::GetStatusBarBorders() const
{
    // Rendered border may be different depending on field's style, we use
    // the largest value so that any field certainly fits into the borders
    // we return:
    wxRect raised = GetBorderDimensions(wxBORDER_RAISED);
    wxRect flat = GetBorderDimensions(wxBORDER_STATIC);
    wxASSERT_MSG( raised.x == raised.width && raised.y == raised.height &&
                  flat.x == flat.width && flat.y == flat.height,
                  wxT("this code expects uniform borders, you must override GetStatusBarBorders") );

    // take the larger of flat/raised values:
    wxSize border(wxMax(raised.x, flat.x), wxMax(raised.y, flat.y));

    return border;
}

wxCoord wxStdRenderer::GetStatusBarBorderBetweenFields() const
{
    return 2;
}

wxSize wxStdRenderer::GetStatusBarFieldMargins() const
{
    return wxSize(2, 2);
}

void wxStdRenderer::DrawStatusField(wxDC& dc,
                                    const wxRect& rect,
                                    const wxString& label,
                                    int flags,
                                    int style)
{
    wxRect rectIn;

    if ( style == wxSB_RAISED )
        DrawBorder(dc, wxBORDER_RAISED, rect, flags, &rectIn);
    else if ( style != wxSB_FLAT )
        DrawBorder(dc, wxBORDER_STATIC, rect, flags, &rectIn);
    else
        rectIn = rect;

    rectIn.Deflate(GetStatusBarFieldMargins());

    wxDCClipper clipper(dc, rectIn);
    DrawLabel(dc, label, rectIn, flags, wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
}

#endif // wxUSE_STATUSBAR

// ----------------------------------------------------------------------------
// top level windows
// ----------------------------------------------------------------------------

int wxStdRenderer::HitTestFrame(const wxRect& rect, const wxPoint& pt, int flags) const
{
    wxRect client = GetFrameClientArea(rect, flags);

    if ( client.Contains(pt) )
        return wxHT_TOPLEVEL_CLIENT_AREA;

    if ( flags & wxTOPLEVEL_TITLEBAR )
    {
        wxRect client = GetFrameClientArea(rect, flags & ~wxTOPLEVEL_TITLEBAR);

        if ( flags & wxTOPLEVEL_ICON )
        {
            if ( wxRect(client.GetPosition(), GetFrameIconSize()).Contains(pt) )
                return wxHT_TOPLEVEL_ICON;
        }

        wxRect btnRect(client.GetRight() - 2 - FRAME_BUTTON_WIDTH,
                       client.GetTop() + (FRAME_TITLEBAR_HEIGHT-FRAME_BUTTON_HEIGHT)/2,
                       FRAME_BUTTON_WIDTH, FRAME_BUTTON_HEIGHT);

        if ( flags & wxTOPLEVEL_BUTTON_CLOSE )
        {
            if ( btnRect.Contains(pt) )
                return wxHT_TOPLEVEL_BUTTON_CLOSE;
            btnRect.x -= FRAME_BUTTON_WIDTH + 2;
        }
        if ( flags & wxTOPLEVEL_BUTTON_MAXIMIZE )
        {
            if ( btnRect.Contains(pt) )
                return wxHT_TOPLEVEL_BUTTON_MAXIMIZE;
            btnRect.x -= FRAME_BUTTON_WIDTH;
        }
        if ( flags & wxTOPLEVEL_BUTTON_RESTORE )
        {
            if ( btnRect.Contains(pt) )
                return wxHT_TOPLEVEL_BUTTON_RESTORE;
            btnRect.x -= FRAME_BUTTON_WIDTH;
        }
        if ( flags & wxTOPLEVEL_BUTTON_ICONIZE )
        {
            if ( btnRect.Contains(pt) )
                return wxHT_TOPLEVEL_BUTTON_ICONIZE;
            btnRect.x -= FRAME_BUTTON_WIDTH;
        }
        if ( flags & wxTOPLEVEL_BUTTON_HELP )
        {
            if ( btnRect.Contains(pt) )
                return wxHT_TOPLEVEL_BUTTON_HELP;
            btnRect.x -= FRAME_BUTTON_WIDTH;
        }

        if ( pt.y >= client.y && pt.y < client.y + FRAME_TITLEBAR_HEIGHT )
            return wxHT_TOPLEVEL_TITLEBAR;
    }

    if ( (flags & wxTOPLEVEL_BORDER) && !(flags & wxTOPLEVEL_MAXIMIZED) )
    {
        // we are certainly at one of borders, let's decide which one:

        int border = 0;
        // dirty trick, relies on the way wxHT_TOPLEVEL_XXX are defined!
        if ( pt.x < client.x )
            border |= wxHT_TOPLEVEL_BORDER_W;
        else if ( pt.x >= client.width + client.x )
            border |= wxHT_TOPLEVEL_BORDER_E;
        if ( pt.y < client.y )
            border |= wxHT_TOPLEVEL_BORDER_N;
        else if ( pt.y >= client.height + client.y )
            border |= wxHT_TOPLEVEL_BORDER_S;
        return border;
    }

    return wxHT_NOWHERE;
}

void wxStdRenderer::DrawFrameTitleBar(wxDC& dc,
                                      const wxRect& rect,
                                      const wxString& title,
                                      const wxIcon& icon,
                                      int flags,
                                      int specialButton,
                                      int specialButtonFlags)
{
    if ( (flags & wxTOPLEVEL_BORDER) && !(flags & wxTOPLEVEL_MAXIMIZED) )
    {
        DrawFrameBorder(dc, rect, flags);
    }
    if ( flags & wxTOPLEVEL_TITLEBAR )
    {
        DrawFrameBackground(dc, rect, flags);
        if ( flags & wxTOPLEVEL_ICON )
            DrawFrameIcon(dc, rect, icon, flags);
        DrawFrameTitle(dc, rect, title, flags);

        wxRect client = GetFrameClientArea(rect, flags & ~wxTOPLEVEL_TITLEBAR);
        wxCoord x,y;
        x = client.GetRight() - 2 - FRAME_BUTTON_WIDTH;
        y = client.GetTop() + (FRAME_TITLEBAR_HEIGHT-FRAME_BUTTON_HEIGHT)/2;

        if ( flags & wxTOPLEVEL_BUTTON_CLOSE )
        {
            DrawFrameButton(dc, x, y, wxTOPLEVEL_BUTTON_CLOSE,
                            (specialButton == wxTOPLEVEL_BUTTON_CLOSE) ?
                            specialButtonFlags : 0);
            x -= FRAME_BUTTON_WIDTH + 2;
        }
        if ( flags & wxTOPLEVEL_BUTTON_MAXIMIZE )
        {
            DrawFrameButton(dc, x, y, wxTOPLEVEL_BUTTON_MAXIMIZE,
                            (specialButton == wxTOPLEVEL_BUTTON_MAXIMIZE) ?
                            specialButtonFlags : 0);
            x -= FRAME_BUTTON_WIDTH;
        }
        if ( flags & wxTOPLEVEL_BUTTON_RESTORE )
        {
            DrawFrameButton(dc, x, y, wxTOPLEVEL_BUTTON_RESTORE,
                            (specialButton == wxTOPLEVEL_BUTTON_RESTORE) ?
                            specialButtonFlags : 0);
            x -= FRAME_BUTTON_WIDTH;
        }
        if ( flags & wxTOPLEVEL_BUTTON_ICONIZE )
        {
            DrawFrameButton(dc, x, y, wxTOPLEVEL_BUTTON_ICONIZE,
                            (specialButton == wxTOPLEVEL_BUTTON_ICONIZE) ?
                            specialButtonFlags : 0);
            x -= FRAME_BUTTON_WIDTH;
        }
        if ( flags & wxTOPLEVEL_BUTTON_HELP )
        {
            DrawFrameButton(dc, x, y, wxTOPLEVEL_BUTTON_HELP,
                            (specialButton == wxTOPLEVEL_BUTTON_HELP) ?
                            specialButtonFlags : 0);
        }
    }
}

void wxStdRenderer::DrawFrameBorder(wxDC& dc, const wxRect& rect, int flags)
{
    if ( !(flags & wxTOPLEVEL_BORDER) )
        return;

    wxRect r(rect);

    DrawAntiSunkenBorder(dc, &r);
    DrawExtraBorder(dc, &r);
    if ( flags & wxTOPLEVEL_RESIZEABLE )
        DrawExtraBorder(dc, &r);
}

void wxStdRenderer::DrawFrameBackground(wxDC& dc, const wxRect& rect, int flags)
{
    if ( !(flags & wxTOPLEVEL_TITLEBAR) )
        return;

    wxColour col = m_scheme->Get(flags & wxTOPLEVEL_ACTIVE
                                    ? wxColourScheme::TITLEBAR_ACTIVE
                                    : wxColourScheme::TITLEBAR);

    wxRect r = GetFrameClientArea(rect, flags & ~wxTOPLEVEL_TITLEBAR);
    r.height = FRAME_TITLEBAR_HEIGHT;

    DrawBackground(dc, col, r);
}

void wxStdRenderer::DrawFrameTitle(wxDC& dc,
                                   const wxRect& rect,
                                   const wxString& title,
                                   int flags)
{
    wxColour col = m_scheme->Get(flags & wxTOPLEVEL_ACTIVE
                                    ? wxColourScheme::TITLEBAR_ACTIVE_TEXT
                                    : wxColourScheme::TITLEBAR_TEXT);
    dc.SetTextForeground(col);

    wxRect r = GetFrameClientArea(rect, flags & ~wxTOPLEVEL_TITLEBAR);
    r.height = FRAME_TITLEBAR_HEIGHT;
    if ( flags & wxTOPLEVEL_ICON )
    {
        r.x += FRAME_TITLEBAR_HEIGHT;
        r.width -= FRAME_TITLEBAR_HEIGHT + 2;
    }
    else
    {
        r.x += 1;
        r.width -= 3;
    }

    if ( flags & wxTOPLEVEL_BUTTON_CLOSE )
        r.width -= FRAME_BUTTON_WIDTH + 2;
    if ( flags & wxTOPLEVEL_BUTTON_MAXIMIZE )
        r.width -= FRAME_BUTTON_WIDTH;
    if ( flags & wxTOPLEVEL_BUTTON_RESTORE )
        r.width -= FRAME_BUTTON_WIDTH;
    if ( flags & wxTOPLEVEL_BUTTON_ICONIZE )
        r.width -= FRAME_BUTTON_WIDTH;
    if ( flags & wxTOPLEVEL_BUTTON_HELP )
        r.width -= FRAME_BUTTON_WIDTH;

    dc.SetFont(m_titlebarFont);

    wxString s;
    wxCoord textW;
    dc.GetTextExtent(title, &textW, nullptr);
    if ( textW > r.width )
    {
        // text is too big, let's shorten it and add "..." after it:
        size_t len = title.length();
        wxCoord WSoFar, letterW;

        dc.GetTextExtent(wxT("..."), &WSoFar, nullptr);
        if ( WSoFar > r.width )
        {
            // not enough space to draw anything
            return;
        }

        s.Alloc(len);
        for (size_t i = 0; i < len; i++)
        {
            dc.GetTextExtent(title[i], &letterW, nullptr);
            if ( letterW + WSoFar > r.width )
                break;
            WSoFar += letterW;
            s << title[i];
        }
        s << wxT("...");
    }
    else // no need to truncate the title
    {
        s = title;
    }

    dc.DrawLabel(s, wxNullBitmap, r, wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
}

void wxStdRenderer::DrawFrameIcon(wxDC& dc,
                                  const wxRect& rect,
                                  const wxIcon& icon,
                                  int flags)
{
    if ( icon.IsOk() )
    {
        wxRect r = GetFrameClientArea(rect, flags & ~wxTOPLEVEL_TITLEBAR);
        dc.DrawIcon(icon, r.x, r.y);
    }
}

void wxStdRenderer::DrawFrameButton(wxDC& dc,
                                    wxCoord x, wxCoord y,
                                    int button,
                                    int flags)
{
    FrameButtonType idx;
    switch (button)
    {
        case wxTOPLEVEL_BUTTON_CLOSE:    idx = FrameButton_Close; break;
        case wxTOPLEVEL_BUTTON_MAXIMIZE: idx = FrameButton_Maximize; break;
        case wxTOPLEVEL_BUTTON_ICONIZE:  idx = FrameButton_Minimize; break;
        case wxTOPLEVEL_BUTTON_RESTORE:  idx = FrameButton_Restore; break;
        case wxTOPLEVEL_BUTTON_HELP:     idx = FrameButton_Help; break;
        default:
            wxFAIL_MSG(wxT("incorrect button specification"));
            return;
    }

    wxBitmap bmp = GetFrameButtonBitmap(idx);
    if ( !bmp.IsOk() )
        return;

    wxRect rectBtn(x, y, FRAME_BUTTON_WIDTH, FRAME_BUTTON_HEIGHT);
    if ( flags & wxCONTROL_PRESSED )
    {
        DrawSunkenBorder(dc, &rectBtn);

        rectBtn.Offset(1, 1);
    }
    else
    {
        DrawRaisedBorder(dc, &rectBtn);
    }

    DrawBackground(dc, wxSCHEME_COLOUR(m_scheme, CONTROL), rectBtn);

    wxRect rectBmp(0, 0, bmp.GetWidth(), bmp.GetHeight());
    dc.DrawBitmap(bmp, rectBmp.CentreIn(rectBtn).GetPosition(), true);
}

int wxStdRenderer::GetFrameBorderWidth(int flags) const
{
    return flags & wxTOPLEVEL_RESIZEABLE ? 4 : 3;
}


wxRect wxStdRenderer::GetFrameClientArea(const wxRect& rect, int flags) const
{
    wxRect r(rect);

    if ( (flags & wxTOPLEVEL_BORDER) && !(flags & wxTOPLEVEL_MAXIMIZED) )
    {
        r.Inflate(-GetFrameBorderWidth(flags));
    }

    if ( flags & wxTOPLEVEL_TITLEBAR )
    {
        r.y += FRAME_TITLEBAR_HEIGHT;
        r.height -= FRAME_TITLEBAR_HEIGHT;
    }

    return r;
}

wxSize
wxStdRenderer::GetFrameTotalSize(const wxSize& clientSize, int flags) const
{
    wxSize s(clientSize);

    if ( (flags & wxTOPLEVEL_BORDER) && !(flags & wxTOPLEVEL_MAXIMIZED) )
    {
        s.IncBy(2*GetFrameBorderWidth(flags));
    }

    if ( flags & wxTOPLEVEL_TITLEBAR )
        s.y += FRAME_TITLEBAR_HEIGHT;

    return s;
}

wxSize wxStdRenderer::GetFrameMinSize(int flags) const
{
    wxSize s;

    if ( (flags & wxTOPLEVEL_BORDER) && !(flags & wxTOPLEVEL_MAXIMIZED) )
    {
        s.IncBy(2*GetFrameBorderWidth(flags));
    }

    if ( flags & wxTOPLEVEL_TITLEBAR )
    {
        s.y += FRAME_TITLEBAR_HEIGHT;

        if ( flags & wxTOPLEVEL_ICON )
            s.x += FRAME_TITLEBAR_HEIGHT + 2;
        if ( flags & wxTOPLEVEL_BUTTON_CLOSE )
            s.x += FRAME_BUTTON_WIDTH + 2;
        if ( flags & wxTOPLEVEL_BUTTON_MAXIMIZE )
            s.x += FRAME_BUTTON_WIDTH;
        if ( flags & wxTOPLEVEL_BUTTON_RESTORE )
            s.x += FRAME_BUTTON_WIDTH;
        if ( flags & wxTOPLEVEL_BUTTON_ICONIZE )
            s.x += FRAME_BUTTON_WIDTH;
        if ( flags & wxTOPLEVEL_BUTTON_HELP )
            s.x += FRAME_BUTTON_WIDTH;
    }

    return s;
}

wxSize wxStdRenderer::GetFrameIconSize() const
{
    return wxSize(16, 16);
}
