mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00

This fixes UI elements being tiny on high resolutions like 4K. Now most UI elements will scale automatically with resolutions above 1920x1080.
326 lines
9.9 KiB
C++
326 lines
9.9 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2015-2023 the OpenMoHAA team
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
OpenMoHAA source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
OpenMoHAA source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenMoHAA source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "ui_local.h"
|
|
|
|
CLASS_DECLARATION(UIWidget, UIField, NULL) {
|
|
{&W_LeftMouseDown, &UIField::Pressed},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
UIField::UIField()
|
|
{
|
|
AllowActivate(qtrue);
|
|
m_iPreStep = 0;
|
|
}
|
|
|
|
void UIField::Draw(void)
|
|
{
|
|
UpdateData();
|
|
|
|
// the width of the widget that can be filled with valid text
|
|
float fVirtualWidth = m_frame.size.width / getVirtualScale()[0] - m_font->getWidth(" ..", -1);
|
|
float indentLength = m_indent;
|
|
float cursorSize = m_font->getCharWidth('_');
|
|
|
|
// The total pixel length of the entire string in the editfield buffer
|
|
float totalDrawLength = m_font->getWidth(m_edit.m_buffer, -1) + indentLength;
|
|
|
|
// The index of the character that has to be shown first
|
|
// when viewing the end of the string to properly fill out the field
|
|
int iMaxPreStep;
|
|
|
|
float fEndLen = totalDrawLength + cursorSize;
|
|
int buflen = strlen(m_edit.m_buffer);
|
|
|
|
// Calculate the max prestep
|
|
for (iMaxPreStep = 0; fEndLen > fVirtualWidth && iMaxPreStep < buflen; iMaxPreStep++) {
|
|
float fCharWidth = m_font->getCharWidth(m_edit.m_buffer[iMaxPreStep]);
|
|
fEndLen -= fCharWidth;
|
|
} // fEndLen is discarded
|
|
|
|
// Index of the last currently visible character in the textfield
|
|
int iLastChar;
|
|
|
|
fEndLen = indentLength + cursorSize;
|
|
for (iLastChar = m_iPreStep; fEndLen < fVirtualWidth; iLastChar++) {
|
|
if (char c = m_edit.m_buffer[iLastChar]) {
|
|
float fCharWidth = m_font->getCharWidth(c);
|
|
fEndLen += fCharWidth;
|
|
} else {
|
|
// important to quit early!
|
|
// otherwise iLastChar would get incremented one extra time
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (IsActive()) {
|
|
if (m_edit.m_cursor >= m_iPreStep) {
|
|
// Fixed in OPM
|
|
// m_iPreStep < iLastChar check added to avoid passing negative size to Q_strncpyz and crashing the game
|
|
while (iLastChar <= m_edit.m_cursor && m_iPreStep < iMaxPreStep && m_iPreStep < iLastChar
|
|
) { // is text too long to fit?
|
|
fEndLen -= m_font->getCharWidth(m_edit.m_buffer[m_iPreStep]);
|
|
m_iPreStep++;
|
|
|
|
// FIXME: repetitive logic
|
|
if (char c = m_edit.m_buffer[iLastChar]) {
|
|
float fCharWidth = m_font->getCharWidth(c);
|
|
fEndLen += fCharWidth;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
while (fEndLen < fVirtualWidth) {
|
|
iLastChar++;
|
|
|
|
if (char c = m_edit.m_buffer[iLastChar]) {
|
|
float fCharWidth = m_font->getCharWidth(c);
|
|
fEndLen += fCharWidth;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
m_iPreStep = m_edit.m_cursor;
|
|
}
|
|
|
|
m_iPreStep = Q_min(m_iPreStep, iMaxPreStep);
|
|
}
|
|
|
|
// Put the portion of the text intended for display into the temptext buffer
|
|
char temptext[EDITFIELD_BUFSIZE];
|
|
Q_strncpyz(temptext, &m_edit.m_buffer[m_iPreStep], iLastChar - m_iPreStep + 1);
|
|
|
|
// Calculate cursor position
|
|
float cursorPos = indentLength - 1.0f;
|
|
|
|
// Added in OPM
|
|
// new i < EDITFIELD_BUFSIZE check for extra safety
|
|
for (int i = m_iPreStep; i < m_edit.m_cursor && i < EDITFIELD_BUFSIZE; i++) {
|
|
float fCharWidth = m_font->getCharWidth(m_edit.m_buffer[i]);
|
|
cursorPos += fCharWidth;
|
|
}
|
|
|
|
cursorPos = Q_min(cursorPos, fVirtualWidth);
|
|
|
|
// Calculate text height
|
|
float y = m_frame.size.height / getVirtualScale()[1] - m_font->getHeight();
|
|
if (m_bottomindent * 2 <= y) // upper and lower margin
|
|
{
|
|
y -= m_bottomindent;
|
|
} else {
|
|
y /= 2;
|
|
}
|
|
|
|
// Show text
|
|
m_font->setColor(m_foreground_color);
|
|
m_font->Print(m_indent, y, temptext, -1, getVirtualScale());
|
|
|
|
// Display blinking cursor if field is in focus
|
|
if (IsActive()) {
|
|
const char *cursorChar = (uid.time / 250) & 1 ? "|" : "_";
|
|
m_font->Print(cursorPos, y, cursorChar, -1, getVirtualScale());
|
|
}
|
|
|
|
Q_strncpyz(temptext, "..", EDITFIELD_BUFSIZE);
|
|
|
|
if (m_iPreStep) {
|
|
// Display leading dots if not starting from the beginning
|
|
m_font->Print(2.0f, y, temptext, -1, getVirtualScale());
|
|
}
|
|
|
|
// Added in OPM
|
|
// new iLastChar < EDITFIELD_BUFSIZE check for extra safety
|
|
if (iLastChar < EDITFIELD_BUFSIZE && m_edit.m_buffer[iLastChar]) {
|
|
// Display trailing dots if not ending at the end
|
|
fVirtualWidth = m_frame.size.width / getVirtualScale()[0] - m_font->getWidth(temptext, -1) - 2.0f;
|
|
m_font->Print(fVirtualWidth, y, temptext, -1, getVirtualScale());
|
|
}
|
|
}
|
|
|
|
qboolean UIField::KeyEvent(int key, unsigned int time)
|
|
{
|
|
if (key == 'l' && uii.Sys_IsKeyDown(K_CTRL)) {
|
|
return qtrue;
|
|
}
|
|
|
|
if (key == K_ENTER || key == K_KP_ENTER) {
|
|
if (m_cvarname.length()) {
|
|
uii.Cvar_Set(m_cvarname, m_edit.m_buffer);
|
|
}
|
|
|
|
if (m_commandhandler) {
|
|
m_commandhandler(str(m_edit.m_buffer) + '\n', NULL);
|
|
}
|
|
|
|
if (m_command.length()) {
|
|
str result = m_command + ' ' + m_edit.m_buffer + '\n';
|
|
Cbuf_AddText(result);
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
if ((key == K_INS || key == K_KP_INS) && uii.Sys_IsKeyDown(K_SHIFT)) {
|
|
return qtrue;
|
|
}
|
|
|
|
int len = strlen(m_edit.m_buffer);
|
|
switch (key) {
|
|
case K_DEL: // DELETE key, backspace is handled in CharEvent
|
|
if (m_edit.m_cursor < len) {
|
|
memmove(&m_edit.m_buffer[m_edit.m_cursor], &m_edit.m_buffer[m_edit.m_cursor + 1], len - m_edit.m_cursor);
|
|
|
|
if (m_cvarname.length()) {
|
|
uii.Cvar_Set(m_cvarname, m_edit.m_buffer);
|
|
}
|
|
}
|
|
return qtrue;
|
|
case K_RIGHTARROW:
|
|
if (m_edit.m_cursor < len) {
|
|
m_edit.m_cursor++;
|
|
}
|
|
return qtrue;
|
|
case K_LEFTARROW:
|
|
if (m_edit.m_cursor > 0) {
|
|
m_edit.m_cursor--;
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
// HOME or Ctrl + A
|
|
if (key == K_HOME || (tolower(key) == 'a' && uii.Sys_IsKeyDown(K_CTRL))) {
|
|
m_edit.m_cursor = 0;
|
|
return qtrue;
|
|
}
|
|
|
|
// END or Ctrl + E
|
|
if (key == K_END || (tolower(key) == 'e' && uii.Sys_IsKeyDown(K_CTRL))) {
|
|
m_edit.m_cursor = len;
|
|
return qtrue;
|
|
}
|
|
|
|
return key == K_INS;
|
|
}
|
|
|
|
void UIField::CharEvent(int ch)
|
|
{
|
|
uii.Snd_PlaySound("sound/menu/typekey.wav");
|
|
|
|
if (ch == 3 || ch == 22) // Ctrl + C (^C) or Ctrl + V (^V)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int len = strlen(m_edit.m_buffer);
|
|
switch (ch) {
|
|
case '\b': // backspace or Ctrl + H (^H) - DEL is handled in KeyEvent
|
|
if (m_edit.m_cursor <= 0) {
|
|
// already at beginning of field
|
|
return;
|
|
}
|
|
|
|
// Move all text after the cursor one position back
|
|
memmove(&m_edit.m_buffer[m_edit.m_cursor - 1], &m_edit.m_buffer[m_edit.m_cursor], len + 1 - m_edit.m_cursor);
|
|
|
|
// move back cursor by one
|
|
m_edit.m_cursor--;
|
|
|
|
if (m_cvarname.length()) {
|
|
uii.Cvar_Set(m_cvarname, m_edit.m_buffer);
|
|
}
|
|
|
|
return;
|
|
case 1: // Home (^A)
|
|
m_edit.m_cursor = 0;
|
|
return;
|
|
case 5: // End (^E)
|
|
m_edit.m_cursor = len;
|
|
return;
|
|
}
|
|
|
|
// original check only does ch < 32,
|
|
// this one excludes DEL too but it shouldn't be a problem,
|
|
// DEL is handled in KeyEvent
|
|
if (iscntrl(ch)) {
|
|
return;
|
|
}
|
|
|
|
len++; // new printable character was typed
|
|
|
|
// Fixed in OPM
|
|
// originally this was len == EDITFIELD_BUFSIZE, just being extra safe
|
|
if (len >= EDITFIELD_BUFSIZE) // is it out of bounds?
|
|
{
|
|
return;
|
|
}
|
|
|
|
// make room for the character in the buffer
|
|
memmove(&m_edit.m_buffer[m_edit.m_cursor + 1], &m_edit.m_buffer[m_edit.m_cursor], len - m_edit.m_cursor);
|
|
|
|
// insert the new character at the cursor's location
|
|
m_edit.m_buffer[m_edit.m_cursor] = ch;
|
|
|
|
// advance the cursor
|
|
m_edit.m_cursor++;
|
|
|
|
// if the character was inserted at the end of the string,
|
|
// make sure it remains NULL-terminated
|
|
if (m_edit.m_cursor == len) {
|
|
m_edit.m_buffer[m_edit.m_cursor] = 0;
|
|
}
|
|
|
|
if (m_cvarname.length()) {
|
|
uii.Cvar_Set(m_cvarname, m_edit.m_buffer);
|
|
}
|
|
}
|
|
|
|
void UIField::UpdateData(void)
|
|
{
|
|
if (m_cvarname.length()) {
|
|
str::snprintf(m_edit.m_buffer, EDITFIELD_BUFSIZE, "%s", CvarGetForUI(m_cvarname, ""));
|
|
m_edit.m_cursor = Q_min(m_edit.m_cursor, strlen(m_edit.m_buffer));
|
|
}
|
|
}
|
|
|
|
void UIField::Pressed(Event *ev)
|
|
{
|
|
float xpos = ev->GetFloat(1);
|
|
float point = (xpos - m_frame.pos.x) / getVirtualScale()[0];
|
|
point = Q_max(point, 0.0f); // added in 2.15
|
|
|
|
int iStep = m_iPreStep;
|
|
float fStringWidth = m_indent;
|
|
while (char c = m_edit.m_buffer[iStep]) {
|
|
iStep++;
|
|
fStringWidth += m_font->getCharWidth(c);
|
|
if (point <= fStringWidth) {
|
|
if (iStep > 0) {
|
|
iStep--; // put the cursor BEFORE the clicked character
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_edit.m_cursor = iStep;
|
|
}
|