Add keyboard example program

This commit is contained in:
kimci86 2024-11-22 21:34:00 +01:00
parent 4271697e04
commit 1d652e0b1e
8 changed files with 901 additions and 15 deletions

View File

@ -44,6 +44,7 @@ if(SFML_BUILD_GRAPHICS)
endif()
if(SFML_BUILD_GRAPHICS AND SFML_BUILD_AUDIO)
add_subdirectory(tennis)
add_subdirectory(keyboard)
if(NOT SFML_OPENGL_ES)
add_subdirectory(sound_effects)

View File

@ -2,19 +2,22 @@
All assets are under public domain (CC0):
| Name | Author | Link |
| ------------------------------- | ------------------------- | -------------------------- |
| Tuffy 1.1 font | Thatcher Ulrich | [Ulrich's fonts][1] |
| sounds/resources/doodle_pop.ogg | Elijah Hopp | [public-domain][2] |
| tennis/resources/ball.wav | Elijah Hopp | [public-domain][2] |
| opengl/resources/background.jpg | Nidhoggn | [Open Game Art][3] |
| shader/resources/background.jpg | Arcana Dea | [Public Domain Images][4] |
| shader/resources/devices.png | Kenney.nl | [Game Icons Pack][5] |
| sound/resources/ding.flac | Kenney.nl | [Interface Sounds Pack][6] |
| sound/resources/ding.mp3 | Kenney.nl | [Interface Sounds Pack][6] |
| win32/resources/image1.jpg | Kenney.nl | [Toon Character Pack][7] |
| win32/resources/image2.jpg | Kenney.nl | [Toon Character Pack][7] |
| sound/resources/killdeer.wav | US National Park Services | [Bird sounds][8] |
| Name | Author | Link |
| ------------------------------------ | ------------------------- | -------------------------- |
| Tuffy 1.1 and 1.28 fonts | Thatcher Ulrich | [Ulrich's fonts][1] |
| sounds/resources/doodle_pop.ogg | Elijah Hopp | [public-domain][2] |
| tennis/resources/ball.wav | Elijah Hopp | [public-domain][2] |
| opengl/resources/background.jpg | Nidhoggn | [Open Game Art][3] |
| shader/resources/background.jpg | Arcana Dea | [Public Domain Images][4] |
| shader/resources/devices.png | Kenney.nl | [Game Icons Pack][5] |
| sound/resources/ding.flac | Kenney.nl | [Interface Sounds Pack][6] |
| sound/resources/ding.mp3 | Kenney.nl | [Interface Sounds Pack][6] |
| keyboard/resources/error_005.ogg | Kenney.nl | [Interface Sounds Pack][6] |
| keyboard/resources/mouseclick1.ogg | Kenney.nl | [UI Audio Pack][7] |
| keyboard/resources/mouserelease1.ogg | Kenney.nl | [UI Audio Pack][7] |
| win32/resources/image1.jpg | Kenney.nl | [Toon Character Pack][8] |
| win32/resources/image2.jpg | Kenney.nl | [Toon Character Pack][8] |
| sound/resources/killdeer.wav | US National Park Services | [Bird sounds][9] |
[1]: http://tulrich.com/fonts/
[2]: https://github.com/elijahfhopp/public-domain
@ -22,5 +25,6 @@ All assets are under public domain (CC0):
[4]: https://www.publicdomainpictures.net/en/view-image.php?image=10979&picture=monarch-butterfly
[5]: https://www.kenney.nl/assets/game-icons
[6]: https://www.kenney.nl/assets/interface-sounds
[7]: https://www.kenney.nl/assets/toon-characters-1
[8]: https://www.nps.gov/subjects/sound/sounds-killdeer.htm
[7]: https://www.kenney.nl/assets/ui-audio
[8]: https://www.kenney.nl/assets/toon-characters-1
[9]: https://www.nps.gov/subjects/sound/sounds-killdeer.htm

View File

@ -0,0 +1,16 @@
# all source files
set(SRC Keyboard.cpp)
if(SFML_OS_IOS)
set(RESOURCES
resources/error_005.ogg
resources/mouseclick1.ogg
resources/mouserelease1.ogg
resources/Tuffy.ttf)
set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
endif()
# define the keyboard target
sfml_add_example(keyboard GUI_APP
SOURCES ${SRC}
BUNDLE_RESOURCES ${RESOURCES}
DEPENDS SFML::Audio SFML::Graphics)

View File

@ -0,0 +1,865 @@
////////////////////////////////////////////////////////////
// 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;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.