Use wxOverlay to show docking hint instead of transparent wxFrame

The main advantage of using wxOverlay over wxFrame, besides reduced
code complexity, is that the docking hint now works correctly under
Wayland which it didn't work before.

wxAUI_MGR_RECTANGLE_HINT now works everywhere (including wxGTK and wxOSX)

We simply use a bitmap to draw the venetian blinds hint instead of the
exotic and esoteric code using wxRegion.

wxGTK2 now uses graphics context for transparency. If not available, the hint
will be drawn using wxINVERT raster operation mode.
This commit is contained in:
AliKet 2024-01-31 18:39:45 +01:00
parent 7491ac5b7a
commit 29c8bfc249
2 changed files with 135 additions and 419 deletions

View file

@ -497,8 +497,6 @@ public:
protected:
void UpdateHintWindowConfig();
void DoFrameLayout();
void LayoutAddPane(wxSizer* container,
@ -611,7 +609,6 @@ protected:
double m_dockConstraintX; // 0.0 .. 1.0; max pct of window width a dock can consume
double m_dockConstraintY; // 0.0 .. 1.0; max pct of window height a dock can consume
wxFrame* m_hintWnd; // transparent hint window, if supported by platform
wxTimer m_hintFadeTimer; // transparent fade timer
wxByte m_hintFadeAmt; // transparent fade amount
wxByte m_hintFadeMax; // maximum value of hint fade

View file

@ -33,7 +33,7 @@
#include "wx/settings.h"
#include "wx/app.h"
#include "wx/dcclient.h"
#include "wx/dcscreen.h"
#include "wx/dcmemory.h"
#include "wx/toolbar.h"
#include "wx/image.h"
#include "wx/statusbr.h"
@ -64,6 +64,8 @@ wxDEFINE_EVENT( wxEVT_AUI_FIND_MANAGER, wxAuiManagerEvent );
#include "wx/msw/dc.h"
#endif
#include "wx/dcgraph.h"
#include <memory>
wxIMPLEMENT_DYNAMIC_CLASS(wxAuiManagerEvent, wxEvent);
@ -73,202 +75,18 @@ wxIMPLEMENT_CLASS(wxAuiManager, wxEvtHandler);
const int auiToolBarLayer = 10;
#ifndef __WXGTK__
class wxPseudoTransparentFrame : public wxFrame
{
public:
wxPseudoTransparentFrame(wxWindow* parent = nullptr,
wxWindowID id = wxID_ANY,
const wxString& title = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxDEFAULT_FRAME_STYLE,
const wxString &name = wxT("frame"))
: wxFrame(parent, id, title, pos, size, style | wxFRAME_SHAPED, name)
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
m_amount=0;
m_maxWidth=0;
m_maxHeight=0;
m_lastWidth=0;
m_lastHeight=0;
m_canSetShape = true;
m_region = wxRegion(0, 0, 0, 0);
SetTransparent(0);
}
virtual bool SetTransparent(wxByte alpha) override
{
if (m_canSetShape)
{
wxSize size = GetClientSize();
m_maxWidth = size.x;
m_maxHeight = size.y;
m_amount = alpha;
m_region.Clear();
// m_region.Union(0, 0, 1, m_maxWidth);
if (m_amount)
{
for (int y=0; y<m_maxHeight; y++)
{
// Reverse the order of the bottom 4 bits
int j=((y&8)?1:0)|((y&4)?2:0)|((y&2)?4:0)|((y&1)?8:0);
if ((j*16+8)<m_amount)
m_region.Union(0, y, m_maxWidth, 1);
}
}
SetShape(m_region);
Refresh();
}
return true;
}
void OnPaint(wxPaintEvent& WXUNUSED(event))
{
wxPaintDC dc(this);
if (m_region.IsEmpty())
return;
#ifdef __WXMAC__
dc.SetBrush(wxColour(128, 192, 255));
#else
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_ACTIVECAPTION));
#endif
dc.SetPen(*wxTRANSPARENT_PEN);
wxRegionIterator upd(GetUpdateRegion()); // get the update rect list
while (upd)
{
wxRect rect(upd.GetRect());
dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height);
++upd;
}
}
void OnSize(wxSizeEvent& event)
{
// We sometimes get surplus size events
if ((event.GetSize().GetWidth() == m_lastWidth) &&
(event.GetSize().GetHeight() == m_lastHeight))
{
event.Skip();
return;
}
m_lastWidth = event.GetSize().GetWidth();
m_lastHeight = event.GetSize().GetHeight();
SetTransparent(m_amount);
m_region.Intersect(0, 0, event.GetSize().GetWidth(),
event.GetSize().GetHeight());
SetShape(m_region);
Refresh();
event.Skip();
}
private:
wxByte m_amount;
int m_maxWidth;
int m_maxHeight;
bool m_canSetShape;
int m_lastWidth,m_lastHeight;
wxRegion m_region;
wxDECLARE_DYNAMIC_CLASS(wxPseudoTransparentFrame);
wxDECLARE_EVENT_TABLE();
};
wxIMPLEMENT_DYNAMIC_CLASS(wxPseudoTransparentFrame, wxFrame);
wxBEGIN_EVENT_TABLE(wxPseudoTransparentFrame, wxFrame)
EVT_PAINT(wxPseudoTransparentFrame::OnPaint)
EVT_SIZE(wxPseudoTransparentFrame::OnSize)
wxEND_EVENT_TABLE()
#else // __WXGTK__
#include "wx/gtk/private/wrapgtk.h"
static void
gtk_pseudo_window_realized_callback( GtkWidget *m_widget, void *WXUNUSED(win) )
{
wxSize disp = wxGetDisplaySize();
int amount = 128;
wxRegion region;
for (int y=0; y<disp.y; y++)
{
// Reverse the order of the bottom 4 bits
int j=((y&8)?1:0)|((y&4)?2:0)|((y&2)?4:0)|((y&1)?8:0);
if ((j*16+8)<amount)
region.Union(0, y, disp.x, 1);
}
gdk_window_shape_combine_region(gtk_widget_get_window(m_widget), region.GetRegion(), 0, 0);
}
class wxPseudoTransparentFrame: public wxFrame
{
public:
wxPseudoTransparentFrame(wxWindow* parent = nullptr,
wxWindowID id = wxID_ANY,
const wxString& title = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxDEFAULT_FRAME_STYLE,
const wxString &name = wxT("frame"))
{
if (!CreateBase( parent, id, pos, size, style, wxDefaultValidator, name ))
return;
m_title = title;
m_widget = gtk_window_new( GTK_WINDOW_POPUP );
g_object_ref(m_widget);
if (parent) parent->AddChild(this);
g_signal_connect( m_widget, "realize",
G_CALLBACK (gtk_pseudo_window_realized_callback), this );
m_backgroundColour.Set(128, 192, 255);
GTKApplyWidgetStyle();
}
bool SetTransparent(wxByte WXUNUSED(alpha)) override
{
return true;
}
protected:
virtual void DoSetSizeHints( int minW, int minH,
int maxW, int maxH,
int incW, int incH) override
{
// the real wxFrame method doesn't work for us because we're not really
// a top level window so skip it
wxWindow::DoSetSizeHints(minW, minH, maxW, maxH, incW, incH);
}
private:
wxDECLARE_DYNAMIC_CLASS(wxPseudoTransparentFrame);
};
wxIMPLEMENT_DYNAMIC_CLASS(wxPseudoTransparentFrame, wxFrame);
#endif // !__WXGTK__/__WXGTK__
// -- static utility functions --
static wxBitmap wxCreateVenetianBlindsBitmap(wxByte r, wxByte g, wxByte b, wxByte a)
{
unsigned char data[] = { r,g,b, 0,0,0, r,g,b };
unsigned char alpha[] = { a, 128, a };
wxImage img(1,3,data,true);
img.SetAlpha(alpha,true);
return wxBitmap(img);
}
static wxBitmap wxPaneCreateStippleBitmap()
{
// Notice that wxOverlay, under wxMSW, uses the wxBLACK colour i.e.(0,0,0)
@ -586,12 +404,13 @@ wxAuiManager::wxAuiManager(wxWindow* managed_wnd, unsigned int flags)
m_actionWindow = nullptr;
m_hoverButton = nullptr;
m_art = new wxAuiDefaultDockArt;
m_hintWnd = nullptr;
m_flags = flags;
m_hasMaximized = false;
m_frame = nullptr;
m_dockConstraintX = 0.3;
m_dockConstraintY = 0.3;
m_hintFadeMax = 128;
m_reserved = nullptr;
m_currentDragItem = -1;
@ -736,22 +555,8 @@ wxAuiDockUIPart* wxAuiManager::HitTest(int x, int y)
// options which are global to wxAuiManager
void wxAuiManager::SetFlags(unsigned int flags)
{
// find out if we have to call UpdateHintWindowConfig()
bool update_hint_wnd = false;
unsigned int hint_mask = wxAUI_MGR_TRANSPARENT_HINT |
wxAUI_MGR_VENETIAN_BLINDS_HINT |
wxAUI_MGR_RECTANGLE_HINT;
if ((flags & hint_mask) != (m_flags & hint_mask))
update_hint_wnd = true;
// set the new flags
m_flags = flags;
if (update_hint_wnd)
{
UpdateHintWindowConfig();
}
}
unsigned int wxAuiManager::GetFlags() const
@ -766,8 +571,7 @@ unsigned int wxAuiManager::GetFlags() const
bool wxAuiManager::HasLiveResize() const
{
return AlwaysUsesLiveResize(m_frame) ||
(GetFlags() & wxAUI_MGR_LIVE_RESIZE) == wxAUI_MGR_LIVE_RESIZE;
return (GetFlags() & wxAUI_MGR_LIVE_RESIZE) == wxAUI_MGR_LIVE_RESIZE;
}
// don't use these anymore as they are deprecated
@ -800,79 +604,6 @@ wxAuiManager* wxAuiManager::GetManager(wxWindow* window)
}
void wxAuiManager::UpdateHintWindowConfig()
{
// find out if the system can do transparent frames
bool can_do_transparent = false;
wxWindow* w = m_frame;
while (w)
{
if (wxDynamicCast(w, wxFrame))
{
wxFrame* f = static_cast<wxFrame*>(w);
can_do_transparent = f->CanSetTransparent();
break;
}
w = w->GetParent();
}
// if there is an existing hint window, delete it
if (m_hintWnd)
{
m_hintWnd->Destroy();
m_hintWnd = nullptr;
}
m_hintFadeMax = 50;
m_hintWnd = nullptr;
if ((m_flags & wxAUI_MGR_TRANSPARENT_HINT) && can_do_transparent)
{
// Make a window to use for a transparent hint
m_hintWnd = new wxFrame(m_frame, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxSize(1,1),
wxFRAME_TOOL_WINDOW |
wxFRAME_FLOAT_ON_PARENT |
wxFRAME_NO_TASKBAR |
wxNO_BORDER);
#ifdef __WXMAC__
// Do nothing so this event isn't handled in the base handlers.
// Letting the hint window activate without this handler can lead to
// weird behaviour on Mac where the menu is switched out to the top
// window's menu in MDI applications when it shouldn't be. So since
// we don't want user interaction with the hint window anyway, we just
// prevent it from activating here.
m_hintWnd->Bind(wxEVT_ACTIVATE, [](wxActivateEvent&) {});
#endif
m_hintWnd->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HOTLIGHT));
}
else
{
if ((m_flags & wxAUI_MGR_TRANSPARENT_HINT) != 0 ||
(m_flags & wxAUI_MGR_VENETIAN_BLINDS_HINT) != 0)
{
// system can't support transparent fade, or the venetian
// blinds effect was explicitly requested
m_hintWnd = new wxPseudoTransparentFrame(m_frame,
wxID_ANY,
wxEmptyString,
wxDefaultPosition,
wxSize(1,1),
wxFRAME_TOOL_WINDOW |
wxFRAME_FLOAT_ON_PARENT |
wxFRAME_NO_TASKBAR |
wxNO_BORDER);
m_hintFadeMax = 128;
}
}
}
// SetManagedWindow() is usually called once when the frame
// manager class is being initialized. "frame" specifies
// the frame which should be managed by the frame manager
@ -913,8 +644,6 @@ void wxAuiManager::SetManagedWindow(wxWindow* wnd)
}
#endif
UpdateHintWindowConfig();
}
@ -3269,10 +2998,9 @@ bool wxAuiManager::DoDrop(wxAuiDockInfoArray& docks,
return false;
}
void wxAuiManager::OnHintFadeTimer(wxTimerEvent& WXUNUSED(event))
{
if (!m_hintWnd || m_hintFadeAmt >= m_hintFadeMax)
if (m_hintFadeAmt >= m_hintFadeMax)
{
m_hintFadeTimer.Stop();
Unbind(wxEVT_TIMER, &wxAuiManager::OnHintFadeTimer, this,
@ -3281,132 +3009,114 @@ void wxAuiManager::OnHintFadeTimer(wxTimerEvent& WXUNUSED(event))
}
m_hintFadeAmt++;
m_hintWnd->SetTransparent(m_hintFadeAmt);
ShowHint(m_lastHint);
}
void wxAuiManager::ShowHint(const wxRect& rect)
{
if (m_hintWnd)
wxClientDC dc(m_frame);
wxDCOverlay overlaydc(m_overlay, &dc);
overlaydc.Clear();
wxDCClipper clip(dc, rect);
if ( m_flags & wxAUI_MGR_RECTANGLE_HINT )
{
// if the hint rect is the same as last time, don't do anything
if (m_lastHint == rect)
return;
m_lastHint = rect;
// Decide if we want to fade in the hint and set it to the end value if
// we don't.
if ((m_flags & wxAUI_MGR_HINT_FADE)
&& !((m_flags & wxAUI_MGR_VENETIAN_BLINDS_HINT) &&
(m_flags & wxAUI_MGR_NO_VENETIAN_BLINDS_FADE))
)
m_hintFadeAmt = 0;
else
m_hintFadeAmt = m_hintFadeMax;
m_hintWnd->SetSize(rect);
m_hintWnd->SetTransparent(m_hintFadeAmt);
if (!m_hintWnd->IsShown())
m_hintWnd->Show();
// if we are dragging a floating pane, set the focus
// back to that floating pane (otherwise it becomes unfocused)
if (m_action == actionDragFloatingPane && m_actionWindow)
m_actionWindow->SetFocus();
m_hintWnd->Raise();
if (m_hintFadeAmt != m_hintFadeMax) // Only fade if we need to
{
// start fade in timer
m_hintFadeTimer.SetOwner(this);
m_hintFadeTimer.Start(15);
Bind(wxEVT_TIMER, &wxAuiManager::OnHintFadeTimer, this,
m_hintFadeTimer.GetId());
}
}
else // Not using a transparent hint window...
{
if (!(m_flags & wxAUI_MGR_RECTANGLE_HINT))
return;
if (m_lastHint != rect)
{
// remove the last hint rectangle
m_lastHint = rect;
m_frame->Refresh();
m_frame->Update();
}
wxScreenDC screendc;
wxRegion clip(1, 1, 10000, 10000);
// clip all floating windows, so we don't draw over them
int i, pane_count;
for (i = 0, pane_count = m_panes.GetCount(); i < pane_count; ++i)
{
wxAuiPaneInfo& pane = m_panes.Item(i);
if (pane.IsFloating() &&
pane.frame &&
pane.frame->IsShown())
{
wxRect r = pane.frame->GetRect();
#ifdef __WXGTK__
// wxGTK returns the client size, not the whole frame size
r.width += pane.frame->FromDIP(15);
r.height += pane.frame->FromDIP(35);
r.Inflate(pane.frame->FromDIP(wxSize(5, 5)));
#endif
clip.Subtract(r);
}
}
// As we can only hide the hint by redrawing the managed window, we
// need to clip the region to the managed window too or we get
// nasty redrawn problems.
clip.Intersect(m_frame->GetRect());
screendc.SetDeviceClippingRegion(clip);
// Not using a transparent hint window...
wxBitmap stipple = wxPaneCreateStippleBitmap();
wxBrush brush(stipple);
screendc.SetBrush(brush);
screendc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(brush);
dc.SetPen(*wxTRANSPARENT_PEN);
screendc.DrawRectangle(rect.x, rect.y, m_frame->FromDIP(5), rect.height);
screendc.DrawRectangle(rect.x + m_frame->FromDIP(5), rect.y, rect.width - m_frame->FromDIP(10), m_frame->FromDIP(5));
screendc.DrawRectangle(rect.x + rect.width - m_frame->FromDIP(5), rect.y, m_frame->FromDIP(5), rect.height);
screendc.DrawRectangle(rect.x + m_frame->FromDIP(5), rect.y + rect.height - m_frame->FromDIP(5), rect.width - m_frame->FromDIP(10), m_frame->FromDIP(5));
const int d = m_frame->FromDIP(5);
const int dd = m_frame->FromDIP(10);
dc.DrawRectangle(rect.x, rect.y, d, rect.height);
dc.DrawRectangle(rect.x + d, rect.y, rect.width - dd, d);
dc.DrawRectangle(rect.x + rect.width - d, rect.y, d, rect.height);
dc.DrawRectangle(rect.x + d, rect.y + rect.height - d, rect.width - dd, d);
return;
}
#ifdef __WXGTK3__
// The standard DC under wxGTK3 supports alpha drawing, whether the overlay
// is native (Wayland) or generic (X11).
const bool canDrawTransparentHint = true;
#else
const bool canDrawTransparentHint = m_overlay.IsNative();
#endif
const auto hintCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HOTLIGHT);
const unsigned char r = hintCol.GetRed();
const unsigned char g = hintCol.GetGreen();
const unsigned char b = hintCol.GetBlue();
const unsigned char a = m_hintFadeAmt;
const auto makeBrush = [=]()
{
return (m_flags & wxAUI_MGR_VENETIAN_BLINDS_HINT) != 0
? wxCreateVenetianBlindsBitmap(r, g, b, a)
: wxBrush(wxColour(r, g, b, a));
};
if ( canDrawTransparentHint )
{
#ifdef __WXMSW__
m_overlay.SetOpacity(m_hintFadeAmt);
#endif // __WXMSW__
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(makeBrush());
dc.DrawRectangle(rect);
}
else
{
wxBitmap bmp(rect.GetSize(), 32);
wxMemoryDC mdc(bmp);
mdc.Blit(0, 0, rect.width, rect.height, &dc, rect.x, rect.y);
wxRasterOperationMode logicalFunc = wxINVERT;
#if wxUSE_GRAPHICS_CONTEXT
{
wxGCDC gdc(mdc);
gdc.SetPen(*wxTRANSPARENT_PEN);
gdc.SetBrush(makeBrush());
gdc.DrawRectangle(wxPoint(0, 0), rect.GetSize());
}
logicalFunc = wxCOPY;
#endif // wxUSE_GRAPHICS_CONTEXT
dc.Blit(rect.x, rect.y, rect.width, rect.height, &mdc, 0, 0, logicalFunc);
}
// if we are dragging a floating pane, set the focus
// back to that floating pane (otherwise it becomes unfocused)
if (m_action == actionDragFloatingPane && m_actionWindow)
m_actionWindow->SetFocus();
if (m_hintFadeAmt != m_hintFadeMax) // Only fade if we need to
{
// start fade in timer
m_hintFadeTimer.SetOwner(this);
m_hintFadeTimer.Start(10);
Bind(wxEVT_TIMER, &wxAuiManager::OnHintFadeTimer, this,
m_hintFadeTimer.GetId());
}
}
void wxAuiManager::HideHint()
{
// hides a transparent window hint, if there is one
if (m_hintWnd)
{
if (m_hintWnd->IsShown())
m_hintWnd->Show(false);
m_hintWnd->SetTransparent(0);
m_hintFadeTimer.Stop();
// In case this is called while a hint fade is going, we need to
// disconnect the event handler.
Unbind(wxEVT_TIMER, &wxAuiManager::OnHintFadeTimer, this,
m_hintFadeTimer.GetId());
m_lastHint = wxRect();
return;
}
m_overlay.Reset();
m_hintFadeTimer.Stop();
// In case this is called while a hint fade is going, we need to
// disconnect the event handler.
Unbind(wxEVT_TIMER, &wxAuiManager::OnHintFadeTimer, this,
m_hintFadeTimer.GetId());
// hides a painted hint by redrawing the frame window
if (!m_lastHint.IsEmpty())
{
m_frame->Refresh();
m_frame->Update();
m_lastHint = wxRect();
}
m_lastHint = wxRect();
}
@ -3445,7 +3155,7 @@ void wxAuiManager::StartPaneDrag(wxWindow* pane_window,
// first calls DoDrop() to determine the exact position the pane would
// be at were if dropped. If the pane would indeed become docked at the
// specified drop point, the rectangle hint will be returned in
// screen coordinates. Otherwise, an empty rectangle is returned.
// client coordinates. Otherwise, an empty rectangle is returned.
// |pane_window| is the window pointer of the pane being dragged, |pt| is
// the mouse position, in client coordinates. |offset| describes the offset
// that the mouse is from the upper-left corner of the item being dragged
@ -3516,18 +3226,15 @@ wxRect wxAuiManager::CalculateHintRect(wxWindow* pane_window,
delete sizer;
if (rect.IsEmpty())
if ( !rect.IsEmpty() )
{
return rect;
}
rect.Offset( m_frame->GetClientAreaOrigin() );
// actually show the hint rectangle on the screen
m_frame->ClientToScreen(&rect.x, &rect.y);
if ( m_frame->GetLayoutDirection() == wxLayout_RightToLeft )
{
// Mirror rectangle in RTL mode
rect.x -= rect.GetWidth();
if ( m_frame->GetLayoutDirection() == wxLayout_RightToLeft )
{
// Mirror rectangle in RTL mode
rect.x -= rect.GetWidth();
}
}
return rect;
@ -3547,10 +3254,22 @@ void wxAuiManager::DrawHintRect(wxWindow* pane_window,
{
HideHint();
}
else
else if (m_lastHint != rect) // if the hint rect is the same as last time, don't do anything
{
ShowHint(rect);
}
m_lastHint = rect;
// Decide if we want to fade in the hint and set it to the end value if
// we don't.
if ((m_flags & wxAUI_MGR_HINT_FADE)
&& !((m_flags & wxAUI_MGR_VENETIAN_BLINDS_HINT) &&
(m_flags & wxAUI_MGR_NO_VENETIAN_BLINDS_FADE))
)
m_hintFadeAmt = 0;
else
m_hintFadeAmt = m_hintFadeMax;
ShowHint(rect);
}
}
void wxAuiManager::OnFloatingPaneMoveStart(wxWindow* wnd)
@ -3886,7 +3605,7 @@ void wxAuiManager::Repaint(wxDC* dc)
// make a client dc
if (!dc)
{
if ( AlwaysUsesLiveResize(m_frame) )
if ( !wxClientDC::CanBeUsedForDrawing(m_frame) )
{
// We can't use wxClientDC in these ports.
m_frame->Refresh() ;