Add flexible keybinding/command system to sk_app.
authorbrianosman <brianosman@google.com>
Tue, 10 May 2016 13:50:49 +0000 (06:50 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 10 May 2016 13:50:49 +0000 (06:50 -0700)
Viewer demonstrates use: Just create an instance of CommandSet,
register with the window, and add commands. Hopefully, we can keep
all commands in one place, and get some nice side-benefits. With
this framework, if you want to add a new command, you are only
required to add code in ONE place. And you get added to the help
screen, for free.

CommandSet automatically binds 'h' to cycle through the help modes.
(Functional grouping is most useful for general use, but the other
mode is nice to know what a key does, or to find an unused key for
a new feature).

Grouped by function: https://screenshot.googleplex.com/G5h3f52wFKu.png
Alphabetical by key: https://screenshot.googleplex.com/nZiopabLKJ6.png

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1955293002

Review-Url: https://codereview.chromium.org/1955293002

tools/viewer/Viewer.cpp
tools/viewer/Viewer.h
tools/viewer/sk_app/CommandSet.cpp [new file with mode: 0644]
tools/viewer/sk_app/CommandSet.h [new file with mode: 0644]
tools/viewer/sk_app/Window.h
tools/viewer/sk_app/win/Window_win.cpp

index 62c3048d914e720ed392f466c168231eb980c878..7dd265e6e2183cd75c4e726410be8a845aa12d41 100644 (file)
@@ -22,19 +22,6 @@ Application* Application::Create(int argc, char** argv, void* platformData) {
     return new Viewer(argc, argv, platformData);
 }
 
-static bool on_key_handler(Window::Key key, Window::InputState state, uint32_t modifiers,
-                           void* userData) {
-    Viewer* vv = reinterpret_cast<Viewer*>(userData);
-
-    return vv->onKey(key, state, modifiers);
-}
-
-static bool on_char_handler(SkUnichar c, uint32_t modifiers, void* userData) {
-    Viewer* vv = reinterpret_cast<Viewer*>(userData);
-
-    return vv->onChar(c, modifiers);
-}
-
 static void on_paint_handler(SkCanvas* canvas, void* userData) {
     Viewer* vv = reinterpret_cast<Viewer*>(userData);
 
@@ -76,10 +63,47 @@ Viewer::Viewer(int argc, char** argv, void* platformData)
     fWindow->attach(Window::kVulkan_BackendType, DisplayParams());
 
     // register callbacks
-    fWindow->registerKeyFunc(on_key_handler, this);
-    fWindow->registerCharFunc(on_char_handler, this);
+    fCommands.attach(fWindow);
     fWindow->registerPaintFunc(on_paint_handler, this);
 
+    // add key-bindings
+    fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() {
+        this->fDisplayStats = !this->fDisplayStats;
+        fWindow->inval();
+    });
+    fCommands.addCommand('c', "Modes", "Toggle sRGB color mode", [this]() {
+        DisplayParams params = fWindow->getDisplayParams();
+        params.fProfileType = (kLinear_SkColorProfileType == params.fProfileType)
+            ? kSRGB_SkColorProfileType : kLinear_SkColorProfileType;
+        fWindow->setDisplayParams(params);
+        this->updateTitle();
+        fWindow->inval();
+    });
+    fCommands.addCommand(Window::Key::kRight, "Right", "Navigation", "Next slide", [this]() {
+        int previousSlide = fCurrentSlide;
+        fCurrentSlide++;
+        if (fCurrentSlide >= fSlides.count()) {
+            fCurrentSlide = 0;
+        }
+        this->setupCurrentSlide(previousSlide);
+    });
+    fCommands.addCommand(Window::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() {
+        int previousSlide = fCurrentSlide;
+        fCurrentSlide--;
+        if (fCurrentSlide < 0) {
+            fCurrentSlide = fSlides.count() - 1;
+        }
+        this->setupCurrentSlide(previousSlide);
+    });
+    fCommands.addCommand(Window::Key::kUp, "Up", "Transform", "Zoom in", [this]() {
+        this->changeZoomLevel(1.f / 32.f);
+        fWindow->inval();
+    });
+    fCommands.addCommand(Window::Key::kDown, "Down", "Transform", "Zoom out", [this]() {
+        this->changeZoomLevel(-1.f / 32.f);
+        fWindow->inval();
+    });
+
     // set up slides
     this->initSlides();
 
@@ -208,67 +232,6 @@ void Viewer::updateMatrix(){
     fLocalMatrix = m;
 }
 
-bool Viewer::onKey(Window::Key key, Window::InputState state, uint32_t modifiers) {
-    if (Window::kDown_InputState == state) {
-        switch (key) {
-            case Window::kRight_Key: {
-                int previousSlide = fCurrentSlide;
-                fCurrentSlide++;
-                if (fCurrentSlide >= fSlides.count()) {
-                    fCurrentSlide = 0;
-                }
-                setupCurrentSlide(previousSlide);
-                return true;
-            }
-
-            case Window::kLeft_Key: {
-                int previousSlide = fCurrentSlide;
-                fCurrentSlide--;
-                if (fCurrentSlide < 0) {
-                    fCurrentSlide = fSlides.count() - 1;
-                }
-                setupCurrentSlide(previousSlide);
-                return true;
-            }
-
-            case Window::kUp_Key: {
-                this->changeZoomLevel(1.f / 32.f);
-                fWindow->inval();
-                return true;
-            }
-
-            case Window::kDown_Key: {
-                this->changeZoomLevel(-1.f / 32.f);
-                fWindow->inval();
-                return true;
-            }
-
-            default:
-                break;
-        }
-    }
-
-    return false;
-}
-
-bool Viewer::onChar(SkUnichar c, uint32_t modifiers) {
-    switch (c) {
-        case 's':
-            fDisplayStats = !fDisplayStats;
-            return true;
-        case 'c':
-            DisplayParams params = fWindow->getDisplayParams();
-            params.fProfileType = (kLinear_SkColorProfileType == params.fProfileType)
-                ? kSRGB_SkColorProfileType : kLinear_SkColorProfileType;
-            fWindow->setDisplayParams(params);
-            this->updateTitle();
-            fWindow->inval();
-            return true;
-    }
-
-    return false;
-}
-
 void Viewer::onPaint(SkCanvas* canvas) {
 
     int count = canvas->save();
@@ -296,6 +259,7 @@ void Viewer::onPaint(SkCanvas* canvas) {
     if (fDisplayStats) {
         drawStats(canvas);
     }
+    fCommands.drawHelp(canvas);
 }
 
 void Viewer::drawStats(SkCanvas* canvas) {
index 2e9650a846f470d2fb9238b0dc7d49bc79218e8a..033bd6969b9684a7c8d10ca474583876e0456b49 100644 (file)
@@ -9,6 +9,7 @@
 #define Viewer_DEFINED
 
 #include "sk_app/Application.h"
+#include "sk_app/CommandSet.h"
 #include "sk_app/Window.h"
 #include "gm.h"
 #include "SkAnimTimer.h"
@@ -21,8 +22,6 @@ public:
     Viewer(int argc, char** argv, void* platformData);
     ~Viewer() override;
 
-    bool onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers);
-    bool onChar(SkUnichar, uint32_t modifiers);
     void onPaint(SkCanvas* canvas);
     void onIdle(double ms) override;
 
@@ -55,6 +54,7 @@ private:
     SkScalar               fZoomLevel;
     SkScalar               fZoomScale;
 
+    sk_app::CommandSet     fCommands;
 };
 
 
diff --git a/tools/viewer/sk_app/CommandSet.cpp b/tools/viewer/sk_app/CommandSet.cpp
new file mode 100644 (file)
index 0000000..e426eaa
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "CommandSet.h"
+
+#include "SkCanvas.h"
+#include "SkTSort.h"
+
+namespace sk_app {
+
+static bool on_key_handler(Window::Key key, Window::InputState state, uint32_t modifiers,
+                           void* userData) {
+    CommandSet* cs = reinterpret_cast<CommandSet*>(userData);
+    return cs->onKey(key, state, modifiers);
+}
+
+static bool on_char_handler(SkUnichar c, uint32_t modifiers, void* userData) {
+    CommandSet* cs = reinterpret_cast<CommandSet*>(userData);
+    return cs->onChar(c, modifiers);
+}
+
+CommandSet::CommandSet()
+    : fHelpMode(kNone_HelpMode) {
+    this->addCommand('h', "Overlays", "Show help screen", [this]() {
+        switch (this->fHelpMode) {
+            case kNone_HelpMode:
+                this->fHelpMode = kGrouped_HelpMode;
+                break;
+            case kGrouped_HelpMode:
+                this->fHelpMode = kAlphabetical_HelpMode;
+                break;
+            case kAlphabetical_HelpMode:
+                this->fHelpMode = kNone_HelpMode;
+                break;
+        }
+        fWindow->inval();
+    });
+}
+
+void CommandSet::attach(Window* window) {
+    fWindow = window;
+    window->registerKeyFunc(on_key_handler, this);
+    window->registerCharFunc(on_char_handler, this);
+}
+
+bool CommandSet::onKey(Window::Key key, Window::InputState state, uint32_t modifiers) {
+    if (Window::kDown_InputState == state) {
+        for (Command& cmd : fCommands) {
+            if (Command::kKey_CommandType == cmd.fType && key == cmd.fKey) {
+                cmd.fFunction();
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+bool CommandSet::onChar(SkUnichar c, uint32_t modifiers) {
+    for (Command& cmd : fCommands) {
+        if (Command::kChar_CommandType == cmd.fType && c == cmd.fChar) {
+            cmd.fFunction();
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void CommandSet::addCommand(SkUnichar c, const char* group, const char* description,
+                            std::function<void(void)> function) {
+    fCommands.push_back(Command(c, group, description, function));
+}
+
+void CommandSet::addCommand(Window::Key k, const char* keyName, const char* group,
+                            const char* description, std::function<void(void)> function) {
+    fCommands.push_back(Command(k, keyName, group, description, function));
+}
+
+#if defined(SK_BUILD_FOR_WIN32)
+    #define SK_strcasecmp   _stricmp
+#else
+    #define SK_strcasecmp   strcasecmp
+#endif
+
+bool CommandSet::compareCommandKey(const Command& first, const Command& second) {
+    return SK_strcasecmp(first.fKeyName.c_str(), second.fKeyName.c_str()) < 0;
+}
+
+bool CommandSet::compareCommandGroup(const Command& first, const Command& second) {
+    return SK_strcasecmp(first.fGroup.c_str(), second.fGroup.c_str()) < 0;
+}
+
+void CommandSet::drawHelp(SkCanvas* canvas) {
+    if (kNone_HelpMode == fHelpMode) {
+        return;
+    }
+
+    // Sort commands for current mode:
+    SkTQSort(fCommands.begin(), fCommands.end() - 1,
+             kAlphabetical_HelpMode == fHelpMode ? compareCommandKey : compareCommandGroup);
+
+    SkPaint bgPaint;
+    bgPaint.setColor(0xC0000000);
+    canvas->drawPaint(bgPaint);
+
+    SkPaint paint;
+    paint.setTextSize(16);
+    paint.setAntiAlias(true);
+    paint.setColor(0xFFFFFFFF);
+
+    SkPaint groupPaint;
+    groupPaint.setTextSize(18);
+    groupPaint.setUnderlineText(true);
+    groupPaint.setAntiAlias(true);
+    groupPaint.setColor(0xFFFFFFFF);
+
+    SkScalar x = SkIntToScalar(10);
+    SkScalar y = SkIntToScalar(10);
+
+    // Measure all key strings:
+    SkScalar keyWidth = 0;
+    for (Command& cmd : fCommands) {
+        keyWidth = SkMaxScalar(keyWidth,
+                               paint.measureText(cmd.fKeyName.c_str(), cmd.fKeyName.size()));
+    }
+    keyWidth += paint.measureText(" ", 1);
+
+    // If we're grouping by category, we'll be adding text height on every new group (including the
+    // first), so no need to do that here. Otherwise, skip down so the first line is where we want.
+    if (kGrouped_HelpMode != fHelpMode) {
+        y += paint.getTextSize();
+    }
+
+    // Print everything:
+    SkString lastGroup;
+    for (Command& cmd : fCommands) {
+        if (kGrouped_HelpMode == fHelpMode && lastGroup != cmd.fGroup) {
+            // Group change. Advance and print header:
+            y += paint.getTextSize();
+            canvas->drawText(cmd.fGroup.c_str(), cmd.fGroup.size(), x, y, groupPaint);
+            y += groupPaint.getTextSize() + 2;
+            lastGroup = cmd.fGroup;
+        }
+
+        canvas->drawText(cmd.fKeyName.c_str(), cmd.fKeyName.size(), x, y, paint);
+        SkString text = SkStringPrintf(": %s", cmd.fDescription.c_str());
+        canvas->drawText(text.c_str(), text.size(), x + keyWidth, y, paint);
+        y += paint.getTextSize() + 2;
+    }
+}
+
+}   // namespace sk_app
diff --git a/tools/viewer/sk_app/CommandSet.h b/tools/viewer/sk_app/CommandSet.h
new file mode 100644 (file)
index 0000000..5e6373c
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef CommandSet_DEFINED
+#define CommandSet_DEFINED
+
+#include "SkString.h"
+#include "Window.h"
+
+#include <functional>
+
+class SkCanvas;
+
+namespace sk_app {
+
+/**
+ * Helper class used by applications that want to hook keypresses to trigger events.
+ *
+ * An app can simply store an instance of CommandSet and then use it as follows:
+ * 1) Attach to the Window at initialization time. This registers key handlers on the window.
+ * 2) Register commands to be executed for characters or keys. Each command needs a Group and a
+ *    description (both just strings). Commands attached to Keys (rather than characters) also need
+ *    a displayable name for the Key. Finally, a function to execute when the key or character is
+ *    pressed must be supplied. The easiest option to is pass in a lambda that captures [this]
+ *    (your application object), and performs whatever action is desired.
+ * 3) At the end of your onPaint, call drawHelp, and pass in the application's canvas.
+
+ * The CommandSet always binds 'h' to cycle through two different help screens. The first shows
+ * all commands, organized by Group (with headings for each Group). The second shows all commands
+ * alphabetically by key/character.
+ */
+class CommandSet {
+public:
+    CommandSet();
+
+    void attach(Window* window);
+    bool onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers);
+    bool onChar(SkUnichar, uint32_t modifiers);
+
+    void addCommand(SkUnichar c, const char* group, const char* description,
+                    std::function<void(void)> function);
+    void addCommand(Window::Key k, const char* keyName, const char* group, const char* description,
+                    std::function<void(void)> function);
+
+    void drawHelp(SkCanvas* canvas);
+
+private:
+    struct Command {
+        enum CommandType {
+            kChar_CommandType,
+            kKey_CommandType,
+        };
+
+        Command(SkUnichar c, const char* group, const char* description,
+                std::function<void(void)> function)
+            : fType(kChar_CommandType)
+            , fChar(c)
+            , fKeyName(SkStringPrintf("%c", c))
+            , fGroup(group)
+            , fDescription(description)
+            , fFunction(function) {}
+
+        Command(Window::Key k, const char* keyName, const char* group, const char* description,
+                std::function<void(void)> function)
+            : fType(kKey_CommandType)
+            , fKey(k)
+            , fKeyName(keyName)
+            , fGroup(group)
+            , fDescription(description)
+            , fFunction(function) {}
+
+        CommandType fType;
+
+        // For kChar_CommandType
+        SkUnichar fChar;
+
+        // For kKey_CommandType
+        Window::Key fKey;
+
+        // Common to all command types
+        SkString fKeyName;
+        SkString fGroup;
+        SkString fDescription;
+        std::function<void(void)> fFunction;
+    };
+
+    static bool compareCommandKey(const Command& first, const Command& second);
+    static bool compareCommandGroup(const Command& first, const Command& second);
+
+    enum HelpMode {
+        kNone_HelpMode,
+        kGrouped_HelpMode,
+        kAlphabetical_HelpMode,
+    };
+
+    Window*           fWindow;
+    SkTArray<Command> fCommands;
+    HelpMode          fHelpMode;
+};
+
+}   // namespace sk_app
+
+#endif
index 56444a67b76639577e9b0e24a7b506c6bce1a54a..5a7c9b836095240189de16c618e6f2670fa04c9c 100644 (file)
@@ -41,45 +41,45 @@ public:
     void detach();
 
     // input handling
-    enum Key {
-        kNONE_Key,    //corresponds to android's UNKNOWN
-
-        kLeftSoftKey_Key,
-        kRightSoftKey_Key,
-
-        kHome_Key,    //!< the home key - added to match android
-        kBack_Key,    //!< (CLR)
-        kSend_Key,    //!< the green (talk) key
-        kEnd_Key,     //!< the red key
-
-        k0_Key,
-        k1_Key,
-        k2_Key,
-        k3_Key,
-        k4_Key,
-        k5_Key,
-        k6_Key,
-        k7_Key,
-        k8_Key,
-        k9_Key,
-        kStar_Key,    //!< the * key
-        kHash_Key,    //!< the # key
-
-        kUp_Key,
-        kDown_Key,
-        kLeft_Key,
-        kRight_Key,
-
-        kOK_Key,      //!< the center key
-
-        kVolUp_Key,   //!< volume up    - match android
-        kVolDown_Key, //!< volume down  - same
-        kPower_Key,   //!< power button - same
-        kCamera_Key,  //!< camera       - same
-
-        kLast_Key = kCamera_Key
+    enum class Key {
+        kNONE,    //corresponds to android's UNKNOWN
+
+        kLeftSoftKey,
+        kRightSoftKey,
+
+        kHome,    //!< the home key - added to match android
+        kBack,    //!< (CLR)
+        kSend,    //!< the green (talk) key
+        kEnd,     //!< the red key
+
+        k0,
+        k1,
+        k2,
+        k3,
+        k4,
+        k5,
+        k6,
+        k7,
+        k8,
+        k9,
+        kStar,    //!< the * key
+        kHash,    //!< the # key
+
+        kUp,
+        kDown,
+        kLeft,
+        kRight,
+
+        kOK,      //!< the center key
+
+        kVolUp,   //!< volume up    - match android
+        kVolDown, //!< volume down  - same
+        kPower,   //!< power button - same
+        kCamera,  //!< camera       - same
+
+        kLast = kCamera
     };
-    static const int kKeyCount = kLast_Key + 1;
+    static const int kKeyCount = static_cast<int>(Key::kLast) + 1;
 
     enum ModifierKeys {
         kShift_ModifierKey = 1 << 0,
index 16afcc7275e7d89f34515153dc6a33bf2ac8b0c5..241a41cabaac7d7a20a3ef3bef2f493b0c85e22d 100644 (file)
@@ -93,20 +93,20 @@ static Window::Key get_key(WPARAM vk) {
         WPARAM      fVK;
         Window::Key fKey;
     } gPair[] = {
-        { VK_BACK, Window::kBack_Key },
-        { VK_CLEAR, Window::kBack_Key },
-        { VK_RETURN, Window::kOK_Key },
-        { VK_UP, Window::kUp_Key },
-        { VK_DOWN, Window::kDown_Key },
-        { VK_LEFT, Window::kLeft_Key },
-        { VK_RIGHT, Window::kRight_Key }
+        { VK_BACK, Window::Key::kBack },
+        { VK_CLEAR, Window::Key::kBack },
+        { VK_RETURN, Window::Key::kOK },
+        { VK_UP, Window::Key::kUp },
+        { VK_DOWN, Window::Key::kDown },
+        { VK_LEFT, Window::Key::kLeft },
+        { VK_RIGHT, Window::Key::kRight }
     };
     for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
         if (gPair[i].fVK == vk) {
             return gPair[i].fKey;
         }
     }
-    return Window::kNONE_Key;
+    return Window::Key::kNONE;
 }
 
 static uint32_t get_modifiers(UINT message, WPARAM wParam, LPARAM lParam) {