mirror of
https://github.com/SFML/SFML.git
synced 2024-11-28 14:21:04 +08:00
Add keyboard example program
This commit is contained in:
parent
4271697e04
commit
1d652e0b1e
@ -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)
|
||||
|
@ -3,8 +3,8 @@
|
||||
All assets are under public domain (CC0):
|
||||
|
||||
| Name | Author | Link |
|
||||
| ------------------------------- | ------------------------- | -------------------------- |
|
||||
| Tuffy 1.1 font | Thatcher Ulrich | [Ulrich's fonts][1] |
|
||||
| ------------------------------------ | ------------------------- | -------------------------- |
|
||||
| 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] |
|
||||
@ -12,9 +12,12 @@ All assets are under public domain (CC0):
|
||||
| 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] |
|
||||
| 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
|
||||
|
16
examples/keyboard/CMakeLists.txt
Normal file
16
examples/keyboard/CMakeLists.txt
Normal 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)
|
865
examples/keyboard/Keyboard.cpp
Normal file
865
examples/keyboard/Keyboard.cpp
Normal 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;
|
||||
}
|
BIN
examples/keyboard/resources/Tuffy.ttf
Executable file
BIN
examples/keyboard/resources/Tuffy.ttf
Executable file
Binary file not shown.
BIN
examples/keyboard/resources/error_005.ogg
Normal file
BIN
examples/keyboard/resources/error_005.ogg
Normal file
Binary file not shown.
BIN
examples/keyboard/resources/mouseclick1.ogg
Normal file
BIN
examples/keyboard/resources/mouseclick1.ogg
Normal file
Binary file not shown.
BIN
examples/keyboard/resources/mouserelease1.ogg
Normal file
BIN
examples/keyboard/resources/mouserelease1.ogg
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user