Rigs of Rods 2023.09
Soft-body Physics Simulation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Loading...
Searching...
No Matches
GUI_GameControls.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2020 tritonas00
4 Copyright 2021 Petr Ohlidal
5
6 For more information, see http://www.rigsofrods.org/
7
8 Rigs of Rods is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 3, as
10 published by the Free Software Foundation.
11
12 Rigs of Rods is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
19*/
20
21
22#include "GUI_GameControls.h"
23
24#include "Actor.h"
25#include "Application.h"
26#include "Language.h"
27#include "OgreImGui.h"
28#include "GUI_LoadingWindow.h"
29#include "GUIManager.h"
30#include "InputEngine.h"
31
32#include <fmt/format.h>
33
34using namespace RoR;
35using namespace GUI;
36
38{
40 {
41 // Interactive keybind mode - controls window remains 'visible', but box is drawn instead.
42
44 Ogre::String keys_pressed;
45 int num_nonmodifier_keys = App::GetInputEngine()->getCurrentKeyCombo(&keys_pressed);
46
47 if (num_nonmodifier_keys > 0)
48 {
50 {
51 m_active_buffer << "EXPL+" << keys_pressed;
52 }
53 else
54 {
55 m_active_buffer = keys_pressed;
56 }
57 this->ApplyChanges();
58 App::GetInputEngine()->resetKeysAndMouseButtons(); // Do not leak the pressed keys to gameplay.
59 }
60 else
61 {
62 ImGui::SetNextWindowPosCenter();
63 ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize;
64 bool open = true;
65 ImGui::Begin(_LC("GameControls", "Press a new key"), &open, flags);
66 // Title and description
67 ImGui::TextColored(theme.value_blue_text_color, "%s", App::GetInputEngine()->eventIDToName(m_active_event).c_str());
68 ImGui::TextColored(GRAY_HINT_TEXT, "%s", App::GetInputEngine()->eventIDToDescription(m_active_event).c_str());
69 // Keys preview
70 ImGui::NewLine();
71 ImGui::Text(keys_pressed.c_str());
72 // EXPL checkbox + tooltip
73 ImGui::NewLine();
74 ImGui::Checkbox(_LC("GameControls", "EXPL"), &m_interactive_keybinding_expl);
75 const bool checkbox_hovered = ImGui::IsItemHovered();
76 ImGui::SameLine();
77 ImGui::TextDisabled("(?)");
78 const bool hint_hovered = ImGui::IsItemHovered();
79 if (checkbox_hovered || hint_hovered)
80 {
81 ImGui::BeginTooltip();
82 ImGui::Text("%s", _LC("GameControls",
83 "With EXPL tag, only exactly matching key combos will be triggered.\n"
84 "Without it, partial matches will trigger, too."));
85 ImGui::Separator();
86 ImGui::Text("%s", _LC("GameControls",
87 "Example: Pressing CTRL+F1 will trigger COMMANDS_03 and COMMANDS_01\n"
88 "but not COMMANDS_02 which has EXPL tag."));
89 ImGui::TextDisabled(" COMMANDS_01 Keyboard F1");
90 ImGui::TextDisabled(" COMMANDS_02 Keyboard EXPL+F1");
91 ImGui::TextDisabled(" COMMANDS_03 Keyboard CTRL+F1");
92 ImGui::EndTooltip();
93 }
94 ImGui::End();
95 if (!open)
96 {
97 this->CancelChanges();
98 }
99 }
100 }
101 else
102 {
103 // regular window display
104
105 ImGui::SetNextWindowPosCenter(ImGuiCond_FirstUseEver);
106 ImGui::SetNextWindowSize(ImVec2(800.f, 600.f), ImGuiCond_FirstUseEver);
107 bool keep_open = true;
108 ImGui::Begin(_LC("GameControls", "Game Controls"), &keep_open);
109
111
112 // Toolbar
113
115 {
116 if (ImGui::Button(_LC("GameControls", "Save changes")))
117 {
118 this->SaveMapFile();
119 }
120 ImGui::SameLine();
121 if (ImGui::Button(_LC("GameControls", "Reset changes")))
122 {
123 this->ReloadMapFile();
124 }
125 }
126
127 // Tabs
128
129 ImGui::BeginTabBar("GameSettingsTabs");
130
131 this->DrawControlsTabItem("Airplane", "AIRPLANE");
132 this->DrawControlsTabItem("Boat", "BOAT");
133 this->DrawControlsTabItem("Camera", "CAMERA");
134 this->DrawControlsTabItem("Sky", "SKY");
135 this->DrawControlsTabItem("Character", "CHARACTER");
136 this->DrawControlsTabItem("Commands", "COMMANDS");
137 this->DrawControlsTabItem("Common", "COMMON");
138 this->DrawControlsTabItem("Grass", "GRASS");
139 this->DrawControlsTabItem("Map", "SURVEY_MAP");
140 this->DrawControlsTabItem("Menu", "MENU");
141 this->DrawControlsTabItem("Truck", "TRUCK");
142 this->DrawControlsTabItem("Road editor", "ROAD_EDITOR");
143
144 ImGui::EndTabBar(); // GameSettingsTabs
145
146 m_is_hovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
147
148 ImGui::End();
149 if (!keep_open)
150 {
151 this->SetVisible(false);
152 }
153 }
154}
155
157{
158 // Var
159 InputEngine::TriggerVec& triggers = App::GetInputEngine()->getEvents()[ev_code];
161 float cursor_x = ImGui::GetCursorPosX();
162
163 // Check if we have anything to show
164 int display_count = 0;
165 for (event_trigger_t& trig : triggers)
166 {
167 display_count += (int)this->ShouldDisplay(trig);
168 }
169 if (display_count == 0)
170 {
171 return;
172 }
173
174 // Set up
175 ImGui::PushID((int)ev_code);
176
177 // Name column
178 ImGui::TextColored(theme.value_blue_text_color, "%s", App::GetInputEngine()->eventIDToName(ev_code).c_str());
179
180 ImGui::NextColumn();
181
182 // Command column
183
184 int num_visible_commands = 0; // Count visible commands ahead of time
185 for (event_trigger_t& trig : triggers)
186 {
187 num_visible_commands += this->ShouldDisplay(trig);
188 }
189
190 int num_drawn_commands = 0;
191 for (event_trigger_t& trig: triggers)
192 {
193 if (!this->ShouldDisplay(trig)) continue;
194
195 ImGui::PushID(&trig);
196
197 ImVec2 cursor_before_command = ImGui::GetCursorScreenPos();
198
199 if (ImGui::Button(App::GetInputEngine()->getTriggerCommand(trig).c_str(), ImVec2(ImGui::GetColumnWidth() - 2*ImGui::GetStyle().ItemSpacing.x, 0)))
200 {
201 // Begin interactive keybind
202 m_active_event = ev_code;
203 m_active_trigger = &trig;
207 m_interactive_keybinding_expl = trig.explicite;
208 }
209
210 // If there's more than 1 commands, add numbering at the left side of the buttons
211 num_drawn_commands++;
212 if (num_visible_commands > 1)
213 {
214 ImVec2 text_pos = cursor_before_command + ImGui::GetStyle().FramePadding;
215 ImU32 text_color = ImColor(ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
216 ImGui::GetWindowDrawList()->AddText(text_pos, text_color, fmt::format("{}.", num_drawn_commands).c_str());
217 }
218
219 ImGui::PopID(); // &trig
220 }
221 ImGui::NextColumn();
222
223 // Description column
224 ImGui::TextColored(GRAY_HINT_TEXT, "%s", App::GetInputEngine()->eventIDToDescription(ev_code).c_str());
225 ImGui::NextColumn();
226
227 // Clean up.
228 ImGui::PopID(); // ev_code
229}
230
232{
233 // Device type selector
234 // - General BeginCombo() API, you have full control over your selection data and display type.
235 if (ImGui::BeginCombo(_LC("GameSettings", "EventType"), InputEngine::getEventTypeName(m_selected_evtype))) // The second parameter is the label previewed before opening the combo.
236 {
237 for (int i = ET_Keyboard; i < ET_END; i++)
238 {
239 switch (i)
240 {
241 case ET_MouseButton:
242 case ET_MouseAxisX:
243 case ET_MouseAxisY:
244 case ET_MouseAxisZ:
245 case ET_JoystickAxisRel: // Configured as "JoystickAxis RELATIVE".
246 case ET_JoystickSliderX: // X/Y is determined by special parameter.
247 continue; // Not available
248 default:
249 break;
250 }
251 const bool is_selected = (m_selected_evtype == i);
252 if (ImGui::Selectable(InputEngine::getEventTypeName((eventtypes)i), is_selected))
253 {
255 }
256 if (is_selected)
257 {
258 ImGui::SetItemDefaultFocus(); // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch)
259 }
260 }
261 ImGui::EndCombo();
262 }
263
264 // Combo text input
265 int flags = ImGuiInputTextFlags_EnterReturnsTrue;
267 {
268 flags |= ImGuiInputTextFlags_CharsNoBlank;
269 }
271 {
272 flags |= ImGuiInputTextFlags_CharsUppercase; // POV options are case-sensitive.
273 }
274 if (ImGui::InputText("", m_active_buffer.GetBuffer(), m_active_buffer.GetCapacity(), flags))
275 {
276 this->ApplyChanges();
277 }
278
279 // Buttons
280 if (ImGui::Button(_LC("GameSettings", "OK")))
281 {
282 this->ApplyChanges();
283 }
284 ImGui::SameLine();
285 if (ImGui::Button(_LC("GameSettings", "Cancel")))
286 {
287 this->CancelChanges();
288 }
289 ImGui::SameLine();
290 if (ImGui::Button(_LC("GameSettings", "Delete")))
291 {
293 this->CancelChanges();
294 }
295}
296
297void GameControls::DrawControlsTab(const char* prefix)
298{
299 ImGui::Columns(3, /*id=*/nullptr, /*border=*/true);
300
301 for (auto& ev_pair: App::GetInputEngine()->getEvents())
302 {
303 // Retrieve data
304 const RoR::events ev_code = RoR::events(ev_pair.first);
305 const std::string ev_name = App::GetInputEngine()->eventIDToName(ev_code);
306
307 // Filter event list by prefix
308 if (ev_name.find(prefix) == 0)
309 {
310 this->DrawEvent(ev_code);
311 }
312 }
313
314 m_colum_widths[0] = ImGui::GetColumnWidth(0);
315 m_colum_widths[1] = ImGui::GetColumnWidth(1);
316 m_colum_widths[2] = ImGui::GetColumnWidth(2);
317
318 ImGui::Columns(1);
319}
320
321void GameControls::DrawControlsTabItem(const char* name, const char* prefix)
322{
323 if (ImGui::BeginTabItem(_LC("GameSettings", name)))
324 {
325 ImGui::PushID(prefix);
326
327 // Table header
328 ImGui::Columns(3, /*id=*/nullptr, /*border=*/true);
329 ImGui::SetColumnWidth(0, m_colum_widths[0] + ImGui::GetStyle().FramePadding.x + 1);
330 ImGui::SetColumnWidth(1, m_colum_widths[1]);
331 ImGui::SetColumnWidth(2, m_colum_widths[2]);
332 ImGui::TextColored(GRAY_HINT_TEXT, "Name");
333 ImGui::NextColumn();
334 ImGui::TextColored(GRAY_HINT_TEXT, "Shortcut");
335 ImGui::NextColumn();
336 ImGui::TextColored(GRAY_HINT_TEXT, "Description");
337 ImGui::NextColumn();
338 ImGui::Separator();
339 ImGui::Columns(1); // Cannot cross with child window.
340
341 // Scroll region
342 ImGui::BeginChild("scroll");
343
344 // Actual controls table
345 this->DrawControlsTab(prefix);
346
347 // Cleanup
348 ImGui::EndChild(); // "scroll"
349 ImGui::PopID(); // `prefix`
350 ImGui::EndTabItem(); // `name`
351 }
352}
353
355{
356 // Validate input (the '_CharsNoBlank' flag ensures there is no whitespace)
357 if (m_active_buffer.GetLength() == 0)
358 {
359 this->CancelChanges();
360 return;
361 }
362
363 // Erase the old trigger.
365
366 // Format a '.map' format line with new config
367 std::string format_string;
369 {
370 format_string = "{} {} {}"; // Name, Type, Binding
371 }
372 else // Joystick
373 {
374 format_string = "{} {} 0 {}"; // Name, Type, DeviceNumber (unused), Binding
375 }
376 std::string line = fmt::format(format_string,
377 App::GetInputEngine()->eventIDToName(m_active_event),
380
381 // Parse the line - this creates new trigger.
383
384 // Reset editing context.
386 m_active_trigger = nullptr;
389 m_unsaved_changes = true;
390}
391
393{
394 // Reset editing context.
396 m_active_trigger = nullptr;
399}
400
407
415
417{
418 m_is_visible = vis;
419 m_is_hovered = false;
420 if (!vis)
421 {
422 this->CancelChanges();
424 }
425}
426
428{
429 // display only keyboard items from "input.map" or defaults
430 return trig.eventtype == eventtypes::ET_Keyboard &&
433}
434
Central state/object manager and communications hub.
Handles controller inputs from player.
#define _LC(ctx, str)
Definition Language.h:38
void DrawEventEditBox()
Only the editing UI, embeddable.
void DrawEvent(RoR::events ev_code)
One line in table.
void SetVisible(bool visible)
void DrawControlsTab(const char *prefix)
Draws table with events matching prefix.
event_trigger_t * m_active_trigger
void DrawControlsTabItem(const char *name, const char *prefix)
Wraps DrawControlsTab() with scrollbar and tabs-bar logic.
bool ShouldDisplay(event_trigger_t &trig)
float m_colum_widths[3]
body->header width sync
GUI::GameMainMenu GameMainMenu
Definition GUIManager.h:117
GuiTheme & GetTheme()
Definition GUIManager.h:168
static const int DEFAULT_MAPFILE_DEVICEID
virtual device ID for "input.map" entries
void clearEventsByDevice(int deviceID)
Clears all bindings with given deviceID (-1 is no exception).
static const char * getEventTypeName(eventtypes type)
Enum to string helper.
EventMap & getEvents()
bool loadConfigFile(int deviceID=-1)
Loads config file specific to a device and OS (or default config if deviceID is -1).
bool saveConfigFile(int deviceID=-1)
Wites events with matching deviceID to loaded file with matching deviceID (or default file if deviceI...
int getCurrentKeyCombo(Ogre::String *combo)
Returns number of non-modifier keys pressed (or modifier count as negative number).
static Ogre::String eventIDToName(int eventID)
bool processLine(const char *line, int deviceID=-1)
void resetKeysAndMouseButtons()
void eraseEvent(int eventID, const event_trigger_t *t)
std::vector< event_trigger_t > TriggerVec
static const int BUILTIN_MAPPING_DEVICEID
virtual device ID for builtin defaults
const char * ToCStr() const
Definition Str.h:46
Str & Clear()
Definition Str.h:54
char * GetBuffer()
Definition Str.h:48
size_t GetLength() const
Definition Str.h:51
size_t GetCapacity() const
Definition Str.h:49
eventtypes
Definition InputEngine.h:50
@ ET_END
Definition InputEngine.h:63
@ ET_MouseButton
Definition InputEngine.h:53
@ ET_JoystickAxisRel
Definition InputEngine.h:59
@ ET_MouseAxisX
Definition InputEngine.h:54
@ ET_JoystickPov
Definition InputEngine.h:60
@ ET_JoystickSliderX
Definition InputEngine.h:61
@ ET_MouseAxisZ
Definition InputEngine.h:56
@ ET_JoystickButton
Definition InputEngine.h:57
@ ET_Keyboard
Definition InputEngine.h:52
@ ET_MouseAxisY
Definition InputEngine.h:55
@ EV_MODE_LAST
InputEngine * GetInputEngine()
GUIManager * GetGuiManager()
int configDeviceID
For which device (which config file) was this binding defined?
enum eventtypes eventtype