/* * 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 . --- HISTORICAL COMMENTS FOLLOW --- * $Logfile: /DescentIII/Main/ui/UIWindow.cpp $ * $Revision: 39 $ * $Date: 5/02/99 2:09a $ * $Author: Samir $ * * Interface code. * * $Log: /DescentIII/Main/ui/UIWindow.cpp $ * * 39 5/02/99 2:09a Samir * don't process input on disabled gadgets. * * 38 4/26/99 9:11p Matt * Took out saturation used by old newui. * * 37 4/22/99 4:28p Samir * made gadget clip check include the right and bottom boundaries of the * screen. * * 36 4/21/99 12:42p Samir * clip gadgets on screen. * * 35 4/14/99 1:54a Jeff * fixed case mismatched #includes * * 34 3/01/99 6:56p Samir * saturate text for old ui menus (UIF_PROCESS_ALL) * * 32 3/01/99 5:00a Samir * whenever m_GadgetCur is being NULLed, do a lose focus on it first. * * 31 2/26/99 2:10a Samir * added accelerator table. * * 30 2/22/99 8:13p Samir * added slave gadget system that only uses mouse input from window, but * defers keys to its master. * * 29 2/21/99 6:36p Samir * focusing changes and key input changes to ui., * * 28 2/17/99 8:32p Samir * fixed bugs in remove gadget, added callbacks for attaching and * detaching gadgets. * * 27 2/16/99 11:58a Samir * small fixes and added gadget notification. * * 26 10/19/98 12:18p Samir * made tabbing between edit controls possible. * * 25 10/13/98 8:23p Samir * communication between gadget and window and edit system to work * together. * * 24 10/06/98 7:31p Samir * both tab keys and arrow keys scroll through gadgets. * * 23 9/10/98 10:20a Samir * when removing a gadget from a window, check if it's being used by the * user intefrace, if it is then remove it from that. * * 22 8/31/98 12:36p Samir * implemented dialogversion of window. * * 21 8/27/98 2:52p Samir * changing window system to work in two modes. * * 20 8/26/98 12:25p Samir * fixed the whole editbox, keyboard access mess. * * 19 8/25/98 7:08p Samir * select how keys are sent to a gadget. * * 18 8/25/98 6:33p Kevin * PXO screens * * 17 8/25/98 5:27p Samir * new focusing rules for keyboard. * * 16 6/05/98 5:35p Samir * massive improvement in UI keyboard interface. * * 15 3/10/98 12:48p Samir * Added window centering ability. * * 14 3/09/98 2:57p Samir * Remove gadget from window called from gadget destroy. * * 13 3/05/98 6:38p Samir * Fixed key presses within windows. * * 12 2/15/98 7:07p Samir * Revamped way keys like up and down are handled in window system. * * 11 2/13/98 6:35p Samir * Fixed some gadget creation/destruction inconsistancies * * 10 2/02/98 7:36p Samir * Added UIObject::Destroy. * * 9 1/30/98 7:05p Samir * Each gadget now has their own coordinate system. * * 8 1/26/98 10:54a Samir * Fixed bug in UIWindow returning -2 always. * * 7 1/23/98 5:45p Samir * Call OnUserProcess after default Window Process. * * 6 1/18/98 4:23p Samir * Moved object destruction code to a virtual OnDestroy function for * gadgets. Must destroy window too. * * 5 1/13/98 4:26p Samir * Changed process of setting and getting focus. * * 4 1/08/98 12:17p Samir * Added TitledWindow and modified the UI interface for polling. * * 3 1/05/98 11:10a Jason * FROM SAMIR: Draw background rectangle for menus * * 2 1/02/98 12:48p Samir * Redid setting and releasing keyboard/mouse focus for gadgets. * * 1 12/30/97 4:36p Samir * Initial revision * * $NoKeywords: $ */ #include "UIlib.h" #include "Macros.h" // the window font for all windows int UIWindow::m_WindowFont = 0; // ---------------------------------------------------------------------------- // UIWindow class UIWindow::UIWindow() : UIObject() { m_GadgetHead = NULL; m_GadgetTail = NULL; m_GadgetCur = NULL; m_BackItem = NULL; m_HoldHotkeys = false; m_CurGadgetInGroup = false; m_ResetCurGadget = false; m_naccels = 0; } UIWindow::~UIWindow() { if (m_GadgetTail) { Destroy(); } } // create a window at the specified coords. void UIWindow::Create(int x, int y, int w, int h, int flags) { m_FontHandle = m_WindowFont; m_LockedGadget = NULL; m_BackItem = UIPrimativeItem(TRANSPARENT_COLOR32).CopyUIItem(); m_BackColor = GR_RGB(0, 0, 0); m_Flags = flags; m_GadgetTail = NULL; m_GadgetHead = NULL; m_LastKey = 0; m_LastKeyTime = 0.0f; m_HoldHotkeys = false; m_ResetCurGadget = false; m_CurGadgetInGroup = false; m_naccels = 0; if (m_Flags & UIF_CENTER) { x = UI_screen_width / 2 - w / 2; y = UI_screen_height / 2 - h / 2; } UIObject::Create(x, y, w, h); } void UIWindow::Destroy() { OnDestroy(); while (m_GadgetTail) { UIGadget *gadget = m_GadgetTail; gadget->Destroy(); // gadget must be destroyed AFTER it's removed from list. } m_GadgetTail = NULL; m_GadgetHead = NULL; SetBackItem(NULL); UIObject::Destroy(); } // interface functions // adds a UIObject to the interface's draw list. void UIWindow::AddGadget(UIGadget *gadget) { ASSERT(IsCreated()); ASSERT(gadget->IsCreated()); // perform a linked list add. if (!m_GadgetHead) { m_GadgetHead = gadget; m_GadgetTail = m_GadgetHead; } else { m_GadgetTail->m_Next = gadget; gadget->m_Prev = m_GadgetTail; gadget->m_Next = NULL; m_GadgetTail = gadget; } if (m_GadgetCur) { m_GadgetCur->LostFocus(); m_GadgetCur = NULL; } m_ResetCurGadget = true; m_CurGadgetInGroup = false; gadget->m_Wnd = this; gadget->OnAttachToWindow(); } // removes a UIObject from the draw list. void UIWindow::RemoveGadget(UIGadget *gadget) { if (!gadget) return; ASSERT(IsCreated()); ASSERT(gadget->IsCreated()); ASSERT(gadget->m_Wnd == this); gadget->OnDetachFromWindow(); // remove object from list. if (gadget->m_Prev) gadget->m_Prev->m_Next = gadget->m_Next; if (gadget->m_Next) gadget->m_Next->m_Prev = gadget->m_Prev; if (gadget == m_GadgetTail) m_GadgetTail = gadget->m_Prev; if (gadget == m_GadgetHead) m_GadgetHead = gadget->m_Next; if (gadget == m_GadgetCur) { m_GadgetCur->LostFocus(); m_GadgetCur = NULL; m_ResetCurGadget = true; m_CurGadgetInGroup = false; } if (gadget == m_LockedGadget) m_LockedGadget = NULL; // break link. gadget->m_Prev = NULL; gadget->m_Next = NULL; gadget->m_Wnd = NULL; } // forces a particular gadget into focus, always until unlocked. void UIWindow::LockFocusOnGadget(UIGadget *gadget) { m_LockedGadget = gadget; } void UIWindow::UnlockFocusOnGadget() { m_LockedGadget = NULL; } // forces all keyinput onto one gadget. void UIWindow::LockKeyFocusOnGadget(UIGadget *gadget) {} // Process // run this to handle user input inside an interface // sets focus on a control // m_GadgetCur is gadget in focus. int UIWindow::Process() { ASSERT(IsCreated()); bool no_process = false; // don't do input for disabled gadgets. if (m_ResetCurGadget) { // find first valid gadget to set focus to. UIGadget *start_gadget, *gadget; start_gadget = gadget = m_GadgetHead; if (gadget) { bool in_group = false; do { if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_START)) { in_group = true; } if (!gadget->IsDisabled() && !CHECK_FLAG(gadget->GetFlags(), UIF_SLAVE)) { if (m_GadgetCur) { m_GadgetCur->LostFocus(); } m_GadgetCur = gadget; m_GadgetCur->GainFocus(); m_CurGadgetInGroup = in_group; break; } if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_END)) { in_group = false; } gadget = (gadget->m_Next) ? gadget->m_Next : m_GadgetHead; } while (gadget != start_gadget); } m_ResetCurGadget = false; } // setup default output values UI_output.gadget = NULL; UI_output.id = -1; // we can do focus processing if no one gadget has locked the keyboard, mouse focus. if (!m_LockedGadget) { UIGadget *test_gadget = NULL; if (UI_input.b1_status == UIMSEBTN_PRESSED || UI_input.b1_status == UIMSEBTN_CLICKED) { // start from top most gadget down for mouse focus check. bool in_group = false; for (test_gadget = m_GadgetTail; test_gadget != NULL; test_gadget = test_gadget->m_Prev) { if (CHECK_FLAG(test_gadget->GetFlags(), UIF_GROUP_END)) { // going from end of list to start, so reverse in_group = true; } if (PT_IN_GADGET(this, test_gadget, UI_input.mx, UI_input.my)) { if (!CHECK_FLAG(test_gadget->GetFlags(), UIF_SLAVE)) { if (!test_gadget->IsDisabled()) { break; } else { test_gadget = NULL; no_process = true; break; } } } if (CHECK_FLAG(test_gadget->GetFlags(), UIF_GROUP_START)) { in_group = false; } } // switch focus from old gadget to new. if (test_gadget) { if (m_GadgetCur && m_GadgetCur != test_gadget) { m_GadgetCur->LostFocus(); m_GadgetCur = NULL; } if (!m_GadgetCur) { m_GadgetCur = test_gadget; m_GadgetCur->GainFocus(); m_CurGadgetInGroup = in_group; } test_gadget->CheckFocusOnSlaves(UI_input.mx, UI_input.my); } } else { // check for keyboard switching. if (UI_input.key_status == UIKEY_PRESSED) { if (TrapKey(UI_input.key)) { OnKeyDown(UI_input.key); } } else if (UI_input.key_status == UIKEY_RELEASED) { UntrapKey(UI_input.key); OnKeyUp(UI_input.key); } else if (UI_input.key_status == UIKEY_CLICKED) { if (TrapKey(UI_input.key)) { OnKeyDown(UI_input.key); } UntrapKey(UI_input.key); OnKeyUp(UI_input.key); } } } else { // always assert that current gadget is locked gadget. // if not, then m_CurGadgetInGroup can be wrong. // ASSERT(m_GadgetCur == m_LockedGadget); // m_GadgetCur = m_LockedGadget; } // process input for current gadget in focus. if (m_LockedGadget) { m_LockedGadget->Process(true); } else if (m_GadgetCur && !m_GadgetCur->IsDisabled()) { // check what slave gadget a mouse event occurs inside m_GadgetCur if (!no_process) { if (m_GadgetCur->m_CurrentSlave) { m_GadgetCur->m_CurrentSlave->Process(false, true); } m_GadgetCur->Process(true, m_GadgetCur->m_CurrentSlave ? false : true); } } // perform user processing of window input. OnUserProcess(); return UI_output.id; } // overridable: runs after window is processed, to supplement it. void UIWindow::OnUserProcess() {} // behavior when key is pressed. void UIWindow::OnKeyDown(int key) { UIGadget *gadget, *start_gadget; bool in_group; // advance down gadget list. if (key == KEY_TAB) { if (!m_GadgetCur) { m_ResetCurGadget = true; return; } start_gadget = gadget = m_GadgetCur; in_group = m_CurGadgetInGroup; do { if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_START)) { in_group = true; } if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_END)) { in_group = false; } gadget = (gadget->m_Next) ? gadget->m_Next : m_GadgetHead; if (!gadget->IsDisabled() && !CHECK_FLAG(gadget->GetFlags(), UIF_SLAVE) && !in_group) { start_gadget->LostFocus(); gadget->GainFocus(); m_CurGadgetInGroup = (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_START) > 0); m_GadgetCur = gadget; break; } } while (gadget != start_gadget); } if (key == (KEY_SHIFTED + KEY_TAB)) { if (!m_GadgetCur) { m_ResetCurGadget = true; return; } start_gadget = gadget = m_GadgetCur; in_group = m_CurGadgetInGroup; do { gadget = (gadget->m_Prev) ? gadget->m_Prev : m_GadgetTail; if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_END)) { in_group = true; } if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_START)) { in_group = false; } if (!gadget->IsDisabled() && !CHECK_FLAG(gadget->GetFlags(), UIF_SLAVE) && !in_group) { start_gadget->LostFocus(); gadget->GainFocus(); m_CurGadgetInGroup = (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_END)) || (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_START)); m_GadgetCur = gadget; break; } } while (gadget != start_gadget); } // handle direction keys only in a group if (m_CurGadgetInGroup) { if (key == KEY_UP || key == KEY_LEFT) { if (!m_GadgetCur) { m_ResetCurGadget = true; return; } start_gadget = gadget = m_GadgetCur; do { if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_START)) { break; } gadget = (gadget->m_Prev) ? gadget->m_Prev : m_GadgetTail; if (!gadget->IsDisabled() && !CHECK_FLAG(gadget->GetFlags(), UIF_SLAVE)) { start_gadget->LostFocus(); gadget->GainFocus(); m_GadgetCur = gadget; break; } } while (start_gadget != gadget); } if (key == KEY_DOWN || key == KEY_RIGHT) { if (!m_GadgetCur) { m_ResetCurGadget = true; return; } start_gadget = gadget = m_GadgetCur; do { if (CHECK_FLAG(gadget->GetFlags(), UIF_GROUP_END)) { break; } gadget = (gadget->m_Next) ? gadget->m_Next : m_GadgetHead; if (!gadget->IsDisabled() && !CHECK_FLAG(gadget->GetFlags(), UIF_SLAVE)) { start_gadget->LostFocus(); gadget->GainFocus(); m_GadgetCur = gadget; break; } } while (start_gadget != gadget); } } // determine if any hotkey was pressed if (!m_HoldHotkeys) { int i; for (gadget = m_GadgetHead; gadget != NULL; gadget = gadget->m_Next) { if (!gadget->IsDisabled()) { if (gadget->GetID() == UID_CANCEL && key == KEY_ESC) gadget->OnSelect(); } } // do hotkeys for (gadget = m_GadgetHead; gadget != NULL; gadget = gadget->m_Next) { if (!gadget->IsDisabled() && UI_input.key && ISKEYPRESSED(gadget->m_Hotkey)) gadget->OnSelect(); } // do window accelerators. for (i = 0; i < m_naccels; i++) { if (UI_input.key && ISKEYPRESSED(m_Accelerators[i].key) && UI_output.id < 0) { UI_output.gadget = NULL; UI_output.id = m_Accelerators[i].id; break; } } } } // behavior when key is released. void UIWindow::OnKeyUp(int key) {} // redraw all gadgets void UIWindow::Render() { UIGadget *gadget = m_GadgetHead; // draw background of window. ui_StartDraw(m_X, m_Y, m_X + m_W, m_Y + m_H); ui_DrawSetFont(m_FontHandle); OnDraw(); ui_EndDraw(); while (gadget) { int l = m_X + gadget->X(); int t = m_Y + gadget->Y(); int r = l + gadget->W(); int b = t + gadget->H(); // reformat gadget gadget->OnFormat(); // draw gadget if (l >= 0 && r <= UI_screen_width && t >= 0 && b <= UI_screen_height) { ui_StartDraw(l, t, r, b); ui_DrawSetFont(m_FontHandle); gadget->OnDraw(); ui_EndDraw(); } else { mprintf(0, "WINDOW: Gadget clipped on screen border (%d,%d,%d,%d)\n", l, t, r, b); // Int3(); } // proeceed... gadget = gadget->m_Next; } UITextItem::SetSaturationFactor(0); // draw ugly looking focus box around current gadget. #if 0 if (m_GadgetCur && !CHECK_FLAG(m_GadgetCur->m_Flags, UIF_SLAVE) ) { int l = m_X+m_GadgetCur->X(); int t = m_Y+m_GadgetCur->Y(); int r = l+m_GadgetCur->W(); int b = t+m_GadgetCur->H(); l--; t--; r++; b++; if (l < 0) l = 0; if (t < 0) t = 0; if (r >(m_X+m_W)) r = m_X+m_W; if (b >(m_Y+m_H)) b = m_Y+m_H; ui_StartDraw(l,t,r,b); ui_DrawBox(GR_RGB(128,128,128),0,0,(r-l),(b-t)); ui_EndDraw(); } #endif } // adds window to ui list. void UIWindow::Open() { ui_AddWindow(this); } // removes window from ui list. void UIWindow::Close() { ui_RemoveWindow(this); } // settings void UIWindow::SetFont(int handle) { m_FontHandle = handle; } // overridable draws the window background before gadgets void UIWindow::OnDraw() { if (m_BackItem) m_BackItem->draw(0, 0, m_W, m_H); } // used to manage keypresses so repeating isn't quick. bool UIWindow::TrapKey(int key) { float time; time = timer_GetTime(); // take care of multiple keypresses and key down repeats. handle timing // so it feels 'natural' if (m_LastKey && m_LastKey == key) { if (m_LastKeyCount == 1) { if ((time - m_LastKeyTime) < KEYDOWN_FIRST_DELAY) return false; m_LastKeyCount++; } else if ((time - m_LastKeyTime) < KEYDOWN_REPEAT_DELAY) { return false; } m_LastKeyCount++; } else { m_LastKeyCount = 1; } m_LastKey = key; m_LastKeyTime = time; return true; } void UIWindow::UntrapKey(int key) { // if the key previously pressed went up, then // turn off key repeat management. if (key == m_LastKey) { m_LastKeyCount = 0; m_LastKey = 0; } } void UIWindow::SetFocusOnGadget(UIGadget *gadget, bool key) { if (m_GadgetCur) { m_GadgetCur->LostFocus(); } m_GadgetCur = gadget; if (m_GadgetCur) { m_GadgetCur->GainFocus(); m_ResetCurGadget = false; } } // used to mark key pressed to id numbers void UIWindow::AddAcceleratorKey(int key, int id) { if (m_naccels == N_WINDOW_ACCELS) { // window has asked for too many accelerators. will not add this one. it's okay to continue, but tell someone Int3(); return; } m_Accelerators[m_naccels].id = id; m_Accelerators[m_naccels].key = key; m_naccels++; } void UIWindow::ResetAcceleratorKey() { m_naccels = 0; } // ---------------------------------------------------------------------------- // UITitledWindow class UITitledWindow::UITitledWindow() {} UITitledWindow::~UITitledWindow() {} void UITitledWindow::Create(UITextItem &title, int x, int y, int w, int h) { m_Title = title; m_BorderThickness = 2; m_CaptionColor = GR_DARKGRAY; UIWindow::Create(x, y, w, h); UIPrimativeItem item{GR_LIGHTGRAY}; UIWindow::SetBackItem(&item); } // inheritable actions // draws the window background before gadgets void UITitledWindow::OnDraw() { UIWindow::OnDraw(); int m_CaptionHeight; // caption height // set font for title. if (m_Title.get_font() > -1) ui_DrawSetFont(m_Title.get_font()); m_CaptionHeight = ui_GetFontHeight() + ui_GetFontHeight() / 2; // draw border. for (int i = 0; i < m_BorderThickness; i++) ui_DrawBox(GR_RGB(0, 0, 0), m_X + i, m_Y + i, m_X + m_W - i, m_Y + m_H - i); // draw caption. int x, y; ui_DrawRect(m_CaptionColor, m_BorderThickness, m_BorderThickness, m_W - m_BorderThickness, m_CaptionHeight); x = m_W / 2 - ui_GetTextWidth(m_Title) / 2; y = m_BorderThickness + m_CaptionHeight / 2 - ui_GetFontHeight() / 2; ui_SetCharAlpha(m_Title.get_alpha()); ui_DrawString(m_Title.get_color(), x, y, m_Title); }