mirror of
https://github.com/SFML/SFML.git
synced 2024-12-05 09:41:05 +08:00
866 lines
27 KiB
C++
866 lines
27 KiB
C++
|
////////////////////////////////////////////////////////////
|
||
|
// Headers
|
||
|
////////////////////////////////////////////////////////////
|
||
|
#include <SFML/Graphics.hpp>
|
||
|
|
||
|
#include <SFML/Audio.hpp>
|
||
|
|
||
|
#ifdef SFML_SYSTEM_IOS
|
||
|
#include <SFML/Main.hpp>
|
||
|
#endif
|
||
|
|
||
|
#include <sstream>
|
||
|
|
||
|
#include <cassert>
|
||
|
#include <cmath>
|
||
|
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
std::filesystem::path resourcesDir()
|
||
|
{
|
||
|
#ifdef SFML_SYSTEM_IOS
|
||
|
return "";
|
||
|
#else
|
||
|
return "resources";
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Get the C++ enumerator name of the given `sf::Keyboard::Key` value including `Key::` prefix
|
||
|
std::string keyIdentifier(sf::Keyboard::Key code)
|
||
|
{
|
||
|
switch (code)
|
||
|
{
|
||
|
#define CASE(code) \
|
||
|
case sf::Keyboard::Key::code: \
|
||
|
return "Key::" #code
|
||
|
CASE(Unknown);
|
||
|
CASE(A);
|
||
|
CASE(B);
|
||
|
CASE(C);
|
||
|
CASE(D);
|
||
|
CASE(E);
|
||
|
CASE(F);
|
||
|
CASE(G);
|
||
|
CASE(H);
|
||
|
CASE(I);
|
||
|
CASE(J);
|
||
|
CASE(K);
|
||
|
CASE(L);
|
||
|
CASE(M);
|
||
|
CASE(N);
|
||
|
CASE(O);
|
||
|
CASE(P);
|
||
|
CASE(Q);
|
||
|
CASE(R);
|
||
|
CASE(S);
|
||
|
CASE(T);
|
||
|
CASE(U);
|
||
|
CASE(V);
|
||
|
CASE(W);
|
||
|
CASE(X);
|
||
|
CASE(Y);
|
||
|
CASE(Z);
|
||
|
CASE(Num0);
|
||
|
CASE(Num1);
|
||
|
CASE(Num2);
|
||
|
CASE(Num3);
|
||
|
CASE(Num4);
|
||
|
CASE(Num5);
|
||
|
CASE(Num6);
|
||
|
CASE(Num7);
|
||
|
CASE(Num8);
|
||
|
CASE(Num9);
|
||
|
CASE(Escape);
|
||
|
CASE(LControl);
|
||
|
CASE(LShift);
|
||
|
CASE(LAlt);
|
||
|
CASE(LSystem);
|
||
|
CASE(RControl);
|
||
|
CASE(RShift);
|
||
|
CASE(RAlt);
|
||
|
CASE(RSystem);
|
||
|
CASE(Menu);
|
||
|
CASE(LBracket);
|
||
|
CASE(RBracket);
|
||
|
CASE(Semicolon);
|
||
|
CASE(Comma);
|
||
|
CASE(Period);
|
||
|
CASE(Apostrophe);
|
||
|
CASE(Slash);
|
||
|
CASE(Backslash);
|
||
|
CASE(Grave);
|
||
|
CASE(Equal);
|
||
|
CASE(Hyphen);
|
||
|
CASE(Space);
|
||
|
CASE(Enter);
|
||
|
CASE(Backspace);
|
||
|
CASE(Tab);
|
||
|
CASE(PageUp);
|
||
|
CASE(PageDown);
|
||
|
CASE(End);
|
||
|
CASE(Home);
|
||
|
CASE(Insert);
|
||
|
CASE(Delete);
|
||
|
CASE(Add);
|
||
|
CASE(Subtract);
|
||
|
CASE(Multiply);
|
||
|
CASE(Divide);
|
||
|
CASE(Left);
|
||
|
CASE(Right);
|
||
|
CASE(Up);
|
||
|
CASE(Down);
|
||
|
CASE(Numpad0);
|
||
|
CASE(Numpad1);
|
||
|
CASE(Numpad2);
|
||
|
CASE(Numpad3);
|
||
|
CASE(Numpad4);
|
||
|
CASE(Numpad5);
|
||
|
CASE(Numpad6);
|
||
|
CASE(Numpad7);
|
||
|
CASE(Numpad8);
|
||
|
CASE(Numpad9);
|
||
|
CASE(F1);
|
||
|
CASE(F2);
|
||
|
CASE(F3);
|
||
|
CASE(F4);
|
||
|
CASE(F5);
|
||
|
CASE(F6);
|
||
|
CASE(F7);
|
||
|
CASE(F8);
|
||
|
CASE(F9);
|
||
|
CASE(F10);
|
||
|
CASE(F11);
|
||
|
CASE(F12);
|
||
|
CASE(F13);
|
||
|
CASE(F14);
|
||
|
CASE(F15);
|
||
|
CASE(Pause);
|
||
|
#undef CASE
|
||
|
}
|
||
|
|
||
|
throw std::runtime_error("invalid keyboard code");
|
||
|
}
|
||
|
|
||
|
// Get the C++ enumerator name of the given `sf::Keyboard::Scancode` value including `Scan::` prefix
|
||
|
std::string scancodeIdentifier(sf::Keyboard::Scancode scancode)
|
||
|
{
|
||
|
switch (scancode)
|
||
|
{
|
||
|
#define CASE(scancode) \
|
||
|
case sf::Keyboard::Scan::scancode: \
|
||
|
return "Scan::" #scancode
|
||
|
CASE(Unknown);
|
||
|
CASE(A);
|
||
|
CASE(B);
|
||
|
CASE(C);
|
||
|
CASE(D);
|
||
|
CASE(E);
|
||
|
CASE(F);
|
||
|
CASE(G);
|
||
|
CASE(H);
|
||
|
CASE(I);
|
||
|
CASE(J);
|
||
|
CASE(K);
|
||
|
CASE(L);
|
||
|
CASE(M);
|
||
|
CASE(N);
|
||
|
CASE(O);
|
||
|
CASE(P);
|
||
|
CASE(Q);
|
||
|
CASE(R);
|
||
|
CASE(S);
|
||
|
CASE(T);
|
||
|
CASE(U);
|
||
|
CASE(V);
|
||
|
CASE(W);
|
||
|
CASE(X);
|
||
|
CASE(Y);
|
||
|
CASE(Z);
|
||
|
CASE(Num1);
|
||
|
CASE(Num2);
|
||
|
CASE(Num3);
|
||
|
CASE(Num4);
|
||
|
CASE(Num5);
|
||
|
CASE(Num6);
|
||
|
CASE(Num7);
|
||
|
CASE(Num8);
|
||
|
CASE(Num9);
|
||
|
CASE(Num0);
|
||
|
CASE(Enter);
|
||
|
CASE(Escape);
|
||
|
CASE(Backspace);
|
||
|
CASE(Tab);
|
||
|
CASE(Space);
|
||
|
CASE(Hyphen);
|
||
|
CASE(Equal);
|
||
|
CASE(LBracket);
|
||
|
CASE(RBracket);
|
||
|
CASE(Backslash);
|
||
|
CASE(Semicolon);
|
||
|
CASE(Apostrophe);
|
||
|
CASE(Grave);
|
||
|
CASE(Comma);
|
||
|
CASE(Period);
|
||
|
CASE(Slash);
|
||
|
CASE(F1);
|
||
|
CASE(F2);
|
||
|
CASE(F3);
|
||
|
CASE(F4);
|
||
|
CASE(F5);
|
||
|
CASE(F6);
|
||
|
CASE(F7);
|
||
|
CASE(F8);
|
||
|
CASE(F9);
|
||
|
CASE(F10);
|
||
|
CASE(F11);
|
||
|
CASE(F12);
|
||
|
CASE(F13);
|
||
|
CASE(F14);
|
||
|
CASE(F15);
|
||
|
CASE(F16);
|
||
|
CASE(F17);
|
||
|
CASE(F18);
|
||
|
CASE(F19);
|
||
|
CASE(F20);
|
||
|
CASE(F21);
|
||
|
CASE(F22);
|
||
|
CASE(F23);
|
||
|
CASE(F24);
|
||
|
CASE(CapsLock);
|
||
|
CASE(PrintScreen);
|
||
|
CASE(ScrollLock);
|
||
|
CASE(Pause);
|
||
|
CASE(Insert);
|
||
|
CASE(Home);
|
||
|
CASE(PageUp);
|
||
|
CASE(Delete);
|
||
|
CASE(End);
|
||
|
CASE(PageDown);
|
||
|
CASE(Right);
|
||
|
CASE(Left);
|
||
|
CASE(Down);
|
||
|
CASE(Up);
|
||
|
CASE(NumLock);
|
||
|
CASE(NumpadDivide);
|
||
|
CASE(NumpadMultiply);
|
||
|
CASE(NumpadMinus);
|
||
|
CASE(NumpadPlus);
|
||
|
CASE(NumpadEqual);
|
||
|
CASE(NumpadEnter);
|
||
|
CASE(NumpadDecimal);
|
||
|
CASE(Numpad1);
|
||
|
CASE(Numpad2);
|
||
|
CASE(Numpad3);
|
||
|
CASE(Numpad4);
|
||
|
CASE(Numpad5);
|
||
|
CASE(Numpad6);
|
||
|
CASE(Numpad7);
|
||
|
CASE(Numpad8);
|
||
|
CASE(Numpad9);
|
||
|
CASE(Numpad0);
|
||
|
CASE(NonUsBackslash);
|
||
|
CASE(Application);
|
||
|
CASE(Execute);
|
||
|
CASE(ModeChange);
|
||
|
CASE(Help);
|
||
|
CASE(Menu);
|
||
|
CASE(Select);
|
||
|
CASE(Redo);
|
||
|
CASE(Undo);
|
||
|
CASE(Cut);
|
||
|
CASE(Copy);
|
||
|
CASE(Paste);
|
||
|
CASE(VolumeMute);
|
||
|
CASE(VolumeUp);
|
||
|
CASE(VolumeDown);
|
||
|
CASE(MediaPlayPause);
|
||
|
CASE(MediaStop);
|
||
|
CASE(MediaNextTrack);
|
||
|
CASE(MediaPreviousTrack);
|
||
|
CASE(LControl);
|
||
|
CASE(LShift);
|
||
|
CASE(LAlt);
|
||
|
CASE(LSystem);
|
||
|
CASE(RControl);
|
||
|
CASE(RShift);
|
||
|
CASE(RAlt);
|
||
|
CASE(RSystem);
|
||
|
CASE(Back);
|
||
|
CASE(Forward);
|
||
|
CASE(Refresh);
|
||
|
CASE(Stop);
|
||
|
CASE(Search);
|
||
|
CASE(Favorites);
|
||
|
CASE(HomePage);
|
||
|
CASE(LaunchApplication1);
|
||
|
CASE(LaunchApplication2);
|
||
|
CASE(LaunchMail);
|
||
|
CASE(LaunchMediaSelect);
|
||
|
#undef CASE
|
||
|
}
|
||
|
|
||
|
throw std::runtime_error("invalid keyboard scancode");
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////
|
||
|
// Entity showing keyboard events and real-time state on a keyboard
|
||
|
////////////////////////////////////////////////////////////
|
||
|
class KeyboardView : public sf::Drawable, public sf::Transformable
|
||
|
{
|
||
|
public:
|
||
|
explicit KeyboardView(const sf::Font& font) : m_labels(sf::Keyboard::ScancodeCount, sf::Text(font, "", 14))
|
||
|
{
|
||
|
// Check all the scancodes are in the matrix exactly once
|
||
|
{
|
||
|
std::unordered_set<sf::Keyboard::Scancode> scancodesInMatrix;
|
||
|
for (const auto& [cells, marginBottom] : m_matrix)
|
||
|
{
|
||
|
for (const auto& [scancode, size, marginRight] : cells)
|
||
|
{
|
||
|
assert(scancodesInMatrix.count(scancode) == 0);
|
||
|
scancodesInMatrix.insert(scancode);
|
||
|
}
|
||
|
}
|
||
|
assert(scancodesInMatrix.size() == sf::Keyboard::ScancodeCount);
|
||
|
}
|
||
|
|
||
|
// Initialize keys color and label
|
||
|
forEachKey(
|
||
|
[this](sf::Keyboard::Scancode scancode, const sf::FloatRect& rect)
|
||
|
{
|
||
|
const auto scancodeIndex = static_cast<std::size_t>(scancode);
|
||
|
|
||
|
for (std::size_t vertexIndex = 0u; vertexIndex < 6u; ++vertexIndex)
|
||
|
m_triangles[6u * scancodeIndex + vertexIndex]
|
||
|
.color = sf::Keyboard::delocalize(sf::Keyboard::localize(scancode)) != scancode
|
||
|
? sf::Color::Red
|
||
|
: sf::Color::White;
|
||
|
|
||
|
sf::Text& label = m_labels[scancodeIndex];
|
||
|
label.setString(sf::Keyboard::getDescription(scancode));
|
||
|
label.setPosition(rect.position + rect.size / 2.f);
|
||
|
|
||
|
if (rect.size.x < label.getLocalBounds().size.x + padding * 2.f + 2.f)
|
||
|
{
|
||
|
sf::String string = label.getString();
|
||
|
string.replace(" ", "\n");
|
||
|
label.setString(string);
|
||
|
}
|
||
|
while (rect.size.x < label.getLocalBounds().size.x + padding * 2.f + 2.f)
|
||
|
label.setCharacterSize(label.getCharacterSize() - 2);
|
||
|
|
||
|
const sf::FloatRect bounds = label.getLocalBounds();
|
||
|
label.setOrigin({std::round(bounds.position.x + bounds.size.x / 2.f),
|
||
|
std::round(static_cast<float>(label.getCharacterSize()) / 2.f)});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void handle(const sf::Event& event)
|
||
|
{
|
||
|
// React to keyboard events by starting an animation
|
||
|
if (const auto* keyPressed = event.getIf<sf::Event::KeyPressed>())
|
||
|
{
|
||
|
if (keyPressed->scancode != sf::Keyboard::Scan::Unknown)
|
||
|
m_moveFactors[static_cast<std::size_t>(keyPressed->scancode)] = 1.f;
|
||
|
}
|
||
|
else if (const auto* keyReleased = event.getIf<sf::Event::KeyReleased>())
|
||
|
{
|
||
|
if (keyReleased->scancode != sf::Keyboard::Scan::Unknown)
|
||
|
m_moveFactors[static_cast<std::size_t>(keyReleased->scancode)] = -1.f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void update(sf::Time frameTime)
|
||
|
{
|
||
|
// Animate m_moveFactors values linearly towards zero
|
||
|
static constexpr sf::Time transitionDuration = sf::milliseconds(200);
|
||
|
for (float& factor : m_moveFactors)
|
||
|
{
|
||
|
const float absoluteChange = std::min(std::abs(factor), frameTime / transitionDuration);
|
||
|
factor += factor > 0.f ? -absoluteChange : absoluteChange;
|
||
|
}
|
||
|
|
||
|
// Update vertices positions from m_moveFactors and opacity from real-time keyboard state
|
||
|
forEachKey(
|
||
|
[this](sf::Keyboard::Scancode scancode, const sf::FloatRect& rect)
|
||
|
{
|
||
|
const auto scancodeIndex = static_cast<std::size_t>(scancode);
|
||
|
|
||
|
static constexpr std::array square = {
|
||
|
sf::Vector2f(0.f, 0.f),
|
||
|
sf::Vector2f(1.f, 0.f),
|
||
|
sf::Vector2f(1.f, 1.f),
|
||
|
sf::Vector2f(0.f, 1.f),
|
||
|
};
|
||
|
static constexpr std::array cornerIndexes = {0u, 1u, 3u, 3u, 1u, 2u};
|
||
|
|
||
|
const float moveFactor = m_moveFactors[scancodeIndex];
|
||
|
const sf::Vector2f move(0.f, 2.f * moveFactor * (1.f - std::abs(moveFactor)) * padding);
|
||
|
|
||
|
const bool pressed = sf::Keyboard::isKeyPressed(scancode);
|
||
|
|
||
|
for (std::size_t vertexIndex = 0u; vertexIndex < 6u; ++vertexIndex)
|
||
|
{
|
||
|
sf::Vertex& vertex = m_triangles[6u * scancodeIndex + vertexIndex];
|
||
|
const sf::Vector2f& corner = square[cornerIndexes[vertexIndex]];
|
||
|
static constexpr sf::Vector2f pad(padding, padding);
|
||
|
vertex.position = rect.position + pad + (rect.size - 2.f * pad).componentWiseMul(corner) + move;
|
||
|
vertex.color.a = pressed ? 96 : 48;
|
||
|
}
|
||
|
m_labels[scancodeIndex].setPosition(rect.position + rect.size / 2.f + move);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
void draw(sf::RenderTarget& target, sf::RenderStates states) const override
|
||
|
{
|
||
|
states.transform *= getTransform();
|
||
|
target.draw(m_triangles, states);
|
||
|
for (const sf::Text& label : m_labels)
|
||
|
target.draw(label, states);
|
||
|
}
|
||
|
|
||
|
// Template to iterate on scancodes and the corresponding computed rectangle in local coordinates
|
||
|
template <typename F>
|
||
|
void forEachKey(F function) const
|
||
|
{
|
||
|
sf::Vector2f position;
|
||
|
for (const auto& [cells, marginBottom] : m_matrix)
|
||
|
{
|
||
|
for (const auto& [scancode, size, marginRight] : cells)
|
||
|
{
|
||
|
function(scancode, sf::FloatRect(position, size));
|
||
|
position.x += size.x + marginRight;
|
||
|
}
|
||
|
position.x = 0.f;
|
||
|
position.y += keySize + marginBottom;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static constexpr float keySize = 54.f;
|
||
|
static constexpr float padding = 4.f;
|
||
|
|
||
|
struct Cell
|
||
|
{
|
||
|
Cell(sf::Keyboard::Scancode theScancode, sf::Vector2f sizeRatio = {1.f, 1.f}, float marginRightRatio = 0.f) :
|
||
|
scancode(theScancode),
|
||
|
size(sizeRatio * keySize),
|
||
|
marginRight(marginRightRatio * keySize)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Cell(sf::Keyboard::Scancode theScancode, float marginRightRatio) :
|
||
|
Cell(theScancode, {1.f, 1.f}, marginRightRatio)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
sf::Keyboard::Scancode scancode;
|
||
|
sf::Vector2f size;
|
||
|
float marginRight;
|
||
|
};
|
||
|
|
||
|
struct Row
|
||
|
{
|
||
|
Row(std::vector<Cell> theCells, float marginBottomRatio = 0.f) :
|
||
|
cells(std::move(theCells)),
|
||
|
marginBottom(marginBottomRatio * keySize)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
std::vector<Cell> cells;
|
||
|
float marginBottom;
|
||
|
};
|
||
|
|
||
|
const std::array<Row, 9> m_matrix{{
|
||
|
{{{sf::Keyboard::Scan::Escape, 1},
|
||
|
{sf::Keyboard::Scan::F1},
|
||
|
{sf::Keyboard::Scan::F2},
|
||
|
{sf::Keyboard::Scan::F3},
|
||
|
{sf::Keyboard::Scan::F4, 0.5},
|
||
|
{sf::Keyboard::Scan::F5},
|
||
|
{sf::Keyboard::Scan::F6},
|
||
|
{sf::Keyboard::Scan::F7},
|
||
|
{sf::Keyboard::Scan::F8, 0.5},
|
||
|
{sf::Keyboard::Scan::F9},
|
||
|
{sf::Keyboard::Scan::F10},
|
||
|
{sf::Keyboard::Scan::F11},
|
||
|
{sf::Keyboard::Scan::F12, 0.5},
|
||
|
{sf::Keyboard::Scan::PrintScreen},
|
||
|
{sf::Keyboard::Scan::ScrollLock},
|
||
|
{sf::Keyboard::Scan::Pause}},
|
||
|
0.5},
|
||
|
{{{sf::Keyboard::Scan::Grave}, //
|
||
|
{sf::Keyboard::Scan::Num1},
|
||
|
{sf::Keyboard::Scan::Num2},
|
||
|
{sf::Keyboard::Scan::Num3},
|
||
|
{sf::Keyboard::Scan::Num4},
|
||
|
{sf::Keyboard::Scan::Num5},
|
||
|
{sf::Keyboard::Scan::Num6},
|
||
|
{sf::Keyboard::Scan::Num7},
|
||
|
{sf::Keyboard::Scan::Num8},
|
||
|
{sf::Keyboard::Scan::Num9},
|
||
|
{sf::Keyboard::Scan::Num0},
|
||
|
{sf::Keyboard::Scan::Hyphen},
|
||
|
{sf::Keyboard::Scan::Equal},
|
||
|
{sf::Keyboard::Scan::Backspace, {2, 1}, 0.5},
|
||
|
{sf::Keyboard::Scan::Insert},
|
||
|
{sf::Keyboard::Scan::Home},
|
||
|
{sf::Keyboard::Scan::PageUp, 0.5},
|
||
|
{sf::Keyboard::Scan::NumLock},
|
||
|
{sf::Keyboard::Scan::NumpadDivide},
|
||
|
{sf::Keyboard::Scan::NumpadMultiply},
|
||
|
{sf::Keyboard::Scan::NumpadMinus}}},
|
||
|
{{{sf::Keyboard::Scan::Tab, {1.5, 1}},
|
||
|
{sf::Keyboard::Scan::Q},
|
||
|
{sf::Keyboard::Scan::W},
|
||
|
{sf::Keyboard::Scan::E},
|
||
|
{sf::Keyboard::Scan::R},
|
||
|
{sf::Keyboard::Scan::T},
|
||
|
{sf::Keyboard::Scan::Y},
|
||
|
{sf::Keyboard::Scan::U},
|
||
|
{sf::Keyboard::Scan::I},
|
||
|
{sf::Keyboard::Scan::O},
|
||
|
{sf::Keyboard::Scan::P},
|
||
|
{sf::Keyboard::Scan::LBracket},
|
||
|
{sf::Keyboard::Scan::RBracket},
|
||
|
{sf::Keyboard::Scan::Backslash, {1.5, 1}, 0.5},
|
||
|
{sf::Keyboard::Scan::Delete},
|
||
|
{sf::Keyboard::Scan::End},
|
||
|
{sf::Keyboard::Scan::PageDown, 0.5},
|
||
|
{sf::Keyboard::Scan::Numpad7},
|
||
|
{sf::Keyboard::Scan::Numpad8},
|
||
|
{sf::Keyboard::Scan::Numpad9},
|
||
|
{sf::Keyboard::Scan::NumpadPlus}}},
|
||
|
{{{sf::Keyboard::Scan::CapsLock, {1.75, 1}},
|
||
|
{sf::Keyboard::Scan::A},
|
||
|
{sf::Keyboard::Scan::S},
|
||
|
{sf::Keyboard::Scan::D},
|
||
|
{sf::Keyboard::Scan::F},
|
||
|
{sf::Keyboard::Scan::G},
|
||
|
{sf::Keyboard::Scan::H},
|
||
|
{sf::Keyboard::Scan::J},
|
||
|
{sf::Keyboard::Scan::K},
|
||
|
{sf::Keyboard::Scan::L},
|
||
|
{sf::Keyboard::Scan::Semicolon},
|
||
|
{sf::Keyboard::Scan::Apostrophe},
|
||
|
{sf::Keyboard::Scan::Enter, {2.25, 1}, 4},
|
||
|
{sf::Keyboard::Scan::Numpad4},
|
||
|
{sf::Keyboard::Scan::Numpad5},
|
||
|
{sf::Keyboard::Scan::Numpad6},
|
||
|
{sf::Keyboard::Scan::NumpadEqual}}},
|
||
|
{{{sf::Keyboard::Scan::LShift, {1.25, 1}},
|
||
|
{sf::Keyboard::Scan::NonUsBackslash},
|
||
|
{sf::Keyboard::Scan::Z},
|
||
|
{sf::Keyboard::Scan::X},
|
||
|
{sf::Keyboard::Scan::C},
|
||
|
{sf::Keyboard::Scan::V},
|
||
|
{sf::Keyboard::Scan::B},
|
||
|
{sf::Keyboard::Scan::N},
|
||
|
{sf::Keyboard::Scan::M},
|
||
|
{sf::Keyboard::Scan::Comma},
|
||
|
{sf::Keyboard::Scan::Period},
|
||
|
{sf::Keyboard::Scan::Slash},
|
||
|
{sf::Keyboard::Scan::RShift, {2.75, 1}, 1.5},
|
||
|
{sf::Keyboard::Scan::Up, 1.5},
|
||
|
{sf::Keyboard::Scan::Numpad1},
|
||
|
{sf::Keyboard::Scan::Numpad2},
|
||
|
{sf::Keyboard::Scan::Numpad3},
|
||
|
{sf::Keyboard::Scan::NumpadEnter, {1, 2}}}},
|
||
|
{{{sf::Keyboard::Scan::LControl, {1.5, 1}},
|
||
|
{sf::Keyboard::Scan::LSystem, {1.25, 1}},
|
||
|
{sf::Keyboard::Scan::LAlt, {1.5, 1}},
|
||
|
{sf::Keyboard::Scan::Space, {5.75, 1}},
|
||
|
{sf::Keyboard::Scan::RAlt, {1.25, 1}},
|
||
|
{sf::Keyboard::Scan::RSystem, {1.25, 1}},
|
||
|
{sf::Keyboard::Scan::Menu, {1.25, 1}},
|
||
|
{sf::Keyboard::Scan::RControl, {1.25, 1}, 0.5},
|
||
|
{sf::Keyboard::Scan::Left},
|
||
|
{sf::Keyboard::Scan::Down},
|
||
|
{sf::Keyboard::Scan::Right, 0.5},
|
||
|
{sf::Keyboard::Scan::Numpad0, {2, 1}},
|
||
|
{sf::Keyboard::Scan::NumpadDecimal}},
|
||
|
0.5},
|
||
|
{{{sf::Keyboard::Scan::F13},
|
||
|
{sf::Keyboard::Scan::F14},
|
||
|
{sf::Keyboard::Scan::F15},
|
||
|
{sf::Keyboard::Scan::F16},
|
||
|
{sf::Keyboard::Scan::F17},
|
||
|
{sf::Keyboard::Scan::F18},
|
||
|
{sf::Keyboard::Scan::F19},
|
||
|
{sf::Keyboard::Scan::F20},
|
||
|
{sf::Keyboard::Scan::F21},
|
||
|
{sf::Keyboard::Scan::F22},
|
||
|
{sf::Keyboard::Scan::F23},
|
||
|
{sf::Keyboard::Scan::F24}}},
|
||
|
{{{sf::Keyboard::Scan::Application},
|
||
|
{sf::Keyboard::Scan::Execute},
|
||
|
{sf::Keyboard::Scan::ModeChange},
|
||
|
{sf::Keyboard::Scan::Help},
|
||
|
{sf::Keyboard::Scan::Select},
|
||
|
{sf::Keyboard::Scan::Redo},
|
||
|
{sf::Keyboard::Scan::Undo},
|
||
|
{sf::Keyboard::Scan::Cut},
|
||
|
{sf::Keyboard::Scan::Copy},
|
||
|
{sf::Keyboard::Scan::Paste},
|
||
|
{sf::Keyboard::Scan::VolumeMute},
|
||
|
{sf::Keyboard::Scan::VolumeUp},
|
||
|
{sf::Keyboard::Scan::VolumeDown},
|
||
|
{sf::Keyboard::Scan::MediaPlayPause},
|
||
|
{sf::Keyboard::Scan::MediaStop},
|
||
|
{sf::Keyboard::Scan::MediaNextTrack},
|
||
|
{sf::Keyboard::Scan::MediaPreviousTrack}}},
|
||
|
{{{sf::Keyboard::Scan::Back},
|
||
|
{sf::Keyboard::Scan::Forward},
|
||
|
{sf::Keyboard::Scan::Refresh},
|
||
|
{sf::Keyboard::Scan::Stop},
|
||
|
{sf::Keyboard::Scan::Search},
|
||
|
{sf::Keyboard::Scan::Favorites},
|
||
|
{sf::Keyboard::Scan::HomePage},
|
||
|
{sf::Keyboard::Scan::LaunchApplication1},
|
||
|
{sf::Keyboard::Scan::LaunchApplication2},
|
||
|
{sf::Keyboard::Scan::LaunchMail},
|
||
|
{sf::Keyboard::Scan::LaunchMediaSelect}}},
|
||
|
}};
|
||
|
|
||
|
sf::VertexArray m_triangles{sf::PrimitiveType::Triangles, sf::Keyboard::ScancodeCount * 6};
|
||
|
std::vector<sf::Text> m_labels;
|
||
|
std::array<float, sf::Keyboard::ScancodeCount> m_moveFactors{};
|
||
|
};
|
||
|
|
||
|
////////////////////////////////////////////////////////////
|
||
|
// Text with fading opacity outline
|
||
|
////////////////////////////////////////////////////////////
|
||
|
class ShinyText : public sf::Text
|
||
|
{
|
||
|
public:
|
||
|
using sf::Text::Text;
|
||
|
|
||
|
// Start the outline animation
|
||
|
void shine(sf::Color color = sf::Color::Yellow)
|
||
|
{
|
||
|
setOutlineColor(color);
|
||
|
m_remaining = duration;
|
||
|
}
|
||
|
|
||
|
// Fade out ouline
|
||
|
void update(sf::Time frameTime)
|
||
|
{
|
||
|
const float ratio = m_remaining / duration;
|
||
|
const float alpha = std::max(0.f, ratio * (2.f - ratio)) * 0.5f;
|
||
|
|
||
|
sf::Color color = getOutlineColor();
|
||
|
color.a = static_cast<std::uint8_t>(255 * alpha);
|
||
|
setOutlineColor(color);
|
||
|
|
||
|
if (m_remaining > sf::Time::Zero)
|
||
|
m_remaining -= frameTime;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
static constexpr sf::Time duration = sf::milliseconds(150);
|
||
|
sf::Time m_remaining;
|
||
|
};
|
||
|
|
||
|
////////////////////////////////////////////////////////////
|
||
|
// Utilities to create text objets
|
||
|
////////////////////////////////////////////////////////////
|
||
|
|
||
|
constexpr unsigned int textSize = 18u;
|
||
|
constexpr unsigned int space = 2u;
|
||
|
constexpr unsigned int lineSize = textSize + space;
|
||
|
|
||
|
float getSpacingFactor(const sf::Font& font)
|
||
|
{
|
||
|
return static_cast<float>(lineSize) / font.getLineSpacing(textSize);
|
||
|
}
|
||
|
|
||
|
ShinyText makeShinyText(const sf::Font& font, const sf::String& string, sf::Vector2f position)
|
||
|
{
|
||
|
ShinyText text(font, string, textSize);
|
||
|
text.setLineSpacing(getSpacingFactor(font));
|
||
|
text.setOutlineThickness(2.f);
|
||
|
text.setPosition(position);
|
||
|
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
sf::Text makeText(const sf::Font& font, const sf::String& string, sf::Vector2f position)
|
||
|
{
|
||
|
sf::Text text(font, string, textSize);
|
||
|
text.setLineSpacing(getSpacingFactor(font));
|
||
|
text.setPosition(position);
|
||
|
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////
|
||
|
// Utilities to describe keyboard events
|
||
|
////////////////////////////////////////////////////////////
|
||
|
|
||
|
template <typename KeyEventType>
|
||
|
bool somethingIsOdd(const KeyEventType& keyEvent)
|
||
|
{
|
||
|
return keyEvent.code == sf::Keyboard::Key::Unknown || keyEvent.scancode == sf::Keyboard::Scan::Unknown ||
|
||
|
sf::Keyboard::getDescription(keyEvent.scancode) == "" ||
|
||
|
sf::Keyboard::localize(keyEvent.scancode) != keyEvent.code ||
|
||
|
sf::Keyboard::delocalize(keyEvent.code) != keyEvent.scancode;
|
||
|
}
|
||
|
|
||
|
// Append information to string about a keyboard event
|
||
|
template <typename KeyEventType>
|
||
|
sf::String keyEventDescription(sf::String text, const KeyEventType& keyEvent)
|
||
|
{
|
||
|
text += "\n\n";
|
||
|
text += keyIdentifier(keyEvent.code);
|
||
|
text += "\n";
|
||
|
text += scancodeIdentifier(keyEvent.scancode);
|
||
|
if (somethingIsOdd(keyEvent))
|
||
|
{
|
||
|
text += "\nLocalized:\t";
|
||
|
text += keyIdentifier(sf::Keyboard::localize(keyEvent.scancode));
|
||
|
text += "\nDelocalized:\t";
|
||
|
text += scancodeIdentifier(sf::Keyboard::delocalize(keyEvent.code));
|
||
|
}
|
||
|
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
// Make a string describing a text event
|
||
|
sf::String textEventDescription(const sf::Event::TextEntered& textEntered)
|
||
|
{
|
||
|
sf::String text = "Text Entered\n\n";
|
||
|
text += static_cast<char32_t>(textEntered.unicode);
|
||
|
text += "\nU+";
|
||
|
|
||
|
std::ostringstream oss;
|
||
|
oss << std::hex << std::setw(4) << std::setfill('0') << textEntered.unicode;
|
||
|
text += oss.str();
|
||
|
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////
|
||
|
/// Entry point of application
|
||
|
///
|
||
|
/// \return Application exit code
|
||
|
///
|
||
|
////////////////////////////////////////////////////////////
|
||
|
int main()
|
||
|
{
|
||
|
// Create the main window
|
||
|
sf::RenderWindow window(sf::VideoMode({1280, 720}), "Keyboard", sf::Style::Titlebar | sf::Style::Close);
|
||
|
window.setFramerateLimit(25);
|
||
|
|
||
|
// Load sound buffers
|
||
|
const sf::SoundBuffer errorSoundBuffer(resourcesDir() / "error_005.ogg");
|
||
|
const sf::SoundBuffer pressedSoundBuffer(resourcesDir() / "mouseclick1.ogg");
|
||
|
const sf::SoundBuffer releasedSoundBuffer(resourcesDir() / "mouserelease1.ogg");
|
||
|
|
||
|
// Create sound objects to play them upon keyboard events
|
||
|
sf::Sound errorSound(errorSoundBuffer);
|
||
|
sf::Sound pressedSound(pressedSoundBuffer);
|
||
|
sf::Sound releasedSound(releasedSoundBuffer);
|
||
|
|
||
|
// Open the font used for all texts
|
||
|
const sf::Font font(resourcesDir() / "Tuffy.ttf");
|
||
|
|
||
|
// Create object to display all scancodes descriptions, related events and real-time state
|
||
|
KeyboardView keyboardView(font);
|
||
|
keyboardView.setPosition({16, 16});
|
||
|
|
||
|
// Create text to display information about keyboard events and key codes real-time state
|
||
|
ShinyText keyPressedText(makeShinyText(font, "Key Pressed", {16, 575}));
|
||
|
ShinyText keyReleasedText(makeShinyText(font, "Key Released", {300, 575}));
|
||
|
ShinyText textEnteredText(makeShinyText(font, "Text Entered", {600, 575}));
|
||
|
sf::Text keyPressedCheckText(makeText(font, "", {900, 575}));
|
||
|
|
||
|
sf::Clock clock;
|
||
|
while (window.isOpen())
|
||
|
{
|
||
|
// Handle events
|
||
|
while (const std::optional event = window.pollEvent())
|
||
|
{
|
||
|
// Window closed: exit
|
||
|
if (event->is<sf::Event::Closed>())
|
||
|
{
|
||
|
window.close();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Window size changed: adjust view appropriately
|
||
|
if (const auto* resized = event->getIf<sf::Event::Resized>())
|
||
|
window.setView(sf::View(sf::FloatRect({}, sf::Vector2f(resized->size))));
|
||
|
|
||
|
// Key events: update text and play sound
|
||
|
if (const auto* keyPressed = event->getIf<sf::Event::KeyPressed>())
|
||
|
{
|
||
|
keyPressedText.setString(keyEventDescription("Key Pressed", *keyPressed));
|
||
|
if (somethingIsOdd(*keyPressed))
|
||
|
{
|
||
|
keyPressedText.shine(sf::Color::Red);
|
||
|
errorSound.play();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
keyPressedText.shine(sf::Color::Green);
|
||
|
pressedSound.play();
|
||
|
}
|
||
|
}
|
||
|
if (const auto* keyReleased = event->getIf<sf::Event::KeyReleased>())
|
||
|
{
|
||
|
keyReleasedText.setString(keyEventDescription("Key Released", *keyReleased));
|
||
|
if (somethingIsOdd(*keyReleased))
|
||
|
{
|
||
|
keyReleasedText.shine(sf::Color::Red);
|
||
|
errorSound.play();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
keyReleasedText.shine(sf::Color::Green);
|
||
|
releasedSound.play();
|
||
|
}
|
||
|
}
|
||
|
if (const auto* textEntered = event->getIf<sf::Event::TextEntered>())
|
||
|
{
|
||
|
textEnteredText.setString(textEventDescription(*textEntered));
|
||
|
textEnteredText.shine();
|
||
|
}
|
||
|
|
||
|
// Let the KeyboardView process the event
|
||
|
keyboardView.handle(*event);
|
||
|
}
|
||
|
|
||
|
// Update animations and displayed keyboard real-time state
|
||
|
const sf::Time frameTime = clock.restart();
|
||
|
keyboardView.update(frameTime);
|
||
|
keyPressedText.update(frameTime);
|
||
|
keyReleasedText.update(frameTime);
|
||
|
textEnteredText.update(frameTime);
|
||
|
{
|
||
|
sf::String text = "isKeyPressed(sf::Keyboard::Key)\n\n";
|
||
|
for (std::size_t keyIndex = 0u; keyIndex < sf::Keyboard::KeyCount; ++keyIndex)
|
||
|
{
|
||
|
const auto key = static_cast<sf::Keyboard::Key>(keyIndex);
|
||
|
if (sf::Keyboard::isKeyPressed(key))
|
||
|
text += keyIdentifier(key) + "\n";
|
||
|
}
|
||
|
keyPressedCheckText.setString(text);
|
||
|
}
|
||
|
|
||
|
// Render frame
|
||
|
window.clear();
|
||
|
window.draw(keyboardView);
|
||
|
window.draw(keyPressedText);
|
||
|
window.draw(keyReleasedText);
|
||
|
window.draw(textEnteredText);
|
||
|
window.draw(keyPressedCheckText);
|
||
|
window.display();
|
||
|
}
|
||
|
|
||
|
return EXIT_SUCCESS;
|
||
|
}
|