Hook up pinch-zoom and swipe gestures.
Sets UIPinchGestureRecognizer and UISwipeGestureRecognizers and
passes the result down to the sk_app::Window. To simplify detection,
swipes take precedence over pans, and pans require a single touch.
This is less flexible for the app, but in most cases I think is
what we want.
Bug: skia:8737
Change-Id: Ib031b6ad465d3a353da29d7e0b48a666d4ff8b9a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/239776
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/tools/sk_app/Window.cpp b/tools/sk_app/Window.cpp
index 56ffbb6..2ccad55 100644
--- a/tools/sk_app/Window.cpp
+++ b/tools/sk_app/Window.cpp
@@ -60,6 +60,14 @@
return this->signalLayers([=](Layer* layer) { return layer->onTouch(owner, state, x, y); });
}
+bool Window::onFling(skui::InputState state) {
+ return this->signalLayers([=](Layer* layer) { return layer->onFling(state); });
+}
+
+bool Window::onPinch(skui::InputState state, float scale, float x, float y) {
+ return this->signalLayers([=](Layer* layer) { return layer->onPinch(state, scale, x, y); });
+}
+
void Window::onUIStateChanged(const SkString& stateName, const SkString& stateValue) {
this->visitLayers([=](Layer* layer) { layer->onUIStateChanged(stateName, stateValue); });
}
diff --git a/tools/sk_app/Window.h b/tools/sk_app/Window.h
index b46a13b..76be662 100644
--- a/tools/sk_app/Window.h
+++ b/tools/sk_app/Window.h
@@ -87,6 +87,9 @@
virtual bool onMouse(int x, int y, skui::InputState, skui::ModifierKey) { return false; }
virtual bool onMouseWheel(float delta, skui::ModifierKey) { return false; }
virtual bool onTouch(intptr_t owner, skui::InputState, float x, float y) { return false; }
+ // Platform-detected gesture events
+ virtual bool onFling(skui::InputState state) { return false; }
+ virtual bool onPinch(skui::InputState state, float scale, float x, float y) { return false; }
virtual void onUIStateChanged(const SkString& stateName, const SkString& stateValue) {}
virtual void onPrePaint() {}
virtual void onPaint(SkSurface*) {}
@@ -108,6 +111,9 @@
bool onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers);
bool onMouseWheel(float delta, skui::ModifierKey modifiers);
bool onTouch(intptr_t owner, skui::InputState state, float x, float y); // multi-owner = multi-touch
+ // Platform-detected gesture events
+ bool onFling(skui::InputState state);
+ bool onPinch(skui::InputState state, float scale, float x, float y);
void onUIStateChanged(const SkString& stateName, const SkString& stateValue);
void onPaint();
void onResize(int width, int height);
diff --git a/tools/sk_app/ios/Window_ios.mm b/tools/sk_app/ios/Window_ios.mm
index 39037c4..82d8a73 100644
--- a/tools/sk_app/ios/Window_ios.mm
+++ b/tools/sk_app/ios/Window_ios.mm
@@ -43,7 +43,7 @@
return true;
}
- // Create a delegate to track certain events
+ // Create a view controller to track certain events
WindowViewController* viewController = [[WindowViewController alloc] initWithWindow:this];
if (nil == viewController) {
return false;
@@ -145,7 +145,7 @@
switch (sender.state) {
case UIGestureRecognizerStateBegan:
fWindow->onMouse(location.x, location.y,
- skui::InputState::kDown,skui::ModifierKey::kNone);
+ skui::InputState::kDown, skui::ModifierKey::kNone);
break;
case UIGestureRecognizerStateChanged:
fWindow->onMouse(location.x, location.y,
@@ -178,10 +178,45 @@
}
}
+- (IBAction)pinchGestureAction:(UIGestureRecognizer*)sender {
+ CGPoint location = [sender locationInView:self];
+ UIPinchGestureRecognizer* pinchGestureRecognizer = (UIPinchGestureRecognizer*) sender;
+ float scale = pinchGestureRecognizer.scale;
+ switch (sender.state) {
+ case UIGestureRecognizerStateBegan:
+ fWindow->onPinch(skui::InputState::kDown, scale, location.x, location.y);
+ break;
+ case UIGestureRecognizerStateChanged:
+ fWindow->onPinch(skui::InputState::kMove, scale, location.x, location.y);
+ break;
+ case UIGestureRecognizerStateEnded:
+ fWindow->onPinch(skui::InputState::kUp, scale, location.x, location.y);
+ break;
+ case UIGestureRecognizerStateCancelled:
+ fWindow->onPinch(skui::InputState::kUp, scale, location.x, location.y);
+ break;
+ default:
+ break;
+ }
+}
+
+- (IBAction)swipeRightGestureAction:(UIGestureRecognizer*)sender {
+ if (UIGestureRecognizerStateEnded == sender.state) {
+ fWindow->onFling(skui::InputState::kRight);
+ }
+}
+
+- (IBAction)swipeLeftGestureAction:(UIGestureRecognizer*)sender {
+ if (UIGestureRecognizerStateEnded == sender.state) {
+ fWindow->onFling(skui::InputState::kLeft);
+ }
+}
+
- (MainView*)initWithWindow:(sk_app::Window_ios *)initWindow {
self = [super init];
UIPanGestureRecognizer* panGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
+ panGestureRecognizer.maximumNumberOfTouches = 1;
[panGestureRecognizer addTarget:self action:@selector(panGestureAction:)];
[self addGestureRecognizer:panGestureRecognizer];
@@ -189,6 +224,24 @@
[tapGestureRecognizer addTarget:self action:@selector(tapGestureAction:)];
[self addGestureRecognizer:tapGestureRecognizer];
+ UIPinchGestureRecognizer* pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] init];
+ [pinchGestureRecognizer addTarget:self action:@selector(pinchGestureAction:)];
+ [self addGestureRecognizer:pinchGestureRecognizer];
+
+ UISwipeGestureRecognizer* swipeRightGestureRecognizer = [[UISwipeGestureRecognizer alloc] init];
+ swipeRightGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
+ [swipeRightGestureRecognizer addTarget:self action:@selector(swipeRightGestureAction:)];
+ [self addGestureRecognizer:swipeRightGestureRecognizer];
+
+ UISwipeGestureRecognizer* swipeLeftGestureRecognizer = [[UISwipeGestureRecognizer alloc] init];
+ swipeLeftGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
+ [swipeLeftGestureRecognizer addTarget:self action:@selector(swipeLeftGestureAction:)];
+ [self addGestureRecognizer:swipeLeftGestureRecognizer];
+
+ // disable pan recognition until swipes fail
+ [panGestureRecognizer requireGestureRecognizerToFail:swipeLeftGestureRecognizer];
+ [panGestureRecognizer requireGestureRecognizerToFail:swipeRightGestureRecognizer];
+
fWindow = initWindow;
return self;
diff --git a/tools/skui/InputState.h b/tools/skui/InputState.h
index 75821d4..90d5c6c 100644
--- a/tools/skui/InputState.h
+++ b/tools/skui/InputState.h
@@ -6,7 +6,9 @@
enum class InputState {
kDown,
kUp,
- kMove // only valid for mouse
+ kMove, // only valid for mouse
+ kRight, // only valid for fling
+ kLeft, // only valid for fling
};
}
#endif // skui_inputstate_DEFINED
diff --git a/tools/viewer/SKPSlide.cpp b/tools/viewer/SKPSlide.cpp
index 5b4d312..1c03a5e 100644
--- a/tools/viewer/SKPSlide.cpp
+++ b/tools/viewer/SKPSlide.cpp
@@ -49,7 +49,9 @@
void SKPSlide::load(SkScalar, SkScalar) {
fPic = read_picture(fPath.c_str());
- fCullRect = fPic->cullRect().roundOut();
+ if (fPic) {
+ fCullRect = fPic->cullRect().roundOut();
+ }
}
void SKPSlide::unload() {
diff --git a/tools/viewer/TouchGesture.cpp b/tools/viewer/TouchGesture.cpp
index 249aa44..4bef1f2 100644
--- a/tools/viewer/TouchGesture.cpp
+++ b/tools/viewer/TouchGesture.cpp
@@ -169,7 +169,7 @@
fState = kTranslate_State;
break;
case 2:
- fState = kZoom_State;
+ this->startZoom();
break;
default:
break;
@@ -204,6 +204,24 @@
return scale;
}
+void TouchGesture::startZoom() {
+ fState = kZoom_State;
+}
+
+void TouchGesture::updateZoom(float scale, float startX, float startY, float lastX, float lastY) {
+ scale = this->limitTotalZoom(scale);
+
+ fLocalM.setTranslate(-startX, -startY);
+ fLocalM.postScale(scale, scale);
+ fLocalM.postTranslate(lastX, lastY);
+}
+
+void TouchGesture::endZoom() {
+ this->flushLocalM();
+ SkASSERT(kZoom_State == fState);
+ fState = kEmpty_State;
+}
+
void TouchGesture::touchMoved(void* owner, float x, float y) {
// SkDebugf("--- %d touchMoved %p %g %g\n", fTouches.count(), owner, x, y);
@@ -246,13 +264,11 @@
const Rec& rec1 = fTouches[1];
float scale = this->computePinch(rec0, rec1);
- scale = this->limitTotalZoom(scale);
-
- fLocalM.setTranslate(-center(rec0.fStartX, rec1.fStartX),
- -center(rec0.fStartY, rec1.fStartY));
- fLocalM.postScale(scale, scale);
- fLocalM.postTranslate(center(rec0.fLastX, rec1.fLastX),
- center(rec0.fLastY, rec1.fLastY));
+ this->updateZoom(scale,
+ center(rec0.fStartX, rec1.fStartX),
+ center(rec0.fStartY, rec1.fStartY),
+ center(rec0.fLastX, rec1.fLastX),
+ center(rec0.fLastY, rec1.fLastY));
} break;
default:
break;
@@ -286,9 +302,7 @@
fState = kEmpty_State;
} break;
case 2:
- this->flushLocalM();
- SkASSERT(kZoom_State == fState);
- fState = kEmpty_State;
+ this->endZoom();
break;
default:
SkASSERT(kZoom_State == fState);
diff --git a/tools/viewer/TouchGesture.h b/tools/viewer/TouchGesture.h
index f6cdb3a..475b438 100644
--- a/tools/viewer/TouchGesture.h
+++ b/tools/viewer/TouchGesture.h
@@ -27,6 +27,10 @@
bool isBeingTouched() { return kEmpty_State != fState; }
bool isFling(SkPoint* dir);
+ void startZoom();
+ void updateZoom(float scale, float startX, float startY, float lastX, float lastY);
+ void endZoom();
+
const SkMatrix& localM();
const SkMatrix& globalM() const { return fGlobalM; }
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 601856c..6349113 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -1444,6 +1444,11 @@
fGesture.touchMoved(castedOwner, x, y);
break;
}
+ default: {
+ // kLeft and kRight are only for swipes
+ SkASSERT(false);
+ break;
+ }
}
fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kTouch : GestureDevice::kNone;
fWindow->inval();
@@ -1474,6 +1479,10 @@
fGesture.touchMoved(nullptr, x, y);
break;
}
+ default: {
+ SkASSERT(false); // shouldn't see kRight or kLeft here
+ break;
+ }
}
fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kMouse : GestureDevice::kNone;
@@ -1483,6 +1492,39 @@
return true;
}
+bool Viewer::onFling(skui::InputState state) {
+ if (skui::InputState::kRight == state) {
+ this->setCurrentSlide(fCurrentSlide > 0 ? fCurrentSlide - 1 : fSlides.count() - 1);
+ return true;
+ } else if (skui::InputState::kLeft == state) {
+ this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ? fCurrentSlide + 1 : 0);
+ return true;
+ }
+ return false;
+}
+
+bool Viewer::onPinch(skui::InputState state, float scale, float x, float y) {
+ switch (state) {
+ case skui::InputState::kDown:
+ fGesture.startZoom();
+ return true;
+ break;
+ case skui::InputState::kMove:
+ fGesture.updateZoom(scale, x, y, x, y);
+ return true;
+ break;
+ case skui::InputState::kUp:
+ fGesture.endZoom();
+ return true;
+ break;
+ default:
+ SkASSERT(false);
+ break;
+ }
+
+ return false;
+}
+
static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) {
// The gamut image covers a (0.8 x 0.9) shaped region
ImGui::DragCanvas dc(primaries, { 0.0f, 0.9f }, { 0.8f, 0.0f });
diff --git a/tools/viewer/Viewer.h b/tools/viewer/Viewer.h
index 3567218..be25ef0 100644
--- a/tools/viewer/Viewer.h
+++ b/tools/viewer/Viewer.h
@@ -42,6 +42,8 @@
void onUIStateChanged(const SkString& stateName, const SkString& stateValue) override;
bool onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers) override;
bool onChar(SkUnichar c, skui::ModifierKey modifiers) override;
+ bool onPinch(skui::InputState state, float scale, float x, float y) override;
+ bool onFling(skui::InputState state) override;
struct SkFontFields {
bool fTypeface = false;