// by oggzee // wii gui #include #include #include #include #include "wpad.h" #include "my_GRRLIB.h" #include "gui.h" #include "cfg.h" #include "wgui.h" #include "gettext.h" #include "button_png.h" #include "button_old_png.h" #include "window_png.h" /* Resources: button.png window.png */ #define WGUI_IMG_NORMAL 0 #define WGUI_IMG_FLASH 1 #define WGUI_IMG_HOVER 2 #define WGUI_IMG_PRESS 3 #define WGUI_IMG_PRESS_HOVER 4 #define WGUI_IMG_INACTIVE 5 GRRLIB_texImg *tx_button; GRRLIB_texImg *tx_checkbox; GRRLIB_texImg *tx_radio; GRRLIB_texImg *tx_window; GRRLIB_texImg *tx_page; GRRLIB_texImg *tx_custom[GUI_BUTTON_NUM]; FontColor wgui_fc = { 0xFFFFFFFF, 0xA0, 0x44444444 }; // GUI_TC_MENU FontColor text_fc = { 0xFFFFFFFF, 0xA0, 0 }; // GUI_TC_INFO FontColor about_fc = { 0xFFFFFFFF, 0xFF }; // GUI_TC_ABOUT u32 disabled_color = 0xB0B0B0A0; const Pos pos_auto = { POS_AUTO, POS_AUTO, SIZE_AUTO, SIZE_AUTO, 0, 0 }; const Pos pos_fill = { POS_AUTO, POS_AUTO, SIZE_FULL, SIZE_FULL, 0, 0 }; const Pos pos_full = { 0, 0, SIZE_FULL, SIZE_FULL, 0, 0 }; #define P_ALL(P) P.x, P.y, P.w, P.h struct Wgui_Input winput; static struct { int buttons; float sx, sy; int smooth_valid; int valid; } save_input; int wgui_inited = 0; void wgui_init_button_press(GRRLIB_texImg *texsrc, GRRLIB_texImg *texdest, int src_h, int dest_y) { unsigned int x, y; u32 color; for(y=0; y < src_h; y++) { for(x=0; x < texsrc->w; x++) { color = GRRLIB_GetPixelFromtexImg(x, y, texsrc); GRRLIB_SetPixelTotexImg(x, dest_y + y, texdest, (0xFFFFFF00 | (color & 0xFF))); } } GRRLIB_FlushTex(texdest); } void wgui_init_button(GRRLIB_texImg *tx) { if (!tx) return; // ratio w:h of a single button image is 2:1 int h = tx->w / 2; GRRLIB_InitTileSet(tx, tx->w/4, h, 0); // nbtileh is number of button images inside the texture if (tx->nbtileh == 1) { // single button: resize and // create "flash" version of the button for button presses void *data = mem_realloc(tx->data, GRRLIB_DataSize(tx->w, tx->w, 0, 0)); if (!data) return; tx->data = data; tx->h = tx->w; wgui_init_button_press(tx, tx, h, h); GRRLIB_InitTileSet(tx, tx->w/4, h, 0); } } // iw,ih: icon size on screen void wgui_init_icon(GRRLIB_texImg *tx, int iw, int ih) { if (!tx) return; // w:h of a single button has same proportions as iw:ih // w:h = iw:ih h=w/iw*ih float fh = (float)tx->w / (float)iw * (float)ih; int num = (int)roundf((float)tx->h / fh); if (num < 1) num = 1; if (num > 6) num = 6; int h = tx->h / num; // num is number of icon images inside the texture if (num == 1) { // single icon: resize and // create "flash" version of the button for button presses void *data = mem_realloc(tx->data, GRRLIB_DataSize(tx->w, h*2, 0, 0)); if (!data) return; tx->data = data; tx->h = h * 2; wgui_init_button_press(tx, tx, h, h); } GRRLIB_InitTileSet(tx, tx->w, h, 0); } int wgui_load_texture1(GRRLIB_texImg **ptx, char *name, bool global) { GRRLIB_texImg *tx = NULL; void *img_data = NULL; GRRLIB_FREE_TEX(*ptx); int ret = Load_Theme_Image2(name, &img_data, global); if (!img_data) return ret; tx = GRRLIB_LoadTexture(img_data); SAFE_FREE(img_data); if (!tx) return -1; // must be divisible by 4, min size: 8x8 max size: 512x512 if ( (tx->w % 4) || (tx->h % 4) || tx->w < 8 || tx->h < 8 || tx->w > 512 || tx->h > 512) { GRRLIB_FREE_TEX(tx); return -1; } GRRLIB_TextureToMEM2(tx); *ptx = tx; return ret; } int wgui_load_texture(GRRLIB_texImg **ptx, char *name, const u8 *img, bool global) { int ret = wgui_load_texture1(ptx, name, global); if (!*ptx && img) { *ptx = GRRLIB_LoadTexture(img); GRRLIB_TextureToMEM2(*ptx); } return ret; } int wgui_load_button(GRRLIB_texImg **ptx, char *name, const u8 *img, bool global) { int ret = wgui_load_texture(ptx, name, img, global); wgui_init_button(*ptx); return ret; } int wgui_load_icon(GRRLIB_texImg **ptx, char *name, const u8 *img, bool global, int w, int h) { int ret = wgui_load_texture(ptx, name, img, global); wgui_init_icon(*ptx, w, h); //dbg_printf("icon: %s %d %d : %d\n", name, w, h, *ptx ? (*ptx)->nbtileh : -1); return ret; } void wgui_init_window(GRRLIB_texImg *tx) { if (!tx) return; GRRLIB_InitTileSet(tx, tx->w/4, tx->h/4, 0); } int wgui_load_window(GRRLIB_texImg **ptx, char *name, const u8 *img, bool global) { int ret = wgui_load_texture(ptx, name, img, global); wgui_init_window(*ptx); return ret; } void wgui_init() { if (wgui_inited) return; bool global = true; int ret; // button if (CFG.old_button_color) ret = wgui_load_button(&tx_button, "button.png", button_old_png, global); else ret = wgui_load_button(&tx_button, "button.png", button_png, global); // if a themed button.png is found // then all other wgui images must be themed too if (ret == 0) global = false; // checkbox wgui_load_button(&tx_checkbox, "checkbox.png", NULL, global); // radio wgui_load_button(&tx_radio, "radio.png", NULL, global); // window wgui_load_window(&tx_window, "window.png", window_png, global); // page wgui_load_window(&tx_page, "page.png", NULL, global); // custom int i; struct CfgButton *bb; for (i=0; ienabled) { if (bb->type == 0) { wgui_load_button(&tx_custom[i], bb->image, NULL, global); } else { wgui_load_icon(&tx_custom[i], bb->image, NULL, global, bb->pos.w, bb->pos.h); } } } wgui_inited = 1; } void wgui_DrawWindowBase(GRRLIB_texImg *tx, int x, int y, int w, int h, u32 color) { int tw = tx->tilew; int th = tx->tileh; int ww = w - tw*2; int hh = h - th*2; // v: // 0 1 2 // 3 4 5 // 6 7 8 guVector v1[] = { {x+tw, y}, {x+tw+ww, y}, {x+tw+ww, y+th}, {x+tw, y+th} }; guVector v3[] = { {x, y+th}, {x+tw, y+th}, {x+tw, y+th+hh}, {x, y+th+hh} }; guVector v4[] = { {x+tw, y+th}, {x+tw+ww, y+th}, {x+tw+ww, y+th+hh}, {x+tw, y+th+hh} }; guVector v5[] = { {x+tw+ww, y+th}, {x+w, y+th}, {x+w, y+th+hh}, {x+tw+ww, y+th+hh} }; guVector v7[] = { {x+tw, y+th+hh}, {x+tw+ww, y+th+hh}, {x+tw+ww, y+h}, {x+tw, y+h} }; // tile: // 0 1 2 3 // 4 5 6 7 // 8 9 10 11 // 12 13 14 15 GRRLIB_DrawTile(x, y, tx, 0,1,1, color, 0); GRRLIB_DrawTileRectQuad(v1, tx, color, 1, 2, 1); GRRLIB_DrawTile(x+w-tw, y, tx, 0,1,1, color, 3); GRRLIB_DrawTileRectQuad(v3, tx, color, 4, 1, 2); GRRLIB_DrawTileRectQuad(v4, tx, color, 5, 2, 2); GRRLIB_DrawTileRectQuad(v5, tx, color, 7, 1, 2); GRRLIB_DrawTile(x, y+th+hh, tx, 0,1,1, color, 12); GRRLIB_DrawTileRectQuad(v7, tx, color, 13, 2, 1); GRRLIB_DrawTile(x+tw+ww, y+th+hh, tx, 0,1,1, color, 15); } void wgui_DrawWindow(GRRLIB_texImg *tx, int x, int y, int w, int h, u32 window_color, u32 color, float txt_scale, char *title) { int cx = x + w / 2; window_color = color_multiply(window_color, color); wgui_DrawWindowBase(tx, x, y, w, h, window_color); if (title) { FontColor fc; font_color_multiply(&CFG.gui_tc[GUI_TC_TITLE], &fc, color); Gui_PrintAlignZ(cx, y+PAD2, 0, 0, &tx_font, fc, txt_scale, title); /*char str[16]; sprintf(str, "%.2f %.2fx%.2f", txt_scale, tx_font.tilew*txt_scale, tx_font.tileh*txt_scale); Gui_PrintAlignZ(x, y, -1, -1, &tx_font, fc, txt_scale, str);*/ } } void wgui_DrawButtonBase(GRRLIB_texImg *tx, float x, float y, float w, float h, float zoom, u32 color, int state) { float w2 = w / 2.0; float h2 = h / 2.0; bool do_middle = true; x = (x + w2) - w2 * zoom; y = (y + h2) - h2 * zoom; w *= zoom; h *= zoom; h2 = h / 2.0; if (fabsf(h) >= fabsf(w)) { h2 = w / 2.0; do_middle = false; } guVector v1[] = { {x, y}, {x+h2, y}, {x+h2, y+h}, {x, y+h} }; guVector v2[] = { {x+h2, y}, {x+w-h2, y}, {x+w-h2, y+h}, {x+h2, y+h} }; guVector v3[] = { {x+w-h2, y}, {x+w, y}, {x+w, y+h}, {x+w-h2, y+h} }; state *= 4; GRRLIB_DrawTileQuad(v1, tx, color, state + 0); if (do_middle) { GRRLIB_DrawTileRectQuad(v2, tx, color, state + 1, 2, 1); } GRRLIB_DrawTileQuad(v3, tx, color, state + 3); } void wgui_DrawIconBase(GRRLIB_texImg *tx, float x, float y, float w, float h, float zoom, u32 color, int state) { float w2 = w / 2.0; float h2 = h / 2.0; x = (x + w2) - w2 * zoom; y = (y + h2) - h2 * zoom; w *= zoom; h *= zoom; guVector v1[] = { {x, y}, {x+w, y}, {x+w, y+h}, {x, y+h} }; GRRLIB_DrawTileQuad(v1, tx, color, state); } void wgui_DrawButtonX(GRRLIB_texImg *tx, float x, float y, float w, float h, float zoom, u32 color, int state) { if (state >= tx->nbtileh) { if (state == WGUI_IMG_HOVER || state == WGUI_IMG_PRESS_HOVER) { color = color_multiply(color, 0xE0E0FFFF); } switch (state) { case WGUI_IMG_PRESS: case WGUI_IMG_PRESS_HOVER: color = color_multiply(color, 0xA0A0C0FF); zoom = -zoom; break; case WGUI_IMG_FLASH: color = color_multiply(color, 0x808080FF); break; case WGUI_IMG_INACTIVE: color = color_multiply(color, disabled_color); break; } state = 0; } if (tx->nbtilew == 4) { wgui_DrawButtonBase(tx, x, y, w, h, zoom, color, state); } else { wgui_DrawIconBase(tx, x, y, w, h, zoom, color, state); } } #if 0 float _wgui_DrawButton(GRRLIB_texImg *tx, int x, int y, int w, int h, u32 color, int state, float txt_scale, char *txt) { float cx = (float)x + (float)w / 2.0; float cy = (float)y + (float)h / 2.0; float zoom; FontColor fc; if (state == WGUI_STATE_HOVER) { zoom = 1.1; txt_scale *= zoom; } else { zoom = 1.0; } if (state == WGUI_STATE_INACTIVE) { font_color_multiply(&disabled_fc, &fc, color); color = color_multiply(color, disabled_color); } else { font_color_multiply(&CFG.gui_tc[GUI_TC_MENU], &fc, color); } wgui_DrawButtonBase(tx, x, y, w, h, zoom, color, state); Gui_PrintAlignZ(cx, cy, 0, 0, &tx_font, fc, txt_scale, txt); /*char str[16]; sprintf(str, "%.2f %.2f", txt_scale, tx_font.tilew*txt_scale); Gui_PrintAlignZ(cx, y+h, 0, -1, &tx_font, fc, 0.7, str);*/ return zoom; } #endif void wgui_input_save2(ir_t *ir, int *p_buttons) { if (ir) { save_input.sx = ir->sx; save_input.sy = ir->sy; save_input.smooth_valid = ir->smooth_valid; save_input.valid = ir->valid; } if (p_buttons) { save_input.buttons = *p_buttons; } } void wgui_input_save() { wgui_input_save2(winput.w_ir, winput.p_buttons); } void wgui_input_set(ir_t *ir, int *buttons, int *held) { winput.w_ir = ir; winput.w_buttons = *buttons; winput.p_buttons = buttons; winput.p_held = held; wgui_input_save(); } void wgui_input_steal2(ir_t *ir, int *buttons) { // steal wpad ir->sx = -100; ir->sy = -100; ir->smooth_valid = 0; ir->valid = 0; if (*buttons & (WPAD_BUTTON_A | WPAD_BUTTON_B)) { *buttons = 0; winput.w_buttons = 0; } } void wgui_input_steal_buttons() { if (winput.p_buttons) *winput.p_buttons = 0; if (winput.p_held) *winput.p_held = 0; winput.w_buttons = 0; } void wgui_input_steal() { wgui_input_steal2(winput.w_ir, winput.p_buttons); } void wgui_input_restore(ir_t *ir, int *buttons) { if (buttons) *buttons = save_input.buttons; winput.w_buttons = save_input.buttons; if (ir) { ir->sx = save_input.sx; ir->sy = save_input.sy; ir->smooth_valid = save_input.smooth_valid; ir->valid = save_input.valid; } } void wgui_input_get() { winput.w_buttons = Wpad_GetButtons(); Wpad_getIR(winput.w_ir); if (winput.p_buttons) *winput.p_buttons = winput.w_buttons; wgui_input_save(); } bool wgui_over_widget(Widget *ww, ir_t *ir) { if (!ir->smooth_valid) return false; return GRRLIB_PtInRect(ww->ax, ww->ay, ww->w, ww->h, ir->sx, ir->sy); } bool wgui_over(Widget *ww) { return wgui_over_widget(ww, winput.w_ir); } void traverse_children1(Widget *ww, void (*fun)(Widget *)) { int i; Widget *cc; for (i=0; i < ww->num_child; i++) { cc = ww->child[i]; fun(cc); if (cc->num_child) { traverse_children1(cc, fun); } } } void traverse1(Widget *ww, void (*fun)(Widget *)) { fun(ww); if (ww->num_child) { traverse_children1(ww, fun); } } void traverse_children(Widget *ww, void (*fun)(Widget *, int), int val) { int i; Widget *cc; for (i=0; i < ww->num_child; i++) { cc = ww->child[i]; fun(cc, val); if (cc->num_child) { traverse_children(cc, fun, val); } } } void traverse(Widget *ww, void (*fun)(Widget *, int), int val) { fun(ww, val); if (ww->num_child) { traverse_children(ww, fun, val); } } // traverse linked only, not children void traverse_linked(Widget *ww, void (*fun2)(Widget *, int), int val) { while (ww) { fun2(ww, val); ww = ww->link_next; } } // traverse linked and children void traverse_linked_children(Widget *ww, void (*fun2)(Widget *, int), int val) { while (ww) { traverse(ww, fun2, val); ww = ww->link_next; } } void wgui_update(Widget *ww) { if (ww->update) ww->update(ww); } void wgui_action(Widget *ww) { if (ww->action) ww->action(ww); if (ww->action2) ww->action2(ww); } int wgui_set_value_local_simple(Widget *ww, int flags, int val) { if (flags & SET_VAL) { if (!(flags & SET_VAL_FORCE)) { if (val > ww->val_max) val = ww->val_max; if (val < ww->val_min) val = ww->val_min; } ww->value = val; } else if (flags & SET_VAL_MIN) { ww->val_min = val; } else if (flags & SET_VAL_MAX) { // XXX maybe by default: increase only // so that linked page and radio can be constructed and linked // independently, without one decreasing the max limit of the other if (!(flags & SET_VAL_FORCE)) { ww->val_max = val; } ww->val_max = val; // XXX propagate fixed value? if (ww->value > ww->val_max) ww->value = ww->val_max; } return val; } // no set_action callback int wgui_set_value_local_x(Widget *ww, int flags, int val) { int old_val = ww->value; val = wgui_set_value_local_simple(ww, flags, val); if ((flags & SET_VAL) && (flags & SET_VAL_ACTION) && (ww->value != old_val)) { wgui_action(ww); } return val; } int wgui_set_value_local(Widget *ww, int flags, int val) { if (!ww->set_value) { val = wgui_set_value_local_x(ww, flags, val); } else { val = ww->set_value(ww, flags, val); } return val; } // propagate value to all value-linked widgets int wgui_propagate_value(Widget *ww, int flags, int val) { if (!ww) return val; int old_val = ww->value; int error = 0; restart:; Widget *link = ww; int ret; do { ret = wgui_set_value_local(link, flags, val); if (ret != val) { // value not accepted error++; if (error == 1) { // use returned value and restart loop val = ret; goto restart; } else if (error == 2) { // use old value and restart loop val = old_val; goto restart; } else { // ignore, let the loop through } } link = link->val_link; } while (link && link != ww); return val; } int wgui_set_value(Widget *ww, int val) { // by default no action is called return wgui_propagate_value(ww, SET_VAL, val); } // a->x->y b->q->w // a->x->y->b->q->w // a: existing, b: new // copy val a->b void wgui_link_value(Widget *a, Widget *b) { if (!a && !b) return; if (a && b) { wgui_propagate_value(b, SET_VAL_FORCE | SET_VAL, a->value); wgui_propagate_value(b, SET_VAL_FORCE | SET_VAL_MIN, a->val_min); wgui_propagate_value(b, SET_VAL_FORCE | SET_VAL_MAX, a->val_max); } // if one of ptr is NULL make it link to self if (!a) a = b; if (!b) b = a; Widget *last_a = a; Widget *last_b = b; while (last_a->val_link && last_a->val_link != a) last_a = last_a->val_link; while (last_b->val_link && last_b->val_link != b) last_b = last_b->val_link; last_a->val_link = b; last_b->val_link = a; } void wgui_unlink_value(Widget *a) { if (!a) return; Widget *last_a = a; while (last_a->val_link && last_a->val_link != a) last_a = last_a->val_link; last_a->val_link = a->val_link; a->val_link = NULL; } void wgui_set_value_list(Widget *ww, int n, char **values) { ww->list_num = n; SAFE_FREE(ww->list_values); if (n > 0 && values) { ww->list_values = malloc(sizeof(char*) * n); memcpy(ww->list_values, values, sizeof(char*) * n); } wgui_propagate_value(ww, SET_VAL_MAX, n - 1); } void wgui_remove(Widget *ww); void wgui_remove_children(Widget *ww) { int i; // must be in reverse order for (i = ww->num_child - 1; i >= 0; i--) { wgui_remove(ww->child[i]); } } void wgui_close(Widget *ww) { if (ww->cleanup) ww->cleanup(ww); if (ww->self_ptr && *ww->self_ptr == ww) { *ww->self_ptr = NULL; } wgui_unlink_value(ww); wgui_remove_children(ww); SAFE_FREE(ww->child); SAFE_FREE(ww->list_values); memset(ww, 0, sizeof(Widget)); } void wgui_remove(Widget *ww) { Widget *pp = ww->parent; wgui_close(ww); // remove ww from parent list of children if (!pp) return; int i; for (i=0; inum_child; i++) { if (pp->child[i] == ww) { SAFE_FREE(pp->child[i]); // move following over int n = pp->num_child - i - 1; if (n) memmove(&pp->child[i], &pp->child[i+1], n * sizeof(Widget*)); // could also realloc, but we can just keep the mem pp->num_child--; break; } } } // handling order: // // root ... children // update -> // set_val / no action // handle <- // set_val / action // render -> Widget* wgui_handle(Widget *ww); Widget* wgui_handle_children(Widget *ww) { int i; Widget *cc; Widget *r; Widget *ret = NULL; // handle from last to first // (opposite direction than render) for (i = ww->num_child-1; i >= 0; i--) { cc = ww->child[i]; r = wgui_handle(cc); // close marked dialogs if (cc->closing) { wgui_remove(cc); } else if (r) { // return this child // unless cc is a container if (cc->type & WGUI_TYPE_CONTAINER) { ret = r; } else { ret = cc; } } } return ret; } Widget* wgui_handle(Widget *ww) { int state = 0; Widget *ret = NULL; wgui_update(ww); if (ww->state == WGUI_STATE_DISABLED) return NULL; // handle children first if (!ww->skip_child_handle) { ret = wgui_handle_children(ww); } if (ww->handle && ww->state != WGUI_STATE_INACTIVE) { state = ww->handle(ww); } // if ww->parent == NULL then we're the desktop, // so don't steal the input if (ww->lock_focus || (ww->parent && wgui_over(ww) && ( ww->type == WGUI_TYPE_DIALOG || state == WGUI_STATE_PRESS))) { wgui_input_steal(); } if (state == WGUI_STATE_PRESS) { ret = ww; } return ret; } void wgui_render(Widget *ww); void wgui_render_children(Widget *ww) { int i; for (i=0; inum_child; i++) { wgui_render(ww->child[i]); } } void wgui_render(Widget *ww) { if (ww->state == WGUI_STATE_DISABLED) return; if (ww->render) ww->render(ww); if (!ww->skip_child_render) { wgui_render_children(ww); } } // SIZE_AUTO: // use parent's post suggested size or pos.auto calculated size Pos pos(int x, int y, int w, int h) { Pos p = pos_auto; p.x = x; p.y = y; p.w = w; p.h = h; return p; } Pos pos_xy(int x, int y) { Pos p = pos_auto; p.x = x; p.y = y; return p; } Pos pos_x(int x) { Pos p = pos_auto; p.x = x; return p; } Pos pos_y(int y) { Pos p = pos_auto; p.y = y; return p; } Pos pos_wh(int w, int h) { Pos p = pos_auto; p.w = w; p.h = h; return p; } Pos pos_w(int w) { Pos p = pos_auto; p.w = w; return p; } Pos pos_h(int h) { Pos p = pos_auto; p.h = h; return p; } bool pos_special(int x) { return (x == POS_AUTO || x == POS_CENTER || x == POS_MIDDLE || x == POS_EDGE); } Pos pos_get(Widget *ww) { Pos p = pos_auto; p.x = ww->x; p.y = ww->y; p.w = ww->w; p.h = ww->h; if (ww->parent) { p.x -= ww->parent->post.margin; p.y -= ww->parent->post.margin; } return p; } void pos_init(Widget *ww) { PosState *p = &ww->post; memset(p, 0, sizeof(*p)); } int pos_margin(Widget *ww, int margin) { int old = ww->post.margin; if (margin < 0) { margin = (ww->w + margin) / 2; } ww->post.margin = margin; if (ww->post.x == old) ww->post.x = margin; if (ww->post.y == old) ww->post.y = margin; return old; } void pos_pad(Widget *ww, int pad) { ww->post.pad = pad; } void pos_prefsize(Widget *ww, int w, int h) { ww->post.w = w; ww->post.h = h; } void pos_reset(Widget *ww) { ww->post.w = 0; ww->post.h = 0; } void pos_move(Widget *ww, int x, int y) { ww->post.x += x; ww->post.y += y; } void pos_move_x(Widget *ww, int x) { if (x == POS_AUTO) { // nothing } else if (x == POS_CENTER) { ww->post.x = ww->post.w / 2; } else if (x == POS_EDGE) { ww->post.x = ww->post.w; } else if (x >= 0) { ww->post.x = ww->post.margin + x; } else { ww->post.x = ww->w - ww->post.margin + x; } } void pos_move_y(Widget *ww, int y) { if (y == POS_AUTO) { // nothing } else if (y == POS_CENTER) { ww->post.y = ww->post.h / 2; } else if (y == POS_EDGE) { ww->post.y = ww->post.h; } else if (y >= 0) { ww->post.y = ww->post.margin + y; } else { ww->post.y = ww->h - ww->post.margin + y; } } void pos_move_to(Widget *ww, int x, int y) { pos_move_x(ww, x); pos_move_y(ww, y); } void pos_move_pos(Widget *ww, Pos p) { pos_move_to(ww, p.x, p.y); } void pos_move_abs(Widget *ww, int x, int y) { ww->post.x = x; ww->post.y = y; } int pos_avail_w(Widget *ww) { return ww->w - ww->post.x - ww->post.margin; } int pos_avail_h(Widget *ww) { return ww->h - ww->post.y - ww->post.margin; } void pos_newlines(Widget *ww, int lines) { int i; int my = 0, y; Widget *cc; ww->post.x = ww->post.margin; if (lines >= 0) { // find max y for (i=0; i < ww->num_child; i++) { cc = ww->child[i]; y = cc->y + cc->h; if (y > my) my = y; } if (my > ww->post.y) ww->post.y = my; ww->post.y += ww->post.pad * lines; } } void pos_newline(Widget *ww) { pos_newlines(ww, 1); } int pos_simple_w(Widget *ww, int x, int pw) { int w = pw; if (ww) { PosState *post = &ww->post; if (pw == SIZE_AUTO || pw == SIZE_FULL) { w = ww->w - x - post->margin; } else if (pw < 0) { w = ww->w - x - post->margin + pw; } } else { if (w == SIZE_FULL || w == SIZE_AUTO) w = 640; else if (w < 0) w = 640 + w; } if (w < PAD1) w = PAD1; return w; } int pos_simple_h(Widget *ww, int y, int ph) { int h = ph; if (ww) { PosState *post = &ww->post; if (ph == SIZE_AUTO || ph == SIZE_FULL) { h = ww->h - y - post->margin; } else if (ph < 0) { h = ww->h - y - post->margin + ph; } } else { if (h == SIZE_FULL || h == SIZE_AUTO) h = 480; else if (h < 0) h = 480 + h; } if (h < PAD1) h = PAD1; return h; } int pos_simple_x(Widget *parent, int px, int *pw) { int x = px; int w = *pw; if (parent) { PosState *post = &parent->post; if (pos_special(x)) { x = post->x; w = pos_simple_w(parent, x, *pw); int avail = pos_avail_w(parent) - w; if ((avail < 0 || w < 0) && x > post->margin) { pos_newline(parent); x = post->x; w = pos_simple_w(parent, x, *pw); avail = pos_avail_w(parent) - w; } if (px == POS_CENTER) { x = (parent->w - w) / 2; } if (px == POS_MIDDLE) { if (avail > 0) x += avail / 2; } if (px == POS_EDGE) { x = parent->w - post->margin - w; } } else if (x >= 0) { // left offset x = post->margin + x; } else { // x < 0 // right offset x = parent->w - post->margin + x; } } else { if (pos_special(x)) { x = 0; w = pos_simple_w(parent, x, *pw); int avail = 640 - w; if (px == POS_CENTER) { x = avail / 2; } if (px == POS_MIDDLE) { if (avail > 0) x += avail / 2; } if (px == POS_EDGE) { if (avail > 0) x += avail; } } else if (x >= 0) { // left offset } else { // x < 0 // right offset x = 640 + x; } } *pw = w; return x; } int pos_simple_y(Widget *parent, int py, int *ph) { int y = py; int h = *ph; if (parent) { PosState *post = &parent->post; if (pos_special(y)) { y = post->y; h = pos_simple_h(parent, y, *ph); if (py == POS_CENTER) { y = (parent->h - h) / 2; } if (py == POS_MIDDLE) { int avail = pos_avail_h(parent) - h; if (avail > 0) y += avail / 2; } if (py == POS_EDGE) { y = parent->h - post->margin - h; } } else if (y >= 0) { // top offset y = post->margin + y; } else { // y < 0 // bottom offset y = parent->h - post->margin + y; } } else { if (pos_special(y)) { y = 0; h = pos_simple_h(parent, y, *ph); int avail = 480 - h; if (py == POS_CENTER) { y = avail / 2; } if (py == POS_MIDDLE) { if (avail > 0) y += avail / 2; } if (py == POS_EDGE) { if (avail > 0) y += avail; } } else if (y >= 0) { // top offset } else { // y < 0 // bottom offset y = 480 + y; } } *ph = h; return y; } // simplify pos void pos_simple(Widget *ww, Pos *p) { p->x = pos_simple_x(ww, p->x, &p->w); p->w = pos_simple_w(ww, p->x, p->w); p->y = pos_simple_y(ww, p->y, &p->h); p->h = pos_simple_h(ww, p->y, p->h); } int pos_auto_only_w(Widget *parent, Pos p) { int w = p.w; if (parent && p.w == SIZE_AUTO) { PosState *post = &parent->post; if (post->w > PAD1) w = post->w; else w = p.auto_w; } return w; } int pos_auto_w(int x, Widget *parent, Pos p) { int w = pos_auto_only_w(parent, p); w = pos_simple_w(parent, x, w); if (w < PAD1) w = PAD1; return w; } int pos_auto_x(int *pw, Widget *parent, Pos p) { *pw = pos_auto_only_w(parent, p); return pos_simple_x(parent, p.x, pw); } int pos_auto_only_h(Widget *parent, Pos p) { int h = p.h; if (parent && p.h == SIZE_AUTO) { PosState *post = &parent->post; if (post->h > PAD1) h = post->h; else h = p.auto_h; } return h; } int pos_auto_h(int y, Widget *parent, Pos p) { int h = pos_auto_only_h(parent, p); h = pos_simple_h(parent, y, h); if (h < PAD1) h = PAD1; return h; } int pos_auto_y(int *ph, Widget *parent, Pos p) { *ph = pos_auto_only_h(parent, p); return pos_simple_y(parent, p.y, ph); } void pos_auto_default(Pos *p, char *name) { // calculate auto size if not specified if (name) { if (!p->auto_w) p->auto_w = tx_font.tilew * con_len(name); if (!p->auto_h) p->auto_h = tx_font.tileh; } else { if (!p->auto_w) p->auto_w = PAD1; if (!p->auto_h) p->auto_h = PAD1; } } void pos_auto_expand(Widget *parent, Pos *p) { p->x = pos_auto_x(&p->w, parent, *p); if (p->w < PAD1) p->w = pos_auto_w(p->x, parent, *p); p->y = pos_auto_y(&p->h, parent, *p); if (p->h < PAD1) p->h = pos_auto_h(p->y, parent, *p); } void pos_columns(Widget *ww, int cols, int pw) { int w = 0; if (cols > 0) { pw = pos_simple_w(ww, ww->post.x, pw); w = (pw + ww->post.pad) / cols - ww->post.pad; } ww->post.w = w; } void pos_rows(Widget *ww, int rows, int ph) { int h = 0; if (rows > 0) { ph = pos_simple_h(ww, ww->post.y, ph); h = (ph + ww->post.pad) / rows - ww->post.pad; } ww->post.h = h; } static inline u32 get_gui_color(u32 color) { if (color > 0 && color < GUI_COLOR_NUM) { return CFG.gui_window_color[color]; } return color; } static inline FontColor get_text_color(u32 tc) { if (tc >= GUI_TC_NUM) tc = GUI_TC_NUM - 1; return CFG.gui_tc[tc]; } void Widget_init(Widget *ww, Widget *parent, Pos p, char *name) { memset(ww, 0, sizeof(*ww)); ww->type = WGUI_TYPE_WIDGET; ww->name = name; ww->parent = parent; ww->color = 0xFFFFFFFF; ww->dialog_color = GUI_COLOR_BASE; ww->zoom = 1.0; ww->max_zoom = 1.1; // +10% ww->text_scale = 1.0; ww->text_color = GUI_TC_MENU; pos_auto_default(&p, name); pos_auto_expand(parent, &p); if (parent) { ww->ax = parent->ax + p.x; ww->ay = parent->ay + p.y; parent->post.x = p.x + p.w + parent->post.pad; parent->post.y = p.y; } else { ww->ax = p.x; ww->ay = p.y; } ww->x = p.x; ww->y = p.y; ww->w = p.w; ww->h = p.h; //dbg_printf("%s: %d %d %d %d\n", name?name:"", x, y, w, h); pos_init(ww); } Widget* wgui_add(Widget *parent, Pos p, char *name) { Widget *ww; ww = calloc(1, sizeof(Widget)); if (ww) { if (parent) { Widget **pw; pw = realloc(parent->child, (parent->num_child+1) * sizeof(Widget*)); if (!pw) return NULL; parent->child = pw; parent->child[parent->num_child] = ww; parent->num_child++; } Widget_init(ww, parent, p, name); } return ww; } // return index int wgui_link(Widget *link, Widget *ww) { int i = 0; if (link) { i++; // link can point to any list element, get first. if (link->link_first) link = link->link_first; ww->link_first = link; // add link to last element while (link->link_next) { link = link->link_next; i++; } link->link_next = ww; } else { // first link element. // point to self ww->link_first = ww; } ww->link_index = i; return i; } // return index // should be same as ww->link_index int wgui_link_count(Widget *ww) { int i = 0; Widget *link = ww->link_first; while (link) { if (ww == link) return i; link = link->link_next; i++; } return -1; } Widget* wgui_link_get(Widget *ww, int index) { int i = 0; Widget *link = ww->link_first; while (link) { if (i == index) return link; link = link->link_next; i++; } return NULL; } void wgui_set_state(Widget *ww, int state) { if (!ww) return; if (state == WGUI_STATE_NORMAL) { // only set to normal if disabled or inactive // so that hover or pressed are not changed if (ww->state == WGUI_STATE_INACTIVE || ww->state == WGUI_STATE_DISABLED) { ww->state = state; } } else { ww->state = state; } } void wgui_set_inactive(Widget *ww, int cond) { if (!ww) return; if (cond) { ww->state = WGUI_STATE_INACTIVE; } else { if (ww->state == WGUI_STATE_INACTIVE) ww->state = WGUI_STATE_NORMAL; } } void wgui_set_disabled(Widget *ww, int cond) { if (!ww) return; if (cond) { ww->state = WGUI_STATE_DISABLED; } else { if (ww->state == WGUI_STATE_DISABLED) ww->state = WGUI_STATE_NORMAL; } } Widget* wgui_primary_parent(Widget *parent) { while (parent && parent->parent) parent = parent->parent; return parent; } Widget *wgui_find_child_type(Widget *ww, int type) { int i; Widget *ret = NULL; if (ww->type == type) return ww; for (i=0; inum_child; i++) { if (ww->child[i]->type == type) return ww->child[i]; } for (i=0; inum_child; i++) { ret = wgui_find_child_type(ww->child[i], type); if (ret) break; } return ret; } Widget *wgui_find_parent_type(Widget *ww, int type) { Widget *parent = ww->parent; while (parent) { if (parent->type == type) break; parent = parent->parent; } return parent; } int handle_B_close(Widget *ww) { if (winput.w_buttons & WPAD_BUTTON_B) { ww->closing = 1; wgui_input_steal(); } return 0; } void action_close_parent(Widget *ww) { // mark closure, don't remove just yet // because we are still in handle loop if (ww->parent) ww = ww->parent; ww->closing = 1; } void action_close_parent_dialog(Widget *ww) { // mark closure, don't remove just yet // because we are still in handle loop Widget *parent = wgui_find_parent_type(ww, WGUI_TYPE_DIALOG); if (parent) parent->closing = 1; } void wgui_set_color(Widget *ww, int color) { ww->color = color; } void adjust_position(Widget *ww) { if (ww->parent) { ww->ax = ww->x + ww->parent->ax; ww->ay = ww->y + ww->parent->ay; } } void update_val_from_ptr_int(Widget *ww) { if (ww->val_ptr) { int val = *(int*)ww->val_ptr; // update val, no action if (val != ww->value) wgui_set_value(ww, val); } } void update_val_from_ptr_bool(Widget *ww) { if (ww->val_ptr) { int val = *(bool*)ww->val_ptr ? 1 : 0; // update val, no action if (val != ww->value) wgui_set_value(ww, val); } } void update_val_from_ptr_int_active(Widget *ww) { if (ww->state != WGUI_STATE_INACTIVE && ww->state != WGUI_STATE_DISABLED) { update_val_from_ptr_int(ww); } } void action_write_val_ptr_int(Widget *ww) { if (ww->val_ptr) { *(int*)ww->val_ptr = ww->value; } } void action_write_val_ptr_bool(Widget *ww) { if (ww->val_ptr) { *(bool*)ww->val_ptr = ww->value ? true : false; } } void wgui_action_change_parent_val(Widget *ww) { Widget *ll = ww->parent; int value = ll->value + ww->value; // propagate value and call action hooks wgui_propagate_value(ll, SET_VAL | SET_VAL_ACTION, value); } //////// Text float text_round_scale_w(float scale, int dir) { // round to an integral width int w; if (dir < 0) { // round down w = (int)((float)tx_font.tilew * scale); } else if (dir > 0) { // round up w = (int)ceilf((float)tx_font.tilew * scale); } else { // round nearest w = (int)roundf((float)tx_font.tilew * scale); } // recalculate scale return (float)w / (float)tx_font.tilew; } float text_h_to_scale(int h) { float scale = (float)h / (float)tx_font.tileh; return text_round_scale_w(scale, -1); // round down } float pos_text_w(int len, float scale) { return tx_font.tilew * len * scale; } float text_scale_fit(int w, int h, int len, float scale) { float tw = pos_text_w(len, scale); if (tw > w) { scale = (float)w / pos_text_w(len, 1.0); scale = text_round_scale_w(scale, -1); if (scale < 0.8) scale = text_round_scale_w(0.8, 0); } return scale; } float get_text_scale_fit_button(Widget *ww, int len, float scale) { int h = ww->h; int w = ww->w - h / 2; if (w < h) w = h; return text_scale_fit(w, h, len, scale); } float text_scale_fit_button(Widget *ww, int len, float scale) { return (ww->text_scale = get_text_scale_fit_button(ww, len, scale)); } void pos_auto_textlen(Pos *p, int len, float scale) { p->auto_w = pos_text_w(len, scale); p->auto_h = tx_font.tileh * scale; } Pos pos_get_prefsize(Widget *parent, Pos p) { int w = 0; if (p.w > 0) { w = p.w; } else if (p.w == SIZE_AUTO && parent && parent->post.w > 0) { w = parent->post.w; } int h = 0; if (p.h > 0) { h = p.h; } else if (p.h == SIZE_AUTO && parent && parent->post.h > 0) { h = parent->post.h; } p.w = w; p.h = h; return p; } float pos_auto_text_scale_h(int h) { int th = TXT_H_MEDIUM; if (h > 0) { if (h > H_NORMAL) th = TXT_H_LARGE; //scale = 1.2; if (h == H_NORMAL) th = TXT_H_MEDIUM; //scale = 1.1; if (h < H_NORMAL) th = TXT_H_NORMAL; //scale = 1.0; if (h <= H_SMALL) th = TXT_H_SMALL; //scale = 0.9; if (h <= H_TINY) th = TXT_H_TINY; //scale = 0.8; } float scale = text_h_to_scale(th); return scale; } float pos_auto_text_scale(Widget *parent, Pos p) { int h = pos_get_prefsize(parent, p).h; return pos_auto_text_scale_h(h); } void wgui_render_str2(Widget *ww, char *str, float add_zoom, u32 color, FontColor fc) { if (ww->state == WGUI_STATE_INACTIVE) { //font_color_multiply(&disabled_fc, &fc, color); fc.outline = color_multiply(fc.outline, 0xFFFFFFC0); fc.shadow = color_multiply(fc.shadow, 0xFFFFFFC0); color = color_multiply(color, disabled_color); } font_color_multiply(&fc, &fc, color); float scale = ww->text_scale; int cx, alignx; int cy = ww->ay + ww->h / 2.0; if (ww->text_opt & WGUI_TEXT_ALIGN_LEFT) { alignx = -1; cx = ww->ax; } else if (ww->text_opt & WGUI_TEXT_ALIGN_RIGHT) { alignx = 1; cx = ww->ax + ww->w; } else { alignx = 0; cx = ww->ax + ww->w / 2.0; } if (ww->text_opt & WGUI_TEXT_SCALE_FIT) { scale = text_scale_fit(ww->w, ww->h, con_len(str), scale); } else if (ww->text_opt & WGUI_TEXT_SCALE_FIT_BUTTON) { scale = get_text_scale_fit_button(ww, con_len(str), scale); } //GRRLIB_Rectangle(ww->ax, ww->ay, ww->w, ww->h, 0x40408040, 1); Gui_PrintAlignZ(cx, cy, alignx, 0, &tx_font, fc, scale * add_zoom, str); } void wgui_render_str(Widget *ww, char *str, float add_zoom, u32 color) { wgui_render_str2(ww, str, add_zoom, color, get_text_color(ww->text_color)); } void wgui_render_text(Widget *ww) { wgui_render_str(ww, ww->name, 1.0, ww->color); } float pos_auto_text(Widget *parent, Pos *p, const char *name) { int len = name ? con_len(name) : 0; float scale = pos_auto_text_scale(parent, *p); pos_auto_textlen(p, len, scale); return scale; } Widget* wgui_add_text(Widget *parent, Pos p, const char *name) { Widget *ww; int len = name ? con_len(name) : 0; float scale = pos_auto_text(parent, &p, name); ww = wgui_add(parent, p, (char*)name); if (ww) { ww->type = WGUI_TYPE_TEXT; ww->render = wgui_render_text; ww->text_scale = text_scale_fit(ww->w, ww->h, len, scale); } return ww; } //////// Text Box void wgui_textbox_coords(Widget *ww, int *fw, int *fh, int *rows, int *cols) { // round nearest font width (scale was already rounded down) int w = (int)roundf((float)tx_font.tilew * ww->text_scale); // round up font height int h = (int)ceilf((float)tx_font.tileh * ww->text_scale); if (fw) *fw = w; if (fh) *fh = h; if (cols) *cols = ww->w / w; if (rows) *rows = ww->h / h; } void wgui_render_textbox(Widget *ww) { FontColor fc; FontColor fc1 = get_text_color(ww->text_color); font_color_multiply(&fc1, &fc, ww->color); int fw, fh, rows, cols; wgui_textbox_coords(ww, &fw, &fh, &rows, &cols); if (ww->opt) { GRRLIB_Rectangle(ww->ax, ww->ay, ww->w, ww->h, 0x80808080, 1); } //GRRLIB_Printf(ww->ax, ww->ay - 20, &tx_font, 0xffffffff, 1.0, // "%d/%d %.4f %.4f %dx%d", ww->value, ww->val_max, // ww->text_scale, ww->text_scale * tx_font.tilew, fw, fh); int page = ww->value; char *s = skip_lines(ww->name, page * rows); if (!s) return; int y = ww->ay; while (rows && *s) { GRRLIB_Print4(ww->ax, y, &tx_font, fc.color, fc.outline, fc.shadow, ww->text_scale, s, cols); s = strchr(s, '\n'); if (!s) break; s++; rows--; y += fh; } } void wgui_textbox_wordwrap(Widget *ww, char *text, int strsize) { if (!strsize || !text) return; ww->name = text; // round up scale so that font width aligns to a pixel int fw, fh, rows, cols; wgui_textbox_coords(ww, &fw, &fh, &rows, &cols); con_wordwrap(ww->name, cols, strsize); int num_pages = (count_lines(ww->name) + rows - 1) / rows; wgui_propagate_value(ww, SET_VAL_MAX, num_pages - 1); } Widget* wgui_add_textbox(Widget *parent, Pos p, int font_h, char *text, int strsize) { Widget *ww; p.auto_w = pos_avail_w(parent); p.auto_h = pos_avail_h(parent); ww = wgui_add(parent, p, text); if (ww) { ww->type = WGUI_TYPE_TEXTBOX; ww->render = wgui_render_textbox; ww->text_color = GUI_TC_INFO; ww->text_scale = text_h_to_scale(font_h); wgui_textbox_wordwrap(ww, text, strsize); } return ww; } //////// Number void wgui_render_num(Widget *ww) { char str[100]; snprintf(str, sizeof(str), ww->name, ww->opt + ww->value, ww->opt + ww->val_max); wgui_render_str(ww, str, 1.0, ww->color); } Widget* wgui_add_num(Widget *parent, Pos p, char *fmt, int base) { Widget *ww; if (!fmt) fmt = "%d"; pos_auto_textlen(&p, con_len(fmt), 1.0); ww = wgui_add(parent, p, fmt); if (ww) { ww->type = WGUI_TYPE_NUM; ww->render = wgui_render_num; ww->opt = base; } return ww; } //////// Dialog void wgui_render_dialog(Widget *ww) { wgui_DrawWindow(tx_window, ww->ax, ww->ay, ww->w, ww->h, get_gui_color(ww->dialog_color), ww->color, ww->text_scale, ww->name); } void pos_init_dialog(Widget *ww) { pos_init(ww); pos_pad(ww, PAD1); pos_margin(ww, PAD3); pos_prefsize(ww, 0, H_NORMAL); } void text_scale_fit_dialog(Widget *ww) { float scale = text_h_to_scale(TXT_H_HUGE); if (ww->name) { int len = con_len(ww->name); scale = text_scale_fit(ww->w - PAD1*2, TXT_H_HUGE, len, scale); } ww->text_scale = scale; } void wgui_dialog_ini(Widget *dialog) { dialog->type = WGUI_TYPE_DIALOG; dialog->handle = handle_B_close; dialog->render = wgui_render_dialog; dialog->lock_focus = true; text_scale_fit_dialog(dialog); pos_init_dialog(dialog); if (dialog->name) { pos_move(dialog, 0, PAD1); } } void wgui_dialog_init(Widget *dialog, Pos p, char *title) { Widget_init(dialog, NULL, p, title); wgui_dialog_ini(dialog); } Widget* wgui_add_dialog(Widget *parent, Pos p, char *name) { Widget *ww; ww = wgui_add(parent, p, name); if (ww) { wgui_dialog_ini(ww); } return ww; } //////// Button int wgui_handle_button(Widget *ww) { int state = WGUI_STATE_NORMAL; if (wgui_over(ww)) { state = WGUI_STATE_HOVER; if (winput.w_buttons & WPAD_BUTTON_A) { state = WGUI_STATE_PRESS; wgui_input_steal_buttons(); } } if (winput.w_buttons & ww->action_button) { state = WGUI_STATE_PRESS; wgui_input_steal_buttons(); } ww->state = state; if (state == WGUI_STATE_PRESS) { wgui_action(ww); } return state; } float wgui_DrawButton(Widget *ww, char *str) { u32 color = ww->color; int img_state = WGUI_IMG_NORMAL; GRRLIB_texImg *tx = tx_button; if (ww->state == WGUI_STATE_HOVER) { if (ww->zoom < ww->max_zoom) ww->zoom += 0.02; if (ww->zoom > ww->max_zoom) ww->zoom = ww->max_zoom; img_state = WGUI_IMG_HOVER; } else { if (ww->zoom > 1.0) ww->zoom -= 0.02; if (ww->zoom < 1.0) ww->zoom = 1.0; } if (ww->type == WGUI_TYPE_RADIO) { if (tx_radio) tx = tx_radio; Widget *rr = ww->link_first; if (rr && rr->value == ww->link_index) { img_state = WGUI_IMG_PRESS; } } else if (ww->type == WGUI_TYPE_CHECKBOX) { if (tx_checkbox) tx = tx_checkbox; if (ww->value) { img_state = WGUI_IMG_PRESS; } } if (ww->custom_tx) { if (tx_custom[ww->tx_idx]) tx = tx_custom[ww->tx_idx]; } if (img_state == WGUI_IMG_PRESS && ww->state == WGUI_STATE_HOVER) { img_state = WGUI_IMG_PRESS_HOVER; } if (ww->state == WGUI_STATE_INACTIVE) { img_state = WGUI_IMG_INACTIVE; } wgui_DrawButtonX(tx, ww->ax, ww->ay, ww->w, ww->h, ww->zoom, color, img_state); if (ww->state == WGUI_STATE_PRESS) ww->click = 1.0; if (ww->click > 0 && img_state != WGUI_IMG_PRESS && img_state != WGUI_IMG_PRESS_HOVER) { color = 0xFFFFFF00 | (u8)(ww->click * 255); wgui_DrawButtonX(tx, ww->ax, ww->ay, ww->w, ww->h, ww->zoom, color, WGUI_IMG_PRESS); } wgui_render_str(ww, str, ww->zoom, ww->color); if (ww->click > 0) { color = 0xFFFFFF00 | (u8)(ww->click * 255); wgui_DrawButtonX(tx, ww->ax, ww->ay, ww->w, ww->h, ww->zoom, color, WGUI_IMG_FLASH); ww->click -= 0.06; if (ww->click < 0) ww->click = 0; } /*char str[16]; sprintf(str, "%.2f %.2f", txt_scale, tx_font.tilew*txt_scale); Gui_PrintAlignZ(cx, y+h, 0, -1, &tx_font, fc, 0.7, str);*/ return ww->zoom; } void wgui_render_button(Widget *ww) { wgui_DrawButton(ww, ww->name); } int button_auto_w(Widget *parent, Pos p, char *name) { return H_NORMAL + con_len(name) * tx_font.tilew * 1.1; } void pos_auto_button(Pos *p, char *name) { p->auto_w = H_SMALL*2 + con_len(name) * tx_font.tilew * 1.1; p->auto_h = H_SMALL; } float pos_auto_button_scale_len(Widget *parent, Pos *p, int len) { int h = pos_get_prefsize(parent, *p).h; if (!h) h = H_NORMAL; float scale = pos_auto_text_scale_h(h); int w = h + pos_text_w(len, scale); p->auto_w = w; p->auto_h = h; return scale; } float pos_auto_button_scale(Widget *parent, Pos *p, char *name) { return pos_auto_button_scale_len(parent, p, con_len(name)); } Widget* wgui_add_button(Widget *parent, Pos p, char *name) { float scale = pos_auto_button_scale(parent, &p, name); Widget *ww = wgui_add(parent, p, name); if (ww) { ww->type = WGUI_TYPE_BUTTON; ww->handle = wgui_handle_button; ww->render = wgui_render_button; ww->text_color = GUI_TC_BUTTON; text_scale_fit_button(ww, con_len(name), scale); } return ww; } //////// Checkbox int wgui_handle_checkbox(Widget *ww) { int state = WGUI_STATE_NORMAL; if (wgui_over(ww)) { state = WGUI_STATE_HOVER; if (winput.w_buttons & WPAD_BUTTON_A) { state = WGUI_STATE_PRESS; // propagate value and call action hooks wgui_propagate_value(ww, SET_VAL | SET_VAL_ACTION, ww->value ? 0 : 1); wgui_input_steal_buttons(); } } ww->state = state; return state; } void wgui_RenderCheckbox(Widget *ww) { char *val_text; if (ww->list_values && ww->list_num == 2) { val_text = ww->value ? ww->list_values[1] : ww->list_values[0]; } else { val_text = ww->value ? gt("On") : gt("Off"); } char str[100]; if (ww->opt) { snprintf(str, sizeof(str), "%s: %s", ww->name, val_text); val_text = str; } wgui_DrawButton(ww, val_text); } Widget* wgui_add_checkboxx(Widget *parent, Pos p, char *name, bool show_name, char *off, char *on) { int len = con_len(gt("Off")); if (off && on) len = con_len(off); if (show_name) len = con_len(name?name:"") + 2 + len; // 2=": " float scale = pos_auto_button_scale_len(parent, &p, len); Widget *ww = wgui_add(parent, p, name); if (ww) { ww->type = WGUI_TYPE_CHECKBOX; ww->update = update_val_from_ptr_bool; ww->handle = wgui_handle_checkbox; ww->render = wgui_RenderCheckbox; ww->opt = show_name; ww->val_max = 1; ww->text_color = GUI_TC_CHECKBOX; text_scale_fit_button(ww, len, scale); if (off && on) { char *vlist[2] = { off, on }; wgui_set_value_list(ww, 2, vlist); } } return ww; } Widget* wgui_add_checkbox(Widget *parent, Pos p, char *name, bool show_name) { return wgui_add_checkboxx(parent, p, name, show_name, NULL, NULL); } //////// Radio int wgui_handle_radio(Widget *ww) { int state = WGUI_STATE_NORMAL; if (wgui_over(ww)) { Widget *rr = ww->link_first; // ignore hover and action if already selected if (rr->value != ww->link_index) { state = WGUI_STATE_HOVER; if (winput.w_buttons & WPAD_BUTTON_A) { state = WGUI_STATE_PRESS; // set radio value to instance index and call action hooks wgui_propagate_value(rr, SET_VAL | SET_VAL_ACTION, ww->link_index); wgui_input_steal_buttons(); } } } ww->state = state; return state; } void wgui_render_radio(Widget *ww) { //int i; //Widget *rr = ww->link_first; //char str[100]; // dbg //sprintf(str, "%d.%d %s", rr->value, ww->link_index, ww->name); //float zoom = wgui_DrawButton(ww, ww->name); /* if (rr->value == ww->link_index) { float cx, cy, w2, h2; float pad = 1.5; int x, y, w, h; u32 color = color_multiply(0xEE2222FF, ww->color); if (ww->state == WGUI_STATE_INACTIVE) { color = color_multiply(color, disabled_color); } w2 = (float)ww->w / 2.0; h2 = (float)ww->h / 2.0; cx = (float)ww->ax + w2; cy = (float)ww->ay + h2; x = cx - w2 * zoom - pad; y = cy - h2 * zoom - pad; w = (float)ww->w * zoom + pad * 2.0 + 1; h = (float)ww->h * zoom + pad * 2.0 + 1; for (i=0; i<4; i++) { GRRLIB_Rectangle(x-i, y-i, w+i*2, h+i*2, color, 0); GRRLIB_Plot(x-i, y-i, color); } } */ } Widget* wgui_add_radio(Widget *parent, Widget *radio, Pos p, char *name) { float scale = pos_auto_button_scale(parent, &p, name); Widget *ww = wgui_add(parent, p, name); if (ww) { ww->type = WGUI_TYPE_RADIO; ww->handle = wgui_handle_radio; ww->render = wgui_render_radio; ww->text_color = GUI_TC_RADIO; text_scale_fit_button(ww, con_len(name), scale); wgui_link(radio, ww); if (!radio) { ww->update = update_val_from_ptr_int; // -1 means no radio selected ww->val_min = -1; } else { radio = radio->link_first; // without force, this only increases max which is // handy when combined with page or other value links // in case max is already higher wgui_propagate_value(radio, SET_VAL_MAX, ww->link_index); } } return ww; } void wgui_radio_set(Widget *ww, int val) { // no action wgui_set_value(ww, val); } Widget* wgui_auto_radio2(Widget *parent, Widget *rr, int cols, int n, char *names[]) { Widget *ww; int i; for (i=0; i 0 && i > 0 && i % cols == 0 ) { pos_newline(parent); pos_move_abs(parent, rr->x, parent->post.y); } ww = wgui_add_radio(parent, rr, pos_auto, names[i]); if (i == 0) rr = ww; } return rr; } Widget* wgui_auto_radio(Widget *parent, int cols, int n, char *names[]) { return wgui_auto_radio2(parent, NULL, cols, n, names); } Widget* wgui_auto_radio_a(Widget *parent, int cols, int n, ...) { int i; va_list ap; char *names[n]; va_start(ap, n); for (i=0; i maxlen) { maxlen = len; s = str[i]; } } } return s; } void pos_arrange_radio(Widget *parent, Pos *p, int cols, int rows, int n, char *names[]) { Pos tmp; if (p->w == SIZE_AUTO) { // use max auto size of names char *s = get_longest_str(n, names); tmp = pos_auto; pos_auto_button_scale(parent, &tmp, s); pos_auto_expand(parent, &tmp); p->w = cols * (tmp.w + parent->post.pad) - parent->post.pad; int avail = pos_avail_w(parent); if (p->w > avail) p->w = avail; } if (p->h == SIZE_AUTO) { tmp = pos_auto; pos_auto_button_scale(parent, &tmp, ""); pos_auto_expand(parent, &tmp); p->h = rows * (tmp.h + parent->post.pad) - parent->post.pad; } } Widget* wgui_arrange_radio(Widget *parent, Pos p, int cols, int n, char *names[]) { Widget *rr = NULL; int pref_w = parent->post.w; int pref_h = parent->post.h; int ph = p.h; int rows = (n + cols - 1) / cols; pos_arrange_radio(parent, &p, cols, rows, n, names); pos_auto_expand(parent, &p); pos_columns(parent, cols, p.w); if (ph != SIZE_AUTO) { pos_rows(parent, rows, p.h); } pos_move_abs(parent, p.x, p.y); rr = wgui_auto_radio(parent, cols, n, names); pos_prefsize(parent, pref_w, pref_h); return rr; } Widget* wgui_arrange_radio_a(Widget *parent, Pos p, int cols, int n, ...) { int i; va_list ap; char *names[n]; va_start(ap, n); for (i=0; iparent; Widget **cc = pp->child; int i; int first = -1; int maxy; if (ay < 0) ay = pp->h + ay; if (aw <= 0) aw = aw + pp->w - ax * 2; if (ah <= 0) ah = ah + pp->h - ay; maxy = ay; for (i = 0; i < pp->num_child; i++) { if (cc[i] == ww) { first = i; break; } } if (first < 0) return maxy; int w, h; int rows = (n + cols - 1) / cols; w = (aw - pad) / cols - pad; h = (ah - pad) / rows - pad; if (h > maxh) h = maxh; int r, c; for (i = 0; i < n; i++) { ww = cc[first + i]; r = i / cols; c = i % cols; ww->x = ax + pad + c * (w + pad); ww->y = ay + pad + r * (h + pad); ww->w = w; ww->h = h; if (ww->y + h > maxy) maxy = ww->y + h; adjust_position(ww); } return maxy; } Widget* auto_radio(Widget *parent, int x, int y, int cols, int n, char *names[], int *max_y) { Widget *rr = NULL; Widget *ww; int i, my; for (i=0; ilink_first; int v = page->value; while (page) { wgui_set_disabled(page, page->link_index != v); page = page->link_next; } return val; } void wgui_render_page(Widget *ww) { u32 wcolor = get_gui_color(ww->dialog_color); GRRLIB_texImg *tx = tx_page; if (!tx) { tx = tx_window; wcolor = color_multiply(wcolor, 0x80808080); } wgui_DrawWindow(tx, ww->ax, ww->ay, ww->w, ww->h, wcolor, ww->color, 0.0, NULL); } Widget* wgui_add_page(Widget *parent, Widget *page, Pos p, char *name) { if (page) { if (p.x == POS_AUTO) p.x = page->x - parent->post.margin; if (p.y == POS_AUTO) p.y = page->y - parent->post.margin; if (p.w == SIZE_AUTO) p.w = page->w; if (p.h == SIZE_AUTO) p.h = page->h; } Widget *ww = wgui_add(parent, p, name); if (ww) { ww->type = WGUI_TYPE_PAGE; ww->handle = NULL; ww->render = wgui_render_page; ww->dialog_color = 0xFFFFFFFF; // link pages wgui_link(page, ww); if (!page) { // this is first page // link to radio value page = ww; page->set_value = wgui_set_value_page; pos_margin(ww, PAD1); pos_pad(ww, PAD1); } else { wgui_propagate_value(page->link_first, SET_VAL_MAX, ww->link_index); pos_margin(ww, page->post.margin); pos_pad(ww, page->post.pad); pos_prefsize(ww, page->post.w, page->post.h); ww->dialog_color = page->dialog_color; } // disable page if not selected wgui_set_disabled(ww, ww->link_index != page->value); } return ww; } Widget* wgui_add_pages(Widget *parent, Pos p, int num, char *name) { int i; Widget *page = NULL; Widget *ww = NULL; for (i=0; ilink_first) page = page->link_first; if (page && ctrl) wgui_link_value(page, ctrl); } // create as many pages as is ctrl max value (+1) Widget* wgui_add_pages_ctrl(Widget *parent, Pos p, Widget *ctrl, char *name) { if (!ctrl) return NULL; int num = ctrl->val_max + 1; Widget *page = wgui_add_pages(parent, p, num, name); wgui_link_page_ctrl(page, ctrl); return page; } //////// Num Switch int wgui_num_expected_len(char *fmt, int expected_max) { char tmp[100]; snprintf(tmp, sizeof(tmp), fmt, expected_max, expected_max); return con_len(tmp); } Widget* wgui_add_numswitch(Widget *parent, Pos p, char *fmt, int base, int expected_max) { Widget *ww; if (!fmt) fmt = "%2d / %-2d"; int len = wgui_num_expected_len(fmt, expected_max); float scale = 1.0; pos_auto_textlen(&p, len + 4, scale); ww = wgui_add(parent, p, fmt); if (ww) { ww->type = WGUI_TYPE_NUMSWITCH; Widget *cc; int h = ww->h; int w = h; if (w * 3 > ww->w) w = ww->w / 3; cc = wgui_add_button(ww, pos(0, 0, w, h), "<"); cc->action = wgui_action_change_parent_val; cc->action_button = WPAD_BUTTON_UP; cc->value = -1; cc = wgui_add_num(ww, pos(w, 0, -w, h), fmt, base); wgui_link_value(ww, cc); cc->text_scale = scale; cc = wgui_add_button(ww, pos(-w, 0, w, h), ">"); cc->action = wgui_action_change_parent_val; cc->action_button = WPAD_BUTTON_DOWN; cc->value = 1; // resize container ww->w = cc->x + cc->w; parent->post.x = ww->x + ww->w + parent->post.pad; } return ww; } // dir: -1 button left, 0: num, 1: button right Widget *wgui_numswitch_get_element(Widget *ww, int dir) { ww = wgui_find_child_type(ww, WGUI_TYPE_NUMSWITCH); if (ww) return ww->child[dir + 1]; return NULL; } //////// Page Switch Widget* wgui_add_pgswitchx(Widget *parent, Widget *page, Pos p, char *name, int pad_len, char *fmt, int expected_max) { Widget *ww; if (!name) name = gt("Page:"); if (!fmt) fmt = "%2d / %-2d"; int num_len = wgui_num_expected_len(fmt, expected_max); int name_len = con_len(name) + pad_len; pos_auto_textlen(&p, name_len + num_len, 1.0); ww = wgui_add(parent, p, name); if (ww) { ww->type = WGUI_TYPE_PGSWITCH; if (page) wgui_link_page_ctrl(page, ww); Widget *cc; int h = ww->h; pos_auto_textlen(&p, name_len, 1.0); int w1 = p.auto_w; pos_auto_textlen(&p, num_len, 1.0); int w2 = p.auto_w + h * 2; int w = w1 + w2; int x = ww->w - w; if (x < 0) x = 0; cc = wgui_add_text(ww, pos(x, 0, w1, h), name); cc->text_scale = 1.0; cc = wgui_add_numswitch(ww, pos(x+w1, 0, w2, h), fmt, 1, 9); wgui_link_value(ww, cc); // resize container ww->w = cc->x + cc->w; parent->post.x = ww->x + ww->w + parent->post.pad; } return ww; } Widget* wgui_add_pgswitch(Widget *parent, Widget *page, Pos p, char *name) { return wgui_add_pgswitchx(parent, page, p, name, 1, NULL, 99); } //////// ListBox int wgui_set_value_listbox(Widget *ww, int flags, int val) { val = wgui_set_value_local_x(ww, flags, val); // update text value Widget *txt = ww->child[1]; int v = ww->value; if (v >= 0 && v < ww->list_num) { txt->name = ww->list_values[v]; } else { txt->name = ""; } return val; } void wgui_render_listbox(Widget *ww) { GRRLIB_Rectangle(ww->ax + ww->h / 2, ww->ay, ww->w - ww->h, ww->h, 0x60606080, 1); } void wgui_listbox_set_values(Widget *ww, int n, char **values) { wgui_set_value_list(ww, n, values); } float pos_auto_listbox(Widget *parent, Pos *p, int n, char **values) { char *s = get_longest_str(n, values); int maxlen = con_len(s) + 2; int h = pos_get_prefsize(parent, *p).h; if (!h) h = H_NORMAL; float scale = pos_auto_text_scale(parent, *p); p->auto_w = h * 2 + pos_text_w(maxlen, scale); p->auto_h = h; return scale; } Widget* wgui_add_listbox1(Widget *parent, Pos p, char *name, int n, char **values) { float scale = pos_auto_listbox(parent, &p, n, values); Widget *ww = wgui_add(parent, p, name); Widget *aa; if (ww) { ww->type = WGUI_TYPE_LISTBOX; ww->update = update_val_from_ptr_int; ww->set_value = wgui_set_value_listbox; ww->render = wgui_render_listbox; int h = ww->h; // button < aa = wgui_add_button(ww, pos(0, 0, h, h), "<"); aa->action = wgui_action_change_parent_val; aa->value = -1; // text value area aa = wgui_add_text(ww, pos(h, 0, -h, h), n > 0 ? values[0] : NULL); aa->text_scale = scale; aa->text_opt = WGUI_TEXT_SCALE_FIT; // button > aa = wgui_add_button(ww, pos(-h, 0, h, h), ">"); aa->action = wgui_action_change_parent_val; aa->value = 1; // set list of values wgui_listbox_set_values(ww, n, values); } return ww; } Widget* wgui_add_listbox(Widget *parent, Pos p, int n, char **values) { return wgui_add_listbox1(parent, p, NULL, n, values); } // extended listbox void wgui_render_listboxx(Widget *ww) { if (ww->state == WGUI_STATE_HOVER || ww->state == WGUI_STATE_PRESS) { //wgui_render_button(ww); wgui_DrawButtonX(tx_button, ww->ax, ww->ay, ww->w, ww->h, 1.0, 0xFFFFFFFF, WGUI_IMG_HOVER); wgui_render_str2(ww, ww->name, 1.0, ww->color, CFG.gui_tc[GUI_TC_BUTTON]); } else { wgui_render_text(ww); } } bool wgui_action_listboxx_base(Widget *ww) { Widget *ll = ww->parent; if (ll->list_num <= 1) return true; // if only 2 values, just switch as a checkbox if (ll->list_num == 2) { wgui_propagate_value(ll, SET_VAL | SET_VAL_ACTION, ll->value ? 0 : 1); return true; } // return false if action unhandled and a dialog needs to be opened return false; } Widget* wgui_listboxx_open_dialog(Widget *ww) { Widget *ll = ww->parent; // find primary parent (the top level dialog) Widget *parent = wgui_primary_parent(ll); Pos p = pos(50, 50, 640-100, 480-50-PAD2); Widget *dd = wgui_add_dialog(parent, p, ll->name); dd->ax = 50; dd->ay = 50; dd->dialog_color = GUI_COLOR_POPUP; return dd; } void wgui_listboxx_init_dialog(Widget *dd, Widget *ll) { // dialog was opened, fill up with radio buttons // find width of the longest value Pos p1 = pos_auto; pos_arrange_radio(dd, &p1, 1, 1, ll->list_num, ll->list_values); int cols = (ll->list_num + 5 - 1) / 5; Pos p = pos_auto; p.x = POS_CENTER; if (cols == 1 && p1.w < W_NORMAL) p.w = W_NORMAL; if (ll->list_num < 5) pos_newline(dd); Widget *rr = wgui_arrange_radio(dd, p, cols, ll->list_num, ll->list_values); wgui_link_value(ll, rr); Widget *cc = wgui_add_button(dd, pos(POS_EDGE, POS_EDGE, W_NORMAL, H_NORMAL), gt("Back")); cc->action = action_close_parent; } void wgui_action_listboxx(Widget *ww) { if (wgui_action_listboxx_base(ww)) return; Widget *dd = wgui_listboxx_open_dialog(ww); Widget *ll = ww->parent; wgui_listboxx_init_dialog(dd, ll); } Widget* wgui_add_listboxx(Widget *parent, Pos p, char *name, int n, char **values) { Widget *ww = wgui_add_listbox1(parent, p, name, n, values); ww->child[1]->handle = wgui_handle_button; ww->child[1]->action = wgui_action_listboxx; ww->child[1]->render = wgui_render_listboxx; return ww; } //////// super listbox // as listboxx + auto paginate if too many values Widget* wgui_paginate_radio(Widget *parent, Pos p, int cols, int rows, int n, char *names[]) { Widget *r1 = NULL; Widget *rr = NULL; Widget *pp = NULL; int pref_w = parent->post.w; int pref_h = parent->post.h; //int ph = p.h; //int rows = (n + cols - 1) / cols; pos_arrange_radio(parent, &p, cols, rows + 1, n, names); pos_auto_expand(parent, &p); int old_margin = pos_margin(parent, 0); p.h -= H_NORMAL; int per_page = cols * rows; int num_pages = (n + per_page - 1) / per_page; int page; int i; for (page = 0; n > 0 && page < num_pages; page++) { pp = wgui_add_page(parent, pp, p, NULL); pos_columns(pp, cols, SIZE_FULL); pos_rows(pp, rows, SIZE_FULL); i = page * per_page; if (per_page > n) per_page = n; rr = wgui_auto_radio2(pp, r1, cols, per_page, names + i); n -= per_page; if (!r1) r1 = rr; } pos_margin(parent, old_margin); pos_newline(parent); //Widget *pswitch = wgui_add_pgswitch(parent, pp, pos_auto, NULL); pos_prefsize(parent, pref_w, pref_h); return r1; } void wgui_action_superbox(Widget *ww) { if (wgui_action_listboxx_base(ww)) return; Widget *dd = wgui_listboxx_open_dialog(ww); // dialog was opened, fill up with radio buttons Widget *ll = ww->parent; Widget *rr; bool paginate = false; if (ll->list_num > 15) { paginate = true; } else { // find width of the longest value Pos p1 = pos_auto; pos_arrange_radio(dd, &p1, 1, 1, ll->list_num, ll->list_values); // allow 0.8 auto scale if that fits all to one page int w = p1.w * 0.8; int cols = pos_avail_w(dd) / w; if (cols < 1) cols = 1; int rows = (ll->list_num + cols - 1) / cols; if (rows > 5) paginate = true; } if (!paginate) { wgui_listboxx_init_dialog(dd, ll); } else { // auto paginate // max: 2 columns, 5 rows // 1 row for page and back rr = wgui_paginate_radio(dd, pos_fill, 2, 5, ll->list_num, ll->list_values); wgui_link_value(ll, rr); Widget *page = rr->parent; wgui_set_value(page, ll->value / (2*5)); Widget *cc = wgui_add_button(dd, pos(POS_EDGE, POS_AUTO, W_NORMAL, H_NORMAL), gt("Back")); cc->action = action_close_parent; } } Widget* wgui_add_superbox(Widget *parent, Pos p, char *name, int n, char **values) { Widget *ww = wgui_add_listbox1(parent, p, name, n, values); ww->child[1]->handle = wgui_handle_button; ww->child[1]->action = wgui_action_superbox; ww->child[1]->render = wgui_render_listboxx; return ww; } //////// Option // name: (<) _value_ (>) Widget* wgui_add_label(Widget *parent, Pos p, char *name) { Widget *ww = wgui_add_text(parent, p, name); ww->text_opt = WGUI_TEXT_ALIGN_LEFT; return ww; } void pos_auto_opt(Widget *parent, Pos *p, int pad, char *name, int num, char **values) { Pos p1 = *p; Pos p2 = *p; pos_auto_text(parent, &p1, name); pos_auto_listbox(parent, &p2, num, values); p->auto_w = p1.auto_w + pad + p2.auto_w; p->auto_h = p2.auto_h; } wgui_Option wgui_add_option_base(Widget *parent, Pos p, int pad, float w_ratio, char *name) { wgui_Option opt; Pos pos; if (!p.auto_w || !p.auto_h) { pos_auto_text(parent, &p, name); if (w_ratio > 0) { p.auto_w = p.auto_w / w_ratio + pad; } else { p.auto_w = p.auto_w * 2 + pad; } } opt.base = wgui_add(parent, p, name); opt.base->type = WGUI_TYPE_OPTION; pos = pos_full; if (w_ratio > 0) { pos.w = (opt.base->w - pad) * w_ratio; } else { pos.w = SIZE_AUTO; } opt.name = wgui_add_label(opt.base, pos, name); pos_move(opt.base, pad, 0); return opt; } wgui_Option wgui_add_option(Widget *parent, Pos p, int pad, float w_ratio, char *name, int num, char **values) { wgui_Option opt; char *on_off[2] = { gt("Off"), gt("On") }; if (num == 2 && values == NULL) { values = on_off; // alternative: use checkbox or radio } bool use_button = false; if (num == -1) { use_button = true; num = 1; } pos_auto_opt(parent, &p, pad, name, num, values); opt = wgui_add_option_base(parent, p, pad, w_ratio, name); Pos pos = pos_wh(SIZE_FULL, SIZE_FULL); if (use_button) { opt.value = wgui_add_button(opt.base, pos, values ? values[0] : NULL); } else { //opt.value = wgui_add_listboxx(opt.base, pos, name, num, values); opt.value = wgui_add_superbox(opt.base, pos, name, num, values); opt.value->update = update_val_from_ptr_int_active; } opt.control = opt.value; return opt; } Widget* wgui_add_opt(Widget *parent, char *name, int num, char **values) { Pos p = pos_auto; p.w = SIZE_FULL; // 40% name 60% value wgui_Option opt = wgui_add_option(parent, p, 2, 0.40, name, num, values); pos_newline(parent); return opt.control; } Widget* wgui_add_opt_button(Widget *parent, char *name, char *value) { char *names[1]; names[0] = value; return wgui_add_opt(parent, name, -1, names); } Widget* wgui_add_opt_a(Widget *parent, char *name, int num, ...) { int i; va_list ap; char *names[num]; va_start(ap, num); for (i=0; i