Keyboard.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. #include <cpp3ds/System/FileSystem.hpp>
  2. #include "Keyboard.hpp"
  3. using namespace tinyxml2;
  4. namespace util3ds {
  5. Keyboard::Keyboard() {
  6. m_loaded = false;
  7. m_activeButton = NULL;
  8. m_usingTempLayout = false;
  9. m_needsUpdate = false;
  10. }
  11. void Keyboard::loadFromFile(const std::string &filename)
  12. {
  13. m_loaded = false;
  14. m_activeButton = NULL;
  15. m_filename = cpp3ds::FileSystem::getFilePath(filename);
  16. if (m_xml.LoadFile(m_filename.c_str()) != XML_NO_ERROR)
  17. return;
  18. // Keyboard element
  19. const XMLElement* keyboard = m_xml.FirstChildElement("keyboard");
  20. if (keyboard == NULL) {
  21. cpp3ds::err() << "No \"keyboard\" element defined in: " << filename << std::endl;
  22. return;
  23. }
  24. const char* defaultLayoutName = keyboard->Attribute("default-layout");
  25. const char* image = keyboard->Attribute("image");
  26. const char* font = keyboard->Attribute("font");
  27. std::string path = m_filename.substr(0, m_filename.find_last_of("/"));
  28. if (image)
  29. m_texture.loadFromFile(path + "/" + image);
  30. m_usingFont = font != NULL;
  31. if (m_usingFont)
  32. m_usingFont = m_font.loadFromFile(path + "/" + font);
  33. // Style elements
  34. const XMLElement* styleNode = keyboard->FirstChildElement("style");
  35. if (styleNode == NULL) {
  36. cpp3ds::err() << "At least one \"style\" element needed in: " << filename << std::endl;
  37. return;
  38. }
  39. loadStyles(styleNode);
  40. // Layout element(s)
  41. m_layouts.clear();
  42. const XMLElement* layoutNode = keyboard->FirstChildElement("layout");
  43. if (layoutNode == NULL) {
  44. cpp3ds::err() << "At least one \"layout\" element needed in: " << filename << std::endl;
  45. return;
  46. }
  47. while (layoutNode != NULL) {
  48. Layout layout;
  49. layout.name = layoutNode->Attribute("name");
  50. layout.label = layoutNode->Attribute("label");
  51. if (!layout.name.empty()) {
  52. if (layout.name == defaultLayoutName)
  53. m_layoutIndex = m_layouts.size();
  54. // Load all Button elements
  55. loadButtons(layout, layoutNode);
  56. m_layouts.push_back(layout);
  57. } else {
  58. cpp3ds::err() << "Layout element has no \"name\" attribute. Skipping it." << std::endl;
  59. }
  60. layoutNode = layoutNode->NextSiblingElement("layout");
  61. }
  62. // Input element
  63. const XMLElement* inputNode = keyboard->FirstChildElement("input");
  64. if (inputNode == NULL) {
  65. cpp3ds::err() << "No \"input\" element defined in: " << filename << std::endl;
  66. return;
  67. } else {
  68. const char* styleName = inputNode->Attribute("style");
  69. int x = inputNode->IntAttribute("x");
  70. int y = inputNode->IntAttribute("y");
  71. int w = inputNode->IntAttribute("w");
  72. int h = inputNode->IntAttribute("h");
  73. m_input.rect = cpp3ds::IntRect(x, y, w, h);
  74. inputNode->QueryIntAttribute("max-width", &m_input.maxWidth);
  75. float cursorHeight = 12.f;
  76. float cursorWidth = 1.f;
  77. inputNode->QueryFloatAttribute("cursor-height", &cursorHeight);
  78. inputNode->QueryFloatAttribute("cursor-width", &cursorWidth);
  79. m_cursor.setSize(cpp3ds::Vector2f(cursorWidth, cursorHeight));
  80. m_cursor.setFillColor(cpp3ds::Color::White);
  81. m_cursor.setPosition(0, y + (h - cursorHeight) / 2);
  82. m_input.styleIndex = 0;
  83. for (const Style& style: m_styles) {
  84. if (style.name == styleName)
  85. break;
  86. m_input.styleIndex++;
  87. }
  88. }
  89. m_loaded = true;
  90. updateVertices();
  91. }
  92. bool Keyboard::fillState(State& state, const XMLElement* styleNode, const char* stateName)
  93. {
  94. const XMLElement* stateNode = styleNode->FirstChildElement("state");
  95. while (stateNode != NULL) {
  96. if (strcmp(stateName, stateNode->Attribute("name")) == 0)
  97. break;
  98. stateNode = stateNode->NextSiblingElement("state");
  99. }
  100. if (stateNode == NULL)
  101. return false;
  102. const XMLElement* textNode = stateNode->FirstChildElement("text");
  103. if (textNode != NULL) {
  104. textNode->QueryUnsignedAttribute("fontsize", &state.fontSize);
  105. textNode->QueryIntAttribute("offsetx", &state.offsetX);
  106. textNode->QueryIntAttribute("offsety", &state.offsetY);
  107. state.color = cpp3ds::Color::Black;
  108. }
  109. const XMLElement* imageNode = stateNode->FirstChildElement("image");
  110. if (imageNode != NULL) {
  111. int x = imageNode->IntAttribute("x");
  112. int y = imageNode->IntAttribute("y");
  113. int w = imageNode->IntAttribute("w");
  114. int h = imageNode->IntAttribute("h");
  115. state.textureRect = cpp3ds::IntRect(x, y, w, h);
  116. }
  117. return true;
  118. }
  119. void Keyboard::loadStyles(const XMLElement* styleNode)
  120. {
  121. m_styles.clear();
  122. while (styleNode != NULL) {
  123. Style style;
  124. style.name = styleNode->Attribute("name");
  125. if (!style.name.empty()) {
  126. if (!fillState(style.defaultState, styleNode, "default")) {
  127. cpp3ds::err() << "Style element has no State element named \"default\". Skipping it." << std::endl;
  128. } else {
  129. if (!fillState(style.activeState, styleNode, "active"))
  130. style.activeState = style.defaultState;
  131. m_styles.push_back(style);
  132. }
  133. } else {
  134. cpp3ds::err() << "Style element has no \"name\" attribute. Skipping it." << std::endl;
  135. }
  136. styleNode = styleNode->NextSiblingElement("style");
  137. }
  138. }
  139. void Keyboard::loadButtons(Layout& layout, const XMLElement* layoutNode) {
  140. const XMLElement *buttonNode = layoutNode->FirstChildElement("button");
  141. while (buttonNode != NULL) {
  142. const char* styleName = buttonNode->Attribute("style");
  143. if (styleName != NULL) {
  144. Button button;
  145. int x = buttonNode->IntAttribute("x");
  146. int y = buttonNode->IntAttribute("y");
  147. int w = buttonNode->IntAttribute("w");
  148. int h = buttonNode->IntAttribute("h");
  149. button.styleIndex = 0;
  150. for (const Style& style: m_styles) {
  151. if (style.name == styleName)
  152. break;
  153. button.styleIndex++;
  154. }
  155. if (button.styleIndex < m_styles.size()) {
  156. if (buttonNode->GetText() != NULL) {
  157. std::string stringUtf8 = buttonNode->GetText();
  158. std::wstring stringUtf32;
  159. cpp3ds::Utf8::toUtf32(stringUtf8.begin(), stringUtf8.end(), std::back_inserter(stringUtf32));
  160. button.data = stringUtf32;
  161. button.text.setString(button.data);
  162. }
  163. button.type = Key;
  164. const char* type = buttonNode->Attribute("type");
  165. if (type != NULL) {
  166. if (strcmp(type, "layoutmenu") == 0) {
  167. button.type = LayoutMenu;
  168. } else if (strcmp(type, "layoutchange") == 0) {
  169. button.type = LayoutChange;
  170. button.data = buttonNode->Attribute("layout");
  171. } else if (strcmp(type, "layoutchangetemp") == 0) {
  172. button.type = LayoutChangeTemp;
  173. button.data = buttonNode->Attribute("layout");
  174. } else if (strcmp(type, "enter") == 0) {
  175. button.type = Enter;
  176. } else if (strcmp(type, "backspace") == 0) {
  177. button.type = Backspace;
  178. }
  179. }
  180. button.rect = cpp3ds::IntRect(x, y, w, h);
  181. if (m_usingFont)
  182. button.text.setFont(m_font);
  183. layout.buttons.push_back(button);
  184. } else {
  185. cpp3ds::err() << "Key style \"" << styleName << "\" not found. Skipping the key." << std::endl;
  186. }
  187. } else {
  188. cpp3ds::err() << "Key element has no \"style\" attribute. Skipping it." << std::endl;
  189. }
  190. buttonNode = buttonNode->NextSiblingElement("button");
  191. }
  192. }
  193. void Keyboard::updateButtonVertices(Button& button)
  194. {
  195. const Style& style = m_styles[button.styleIndex];
  196. const State& state = &button == m_activeButton ? style.activeState : style.defaultState;
  197. button.text.setCharacterSize(state.fontSize);
  198. button.text.setFillColor(state.color);
  199. button.text.setPosition(button.rect.left + state.offsetX, button.rect.top + state.offsetY);
  200. cpp3ds::Vertex vertices[4];
  201. vertices[0].position = cpp3ds::Vector2f(button.rect.left, button.rect.top);
  202. vertices[1].position = cpp3ds::Vector2f(button.rect.left + button.rect.width, button.rect.top);
  203. vertices[2].position = cpp3ds::Vector2f(button.rect.left, button.rect.top + button.rect.height);
  204. vertices[3].position = cpp3ds::Vector2f(button.rect.left + button.rect.width, button.rect.top + button.rect.height);
  205. vertices[0].texCoords = cpp3ds::Vector2f(state.textureRect.left, state.textureRect.top);
  206. vertices[1].texCoords = cpp3ds::Vector2f(state.textureRect.left + state.textureRect.width, state.textureRect.top);
  207. vertices[2].texCoords = cpp3ds::Vector2f(state.textureRect.left, state.textureRect.top + state.textureRect.height);
  208. vertices[3].texCoords = cpp3ds::Vector2f(state.textureRect.left + state.textureRect.width, state.textureRect.top + state.textureRect.height);
  209. m_vertices.append(vertices[0]);
  210. m_vertices.append(vertices[2]);
  211. m_vertices.append(vertices[3]);
  212. m_vertices.append(vertices[1]);
  213. m_vertices.append(vertices[0]);
  214. m_vertices.append(vertices[3]);
  215. }
  216. void Keyboard::updateVertices()
  217. {
  218. m_vertices.clear();
  219. Layout& layout = m_layouts[m_usingTempLayout ? m_tempLayoutIndex: m_layoutIndex];
  220. for (Button& button: layout.buttons)
  221. {
  222. updateButtonVertices(button);
  223. if (button.type == LayoutMenu)
  224. button.text.setString(layout.label);
  225. }
  226. updateButtonVertices(m_input);
  227. // Update input text position
  228. const Style& style = m_styles[m_input.styleIndex];
  229. m_input.text.setFillColor(cpp3ds::Color::White);
  230. m_input.text.setPosition(static_cast<int>(160.f + style.defaultState.offsetX - m_input.text.getGlobalBounds().width / 2),
  231. m_input.text.getPosition().y);
  232. // Update cursor position
  233. cpp3ds::Text dummyText(m_input.data.substring(m_input.data.getSize() - m_input.cursorPosition, m_input.cursorPosition),
  234. *m_input.text.getFont(),
  235. m_input.text.getCharacterSize());
  236. m_cursor.setPosition(m_input.text.getPosition().x + m_input.text.getGlobalBounds().width - dummyText.getLocalBounds().width,
  237. m_cursor.getPosition().y);
  238. m_needsUpdate = false;
  239. }
  240. bool Keyboard::processEvents(const cpp3ds::Event &event)
  241. {
  242. if (!m_loaded)
  243. return true;
  244. Layout& layout = m_layouts[m_usingTempLayout ? m_tempLayoutIndex: m_layoutIndex];
  245. if (event.type == cpp3ds::Event::TouchBegan) {
  246. for (Button& button: layout.buttons) {
  247. if (button.rect.contains(event.touch.x, event.touch.y)) {
  248. m_activeButton = &button;
  249. m_clockKeyPressStart.restart();
  250. }
  251. }
  252. if (m_activeButton) {
  253. m_usingTempLayout = false;
  254. processActiveKey();
  255. }
  256. // Check for cursor position change
  257. const float cursorPadding = 6.f;
  258. cpp3ds::FloatRect inputBounds = m_input.text.getGlobalBounds();
  259. inputBounds.left -= cursorPadding;
  260. inputBounds.width += cursorPadding * 2;
  261. if (inputBounds.contains(event.touch.x, event.touch.y)) {
  262. cpp3ds::String inputString = m_input.text.getString();
  263. cpp3ds::Text testText("", *m_input.text.getFont(), m_input.text.getCharacterSize());
  264. float clickedPosition = inputBounds.left + inputBounds.width - event.touch.x - cursorPadding + 1.f;
  265. int i;
  266. for (i = 0; i <= inputString.getSize(); i++) {
  267. testText.setString(inputString.substring(inputString.getSize() - i, i));
  268. float width = testText.getLocalBounds().width;
  269. if (width > clickedPosition) {
  270. if (i == 0)
  271. break;
  272. testText.setString(inputString.substring(inputString.getSize() - i, 1));
  273. if (clickedPosition > width - testText.getLocalBounds().width / 2.f) {
  274. break;
  275. } else {
  276. i--;
  277. break;
  278. }
  279. }
  280. }
  281. if (i > inputString.getSize())
  282. i = inputString.getSize();
  283. m_input.cursorPosition = i;
  284. }
  285. m_needsUpdate = true;
  286. }
  287. if (event.type == cpp3ds::Event::TouchEnded) {
  288. if (m_activeButton) {
  289. m_activeButton = NULL;
  290. m_needsUpdate = true;
  291. }
  292. }
  293. if (event.type == cpp3ds::Event::TouchMoved) {
  294. if (m_activeButton && !m_activeButton->rect.contains(event.touch.x, event.touch.y)) {
  295. m_activeButton = NULL;
  296. m_needsUpdate = true;
  297. }
  298. }
  299. if (event.type == cpp3ds::Event::KeyPressed) {
  300. if (event.key.code == cpp3ds::Keyboard::A) {
  301. enterText();
  302. m_needsUpdate = true;
  303. return false;
  304. }
  305. }
  306. return true;
  307. }
  308. void Keyboard::update(float delta)
  309. {
  310. // Repeat key being held
  311. if (m_activeButton != NULL)
  312. if (m_clockKeyPressStart.getElapsedTime() > cpp3ds::milliseconds(700))
  313. if (m_clockKeyRepeat.getElapsedTime() > cpp3ds::milliseconds(40)) {
  314. m_clockKeyRepeat.restart();
  315. processActiveKey();
  316. }
  317. if (m_needsUpdate)
  318. updateVertices();
  319. }
  320. void Keyboard::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
  321. {
  322. if (!m_loaded)
  323. return;
  324. states.transform *= getTransform();
  325. states.texture = &m_texture;
  326. target.draw(m_vertices, states);
  327. states.texture = nullptr;
  328. const Layout& layout = m_layouts[m_usingTempLayout ? m_tempLayoutIndex: m_layoutIndex];
  329. for (const Button& button: layout.buttons) {
  330. if (!button.text.getString().isEmpty())
  331. target.draw(button.text, states);
  332. }
  333. target.draw(m_input.text, states);
  334. if (m_cursorClock.getElapsedTime() > cpp3ds::seconds(1.f))
  335. m_cursorClock.restart();
  336. else if (m_cursorClock.getElapsedTime() > cpp3ds::seconds(0.5f))
  337. target.draw(m_cursor, states);
  338. }
  339. bool Keyboard::popString(cpp3ds::String& string)
  340. {
  341. if (m_strings.empty())
  342. return false;
  343. string = m_strings.front();
  344. m_strings.pop();
  345. return true;
  346. }
  347. void Keyboard::enterText()
  348. {
  349. if (!m_input.data.isEmpty()) {
  350. m_strings.push(m_input.data);
  351. m_input.data.clear();
  352. m_input.text.setString(m_input.data);
  353. m_input.cursorPosition = 0;
  354. }
  355. }
  356. void Keyboard::processActiveKey()
  357. {
  358. if (!m_activeButton)
  359. return;
  360. switch (m_activeButton->type) {
  361. case Enter:
  362. enterText();
  363. break;
  364. case Backspace:
  365. if (m_input.cursorPosition == m_input.data.getSize())
  366. break;
  367. m_input.data.erase(m_input.data.getSize() - m_input.cursorPosition - 1, 1);
  368. m_input.text.setString(m_input.data);
  369. break;
  370. case LayoutChange:
  371. m_layoutIndex = 0;
  372. for (const Layout& layout: m_layouts) {
  373. if (layout.name == m_activeButton->data)
  374. break;
  375. m_layoutIndex++;
  376. }
  377. break;
  378. case LayoutChangeTemp:
  379. m_usingTempLayout = true;
  380. m_tempLayoutIndex = 0;
  381. for (const Layout& layout: m_layouts) {
  382. if (layout.name == m_activeButton->data)
  383. break;
  384. m_tempLayoutIndex++;
  385. }
  386. break;
  387. default:
  388. if (m_input.text.getGlobalBounds().width < m_input.maxWidth) {
  389. m_input.data.insert(m_input.data.getSize() - m_input.cursorPosition, m_activeButton->data);
  390. m_input.text.setString(m_input.data);
  391. }
  392. }
  393. m_needsUpdate = true;
  394. }
  395. cpp3ds::String Keyboard::getCurrentInput() const
  396. {
  397. return m_input.text.getString();
  398. }
  399. void Keyboard::setCurrentInput(const cpp3ds::String &input)
  400. {
  401. m_input.text.setString(input);
  402. m_input.data = input;
  403. }
  404. } // namesapce util3ds