Ver código fonte

:sparkles: first working version of a line input field

Felix Bytow 1 ano atrás
pai
commit
c8fbe994b9

+ 14 - 0
game/DummyState.cxx

@@ -5,6 +5,7 @@
 
 void DummyState::on_enter(GameStateManager& gsm)
 {
+  name_input_.set_value("");
   name_input_.set_focus(true);
 
   pause_button_.set_on_click([&gsm] {
@@ -12,6 +13,19 @@ void DummyState::on_enter(GameStateManager& gsm)
   });
 }
 
+void DummyState::on_leave()
+{
+  SDL_StopTextInput();
+}
+
+void DummyState::on_event(GameStateManager& gsm, SDL_Event const& evt)
+{
+  name_input_.on_event(evt);
+
+  if (evt.type==SDL_KEYUP && evt.key.keysym.scancode==SDL_SCANCODE_ESCAPE)
+    gsm.push_state(GameStates::MainMenu);
+}
+
 void DummyState::update(GameStateManager& gsm, std::chrono::milliseconds delta_time)
 {
   pause_button_.update();

+ 5 - 1
game/DummyState.hxx

@@ -11,13 +11,17 @@ class DummyState final : public GameState {
 public:
   void on_enter(GameStateManager& gsm) override;
 
+  void on_leave() override;
+
+  void on_event(GameStateManager& gsm, SDL_Event const& evt) override;
+
   void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) override;
 
   void render(SDLRenderer& renderer) override;
 
 private:
   Button pause_button_{"Pause", 15, 15, 800, 80};
-  LineInput name_input_{15, 200, 800, 80, "Felix"};
+  LineInput name_input_{15, 200, 800, 80};
 };
 
 #endif // SNAKE_DUMMYSTATE_HXX

+ 8 - 0
game/GameState.hxx

@@ -5,6 +5,8 @@
 
 #include <chrono>
 
+#include <SDL.h>
+
 class GameStateManager;
 
 class SDLRenderer;
@@ -17,6 +19,12 @@ public:
 
   virtual void on_leave() { }
 
+  virtual void on_event(GameStateManager& gsm, SDL_Event const& event)
+  {
+    (void) gsm;
+    (void) event;
+  }
+
   virtual void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) = 0;
 
   virtual void render(SDLRenderer& renderer) = 0;

+ 7 - 7
game/MenuState.cxx

@@ -21,6 +21,13 @@ void MenuState::on_enter(GameStateManager& gsm)
   });
 }
 
+void MenuState::on_event(GameStateManager& gsm, SDL_Event const& evt)
+{
+  if (evt.type==SDL_KEYUP && evt.key.keysym.scancode==SDL_SCANCODE_ESCAPE) {
+    gsm.pop_state();
+  }
+}
+
 void MenuState::update(GameStateManager& gsm, std::chrono::milliseconds delta_time)
 {
   (void) delta_time;
@@ -30,13 +37,6 @@ void MenuState::update(GameStateManager& gsm, std::chrono::milliseconds delta_ti
   new_game_button_.update();
   continue_button_.update();
   quit_button_.update();
-
-  auto const key_state = SDL_GetKeyboardState(nullptr);
-  auto const escape_pressed = !!key_state[SDL_SCANCODE_ESCAPE];
-  if (!escape_pressed && escape_pressed_) {
-    gsm.pop_state();
-  }
-  escape_pressed_ = escape_pressed;
 }
 
 void MenuState::render(SDLRenderer& renderer)

+ 2 - 1
game/MenuState.hxx

@@ -10,6 +10,8 @@ class MenuState final : public GameState {
 public:
   void on_enter(GameStateManager& gsm) override;
 
+  void on_event(GameStateManager& gsm, SDL_Event const& evt) override;
+
   void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) override;
 
   void render(SDLRenderer& renderer) override;
@@ -21,7 +23,6 @@ private:
   Button new_game_button_{"New game", 0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, UiColor::Green};
   Button continue_button_{"Continue", 0, 0, BUTTON_WIDTH, BUTTON_HEIGHT};
   Button quit_button_{"Quit", 0, 0, BUTTON_WIDTH, BUTTON_HEIGHT};
-  bool escape_pressed_{false};
 };
 
 #endif // SNAKE_MENUSTATE_HXX

+ 19 - 3
game/SplashState.cxx

@@ -4,11 +4,27 @@
 
 using namespace std::chrono_literals;
 
+void SplashState::on_event(GameStateManager& gsm, SDL_Event const& evt)
+{
+  if (evt.type==SDL_KEYUP) {
+    switch (evt.key.keysym.scancode) {
+    default:
+      break;
+    case SDL_SCANCODE_SPACE:
+      [[fallthrough]];
+    case SDL_SCANCODE_RETURN:
+      [[fallthrough]];
+    case SDL_SCANCODE_ESCAPE:
+      gsm.replace_state(GameStates::MainMenu);
+      break;
+    }
+  }
+}
+
 void SplashState::update(GameStateManager& gsm, std::chrono::milliseconds delta_time)
 {
   time_in_state_ += delta_time;
-  auto const key_state = SDL_GetKeyboardState(nullptr);
-  if (time_in_state_>13'000ms || key_state[SDL_SCANCODE_SPACE] || key_state[SDL_SCANCODE_RETURN])
+  if (time_in_state_>13'000ms)
     gsm.replace_state(GameStates::MainMenu);
 }
 
@@ -68,4 +84,4 @@ void SplashState::on_enter(GameStateManager& gsm)
   time_in_state_ = 0ms;
   logo_ = AssetManager::instance().get_texture_asset("logo.jpg");
   SDL_SetTextureBlendMode(logo_, SDL_BLENDMODE_BLEND);
-}
+}

+ 2 - 0
game/SplashState.hxx

@@ -11,6 +11,8 @@ class SplashState final : public GameState {
 public:
   void on_enter(GameStateManager& gsm) override;
 
+  void on_event(GameStateManager& gsm, SDL_Event const& evt) override;
+
   void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) override;
 
   void render(SDLRenderer& renderer) override;

+ 49 - 5
game/ui/LineInput.cxx

@@ -20,6 +20,11 @@ namespace {
     int text_w, text_h;
     SDL_QueryTexture(texture, nullptr, nullptr, &text_w, &text_h);
 
+    // TODO: the height value is based on the default font height, therefor it should not be hard coded
+    // this hack here is a solution for when the text is empty,
+    // because in that case we cannot determine the height from the rendered texture
+    text_h = 32;
+
     int const w = std::min(text_w, area.w-LineInput::MIN_WIDTH-8);
     int const h = std::min(text_h, area.h-LineInput::MIN_HEIGHT-8);
 
@@ -64,6 +69,8 @@ SDL_Rect LineInput::get_bounding_box() const
 void LineInput::set_focus(bool const focus)
 {
   focus_ = focus;
+  if (!focus_)
+    SDL_StopTextInput();
 }
 
 bool LineInput::has_focus() const
@@ -71,9 +78,36 @@ bool LineInput::has_focus() const
   return focus_;
 }
 
+void LineInput::on_event(SDL_Event const& evt)
+{
+  if (!focus_)
+    return;
+
+  if (evt.type==SDL_TEXTINPUT) {
+    value_ += evt.text.text;
+  }
+  else if (evt.type==SDL_KEYUP && evt.key.keysym.sym==SDLK_BACKSPACE) {
+    if (!value_.empty()) {
+      auto const begin = value_.begin();
+      auto const end = value_.end();
+      auto it = value_.end()-1;
+      // For UTF-8 multibyte characters
+      while (it!=begin && ((*it & 0xC0)==0x80)) {
+        --it;
+      }
+      value_.erase(it, end);
+    }
+  }
+}
+
 void LineInput::update(std::chrono::milliseconds const delta_time)
 {
+  if (focus_ && !SDL_IsTextInputActive())
+    SDL_StartTextInput();
+
   blink_timer_ = (blink_timer_+delta_time)%1'000u;
+  auto const rect = get_bounding_box();
+  SDL_SetTextInputRect(&rect);
 }
 
 void LineInput::render(SDLRenderer& renderer)
@@ -106,14 +140,24 @@ void LineInput::render(SDLRenderer& renderer)
   SDL_RenderCopy(renderer, text_ure, &text_texture_rect, &text_rect);
   SDL_DestroyTexture(text_ure);
 
-  if(focus_) {
+  if (focus_) {
     SDL_Rect cursor_rect{
-        .x = text_rect.x + text_rect.w + 1, .y = text_rect.y, .w = 2, .h = text_rect.h,
+        .x = text_rect.x+text_rect.w+1, .y = text_rect.y, .w = 2, .h = text_rect.h,
     };
     SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
-    if(blink_timer_ < 500ms) {
-        SDL_RenderFillRect(renderer, &cursor_rect);
+    if (blink_timer_<500ms) {
+      SDL_RenderFillRect(renderer, &cursor_rect);
     }
     SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
   }
-}
+}
+
+char const* LineInput::value() const
+{
+  return value_.c_str();
+}
+
+void LineInput::set_value(std::string value)
+{
+  value_ = std::move(value);
+}

+ 6 - 0
game/ui/LineInput.hxx

@@ -16,6 +16,8 @@ public:
 
   LineInput(int x, int y, int w, int h, std::string value = "");
 
+  void on_event(SDL_Event const& evt);
+
   void update(std::chrono::milliseconds delta_time);
 
   void render(SDLRenderer& renderer);
@@ -31,6 +33,10 @@ public:
 
   [[nodiscard]] bool has_focus() const;
 
+  void set_value(std::string value);
+
+  [[nodiscard]]char const* value() const;
+
 private:
   std::string value_;
   int x_, y_, w_, h_;

+ 5 - 0
main.cxx

@@ -20,6 +20,11 @@ void main_loop(SDLRenderer& renderer)
     while (SDL_PollEvent(&evt)!=0) {
       if (evt.type==SDL_QUIT)
         return;
+
+      auto const state = gsm.current();
+      if (state==nullptr)
+        return;
+      state->on_event(gsm, evt);
     }
 
     auto const state = gsm.current();