Annotation of brogue-ce/src/brogue/Buttons.c, Revision 1.1.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