1 // Copyright 2017 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
6 
7 #include <algorithm>
8 #include <cmath>
9 #include <limits>
10 #include <memory>
11 #include <string>
12 
13 #include "Common/Common.h"
14 #include "Common/MathUtil.h"
15 
16 #include "InputCommon/ControlReference/ControlReference.h"
17 #include "InputCommon/ControllerEmu/Control/Control.h"
18 #include "InputCommon/ControllerEmu/Control/Input.h"
19 #include "InputCommon/ControllerEmu/ControllerEmu.h"
20 #include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
21 
22 namespace ControllerEmu
23 {
Cursor(std::string name_,std::string ui_name_)24 Cursor::Cursor(std::string name_, std::string ui_name_)
25     : ReshapableInput(std::move(name_), std::move(ui_name_), GroupType::Cursor),
26       m_last_update(Clock::now())
27 {
28   for (auto& named_direction : named_directions)
29     AddInput(Translate, named_direction);
30 
31   AddInput(Translate, _trans("Hide"));
32   AddInput(Translate, _trans("Recenter"));
33 
34   AddInput(Translate, _trans("Relative Input Hold"));
35 
36   // Default values chosen to reach screen edges in most games including the Wii Menu.
37 
38   AddSetting(&m_vertical_offset_setting,
39              // i18n: Refers to a positional offset applied to an emulated wiimote.
40              {_trans("Vertical Offset"),
41               // i18n: The symbol/abbreviation for centimeters.
42               _trans("cm")},
43              10, -100, 100);
44 
45   AddSetting(&m_yaw_setting,
46              // i18n: Refers to an amount of rotational movement about the "yaw" axis.
47              {_trans("Total Yaw"),
48               // i18n: The symbol/abbreviation for degrees (unit of angular measure).
49               _trans("°"),
50               // i18n: Refers to emulated wii remote movements.
51               _trans("Total rotation about the yaw axis.")},
52              25, 0, 360);
53 
54   AddSetting(&m_pitch_setting,
55              // i18n: Refers to an amount of rotational movement about the "pitch" axis.
56              {_trans("Total Pitch"),
57               // i18n: The symbol/abbreviation for degrees (unit of angular measure).
58               _trans("°"),
59               // i18n: Refers to emulated wii remote movements.
60               _trans("Total rotation about the pitch axis.")},
61              20, 0, 360);
62 
63   AddSetting(&m_relative_setting, {_trans("Relative Input")}, false);
64   AddSetting(&m_autohide_setting, {_trans("Auto-Hide")}, false);
65 }
66 
GetReshapableState(bool adjusted)67 Cursor::ReshapeData Cursor::GetReshapableState(bool adjusted)
68 {
69   const ControlState y = controls[0]->GetState() - controls[1]->GetState();
70   const ControlState x = controls[3]->GetState() - controls[2]->GetState();
71 
72   // Return raw values. (used in UI)
73   if (!adjusted)
74     return {x, y};
75 
76   return Reshape(x, y, 0.0);
77 }
78 
GetGateRadiusAtAngle(double ang) const79 ControlState Cursor::GetGateRadiusAtAngle(double ang) const
80 {
81   return SquareStickGate(1.0).GetRadiusAtAngle(ang);
82 }
83 
GetState(const bool adjusted)84 Cursor::StateData Cursor::GetState(const bool adjusted)
85 {
86   if (!adjusted)
87   {
88     const auto raw_input = GetReshapableState(false);
89 
90     return {raw_input.x, raw_input.y};
91   }
92 
93   const auto input = GetReshapableState(true);
94 
95   // TODO: Using system time is ugly.
96   // Kill this after state is moved into wiimote rather than this class.
97   const auto now = Clock::now();
98   const auto ms_since_update =
99       std::chrono::duration_cast<std::chrono::milliseconds>(now - m_last_update).count();
100   m_last_update = now;
101 
102   const double max_step = STEP_PER_SEC / 1000.0 * ms_since_update;
103 
104   // Relative input:
105   if (m_relative_setting.GetValue() ^ (controls[6]->GetState<bool>()))
106   {
107     // Recenter:
108     if (controls[5]->GetState<bool>())
109     {
110       m_state.x = 0.0;
111       m_state.y = 0.0;
112     }
113     else
114     {
115       m_state.x = std::clamp(m_state.x + input.x * max_step, -1.0, 1.0);
116       m_state.y = std::clamp(m_state.y + input.y * max_step, -1.0, 1.0);
117     }
118   }
119   // Absolute input:
120   else
121   {
122     m_state.x = input.x;
123     m_state.y = input.y;
124   }
125 
126   StateData result = m_state;
127 
128   const bool autohide = m_autohide_setting.GetValue();
129 
130   // Auto-hide timer:
131   // TODO: should Z movement reset this?
132   if (!autohide || std::abs(m_prev_result.x - result.x) > AUTO_HIDE_DEADZONE ||
133       std::abs(m_prev_result.y - result.y) > AUTO_HIDE_DEADZONE)
134   {
135     m_auto_hide_timer = AUTO_HIDE_MS;
136   }
137   else if (m_auto_hide_timer)
138   {
139     m_auto_hide_timer -= std::min<int>(ms_since_update, m_auto_hide_timer);
140   }
141 
142   m_prev_result = result;
143 
144   // If auto-hide time is up or hide button is held:
145   if (!m_auto_hide_timer || controls[4]->GetState<bool>())
146   {
147     result.x = std::numeric_limits<ControlState>::quiet_NaN();
148     result.y = 0;
149   }
150 
151   return result;
152 }
153 
GetTotalYaw() const154 ControlState Cursor::GetTotalYaw() const
155 {
156   return m_yaw_setting.GetValue() * MathUtil::TAU / 360;
157 }
158 
GetTotalPitch() const159 ControlState Cursor::GetTotalPitch() const
160 {
161   return m_pitch_setting.GetValue() * MathUtil::TAU / 360;
162 }
163 
GetVerticalOffset() const164 ControlState Cursor::GetVerticalOffset() const
165 {
166   return m_vertical_offset_setting.GetValue() / 100;
167 }
168 
IsVisible() const169 bool Cursor::StateData::IsVisible() const
170 {
171   return !std::isnan(x);
172 }
173 
174 }  // namespace ControllerEmu
175