Implement wxDC mirroring for RTL layout with GTK3

Co-Authored-By: AliKet <aliket1435@gmail.com>
This commit is contained in:
Paul Cornett
2021-01-31 20:59:29 -08:00
parent 550bb1e2ce
commit e85f89e522
4 changed files with 229 additions and 43 deletions

View File

@@ -15,13 +15,16 @@
class wxGTKCairoDCImpl: public wxGCDCImpl
{
typedef wxGCDCImpl BaseType;
public:
wxGTKCairoDCImpl(wxDC* owner);
wxGTKCairoDCImpl(wxDC* owner, double scaleFactor);
wxGTKCairoDCImpl(wxDC* owner, wxWindow* window);
wxGTKCairoDCImpl(wxDC* owner, wxWindow* window, wxLayoutDirection dir = wxLayout_Default, int width = 0);
virtual void DoDrawBitmap(const wxBitmap& bitmap, int x, int y, bool useMask) wxOVERRIDE;
virtual void DoDrawCheckMark(int x, int y, int width, int height) wxOVERRIDE;
virtual void DoDrawIcon(const wxIcon& icon, int x, int y) wxOVERRIDE;
virtual void DoDrawText(const wxString& text, int x, int y) wxOVERRIDE;
virtual void DoDrawRotatedText(const wxString& text, int x, int y, double angle) wxOVERRIDE;
#if wxUSE_IMAGE
virtual bool DoFloodFill(int x, int y, const wxColour& col, wxFloodFillStyle style) wxOVERRIDE;
#endif
@@ -32,12 +35,15 @@ public:
virtual void* GetCairoContext() const wxOVERRIDE;
virtual wxSize GetPPI() const wxOVERRIDE;
virtual void SetLayoutDirection(wxLayoutDirection dir) wxOVERRIDE;
virtual wxLayoutDirection GetLayoutDirection() const wxOVERRIDE;
protected:
// Set m_size from the given (valid) GdkWindow.
void InitSize(GdkWindow* window);
wxSize m_size;
wxLayoutDirection m_layoutDir;
wxDECLARE_NO_COPY_CLASS(wxGTKCairoDCImpl);
};
@@ -108,7 +114,7 @@ private:
class WXDLLIMPEXP_CORE wxGTKCairoDC: public wxDC
{
public:
wxGTKCairoDC(cairo_t* cr, wxWindow* window);
wxGTKCairoDC(cairo_t* cr, wxWindow* window, wxLayoutDirection dir = wxLayout_LeftToRight, int width = 0);
wxDECLARE_NO_COPY_CLASS(wxGTKCairoDC);
};

View File

@@ -25,17 +25,14 @@
wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner)
: wxGCDCImpl(owner)
{
m_layoutDir = wxLayout_Default;
}
wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner, double scaleFactor)
: wxGCDCImpl(owner, 0)
{
m_contentScaleFactor = scaleFactor;
}
wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner, wxWindow* window)
wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner, wxWindow* window, wxLayoutDirection dir, int width)
: wxGCDCImpl(owner, 0)
, m_size(width, 0)
{
m_layoutDir = dir;
if ( window )
{
m_window = window;
@@ -62,6 +59,12 @@ void wxGTKCairoDCImpl::DoDrawBitmap(const wxBitmap& bitmap, int x, int y, bool u
if (cr)
{
cairo_save(cr);
if (m_layoutDir == wxLayout_RightToLeft)
{
// bitmap is not mirrored
cairo_scale(cr, -1, 1);
x = -x - bitmap.GetWidth();
}
bitmap.Draw(cr, x, y, useMask, &m_textForegroundColour, &m_textBackgroundColour);
cairo_restore(cr);
}
@@ -81,6 +84,108 @@ bool wxGTKCairoDCImpl::DoFloodFill(int x, int y, const wxColour& col, wxFloodFil
}
#endif
void wxGTKCairoDCImpl::DoDrawText(const wxString& text, int x, int y)
{
wxCHECK_RET(IsOk(), "invalid DC");
if (text.empty())
return;
if (m_layoutDir == wxLayout_RightToLeft && text.find('\n') != wxString::npos)
{
// RTL needs each line separately to position text properly.
// DrawLabel() will split the text and call back for each line.
GetOwner()->DrawLabel(text, wxRect(x, y, 0, 0));
return;
}
int w, h;
DoGetTextExtent(text, &w, &h);
CalcBoundingBox(x, y);
CalcBoundingBox(x + w, y + h);
if (m_layoutDir == wxLayout_RightToLeft)
{
m_graphicContext->PushState();
// text is not mirrored
m_graphicContext->Scale(-1, 1);
x = -x - w;
}
wxCompositionMode curMode = m_graphicContext->GetCompositionMode();
m_graphicContext->SetCompositionMode(wxCOMPOSITION_OVER);
if (m_backgroundMode == wxBRUSHSTYLE_TRANSPARENT)
m_graphicContext->DrawText(text, x, y);
else
m_graphicContext->DrawText(text, x, y, m_graphicContext->CreateBrush(m_textBackgroundColour));
m_graphicContext->SetCompositionMode(curMode);
if (m_layoutDir == wxLayout_RightToLeft)
m_graphicContext->PopState();
}
void wxGTKCairoDCImpl::DoDrawRotatedText(const wxString& text, int x, int y, double angle)
{
wxCHECK_RET(IsOk(), "invalid DC");
// save current bounding box
// rotation will cause DoDrawText() to update it incorrectly
const bool isBBoxValid = m_isBBoxValid;
const int minX = m_minX;
const int minY = m_minY;
const int maxX = m_maxX;
const int maxY = m_maxY;
const double rad = wxDegToRad(-angle);
m_graphicContext->PushState();
m_graphicContext->Translate(x, y);
m_graphicContext->Rotate(rad);
DoDrawText(text, 0, 0);
m_graphicContext->PopState();
// restore bounding box and update it correctly
m_isBBoxValid = isBBoxValid;
m_minX = minX;
m_minY = minY;
m_maxX = maxX;
m_maxY = maxY;
CalcBoundingBox(x, y);
int w, h;
DoGetTextExtent(text, &w, &h);
cairo_matrix_t m;
cairo_matrix_init_translate(&m, x, y);
cairo_matrix_rotate(&m, rad);
double xx = w, yy = 0;
cairo_matrix_transform_point(&m, &xx, &yy);
CalcBoundingBox(int(xx), int(yy));
xx = w; yy = h;
cairo_matrix_transform_point(&m, &xx, &yy);
CalcBoundingBox(int(xx), int(yy));
xx = 0; yy = h;
cairo_matrix_transform_point(&m, &xx, &yy);
CalcBoundingBox(int(xx), int(yy));
}
void wxGTKCairoDCImpl::DoDrawCheckMark(int x, int y, int width, int height)
{
if (m_layoutDir == wxLayout_RightToLeft)
{
wxCHECK_RET(IsOk(), "invalid DC");
// checkmark is not mirrored
m_graphicContext->PushState();
m_graphicContext->Scale(-1, 1);
BaseType::DoDrawCheckMark(-x - width, y, width, height);
m_graphicContext->PopState();
}
else
BaseType::DoDrawCheckMark(x, y, width, height);
}
wxBitmap wxGTKCairoDCImpl::DoGetAsBitmap(const wxRect* /*subrect*/) const
{
wxFAIL_MSG("DoGetAsBitmap not implemented");
@@ -183,6 +288,12 @@ bool wxGTKCairoDCImpl::DoStretchBlit(int xdest, int ydest, int dstWidth, int dst
}
}
cairo_save(cr);
if (m_layoutDir == wxLayout_RightToLeft)
{
// blit is not mirrored
cairo_scale(cr, -1, 1);
xdest = -xdest - dstWidth;
}
cairo_translate(cr, xdest, ydest);
cairo_rectangle(cr, 0, 0, dstWidth, dstHeight);
double sx, sy;
@@ -250,6 +361,40 @@ wxSize wxGTKCairoDCImpl::GetPPI() const
return wxGCDCImpl::GetPPI();
}
void wxGTKCairoDCImpl::SetLayoutDirection(wxLayoutDirection dir)
{
if (dir == wxLayout_Default && m_window)
dir = m_window->GetLayoutDirection();
if (m_layoutDir != dir)
{
if (m_graphicContext)
{
if (dir == wxLayout_RightToLeft)
{
// wxDC is mirrored for RTL
m_graphicContext->Translate(m_size.x, 0);
m_graphicContext->Scale(-1, 1);
}
else if (m_layoutDir == wxLayout_RightToLeft)
{
m_graphicContext->Scale(-1, 1);
m_graphicContext->Translate(-m_size.x, 0);
}
}
m_layoutDir = dir;
}
}
wxLayoutDirection wxGTKCairoDCImpl::GetLayoutDirection() const
{
// LTR unless explicitly RTL
return
m_layoutDir == wxLayout_RightToLeft
? wxLayout_RightToLeft
: wxLayout_LeftToRight;
}
//-----------------------------------------------------------------------------
wxWindowDCImpl::wxWindowDCImpl(wxWindowDC* owner, wxWindow* window)
@@ -292,6 +437,8 @@ wxWindowDCImpl::wxWindowDCImpl(wxWindowDC* owner, wxWindow* window)
}
if (x || y)
SetDeviceLocalOrigin(x, y);
SetLayoutDirection(wxLayout_Default);
}
else
SetGraphicsContext(wxGraphicsContext::Create());
@@ -326,6 +473,7 @@ wxClientDCImpl::wxClientDCImpl(wxClientDC* owner, wxWindow* window)
cairo_clip(cr);
SetDeviceLocalOrigin(a.x, a.y);
}
SetLayoutDirection(wxLayout_Default);
}
else
SetGraphicsContext(wxGraphicsContext::Create());
@@ -342,6 +490,8 @@ wxPaintDCImpl::wxPaintDCImpl(wxPaintDC* owner, wxWindow* window)
wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr);
gc->EnableOffset(m_contentScaleFactor <= 1);
SetGraphicsContext(gc);
// context is already adjusted for RTL
m_layoutDir = window->GetLayoutDirection();
}
void wxPaintDCImpl::DestroyClippingRegion()
@@ -433,15 +583,23 @@ void wxMemoryDCImpl::Setup()
gc->EnableOffset(m_contentScaleFactor <= 1);
}
SetGraphicsContext(gc);
// re-apply layout direction
const wxLayoutDirection dir = m_layoutDir;
m_layoutDir = wxLayout_Default;
SetLayoutDirection(dir);
}
//-----------------------------------------------------------------------------
wxGTKCairoDC::wxGTKCairoDC(cairo_t* cr, wxWindow* window)
: wxDC(new wxGTKCairoDCImpl(this, window->GetContentScaleFactor()))
wxGTKCairoDC::wxGTKCairoDC(cairo_t* cr, wxWindow* window, wxLayoutDirection dir, int width)
: wxDC(new wxGTKCairoDCImpl(this, window, dir, width))
{
wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr);
gc->EnableOffset(window->GetContentScaleFactor() <= 1);
SetGraphicsContext(gc);
if (dir == wxLayout_Default)
SetLayoutDirection(window->GetLayoutDirection());
// else context is already adjusted for RTL
}
#else

View File

@@ -220,10 +220,6 @@ wxRendererGTK::DrawHeaderButton(wxWindow *win,
if (flags & wxCONTROL_DIRTY)
button = wxGTKPrivate::GetHeaderButtonWidgetLast();
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
GtkStateType state = GTK_STATE_NORMAL;
if (flags & wxCONTROL_DISABLED)
state = GTK_STATE_INSENSITIVE;
@@ -252,8 +248,8 @@ wxRendererGTK::DrawHeaderButton(wxWindow *win,
sc.AddTreeviewHeaderButton(pos);
gtk_style_context_set_state(sc, stateTypeToFlags[state]);
gtk_render_background(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height);
gtk_render_frame(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height);
gtk_render_background(sc, cr, rect.x, rect.y, rect.width, rect.height);
gtk_render_frame(sc, cr, rect.x, rect.y, rect.width, rect.height);
}
else
#endif // GTK >= 3.20
@@ -261,11 +257,15 @@ wxRendererGTK::DrawHeaderButton(wxWindow *win,
GtkStyleContext* sc = gtk_widget_get_style_context(button);
gtk_style_context_save(sc);
gtk_style_context_set_state(sc, stateTypeToFlags[state]);
gtk_render_background(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height);
gtk_render_frame(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height);
gtk_render_background(sc, cr, rect.x, rect.y, rect.width, rect.height);
gtk_render_frame(sc, cr, rect.x, rect.y, rect.width, rect.height);
gtk_style_context_restore(sc);
}
#else
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
GdkWindow* gdk_window = wxGetGTKDrawable(dc);
gtk_paint_box
(
@@ -305,7 +305,7 @@ int wxRendererGTK::GetHeaderButtonMargin(wxWindow *WXUNUSED(win))
// draw a ">" or "v" button
void
wxRendererGTK::DrawTreeItemButton(wxWindow* win,
wxRendererGTK::DrawTreeItemButton(wxWindow* WXUNUSED_IN_GTK3(win),
wxDC& dc, const wxRect& rect, int flags)
{
wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
@@ -314,10 +314,6 @@ wxRendererGTK::DrawTreeItemButton(wxWindow* win,
GtkWidget *tree = wxGTKPrivate::GetTreeWidget();
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
#ifdef __WXGTK3__
int state = GTK_STATE_FLAG_NORMAL;
if (flags & wxCONTROL_EXPANDED)
@@ -340,9 +336,13 @@ wxRendererGTK::DrawTreeItemButton(wxWindow* win,
gtk_style_context_save(sc);
gtk_style_context_set_state(sc, GtkStateFlags(state));
gtk_style_context_add_class(sc, GTK_STYLE_CLASS_EXPANDER);
gtk_render_expander(sc, drawable, x - x_diff, y, expander_size, expander_size);
gtk_render_expander(sc, drawable, x, y, expander_size, expander_size);
gtk_style_context_restore(sc);
#else
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
GtkStateType state;
if ( flags & wxCONTROL_CURRENT )
state = GTK_STATE_PRELIGHT;
@@ -444,14 +444,10 @@ wxRendererGTK::DrawSplitterSash(wxWindow* win,
rect.width = size.x;
}
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
#ifdef __WXGTK3__
wxGtkStyleContext sc(dc.GetContentScaleFactor());
sc.AddWindow();
gtk_render_background(sc, drawable, rect.x - x_diff, rect.y, rect.width, rect.height);
gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height);
sc.Add(GTK_TYPE_PANED, "paned", "pane-separator", NULL);
if (gtk_check_version(3,20,0) == NULL)
@@ -459,8 +455,12 @@ wxRendererGTK::DrawSplitterSash(wxWindow* win,
gtk_style_context_set_state(sc,
flags & wxCONTROL_CURRENT ? GTK_STATE_FLAG_PRELIGHT : GTK_STATE_FLAG_NORMAL);
gtk_render_handle(sc, drawable, rect.x - x_diff, rect.y, rect.width, rect.height);
gtk_render_handle(sc, drawable, rect.x, rect.y, rect.width, rect.height);
#else
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
GdkWindow* gdk_window = wxGetGTKDrawable(dc);
if (gdk_window == NULL)
return;
@@ -731,8 +731,17 @@ wxRendererGTK::DrawCheckBox(wxWindow*,
const int w = info.indicator_width + info.margin_left + info.margin_right;
const int h = info.indicator_height + info.margin_top + info.margin_bottom;
const int x = rect.x + (rect.width - w) / 2;
const int y = rect.y + (rect.height - h) / 2;
int x = rect.x + (rect.width - w) / 2;
int y = rect.y + (rect.height - h) / 2;
const bool isRTL = dc.GetLayoutDirection() == wxLayout_RightToLeft;
if (isRTL)
{
// checkbox is not mirrored
cairo_save(cr);
cairo_scale(cr, -1, 1);
x = -x - w;
}
if (gtk_check_version(3,20,0) == NULL)
{
@@ -756,6 +765,9 @@ wxRendererGTK::DrawCheckBox(wxWindow*,
gtk_render_check(sc, cr, x, y, w, h);
gtk_style_context_restore(sc);
}
if (isRTL)
cairo_restore(cr);
#else // !__WXGTK3__
GtkWidget* button = wxGTKPrivate::GetCheckButtonWidget();
@@ -867,10 +879,6 @@ wxRendererGTK::DrawItemSelectionRect(wxWindow* win,
if (flags & wxCONTROL_SELECTED)
{
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
GtkWidget* treeWidget = wxGTKPrivate::GetTreeWidget();
#ifdef __WXGTK3__
@@ -881,9 +889,13 @@ wxRendererGTK::DrawItemSelectionRect(wxWindow* win,
state |= GTK_STATE_FLAG_FOCUSED;
gtk_style_context_set_state(sc, GtkStateFlags(state));
gtk_style_context_add_class(sc, GTK_STYLE_CLASS_CELL);
gtk_render_background(sc, drawable, rect.x - x_diff, rect.y, rect.width, rect.height);
gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height);
gtk_style_context_restore(sc);
#else
int x_diff = 0;
if (win->GetLayoutDirection() == wxLayout_RightToLeft)
x_diff = rect.width;
// the wxCONTROL_FOCUSED state is deduced
// directly from the m_wxwindow by GTK+
gtk_paint_flat_box(gtk_widget_get_style(treeWidget),

View File

@@ -5065,9 +5065,11 @@ bool wxWindowGTK::DoIsExposed( int x, int y ) const
bool wxWindowGTK::DoIsExposed( int x, int y, int w, int h ) const
{
#ifndef __WXGTK3__
if (GetLayoutDirection() == wxLayout_RightToLeft)
return m_updateRegion.Contains(x-w, y, w, h) != wxOutRegion;
else
#endif
return m_updateRegion.Contains(x, y, w, h) != wxOutRegion;
}
@@ -5086,6 +5088,13 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region)
cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
cairo_clip(cr);
}
if (GetLayoutDirection() == wxLayout_RightToLeft)
{
// wxDC is mirrored for RTL
const int w = gdk_window_get_width(gtk_widget_get_window(m_wxwindow));
cairo_translate(cr, w, 0);
cairo_scale(cr, -1, 1);
}
double x1, y1, x2, y2;
cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
@@ -5094,7 +5103,6 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region)
m_paintContext = cr;
m_updateRegion = wxRegion(int(x1), int(y1), int(x2 - x1), int(y2 - y1));
m_nativeUpdateRegion = m_updateRegion;
#else // !__WXGTK3__
m_updateRegion = wxRegion(region);
#if wxGTK_HAS_COMPOSITING_SUPPORT
@@ -5106,6 +5114,7 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region)
m_nativeUpdateRegion = m_updateRegion;
#ifndef __WXGTK3__
if (GetLayoutDirection() == wxLayout_RightToLeft)
{
// Transform m_updateRegion under RTL
@@ -5128,6 +5137,7 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region)
++upd;
}
}
#endif
switch ( GetBackgroundStyle() )
{
@@ -5158,7 +5168,7 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region)
case wxBG_STYLE_ERASE:
{
#ifdef __WXGTK3__
wxGTKCairoDC dc(cr, static_cast<wxWindow*>(this));
wxGTKCairoDC dc(cr, static_cast<wxWindow*>(this), GetLayoutDirection());
#else
wxWindowDC dc( (wxWindow*)this );
dc.SetDeviceClippingRegion( m_updateRegion );