corrected painting implementation for wxDFB

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41185 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Václav Slavík 2006-09-13 09:50:02 +00:00
parent 47e5915415
commit 20671963ef
9 changed files with 231 additions and 120 deletions

View File

@ -22,39 +22,31 @@ class WXDLLIMPEXP_CORE wxWindow;
class WXDLLIMPEXP_CORE wxWindowDC : public wxDC
{
public:
wxWindowDC() {}
wxWindowDC() : m_win(NULL) {}
wxWindowDC(wxWindow *win);
virtual ~wxWindowDC();
protected:
// initializes the DC for painting on given window; if rect!=NULL, then
// for painting only on the given region of the window
void InitForWin(wxWindow *win, const wxRect *rect);
private:
wxWindow *m_win; // the window the DC paints on
DECLARE_DYNAMIC_CLASS(wxWindowDC)
DECLARE_NO_COPY_CLASS(wxWindowDC)
};
//-----------------------------------------------------------------------------
// base class for wxClientDC and wxPaintDC
//-----------------------------------------------------------------------------
class WXDLLIMPEXP_CORE wxClientDCBase : public wxWindowDC
{
public:
wxClientDCBase() {}
wxClientDCBase(wxWindow *win);
};
//-----------------------------------------------------------------------------
// wxClientDC
//-----------------------------------------------------------------------------
class WXDLLIMPEXP_CORE wxClientDC : public wxClientDCBase
class WXDLLIMPEXP_CORE wxClientDC : public wxWindowDC
{
public:
wxClientDC() {}
wxClientDC(wxWindow *win) : wxClientDCBase(win) {}
~wxClientDC();
wxClientDC(wxWindow *win);
DECLARE_DYNAMIC_CLASS(wxClientDC)
DECLARE_NO_COPY_CLASS(wxClientDC)
@ -65,12 +57,11 @@ public:
// wxPaintDC
//-----------------------------------------------------------------------------
class WXDLLIMPEXP_CORE wxPaintDC : public wxClientDCBase
class WXDLLIMPEXP_CORE wxPaintDC : public wxClientDC
{
public:
wxPaintDC() {}
wxPaintDC(wxWindow *win) : wxClientDCBase(win) {}
~wxPaintDC();
wxPaintDC(wxWindow *win) : wxClientDC(win) {}
DECLARE_DYNAMIC_CLASS(wxPaintDC)
DECLARE_NO_COPY_CLASS(wxPaintDC)

View File

@ -77,6 +77,10 @@ public:
wxIDirectFBWindowPtr GetDirectFBWindow() const { return m_dfbwin; }
// Returns true if some invalidated area of the TLW is currently being
// painted
bool IsPainting() const { return m_isPainting; }
protected:
// common part of all ctors
void Init();
@ -88,7 +92,7 @@ protected:
virtual void DoGetSize(int *width, int *height) const;
virtual void DoMoveWindow(int x, int y, int width, int height);
virtual void DoRefreshRect(const wxRect& rect, bool eraseBack = true);
virtual void DoRefreshRect(const wxRect& rect);
private:
// do queued painting in idle time
@ -119,7 +123,10 @@ protected:
wxIDirectFBWindowPtr m_dfbwin;
private:
// invalidated areas of the TLW that need repainting
wxDfbQueuedPaintRequests *m_toPaint;
// are we currently painting some area of this TLW?
bool m_isPainting;
friend class wxEventLoop; // for HandleDFBWindowEvent
};

View File

@ -140,11 +140,12 @@ protected:
void InvalidateDfbSurface();
// called by parent to render (part of) the window
void PaintWindow(const wxRect& rect, bool eraseBackground);
void PaintWindow(const wxRect& rect);
// implementation of Refresh()
void DoRefreshWindow(bool eraseBack = true);
virtual void DoRefreshRect(const wxRect& rect, bool eraseBack = true);
// refreshes the entire window (including non-client areas)
void DoRefreshWindow();
// refreshes given rectangle of the window (in window, _not_ client coords)
virtual void DoRefreshRect(const wxRect& rect);
// DirectFB events handling
void HandleKeyEvent(const wxDFBWindowEvent& event_);
@ -177,5 +178,4 @@ private:
DECLARE_EVENT_TABLE()
};
#endif // _WX_DFB_WINDOW_H_

View File

@ -231,10 +231,11 @@ struct wxIDirectFBSurface : public wxDfbWrapper<IDirectFBSurface>
(DFBSurfaceTextFlags)flags));
}
bool Flip(const DFBRegion *region, int flags)
{
return Check(m_ptr->Flip(m_ptr, region, (DFBSurfaceFlipFlags)flags));
}
/**
Updates the front buffer from the back buffer. If @a region is not
NULL, only given rectangle is updated.
*/
bool FlipToFront(const DFBRegion *region = NULL);
wxIDirectFBSurfacePtr GetSubSurface(const DFBRectangle *rect)
{
@ -293,6 +294,10 @@ struct wxIDirectFBSurface : public wxDfbWrapper<IDirectFBSurface>
size of this surface.
*/
wxIDirectFBSurfacePtr CreateCompatible(const wxSize& size = wxDefaultSize);
private:
// this is private because we want user code to use FlipToFront()
bool Flip(const DFBRegion *region, int flags);
};

View File

@ -151,6 +151,10 @@ void wxDC::Clear()
wxColour clr = m_backgroundBrush.GetColour();
m_surface->Clear(clr.Red(), clr.Green(), clr.Blue(), clr.Alpha());
wxSize size(GetSize());
CalcBoundingBox(XDEV2LOG(0), YDEV2LOG(0));
CalcBoundingBox(XDEV2LOG(size.x), YDEV2LOG(size.y));
}
extern bool wxDoFloodFill(wxDC *dc, wxCoord x, wxCoord y,

View File

@ -31,6 +31,8 @@
#include "wx/dfb/private.h"
#define TRACE_PAINT _T("paint")
// ===========================================================================
// implementation
// ===========================================================================
@ -48,13 +50,12 @@ wxWindowDC::wxWindowDC(wxWindow *win)
void wxWindowDC::InitForWin(wxWindow *win, const wxRect *rect)
{
m_win = win;
wxCHECK_RET( win, _T("invalid window") );
// check if the rectangle covers full window and so is not needed:
if ( rect && *rect == wxRect(win->GetSize()) )
rect = NULL;
// obtain the surface used for painting:
wxPoint origin;
wxIDirectFBSurfacePtr surface;
if ( !win->IsVisible() )
@ -64,21 +65,37 @@ void wxWindowDC::InitForWin(wxWindow *win, const wxRect *rect)
// we still need a valid DC so that e.g. text extents can be measured,
// so let's create a dummy surface that has the same format as the real
// one would have and let the code paint on it:
wxLogTrace(TRACE_PAINT, _T("%p ('%s'): creating dummy DC surface"),
win, win->GetName().c_str());
wxSize size(rect ? rect->GetSize() : win->GetSize());
surface = win->GetDfbSurface()->CreateCompatible(size);
}
else if ( !rect )
{
wxCHECK_RET( win->GetSize().x > 0 && win->GetSize().y > 0,
_T("window has invalid size") );
surface = win->GetDfbSurface();
}
else
{
wxCHECK_RET( !rect || !rect->IsEmpty(), _T("invalid rectangle") );
wxRect rectOrig(rect ? *rect : wxRect(win->GetSize()));
DFBRectangle dfbrect = { rect->x, rect->y, rect->width, rect->height };
// compute painting rectangle after clipping if we're in PaintWindow
// code, otherwise paint on the entire window:
wxRect r(rectOrig);
if ( win->GetTLW()->IsPainting() )
r.Intersect(win->GetUpdateRegion().AsRect());
wxCHECK_RET( !r.IsEmpty(), _T("invalid painting rectangle") );
// if the DC was clipped thanks to rectPaint, we must adjust the origin
// accordingly; but we do *not* adjust for 'rect', because
// rect.GetPosition() has coordinates (0,0) in the DC:
origin.x = rectOrig.x - r.x;
origin.y = rectOrig.y - r.y;
wxLogTrace(TRACE_PAINT,
_T("%p ('%s'): creating DC for area [%i,%i,%i,%i], clipped to [%i,%i,%i,%i], origin [%i,%i]"),
win, win->GetName().c_str(),
rectOrig.x, rectOrig.y, rectOrig.GetRight(), rectOrig.GetBottom(),
r.x, r.y, r.GetRight(), r.GetBottom(),
origin.x, origin.y);
DFBRectangle dfbrect = { r.x, r.y, r.width, r.height };
surface = win->GetDfbSurface()->GetSubSurface(&dfbrect);
}
@ -89,20 +106,31 @@ void wxWindowDC::InitForWin(wxWindow *win, const wxRect *rect)
SetFont(win->GetFont());
// offset coordinates to account for subsurface's origin coordinates:
if ( rect )
SetDeviceOrigin(rect->x, rect->y);
SetDeviceOrigin(origin.x, origin.y);
}
//-----------------------------------------------------------------------------
// base class for wxClientDC and wxPaintDC
//-----------------------------------------------------------------------------
wxClientDCBase::wxClientDCBase(wxWindow *win)
wxWindowDC::~wxWindowDC()
{
wxCHECK_RET( win, _T("invalid window") );
wxIDirectFBSurfacePtr surface(GetDirectFBSurface());
if ( !surface || !m_win )
return;
wxRect rect = win->GetClientRect();
InitForWin(win, &rect);
// painting on hidden window has no effect on TLW's surface, don't
// waste time flipping the dummy surface:
if ( !m_win->IsVisible() )
return;
// if no painting was done on the DC, we don't have to flip the surface:
if ( !m_isBBoxValid )
return;
if ( !m_win->GetTLW()->IsPainting() )
{
// FIXME: flip only modified parts of the surface
surface->FlipToFront();
}
// else: don't flip the surface, wxTLW will do it when it finishes
// painting of its invalidated areas
}
//-----------------------------------------------------------------------------
@ -111,15 +139,12 @@ wxClientDCBase::wxClientDCBase(wxWindow *win)
IMPLEMENT_DYNAMIC_CLASS(wxClientDC, wxWindowDC)
wxClientDC::~wxClientDC()
wxClientDC::wxClientDC(wxWindow *win)
{
// flip to surface so that the changes become visible
wxIDirectFBSurfacePtr surface(GetDirectFBSurface());
wxCHECK_RET( win, _T("invalid window") );
// FIXME: do this only if the surface was modified (as opposed to e.g.
// used only to obtain text metrics)
if ( surface )
surface->Flip(NULL, DSFLIP_NONE);
wxRect rect = win->GetClientRect();
InitForWin(win, &rect);
}
//-----------------------------------------------------------------------------
@ -127,12 +152,3 @@ wxClientDC::~wxClientDC()
//-----------------------------------------------------------------------------
IMPLEMENT_DYNAMIC_CLASS(wxPaintDC, wxWindowDC)
#warning "wxPaintDC ctor must respect m_updateRegion"
wxPaintDC::~wxPaintDC()
{
// NB: do *not* flip the surface: wxPaintDC is used with EVT_PAINT and the
// surface will be flipped for the entire TLW once all children are
// repainted
}

View File

@ -41,13 +41,10 @@ static wxDfbWindowsMap gs_dfbWindowsMap;
struct wxDfbPaintRequest
{
wxDfbPaintRequest(const wxRect& rect, bool eraseBackground)
: m_rect(rect), m_eraseBackground(eraseBackground) {}
wxDfbPaintRequest(const wxDfbPaintRequest& r)
: m_rect(r.m_rect), m_eraseBackground(r.m_eraseBackground) {}
wxDfbPaintRequest(const wxRect& rect) : m_rect(rect) {}
wxDfbPaintRequest(const wxDfbPaintRequest& r) : m_rect(r.m_rect) {}
wxRect m_rect;
bool m_eraseBackground;
};
WX_DEFINE_ARRAY_PTR(wxDfbPaintRequest*, wxDfbQueuedPaintRequestsList);
@ -59,8 +56,8 @@ public:
~wxDfbQueuedPaintRequests() { Clear(); }
// Adds paint request to the queue
void Add(const wxRect& rect, bool eraseBack)
{ m_queue.push_back(new wxDfbPaintRequest(rect, eraseBack)); }
void Add(const wxRect& rect)
{ m_queue.push_back(new wxDfbPaintRequest(rect)); }
// Is the queue empty?
bool IsEmpty() const { return m_queue.empty(); }
@ -91,6 +88,7 @@ void wxTopLevelWindowDFB::Init()
m_sizeSet = false;
m_opacity = 255;
m_toPaint = new wxDfbQueuedPaintRequests;
m_isPainting = false;
}
bool wxTopLevelWindowDFB::Create(wxWindow *parent,
@ -148,7 +146,8 @@ bool wxTopLevelWindowDFB::Create(wxWindow *parent,
if ( !m_dfbwin->SetOpacity(wxALPHA_TRANSPARENT) )
return false;
wxWindow::Create(NULL, id, pos, size, style, name);
if ( !wxWindow::Create(NULL, id, pos, size, style, name) )
return false;
SetParent(parent);
if ( parent )
@ -217,7 +216,8 @@ void wxTopLevelWindowDFB::DoMoveWindow(int x, int y, int width, int height)
{
m_dfbwin->Resize(width, height);
// we must repaint the window after it changed size:
Refresh();
if ( IsShown() )
DoRefreshWindow();
}
}
@ -375,23 +375,30 @@ void wxTopLevelWindowDFB::HandleQueuedPaintRequests()
wxRect winRect(wxPoint(0, 0), GetSize());
wxRect paintedRect;
// important note: all DCs created from now until m_isPainting is reset to
// false will not update the front buffer as this flag indicates that we'll
// blit the entire back buffer to front soon
m_isPainting = true;
size_t cnt = requests.size();
wxLogTrace(TRACE_PAINT, _T("%p ('%s'): processing %i paint requests"),
this, GetName().c_str(), cnt);
for ( size_t i = 0; i < cnt; ++i )
{
const wxDfbPaintRequest& request = *requests[i];
wxRect clipped(request.m_rect);
wxLogTrace(TRACE_PAINT,
_T("%p ('%s'): processing paint request [x=%i,y=%i,w=%i,h=%i]"),
this, GetName().c_str(),
clipped.x, clipped.y, clipped.width, clipped.height);
clipped.Intersect(winRect);
if ( clipped.IsEmpty() )
continue; // nothing to refresh
PaintWindow(clipped, request.m_eraseBackground);
wxLogTrace(TRACE_PAINT,
_T("%p ('%s'): processing paint request [%i,%i,%i,%i]"),
this, GetName().c_str(),
clipped.x, clipped.y, clipped.GetRight(), clipped.GetBottom());
PaintWindow(clipped);
// remember rectangle covering all repainted areas:
if ( paintedRect.IsEmpty() )
@ -400,23 +407,49 @@ void wxTopLevelWindowDFB::HandleQueuedPaintRequests()
paintedRect.Union(clipped);
}
m_isPainting = false;
m_toPaint->Clear();
if ( paintedRect.IsEmpty() )
return; // no painting occurred, no need to flip
// flip the surface to make the changes visible:
// Flip the surface to make the changes visible. Note that the rectangle we
// flip is *superset* of the union of repainted rectangles (created as
// "rectangles union" by wxRect::Union) and so some parts of the back
// buffer that we didn't touch in this HandleQueuedPaintRequests call will
// be copied to the front buffer as well. This is safe/correct thing to do
// *only* because wx always use wxIDirectFBSurface::FlipToFront() and so
// the back and front buffers contain the same data.
//
// Note that we do _not_ split m_toPaint into disjoint rectangles and
// do FlipToFront() for each of them, because that could result in visible
// updating of the screen; instead, we prefer to flip everything at once.
DFBRegion r = {paintedRect.GetLeft(), paintedRect.GetTop(),
paintedRect.GetRight(), paintedRect.GetBottom()};
DFBRegion *rptr = (winRect == paintedRect) ? NULL : &r;
GetDfbSurface()->Flip(rptr, DSFLIP_NONE);
GetDfbSurface()->FlipToFront(rptr);
wxLogTrace(TRACE_PAINT,
_T("%p ('%s'): flipped surface: [%i,%i,%i,%i]"),
this, GetName().c_str(),
paintedRect.x, paintedRect.y,
paintedRect.GetRight(), paintedRect.GetBottom());
}
void wxTopLevelWindowDFB::DoRefreshRect(const wxRect& rect, bool eraseBack)
void wxTopLevelWindowDFB::DoRefreshRect(const wxRect& rect)
{
wxASSERT_MSG( rect.width > 0 && rect.height > 0, _T("invalid rect") );
wxLogTrace(TRACE_PAINT,
_T("%p ('%s'): [TLW] refresh rect [%i,%i,%i,%i]"),
this, GetName().c_str(),
rect.x, rect.y, rect.GetRight(), rect.GetBottom());
// defer painting until idle time or until Update() is called:
m_toPaint->Add(rect, eraseBack);
m_toPaint->Add(rect);
}
void wxTopLevelWindowDFB::Update()

View File

@ -590,34 +590,56 @@ void wxWindowDFB::Clear()
dc.Clear();
}
void wxWindowDFB::Refresh(bool eraseBack, const wxRect *rect)
void wxWindowDFB::Refresh(bool WXUNUSED(eraseBack), const wxRect *rect)
{
if ( !IsShown() || IsFrozen() )
return;
// NB[1]: We intentionally ignore the eraseBack argument here. This is
// because of the way wxDFB's painting is implemented: the refresh
// request is probagated up to wxTLW, which is then painted in
// top-down order. This means that this window's area is first
// painted by its parent and this window is then painted over it, so
// it's not safe to not paint this window's background even if
// eraseBack=false.
// NB[2]: wxWindow::Refresh() takes the rectangle in client coords, but
// wxUniv translates it to window coords before passing it to
// wxWindowDFB::Refresh(), so we can directly pass the rect to
// DoRefreshRect (which takes window, not client, coords) here.
if ( rect )
DoRefreshRect(*rect, eraseBack);
DoRefreshRect(*rect);
else
DoRefreshWindow(eraseBack);
DoRefreshWindow();
}
void wxWindowDFB::DoRefreshWindow(bool eraseBack)
void wxWindowDFB::DoRefreshWindow()
{
DoRefreshRect(wxRect(wxPoint(0, 0), GetSize()), eraseBack);
// NB: DoRefreshRect() takes window coords, not client, so this is correct
DoRefreshRect(wxRect(GetSize()));
}
void wxWindowDFB::DoRefreshRect(const wxRect& rect, bool eraseBack)
void wxWindowDFB::DoRefreshRect(const wxRect& rect)
{
wxWindow *parent = GetParent();
wxCHECK_RET( parent, _T("no parent") );
// don't overlap outside of the window (NB: 'rect' is in window coords):
wxRect r(rect);
r.Intersect(wxRect(GetSize()));
if ( r.IsEmpty() )
return;
wxLogTrace(TRACE_PAINT,
_T("%p ('%s'): refresh rect [%i,%i,%i,%i]"),
this, GetName().c_str(),
rect.x, rect.y, rect.GetRight(), rect.GetBottom());
// convert the refresh rectangle to parent's coordinates and
// recursively refresh the parent:
wxRect r(rect);
r.Offset(GetPosition());
r.Offset(parent->GetClientAreaOrigin());
parent->DoRefreshRect(r, eraseBack);
parent->DoRefreshRect(r);
}
void wxWindowDFB::Update()
@ -640,24 +662,21 @@ void wxWindowDFB::Thaw()
if ( --m_frozenness == 0 )
{
if ( IsShown() )
Refresh();
DoRefreshWindow();
}
}
void wxWindowDFB::PaintWindow(const wxRect& rect, bool eraseBackground)
void wxWindowDFB::PaintWindow(const wxRect& rect)
{
wxCHECK_RET( !IsFrozen() && IsShown(), _T("shouldn't be called") );
wxLogTrace(TRACE_PAINT,
_T("%p ('%s'): painting region [x=%i,y=%i,w=%i,h=%i]"),
_T("%p ('%s'): painting region [%i,%i,%i,%i]"),
this, GetName().c_str(),
rect.x, rect.y, rect.width, rect.height);
rect.x, rect.y, rect.GetRight(), rect.GetBottom());
m_updateRegion = rect;
// FIXME_DFB: don't waste time rendering the area if it's fully covered
// by some children, go directly to rendering the children
#if wxUSE_CARET
// must hide caret temporarily, otherwise we'd get rendering artifacts
wxCaret *caret = GetCaret();
@ -665,27 +684,52 @@ void wxWindowDFB::PaintWindow(const wxRect& rect, bool eraseBackground)
caret->Hide();
#endif // wxUSE_CARET
if ( eraseBackground )
// FIXME_DFB: don't waste time rendering the area if it's fully covered
// by some children, go directly to rendering the children
// NB: unconditionally send wxEraseEvent, because our implementation of
// wxWindow::Refresh() ignores the eraseBack argument
wxWindowDC dc((wxWindow*)this);
wxEraseEvent eventEr(m_windowId, &dc);
eventEr.SetEventObject(this);
GetEventHandler()->ProcessEvent(eventEr);
wxRect clientRect(GetClientRect());
// only send wxNcPaintEvent if drawing at least part of nonclient area:
if ( !clientRect.Inside(rect) )
{
wxWindowDC dc((wxWindow*)this);
wxEraseEvent eventEr(m_windowId, &dc);
eventEr.SetEventObject(this);
GetEventHandler()->ProcessEvent(eventEr);
wxNcPaintEvent eventNc(GetId());
eventNc.SetEventObject(this);
GetEventHandler()->ProcessEvent(eventNc);
}
else
{
wxLogTrace(TRACE_PAINT, _T("%p ('%s'): not sending wxNcPaintEvent"),
this, GetName().c_str());
}
wxNcPaintEvent eventNc(GetId());
eventNc.SetEventObject(this);
GetEventHandler()->ProcessEvent(eventNc);
wxPaintEvent eventPt(GetId());
eventPt.SetEventObject(this);
GetEventHandler()->ProcessEvent(eventPt);
// only send wxPaintEvent if drawing at least part of client area:
if ( rect.Intersects(clientRect) )
{
wxPaintEvent eventPt(GetId());
eventPt.SetEventObject(this);
GetEventHandler()->ProcessEvent(eventPt);
}
else
{
wxLogTrace(TRACE_PAINT, _T("%p ('%s'): not sending wxPaintEvent"),
this, GetName().c_str());
}
#if wxUSE_CARET
if ( caret )
caret->Show();
#endif // wxUSE_CARET
m_updateRegion.Clear();
// paint the children:
wxPoint origin = GetClientAreaOrigin();
wxWindowList& children = GetChildren();
for ( wxWindowList::iterator i = children.begin();
@ -704,13 +748,10 @@ void wxWindowDFB::PaintWindow(const wxRect& rect, bool eraseBackground)
continue;
// and repaint it:
wxPoint childpos(child->GetPosition());
childrect.Offset(-childpos.x, -childpos.y);
childrect.Offset(-origin.x, -origin.y);
child->PaintWindow(childrect, eraseBackground);
childrect.Offset(-child->GetPosition());
childrect.Offset(-origin);
child->PaintWindow(childrect);
}
m_updateRegion.Clear();
}

View File

@ -166,3 +166,17 @@ wxIDirectFBSurfacePtr wxIDirectFBSurface::Clone()
return snew;
}
bool wxIDirectFBSurface::Flip(const DFBRegion *region, int flags)
{
return Check(m_ptr->Flip(m_ptr, region, (DFBSurfaceFlipFlags)flags));
}
bool wxIDirectFBSurface::FlipToFront(const DFBRegion *region)
{
// Blit to the front buffer instead of exchanging front and back ones.
// Always doing this ensures that back and front buffer have same content
// and so painting to the back buffer will never lose any previous
// drawings:
return Flip(region, DSFLIP_BLIT);
}