/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "io/timer.h" #include "ui/ui.h" #include "ui/uidefs.h" // --------------------------------------------------------------------------------------- // input: // do_repeat => property of button, set to 1 to allow pressed events if mouse // pointer is held over button with left mouse button down, // otherwise 0 (useful for buttons that scroll items) // ignore_focus => whether to allow Enter/Spacebar to affect pressed state when // control has focus // void UI_BUTTON::create(UI_WINDOW *wnd, const char *_text, int _x, int _y, int _w, int _h, int do_repeat, int ignore_focus) { text = NULL; if (_text) { if ( _text[0] != '\0' ) { text = vm_strdup(_text); } } // register gadget with UI window base_create( wnd, UI_KIND_BUTTON, _x, _y, _w, _h ); // initialize variables m_flags = 0; next_repeat = UI_TIMESTAMP::never(); m_just_highlighted_function = NULL; // assume there is no callback m_disabled_function = NULL; // ditto if (do_repeat) { m_flags |= BF_REPEATS; next_repeat = UI_TIMESTAMP::immediate(); } m_press_linger = UI_TIMESTAMP::immediate(); first_callback = 1; hotkey_if_focus = KEY_SPACEBAR; if (ignore_focus){ m_flags |= BF_IGNORE_FOCUS; } custom_cursor = NULL; previous_cursor = NULL; } void UI_BUTTON::destroy() { if (text) { vm_free(text); text = NULL; } UI_GADGET::destroy(); // call base as well } // sets a hotkey for button that works only when it had focus (or derived focus) void UI_BUTTON::set_hotkey_if_focus(int key) { hotkey_if_focus = key; } void UI_BUTTON::reset_status() { m_flags &= ~BF_HIGHLIGHTED; m_flags &= ~BF_HOTKEY_JUST_PRESSED; m_flags &= ~BF_DOWN; m_flags &= ~BF_DOUBLE_CLICKED; m_flags &= ~BF_JUST_HIGHLIGHTED; m_flags &= ~BF_CLICKED; } // reset anything that needs to be at the start of a new frame before processing void UI_BUTTON::frame_reset() { m_flags &= ~BF_HIGHLIGHTED; m_flags &= ~BF_HOTKEY_JUST_PRESSED; m_flags &= ~BF_DOWN; m_flags &= ~BF_JUST_PRESSED; m_flags &= ~BF_JUST_RELEASED; m_flags &= ~BF_CLICKED; m_flags &= ~BF_DOUBLE_CLICKED; m_flags &= ~BF_JUST_HIGHLIGHTED; restore_previous_cursor(); } // Force button to draw a specified frame void UI_BUTTON::draw_forced(int frame_num) { if (uses_bmaps) { if (bmap_ids[frame_num] >= 0) { gr_set_bitmap(bmap_ids[frame_num]); gr_bitmap(x, y, GR_RESIZE_MENU); // my_wnd->draw_tooltip(); // redraw any associated xstr my_wnd->draw_XSTR_forced(this, frame_num); } } } // Render button. How it draws exactly depends on it's current state. void UI_BUTTON::draw() { int offset, frame_num = -1; if (uses_bmaps) { gr_reset_clip(); // if button is down, draw it that way if (button_down()) { if (bmap_ids[B_PRESSED] >= 0){ frame_num = B_PRESSED; } // otherwise if button is disabled, draw it that way } else if (disabled_flag) { if (bmap_ids[B_DISABLED] >= 0){ frame_num = B_DISABLED; } // otherwise, if button is highlighted (mouse is over it, but mouse buttons not down) draw it that way } else if (m_flags & BF_HIGHLIGHTED) { if (bmap_ids[B_HIGHLIGHT] >= 0){ frame_num = B_HIGHLIGHT; } // otherwise, just draw it normally } else { if (bmap_ids[B_NORMAL] >= 0){ frame_num = B_NORMAL; } } if (frame_num >= 0) { gr_set_bitmap(bmap_ids[frame_num]); gr_bitmap(x, y, GR_RESIZE_MENU); } } else { font::set_font(my_wnd->f_id); gr_set_clip( x, y, w, h, GR_RESIZE_MENU ); // draw the button's box if (button_down()) { ui_draw_box_in( 0, 0, w-1, h-1 ); offset = 1; } else { ui_draw_box_out( 0, 0, w-1, h-1 ); offset = 0; } // now draw the button's text if (disabled_flag){ gr_set_color_fast(&CDARK_GRAY); } else if (my_wnd->selected_gadget == this){ gr_set_color_fast(&CBRIGHT_GREEN); } else { gr_set_color_fast(&CBLACK); } if (text){ ui_string_centered( Middle(w) + offset, Middle(h) + offset, text ); } gr_reset_clip(); } } // process() is called to process the button, which amounts to: // If mouse is over button, hilight it // If highlighted and mouse button down, flag button as down // If hotkey pressed, flag button as down // If hotkey_if_focus pressed, and button has focus, flag button as down // Set various BF_JUST_* flags if events changed from last frame // void UI_BUTTON::process(int focus) { int mouse_on_me, old_flags; old_flags = m_flags; frame_reset(); // check mouse over control and handle hilighting state mouse_on_me = is_mouse_on(); // if gadget is disabled, force button up and return if (disabled_flag) { if (old_flags & BF_DOWN){ m_flags |= BF_JUST_RELEASED; } if (!hidden && !my_wnd->use_hack_to_get_around_stupid_problem_flag) { if (mouse_on_me && B1_JUST_PRESSED){ gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ){ gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } // do callback if the button is disabled if (mouse_on_me && B1_JUST_PRESSED){ if (m_disabled_function != NULL) { m_disabled_function(); } } return; } // check focus and derived focus with one variable if (my_wnd->selected_gadget == this) { focus = 1; } // show alternate cursor, perhaps? maybe_show_custom_cursor(); if ( !mouse_on_me ) { next_repeat = UI_TIMESTAMP::never(); } else { m_flags |= BF_HIGHLIGHTED; if ( !(old_flags & BF_HIGHLIGHTED) ) { int do_callback = 1; m_flags |= BF_JUST_HIGHLIGHTED; // if a callback exists, call it if (m_just_highlighted_function) { if ( m_flags & BF_SKIP_FIRST_HIGHLIGHT_CALLBACK ) { if ( first_callback ) { do_callback = 0; } } first_callback = 0; if ( do_callback ) { m_just_highlighted_function(); } } } } // check if mouse is pressed if ( B1_PRESSED && mouse_on_me ) { m_flags |= BF_DOWN; capture_mouse(); } // check if hotkey is down or not if ( (hotkey >= 0) && (my_wnd->keypress == hotkey) ) { m_flags |= BF_DOWN | BF_CLICKED; } // only check for space/enter keystrokes if we are not ignoring the focus (this is the // default behavior) if ( !(m_flags & BF_IGNORE_FOCUS) ) { if ( focus && (hotkey_if_focus >= 0) ) { if (my_wnd->keypress == hotkey_if_focus) m_flags |= BF_DOWN | BF_CLICKED; if ( (hotkey_if_focus == KEY_SPACEBAR) && (my_wnd->keypress == KEY_ENTER) ) m_flags |= BF_DOWN | BF_CLICKED; } } // handler for button not down if ( !(m_flags & BF_DOWN) ) { next_repeat = UI_TIMESTAMP::never(); if ( (old_flags & BF_DOWN) && !(old_flags & BF_CLICKED) ) // check for release of mouse, not hotkey m_flags |= BF_JUST_RELEASED; // non-repeating buttons behave sort of uniquely.. They activate when released over button if (!(m_flags & BF_REPEATS)) { if ( (m_flags & BF_JUST_RELEASED) && (m_flags & BF_HIGHLIGHTED) ) m_flags |= BF_CLICKED; } return; } // check if button just went down this frame if ( !(old_flags & BF_DOWN) ) { m_flags |= BF_JUST_PRESSED; m_press_linger = ui_timestamp(100); if (user_function) user_function(); if (m_flags & BF_REPEATS) { next_repeat = ui_timestamp(B_REPEAT_TIME * 3); m_flags |= BF_CLICKED; } } // check if a repeat event should occur if ( ui_timestamp_elapsed(next_repeat) && (m_flags & BF_REPEATS) ) { next_repeat = ui_timestamp(B_REPEAT_TIME); m_flags |= BF_CLICKED; m_press_linger = ui_timestamp(100); } // check for double click occurance if (B1_DOUBLE_CLICKED && mouse_on_me) { m_flags |= BF_DOUBLE_CLICKED; m_press_linger = ui_timestamp(100); } } // Check if button should do it's function in life (trigger the event) // int UI_BUTTON::pressed() { if (m_flags & BF_CLICKED) return TRUE; return FALSE; } int UI_BUTTON::double_clicked() { if ( m_flags & BF_DOUBLE_CLICKED ) return TRUE; else return FALSE; } int UI_BUTTON::just_pressed() { if ( m_flags & BF_JUST_PRESSED ) return TRUE; else return FALSE; } int UI_BUTTON::just_highlighted() { if ( m_flags & BF_JUST_HIGHLIGHTED ) return TRUE; else return FALSE; } // ---------------------------------------------------------------------------------- // Checks if button is down (or up). This checks the button instead, rather than any // events that may have caused it to be down. Buttons also stay down for a certain amount // of time minimum, to make sure it's long enough for the user to see it has went down (since // one frame is probably far to quick for users to notice it). Basically, this indicates // how the button is being drawn, if you want to think of it that way. int UI_BUTTON::button_down() { if ( (m_flags & BF_DOWN) || !ui_timestamp_elapsed(m_press_linger) ) return TRUE; else return FALSE; } // ------------------------------------------------------------ // set the callback function for when the mouse first goes over // a button // void UI_BUTTON::set_highlight_action( void (*_user_function)(void) ) { m_just_highlighted_function = _user_function; } void UI_BUTTON::set_disabled_action( void (*_user_function)(void) ) { m_disabled_function = _user_function; } // Is the mouse over this button? int UI_BUTTON::button_hilighted() { return m_flags & BF_HIGHLIGHTED; } // Is the mouse over this button? void UI_BUTTON::set_button_hilighted() { m_flags |= BF_HIGHLIGHTED; } // Force button to get pressed void UI_BUTTON::press_button() { if ( !disabled_flag ) { m_flags |= BF_DOWN | BF_CLICKED; //m_flags |= BF_JUST_PRESSED; } } // reset the "pressed" timestamps void UI_BUTTON::reset_timestamps() { m_press_linger = UI_TIMESTAMP::immediate(); next_repeat = UI_TIMESTAMP::never(); } void UI_BUTTON::skip_first_highlight_callback() { m_flags |= BF_SKIP_FIRST_HIGHLIGHT_CALLBACK; first_callback = 1; } void UI_BUTTON::repeatable(int yes) { if(yes){ m_flags |= BF_REPEATS; next_repeat = UI_TIMESTAMP::immediate(); } else { m_flags &= ~(BF_REPEATS); next_repeat = UI_TIMESTAMP::never(); } } void UI_BUTTON::maybe_show_custom_cursor() { if (disabled_flag) return; // set the mouseover cursor if (is_mouse_on()) { if ((custom_cursor != NULL) && (previous_cursor == NULL)) { previous_cursor = io::mouse::CursorManager::get()->getCurrentCursor(); io::mouse::CursorManager::get()->setCurrentCursor(custom_cursor); } } } void UI_BUTTON::restore_previous_cursor() { if (previous_cursor != nullptr && !is_mouse_on()) { io::mouse::CursorManager::get()->setCurrentCursor(previous_cursor); previous_cursor = nullptr; } }