Permalink
Fetching contributors…
Cannot retrieve contributors at this time
6160 lines (5066 sloc) 159 KB
/**
* \file main-sdl2.c
* \brief Angband SDL2 port
*
* Copyright (c) 1997 Ben Harrison and others
*
* This work is free software; you can redistribute it and/or modify it
* under the terms of either:
*
* a) the GNU General Public License as published by the Free Software
* Foundation, version 2, or
*
* b) the "Angband licence":
* This software may be copied and distributed for educational, research,
* and not for profit purposes provided that this copyright and statement
* are included in all such copies. Other copyrights may also apply.
*/
#include "angband.h"
#ifdef USE_SDL2
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
#include "init.h"
#include "ui-term.h"
#include "buildid.h"
#include "ui-display.h"
#include "ui-command.h"
#include "player-calcs.h"
#include "ui-output.h"
#include "game-world.h"
#include "ui-input.h"
#include "ui-prefs.h"
#include "grafmode.h"
#include "ui-game.h"
#include "ui-map.h"
#include "parser.h"
#define MAX_SUBWINDOWS \
ANGBAND_TERM_MAX
/* that should be plenty... */
#define MAX_WINDOWS 4
#define MAX_FONTS 128
#define MAX_BUTTONS 32
#define INIT_SDL_FLAGS \
(SDL_INIT_VIDEO)
#define INIT_IMG_FLAGS \
(IMG_INIT_PNG)
/* this is the main term screen, where all the action takes place */
#define MAIN_SUBWINDOW 0
/* for symmetry with main subwindow */
#define MAIN_WINDOW 0
/* size of the keypress queue (term->key_queue) */
#define SUBWINDOW_KEYQ_SIZE(subwindow_ptr) \
((subwindow_ptr)->index == MAIN_SUBWINDOW ? 1024 : 32)
#define DEFAULT_ATTR_BLANK \
COLOUR_WHITE
#define DEFAULT_CHAR_BLANK ' '
#define DEFAULT_DISPLAY 0
#define DEFAULT_CONFIG_FILE "sdl2init.txt"
#define DEFAULT_CONFIG_FILE_DIR \
ANGBAND_DIR_USER
#define DEFAULT_ALPHA_FULL 0xFF
#define ALPHA_PERCENT(p) \
(DEFAULT_ALPHA_FULL * (p) / 100)
#define DEFAULT_ALPHA_LOW \
ALPHA_PERCENT(80)
/* for "Alpha" button; in percents */
#define DEFAULT_ALPHA_STEP 10
#define DEFAULT_ALPHA_LOWEST 0
#define DEFAULT_WALLPAPER "att-128.png"
#define DEFAULT_WALLPAPER_DIR \
ANGBAND_DIR_ICONS
#define DEFAULT_WINDOW_ICON "att-32.png"
#define DEFAULT_WINDOW_ICON_DIR \
ANGBAND_DIR_ICONS
#define DEFAULT_ABOUT_ICON "att-128.png"
#define DEFAULT_ABOUT_ICON_DIR \
ANGBAND_DIR_ICONS
#define DEFAULT_FONT_HINTING \
TTF_HINTING_LIGHT
/* border of subwindows, in pixels */
#define DEFAULT_BORDER 8
#define DEFAULT_XTRA_BORDER \
(DEFAULT_BORDER * 2)
#define DEFAULT_VISIBLE_BORDER 2
#define DEFAULT_FONT_SIZE 0
/* XXX hack: the widest character present in a font
* for determining font advance (width) */
#define GLYPH_FOR_ADVANCE 'W'
#define GLYPH_PADDING 1
#define DEFAULT_VECTOR_FONT_SIZE 12
#define DEFAULT_FONT "10x20x.fon"
#define DEFAULT_FONT_W \
(10 + 2 * GLYPH_PADDING)
#define DEFAULT_FONT_H \
(20 + 2 * GLYPH_PADDING)
#define DEFAULT_STATUS_BAR_FONT "8x13x.fon"
#define DEFAULT_STATUS_BAR_FONT_W \
(8 + 2 * GLYPH_PADDING)
#define DEFAULT_STATUS_BAR_FONT_H \
(13 + 2 * GLYPH_PADDING)
#define MAX_VECTOR_FONT_SIZE 24
#define MIN_VECTOR_FONT_SIZE 4
#define DEFAULT_BUTTON_BORDER 8
#define DEFAULT_LINE_HEIGHT(h) ((h) * 150 / 100)
#define DEFAULT_MENU_LINE_HEIGHT(h) ((h) * 200 / 100)
#define DEFAULT_MENU_LINE_WIDTH(w) \
((w) + DEFAULT_BUTTON_BORDER + DEFAULT_XTRA_BORDER)
/* update period in window delays (160 milliseconds, assuming 60 fps) */
#define DEFAULT_IDLE_UPDATE_PERIOD 10
#define DEFAULT_WINDOW_BG_COLOR \
COLOUR_L_DARK
#define DEFAULT_SUBWINDOW_BG_COLOR \
COLOUR_DARK
#define DEFAULT_SUBWINDOW_CURSOR_COLOR \
COLOUR_YELLOW
#define DEFAULT_STATUS_BAR_BG_COLOR \
COLOUR_DARK
#define DEFAULT_SHADE_COLOR \
COLOUR_SHADE
#define DEFAULT_SUBWINDOW_BORDER_COLOR \
COLOUR_SHADE
#define DEFAULT_STATUS_BAR_BUTTON_ACTIVE_COLOR \
COLOUR_WHITE
#define DEFAULT_STATUS_BAR_BUTTON_INACTIVE_COLOR \
COLOUR_L_DARK
#define DEFAULT_MENU_FG_ACTIVE_COLOR \
COLOUR_WHITE
#define DEFAULT_MENU_FG_INACTIVE_COLOR \
COLOUR_WHITE
#define DEFAULT_MENU_BG_ACTIVE_COLOR \
COLOUR_SHADE
#define DEFAULT_MENU_BG_INACTIVE_COLOR \
COLOUR_DARK
#define DEFAULT_MENU_TOGGLE_FG_ACTIVE_COLOR \
COLOUR_WHITE
#define DEFAULT_MENU_TOGGLE_FG_INACTIVE_COLOR \
COLOUR_L_DARK
#define DEFAULT_MENU_PANEL_OUTLINE_COLOR \
COLOUR_SHADE
#define DEFAULT_ERROR_COLOR \
COLOUR_RED
#define DEFAULT_ABOUT_BG_COLOR \
COLOUR_SHADE
#define DEFAULT_ABOUT_BORDER_OUTER_COLOR \
COLOUR_L_DARK
#define DEFAULT_ABOUT_BORDER_INNER_COLOR \
COLOUR_WHITE
#define DEFAULT_ABOUT_TEXT_COLOR \
COLOUR_WHITE
/* shockbolt's tiles are 64x64; dungeon is 198 tiles long;
* 64 * 198 is 12672 which is bigger than any possible texture! */
#define REASONABLE_MAP_TILE_WIDTH 16
#define REASONABLE_MAP_TILE_HEIGHT 16
/* angband needs at least 80x24 main term, else severe bugs happen */
#define MIN_COLS_MAIN 80
#define MIN_ROWS_MAIN 24
/* some reasonable values - we dont want the player to resize
* the term into nothing! */
#define MIN_COLS_OTHER 12
#define MIN_ROWS_OTHER 3
#define MIN_TILE_WIDTH 1
#define MAX_TILE_WIDTH 9
#define MIN_TILE_HEIGHT 1
#define MAX_TILE_HEIGHT 9
/* some random numbers */
#define DEFAULT_WINDOW_MINIMUM_W 198
#define DEFAULT_WINDOW_MINIMUM_H 66
#define DEFAULT_SNAP_RANGE \
DEFAULT_FONT_W
#define CHECK_BUTTON_GROUP_TYPE(button, button_group, data_type) \
do { \
assert((button)->info.group == (button_group)); \
assert((button)->info.type == (data_type)); \
} while (0)
enum wallpaper_mode {
/* so that we won't forget to actually set wallpaper */
WALLPAPER_INVALID = 0,
WALLPAPER_DONT_SHOW,
WALLPAPER_TILED,
WALLPAPER_CENTERED,
WALLPAPER_SCALED
};
enum button_data_type {
BUTTON_DATA_INVALID = 0,
BUTTON_DATA_NONE,
BUTTON_DATA_IVAL,
BUTTON_DATA_UVAL,
BUTTON_DATA_SUBVAL,
BUTTON_DATA_FONTVAL,
BUTTON_DATA_TERMVAL,
BUTTON_DATA_ALPHAVAL
};
enum button_group {
BUTTON_GROUP_INVALID = 0,
BUTTON_GROUP_NONE,
BUTTON_GROUP_MOVESIZE,
BUTTON_GROUP_SUBWINDOWS,
BUTTON_GROUP_MENU
};
enum button_movesize {
BUTTON_MOVESIZE_INVALID = 0,
BUTTON_MOVESIZE_MOVING,
BUTTON_MOVESIZE_SIZING
};
enum button_tile_scale {
BUTTON_TILE_SIZE_INVALID = 0,
BUTTON_TILE_SIZE_WIDTH,
BUTTON_TILE_SIZE_HEIGHT
};
enum button_caption_position {
CAPTION_POSITION_INVALID = 0,
CAPTION_POSITION_CENTER,
CAPTION_POSITION_LEFT,
CAPTION_POSITION_RIGHT
};
enum font_type {
FONT_TYPE_INVALID = 0,
FONT_TYPE_RASTER,
FONT_TYPE_VECTOR
};
struct ttf {
TTF_Font *handle;
/* this is not a real rect for glyphs;
* x and y are offsets to add to coords of where glyph/string should be rendered;
* f and h are glyph metrics but include glyph padding
* (for calculating rows/cols, mostly) */
SDL_Rect glyph;
};
/* the array of ascii chars was generated by this perl script:
* my $ascii = join '', map /\p{Print}/ ? $_ : ' ', map chr, 0 .. 127;
* for (my $i = 0; $i < length($ascii); $i += 40) {
* my $s = substr $ascii, $i, 40;
* $s =~ s#\\#\\\\#g;
* $s =~ s#"#\\"#g;
* print qq(\t"$s"\n);
* }
*/
static const char g_ascii_codepoints_for_cache[] =
" !\"#$%&'"
"()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO"
"PQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvw"
"xyz{|}~ ";
/* Simple font cache. Only for ascii (which is like 99.99% (?) of what the game
* displays, anyway) */
#define ASCII_CACHE_SIZE \
(N_ELEMENTS(g_ascii_codepoints_for_cache) - 1)
struct font_cache {
SDL_Texture *texture;
/* it wastes some space... so what? */
SDL_Rect rects[ASCII_CACHE_SIZE];
};
/* 0 is also a valid codepoint, of course... that's just for finding bugs */
#define IS_CACHED_ASCII_CODEPOINT(c) \
((c) > 0 && (c) < ASCII_CACHE_SIZE)
struct font {
struct ttf ttf;
char *name;
char *path;
int size;
/* index of font in g_font_info array */
size_t index;
struct font_cache cache;
};
struct subwindow_border {
bool visible;
bool error;
SDL_Color color;
int width;
};
struct subwindow_config {
char *font_name;
int font_size;
};
struct window_config {
int renderer_flags;
int renderer_index;
int window_flags;
char *wallpaper_path;
char *font_name;
int font_size;
};
/* struct subwindow is representation of angband's term */
struct subwindow {
bool inited;
bool loaded;
bool linked;
bool visible;
struct subwindow_config *config;
/* top in z-order */
bool top;
bool always_top;
unsigned index;
int rows;
int cols;
/* struct ttf also has this information; these members are
* just for convinience */
int font_width;
int font_height;
/* coordinates of full rect are relative to coordinates of window
* (basically, full rect is texture) */
SDL_Rect full_rect;
/* coordinates of inner rect are relative to that of full rect */
SDL_Rect inner_rect;
/* for use when resizing term */
SDL_Rect sizing_rect;
/* a one pixel texture, mostly for displaying something when
* the player is resizing term */
SDL_Texture *aux_texture;
/* background color */
SDL_Color color;
struct subwindow_border borders;
SDL_Texture *texture;
struct font *font;
struct window *window;
struct term *term;
};
struct button;
struct button_bank {
struct button *buttons;
size_t size;
size_t number;
};
struct menu_panel {
SDL_Rect rect;
struct button_bank button_bank;
struct menu_panel *next;
};
typedef bool (*button_click)(struct window *window,
struct button *button);
typedef void (*button_render)(const struct window *window,
struct button *button);
typedef bool (*button_event)(struct window *window,
struct button *button, const SDL_Event *event);
typedef void (*button_menu)(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel);
struct fontval {
struct subwindow *subwindow;
/* index of font in g_font_info array */
size_t index;
bool size_ok;
};
struct termval {
struct subwindow *subwindow;
u32b value;
};
struct alphaval {
struct subwindow *subwindow;
int real_value;
int show_value;
};
struct button_info {
enum button_data_type type;
union {
int ival;
unsigned uval;
struct subwindow *subval;
struct fontval fontval;
struct termval termval;
struct alphaval alphaval;
} data;
enum button_group group;
};
struct menu_elem {
const char *caption;
struct button_info info;
button_render on_render;
button_menu on_menu;
};
struct button_callbacks {
/* this function should render the button;
* otherwise, the button will be invisible */
button_render on_render;
/* optional custom event handler */
button_event on_event;
/* if there is no custon handler,
* this function should do something when button is clicked
* otherwise, the button will be useless */
button_click on_click;
/* for use only with menu; only custom events */
button_menu on_menu;
};
struct button {
/* selected means the user pointed at button and pressed
* mouse button (but not released yet) */
bool selected;
/* highlighted means the user pointed at button but
* not clicking yet */
bool highlighted;
char *caption;
SDL_Rect full_rect;
SDL_Rect inner_rect;
struct button_info info;
struct button_callbacks callbacks;
};
struct status_bar {
struct font *font;
struct button_bank button_bank;
struct menu_panel *menu_panel;
struct window *window;
SDL_Rect full_rect;
SDL_Rect inner_rect;
SDL_Color color;
SDL_Texture *texture;
bool in_menu;
};
struct graphics {
SDL_Texture *texture;
int id;
int tile_pixel_w;
int tile_pixel_h;
int overdraw_row;
int overdraw_max;
};
/* thats for dragging terms */
struct move_state {
bool active;
bool moving;
int originx;
int originy;
struct subwindow *subwindow;
};
/* thats for resizing terms */
struct size_state {
bool active;
bool sizing;
int originx;
int originy;
bool left;
bool top;
struct subwindow *subwindow;
};
struct wallpaper {
int w;
int h;
SDL_Texture *texture;
enum wallpaper_mode mode;
};
/* struct window is a real window on screen, it has one or more
* subwindows (terms) in it */
struct window {
bool inited;
bool loaded;
/* id is SDL's id, for use with events */
Uint32 id;
/* and this is our id, mostly for debugging */
unsigned index;
struct window_config *config;
/* toggles visibility of cursor */
bool cursor;
/* does window have mouse focus? */
bool focus;
/* window has changed and must be redrawn */
bool dirty;
/* limiter for frames */
Uint32 next_redraw;
/* from display mode */
int delay;
/* as reported by SDL_GetWindowFlags() */
Uint32 flags;
/* position and size of window as it is on display */
SDL_Rect full_rect;
/* size of window without status bar, basically */
SDL_Rect inner_rect;
SDL_Color color;
/* for making terms transparent while moving or sizing them */
Uint8 alpha;
SDL_Window *window;
SDL_Renderer *renderer;
int pixelformat;
struct wallpaper wallpaper;
struct move_state move_state;
struct size_state size_state;
struct status_bar status_bar;
struct graphics graphics;
struct subwindow *subwindows[MAX_SUBWINDOWS];
};
struct font_info {
char *name;
char *path;
int size;
size_t index;
enum font_type type;
bool loaded;
};
/* there are also global arrays of subwindows and windows
* those are at the end of the file */
const char help_sdl2[] = "SDL2 frontend";
static SDL_Color g_colors[MAX_COLORS];
static struct font_info g_font_info[MAX_FONTS];
/* Forward declarations */
static void init_globals(void);
static void free_globals(void);
static bool read_config_file(void);
static void dump_config_file(void);
static void init_colors(void);
static void start_windows(void);
static void start_window(struct window *window);
static void load_font(struct font *font);
static bool reload_font(struct subwindow *subwindow,
const struct font_info *info);
static void free_font(struct font *font);
static const struct font_info *find_font_info(const char *name);
static void get_string_metrics(struct font *font, const char *str, int *w, int *h);
static struct window *get_new_window(unsigned index);
static void wipe_window(struct window *window, int display);
/* create default config for spawning a window via gui */
static void wipe_window_aux_config(struct window *window);
static void adjust_window_geometry(struct window *window);
static void free_window(struct window *window);
static struct window *get_window_by_id(Uint32 id);
static struct window *get_window_direct(unsigned index);
static bool has_visible_subwindow(const struct window *window, unsigned index);
static void resize_window(struct window *window, int w, int h);
static struct subwindow *get_new_subwindow(unsigned index);
static void load_subwindow(struct window *window, struct subwindow *subwindow);
static bool is_subwindow_loaded(unsigned index);
static struct subwindow *transfer_subwindow(struct window *window, unsigned index);
static struct subwindow *get_subwindow_by_xy(const struct window *window, int x, int y);
static struct subwindow *get_subwindow_by_index(const struct window *window,
unsigned index, bool visible);
static struct subwindow *get_subwindow_direct(unsigned index);
/* this function loads new subwindow if it's not already loaded */
static struct subwindow *make_subwindow(struct window *window, unsigned index);
static void sort_to_top(struct window *window);
static void bring_to_top(struct window *window, struct subwindow *subwindow);
static void render_borders(struct subwindow *subwindow);
static SDL_Texture *load_image(const struct window *window, const char *path);
static void reload_all_graphics(graphics_mode *mode);
static void free_graphics(struct graphics *graphics);
static void load_terms(void);
static void load_term(struct subwindow *subwindow);
static void clear_pw_flag(struct subwindow *subwindow);
static bool adjust_subwindow_geometry(const struct window *window,
struct subwindow *subwindow);
static bool is_ok_col_row(const struct subwindow *subwindow,
const SDL_Rect *rect, int cell_w, int cell_h);
static void resize_rect(SDL_Rect *rect,
int left, int top, int right, int bottom);
static bool is_point_in_rect(int x, int y, const SDL_Rect *rect);
static bool is_close_to(int a, int b, unsigned range);
static bool is_over_status_bar(const struct status_bar *status_bar, int x, int y);
static void make_button_bank(struct button_bank *bank);
static void free_button_bank(struct button_bank *button_bank);
static void free_menu_panel(struct menu_panel *menu_panel);
static struct menu_panel *get_menu_panel_by_xy(struct menu_panel *menu_panel,
int x, int y);
static void refresh_angband_terms(void);
static void handle_quit(void);
static void wait_anykey(void);
/* Functions */
static void render_clear(const struct window *window,
SDL_Texture *texture, const SDL_Color *color)
{
SDL_SetRenderTarget(window->renderer, texture);
SDL_SetRenderDrawColor(window->renderer,
color->r, color->g, color->b, color->a);
SDL_RenderClear(window->renderer);
}
static void render_wallpaper_tiled(const struct window *window)
{
SDL_SetRenderTarget(window->renderer, NULL);
SDL_Rect rect = {0, 0, window->wallpaper.w, window->wallpaper.h};
for (rect.y = window->inner_rect.y;
rect.y < window->inner_rect.h;
rect.y += rect.h)
{
for (rect.x = window->inner_rect.x;
rect.x < window->inner_rect.w;
rect.x += rect.w)
{
SDL_RenderCopy(window->renderer, window->wallpaper.texture, NULL, &rect);
}
}
}
static void render_wallpaper_scaled(const struct window *window)
{
SDL_SetRenderTarget(window->renderer, NULL);
SDL_RenderCopy(window->renderer, window->wallpaper.texture, NULL, NULL);
}
static void render_wallpaper_centered(const struct window *window)
{
SDL_Rect rect;
rect.w = window->wallpaper.w;
rect.h = window->wallpaper.h;
rect.x = window->inner_rect.x + (window->inner_rect.w - rect.w) / 2;
rect.y = window->inner_rect.y + (window->inner_rect.h - rect.h) / 2;
SDL_SetRenderTarget(window->renderer, NULL);
SDL_RenderCopy(window->renderer, window->wallpaper.texture, NULL, &rect);
}
static void render_background(const struct window *window)
{
render_clear(window, NULL, &window->color);
switch (window->wallpaper.mode) {
case WALLPAPER_DONT_SHOW:
return;
case WALLPAPER_TILED:
render_wallpaper_tiled(window);
return;
case WALLPAPER_CENTERED:
render_wallpaper_centered(window);
return;
case WALLPAPER_SCALED:
render_wallpaper_scaled(window);
return;
default:
quit_fmt("bad wallpaper mode %d in window %u",
window->wallpaper.mode, window->index);
break;
}
}
static void render_all(const struct window *window)
{
render_background(window);
SDL_RenderCopy(window->renderer,
window->status_bar.texture, NULL, &window->status_bar.full_rect);
for (size_t i = 0; i < N_ELEMENTS(window->subwindows); i++) {
struct subwindow *subwindow = window->subwindows[i];
if (subwindow != NULL && subwindow->visible) {
SDL_RenderCopy(window->renderer,
subwindow->texture,
NULL, &subwindow->full_rect);
}
}
}
static void render_status_bar(const struct window *window)
{
render_clear(window, window->status_bar.texture, &window->status_bar.color);
for (size_t i = 0; i < window->status_bar.button_bank.number; i++) {
struct button *button = &window->status_bar.button_bank.buttons[i];
if (button->callbacks.on_render != NULL) {
button->callbacks.on_render(window, button);
}
}
}
static void render_outline_rect(const struct window *window,
SDL_Texture *texture, const SDL_Rect *rect, const SDL_Color *color)
{
SDL_SetRenderTarget(window->renderer, texture);
SDL_SetRenderDrawColor(window->renderer,
color->r, color->g, color->b, color->a);
SDL_RenderDrawRect(window->renderer, rect);
}
static void render_outline_rect_width(const struct window *window,
SDL_Texture *texture, const SDL_Rect *rect, const SDL_Color *color, int width)
{
SDL_Rect dst = *rect;
for (int i = 0; i < width; i++) {
render_outline_rect(window, texture, &dst, color);
resize_rect(&dst, 1, 1, -1, -1);
}
}
static void render_fill_rect(const struct window *window,
SDL_Texture *texture, const SDL_Rect *rect, const SDL_Color *color)
{
SDL_SetRenderTarget(window->renderer, texture);
SDL_SetRenderDrawColor(window->renderer,
color->r, color->g, color->b, color->a);
SDL_RenderFillRect(window->renderer, rect);
}
static void render_window_in_menu(const struct window *window)
{
render_background(window);
SDL_SetRenderTarget(window->renderer, NULL);
for (size_t i = 0; i < N_ELEMENTS(window->subwindows); i++) {
struct subwindow *subwindow = window->subwindows[i];
if (subwindow != NULL && subwindow->visible) {
if (subwindow->sizing_rect.w > 0 && subwindow->sizing_rect.h > 0) {
SDL_SetRenderTarget(window->renderer, subwindow->aux_texture);
/* in case subwindow's color changed */
render_fill_rect(window,
subwindow->aux_texture, NULL, &subwindow->color);
SDL_SetRenderTarget(window->renderer, NULL);
SDL_RenderCopy(window->renderer,
subwindow->aux_texture, NULL, &subwindow->sizing_rect);
}
SDL_RenderCopy(window->renderer,
subwindow->texture,
NULL, &subwindow->full_rect);
}
}
/* render it last to allow the menu to draw over subwindows */
render_status_bar(window);
SDL_SetRenderTarget(window->renderer, NULL);
SDL_RenderCopy(window->renderer,
window->status_bar.texture, NULL, &window->status_bar.full_rect);
}
static void set_subwindow_alpha(struct subwindow *subwindow, int alpha)
{
SDL_SetTextureAlphaMod(subwindow->texture, alpha);
SDL_SetTextureAlphaMod(subwindow->aux_texture, alpha);
}
static void set_subwindows_alpha(const struct window *window, int alpha)
{
for (size_t i = 0; i < N_ELEMENTS(window->subwindows); i++) {
struct subwindow *subwindow = window->subwindows[i];
if (subwindow != NULL) {
set_subwindow_alpha(subwindow, alpha);
}
}
}
/* this function allows to perform special things that are not
* needed while playing the game, like moving terms */
static void redraw_window_in_menu(struct window *window)
{
set_subwindows_alpha(window, window->alpha);
render_window_in_menu(window);
SDL_RenderPresent(window->renderer);
window->next_redraw = SDL_GetTicks() + window->delay;
}
/* this function is mostly used while normally playing the game */
static void redraw_window(struct window *window)
{
if (window->status_bar.in_menu) {
/* we called (perhaps via refresh_angband_terms()) Term_fresh() in menu */
redraw_window_in_menu(window);
return;
}
/* XXX XXX dont forget to prerender status bar in loader! */
render_all(window);
SDL_RenderPresent(window->renderer);
window->next_redraw = SDL_GetTicks() + window->delay;
}
static void try_redraw_window(struct window *window)
{
if (window->next_redraw < SDL_GetTicks()) {
redraw_window(window);
}
}
static void redraw_all_windows(bool dirty)
{
for (unsigned i = 0; i < MAX_WINDOWS; i++) {
struct window *window = get_window_direct(i);
if (window != NULL && (dirty ? window->dirty : true)) {
render_status_bar(window);
redraw_window(window);
window->dirty = false;
}
}
}
static void render_utf8_string(const struct window *window,
const struct font *font, SDL_Texture *dst_texture,
SDL_Color fg, SDL_Rect rect, const char *utf8_string)
{
SDL_Surface *surface = TTF_RenderUTF8_Blended(font->ttf.handle, utf8_string, fg);
SDL_Texture *src_texture = SDL_CreateTextureFromSurface(window->renderer, surface);
SDL_FreeSurface(surface);
rect.x += font->ttf.glyph.x;
rect.y += font->ttf.glyph.y;
SDL_SetRenderTarget(window->renderer, dst_texture);
SDL_RenderCopy(window->renderer, src_texture, NULL, &rect);
SDL_DestroyTexture(src_texture);
}
/* this function is typically called in a loop, so for efficiency it doesnt
* SetRenderTarget; caller must do it (but it does SetTextureColorMod) */
static void render_glyph_mono(const struct window *window,
const struct font *font, SDL_Texture *dst_texture,
int x, int y, const SDL_Color *fg, uint32_t codepoint)
{
if (codepoint == DEFAULT_CHAR_BLANK) {
return;
}
SDL_Rect dst = {
x + font->ttf.glyph.x,
y + font->ttf.glyph.y,
0, 0
};
if (IS_CACHED_ASCII_CODEPOINT(codepoint)) {
dst.w = font->cache.rects[codepoint].w;
dst.h = font->cache.rects[codepoint].h;
SDL_SetTextureColorMod(font->cache.texture, fg->r, fg->g, fg->b);
SDL_RenderCopy(window->renderer,
font->cache.texture, &font->cache.rects[codepoint], &dst);
} else {
SDL_Surface *surface = TTF_RenderGlyph_Blended(font->ttf.handle,
(Uint16) codepoint, *fg);
if (surface == NULL) {
return;
}
SDL_Rect src = {
0, 0,
MIN(surface->w, font->ttf.glyph.w - font->ttf.glyph.x),
MIN(surface->h, font->ttf.glyph.h - font->ttf.glyph.y)
};
dst.w = src.w;
dst.h = src.h;
SDL_Texture *texture = SDL_CreateTextureFromSurface(window->renderer, surface);
assert(texture != NULL);
SDL_RenderCopy(window->renderer, texture, &src, &dst);
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);
}
}
static void render_cursor(struct subwindow *subwindow,
int col, int row, bool big)
{
if (!subwindow->window->cursor) {
return;
}
SDL_Color color = g_colors[DEFAULT_SUBWINDOW_CURSOR_COLOR];
SDL_Rect rect = {
subwindow->inner_rect.x + subwindow->font_width * col,
subwindow->inner_rect.y + subwindow->font_height * row,
subwindow->font_width * (big ? tile_width : 1),
subwindow->font_height * (big ? tile_height : 1)
};
render_outline_rect(subwindow->window, subwindow->texture,
&rect, &color);
}
static void render_grid_cell_text(const struct subwindow *subwindow,
SDL_Texture *texture, int x, int y)
{
struct grid_data grid_data;
int a;
int ta;
wchar_t c;
wchar_t tc;
map_info(y, x, &grid_data);
grid_data_as_text(&grid_data, &a, &c, &ta, &tc);
/* apparently either the same as a or obscured by a */
(void) tc;
SDL_Color fg = g_colors[a % MAX_COLORS];
SDL_Color bg;
switch (ta / MAX_COLORS) {
case BG_BLACK:
bg = subwindow->color;
break;
case BG_SAME:
bg = fg;
break;
case BG_DARK:
bg = g_colors[DEFAULT_SHADE_COLOR];
break;
default:
/* debugging */
bg = g_colors[DEFAULT_ERROR_COLOR];
}
SDL_Rect rect = {
x * subwindow->font_width,
y * subwindow->font_height,
subwindow->font_width,
subwindow->font_height
};
render_fill_rect(subwindow->window, texture, &rect, &bg);
render_glyph_mono(subwindow->window,
subwindow->font, texture, rect.x, rect.y, &fg, (uint32_t) c);
}
/* does not SetRenderTarget */
static void render_tile_rect_scaled(const struct subwindow *subwindow,
int col, int row, SDL_Rect dst, int a, int c)
{
struct graphics *graphics = &subwindow->window->graphics;
SDL_Rect src = {0, 0, graphics->tile_pixel_w, graphics->tile_pixel_h};
int src_row = a & 0x7f;
int src_col = c & 0x7f;
src.x = src_col * src.w;
src.y = src_row * src.h;
if (graphics->overdraw_row != 0
&& src_row >= graphics->overdraw_row
&& src_row <= graphics->overdraw_max)
{
src.y -= src.h;
dst.y -= dst.h;
dst.h *= 2;
src.h *= 2;
}
SDL_RenderCopy(subwindow->window->renderer,
graphics->texture, &src, &dst);
}
static void render_tile_font_scaled(const struct subwindow *subwindow,
int col, int row, int a, int c, bool fill)
{
struct graphics *graphics = &subwindow->window->graphics;
SDL_Rect dst = {
subwindow->inner_rect.x + col * subwindow->font_width,
subwindow->inner_rect.y + row * subwindow->font_height,
subwindow->font_width * tile_width,
subwindow->font_height * tile_height
};
if (fill) {
render_fill_rect(subwindow->window, subwindow->texture, &dst, &subwindow->color);
}
SDL_Rect src = {0, 0, graphics->tile_pixel_w, graphics->tile_pixel_h};
SDL_SetRenderTarget(subwindow->window->renderer, subwindow->texture);
int src_row = a & 0x7f;
int src_col = c & 0x7f;
src.x = src_col * src.w;
src.y = src_row * src.h;
if (graphics->overdraw_row != 0
&& row > 2
&& src_row >= graphics->overdraw_row
&& src_row <= graphics->overdraw_max)
{
src.y -= src.h;
dst.y -= dst.h;
dst.h *= 2;
src.h *= 2;
SDL_RenderCopy(subwindow->window->renderer,
graphics->texture, &src, &dst);
Term_mark(col, row - tile_height);
Term_mark(col, row);
} else {
SDL_RenderCopy(subwindow->window->renderer,
graphics->texture, &src, &dst);
}
}
static void render_grid_cell_tile(const struct subwindow *subwindow,
SDL_Texture *texture, SDL_Rect tile, int x, int y)
{
struct grid_data grid_data;
int a;
int ta;
wchar_t c;
wchar_t tc;
map_info(y, x, &grid_data);
grid_data_as_text(&grid_data, &a, &c, &ta, &tc);
SDL_SetRenderTarget(subwindow->window->renderer, texture);
render_tile_rect_scaled(subwindow, x, y, tile, ta, tc);
if (a == ta && c == tc) {
return;
}
render_tile_rect_scaled(subwindow, x, y, tile, a, c);
}
static void clear_all_borders(struct window *window)
{
for (size_t i = 0; i < N_ELEMENTS(window->subwindows); i++) {
struct subwindow *subwindow = window->subwindows[i];
if (subwindow != NULL) {
subwindow->borders.error = false;
render_borders(subwindow);
}
}
}
static void render_borders(struct subwindow *subwindow)
{
SDL_Rect rect = {0};
SDL_QueryTexture(subwindow->texture, NULL, NULL, &rect.w, &rect.h);
SDL_Color *color;
if (!subwindow->borders.error) {
if (subwindow->borders.visible) {
color = &subwindow->borders.color;
} else {
color = &subwindow->color;
}
} else {
color = &g_colors[DEFAULT_ERROR_COLOR];
}
render_outline_rect_width(subwindow->window,
subwindow->texture, &rect, color,
subwindow->borders.width);
}
static SDL_Texture *make_subwindow_texture(const struct window *window, int w, int h)
{
SDL_Texture *texture = SDL_CreateTexture(window->renderer,
window->pixelformat, SDL_TEXTUREACCESS_TARGET, w, h);
if (texture == NULL) {
quit_fmt("cant create texture for subwindow in window %u: %s",
window->index, SDL_GetError());
}
if (SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND) != 0) {
SDL_DestroyTexture(texture);
quit_fmt("cant set blend mode for texture in window %u: %s",
window->index, SDL_GetError());
}
return texture;
}
static void render_menu_panel(const struct window *window, struct menu_panel *menu_panel)
{
if (menu_panel == NULL) {
return;
}
for (size_t i = 0; i < menu_panel->button_bank.number; i++) {
struct button *button = &menu_panel->button_bank.buttons[i];
assert(button->callbacks.on_render != NULL);
button->callbacks.on_render(window, button);
}
render_outline_rect(window,
NULL, &menu_panel->rect, &g_colors[DEFAULT_MENU_PANEL_OUTLINE_COLOR]);
/* recurse */
render_menu_panel(window, menu_panel->next);
}
static SDL_Rect get_button_caption_rect(const struct button *button)
{
SDL_Rect rect = {
button->full_rect.x + button->inner_rect.x,
button->full_rect.y + button->inner_rect.y,
button->inner_rect.w,
button->inner_rect.h
};
return rect;
}
static void render_button_menu(const struct window *window,
struct button *button, const SDL_Color *fg, const SDL_Color *bg)
{
SDL_Rect rect = get_button_caption_rect(button);
render_fill_rect(window,
NULL, &button->full_rect, bg);
render_utf8_string(window, window->status_bar.font, NULL,
*fg, rect, button->caption);
}
static void render_button_menu_toggle(const struct window *window,
struct button *button, bool active)
{
SDL_Color *bg;
SDL_Color *fg;
if (active) {
fg = &g_colors[DEFAULT_MENU_TOGGLE_FG_ACTIVE_COLOR];
} else {
fg = &g_colors[DEFAULT_MENU_TOGGLE_FG_INACTIVE_COLOR];
}
if (button->highlighted) {
bg = &g_colors[DEFAULT_MENU_BG_ACTIVE_COLOR];
} else {
bg = &g_colors[DEFAULT_MENU_BG_INACTIVE_COLOR];
}
render_button_menu(window, button, fg, bg);
}
static void render_button_menu_simple(const struct window *window, struct button *button)
{
SDL_Color *fg;
SDL_Color *bg;
if (button->highlighted) {
fg = &g_colors[DEFAULT_MENU_FG_ACTIVE_COLOR];
bg = &g_colors[DEFAULT_MENU_BG_ACTIVE_COLOR];
} else {
fg = &g_colors[DEFAULT_MENU_FG_INACTIVE_COLOR];
bg = &g_colors[DEFAULT_MENU_BG_INACTIVE_COLOR];
}
render_button_menu(window, button, fg, bg);
}
static void render_button_menu_cursor(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
render_button_menu_toggle(window, button, window->cursor);
}
static void render_button_menu_pw(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_TERMVAL);
struct subwindow *subwindow = button->info.data.termval.subwindow;
u32b value = button->info.data.termval.value;
assert(subwindow->index != MAIN_SUBWINDOW);
assert(subwindow->index < N_ELEMENTS(window_flag));
render_button_menu_toggle(window,
button,
(window_flag[subwindow->index] & value) == value);
}
static void render_button_menu_terms(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (button->highlighted) {
/* draw a border around subwindow, so that it would be easy to see
* which subwindow corresponds to that button */
struct subwindow *subwindow = button->info.data.subval;
int outline_width = (subwindow->full_rect.w - subwindow->inner_rect.w) / 2
- subwindow->borders.width;
SDL_Rect outline_rect = subwindow->full_rect;
resize_rect(&outline_rect,
subwindow->borders.width, subwindow->borders.width,
-subwindow->borders.width, -subwindow->borders.width);
render_outline_rect_width(window,
NULL,
&outline_rect,
&g_colors[DEFAULT_SUBWINDOW_BORDER_COLOR],
outline_width);
}
render_button_menu_simple(window, button);
}
static void render_button_menu_borders(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
struct subwindow *subwindow = button->info.data.subval;
render_button_menu_toggle(window, button, subwindow->borders.visible);
}
static void render_button_menu_alpha(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_ALPHAVAL);
struct subwindow *subwindow = button->info.data.alphaval.subwindow;
int alpha = button->info.data.alphaval.real_value;
SDL_Color fg;
SDL_Color *bg;
if (is_close_to(alpha, subwindow->color.a, DEFAULT_ALPHA_STEP / 2)) {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_ACTIVE_COLOR];
} else {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_INACTIVE_COLOR];
}
if (button->highlighted) {
bg = &g_colors[DEFAULT_MENU_BG_ACTIVE_COLOR];
} else {
bg = &g_colors[DEFAULT_MENU_BG_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
render_fill_rect(window,
NULL, &button->full_rect, bg);
render_utf8_string(window, window->status_bar.font, NULL,
fg, rect, format(button->caption, button->info.data.alphaval.show_value));
}
static void render_button_menu_top(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
struct subwindow *subwindow = button->info.data.subval;
render_button_menu_toggle(window, button, subwindow->always_top);
}
static void render_button_menu_tile_size(const struct window *window,
struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_IVAL);
assert(button->info.data.ival == BUTTON_TILE_SIZE_WIDTH
|| button->info.data.ival == BUTTON_TILE_SIZE_HEIGHT);
SDL_Color fg;
SDL_Color *bg;
if (window->graphics.id != GRAPHICS_NONE) {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_ACTIVE_COLOR];
} else {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_INACTIVE_COLOR];
}
if (button->highlighted) {
bg = &g_colors[DEFAULT_MENU_BG_ACTIVE_COLOR];
} else {
bg = &g_colors[DEFAULT_MENU_BG_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
int scale = 0;
if (button->info.data.ival == BUTTON_TILE_SIZE_WIDTH) {
scale = tile_width;
} else if (button->info.data.ival == BUTTON_TILE_SIZE_HEIGHT) {
scale = tile_height;
}
render_fill_rect(window,
NULL, &button->full_rect, bg);
render_utf8_string(window, window->status_bar.font, NULL,
fg, rect, format(button->caption, scale));
}
static void render_button_menu_tile_set(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_IVAL);
render_button_menu_toggle(window,
button, button->info.data.ival == current_graphics_mode->grafID);
}
static void render_button_menu_font_size(const struct window *window,
struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_FONTVAL);
SDL_Color fg;
SDL_Color *bg;
struct fontval fontval = button->info.data.fontval;
if (!button->info.data.fontval.size_ok) {
fg = g_colors[DEFAULT_ERROR_COLOR];
} else if (g_font_info[fontval.index].type == FONT_TYPE_VECTOR) {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_ACTIVE_COLOR];
} else {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_INACTIVE_COLOR];
}
if (button->highlighted) {
bg = &g_colors[DEFAULT_MENU_BG_ACTIVE_COLOR];
} else {
bg = &g_colors[DEFAULT_MENU_BG_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
render_fill_rect(window,
NULL, &button->full_rect, bg);
render_utf8_string(window, window->status_bar.font, NULL,
fg, rect, format(button->caption, fontval.subwindow->font->size));
}
static void render_button_menu_font_name(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_FONTVAL);
SDL_Color fg;
SDL_Color *bg;
struct subwindow *subwindow = button->info.data.fontval.subwindow;
size_t index = button->info.data.fontval.index;
if (!button->info.data.fontval.size_ok) {
fg = g_colors[DEFAULT_ERROR_COLOR];
} else if (subwindow->font->index == index) {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_ACTIVE_COLOR];
} else {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_INACTIVE_COLOR];
}
if (button->highlighted) {
bg = &g_colors[DEFAULT_MENU_BG_ACTIVE_COLOR];
} else {
bg = &g_colors[DEFAULT_MENU_BG_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
render_fill_rect(window,
NULL, &button->full_rect, bg);
render_utf8_string(window, window->status_bar.font, NULL,
fg, rect, button->caption);
}
static void render_button_menu_window(const struct window *window,
struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_UVAL);
struct window *w = get_window_direct(button->info.data.uval);
SDL_Color fg;
SDL_Color *bg;
if (w != NULL) {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_ACTIVE_COLOR];
} else {
fg = g_colors[DEFAULT_MENU_TOGGLE_FG_INACTIVE_COLOR];
}
if (button->highlighted) {
bg = &g_colors[DEFAULT_MENU_BG_ACTIVE_COLOR];
} else {
bg = &g_colors[DEFAULT_MENU_BG_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
render_fill_rect(window, NULL, &button->full_rect, bg);
render_utf8_string(window, window->status_bar.font, NULL,
fg, rect, format(button->caption, button->info.data.uval));
}
static void render_button_menu_fullscreen(const struct window *window,
struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_NONE);
render_button_menu_toggle(window, button,
window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP);
}
/* the menu proper is rendered via this callback, used by the "Menu" button */
static void render_menu_button(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_NONE);
SDL_Color color;
if (button->highlighted) {
color = g_colors[DEFAULT_STATUS_BAR_BUTTON_ACTIVE_COLOR];
} else {
color = g_colors[DEFAULT_STATUS_BAR_BUTTON_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
render_utf8_string(window, window->status_bar.font, window->status_bar.texture,
color, rect, button->caption);
if (button->highlighted) {
render_menu_panel(window, window->status_bar.menu_panel);
}
}
static void render_button_subwindows(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_SUBWINDOWS, BUTTON_DATA_UVAL);
SDL_Color color;
if (has_visible_subwindow(window, button->info.data.uval)
|| button->highlighted) {
color = g_colors[DEFAULT_STATUS_BAR_BUTTON_ACTIVE_COLOR];
} else {
color = g_colors[DEFAULT_STATUS_BAR_BUTTON_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
render_utf8_string(window, window->status_bar.font, window->status_bar.texture,
color, rect, button->caption);
}
static void render_button_movesize(const struct window *window, struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MOVESIZE, BUTTON_DATA_IVAL);
bool active = false;
switch (button->info.data.ival) {
case BUTTON_MOVESIZE_MOVING:
active = window->move_state.active;
break;
case BUTTON_MOVESIZE_SIZING:
active = window->size_state.active;
break;
default:
quit_fmt("button '%s' has wrong ival %d",
button->caption, button->info.data.ival);
break;
}
SDL_Color color;
if (active || button->highlighted) {
color = g_colors[DEFAULT_STATUS_BAR_BUTTON_ACTIVE_COLOR];
} else {
color = g_colors[DEFAULT_STATUS_BAR_BUTTON_INACTIVE_COLOR];
}
SDL_Rect rect = get_button_caption_rect(button);
render_utf8_string(window, window->status_bar.font, window->status_bar.texture,
color, rect, button->caption);
}
static void show_about(const struct window *window)
{
const char *about_text[] = {
buildid,
"See http://www.rephial.org",
"Visit our forum at http://angband.oook.cz/forum"
};
struct { SDL_Rect rect; const char *text; } elems[N_ELEMENTS(about_text)];
for (size_t i = 0; i < N_ELEMENTS(elems); i++) {
elems[i].text = about_text[i];
}
char path[4096];
path_build(path, sizeof(path), DEFAULT_ABOUT_ICON_DIR, DEFAULT_ABOUT_ICON);
SDL_Texture *texture = load_image(window, path);
SDL_Rect texture_rect = {0};
SDL_QueryTexture(texture, NULL, NULL, &texture_rect.w, &texture_rect.h);
SDL_Rect total = {
0, 0,
2 * DEFAULT_XTRA_BORDER + texture_rect.w,
/* the default icon just looks better without bottom border */
DEFAULT_XTRA_BORDER + texture_rect.h
};
for (size_t i = 0; i < N_ELEMENTS(elems); i++) {
int w;
int h;
get_string_metrics(window->status_bar.font,
elems[i].text, &w, &h);
elems[i].rect.h = h;
elems[i].rect.w = w;
elems[i].rect.y = total.h + (DEFAULT_LINE_HEIGHT(h) - h) / 2;
total.w = MAX(w + 2 * DEFAULT_XTRA_BORDER, total.w);
total.h += DEFAULT_LINE_HEIGHT(h);
}
total.h += DEFAULT_XTRA_BORDER;
total.x = window->full_rect.w / 2 - total.w / 2;
total.y = window->full_rect.h / 2 - total.h / 2;
render_window_in_menu(window);
render_fill_rect(window, NULL, &total, &g_colors[DEFAULT_ABOUT_BG_COLOR]);
render_outline_rect_width(window, NULL, &total,
&g_colors[DEFAULT_ABOUT_BORDER_OUTER_COLOR], DEFAULT_VISIBLE_BORDER);
resize_rect(&total,
DEFAULT_VISIBLE_BORDER, DEFAULT_VISIBLE_BORDER,
-DEFAULT_VISIBLE_BORDER, -DEFAULT_VISIBLE_BORDER);
render_outline_rect_width(window, NULL, &total,
&g_colors[DEFAULT_ABOUT_BORDER_INNER_COLOR], DEFAULT_VISIBLE_BORDER);
for (size_t i = 0; i < N_ELEMENTS(elems); i++) {
/* center the string in total rect */
elems[i].rect.x = total.x + (total.w - elems[i].rect.w) / 2;
/* make the y coord of string absolute (was relative to total rect) */
elems[i].rect.y += total.y;
render_utf8_string(window, window->status_bar.font,
NULL, g_colors[DEFAULT_ABOUT_TEXT_COLOR],
elems[i].rect, elems[i].text);
}
texture_rect.x = total.x + (total.w - texture_rect.w) / 2;
texture_rect.y = total.y + DEFAULT_XTRA_BORDER;
SDL_SetRenderTarget(window->renderer, NULL);
SDL_RenderCopy(window->renderer, texture, NULL, &texture_rect);
SDL_RenderPresent(window->renderer);
wait_anykey();
SDL_DestroyTexture(texture);
}
static void signal_move_state(struct window *window)
{
assert(!window->size_state.active);
bool was_active = window->move_state.active;
if (was_active) {
window->move_state.active = false;
window->move_state.moving = false;
window->move_state.subwindow = NULL;
} else {
window->move_state.active = true;
}
SDL_SetWindowGrab(window->window,
was_active ? SDL_FALSE : SDL_TRUE);
window->alpha = was_active ? DEFAULT_ALPHA_FULL : DEFAULT_ALPHA_LOW;
}
static void signal_size_state(struct window *window)
{
assert(!window->move_state.active);
bool was_active = window->size_state.active;
if (was_active) {
window->size_state.active = false;
window->size_state.sizing = false;
if (window->size_state.subwindow != NULL) {
memset(&window->size_state.subwindow->sizing_rect,
0, sizeof(window->size_state.subwindow->sizing_rect));
window->size_state.subwindow = NULL;
}
} else {
window->size_state.active = true;
}
SDL_SetWindowGrab(window->window,
was_active ? SDL_FALSE : SDL_TRUE);
window->alpha = was_active ? DEFAULT_ALPHA_FULL : DEFAULT_ALPHA_LOW;
}
static bool do_button_movesize(struct window *window,
struct button *button)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MOVESIZE, BUTTON_DATA_IVAL);
switch (button->info.data.ival) {
case BUTTON_MOVESIZE_MOVING:
if (window->size_state.active) {
/* toggle size button */
signal_size_state(window);
}
signal_move_state(window);
break;
case BUTTON_MOVESIZE_SIZING:
if (window->move_state.active) {
/* toggle move button */
signal_move_state(window);
}
signal_size_state(window);
break;
}
return true;
}
static void push_button(struct button_bank *bank, struct font *font,
const char *caption, struct button_info info, struct button_callbacks callbacks,
const SDL_Rect *rect, enum button_caption_position position)
{
assert(bank->number < bank->size);
struct button *button = &bank->buttons[bank->number];
int w;
int h;
get_string_metrics(font, caption, &w, &h);
int x = 0;
switch (position) {
case CAPTION_POSITION_CENTER:
x = (rect->w - w) / 2;
break;
case CAPTION_POSITION_LEFT:
x = DEFAULT_BUTTON_BORDER;
break;
case CAPTION_POSITION_RIGHT:
x = rect->w - DEFAULT_BUTTON_BORDER - w;
break;
default:
quit_fmt("bad caption position %d in button '%s'",
position, button->caption);
break;
}
button->inner_rect.x = x;
button->inner_rect.y = (rect->h - h) / 2;
button->inner_rect.w = w;
button->inner_rect.h = h;
button->full_rect = *rect;
assert(button->full_rect.w >= button->inner_rect.w
&& button->full_rect.h >= button->inner_rect.h);
button->caption = string_make(caption);
button->callbacks = callbacks;
button->info = info;
button->highlighted = false;
button->selected = false;
bank->number++;
}
static struct menu_panel *new_menu_panel(void)
{
struct menu_panel *menu = mem_zalloc(sizeof(*menu));
make_button_bank(&menu->button_bank);
menu->next = NULL;
return menu;
}
/* if caption of some button is NULL, the button is not included in menu (skipped) */
static struct menu_panel *make_menu_panel(const struct button *origin,
struct font *font, size_t n_elems, struct menu_elem *elems)
{
int maxlen = 0;
for (size_t i = 0; i < n_elems; i++) {
if (elems[i].caption == NULL) {
continue;
}
int w;
get_string_metrics(font, elems[i].caption, &w, NULL);
maxlen = MAX(maxlen, w);
}
struct menu_panel *menu_panel = new_menu_panel();
if (menu_panel == NULL) {
return NULL;
}
SDL_Rect rect = {
origin->full_rect.x + origin->full_rect.w,
origin->full_rect.y,
DEFAULT_MENU_LINE_WIDTH(maxlen),
DEFAULT_MENU_LINE_HEIGHT(font->ttf.glyph.h)
};
menu_panel->rect = rect;
menu_panel->rect.h = 0;
for (size_t i = 0; i < n_elems; i++) {
if (elems[i].caption == NULL) {
continue;
}
struct button_callbacks callbacks = {
elems[i].on_render, NULL, NULL, elems[i].on_menu
};
push_button(&menu_panel->button_bank,
font,
elems[i].caption,
elems[i].info,
callbacks,
&rect,
CAPTION_POSITION_LEFT);
rect.y += rect.h;
menu_panel->rect.h += rect.h;
}
return menu_panel;
}
static void load_next_menu_panel(const struct window *window,
struct menu_panel *menu_panel, const struct button *origin,
size_t n_elems, struct menu_elem *elems)
{
assert(menu_panel->next == NULL);
menu_panel->next = make_menu_panel(origin,
window->status_bar.font, n_elems, elems);
}
static void do_menu_cleanup(struct button *button,
struct menu_panel *menu_panel, const SDL_Event *event)
{
switch (event->type) {
case SDL_MOUSEMOTION: /* fallthru */
case SDL_MOUSEBUTTONDOWN: /* fallthru */
case SDL_MOUSEBUTTONUP:
if (menu_panel->next != NULL) {
free_menu_panel(menu_panel->next);
menu_panel->next = NULL;
}
break;
default:
quit_fmt("non mouse event %d for button '%s'",
event->type, button->caption);
break;
}
}
static bool select_menu_button(struct button *button,
struct menu_panel *menu_panel, const SDL_Event *event)
{
if (button->selected) {
return false;
} else {
do_menu_cleanup(button, menu_panel, event);
button->selected = true;
return true;
}
}
static bool click_menu_button(struct button *button,
struct menu_panel *menu_panel, const SDL_Event *event)
{
/* any event on that button removes the existing panel submenus
* (and clickable buttons should not have their own submenus) */
do_menu_cleanup(button, menu_panel, event);
switch (event->type) {
case SDL_MOUSEBUTTONDOWN:
button->selected = true;
return false;
case SDL_MOUSEBUTTONUP:
if (button->selected) {
button->selected = false;
/* click the button */
return true;
} else {
return false;
}
default:
return false;
}
}
static void handle_menu_cursor(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
window->cursor = !window->cursor;
}
static void handle_menu_window(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_UVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
struct window *other = get_window_direct(button->info.data.uval);
if (other == NULL) {
other = get_new_window(button->info.data.uval);
assert(other != NULL);
wipe_window_aux_config(other);
start_window(other);
}
}
static void handle_menu_windows(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_NONE);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct menu_elem elems[MAX_WINDOWS];
for (unsigned i = 0; i < MAX_WINDOWS; i++) {
elems[i].caption = "Window-%u";
elems[i].info.type = BUTTON_DATA_UVAL;
elems[i].info.data.uval = i;
elems[i].info.group = BUTTON_GROUP_MENU;
elems[i].on_render = render_button_menu_window;
elems[i].on_menu = handle_menu_window;
}
load_next_menu_panel(window, menu_panel, button, N_ELEMENTS(elems), elems);
}
static void handle_menu_fullscreen(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_NONE);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
if (window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
SDL_SetWindowFullscreen(window->window, 0);
SDL_SetWindowMinimumSize(window->window,
DEFAULT_WINDOW_MINIMUM_W, DEFAULT_WINDOW_MINIMUM_H);
} else {
SDL_SetWindowFullscreen(window->window, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
window->flags = SDL_GetWindowFlags(window->window);
}
static void handle_menu_about(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_NONE);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
show_about(window);
}
static void handle_menu_quit(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_NONE);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
handle_quit();
}
static void handle_menu_tile_set(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_IVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
graphics_mode *mode = get_graphics_mode(button->info.data.ival);
assert(mode != NULL);
reload_all_graphics(mode);
refresh_angband_terms();
}
static void handle_menu_tile_size(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_IVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
if (window->graphics.id == GRAPHICS_NONE) {
return;
}
int increment =
(event->button.x - button->full_rect.x < button->full_rect.w / 2) ? -1 : +1;
if (button->info.data.ival == BUTTON_TILE_SIZE_WIDTH) {
tile_width += increment;
if (tile_width < MIN_TILE_WIDTH) {
tile_width = MAX_TILE_WIDTH;
} else if (tile_width > MAX_TILE_WIDTH) {
tile_width = MIN_TILE_WIDTH;
}
} else if (button->info.data.ival == BUTTON_TILE_SIZE_HEIGHT) {
tile_height += increment;
if (tile_height < MIN_TILE_HEIGHT) {
tile_height = MAX_TILE_HEIGHT;
} else if (tile_height > MAX_TILE_HEIGHT) {
tile_height = MIN_TILE_HEIGHT;
}
} else {
quit_fmt("bad ival in button '%s'", button->caption);
}
refresh_angband_terms();
}
static void handle_menu_tile_sizes(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct menu_elem elems[] = {
{
"< Tile width %d >",
{
BUTTON_DATA_IVAL,
{.ival = BUTTON_TILE_SIZE_WIDTH},
BUTTON_GROUP_MENU
},
render_button_menu_tile_size,
handle_menu_tile_size
},
{
"< Tile height %d >",
{
BUTTON_DATA_IVAL,
{.ival = BUTTON_TILE_SIZE_HEIGHT},
BUTTON_GROUP_MENU
},
render_button_menu_tile_size,
handle_menu_tile_size
}
};
load_next_menu_panel(window, menu_panel, button, N_ELEMENTS(elems), elems);
}
static void handle_menu_tile_sets(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
size_t num_elems = 0;
graphics_mode *mode = graphics_modes;
while (mode != NULL) {
num_elems++;
mode = mode->pNext;
}
struct menu_elem elems[num_elems];
mode = graphics_modes;
for (size_t i = 0; i < num_elems; i++) {
elems[i].caption = mode->menuname;
elems[i].info.type = BUTTON_DATA_IVAL;
elems[i].info.data.ival = mode->grafID;
elems[i].info.group = BUTTON_GROUP_MENU;
elems[i].on_render = render_button_menu_tile_set;
elems[i].on_menu = handle_menu_tile_set;
mode = mode->pNext;
}
load_next_menu_panel(window, menu_panel, button, num_elems, elems);
}
static void handle_menu_tiles(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct button_info info = {
BUTTON_DATA_SUBVAL, {.subval = button->info.data.subval}, BUTTON_GROUP_MENU
};
struct menu_elem elems[] = {
{"Set", info, render_button_menu_simple, handle_menu_tile_sets},
{"Size", info, render_button_menu_simple, handle_menu_tile_sizes}
};
load_next_menu_panel(window, menu_panel, button, N_ELEMENTS(elems), elems);
}
static void handle_menu_pw(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_TERMVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
u32b new_flags[N_ELEMENTS(window_flag)];
assert(sizeof(new_flags) == sizeof(window_flag));
memcpy(new_flags, window_flag, sizeof(new_flags));
struct subwindow *subwindow = button->info.data.termval.subwindow;
assert(subwindow->index != MAIN_SUBWINDOW);
assert(subwindow->index < N_ELEMENTS(window_flag));
uint32_t value = button->info.data.termval.value;
new_flags[subwindow->index] = value;
subwindows_set_flags(new_flags, N_ELEMENTS(new_flags));
refresh_angband_terms();
}
static void handle_menu_font_name(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_FONTVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
assert(button->info.data.fontval.index < N_ELEMENTS(g_font_info));
unsigned index = button->info.data.fontval.index;
struct subwindow *subwindow = button->info.data.fontval.subwindow;
const struct font_info *font_info = &g_font_info[index];
assert(font_info->loaded);
if (subwindow->font->index == index) {
/* already loaded */
return;
}
if (reload_font(subwindow, font_info)) {
button->info.data.fontval.size_ok = true;
} else {
button->info.data.fontval.size_ok = false;
}
}
static void handle_menu_font_size(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_FONTVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
if (!button->info.data.fontval.size_ok) {
return;
}
unsigned index = button->info.data.fontval.index;
assert(index < N_ELEMENTS(g_font_info));
struct font_info *info = &g_font_info[index];
if (info->type == FONT_TYPE_RASTER) {
return;
}
struct subwindow *subwindow = button->info.data.fontval.subwindow;
int size = subwindow->font->size;
int increment =
(event->button.x - button->full_rect.x < button->full_rect.w / 2) ? -1 : +1;
for (size_t i = 0; i < MAX_VECTOR_FONT_SIZE - MIN_VECTOR_FONT_SIZE; i++) {
size += increment;
if (size > MAX_VECTOR_FONT_SIZE) {
size = MIN_VECTOR_FONT_SIZE;
} else if (size < MIN_VECTOR_FONT_SIZE) {
size = MAX_VECTOR_FONT_SIZE;
}
info->size = size;
if (reload_font(subwindow, info)) {
return;
}
}
button->info.data.fontval.size_ok = false;
}
static void handle_menu_font_sizes(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct subwindow *subwindow = button->info.data.subval;
struct button_info info = {
BUTTON_DATA_FONTVAL,
{.fontval =
{.subwindow = subwindow,.index = subwindow->font->index, .size_ok = true}},
BUTTON_GROUP_MENU
};
struct menu_elem elems[] = {
{"< %2d points >", info, render_button_menu_font_size, handle_menu_font_size}
};
load_next_menu_panel(window, menu_panel, button, N_ELEMENTS(elems), elems);
}
static void handle_menu_font_names(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct menu_elem elems[N_ELEMENTS(g_font_info)];
size_t num_elems = 0;
for (size_t i = 0; i < N_ELEMENTS(g_font_info); i++) {
if (g_font_info[i].loaded) {
elems[num_elems].caption = g_font_info[i].name;
elems[num_elems].info.type = BUTTON_DATA_FONTVAL;
elems[num_elems].info.data.fontval.subwindow = button->info.data.subval;
elems[num_elems].info.data.fontval.size_ok = true;
elems[num_elems].info.data.fontval.index = i;
elems[num_elems].info.group = BUTTON_GROUP_MENU;
elems[num_elems].on_render = render_button_menu_font_name;
elems[num_elems].on_menu = handle_menu_font_name;
num_elems++;
}
}
load_next_menu_panel(window, menu_panel, button, num_elems, elems);
}
static void handle_menu_purpose(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
struct subwindow *subwindow = button->info.data.subval;
assert(subwindow->index != MAIN_SUBWINDOW);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct menu_elem elems[N_ELEMENTS(window_flag_desc)];
size_t num_elems = 0;
while (num_elems < N_ELEMENTS(elems)
&& window_flag_desc[num_elems] != NULL)
{
elems[num_elems].caption = window_flag_desc[num_elems];
elems[num_elems].info.group = BUTTON_GROUP_MENU;
elems[num_elems].info.data.termval.subwindow = subwindow;
elems[num_elems].info.data.termval.value = 1L << num_elems;
elems[num_elems].info.type = BUTTON_DATA_TERMVAL;
elems[num_elems].on_render = render_button_menu_pw;
elems[num_elems].on_menu = handle_menu_pw;
num_elems++;
}
load_next_menu_panel(window, menu_panel, button, num_elems, elems);
}
static void handle_menu_font(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct button_info info = {
BUTTON_DATA_SUBVAL, {.subval = button->info.data.subval}, BUTTON_GROUP_MENU
};
struct menu_elem elems[] = {
{"Name", info, render_button_menu_simple, handle_menu_font_names},
{"Size", info, render_button_menu_simple, handle_menu_font_sizes}
};
load_next_menu_panel(window, menu_panel, button, N_ELEMENTS(elems), elems);
}
static void handle_menu_borders(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
struct subwindow *subwindow = button->info.data.subval;
subwindow->borders.visible = !subwindow->borders.visible;
render_borders(subwindow);
}
static void handle_menu_subwindow_alpha(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_ALPHAVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
struct subwindow *subwindow = button->info.data.alphaval.subwindow;
subwindow->color.a = button->info.data.alphaval.real_value;
render_clear(subwindow->window, subwindow->texture, &subwindow->color);
render_borders(subwindow);
refresh_angband_terms();
}
static void handle_menu_alpha(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct subwindow *subwindow = button->info.data.subval;
struct menu_elem elems[(100 - DEFAULT_ALPHA_LOWEST) / DEFAULT_ALPHA_STEP +
1 + ((100 - DEFAULT_ALPHA_LOWEST) % DEFAULT_ALPHA_STEP == 0 ? 0 : 1)];
for (size_t i = 0; i < N_ELEMENTS(elems); i++) {
int alpha = ALPHA_PERCENT(DEFAULT_ALPHA_LOWEST + i * DEFAULT_ALPHA_STEP);
elems[i].caption = " %3d%% ";
elems[i].info.type = BUTTON_DATA_ALPHAVAL;
elems[i].info.data.alphaval.subwindow = subwindow;
elems[i].info.data.alphaval.real_value = alpha;
elems[i].info.data.alphaval.show_value = i * DEFAULT_ALPHA_STEP;
elems[i].info.group = BUTTON_GROUP_MENU;
elems[i].on_render = render_button_menu_alpha;
elems[i].on_menu = handle_menu_subwindow_alpha;
}
elems[N_ELEMENTS(elems) - 1].info.data.alphaval.real_value =
DEFAULT_ALPHA_FULL;
load_next_menu_panel(window, menu_panel, button, N_ELEMENTS(elems), elems);
}
static void handle_menu_top(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!click_menu_button(button, menu_panel, event)) {
return;
}
struct subwindow *subwindow = button->info.data.subval;
subwindow->always_top = !subwindow->always_top;
sort_to_top(subwindow->window);
}
static void handle_menu_terms(struct window *window,
struct button *button, const SDL_Event *event,
struct menu_panel *menu_panel)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_SUBVAL);
if (!select_menu_button(button, menu_panel, event)) {
return;
}
struct subwindow *subwindow = button->info.data.subval;
struct button_info info = {
BUTTON_DATA_SUBVAL, {.subval = subwindow}, BUTTON_GROUP_MENU
};
struct menu_elem elems[] = {
{
"Font", info, render_button_menu_simple, handle_menu_font
},
{
subwindow->index == MAIN_SUBWINDOW ? "Tiles" : NULL,
info, render_button_menu_simple, handle_menu_tiles
},
{
subwindow->index == MAIN_SUBWINDOW ? NULL: "Purpose",
info, render_button_menu_simple, handle_menu_purpose
},
{
subwindow->index == MAIN_SUBWINDOW ? NULL : "Alpha",
info, render_button_menu_simple, handle_menu_alpha
},
{
"Borders", info, render_button_menu_borders, handle_menu_borders
},
{
subwindow->index == MAIN_SUBWINDOW ? "Cursor" : NULL,
info, render_button_menu_cursor, handle_menu_cursor
},
{
"Top", info, render_button_menu_top, handle_menu_top
}
};
load_next_menu_panel(window, menu_panel, button, N_ELEMENTS(elems), elems);
}
static void load_main_menu_panel(struct status_bar *status_bar)
{
assert(N_ELEMENTS(angband_term_name) == MAX_SUBWINDOWS);
struct menu_elem term_elems[N_ELEMENTS(angband_term_name)];
size_t n_terms = 0;
for (size_t i = 0; i < N_ELEMENTS(angband_term_name); i++) {
struct subwindow *subwindow =
get_subwindow_by_index(status_bar->window, i, true);
if (subwindow == NULL) {
continue;
}
term_elems[n_terms].caption = angband_term_name[i];
term_elems[n_terms].info.type = BUTTON_DATA_SUBVAL;
term_elems[n_terms].info.data.subval = subwindow;
term_elems[n_terms].info.group = BUTTON_GROUP_MENU;
term_elems[n_terms].on_render = render_button_menu_terms;
term_elems[n_terms].on_menu = handle_menu_terms;
n_terms++;
}
struct button_info info = {BUTTON_DATA_NONE, {0}, BUTTON_GROUP_MENU};
struct menu_elem other_elems[] = {
{
"Fullscreen",
info, render_button_menu_fullscreen, handle_menu_fullscreen
},
{
status_bar->window->index == MAIN_WINDOW ? "Windows" : NULL,
info, render_button_menu_simple, handle_menu_windows
},
{
"About",
info, render_button_menu_simple, handle_menu_about
},
{
"Quit",
info, render_button_menu_simple, handle_menu_quit
}
};
struct menu_elem elems[N_ELEMENTS(term_elems) + N_ELEMENTS(other_elems)];
memcpy(elems, term_elems, n_terms * sizeof(term_elems[0]));
memcpy(elems + n_terms, other_elems, sizeof(other_elems));
struct button dummy = {0};
dummy.full_rect.x = status_bar->full_rect.x;
dummy.full_rect.y = status_bar->full_rect.y + status_bar->full_rect.h;
status_bar->menu_panel = make_menu_panel(&dummy, status_bar->font,
n_terms + N_ELEMENTS(other_elems), elems);
}
static void unselect_menu_buttons(struct menu_panel *menu_panel)
{
if (menu_panel == NULL) {
return;
}
for (size_t i = 0; i < menu_panel->button_bank.number; i++) {
menu_panel->button_bank.buttons[i].selected = false;
menu_panel->button_bank.buttons[i].highlighted = false;
}
unselect_menu_buttons(menu_panel->next);
}
static bool handle_menu_button_mousemotion(struct window *window,
const SDL_Event *event)
{
assert(event->type == SDL_MOUSEMOTION);
bool handled = false;
struct menu_panel *menu_panel = get_menu_panel_by_xy(window->status_bar.menu_panel,
event->motion.x, event->motion.y);
if (menu_panel == NULL) {
return handled;
}
for (size_t i = 0; i < menu_panel->button_bank.number; i++) {
struct button *button = &menu_panel->button_bank.buttons[i];
if (is_point_in_rect(event->motion.x, event->motion.y,
&button->full_rect))
{
/* note that the buttons themselves set "selected" */
button->highlighted = true;
assert(button->callbacks.on_menu != NULL);
button->callbacks.on_menu(window, button, event, menu_panel);
handled = true;
} else {
button->highlighted = false;
/* but we do unset selected */
button->selected = false;
}
}
unselect_menu_buttons(menu_panel->next);
return handled;
}
static bool handle_menu_button_click(struct window *window,
const SDL_Event *event)
{
assert(event->type == SDL_MOUSEBUTTONDOWN
|| event->type == SDL_MOUSEBUTTONUP);
bool handled = false;
struct menu_panel *menu_panel = get_menu_panel_by_xy(window->status_bar.menu_panel,
event->button.x, event->button.y);
if (menu_panel == NULL) {
return handled;
}
for (size_t i = 0; i < menu_panel->button_bank.number; i++) {
struct button *button = &menu_panel->button_bank.buttons[i];
if (is_point_in_rect(event->motion.x, event->motion.y,
&button->full_rect))
{
assert(button->callbacks.on_menu != NULL);
button->callbacks.on_menu(window, button, event, menu_panel);
handled = true;
}
}
return handled;
}
static bool handle_menu_event(struct window *window, const SDL_Event *event)
{
switch (event->type) {
case SDL_MOUSEMOTION:
return handle_menu_button_mousemotion(window, event);
case SDL_MOUSEBUTTONDOWN: /* fallthru */
case SDL_MOUSEBUTTONUP:
return handle_menu_button_click(window, event);
default:
return false;
}
}
static bool is_menu_button_mouse_click(const struct button *button,
const SDL_Event *event)
{
if ((event->type == SDL_MOUSEBUTTONDOWN || event->type == SDL_MOUSEBUTTONUP)
&& is_point_in_rect(event->button.x, event->button.y, &button->full_rect))
{
return true;
}
return false;
}
static bool handle_menu_button(struct window *window,
struct button *button, const SDL_Event *event)
{
CHECK_BUTTON_GROUP_TYPE(button, BUTTON_GROUP_MENU, BUTTON_DATA_NONE);
switch (event->type) {
case SDL_MOUSEMOTION:
if (is_point_in_rect(event->motion.x, event->motion.y, &button->full_rect)) {
/* create menu on mouseover */
if (window->status_bar.menu_panel == NULL) {
load_main_menu_panel(&window->status_bar);
}
button->highlighted = true;
return true;
} else if (handle_menu_event(window, event)) {
return true;
} else if (button->highlighted) {
/* the menu sticks around so that it is not horrible to use */
return true;
}
return false;
default:
if (handle_menu_event(window, event)) {
return true;
} else if (is_menu_button_mouse_click(button, event)) {
/* menu button just eats mouse clicks */
return true;
}
if (window->status_bar.menu_panel != NULL) {
free_menu_panel(window->status_bar.menu_panel);
window->status_bar.menu_panel = NULL;
}
button->highlighted = false;
return false;
}
}
static bool do_button(struct window *window,
struct button *button, const SDL_Event *event)
{
switch (event->type) {
case SDL_MOUSEBUTTONDOWN:
if (is_point_in_rect(event->button.x, event->button.y, &button->full_rect)) {
button->selected = true;
return true;
}
break;
case SDL_MOUSEBUTTONUP:
if (is_point_in_rect(event->button.x, event->button.y, &button->full_rect)
&& button->selected)
{
assert(button->callbacks.on_click != NULL);
button->callbacks.on_click(window, button);
button->selected = false;
return true;
}
break;
case SDL_MOUSEMOTION:
if (is_point_in_rect(event->button.x, event->button.y, &button->full_rect)) {
button->highlighted = true;
return true;
}
break;
}
button->highlighted = false;
button->selected = false;
return false;
}
static bool is_close_to(int a, int b, unsigned range)
{
if (a > 0 && b > 0) {
return (unsigned) ABS(a - b) < range;
} else if (a < 0 && b < 0) {
return (unsigned) ABS(ABS(a) - ABS(b)) < range;
} else {
return (unsigned) (ABS(a) + ABS(b)) < range;
}
}
static bool is_point_in_rect(int x, int y, const SDL_Rect *rect)
{
return x >= rect->x && x < rect->x + rect->w
&& y >= rect->y && y < rect->y + rect->h;
}
static bool is_rect_in_rect(const SDL_Rect *small, const SDL_Rect *big)
{
return small->x >= big->x
&& small->x + small->w <= big->x + big->w
&& small->y >= big->y
&& small->y + small->h <= big->y + big->h;
}
static void fit_rect_in_rect_by_hw(SDL_Rect *small, const SDL_Rect *big)
{
if (small->x < big->x) {
small->w -= big->x - small->x;
small->x = big->x;
}
if (small->x + small->w > big->x + big->w) {
small->w = big->x + big->w - small->x;
}
if (small->y < big->y) {
small->h -= big->y - small->y;
small->y = big->y;
}
if (small->y + small->h > big->y + big->h) {
small->h = big->y + big->h - small->y;
}
}
static void fit_rect_in_rect_by_xy(SDL_Rect *small, const SDL_Rect *big)
{
if (small->x < big->x) {
small->x = big->x;
}
if (small->y < big->y) {
small->y = big->y;
}
if (small->x + small->w > big->x + big->w) {
small->x = MAX(big->x, big->x + big->w - small->w);
}
if (small->y + small->h > big->y + big->h) {
small->y = MAX(big->y, big->y + big->h - small->h);
}
}
static void fit_rect_in_rect_proportional(SDL_Rect *small, const SDL_Rect *big)
{
if (small->x < big->x) {
small->x = big->x;
}
if (small->y < big->y) {
small->y = big->y;
}
if (small->w > big->w) {
small->h = small->h * big->w / small->w;
small->w = big->w;
}
if (small->h > big->h) {
small->w = small->w * big->h / small->h;
small->h = big->h;
}
}
static void resize_rect(SDL_Rect *rect,
int left, int top, int right, int bottom)
{
if (rect->w - left + right <= 0
|| rect->h - top + bottom <= 0)
{
return;
}
rect->x += left;
rect->w -= left;
rect->y += top;
rect->h -= top;
rect->w += right;
rect->h += bottom;
}
/* tries to snap to other term in such a way so that their
* (visible) borders overlap */
static void try_snap(struct window *window,
struct subwindow *subwindow, SDL_Rect *rect)
{
for (size_t i = N_ELEMENTS(window->subwindows); i > 0; i--) {
struct subwindow *other = window->subwindows[i - 1];
if (other == NULL
|| !other->visible
|| other->index == subwindow->index)
{
continue;
}
int ox = other->full_rect.x;
int oy = other->full_rect.y;
int ow = other->full_rect.w;
int oh = other->full_rect.h;
if (oy < rect->y + rect->h && rect->y < oy + oh) {
if (is_close_to(rect->x, ox + ow, DEFAULT_SNAP_RANGE)) {
rect->x = ox + ow - DEFAULT_VISIBLE_BORDER;
}
if (is_close_to(rect->x + rect->w, ox, DEFAULT_SNAP_RANGE)) {
rect->x = ox - rect->w + DEFAULT_VISIBLE_BORDER;
}
}
if (ox < rect->x + rect->w && rect->x < ox + ow) {
if (is_close_to(rect->y, oy + oh, DEFAULT_SNAP_RANGE)) {
rect->y = oy + oh - DEFAULT_VISIBLE_BORDER;
}
if (is_close_to(rect->y + rect->h, oy, DEFAULT_SNAP_RANGE)) {
rect->y = oy - rect->h + DEFAULT_VISIBLE_BORDER;
}
}
}
}
static void start_moving(struct window *window,
struct subwindow *subwindow, const SDL_MouseButtonEvent *mouse)
{
assert(!window->size_state.active);
bring_to_top(window, subwindow);
window->move_state.originx = mouse->x;
window->move_state.originy = mouse->y;
window->move_state.subwindow = subwindow;
window->move_state.moving = true;
}
static void start_sizing(struct window *window,
struct subwindow *subwindow, const SDL_MouseButtonEvent *mouse)
{
assert(!window->move_state.active);
bring_to_top(window, subwindow);
subwindow->sizing_rect = subwindow->full_rect;
int x = mouse->x - (subwindow->full_rect.x + subwindow->full_rect.w / 2);
int y = mouse->y - (subwindow->full_rect.y + subwindow->full_rect.h / 2);
window->size_state.left = x < 0 ? true : false;
window->size_state.top = y < 0 ? true : false;
window->size_state.originx = mouse->x;
window->size_state.originy = mouse->y;
window->size_state.subwindow = subwindow;
window->size_state.sizing = true;
}
static bool handle_menu_mousebuttondown(struct window *window,
const SDL_MouseButtonEvent *mouse)
{
if (window->move_state.active || window->size_state.active) {
struct subwindow *subwindow = get_subwindow_by_xy(window, mouse->x, mouse->y);
if (subwindow != NULL
&& is_rect_in_rect(&subwindow->full_rect, &window->inner_rect))
{
if (window->move_state.active && !window->move_state.moving) {
start_moving(window, subwindow, mouse);
} else if (window->size_state.active && !window->size_state.sizing) {
start_sizing(window, subwindow, mouse);
}
}
return true;
} else if (is_over_status_bar(&window->status_bar, mouse->x, mouse->y)) {
return true;
} else {
return false;
}
}
static void handle_window_closed(const SDL_WindowEvent *event)
{
struct window *window = get_window_by_id(event->windowID);
assert(window != NULL);
if (window->index == MAIN_WINDOW) {
handle_quit();
} else {
for (size_t i = 0; i < N_ELEMENTS(window->subwindows); i++) {
struct subwindow *subwindow = window->subwindows[i];
if (subwindow != NULL) {
clear_pw_flag(subwindow);
}
}
free_window(window);
}
}
static void handle_window_focus(const SDL_WindowEvent *event)
{
assert(event->event == SDL_WINDOWEVENT_FOCUS_GAINED
|| event->event == SDL_WINDOWEVENT_FOCUS_LOST);
struct window *window = get_window_by_id(event->windowID);
if (window == NULL) {
/* when window is closed, it sends FOCUS_LOST event */
assert(event->event == SDL_WINDOWEVENT_FOCUS_LOST);
return;
}
switch (event->event) {
case SDL_WINDOWEVENT_FOCUS_GAINED:
window->focus = true;
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
window->focus = false;
break;
}
}
static void handle_last_resize_event(int num_events, const SDL_Event *events)
{
assert(num_events > 0);
for (int i = num_events - 1; i >= 0; i--) {
if (events[i].window.event == SDL_WINDOWEVENT_RESIZED) {
const SDL_WindowEvent event = events[i].window;
struct window *window = get_window_by_id(event.windowID);
assert(window != NULL);
resize_window(window, event.data1, event.data2);
return;
}
}
}
static void handle_windowevent(const SDL_WindowEvent *event)
{
SDL_Event events[128];
events[0].window = *event;
int num_events = 1 + SDL_PeepEvents(events + 1, (int) N_ELEMENTS(events) - 1,
SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT);
bool resize = false;
for (int i = 0; i < num_events; i++) {
switch (events[i].window.event) {
case SDL_WINDOWEVENT_RESIZED:
/* just for efficiency */
resize = true;
break;
case SDL_WINDOWEVENT_CLOSE:
handle_window_closed(&events[i].window);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED: /* fallthru */
case SDL_WINDOWEVENT_FOCUS_LOST:
handle_window_focus(&events[i].window);
break;
}
}
if (resize) {
handle_last_resize_event(num_events, events);
}
redraw_all_windows(false);
}
static void resize_subwindow(struct subwindow *subwindow)
{
SDL_DestroyTexture(subwindow->texture);
subwindow->full_rect = subwindow->sizing_rect;
if (!adjust_subwindow_geometry(subwindow->window, subwindow)) {
quit_fmt("bad_geometry of subwindow %u in window %u",
subwindow->index, subwindow->window->index);
}
subwindow->texture = make_subwindow_texture(subwindow->window,
subwindow->full_rect.w, subwindow->full_rect.h);
render_clear(subwindow->window, subwindow->texture, &subwindow->color);
render_borders(subwindow);
term *old = Term;
Term_activate(subwindow->term);
Term_resize(subwindow->cols, subwindow->rows);
/* XXX if we don't redraw the term, resizing in birth screen is buggy */
Term_redraw();
Term_activate(old);
refresh_angband_terms();
}
static void do_sizing(struct window *window, int x, int y)
{
struct size_state *size_state = &window->size_state;
assert(size_state->subwindow != NULL);
SDL_Rect rect = size_state->subwindow->sizing_rect;
int newx = x - size_state->originx;
int newy = y - size_state->originy;
int left = size_state->left ? newx : 0;
int top = size_state->top ? newy : 0;
int right = size_state->left ? 0 : newx;
int bottom = size_state->top ? 0 : newy;
resize_rect(&rect, left, top, right, bottom);
fit_rect_in_rect_by_hw(&rect, &window->inner_rect);
if (is_ok_col_row(size_state->subwindow,
&rect,
size_state->subwindow->font_width,
size_state->subwindow->font_height))
{
size_state->subwindow->sizing_rect = rect;
}
size_state->originx = x;
size_state->originy = y;
}
static void do_moving(struct window *window, int x, int y)
{
struct move_state *move_state = &window->move_state;
assert(move_state->subwindow != NULL);
SDL_Rect *rect = &move_state->subwindow->full_rect;
rect->x += x - move_state->originx;
rect->y += y - move_state->originy;
try_snap(window, move_state->subwindow, rect);
fit_rect_in_rect_by_xy(rect, &window->inner_rect);
move_state->originx = x;
move_state->originy = y;
}
static bool handle_menu_mousebuttonup(struct window *window,
const SDL_MouseButtonEvent *mouse)
{
if (window->move_state.active && window->move_state.moving) {
window->move_state.moving = false;
} else if (window->size_state.active && window->size_state.sizing) {
window->size_state.sizing = false;
if (window->size_state.subwindow != NULL) {
resize_subwindow(window->size_state.subwindow);
}
}
if (window->move_state.active
|| window->size_state.active
|| is_over_status_bar(&window->status_bar, mouse->x, mouse->y))
{
return true;
} else {
return false;
}
}
static bool handle_menu_mousemotion(struct window *window,
const SDL_MouseMotionEvent *mouse)
{
if (window->move_state.moving) {
do_moving(window, mouse->x, mouse->y);
return true;
} else if (window->size_state.sizing) {
do_sizing(window, mouse->x, mouse->y);
return true;
} else if (window->move_state.active || window->size_state.active) {
return true;
} else if (is_over_status_bar(&window->status_bar, mouse->x, mouse->y)) {
return true;
}
return false;
}
static bool handle_menu_keyboard(struct window *window, const SDL_Event *event)
{
if (window->move_state.active || window->size_state.active) {
return true;
}
SDL_Event key = *event;
/* the user pressed a key; probably wants to play? */
SDL_PushEvent(&key);
return false;
}
static bool handle_status_bar_buttons(struct window *window,
const SDL_Event *event)
{
bool handled = false;
for (size_t i = 0; i < window->status_bar.button_bank.number; i++) {
struct button *button = &window->status_bar.button_bank.buttons[i];
if (button->callbacks.on_event != NULL) {
handled |= button->callbacks.on_event(window, button, event);
} else {
handled |= do_button(window, button, event);
}
}
return handled;
}
static void redraw_status_bar_buttons(struct window *window)
{
SDL_Event shutdown = {.type = SDL_USEREVENT};
(void) handle_status_bar_buttons(window, &shutdown);
}
static bool handle_menu_windowevent(struct window *window,
const SDL_WindowEvent *event)
{
if (window->move_state.active) {
signal_move_state(window);
} else if (window->size_state.active) {
signal_size_state(window);
}
redraw_status_bar_buttons(window);
handle_windowevent(event);
return false;
}
static bool is_event_windowid_ok(const struct window *window, const SDL_Event *event)
{
switch (event->type) {
case SDL_KEYDOWN: /* fallthru */
case SDL_KEYUP:
return event->key.windowID == window->id;
case SDL_TEXTINPUT:
return event->text.windowID == window->id;
case SDL_MOUSEMOTION:
return event->motion.windowID == window->id;
case SDL_MOUSEBUTTONDOWN: /* fallthru */
case SDL_MOUSEBUTTONUP:
return event->button.windowID == window->id;
default:
return true;
}
}
/* returns true for events that should be processed by buttons */
static bool is_ok_button_event(const struct window *window, const SDL_Event *event)
{
switch (event->type) {
case SDL_KEYDOWN: /* fallthru */
case SDL_KEYUP: /* fallthru */
case SDL_TEXTINPUT: /* fallthru */
return is_event_windowid_ok(window, event);
case SDL_MOUSEMOTION: /* fallthru */
case SDL_MOUSEBUTTONDOWN: /* fallthru */
case SDL_MOUSEBUTTONUP:
return window->focus && is_event_windowid_ok(window, event);
case SDL_USEREVENT:
return true;
default:
return false;
}
}
static bool handle_status_bar_events(struct window *window,
const SDL_Event *event)
{
if (!is_event_windowid_ok(window, event)) {
/* just in case */
if (window->move_state.active) {
signal_move_state(window);
} else if (window->size_state.active) {
signal_size_state(window);
}
return false;
}
switch (event->type) {
case SDL_MOUSEMOTION:
return handle_menu_mousemotion(window, &event->motion);
case SDL_MOUSEBUTTONDOWN:
return handle_menu_mousebuttondown(window, &event->button);
case SDL_MOUSEBUTTONUP:
return handle_menu_mousebuttonup(window, &event->button);
case SDL_KEYDOWN: /* fallthru */
case SDL_KEYUP: /* fallthru */
case SDL_TEXTEDITING: /* fallthru */
case SDL_TEXTINPUT:
return handle_menu_keyboard(window, event);
case SDL_WINDOWEVENT:
return handle_menu_windowevent(window, &event->window);
case SDL_QUIT:
handle_quit();
return false;
default:
return false;
}
}
static void do_status_bar_loop(struct window *window)
{
window->status_bar.in_menu = true;
bool keep_going = true;
while (keep_going) {
SDL_Delay(window->delay);
SDL_Event event;
SDL_WaitEvent(&event);
bool handled = false;
if (is_ok_button_event(window, &event)
&& !window->move_state.moving
&& !window->size_state.sizing)
{
handled = handle_status_bar_buttons(window, &event);
}
if (event.type == SDL_MOUSEMOTION) {
/* annoying mousemotion spam! */
SDL_FlushEvent(SDL_MOUSEMOTION);
}
if (!handled) {
/* so the user didnt click on a button */
keep_going = handle_status_bar_events(window, &event);
}
redraw_window(window);
}
window->status_bar.in_menu = false;
}
static bool has_visible_subwindow(const struct window *window, unsigned index)
{
return get_subwindow_by_index(window, index, true) != NULL;
}
static bool handle_mousemotion(const SDL_MouseMotionEvent *mouse)
{
struct window *window = get_window_by_id(mouse->windowID);
if (is_over_status_bar(&window->status_bar, mouse->x, mouse->y)) {
do_status_bar_loop(window);
}
/* dont need other mousemotion events */
SDL_FlushEvent(SDL_MOUSEMOTION);
return false;
}
/* x and y are relative to window */
static bool get_colrow_from_xy(const struct subwindow *subwindow,
int x, int y, int *col, int *row)
{
SDL_Rect rect = {
subwindow->full_rect.x + subwindow->inner_rect.x,
subwindow->full_rect.y + subwindow->inner_rect.y,
subwindow->inner_rect.w,
subwindow->inner_rect.h
};
if (!is_point_in_rect(x, y, &rect)) {
return false;
}
*col = (x - rect.x) / subwindow->font_width;
*row = (y - rect.y) / subwindow->font_height;
return true;
}
static byte translate_key_mods(Uint16 mods)
{
#define TRANSLATE_K_MOD(m, k) ((m) & mods ? (k) : 0)
byte angband_mods =
TRANSLATE_K_MOD(KMOD_SHIFT, KC_MOD_SHIFT)
| TRANSLATE_K_MOD(KMOD_CTRL, KC_MOD_CONTROL)
| TRANSLATE_K_MOD(KMOD_ALT, KC_MOD_ALT)
| TRANSLATE_K_MOD(KMOD_GUI, KC_MOD_META);
#undef TRANSLATE_K_MOD
return angband_mods;
}
static bool handle_mousebuttondown(const SDL_MouseButtonEvent *mouse)
{
struct window *window = get_window_by_id(mouse->windowID);
assert(window != NULL);
struct subwindow *subwindow = get_subwindow_by_xy(window, mouse->x, mouse->y);
if (subwindow == NULL) {
/* not an error, the user clicked in some random place */
return false;
} else if (!subwindow->top) {
bring_to_top(window, subwindow);
redraw_window(window);
return false;
}
/* terms that are not main do not react to events, and main term
* lives in main window */
if (window->index != MAIN_WINDOW) {
return false;
}
/* all magic numbers are from ui-term.c and ui-context.c :) */
int button;
switch (mouse->button) {
case SDL_BUTTON_LEFT:
button = 1;
break;
case SDL_BUTTON_RIGHT:
button = 2;
break;
default:
/* XXX other buttons? */
return false;
}
int col;
int row;
if (!get_colrow_from_xy(subwindow, mouse->x, mouse->y, &col, &row)) {
return false;
}
byte mods = translate_key_mods(SDL_GetModState());
/* apparently mouse buttons dont get this */
mods &= ~KC_MOD_META;
button |= mods << 4;
term *old = Term;
Term_activate(subwindow->term);
Term_mousepress(col, row, button);
Term_activate(old);
return true;
}
static bool handle_keydown(const SDL_KeyboardEvent *key)
{
byte mods = translate_key_mods(key->keysym.mod);
keycode_t ch = 0;
if (!(key->keysym.mod & KMOD_NUM)
|| (key->keysym.mod & KMOD_NUM
&& key->keysym.mod & KMOD_SHIFT))
{
switch (key->keysym.sym) {
/* keypad keys without numlock */
case SDLK_KP_0: ch = '0'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_1: ch = '1'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_2: ch = '2'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_3: ch = '3'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_4: ch = '4'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_5: ch = '5'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_6: ch = '6'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_7: ch = '7'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_8: ch = '8'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_9: ch = '9'; mods |= KC_MOD_KEYPAD; break;
}
}
switch (key->keysym.sym) {
case SDLK_KP_MULTIPLY: ch = '*'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_PERIOD: ch = '.'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_DIVIDE: ch = '/'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_EQUALS: ch = '='; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_MINUS: ch = '-'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_PLUS: ch = '+'; mods |= KC_MOD_KEYPAD; break;
case SDLK_KP_ENTER: ch = KC_ENTER; mods |= KC_MOD_KEYPAD; break;
/* arrow keys */
case SDLK_UP: ch = ARROW_UP; break;
case SDLK_DOWN: ch = ARROW_DOWN; break;
case SDLK_LEFT: ch = ARROW_LEFT; break;
case SDLK_RIGHT: ch = ARROW_RIGHT; break;
/* text editing keys */
case SDLK_BACKSPACE: ch = KC_BACKSPACE; break;
case SDLK_PAGEDOWN: ch = KC_PGDOWN; break;
case SDLK_PAGEUP: ch = KC_PGUP; break;
case SDLK_INSERT: ch = KC_INSERT; break;
case SDLK_DELETE: ch = KC_DELETE; break;
case SDLK_RETURN: ch = KC_ENTER; break;
case SDLK_ESCAPE: ch = ESCAPE; break;
case SDLK_HOME: ch = KC_HOME; break;
case SDLK_END: ch = KC_END; break;
case SDLK_TAB: ch = KC_TAB; break;
/* function keys */
case SDLK_F1: ch = KC_F1; break;
case SDLK_F2: ch = KC_F2; break;
case SDLK_F3: ch = KC_F3; break;
case SDLK_F4: ch = KC_F4; break;
case SDLK_F5: ch = KC_F5; break;
case SDLK_F6: ch = KC_F6; break;
case SDLK_F7: ch = KC_F7; break;
case SDLK_F8: ch = KC_F8; break;
case SDLK_F9: ch = KC_F9; break;
case SDLK_F10: ch = KC_F10; break;
case SDLK_F11: ch = KC_F11; break;
case SDLK_F12: ch = KC_F12; break;
case SDLK_F13: ch = KC_F13; break;
case SDLK_F14: ch = KC_F14; break;
case SDLK_F15: ch = KC_F15; break;
}
if (mods & KC_MOD_CONTROL) {
switch (key->keysym.sym) {
case SDLK_0: ch = '0'; break;
case SDLK_1: ch = '1'; break;
case SDLK_2: ch = '2'; break;
case SDLK_3: ch = '3'; break;
case SDLK_4: ch = '4'; break;
case SDLK_5: ch = '5'; break;
case SDLK_6: ch = '6'; break;
case SDLK_7: ch = '7'; break;
case SDLK_8: ch = '8'; break;
case SDLK_9: ch = '9'; break;
case SDLK_a: ch = 'a'; break;
case SDLK_b: ch = 'b'; break;
case SDLK_c: ch = 'c'; break;
case SDLK_d: ch = 'd'; break;
case SDLK_e: ch = 'e'; break;
case SDLK_f: ch = 'f'; break;
case SDLK_g: ch = 'g'; break;
case SDLK_h: ch = 'h'; break;
case SDLK_i: ch = 'i'; break;
case SDLK_j: ch = 'j'; break;
case SDLK_k: ch = 'k'; break;
case SDLK_l: ch = 'l'; break;
case SDLK_m: ch = 'm'; break;
case SDLK_n: ch = 'n'; break;
case SDLK_o: ch = 'o'; break;
case SDLK_p: ch = 'p'; break;
case SDLK_q: ch = 'q'; break;
case SDLK_r: ch = 'r'; break;
case SDLK_s: ch = 's'; break;
case SDLK_t: ch = 't'; break;
case SDLK_u: ch = 'u'; break;
case SDLK_v: ch = 'v'; break;
case SDLK_w: ch = 'w'; break;
case SDLK_x: ch = 'x'; break;
case SDLK_y: ch = 'y'; break;
case SDLK_z: ch = 'z'; break;
}
}
if (ch) {
if (mods & KC_MOD_CONTROL && !(mods & KC_MOD_KEYPAD)) {
ch = KTRL(ch);
if (!MODS_INCLUDE_CONTROL(ch)) {
mods &= ~KC_MOD_CONTROL;
}
}
Term_keypress(ch, mods);
return true;
} else {
return false;
}
}
static keycode_t utf8_to_codepoint(const char *utf8_string)
{
/* hex == binary
* 0x00 == 00000000
* 0x80 == 10000000
* 0xc0 == 11000000
* 0xe0 == 11100000
* 0xf0 == 11110000
* 0xf8 == 11111000
* 0x3f == 00111111
* 0x1f == 00011111
* 0x0f == 00001111
* 0x07 == 00000111 */
keycode_t key = 0;
#define IS_UTF8_INFO(mask, result) (((unsigned char) utf8_string[0] & (mask)) == (result))
#define EXTRACT_UTF8_INFO(pos, mask, shift) (((unsigned char) utf8_string[(pos)] & (mask)) << (shift))
/* 6 is the number of information bits in a utf8 continuation byte (10xxxxxx) */
if (IS_UTF8_INFO(0x80, 0)) {
key = utf8_string[0];
} else if (IS_UTF8_INFO(0xe0, 0xc0)) {
key = EXTRACT_UTF8_INFO(0, 0x1f, 6)
| EXTRACT_UTF8_INFO(1, 0x3f, 0);
} else if (IS_UTF8_INFO(0xf0, 0xe0)) {
key = EXTRACT_UTF8_INFO(0, 0x0f, 12)
| EXTRACT_UTF8_INFO(1, 0x3f, 6)
| EXTRACT_UTF8_INFO(2, 0x3f, 0);
} else if (IS_UTF8_INFO(0xf8, 0xf0)) {
key = EXTRACT_UTF8_INFO(0, 0x07, 18)
| EXTRACT_UTF8_INFO(1, 0x3f, 12)
| EXTRACT_UTF8_INFO(2, 0x3f, 6)
| EXTRACT_UTF8_INFO(3, 0x3f, 0);
}
#undef IS_UTF8_INFO
#undef EXTRACT_UTF8_INFO
return key;
}
static bool handle_text_input(const SDL_TextInputEvent *input)
{
keycode_t ch = utf8_to_codepoint(input->text);
if (ch == 0) {
return false;
}
byte mods = translate_key_mods(SDL_GetModState());
if (mods & KC_MOD_SHIFT) {
switch (ch) {
/* maybe the player pressed key on keypad? */
case '0': /* fallthru */
case '1': /* fallthru */
case '2': /* fallthru */
case '3': /* fallthru */
case '4': /* fallthru */
case '5': /* fallthru */
case '6': /* fallthru */
case '7': /* fallthru */
case '8': /* fallthru */
case '9':
return false;
}
}
if (!MODS_INCLUDE_SHIFT(ch)) {
mods &= ~KC_MOD_SHIFT;
}
Term_keypress(ch, mods);
return true;
}
static void wait_anykey(void)
{
SDL_Event event;
SDL_EventType expected = SDL_USEREVENT;
while (true) {
SDL_WaitEvent(&event);
if (event.type == expected) {
return;
}
switch (event.type) {
case SDL_KEYDOWN:
expected = SDL_KEYUP;
break;;
case SDL_MOUSEBUTTONDOWN:
expected = SDL_MOUSEBUTTONUP;
break;
case SDL_MOUSEMOTION:
SDL_FlushEvent(SDL_MOUSEMOTION);
break;
case SDL_QUIT:
handle_quit();
break;
case SDL_WINDOWEVENT:
handle_windowevent(&event.window);
return;
}
}
}
static void handle_quit(void)
{
/* XXX copied from main-sdl.c */
if (character_generated && inkey_flag) {
/* no idea what that does :) */
msg_flag = false;
save_game();
}
quit(NULL);
}
static bool get_event(void)
{
SDL_Event event;
if (!SDL_PollEvent(&event)) {
return false;
}
switch (event.type) {
case SDL_KEYDOWN:
return handle_keydown(&event.key);
case SDL_TEXTINPUT:
return handle_text_input(&event.text);
case SDL_MOUSEMOTION:
return handle_mousemotion(&event.motion);
case SDL_MOUSEBUTTONDOWN:
return handle_mousebuttondown(&event.button);
case SDL_WINDOWEVENT:
handle_windowevent(&event.window);
return false;
case SDL_QUIT:
handle_quit();
return false;
default:
return false;
}
}
static void refresh_angband_terms(void)
{
if (!character_dungeon) {
return;
}
term *old = Term;
Term_activate(term_screen);
/* XXX XXX this is basically do_cmd_redraw(), just without EVENT_FLUSH_INPUT */
{
/* XXX XXX this works for refreshing monster's attrs */
event_signal_point(EVENT_MAP, -1, -1);
Term_flush();
verify_panel();
player->upkeep->notice |= (PN_COMBINE);
player->upkeep->update |= (PU_TORCH | PU_INVEN);
player->upkeep->update |= (PU_BONUS | PU_HP | PU_SPELLS);
player->upkeep->update |= (PU_UPDATE_VIEW | PU_MONSTERS);
player->upkeep->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_INVEN |
PR_EQUIP | PR_MESSAGE | PR_MONSTER |
PR_OBJECT | PR_MONLIST | PR_ITEMLIST);
Term_clear();
handle_stuff(player);
move_cursor_relative(player->px, player->py);
for (size_t i = 0; i < ANGBAND_TERM_MAX; i++) {
if (angband_term[i] == NULL) {
continue;
}
Term_activate(angband_term[i]);
Term_redraw();
}
}
Term_activate(old);
redraw_all_windows(false);
}
static errr term_xtra_event(int v)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
redraw_all_windows(true);
if (v) {
while (true) {
for (int i = 0; i < DEFAULT_IDLE_UPDATE_PERIOD; i++) {
if (get_event()) {
return 0;
}
SDL_Delay(subwindow->window->delay);
}
idle_update();
}
} else {
(void) get_event();
}
return 0;
}
static errr term_xtra_flush(void)
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_WINDOWEVENT:
handle_windowevent(&event.window);
break;
}
}
return 0;
}
static errr term_xtra_clear(void)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
render_fill_rect(subwindow->window,
subwindow->texture, &subwindow->inner_rect, &subwindow->color);
subwindow->window->dirty = true;
return 0;
}
static errr term_xtra_fresh(void)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
if (!subwindow->window->status_bar.in_menu) {
try_redraw_window(subwindow->window);
}
return 0;
}
static errr term_xtra_delay(int v)
{
if (v > 0) {
term_xtra_event(0);
SDL_Delay(v);
}
return 0;
}
static errr term_xtra_react(void)
{
init_colors();
return 0;
}
static errr term_xtra_hook(int n, int v)
{
switch (n) {
case TERM_XTRA_EVENT:
return term_xtra_event(v);
case TERM_XTRA_DELAY:
return term_xtra_delay(v);
case TERM_XTRA_FLUSH:
return term_xtra_flush();
case TERM_XTRA_CLEAR:
return term_xtra_clear();
case TERM_XTRA_FRESH:
return term_xtra_fresh();
case TERM_XTRA_REACT:
return term_xtra_react();
default:
return 0;
}
}
static errr term_curs_hook(int col, int row)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
render_cursor(subwindow, col, row, false);
subwindow->window->dirty = true;
return 0;
}
static errr term_bigcurs_hook(int col, int row)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
render_cursor(subwindow, col, row, true);
subwindow->window->dirty = true;
return 0;
}
static errr term_wipe_hook(int col, int row, int n)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
SDL_Rect rect = {
subwindow->inner_rect.x + col * subwindow->font_width,
subwindow->inner_rect.y + row * subwindow->font_height,
n * subwindow->font_width,
subwindow->font_height
};
render_fill_rect(subwindow->window, subwindow->texture, &rect, &subwindow->color);
subwindow->window->dirty = true;
return 0;
}
static errr term_text_hook(int col, int row, int n, int a, const wchar_t *s)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
SDL_Color fg = g_colors[a % MAX_COLORS];
SDL_Color bg;
switch (a / MAX_COLORS) {
case BG_BLACK:
bg = subwindow->color;
break;
case BG_SAME:
bg = fg;
break;
case BG_DARK:
bg = g_colors[DEFAULT_SHADE_COLOR];
break;
default:
/* debugging */
bg = g_colors[DEFAULT_ERROR_COLOR];
break;
}
bg.a = subwindow->color.a;
SDL_Rect rect = {
subwindow->inner_rect.x + col * subwindow->font_width,
subwindow->inner_rect.y + row * subwindow->font_height,
n * subwindow->font_width,
subwindow->font_height
};
render_fill_rect(subwindow->window, subwindow->texture, &rect, &bg);
rect.w = subwindow->font_width;
for (int i = 0; i < n; i++) {
render_glyph_mono(subwindow->window,
subwindow->font, subwindow->texture,
rect.x, rect.y, &fg, (uint32_t) s[i]);
rect.x += subwindow->font_width;
}
subwindow->window->dirty = true;
return 0;
}
static errr term_pict_hook(int col, int row, int n,
const int *ap, const wchar_t *cp, const int *tap, const int *tcp)
{
struct subwindow *subwindow = Term->data;
assert(subwindow != NULL);
assert(subwindow->window->graphics.texture != NULL);
for (int i = 0; i < n; i++) {
render_tile_font_scaled(subwindow, col + i, row, tap[i], tcp[i], true);
if (tap[i] == ap[i] && tcp[i] == cp[i]) {
continue;
}
render_tile_font_scaled(subwindow, col + i, row, ap[i], cp[i], false);
}
subwindow->window->dirty = true;
return 0;
}
static void term_view_map_shared(struct subwindow *subwindow,
SDL_Texture *map, int w, int h)
{
render_all(subwindow->window);
SDL_Rect dst = {
0, 0,
w + 2 * DEFAULT_VISIBLE_BORDER,
h + 2 * DEFAULT_VISIBLE_BORDER
};
SDL_Rect full = {
0, 0,
subwindow->window->full_rect.w,
subwindow->window->full_rect.h
};
fit_rect_in_rect_proportional(&dst, &full);
dst.x = (subwindow->window->full_rect.w - dst.w) / 2;
dst.y = (subwindow->window->full_rect.h - dst.h) / 2;