CfgUSBLoader/source/wgui.c
2015-01-17 10:11:08 +00:00

2886 lines
63 KiB
C

// by oggzee
// wii gui
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <stdarg.h>
#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; i<GUI_BUTTON_NUM; i++) {
GRRLIB_FREE_TEX(tx_custom[i]);
bb = &CFG.gui_button[i];
if (bb->enabled) {
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; i<pp->num_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; i<ww->num_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; i<ww->num_child; i++) {
if (ww->child[i]->type == type) return ww->child[i];
}
for (i=0; i<ww->num_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<n; i++) {
if (cols > 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<n; i++) names[i] = va_arg(ap, char *);
va_end(ap);
return wgui_auto_radio(parent, cols, n, names);
}
char* get_longest_str(int num, char *str[])
{
int i, len, maxlen = 0;
char *s = "";
for (i=0; i < num; i++) {
if (str[i]) {
len = con_len(str[i]);
if (len > 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; i<n; i++) names[i] = va_arg(ap, char *);
va_end(ap);
return wgui_arrange_radio(parent, p, cols, n, names);
}
// old:
/*
order:
1 2 3
4 5 6
*/
// return: bottom y coord (max y)
/*
int wgui_arrange(Widget *ww, int n,
int ax, int ay, int aw, int ah, // area
int cols, int maxh, int pad)
{
Widget *pp = ww->parent;
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; i<n; i++) {
ww = wgui_add_radio(parent, rr, 1, 1, 1, 1, names[i]);
if (i == 0) rr = ww;
}
my = wgui_arrange(rr, n, x, y, 0, 0, cols, 40, PAD1);
if (max_y) *max_y = my;
return rr;
}
*/
//////// Page
int wgui_set_value_page(Widget *ww, int flags, int val)
{
val = wgui_set_value_local_x(ww, flags, val);
// update active page
Widget *page = ww->link_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; i<num; i++) {
ww = wgui_add_page(parent, page, p, name);
if (!page) {
page = ww;
p = pos_auto;
}
}
return page;
}
Widget* wgui_page_get(Widget *page, int index)
{
return wgui_link_get(page, index);
}
void wgui_link_page_ctrl(Widget *page, Widget *ctrl)
{
if (page->link_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<num; i++) names[i] = va_arg(ap, char *);
va_end(ap);
return wgui_add_opt(parent, name, num, names);
}
Widget* wgui_add_opt_map(Widget *parent, char *name, struct TextMap *map)
{
int num = map_get_num(map);
char *names[num];
map_to_list(map, num, names);
return wgui_add_opt(parent, name, num, names);
}
Widget* wgui_add_opt_array(Widget *parent, char *name, int num, int size, char array[][size])
{
int i;
char *names[num];
for (i=0; i<num; i++) names[i] = array[i];
return wgui_add_opt(parent, name, num, names);
}
////////