Add wxActivityIndicator control.
This is a simple animated control indicating some program activity. Provide native GTK+ (for > 2.20) and OS X implementations as well as a generic one used under MSW. Update the sample and the documentation.
This commit is contained in:
260
src/generic/activityindicator.cpp
Normal file
260
src/generic/activityindicator.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: src/generic/activityindicator.cpp
|
||||
// Purpose: Generic wxActivityIndicator implementation.
|
||||
// Author: Vadim Zeitlin
|
||||
// Created: 2015-03-06
|
||||
// Copyright: (c) 2015 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ============================================================================
|
||||
// declarations
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// headers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// for compilers that support precompilation, includes "wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#if wxUSE_ACTIVITYINDICATOR && !defined(__WXGTK3__)
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/timer.h"
|
||||
#endif // WX_PRECOMP
|
||||
|
||||
#include "wx/activityindicator.h"
|
||||
|
||||
#include "wx/graphics.h"
|
||||
#include "wx/scopedptr.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// constants
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// For now the appearance is fixed, we could make these constants customizable
|
||||
// later if really needed.
|
||||
namespace
|
||||
{
|
||||
|
||||
// Total number of "running" dots.
|
||||
static const int NUM_DOTS = 8;
|
||||
|
||||
// Delay between the consecutive updates in milliseconds.
|
||||
static const int FRAME_DELAY = 150;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxActivityIndicatorImpl: class containing the real implementation.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class wxActivityIndicatorImpl
|
||||
{
|
||||
public:
|
||||
explicit wxActivityIndicatorImpl(wxWindow* win)
|
||||
: m_timer(this),
|
||||
m_win(win)
|
||||
{
|
||||
m_frame = 0;
|
||||
|
||||
win->Bind(wxEVT_PAINT, &wxActivityIndicatorImpl::OnPaint, this);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Avoid restarting the timer if it's already running, this could
|
||||
// result in jumps in appearance of the next frame while calling
|
||||
// Start() is not supposed to have any effect at all in this case.
|
||||
if ( m_timer.IsRunning() )
|
||||
return;
|
||||
|
||||
m_timer.Start(FRAME_DELAY);
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
// Unlike above, it's not a problem to call Stop() even if we're not
|
||||
// running.
|
||||
m_timer.Stop();
|
||||
}
|
||||
|
||||
bool IsRunning() const
|
||||
{
|
||||
return m_timer.IsRunning();
|
||||
}
|
||||
|
||||
// This one is only called by AdvanceTimer.
|
||||
void Advance()
|
||||
{
|
||||
if ( ++m_frame == NUM_DOTS )
|
||||
m_frame = 0;
|
||||
|
||||
m_win->Refresh();
|
||||
}
|
||||
|
||||
private:
|
||||
class AdvanceTimer : public wxTimer
|
||||
{
|
||||
public:
|
||||
explicit AdvanceTimer(wxActivityIndicatorImpl* owner)
|
||||
: m_owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void Notify() wxOVERRIDE
|
||||
{
|
||||
m_owner->Advance();
|
||||
}
|
||||
|
||||
private:
|
||||
wxActivityIndicatorImpl* const m_owner;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(AdvanceTimer);
|
||||
};
|
||||
|
||||
void OnPaint(wxPaintEvent& WXUNUSED(event))
|
||||
{
|
||||
wxPaintDC pdc(m_win);
|
||||
|
||||
wxScopedPtr<wxGraphicsContext> const
|
||||
gc(wxGraphicsRenderer::GetDefaultRenderer()->CreateContext(pdc));
|
||||
|
||||
const wxSize size = m_win->GetClientSize();
|
||||
|
||||
// Centre everything.
|
||||
gc->Translate(size.x/2., size.y/2.);
|
||||
|
||||
// Radius of 1/10th allows to have reasonably sized dots with a bit of
|
||||
// separation between them and so subjectively looks a bit nicer than
|
||||
// perhaps more natural 1/8th.
|
||||
static const double RADIUS_FACTOR = 10;
|
||||
|
||||
const double r = wxMin(size.x, size.y) / RADIUS_FACTOR;
|
||||
|
||||
// The initial dot touches the top border.
|
||||
wxGraphicsPath path = gc->CreatePath();
|
||||
path.AddCircle(0, -(RADIUS_FACTOR / 2. - 1.)*r, r);
|
||||
|
||||
// Subsequent dots are rotated by this angle with respect to the
|
||||
// previous one.
|
||||
const double angle = wxDegToRad(360. / NUM_DOTS);
|
||||
|
||||
// And the animation effect is achieved just by starting to draw from
|
||||
// the next position every time.
|
||||
gc->Rotate(m_frame*angle);
|
||||
|
||||
const bool isEnabled = m_win->IsEnabled();
|
||||
for ( int n = 0; n < NUM_DOTS; n++ )
|
||||
{
|
||||
// Draw all dots uniformly grey when the window is disabled,
|
||||
// otherwise each subsequent dot is slightly more opaque.
|
||||
const int opacityIndex = isEnabled ? n + 1 : 2;
|
||||
|
||||
// wxALPHA_OPAQUE+1 is used just because it is divisible by the
|
||||
// default NUM_DOTS value, and we need -1 because of this to keep
|
||||
// it in 0..wxALPHA_OPAQUE range.
|
||||
const int opacity = opacityIndex*(wxALPHA_OPAQUE + 1)/NUM_DOTS - 1;
|
||||
|
||||
gc->SetBrush(wxBrush(wxColour(0, 0, 0, opacity)));
|
||||
|
||||
gc->FillPath(path);
|
||||
gc->Rotate(angle);
|
||||
}
|
||||
}
|
||||
|
||||
AdvanceTimer m_timer;
|
||||
wxWindow* const m_win;
|
||||
|
||||
int m_frame;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(wxActivityIndicatorImpl);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
// ============================================================================
|
||||
|
||||
#ifndef wxHAS_NATIVE_ACTIVITYINDICATOR
|
||||
wxIMPLEMENT_DYNAMIC_CLASS(wxActivityIndicator, wxControl);
|
||||
#endif
|
||||
|
||||
bool
|
||||
wxActivityIndicatorGeneric::Create(wxWindow* parent,
|
||||
wxWindowID winid,
|
||||
const wxPoint& pos,
|
||||
const wxSize& size,
|
||||
long style,
|
||||
const wxString& name)
|
||||
{
|
||||
// Notice that we skip wxControl version, we don't need the validator
|
||||
// support that it adds.
|
||||
if ( !wxWindow::Create(parent, winid, pos, size, style, name) )
|
||||
return false;
|
||||
|
||||
m_impl = new wxActivityIndicatorImpl(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxActivityIndicatorGeneric::~wxActivityIndicatorGeneric()
|
||||
{
|
||||
delete m_impl;
|
||||
}
|
||||
|
||||
void wxActivityIndicatorGeneric::Start()
|
||||
{
|
||||
wxCHECK_RET( m_impl, wxS("Must be created first") );
|
||||
|
||||
m_impl->Start();
|
||||
}
|
||||
|
||||
void wxActivityIndicatorGeneric::Stop()
|
||||
{
|
||||
wxCHECK_RET( m_impl, wxS("Must be created first") );
|
||||
|
||||
m_impl->Stop();
|
||||
}
|
||||
|
||||
bool wxActivityIndicatorGeneric::IsRunning() const
|
||||
{
|
||||
return m_impl && m_impl->IsRunning();
|
||||
}
|
||||
|
||||
wxSize wxActivityIndicatorGeneric::DoGetBestClientSize() const
|
||||
{
|
||||
int size = 0;
|
||||
switch ( GetWindowVariant() )
|
||||
{
|
||||
case wxWINDOW_VARIANT_MAX:
|
||||
wxFAIL_MSG(wxS("Invalid window variant"));
|
||||
wxFALLTHROUGH;
|
||||
|
||||
case wxWINDOW_VARIANT_NORMAL:
|
||||
size = 24;
|
||||
break;
|
||||
|
||||
case wxWINDOW_VARIANT_SMALL:
|
||||
size = 16;
|
||||
break;
|
||||
|
||||
case wxWINDOW_VARIANT_MINI:
|
||||
size = 12;
|
||||
break;
|
||||
|
||||
case wxWINDOW_VARIANT_LARGE:
|
||||
size = 32;
|
||||
break;
|
||||
}
|
||||
|
||||
wxASSERT_MSG( size, wxS("Unknown window variant") );
|
||||
|
||||
return FromDIP(wxSize(size, size));
|
||||
}
|
||||
|
||||
#endif // wxUSE_ACTIVITYINDICATOR && !__WXGTK3__
|
||||
168
src/gtk/activityindicator.cpp
Normal file
168
src/gtk/activityindicator.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: src/gtk/activityindicator.cpp
|
||||
// Purpose: wxActivityIndicator implementation for wxGTK.
|
||||
// Author: Vadim Zeitlin
|
||||
// Created: 2015-03-05
|
||||
// Copyright: (c) 2015 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ============================================================================
|
||||
// declarations
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// headers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// for compilers that support precompilation, includes "wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#if wxUSE_ACTIVITYINDICATOR && defined(__WXGTK220__)
|
||||
|
||||
#include "wx/activityindicator.h"
|
||||
|
||||
#include "wx/math.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
// Macro return the specified expression only if GTK+ run time version is less
|
||||
// than 2.20 and compiling it only if it is less than 3.0 (which is why this
|
||||
// has to be a macro and not a function).
|
||||
#if defined(__WXGTK220__) && !defined(__WXGTK3__)
|
||||
#define RETURN_IF_NO_GTK_SPINNER(expr) \
|
||||
if ( gtk_check_version(2, 20, 0) != 0 ) { return expr; }
|
||||
#else
|
||||
#define RETURN_IF_NO_GTK_SPINNER(expr)
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
// ============================================================================
|
||||
|
||||
wxIMPLEMENT_DYNAMIC_CLASS(wxActivityIndicator, wxControl);
|
||||
|
||||
bool
|
||||
wxActivityIndicator::Create(wxWindow* parent,
|
||||
wxWindowID winid,
|
||||
const wxPoint& pos,
|
||||
const wxSize& size,
|
||||
long style,
|
||||
const wxString& name)
|
||||
{
|
||||
RETURN_IF_NO_GTK_SPINNER(
|
||||
wxActivityIndicatorGeneric::Create(parent, winid, pos, size, style, name)
|
||||
)
|
||||
|
||||
if ( !PreCreation(parent, pos, size) ||
|
||||
!CreateBase(parent, winid, pos, size, style, name) )
|
||||
return false;
|
||||
|
||||
m_widget = gtk_spinner_new();
|
||||
g_object_ref(m_widget);
|
||||
|
||||
m_parent->DoAddChild(this);
|
||||
|
||||
PostCreation(size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxActivityIndicator::Start()
|
||||
{
|
||||
RETURN_IF_NO_GTK_SPINNER( wxActivityIndicatorGeneric::Start() )
|
||||
|
||||
wxCHECK_RET( m_widget, wxS("Must be created first") );
|
||||
|
||||
gtk_spinner_start(GTK_SPINNER(m_widget));
|
||||
}
|
||||
|
||||
void wxActivityIndicator::Stop()
|
||||
{
|
||||
RETURN_IF_NO_GTK_SPINNER( wxActivityIndicatorGeneric::Stop() )
|
||||
|
||||
wxCHECK_RET( m_widget, wxS("Must be created first") );
|
||||
|
||||
gtk_spinner_stop(GTK_SPINNER(m_widget));
|
||||
}
|
||||
|
||||
bool wxActivityIndicator::IsRunning() const
|
||||
{
|
||||
RETURN_IF_NO_GTK_SPINNER( wxActivityIndicatorGeneric::IsRunning() )
|
||||
|
||||
if ( !m_widget )
|
||||
return false;
|
||||
|
||||
gboolean b;
|
||||
g_object_get(m_widget, "active", &b, NULL);
|
||||
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
wxSize wxActivityIndicator::DoGetBestClientSize() const
|
||||
{
|
||||
RETURN_IF_NO_GTK_SPINNER( wxActivityIndicatorGeneric::DoGetBestClientSize() )
|
||||
|
||||
if ( !m_widget )
|
||||
return wxDefaultSize;
|
||||
|
||||
gint w, h;
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
// gtk_widget_get_preferred_size() seems to return the size based on the
|
||||
// current size of the widget and also always returns 0 if it is hidden,
|
||||
// so ask GtkSpinner for its preferred size directly instead of using it.
|
||||
GtkWidgetClass* const wc = GTK_WIDGET_GET_CLASS(m_widget);
|
||||
|
||||
// We're not interested in the natural size (and it's the same as minimal
|
||||
// one anyhow currently), but we still need a non-NULL pointer for it.
|
||||
gint dummy;
|
||||
wc->get_preferred_width(m_widget, &w, &dummy);
|
||||
wc->get_preferred_height(m_widget, &h, &dummy);
|
||||
#else // GTK+ 2
|
||||
// GTK+ 2 doesn't return any valid preferred size for this control, so we
|
||||
// use the size of something roughly equivalent to it.
|
||||
gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &w, &h);
|
||||
#endif // GTK+ 3/2
|
||||
|
||||
// Adjust for the window variant: note that the default size in GTK+ 3 is
|
||||
// really small (12px until ~3.10, 16px since then), so we use this size as
|
||||
// the small size and double it for the normal size.
|
||||
double factor = 0.;
|
||||
switch ( GetWindowVariant() )
|
||||
{
|
||||
case wxWINDOW_VARIANT_MAX:
|
||||
wxFAIL_MSG(wxS("Invalid window variant"));
|
||||
wxFALLTHROUGH;
|
||||
|
||||
case wxWINDOW_VARIANT_NORMAL:
|
||||
factor = 2.;
|
||||
break;
|
||||
|
||||
case wxWINDOW_VARIANT_SMALL:
|
||||
factor = 1.;
|
||||
break;
|
||||
|
||||
case wxWINDOW_VARIANT_MINI:
|
||||
factor = 0.75;
|
||||
break;
|
||||
|
||||
case wxWINDOW_VARIANT_LARGE:
|
||||
// GTK+ 3.11+ limits GtkSpinner size to twice its minimal size, so
|
||||
// the effective factor here is actually just 2, i.e. the same as
|
||||
// for the normal size, but use something larger just in case GTK+
|
||||
// changes its mind again later.
|
||||
factor = 2.5;
|
||||
break;
|
||||
}
|
||||
|
||||
wxASSERT_MSG( !wxIsSameDouble(factor, 0), wxS("Unknown window variant") );
|
||||
|
||||
return wxSize(wxRound(w*factor), wxRound(h*factor));
|
||||
}
|
||||
|
||||
#endif // wxUSE_ACTIVITYINDICATOR
|
||||
111
src/osx/cocoa/activityindicator.mm
Normal file
111
src/osx/cocoa/activityindicator.mm
Normal file
@@ -0,0 +1,111 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: src/osx/cocoa/activityindicator.cpp
|
||||
// Purpose: wxActivityIndicator implementation for wxOSX/Cocoa.
|
||||
// Author: Vadim Zeitlin
|
||||
// Created: 2015-03-08
|
||||
// Copyright: (c) 2015 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ============================================================================
|
||||
// declarations
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// headers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// for compilers that support precompilation, includes "wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#if wxUSE_ACTIVITYINDICATOR
|
||||
|
||||
#include "wx/activityindicator.h"
|
||||
|
||||
#include "wx/osx/private.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// local helpers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Return the NSProgressIndicator from the given peer.
|
||||
inline
|
||||
NSProgressIndicator *GetProgressIndicator(wxWidgetImpl* peer)
|
||||
{
|
||||
return peer ? static_cast<NSProgressIndicator*>(peer->GetWXWidget())
|
||||
: NULL;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
// ============================================================================
|
||||
|
||||
wxIMPLEMENT_DYNAMIC_CLASS(wxActivityIndicator, wxControl);
|
||||
|
||||
bool
|
||||
wxActivityIndicator::Create(wxWindow* parent,
|
||||
wxWindowID winid,
|
||||
const wxPoint& pos,
|
||||
const wxSize& size,
|
||||
long style,
|
||||
const wxString& name)
|
||||
{
|
||||
DontCreatePeer();
|
||||
|
||||
// Notice that we skip wxControl version, we don't need the validator
|
||||
// support that it adds.
|
||||
if ( !wxWindow::Create(parent, winid, pos, size, style, name) )
|
||||
return false;
|
||||
|
||||
NSRect r = wxOSXGetFrameForControl(this, pos , size);
|
||||
NSProgressIndicator* w = [[NSProgressIndicator alloc] initWithFrame:r];
|
||||
[w setStyle: NSProgressIndicatorSpinningStyle];
|
||||
|
||||
SetPeer(new wxWidgetCocoaImpl(this, w));
|
||||
|
||||
MacPostControlCreate(pos, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxActivityIndicator::Start()
|
||||
{
|
||||
if ( m_isRunning )
|
||||
return;
|
||||
|
||||
NSProgressIndicator* const ind = GetProgressIndicator(GetPeer());
|
||||
wxCHECK_RET( ind, wxS("Must be created first") );
|
||||
|
||||
[ind startAnimation:nil];
|
||||
|
||||
m_isRunning = true;
|
||||
}
|
||||
|
||||
void wxActivityIndicator::Stop()
|
||||
{
|
||||
if ( !m_isRunning )
|
||||
return;
|
||||
|
||||
NSProgressIndicator* const ind = GetProgressIndicator(GetPeer());
|
||||
wxCHECK_RET( ind, wxS("Must be created first") );
|
||||
|
||||
[ind stopAnimation:nil];
|
||||
|
||||
m_isRunning = false;
|
||||
}
|
||||
|
||||
bool wxActivityIndicator::IsRunning() const
|
||||
{
|
||||
return m_isRunning;
|
||||
}
|
||||
|
||||
#endif // wxUSE_ACTIVITYINDICATOR
|
||||
Reference in New Issue
Block a user