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.
smoothHiliteGradient(const short currentXValue,const short maxXValue)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).
drawButton(brogueButton * button,enum buttonDrawStates highlight,cellDisplayBuffer dbuf[COLS][ROWS])40 void drawButton(brogueButton *button, enum buttonDrawStates highlight, cellDisplayBuffer dbuf[COLS][ROWS]) {
41 if (!(button->flags & B_DRAW)) {
42 return;
43 }
44 //assureCosmeticRNG;
45 short oldRNG = rogue.RNG;
46 rogue.RNG = RNG_COSMETIC;
47
48 const int width = strLenWithoutEscapes(button->text);
49 color bColorBase = button->buttonColor;
50 color fColorBase = ((button->flags & B_ENABLED) ? white : gray);
51
52 if (highlight == BUTTON_HOVER && (button->flags & B_HOVER_ENABLED)) {
53 //applyColorAugment(&fColorBase, &buttonHoverColor, 20);
54 //applyColorAugment(&bColorBase, &buttonHoverColor, 20);
55 applyColorAverage(&fColorBase, &buttonHoverColor, 25);
56 applyColorAverage(&bColorBase, &buttonHoverColor, 25);
57 }
58
59 color bColorEdge = bColorBase;
60 color bColorMid = bColorBase;
61 applyColorAverage(&bColorEdge, &black, 50);
62
63 if (highlight == BUTTON_PRESSED) {
64 applyColorAverage(&bColorMid, &black, 75);
65 if (COLOR_DIFF(bColorMid, bColorBase) < 50) {
66 bColorMid = bColorBase;
67 applyColorAverage(&bColorMid, &buttonHoverColor, 50);
68 }
69 }
70 color bColor = bColorMid;
71
72 short opacity = button->opacity;
73 if (highlight == BUTTON_HOVER || highlight == BUTTON_PRESSED) {
74 opacity = 100 - ((100 - opacity) * opacity / 100); // Apply the opacity twice.
75 }
76
77 short symbolNumber = 0;
78
79 for (int i = 0, textLoc = 0; i < width && i + button->x < COLS; i++, textLoc++) {
80 while (button->text[textLoc] == COLOR_ESCAPE) {
81 textLoc = decodeMessageColor(button->text, textLoc, &fColorBase);
82 }
83
84 color fColor = fColorBase;
85
86 if (button->flags & B_GRADIENT) {
87 const int midPercent = smoothHiliteGradient(i, width - 1);
88 bColor = bColorEdge;
89 applyColorAverage(&bColor, &bColorMid, midPercent);
90 }
91
92 if (highlight == BUTTON_PRESSED) {
93 applyColorAverage(&fColor, &bColor, 30);
94 }
95
96 if (button->opacity < 100) {
97 applyColorAverage(&fColor, &bColor, 100 - opacity);
98 }
99
100 bakeColor(&fColor);
101 bakeColor(&bColor);
102 separateColors(&fColor, &bColor);
103
104 enum displayGlyph displayCharacter = button->text[textLoc];
105 if (button->text[textLoc] == '*') {
106 if (button->symbol[symbolNumber]) {
107 displayCharacter = button->symbol[symbolNumber];
108 }
109 symbolNumber++;
110 }
111
112 if (coordinatesAreInWindow(button->x + i, button->y)) {
113 if (dbuf) {
114 plotCharToBuffer(displayCharacter, button->x + i, button->y, &fColor, &bColor, dbuf);
115 dbuf[button->x + i][button->y].opacity = opacity;
116 } else {
117 plotCharWithColor(displayCharacter, button->x + i, button->y, &fColor, &bColor);
118 }
119 }
120 }
121 restoreRNG;
122 }
123
initializeButton(brogueButton * button)124 void initializeButton(brogueButton *button) {
125 memset((void *) button, 0, sizeof( brogueButton ));
126 button->text[0] = '\0';
127 button->flags |= (B_ENABLED | B_GRADIENT | B_HOVER_ENABLED | B_DRAW | B_KEYPRESS_HIGHLIGHT);
128 button->buttonColor = interfaceButtonColor;
129 button->opacity = 100;
130 }
131
drawButtonsInState(buttonState * state)132 void drawButtonsInState(buttonState *state) {
133 // Draw the buttons to the dbuf:
134 for (int i=0; i < state->buttonCount; i++) {
135 if (state->buttons[i].flags & B_DRAW) {
136 drawButton(&(state->buttons[i]), BUTTON_NORMAL, state->dbuf);
137 }
138 }
139 }
140
initializeButtonState(buttonState * state,brogueButton * buttons,short buttonCount,short winX,short winY,short winWidth,short winHeight)141 void initializeButtonState(buttonState *state,
142 brogueButton *buttons,
143 short buttonCount,
144 short winX,
145 short winY,
146 short winWidth,
147 short winHeight) {
148 // Initialize variables for the state struct:
149 state->buttonChosen = state->buttonFocused = state->buttonDepressed = -1;
150 state->buttonCount = buttonCount;
151 state->winX = winX;
152 state->winY = winY;
153 state->winWidth = winWidth;
154 state->winHeight = winHeight;
155 for (int i=0; i < state->buttonCount; i++) {
156 state->buttons[i] = buttons[i];
157 }
158 copyDisplayBuffer(state->rbuf, displayBuffer);
159 clearDisplayBuffer(state->dbuf);
160
161 drawButtonsInState(state);
162
163 // Clear the rbuf so that it resets only those parts of the screen in which buttons are drawn in the first place:
164 for (int i=0; i<COLS; i++) {
165 for (int j=0; j<ROWS; j++) {
166 state->rbuf[i][j].opacity = (state->dbuf[i][j].opacity ? 100 : 0);
167 }
168 }
169 }
170
171 // Processes one round of user input, and bakes the necessary graphical changes into state->dbuf.
172 // Does NOT display the buttons or revert the display afterward.
173 // Assumes that the display has already been updated (via overlayDisplayBuffer(state->dbuf, NULL))
174 // and that input has been solicited (via nextBrogueEvent(event, ___, ___, ___)).
175 // Also relies on the buttonState having been initialized with initializeButtonState() or otherwise.
176 // Returns the index of a button if one is chosen.
177 // Otherwise, returns -1. That can be if the user canceled (in which case *canceled is true),
178 // or, more commonly, if the user's input in this particular split-second round was not decisive.
processButtonInput(buttonState * state,boolean * canceled,rogueEvent * event)179 short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *event) {
180 boolean buttonUsed = false;
181
182 // Mouse event:
183 if (event->eventType == MOUSE_DOWN
184 || event->eventType == MOUSE_UP
185 || event->eventType == MOUSE_ENTERED_CELL) {
186
187 int x = event->param1;
188 int y = event->param2;
189
190 // Revert the button with old focus, if any.
191 if (state->buttonFocused >= 0) {
192 drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf);
193 state->buttonFocused = -1;
194 }
195
196 // Find the button with new focus, if any.
197 int focusIndex;
198 for (focusIndex=0; focusIndex < state->buttonCount; focusIndex++) {
199 if ((state->buttons[focusIndex].flags & B_DRAW)
200 && (state->buttons[focusIndex].flags & B_ENABLED)
201 && (state->buttons[focusIndex].y == y || ((state->buttons[focusIndex].flags & B_WIDE_CLICK_AREA) && abs(state->buttons[focusIndex].y - y) <= 1))
202 && x >= state->buttons[focusIndex].x
203 && x < state->buttons[focusIndex].x + strLenWithoutEscapes(state->buttons[focusIndex].text)) {
204
205 state->buttonFocused = focusIndex;
206 if (event->eventType == MOUSE_DOWN) {
207 state->buttonDepressed = focusIndex; // Keeps track of which button is down at the moment. Cleared on mouseup.
208 }
209 break;
210 }
211 }
212 if (focusIndex == state->buttonCount) { // No focus this round.
213 state->buttonFocused = -1;
214 }
215
216 if (state->buttonDepressed >= 0) {
217 if (state->buttonDepressed == state->buttonFocused) {
218 drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_PRESSED, state->dbuf);
219 }
220 } else if (state->buttonFocused >= 0) {
221 // If no button is depressed, then update the appearance of the button with the new focus, if any.
222 drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf);
223 }
224
225 // Mouseup:
226 if (event->eventType == MOUSE_UP) {
227 if (state->buttonDepressed == state->buttonFocused && state->buttonFocused >= 0) {
228 // If a button is depressed, and the mouseup happened on that button, it has been chosen and we're done.
229 buttonUsed = true;
230 } else {
231 // Otherwise, no button is depressed. If one was previously depressed, redraw it.
232 if (state->buttonDepressed >= 0) {
233 drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf);
234 } else if (!(x >= state->winX && x < state->winX + state->winWidth
235 && y >= state->winY && y < state->winY + state->winHeight)) {
236 // Clicking outside of a button means canceling.
237 if (canceled) {
238 *canceled = true;
239 }
240 }
241
242 if (state->buttonFocused >= 0) {
243 // Buttons don't hover-highlight when one is depressed, so we have to fix that when the mouse is up.
244 drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf);
245 }
246 state->buttonDepressed = -1;
247 }
248 }
249 }
250
251 // Keystroke:
252 if (event->eventType == KEYSTROKE) {
253
254 // Cycle through all of the hotkeys of all of the buttons.
255 for (int i=0; i < state->buttonCount; i++) {
256 for (int k = 0; k < 10 && state->buttons[i].hotkey[k]; k++) {
257 if (event->param1 == state->buttons[i].hotkey[k]) {
258 // This button was chosen.
259
260 if (state->buttons[i].flags & B_DRAW) {
261 // Restore the depressed and focused buttons.
262 if (state->buttonDepressed >= 0) {
263 drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf);
264 }
265 if (state->buttonFocused >= 0) {
266 drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf);
267 }
268
269 // If the button likes to flash when keypressed:
270 if (state->buttons[i].flags & B_KEYPRESS_HIGHLIGHT) {
271 // Depress the chosen button.
272 drawButton(&(state->buttons[i]), BUTTON_PRESSED, state->dbuf);
273
274 // Update the display.
275 overlayDisplayBuffer(state->rbuf, NULL);
276 overlayDisplayBuffer(state->dbuf, NULL);
277
278 // Wait for a little; then we're done.
279 pauseBrogue(50);
280 }
281 }
282
283 state->buttonDepressed = i;
284 buttonUsed = true;
285 break;
286 }
287 }
288 }
289
290 if (!buttonUsed
291 && (event->param1 == ESCAPE_KEY || event->param1 == ACKNOWLEDGE_KEY)) {
292 // If the player pressed escape, we're done.
293 if (canceled) {
294 *canceled = true;
295 }
296 }
297 }
298
299 if (buttonUsed) {
300 state->buttonChosen = state->buttonDepressed;
301 return state->buttonChosen;
302 } else {
303 return -1;
304 }
305 }
306
307 // Displays a bunch of buttons and collects user input.
308 // Returns the index number of the chosen button, or -1 if the user cancels.
309 // A window region is described by winX, winY, winWidth and winHeight.
310 // Clicking outside of that region will constitute canceling.
buttonInputLoop(brogueButton * buttons,short buttonCount,short winX,short winY,short winWidth,short winHeight,rogueEvent * returnEvent)311 short buttonInputLoop(brogueButton *buttons,
312 short buttonCount,
313 short winX,
314 short winY,
315 short winWidth,
316 short winHeight,
317 rogueEvent *returnEvent) {
318 short button;
319 boolean canceled;
320 rogueEvent theEvent;
321 buttonState state = {0};
322
323 assureCosmeticRNG;
324
325 canceled = false;
326 initializeButtonState(&state, buttons, buttonCount, winX, winY, winWidth, winHeight);
327
328 do {
329 // Update the display.
330 overlayDisplayBuffer(state.dbuf, NULL);
331
332 // Get input.
333 nextBrogueEvent(&theEvent, true, false, false);
334
335 // Process the input.
336 button = processButtonInput(&state, &canceled, &theEvent);
337
338 // Revert the display.
339 overlayDisplayBuffer(state.rbuf, NULL);
340
341 } while (button == -1 && !canceled);
342
343 if (returnEvent) {
344 *returnEvent = theEvent;
345 }
346
347 //overlayDisplayBuffer(dbuf, NULL); // hangs around
348
349 restoreRNG;
350
351 return button;
352 }
353