mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00
Implement UIField (#90)
* Implement UIField * Formatting and naming fixes - Ran clang-format on previously modified files - Replaced ifdef guards with `#pragma once` - Changed wording in comments above OPM-only changes for easier searchability
This commit is contained in:
parent
6b4a8bd627
commit
110ffc3a01
3 changed files with 309 additions and 67 deletions
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015 the OpenMoHAA team
|
||||
Copyright (C) 2015-2023 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
|
@ -22,59 +22,305 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
||||
#include "ui_local.h"
|
||||
|
||||
CLASS_DECLARATION( UIWidget, UIField, NULL )
|
||||
{
|
||||
{ NULL, NULL }
|
||||
CLASS_DECLARATION(UIWidget, UIField, NULL) {
|
||||
{&W_LeftMouseDown, &UIField::Pressed},
|
||||
{NULL, NULL }
|
||||
};
|
||||
|
||||
UIField::UIField()
|
||||
{
|
||||
// FIXME: stub
|
||||
AllowActivate(qtrue);
|
||||
m_iPreStep = 0;
|
||||
}
|
||||
|
||||
void UIField::Draw
|
||||
(
|
||||
void
|
||||
)
|
||||
|
||||
void UIField::Draw(void)
|
||||
{
|
||||
// FIXME: stub
|
||||
UpdateData();
|
||||
|
||||
// the width of the widget that can be filled with valid text
|
||||
float fVirtualWidth = m_frame.size.width / m_vVirtualScale[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 / m_vVirtualScale[1] - m_font->getHeight(qfalse);
|
||||
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, m_bVirtual);
|
||||
|
||||
// Display blinking cursor if field is in focus
|
||||
if (IsActive()) {
|
||||
const char *cursorChar = (uid.time / 250) & 1 ? "|" : "_";
|
||||
m_font->Print(cursorPos, y, cursorChar, -1, m_bVirtual);
|
||||
}
|
||||
|
||||
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, m_bVirtual);
|
||||
}
|
||||
|
||||
// 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 / m_vVirtualScale[0] - m_font->getWidth(temptext, -1) - 2.0f;
|
||||
m_font->Print(fVirtualWidth, y, temptext, -1, m_bVirtual);
|
||||
}
|
||||
}
|
||||
|
||||
qboolean UIField::KeyEvent
|
||||
(
|
||||
int key,
|
||||
unsigned int time
|
||||
)
|
||||
|
||||
qboolean UIField::KeyEvent(int key, unsigned int time)
|
||||
{
|
||||
// FIXME: stub
|
||||
return qfalse;
|
||||
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
|
||||
)
|
||||
|
||||
void UIField::CharEvent(int ch)
|
||||
{
|
||||
// FIXME: stub
|
||||
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] = NULL;
|
||||
}
|
||||
|
||||
if (m_cvarname.length()) {
|
||||
uii.Cvar_Set(m_cvarname, m_edit.m_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void UIField::UpdateData
|
||||
(
|
||||
void
|
||||
)
|
||||
|
||||
void UIField::UpdateData(void)
|
||||
{
|
||||
// FIXME: stub
|
||||
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
|
||||
)
|
||||
|
||||
void UIField::Pressed(Event *ev)
|
||||
{
|
||||
// FIXME: stub
|
||||
float xpos = ev->GetFloat(1);
|
||||
float point = (xpos - m_frame.pos.x) / m_vVirtualScale[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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue