ddnet/src/game/client/components/menus_settings.cpp

3209 lines
126 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
2010-05-29 07:25:38 +00:00
#include <base/math.h>
2022-05-18 16:00:05 +00:00
#include <base/system.h>
2010-05-29 07:25:38 +00:00
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/shared/linereader.h>
#include <engine/storage.h>
2010-05-29 07:25:38 +00:00
#include <engine/textrender.h>
#include <engine/updater.h>
#include <game/generated/protocol.h>
2020-09-18 16:45:42 +00:00
#include <game/client/animstate.h>
2020-10-13 20:08:52 +00:00
#include <game/client/components/chat.h>
2020-09-18 16:45:42 +00:00
#include <game/client/components/menu_background.h>
#include <game/client/components/sounds.h>
2010-05-29 07:25:38 +00:00
#include <game/client/gameclient.h>
2020-09-18 16:45:42 +00:00
#include <game/client/render.h>
#include <game/client/skin.h>
2020-09-18 16:45:42 +00:00
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h>
2010-05-29 07:25:38 +00:00
#include <game/localization.h>
2010-05-29 07:25:38 +00:00
#include "binds.h"
#include "countryflags.h"
2010-05-29 07:25:38 +00:00
#include "menus.h"
#include "skins.h"
2022-05-29 16:33:38 +00:00
#include <array>
#include <chrono>
2022-03-20 17:03:25 +00:00
#include <memory>
2022-05-29 16:33:38 +00:00
#include <numeric>
#include <string>
2022-03-20 17:03:25 +00:00
#include <vector>
using namespace FontIcons;
2022-05-18 16:00:05 +00:00
using namespace std::chrono_literals;
2010-05-29 07:25:38 +00:00
CMenusKeyBinder CMenus::m_Binder;
2010-05-29 07:25:38 +00:00
CMenusKeyBinder::CMenusKeyBinder()
{
2010-05-29 07:25:38 +00:00
m_TakeKey = false;
m_GotKey = false;
2022-02-06 22:14:26 +00:00
m_ModifierCombination = 0;
}
bool CMenusKeyBinder::OnInput(const IInput::CEvent &Event)
{
2010-05-29 07:25:38 +00:00
if(m_TakeKey)
{
int TriggeringEvent = (Event.m_Key == KEY_MOUSE_1) ? IInput::FLAG_PRESS : IInput::FLAG_RELEASE;
if(Event.m_Flags & TriggeringEvent)
{
2010-05-29 07:25:38 +00:00
m_Key = Event;
m_GotKey = true;
m_TakeKey = false;
m_ModifierCombination = CBinds::GetModifierMask(Input());
if(m_ModifierCombination == CBinds::GetModifierMaskOfKey(Event.m_Key))
{
2022-02-06 22:14:26 +00:00
m_ModifierCombination = 0;
}
}
return true;
}
2010-05-29 07:25:38 +00:00
return false;
}
2011-04-01 17:36:44 +00:00
void CMenus::RenderSettingsGeneral(CUIRect MainView)
{
char aBuf[128 + IO_MAX_PATH_LENGTH];
2020-09-18 16:45:42 +00:00
CUIRect Label, Button, Left, Right, Game, Client;
2020-09-19 06:53:26 +00:00
MainView.HSplitTop(150.0f, &Game, &Client);
2011-04-01 17:36:44 +00:00
// game
2010-05-29 07:25:38 +00:00
{
2011-04-01 17:36:44 +00:00
// headline
2020-09-29 01:23:39 +00:00
Game.HSplitTop(20.0f, &Label, &Game);
UI()->DoLabel(&Label, Localize("Game"), 20.0f, TEXTALIGN_ML);
2011-04-01 17:36:44 +00:00
Game.Margin(5.0f, &Game);
Game.VSplitMid(&Left, &Right);
Left.VSplitRight(5.0f, &Left, 0);
Right.VMargin(5.0f, &Right);
2009-06-15 08:15:53 +00:00
2011-04-01 17:36:44 +00:00
// dynamic camera
Left.HSplitTop(20.0f, &Button, &Left);
bool IsDyncam = g_Config.m_ClDyncam || g_Config.m_ClMouseFollowfactor > 0;
if(DoButton_CheckBox(&g_Config.m_ClDyncam, Localize("Dynamic Camera"), IsDyncam, &Button))
{
if(IsDyncam)
{
g_Config.m_ClDyncam = 0;
g_Config.m_ClMouseFollowfactor = 0;
}
else
{
g_Config.m_ClDyncam = 1;
}
}
// smooth dynamic camera
2020-10-14 00:43:30 +00:00
Left.HSplitTop(5.0f, 0, &Left);
Left.HSplitTop(20.0f, &Button, &Left);
if(g_Config.m_ClDyncam)
2020-10-14 00:43:30 +00:00
{
if(DoButton_CheckBox(&g_Config.m_ClDyncamSmoothness, Localize("Smooth Dynamic Camera"), g_Config.m_ClDyncamSmoothness, &Button))
2020-10-14 00:43:30 +00:00
{
if(g_Config.m_ClDyncamSmoothness)
{
g_Config.m_ClDyncamSmoothness = 0;
}
else
{
g_Config.m_ClDyncamSmoothness = 50;
g_Config.m_ClDyncamStabilizing = 50;
}
2020-10-14 00:43:30 +00:00
}
}
2011-04-01 17:36:44 +00:00
// weapon pickup
Left.HSplitTop(5.0f, 0, &Left);
Left.HSplitTop(20.0f, &Button, &Left);
2010-05-29 07:25:38 +00:00
if(DoButton_CheckBox(&g_Config.m_ClAutoswitchWeapons, Localize("Switch weapon on pickup"), g_Config.m_ClAutoswitchWeapons, &Button))
g_Config.m_ClAutoswitchWeapons ^= 1;
2014-06-16 11:29:18 +00:00
// weapon out of ammo autoswitch
Left.HSplitTop(5.0f, 0, &Left);
Left.HSplitTop(20.0f, &Button, &Left);
if(DoButton_CheckBox(&g_Config.m_ClAutoswitchWeaponsOutOfAmmo, Localize("Switch weapon when out of ammo"), g_Config.m_ClAutoswitchWeaponsOutOfAmmo, &Button))
g_Config.m_ClAutoswitchWeaponsOutOfAmmo ^= 1;
}
2010-05-29 07:25:38 +00:00
2011-04-01 17:36:44 +00:00
// client
{
2011-04-01 17:36:44 +00:00
// headline
2020-09-29 01:23:39 +00:00
Client.HSplitTop(20.0f, &Label, &Client);
UI()->DoLabel(&Label, Localize("Client"), 20.0f, TEXTALIGN_ML);
2011-04-01 17:36:44 +00:00
Client.Margin(5.0f, &Client);
Client.VSplitMid(&Left, &Right);
Left.VSplitRight(5.0f, &Left, 0);
Right.VMargin(5.0f, &Right);
2010-05-29 07:25:38 +00:00
// skip main menu
Left.HSplitTop(5.0f, 0, &Left);
Left.HSplitTop(20.0f, &Button, &Left);
if(DoButton_CheckBox(&g_Config.m_ClSkipStartMenu, Localize("Skip the main menu"), g_Config.m_ClSkipStartMenu, &Button))
2020-09-10 18:14:47 +00:00
g_Config.m_ClSkipStartMenu ^= 1;
float SliderGroupMargin = 10.0f;
2020-09-29 01:23:39 +00:00
2011-04-01 17:36:44 +00:00
// auto demo settings
{
Right.HSplitTop(40.0f, nullptr, &Right);
2020-09-29 01:23:39 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
2011-04-01 17:36:44 +00:00
if(DoButton_CheckBox(&g_Config.m_ClAutoDemoRecord, Localize("Automatically record demos"), g_Config.m_ClAutoDemoRecord, &Button))
g_Config.m_ClAutoDemoRecord ^= 1;
Right.HSplitTop(2 * 20.0f, &Button, &Right);
if(g_Config.m_ClAutoDemoRecord)
UI()->DoScrollbarOption(&g_Config.m_ClAutoDemoMax, &g_Config.m_ClAutoDemoMax, &Button, Localize("Max demos"), 1, 1000, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_INFINITE | CUI::SCROLLBAR_OPTION_MULTILINE);
2011-04-01 17:36:44 +00:00
Right.HSplitTop(SliderGroupMargin, nullptr, &Right);
2020-09-29 01:23:39 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClAutoScreenshot, Localize("Automatically take game over screenshot"), g_Config.m_ClAutoScreenshot, &Button))
g_Config.m_ClAutoScreenshot ^= 1;
Right.HSplitTop(2 * 20.0f, &Button, &Right);
if(g_Config.m_ClAutoScreenshot)
UI()->DoScrollbarOption(&g_Config.m_ClAutoScreenshotMax, &g_Config.m_ClAutoScreenshotMax, &Button, Localize("Max Screenshots"), 1, 1000, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_INFINITE | CUI::SCROLLBAR_OPTION_MULTILINE);
2011-04-01 17:36:44 +00:00
}
2014-06-16 13:43:11 +00:00
Left.HSplitTop(10.0f, nullptr, &Left);
2020-09-18 16:45:42 +00:00
Left.HSplitTop(20.0f, &Button, &Left);
UI()->DoScrollbarOption(&g_Config.m_ClRefreshRate, &g_Config.m_ClRefreshRate, &Button, Localize("Refresh Rate"), 10, 10000, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_INFINITE, " Hz");
Left.HSplitTop(5.0f, nullptr, &Left);
Left.HSplitTop(20.0f, &Button, &Left);
int s_LowerRefreshRate;
if(DoButton_CheckBox(&s_LowerRefreshRate, Localize("Save power by lowering refresh rate (higher input latency)"), g_Config.m_ClRefreshRate <= 480 && g_Config.m_ClRefreshRate != 0, &Button))
g_Config.m_ClRefreshRate = g_Config.m_ClRefreshRate > 480 || g_Config.m_ClRefreshRate == 0 ? 480 : 0;
CUIRect SettingsButton;
Left.HSplitBottom(25.0f, &Left, &SettingsButton);
SettingsButton.HSplitTop(5.0f, 0, &SettingsButton);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_SettingsButtonID;
if(DoButton_Menu(&s_SettingsButtonID, Localize("Settings file"), 0, &SettingsButton))
{
Storage()->GetCompletePath(IStorage::TYPE_SAVE, CONFIG_FILE, aBuf, sizeof(aBuf));
if(!open_file(aBuf))
{
dbg_msg("menus", "couldn't open file '%s'", aBuf);
}
}
GameClient()->m_Tooltips.DoToolTip(&s_SettingsButtonID, &SettingsButton, Localize("Open the settings file"));
Left.HSplitTop(15.0f, 0, &Left);
CUIRect ConfigButton;
Left.HSplitBottom(25.0f, &Left, &ConfigButton);
ConfigButton.HSplitTop(5.0f, 0, &ConfigButton);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ConfigButtonID;
if(DoButton_Menu(&s_ConfigButtonID, Localize("Config directory"), 0, &ConfigButton))
{
Storage()->GetCompletePath(IStorage::TYPE_SAVE, "", aBuf, sizeof(aBuf));
if(!open_file(aBuf))
{
dbg_msg("menus", "couldn't open file '%s'", aBuf);
}
}
GameClient()->m_Tooltips.DoToolTip(&s_ConfigButtonID, &ConfigButton, Localize("Open the directory that contains the configuration and user files"));
2020-09-29 01:23:39 +00:00
Left.HSplitTop(15.0f, 0, &Left);
CUIRect DirectoryButton;
Left.HSplitBottom(25.0f, &Left, &DirectoryButton);
2020-09-18 16:45:42 +00:00
RenderThemeSelection(Left);
2020-09-29 01:23:39 +00:00
DirectoryButton.HSplitTop(5.0f, 0, &DirectoryButton);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ThemesButtonID;
2021-05-17 07:11:36 +00:00
if(DoButton_Menu(&s_ThemesButtonID, Localize("Themes directory"), 0, &DirectoryButton))
2020-09-29 01:23:39 +00:00
{
Storage()->GetCompletePath(IStorage::TYPE_SAVE, "themes", aBuf, sizeof(aBuf));
Storage()->CreateFolder("themes", IStorage::TYPE_SAVE);
2021-12-19 00:13:08 +00:00
if(!open_file(aBuf))
2020-09-29 01:23:39 +00:00
{
dbg_msg("menus", "couldn't open file '%s'", aBuf);
2020-09-29 01:23:39 +00:00
}
}
GameClient()->m_Tooltips.DoToolTip(&s_ThemesButtonID, &DirectoryButton, Localize("Open the directory to add custom themes"));
2020-09-29 01:23:39 +00:00
2015-05-21 12:34:20 +00:00
// auto statboard screenshot
{
Right.HSplitTop(SliderGroupMargin, nullptr, &Right);
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClAutoStatboardScreenshot, Localize("Automatically take statboard screenshot"), g_Config.m_ClAutoStatboardScreenshot, &Button))
{
g_Config.m_ClAutoStatboardScreenshot ^= 1;
}
Right.HSplitTop(2 * 20.0f, &Button, &Right);
if(g_Config.m_ClAutoStatboardScreenshot)
UI()->DoScrollbarOption(&g_Config.m_ClAutoStatboardScreenshotMax, &g_Config.m_ClAutoStatboardScreenshotMax, &Button, Localize("Max Screenshots"), 1, 1000, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_INFINITE | CUI::SCROLLBAR_OPTION_MULTILINE);
}
// auto statboard csv
{
Right.HSplitTop(SliderGroupMargin, nullptr, &Right);
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClAutoCSV, Localize("Automatically create statboard csv"), g_Config.m_ClAutoCSV, &Button))
{
g_Config.m_ClAutoCSV ^= 1;
}
Right.HSplitTop(2 * 20.0f, &Button, &Right);
if(g_Config.m_ClAutoCSV)
UI()->DoScrollbarOption(&g_Config.m_ClAutoCSVMax, &g_Config.m_ClAutoCSVMax, &Button, Localize("Max CSVs"), 1, 1000, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_INFINITE | CUI::SCROLLBAR_OPTION_MULTILINE);
}
2011-04-01 17:36:44 +00:00
}
}
2010-05-29 07:25:38 +00:00
void CMenus::SetNeedSendInfo()
{
if(m_Dummy)
m_NeedSendDummyinfo = true;
else
m_NeedSendinfo = true;
}
2011-04-01 17:36:44 +00:00
void CMenus::RenderSettingsPlayer(CUIRect MainView)
{
CUIRect Button, Label, Dummy;
MainView.HSplitTop(10.0f, 0, &MainView);
char *pName = g_Config.m_PlayerName;
const char *pNameFallback = Client()->PlayerName();
char *pClan = g_Config.m_PlayerClan;
int *pCountry = &g_Config.m_PlayerCountry;
2015-08-28 18:44:07 +00:00
if(m_Dummy)
{
pName = g_Config.m_ClDummyName;
pNameFallback = Client()->DummyName();
pClan = g_Config.m_ClDummyClan;
pCountry = &g_Config.m_ClDummyCountry;
}
// player name
MainView.HSplitTop(20.0f, &Button, &MainView);
Button.VSplitLeft(80.0f, &Label, &Button);
Button.VSplitLeft(150.0f, &Button, 0);
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name"));
UI()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_NameInput;
s_NameInput.SetBuffer(pName, sizeof(g_Config.m_PlayerName));
s_NameInput.SetEmptyText(pNameFallback);
if(UI()->DoEditBox(&s_NameInput, &Button, 14.0f))
{
SetNeedSendInfo();
}
// player clan
MainView.HSplitTop(5.0f, 0, &MainView);
MainView.HSplitTop(20.0f, &Button, &MainView);
Button.VSplitLeft(80.0f, &Label, &Button);
Button.VSplitLeft(200.0f, &Button, &Dummy);
Button.VSplitLeft(150.0f, &Button, 0);
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan"));
UI()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_ClanInput;
s_ClanInput.SetBuffer(pClan, sizeof(g_Config.m_PlayerClan));
if(UI()->DoEditBox(&s_ClanInput, &Button, 14.0f))
{
SetNeedSendInfo();
}
if(DoButton_CheckBox(&m_Dummy, Localize("Dummy settings"), m_Dummy, &Dummy))
{
m_Dummy ^= 1;
}
// country flag selector
MainView.HSplitTop(20.0f, 0, &MainView);
int OldSelected = -1;
static CListBox s_ListBox;
if(UI()->CheckActiveItem(&s_ClanInput) || UI()->CheckActiveItem(&s_NameInput))
s_ListBox.SetActive(false);
s_ListBox.DoStart(50.0f, m_pClient->m_CountryFlags.Num(), 10, 3, OldSelected, &MainView);
2010-05-29 07:25:38 +00:00
for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i)
{
2021-07-12 09:43:56 +00:00
const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_CountryFlags.GetByIndex(i);
if(pEntry->m_CountryCode == *pCountry)
OldSelected = i;
2010-05-29 07:25:38 +00:00
const CListboxItem Item = s_ListBox.DoNextItem(&pEntry->m_CountryCode, OldSelected >= 0 && (size_t)OldSelected == i);
if(!Item.m_Visible)
continue;
CUIRect FlagRect;
Item.m_Rect.Margin(5.0f, &FlagRect);
FlagRect.HSplitBottom(12.0f, &FlagRect, &Label);
Label.HSplitTop(2.0f, nullptr, &Label);
float OldWidth = FlagRect.w;
FlagRect.w = FlagRect.h * 2;
FlagRect.x += (OldWidth - FlagRect.w) / 2.0f;
m_pClient->m_CountryFlags.Render(pEntry->m_CountryCode, ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f), FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
if(pEntry->m_Texture.IsValid())
{
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_MC);
}
}
2010-05-29 07:25:38 +00:00
const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected)
{
2021-07-12 09:43:56 +00:00
*pCountry = m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode;
SetNeedSendInfo();
}
}
2010-05-29 07:25:38 +00:00
struct CUISkin
{
const CSkin *m_pSkin;
CUISkin() :
m_pSkin(nullptr) {}
CUISkin(const CSkin *pSkin) :
m_pSkin(pSkin) {}
bool operator<(const CUISkin &Other) const { return str_comp_nocase(m_pSkin->GetName(), Other.m_pSkin->GetName()) < 0; }
bool operator<(const char *pOther) const { return str_comp_nocase(m_pSkin->GetName(), pOther) < 0; }
bool operator==(const char *pOther) const { return !str_comp_nocase(m_pSkin->GetName(), pOther); }
};
void CMenus::RefreshSkins()
{
auto SkinStartLoadTime = time_get_nanoseconds();
m_pClient->m_Skins.Refresh([&](int) {
// if skin refreshing takes to long, swap to a loading screen
if(time_get_nanoseconds() - SkinStartLoadTime > 500ms)
{
RenderLoading(Localize("Loading skin files"), "", 0, false);
}
});
2022-08-29 16:27:36 +00:00
if(Client()->State() >= IClient::STATE_ONLINE)
{
m_pClient->RefindSkins();
}
}
2023-03-24 22:14:17 +00:00
void CMenus::RandomSkin()
{
2023-03-25 14:43:25 +00:00
static const float s_aSchemes[] = {1.0f / 2.0f, 1.0f / 3.0f, 1.0f / -3.0f, 1.0f / 12.0f, 1.0f / -12.0f}; // complementary, triadic, analogous
2023-03-24 22:14:17 +00:00
2023-03-25 14:43:25 +00:00
float GoalSat = random_float(0.3f, 1.0f);
float MaxBodyLht = 1.0f - GoalSat * GoalSat; // max allowed lightness before we start losing saturation
2023-03-24 22:14:17 +00:00
ColorHSLA Body;
Body.h = random_float();
2023-03-25 14:43:25 +00:00
Body.l = random_float(0.0f, MaxBodyLht);
Body.s = clamp(GoalSat * GoalSat / (1.0f - Body.l), 0.0f, 1.0f);
2023-03-24 22:14:17 +00:00
ColorHSLA Feet;
2023-03-25 14:43:25 +00:00
Feet.h = std::fmod(Body.h + s_aSchemes[rand() % std::size(s_aSchemes)], 1.0f);
2023-03-24 22:14:17 +00:00
Feet.l = random_float();
2023-03-25 14:43:25 +00:00
Feet.s = clamp(GoalSat * GoalSat / (1.0f - Feet.l), 0.0f, 1.0f);
2023-03-24 22:14:17 +00:00
2023-03-25 14:43:25 +00:00
const char *pRandomSkinName = CSkins::VANILLA_SKINS[rand() % (std::size(CSkins::VANILLA_SKINS) - 2)]; // last 2 skins are x_ninja and x_spec
2023-03-24 22:14:17 +00:00
char *pSkinName = !m_Dummy ? g_Config.m_ClPlayerSkin : g_Config.m_ClDummySkin;
unsigned *pColorBody = !m_Dummy ? &g_Config.m_ClPlayerColorBody : &g_Config.m_ClDummyColorBody;
unsigned *pColorFeet = !m_Dummy ? &g_Config.m_ClPlayerColorFeet : &g_Config.m_ClDummyColorFeet;
mem_copy(pSkinName, pRandomSkinName, sizeof(g_Config.m_ClPlayerSkin));
*pColorBody = Body.Pack(false);
*pColorFeet = Feet.Pack(false);
SetNeedSendInfo();
}
2022-10-04 16:40:24 +00:00
void CMenus::Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData)
{
auto *pSelf = (CMenus *)pUserData;
if(pResult->NumArguments() >= 1)
{
pSelf->m_SkinFavorites.emplace(pResult->GetString(0));
pSelf->m_SkinFavoritesChanged = true;
}
}
void CMenus::Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData)
{
auto *pSelf = (CMenus *)pUserData;
if(pResult->NumArguments() >= 1)
{
const auto it = pSelf->m_SkinFavorites.find(pResult->GetString(0));
if(it != pSelf->m_SkinFavorites.end())
{
pSelf->m_SkinFavorites.erase(it);
pSelf->m_SkinFavoritesChanged = true;
}
}
}
void CMenus::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
{
auto *pSelf = (CMenus *)pUserData;
pSelf->OnConfigSave(pConfigManager);
}
void CMenus::OnConfigSave(IConfigManager *pConfigManager)
{
for(const auto &Entry : m_SkinFavorites)
{
char aBuffer[256];
char aNameEscaped[256];
char *pDst = aNameEscaped;
str_escape(&pDst, Entry.c_str(), aNameEscaped + std::size(aNameEscaped));
str_format(aBuffer, std::size(aBuffer), "add_favorite_skin \"%s\"", Entry.c_str());
pConfigManager->WriteLine(aBuffer);
}
}
2011-04-01 17:36:44 +00:00
void CMenus::RenderSettingsTee(CUIRect MainView)
{
CUIRect Button, Label, Dummy, DummyLabel, SkinList, QuickSearch, QuickSearchClearButton, SkinDB, SkinPrefix, SkinPrefixLabel, DirectoryButton, RefreshButton, Eyes, EyesLabel, EyesTee, EyesRight;
2013-12-26 17:02:37 +00:00
static bool s_InitSkinlist = true;
Eyes = MainView;
2010-05-29 07:25:38 +00:00
char *pSkinName = g_Config.m_ClPlayerSkin;
int *pUseCustomColor = &g_Config.m_ClPlayerUseCustomColor;
unsigned *pColorBody = &g_Config.m_ClPlayerColorBody;
unsigned *pColorFeet = &g_Config.m_ClPlayerColorFeet;
2015-08-28 18:44:07 +00:00
if(m_Dummy)
{
pSkinName = g_Config.m_ClDummySkin;
pUseCustomColor = &g_Config.m_ClDummyUseCustomColor;
pColorBody = &g_Config.m_ClDummyColorBody;
pColorFeet = &g_Config.m_ClDummyColorFeet;
}
2022-06-12 11:31:33 +00:00
MainView.HSplitTop(10.0f, &Label, &MainView);
Label.VSplitLeft(280.0f, &Label, &Dummy);
Label.VSplitLeft(230.0f, &Label, 0);
Dummy.VSplitLeft(170.0f, &Dummy, &SkinPrefix);
SkinPrefix.VSplitLeft(120.0f, &SkinPrefix, &EyesRight);
char aBuf[128 + IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Your skin"));
UI()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML);
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
if(DoButton_CheckBox(&m_Dummy, Localize("Dummy settings"), m_Dummy, &DummyLabel))
{
2015-08-28 18:44:07 +00:00
m_Dummy ^= 1;
}
2022-04-28 14:46:18 +00:00
GameClient()->m_Tooltips.DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings"));
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
if(DoButton_CheckBox(&g_Config.m_ClDownloadSkins, Localize("Download skins"), g_Config.m_ClDownloadSkins, &DummyLabel))
{
g_Config.m_ClDownloadSkins ^= 1;
RefreshSkins();
s_InitSkinlist = true;
}
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
2022-06-12 11:31:33 +00:00
if(DoButton_CheckBox(&g_Config.m_ClDownloadCommunitySkins, Localize("Download community skins"), g_Config.m_ClDownloadCommunitySkins, &DummyLabel))
{
g_Config.m_ClDownloadCommunitySkins ^= 1;
RefreshSkins();
s_InitSkinlist = true;
2022-06-12 11:31:33 +00:00
}
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
2017-07-24 21:48:26 +00:00
if(DoButton_CheckBox(&g_Config.m_ClVanillaSkinsOnly, Localize("Vanilla skins only"), g_Config.m_ClVanillaSkinsOnly, &DummyLabel))
{
2015-08-28 18:44:07 +00:00
g_Config.m_ClVanillaSkinsOnly ^= 1;
RefreshSkins();
s_InitSkinlist = true;
}
2017-07-24 21:48:26 +00:00
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
if(DoButton_CheckBox(&g_Config.m_ClFatSkins, Localize("Fat skins (DDFat)"), g_Config.m_ClFatSkins, &DummyLabel))
2017-07-24 21:48:26 +00:00
{
g_Config.m_ClFatSkins ^= 1;
}
2018-07-24 15:26:39 +00:00
SkinPrefix.HSplitTop(20.0f, &SkinPrefixLabel, &SkinPrefix);
UI()->DoLabel(&SkinPrefixLabel, Localize("Skin prefix"), 14.0f, TEXTALIGN_ML);
2018-07-24 15:26:39 +00:00
SkinPrefix.HSplitTop(20.0f, &SkinPrefixLabel, &SkinPrefix);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_SkinPrefixInput(g_Config.m_ClSkinPrefix, sizeof(g_Config.m_ClSkinPrefix));
UI()->DoClearableEditBox(&s_SkinPrefixInput, &SkinPrefixLabel, 14.0f);
2017-07-24 21:48:26 +00:00
SkinPrefix.HSplitTop(2.0f, 0, &SkinPrefix);
{
static const char *s_apSkinPrefixes[] = {"kitty", "santa"};
static CButtonContainer s_aPrefixButtons[std::size(s_apSkinPrefixes)];
for(size_t i = 0; i < std::size(s_apSkinPrefixes); i++)
{
SkinPrefix.HSplitTop(20.0f, &Button, &SkinPrefix);
Button.HMargin(2.0f, &Button);
if(DoButton_Menu(&s_aPrefixButtons[i], s_apSkinPrefixes[i], 0, &Button))
{
str_copy(g_Config.m_ClSkinPrefix, s_apSkinPrefixes[i]);
}
}
}
2017-07-24 21:48:26 +00:00
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
// note: get the skin info after the settings buttons, because they can trigger a refresh
// which invalidates the skin
// skin info
CTeeRenderInfo OwnSkinInfo;
const CSkin *pSkin = m_pClient->m_Skins.Find(pSkinName);
OwnSkinInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
OwnSkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
OwnSkinInfo.m_SkinMetrics = pSkin->m_Metrics;
OwnSkinInfo.m_CustomColoredSkin = *pUseCustomColor;
if(*pUseCustomColor)
{
OwnSkinInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(*pColorBody).UnclampLighting());
OwnSkinInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(*pColorFeet).UnclampLighting());
}
else
{
OwnSkinInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f);
OwnSkinInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f);
}
OwnSkinInfo.m_Size = 50.0f;
MainView.HSplitTop(50.0f, &Label, &MainView);
2023-03-25 14:43:25 +00:00
Label.VSplitLeft(260.0f, &Label, 0);
2021-03-12 20:23:29 +00:00
CAnimState *pIdleState = CAnimState::GetIdle();
vec2 OffsetToMid;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &OwnSkinInfo, OffsetToMid);
vec2 TeeRenderPos(Label.x + 30.0f, Label.y + Label.h / 2.0f + OffsetToMid.y);
int Emote = m_Dummy ? g_Config.m_ClDummyDefaultEyes : g_Config.m_ClPlayerDefaultEyes;
RenderTools()->RenderTee(pIdleState, &OwnSkinInfo, Emote, vec2(1, 0), TeeRenderPos);
Label.VSplitLeft(70.0f, 0, &Label);
2020-09-21 08:35:13 +00:00
Label.HMargin(15.0f, &Label);
// default eyes
bool RenderEyesBelow = MainView.w < 750.0f;
if(RenderEyesBelow)
{
Eyes.VSplitLeft(190.0f, 0, &Eyes);
2022-06-12 11:31:33 +00:00
Eyes.HSplitTop(105.0f, 0, &Eyes);
}
else
{
Eyes = EyesRight;
if(MainView.w < 810.0f)
Eyes.VSplitRight(205.0f, 0, &Eyes);
Eyes.HSplitTop(50.0f, &Eyes, 0);
}
Eyes.HSplitTop(120.0f, &EyesLabel, &Eyes);
EyesLabel.VSplitLeft(20.0f, 0, &EyesLabel);
EyesLabel.HSplitTop(50.0f, &EyesLabel, &Eyes);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_aEyeButtons[6];
static int s_aEyesToolTip[6];
for(int CurrentEyeEmote = 0; CurrentEyeEmote < 6; CurrentEyeEmote++)
{
EyesLabel.VSplitLeft(10.0f, 0, &EyesLabel);
EyesLabel.VSplitLeft(50.0f, &EyesTee, &EyesLabel);
if(CurrentEyeEmote == 2 && !RenderEyesBelow)
{
Eyes.HSplitTop(60.0f, &EyesLabel, 0);
EyesLabel.HSplitTop(10.0f, 0, &EyesLabel);
}
float Highlight = (m_Dummy ? g_Config.m_ClDummyDefaultEyes == CurrentEyeEmote : g_Config.m_ClPlayerDefaultEyes == CurrentEyeEmote) ? 1.0f : 0.0f;
if(DoButton_Menu(&s_aEyeButtons[CurrentEyeEmote], "", 0, &EyesTee, 0, IGraphics::CORNER_ALL, 10.0f, 0.0f, vec4(1, 1, 1, 0.5f + Highlight * 0.25f), vec4(1, 1, 1, 0.25f + Highlight * 0.25f)))
{
if(m_Dummy)
{
g_Config.m_ClDummyDefaultEyes = CurrentEyeEmote;
if(g_Config.m_ClDummy)
GameClient()->m_Emoticon.EyeEmote(CurrentEyeEmote);
}
else
{
g_Config.m_ClPlayerDefaultEyes = CurrentEyeEmote;
if(!g_Config.m_ClDummy)
GameClient()->m_Emoticon.EyeEmote(CurrentEyeEmote);
}
}
2022-04-28 14:46:18 +00:00
GameClient()->m_Tooltips.DoToolTip(&s_aEyesToolTip[CurrentEyeEmote], &EyesTee, Localize("Choose default eyes when joining a server"));
RenderTools()->RenderTee(pIdleState, &OwnSkinInfo, CurrentEyeEmote, vec2(1, 0), vec2(EyesTee.x + 25.0f, EyesTee.y + EyesTee.h / 2.0f + OffsetToMid.y));
}
2023-03-25 14:43:25 +00:00
Label.VSplitRight(34.0f, &Label, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_SkinInput;
s_SkinInput.SetBuffer(pSkinName, sizeof(g_Config.m_ClPlayerSkin));
s_SkinInput.SetEmptyText("default");
if(UI()->DoClearableEditBox(&s_SkinInput, &Label, 14.0f))
{
SetNeedSendInfo();
}
2023-03-25 14:43:25 +00:00
// random skin button
Button.VSplitRight(30.0f, 0, &Button);
static CButtonContainer s_RandomSkinButtonID;
static const char *s_apDice[] = {FONT_ICON_DICE_ONE, FONT_ICON_DICE_TWO, FONT_ICON_DICE_THREE, FONT_ICON_DICE_FOUR, FONT_ICON_DICE_FIVE, FONT_ICON_DICE_SIX};
2023-03-25 14:43:25 +00:00
static int s_CurrentDie = rand() % std::size(s_apDice);
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
if(DoButton_Menu(&s_RandomSkinButtonID, s_apDice[s_CurrentDie], 1, &Button, nullptr, IGraphics::CORNER_ALL, 5.0f, -0.2f))
{
RandomSkin();
s_CurrentDie = rand() % std::size(s_apDice);
}
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(nullptr);
2023-03-25 15:13:29 +00:00
GameClient()->m_Tooltips.DoToolTip(&s_RandomSkinButtonID, &Button, Localize("Create a random skin"));
2023-03-24 22:14:17 +00:00
2023-03-25 14:43:25 +00:00
// custom color selector
MainView.HSplitTop(20.0f + RenderEyesBelow * 25.0f, 0, &MainView);
MainView.HSplitTop(20.0f, &Button, &MainView);
Button.VSplitLeft(150.0f, &Button, 0);
2021-05-17 07:11:36 +00:00
static int s_CustomColorID = 0;
if(DoButton_CheckBox(&s_CustomColorID, Localize("Custom colors"), *pUseCustomColor, &Button))
{
*pUseCustomColor = *pUseCustomColor ? 0 : 1;
SetNeedSendInfo();
}
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(5.0f, 0, &MainView);
MainView.HSplitTop(82.5f, &Label, &MainView);
if(*pUseCustomColor)
{
CUIRect aRects[2];
2022-01-26 19:19:44 +00:00
Label.VSplitMid(&aRects[0], &aRects[1], 20.0f);
2010-05-29 07:25:38 +00:00
unsigned *apColors[2] = {pColorBody, pColorFeet};
const char *apParts[] = {Localize("Body"), Localize("Feet")};
2010-05-29 07:25:38 +00:00
for(int i = 0; i < 2; i++)
{
aRects[i].HSplitTop(20.0f, &Label, &aRects[i]);
UI()->DoLabel(&Label, apParts[i], 14.0f, TEXTALIGN_ML);
2020-06-27 13:08:35 +00:00
aRects[i].VSplitLeft(10.0f, 0, &aRects[i]);
aRects[i].HSplitTop(2.5f, 0, &aRects[i]);
unsigned PrevColor = *apColors[i];
RenderHSLScrollbars(&aRects[i], apColors[i], false, true);
2010-05-29 07:25:38 +00:00
if(PrevColor != *apColors[i])
2014-04-28 13:19:57 +00:00
{
SetNeedSendInfo();
2014-04-28 13:19:57 +00:00
}
2010-05-29 07:25:38 +00:00
}
}
2009-10-27 14:38:53 +00:00
// skin selector
MainView.HSplitTop(20.0f, 0, &MainView);
MainView.HSplitTop(230.0f - RenderEyesBelow * 25.0f, &SkinList, &MainView);
static std::vector<CUISkin> s_vSkinList;
2022-10-04 16:40:24 +00:00
static std::vector<CUISkin> s_vSkinListHelper;
static std::vector<CUISkin> s_vFavoriteSkinListHelper;
static int s_SkinCount = 0;
static CListBox s_ListBox;
2022-10-04 16:40:24 +00:00
// be nice to the CPU
static auto s_SkinLastRebuildTime = time_get_nanoseconds();
const auto CurTime = time_get_nanoseconds();
if(s_InitSkinlist || m_pClient->m_Skins.Num() != s_SkinCount || m_SkinFavoritesChanged || (m_pClient->m_Skins.IsDownloadingSkins() && (CurTime - s_SkinLastRebuildTime > 500ms)))
{
2022-10-04 16:40:24 +00:00
s_SkinLastRebuildTime = CurTime;
s_vSkinList.clear();
2022-10-04 16:40:24 +00:00
s_vSkinListHelper.clear();
s_vFavoriteSkinListHelper.clear();
// set skin count early, since Find of the skin class might load
// a downloading skin
s_SkinCount = m_pClient->m_Skins.Num();
m_SkinFavoritesChanged = false;
2016-02-12 17:52:10 +00:00
2022-10-04 16:40:24 +00:00
auto &&SkinNotFiltered = [&](const CSkin *pSkinToBeSelected) {
2016-02-12 17:52:10 +00:00
// filter quick search
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkinToBeSelected->GetName(), g_Config.m_ClSkinFilterString))
2022-10-04 16:40:24 +00:00
return false;
2016-02-12 17:52:10 +00:00
// no special skins
if((pSkinToBeSelected->GetName()[0] == 'x' && pSkinToBeSelected->GetName()[1] == '_'))
2022-10-04 16:40:24 +00:00
return false;
2016-02-12 17:52:10 +00:00
2022-10-04 16:40:24 +00:00
return true;
};
for(const auto &it : m_SkinFavorites)
{
const CSkin *pSkinToBeSelected = m_pClient->m_Skins.FindOrNullptr(it.c_str());
2020-12-16 06:11:52 +00:00
if(pSkinToBeSelected == nullptr || !SkinNotFiltered(pSkinToBeSelected))
2022-10-04 16:40:24 +00:00
continue;
s_vFavoriteSkinListHelper.emplace_back(pSkinToBeSelected);
}
for(const auto &SkinIt : m_pClient->m_Skins.GetSkinsUnsafe())
2022-10-04 16:40:24 +00:00
{
const auto &pSkinToBeSelected = SkinIt.second;
if(!SkinNotFiltered(pSkinToBeSelected.get()))
2022-10-04 16:40:24 +00:00
continue;
if(std::find(m_SkinFavorites.begin(), m_SkinFavorites.end(), pSkinToBeSelected->GetName()) == m_SkinFavorites.end())
s_vSkinListHelper.emplace_back(pSkinToBeSelected.get());
2022-10-04 16:40:24 +00:00
}
std::sort(s_vSkinListHelper.begin(), s_vSkinListHelper.end());
std::sort(s_vFavoriteSkinListHelper.begin(), s_vFavoriteSkinListHelper.end());
s_vSkinList = s_vFavoriteSkinListHelper;
s_vSkinList.insert(s_vSkinList.end(), s_vSkinListHelper.begin(), s_vSkinListHelper.end());
s_InitSkinlist = false;
}
2010-05-29 07:25:38 +00:00
2022-10-04 16:40:24 +00:00
auto &&RenderFavIcon = [&](const CUIRect &FavIcon, bool AsFav) {
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
if(AsFav)
TextRender()->TextColor({1, 1, 0, 1});
else
TextRender()->TextColor({0.5f, 0.5f, 0.5f, 1});
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
SLabelProperties Props;
Props.m_MaxWidth = FavIcon.w;
UI()->DoLabel(&FavIcon, FONT_ICON_STAR, 12.0f, TEXTALIGN_MR, Props);
2022-10-04 16:40:24 +00:00
TextRender()->TextColor(TextRender()->DefaultTextColor());
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(nullptr);
};
int OldSelected = -1;
s_ListBox.DoStart(50.0f, s_vSkinList.size(), 4, 1, OldSelected, &SkinList);
for(size_t i = 0; i < s_vSkinList.size(); ++i)
{
const CSkin *pSkinToBeDraw = s_vSkinList[i].m_pSkin;
2010-05-29 07:25:38 +00:00
if(str_comp(pSkinToBeDraw->GetName(), pSkinName) == 0)
OldSelected = i;
2010-05-29 07:25:38 +00:00
const CListboxItem Item = s_ListBox.DoNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i);
if(!Item.m_Visible)
continue;
2022-10-04 16:40:24 +00:00
const CUIRect OriginalRect = Item.m_Rect;
CTeeRenderInfo Info = OwnSkinInfo;
Info.m_CustomColoredSkin = *pUseCustomColor;
Info.m_OriginalRenderSkin = pSkinToBeDraw->m_OriginalSkin;
Info.m_ColorableRenderSkin = pSkinToBeDraw->m_ColorableSkin;
Info.m_SkinMetrics = pSkinToBeDraw->m_Metrics;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Info, OffsetToMid);
TeeRenderPos = vec2(OriginalRect.x + 30, OriginalRect.y + OriginalRect.h / 2 + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &Info, Emote, vec2(1.0f, 0.0f), TeeRenderPos);
OriginalRect.VSplitLeft(60.0f, 0, &Label);
{
SLabelProperties Props;
Props.m_MaxWidth = Label.w - 5.0f;
UI()->DoLabel(&Label, pSkinToBeDraw->GetName(), 12.0f, TEXTALIGN_ML, Props);
}
if(g_Config.m_Debug)
{
ColorRGBA BloodColor = *pUseCustomColor ? color_cast<ColorRGBA>(ColorHSLA(*pColorBody).UnclampLighting()) : pSkinToBeDraw->m_BloodColor;
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(BloodColor.r, BloodColor.g, BloodColor.b, 1.0f);
IGraphics::CQuadItem QuadItem(Label.x, Label.y, 12.0f, 12.0f);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
}
2010-05-29 07:25:38 +00:00
// render skin favorite icon
{
const auto SkinItFav = m_SkinFavorites.find(pSkinToBeDraw->GetName());
const auto IsFav = SkinItFav != m_SkinFavorites.end();
CUIRect FavIcon;
OriginalRect.HSplitTop(20.0f, &FavIcon, nullptr);
FavIcon.VSplitRight(20.0f, nullptr, &FavIcon);
if(IsFav)
2022-10-04 16:40:24 +00:00
{
RenderFavIcon(FavIcon, IsFav);
2022-10-04 16:40:24 +00:00
}
else
2009-10-27 14:38:53 +00:00
{
if(UI()->MouseInside(&FavIcon))
{
RenderFavIcon(FavIcon, IsFav);
}
2009-10-27 14:38:53 +00:00
}
if(UI()->DoButtonLogic(&pSkinToBeDraw->m_Metrics.m_Body, 0, &FavIcon))
2022-10-04 16:40:24 +00:00
{
if(IsFav)
{
m_SkinFavorites.erase(SkinItFav);
2022-10-04 16:40:24 +00:00
}
else
{
m_SkinFavorites.emplace(pSkinToBeDraw->GetName());
2022-10-04 16:40:24 +00:00
}
s_InitSkinlist = true;
2022-10-04 16:40:24 +00:00
}
}
2010-05-29 07:25:38 +00:00
}
const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected)
{
mem_copy(pSkinName, s_vSkinList[NewSelected].m_pSkin->GetName(), sizeof(g_Config.m_ClPlayerSkin));
SetNeedSendInfo();
2010-05-29 07:25:38 +00:00
}
2016-02-12 17:52:10 +00:00
// render quick search
{
MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch);
2020-09-21 08:39:42 +00:00
QuickSearch.VSplitLeft(240.0f, &QuickSearch, &SkinDB);
2016-02-12 17:52:10 +00:00
QuickSearch.HSplitTop(5.0f, 0, &QuickSearch);
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
2020-10-06 10:25:10 +00:00
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
2022-03-11 16:34:48 +00:00
UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML);
float wSearch = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(NULL);
2016-02-12 17:52:10 +00:00
QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
QuickSearch.VSplitLeft(QuickSearch.w - 15.0f, &QuickSearch, &QuickSearchClearButton);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
2022-03-13 18:09:33 +00:00
{
UI()->SetActiveItem(&s_SkinFilterInput);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
s_SkinFilterInput.SelectAll();
2022-03-13 18:09:33 +00:00
}
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
s_SkinFilterInput.SetEmptyText(Localize("Search"));
if(UI()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f))
2016-02-12 17:52:10 +00:00
s_InitSkinlist = true;
}
2020-09-29 01:23:39 +00:00
SkinDB.VSplitLeft(150.0f, &SkinDB, &DirectoryButton);
2020-09-21 08:39:42 +00:00
SkinDB.HSplitTop(5.0f, 0, &SkinDB);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_SkinDBDirID;
2021-05-17 07:11:36 +00:00
if(DoButton_Menu(&s_SkinDBDirID, Localize("Skin Database"), 0, &SkinDB))
2020-09-21 08:39:42 +00:00
{
2022-08-30 08:09:06 +00:00
const char *pLink = "https://ddnet.org/skins/";
if(!open_link(pLink))
2020-09-21 08:39:42 +00:00
{
dbg_msg("menus", "couldn't open link '%s'", pLink);
2020-09-21 08:39:42 +00:00
}
}
2020-09-29 01:23:39 +00:00
DirectoryButton.HSplitTop(5.0f, 0, &DirectoryButton);
DirectoryButton.VSplitRight(175.0f, 0, &DirectoryButton);
DirectoryButton.VSplitRight(25.0f, &DirectoryButton, &RefreshButton);
DirectoryButton.VSplitRight(10.0f, &DirectoryButton, 0);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_DirectoryButtonID;
2021-05-17 07:11:36 +00:00
if(DoButton_Menu(&s_DirectoryButtonID, Localize("Skins directory"), 0, &DirectoryButton))
2020-09-29 01:23:39 +00:00
{
Storage()->GetCompletePath(IStorage::TYPE_SAVE, "skins", aBuf, sizeof(aBuf));
Storage()->CreateFolder("skins", IStorage::TYPE_SAVE);
2021-12-19 00:13:08 +00:00
if(!open_file(aBuf))
2020-09-29 01:23:39 +00:00
{
dbg_msg("menus", "couldn't open file '%s'", aBuf);
2020-09-29 01:23:39 +00:00
}
}
GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButtonID, &DirectoryButton, Localize("Open the directory to add custom skins"));
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
2020-10-06 10:25:10 +00:00
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_SkinRefreshButtonID;
if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton, nullptr, IGraphics::CORNER_ALL, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f)))
{
// reset render flags for possible loading screen
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(NULL);
RefreshSkins();
s_InitSkinlist = true;
}
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(NULL);
}
2011-04-01 17:36:44 +00:00
2010-05-29 07:25:38 +00:00
typedef struct
{
2010-05-29 07:25:38 +00:00
CLocConstString m_Name;
const char *m_pCommand;
int m_KeyId;
2022-02-06 22:14:26 +00:00
int m_ModifierCombination;
2010-05-29 07:25:38 +00:00
} CKeyInfo;
2010-05-29 07:25:38 +00:00
static CKeyInfo gs_aKeys[] =
{
{"Move left", "+left", 0, 0}, // Localize - these strings are localized within CLocConstString
{"Move right", "+right", 0, 0},
{"Jump", "+jump", 0, 0},
{"Fire", "+fire", 0, 0},
{"Hook", "+hook", 0, 0},
{"Hook collisions", "+showhookcoll", 0, 0},
{"Pause", "say /pause", 0, 0},
{"Kill", "kill", 0, 0},
{"Zoom in", "zoom+", 0, 0},
{"Zoom out", "zoom-", 0, 0},
{"Default zoom", "zoom", 0, 0},
{"Show others", "say /showothers", 0, 0},
{"Show all", "say /showall", 0, 0},
{"Toggle dyncam", "toggle cl_dyncam 0 1", 0, 0},
{"Toggle ghost", "toggle cl_race_show_ghost 0 1", 0, 0},
{"Hammer", "+weapon1", 0, 0},
{"Pistol", "+weapon2", 0, 0},
{"Shotgun", "+weapon3", 0, 0},
{"Grenade", "+weapon4", 0, 0},
{"Laser", "+weapon5", 0, 0},
{"Next weapon", "+nextweapon", 0, 0},
{"Prev. weapon", "+prevweapon", 0, 0},
{"Vote yes", "vote yes", 0, 0},
{"Vote no", "vote no", 0, 0},
{"Chat", "+show_chat; chat all", 0, 0},
{"Team chat", "+show_chat; chat team", 0, 0},
{"Converse", "+show_chat; chat all /c ", 0, 0},
{"Chat command", "+show_chat; chat all /", 0, 0},
{"Show chat", "+show_chat", 0, 0},
{"Toggle dummy", "toggle cl_dummy 0 1", 0, 0},
{"Dummy copy", "toggle cl_dummy_copy_moves 0 1", 0, 0},
{"Hammerfly dummy", "toggle cl_dummy_hammer 0 1", 0, 0},
{"Emoticon", "+emote", 0, 0},
{"Spectator mode", "+spectate", 0, 0},
{"Spectate next", "spectate_next", 0, 0},
{"Spectate previous", "spectate_previous", 0, 0},
{"Console", "toggle_local_console", 0, 0},
{"Remote console", "toggle_remote_console", 0, 0},
{"Screenshot", "screenshot", 0, 0},
{"Scoreboard", "+scoreboard", 0, 0},
{"Statboard", "+statboard", 0, 0},
{"Lock team", "say /lock", 0, 0},
{"Show entities", "toggle cl_overlay_entities 0 100", 0, 0},
{"Show HUD", "toggle cl_showhud 0 1", 0, 0},
};
/* This is for scripts/languages to work, don't remove!
Localize("Move left");Localize("Move right");Localize("Jump");Localize("Fire");Localize("Hook");
Localize("Hook collisions");Localize("Pause");Localize("Kill");Localize("Zoom in");Localize("Zoom out");
Localize("Default zoom");Localize("Show others");Localize("Show all");Localize("Toggle dyncam");
Localize("Toggle dummy");Localize("Toggle ghost");Localize("Dummy copy");Localize("Hammerfly dummy");
Localize("Hammer");Localize("Pistol");Localize("Shotgun");Localize("Grenade");Localize("Laser");
Localize("Next weapon");Localize("Prev. weapon");Localize("Vote yes");Localize("Vote no");
Localize("Chat");Localize("Team chat");Localize("Converse");Localize("Show chat");Localize("Emoticon");
Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console");
Localize("Remote console");Localize("Screenshot");Localize("Scoreboard");Localize("Statboard");
2021-03-03 14:21:02 +00:00
Localize("Lock team");Localize("Show entities");Localize("Show HUD");Localize("Chat command");
2010-08-08 22:35:37 +00:00
*/
void CMenus::DoSettingsControlsButtons(int Start, int Stop, CUIRect View)
{
for(int i = Start; i < Stop; i++)
{
2010-05-29 07:25:38 +00:00
CKeyInfo &Key = gs_aKeys[i];
CUIRect Button, Label;
View.HSplitTop(20.0f, &Button, &View);
Button.VSplitLeft(135.0f, &Label, &Button);
2010-05-29 07:25:38 +00:00
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%s:", Localize((const char *)Key.m_Name));
2017-07-22 20:43:20 +00:00
UI()->DoLabel(&Label, aBuf, 13.0f, TEXTALIGN_ML);
int OldId = Key.m_KeyId, OldModifierCombination = Key.m_ModifierCombination, NewModifierCombination;
int NewId = DoKeyReader((void *)&Key.m_Name, &Button, OldId, OldModifierCombination, &NewModifierCombination);
if(NewId != OldId || NewModifierCombination != OldModifierCombination)
{
if(OldId != 0 || NewId == 0)
m_pClient->m_Binds.Bind(OldId, "", false, OldModifierCombination);
if(NewId != 0)
m_pClient->m_Binds.Bind(NewId, gs_aKeys[i].m_pCommand, false, NewModifierCombination);
}
2017-07-22 20:43:20 +00:00
2015-05-31 20:40:38 +00:00
View.HSplitTop(2.0f, 0, &View);
}
}
float CMenus::RenderSettingsControlsJoystick(CUIRect View)
{
bool JoystickEnabled = g_Config.m_InpControllerEnable;
int NumJoysticks = Input()->NumJoysticks();
int NumOptions = 1; // expandable header
if(JoystickEnabled)
{
if(NumJoysticks == 0)
NumOptions++; // message
else
{
if(NumJoysticks > 1)
NumOptions++; // joystick selection
NumOptions += 3; // mode, ui sens, tolerance
if(!g_Config.m_InpControllerAbsolute)
NumOptions++; // ingame sens
NumOptions += Input()->GetActiveJoystick()->GetNumAxes() + 1; // axis selection + header
}
}
const float ButtonHeight = 20.0f;
const float Spacing = 2.0f;
const float BackgroundHeight = NumOptions * (ButtonHeight + Spacing) + (NumOptions == 1 ? 0.0f : Spacing);
if(View.h < BackgroundHeight)
return BackgroundHeight;
View.HSplitTop(BackgroundHeight, &View, nullptr);
CUIRect Button;
View.HSplitTop(Spacing, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
if(DoButton_CheckBox(&g_Config.m_InpControllerEnable, Localize("Enable controller"), g_Config.m_InpControllerEnable, &Button))
{
g_Config.m_InpControllerEnable ^= 1;
}
if(JoystickEnabled)
{
if(NumJoysticks > 0)
{
// show joystick device selection if more than one available or just the joystick name if there is only one
{
View.HSplitTop(Spacing, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
char aBuf[96];
str_format(aBuf, sizeof(aBuf), Localize("Controller %d: %s"), Input()->GetActiveJoystick()->GetIndex(), Input()->GetActiveJoystick()->GetName());
if(NumJoysticks > 1)
{
static CButtonContainer s_ButtonJoystickId;
if(DoButton_Menu(&s_ButtonJoystickId, aBuf, 0, &Button))
Input()->SelectNextJoystick();
GameClient()->m_Tooltips.DoToolTip(&s_ButtonJoystickId, &Button, Localize("Click to cycle through all available controllers."));
}
else
{
UI()->DoLabel(&Button, aBuf, 13.0f, TEXTALIGN_ML);
}
}
{
View.HSplitTop(Spacing, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
CUIRect Label, ButtonRelative, ButtonAbsolute;
Button.VSplitMid(&Label, &Button, 10.0f);
Button.HMargin(2.0f, &Button);
Button.VSplitMid(&ButtonRelative, &ButtonAbsolute);
UI()->DoLabel(&Label, Localize("Ingame controller mode"), 13.0f, TEXTALIGN_ML);
CButtonContainer s_RelativeButton;
if(DoButton_Menu(&s_RelativeButton, Localize("Relative", "Ingame controller mode"), g_Config.m_InpControllerAbsolute == 0, &ButtonRelative, nullptr, IGraphics::CORNER_L))
{
g_Config.m_InpControllerAbsolute = 0;
}
CButtonContainer s_AbsoluteButton;
if(DoButton_Menu(&s_AbsoluteButton, Localize("Absolute", "Ingame controller mode"), g_Config.m_InpControllerAbsolute == 1, &ButtonAbsolute, nullptr, IGraphics::CORNER_R))
{
g_Config.m_InpControllerAbsolute = 1;
}
}
if(!g_Config.m_InpControllerAbsolute)
{
View.HSplitTop(Spacing, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
UI()->DoScrollbarOption(&g_Config.m_InpControllerSens, &g_Config.m_InpControllerSens, &Button, Localize("Ingame controller sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE);
}
View.HSplitTop(Spacing, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
UI()->DoScrollbarOption(&g_Config.m_UiControllerSens, &g_Config.m_UiControllerSens, &Button, Localize("UI controller sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE);
View.HSplitTop(Spacing, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
UI()->DoScrollbarOption(&g_Config.m_InpControllerTolerance, &g_Config.m_InpControllerTolerance, &Button, Localize("Controller jitter tolerance"), 0, 50);
View.HSplitTop(Spacing, nullptr, &View);
View.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.125f), IGraphics::CORNER_ALL, 5.0f);
DoJoystickAxisPicker(View);
}
else
{
View.HSplitTop(View.h - ButtonHeight, nullptr, &View);
View.HSplitTop(ButtonHeight, &Button, &View);
UI()->DoLabel(&Button, Localize("No controller found. Plug in a controller."), 13.0f, TEXTALIGN_ML);
}
}
return BackgroundHeight;
}
void CMenus::DoJoystickAxisPicker(CUIRect View)
{
const float FontSize = 13.0f;
const float RowHeight = 20.0f;
const float SpacingH = 2.0f;
const float AxisWidth = 0.2f * View.w;
const float StatusWidth = 0.4f * View.w;
const float AimBindWidth = 90.0f;
const float SpacingV = (View.w - AxisWidth - StatusWidth - AimBindWidth) / 2.0f;
CUIRect Row, Axis, Status, AimBind;
View.HSplitTop(SpacingH, nullptr, &View);
View.HSplitTop(RowHeight, &Row, &View);
Row.VSplitLeft(AxisWidth, &Axis, &Row);
Row.VSplitLeft(SpacingV, nullptr, &Row);
Row.VSplitLeft(StatusWidth, &Status, &Row);
Row.VSplitLeft(SpacingV, nullptr, &Row);
Row.VSplitLeft(AimBindWidth, &AimBind, &Row);
UI()->DoLabel(&Axis, Localize("Axis"), FontSize, TEXTALIGN_MC);
UI()->DoLabel(&Status, Localize("Status"), FontSize, TEXTALIGN_MC);
UI()->DoLabel(&AimBind, Localize("Aim bind"), FontSize, TEXTALIGN_MC);
IInput::IJoystick *pJoystick = Input()->GetActiveJoystick();
static int s_aActive[NUM_JOYSTICK_AXES][2];
for(int i = 0; i < minimum<int>(pJoystick->GetNumAxes(), NUM_JOYSTICK_AXES); i++)
{
const bool Active = g_Config.m_InpControllerX == i || g_Config.m_InpControllerY == i;
View.HSplitTop(SpacingH, nullptr, &View);
View.HSplitTop(RowHeight, &Row, &View);
Row.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.125f), IGraphics::CORNER_ALL, 5.0f);
Row.VSplitLeft(AxisWidth, &Axis, &Row);
Row.VSplitLeft(SpacingV, nullptr, &Row);
Row.VSplitLeft(StatusWidth, &Status, &Row);
Row.VSplitLeft(SpacingV, nullptr, &Row);
Row.VSplitLeft(AimBindWidth, &AimBind, &Row);
// Axis label
char aBuf[16];
str_format(aBuf, sizeof(aBuf), "%d", i + 1);
if(Active)
TextRender()->TextColor(TextRender()->DefaultTextColor());
else
TextRender()->TextColor(0.7f, 0.7f, 0.7f, 1.0f);
UI()->DoLabel(&Axis, aBuf, FontSize, TEXTALIGN_MC);
// Axis status
Status.HMargin(7.0f, &Status);
DoJoystickBar(&Status, (pJoystick->GetAxisValue(i) + 1.0f) / 2.0f, g_Config.m_InpControllerTolerance / 50.0f, Active);
// Bind to X/Y
CUIRect AimBindX, AimBindY;
AimBind.VSplitMid(&AimBindX, &AimBindY);
if(DoButton_CheckBox(&s_aActive[i][0], "X", g_Config.m_InpControllerX == i, &AimBindX))
{
if(g_Config.m_InpControllerY == i)
g_Config.m_InpControllerY = g_Config.m_InpControllerX;
g_Config.m_InpControllerX = i;
}
if(DoButton_CheckBox(&s_aActive[i][1], "Y", g_Config.m_InpControllerY == i, &AimBindY))
{
if(g_Config.m_InpControllerX == i)
g_Config.m_InpControllerX = g_Config.m_InpControllerY;
g_Config.m_InpControllerY = i;
}
}
TextRender()->TextColor(TextRender()->DefaultTextColor());
}
void CMenus::DoJoystickBar(const CUIRect *pRect, float Current, float Tolerance, bool Active)
{
CUIRect Handle;
pRect->VSplitLeft(pRect->h, &Handle, nullptr); // Slider size
Handle.x += (pRect->w - Handle.w) * Current;
pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, Active ? 0.25f : 0.125f), IGraphics::CORNER_ALL, pRect->h / 2.0f);
CUIRect ToleranceArea = *pRect;
ToleranceArea.w *= Tolerance;
ToleranceArea.x += (pRect->w - ToleranceArea.w) / 2.0f;
const ColorRGBA ToleranceColor = Active ? ColorRGBA(0.8f, 0.35f, 0.35f, 1.0f) : ColorRGBA(0.7f, 0.5f, 0.5f, 1.0f);
ToleranceArea.Draw(ToleranceColor, IGraphics::CORNER_ALL, ToleranceArea.h / 2.0f);
const ColorRGBA SliderColor = Active ? ColorRGBA(0.95f, 0.95f, 0.95f, 1.0f) : ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f);
Handle.Draw(SliderColor, IGraphics::CORNER_ALL, Handle.h / 2.0f);
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderSettingsControls(CUIRect MainView)
{
// this is kinda slow, but whatever
2020-10-26 14:14:07 +00:00
for(auto &Key : gs_aKeys)
2022-02-06 22:14:26 +00:00
Key.m_KeyId = Key.m_ModifierCombination = 0;
2010-05-29 07:25:38 +00:00
for(int Mod = 0; Mod < CBinds::MODIFIER_COMBINATION_COUNT; Mod++)
{
2019-04-28 13:32:14 +00:00
for(int KeyId = 0; KeyId < KEY_LAST; KeyId++)
{
2021-07-12 09:43:56 +00:00
const char *pBind = m_pClient->m_Binds.Get(KeyId, Mod);
2019-04-28 13:32:14 +00:00
if(!pBind[0])
continue;
2010-05-29 07:25:38 +00:00
2020-10-26 14:14:07 +00:00
for(auto &Key : gs_aKeys)
if(str_comp(pBind, Key.m_pCommand) == 0)
2019-04-28 13:32:14 +00:00
{
2020-10-26 14:14:07 +00:00
Key.m_KeyId = KeyId;
2022-02-06 22:14:26 +00:00
Key.m_ModifierCombination = Mod;
2019-04-28 13:32:14 +00:00
break;
}
}
}
// scrollable controls
static float s_JoystickSettingsHeight = 0.0f; // we calculate this later and don't render until enough space is available
static CScrollRegion s_ScrollRegion;
vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
ScrollParams.m_ScrollUnit = 120.0f;
s_ScrollRegion.Begin(&MainView, &ScrollOffset, &ScrollParams);
MainView.y += ScrollOffset.y;
2010-05-29 07:25:38 +00:00
const float FontSize = 14.0f;
const float Margin = 10.0f;
const float HeaderHeight = FontSize + 5.0f + Margin;
CUIRect MouseSettings, MovementSettings, WeaponSettings, VotingSettings, ChatSettings, DummySettings, MiscSettings, JoystickSettings, ResetButton;
MainView.VSplitMid(&MouseSettings, &VotingSettings);
// mouse settings
2008-10-05 15:59:36 +00:00
{
MouseSettings.VMargin(5.0f, &MouseSettings);
MouseSettings.HSplitTop(80.0f, &MouseSettings, &JoystickSettings);
if(s_ScrollRegion.AddRect(MouseSettings))
{
MouseSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
MouseSettings.VMargin(10.0f, &MouseSettings);
2010-05-29 07:25:38 +00:00
TextRender()->Text(MouseSettings.x, MouseSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Mouse"), -1.0f);
2010-05-29 07:25:38 +00:00
MouseSettings.HSplitTop(HeaderHeight, 0, &MouseSettings);
2010-05-29 07:25:38 +00:00
CUIRect Button;
MouseSettings.HSplitTop(20.0f, &Button, &MouseSettings);
UI()->DoScrollbarOption(&g_Config.m_InpMousesens, &g_Config.m_InpMousesens, &Button, Localize("Ingame mouse sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE);
MouseSettings.HSplitTop(2.0f, 0, &MouseSettings);
MouseSettings.HSplitTop(20.0f, &Button, &MouseSettings);
UI()->DoScrollbarOption(&g_Config.m_UiMousesens, &g_Config.m_UiMousesens, &Button, Localize("UI mouse sens."), 1, 500, &CUI::ms_LogarithmicScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE);
}
}
// joystick settings
{
JoystickSettings.HSplitTop(Margin, 0, &JoystickSettings);
JoystickSettings.HSplitTop(s_JoystickSettingsHeight + HeaderHeight + Margin, &JoystickSettings, &MovementSettings);
if(s_ScrollRegion.AddRect(JoystickSettings))
{
JoystickSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
JoystickSettings.VMargin(Margin, &JoystickSettings);
TextRender()->Text(JoystickSettings.x, JoystickSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Controller"), -1.0f);
JoystickSettings.HSplitTop(HeaderHeight, 0, &JoystickSettings);
s_JoystickSettingsHeight = RenderSettingsControlsJoystick(JoystickSettings);
}
}
// movement settings
{
MovementSettings.HSplitTop(Margin, 0, &MovementSettings);
MovementSettings.HSplitTop(365.0f, &MovementSettings, &WeaponSettings);
if(s_ScrollRegion.AddRect(MovementSettings))
{
MovementSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
MovementSettings.VMargin(Margin, &MovementSettings);
TextRender()->Text(MovementSettings.x, MovementSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Movement"), -1.0f);
MovementSettings.HSplitTop(HeaderHeight, 0, &MovementSettings);
DoSettingsControlsButtons(0, 15, MovementSettings);
}
2008-10-05 15:59:36 +00:00
}
2010-05-29 07:25:38 +00:00
// weapon settings
{
WeaponSettings.HSplitTop(Margin, 0, &WeaponSettings);
WeaponSettings.HSplitTop(190.0f, &WeaponSettings, &ResetButton);
if(s_ScrollRegion.AddRect(WeaponSettings))
{
WeaponSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
WeaponSettings.VMargin(Margin, &WeaponSettings);
2010-05-29 07:25:38 +00:00
TextRender()->Text(WeaponSettings.x, WeaponSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Weapon"), -1.0f);
2010-05-29 07:25:38 +00:00
WeaponSettings.HSplitTop(HeaderHeight, 0, &WeaponSettings);
DoSettingsControlsButtons(15, 22, WeaponSettings);
}
}
2011-08-11 08:59:14 +00:00
// defaults
{
ResetButton.HSplitTop(Margin, 0, &ResetButton);
ResetButton.HSplitTop(40.0f, &ResetButton, 0);
if(s_ScrollRegion.AddRect(ResetButton))
{
ResetButton.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
ResetButton.HMargin(10.0f, &ResetButton);
ResetButton.VMargin(30.0f, &ResetButton);
static CButtonContainer s_DefaultButton;
if(DoButton_Menu(&s_DefaultButton, Localize("Reset to defaults"), 0, &ResetButton))
{
PopupConfirm(Localize("Reset controls"), Localize("Are you sure that you want to reset the controls to their defaults?"),
Localize("Reset"), Localize("Cancel"), &CMenus::ResetSettingsControls);
}
}
}
2011-08-11 08:59:14 +00:00
2010-05-29 07:25:38 +00:00
// voting settings
{
VotingSettings.VMargin(5.0f, &VotingSettings);
VotingSettings.HSplitTop(80.0f, &VotingSettings, &ChatSettings);
if(s_ScrollRegion.AddRect(VotingSettings))
{
VotingSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
VotingSettings.VMargin(Margin, &VotingSettings);
2010-05-29 07:25:38 +00:00
TextRender()->Text(VotingSettings.x, VotingSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Voting"), -1.0f);
2010-05-29 07:25:38 +00:00
VotingSettings.HSplitTop(HeaderHeight, 0, &VotingSettings);
DoSettingsControlsButtons(22, 24, VotingSettings);
}
}
2010-05-29 07:25:38 +00:00
// chat settings
{
ChatSettings.HSplitTop(Margin, 0, &ChatSettings);
ChatSettings.HSplitTop(145.0f, &ChatSettings, &DummySettings);
if(s_ScrollRegion.AddRect(ChatSettings))
{
ChatSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
ChatSettings.VMargin(Margin, &ChatSettings);
2010-05-29 07:25:38 +00:00
TextRender()->Text(ChatSettings.x, ChatSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Chat"), -1.0f);
2010-05-29 07:25:38 +00:00
ChatSettings.HSplitTop(HeaderHeight, 0, &ChatSettings);
DoSettingsControlsButtons(24, 29, ChatSettings);
}
}
// dummy settings
{
DummySettings.HSplitTop(Margin, 0, &DummySettings);
DummySettings.HSplitTop(100.0f, &DummySettings, &MiscSettings);
if(s_ScrollRegion.AddRect(DummySettings))
{
DummySettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
DummySettings.VMargin(Margin, &DummySettings);
TextRender()->Text(DummySettings.x, DummySettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Dummy"), -1.0f);
DummySettings.HSplitTop(HeaderHeight, 0, &DummySettings);
DoSettingsControlsButtons(29, 32, DummySettings);
}
}
2010-05-29 07:25:38 +00:00
// misc settings
{
MiscSettings.HSplitTop(Margin, 0, &MiscSettings);
MiscSettings.HSplitTop(300.0f, &MiscSettings, 0);
if(s_ScrollRegion.AddRect(MiscSettings))
{
MiscSettings.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
MiscSettings.VMargin(Margin, &MiscSettings);
2010-05-29 07:25:38 +00:00
TextRender()->Text(MiscSettings.x, MiscSettings.y + (HeaderHeight - FontSize) / 2.f, FontSize, Localize("Miscellaneous"), -1.0f);
2010-05-29 07:25:38 +00:00
MiscSettings.HSplitTop(HeaderHeight, 0, &MiscSettings);
DoSettingsControlsButtons(32, 44, MiscSettings);
}
}
2010-05-29 07:25:38 +00:00
s_ScrollRegion.End();
}
void CMenus::ResetSettingsControls()
{
m_pClient->m_Binds.SetDefaults();
g_Config.m_InpMousesens = 200;
g_Config.m_UiMousesens = 200;
g_Config.m_InpControllerEnable = 0;
g_Config.m_InpControllerGUID[0] = '\0';
g_Config.m_InpControllerAbsolute = 0;
g_Config.m_InpControllerSens = 100;
g_Config.m_InpControllerX = 0;
g_Config.m_InpControllerY = 1;
g_Config.m_InpControllerTolerance = 5;
g_Config.m_UiControllerSens = 100;
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderSettingsGraphics(CUIRect MainView)
{
CUIRect Button;
2010-05-29 07:25:38 +00:00
char aBuf[128];
bool CheckSettings = false;
2010-05-29 07:25:38 +00:00
static const int MAX_RESOLUTIONS = 256;
2010-05-29 07:25:38 +00:00
static CVideoMode s_aModes[MAX_RESOLUTIONS];
2015-08-24 20:46:28 +00:00
static int s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen);
static int s_GfxFsaaSamples = g_Config.m_GfxFsaaSamples;
2022-03-20 17:04:00 +00:00
static bool s_GfxBackendChanged = false;
static bool s_GfxGPUChanged = false;
2020-04-07 20:37:46 +00:00
static int s_GfxHighdpi = g_Config.m_GfxHighdpi;
2021-08-21 19:41:51 +00:00
static int s_InitDisplayAllVideoModes = g_Config.m_GfxDisplayAllVideoModes;
2010-05-29 07:25:38 +00:00
2021-08-21 19:41:51 +00:00
static bool s_WasInit = false;
static bool s_ModesReload = false;
if(!s_WasInit)
{
s_WasInit = true;
Graphics()->AddWindowPropChangeListener([]() {
2021-08-21 19:41:51 +00:00
s_ModesReload = true;
});
2021-08-21 19:41:51 +00:00
}
if(s_ModesReload || g_Config.m_GfxDisplayAllVideoModes != s_InitDisplayAllVideoModes)
{
s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen);
s_ModesReload = false;
s_InitDisplayAllVideoModes = g_Config.m_GfxDisplayAllVideoModes;
}
CUIRect ModeList, ModeLabel;
MainView.VSplitLeft(350.0f, &MainView, &ModeList);
2021-08-21 19:41:51 +00:00
ModeList.HSplitTop(24.0f, &ModeLabel, &ModeList);
MainView.VSplitLeft(340.0f, &MainView, 0);
2010-05-29 07:25:38 +00:00
// display mode list
static CListBox s_ListBox;
2021-08-21 19:41:51 +00:00
static const float sc_RowHeightResList = 22.0f;
static const float sc_FontSizeResListHeader = 12.0f;
static const float sc_FontSizeResList = 10.0f;
{
2022-04-25 16:23:03 +00:00
int G = std::gcd(g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight);
str_format(aBuf, sizeof(aBuf), "%s: %dx%d @%dhz %d bit (%d:%d)", Localize("Current"), (int)(g_Config.m_GfxScreenWidth * Graphics()->ScreenHiDPIScale()), (int)(g_Config.m_GfxScreenHeight * Graphics()->ScreenHiDPIScale()), g_Config.m_GfxScreenRefreshRate, g_Config.m_GfxColorDepth, g_Config.m_GfxScreenWidth / G, g_Config.m_GfxScreenHeight / G);
UI()->DoLabel(&ModeLabel, aBuf, sc_FontSizeResListHeader, TEXTALIGN_MC);
}
2021-08-21 19:41:51 +00:00
int OldSelected = -1;
s_ListBox.SetActive(!UI()->IsPopupOpen());
s_ListBox.DoStart(sc_RowHeightResList, s_NumNodes, 1, 3, OldSelected, &ModeList);
2010-05-29 07:25:38 +00:00
for(int i = 0; i < s_NumNodes; ++i)
{
const int Depth = s_aModes[i].m_Red + s_aModes[i].m_Green + s_aModes[i].m_Blue > 16 ? 24 : 16;
2010-05-29 07:25:38 +00:00
if(g_Config.m_GfxColorDepth == Depth &&
2021-05-06 10:59:30 +00:00
g_Config.m_GfxScreenWidth == s_aModes[i].m_WindowWidth &&
2021-08-21 19:41:51 +00:00
g_Config.m_GfxScreenHeight == s_aModes[i].m_WindowHeight &&
g_Config.m_GfxScreenRefreshRate == s_aModes[i].m_RefreshRate)
{
2010-05-29 07:25:38 +00:00
OldSelected = i;
}
2010-05-29 07:25:38 +00:00
const CListboxItem Item = s_ListBox.DoNextItem(&s_aModes[i], OldSelected == i);
if(!Item.m_Visible)
continue;
int G = std::gcd(s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight);
str_format(aBuf, sizeof(aBuf), " %dx%d @%dhz %d bit (%d:%d)", s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight, s_aModes[i].m_RefreshRate, Depth, s_aModes[i].m_CanvasWidth / G, s_aModes[i].m_CanvasHeight / G);
UI()->DoLabel(&Item.m_Rect, aBuf, sc_FontSizeResList, TEXTALIGN_ML);
}
2010-05-29 07:25:38 +00:00
const int NewSelected = s_ListBox.DoEnd();
2010-05-29 07:25:38 +00:00
if(OldSelected != NewSelected)
{
const int Depth = s_aModes[NewSelected].m_Red + s_aModes[NewSelected].m_Green + s_aModes[NewSelected].m_Blue > 16 ? 24 : 16;
2010-05-29 07:25:38 +00:00
g_Config.m_GfxColorDepth = Depth;
2021-05-06 10:59:30 +00:00
g_Config.m_GfxScreenWidth = s_aModes[NewSelected].m_WindowWidth;
g_Config.m_GfxScreenHeight = s_aModes[NewSelected].m_WindowHeight;
2021-08-21 19:41:51 +00:00
g_Config.m_GfxScreenRefreshRate = s_aModes[NewSelected].m_RefreshRate;
Graphics()->Resize(g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight, g_Config.m_GfxScreenRefreshRate);
2010-05-29 07:25:38 +00:00
}
// switches
CUIRect WindowModeDropDown;
MainView.HSplitTop(20.0f, &WindowModeDropDown, &MainView);
const char *apWindowModes[] = {Localize("Windowed"), Localize("Windowed borderless"), Localize("Windowed fullscreen"), Localize("Desktop fullscreen"), Localize("Fullscreen")};
static const int s_NumWindowMode = std::size(apWindowModes);
2022-03-20 17:04:00 +00:00
const int OldWindowMode = (g_Config.m_GfxFullscreen ? (g_Config.m_GfxFullscreen == 1 ? 4 : (g_Config.m_GfxFullscreen == 2 ? 3 : 2)) : (g_Config.m_GfxBorderless ? 1 : 0));
static CUI::SDropDownState s_WindowModeDropDownState;
static CScrollRegion s_WindowModeDropDownScrollRegion;
s_WindowModeDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_WindowModeDropDownScrollRegion;
const int NewWindowMode = UI()->DoDropDown(&WindowModeDropDown, OldWindowMode, apWindowModes, s_NumWindowMode, s_WindowModeDropDownState);
if(OldWindowMode != NewWindowMode)
{
if(NewWindowMode == 0)
Client()->SetWindowParams(0, false, true);
else if(NewWindowMode == 1)
Client()->SetWindowParams(0, true, true);
else if(NewWindowMode == 2)
Client()->SetWindowParams(3, false, false);
else if(NewWindowMode == 3)
Client()->SetWindowParams(2, false, true);
else if(NewWindowMode == 4)
Client()->SetWindowParams(1, false, true);
}
MainView.HSplitTop(2.0f, nullptr, &MainView);
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
str_format(aBuf, sizeof(aBuf), "%s (%s)", Localize("V-Sync"), Localize("may cause delay"));
if(DoButton_CheckBox(&g_Config.m_GfxVsync, aBuf, g_Config.m_GfxVsync, &Button))
2009-01-21 20:55:07 +00:00
{
Client()->ToggleWindowVSync();
}
if(Graphics()->GetNumScreens() > 1)
{
int NumScreens = Graphics()->GetNumScreens();
MainView.HSplitTop(20.0f, &Button, &MainView);
int Screen_MouseButton = DoButton_CheckBox_Number(&g_Config.m_GfxScreen, Localize("Screen"), g_Config.m_GfxScreen, &Button);
2022-03-20 17:03:25 +00:00
if(Screen_MouseButton == 1) // inc
{
Client()->SwitchWindowScreen((g_Config.m_GfxScreen + 1) % NumScreens);
s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen);
}
2022-03-20 17:03:25 +00:00
else if(Screen_MouseButton == 2) // dec
{
Client()->SwitchWindowScreen((g_Config.m_GfxScreen - 1 + NumScreens) % NumScreens);
s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen);
}
2009-01-21 20:55:07 +00:00
}
2022-04-26 18:09:47 +00:00
bool MultiSamplingChanged = false;
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
str_format(aBuf, sizeof(aBuf), "%s (%s)", Localize("FSAA samples"), Localize("may cause delay"));
int GfxFsaaSamples_MouseButton = DoButton_CheckBox_Number(&g_Config.m_GfxFsaaSamples, aBuf, g_Config.m_GfxFsaaSamples, &Button);
2022-04-26 18:09:47 +00:00
int CurFSAA = g_Config.m_GfxFsaaSamples == 0 ? 1 : g_Config.m_GfxFsaaSamples;
2022-03-20 17:03:25 +00:00
if(GfxFsaaSamples_MouseButton == 1) // inc
{
2022-04-26 18:09:47 +00:00
g_Config.m_GfxFsaaSamples = std::pow(2, (int)std::log2(CurFSAA) + 1);
if(g_Config.m_GfxFsaaSamples > 64)
g_Config.m_GfxFsaaSamples = 0;
MultiSamplingChanged = true;
}
2022-03-20 17:03:25 +00:00
else if(GfxFsaaSamples_MouseButton == 2) // dec
{
2022-04-26 18:09:47 +00:00
if(CurFSAA == 1)
g_Config.m_GfxFsaaSamples = 64;
else if(CurFSAA == 2)
g_Config.m_GfxFsaaSamples = 0;
else
g_Config.m_GfxFsaaSamples = std::pow(2, (int)std::log2(CurFSAA) - 1);
MultiSamplingChanged = true;
}
uint32_t MultiSamplingCountBackend = 0;
if(MultiSamplingChanged)
{
if(Graphics()->SetMultiSampling(g_Config.m_GfxFsaaSamples, MultiSamplingCountBackend))
{
// try again with 0 if mouse click was increasing multi sampling
// else just accept the current value as is
if((uint32_t)g_Config.m_GfxFsaaSamples > MultiSamplingCountBackend && GfxFsaaSamples_MouseButton == 1)
Graphics()->SetMultiSampling(0, MultiSamplingCountBackend);
g_Config.m_GfxFsaaSamples = (int)MultiSamplingCountBackend;
}
else
{
CheckSettings = true;
}
}
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxHighDetail, Localize("High Detail"), g_Config.m_GfxHighDetail, &Button))
g_Config.m_GfxHighDetail ^= 1;
2022-04-28 14:46:18 +00:00
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_GfxHighDetail, &Button, Localize("Allows maps to render with more detail"));
2020-04-07 20:37:46 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxHighdpi, Localize("Use high DPI"), g_Config.m_GfxHighdpi, &Button))
{
CheckSettings = true;
g_Config.m_GfxHighdpi ^= 1;
}
MainView.HSplitTop(20.0f, &Button, &MainView);
UI()->DoScrollbarOption(&g_Config.m_GfxRefreshRate, &g_Config.m_GfxRefreshRate, &Button, Localize("Refresh Rate"), 10, 1000, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_INFINITE | CUI::SCROLLBAR_OPTION_NOCLAMPVALUE, " Hz");
MainView.HSplitTop(2.0f, nullptr, &MainView);
static CButtonContainer s_UiColorResetId;
DoLine_ColorPicker(&s_UiColorResetId, 25.0f, 13.0f, 2.0f, &MainView, Localize("UI Color"), &g_Config.m_UiColor, color_cast<ColorRGBA>(ColorHSLA(0xE4A046AFU, true)), false, nullptr, true);
2022-03-20 17:03:25 +00:00
2022-03-20 17:04:00 +00:00
// Backend list
struct SMenuBackendInfo
{
int m_Major = 0;
int m_Minor = 0;
int m_Patch = 0;
const char *m_pBackendName = "";
bool m_Found = false;
};
std::array<std::array<SMenuBackendInfo, EGraphicsDriverAgeType::GRAPHICS_DRIVER_AGE_TYPE_COUNT>, EBackendType::BACKEND_TYPE_COUNT> aaSupportedBackends{};
uint32_t FoundBackendCount = 0;
for(uint32_t i = 0; i < BACKEND_TYPE_COUNT; ++i)
{
if(EBackendType(i) == BACKEND_TYPE_AUTO)
continue;
for(uint32_t n = 0; n < GRAPHICS_DRIVER_AGE_TYPE_COUNT; ++n)
{
auto &Info = aaSupportedBackends[i][n];
if(Graphics()->GetDriverVersion(EGraphicsDriverAgeType(n), Info.m_Major, Info.m_Minor, Info.m_Patch, Info.m_pBackendName, EBackendType(i)))
{
// don't count blocked opengl drivers
if(EBackendType(i) != BACKEND_TYPE_OPENGL || EGraphicsDriverAgeType(n) == GRAPHICS_DRIVER_AGE_TYPE_LEGACY || g_Config.m_GfxDriverIsBlocked == 0)
{
Info.m_Found = true;
++FoundBackendCount;
}
}
}
}
if(FoundBackendCount > 1)
{
CUIRect Text, BackendDropDown;
2022-03-20 17:04:00 +00:00
MainView.HSplitTop(10.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Text, &MainView);
MainView.HSplitTop(2.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &BackendDropDown, &MainView);
UI()->DoLabel(&Text, Localize("Renderer"), 16.0f, TEXTALIGN_MC);
2022-03-20 17:04:00 +00:00
static std::vector<std::string> s_vBackendIDNames;
static std::vector<const char *> s_vpBackendIDNamesCStr;
static std::vector<SMenuBackendInfo> s_vBackendInfos;
2022-03-20 17:04:00 +00:00
size_t BackendCount = FoundBackendCount + 1;
s_vBackendIDNames.resize(BackendCount);
s_vpBackendIDNamesCStr.resize(BackendCount);
s_vBackendInfos.resize(BackendCount);
2022-03-20 17:04:00 +00:00
char aTmpBackendName[256];
auto IsInfoDefault = [](const SMenuBackendInfo &CheckInfo) {
2022-12-13 18:29:48 +00:00
return str_comp_nocase(CheckInfo.m_pBackendName, g_Config.ms_pGfxBackend) == 0 && CheckInfo.m_Major == g_Config.ms_GfxGLMajor && CheckInfo.m_Minor == g_Config.ms_GfxGLMinor && CheckInfo.m_Patch == g_Config.ms_GfxGLPatch;
2022-03-20 17:04:00 +00:00
};
int OldSelectedBackend = -1;
uint32_t CurCounter = 0;
for(uint32_t i = 0; i < BACKEND_TYPE_COUNT; ++i)
{
for(uint32_t n = 0; n < GRAPHICS_DRIVER_AGE_TYPE_COUNT; ++n)
{
auto &Info = aaSupportedBackends[i][n];
if(Info.m_Found)
{
bool IsDefault = IsInfoDefault(Info);
str_format(aTmpBackendName, sizeof(aTmpBackendName), "%s (%d.%d.%d)%s%s", Info.m_pBackendName, Info.m_Major, Info.m_Minor, Info.m_Patch, IsDefault ? " - " : "", IsDefault ? Localize("default") : "");
s_vBackendIDNames[CurCounter] = aTmpBackendName;
s_vpBackendIDNamesCStr[CurCounter] = s_vBackendIDNames[CurCounter].c_str();
if(str_comp_nocase(Info.m_pBackendName, g_Config.m_GfxBackend) == 0 && g_Config.m_GfxGLMajor == Info.m_Major && g_Config.m_GfxGLMinor == Info.m_Minor && g_Config.m_GfxGLPatch == Info.m_Patch)
2022-03-20 17:04:00 +00:00
{
OldSelectedBackend = CurCounter;
2022-03-20 17:04:00 +00:00
}
s_vBackendInfos[CurCounter] = Info;
2022-03-20 17:04:00 +00:00
++CurCounter;
}
}
}
if(OldSelectedBackend != -1)
{
// no custom selected
BackendCount -= 1;
}
else
{
// custom selected one
str_format(aTmpBackendName, sizeof(aTmpBackendName), "%s (%s %d.%d.%d)", Localize("custom"), g_Config.m_GfxBackend, g_Config.m_GfxGLMajor, g_Config.m_GfxGLMinor, g_Config.m_GfxGLPatch);
s_vBackendIDNames[CurCounter] = aTmpBackendName;
s_vpBackendIDNamesCStr[CurCounter] = s_vBackendIDNames[CurCounter].c_str();
2022-03-20 17:04:00 +00:00
OldSelectedBackend = CurCounter;
s_vBackendInfos[CurCounter].m_pBackendName = "custom";
s_vBackendInfos[CurCounter].m_Major = g_Config.m_GfxGLMajor;
s_vBackendInfos[CurCounter].m_Minor = g_Config.m_GfxGLMinor;
s_vBackendInfos[CurCounter].m_Patch = g_Config.m_GfxGLPatch;
2022-03-20 17:04:00 +00:00
}
static int s_OldSelectedBackend = -1;
2022-03-20 17:04:00 +00:00
if(s_OldSelectedBackend == -1)
s_OldSelectedBackend = OldSelectedBackend;
static CUI::SDropDownState s_BackendDropDownState;
static CScrollRegion s_BackendDropDownScrollRegion;
s_BackendDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_BackendDropDownScrollRegion;
const int NewBackend = UI()->DoDropDown(&BackendDropDown, OldSelectedBackend, s_vpBackendIDNamesCStr.data(), BackendCount, s_BackendDropDownState);
2022-03-20 17:04:00 +00:00
if(OldSelectedBackend != NewBackend)
{
str_copy(g_Config.m_GfxBackend, s_vBackendInfos[NewBackend].m_pBackendName);
g_Config.m_GfxGLMajor = s_vBackendInfos[NewBackend].m_Major;
g_Config.m_GfxGLMinor = s_vBackendInfos[NewBackend].m_Minor;
g_Config.m_GfxGLPatch = s_vBackendInfos[NewBackend].m_Patch;
2022-03-20 17:04:00 +00:00
CheckSettings = true;
s_GfxBackendChanged = s_OldSelectedBackend != NewBackend;
}
}
2022-03-20 17:03:25 +00:00
// GPU list
const auto &GPUList = Graphics()->GetGPUs();
if(GPUList.m_vGPUs.size() > 1)
2022-03-20 17:03:25 +00:00
{
CUIRect Text, GpuDropDown;
2022-03-20 17:03:25 +00:00
MainView.HSplitTop(10.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Text, &MainView);
MainView.HSplitTop(2.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &GpuDropDown, &MainView);
UI()->DoLabel(&Text, Localize("Graphics card"), 16.0f, TEXTALIGN_MC);
2022-03-20 17:03:25 +00:00
static std::vector<const char *> s_vpGPUIDNames;
2022-03-20 17:03:25 +00:00
size_t GPUCount = GPUList.m_vGPUs.size() + 1;
s_vpGPUIDNames.resize(GPUCount);
2022-03-20 17:03:25 +00:00
char aCurDeviceName[256 + 4];
int OldSelectedGPU = -1;
for(size_t i = 0; i < GPUCount; ++i)
{
if(i == 0)
{
str_format(aCurDeviceName, sizeof(aCurDeviceName), "%s (%s)", Localize("auto"), GPUList.m_AutoGPU.m_aName);
s_vpGPUIDNames[i] = aCurDeviceName;
2022-03-20 17:03:25 +00:00
if(str_comp("auto", g_Config.m_GfxGPUName) == 0)
{
OldSelectedGPU = 0;
}
}
else
{
s_vpGPUIDNames[i] = GPUList.m_vGPUs[i - 1].m_aName;
if(str_comp(GPUList.m_vGPUs[i - 1].m_aName, g_Config.m_GfxGPUName) == 0)
2022-03-20 17:03:25 +00:00
{
OldSelectedGPU = i;
}
}
}
static int s_OldSelectedGPU = -1;
if(s_OldSelectedGPU == -1)
s_OldSelectedGPU = OldSelectedGPU;
static CUI::SDropDownState s_GpuDropDownState;
static CScrollRegion s_GpuDropDownScrollRegion;
s_GpuDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_GpuDropDownScrollRegion;
const int NewGPU = UI()->DoDropDown(&GpuDropDown, OldSelectedGPU, s_vpGPUIDNames.data(), GPUCount, s_GpuDropDownState);
2022-03-20 17:03:25 +00:00
if(OldSelectedGPU != NewGPU)
{
if(NewGPU == 0)
2022-07-09 16:14:56 +00:00
str_copy(g_Config.m_GfxGPUName, "auto");
2022-03-20 17:03:25 +00:00
else
2022-07-09 16:14:56 +00:00
str_copy(g_Config.m_GfxGPUName, GPUList.m_vGPUs[NewGPU - 1].m_aName);
CheckSettings = true;
s_GfxGPUChanged = NewGPU != s_OldSelectedGPU;
2022-03-20 17:03:25 +00:00
}
}
2022-03-20 17:04:00 +00:00
// check if the new settings require a restart
if(CheckSettings)
{
m_NeedRestartGraphics = !(s_GfxFsaaSamples == g_Config.m_GfxFsaaSamples &&
!s_GfxBackendChanged &&
!s_GfxGPUChanged &&
2022-03-20 17:04:00 +00:00
s_GfxHighdpi == g_Config.m_GfxHighdpi);
}
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderSettingsSound(CUIRect MainView)
{
static int s_SndEnable = g_Config.m_SndEnable;
2010-05-29 07:25:38 +00:00
CUIRect Button;
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndEnable, Localize("Use sounds"), g_Config.m_SndEnable, &Button))
{
2010-05-29 07:25:38 +00:00
g_Config.m_SndEnable ^= 1;
UpdateMusicState();
m_NeedRestartSound = g_Config.m_SndEnable && !s_SndEnable;
}
2010-05-29 07:25:38 +00:00
if(!g_Config.m_SndEnable)
return;
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndMusic, Localize("Play background music"), g_Config.m_SndMusic, &Button))
{
g_Config.m_SndMusic ^= 1;
UpdateMusicState();
}
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndNonactiveMute, Localize("Mute when not active"), g_Config.m_SndNonactiveMute, &Button))
g_Config.m_SndNonactiveMute ^= 1;
MainView.HSplitTop(20.0f, &Button, &MainView);
2014-03-22 09:24:10 +00:00
if(DoButton_CheckBox(&g_Config.m_SndGame, Localize("Enable game sounds"), g_Config.m_SndGame, &Button))
g_Config.m_SndGame ^= 1;
2014-10-25 23:00:30 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndGun, Localize("Enable gun sound"), g_Config.m_SndGun, &Button))
g_Config.m_SndGun ^= 1;
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndLongPain, Localize("Enable long pain sound (used when shooting in freeze)"), g_Config.m_SndLongPain, &Button))
g_Config.m_SndLongPain ^= 1;
2014-03-22 09:24:10 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndServerMessage, Localize("Enable server message sound"), g_Config.m_SndServerMessage, &Button))
g_Config.m_SndServerMessage ^= 1;
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndChat, Localize("Enable regular chat sound"), g_Config.m_SndChat, &Button))
g_Config.m_SndChat ^= 1;
2014-05-04 16:35:37 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_SndTeamChat, Localize("Enable team chat sound"), g_Config.m_SndTeamChat, &Button))
g_Config.m_SndTeamChat ^= 1;
2013-10-21 00:16:45 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
2014-03-22 09:24:10 +00:00
if(DoButton_CheckBox(&g_Config.m_SndHighlight, Localize("Enable highlighted chat sound"), g_Config.m_SndHighlight, &Button))
g_Config.m_SndHighlight ^= 1;
2013-10-21 00:16:45 +00:00
// volume slider
{
MainView.HSplitTop(5.0f, nullptr, &MainView);
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
UI()->DoScrollbarOption(&g_Config.m_SndVolume, &g_Config.m_SndVolume, &Button, Localize("Sound volume"), 0, 100, &CUI::ms_LogarithmicScrollbarScale, 0u, "%");
}
2014-10-12 15:02:47 +00:00
// volume slider game sounds
{
MainView.HSplitTop(5.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Button, &MainView);
UI()->DoScrollbarOption(&g_Config.m_SndGameSoundVolume, &g_Config.m_SndGameSoundVolume, &Button, Localize("Game sound volume"), 0, 100, &CUI::ms_LogarithmicScrollbarScale, 0u, "%");
}
// volume slider gui sounds
{
MainView.HSplitTop(5.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Button, &MainView);
UI()->DoScrollbarOption(&g_Config.m_SndChatSoundVolume, &g_Config.m_SndChatSoundVolume, &Button, Localize("Chat sound volume"), 0, 100, &CUI::ms_LogarithmicScrollbarScale, 0u, "%");
}
2014-10-12 15:02:47 +00:00
// volume slider map sounds
{
MainView.HSplitTop(5.0f, nullptr, &MainView);
2014-10-12 15:02:47 +00:00
MainView.HSplitTop(20.0f, &Button, &MainView);
UI()->DoScrollbarOption(&g_Config.m_SndMapSoundVolume, &g_Config.m_SndMapSoundVolume, &Button, Localize("Map sound volume"), 0, 100, &CUI::ms_LogarithmicScrollbarScale, 0u, "%");
2014-10-12 15:02:47 +00:00
}
// volume slider background music
{
MainView.HSplitTop(5.0f, nullptr, &MainView);
MainView.HSplitTop(20.0f, &Button, &MainView);
UI()->DoScrollbarOption(&g_Config.m_SndBackgroundMusicVolume, &g_Config.m_SndBackgroundMusicVolume, &Button, Localize("Background music volume"), 0, 100, &CUI::ms_LogarithmicScrollbarScale, 0u, "%");
}
}
bool CMenus::RenderLanguageSelection(CUIRect MainView)
2009-06-15 08:15:53 +00:00
{
static int s_SelectedLanguage = -2; // -2 = unloaded, -1 = unset
static CListBox s_ListBox;
2010-05-29 07:25:38 +00:00
if(s_SelectedLanguage == -2)
{
s_SelectedLanguage = -1;
for(size_t i = 0; i < g_Localization.Languages().size(); i++)
{
if(str_comp(g_Localization.Languages()[i].m_FileName.c_str(), g_Config.m_ClLanguagefile) == 0)
2009-06-15 08:15:53 +00:00
{
2010-05-29 07:25:38 +00:00
s_SelectedLanguage = i;
s_ListBox.ScrollToSelected();
2009-06-15 08:15:53 +00:00
break;
}
}
2009-06-15 08:15:53 +00:00
}
2010-05-29 07:25:38 +00:00
const int OldSelected = s_SelectedLanguage;
2010-05-29 07:25:38 +00:00
s_ListBox.DoStart(24.0f, g_Localization.Languages().size(), 1, 3, s_SelectedLanguage, &MainView);
for(const auto &Language : g_Localization.Languages())
{
const CListboxItem Item = s_ListBox.DoNextItem(&Language.m_Name, s_SelectedLanguage != -1 && !str_comp(g_Localization.Languages()[s_SelectedLanguage].m_Name.c_str(), Language.m_Name.c_str()));
if(!Item.m_Visible)
continue;
CUIRect FlagRect, Label;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &FlagRect, &Label);
FlagRect.VMargin(6.0f, &FlagRect);
FlagRect.HMargin(3.0f, &FlagRect);
m_pClient->m_CountryFlags.Render(Language.m_CountryCode, ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f), FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
UI()->DoLabel(&Label, Language.m_Name.c_str(), 16.0f, TEXTALIGN_ML);
}
s_SelectedLanguage = s_ListBox.DoEnd();
if(OldSelected != s_SelectedLanguage)
{
str_copy(g_Config.m_ClLanguagefile, g_Localization.Languages()[s_SelectedLanguage].m_FileName.c_str());
g_Localization.Load(g_Localization.Languages()[s_SelectedLanguage].m_FileName.c_str(), Storage(), Console());
2020-10-12 10:29:47 +00:00
GameClient()->OnLanguageChange();
}
return s_ListBox.WasItemActivated();
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderSettings(CUIRect MainView)
{
// render background
2010-10-16 09:27:49 +00:00
CUIRect Temp, TabBar, RestartWarning;
2010-05-29 07:25:38 +00:00
MainView.VSplitRight(120.0f, &MainView, &TabBar);
MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f);
MainView.Margin(10.0f, &MainView);
MainView.HSplitBottom(15.0f, &MainView, &RestartWarning);
2010-05-29 07:25:38 +00:00
TabBar.HSplitTop(50.0f, &Temp, &TabBar);
Temp.Draw(ms_ColorTabbarActive, IGraphics::CORNER_BR, 10.0f);
2010-05-29 07:25:38 +00:00
MainView.HSplitTop(10.0f, 0, &MainView);
CUIRect Button;
const char *apTabs[] = {
2011-04-01 17:36:44 +00:00
Localize("Language"),
2010-05-29 07:25:38 +00:00
Localize("General"),
Localize("Player"),
2011-04-01 17:36:44 +00:00
("Tee"),
2022-06-26 22:38:49 +00:00
Localize("Appearance"),
2010-05-29 07:25:38 +00:00
Localize("Controls"),
Localize("Graphics"),
Localize("Sound"),
2020-09-26 07:37:35 +00:00
Localize("DDNet"),
Localize("Assets")};
2022-07-16 13:32:06 +00:00
static CButtonContainer s_aTabButtons[sizeof(apTabs)];
2010-05-29 07:25:38 +00:00
int NumTabs = (int)std::size(apTabs);
2010-05-29 07:25:38 +00:00
for(int i = 0; i < NumTabs; i++)
{
2010-05-29 07:25:38 +00:00
TabBar.HSplitTop(10, &Button, &TabBar);
TabBar.HSplitTop(26, &Button, &TabBar);
if(DoButton_MenuTab(&s_aTabButtons[i], apTabs[i], g_Config.m_UiSettingsPage == i, &Button, IGraphics::CORNER_R, &m_aAnimatorsSettingsTab[i]))
g_Config.m_UiSettingsPage = i;
}
2010-05-29 07:25:38 +00:00
MainView.Margin(10.0f, &MainView);
if(g_Config.m_UiSettingsPage == SETTINGS_LANGUAGE)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_LANGUAGE);
2011-04-01 17:36:44 +00:00
RenderLanguageSelection(MainView);
2020-09-18 16:45:42 +00:00
}
else if(g_Config.m_UiSettingsPage == SETTINGS_GENERAL)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_GENERAL);
2011-04-01 17:36:44 +00:00
RenderSettingsGeneral(MainView);
2020-09-18 16:45:42 +00:00
}
else if(g_Config.m_UiSettingsPage == SETTINGS_PLAYER)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_PLAYER);
2011-04-01 17:36:44 +00:00
RenderSettingsPlayer(MainView);
2020-09-18 16:45:42 +00:00
}
else if(g_Config.m_UiSettingsPage == SETTINGS_TEE)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_TEE);
2011-04-01 17:36:44 +00:00
RenderSettingsTee(MainView);
2020-09-18 16:45:42 +00:00
}
2022-06-26 22:38:49 +00:00
else if(g_Config.m_UiSettingsPage == SETTINGS_APPEARANCE)
2020-09-18 16:45:42 +00:00
{
2022-06-26 22:38:49 +00:00
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_APPEARANCE);
RenderSettingsAppearance(MainView);
2020-09-18 16:45:42 +00:00
}
else if(g_Config.m_UiSettingsPage == SETTINGS_CONTROLS)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_CONTROLS);
RenderSettingsControls(MainView);
2020-09-18 16:45:42 +00:00
}
else if(g_Config.m_UiSettingsPage == SETTINGS_GRAPHICS)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_GRAPHICS);
RenderSettingsGraphics(MainView);
2020-09-18 16:45:42 +00:00
}
else if(g_Config.m_UiSettingsPage == SETTINGS_SOUND)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_SOUND);
RenderSettingsSound(MainView);
2020-09-18 16:45:42 +00:00
}
else if(g_Config.m_UiSettingsPage == SETTINGS_DDNET)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_DDNET);
RenderSettingsDDNet(MainView);
2020-09-18 16:45:42 +00:00
}
2020-09-26 07:37:35 +00:00
else if(g_Config.m_UiSettingsPage == SETTINGS_ASSETS)
{
m_pBackground->ChangePosition(CMenuBackground::POS_SETTINGS_ASSETS);
RenderSettingsCustom(MainView);
}
2010-05-29 07:25:38 +00:00
if(m_NeedRestartUpdate)
{
TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f);
UI()->DoLabel(&RestartWarning, Localize("DDNet Client needs to be restarted to complete update!"), 14.0f, TEXTALIGN_ML);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
}
else if(m_NeedRestartGeneral || m_NeedRestartSkins || m_NeedRestartGraphics || m_NeedRestartSound || m_NeedRestartDDNet)
UI()->DoLabel(&RestartWarning, Localize("You must restart the game for all settings to take effect."), 14.0f, TEXTALIGN_ML);
2020-10-28 02:59:50 +00:00
}
2021-05-03 03:59:50 +00:00
ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, bool ClampedLight)
2020-06-27 13:08:35 +00:00
{
ColorHSLA Color(*pColor, Alpha);
2021-05-03 03:59:50 +00:00
CUIRect Preview, Button, Label;
char aBuf[32];
float *apComponent[] = {&Color.h, &Color.s, &Color.l, &Color.a};
const char *apLabels[] = {Localize("Hue"), Localize("Sat."), Localize("Lht."), Localize("Alpha")};
2020-06-27 13:08:35 +00:00
2021-05-03 03:59:50 +00:00
float SizePerEntry = 20.0f;
float MarginPerEntry = 5.0f;
float OffY = (SizePerEntry + MarginPerEntry) * (3 + (Alpha ? 1 : 0)) - 40.0f;
pRect->VSplitLeft(40.0f, &Preview, pRect);
Preview.HSplitTop(OffY / 2.0f, NULL, &Preview);
Preview.HSplitTop(40.0f, &Preview, NULL);
Graphics()->TextureClear();
{
const float SizeBorder = 5.0f;
Graphics()->SetColor(ColorRGBA(0.15f, 0.15f, 0.15f, 1));
int TmpCont = Graphics()->CreateRectQuadContainer(Preview.x - SizeBorder / 2.0f, Preview.y - SizeBorder / 2.0f, Preview.w + SizeBorder, Preview.h + SizeBorder, 4.0f + SizeBorder / 2.0f, IGraphics::CORNER_ALL);
2021-05-03 03:59:50 +00:00
Graphics()->RenderQuadContainer(TmpCont, -1);
Graphics()->DeleteQuadContainer(TmpCont);
}
ColorHSLA RenderColorHSLA(Color.r, Color.g, Color.b, Color.a);
2021-05-03 03:59:50 +00:00
if(ClampedLight)
RenderColorHSLA = RenderColorHSLA.UnclampLighting();
Graphics()->SetColor(color_cast<ColorRGBA>(RenderColorHSLA));
int TmpCont = Graphics()->CreateRectQuadContainer(Preview.x, Preview.y, Preview.w, Preview.h, 4.0f, IGraphics::CORNER_ALL);
2021-05-03 03:59:50 +00:00
Graphics()->RenderQuadContainer(TmpCont, -1);
Graphics()->DeleteQuadContainer(TmpCont);
auto &&RenderHSLColorsRect = [&](CUIRect *pColorRect) {
Graphics()->TextureClear();
Graphics()->TrianglesBegin();
float CurXOff = pColorRect->x;
float SizeColor = pColorRect->w / 6;
// red to yellow
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, 1, 0, 0, 1),
IGraphics::CColorVertex(1, 1, 1, 0, 1),
IGraphics::CColorVertex(2, 1, 0, 0, 1),
IGraphics::CColorVertex(3, 1, 1, 0, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
// yellow to green
CurXOff += SizeColor;
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, 1, 1, 0, 1),
IGraphics::CColorVertex(1, 0, 1, 0, 1),
IGraphics::CColorVertex(2, 1, 1, 0, 1),
IGraphics::CColorVertex(3, 0, 1, 0, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
CurXOff += SizeColor;
// green to turquoise
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, 0, 1, 0, 1),
IGraphics::CColorVertex(1, 0, 1, 1, 1),
IGraphics::CColorVertex(2, 0, 1, 0, 1),
IGraphics::CColorVertex(3, 0, 1, 1, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
CurXOff += SizeColor;
// turquoise to blue
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, 0, 1, 1, 1),
IGraphics::CColorVertex(1, 0, 0, 1, 1),
IGraphics::CColorVertex(2, 0, 1, 1, 1),
IGraphics::CColorVertex(3, 0, 0, 1, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
CurXOff += SizeColor;
// blue to purple
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, 0, 0, 1, 1),
IGraphics::CColorVertex(1, 1, 0, 1, 1),
IGraphics::CColorVertex(2, 0, 0, 1, 1),
IGraphics::CColorVertex(3, 1, 0, 1, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
CurXOff += SizeColor;
// purple to red
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, 1, 0, 1, 1),
IGraphics::CColorVertex(1, 1, 0, 0, 1),
IGraphics::CColorVertex(2, 1, 0, 1, 1),
IGraphics::CColorVertex(3, 1, 0, 0, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
Graphics()->TrianglesEnd();
};
auto &&RenderHSLSatRect = [&](CUIRect *pColorRect, ColorRGBA &CurColor) {
Graphics()->TextureClear();
Graphics()->TrianglesBegin();
float CurXOff = pColorRect->x;
float SizeColor = pColorRect->w;
ColorHSLA RightColor = color_cast<ColorHSLA>(CurColor);
ColorHSLA LeftColor = color_cast<ColorHSLA>(CurColor);
LeftColor.g = 0;
RightColor.g = 1;
ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(RightColor);
ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(LeftColor);
// saturation
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1),
IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1),
IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1),
IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
Graphics()->TrianglesEnd();
};
auto &&RenderHSLLightRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorSat) {
Graphics()->TextureClear();
Graphics()->TrianglesBegin();
float CurXOff = pColorRect->x;
float SizeColor = pColorRect->w / (ClampedLight ? 1.0f : 2.0f);
ColorHSLA RightColor = color_cast<ColorHSLA>(CurColorSat);
ColorHSLA LeftColor = color_cast<ColorHSLA>(CurColorSat);
LeftColor.b = ColorHSLA::DARKEST_LGT;
RightColor.b = 1;
ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(RightColor);
ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(LeftColor);
if(!ClampedLight)
CurXOff += SizeColor;
// light
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1),
IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1),
IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1),
IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
if(!ClampedLight)
{
CurXOff -= SizeColor;
LeftColor.b = 0;
RightColor.b = ColorHSLA::DARKEST_LGT;
RightColorRGBA = color_cast<ColorRGBA>(RightColor);
LeftColorRGBA = color_cast<ColorRGBA>(LeftColor);
2021-05-03 03:59:50 +00:00
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1),
IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1),
IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1),
IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
Graphics()->TrianglesEnd();
};
auto &&RenderHSLAlphaRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorFull) {
Graphics()->TextureClear();
Graphics()->TrianglesBegin();
float CurXOff = pColorRect->x;
float SizeColor = pColorRect->w;
ColorHSLA RightColor = color_cast<ColorHSLA>(CurColorFull);
ColorHSLA LeftColor = color_cast<ColorHSLA>(CurColorFull);
LeftColor.a = 0;
RightColor.a = 1;
ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(RightColor);
ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(LeftColor);
// alpha
{
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a),
IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a),
IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a),
IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a)};
Graphics()->SetColorVertex(Array, 4);
IGraphics::CFreeformItem Freeform(
CurXOff, pColorRect->y,
CurXOff + SizeColor, pColorRect->y,
CurXOff, pColorRect->y + pColorRect->h,
CurXOff + SizeColor, pColorRect->y + pColorRect->h);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
Graphics()->TrianglesEnd();
};
2020-06-27 13:08:35 +00:00
for(int i = 0; i < 3 + Alpha; i++)
{
2021-05-03 03:59:50 +00:00
pRect->HSplitTop(SizePerEntry, &Button, pRect);
pRect->HSplitTop(MarginPerEntry, NULL, pRect);
2020-06-27 13:08:35 +00:00
Button.VSplitLeft(10.0f, 0, &Button);
Button.VSplitLeft(100.0f, &Label, &Button);
2021-05-03 03:59:50 +00:00
Button.Draw(ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), IGraphics::CORNER_ALL, 1.0f);
2021-05-03 03:59:50 +00:00
2021-11-26 20:55:31 +00:00
CUIRect Rail;
Button.Margin(2.0f, &Rail);
str_format(aBuf, sizeof(aBuf), "%s: %03d", apLabels[i], (int)(*apComponent[i] * 255));
UI()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML);
2021-05-03 03:59:50 +00:00
ColorHSLA CurColorPureHSLA(RenderColorHSLA.r, 1, 0.5f, 1);
2021-05-03 03:59:50 +00:00
ColorRGBA CurColorPure = color_cast<ColorRGBA>(CurColorPureHSLA);
ColorRGBA ColorInner(1, 1, 1, 0.25f);
2021-05-03 03:59:50 +00:00
if(i == 0)
{
ColorInner = CurColorPure;
2021-11-26 20:55:31 +00:00
RenderHSLColorsRect(&Rail);
2021-05-03 03:59:50 +00:00
}
else if(i == 1)
{
2021-11-26 20:55:31 +00:00
RenderHSLSatRect(&Rail, CurColorPure);
ColorInner = color_cast<ColorRGBA>(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], CurColorPureHSLA.b, 1));
2021-05-03 03:59:50 +00:00
}
else if(i == 2)
{
ColorRGBA CurColorSat = color_cast<ColorRGBA>(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], 0.5f, 1));
2021-11-26 20:55:31 +00:00
RenderHSLLightRect(&Rail, CurColorSat);
float LightVal = *apComponent[2];
2021-05-03 03:59:50 +00:00
if(ClampedLight)
LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT);
ColorInner = color_cast<ColorRGBA>(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], LightVal, 1));
2021-05-03 03:59:50 +00:00
}
else if(i == 3)
{
ColorRGBA CurColorFull = color_cast<ColorRGBA>(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], *apComponent[2], 1));
2021-11-26 20:55:31 +00:00
RenderHSLAlphaRect(&Rail, CurColorFull);
float LightVal = *apComponent[2];
2021-05-03 03:59:50 +00:00
if(ClampedLight)
LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT);
ColorInner = color_cast<ColorRGBA>(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], LightVal, *apComponent[3]));
2021-05-03 03:59:50 +00:00
}
*apComponent[i] = UI()->DoScrollbarH(&((char *)pColor)[i], &Button, *apComponent[i], &ColorInner);
2020-06-27 13:08:35 +00:00
}
2020-06-27 17:40:04 +00:00
*pColor = Color.Pack(Alpha);
2020-06-27 13:08:35 +00:00
return Color;
}
2022-06-26 22:38:49 +00:00
enum
{
APPEARANCE_TAB_HUD = 0,
APPEARANCE_TAB_CHAT = 1,
APPEARANCE_TAB_NAME_PLATE = 2,
APPEARANCE_TAB_HOOK_COLLISION = 3,
APPEARANCE_TAB_KILL_MESSAGES = 4,
APPEARANCE_TAB_LASER = 5,
NUMBER_OF_APPEARANCE_TABS = 6,
};
void CMenus::RenderSettingsAppearance(CUIRect MainView)
{
char aBuf[128];
2021-10-02 21:15:59 +00:00
static int s_CurTab = 0;
CUIRect TabBar, Page1Tab, Page2Tab, Page3Tab, Page4Tab, Page5Tab, Page6Tab, LeftView, RightView, Section, Button, Label;
2022-06-26 22:38:49 +00:00
MainView.HSplitTop(20, &TabBar, &MainView);
float TabsW = TabBar.w;
TabBar.VSplitLeft(TabsW / NUMBER_OF_APPEARANCE_TABS, &Page1Tab, &Page2Tab);
Page2Tab.VSplitLeft(TabsW / NUMBER_OF_APPEARANCE_TABS, &Page2Tab, &Page3Tab);
Page3Tab.VSplitLeft(TabsW / NUMBER_OF_APPEARANCE_TABS, &Page3Tab, &Page4Tab);
Page4Tab.VSplitLeft(TabsW / NUMBER_OF_APPEARANCE_TABS, &Page4Tab, &Page5Tab);
Page5Tab.VSplitLeft(TabsW / NUMBER_OF_APPEARANCE_TABS, &Page5Tab, &Page6Tab);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_aPageTabs[NUMBER_OF_APPEARANCE_TABS] = {};
2022-06-26 22:38:49 +00:00
if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_HUD], Localize("HUD"), s_CurTab == APPEARANCE_TAB_HUD, &Page1Tab, IGraphics::CORNER_L, NULL, NULL, NULL, NULL, 4))
2022-06-26 22:38:49 +00:00
s_CurTab = APPEARANCE_TAB_HUD;
2022-07-16 13:32:06 +00:00
if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_CHAT], Localize("Chat"), s_CurTab == APPEARANCE_TAB_CHAT, &Page2Tab, 0, NULL, NULL, NULL, NULL, 4))
2022-06-26 22:38:49 +00:00
s_CurTab = APPEARANCE_TAB_CHAT;
2022-07-16 13:32:06 +00:00
if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_NAME_PLATE], Localize("Name Plate"), s_CurTab == APPEARANCE_TAB_NAME_PLATE, &Page3Tab, 0, NULL, NULL, NULL, NULL, 4))
2022-06-26 22:38:49 +00:00
s_CurTab = APPEARANCE_TAB_NAME_PLATE;
2022-07-16 13:32:06 +00:00
if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_HOOK_COLLISION], Localize("Hook Collisions"), s_CurTab == APPEARANCE_TAB_HOOK_COLLISION, &Page4Tab, 0, NULL, NULL, NULL, NULL, 4))
2022-06-26 22:38:49 +00:00
s_CurTab = APPEARANCE_TAB_HOOK_COLLISION;
2022-07-16 13:32:06 +00:00
if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_KILL_MESSAGES], Localize("Kill Messages"), s_CurTab == APPEARANCE_TAB_KILL_MESSAGES, &Page5Tab, 0, NULL, NULL, NULL, NULL, 4))
2022-06-26 22:38:49 +00:00
s_CurTab = APPEARANCE_TAB_KILL_MESSAGES;
if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_LASER], Localize("Laser"), s_CurTab == APPEARANCE_TAB_LASER, &Page6Tab, IGraphics::CORNER_R, NULL, NULL, NULL, NULL, 4))
2022-06-26 22:38:49 +00:00
s_CurTab = APPEARANCE_TAB_LASER;
2021-10-02 21:15:59 +00:00
MainView.HSplitTop(10.0f, 0x0, &MainView); // Margin
2017-02-27 20:38:24 +00:00
const float LineSize = 20.0f;
const float ColorPickerLineSize = 25.0f;
const float SectionMargin = 5.0f;
const float SectionTotalMargin = 10.0f; // SectionMargin * 2;
const float HeadlineFontSize = 20.0f;
const float HeadlineAndVMargin = 30.0f; // HeadlineFontSize + SectionTotalMargin;
const float MarginToNextSection = 5.0f;
const float ColorPickerLabelSize = 13.0f;
const float ColorPickerLineSpacing = 5.0f;
2017-02-27 20:38:24 +00:00
2022-06-26 22:38:49 +00:00
if(s_CurTab == APPEARANCE_TAB_HUD)
{
MainView.VSplitMid(&LeftView, &RightView);
2021-10-02 21:15:59 +00:00
// ***** HUD ***** //
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("HUD"), HeadlineFontSize, TEXTALIGN_ML);
// Switch of the entire HUD
LeftView.HSplitTop(SectionTotalMargin + LineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
2021-05-09 11:21:07 +00:00
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhud, Localize("Show ingame HUD"), &g_Config.m_ClShowhud, &Section, LineSize);
2021-05-09 11:21:07 +00:00
// Switches of the various normal HUD elements
LeftView.HSplitTop(SectionTotalMargin + 5 * LineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
2021-05-09 11:21:07 +00:00
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudHealthAmmo, Localize("Show health, shields and ammo"), &g_Config.m_ClShowhudHealthAmmo, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowChat, Localize("Show chat"), &g_Config.m_ClShowChat, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClNameplates, Localize("Show name plates"), &g_Config.m_ClNameplates, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowKillMessages, Localize("Show kill messages"), &g_Config.m_ClShowKillMessages, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudScore, Localize("Show score"), &g_Config.m_ClShowhudScore, &Section, LineSize);
2021-05-09 11:21:07 +00:00
// Settings of the HUD element for votes
LeftView.HSplitTop(SectionTotalMargin + LineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
2020-10-28 02:59:50 +00:00
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowVotesAfterVoting, Localize("Show votes window after voting"), &g_Config.m_ClShowVotesAfterVoting, &Section, LineSize);
2020-10-28 02:59:50 +00:00
2022-06-26 22:38:49 +00:00
// ***** DDRace HUD ***** //
RightView.HSplitTop(HeadlineAndVMargin, &Label, &RightView);
UI()->DoLabel(&Label, Localize("DDRace HUD"), HeadlineFontSize, TEXTALIGN_ML);
2022-06-15 19:37:54 +00:00
// Switches of various DDRace HUD elements
RightView.HSplitTop(SectionTotalMargin + 4 * LineSize, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClDDRaceScoreBoard, Localize("Use DDRace Scoreboard"), &g_Config.m_ClDDRaceScoreBoard, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowIDs, Localize("Show client IDs in scoreboard"), &g_Config.m_ClShowIDs, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudDDRace, Localize("Show DDRace HUD"), &g_Config.m_ClShowhudDDRace, &Section, LineSize);
2022-06-26 22:38:49 +00:00
if(g_Config.m_ClShowhudDDRace)
{
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudJumpsIndicator, Localize("Show jumps indicator"), &g_Config.m_ClShowhudJumpsIndicator, &Section, LineSize);
}
else
2022-03-31 01:15:27 +00:00
{
Section.HSplitTop(LineSize, 0x0, &Section); // Create empty space for hidden option
2022-03-31 01:15:27 +00:00
}
2017-02-27 20:39:06 +00:00
// Switch for dummy actions display
RightView.HSplitTop(SectionTotalMargin + LineSize, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudDummyActions, Localize("Show dummy actions"), &g_Config.m_ClShowhudDummyActions, &Section, LineSize);
2022-06-15 19:37:54 +00:00
// Player movement information display settings
RightView.HSplitTop(SectionTotalMargin + 3 * LineSize, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
2022-06-15 19:37:54 +00:00
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudPlayerPosition, Localize("Show player position"), &g_Config.m_ClShowhudPlayerPosition, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudPlayerSpeed, Localize("Show player speed"), &g_Config.m_ClShowhudPlayerSpeed, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudPlayerAngle, Localize("Show player target angle"), &g_Config.m_ClShowhudPlayerAngle, &Section, LineSize);
2022-06-15 19:37:54 +00:00
// Freeze bar settings
RightView.HSplitTop(SectionTotalMargin + 3 * LineSize, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
2022-06-15 19:37:54 +00:00
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowFreezeBars, Localize("Show freeze bars"), &g_Config.m_ClShowFreezeBars, &Section, LineSize);
2022-06-26 22:38:49 +00:00
{
Section.HSplitTop(2 * LineSize, &Button, &Section);
2022-06-26 22:38:49 +00:00
if(g_Config.m_ClShowFreezeBars)
{
UI()->DoScrollbarOption(&g_Config.m_ClFreezeBarsAlphaInsideFreeze, &g_Config.m_ClFreezeBarsAlphaInsideFreeze, &Button, Localize("Opacity of freeze bars inside freeze"), 0, 100, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_MULTILINE, "%");
}
2022-06-26 22:38:49 +00:00
}
2021-10-02 21:15:59 +00:00
}
2022-06-26 22:38:49 +00:00
else if(s_CurTab == APPEARANCE_TAB_CHAT)
{
MainView.VSplitMid(&LeftView, &RightView);
2019-04-25 16:49:27 +00:00
// ***** Chat ***** //
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("Chat"), HeadlineFontSize, TEXTALIGN_ML);
2019-04-25 16:49:27 +00:00
// General chat settings
LeftView.HSplitTop(SectionTotalMargin + 3 * LineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClChatTeamColors, Localize("Show names in chat in team colors"), &g_Config.m_ClChatTeamColors, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowChatFriends, Localize("Show only chat messages from friends"), &g_Config.m_ClShowChatFriends, &Section, LineSize);
if(DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClChatOld, Localize("Use old chat style"), &g_Config.m_ClChatOld, &Section, LineSize))
2021-10-02 21:15:59 +00:00
GameClient()->m_Chat.RebuildChat();
// ***** Messages ***** //
LeftView.HSplitTop(MarginToNextSection, 0x0, &LeftView);
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("Messages"), HeadlineFontSize, TEXTALIGN_ML);
// Message Colors and extra settings
LeftView.HSplitTop(SectionTotalMargin + 6 * ColorPickerLineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
2021-10-02 21:15:59 +00:00
int i = 0;
2022-07-16 13:32:06 +00:00
static CButtonContainer s_aResetIDs[24];
DoLine_ColorPicker(&s_aResetIDs[i++], ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("System message"), &g_Config.m_ClMessageSystemColor, ColorRGBA(1.0f, 1.0f, 0.5f), true, &g_Config.m_ClShowChatSystem);
DoLine_ColorPicker(&s_aResetIDs[i++], ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Highlighted message"), &g_Config.m_ClMessageHighlightColor, ColorRGBA(1.0f, 0.5f, 0.5f));
DoLine_ColorPicker(&s_aResetIDs[i++], ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Team message"), &g_Config.m_ClMessageTeamColor, ColorRGBA(0.65f, 1.0f, 0.65f));
DoLine_ColorPicker(&s_aResetIDs[i++], ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Friend message"), &g_Config.m_ClMessageFriendColor, ColorRGBA(1.0f, 0.137f, 0.137f), true, &g_Config.m_ClMessageFriend);
DoLine_ColorPicker(&s_aResetIDs[i++], ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Normal message"), &g_Config.m_ClMessageColor, ColorRGBA(1.0f, 1.0f, 1.0f));
2020-12-14 00:51:31 +00:00
2021-10-02 21:15:59 +00:00
str_format(aBuf, sizeof(aBuf), "%s (echo)", Localize("Client message"));
DoLine_ColorPicker(&s_aResetIDs[i++], ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, aBuf, &g_Config.m_ClMessageClientColor, ColorRGBA(0.5f, 0.78f, 1.0f));
2020-12-14 00:51:31 +00:00
2021-10-02 21:15:59 +00:00
// ***** Chat Preview ***** //
RightView.HSplitTop(HeadlineAndVMargin, &Label, &RightView);
UI()->DoLabel(&Label, Localize("Preview"), HeadlineFontSize, TEXTALIGN_ML);
2019-04-25 16:49:27 +00:00
// Use the rest of the view for preview
Section = RightView;
Section.Margin(SectionMargin, &Section);
2021-10-02 21:15:59 +00:00
Section.Draw(ColorRGBA(1, 1, 1, 0.1f), IGraphics::CORNER_ALL, 8.0f);
2021-10-02 21:15:59 +00:00
Section.HSplitTop(10.0f, 0x0, &Section); // Margin
2021-10-02 21:15:59 +00:00
ColorRGBA SystemColor = color_cast<ColorRGBA, ColorHSLA>(ColorHSLA(g_Config.m_ClMessageSystemColor));
ColorRGBA HighlightedColor = color_cast<ColorRGBA, ColorHSLA>(ColorHSLA(g_Config.m_ClMessageHighlightColor));
ColorRGBA TeamColor = color_cast<ColorRGBA, ColorHSLA>(ColorHSLA(g_Config.m_ClMessageTeamColor));
ColorRGBA FriendColor = color_cast<ColorRGBA, ColorHSLA>(ColorHSLA(g_Config.m_ClMessageFriendColor));
ColorRGBA NormalColor = color_cast<ColorRGBA, ColorHSLA>(ColorHSLA(g_Config.m_ClMessageColor));
ColorRGBA ClientColor = color_cast<ColorRGBA, ColorHSLA>(ColorHSLA(g_Config.m_ClMessageClientColor));
ColorRGBA DefaultNameColor(0.8f, 0.8f, 0.8f, 1.0f);
constexpr float RealFontSize = CChat::FONT_SIZE * 2;
const float RealMsgPaddingX = (!g_Config.m_ClChatOld ? CChat::MESSAGE_PADDING_X : 0) * 2;
const float RealMsgPaddingY = (!g_Config.m_ClChatOld ? CChat::MESSAGE_PADDING_Y : 0) * 2;
const float RealMsgPaddingTee = (!g_Config.m_ClChatOld ? CChat::MESSAGE_TEE_SIZE + CChat::MESSAGE_TEE_PADDING_RIGHT : 0) * 2;
const float RealOffsetY = RealFontSize + RealMsgPaddingY;
const float X = 5.0f + RealMsgPaddingX / 2.0f + Section.x;
float Y = Section.y;
2021-10-02 21:15:59 +00:00
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, X, Y, RealFontSize, TEXTFLAG_RENDER);
2022-07-09 16:14:56 +00:00
str_copy(aBuf, Client()->PlayerName());
2021-10-02 21:15:59 +00:00
CAnimState *pIdleState = CAnimState::GetIdle();
constexpr int PreviewTeeCount = 4;
constexpr float RealTeeSize = CChat::MESSAGE_TEE_SIZE * 2;
constexpr float RealTeeSizeHalved = CChat::MESSAGE_TEE_SIZE;
constexpr float TWSkinUnreliableOffset = -0.25f;
constexpr float OffsetTeeY = RealTeeSizeHalved;
const float FullHeightMinusTee = RealOffsetY - RealTeeSize;
CTeeRenderInfo aRenderInfo[PreviewTeeCount];
2021-10-02 21:15:59 +00:00
// Backgrounds first
if(!g_Config.m_ClChatOld)
{
2021-10-02 21:15:59 +00:00
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(0, 0, 0, 0.12f);
char aLineBuilder[128];
2021-10-02 21:15:59 +00:00
float Width;
float TempY = Y;
constexpr float RealBackgroundRounding = CChat::MESSAGE_ROUNDING * 2.0f;
if(g_Config.m_ClShowChatSystem)
{
str_format(aLineBuilder, sizeof(aLineBuilder), "*** '%s' entered and joined the game", aBuf);
Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1);
Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL);
2021-10-02 21:15:59 +00:00
TempY += RealOffsetY;
}
str_format(aLineBuilder, sizeof(aLineBuilder), "%sRandom Tee: Hey, how are you %s?", g_Config.m_ClShowIDs ? "7: " : "", aBuf);
Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1);
Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL);
TempY += RealOffsetY;
str_format(aLineBuilder, sizeof(aLineBuilder), "%sYour Teammate: Let's speedrun this!", g_Config.m_ClShowIDs ? "11: " : "");
Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1);
Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL);
2021-10-02 21:15:59 +00:00
TempY += RealOffsetY;
str_format(aLineBuilder, sizeof(aLineBuilder), "%s%sFriend: Hello there", g_Config.m_ClMessageFriend ? "" : "", g_Config.m_ClShowIDs ? "8: " : "");
Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1);
Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL);
TempY += RealOffsetY;
str_format(aLineBuilder, sizeof(aLineBuilder), "%sSpammer [6]: Hey fools, I'm spamming here!", g_Config.m_ClShowIDs ? "9: " : "");
Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1);
Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL);
2021-10-02 21:15:59 +00:00
TempY += RealOffsetY;
2019-04-25 16:49:27 +00:00
Width = TextRender()->TextWidth(RealFontSize, "— Echo command executed", -1, -1);
Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL);
2021-10-02 21:15:59 +00:00
Graphics()->QuadsEnd();
2021-10-02 21:15:59 +00:00
// Load skins
const auto *pDefaultSkin = GameClient()->m_Skins.Find("default");
for(auto &Info : aRenderInfo)
2021-10-02 21:15:59 +00:00
{
Info.m_Size = RealTeeSize;
Info.m_CustomColoredSkin = false;
2021-10-02 21:15:59 +00:00
}
const CSkin *pSkin = nullptr;
int pos = 0;
aRenderInfo[pos++].m_OriginalRenderSkin = pDefaultSkin->m_OriginalSkin;
aRenderInfo[pos++].m_OriginalRenderSkin = (pSkin = GameClient()->m_Skins.FindOrNullptr("pinky")) != nullptr ? pSkin->m_OriginalSkin : aRenderInfo[0].m_OriginalRenderSkin;
aRenderInfo[pos++].m_OriginalRenderSkin = (pSkin = GameClient()->m_Skins.FindOrNullptr("cammostripes")) != nullptr ? pSkin->m_OriginalSkin : aRenderInfo[0].m_OriginalRenderSkin;
aRenderInfo[pos++].m_OriginalRenderSkin = (pSkin = GameClient()->m_Skins.FindOrNullptr("beast")) != nullptr ? pSkin->m_OriginalSkin : aRenderInfo[0].m_OriginalRenderSkin;
2021-10-02 21:15:59 +00:00
}
2021-10-02 21:15:59 +00:00
// System
if(g_Config.m_ClShowChatSystem)
{
2021-10-02 21:15:59 +00:00
TextRender()->TextColor(SystemColor);
TextRender()->TextEx(&Cursor, "*** '", -1);
TextRender()->TextEx(&Cursor, aBuf, -1);
TextRender()->TextEx(&Cursor, "' entered and joined the game", -1);
TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY);
}
2021-10-02 21:15:59 +00:00
// Highlighted
TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0);
TextRender()->TextColor(DefaultNameColor);
if(g_Config.m_ClShowIDs)
TextRender()->TextEx(&Cursor, "7: ", -1);
TextRender()->TextEx(&Cursor, "Random Tee: ", -1);
TextRender()->TextColor(HighlightedColor);
TextRender()->TextEx(&Cursor, "Hey, how are you ", -1);
TextRender()->TextEx(&Cursor, aBuf, -1);
TextRender()->TextEx(&Cursor, "?", -1);
if(!g_Config.m_ClChatOld)
RenderTools()->RenderTee(pIdleState, &aRenderInfo[1], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
2021-10-02 21:15:59 +00:00
TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY);
2021-10-02 21:15:59 +00:00
// Team
TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0);
TextRender()->TextColor(TeamColor);
if(g_Config.m_ClShowIDs)
TextRender()->TextEx(&Cursor, "11: ", -1);
TextRender()->TextEx(&Cursor, "Your Teammate: Let's speedrun this!", -1);
if(!g_Config.m_ClChatOld)
RenderTools()->RenderTee(pIdleState, &aRenderInfo[0], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
2021-10-02 21:15:59 +00:00
TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY);
2021-10-02 21:15:59 +00:00
// Friend
TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0);
if(g_Config.m_ClMessageFriend)
{
TextRender()->TextColor(FriendColor);
TextRender()->TextEx(&Cursor, "", -1);
}
TextRender()->TextColor(DefaultNameColor);
if(g_Config.m_ClShowIDs)
TextRender()->TextEx(&Cursor, "8: ", -1);
TextRender()->TextEx(&Cursor, "Friend: ", -1);
TextRender()->TextColor(NormalColor);
TextRender()->TextEx(&Cursor, "Hello there", -1);
if(!g_Config.m_ClChatOld)
RenderTools()->RenderTee(pIdleState, &aRenderInfo[2], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY);
2021-10-02 21:15:59 +00:00
// Normal
TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0);
TextRender()->TextColor(DefaultNameColor);
if(g_Config.m_ClShowIDs)
TextRender()->TextEx(&Cursor, "9: ", -1);
TextRender()->TextEx(&Cursor, "Spammer ", -1);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.3f);
TextRender()->TextEx(&Cursor, "[6]", -1);
TextRender()->TextColor(NormalColor);
TextRender()->TextEx(&Cursor, ": Hey fools, I'm spamming here!", -1);
if(!g_Config.m_ClChatOld)
RenderTools()->RenderTee(pIdleState, &aRenderInfo[3], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
2021-10-02 21:15:59 +00:00
TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY);
// Client
TextRender()->TextColor(ClientColor);
TextRender()->TextEx(&Cursor, "— Echo command executed", -1);
2021-10-02 21:15:59 +00:00
TextRender()->SetCursorPosition(&Cursor, X, Y);
TextRender()->TextColor(TextRender()->DefaultTextColor());
2021-10-02 21:15:59 +00:00
}
2022-06-26 22:38:49 +00:00
else if(s_CurTab == APPEARANCE_TAB_NAME_PLATE)
{
MainView.VSplitMid(&LeftView, &RightView);
2022-06-26 22:38:49 +00:00
// ***** Name Plate ***** //
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("Name Plate"), HeadlineFontSize, TEXTALIGN_ML);
2022-06-26 22:38:49 +00:00
// General chat settings
LeftView.HSplitTop(SectionTotalMargin + 9 * LineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
Section.HSplitTop(2 * LineSize, &Button, &Section);
UI()->DoScrollbarOption(&g_Config.m_ClNameplatesSize, &g_Config.m_ClNameplatesSize, &Button, Localize("Name plates size"), 0, 100, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_MULTILINE);
2022-06-26 22:38:49 +00:00
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClNameplatesClan, Localize("Show clan above name plates"), &g_Config.m_ClNameplatesClan, &Section, LineSize);
Section.HSplitTop(2 * LineSize, &Button, &Section);
2022-06-26 22:38:49 +00:00
if(g_Config.m_ClNameplatesClan)
{
UI()->DoScrollbarOption(&g_Config.m_ClNameplatesClanSize, &g_Config.m_ClNameplatesClanSize, &Button, Localize("Clan plates size"), 0, 100, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_MULTILINE);
}
2022-06-26 22:38:49 +00:00
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClNameplatesTeamcolors, Localize("Use team colors for name plates"), &g_Config.m_ClNameplatesTeamcolors, &Section, LineSize);
DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClNameplatesStrong, Localize("Show hook strength indicator"), &g_Config.m_ClNameplatesStrong, &Section, LineSize);
2022-06-26 22:38:49 +00:00
Section.HSplitTop(LineSize, &Button, &Section);
2022-06-26 22:38:49 +00:00
if(DoButton_CheckBox(&g_Config.m_ClShowDirection, Localize("Show other players' key presses"), g_Config.m_ClShowDirection >= 1, &Button))
{
g_Config.m_ClShowDirection = g_Config.m_ClShowDirection >= 1 ? 0 : 1;
}
Section.HSplitTop(LineSize, &Button, &Section);
2022-06-26 22:38:49 +00:00
static int s_ShowLocalPlayer = 0;
if(DoButton_CheckBox(&s_ShowLocalPlayer, Localize("Show local player's key presses"), g_Config.m_ClShowDirection == 2, &Button))
{
g_Config.m_ClShowDirection = g_Config.m_ClShowDirection != 2 ? 2 : 1;
}
}
else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION)
{
MainView.VSplitMid(&LeftView, &RightView);
2022-06-26 22:38:49 +00:00
// ***** Hookline ***** //
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("Hook collision line"), HeadlineFontSize, TEXTALIGN_ML);
2022-06-26 22:38:49 +00:00
// General hookline settings
LeftView.HSplitTop(SectionTotalMargin + 6 * LineSize + 3 * ColorPickerLineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
2022-06-26 22:38:49 +00:00
Section.HSplitTop(LineSize, &Button, &Section);
2022-06-26 22:38:49 +00:00
if(DoButton_CheckBox(&g_Config.m_ClShowHookCollOther, Localize("Show other players' hook collision lines"), g_Config.m_ClShowHookCollOther, &Button))
{
g_Config.m_ClShowHookCollOther = g_Config.m_ClShowHookCollOther >= 1 ? 0 : 1;
}
Section.HSplitTop(2 * LineSize, &Button, &Section);
UI()->DoScrollbarOption(&g_Config.m_ClHookCollSize, &g_Config.m_ClHookCollSize, &Button, Localize("Hook collision line width"), 0, 100, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_MULTILINE);
2022-06-26 22:38:49 +00:00
Section.HSplitTop(2 * LineSize, &Button, &Section);
UI()->DoScrollbarOption(&g_Config.m_ClHookCollAlpha, &g_Config.m_ClHookCollAlpha, &Button, Localize("Hook collision line opacity"), 0, 100, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_MULTILINE, "%");
2022-06-26 22:38:49 +00:00
static CButtonContainer s_HookCollNoCollResetID, s_HookCollHookableCollResetID, s_HookCollTeeCollResetID;
static int s_HookCollToolTip;
2022-06-26 22:38:49 +00:00
Section.HSplitTop(LineSize, &Label, &Section);
UI()->DoLabel(&Label, Localize("Colors of the hook collision line, in case of a possible collision with:"), 13.0f, TEXTALIGN_ML);
GameClient()->m_Tooltips.DoToolTip(&s_HookCollToolTip, &Label, Localize("Your movements are not taken into account when calculating the line colors"));
DoLine_ColorPicker(&s_HookCollNoCollResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Nothing hookable"), &g_Config.m_ClHookCollColorNoColl, ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), false);
DoLine_ColorPicker(&s_HookCollHookableCollResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Something hookable"), &g_Config.m_ClHookCollColorHookableColl, ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), false);
DoLine_ColorPicker(&s_HookCollTeeCollResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("A Tee"), &g_Config.m_ClHookCollColorTeeColl, ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f), false);
2022-06-26 22:38:49 +00:00
}
else if(s_CurTab == APPEARANCE_TAB_KILL_MESSAGES)
{
MainView.VSplitMid(&LeftView, &RightView);
2022-06-26 22:38:49 +00:00
// ***** Kill Messages ***** //
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("Kill Messages"), HeadlineFontSize, TEXTALIGN_ML);
2022-06-26 22:38:49 +00:00
// General kill messages settings
LeftView.HSplitTop(SectionTotalMargin + 2 * ColorPickerLineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
2022-06-26 22:38:49 +00:00
static CButtonContainer s_KillMessageNormalColorID, s_KillMessageHighlightColorID;
2022-06-26 22:38:49 +00:00
DoLine_ColorPicker(&s_KillMessageNormalColorID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Normal Color"), &g_Config.m_ClKillMessageNormalColor, ColorRGBA(1.0f, 1.0f, 1.0f), false);
DoLine_ColorPicker(&s_KillMessageHighlightColorID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Highlight Color"), &g_Config.m_ClKillMessageHighlightColor, ColorRGBA(1.0f, 1.0f, 1.0f), false);
2022-06-26 22:38:49 +00:00
}
else if(s_CurTab == APPEARANCE_TAB_LASER)
{
MainView.VSplitMid(&LeftView, &RightView);
2022-06-26 22:38:49 +00:00
// ***** Weapons ***** //
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("Weapons"), HeadlineFontSize, TEXTALIGN_ML);
2022-06-26 22:38:49 +00:00
// General weapon laser settings
LeftView.HSplitTop(SectionTotalMargin + 4 * ColorPickerLineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
2022-06-26 22:38:49 +00:00
static CButtonContainer s_LaserRifleOutResetID, s_LaserRifleInResetID, s_LaserShotgunOutResetID, s_LaserShotgunInResetID;
ColorHSLA LaserRifleOutlineColor = DoLine_ColorPicker(&s_LaserRifleOutResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Rifle Laser Outline Color"), &g_Config.m_ClLaserRifleOutlineColor, ColorRGBA(0.074402f, 0.074402f, 0.247166f, 1.0f), false);
ColorHSLA LaserRifleInnerColor = DoLine_ColorPicker(&s_LaserRifleInResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Rifle Laser Inner Color"), &g_Config.m_ClLaserRifleInnerColor, ColorRGBA(0.498039f, 0.498039f, 1.0f, 1.0f), false);
ColorHSLA LaserShotgunOutlineColor = DoLine_ColorPicker(&s_LaserShotgunOutResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Shotgun Laser Outline Color"), &g_Config.m_ClLaserShotgunOutlineColor, ColorRGBA(0.125490f, 0.098039f, 0.043137f, 1.0f), false);
ColorHSLA LaserShotgunInnerColor = DoLine_ColorPicker(&s_LaserShotgunInResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Shotgun Laser Inner Color"), &g_Config.m_ClLaserShotgunInnerColor, ColorRGBA(0.570588f, 0.417647f, 0.252941f, 1.0f), false);
2022-09-17 01:06:20 +00:00
// ***** Entities ***** //
LeftView.HSplitTop(MarginToNextSection * 2.0f, 0x0, &LeftView);
LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView);
UI()->DoLabel(&Label, Localize("Entities"), HeadlineFontSize, TEXTALIGN_ML);
2022-06-26 22:38:49 +00:00
// General entity laser settings
LeftView.HSplitTop(SectionTotalMargin + 4 * ColorPickerLineSize, &Section, &LeftView);
Section.Margin(SectionMargin, &Section);
static CButtonContainer s_LaserDoorOutResetID, s_LaserDoorInResetID, s_LaserFreezeOutResetID, s_LaserFreezeInResetID;
ColorHSLA LaserDoorOutlineColor = DoLine_ColorPicker(&s_LaserDoorOutResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Door Laser Outline Color"), &g_Config.m_ClLaserDoorOutlineColor, ColorRGBA(0.0f, 0.131372f, 0.096078f, 1.0f), false);
ColorHSLA LaserDoorInnerColor = DoLine_ColorPicker(&s_LaserDoorInResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Door Laser Inner Color"), &g_Config.m_ClLaserDoorInnerColor, ColorRGBA(0.262745f, 0.760784f, 0.639215f, 1.0f), false);
ColorHSLA LaserFreezeOutlineColor = DoLine_ColorPicker(&s_LaserFreezeOutResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Freeze Laser Outline Color"), &g_Config.m_ClLaserFreezeOutlineColor, ColorRGBA(0.131372f, 0.123529f, 0.182352f, 1.0f), false);
ColorHSLA LaserFreezeInnerColor = DoLine_ColorPicker(&s_LaserFreezeInResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Freeze Laser Inner Color"), &g_Config.m_ClLaserFreezeInnerColor, ColorRGBA(0.482352f, 0.443137f, 0.564705f, 1.0f), false);
2022-09-17 01:06:20 +00:00
static CButtonContainer s_AllToRifleResetID, s_AllToDefaultResetID;
LeftView.HSplitTop(20.0f, 0x0, &LeftView);
LeftView.HSplitTop(20.0f, &Button, &LeftView);
if(DoButton_Menu(&s_AllToRifleResetID, Localize("Set all to Rifle"), 0, &Button))
{
g_Config.m_ClLaserShotgunOutlineColor = g_Config.m_ClLaserRifleOutlineColor;
g_Config.m_ClLaserShotgunInnerColor = g_Config.m_ClLaserRifleInnerColor;
g_Config.m_ClLaserDoorOutlineColor = g_Config.m_ClLaserRifleOutlineColor;
g_Config.m_ClLaserDoorInnerColor = g_Config.m_ClLaserRifleInnerColor;
g_Config.m_ClLaserFreezeOutlineColor = g_Config.m_ClLaserRifleOutlineColor;
g_Config.m_ClLaserFreezeInnerColor = g_Config.m_ClLaserRifleInnerColor;
}
2022-06-26 22:38:49 +00:00
2022-09-17 01:06:20 +00:00
// values taken from the CL commands
LeftView.HSplitTop(10.0f, 0x0, &LeftView);
LeftView.HSplitTop(20.0f, &Button, &LeftView);
if(DoButton_Menu(&s_AllToDefaultResetID, Localize("Reset to defaults"), 0, &Button))
2022-09-17 01:06:20 +00:00
{
g_Config.m_ClLaserRifleOutlineColor = 11176233;
g_Config.m_ClLaserRifleInnerColor = 11206591;
g_Config.m_ClLaserShotgunOutlineColor = 1866773;
g_Config.m_ClLaserShotgunInnerColor = 1467241;
2022-09-17 01:06:20 +00:00
g_Config.m_ClLaserDoorOutlineColor = 7667473;
g_Config.m_ClLaserDoorInnerColor = 7701379;
g_Config.m_ClLaserFreezeOutlineColor = 11613223;
g_Config.m_ClLaserFreezeInnerColor = 12001153;
}
2022-06-26 22:38:49 +00:00
// ***** Laser Preview ***** //
RightView.HSplitTop(HeadlineAndVMargin, &Label, &RightView);
UI()->DoLabel(&Label, Localize("Preview"), HeadlineFontSize, TEXTALIGN_ML);
2022-06-26 22:38:49 +00:00
RightView.HSplitTop(SectionTotalMargin + 50.0f, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
DoLaserPreview(&Section, LaserRifleOutlineColor, LaserRifleInnerColor, LASERTYPE_RIFLE);
RightView.HSplitTop(SectionTotalMargin + 50.0f, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
DoLaserPreview(&Section, LaserShotgunOutlineColor, LaserShotgunInnerColor, LASERTYPE_SHOTGUN);
2022-06-26 22:38:49 +00:00
RightView.HSplitTop(SectionTotalMargin + 50.0f, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
DoLaserPreview(&Section, LaserDoorOutlineColor, LaserDoorInnerColor, LASERTYPE_DOOR);
RightView.HSplitTop(SectionTotalMargin + 50.0f, &Section, &RightView);
Section.Margin(SectionMargin, &Section);
DoLaserPreview(&Section, LaserFreezeOutlineColor, LaserFreezeInnerColor, LASERTYPE_DOOR);
2022-06-26 22:38:49 +00:00
}
}
void CMenus::RenderSettingsDDNet(CUIRect MainView)
{
CUIRect Button, Left, Right, LeftLeft, Demo, Gameplay, Miscellaneous, Label, Background;
MainView.HSplitTop(100.0f, &Demo, &MainView);
2014-04-15 20:10:46 +00:00
Demo.HSplitTop(30.0f, &Label, &Demo);
UI()->DoLabel(&Label, Localize("Demo"), 20.0f, TEXTALIGN_ML);
2014-04-15 20:10:46 +00:00
Demo.Margin(5.0f, &Demo);
Demo.VSplitMid(&Left, &Right);
Left.VSplitRight(5.0f, &Left, 0);
Right.VMargin(5.0f, &Right);
Left.HSplitTop(20.0f, &Button, &Left);
if(DoButton_CheckBox(&g_Config.m_ClAutoRaceRecord, Localize("Save the best demo of each race"), g_Config.m_ClAutoRaceRecord, &Button))
2011-04-17 17:14:49 +00:00
{
2014-04-15 20:10:46 +00:00
g_Config.m_ClAutoRaceRecord ^= 1;
2011-04-17 17:14:49 +00:00
}
2013-08-30 07:50:05 +00:00
2019-05-20 21:55:40 +00:00
{
Left.HSplitTop(20.0f, &Button, &Left);
if(DoButton_CheckBox(&g_Config.m_ClReplays, Localize("Enable replays"), g_Config.m_ClReplays, &Button))
{
g_Config.m_ClReplays ^= 1;
if(!g_Config.m_ClReplays)
{
// stop recording and remove the tmp demo file
Client()->DemoRecorder_Stop(RECORDER_REPLAYS, true);
}
else
{
// start recording
Client()->DemoRecorder_HandleAutoStart();
}
}
Left.HSplitTop(20.0f, &Button, &Left);
if(g_Config.m_ClReplays)
UI()->DoScrollbarOption(&g_Config.m_ClReplayLength, &g_Config.m_ClReplayLength, &Button, Localize("Default length"), 10, 600, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_NOCLAMPVALUE);
2019-05-20 21:55:40 +00:00
}
2014-04-15 20:10:46 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClRaceGhost, Localize("Ghost"), g_Config.m_ClRaceGhost, &Button))
2013-10-09 14:02:23 +00:00
{
2014-04-15 20:10:46 +00:00
g_Config.m_ClRaceGhost ^= 1;
2013-10-09 14:02:23 +00:00
}
2022-04-28 14:46:18 +00:00
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClRaceGhost, &Button, Localize("When you cross the start line, show a ghost tee replicating the movements of your best time"));
2013-10-09 14:02:23 +00:00
2014-12-14 16:16:44 +00:00
if(g_Config.m_ClRaceGhost)
2013-10-15 16:08:06 +00:00
{
2014-12-14 16:16:44 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClRaceShowGhost, Localize("Show ghost"), g_Config.m_ClRaceShowGhost, &Button))
{
g_Config.m_ClRaceShowGhost ^= 1;
}
2013-10-15 16:08:06 +00:00
2014-12-14 16:16:44 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClRaceSaveGhost, Localize("Save ghost"), g_Config.m_ClRaceSaveGhost, &Button))
{
g_Config.m_ClRaceSaveGhost ^= 1;
}
2013-11-02 02:09:56 +00:00
}
MainView.HSplitTop(330.0f, &Gameplay, &MainView);
2014-04-15 20:10:46 +00:00
Gameplay.HSplitTop(30.0f, &Label, &Gameplay);
UI()->DoLabel(&Label, Localize("Gameplay"), 20.0f, TEXTALIGN_ML);
2014-04-15 20:10:46 +00:00
Gameplay.Margin(5.0f, &Gameplay);
Gameplay.VSplitMid(&Left, &Right);
Left.VSplitRight(5.0f, &Left, 0);
Right.VMargin(5.0f, &Right);
2013-11-02 02:09:56 +00:00
{
Left.HSplitTop(20.0f, &Button, &Left);
UI()->DoScrollbarOption(&g_Config.m_ClOverlayEntities, &g_Config.m_ClOverlayEntities, &Button, Localize("Overlay entities"), 0, 100);
2013-11-02 02:09:56 +00:00
}
{
Left.HSplitTop(20.0f, &Button, &Left);
Button.VSplitMid(&LeftLeft, &Button);
if(DoButton_CheckBox(&g_Config.m_ClTextEntities, Localize("Show text entities"), g_Config.m_ClTextEntities, &LeftLeft))
g_Config.m_ClTextEntities ^= 1;
if(g_Config.m_ClTextEntities)
UI()->DoScrollbarOption(&g_Config.m_ClTextEntitiesSize, &g_Config.m_ClTextEntitiesSize, &Button, Localize("Size"), 0, 100);
}
2013-11-14 15:23:15 +00:00
{
2014-05-17 21:00:52 +00:00
Left.HSplitTop(20.0f, &Button, &Left);
2014-05-24 22:59:52 +00:00
Button.VSplitMid(&LeftLeft, &Button);
2022-03-03 21:56:32 +00:00
if(DoButton_CheckBox(&g_Config.m_ClShowOthers, Localize("Show others"), g_Config.m_ClShowOthers == SHOW_OTHERS_ON, &LeftLeft))
g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ON ? SHOW_OTHERS_ON : SHOW_OTHERS_OFF;
UI()->DoScrollbarOption(&g_Config.m_ClShowOthersAlpha, &g_Config.m_ClShowOthersAlpha, &Button, Localize("Opacity"), 0, 100);
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClShowOthersAlpha, &Button, Localize("Adjust the opacity of entities belonging to other teams, such as tees and nameplates"));
2013-11-14 15:23:15 +00:00
}
Left.HSplitTop(20.0f, &Button, &Left);
2021-05-17 07:11:36 +00:00
static int s_ShowOwnTeamID = 0;
2022-03-03 21:56:32 +00:00
if(DoButton_CheckBox(&s_ShowOwnTeamID, Localize("Show others (own team only)"), g_Config.m_ClShowOthers == SHOW_OTHERS_ONLY_TEAM, &Button))
{
2022-03-03 21:56:32 +00:00
g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ONLY_TEAM ? SHOW_OTHERS_ONLY_TEAM : SHOW_OTHERS_OFF;
}
2014-04-15 20:10:46 +00:00
Left.HSplitTop(20.0f, &Button, &Left);
if(DoButton_CheckBox(&g_Config.m_ClShowQuads, Localize("Show quads"), g_Config.m_ClShowQuads, &Button))
2014-01-15 14:25:13 +00:00
{
2014-04-15 20:10:46 +00:00
g_Config.m_ClShowQuads ^= 1;
2014-01-15 14:25:13 +00:00
}
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClShowQuads, &Button, Localize("Quads are used for background decoration"));
2014-01-15 14:25:13 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
UI()->DoScrollbarOption(&g_Config.m_ClDefaultZoom, &g_Config.m_ClDefaultZoom, &Button, Localize("Default zoom"), 0, 20);
2015-01-03 02:13:21 +00:00
2014-04-15 20:10:46 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClAntiPing, Localize("AntiPing"), g_Config.m_ClAntiPing, &Button))
2014-01-13 23:25:25 +00:00
{
2014-04-15 20:10:46 +00:00
g_Config.m_ClAntiPing ^= 1;
2014-01-13 23:25:25 +00:00
}
2022-04-28 14:46:18 +00:00
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClAntiPing, &Button, Localize("Tries to predict other entities to give a feel of low latency"));
2014-01-13 23:25:25 +00:00
2014-12-14 16:16:44 +00:00
if(g_Config.m_ClAntiPing)
2014-03-21 13:01:14 +00:00
{
2014-12-14 16:16:44 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClAntiPingPlayers, Localize("AntiPing: predict other players"), g_Config.m_ClAntiPingPlayers, &Button))
{
g_Config.m_ClAntiPingPlayers ^= 1;
}
2014-03-21 13:01:14 +00:00
2014-12-14 16:16:44 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClAntiPingWeapons, Localize("AntiPing: predict weapons"), g_Config.m_ClAntiPingWeapons, &Button))
{
g_Config.m_ClAntiPingWeapons ^= 1;
}
2014-03-28 12:39:30 +00:00
2014-12-14 16:16:44 +00:00
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_CheckBox(&g_Config.m_ClAntiPingGrenade, Localize("AntiPing: predict grenade paths"), g_Config.m_ClAntiPingGrenade, &Button))
{
g_Config.m_ClAntiPingGrenade ^= 1;
}
2014-05-06 14:25:00 +00:00
}
2014-12-14 16:16:44 +00:00
else
{
2014-12-14 16:16:44 +00:00
Right.HSplitTop(60.0f, 0, &Right);
}
Right.HSplitTop(40.0f, 0, &Right);
2014-04-15 20:10:46 +00:00
Left.HSplitTop(5.0f, &Button, &Left);
2020-12-16 04:55:41 +00:00
Left.VSplitRight(10.0f, &Left, 0x0);
Right.VSplitLeft(10.0f, 0x0, &Right);
Left.HSplitTop(25.0f, 0x0, &Left);
CUIRect TempLabel;
2020-12-18 20:28:33 +00:00
Left.HSplitTop(25.0f, &TempLabel, &Left);
Left.HSplitTop(5.0f, 0x0, &Left);
UI()->DoLabel(&TempLabel, Localize("Background"), 20.0f, TEXTALIGN_ML);
2020-12-18 20:28:33 +00:00
2020-12-16 04:55:41 +00:00
Right.HSplitTop(25.0f, &TempLabel, &Right);
2020-12-18 20:28:33 +00:00
Right.HSplitTop(5.0f, 0x0, &Miscellaneous);
UI()->DoLabel(&TempLabel, Localize("Miscellaneous"), 20.0f, TEXTALIGN_ML);
2013-08-30 07:50:05 +00:00
static CButtonContainer s_ResetID2;
2020-12-16 04:55:41 +00:00
ColorRGBA GreyDefault(0.5f, 0.5f, 0.5f, 1);
DoLine_ColorPicker(&s_ResetID2, 25.0f, 13.0f, 5.0f, &Left, Localize("Entities Background color"), &g_Config.m_ClBackgroundEntitiesColor, GreyDefault, false);
2016-05-03 22:24:54 +00:00
2020-12-18 20:35:22 +00:00
Left.VSplitLeft(5.0f, 0x0, &Left);
Left.HSplitTop(25.0f, &Background, &Left);
Background.HSplitTop(20.0f, &Background, 0);
Background.VSplitLeft(100.0f, &Label, &TempLabel);
UI()->DoLabel(&Label, Localize("Map"), 14.0f, TEXTALIGN_ML);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_BackgroundEntitiesInput(g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities));
UI()->DoEditBox(&s_BackgroundEntitiesInput, &TempLabel, 14.0f);
2020-12-18 20:35:22 +00:00
Left.HSplitTop(20.0f, &Button, &Left);
bool UseCurrentMap = str_comp(g_Config.m_ClBackgroundEntities, CURRENT_MAP) == 0;
2021-05-17 07:11:36 +00:00
static int s_UseCurrentMapID = 0;
if(DoButton_CheckBox(&s_UseCurrentMapID, Localize("Use current map as background"), UseCurrentMap, &Button))
2020-12-18 20:35:22 +00:00
{
if(UseCurrentMap)
g_Config.m_ClBackgroundEntities[0] = '\0';
else
2022-07-09 16:14:56 +00:00
str_copy(g_Config.m_ClBackgroundEntities, CURRENT_MAP);
}
2016-05-03 22:24:54 +00:00
2020-12-18 20:35:22 +00:00
Left.HSplitTop(20.0f, &Button, &Left);
if(DoButton_CheckBox(&g_Config.m_ClBackgroundShowTilesLayers, Localize("Show tiles layers from BG map"), g_Config.m_ClBackgroundShowTilesLayers, &Button))
g_Config.m_ClBackgroundShowTilesLayers ^= 1;
2014-05-24 19:46:42 +00:00
static CButtonContainer s_ResetID1;
2020-12-18 20:28:33 +00:00
Miscellaneous.HSplitTop(25.0f, &Button, &Right);
DoLine_ColorPicker(&s_ResetID1, 25.0f, 13.0f, 5.0f, &Button, Localize("Regular Background Color"), &g_Config.m_ClBackgroundColor, GreyDefault, false);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonTimeout;
2020-12-18 20:35:22 +00:00
Right.HSplitTop(10.0f, 0x0, &Right);
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_Menu(&s_ButtonTimeout, Localize("New random timeout code"), 0, &Button))
2020-12-18 20:28:33 +00:00
{
2020-12-18 20:35:22 +00:00
Client()->GenerateTimeoutSeed();
2020-12-18 20:28:33 +00:00
}
Right.HSplitTop(5.0f, 0, &Right);
Right.HSplitTop(20.0f, &Label, &Right);
Label.VSplitLeft(5.0f, 0, &Label);
UI()->DoLabel(&Label, Localize("Run on join"), 14.0f, TEXTALIGN_ML);
Right.HSplitTop(20.0f, &Button, &Right);
Button.VSplitLeft(5.0f, 0, &Button);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
static CLineInput s_RunOnJoinInput(g_Config.m_ClRunOnJoin, sizeof(g_Config.m_ClRunOnJoin));
s_RunOnJoinInput.SetEmptyText(Localize("Chat command (e.g. showall 1)"));
UI()->DoEditBox(&s_RunOnJoinInput, &Button, 14.0f);
#if defined(CONF_FAMILY_WINDOWS)
static CButtonContainer s_ButtonUnregisterShell;
Right.HSplitTop(10.0f, nullptr, &Right);
Right.HSplitTop(20.0f, &Button, &Right);
if(DoButton_Menu(&s_ButtonUnregisterShell, Localize("Unregister protocol and file extensions"), 0, &Button))
{
Client()->ShellUnregister();
}
#endif
2020-12-18 20:35:22 +00:00
// Updater
#if defined(CONF_AUTOUPDATE)
2015-03-05 21:09:14 +00:00
{
2020-12-18 20:28:33 +00:00
MainView.VSplitMid(&Left, &Right);
Left.w += 20.0f;
Left.HSplitBottom(20.0f, 0x0, &Label);
2015-03-05 21:09:14 +00:00
bool NeedUpdate = str_comp(Client()->LatestVersion(), "0");
int State = Updater()->GetCurrentState();
// Update Button
char aBuf[256];
if(NeedUpdate && State <= IUpdater::CLEAN)
2015-03-05 21:09:14 +00:00
{
2015-06-26 18:44:55 +00:00
str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is available:"), Client()->LatestVersion());
Label.VSplitLeft(TextRender()->TextWidth(14.0f, aBuf, -1, -1.0f) + 10.0f, &Label, &Button);
2015-03-13 15:49:52 +00:00
Button.VSplitLeft(100.0f, &Button, 0);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonUpdate;
2020-12-18 20:35:22 +00:00
if(DoButton_Menu(&s_ButtonUpdate, Localize("Update now"), 0, &Button))
2020-12-18 20:28:33 +00:00
{
Updater()->InitiateUpdate();
2020-12-18 20:28:33 +00:00
}
2015-03-05 21:09:14 +00:00
}
else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART)
2015-06-26 18:44:55 +00:00
str_format(aBuf, sizeof(aBuf), Localize("Updating..."));
2017-03-04 14:43:49 +00:00
else if(State == IUpdater::NEED_RESTART)
{
2015-06-26 18:44:55 +00:00
str_format(aBuf, sizeof(aBuf), Localize("DDNet Client updated!"));
m_NeedRestartUpdate = true;
}
2015-03-05 21:09:14 +00:00
else
{
2015-06-26 18:44:55 +00:00
str_format(aBuf, sizeof(aBuf), Localize("No updates available"));
Label.VSplitLeft(TextRender()->TextWidth(14.0f, aBuf, -1, -1.0f) + 10.0f, &Label, &Button);
Button.VSplitLeft(100.0f, &Button, 0);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonUpdate;
2015-06-26 18:44:55 +00:00
if(DoButton_Menu(&s_ButtonUpdate, Localize("Check now"), 0, &Button))
{
Client()->RequestDDNetInfo();
}
}
UI()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
2015-03-05 21:09:14 +00:00
}
2015-03-13 14:13:19 +00:00
#endif
}