Files
Taylor Richards 039880491a port to SDL3 (#7220)
Basic port from SDL2 to SDL3. Also updates imgui to 1.92.5.
2026-07-23 15:52:27 +02:00

629 lines
13 KiB
C++

/*
* 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 "anim/animplay.h"
#include "bmpman/bmpman.h"
#include "ui/ui.h"
#include "ui/uidefs.h"
// constructor
UI_GADGET::UI_GADGET()
{
bm_filename = NULL;
}
// destructor
UI_GADGET::~UI_GADGET()
{
}
int UI_GADGET::get_hotspot()
{
if (!linked_to_hotspot)
return -1;
return hotspot_num;
}
void UI_GADGET::reset()
{
m_flags = 0;
}
int UI_GADGET::is_hidden()
{
return hidden;
}
// --------------------------------------------------------------------
// Links a hotspot (palette index in mask) to the given gadget.
//
void UI_GADGET::link_hotspot(int num)
{
hotspot_num = num;
linked_to_hotspot = 1;
}
// --------------------------------------------------------------------
// Extract MAX_BMAPS_PER_CONTROL bitmaps for the different states of the control.
//
// The bitmap ids are stored in the bmap_ids[] array. If you don't want to store
// from the zero index, fill in the offset parameter. offset is a default parameter
// with a default value of zero.
//
// NOTE: The bitmaps stored in a .ani file. What each frame is used for
// is left up to the component to decide.
//
// loads nframes bitmaps, starting at index start_frame.
// anything < start_frame will not be loaded.
// this keeps the loading code from trying to load bitmaps which don't exist
// and taking an unnecessary disk hit.
int UI_GADGET::set_bmaps(const char *ani_fname, int nframes, int start_frame)
{
int first_frame, i;
char full_name[MAX_FILENAME_LEN] = "";
char tmp[33];
int idx, s_idx;
int num_digits;
int its_all_good = 0;
// clear out all frames
for(idx=0; idx<MAX_BMAPS_PER_GADGET; idx++){
bmap_ids[idx] = -1;
}
// load all the bitmaps
bm_filename = ani_fname;
Assertion(nframes < MAX_BMAPS_PER_GADGET, "Too many frames specified (%d), must be less than MAX_BMAPS_PER_GADGET", nframes);
m_num_frames = nframes;
for(idx=start_frame; idx<nframes; idx++){
// clear the string
strcpy_s(full_name, "");
// get the # of digits for this index
num_digits = (idx < 10) ? 1 : (idx < 100) ? 2 : (idx < 1000) ? 3 : 4;
// build the actual filename
strcpy_s(full_name, ani_fname);
for(s_idx=0; s_idx<(4-num_digits); s_idx++){
strcat_s(full_name, NOX("0"));
}
sprintf(tmp, "%d", idx);
strcat_s(full_name, tmp);
// try and load the bitmap
bmap_ids[idx] = bm_load(full_name);
if(bmap_ids[idx] != -1){
its_all_good = 1;
}
}
// done
if(its_all_good){
uses_bmaps = 1;
return 0;
}
// no go, so try and load as an ani. try and load as an .ani
first_frame = bm_load_animation(ani_fname, &m_num_frames);
if((first_frame >= 0) && (m_num_frames <= MAX_BMAPS_PER_GADGET)){
// seems pretty stupid that we didn't just use a variable for the first frame and access all
// other frames offset from it instead of accessing this bmap_ids[] array, but probably too
// much trouble to go through and change this anymore. How sad..
for ( i=0; i<m_num_frames; i++ ) {
bmap_ids[i] = first_frame + i;
}
}
// flag that this control is using bitmaps for art
uses_bmaps = 1;
return 0;
}
// Handle drawing of all children of the gadget. Since this the base of all other gadgets,
// it doesn't have any look to it (kind of like a soul. Can you see someone's soul?) and thus
// doesn't actually render itself.
//
void UI_GADGET::draw()
{
UI_GADGET *cur;
// if hidden, hide children as well
if (hidden){
return;
}
if (children) {
cur = children;
do {
cur->draw();
cur = cur->next;
} while (cur != children);
}
}
// Free up bitmaps used by the gadget, and call children to destroy themselves as well.
//
void UI_GADGET::destroy()
{
int i;
UI_GADGET *cur;
for ( i=0; i<m_num_frames; i++ ) {
if (bmap_ids[i] != -1) {
// we need to unload here rather than release since some controls
// may still need access to the bitmap slot. if it can be released
// then the child should do it - taylor
bm_unload(bmap_ids[i]);
bmap_ids[i] = -1;
}
}
if (children) {
cur = children;
do {
cur->destroy();
cur = cur->next;
} while (cur != children);
}
}
// Use this if you need to change the size and position of a gadget after you have created it.
//
void UI_GADGET::update_dimensions(int _x, int _y, int _w, int _h)
{
if ( _x != -1 ) x = _x;
if ( _y != -1 ) y = _y;
if ( _w != -1 ) w = _w;
if ( _h != -1 ) h = _h;
}
void UI_GADGET::get_dimensions(int *x_, int *y_, int *w_, int *h_)
{
*x_ = x;
*y_ = y;
*w_ = w;
*h_ = h;
}
// Hide (or show) a gadget.
// n != 0: Hide gadget
// n == 0: Show gadget
//
void UI_GADGET::hide(int n)
{
hidden = n ? 1 : 0;
}
void UI_GADGET::hide()
{
hidden = 1;
}
void UI_GADGET::unhide()
{
hidden = 0;
}
// Capture mouse with this gadget, which basically means only this gadget will get process()
// called on it until the mouse button 1 is released.
//
void UI_GADGET::capture_mouse()
{
my_wnd->capture_mouse(this);
}
// Check if (return true if):
// mouse_captured(): this gadget has the mouse captured.
// mouse_captured(x): gadget x has the mouse captured.
//
int UI_GADGET::mouse_captured(UI_GADGET *gadget)
{
if (!gadget)
gadget = this;
return (my_wnd->mouse_captured_gadget == gadget);
}
// Set up the gadget and registers it with the UI window
//
void UI_GADGET::base_create(UI_WINDOW *wnd, int _kind, int _x, int _y, int _w, int _h)
{
int i;
// initialize data with passed values
kind = _kind;
x = _x;
y = _y;
w = _w;
h = _h;
// set up reference to UI window and initialize as having no family
my_wnd = wnd;
parent = NULL;
children = NULL;
next = prev = this;
// this actually links the gadget into the UI window's top level family (as the youngest gadget)
set_parent(NULL);
// initialize variables
hotkey = -1;
user_function = NULL;
disabled_flag = 0;
base_dragging = 0;
base_drag_x = base_drag_y = 0;
hotspot_num = -1;
hidden = 0;
linked_to_hotspot = 0;
uses_bmaps = 0;
m_num_frames = 0;
for ( i=0; i<MAX_BMAPS_PER_GADGET; i++ ) {
bmap_ids[i] = -1;
}
}
void UI_GADGET::set_hotkey(int key)
{
hotkey = key;
}
// Far as I can tell, the callback function is handled differently for each gadget type, if
// handled by a given gadget type at all.
//
void UI_GADGET::set_callback(void (*_user_function)(void))
{
user_function = _user_function;
}
// get the next enabled gadget in sibling list or self if none
//
UI_GADGET *UI_GADGET::get_next()
{
UI_GADGET *tmp;
tmp = next;
while ((tmp != this) && tmp->disabled_flag)
tmp = tmp->next;
return tmp;
}
// get the previous enabled gadget in sibling list or self if none
//
UI_GADGET *UI_GADGET::get_prev()
{
UI_GADGET *tmp;
tmp = prev;
while ((tmp != this) && tmp->disabled_flag)
tmp = tmp->prev;
return tmp;
}
// Set this gadget as the focus (selected gadget) of the UI window
//
void UI_GADGET::set_focus()
{
my_wnd->selected_gadget = this;
if (kind == UI_KIND_INPUTBOX) {
SDL_StartTextInput(os::getSDLMainWindow());
}
}
// Make no gadget have focus in the UI window.
//
void UI_GADGET::clear_focus()
{
my_wnd->selected_gadget = NULL;
if (kind == UI_KIND_INPUTBOX) {
SDL_StopTextInput(os::getSDLMainWindow());
}
}
// Return true or false if this gadget currently has the focus
int UI_GADGET::has_focus()
{
return my_wnd->selected_gadget == this ? 1 : 0;
}
// get mouse pointer position relative to gadget's origin (UL corner)
//
void UI_GADGET::get_mouse_pos(int *xx, int *yy)
{
if (xx)
*xx = ui_mouse.x - x;
if (yy)
*yy = ui_mouse.y - y;
}
// Call process() for all children of gadget. As a base gadget for all other gadget types,
// it doesn't actually do anything for itself.
//
void UI_GADGET::process(int /*focus*/)
{
UI_GADGET *tmp;
if (disabled_flag)
return;
tmp = children; // process all children of gadget
if (tmp) {
do {
tmp->process();
tmp = tmp->next;
} while (tmp != children);
}
}
// Check if the mouse is over the gadget's area or not,
//
int UI_GADGET::is_mouse_on()
{
int offset, pixel_val;
ubyte *mask_data;
int mask_w, mask_h;
// if linked to a hotspot, use the mask for determination
if (linked_to_hotspot) {
mask_data = (ubyte*)my_wnd->get_mask_data(&mask_w, &mask_h);
if ( mask_data == NULL ) {
nprintf(("Warning", "No mask defined, but control is linked to hotspot\n"));
Int3();
return 0;
}
// if the mouse values are out of range of the bitmap
// NOTE : this happens when using smaller mask bitmaps than the screen resolution (during development)
if((ui_mouse.x < 0) || (ui_mouse.x >= mask_w) || (ui_mouse.y < 0) || (ui_mouse.y >= mask_h)){
return 0;
}
// check the pixel value under the mouse
offset = ui_mouse.y * mask_w + ui_mouse.x;
pixel_val = *(mask_data + offset);
if (pixel_val == hotspot_num){
return 1;
} else {
return 0;
}
// otherwise, we just check the bounding box area
} else {
if ((ui_mouse.x >= x) && (ui_mouse.x < x + w) && (ui_mouse.y >= y) && (ui_mouse.y < y + h) ){
return 1;
} else {
return 0;
}
}
}
// check if mouse is over any child of this gadget
//
int UI_GADGET::is_mouse_on_children()
{
UI_GADGET *tmp;
tmp = children;
if (tmp) {
do {
if (tmp->is_mouse_on())
return 1;
if (tmp->is_mouse_on_children())
return 1;
tmp = tmp->next;
} while (tmp != children);
}
return 0;
}
void UI_GADGET::disable()
{
disabled_flag = 1;
}
// Enables (or possibly disables) the gadget. n is an optional argument. If not supplied,
// enables the garget. If supplied, enables garget if n is true, disables it if n is false.
//
void UI_GADGET::enable(int n)
{
disabled_flag = n ? 0 : 1;
}
// Check if gadget is disabled
//
int UI_GADGET::disabled()
{
return disabled_flag;
}
// Check if gadget is enabled
//
int UI_GADGET::enabled()
{
return !disabled_flag;
}
void UI_GADGET::drag_with_children( int dx, int dy )
{
UI_GADGET *tmp;
x = dx + base_start_x;
y = dy + base_start_y;
tmp = children;
if (tmp) {
do {
tmp->drag_with_children(dx, dy);
tmp = tmp->next;
} while (tmp != children);
}
}
void UI_GADGET::start_drag_with_children()
{
UI_GADGET *tmp;
base_dragging = 1;
base_start_x = x;
base_start_y = y;
tmp = children;
if (tmp) {
do {
tmp->start_drag_with_children();
tmp = tmp->next;
} while (tmp != children);
}
}
void UI_GADGET::stop_drag_with_children()
{
UI_GADGET *tmp;
base_dragging = 0;
tmp = children;
if (tmp) {
do {
tmp->stop_drag_with_children();
tmp = tmp->next;
} while (tmp != children);
}
}
// Returns 1 if moving
int UI_GADGET::check_move()
{
#if 0
if ( parent != NULL ) return base_dragging;
if ( !base_dragging ) {
if ( B2_JUST_PRESSED ) {
if ( is_mouse_on() || is_mouse_on_children() ) {
start_drag_with_children();
base_drag_x = ui_mouse.x;
base_drag_y = ui_mouse.y;
return 1;
} else {
return 0;
}
} else
return 0;
} else {
drag_with_children(ui_mouse.x - base_drag_x,ui_mouse.y - base_drag_y);
nprintf(( "UI", "UI: X=%d, Y=%d, Delta=(%d,%d)\n", x, y, (ui_mouse.x - base_drag_x), (ui_mouse.y - base_drag_y) ));
if (B2_RELEASED) {
stop_drag_with_children();
}
return 1;
}
#endif
return 0;
}
// Take gadget out of family. Children of this gadget stay with gadget, though.
//
// A family is basically the whole parent/sibling/children hierarchy for gadgets. Any gadget
// that is linked to another gadget by one of these pointers is said to be in the same family
// as that gadget. This function, therefore, caused all references to a gadget by it's
// family's gadgets' sibling or children pointers to be broken, and all references to any of them
// by this gadget's parent or sibling pointers to also be broken. This isolates the gadget and
// it's children into a new family.
//
void UI_GADGET::remove_from_family()
{
if (parent) {
if (parent->children == this) {
if (next == this) // an only child?
parent->children = NULL; // if so, parent now has no children
else
parent->children = next; // next sibling is now the eldest
}
} else {
if (my_wnd->first_gadget == this) {
if (next == this) // an only child?
my_wnd->first_gadget = NULL; // if so, parent now has no children
else
my_wnd->first_gadget = next; // next sibling is now the eldest
}
}
parent = NULL;
if (next != this) { // does gadget have siblings?
next->prev = prev;
prev->next = next;
}
next = prev = this;
}
// Put gadget into a new family (removing from old one if needed first).
// See remove_from_family() for definition of what a family is.
//
void UI_GADGET::set_parent(UI_GADGET *daddy)
{
remove_from_family();
parent = daddy;
if (!daddy) {
if (!my_wnd->first_gadget) {
my_wnd->first_gadget = this;
} else {
UI_GADGET *eldest_sibling, *youngest_sibling;
eldest_sibling = my_wnd->first_gadget;
youngest_sibling = eldest_sibling->prev;
next = eldest_sibling;
prev = youngest_sibling;
eldest_sibling->prev = this;
youngest_sibling->next = this;
}
return;
}
if (!daddy->children) {
daddy->children = this;
} else {
UI_GADGET *eldest_sibling, *youngest_sibling;
eldest_sibling = daddy->children;
youngest_sibling = eldest_sibling->prev;
next = eldest_sibling;
prev = youngest_sibling->prev;
eldest_sibling->prev = this;
youngest_sibling->next = this;
}
}