diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c new file mode 100644 index 0000000000..a2d2734b8e --- /dev/null +++ b/menu/drivers/wimp.c @@ -0,0 +1,2018 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2011-2016 - Daniel De Matteis + * Copyright (C) 2014-2015 - Jean-AndrĂ© Santoni + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "menu_generic.h" + +#include "../menu_driver.h" +#include "../menu_animation.h" +#include "../menu_navigation.h" +#include "../menu_hash.h" +#include "../menu_display.h" + +#include "../../core_info.h" +#include "../../configuration.h" +#include "../../frontend/frontend_driver.h" +#include "../../system.h" +#include "../../runloop.h" +#include "../../verbosity.h" +#include "../../tasks/tasks_internal.h" + +#include "../../gfx/common/gl_common.h" +#include +#include +#include +#include +#include "../../deps/zahnrad/zahnrad.h" + +#define MAX_VERTEX_MEMORY 512 * 1024 +#define MAX_ELEMENT_MEMORY 128 * 1024 + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) < (b) ? (b) : (a)) +#endif +#ifndef CLAMP +#define CLAMP(i,v,x) (MAX(MIN(v,x), i)) +#endif +#define LEN(a) (sizeof(a)/sizeof(a)[0]) +#define UNUSED(a) ((void)(a)) + +#define MAX_BUFFER 64 +#define MAX_MEMORY (32 * 1024) +#define MAX_COMMAND_MEMORY (16 * 1024) +#define WINDOW_WIDTH 1200 +#define WINDOW_HEIGHT 800 + + +enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; + +struct demo { + void *memory; + struct zr_context ctx; + enum theme theme; + struct zr_memory_status status; +}; +static void +simple_window(struct zr_context *ctx) +{ + /* simple demo window */ + struct zr_panel layout; + if (zr_begin(ctx, &layout, "Show", zr_rect(100, 100, 200, 200), + ZR_WINDOW_BORDER|ZR_WINDOW_MOVABLE|ZR_WINDOW_SCALABLE| + ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_TITLE)) + { + enum {EASY, HARD}; + static int op = EASY; + static int property = 20; + + zr_layout_row_static(ctx, 30, 80, 1); + if (zr_button_text(ctx, "button", ZR_BUTTON_DEFAULT)) { + /* event handling */ + } + zr_layout_row_dynamic(ctx, 30, 2); + if (zr_option(ctx, "easy", op == EASY)) op = EASY; + if (zr_option(ctx, "hard", op == HARD)) op = HARD; + + zr_layout_row_dynamic(ctx, 22, 1); + zr_property_int(ctx, "Compression:", 0, &property, 100, 10, 1); + } + zr_end(ctx); +} + +static int +run_demo(struct demo *gui) +{ + int ret = 1; + static int init = 0; + struct zr_context *ctx = &gui->ctx; + + if (!init) { + init = 1; + } + + /* windows */ + simple_window(ctx); + +// ret = control_window(ctx, gui); + zr_buffer_info(&gui->status, &gui->ctx.memory); + return ret; +} + +static struct demo gui; +/* ============================================================== + * + * Utility + * + * ===============================================================*/ +static void +die(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputs("\n", stderr); + exit(EXIT_FAILURE); +} + +static char* +file_load(const char* path, size_t* siz) +{ + char *buf; + FILE *fd = fopen(path, "rb"); + if (!fd) die("Failed to open file: %s\n", path); + fseek(fd, 0, SEEK_END); + *siz = (size_t)ftell(fd); + fseek(fd, 0, SEEK_SET); + buf = (char*)calloc(*siz, 1); + fread(buf, *siz, 1, fd); + fclose(fd); + return buf; +} + +struct device { + struct zr_buffer cmds; + struct zr_draw_null_texture null; + GLuint vbo, vao, ebo; + + GLuint prog; + GLuint vert_shdr; + GLuint frag_shdr; + + GLint attrib_pos; + GLint attrib_uv; + GLint attrib_col; + + GLint uniform_tex; + GLint uniform_proj; + GLuint font_tex; +}; + +static void +device_init(struct device *dev) +{ + GLint status; + static const GLchar *vertex_shader = + "#version 300 es\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 TexCoord;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main() {\n" + " Frag_UV = TexCoord;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n"; + static const GLchar *fragment_shader = + "#version 300 es\n" + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main(){\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + dev->prog = glCreateProgram(); + dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER); + dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0); + glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0); + glCompileShader(dev->vert_shdr); + glCompileShader(dev->frag_shdr); + glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status); + assert(status == GL_TRUE); + glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status); + assert(status == GL_TRUE); + glAttachShader(dev->prog, dev->vert_shdr); + glAttachShader(dev->prog, dev->frag_shdr); + glLinkProgram(dev->prog); + glGetProgramiv(dev->prog, GL_LINK_STATUS, &status); + assert(status == GL_TRUE); + + dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture"); + dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx"); + dev->attrib_pos = glGetAttribLocation(dev->prog, "Position"); + dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord"); + dev->attrib_col = glGetAttribLocation(dev->prog, "Color"); + + { + /* buffer setup */ + GLsizei vs = sizeof(struct zr_draw_vertex); + size_t vp = offsetof(struct zr_draw_vertex, position); + size_t vt = offsetof(struct zr_draw_vertex, uv); + size_t vc = offsetof(struct zr_draw_vertex, col); + + glGenBuffers(1, &dev->vbo); + glGenBuffers(1, &dev->ebo); + glGenVertexArrays(1, &dev->vao); + + glBindVertexArray(dev->vao); + glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); + + glEnableVertexAttribArray((GLuint)dev->attrib_pos); + glEnableVertexAttribArray((GLuint)dev->attrib_uv); + glEnableVertexAttribArray((GLuint)dev->attrib_col); + + glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp); + glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt); + glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +static struct zr_user_font +font_bake_and_upload(struct device *dev, struct zr_font *font, + const char *path, unsigned int font_height, const zr_rune *range) +{ + int glyph_count; + int img_width, img_height; + struct zr_font_glyph *glyphes; + struct zr_baked_font baked_font; + struct zr_user_font user_font; + struct zr_recti custom; + + memset(&baked_font, 0, sizeof(baked_font)); + memset(&user_font, 0, sizeof(user_font)); + memset(&custom, 0, sizeof(custom)); + + { + /* bake and upload font texture */ + void *img, *tmp; + size_t ttf_size; + size_t tmp_size, img_size; + const char *custom_data = "...."; + struct zr_font_config config; + char *ttf_blob = file_load(path, &ttf_size); + if (!ttf_blob) + die("[Font]: %s is not a file or cannot be found!\n", path); + + /* setup font configuration */ + memset(&config, 0, sizeof(config)); + config.ttf_blob = ttf_blob; + config.ttf_size = ttf_size; + config.font = &baked_font; + config.coord_type = ZR_COORD_UV; + config.range = range; + config.pixel_snap = zr_false; + config.size = (float)font_height; + config.spacing = zr_vec2(0,0); + config.oversample_h = 1; + config.oversample_v = 1; + + /* query needed amount of memory for the font baking process */ + zr_font_bake_memory(&tmp_size, &glyph_count, &config, 1); + glyphes = (struct zr_font_glyph*)calloc(sizeof(struct zr_font_glyph), (size_t)glyph_count); + tmp = calloc(1, tmp_size); + + /* pack all glyphes and return needed image width, height and memory size*/ + custom.w = 2; custom.h = 2; + if (!zr_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1)) + die("[Font]: failed to load font!\n"); + + /* bake all glyphes and custom white pixel into image */ + img = calloc(1, img_size); + zr_font_bake(img, img_width, img_height, tmp, tmp_size, glyphes, glyph_count, &config, 1); + zr_font_bake_custom_data(img, img_width, img_height, custom, custom_data, 2, 2, '.', 'X'); + { + /* convert alpha8 image into rgba8 image */ + void *img_rgba = calloc(4, (size_t)(img_height * img_width)); + zr_font_bake_convert(img_rgba, img_width, img_height, img); + free(img); + img = img_rgba; + } + { + /* upload baked font image */ + glGenTextures(1, &dev->font_tex); + glBindTexture(GL_TEXTURE_2D, dev->font_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)img_width, (GLsizei)img_height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, img); + } + free(ttf_blob); + free(tmp); + free(img); + } + + /* default white pixel in a texture which is needed to draw primitives */ + dev->null.texture.id = (int)dev->font_tex; + dev->null.uv = zr_vec2((custom.x + 0.5f)/(float)img_width, + (custom.y + 0.5f)/(float)img_height); + + /* setup font with glyphes. IMPORTANT: the font only references the glyphes + this was done to have the possibility to have multible fonts with one + total glyph array. Not quite sure if it is a good thing since the + glyphes have to be freed as well. */ + zr_font_init(font, (float)font_height, '?', glyphes, &baked_font, dev->null.texture); + user_font = zr_font_ref(font); + return user_font; +} + +static void +device_shutdown(struct device *dev) +{ + glDetachShader(dev->prog, dev->vert_shdr); + glDetachShader(dev->prog, dev->frag_shdr); + glDeleteShader(dev->vert_shdr); + glDeleteShader(dev->frag_shdr); + glDeleteProgram(dev->prog); + glDeleteTextures(1, &dev->font_tex); + glDeleteBuffers(1, &dev->vbo); + glDeleteBuffers(1, &dev->ebo); +} + +static void +device_draw(struct device *dev, struct zr_context *ctx, int width, int height, + enum zr_anti_aliasing AA) +{ + GLint last_prog, last_tex; + GLint last_ebo, last_vbo, last_vao; + GLfloat ortho[4][4] = { + {2.0f, 0.0f, 0.0f, 0.0f}, + {0.0f,-2.0f, 0.0f, 0.0f}, + {0.0f, 0.0f,-1.0f, 0.0f}, + {-1.0f,1.0f, 0.0f, 1.0f}, + }; + ortho[0][0] /= (GLfloat)width; + ortho[1][1] /= (GLfloat)height; + + /* save previous opengl state */ + glGetIntegerv(GL_CURRENT_PROGRAM, &last_prog); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_vao); + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_ebo); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vbo); + + /* setup global state */ + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glActiveTexture(GL_TEXTURE0); + + /* setup program */ + glUseProgram(dev->prog); + glUniform1i(dev->uniform_tex, 0); + glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]); + + { + /* convert from command queue into draw list and draw to screen */ + const struct zr_draw_command *cmd; + void *vertices, *elements; + const zr_draw_index *offset = NULL; + + /* allocate vertex and element buffer */ + glBindVertexArray(dev->vao); + glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); + + glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_MEMORY, NULL, GL_STREAM_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_MEMORY, NULL, GL_STREAM_DRAW); + + /* load draw vertices & elements directly into vertex + element buffer */ + vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); + elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); + { + struct zr_buffer vbuf, ebuf; + + /* fill converting configuration */ + struct zr_convert_config config; + memset(&config, 0, sizeof(config)); + config.global_alpha = 1.0f; + config.shape_AA = AA; + config.line_AA = AA; + config.circle_segment_count = 22; + config.line_thickness = 1.0f; + config.null = dev->null; + + /* setup buffers to load vertices and elements */ + zr_buffer_init_fixed(&vbuf, vertices, MAX_VERTEX_MEMORY); + zr_buffer_init_fixed(&ebuf, elements, MAX_ELEMENT_MEMORY); + zr_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config); + } + glUnmapBuffer(GL_ARRAY_BUFFER); + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); + + /* iterate over and execute each draw command */ + zr_draw_foreach(cmd, ctx, &dev->cmds) { + if (!cmd->elem_count) continue; + glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); + glScissor((GLint)cmd->clip_rect.x, + height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h), + (GLint)cmd->clip_rect.w, (GLint)cmd->clip_rect.h); + glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); + offset += cmd->elem_count; + } + zr_clear(ctx); + } + + /* restore old state */ + glUseProgram((GLuint)last_prog); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_tex); + glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)last_ebo); + glBindVertexArray((GLuint)last_vao); + glDisable(GL_SCISSOR_TEST); +} + +static void +error_callback(int error, const char *description) +{ + fprintf(stderr, "Error %d: %s\n", error, description); +} + +static void* mem_alloc(zr_handle unused, size_t size) +{UNUSED(unused); return calloc(1, size);} +static void mem_free(zr_handle unused, void *ptr) +{UNUSED(unused); free(ptr);} + +struct device device; +struct zr_font font; +char font_path[PATH_MAX_LENGTH]; +int width = 0, height = 0; + +struct zr_user_font usrfnt; +struct zr_allocator alloc; + +void init() +{ + settings_t *settings = config_get_ptr(); + fill_pathname_join(font_path, settings->assets_directory, + "wimp", sizeof(font_path)); + printf("Font: \n%s\n\n\n",settings->assets_directory); + fill_pathname_join(font_path, font_path, + "Roboto-Regular.ttf", sizeof(font_path)); + printf("Font: %s\n\n\n\n",font_path); +} + +void zdraw(int width, int height) +{ + /* OpenGL */ + glViewport(0, 0, width, height); + alloc.userdata.ptr = NULL; + alloc.alloc = mem_alloc; + alloc.free = mem_free; + zr_buffer_init(&device.cmds, &alloc, 1024); + usrfnt = font_bake_and_upload(&device, &font, font_path, 14, + zr_font_default_glyph_ranges()); + zr_init(&gui.ctx, &alloc, &usrfnt); + device_init(&device); + run_demo(&gui); + glViewport(0, 0, width, height); + device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); +} + +enum +{ + wimp_TEXTURE_POINTER = 0, + wimp_TEXTURE_BACK, + wimp_TEXTURE_SWITCH_ON, + wimp_TEXTURE_SWITCH_OFF, + wimp_TEXTURE_TAB_MAIN_ACTIVE, + wimp_TEXTURE_TAB_PLAYLISTS_ACTIVE, + wimp_TEXTURE_TAB_SETTINGS_ACTIVE, + wimp_TEXTURE_TAB_MAIN_PASSIVE, + wimp_TEXTURE_TAB_PLAYLISTS_PASSIVE, + wimp_TEXTURE_TAB_SETTINGS_PASSIVE, + wimp_TEXTURE_LAST +}; + +enum +{ + wimp_SYSTEM_TAB_MAIN = 0, + wimp_SYSTEM_TAB_PLAYLISTS, + wimp_SYSTEM_TAB_SETTINGS +}; + +#define wimp_SYSTEM_TAB_END wimp_SYSTEM_TAB_SETTINGS + +struct wimp_texture_item +{ + uintptr_t id; +}; + +typedef struct wimp_handle +{ + unsigned tabs_height; + unsigned line_height; + unsigned shadow_height; + unsigned scrollbar_width; + unsigned icon_size; + unsigned margin; + unsigned glyph_width; + char box_message[PATH_MAX_LENGTH]; + + struct + { + struct + { + float alpha; + } arrow; + + struct wimp_texture_item bg; + struct wimp_texture_item list[wimp_TEXTURE_LAST]; + uintptr_t white; + } textures; + + struct + { + struct + { + unsigned idx; + unsigned idx_old; + } active; + + float x_pos; + size_t selection_ptr_old; + size_t selection_ptr; + } categories; + + gfx_font_raster_block_t list_block; + float scroll_y; +} wimp_handle_t; + +static void wimp_context_reset_textures(wimp_handle_t *wimp, + const char *iconpath) +{ + unsigned i; + + for (i = 0; i < wimp_TEXTURE_LAST; i++) + { + struct texture_image ti = {0}; + char path[PATH_MAX_LENGTH] = {0}; + + switch(i) + { + case wimp_TEXTURE_POINTER: + fill_pathname_join(path, iconpath, + "pointer.png", sizeof(path)); + break; + case wimp_TEXTURE_BACK: + fill_pathname_join(path, iconpath, + "back.png", sizeof(path)); + break; + case wimp_TEXTURE_SWITCH_ON: + fill_pathname_join(path, iconpath, + "on.png", sizeof(path)); + break; + case wimp_TEXTURE_SWITCH_OFF: + fill_pathname_join(path, iconpath, + "off.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_MAIN_ACTIVE: + fill_pathname_join(path, iconpath, + "main_tab_active.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_PLAYLISTS_ACTIVE: + fill_pathname_join(path, iconpath, + "playlists_tab_active.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_SETTINGS_ACTIVE: + fill_pathname_join(path, iconpath, + "settings_tab_active.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_MAIN_PASSIVE: + fill_pathname_join(path, iconpath, + "main_tab_passive.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_PLAYLISTS_PASSIVE: + fill_pathname_join(path, iconpath, + "playlists_tab_passive.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_SETTINGS_PASSIVE: + fill_pathname_join(path, iconpath, + "settings_tab_passive.png", sizeof(path)); + break; + } + + if (string_is_empty(path) || !path_file_exists(path)) + continue; + + video_texture_image_load(&ti, path); + video_driver_texture_load(&ti, + TEXTURE_FILTER_MIPMAP_LINEAR, (unsigned*)&wimp->textures.list[i].id); + + video_texture_image_free(&ti); + } +} + +static void wimp_draw_icon(wimp_handle_t *wimp, + uintptr_t texture, + float x, float y, + unsigned width, unsigned height, + float rotation, float scale_factor, + float *color) +{ + menu_display_ctx_rotate_draw_t rotate_draw; + menu_display_ctx_draw_t draw; + struct gfx_coords coords; + math_matrix_4x4 mymat; + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_BEGIN, NULL); + + rotate_draw.matrix = &mymat; + rotate_draw.rotation = rotation; + rotate_draw.scale_x = scale_factor; + rotate_draw.scale_y = scale_factor; + rotate_draw.scale_z = 1; + rotate_draw.scale_enable = true; + + menu_display_ctl(MENU_DISPLAY_CTL_ROTATE_Z, &rotate_draw); + + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = (const float*)color; + + draw.x = x; + draw.y = height - y - wimp->icon_size; + draw.width = wimp->icon_size; + draw.height = wimp->icon_size; + draw.coords = &coords; + draw.matrix_data = &mymat; + draw.texture = texture; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW, &draw); + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_END, NULL); +} + + +static void wimp_draw_tab(wimp_handle_t *wimp, + unsigned i, + unsigned width, unsigned height, + float *pure_white) +{ + unsigned tab_icon; + switch (i) + { + case wimp_SYSTEM_TAB_MAIN: + tab_icon = (i == wimp->categories.selection_ptr) + ? wimp_TEXTURE_TAB_MAIN_ACTIVE + : wimp_TEXTURE_TAB_MAIN_PASSIVE; + break; + case wimp_SYSTEM_TAB_PLAYLISTS: + tab_icon = (i == wimp->categories.selection_ptr) + ? wimp_TEXTURE_TAB_PLAYLISTS_ACTIVE + : wimp_TEXTURE_TAB_PLAYLISTS_PASSIVE; + break; + case wimp_SYSTEM_TAB_SETTINGS: + tab_icon = (i == wimp->categories.selection_ptr) + ? wimp_TEXTURE_TAB_SETTINGS_ACTIVE + : wimp_TEXTURE_TAB_SETTINGS_PASSIVE; + break; + } + + wimp_draw_icon(wimp, wimp->textures.list[tab_icon].id, + width / (wimp_SYSTEM_TAB_END+1) * (i+0.5) - wimp->icon_size/2, + height - wimp->tabs_height, + width, height, 0, 1, &pure_white[0]); +} + +static void wimp_blit_line(float x, float y, unsigned width, unsigned height, + const char *message, uint32_t color, enum text_alignment text_align) +{ + int font_size; + struct font_params params; + void *fb_buf = NULL; + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_SIZE, &font_size); + + params.x = x / width; + params.y = 1.0f - (y + font_size / 3) / height; + params.scale = 1.0f; + params.drop_mod = 0.0f; + params.drop_x = 0.0f; + params.drop_y = 0.0f; + params.color = color; + params.full_screen = true; + params.text_align = text_align; + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_BUF, &fb_buf); + + video_driver_set_osd_msg(message, ¶ms, fb_buf); +} + +static void wimp_render_quad(wimp_handle_t *wimp, + int x, int y, unsigned w, unsigned h, + unsigned width, unsigned height, + float *coord_color) +{ + menu_display_ctx_draw_t draw; + struct gfx_coords coords; + + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = coord_color; + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_BEGIN, NULL); + + draw.x = x; + draw.y = (int)height - y - (int)h; + draw.width = w; + draw.height = h; + draw.coords = &coords; + draw.matrix_data = NULL; + draw.texture = wimp->textures.white; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW, &draw); + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_END, NULL); +} + +static void wimp_draw_tab_begin(wimp_handle_t *wimp, + unsigned width, unsigned height, + float *white_bg, float *grey_bg) +{ + float scale_factor; + menu_display_ctl(MENU_DISPLAY_CTL_GET_DPI, &scale_factor); + + wimp->tabs_height = scale_factor / 3; + + /* tabs background */ + wimp_render_quad(wimp, 0, height - wimp->tabs_height, width, + wimp->tabs_height, + width, height, + white_bg); + + /* tabs separator */ + wimp_render_quad(wimp, 0, height - wimp->tabs_height, width, + 1, + width, height, + grey_bg); +} + +static void wimp_draw_tab_end(wimp_handle_t *wimp, + unsigned width, unsigned height, + unsigned header_height, + float *blue_bg) +{ + /* active tab marker */ + unsigned tab_width = width / (wimp_SYSTEM_TAB_END+1); + + wimp_render_quad(wimp, wimp->categories.selection_ptr * tab_width, + height - (header_height/16), + tab_width, + header_height/16, + width, height, + &blue_bg[0]); +} + +static void wimp_draw_scrollbar(wimp_handle_t *wimp, + unsigned width, unsigned height, float *coord_color) +{ + unsigned header_height; + float content_height, total_height, + scrollbar_height, scrollbar_margin, y; + + if (!wimp) + return; + + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + content_height = menu_entries_get_end() * wimp->line_height; + total_height = height - header_height - wimp->tabs_height; + scrollbar_margin = wimp->scrollbar_width; + scrollbar_height = total_height / (content_height / total_height); + y = total_height * wimp->scroll_y / content_height; + + /* apply a margin on the top and bottom of the scrollbar for aestetic */ + scrollbar_height -= scrollbar_margin * 2; + y += scrollbar_margin; + + if (content_height >= total_height) + { + /* if the scrollbar is extremely short, display it as a square */ + if (scrollbar_height <= wimp->scrollbar_width) + scrollbar_height = wimp->scrollbar_width; + + wimp_render_quad(wimp, + width - wimp->scrollbar_width - scrollbar_margin, + header_height + y, + wimp->scrollbar_width, + scrollbar_height, + width, height, + coord_color); + } +} + +static void wimp_get_message(void *data, const char *message) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp || !message || !*message) + return; + + strlcpy(wimp->box_message, message, sizeof(wimp->box_message)); +} + +static void wimp_render_messagebox(const char *message) +{ + unsigned i, width, height; + uint32_t normal_color; + int x, y, font_size; + settings_t *settings = config_get_ptr(); + struct string_list *list = (struct string_list*) + string_split(message, "\n"); + + if (!list) + return; + if (list->elems == 0) + goto end; + + video_driver_get_size(&width, &height); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_SIZE, &font_size); + + x = width / 2; + y = height / 2 - list->size * font_size / 2; + + normal_color = FONT_COLOR_ARGB_TO_RGBA(settings->menu.entry_normal_color); + + for (i = 0; i < list->size; i++) + { + const char *msg = list->elems[i].data; + if (msg) + wimp_blit_line(x, y + i * font_size, + width, height, + msg, normal_color, TEXT_ALIGN_CENTER); + } + +end: + string_list_free(list); +} + +static void wimp_render(void *data) +{ + size_t i = 0; + float delta_time, dt; + unsigned bottom, width, height, header_height; + wimp_handle_t *wimp = (wimp_handle_t*)data; + settings_t *settings = config_get_ptr(); + + if (!wimp) + return; + + video_driver_get_size(&width, &height); + + menu_animation_ctl(MENU_ANIMATION_CTL_DELTA_TIME, &delta_time); + dt = delta_time / IDEAL_DT; + menu_animation_ctl(MENU_ANIMATION_CTL_UPDATE, &dt); + + menu_display_ctl(MENU_DISPLAY_CTL_SET_WIDTH, &width); + menu_display_ctl(MENU_DISPLAY_CTL_SET_HEIGHT, &height); + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + if (settings->menu.pointer.enable) + { + int16_t pointer_y = menu_input_pointer_state(MENU_POINTER_Y_AXIS); + float old_accel_val, new_accel_val; + unsigned new_pointer_val = + (pointer_y - wimp->line_height + wimp->scroll_y - 16) + / wimp->line_height; + + menu_input_ctl(MENU_INPUT_CTL_POINTER_ACCEL_READ, &old_accel_val); + menu_input_ctl(MENU_INPUT_CTL_POINTER_PTR, &new_pointer_val); + + wimp->scroll_y -= old_accel_val / 60.0; + + new_accel_val = old_accel_val * 0.96; + + menu_input_ctl(MENU_INPUT_CTL_POINTER_ACCEL_WRITE, &new_accel_val); + } + + if (settings->menu.mouse.enable) + { + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + + unsigned new_pointer_val = + (mouse_y - wimp->line_height + wimp->scroll_y - 16) + / wimp->line_height; + + menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &new_pointer_val); + } + + if (wimp->scroll_y < 0) + wimp->scroll_y = 0; + + bottom = menu_entries_get_end() * wimp->line_height + - height + header_height + wimp->tabs_height; + if (wimp->scroll_y > bottom) + wimp->scroll_y = bottom; + + if (menu_entries_get_end() * wimp->line_height + < height - header_height - wimp->tabs_height) + wimp->scroll_y = 0; + + if (menu_entries_get_end() < height / wimp->line_height) { } + else + i = wimp->scroll_y / wimp->line_height; + + menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &i); +} + +static void wimp_render_label_value(wimp_handle_t *wimp, + int y, unsigned width, unsigned height, + uint64_t index, uint32_t color, bool selected, const char *label, + const char *value, float *pure_white) +{ + char label_str[PATH_MAX_LENGTH]; + char value_str[PATH_MAX_LENGTH]; + int value_len = strlen(value); + int ticker_limit = 0; + size_t usable_width = 0; + uintptr_t texture_switch = 0; + bool do_draw_text = false; + uint32_t hash_value = 0; + + usable_width = width - (wimp->margin * 2); + + if (value_len * wimp->glyph_width > usable_width / 2) + value_len = (usable_width/2) / wimp->glyph_width; + + ticker_limit = (usable_width / wimp->glyph_width) - (value_len + 2); + + menu_animation_ticker_str(label_str, ticker_limit, index, label, selected); + menu_animation_ticker_str(value_str, value_len, index, value, selected); + + wimp_blit_line(wimp->margin, y + wimp->line_height / 2, + width, height, label_str, color, TEXT_ALIGN_LEFT); + + hash_value = menu_hash_calculate(value); + + if (string_is_equal(value, "disabled") || string_is_equal(value, "off")) + { + if (wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id; + else + do_draw_text = true; + } + else if (string_is_equal(value, "enabled") || string_is_equal(value, "on")) + { + if (wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id; + else + do_draw_text = true; + } + else + { + switch (hash_value) + { + case MENU_VALUE_COMP: + break; + case MENU_VALUE_MORE: + break; + case MENU_VALUE_CORE: + break; + case MENU_VALUE_RDB: + break; + case MENU_VALUE_CURSOR: + break; + case MENU_VALUE_FILE: + break; + case MENU_VALUE_DIR: + break; + case MENU_VALUE_MUSIC: + break; + case MENU_VALUE_IMAGE: + break; + case MENU_VALUE_MOVIE: + break; + case MENU_VALUE_ON: + if (wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id; + else + do_draw_text = true; + break; + case MENU_VALUE_OFF: + if (wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id; + else + do_draw_text = true; + break; + default: + do_draw_text = true; + break; + } + } + + if (do_draw_text) + wimp_blit_line(width - wimp->margin, + y + wimp->line_height / 2, + width, height, value_str, color, TEXT_ALIGN_RIGHT); + + if (texture_switch) + wimp_draw_icon(wimp, texture_switch, + width - wimp->margin - wimp->icon_size, y, + width, height, 0, 1, &pure_white[0]); +} + +static void wimp_render_menu_list(wimp_handle_t *wimp, + unsigned width, unsigned height, + uint32_t normal_color, + uint32_t hover_color, + float *pure_white) +{ + unsigned header_height; + uint64_t *frame_count; + size_t i = 0; + size_t end = menu_entries_get_end(); + video_driver_ctl(RARCH_DISPLAY_CTL_GET_FRAME_COUNT, &frame_count); + + if (!menu_display_ctl(MENU_DISPLAY_CTL_UPDATE_PENDING, NULL)) + return; + + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + wimp->list_block.carr.coords.vertices = 0; + + menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &i); + + for (; i < end; i++) + { + int y; + size_t selection; + bool entry_selected; + menu_entry_t entry; + + if (!menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection)) + continue; + + y = header_height - wimp->scroll_y + (wimp->line_height * i); + + if ((y - (int)wimp->line_height) > (int)height + || ((y + (int)wimp->line_height) < 0)) + continue; + + menu_entry_get(&entry, 0, i, NULL, true); + + entry_selected = selection == i; + + wimp_render_label_value(wimp, y, width, height, *frame_count / 20, + entry_selected ? hover_color : normal_color, entry_selected, + entry.path, entry.value, pure_white); + } +} + +static void wimp_draw_cursor(wimp_handle_t *wimp, + float *color, + float x, float y, unsigned width, unsigned height) +{ + menu_display_ctx_draw_t draw; + struct gfx_coords coords; + + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = (const float*)color; + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_BEGIN, NULL); + + draw.x = x - 32; + draw.y = (int)height - y - 32; + draw.width = 64; + draw.height = 64; + draw.coords = &coords; + draw.matrix_data = NULL; + draw.texture = wimp->textures.list[wimp_TEXTURE_POINTER].id; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW, &draw); + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_END, NULL); +} + +static size_t wimp_list_get_size(void *data, menu_list_type_t type) +{ + size_t list_size = 0; + (void)data; + + switch (type) + { + case MENU_LIST_PLAIN: + list_size = menu_entries_get_stack_size(0); + break; + case MENU_LIST_TABS: + list_size = wimp_SYSTEM_TAB_END; + break; + default: + break; + } + + return list_size; +} + +static void bgcolor_setalpha(float *bg, float alpha) +{ + bg[3] = alpha; + bg[7] = alpha; + bg[11] = alpha; + bg[15] = alpha; +} + +static int wimp_get_core_title(char *s, size_t len) +{ + struct retro_system_info *system = NULL; + rarch_system_info_t *info = NULL; + settings_t *settings = config_get_ptr(); + const char *core_name = NULL; + const char *core_version = NULL; + + menu_driver_ctl(RARCH_MENU_CTL_SYSTEM_INFO_GET, + &system); + + core_name = system->library_name; + core_version = system->library_version; + + runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); + + if (!settings->menu.core_enable) + return -1; + + if (string_is_empty(core_name)) + core_name = info->info.library_name; + if (string_is_empty(core_name)) + core_name = menu_hash_to_str(MENU_VALUE_NO_CORE); + + if (!core_version) + core_version = info->info.library_version; + if (!core_version) + core_version = ""; + + snprintf(s, len, "%s %s", core_name, core_version); + + return 0; +} + +static void wimp_frame(void *data) +{ + unsigned header_height; + bool display_kb; + float black_bg[16] = { + 0, 0, 0, 0.75, + 0, 0, 0, 0.75, + 0, 0, 0, 0.75, + 0, 0, 0, 0.75, + }; + float blue_bg[16] = { + 0.13, 0.59, 0.95, 1, + 0.13, 0.59, 0.95, 1, + 0.13, 0.59, 0.95, 1, + 0.13, 0.59, 0.95, 1, + }; + float lightblue_bg[16] = { + 0.89, 0.95, 0.99, 1.00, + 0.89, 0.95, 0.99, 1.00, + 0.89, 0.95, 0.99, 1.00, + 0.89, 0.95, 0.99, 1.00, + }; + float pure_white[16]= { + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + }; + float white_bg[16]= { + 0.98, 0.98, 0.98, 1, + 0.98, 0.98, 0.98, 1, + 0.98, 0.98, 0.98, 1, + 0.98, 0.98, 0.98, 1, + }; + float white_transp_bg[16]= { + 0.98, 0.98, 0.98, 0.90, + 0.98, 0.98, 0.98, 0.90, + 0.98, 0.98, 0.98, 0.90, + 0.98, 0.98, 0.98, 0.90, + }; + float grey_bg[16]= { + 0.78, 0.78, 0.78, 1, + 0.78, 0.78, 0.78, 1, + 0.78, 0.78, 0.78, 1, + 0.78, 0.78, 0.78, 1, + }; + float shadow_bg[16]= { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0.2, + 0, 0, 0, 0.2, + }; + unsigned width, height, ticker_limit, i; + char msg[256]; + char title[256]; + char title_buf[256]; + char title_msg[256]; + size_t selection; + size_t title_margin; + uint64_t *frame_count; + menu_display_ctx_draw_t draw; + wimp_handle_t *wimp = (wimp_handle_t*)data; + settings_t *settings = config_get_ptr(); + const uint32_t normal_color = 0x212121ff; + const uint32_t hover_color = 0x212121ff; + const uint32_t title_color = 0xffffffff; + const uint32_t activetab_color = 0x0096f2ff; + const uint32_t passivetab_color = 0x9e9e9eff; + bool background_rendered = false; + bool libretro_running = menu_display_ctl( + MENU_DISPLAY_CTL_LIBRETRO_RUNNING, NULL); + + video_driver_ctl(RARCH_DISPLAY_CTL_GET_FRAME_COUNT, &frame_count); + + (void)passivetab_color; + (void)activetab_color; + + if (!wimp) + return; + + video_driver_get_size(&width, &height); + + menu_display_ctl(MENU_DISPLAY_CTL_SET_VIEWPORT, NULL); + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + if (libretro_running) + { + memset(&draw, 0, sizeof(menu_display_ctx_draw_t)); + + draw.width = width; + draw.height = height; + draw.texture = wimp->textures.white; + draw.handle_alpha = 0.75f; + draw.force_transparency = false; + draw.color = &white_transp_bg[0]; + draw.color2 = &white_bg[0]; + draw.vertex = NULL; + draw.tex_coord = NULL; + draw.vertex_count = 4; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW_BG, &draw); + } + else + { + menu_display_ctx_clearcolor_t clearcolor; + + clearcolor.r = 1.0f; + clearcolor.g = 1.0f; + clearcolor.b = 1.0f; + clearcolor.a = 0.75f; + + menu_display_ctl(MENU_DISPLAY_CTL_CLEAR_COLOR, &clearcolor); + + if (wimp->textures.bg.id) + { + background_rendered = true; + + /* Set new opacity for transposed white background */ + bgcolor_setalpha(white_transp_bg, 0.30); + + memset(&draw, 0, sizeof(menu_display_ctx_draw_t)); + + draw.width = width; + draw.height = height; + draw.texture = wimp->textures.bg.id; + draw.handle_alpha = 0.75f; + draw.force_transparency = true; + draw.color = &white_transp_bg[0]; + draw.color2 = &white_bg[0]; + draw.vertex = NULL; + draw.tex_coord = NULL; + draw.vertex_count = 4; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW_BG, &draw); + + /* Restore opacity of transposed white background */ + bgcolor_setalpha(white_transp_bg, 0.90); + } + } + + zdraw(width, height); + + menu_entries_get_title(title, sizeof(title)); + + if (!menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection)) + return; + + if (background_rendered || libretro_running) + bgcolor_setalpha(lightblue_bg, 0.75); + else + bgcolor_setalpha(lightblue_bg, 1.0); + + /* highlighted entry */ + wimp_render_quad(wimp, 0, + header_height - wimp->scroll_y + wimp->line_height * + selection, width, wimp->line_height, + width, height, + &lightblue_bg[0]); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_BIND_BLOCK, &wimp->list_block); + + wimp_render_menu_list(wimp, width, height, + normal_color, hover_color, &pure_white[0]); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_FLUSH_BLOCK, NULL); + menu_animation_ctl(MENU_ANIMATION_CTL_SET_ACTIVE, NULL); + + /* header */ + wimp_render_quad(wimp, 0, 0, width, header_height, + width, height, &blue_bg[0]); + + wimp->tabs_height = 0; + + /* display tabs if depth equal one, if not hide them */ + if (wimp_list_get_size(wimp, MENU_LIST_PLAIN) == 1) + { + wimp_draw_tab_begin(wimp, width, height, &white_bg[0], &grey_bg[0]); + + for (i = 0; i <= wimp_SYSTEM_TAB_END; i++) + wimp_draw_tab(wimp, i, width, height, &pure_white[0]); + + wimp_draw_tab_end(wimp, width, height, header_height, &blue_bg[0]); + } + + wimp_render_quad(wimp, 0, header_height, width, + wimp->shadow_height, + width, height, + &shadow_bg[0]); + + title_margin = wimp->margin; + + if (menu_entries_ctl(MENU_ENTRIES_CTL_SHOW_BACK, NULL)) + { + title_margin = wimp->icon_size; + wimp_draw_icon(wimp, wimp->textures.list[wimp_TEXTURE_BACK].id, + 0, 0, width, height, 0, 1, &pure_white[0]); + } + + ticker_limit = (width - wimp->margin*2) / wimp->glyph_width; + menu_animation_ticker_str(title_buf, ticker_limit, + *frame_count / 100, title, true); + + /* Title */ + if (wimp_get_core_title(title_msg, sizeof(title_msg)) == 0) + { + char title_buf_msg_tmp[256]; + char title_buf_msg[256]; + size_t usable_width = width - (wimp->margin * 2); + int ticker_limit, value_len; + + snprintf(title_buf_msg, sizeof(title_buf), "%s (%s)", + title_buf, title_msg); + value_len = strlen(title_buf); + ticker_limit = (usable_width / wimp->glyph_width) - (value_len + 2); + + menu_animation_ticker_str(title_buf_msg_tmp, + ticker_limit, *frame_count / 20, title_buf_msg, true); + + strlcpy(title_buf, title_buf_msg_tmp, sizeof(title_buf)); + } + + wimp_blit_line(title_margin, header_height / 2, width, height, + title_buf, title_color, TEXT_ALIGN_LEFT); + + wimp_draw_scrollbar(wimp, width, height, &grey_bg[0]); + + menu_input_ctl(MENU_INPUT_CTL_KEYBOARD_DISPLAY, &display_kb); + + if (display_kb) + { + const char *str = NULL, *label = NULL; + menu_input_ctl(MENU_INPUT_CTL_KEYBOARD_BUFF_PTR, &str); + menu_input_ctl(MENU_INPUT_CTL_KEYBOARD_LABEL, &label); + + if (!str) + str = ""; + wimp_render_quad(wimp, 0, 0, width, height, width, height, &black_bg[0]); + snprintf(msg, sizeof(msg), "%s\n%s", label, str); + wimp_render_messagebox(msg); + } + + if (!string_is_empty(wimp->box_message)) + { + wimp_render_quad(wimp, 0, 0, width, height, width, height, &black_bg[0]); + wimp_render_messagebox(wimp->box_message); + wimp->box_message[0] = '\0'; + } + + if (settings->menu.mouse.enable && (settings->video.fullscreen + || !video_driver_ctl(RARCH_DISPLAY_CTL_HAS_WINDOWED, NULL))) + { + int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + + wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); + } + + menu_display_ctl(MENU_DISPLAY_CTL_RESTORE_CLEAR_COLOR, NULL); + menu_display_ctl(MENU_DISPLAY_CTL_UNSET_VIEWPORT, NULL); +} + +static void wimp_allocate_white_texture(wimp_handle_t *wimp) +{ + struct texture_image ti; + static const uint8_t white_data[] = { 0xff, 0xff, 0xff, 0xff }; + + ti.width = 1; + ti.height = 1; + ti.pixels = (uint32_t*)&white_data; + + video_driver_texture_load(&ti, + TEXTURE_FILTER_NEAREST, (unsigned*)&wimp->textures.white); +} + +static void wimp_font(void) +{ + int font_size; + char mediapath[PATH_MAX_LENGTH], fontpath[PATH_MAX_LENGTH]; + menu_display_ctx_font_t font_info; + settings_t *settings = config_get_ptr(); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_SIZE, &font_size); + + fill_pathname_join(mediapath, settings->assets_directory, + "wimp", sizeof(mediapath)); + fill_pathname_join(fontpath, mediapath, + "Roboto-Regular.ttf", sizeof(fontpath)); + + font_info.path = fontpath; + font_info.size = font_size; + + if (!menu_display_ctl(MENU_DISPLAY_CTL_FONT_MAIN_INIT, &font_info)) + RARCH_WARN("Failed to load font."); +} + +static void wimp_layout(wimp_handle_t *wimp) +{ + void *fb_buf; + float scale_factor; + int new_font_size; + unsigned width, height, new_header_height; + + video_driver_get_size(&width, &height); + + /* Mobiles platforms may have very small display metrics + * coupled to a high resolution, so we should be DPI aware + * to ensure the entries hitboxes are big enough. + * + * On desktops, we just care about readability, with every widget + * size proportional to the display width. */ + menu_display_ctl(MENU_DISPLAY_CTL_GET_DPI, &scale_factor); + + new_header_height = scale_factor / 3; + new_font_size = scale_factor / 9; + + wimp->shadow_height = scale_factor / 36; + wimp->scrollbar_width = scale_factor / 36; + wimp->tabs_height = scale_factor / 3; + wimp->line_height = scale_factor / 3; + wimp->margin = scale_factor / 9; + wimp->icon_size = scale_factor / 3; + + menu_display_ctl(MENU_DISPLAY_CTL_SET_HEADER_HEIGHT, + &new_header_height); + menu_display_ctl(MENU_DISPLAY_CTL_SET_FONT_SIZE, + &new_font_size); + + /* we assume the average glyph aspect ratio is close to 3:4 */ + wimp->glyph_width = new_font_size * 3/4; + + wimp_font(); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_BUF, &fb_buf); + + if (fb_buf) /* calculate a more realistic ticker_limit */ + { + unsigned m_width = + font_driver_get_message_width(fb_buf, "a", 1, 1); + + if (m_width) + wimp->glyph_width = m_width; + } +} + +static void *wimp_init(void **userdata) +{ + wimp_handle_t *wimp = NULL; + menu_handle_t *menu = (menu_handle_t*) + calloc(1, sizeof(*menu)); + + if (!menu) + goto error; + + if (!menu_display_ctl(MENU_DISPLAY_CTL_INIT_FIRST_DRIVER, NULL)) + goto error; + + wimp = (wimp_handle_t*)calloc(1, sizeof(wimp_handle_t)); + + if (!wimp) + goto error; + + *userdata = wimp; + + wimp_layout(wimp); + wimp_allocate_white_texture(wimp); + + init(); + + return menu; +error: + if (menu) + free(menu); + return NULL; +} + +static void wimp_free(void *data) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return; + + gfx_coord_array_free(&wimp->list_block.carr); + + font_driver_bind_block(NULL, NULL); +} + +static void wimp_context_bg_destroy(wimp_handle_t *wimp) +{ + if (!wimp) + return; + + video_driver_texture_unload((uintptr_t*)&wimp->textures.bg.id); + video_driver_texture_unload((uintptr_t*)&wimp->textures.white); +} + +static void wimp_context_destroy(void *data) +{ + unsigned i; + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return; + + for (i = 0; i < wimp_TEXTURE_LAST; i++) + video_driver_texture_unload((uintptr_t*)&wimp->textures.list[i].id); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_MAIN_DEINIT, NULL); + + wimp_context_bg_destroy(wimp); +} + +static bool wimp_load_image(void *userdata, void *data, + menu_image_type_t type) +{ + wimp_handle_t *wimp = (wimp_handle_t*)userdata; + + switch (type) + { + case MENU_IMAGE_NONE: + break; + case MENU_IMAGE_WALLPAPER: + wimp_context_bg_destroy(wimp); + video_driver_texture_load(data, + TEXTURE_FILTER_MIPMAP_LINEAR, (unsigned*)&wimp->textures.bg.id); + wimp_allocate_white_texture(wimp); + break; + case MENU_IMAGE_BOXART: + break; + } + + return true; +} + +static float wimp_get_scroll(wimp_handle_t *wimp) +{ + size_t selection; + unsigned width, height, half = 0; + + if (!wimp) + return 0; + if (!menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection)) + return 0; + + video_driver_get_size(&width, &height); + + if (wimp->line_height) + half = (height / wimp->line_height) / 2; + + if (selection < half) + return 0; + + return ((selection + 2 - half) * wimp->line_height); +} + +static void wimp_navigation_set(void *data, bool scroll) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + float scroll_pos = wimp ? wimp_get_scroll(wimp) : 0.0f; + + if (!wimp || !scroll) + return; + + menu_animation_push(10, scroll_pos, + &wimp->scroll_y, EASING_IN_OUT_QUAD, -1, NULL); +} + +static void wimp_list_set_selection(void *data, file_list_t *list) +{ + wimp_navigation_set(data, true); +} + +static void wimp_navigation_clear(void *data, bool pending_push) +{ + size_t i = 0; + wimp_handle_t *wimp = (wimp_handle_t*)data; + if (!wimp) + return; + + menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &i); + wimp->scroll_y = 0; +} + +static void wimp_navigation_set_last(void *data) +{ + wimp_navigation_set(data, true); +} + +static void wimp_navigation_alphabet(void *data, size_t *unused) +{ + wimp_navigation_set(data, true); +} + +static void wimp_populate_entries( + void *data, const char *path, + const char *label, unsigned i) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + if (!wimp) + return; + + wimp->scroll_y = wimp_get_scroll(wimp); +} + +static void wimp_context_reset(void *data) +{ + char iconpath[PATH_MAX_LENGTH] = {0}; + wimp_handle_t *wimp = (wimp_handle_t*)data; + settings_t *settings = config_get_ptr(); + + if (!wimp || !settings) + return; + + fill_pathname_join(iconpath, settings->assets_directory, + "glui", sizeof(iconpath)); + fill_pathname_slash(iconpath, sizeof(iconpath)); + + wimp_layout(wimp); + wimp_context_bg_destroy(wimp); + wimp_allocate_white_texture(wimp); + wimp_context_reset_textures(wimp, iconpath); + + rarch_task_push_image_load(settings->menu.wallpaper, "cb_menu_wallpaper", + menu_display_handle_wallpaper_upload, NULL); +} + +static int wimp_environ(menu_environ_cb_t type, void *data, void *userdata) +{ + switch (type) + { + case 0: + default: + break; + } + + return -1; +} + +static void wimp_preswitch_tabs(wimp_handle_t *wimp, unsigned action) +{ + size_t idx = 0; + size_t stack_size = 0; + file_list_t *menu_stack = NULL; + + if (!wimp) + return; + + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET_SELECTION, &idx); + + menu_stack = menu_entries_get_menu_stack_ptr(0); + stack_size = menu_stack->size; + + if (menu_stack->list[stack_size - 1].label) + free(menu_stack->list[stack_size - 1].label); + menu_stack->list[stack_size - 1].label = NULL; + + switch (wimp->categories.selection_ptr) + { + case wimp_SYSTEM_TAB_MAIN: + menu_stack->list[stack_size - 1].label = + strdup(menu_hash_to_str(MENU_VALUE_MAIN_MENU)); + menu_stack->list[stack_size - 1].type = + MENU_SETTINGS; + break; + case wimp_SYSTEM_TAB_PLAYLISTS: + menu_stack->list[stack_size - 1].label = + strdup(menu_hash_to_str(MENU_VALUE_PLAYLISTS_TAB)); + menu_stack->list[stack_size - 1].type = + MENU_PLAYLISTS_TAB; + break; + case wimp_SYSTEM_TAB_SETTINGS: + menu_stack->list[stack_size - 1].label = + strdup(menu_hash_to_str(MENU_VALUE_SETTINGS_TAB)); + menu_stack->list[stack_size - 1].type = + MENU_SETTINGS; + break; + } +} + +static void wimp_list_cache(void *data, menu_list_type_t type, unsigned action) +{ + size_t list_size; + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return; + + list_size = wimp_SYSTEM_TAB_END; + + switch (type) + { + case MENU_LIST_PLAIN: + break; + case MENU_LIST_HORIZONTAL: + wimp->categories.selection_ptr_old = wimp->categories.selection_ptr; + + switch (action) + { + case MENU_ACTION_LEFT: + if (wimp->categories.selection_ptr == 0) + { + wimp->categories.selection_ptr = list_size; + wimp->categories.active.idx = list_size - 1; + } + else + wimp->categories.selection_ptr--; + break; + default: + if (wimp->categories.selection_ptr == list_size) + { + wimp->categories.selection_ptr = 0; + wimp->categories.active.idx = 1; + } + else + wimp->categories.selection_ptr++; + break; + } + + wimp_preswitch_tabs(wimp, action); + break; + default: + break; + } +} + +static int wimp_list_push(void *data, void *userdata, + menu_displaylist_info_t *info, unsigned type) +{ + int ret = -1; + core_info_list_t *list = NULL; + menu_handle_t *menu = (menu_handle_t*)data; + + (void)userdata; + + switch (type) + { + case DISPLAYLIST_LOAD_CONTENT_LIST: + menu_entries_clear(info->list); + menu_entries_push(info->list, + menu_hash_to_str(MENU_LABEL_VALUE_LOAD_CONTENT), + menu_hash_to_str(MENU_LABEL_LOAD_CONTENT), + MENU_SETTING_ACTION, 0, 0); + + core_info_ctl(CORE_INFO_CTL_LIST_GET, &list); + if (core_info_list_num_info_files(list)) + { + menu_entries_push(info->list, + menu_hash_to_str(MENU_LABEL_VALUE_DETECT_CORE_LIST), + menu_hash_to_str(MENU_LABEL_DETECT_CORE_LIST), + MENU_SETTING_ACTION, 0, 0); + + menu_entries_push(info->list, + menu_hash_to_str(MENU_LABEL_VALUE_DOWNLOADED_FILE_DETECT_CORE_LIST), + menu_hash_to_str(MENU_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST), + MENU_SETTING_ACTION, 0, 0); + } + + info->need_push = true; + info->need_refresh = true; + ret = 0; + break; + case DISPLAYLIST_MAIN_MENU: + menu_entries_clear(info->list); + + if (!rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL)) + { + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_CONTENT_SETTINGS), PARSE_ACTION, false); + } + + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_START_CORE), PARSE_ACTION, false); + +#ifndef HAVE_DYNAMIC + if (frontend_driver_has_fork()) +#endif + { + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_CORE_LIST), PARSE_ACTION, false); + } + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_LOAD_CONTENT_LIST), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_LOAD_CONTENT_HISTORY), PARSE_ACTION, false); +#if defined(HAVE_NETWORKING) +#if defined(HAVE_LIBRETRODB) + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_ADD_CONTENT_LIST), PARSE_ACTION, false); +#endif + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_ONLINE_UPDATER), PARSE_ACTION, false); +#endif + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_INFORMATION_LIST), PARSE_ACTION, false); +#ifndef HAVE_DYNAMIC + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_RESTART_RETROARCH), PARSE_ACTION, false); +#endif + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_CONFIGURATIONS), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_SAVE_CURRENT_CONFIG), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_SAVE_NEW_CONFIG), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_HELP_LIST), PARSE_ACTION, false); +#if !defined(IOS) + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_QUIT_RETROARCH), PARSE_ACTION, false); +#endif +#if defined(HAVE_LAKKA) + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_SHUTDOWN), PARSE_ACTION, false); +#endif + info->need_push = true; + ret = 0; + break; + } + return ret; +} + +static size_t wimp_list_get_selection(void *data) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return 0; + + return wimp->categories.selection_ptr; +} + +static int wimp_pointer_tap(void *userdata, + unsigned x, unsigned y, + unsigned ptr, menu_file_list_cbs_t *cbs, + menu_entry_t *entry, unsigned action) +{ + size_t selection, idx; + unsigned header_height, width, height, i; + bool scroll = false; + wimp_handle_t *wimp = (wimp_handle_t*)userdata; + file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); + file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); + + if (!wimp) + return 0; + + video_driver_get_size(&width, &height); + + menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection); + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + if (y < header_height) + { + menu_entries_pop_stack(&selection, 0); + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET_SELECTION, &selection); + } + else if (y > height - wimp->tabs_height) + { + for (i = 0; i <= wimp_SYSTEM_TAB_END; i++) + { + unsigned tab_width = width / (wimp_SYSTEM_TAB_END + 1); + unsigned start = tab_width * i; + + if ((x >= start) && (x < (start + tab_width))) + { + wimp->categories.selection_ptr = i; + + wimp_preswitch_tabs(wimp, action); + + if (cbs && cbs->action_content_list_switch) + return cbs->action_content_list_switch(selection_buf, menu_stack, + "", "", 0); + } + } + } + else if (ptr <= (menu_entries_get_size() - 1)) + { + if (ptr == selection && cbs && cbs->action_select) + return menu_entry_action(entry, selection, MENU_ACTION_SELECT); + + idx = ptr; + + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET_SELECTION, &idx); + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET, &scroll); + } + + return 0; +} + +menu_ctx_driver_t menu_ctx_wimp = { + NULL, + wimp_get_message, + generic_menu_iterate, + wimp_render, + wimp_frame, + wimp_init, + wimp_free, + wimp_context_reset, + wimp_context_destroy, + wimp_populate_entries, + NULL, + wimp_navigation_clear, + NULL, + NULL, + wimp_navigation_set, + wimp_navigation_set_last, + wimp_navigation_alphabet, + wimp_navigation_alphabet, + generic_menu_init_list, + NULL, + NULL, + NULL, + wimp_list_cache, + wimp_list_push, + wimp_list_get_selection, + wimp_list_get_size, + NULL, + wimp_list_set_selection, + NULL, + wimp_load_image, + "wimp", + wimp_environ, + wimp_pointer_tap, +};