Annotation of brogue-ce/src/brogue/Buttons.c, Revision 1.1
1.1 ! rubenllo 1: /*
! 2: * Buttons.c
! 3: * Brogue
! 4: *
! 5: * Created by Brian Walker on 11/18/11.
! 6: * Copyright 2012. All rights reserved.
! 7: *
! 8: * This file is part of Brogue.
! 9: *
! 10: * This program is free software: you can redistribute it and/or modify
! 11: * it under the terms of the GNU Affero General Public License as
! 12: * published by the Free Software Foundation, either version 3 of the
! 13: * License, or (at your option) any later version.
! 14: *
! 15: * This program is distributed in the hope that it will be useful,
! 16: * but WITHOUT ANY WARRANTY; without even the implied warranty of
! 17: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! 18: * GNU Affero General Public License for more details.
! 19: *
! 20: * You should have received a copy of the GNU Affero General Public License
! 21: * along with this program. If not, see <http://www.gnu.org/licenses/>.
! 22: */
! 23:
! 24: #include "Rogue.h"
! 25: #include "IncludeGlobals.h"
! 26: #include <math.h>
! 27: #include <time.h>
! 28:
! 29: // Draws the smooth gradient that appears on a button when you hover over or depress it.
! 30: // Returns the percentage by which the current tile should be averaged toward a hilite color.
! 31: short smoothHiliteGradient(const short currentXValue, const short maxXValue) {
! 32: return (short) (100 * sin(3.14159265 * currentXValue / maxXValue));
! 33: }
! 34:
! 35: // Draws the button to the screen, or to a display buffer if one is given.
! 36: // Button back color fades from -50% intensity at the edges to the back color in the middle.
! 37: // Text is white, but can use color escapes.
! 38: // Hovering highlight augments fore and back colors with buttonHoverColor by 20%.
! 39: // Pressed darkens the middle color (or turns it the hover color if the button is black).
! 40: void drawButton(brogueButton *button, enum buttonDrawStates highlight, cellDisplayBuffer dbuf[COLS][ROWS]) {
! 41: short i, textLoc, width, midPercent, symbolNumber, opacity, oldRNG;
! 42: color fColor, bColor, fColorBase, bColorBase, bColorEdge, bColorMid;
! 43: enum displayGlyph displayCharacter;
! 44:
! 45: if (!(button->flags & B_DRAW)) {
! 46: return;
! 47: }
! 48: //assureCosmeticRNG;
! 49: oldRNG = rogue.RNG;
! 50: rogue.RNG = RNG_COSMETIC;
! 51:
! 52: symbolNumber = 0;
! 53:
! 54: width = strLenWithoutEscapes(button->text);
! 55: bColorBase = button->buttonColor;
! 56: fColorBase = ((button->flags & B_ENABLED) ? white : gray);
! 57:
! 58: if (highlight == BUTTON_HOVER && (button->flags & B_HOVER_ENABLED)) {
! 59: //applyColorAugment(&fColorBase, &buttonHoverColor, 20);
! 60: //applyColorAugment(&bColorBase, &buttonHoverColor, 20);
! 61: applyColorAverage(&fColorBase, &buttonHoverColor, 25);
! 62: applyColorAverage(&bColorBase, &buttonHoverColor, 25);
! 63: }
! 64:
! 65: bColorEdge = bColorBase;
! 66: bColorMid = bColorBase;
! 67: applyColorAverage(&bColorEdge, &black, 50);
! 68:
! 69: if (highlight == BUTTON_PRESSED) {
! 70: applyColorAverage(&bColorMid, &black, 75);
! 71: if (COLOR_DIFF(bColorMid, bColorBase) < 50) {
! 72: bColorMid = bColorBase;
! 73: applyColorAverage(&bColorMid, &buttonHoverColor, 50);
! 74: }
! 75: }
! 76: bColor = bColorMid;
! 77:
! 78: opacity = button->opacity;
! 79: if (highlight == BUTTON_HOVER || highlight == BUTTON_PRESSED) {
! 80: opacity = 100 - ((100 - opacity) * opacity / 100); // Apply the opacity twice.
! 81: }
! 82:
! 83: for (i = textLoc = 0; i < width && i + button->x < COLS; i++, textLoc++) {
! 84: while (button->text[textLoc] == COLOR_ESCAPE) {
! 85: textLoc = decodeMessageColor(button->text, textLoc, &fColorBase);
! 86: }
! 87:
! 88: fColor = fColorBase;
! 89:
! 90: if (button->flags & B_GRADIENT) {
! 91: midPercent = smoothHiliteGradient(i, width - 1);
! 92: bColor = bColorEdge;
! 93: applyColorAverage(&bColor, &bColorMid, midPercent);
! 94: }
! 95:
! 96: if (highlight == BUTTON_PRESSED) {
! 97: applyColorAverage(&fColor, &bColor, 30);
! 98: }
! 99:
! 100: if (button->opacity < 100) {
! 101: applyColorAverage(&fColor, &bColor, 100 - opacity);
! 102: }
! 103:
! 104: bakeColor(&fColor);
! 105: bakeColor(&bColor);
! 106: separateColors(&fColor, &bColor);
! 107:
! 108: displayCharacter = button->text[textLoc];
! 109: if (button->text[textLoc] == '*') {
! 110: if (button->symbol[symbolNumber]) {
! 111: displayCharacter = button->symbol[symbolNumber];
! 112: }
! 113: symbolNumber++;
! 114: }
! 115:
! 116: if (coordinatesAreInWindow(button->x + i, button->y)) {
! 117: if (dbuf) {
! 118: plotCharToBuffer(displayCharacter, button->x + i, button->y, &fColor, &bColor, dbuf);
! 119: dbuf[button->x + i][button->y].opacity = opacity;
! 120: } else {
! 121: plotCharWithColor(displayCharacter, button->x + i, button->y, &fColor, &bColor);
! 122: }
! 123: }
! 124: }
! 125: restoreRNG;
! 126: }
! 127:
! 128: void initializeButton(brogueButton *button) {
! 129: memset((void *) button, 0, sizeof( brogueButton ));
! 130: button->text[0] = '\0';
! 131: button->flags |= (B_ENABLED | B_GRADIENT | B_HOVER_ENABLED | B_DRAW | B_KEYPRESS_HIGHLIGHT);
! 132: button->buttonColor = interfaceButtonColor;
! 133: button->opacity = 100;
! 134: }
! 135:
! 136: void drawButtonsInState(buttonState *state) {
! 137: short i;
! 138:
! 139: // Draw the buttons to the dbuf:
! 140: for (i=0; i < state->buttonCount; i++) {
! 141: if (state->buttons[i].flags & B_DRAW) {
! 142: drawButton(&(state->buttons[i]), BUTTON_NORMAL, state->dbuf);
! 143: }
! 144: }
! 145: }
! 146:
! 147: void initializeButtonState(buttonState *state,
! 148: brogueButton *buttons,
! 149: short buttonCount,
! 150: short winX,
! 151: short winY,
! 152: short winWidth,
! 153: short winHeight) {
! 154: short i, j;
! 155:
! 156: // Initialize variables for the state struct:
! 157: state->buttonChosen = state->buttonFocused = state->buttonDepressed = -1;
! 158: state->buttonCount = buttonCount;
! 159: state->winX = winX;
! 160: state->winY = winY;
! 161: state->winWidth = winWidth;
! 162: state->winHeight = winHeight;
! 163: for (i=0; i < state->buttonCount; i++) {
! 164: state->buttons[i] = buttons[i];
! 165: }
! 166: copyDisplayBuffer(state->rbuf, displayBuffer);
! 167: clearDisplayBuffer(state->dbuf);
! 168:
! 169: drawButtonsInState(state);
! 170:
! 171: // Clear the rbuf so that it resets only those parts of the screen in which buttons are drawn in the first place:
! 172: for (i=0; i<COLS; i++) {
! 173: for (j=0; j<ROWS; j++) {
! 174: state->rbuf[i][j].opacity = (state->dbuf[i][j].opacity ? 100 : 0);
! 175: }
! 176: }
! 177: }
! 178:
! 179: // Processes one round of user input, and bakes the necessary graphical changes into state->dbuf.
! 180: // Does NOT display the buttons or revert the display afterward.
! 181: // Assumes that the display has already been updated (via overlayDisplayBuffer(state->dbuf, NULL))
! 182: // and that input has been solicited (via nextBrogueEvent(event, ___, ___, ___)).
! 183: // Also relies on the buttonState having been initialized with initializeButtonState() or otherwise.
! 184: // Returns the index of a button if one is chosen.
! 185: // Otherwise, returns -1. That can be if the user canceled (in which case *canceled is true),
! 186: // or, more commonly, if the user's input in this particular split-second round was not decisive.
! 187: short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *event) {
! 188: short i, k, x, y;
! 189: boolean buttonUsed = false;
! 190:
! 191: // Mouse event:
! 192: if (event->eventType == MOUSE_DOWN
! 193: || event->eventType == MOUSE_UP
! 194: || event->eventType == MOUSE_ENTERED_CELL) {
! 195:
! 196: x = event->param1;
! 197: y = event->param2;
! 198:
! 199: // Revert the button with old focus, if any.
! 200: if (state->buttonFocused >= 0) {
! 201: drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf);
! 202: state->buttonFocused = -1;
! 203: }
! 204:
! 205: // Find the button with new focus, if any.
! 206: for (i=0; i < state->buttonCount; i++) {
! 207: if ((state->buttons[i].flags & B_DRAW)
! 208: && (state->buttons[i].flags & B_ENABLED)
! 209: && (state->buttons[i].y == y || ((state->buttons[i].flags & B_WIDE_CLICK_AREA) && abs(state->buttons[i].y - y) <= 1))
! 210: && x >= state->buttons[i].x
! 211: && x < state->buttons[i].x + strLenWithoutEscapes(state->buttons[i].text)) {
! 212:
! 213: state->buttonFocused = i;
! 214: if (event->eventType == MOUSE_DOWN) {
! 215: state->buttonDepressed = i; // Keeps track of which button is down at the moment. Cleared on mouseup.
! 216: }
! 217: break;
! 218: }
! 219: }
! 220: if (i == state->buttonCount) { // No focus this round.
! 221: state->buttonFocused = -1;
! 222: }
! 223:
! 224: if (state->buttonDepressed >= 0) {
! 225: if (state->buttonDepressed == state->buttonFocused) {
! 226: drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_PRESSED, state->dbuf);
! 227: }
! 228: } else if (state->buttonFocused >= 0) {
! 229: // If no button is depressed, then update the appearance of the button with the new focus, if any.
! 230: drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf);
! 231: }
! 232:
! 233: // Mouseup:
! 234: if (event->eventType == MOUSE_UP) {
! 235: if (state->buttonDepressed == state->buttonFocused && state->buttonFocused >= 0) {
! 236: // If a button is depressed, and the mouseup happened on that button, it has been chosen and we're done.
! 237: buttonUsed = true;
! 238: } else {
! 239: // Otherwise, no button is depressed. If one was previously depressed, redraw it.
! 240: if (state->buttonDepressed >= 0) {
! 241: drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf);
! 242: } else if (!(x >= state->winX && x < state->winX + state->winWidth
! 243: && y >= state->winY && y < state->winY + state->winHeight)) {
! 244: // Clicking outside of a button means canceling.
! 245: if (canceled) {
! 246: *canceled = true;
! 247: }
! 248: }
! 249:
! 250: if (state->buttonFocused >= 0) {
! 251: // Buttons don't hover-highlight when one is depressed, so we have to fix that when the mouse is up.
! 252: drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf);
! 253: }
! 254: state->buttonDepressed = -1;
! 255: }
! 256: }
! 257: }
! 258:
! 259: // Keystroke:
! 260: if (event->eventType == KEYSTROKE) {
! 261:
! 262: // Cycle through all of the hotkeys of all of the buttons.
! 263: for (i=0; i < state->buttonCount; i++) {
! 264: for (k = 0; k < 10 && state->buttons[i].hotkey[k]; k++) {
! 265: if (event->param1 == state->buttons[i].hotkey[k]) {
! 266: // This button was chosen.
! 267:
! 268: if (state->buttons[i].flags & B_DRAW) {
! 269: // Restore the depressed and focused buttons.
! 270: if (state->buttonDepressed >= 0) {
! 271: drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf);
! 272: }
! 273: if (state->buttonFocused >= 0) {
! 274: drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf);
! 275: }
! 276:
! 277: // If the button likes to flash when keypressed:
! 278: if (state->buttons[i].flags & B_KEYPRESS_HIGHLIGHT) {
! 279: // Depress the chosen button.
! 280: drawButton(&(state->buttons[i]), BUTTON_PRESSED, state->dbuf);
! 281:
! 282: // Update the display.
! 283: overlayDisplayBuffer(state->rbuf, NULL);
! 284: overlayDisplayBuffer(state->dbuf, NULL);
! 285:
! 286: // Wait for a little; then we're done.
! 287: pauseBrogue(50);
! 288: }
! 289: }
! 290:
! 291: state->buttonDepressed = i;
! 292: buttonUsed = true;
! 293: break;
! 294: }
! 295: }
! 296: }
! 297:
! 298: if (!buttonUsed
! 299: && (event->param1 == ESCAPE_KEY || event->param1 == ACKNOWLEDGE_KEY)) {
! 300: // If the player pressed escape, we're done.
! 301: if (canceled) {
! 302: *canceled = true;
! 303: }
! 304: }
! 305: }
! 306:
! 307: if (buttonUsed) {
! 308: state->buttonChosen = state->buttonDepressed;
! 309: return state->buttonChosen;
! 310: } else {
! 311: return -1;
! 312: }
! 313: }
! 314:
! 315: // Displays a bunch of buttons and collects user input.
! 316: // Returns the index number of the chosen button, or -1 if the user cancels.
! 317: // A window region is described by winX, winY, winWidth and winHeight.
! 318: // Clicking outside of that region will constitute canceling.
! 319: short buttonInputLoop(brogueButton *buttons,
! 320: short buttonCount,
! 321: short winX,
! 322: short winY,
! 323: short winWidth,
! 324: short winHeight,
! 325: rogueEvent *returnEvent) {
! 326: short button;
! 327: boolean canceled;
! 328: rogueEvent theEvent;
! 329: buttonState state = {0};
! 330:
! 331: assureCosmeticRNG;
! 332:
! 333: canceled = false;
! 334: initializeButtonState(&state, buttons, buttonCount, winX, winY, winWidth, winHeight);
! 335:
! 336: do {
! 337: // Update the display.
! 338: overlayDisplayBuffer(state.dbuf, NULL);
! 339:
! 340: // Get input.
! 341: nextBrogueEvent(&theEvent, true, false, false);
! 342:
! 343: // Process the input.
! 344: button = processButtonInput(&state, &canceled, &theEvent);
! 345:
! 346: // Revert the display.
! 347: overlayDisplayBuffer(state.rbuf, NULL);
! 348:
! 349: } while (button == -1 && !canceled);
! 350:
! 351: if (returnEvent) {
! 352: *returnEvent = theEvent;
! 353: }
! 354:
! 355: //overlayDisplayBuffer(dbuf, NULL); // hangs around
! 356:
! 357: restoreRNG;
! 358:
! 359: return button;
! 360: }
CVSweb