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; @property (nonatomic) bool loaded;
- (bool)loadWithIRI:(OFIRI *)IRI; - (bool)loadWithIRI:(OFIRI *)IRI;
- (void)renderWithLight:(OFVector3D &)light - (void)renderWithLight:(OFVector3D)light
frame:(int)frame frame:(int)frame
range:(int)range range:(int)range
x:(float)x x:(float)x

View file

@ -24,6 +24,12 @@ struct md2_frame {
md2_vertex vertices[1]; md2_vertex vertices[1];
}; };
static float
snap(int sn, float f)
{
return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f;
}
@implementation MD2 { @implementation MD2 {
int _numGlCommands; int _numGlCommands;
int *_glCommands; 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 - (void)scaleWithFrame:(int)frame scale:(float)scale snap:(int)sn
{ {
_mverts[frame] = new OFVector3D[_numVerts]; _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 frame:(int)frame
range:(int)range range:(int)range
x:(float)x 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, 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_LAGGED ? "LAG" : lag, d->ping, d->team,
d->state == CS_DEAD ? name : d->name); 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; const int maxteams = 4;
char *teamname[maxteams]; char *teamname[maxteams];
@ -141,7 +143,7 @@ renderscores()
if (!demoplayback) if (!demoplayback)
renderscore(player1); renderscore(player1);
loopv(players) if (players[i]) renderscore(players[i]); loopv(players) if (players[i]) renderscore(players[i]);
sortmenu(0, scorelines.length()); sortmenu();
if (m_teammode) { if (m_teammode) {
teamsused = 0; teamsused = 0;
loopv(players) addteamscore(players[i]); loopv(players) addteamscore(players[i]);
@ -152,11 +154,13 @@ renderscores()
{ {
sprintf_sd(sc)("[ %s: %d ]", teamname[j], teamscore[j]); sprintf_sd(sc)("[ %s: %d ]", teamname[j], teamscore[j]);
strcat_s(teamscores, sc); strcat_s(teamscores, sc);
}; }
menumanual(0, scorelines.length(), ""); menumanual(0, scorelines.length(), @"");
menumanual(0, scorelines.length() + 1, teamscores); @autoreleasepool {
}; menumanual(0, scorelines.length() + 1, @(teamscores));
}; }
}
}
// sendmap/getmap commands, should be replaced by more intuitive map downloading // 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; refs[nd++] = conlines[i].cref;
if (nd == ndraw) if (nd == ndraw)
break; break;
}; }
@autoreleasepool {
loopj(nd) loopj(nd)
{ {
draw_text(refs[j], FONTH / 3, draw_text(@(refs[j]), FONTH / 3,
(FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2); (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2);
}; }
}; }
}
// keymap is defined externally in keymap.cfg // keymap is defined externally in keymap.cfg
@ -164,11 +166,12 @@ void
history(int n) history(int n)
{ {
static bool rec = false; static bool rec = false;
if (!rec && n >= 0 && n < vhistory.length()) { if (!rec && n >= 0 && n < vhistory.length()) {
rec = true; rec = true;
execute(vhistory[vhistory.length() - n - 1]); execute(vhistory[vhistory.length() - n - 1]);
rec = false; rec = false;
}; }
} }
COMMAND(history, ARG_1INT) COMMAND(history, ARG_1INT)

View file

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

View file

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

View file

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

View file

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

View file

@ -100,40 +100,57 @@ short char_coords[96][4] = {
}; };
int int
text_width(char *str) text_width(OFString *string)
{ {
@autoreleasepool {
const char *str = string.UTF8String;
size_t len = string.UTF8StringLength;
int x = 0; int x = 0;
for (int i = 0; str[i] != 0; i++) { for (int i = 0; i < len; i++) {
int c = str[i]; int c = str[i];
if (c == '\t') { if (c == '\t') {
x = (x + PIXELTAB) / PIXELTAB * PIXELTAB; x = (x + PIXELTAB) / PIXELTAB * PIXELTAB;
continue; continue;
}; }
if (c == '\f') if (c == '\f')
continue; continue;
if (c == ' ') { if (c == ' ') {
x += FONTH / 2; x += FONTH / 2;
continue; continue;
}; }
c -= 33; c -= 33;
if (c < 0 || c >= 95) if (c < 0 || c >= 95)
continue; continue;
int in_width = char_coords[c][2] - char_coords[c][0]; int in_width = char_coords[c][2] - char_coords[c][0];
x += in_width + 1; x += in_width + 1;
} }
return x; return x;
}
} }
void 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); @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); draw_text(str, left, top, gl_num);
}; }
}
void void
draw_text(char *str, int left, int top, int gl_num) draw_text(OFString *string, int left, int top, int gl_num)
{ {
@autoreleasepool {
glBlendFunc(GL_ONE, GL_ONE); glBlendFunc(GL_ONE, GL_ONE);
glBindTexture(GL_TEXTURE_2D, gl_num); glBindTexture(GL_TEXTURE_2D, gl_num);
glColor3ub(255, 255, 255); glColor3ub(255, 255, 255);
@ -145,20 +162,28 @@ draw_text(char *str, int left, int top, int gl_num)
float in_left, in_top, in_right, in_bottom; float in_left, in_top, in_right, in_bottom;
int in_width, in_height; int in_width, in_height;
for (i = 0; str[i] != 0; i++) { const char *str = string.UTF8String;
size_t len = string.UTF8StringLength;
for (i = 0; i < len; i++) {
int c = str[i]; int c = str[i];
if (c == '\t') { if (c == '\t') {
x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left; x = (x - left + PIXELTAB) / PIXELTAB *
PIXELTAB +
left;
continue; continue;
}; }
if (c == '\f') { if (c == '\f') {
glColor3ub(64, 255, 128); glColor3ub(64, 255, 128);
continue; continue;
}; }
if (c == ' ') { if (c == ' ') {
x += FONTH / 2; x += FONTH / 2;
continue; continue;
}; }
c -= 33; c -= 33;
if (c < 0 || c >= 95) if (c < 0 || c >= 95)
continue; continue;
@ -185,6 +210,7 @@ draw_text(char *str, int left, int top, int gl_num)
xtraverts += 4; xtraverts += 4;
x += in_width + 1; x += in_width + 1;
} }
}
} }
// also Don's code, so goes in here too :) // also Don's code, so goes in here too :)

View file

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