Descent3/ui/UISystem.cpp
2024-08-26 00:45:06 +02:00

565 lines
16 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/Main/ui/UISystem.cpp $
* $Revision: 44 $
* $Date: 3/20/00 12:30p $
* $Author: Matt $
*
* UI system code.
*
* $Log: /DescentIII/Main/ui/UISystem.cpp $
*
* 44 3/20/00 12:30p Matt
* Merge of Duane's post-1.3 changes.
* Mac-only debugging change.
*
* 43 10/21/99 3:33p Kevin
* Mac merge!
*
* 42 7/28/99 2:03p Kevin
* Macintosh Changes
*
* 41 5/05/99 6:48p Samir
* hack to get pause key to properly register in UI system.
*
* 40 5/01/99 1:14a Samir
* fixed mouse sequencing probs with selections being canceled out by
* UISystem.
*
* 39 4/14/99 1:54a Jeff
* fixed case mismatched #includes
*
* 38 4/01/99 11:28a Samir
* never flush mouse during ui_Flush. This will screw up emulated mouse
* input and maybe mess up some hardware mouse support from the ddio
* library.
*
* 37 3/23/99 9:02p Samir
* added mouse flush for ui flush. also made keyboard flush too.
*
* 36 2/21/99 6:38p Samir
* mouse and key input better. buffered mouse.
*
* 35 1/28/99 6:49p Samir
* frame limit ui system.
*
* 34 1/25/99 11:02a Samir
* revamped mouse and key controls.
*
* 33 10/19/98 2:50p Samir
* added function to cleanup ui windows.
*
* 32 10/16/98 5:54p Samir
* to prevent infinite loops, error check.
*
* 31 10/16/98 5:53p Samir
* redid add and remove window mechanisms.
*
* 30 10/13/98 8:23p Samir
* added more keyboard input options.
*
* 29 10/13/98 3:47p Samir
* added function to check if a cursor is visible
*
* 28 10/06/98 7:31p Samir
* changed behavior of mouse b1_count calculation.
*
* 27 10/02/98 5:47p Samir
* changed behavior of mouse cursor.
*
* 26 9/30/98 4:32p Samir
* added frametime value.
*
* 25 9/27/98 3:52p Samir
* made mouse input delay 25 fps.
*
* 24 9/01/98 4:12p Samir
* added screenshot caparbilities.
*
* 23 8/31/98 4:08p Samir
* don't reset mouse position when flushing ui.
*
* 22 8/24/98 3:13p Samir
* fixed swimming of mouse.
*
* 21 6/30/98 6:13p Samir
* call ddio_MouseReset on ui_SetScreenMode.
*
* 20 6/29/98 6:44p Samir
* redid ui input polling.
*
* 19 6/05/98 5:35p Samir
* massive improvement in UI keyboard interface.
*
* 18 5/06/98 4:34p Samir
* added function to set ui screen resolution.
*
* 17 5/05/98 5:15p Samir
* ui system now takes a width and height too
*
* 16 4/10/98 7:51p Samir
* added ui_Flush
*
* 15 3/24/98 4:27p Samir
* To check key states in ui_Poll, calls ddio_GetAdjKeyValue.
*
* 14 3/18/98 6:25p Samir
* Don't turn Z-buffering back on.
*
* 13 3/10/98 12:50p Samir
* Made UI_screen_width & height accessable.
*
* 12 2/18/98 2:39p Samir
* fixed mouse cursor clipping.
*
* 11 2/17/98 2:22p Jason
* fixed some potential z sorting problems
*
* 10 2/13/98 6:36p Samir
* Added ability to prevent input polling and mouse input.
*
* 9 2/11/98 5:55p Samir
* Hide and Show cursor aren't cumulative now.
*
* 8 2/02/98 7:35p Samir
* Added show/hide cursor functions.
*
* 7 1/30/98 7:07p Samir
* Cleaned up Window handling code.
*
* 6 1/18/98 4:22p Samir
* Changed some UI initialization stuff.
*
* 5 1/16/98 1:27p Jeff
* Poll always at least once.
*
* 4 1/13/98 4:32p Samir
* Made tUIInput a shared structure to be used by ui_DoFrame to return UI
* input values.
*
* 3 1/08/98 12:17p Samir
* Added TitledWindow and modified the UI interface for polling.
*
* 2 1/02/98 12:48p Samir
* Better keyboard support and full mouse cursor support.
*
* 1 12/30/97 4:36p Samir
* Initial revision
*
* $NoKeywords: $
*/
#include "UIlib.h"
#include "application.h"
#include "bitmap.h"
#include "ddvid.h"
#include "renderer.h"
#include "Macros.h"
#define UI_MOUSE_HOTX 2
#define UI_MOUSE_HOTY 2
constexpr int ui_FrameTimeMS = 50;
//////////////////////////////////////////////////////////////////////////////
// VARIABLES
struct tUIWindowNode {
UIWindow *wnd;
struct tUIWindowNode *next;
struct tUIWindowNode *prev;
};
// user input structure
tUIInput UI_input;
// user output structure
tUIOutput UI_output;
int UI_screen_width, UI_screen_height;
float UI_aspect_x = 1.0f;
float UI_aspect_y = 1.0f;
// application object
static bool UI_init = false;
static oeApplication *UI_app = NULL;
static int UI_cursor_bm = -1;
static tUIWindowNode *UIWindowList = NULL;
static tUIWindowNode *UIWindowTail = NULL;
static UIWindow *UIWindowFocus = NULL;
static int UI_cursor_show = 0;
float UIFrameTime = 0.0f;
bool ui_MousePoll(bool buttons);
bool ui_KeyPoll();
void ui_UpdateWindows();
//////////////////////////////////////////////////////////////////////////////
// FUNCTIONS
// call this function to initialize the UI system. pass a surface where all UI will occur
void ui_Init(oeApplication *app, tUIInitInfo *init_info) {
// initialize variables
if (UI_app) {
ui_Close();
}
// clear out input poller.
ui_Flush();
UI_cursor_bm = -1;
UI_cursor_show = 0;
UI_app = app;
UIWindowList = NULL;
UIWindowTail = NULL;
UITextItem::SetDefaultFont(init_info->window_font);
ui_SetScreenMode(init_info->w, init_info->h);
UI_init = true;
}
// frees ui input cache
void ui_Flush() {
// UI_input.mx = 0;
// UI_input.last_mx = 0;
// UI_input.my = 0;
// UI_input.last_my = 0;
UI_input.b1_status = 0;
UI_input.b1_last_status = 0;
UI_input.b1_count = 0;
UIFrameTime = 0.0f;
UI_input.cur_time = timer_GetTime();
ui_KeyFlush();
}
// flushes out key input info currently in ui.
void ui_KeyFlush() {
UI_input.key = 0;
UI_input.key_first_press = false;
UI_input.key_status = 0;
UI_input.last_key = 0;
UI_input.last_key_status = 0;
UI_input.printscreen = false;
ddio_KeyFlush();
}
// closes UI system. do this when setting a new surface.
void ui_Close() {
if (UI_cursor_bm > -1) {
bm_FreeBitmap(UI_cursor_bm);
UI_cursor_bm = -1;
}
// delete window node list.
ui_RemoveAllWindows();
UI_app = NULL;
UI_init = false;
}
// retrieves input for user interface
// mouse
// keyboard
// possibly joystick?
bool ui_MousePoll(bool buttons) {
int mx, my;
static int btn_mask = 0;
int msebtn;
bool state;
if (!buttons) {
// get all input, mouse maintains persistent button info. key doesn't.
btn_mask = ddio_MouseGetState(&mx, &my, NULL, NULL);
UI_input.last_mx = UI_input.mx;
UI_input.last_my = UI_input.my;
UI_input.mx = mx;
UI_input.my = my;
} else if (UI_cursor_show) {
// if bX_count is 0, then repeat processing can occur, otherwise only real mouse events are processed.
if (ddio_MouseGetEvent(&msebtn, &state)) {
// mprintf(2, "mouse #%d state %d at %04d %04d\n", msebtn, UI_input.b1_status, UI_input.mx, UI_input.my);
if (msebtn == 0) {
UI_input.b1_last_status = UI_input.b1_status;
UI_input.b1_status = state ? UIMSEBTN_PRESSED : UIMSEBTN_RELEASED;
UI_input.b1_count = 1;
// mprintf(0, "M");
return true;
} else {
UI_input.b1_status = 0;
}
} else {
if (UI_input.b1_count == 0) {
// this frame, there was no down or up event, so now we can check if we should
// report button 1's state.
UI_input.b1_last_status = UI_input.b1_status;
UI_input.b1_count = 1;
if (CHECK_FLAG(btn_mask, MOUSE_LB)) {
// process repeat key events.
UI_input.b1_status = UIMSEBTN_PRESSED;
// mprintf(0, "m");
return true;
}
UI_input.b1_status = 0;
}
}
}
return false;
}
bool ui_KeyPoll() {
int key;
key = ddio_KeyInKey();
if (key == KEY_PRINT_SCREEN)
UI_input.printscreen = true;
UI_input.last_key = UI_input.key;
UI_input.last_key_status = UI_input.key_status;
// messy hack for pause key
if ((key & 0x00ff) == KEY_PAUSE) {
key = key & 0xff;
}
UI_input.key = key;
if (UI_input.key) {
if (ddio_GetAdjKeyState(UI_input.key)) {
UI_input.key_status = UIKEY_PRESSED;
UI_input.key_first_press = true;
} else {
UI_input.key_status = UIKEY_CLICKED;
UI_input.key_first_press = true;
}
} else if (UI_input.last_key && UI_input.last_key_status == UIKEY_PRESSED) {
if (ddio_GetAdjKeyState(UI_input.last_key)) {
UI_input.key = UI_input.last_key;
UI_input.key_status = UIKEY_PRESSED;
UI_input.key_first_press = false;
} else {
UI_input.key_status = UIKEY_RELEASED;
UI_input.key = UI_input.last_key;
UI_input.key_first_press = false;
}
} else {
UI_input.key_status = 0;
UI_input.key_first_press = false;
}
// mprintf(0, "key=%d status=%d.\n", UI_input.key, UI_input.key_status);
return UI_input.key ? true : false;
}
// ability to load/use mouse cursors
void ui_UseCursor(const char *fname) {
if (UI_cursor_bm > -1)
bm_FreeBitmap(UI_cursor_bm);
UI_cursor_bm = bm_AllocLoadFileBitmap(fname, 0);
ASSERT(UI_cursor_bm > -1);
}
// adds a window to the ui list.
void ui_AddWindow(UIWindow *wnd) {
tUIWindowNode *newnode = new tUIWindowNode;
if (!UIWindowList) {
// first item to add to list.
UIWindowList = newnode;
newnode->prev = NULL;
} else {
// add to existing list.
tUIWindowNode *node = UIWindowList;
while (node->next) {
node = node->next;
}
node->next = newnode;
newnode->prev = node;
}
newnode->next = NULL;
newnode->wnd = wnd;
UIWindowTail = newnode;
}
// removes a window from ui list.if topmost, the next topmost window has focus.
void ui_RemoveAllWindows() {
int count = 0;
while (UIWindowTail) {
ui_RemoveWindow(UIWindowTail->wnd);
count++;
ASSERT(count < 50);
}
if (count) {
mprintf(0, "%d UI windows left open. Closed them!\n", count);
}
}
// removes a window from ui list.
void ui_RemoveWindow(UIWindow *wnd) {
tUIWindowNode *node = UIWindowList;
bool found_wnd = false;
while (node) {
if (node->wnd == wnd) {
// we found a node.
if (node->prev) {
node->prev->next = node->next;
}
if (node->next) {
node->next->prev = node->prev;
}
if (node == UIWindowList) {
UIWindowList = NULL;
UIWindowList = node->next;
}
if (node == UIWindowTail) {
UIWindowTail = NULL;
UIWindowTail = node->prev;
}
delete node;
found_wnd = true;
break;
}
node = node->next;
}
ASSERT(found_wnd);
}
// Procedure to find which window should gain focus in the
// first check what window contains mouse pointer and save that window
// check if keystroke,
// then adjust focus and save as current focus
// else
// set current focus = saved mouse focus.
void ui_DoWindowFocus() {
// right now, the topmost window always has focus!
tUIWindowNode *wndnode = UIWindowTail;
if (wndnode)
UIWindowFocus = wndnode->wnd;
else
UIWindowFocus = NULL;
}
// returns a result and processes input of a window in focus
int ui_ProcessFocusedWindow() {
int res = -1;
int res2;
// no input processing, just render frame
// process the window in focus.
// process at least once regardless of input (for user processes.)
// get keys. get mouse while real events (non ui manuipulated) are there.
ui_MousePoll(false);
if (UIWindowFocus) {
UI_input.b1_count = 0; // button one state reset.
ui_KeyPoll();
ui_MousePoll(true);
do {
res2 = UIWindowFocus->Process();
if (res2 != -1)
res = res2;
} while (ui_MousePoll(true));
}
return res;
}
// updates all visible windows.
void ui_UpdateWindows() {
tUIWindowNode *wndnode = UIWindowList;
while (wndnode) {
if (wndnode->next == NULL) {
}
wndnode->wnd->Render();
wndnode = wndnode->next;
}
}
// do cursor display and update
void ui_DoCursor() {
ui_StartDraw(0, 0, UI_screen_width, UI_screen_height);
rend_SetOverlayType(OT_NONE);
rend_SetLighting(LS_NONE);
rend_SetColorModel(CM_MONO);
rend_SetZBufferState(0);
rend_SetAlphaType(AT_CONSTANT + AT_TEXTURE);
rend_SetAlphaValue(255);
// This function needs to get called do if there are no windows, stuff gets drawn using
// the ui_StartDraw and ui_EndDraw
if (UI_cursor_show && UI_cursor_bm > -1) {
ui_MousePoll(false); // DAJ
float u0 = 0.0f, v0 = 0.0f, u1 = 1.0f, v1 = 1.0f;
int cur_w = bm_w(UI_cursor_bm, 0);
int cur_h = bm_h(UI_cursor_bm, 0);
if (UI_input.mx > (UI_screen_width - cur_w))
u1 = ((float)(UI_screen_width - UI_input.mx)) / ((float)cur_w);
if (UI_input.my > (UI_screen_height - cur_h))
v1 = ((float)(UI_screen_height - UI_input.my)) / ((float)cur_h);
if (UI_input.mx < UI_MOUSE_HOTX)
u0 = (float)(UI_MOUSE_HOTX - UI_input.mx) / (float)cur_w;
if (UI_input.my < UI_MOUSE_HOTY)
v0 = (float)(UI_MOUSE_HOTY - UI_input.my) / (float)cur_h;
// mprintf_at(1,5,30,"mx=%d my=%d ", UI_input.mx,UI_input.my);
rend_DrawScaledBitmap(UI_input.mx - UI_MOUSE_HOTX, UI_input.my - UI_MOUSE_HOTY,
UI_input.mx + (int)((float)cur_w * u1) - UI_MOUSE_HOTX,
UI_input.my + (int)((float)cur_h * v1) - UI_MOUSE_HOTY, UI_cursor_bm, u0, v0, u1, v1);
}
ui_EndDraw();
}
// ui_DoFrame
// polls input
// does a ui frame given a list of windows
int ui_DoFrame(bool input) {
int res = -1;
if (!UI_init)
return res;
// reset this flag here, so that print screen is valid only until next call to ui_DoFrame
if (input)
UI_input.printscreen = false;
if (input) {
ui_DoWindowFocus(); // determine window with current input focus.
res = ui_ProcessFocusedWindow(); // process focused window
const float elapsedTimeS = timer_GetTime() - UI_input.cur_time;
const int waitTimeMS = ui_FrameTimeMS - static_cast<int>(elapsedTimeS * 1000.0f);
D3::ChronoTimer::SleepMS(waitTimeMS);
float temp_time = timer_GetTime();
UIFrameTime = temp_time - UI_input.cur_time;
UI_input.cur_time = temp_time;
}
ui_UpdateWindows();
ui_DoCursor(); // update mouse cursor position
return res;
}
// does a ui frame and gets mouse and key information.
int ui_DoFrame(tUIInput *input, bool doinput) {
int res;
res = ui_DoFrame(doinput);
*input = UI_input;
return res;
}
// hide and show cursor. effects are cumulative
bool ui_ShowCursor() {
if (!UI_cursor_show) {
UI_cursor_show = 1;
return false;
}
return true;
}
bool ui_HideCursor() {
if (UI_cursor_show) {
UI_cursor_show = 0;
return false;
}
return true; // cursor was hidden before.
}
// set user interface screen resolution
void ui_SetScreenMode(int w, int h) {
int old_w = UI_screen_width;
int old_h = UI_screen_height;
UI_screen_width = w;
UI_screen_height = h;
UI_aspect_x = (float)w / (float)FIXED_SCREEN_WIDTH;
UI_aspect_y = (float)h / (float)FIXED_SCREEN_HEIGHT;
ddio_MouseReset();
// ddio_MouseSetLimits(0,0, UI_screen_width, UI_screen_height);
ddio_MouseSetVCoords(UI_screen_width, UI_screen_height);
UI_input.cur_time = timer_GetTime();
// reposition all active open windows to their correct locations in this new resolution
//@@ tUIWindowNode *wndnode = UIWindowList;
//@@ float scalar_x = -1.0f + (float)w/(float)old_w;
//@@ float scalar_y = -1.0f + (float)h/(float)old_h;
//@@
//@@ while (wndnode)
//@@ {
//@@ wndnode->wnd->Move(wndnode->wnd->X() + (int)(scalar_x*old_w), wndnode->wnd->Y() + (int)(scalar_y*old_h),
//@@ wndnode->wnd->W(), wndnode->wnd->H());
//@@ wndnode = wndnode->next;
//@@ }
}
bool ui_IsCursorVisible() { return UI_cursor_show ? true : false; }