367 lines
10 KiB
C
367 lines
10 KiB
C
|
#include "./menu.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <stddef.h>
|
||
|
|
||
|
#include "util/glyphas/block.h"
|
||
|
#include "util/glyphas/cache.h"
|
||
|
#include "util/math/constant.h"
|
||
|
#include "util/math/vector.h"
|
||
|
|
||
|
#include "core/locommon/easing.h"
|
||
|
#include "core/locommon/input.h"
|
||
|
#include "core/locommon/screen.h"
|
||
|
#include "core/locommon/ticker.h"
|
||
|
#include "core/loplayer/player.h"
|
||
|
#include "core/loplayer/stance.h"
|
||
|
#include "core/loresource/set.h"
|
||
|
#include "core/loresource/text.h"
|
||
|
#include "core/loshader/set.h"
|
||
|
|
||
|
#define FADE_SPEED_ 2
|
||
|
|
||
|
#define STANCE_TEXT_LINEHEIGHT_ 1.5f
|
||
|
#define STANCE_TEXT_MAX_WIDTH_ 3.5f /* inch */
|
||
|
|
||
|
#define STANCE_NAME_RESERVE_ 32
|
||
|
#define STANCE_DESC_RESERVE_ 256
|
||
|
#define STANCE_NOTE_RESERVE_ 256
|
||
|
|
||
|
#define STANCE_NAME_COLOR_ vec4(1, 1, 1, 1)
|
||
|
#define STANCE_DESC_COLOR_ vec4(1, 1, 1, 1)
|
||
|
#define STANCE_NOTE_COLOR_ vec4(.8f, .8f, 0, 1)
|
||
|
|
||
|
#define STANCE_ICON_HIGHLIGHT_SPEED_ 5
|
||
|
#define STANCE_ICON_HIGHLIGHT_SIZE_ .2f
|
||
|
|
||
|
static void loui_menu_calc_geometry_(
|
||
|
loui_menu_t* menu, const locommon_screen_t* screen) {
|
||
|
assert(menu != NULL);
|
||
|
assert(locommon_screen_valid(screen));
|
||
|
|
||
|
typeof(menu->geo)* g = &menu->geo;
|
||
|
|
||
|
# define calc_font_geo_(name, sz) do { \
|
||
|
locommon_screen_calc_pixels_from_inch( \
|
||
|
screen, &g->name##_fontpx, sz); \
|
||
|
locommon_screen_calc_winpos_from_pixels( \
|
||
|
screen, &g->name##_fontsz, &g->big_fontpx); \
|
||
|
} while (0)
|
||
|
|
||
|
calc_font_geo_(big, &vec2(.20f, .20f));
|
||
|
calc_font_geo_(normal, &vec2(.16f, .16f));
|
||
|
calc_font_geo_(small, &vec2(.12f, .12f));
|
||
|
|
||
|
# undef calc_font_geo_
|
||
|
|
||
|
locommon_screen_calc_winpos_from_inch(
|
||
|
screen, &g->stance_icon_interval, &vec2(2, 2));
|
||
|
locommon_screen_calc_winpos_from_inch(
|
||
|
screen, &g->stance_iconsz, &vec2(.3f, .3f));
|
||
|
}
|
||
|
|
||
|
static void loui_menu_initialize_stance_(
|
||
|
loui_menu_t* menu,
|
||
|
loui_menu_stance_t* stance,
|
||
|
const locommon_screen_t* screen,
|
||
|
const char* name,
|
||
|
const char* desc,
|
||
|
const char* note) {
|
||
|
assert(menu != NULL);
|
||
|
assert(stance != NULL);
|
||
|
assert(name != NULL);
|
||
|
assert(desc != NULL);
|
||
|
assert(note != NULL);
|
||
|
assert(locommon_screen_valid(screen));
|
||
|
|
||
|
*stance = (typeof(*stance)) {0};
|
||
|
|
||
|
const float theta =
|
||
|
(stance - menu->stances)*1.f/LOPLAYER_STANCE_COUNT*2*MATH_PI +
|
||
|
MATH_PI/2;
|
||
|
stance->pos.x = cos(theta) * menu->geo.stance_icon_interval.x;
|
||
|
stance->pos.y = sin(theta) * menu->geo.stance_icon_interval.y;
|
||
|
|
||
|
const float width = screen->dpi.x*STANCE_TEXT_MAX_WIDTH_;
|
||
|
const vec2_t invreso = vec2(2/screen->resolution.x, 2/screen->resolution.y);
|
||
|
|
||
|
stance->name = glyphas_block_new(
|
||
|
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL,
|
||
|
-menu->geo.big_fontpx.y*STANCE_TEXT_LINEHEIGHT_,
|
||
|
INT32_MAX,
|
||
|
STANCE_NAME_RESERVE_);
|
||
|
glyphas_block_add_characters(
|
||
|
stance->name,
|
||
|
menu->font.sans.big,
|
||
|
&STANCE_NAME_COLOR_,
|
||
|
name,
|
||
|
strlen(name));
|
||
|
glyphas_block_scale(stance->name, &invreso);
|
||
|
|
||
|
stance->desc = glyphas_block_new(
|
||
|
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL,
|
||
|
-menu->geo.normal_fontpx.y*STANCE_TEXT_LINEHEIGHT_,
|
||
|
width,
|
||
|
STANCE_DESC_RESERVE_);
|
||
|
glyphas_block_add_characters(
|
||
|
stance->desc,
|
||
|
menu->font.sans.normal,
|
||
|
&STANCE_DESC_COLOR_,
|
||
|
desc,
|
||
|
strlen(desc));
|
||
|
glyphas_block_scale(stance->desc, &invreso);
|
||
|
|
||
|
stance->note = glyphas_block_new(
|
||
|
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL,
|
||
|
-menu->geo.small_fontpx.y,
|
||
|
width,
|
||
|
STANCE_NOTE_RESERVE_);
|
||
|
glyphas_block_add_characters(
|
||
|
stance->note,
|
||
|
menu->font.serif.small,
|
||
|
&STANCE_NOTE_COLOR_,
|
||
|
note,
|
||
|
strlen(note));
|
||
|
glyphas_block_scale(stance->note, &invreso);
|
||
|
|
||
|
glyphas_block_set_origin(stance->name, &vec2(.5f, 0));
|
||
|
glyphas_block_set_origin(stance->desc, &vec2(.5f, 0));
|
||
|
glyphas_block_set_origin(stance->note, &vec2(.5f, 0));
|
||
|
|
||
|
vec2_t dummy;
|
||
|
|
||
|
vec2_t namesz;
|
||
|
glyphas_block_calculate_geometry(stance->name, &dummy, &namesz);
|
||
|
vec2_t descsz;
|
||
|
glyphas_block_calculate_geometry(stance->desc, &dummy, &descsz);
|
||
|
vec2_t notesz;
|
||
|
glyphas_block_calculate_geometry(stance->note, &dummy, ¬esz);
|
||
|
|
||
|
const float note_padding = menu->geo.small_fontsz.y;
|
||
|
|
||
|
float y = (namesz.y + descsz.y + note_padding + notesz.y)/2;
|
||
|
glyphas_block_translate(stance->name, &vec2(0, y));
|
||
|
|
||
|
y -= namesz.y;
|
||
|
glyphas_block_translate(stance->desc, &vec2(0, y));
|
||
|
|
||
|
y -= descsz.y + note_padding;
|
||
|
glyphas_block_translate(stance->note, &vec2(0, y));
|
||
|
}
|
||
|
|
||
|
static void loui_menu_deinitialize_stance_(loui_menu_stance_t* stance) {
|
||
|
assert(stance != NULL);
|
||
|
|
||
|
glyphas_block_delete(stance->name);
|
||
|
glyphas_block_delete(stance->desc);
|
||
|
glyphas_block_delete(stance->note);
|
||
|
}
|
||
|
|
||
|
static glyphas_block_t* loui_menu_create_exit_text_(
|
||
|
loui_menu_t* menu, const locommon_screen_t* screen, const char* text) {
|
||
|
assert(menu != NULL);
|
||
|
assert(screen != NULL);
|
||
|
assert(text != NULL);
|
||
|
|
||
|
glyphas_block_t* b = glyphas_block_new(
|
||
|
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL,
|
||
|
-menu->geo.normal_fontpx.y,
|
||
|
INT32_MAX,
|
||
|
32);
|
||
|
glyphas_block_add_characters(
|
||
|
b, menu->font.sans.normal, &vec4(1, 1, 1, 1), text, strlen(text));
|
||
|
|
||
|
glyphas_block_scale(
|
||
|
b, &vec2(2.f/screen->resolution.x, 2.f/screen->resolution.y));
|
||
|
glyphas_block_set_origin(b, &vec2(.5f, -1));
|
||
|
glyphas_block_translate(b, &vec2(0, -1));
|
||
|
return b;
|
||
|
}
|
||
|
|
||
|
static loui_menu_stance_t* loui_menu_find_stance_icon_at_(
|
||
|
loui_menu_t* menu, const vec2_t* pos) {
|
||
|
assert(menu != NULL);
|
||
|
assert(vec2_valid(pos));
|
||
|
|
||
|
for (size_t i = 0; i < LOPLAYER_STANCE_COUNT; ++i) {
|
||
|
vec2_t p;
|
||
|
vec2_sub(&p, pos, &menu->stances[i].pos);
|
||
|
p.x /= menu->geo.stance_iconsz.x;
|
||
|
p.y /= menu->geo.stance_iconsz.y;
|
||
|
if (fabsf(p.x) + fabsf(p.y) < 1) {
|
||
|
return &menu->stances[i];
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void loui_menu_update_stances_(
|
||
|
loui_menu_t* menu, const locommon_input_t* input) {
|
||
|
assert(menu != NULL);
|
||
|
|
||
|
const float dt = menu->ticker->delta_f;
|
||
|
|
||
|
menu->hovered_stance =
|
||
|
input == NULL? NULL:
|
||
|
loui_menu_find_stance_icon_at_(menu, &input->cursor);
|
||
|
|
||
|
for (size_t i = 0; i < LOPLAYER_STANCE_COUNT; ++i) {
|
||
|
loui_menu_stance_t* s = &menu->stances[i];
|
||
|
locommon_easing_linear_float(
|
||
|
&s->highlight,
|
||
|
s == menu->hovered_stance,
|
||
|
dt*STANCE_ICON_HIGHLIGHT_SPEED_);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void loui_menu_draw_stance_icons_(const loui_menu_t* menu) {
|
||
|
assert(menu != NULL);
|
||
|
|
||
|
for (size_t i = 0; i < LOPLAYER_STANCE_COUNT; ++i) {
|
||
|
const loui_menu_stance_t* s = &menu->stances[i];
|
||
|
|
||
|
const loshader_menu_stance_id_t id =
|
||
|
loplayer_stance_set_has(&menu->player->stances, i)?
|
||
|
loplayer_stance_get_menu_shader_id(i):
|
||
|
LOSHADER_MENU_STANCE_ID_EMPTY;
|
||
|
|
||
|
vec2_t sz = menu->geo.stance_iconsz;
|
||
|
vec2_muleq(&sz, 1+powf(s->highlight, 2.f)*STANCE_ICON_HIGHLIGHT_SIZE_);
|
||
|
|
||
|
loshader_menu_stance_drawer_add_instance(
|
||
|
&menu->shaders->drawer.menu_stance,
|
||
|
&(loshader_menu_stance_drawer_instance_t) {
|
||
|
.id = id,
|
||
|
.pos = s->pos,
|
||
|
.size = sz,
|
||
|
.alpha = menu->alpha,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void loui_menu_draw_stance_text_(const loui_menu_t* menu) {
|
||
|
assert(menu != NULL);
|
||
|
|
||
|
if (menu->hovered_stance == NULL) return;
|
||
|
|
||
|
const size_t index = menu->hovered_stance - menu->stances;
|
||
|
|
||
|
const loui_menu_stance_t* s = menu->hovered_stance;
|
||
|
if (!loplayer_stance_set_has(&menu->player->stances, index)) {
|
||
|
s = &menu->stances[LOPLAYER_STANCE_COUNT];
|
||
|
}
|
||
|
|
||
|
loshader_menu_text_drawer_add_block(
|
||
|
&menu->shaders->drawer.menu_text, s->name);
|
||
|
loshader_menu_text_drawer_add_block(
|
||
|
&menu->shaders->drawer.menu_text, s->desc);
|
||
|
loshader_menu_text_drawer_add_block(
|
||
|
&menu->shaders->drawer.menu_text, s->note);
|
||
|
}
|
||
|
|
||
|
void loui_menu_initialize(
|
||
|
loui_menu_t* menu,
|
||
|
loresource_set_t* res,
|
||
|
loshader_set_t* shaders,
|
||
|
const locommon_screen_t* screen,
|
||
|
const locommon_ticker_t* ticker,
|
||
|
loplayer_t* player) {
|
||
|
assert(menu != NULL);
|
||
|
assert(res != NULL);
|
||
|
assert(shaders != NULL);
|
||
|
assert(ticker != NULL);
|
||
|
assert(player != NULL);
|
||
|
assert(locommon_screen_valid(screen));
|
||
|
|
||
|
*menu = (typeof(*menu)) {
|
||
|
.shaders = shaders,
|
||
|
.ticker = ticker,
|
||
|
.player = player,
|
||
|
};
|
||
|
loui_menu_calc_geometry_(menu, screen);
|
||
|
|
||
|
menu->font = (typeof(menu->font)) {
|
||
|
.sans = {
|
||
|
.big = glyphas_cache_new(
|
||
|
shaders->tex.menu_text,
|
||
|
&res->font.sans,
|
||
|
menu->geo.big_fontpx.x,
|
||
|
menu->geo.big_fontpx.y),
|
||
|
.normal = glyphas_cache_new(
|
||
|
shaders->tex.menu_text,
|
||
|
&res->font.sans,
|
||
|
menu->geo.normal_fontpx.x,
|
||
|
menu->geo.normal_fontpx.y),
|
||
|
},
|
||
|
.serif = {
|
||
|
.small = glyphas_cache_new(
|
||
|
shaders->tex.menu_text,
|
||
|
&res->font.serif,
|
||
|
menu->geo.small_fontpx.x,
|
||
|
menu->geo.small_fontpx.y),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
# define text_(v) loresource_text_get(res->lang, v)
|
||
|
|
||
|
# define each_(NAME, name) \
|
||
|
loui_menu_initialize_stance_( \
|
||
|
menu, \
|
||
|
&menu->stances[LOPLAYER_STANCE_##NAME], \
|
||
|
screen, \
|
||
|
text_("stance_"#name"_name"), \
|
||
|
text_("stance_"#name"_desc"), \
|
||
|
text_("stance_"#name"_note"))
|
||
|
LOPLAYER_STANCE_EACH(each_);
|
||
|
# undef each_
|
||
|
|
||
|
loui_menu_initialize_stance_(
|
||
|
menu, &menu->stances[LOPLAYER_STANCE_COUNT],
|
||
|
screen,
|
||
|
text_("stance_unknown_name"),
|
||
|
text_("stance_unknown_desc"),
|
||
|
text_("stance_unknown_note"));
|
||
|
menu->exit_text = loui_menu_create_exit_text_(
|
||
|
menu, screen, text_("menu_exit"));
|
||
|
|
||
|
# undef text_
|
||
|
}
|
||
|
|
||
|
void loui_menu_deinitialize(loui_menu_t* menu) {
|
||
|
assert(menu != NULL);
|
||
|
|
||
|
for (size_t i = 0; i < LOPLAYER_STANCE_COUNT+1; ++i) {
|
||
|
loui_menu_deinitialize_stance_(&menu->stances[i]);
|
||
|
}
|
||
|
glyphas_block_delete(menu->exit_text);
|
||
|
glyphas_cache_delete(menu->font.sans.big);
|
||
|
glyphas_cache_delete(menu->font.sans.normal);
|
||
|
glyphas_cache_delete(menu->font.serif.small);
|
||
|
}
|
||
|
|
||
|
void loui_menu_update(loui_menu_t* menu, const locommon_input_t* input) {
|
||
|
assert(menu != NULL);
|
||
|
|
||
|
loui_menu_update_stances_(menu, input);
|
||
|
|
||
|
menu->request_exit =
|
||
|
input != NULL &&
|
||
|
input->buttons & LOCOMMON_INPUT_BUTTON_OK &&
|
||
|
input->cursor.y < -1+menu->geo.normal_fontsz.y;
|
||
|
}
|
||
|
|
||
|
void loui_menu_draw(const loui_menu_t* menu) {
|
||
|
assert(menu != NULL);
|
||
|
|
||
|
if (menu->alpha <= 0) return;
|
||
|
|
||
|
menu->shaders->drawer.menu_text.alpha = menu->alpha;
|
||
|
menu->shaders->drawer.menu_background.alpha = menu->alpha;
|
||
|
|
||
|
loui_menu_draw_stance_icons_(menu);
|
||
|
loui_menu_draw_stance_text_(menu);
|
||
|
|
||
|
loshader_menu_text_drawer_add_block(
|
||
|
&menu->shaders->drawer.menu_text, menu->exit_text);
|
||
|
}
|