skia2/tools/viewer/sk_app/CommandSet.cpp
brianosman 622c8d5de1 Add flexible keybinding/command system to sk_app.
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
2016-05-10 06:50:49 -07:00

158 lines
4.8 KiB
C++

/*
* 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