diff --git a/headers/private/shared/ShakeTrackingFilter.h b/headers/private/shared/ShakeTrackingFilter.h new file mode 100644 index 0000000000..e6b88c0d71 --- /dev/null +++ b/headers/private/shared/ShakeTrackingFilter.h @@ -0,0 +1,62 @@ +/* + * Copyright 2009, Alexandre Deckner, alex@zappotek.com + * Distributed under the terms of the MIT License. + */ +#ifndef SHAKE_TRACKING_FILTER_H +#define SHAKE_TRACKING_FILTER_H + + +#include +#include + +class BView; +class BHandler; +class BMessageRunner; + +namespace BPrivate { + +class LowPassFilter { +public: + LowPassFilter(uint32 size); + ~LowPassFilter(); + + void Input(const BPoint& p); + BPoint Output() const; +private: + BPoint* fPoints; + uint32 fSize; + BPoint fSum; +}; + + +class ShakeTrackingFilter : public BMessageFilter { +public: + ShakeTrackingFilter( + BView* targetView, + uint32 messageWhat, + uint32 countThreshold = 2, + bigtime_t timeTreshold = 400000); + + ~ShakeTrackingFilter(); + + filter_result Filter(BMessage* message, BHandler** _target); + +private: + BView* fTargetView; + uint32 fMessageWhat; + + BMessageRunner* fCancelRunner; + LowPassFilter fLowPass; + BPoint fLastDelta; + BPoint fLastPosition; + uint32 fCounter; + uint32 fCountThreshold; + bigtime_t fTimeThreshold; +}; + +} // namespace BPrivate + +using BPrivate::ShakeTrackingFilter; +using BPrivate::LowPassFilter; + +#endif // SHAKE_TRACKING_FILTER_H diff --git a/src/kits/shared/Jamfile b/src/kits/shared/Jamfile index 1330a0dabd..486861bfa9 100644 --- a/src/kits/shared/Jamfile +++ b/src/kits/shared/Jamfile @@ -20,6 +20,7 @@ StaticLibrary libshared.a : DragTrackingFilter.cpp HashString.cpp RWLockManager.cpp + ShakeTrackingFilter.cpp Variant.cpp ; diff --git a/src/kits/shared/ShakeTrackingFilter.cpp b/src/kits/shared/ShakeTrackingFilter.cpp new file mode 100644 index 0000000000..ab1b2a2ff5 --- /dev/null +++ b/src/kits/shared/ShakeTrackingFilter.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2009, Alexandre Deckner, alex@zappotek.com + * Distributed under the terms of the MIT License. + */ + +/*! + \class ShakeTrackingFilter + \brief A simple mouse shake detection filter + * + * A simple mouse filter that detects quick mouse shakes. + * + * It's detecting rough edges (u-turns) in the mouse movement + * and counts them within a time window. + * You can configure the message sent, the u-turn count threshold + * and the time threshold. + * It sends the count along with the message. + * For now, detection is limited within the view bounds, but + * it might be modified to accept a BRegion mask. + * +*/ + +#include + +#include +#include +#include +#include + + +ShakeTrackingFilter::ShakeTrackingFilter(BView* targetView, uint32 messageWhat, + uint32 countThreshold, bigtime_t timeThreshold) + : + BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), + fTargetView(targetView), + fMessageWhat(messageWhat), + fCancelRunner(NULL), + fLowPass(8), + fLastDelta(0, 0), + fCounter(0), + fCountThreshold(countThreshold), + fTimeThreshold(timeThreshold) +{ +} + + +ShakeTrackingFilter::~ShakeTrackingFilter() +{ + delete fCancelRunner; +} + + +filter_result +ShakeTrackingFilter::Filter(BMessage* message, BHandler** /*_target*/) +{ + if (fTargetView == NULL) + return B_DISPATCH_MESSAGE; + + switch (message->what) { + case B_MOUSE_MOVED: + { + BPoint position; + message->FindPoint("be:view_where", &position); + + // TODO: allow using BRegion masks + if (!fTargetView->Bounds().Contains(position)) + return B_DISPATCH_MESSAGE; + + fLowPass.Input(position - fLastPosition); + + BPoint delta = fLowPass.Output(); + + // normalized dot product + float norm = delta.x * delta.x + delta.y * delta.y; + if (norm > 0.01) { + delta.x /= norm; + delta.y /= norm; + } + + norm = fLastDelta.x * fLastDelta.x + fLastDelta.y * fLastDelta.y; + if (norm > 0.01) { + fLastDelta.x /= norm; + fLastDelta.y /= norm; + } + + float dot = delta.x * fLastDelta.x + delta.y * fLastDelta.y; + + if (dot < 0.0) { + if (fCounter == 0) { + BMessage * cancelMessage = new BMessage(kMsgCancel); + fCancelRunner = new BMessageRunner(BMessenger(fTargetView), + cancelMessage, fTimeThreshold, 1); + } + + fCounter++; + + if (fCounter >= fCountThreshold) { + BMessage shakeMessage(fMessageWhat); + shakeMessage.AddUInt32("count", fCounter); + BMessenger messenger(fTargetView); + messenger.SendMessage(&shakeMessage); + } + } + + fLastDelta = fLowPass.Output(); + fLastPosition = position; + + return B_DISPATCH_MESSAGE; + } + + case kMsgCancel: + delete fCancelRunner; + fCancelRunner = NULL; + fCounter = 0; + return B_SKIP_MESSAGE; + + default: + break; + } + + return B_DISPATCH_MESSAGE; +} + + +// #pragma mark - + + +LowPassFilter::LowPassFilter(uint32 size) + : + fSize(size) +{ + fPoints = new BPoint[fSize]; +} + + +LowPassFilter::~LowPassFilter() +{ + delete [] fPoints; +} + + +void +LowPassFilter::Input(const BPoint& p) +{ + // A fifo buffer that maintains a sum of its elements + fSum -= fPoints[0]; + for (uint32 i = 0; i < fSize - 1; i++) + fPoints[i] = fPoints[i + 1]; + fPoints[fSize - 1] = p; + fSum += p; +} + + +BPoint +LowPassFilter::Output() const +{ + return BPoint(fSum.x / (float) fSize, fSum.y / (float) fSize); +} diff --git a/src/tests/kits/Jamfile b/src/tests/kits/Jamfile index b256b0cfdd..bb3a1dbd4b 100644 --- a/src/tests/kits/Jamfile +++ b/src/tests/kits/Jamfile @@ -7,6 +7,7 @@ SubInclude HAIKU_TOP src tests kits locale ; SubInclude HAIKU_TOP src tests kits media ; SubInclude HAIKU_TOP src tests kits midi ; SubInclude HAIKU_TOP src tests kits net ; +SubInclude HAIKU_TOP src tests kits shared ; SubInclude HAIKU_TOP src tests kits storage ; SubInclude HAIKU_TOP src tests kits support ; SubInclude HAIKU_TOP src tests kits translation ; diff --git a/src/tests/kits/shared/Jamfile b/src/tests/kits/shared/Jamfile new file mode 100644 index 0000000000..d9aa2dda0c --- /dev/null +++ b/src/tests/kits/shared/Jamfile @@ -0,0 +1,6 @@ +SubDir HAIKU_TOP src tests kits shared ; + +SetSubDirSupportedPlatformsBeOSCompatible ; +AddSubDirSupportedPlatforms libbe_test ; + +SubInclude HAIKU_TOP src tests kits shared shake_filter ; diff --git a/src/tests/kits/shared/shake_filter/Jamfile b/src/tests/kits/shared/shake_filter/Jamfile new file mode 100644 index 0000000000..b305d1c1e1 --- /dev/null +++ b/src/tests/kits/shared/shake_filter/Jamfile @@ -0,0 +1,8 @@ +SubDir HAIKU_TOP src tests kits shared shake_filter ; + +UsePrivateHeaders shared ; + +Application ShakeFilterTest : + ShakeFilterTest.cpp + : libshared.a be $(TARGET_LIBSUPC++) +; diff --git a/src/tests/kits/shared/shake_filter/ShakeFilterTest.cpp b/src/tests/kits/shared/shake_filter/ShakeFilterTest.cpp new file mode 100644 index 0000000000..16495667c1 --- /dev/null +++ b/src/tests/kits/shared/shake_filter/ShakeFilterTest.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2009, Alexandre Deckner + * Distributed under the terms of the MIT License. + */ + + +#include +#include +#include +#include + +#include + + +static const uint32 kMsgShake = 'Shke'; + + +class View : public BView { +public: + View(BRect rect, const char* name, uint32 followMode); + virtual ~View(); + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage* msg); +}; + + +View::View(BRect rect, const char* name, uint32 followMode) + : + BView(rect, name, followMode, B_WILL_DRAW) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetHighColor(255, 0, 0); +} + + +View::~View() +{ +} + + +void +View::AttachedToWindow() +{ + AddFilter(new ShakeTrackingFilter(this, kMsgShake, 3, 500000)); +} + + +void +View::MessageReceived(BMessage* msg) +{ + if (msg->what == kMsgShake) { + uint32 count = 0; + msg->FindUInt32("count", &count); + printf("Shake! %lu\n", count); + } else { + BView::MessageReceived(msg); + } +} + + +// #pragma mark - + + +int +main(int argc, char **argv) +{ + BApplication app("application/x-vnd.haiku-ShakeFilterTest"); + + BWindow* window = new BWindow(BRect(100, 100, 400, 400), + "ShakeFilter-Test", B_TITLED_WINDOW, + B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE); + + BRect frame = window->Bounds(); + + window->AddChild(new View(frame, "L ", B_FOLLOW_ALL)); + window->Show(); + + app.Run(); + return 0; +}