First of all; I think that textui (the stuff in ui-*.[ch] files) is actually a pretty good UI. It performs its functions well, and, IMO, it looks nice (in an old school kind of way ). It's also a rather significant amount of tested, debugged code. Therefore, I think it's desirable to use as much of it as possible. Textui, however, does have significant technical problems. I assume that the readers of this post are well aware of what these problems are.
Here's my tentative plan for textui reform.
(or something like that; note that term_stack is NOT a replacement for angband_term[]; the latter will still exist). Some new functions will be added to ui-term.c (or, perhaps, to ui2-term.c); the main ones are Term_push(term *, struct term_hints *) and Term_pop(void). They will replace two constructions currently used in textui; the first one is
a) Term_activate
instead, it will become:
I think this is pretty self-explanatory. Another, much more important role for Term_push() and Term_pop() is
b) screen_save and screen_load
saving and loading term_screen is used mainly (or exclusively?) by menu code, to draw menus directly on term_screen. If we want to eliminate tile_width/tile_height, menu code cannot use term_screen; the main term should display only one thing - the dungeon. That is, term_screen cannot have messages, sidebars, status lines or menus on it. It's easy to make sidebar or message line just use their own, separate terms (perhaps 'docked' to term_screen). But how to display menus? I propose they also use their own terms:
pushing a NULL term creates a new, termporary, empty term on top of the stack of terms (and a new subwindow appears on display). The menu code can use this term like it uses term_screen currently (Term_get_size(), Term_erase(), Term_gotoxy(), Term_putstr() should all work, and the menu still will be displayed in the new subwindow). Popping this temporary term frees it (and the subwindow disappears).
Why "term_hints"? Well, maybe the new term cannot be centered in term_screen; perhaps term_screen is too small and in the corner of display. Or maybe the frontend (main-something.c) doesn't know how to draw borders. In any case, the frontend should try to display menus in an attractive and useful way, based on information in hints.
I hope this will allow to use current menu code almost as it is; just screen_save() and screen_load() will have to be replaced by Term_push() and Term_pop(), and also stuff like region_erase() won't be necessary. menu_refresh() also does this thing:
New term can just be cleared (Term_clear()) instead.
Actually, even stuff like Term->wid can be used pretty much as it is:
term_stack.stack[0] is always term_screen; trying to pop it (making the stack completely empty) is a fatal error. OTOH, pushing term_screen on top of term_screen in the stack is ok (just like currently with term *old = Term; Term_activate(term_screen); Term_activate(old); old can already be term_screen).
That doesn't have to be a big stack. With current textui the max depth of the stack would be what, 3? (term_screen, store interface and item description, for instance)
By far the most useful moment to update the screen is immediately before trying to get user input. That is, before pressing a key the user should know the latest information about the state of the game. In simpler words: Term_redraw_screen() should be called before calling inkey_ex().
Another useful moment to refresh the screen is various animations. Splashscreen ("Initializing monsters" etc) is also an animation in this sense.
In most other situations redrawing the screen is not so useful; in particular, it's not a useful thing to do after updating a single tile of the map.
The hackery with "a / MAX_COLORS", "a % MAX_COLORS" is just bizarre. Term->text_hook() should get tap and tcp just like pict_hook. Even if it's not very useful now, it may become useful over time. attrs could be something better than ints: for instance, uint32_t (one byte for each of red, green, blue, alpha channels). More colors will allow to achieve some interesting cosmetic effects; slightly transparent clear icky things, lantern light that gradually becomes more dim with the distance from the player, etc. Or how about a DCSS-style minimap?
uint32_t could still be used for the "a / MAX_COLORS" thing in some kind of "low color mode" (perhaps a new grafmode).
Those massive tilesets look like a pain to update or modify. How about splitting them into separate files? The .prf file could look like this:
The frontend will have to read all .png files, create an array of pixels, fill it with contents of files (in this particular order) and remember offsets into it. ui-prefs.c will ignore the file name part (*.png), and use the file number part (that precedes the file name) as char. attr then could be used for color moding the tile, or just be unused.
Actually, the file number part is just for illustration; it's not even necessary.
Well, tilesets currently work ok so I guess that's not that urgent.
So that't my current plan for textui reform. The main change is that any frontend is assumed to be capable of creating and destroying subwindows at will; I know that at least one official frontend can do it (main-sdl.c), and of course main-sdl2.c doesn't have any problems with that either. I don't see why any graphical frontend would find it too difficult. Ncurses, OTOH, needs the functionality of Term_load() and Term_save(). It's probably possible to implement fallback to term_win_copy() for backwards compatibility, but right now that doesn't concern me.
Here's my tentative plan for textui reform.
term_screen
Global Term will be replaced by global stack of termsCode:
static struct { term *stack[TERM_STACK_MAX]; size_t top; } term_stack = { .stack[0] = term_screen };
a) Term_activate
Code:
term *old = Term; Term_activate(inv_term); ... Term_activate(old);
Code:
Term_push(inv_term, NULL); ... Term_pop();
b) screen_save and screen_load
Code:
screen_save(); ... menu_select(); ... screen_load();
Code:
/* that replaces screen_save() */ struct term_hints hints = { .new = true, .width = 80, .height = 24, .position = TERM_POSITION_CENTERED_IN_TERM_SCREEN, .borders = true }; Term_push(NULL, &hints); /* create new term for menu */ ... menu_select(); /* do the usual thing */ ... /* that replaces screen_load() */ Term_pop(); /* destroy temporary term */
Why "term_hints"? Well, maybe the new term cannot be centered in term_screen; perhaps term_screen is too small and in the corner of display. Or maybe the frontend (main-something.c) doesn't know how to draw borders. In any case, the frontend should try to display menus in an attractive and useful way, based on information in hints.
I hope this will allow to use current menu code almost as it is; just screen_save() and screen_load() will have to be replaced by Term_push() and Term_pop(), and also stuff like region_erase() won't be necessary. menu_refresh() also does this thing:
Code:
if (reset_screen) { screen_load(); screen_save(); }
Actually, even stuff like Term->wid can be used pretty much as it is:
Code:
#define Term \ Term_top() term *Term_top(void) { return term_stack.stack[term_stack.top]; }
That doesn't have to be a big stack. With current textui the max depth of the stack would be what, 3? (term_screen, store interface and item description, for instance)
Term_fresh() reform
Currently, Term_fresh() performs two functions; it flushes term's contents (via Term_fresh_row_*()) and it demands that (as main-xxx.c says) "This action should make sure that all 'output' to the window will actually appear on the window". The latter is impossible. Therefore, Term_fresh() should be split into two functions: Term_fresh() (much like it is now, but without calling Term_xtra()) and Term_redraw_screen():Code:
void Term_redraw_screen(void) { Term_xtra(TERM_XTRA_REDRAW, 0); }
Another useful moment to refresh the screen is various animations. Splashscreen ("Initializing monsters" etc) is also an animation in this sense.
In most other situations redrawing the screen is not so useful; in particular, it's not a useful thing to do after updating a single tile of the map.
Colors reform
In text mode, angband's terrain has only 3 different colors, and two of those are used for hybrid_walls/solid_walls. Why doesn't text hook make any use of term_win->vta? Current situation:Code:
struct term { errr (*text_hook)(int x, int y, int n, int a, const wchar_t *s); errr (*pict_hook)(int x, int y, int n, const int *ap, const wchar_t *cp, const int *tap, const wchar_t *tcp); }
uint32_t could still be used for the "a / MAX_COLORS" thing in some kind of "low color mode" (perhaps a new grafmode).
Tilesets
Note that if term_screen can only ever have either tiles or text, the job of ui2-term.c is significantly simplified:Code:
errr Term_fresh(void) { ... if (use_graphics) { Term_fresh_row_pict(); } else { Term_fresh_row_text(); } ... }
Code:
# <darkness> feat:0:*:0:Something.png # open floor feat:1:torch:1:Open floor torch.png feat:1:los:2:Open floor los.png feat:1:lit:3:Open floor lit.png feat:1:dark:4:Open floor dark.png something:...:5:filename.png something:...:6:filename.png something:...:7:filename.png
Actually, the file number part is just for illustration; it's not even necessary.
Well, tilesets currently work ok so I guess that's not that urgent.
Input
Why do terms have key_queue at all? Only one term ever does something with it. term_screen's key_queue may just as well be separated from term_screen and become its own thing.So that't my current plan for textui reform. The main change is that any frontend is assumed to be capable of creating and destroying subwindows at will; I know that at least one official frontend can do it (main-sdl.c), and of course main-sdl2.c doesn't have any problems with that either. I don't see why any graphical frontend would find it too difficult. Ncurses, OTOH, needs the functionality of Term_load() and Term_save(). It's probably possible to implement fallback to term_win_copy() for backwards compatibility, but right now that doesn't concern me.
Comment