mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-11-17 21:25:40 +00:00
779 lines
24 KiB
C++
779 lines
24 KiB
C++
/* $Id$ */
|
|
|
|
/** @file widget.cpp */
|
|
|
|
#include "stdafx.h"
|
|
#include "openttd.h"
|
|
#include "functions.h"
|
|
#include "player.h"
|
|
#include "table/sprites.h"
|
|
#include "table/strings.h"
|
|
#include "window.h"
|
|
#include "gfx.h"
|
|
#include "viewport.h"
|
|
|
|
static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom)
|
|
{
|
|
Point pt;
|
|
int height, count, pos, cap;
|
|
|
|
top += 10;
|
|
bottom -= 9;
|
|
|
|
height = (bottom - top);
|
|
|
|
pos = sb->pos;
|
|
count = sb->count;
|
|
cap = sb->cap;
|
|
|
|
if (count != 0) top += height * pos / count;
|
|
|
|
if (cap > count) cap = count;
|
|
if (count != 0) bottom -= (count - pos - cap) * height / count;
|
|
|
|
pt.x = top;
|
|
pt.y = bottom - 1;
|
|
return pt;
|
|
}
|
|
|
|
/** Special handling for the scrollbar widget type.
|
|
* Handles the special scrolling buttons and other
|
|
* scrolling.
|
|
* @param w Window on which a scroll was performed.
|
|
* @param wi Pointer to the scrollbar widget.
|
|
* @param x The X coordinate of the mouse click.
|
|
* @param y The Y coordinate of the mouse click. */
|
|
void ScrollbarClickHandler(Window *w, const Widget *wi, int x, int y)
|
|
{
|
|
int mi, ma, pos;
|
|
Scrollbar *sb;
|
|
|
|
switch (wi->type) {
|
|
case WWT_SCROLLBAR: {
|
|
/* vertical scroller */
|
|
w->flags4 &= ~WF_HSCROLL;
|
|
w->flags4 &= ~WF_SCROLL2;
|
|
mi = wi->top;
|
|
ma = wi->bottom;
|
|
pos = y;
|
|
sb = &w->vscroll;
|
|
break;
|
|
}
|
|
case WWT_SCROLL2BAR: {
|
|
/* 2nd vertical scroller */
|
|
w->flags4 &= ~WF_HSCROLL;
|
|
w->flags4 |= WF_SCROLL2;
|
|
mi = wi->top;
|
|
ma = wi->bottom;
|
|
pos = y;
|
|
sb = &w->vscroll2;
|
|
break;
|
|
}
|
|
case WWT_HSCROLLBAR: {
|
|
/* horizontal scroller */
|
|
w->flags4 &= ~WF_SCROLL2;
|
|
w->flags4 |= WF_HSCROLL;
|
|
mi = wi->left;
|
|
ma = wi->right;
|
|
pos = x;
|
|
sb = &w->hscroll;
|
|
break;
|
|
}
|
|
default: return; //this should never happen
|
|
}
|
|
if (pos <= mi+9) {
|
|
/* Pressing the upper button? */
|
|
w->flags4 |= WF_SCROLL_UP;
|
|
if (_scroller_click_timeout == 0) {
|
|
_scroller_click_timeout = 6;
|
|
if (sb->pos != 0) sb->pos--;
|
|
}
|
|
_left_button_clicked = false;
|
|
} else if (pos >= ma-10) {
|
|
/* Pressing the lower button? */
|
|
w->flags4 |= WF_SCROLL_DOWN;
|
|
|
|
if (_scroller_click_timeout == 0) {
|
|
_scroller_click_timeout = 6;
|
|
if ((byte)(sb->pos + sb->cap) < sb->count)
|
|
sb->pos++;
|
|
}
|
|
_left_button_clicked = false;
|
|
} else {
|
|
Point pt = HandleScrollbarHittest(sb, mi, ma);
|
|
|
|
if (pos < pt.x) {
|
|
sb->pos = max(sb->pos - sb->cap, 0);
|
|
} else if (pos > pt.y) {
|
|
sb->pos = min(
|
|
sb->pos + sb->cap,
|
|
max(sb->count - sb->cap, 0)
|
|
);
|
|
} else {
|
|
_scrollbar_start_pos = pt.x - mi - 9;
|
|
_scrollbar_size = ma - mi - 23;
|
|
w->flags4 |= WF_SCROLL_MIDDLE;
|
|
_scrolling_scrollbar = true;
|
|
_cursorpos_drag_start = _cursor.pos;
|
|
}
|
|
}
|
|
|
|
SetWindowDirty(w);
|
|
}
|
|
|
|
/** Returns the index for the widget located at the given position
|
|
* relative to the window. It includes all widget-corner pixels as well.
|
|
* @param *w Window to look inside
|
|
* @param x The Window client X coordinate
|
|
* @param y The Window client y coordinate
|
|
* @return A widget index, or -1 if no widget was found.
|
|
*/
|
|
int GetWidgetFromPos(const Window *w, int x, int y)
|
|
{
|
|
uint index;
|
|
int found_index = -1;
|
|
|
|
/* Go through the widgets and check if we find the widget that the coordinate is
|
|
* inside. */
|
|
for (index = 0; index < w->widget_count; index++) {
|
|
const Widget *wi = &w->widget[index];
|
|
if (wi->type == WWT_EMPTY || wi->type == WWT_FRAME) continue;
|
|
|
|
if (x >= wi->left && x <= wi->right && y >= wi->top && y <= wi->bottom &&
|
|
!IsWindowWidgetHidden(w, index)) {
|
|
found_index = index;
|
|
}
|
|
}
|
|
|
|
return found_index;
|
|
}
|
|
|
|
|
|
void DrawFrameRect(int left, int top, int right, int bottom, int ctab, FrameFlags flags)
|
|
{
|
|
uint dark = _colour_gradient[ctab][3];
|
|
uint medium_dark = _colour_gradient[ctab][5];
|
|
uint medium_light = _colour_gradient[ctab][6];
|
|
uint light = _colour_gradient[ctab][7];
|
|
|
|
if (flags & FR_TRANSPARENT) {
|
|
GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT | (1 << USE_COLORTABLE));
|
|
} else {
|
|
uint interior;
|
|
|
|
if (flags & FR_LOWERED) {
|
|
GfxFillRect(left, top, left, bottom, dark);
|
|
GfxFillRect(left + 1, top, right, top, dark);
|
|
GfxFillRect(right, top + 1, right, bottom - 1, light);
|
|
GfxFillRect(left + 1, bottom, right, bottom, light);
|
|
interior = (flags & FR_DARKENED ? medium_dark : medium_light);
|
|
} else {
|
|
GfxFillRect(left, top, left, bottom - 1, light);
|
|
GfxFillRect(left + 1, top, right - 1, top, light);
|
|
GfxFillRect(right, top, right, bottom - 1, dark);
|
|
GfxFillRect(left, bottom, right, bottom, dark);
|
|
interior = medium_dark;
|
|
}
|
|
if (!(flags & FR_BORDERONLY)) {
|
|
GfxFillRect(left + 1, top + 1, right - 1, bottom - 1, interior);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DrawWindowWidgets(const Window *w)
|
|
{
|
|
const DrawPixelInfo* dpi = _cur_dpi;
|
|
Rect r;
|
|
uint i;
|
|
|
|
for (i = 0; i < w->widget_count; i++) {
|
|
const Widget *wi = &w->widget[i];
|
|
bool clicked = IsWindowWidgetLowered(w, i);
|
|
|
|
if (dpi->left > (r.right=/*w->left + */wi->right) ||
|
|
dpi->left + dpi->width <= (r.left=wi->left/* + w->left*/) ||
|
|
dpi->top > (r.bottom=/*w->top +*/ wi->bottom) ||
|
|
dpi->top + dpi->height <= (r.top = /*w->top +*/ wi->top) ||
|
|
IsWindowWidgetHidden(w, i)) {
|
|
continue;
|
|
}
|
|
|
|
switch (wi->type & WWT_MASK) {
|
|
case WWT_IMGBTN:
|
|
case WWT_IMGBTN_2: {
|
|
int img = wi->data;
|
|
assert(img != 0);
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
|
|
/* show different image when clicked for WWT_IMGBTN_2 */
|
|
if ((wi->type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++;
|
|
DrawSprite(img, PAL_NONE, r.left + 1 + clicked, r.top + 1 + clicked);
|
|
goto draw_default;
|
|
}
|
|
|
|
case WWT_PANEL: {
|
|
assert(wi->data == 0);
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
goto draw_default;
|
|
}
|
|
|
|
case WWT_TEXTBTN:
|
|
case WWT_TEXTBTN_2: {
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
}
|
|
/* fall through */
|
|
|
|
case WWT_LABEL: {
|
|
StringID str = wi->data;
|
|
|
|
if ((wi->type & WWT_MASK) == WWT_TEXTBTN_2 && clicked) str++;
|
|
|
|
DrawStringCentered(((r.left + r.right + 1) >> 1) + clicked, ((r.top + r.bottom + 1) >> 1) - 5 + clicked, str, 0);
|
|
goto draw_default;
|
|
}
|
|
|
|
case WWT_TEXT: {
|
|
StringID str = wi->data;
|
|
|
|
if (str != STR_NULL) DrawStringTruncated(r.left, r.top, str, wi->color, r.right - r.left);
|
|
break;
|
|
}
|
|
|
|
case WWT_INSET: {
|
|
StringID str = wi->data;
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, FR_LOWERED | FR_DARKENED);
|
|
|
|
if (str != STR_NULL) DrawStringTruncated(r.left + 2, r.top + 1, str, 0, r.right - r.left - 10);
|
|
goto draw_default;
|
|
}
|
|
|
|
case WWT_MATRIX: {
|
|
int c, d, ctr;
|
|
int x, amt1, amt2;
|
|
int color;
|
|
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
|
|
c = GB(wi->data, 0, 8);
|
|
amt1 = (wi->right - wi->left + 1) / c;
|
|
|
|
d = GB(wi->data, 8, 8);
|
|
amt2 = (wi->bottom - wi->top + 1) / d;
|
|
|
|
color = _colour_gradient[wi->color & 0xF][6];
|
|
|
|
x = r.left;
|
|
for (ctr = c; ctr > 1; ctr--) {
|
|
x += amt1;
|
|
GfxFillRect(x, r.top + 1, x, r.bottom - 1, color);
|
|
}
|
|
|
|
x = r.top;
|
|
for (ctr = d; ctr > 1; ctr--) {
|
|
x += amt2;
|
|
GfxFillRect(r.left + 1, x, r.right - 1, x, color);
|
|
}
|
|
|
|
color = _colour_gradient[wi->color&0xF][4];
|
|
|
|
x = r.left - 1;
|
|
for (ctr = c; ctr > 1; ctr--) {
|
|
x += amt1;
|
|
GfxFillRect(x, r.top + 1, x, r.bottom - 1, color);
|
|
}
|
|
|
|
x = r.top - 1;
|
|
for (ctr = d; ctr > 1; ctr--) {
|
|
x += amt2;
|
|
GfxFillRect(r.left+1, x, r.right-1, x, color);
|
|
}
|
|
|
|
goto draw_default;
|
|
}
|
|
|
|
/* vertical scrollbar */
|
|
case WWT_SCROLLBAR: {
|
|
Point pt;
|
|
int c1,c2;
|
|
|
|
assert(r.right - r.left == 11); // XXX - to ensure the same sizes are used everywhere!
|
|
|
|
/* draw up/down buttons */
|
|
clicked = ((w->flags4 & (WF_SCROLL_UP | WF_HSCROLL | WF_SCROLL2)) == WF_SCROLL_UP);
|
|
DrawFrameRect(r.left, r.top, r.right, r.top + 9, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DoDrawString(UPARROW, r.left + 2 + clicked, r.top + clicked, 0x10);
|
|
|
|
clicked = (((w->flags4 & (WF_SCROLL_DOWN | WF_HSCROLL | WF_SCROLL2)) == WF_SCROLL_DOWN));
|
|
DrawFrameRect(r.left, r.bottom - 9, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DoDrawString(DOWNARROW, r.left + 2 + clicked, r.bottom - 9 + clicked, 0x10);
|
|
|
|
c1 = _colour_gradient[wi->color&0xF][3];
|
|
c2 = _colour_gradient[wi->color&0xF][7];
|
|
|
|
/* draw "shaded" background */
|
|
GfxFillRect(r.left, r.top+10, r.right, r.bottom-10, c2);
|
|
GfxFillRect(r.left, r.top+10, r.right, r.bottom-10, c1 | (1 << PALETTE_MODIFIER_GREYOUT));
|
|
|
|
/* draw shaded lines */
|
|
GfxFillRect(r.left+2, r.top+10, r.left+2, r.bottom-10, c1);
|
|
GfxFillRect(r.left+3, r.top+10, r.left+3, r.bottom-10, c2);
|
|
GfxFillRect(r.left+7, r.top+10, r.left+7, r.bottom-10, c1);
|
|
GfxFillRect(r.left+8, r.top+10, r.left+8, r.bottom-10, c2);
|
|
|
|
pt = HandleScrollbarHittest(&w->vscroll, r.top, r.bottom);
|
|
DrawFrameRect(r.left, pt.x, r.right, pt.y, wi->color, (w->flags4 & (WF_SCROLL_MIDDLE | WF_HSCROLL | WF_SCROLL2)) == WF_SCROLL_MIDDLE ? FR_LOWERED : FR_NONE);
|
|
break;
|
|
}
|
|
case WWT_SCROLL2BAR: {
|
|
Point pt;
|
|
int c1,c2;
|
|
|
|
assert(r.right - r.left == 11); // XXX - to ensure the same sizes are used everywhere!
|
|
|
|
/* draw up/down buttons */
|
|
clicked = ((w->flags4 & (WF_SCROLL_UP | WF_HSCROLL | WF_SCROLL2)) == (WF_SCROLL_UP | WF_SCROLL2));
|
|
DrawFrameRect(r.left, r.top, r.right, r.top + 9, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DoDrawString(UPARROW, r.left + 2 + clicked, r.top + clicked, 0x10);
|
|
|
|
clicked = ((w->flags4 & (WF_SCROLL_DOWN | WF_HSCROLL | WF_SCROLL2)) == (WF_SCROLL_DOWN | WF_SCROLL2));
|
|
DrawFrameRect(r.left, r.bottom - 9, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DoDrawString(DOWNARROW, r.left + 2 + clicked, r.bottom - 9 + clicked, 0x10);
|
|
|
|
c1 = _colour_gradient[wi->color&0xF][3];
|
|
c2 = _colour_gradient[wi->color&0xF][7];
|
|
|
|
/* draw "shaded" background */
|
|
GfxFillRect(r.left, r.top+10, r.right, r.bottom-10, c2);
|
|
GfxFillRect(r.left, r.top+10, r.right, r.bottom-10, c1 | (1 << PALETTE_MODIFIER_GREYOUT));
|
|
|
|
/* draw shaded lines */
|
|
GfxFillRect(r.left+2, r.top+10, r.left+2, r.bottom-10, c1);
|
|
GfxFillRect(r.left+3, r.top+10, r.left+3, r.bottom-10, c2);
|
|
GfxFillRect(r.left+7, r.top+10, r.left+7, r.bottom-10, c1);
|
|
GfxFillRect(r.left+8, r.top+10, r.left+8, r.bottom-10, c2);
|
|
|
|
pt = HandleScrollbarHittest(&w->vscroll2, r.top, r.bottom);
|
|
DrawFrameRect(r.left, pt.x, r.right, pt.y, wi->color, (w->flags4 & (WF_SCROLL_MIDDLE | WF_HSCROLL | WF_SCROLL2)) == (WF_SCROLL_MIDDLE | WF_SCROLL2) ? FR_LOWERED : FR_NONE);
|
|
break;
|
|
}
|
|
|
|
/* horizontal scrollbar */
|
|
case WWT_HSCROLLBAR: {
|
|
Point pt;
|
|
int c1,c2;
|
|
|
|
assert(r.bottom - r.top == 11); // XXX - to ensure the same sizes are used everywhere!
|
|
|
|
clicked = ((w->flags4 & (WF_SCROLL_UP | WF_HSCROLL)) == (WF_SCROLL_UP | WF_HSCROLL));
|
|
DrawFrameRect(r.left, r.top, r.left + 9, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DrawSprite(SPR_ARROW_LEFT, PAL_NONE, r.left + 1 + clicked, r.top + 1 + clicked);
|
|
|
|
clicked = ((w->flags4 & (WF_SCROLL_DOWN | WF_HSCROLL)) == (WF_SCROLL_DOWN | WF_HSCROLL));
|
|
DrawFrameRect(r.right-9, r.top, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, r.right - 8 + clicked, r.top + 1 + clicked);
|
|
|
|
c1 = _colour_gradient[wi->color&0xF][3];
|
|
c2 = _colour_gradient[wi->color&0xF][7];
|
|
|
|
/* draw "shaded" background */
|
|
GfxFillRect(r.left+10, r.top, r.right-10, r.bottom, c2);
|
|
GfxFillRect(r.left+10, r.top, r.right-10, r.bottom, c1 | (1 << PALETTE_MODIFIER_GREYOUT));
|
|
|
|
/* draw shaded lines */
|
|
GfxFillRect(r.left+10, r.top+2, r.right-10, r.top+2, c1);
|
|
GfxFillRect(r.left+10, r.top+3, r.right-10, r.top+3, c2);
|
|
GfxFillRect(r.left+10, r.top+7, r.right-10, r.top+7, c1);
|
|
GfxFillRect(r.left+10, r.top+8, r.right-10, r.top+8, c2);
|
|
|
|
/* draw actual scrollbar */
|
|
pt = HandleScrollbarHittest(&w->hscroll, r.left, r.right);
|
|
DrawFrameRect(pt.x, r.top, pt.y, r.bottom, wi->color, (w->flags4 & (WF_SCROLL_MIDDLE | WF_HSCROLL)) == (WF_SCROLL_MIDDLE | WF_HSCROLL) ? FR_LOWERED : FR_NONE);
|
|
|
|
break;
|
|
}
|
|
|
|
case WWT_FRAME: {
|
|
int c1,c2;
|
|
int x2 = r.left; // by default the left side is the left side of the widget
|
|
|
|
if (wi->data != 0) x2 = DrawString(r.left + 6, r.top, wi->data, 0);
|
|
|
|
c1 = _colour_gradient[wi->color][3];
|
|
c2 = _colour_gradient[wi->color][7];
|
|
|
|
/*Line from upper left corner to start of text */
|
|
GfxFillRect(r.left, r.top+4, r.left+4,r.top+4, c1);
|
|
GfxFillRect(r.left+1, r.top+5, r.left+4,r.top+5, c2);
|
|
|
|
/* Line from end of text to upper right corner */
|
|
GfxFillRect(x2, r.top+4, r.right-1,r.top+4,c1);
|
|
GfxFillRect(x2, r.top+5, r.right-2,r.top+5,c2);
|
|
|
|
/* Line from upper left corner to bottom left corner */
|
|
GfxFillRect(r.left, r.top+5, r.left, r.bottom-1, c1);
|
|
GfxFillRect(r.left+1, r.top+6, r.left+1, r.bottom-2, c2);
|
|
|
|
/*Line from upper right corner to bottom right corner */
|
|
GfxFillRect(r.right-1, r.top+5, r.right-1, r.bottom-2, c1);
|
|
GfxFillRect(r.right, r.top+4, r.right, r.bottom-1, c2);
|
|
|
|
GfxFillRect(r.left+1, r.bottom-1, r.right-1, r.bottom-1, c1);
|
|
GfxFillRect(r.left, r.bottom, r.right, r.bottom, c2);
|
|
|
|
goto draw_default;
|
|
}
|
|
|
|
case WWT_STICKYBOX: {
|
|
assert(r.right - r.left == 11); // XXX - to ensure the same sizes are used everywhere!
|
|
|
|
clicked = !!(w->flags4 & WF_STICKY);
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DrawSprite((clicked) ? SPR_PIN_UP : SPR_PIN_DOWN, PAL_NONE, r.left + 2 + clicked, r.top + 3 + clicked);
|
|
break;
|
|
}
|
|
|
|
case WWT_RESIZEBOX: {
|
|
assert(r.right - r.left == 11); // XXX - to ensure the same sizes are used everywhere!
|
|
|
|
clicked = !!(w->flags4 & WF_SIZING);
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, (clicked) ? FR_LOWERED : FR_NONE);
|
|
DrawSprite(SPR_WINDOW_RESIZE, PAL_NONE, r.left + 3 + clicked, r.top + 3 + clicked);
|
|
break;
|
|
}
|
|
|
|
case WWT_CLOSEBOX: {
|
|
assert(r.right - r.left == 10); // ensure the same sizes are used everywhere
|
|
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, FR_NONE);
|
|
DrawString(r.left + 2, r.top + 2, STR_00C5, 0);
|
|
break;
|
|
}
|
|
|
|
case WWT_CAPTION: {
|
|
assert(r.bottom - r.top == 13); // XXX - to ensure the same sizes are used everywhere!
|
|
DrawFrameRect(r.left, r.top, r.right, r.bottom, wi->color, FR_BORDERONLY);
|
|
DrawFrameRect(r.left+1, r.top+1, r.right-1, r.bottom-1, wi->color, (w->caption_color == 0xFF) ? FR_LOWERED | FR_DARKENED : FR_LOWERED | FR_DARKENED | FR_BORDERONLY);
|
|
|
|
if (w->caption_color != 0xFF) {
|
|
GfxFillRect(r.left+2, r.top+2, r.right-2, r.bottom-2, _colour_gradient[_player_colors[w->caption_color]][4]);
|
|
}
|
|
|
|
DrawStringCenteredTruncated(r.left + 2, r.right - 2, r.top+2, wi->data, 0x84);
|
|
draw_default:;
|
|
if (IsWindowWidgetDisabled(w, i)) {
|
|
GfxFillRect(r.left+1, r.top+1, r.right-1, r.bottom-1, _colour_gradient[wi->color&0xF][2] | (1 << PALETTE_MODIFIER_GREYOUT));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (w->flags4 & WF_WHITE_BORDER_MASK) {
|
|
//DrawFrameRect(w->left, w->top, w->left + w->width-1, w->top+w->height-1, 0xF, 0x10);
|
|
DrawFrameRect(0, 0, w->width-1, w->height-1, 0xF, FR_BORDERONLY);
|
|
}
|
|
|
|
}
|
|
|
|
static const Widget _dropdown_menu_widgets[] = {
|
|
{ WWT_PANEL, RESIZE_NONE, 0, 0, 0, 0, 0, 0x0, STR_NULL},
|
|
{ WWT_SCROLLBAR, RESIZE_NONE, 0, 0, 0, 0, 0, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST},
|
|
{ WIDGETS_END},
|
|
};
|
|
|
|
static int GetDropdownItem(const Window *w)
|
|
{
|
|
byte item, counter;
|
|
int y;
|
|
|
|
if (GetWidgetFromPos(w, _cursor.pos.x - w->left, _cursor.pos.y - w->top) < 0)
|
|
return -1;
|
|
|
|
y = _cursor.pos.y - w->top - 2 + w->vscroll.pos * 10;
|
|
|
|
if (y < 0)
|
|
return - 1;
|
|
|
|
item = y / 10;
|
|
if (item >= WP(w,dropdown_d).num_items || (HASBIT(WP(w,dropdown_d).disabled_state, item) && !HASBIT(WP(w,dropdown_d).hidden_state, item)) || WP(w,dropdown_d).items[item] == 0)
|
|
return - 1;
|
|
|
|
/* Skip hidden items -- +1 for each hidden item before the clicked item. */
|
|
for (counter = 0; item >= counter; ++counter)
|
|
if (HASBIT(WP(w,dropdown_d).hidden_state, counter)) item++;
|
|
|
|
return item;
|
|
}
|
|
|
|
static void DropdownMenuWndProc(Window *w, WindowEvent *e)
|
|
{
|
|
int item;
|
|
|
|
switch (e->event) {
|
|
case WE_PAINT: {
|
|
int x,y,i,sel;
|
|
int width, height;
|
|
|
|
DrawWindowWidgets(w);
|
|
|
|
x = 1;
|
|
y = 2 - w->vscroll.pos * 10;
|
|
|
|
sel = WP(w,dropdown_d).selected_index;
|
|
width = w->widget[0].right - 3;
|
|
height = w->widget[0].bottom - 3;
|
|
|
|
for (i = 0; WP(w,dropdown_d).items[i] != INVALID_STRING_ID; i++, sel--) {
|
|
if (HASBIT(WP(w,dropdown_d).hidden_state, i)) continue;
|
|
|
|
if (y >= 0 && y <= height) {
|
|
if (WP(w,dropdown_d).items[i] != STR_NULL) {
|
|
if (sel == 0) GfxFillRect(x + 1, y, x + width, y + 9, 0);
|
|
DrawStringTruncated(x + 2, y, WP(w,dropdown_d).items[i], sel == 0 ? 12 : 16, x + width);
|
|
|
|
if (HASBIT(WP(w,dropdown_d).disabled_state, i)) {
|
|
GfxFillRect(x, y, x + width, y + 9,
|
|
(1 << PALETTE_MODIFIER_GREYOUT) | _colour_gradient[_dropdown_menu_widgets[0].color][5]
|
|
);
|
|
}
|
|
} else {
|
|
int c1 = _colour_gradient[_dropdown_menu_widgets[0].color][3];
|
|
int c2 = _colour_gradient[_dropdown_menu_widgets[0].color][7];
|
|
|
|
GfxFillRect(x + 1, y + 3, x + w->width - 5, y + 3, c1);
|
|
GfxFillRect(x + 1, y + 4, x + w->width - 5, y + 4, c2);
|
|
}
|
|
}
|
|
y += 10;
|
|
}
|
|
} break;
|
|
|
|
case WE_CLICK: {
|
|
if (e->we.click.widget != 0) break;
|
|
item = GetDropdownItem(w);
|
|
if (item >= 0) {
|
|
WP(w,dropdown_d).click_delay = 4;
|
|
WP(w,dropdown_d).selected_index = item;
|
|
SetWindowDirty(w);
|
|
}
|
|
} break;
|
|
|
|
case WE_MOUSELOOP: {
|
|
Window *w2 = FindWindowById(WP(w,dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
|
|
if (w2 == NULL) {
|
|
DeleteWindow(w);
|
|
return;
|
|
}
|
|
|
|
if (WP(w,dropdown_d).click_delay != 0 && --WP(w,dropdown_d).click_delay == 0) {
|
|
WindowEvent e;
|
|
e.event = WE_DROPDOWN_SELECT;
|
|
e.we.dropdown.button = WP(w,dropdown_d).parent_button;
|
|
e.we.dropdown.index = WP(w,dropdown_d).selected_index;
|
|
w2->wndproc(w2, &e);
|
|
DeleteWindow(w);
|
|
return;
|
|
}
|
|
|
|
if (WP(w,dropdown_d).drag_mode) {
|
|
item = GetDropdownItem(w);
|
|
|
|
if (!_left_button_clicked) {
|
|
WP(w,dropdown_d).drag_mode = false;
|
|
if (item < 0) return;
|
|
WP(w,dropdown_d).click_delay = 2;
|
|
} else {
|
|
if (item < 0) return;
|
|
}
|
|
|
|
WP(w,dropdown_d).selected_index = item;
|
|
SetWindowDirty(w);
|
|
}
|
|
} break;
|
|
|
|
case WE_DESTROY: {
|
|
Window *w2 = FindWindowById(WP(w,dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
|
|
if (w2 != NULL) {
|
|
RaiseWindowWidget(w2, WP(w,dropdown_d).parent_button);
|
|
InvalidateWidget(w2, WP(w,dropdown_d).parent_button);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask)
|
|
{
|
|
int i;
|
|
const Widget *wi;
|
|
Window *w2;
|
|
const Window *w3;
|
|
bool is_dropdown_menu_shown = IsWindowWidgetLowered(w, button);
|
|
int top, height;
|
|
int screen_top, screen_bottom;
|
|
bool scroll = false;
|
|
|
|
DeleteWindowById(WC_DROPDOWN_MENU, 0);
|
|
|
|
if (is_dropdown_menu_shown) return;
|
|
|
|
LowerWindowWidget(w, button);
|
|
|
|
InvalidateWidget(w, button);
|
|
|
|
for (i = 0; strings[i] != INVALID_STRING_ID; i++) {}
|
|
if (i == 0) return;
|
|
|
|
wi = &w->widget[button];
|
|
|
|
if (hidden_mask != 0) {
|
|
uint j;
|
|
|
|
for (j = 0; strings[j] != INVALID_STRING_ID; j++) {
|
|
if (HASBIT(hidden_mask, j)) i--;
|
|
}
|
|
}
|
|
|
|
/* The preferred position is just below the dropdown calling widget */
|
|
top = w->top + wi->bottom + 2;
|
|
height = i * 10 + 4;
|
|
|
|
w3 = FindWindowById(WC_STATUS_BAR, 0);
|
|
screen_bottom = w3 == NULL ? _screen.height : w3->top;
|
|
|
|
/* Check if the dropdown will fully fit below the widget */
|
|
if (top + height >= screen_bottom) {
|
|
w3 = FindWindowById(WC_MAIN_TOOLBAR, 0);
|
|
screen_top = w3 == NULL ? 0 : w3->top + w3->height;
|
|
|
|
/* If not, check if it will fit above the widget */
|
|
if (w->top + wi->top - height - 1 > screen_top) {
|
|
top = w->top + wi->top - height - 1;
|
|
} else {
|
|
/* ... and lastly if it won't, enable the scroll bar and fit the
|
|
* list in below the widget */
|
|
int rows = (screen_bottom - 4 - top) / 10;
|
|
height = rows * 10 + 4;
|
|
scroll = true;
|
|
}
|
|
}
|
|
|
|
w2 = AllocateWindow(
|
|
w->left + wi[-1].left + 1,
|
|
top,
|
|
wi->right - wi[-1].left + 1,
|
|
height,
|
|
DropdownMenuWndProc,
|
|
WC_DROPDOWN_MENU,
|
|
_dropdown_menu_widgets);
|
|
|
|
w2->widget[0].color = wi->color;
|
|
w2->widget[0].right = wi->right - wi[-1].left;
|
|
w2->widget[0].bottom = height - 1;
|
|
|
|
SetWindowWidgetHiddenState(w2, 1, !scroll);
|
|
|
|
if (scroll) {
|
|
/* We're scrolling, so enable the scroll bar and shrink the list by
|
|
* the scrollbar's width */
|
|
w2->widget[1].color = wi->color;
|
|
w2->widget[1].right = w2->widget[0].right;
|
|
w2->widget[1].left = w2->widget[1].right - 11;
|
|
w2->widget[1].bottom = height - 1;
|
|
w2->widget[0].right -= 12;
|
|
|
|
w2->vscroll.cap = (height - 4) / 10;
|
|
w2->vscroll.count = i;
|
|
}
|
|
|
|
w2->desc_flags = WDF_DEF_WIDGET;
|
|
w2->flags4 &= ~WF_WHITE_BORDER_MASK;
|
|
|
|
WP(w2,dropdown_d).disabled_state = disabled_mask;
|
|
WP(w2,dropdown_d).hidden_state = hidden_mask;
|
|
|
|
WP(w2,dropdown_d).parent_wnd_class = w->window_class;
|
|
WP(w2,dropdown_d).parent_wnd_num = w->window_number;
|
|
WP(w2,dropdown_d).parent_button = button;
|
|
|
|
WP(w2,dropdown_d).num_items = i;
|
|
WP(w2,dropdown_d).selected_index = selected;
|
|
WP(w2,dropdown_d).items = strings;
|
|
|
|
WP(w2,dropdown_d).click_delay = 0;
|
|
WP(w2,dropdown_d).drag_mode = true;
|
|
}
|
|
|
|
|
|
static void ResizeWidgets(Window *w, byte a, byte b)
|
|
{
|
|
int16 offset = w->widget[a].left;
|
|
int16 length = w->widget[b].right - offset;
|
|
|
|
w->widget[a].right = (length / 2) + offset;
|
|
|
|
w->widget[b].left = w->widget[a].right + 1;
|
|
}
|
|
|
|
static void ResizeWidgets(Window *w, byte a, byte b, byte c)
|
|
{
|
|
int16 offset = w->widget[a].left;
|
|
int16 length = w->widget[c].right - offset;
|
|
|
|
w->widget[a].right = length / 3;
|
|
w->widget[b].right = w->widget[a].right * 2;
|
|
|
|
w->widget[a].right += offset;
|
|
w->widget[b].right += offset;
|
|
|
|
/* Now the right side of the buttons are set. We will now set the left sides next to them */
|
|
w->widget[b].left = w->widget[a].right + 1;
|
|
w->widget[c].left = w->widget[b].right + 1;
|
|
}
|
|
|
|
/** Evenly distribute some widgets when resizing horizontally (often a button row)
|
|
* When only two arguments are given, the widgets are presumed to be on a line and only the ends are given
|
|
* @param w Window to modify
|
|
* @param left The leftmost widget to resize
|
|
* @param right The rightmost widget to resize. Since right side of it is used, remember to set it to RESIZE_RIGHT
|
|
*/
|
|
void ResizeButtons(Window *w, byte left, byte right)
|
|
{
|
|
int16 num_widgets = right - left + 1;
|
|
|
|
if (num_widgets < 2) NOT_REACHED();
|
|
|
|
switch (num_widgets) {
|
|
case 2: ResizeWidgets(w, left, right); break;
|
|
case 3: ResizeWidgets(w, left, left + 1, right); break;
|
|
default: {
|
|
/* Looks like we got more than 3 widgets to resize
|
|
* Now we will find the middle of the space desinated for the widgets
|
|
* and place half of the widgets on each side of it and call recursively.
|
|
* Eventually we will get down to blocks of 2-3 widgets and we got code to handle those cases */
|
|
int16 offset = w->widget[left].left;
|
|
int16 length = w->widget[right].right - offset;
|
|
byte widget = ((num_widgets - 1)/ 2) + left; // rightmost widget of the left side
|
|
|
|
/* Now we need to find the middle of the widgets.
|
|
* It will not always be the middle because if we got an uneven number of widgets,
|
|
* we will need it to be 2/5, 3/7 and so on
|
|
* To get this, we multiply with num_widgets/num_widgets. Since we calculate in int, we will get:
|
|
*
|
|
* num_widgets/2 (rounding down)
|
|
* ---------------
|
|
* num_widgets
|
|
*
|
|
* as multiplier to length. We just multiply before divide to that we stay in the int area though */
|
|
int16 middle = ((length * num_widgets) / (2 * num_widgets)) + offset;
|
|
|
|
/* Set left and right on the widgets, that's next to our "middle" */
|
|
w->widget[widget].right = middle;
|
|
w->widget[widget + 1].left = w->widget[widget].right + 1;
|
|
/* Now resize the left and right of the middle */
|
|
ResizeButtons(w, left, widget);
|
|
ResizeButtons(w, widget + 1, right);
|
|
}
|
|
}
|
|
}
|