Clean up menus and text drawing

FossilOrigin-Name: f17992369e439fa68605017c6ffb0d11eece9d29a4cdb5768656bd874248d623
This commit is contained in:
Jonathan Schleifer 2025-03-06 00:34:42 +00:00
parent 623076a034
commit 0bd8f1920f
14 changed files with 331 additions and 225 deletions

View file

@ -11,7 +11,7 @@ OF_ASSUME_NONNULL_BEGIN
@property (nonatomic) bool loaded;
- (bool)loadWithIRI:(OFIRI *)IRI;
- (void)renderWithLight:(OFVector3D &)light
- (void)renderWithLight:(OFVector3D)light
frame:(int)frame
range:(int)range
x:(float)x

View file

@ -24,6 +24,12 @@ struct md2_frame {
md2_vertex vertices[1];
};
static float
snap(int sn, float f)
{
return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f;
}
@implementation MD2 {
int _numGlCommands;
int *_glCommands;
@ -103,12 +109,6 @@ struct md2_frame {
}
}
float
snap(int sn, float f)
{
return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f;
}
- (void)scaleWithFrame:(int)frame scale:(float)scale snap:(int)sn
{
_mverts[frame] = new OFVector3D[_numVerts];
@ -125,7 +125,7 @@ snap(int sn, float f)
}
}
- (void)renderWithLight:(OFVector3D &)light
- (void)renderWithLight:(OFVector3D)light
frame:(int)frame
range:(int)range
x:(float)x

16
src/Menu.h Normal file
View file

@ -0,0 +1,16 @@
#import <ObjFW/ObjFW.h>
OF_ASSUME_NONNULL_BEGIN
@class MenuItem;
@interface Menu : OFObject
@property (readonly, nonatomic) OFString *name;
@property (readonly) OFMutableArray<MenuItem *> *items;
@property (nonatomic) int mwidth;
@property (nonatomic) int menusel;
- (instancetype)initWithName:(OFString *)name;
@end
OF_ASSUME_NONNULL_END

13
src/Menu.m Normal file
View file

@ -0,0 +1,13 @@
#import "Menu.h"
@implementation Menu
- (instancetype)initWithName:(OFString *)name
{
self = [super init];
_name = [name copy];
_items = [[OFMutableArray alloc] init];
return self;
}
@end

7
src/MenuItem.h Normal file
View file

@ -0,0 +1,7 @@
#import <ObjFW/ObjFW.h>
@interface MenuItem : OFObject
@property (readonly, nonatomic) OFString *text, *action;
- (instancetype)initWithText:(OFString *)text action:(OFString *)action;
@end

31
src/MenuItem.m Normal file
View file

@ -0,0 +1,31 @@
#import "MenuItem.h"
@implementation MenuItem
- (instancetype)initWithText:(OFString *)text action:(OFString *)action
{
self = [super init];
_text = [text copy];
_action = [action copy];
return self;
}
- (OFComparisonResult)compare:(id)otherObject
{
MenuItem *otherItem;
if (![otherObject isKindOfClass:[MenuItem class]])
@throw [OFInvalidArgumentException exception];
int x = (int)_text.longLongValue;
int y = (int)otherItem.text.longLongValue;
if (x > y)
return OFOrderedAscending;
if (x < y)
return OFOrderedDescending;
return OFOrderedSame;
}
@end

View file

@ -107,8 +107,10 @@ renderscore(dynent *d)
sprintf_s(scorelines.add().s)("%d\t%s\t%d\t%s\t%s", d->frags,
d->state == CS_LAGGED ? "LAG" : lag, d->ping, d->team,
d->state == CS_DEAD ? name : d->name);
menumanual(0, scorelines.length() - 1, scorelines.last().s);
};
@autoreleasepool {
menumanual(0, scorelines.length() - 1, @(scorelines.last().s));
}
}
const int maxteams = 4;
char *teamname[maxteams];
@ -141,7 +143,7 @@ renderscores()
if (!demoplayback)
renderscore(player1);
loopv(players) if (players[i]) renderscore(players[i]);
sortmenu(0, scorelines.length());
sortmenu();
if (m_teammode) {
teamsused = 0;
loopv(players) addteamscore(players[i]);
@ -152,11 +154,13 @@ renderscores()
{
sprintf_sd(sc)("[ %s: %d ]", teamname[j], teamscore[j]);
strcat_s(teamscores, sc);
};
menumanual(0, scorelines.length(), "");
menumanual(0, scorelines.length() + 1, teamscores);
};
};
}
menumanual(0, scorelines.length(), @"");
@autoreleasepool {
menumanual(0, scorelines.length() + 1, @(teamscores));
}
}
}
// sendmap/getmap commands, should be replaced by more intuitive map downloading

View file

@ -85,13 +85,15 @@ renderconsole() // render buffer taking into account time & scrolling
refs[nd++] = conlines[i].cref;
if (nd == ndraw)
break;
};
loopj(nd)
{
draw_text(refs[j], FONTH / 3,
(FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2);
};
};
}
@autoreleasepool {
loopj(nd)
{
draw_text(@(refs[j]), FONTH / 3,
(FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2);
}
}
}
// keymap is defined externally in keymap.cfg
@ -164,11 +166,12 @@ void
history(int n)
{
static bool rec = false;
if (!rec && n >= 0 && n < vhistory.length()) {
rec = true;
execute(vhistory[vhistory.length() - n - 1]);
rec = false;
};
}
}
COMMAND(history, ARG_1INT)

View file

@ -2,22 +2,14 @@
#include "cube.h"
struct mitem {
char *text, *action;
};
#include <memory>
struct gmenu {
char *name;
vector<mitem> items;
int mwidth;
int menusel;
};
#import "Menu.h"
#import "MenuItem.h"
vector<gmenu> menus;
int vmenu = -1;
ivector menustack;
static OFMutableArray<OFNumber *> *menuStack;
static OFMutableArray<Menu *> *menus;
static int vmenu = -1;
void
menuset(int menu)
@ -29,114 +21,108 @@ menuset(int menu)
}
void
showmenu(OFString *name_)
showmenu(OFString *name)
{
@autoreleasepool {
const char *name = name_.UTF8String;
loopv(menus) if (i > 1 && strcmp(menus[i].name, name) == 0)
{
int i = 0;
for (Menu *menu in menus) {
if (i > 1 && [menu.name isEqual:name]) {
menuset(i);
return;
}
i++;
}
}
COMMAND(showmenu, ARG_1STR)
int
menucompare(mitem *a, mitem *b)
{
int x = atoi(a->text);
int y = atoi(b->text);
if (x > y)
return -1;
if (x < y)
return 1;
return 0;
};
void
sortmenu(int start, int num)
sortmenu()
{
qsort(&menus[0].items[start], num, sizeof(mitem),
(int(__cdecl *)(const void *, const void *))menucompare);
};
[menus[0].items sort];
}
void refreshservers();
bool
rendermenu()
{
if (vmenu < 0) {
menustack.setsize(0);
return false;
};
if (vmenu == 1)
refreshservers();
gmenu &m = menus[vmenu];
sprintf_sd(title)(vmenu > 1 ? "[ %s menu ]" : "%s", m.name);
int mdisp = m.items.length();
int w = 0;
loopi(mdisp)
{
int x = text_width(m.items[i].text);
if (x > w)
w = x;
};
int tw = text_width(title);
if (tw > w)
w = tw;
int step = FONTH / 4 * 5;
int h = (mdisp + 2) * step;
int y = (VIRTH - h) / 2;
int x = (VIRTW - w) / 2;
blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3,
y + h + FONTH, true);
draw_text(title, x, y, 2);
y += FONTH * 2;
if (vmenu) {
int bh = y + m.menusel * step;
blendbox(
x - FONTH, bh - 10, x + w + FONTH, bh + FONTH + 10, false);
};
loopj(mdisp)
{
draw_text(m.items[j].text, x, y, 2);
y += step;
};
return true;
};
@autoreleasepool {
if (vmenu < 0) {
[menuStack removeAllObjects];
return false;
}
if (vmenu == 1)
refreshservers();
Menu *m = menus[vmenu];
OFString *title;
if (vmenu > 1)
title =
[OFString stringWithFormat:@"[ %@ menu ]", m.name];
else
title = m.name;
int mdisp = m.items.count;
int w = 0;
loopi(mdisp)
{
int x = text_width(m.items[i].text);
if (x > w)
w = x;
}
int tw = text_width(title);
if (tw > w)
w = tw;
int step = FONTH / 4 * 5;
int h = (mdisp + 2) * step;
int y = (VIRTH - h) / 2;
int x = (VIRTW - w) / 2;
blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3,
y + h + FONTH, true);
draw_text(title, x, y, 2);
y += FONTH * 2;
if (vmenu) {
int bh = y + m.menusel * step;
blendbox(x - FONTH, bh - 10, x + w + FONTH,
bh + FONTH + 10, false);
}
loopj(mdisp)
{
draw_text(m.items[j].text, x, y, 2);
y += step;
}
return true;
}
}
void
newmenu(OFString *name)
{
@autoreleasepool {
gmenu &menu = menus.add();
menu.name = newstring(name.UTF8String);
menu.menusel = 0;
}
if (menus == nil)
menus = [[OFMutableArray alloc] init];
[menus addObject:[[Menu alloc] initWithName:name]];
}
COMMAND(newmenu, ARG_1STR)
void
menumanual(int m, int n, char *text)
menumanual(int m, int n, OFString *text)
{
if (!n)
menus[m].items.setsize(0);
mitem &mitem = menus[m].items.add();
mitem.text = text;
mitem.action = "";
if (n == 0)
[menus[m].items removeAllObjects];
MenuItem *item = [[MenuItem alloc] initWithText:text action:@""];
[menus[m].items addObject:item];
}
void
menuitem(OFString *text, OFString *action)
{
@autoreleasepool {
gmenu &menu = menus.last();
mitem &mi = menu.items.add();
mi.text = newstring(text.UTF8String);
mi.action =
action.length > 0 ? newstring(action.UTF8String) : mi.text;
}
Menu *menu = menus.lastObject;
MenuItem *item =
[[MenuItem alloc] initWithText:text
action:(action.length > 0 ? action : text)];
[menu.items addObject:item];
}
COMMAND(menuitem, ARG_2STR)
@ -145,18 +131,23 @@ menukey(int code, bool isdown)
{
if (vmenu <= 0)
return false;
int menusel = menus[vmenu].menusel;
if (isdown) {
if (code == SDLK_ESCAPE) {
menuset(-1);
if (!menustack.empty())
menuset(menustack.pop());
if (menuStack.count > 0) {
menuset(menuStack.lastObject.intValue);
[menuStack removeLastObject];
}
return true;
} else if (code == SDLK_UP || code == -4)
menusel--;
else if (code == SDLK_DOWN || code == -5)
menusel++;
int n = menus[vmenu].items.length();
int n = menus[vmenu].items.count;
if (menusel < 0)
menusel = n - 1;
else if (menusel >= n)
@ -164,16 +155,23 @@ menukey(int code, bool isdown)
menus[vmenu].menusel = menusel;
} else {
if (code == SDLK_RETURN || code == -2) {
char *action = menus[vmenu].items[menusel].action;
OFString *action = menus[vmenu].items[menusel].action;
if (vmenu == 1) {
@autoreleasepool {
connects(@(getservername(menusel)));
}
}
menustack.add(vmenu);
if (menuStack == nil)
menuStack = [[OFMutableArray alloc] init];
[menuStack addObject:@(vmenu)];
menuset(-1);
execute(action, true);
std::unique_ptr<char> copy(strdup(action.UTF8String));
execute(copy.get(), true);
}
}
return true;
};
}

View file

@ -5,6 +5,8 @@ executable('client',
'KeyMapping.m',
'MD2.mm',
'MapModelInfo.m',
'Menu.m',
'MenuItem.m',
'client.mm',
'clientextras.mm',
'clientgame.mm',

View file

@ -30,8 +30,8 @@ extern void processInitQueue(void);
// menus
extern bool rendermenu();
extern void menuset(int menu);
extern void menumanual(int m, int n, char *text);
extern void sortmenu(int start, int num);
extern void menumanual(int m, int n, OFString *text);
extern void sortmenu();
extern bool menukey(int code, bool isdown);
extern void newmenu(OFString *name);
@ -143,9 +143,10 @@ extern void fatal(OFString *s, OFString *o = @"");
extern void *alloc(int s);
// rendertext
extern void draw_text(char *str, int left, int top, int gl_num);
extern void draw_textf(char *fstr, int left, int top, int gl_num, ...);
extern int text_width(char *str);
extern void draw_text(OFString *string, int left, int top, int gl_num);
extern void draw_textf(
OFConstantString *format, int left, int top, int gl_num, ...);
extern int text_width(OFString *string);
extern void draw_envbox(int t, int fogdist);
// editing

View file

@ -361,18 +361,21 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
dblend -= curtime / 3;
if (dblend < 0)
dblend = 0;
};
}
glEnable(GL_TEXTURE_2D);
char *command = getcurcommand();
char *player = playerincrosshair();
if (command)
draw_textf("> %s_", 20, 1570, 2, command);
else if (closeent[0])
draw_text(closeent, 20, 1570, 2);
else if (player)
draw_text(player, 20, 1570, 2);
@autoreleasepool {
char *command = getcurcommand();
char *player = playerincrosshair();
if (command)
draw_textf(@"> %s_", 20, 1570, 2, command);
else if (closeent[0])
draw_text(@(closeent), 20, 1570, 2);
else if (player)
draw_text(@(player), 20, 1570, 2);
}
renderscores();
if (!rendermenu()) {
@ -387,7 +390,7 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
glColor3ub(255, 0, 0);
else if (player1->health <= 50)
glColor3ub(255, 128, 0);
};
}
float chsize = (float)crosshairsize;
glTexCoord2d(0.0, 0.0);
glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 - chsize);
@ -398,7 +401,7 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
glTexCoord2d(0.0, 1.0);
glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 + chsize);
glEnd();
};
}
glPopMatrix();
@ -410,22 +413,22 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
glPopMatrix();
glPushMatrix();
glOrtho(0, VIRTW * 3 / 2, VIRTH * 3 / 2, 0, -1, 1);
draw_textf("fps %d", 3200, 2390, 2, curfps);
draw_textf("wqd %d", 3200, 2460, 2, nquads);
draw_textf("wvt %d", 3200, 2530, 2, curvert);
draw_textf("evt %d", 3200, 2600, 2, xtraverts);
};
draw_textf(@"fps %d", 3200, 2390, 2, curfps);
draw_textf(@"wqd %d", 3200, 2460, 2, nquads);
draw_textf(@"wvt %d", 3200, 2530, 2, curvert);
draw_textf(@"evt %d", 3200, 2600, 2, xtraverts);
}
glPopMatrix();
if (player1->state == CS_ALIVE) {
glPushMatrix();
glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1);
draw_textf("%d", 90, 827, 2, player1->health);
draw_textf(@"%d", 90, 827, 2, player1->health);
if (player1->armour)
draw_textf("%d", 390, 827, 2, player1->armour);
draw_textf(@"%d", 390, 827, 2, player1->armour);
draw_textf(
"%d", 690, 827, 2, player1->ammo[player1->gunselect]);
@"%d", 690, 827, 2, player1->ammo[player1->gunselect]);
glPopMatrix();
glPushMatrix();
glOrtho(0, VIRTW, VIRTH, 0, -1, 1);
@ -442,10 +445,10 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
};
drawicon((float)(g * 64), (float)r, 1220, 1650);
glPopMatrix();
};
}
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
};
}

View file

@ -100,90 +100,116 @@ short char_coords[96][4] = {
};
int
text_width(char *str)
text_width(OFString *string)
{
int x = 0;
for (int i = 0; str[i] != 0; i++) {
int c = str[i];
if (c == '\t') {
x = (x + PIXELTAB) / PIXELTAB * PIXELTAB;
continue;
};
if (c == '\f')
continue;
if (c == ' ') {
x += FONTH / 2;
continue;
};
c -= 33;
if (c < 0 || c >= 95)
continue;
int in_width = char_coords[c][2] - char_coords[c][0];
x += in_width + 1;
@autoreleasepool {
const char *str = string.UTF8String;
size_t len = string.UTF8StringLength;
int x = 0;
for (int i = 0; i < len; i++) {
int c = str[i];
if (c == '\t') {
x = (x + PIXELTAB) / PIXELTAB * PIXELTAB;
continue;
}
if (c == '\f')
continue;
if (c == ' ') {
x += FONTH / 2;
continue;
}
c -= 33;
if (c < 0 || c >= 95)
continue;
int in_width = char_coords[c][2] - char_coords[c][0];
x += in_width + 1;
}
return x;
}
return x;
}
void
draw_textf(char *fstr, int left, int top, int gl_num, ...)
draw_textf(OFConstantString *format, int left, int top, int gl_num, ...)
{
sprintf_sdlv(str, gl_num, fstr);
draw_text(str, left, top, gl_num);
};
@autoreleasepool {
va_list arguments;
va_start(arguments, gl_num);
OFString *str = [[OFString alloc] initWithFormat:format
arguments:arguments];
va_end(arguments);
draw_text(str, left, top, gl_num);
}
}
void
draw_text(char *str, int left, int top, int gl_num)
draw_text(OFString *string, int left, int top, int gl_num)
{
glBlendFunc(GL_ONE, GL_ONE);
glBindTexture(GL_TEXTURE_2D, gl_num);
glColor3ub(255, 255, 255);
@autoreleasepool {
glBlendFunc(GL_ONE, GL_ONE);
glBindTexture(GL_TEXTURE_2D, gl_num);
glColor3ub(255, 255, 255);
int x = left;
int y = top;
int x = left;
int y = top;
int i;
float in_left, in_top, in_right, in_bottom;
int in_width, in_height;
int i;
float in_left, in_top, in_right, in_bottom;
int in_width, in_height;
for (i = 0; str[i] != 0; i++) {
int c = str[i];
if (c == '\t') {
x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left;
continue;
};
if (c == '\f') {
glColor3ub(64, 255, 128);
continue;
};
if (c == ' ') {
x += FONTH / 2;
continue;
};
c -= 33;
if (c < 0 || c >= 95)
continue;
const char *str = string.UTF8String;
size_t len = string.UTF8StringLength;
for (i = 0; i < len; i++) {
int c = str[i];
in_left = ((float)char_coords[c][0]) / 512.0f;
in_top = ((float)char_coords[c][1] + 2) / 512.0f;
in_right = ((float)char_coords[c][2]) / 512.0f;
in_bottom = ((float)char_coords[c][3] - 2) / 512.0f;
if (c == '\t') {
x = (x - left + PIXELTAB) / PIXELTAB *
PIXELTAB +
left;
continue;
}
in_width = char_coords[c][2] - char_coords[c][0];
in_height = char_coords[c][3] - char_coords[c][1];
if (c == '\f') {
glColor3ub(64, 255, 128);
continue;
}
glBegin(GL_QUADS);
glTexCoord2f(in_left, in_top);
glVertex2i(x, y);
glTexCoord2f(in_right, in_top);
glVertex2i(x + in_width, y);
glTexCoord2f(in_right, in_bottom);
glVertex2i(x + in_width, y + in_height);
glTexCoord2f(in_left, in_bottom);
glVertex2i(x, y + in_height);
glEnd();
if (c == ' ') {
x += FONTH / 2;
continue;
}
xtraverts += 4;
x += in_width + 1;
c -= 33;
if (c < 0 || c >= 95)
continue;
in_left = ((float)char_coords[c][0]) / 512.0f;
in_top = ((float)char_coords[c][1] + 2) / 512.0f;
in_right = ((float)char_coords[c][2]) / 512.0f;
in_bottom = ((float)char_coords[c][3] - 2) / 512.0f;
in_width = char_coords[c][2] - char_coords[c][0];
in_height = char_coords[c][3] - char_coords[c][1];
glBegin(GL_QUADS);
glTexCoord2f(in_left, in_top);
glVertex2i(x, y);
glTexCoord2f(in_right, in_top);
glVertex2i(x + in_width, y);
glTexCoord2f(in_right, in_bottom);
glVertex2i(x + in_width, y + in_height);
glTexCoord2f(in_left, in_bottom);
glVertex2i(x, y + in_height);
glEnd();
xtraverts += 4;
x += in_width + 1;
}
}
}

View file

@ -243,10 +243,10 @@ checkpings()
sgetstr();
strcpy_s(si.sdesc, text);
break;
};
};
};
};
}
}
}
}
int
sicompare(const serverinfo *a, const serverinfo *b)
@ -254,7 +254,7 @@ sicompare(const serverinfo *a, const serverinfo *b)
return a->ping > b->ping
? 1
: (a->ping < b->ping ? -1 : strcmp(a->name, b->name));
};
}
void
refreshservers()
@ -285,11 +285,13 @@ refreshservers()
si.name);
}
si.full[50] = 0; // cut off too long server descriptions
menumanual(1, i, si.full);
@autoreleasepool {
menumanual(1, i, @(si.full));
}
if (!--maxmenu)
return;
};
};
}
}
void
servermenu()