2024-09-13 02:35:09 +00:00
// Shpuld's Simple UI lib - sui
// Created 11/2018
//
// sui is a simple QuakeC UI lib for drawing and handling game interfaces.
// The API is made simple and easy to build upon, but cuts have been made
// to keep complexity low.
//
# ifdef MENU
const float IE_KEYDOWN = 0 ; /* Specifies that a key was pressed. Second argument is the scan code. Third argument is the unicode (printable) char value. Fourth argument denotes which keyboard(or mouse, if its a mouse 'scan' key) the event came from. Note that some systems may completely separate scan codes and unicode values, with a 0 value for the unspecified argument. */
const float IE_KEYUP = 1 ; /* Specifies that a key was released. Arguments are the same as IE_KEYDOWN. On some systems, this may be fired instantly after IE_KEYDOWN was fired. */
const float IE_MOUSEDELTA = 2 ; /* Specifies that a mouse was moved (touch screens and tablets typically give IE_MOUSEABS events instead, use _windowed_mouse 0 to test code to cope with either). Second argument is the X displacement, third argument is the Y displacement. Fourth argument is which mouse or touch event triggered the event. */
const float IE_MOUSEABS = 3 ; /* Specifies that a mouse cursor or touch event was moved to a specific location relative to the virtual screen space. Second argument is the new X position, third argument is the new Y position. Fourth argument is which mouse or touch event triggered the event. */
const float IE_ACCELEROMETER = 4 ;
const float IE_FOCUS = 5 ; /* Specifies that input focus was given. parama says mouse focus, paramb says keyboard focus. If either are -1, then it is unchanged. */
const float IE_JOYAXIS = 6 ; /* Specifies that what value a joystick/controller axis currently specifies. x=axis, y=value. Will be called multiple times, once for each axis of each active controller. */
# define printf(x, ...) print(sprintf(x, __VA_ARGS__))
# endif
float _sui_draw_initialized ;
// framing
// pseudo windowing, sets a new "frame" for whatever we're drawing, instead of
// always using screen [0, 0] as min and [screen_width, screen_height] as max.
// also allows for aligning content to frame start/end/center on both axis
struct _frame_t {
vector pos ;
vector size ;
vector align ;
} ;
const float MAX_FRAMES = 64 ;
_frame_t _frames [ MAX_FRAMES ] ;
float _frame_index ;
const float SUI_ALIGN_START = 0 ;
const float SUI_ALIGN_CENTER = 1 ;
const float SUI_ALIGN_END = 2 ;
void ( ) sui_reset_align =
{
_frames [ _frame_index ] . align = [ SUI_ALIGN_START , SUI_ALIGN_START ] ;
} ;
void ( float align ) sui_set_x_align =
{
_frames [ _frame_index ] . align . x = align ;
} ;
void ( float align ) sui_set_y_align =
{
_frames [ _frame_index ] . align . y = align ;
} ;
void ( vector align ) sui_set_align =
{
_frames [ _frame_index ] . align = align ;
} ;
void ( __inout vector point ) sui_transform_point =
{
int idx = _frame_index ;
switch ( _frames [ idx ] . align . x )
{
case SUI_ALIGN_START : point_x + = _frames [ idx ] . pos . x ; break ;
case SUI_ALIGN_CENTER : point_x + = _frames [ idx ] . pos . x + _frames [ idx ] . size . x * 0.5 ; break ;
case SUI_ALIGN_END : point_x + = _frames [ idx ] . pos . x + _frames [ idx ] . size . x ; break ;
default : break ;
}
switch ( _frames [ idx ] . align . y )
{
case SUI_ALIGN_START : point_y + = _frames [ idx ] . pos . y ; break ;
case SUI_ALIGN_CENTER : point_y + = _frames [ idx ] . pos . y + _frames [ idx ] . size . y * 0.5 ; break ;
case SUI_ALIGN_END : point_y + = _frames [ idx ] . pos . y + _frames [ idx ] . size . y ; break ;
default : break ;
}
} ;
void ( __inout vector point , vector size ) sui_transform_box =
{
int idx = _frame_index ;
switch ( _frames [ idx ] . align . x )
{
case SUI_ALIGN_START :
point_x + = _frames [ idx ] . pos . x ;
break ;
case SUI_ALIGN_CENTER :
point_x + = _frames [ idx ] . pos . x + _frames [ idx ] . size . x * 0.5 - size_x * 0.5 ;
break ;
case SUI_ALIGN_END :
point_x + = _frames [ idx ] . pos . x + _frames [ idx ] . size . x - size_x ;
break ;
default : break ;
}
switch ( _frames [ idx ] . align . y )
{
case SUI_ALIGN_START :
point_y + = _frames [ idx ] . pos . y ;
break ;
case SUI_ALIGN_CENTER :
point_y + = _frames [ idx ] . pos . y + _frames [ idx ] . size . y * 0.5 - size_y * 0.5 ;
break ;
case SUI_ALIGN_END :
point_y + = _frames [ idx ] . pos . y + _frames [ idx ] . size . y - size_y ;
break ;
default : break ;
}
} ;
vector ( ) sui_current_frame_pos =
{
return _frames [ _frame_index ] . pos ;
} ;
vector ( ) sui_current_frame_size =
{
return _frames [ _frame_index ] . size ;
} ;
float _sui_is_clipping ;
vector _sui_clip_area_mins ;
vector _sui_clip_area_maxs ;
void ( ) sui_clip_to_frame =
{
vector pos = _frames [ _frame_index ] . pos ;
vector size = _frames [ _frame_index ] . size ;
_sui_is_clipping = TRUE ;
_sui_clip_area_mins = pos ;
_sui_clip_area_maxs = pos + size ;
drawsetcliparea ( pos . x , pos . y , size . x , size . y ) ;
} ;
void ( ) sui_reset_clip =
{
_sui_is_clipping = FALSE ;
drawresetcliparea ( ) ;
} ;
float ( ) sui_is_clipping =
{
return _sui_is_clipping ;
} ;
void ( vector pos , vector size ) sui_push_frame =
{
sui_transform_box ( pos , size ) ;
_frame_index + = 1 ;
if ( _frame_index > = MAX_FRAMES )
{
2024-10-17 03:46:20 +00:00
//printf("^3sui warning: amount of frames = %.0f exceeds MAX_FRAMES = %.0f, consider increasing MAX_FRAMES\n", _frame_index, MAX_FRAMES);
2024-09-13 02:35:09 +00:00
return ;
}
_frames [ _frame_index ] . pos = pos ;
_frames [ _frame_index ] . size = size ;
_frames [ _frame_index ] . align = [ SUI_ALIGN_START , SUI_ALIGN_START ] ; // TODO allow customizing this
} ;
void ( ) sui_pop_frame =
{
if ( _frame_index > 0 ) _frame_index - = 1 ;
} ;
void ( ) sui_reset_frame =
{
_frame_index = 0 ;
sui_reset_align ( ) ;
} ;
// actions
// interaction for sui elements, relies a lot on reading globals to see which
// element id is under cursor or held or whatever, not the most elegant
// solution but in this highly imperative world of QuakeC we can live with it
float _holding_click ;
vector _cursor_click ;
vector _cursor_position ;
vector _cursor_relative_click ;
vector _cursor_relative_hover ;
struct _action_element_t {
vector pos ;
vector size ;
string id ;
void ( float index , vector click_ratios ) action ;
} ;
const float MAX_ACTION_ELEMENTS = 256 ;
_action_element_t _action_elements [ MAX_ACTION_ELEMENTS ] ;
float _action_elements_index ;
// TODO better naming
float ( vector point , vector min , vector max ) is_2dpoint_in_bounds =
{
if ( point_x < = min_x | | point_y < = min_y ) return FALSE ;
if ( point_x > max_x | | point_y > max_y ) return FALSE ;
return TRUE ;
} ;
// TODO better naming
float ( vector point , vector pos , vector size ) is_2dpoint_in_bbox =
{
return is_2dpoint_in_bounds ( point , pos , pos + size ) ;
} ;
void ( ) _action_element_count_sanity =
{
if ( _action_elements_index > MAX_ACTION_ELEMENTS )
{
// let the user know if they're hitting the bounds
2024-10-17 03:46:20 +00:00
//printf("^3sui warning: amount of action elements = %.0f exceeds MAX_ACTION_ELEMENTS = %.0f, consider increasing MAX_ACTION_ELEMENTS\n", _action_elements_index, MAX_ACTION_ELEMENTS);
2024-09-13 02:35:09 +00:00
}
} ;
const float MAX_MOUSE_ACTIONS = 16 ;
string _hover_actions [ MAX_MOUSE_ACTIONS ] ;
string _click_actions [ MAX_MOUSE_ACTIONS ] ;
string _hold_actions [ MAX_MOUSE_ACTIONS ] ;
string _release_actions [ MAX_MOUSE_ACTIONS ] ;
string _last_clicked_actions [ MAX_MOUSE_ACTIONS ] ;
float _hover_action_count ;
float _click_action_count ;
float _hold_action_count ;
float _release_action_count ;
float _last_clicked_action_count ;
// Resets things you might want to persist normally
void ( ) sui_reset_actions =
{
_hover_action_count = 0 ;
_click_action_count = 0 ;
_hold_action_count = 0 ;
_release_action_count = 0 ;
_last_clicked_action_count = 0 ;
_holding_click = FALSE ;
} ;
// Per frame reset?
void ( ) sui_reset_click =
{
_hold_action_count = 0 ;
_click_action_count = 0 ;
_holding_click = FALSE ;
} ;
float ( ) sui_click_held = { return _holding_click ; } ;
// click: on mouse1 button down AND button op, once
// Returns true if id was the topmost click (what usually is cared about the most)
float ( string id ) sui_is_clicked =
{
return _click_action_count > 0 & & _click_actions [ 0 ] = = id ;
} ;
// Returns the index of the clicked id, -1 if wasn't hit at all. 0 is topmost
float ( string id ) sui_click_index =
{
for ( int i = 0 ; i < _click_action_count ; i + + )
{
if ( _click_actions [ i ] = = id ) return i ;
}
return - 1 ;
} ;
// hover: mouse is on top of the action element id
string last_hovered_id ;
float ( string id ) sui_is_hovered =
{
float is_hovered = _hover_action_count > 0 & & _hover_actions [ 0 ] = = id ;
if ( is_hovered & & last_hovered_id ! = id & & substring ( id , 0 , 4 ) ! = " soc_ " ) {
last_hovered_id = id ;
Menu_PlaySound ( MENU_SND_NAVIGATE ) ;
}
return is_hovered ;
} ;
float ( string id ) sui_hover_index =
{
for ( int i = 0 ; i < _hover_action_count ; i + + )
{
if ( _hover_actions [ i ] = = id ) return i ;
}
return - 1 ;
} ;
// hold: mouse button was clicked on top of this id and is held down, but not necessarily over this id anymore
float ( string id ) sui_is_held =
{
return _hold_action_count > 0 & & _hold_actions [ 0 ] = = id ;
} ;
float ( string id ) sui_hold_index =
{
for ( int i = 0 ; i < _hold_action_count ; i + + )
{
if ( _hold_actions [ i ] = = id ) return i ;
}
return - 1 ;
} ;
// last clicked: is this the last action element that was clicked, good for focusing on input boxes for example
float ( string id ) sui_is_last_clicked =
{
return _last_clicked_action_count > 0 & & _last_clicked_actions [ 0 ] = = id ;
} ;
float ( string id ) sui_last_clicked_index =
{
for ( int i = 0 ; i < _last_clicked_action_count ; i + + )
{
if ( _last_clicked_actions [ i ] = = id ) return i ;
}
return - 1 ;
} ;
// release: a thing was held, but now it was released, once
float ( string id ) sui_is_released =
{
return _release_action_count > 0 & & _release_actions [ 0 ] = = id ;
} ;
float ( string id ) sui_release_index =
{
for ( int i = 0 ; i < _release_action_count ; i + + )
{
if ( _release_actions [ i ] = = id ) return i ;
}
return - 1 ;
} ;
float ( float num ) mouse_action_sanity =
{
if ( num > = MAX_MOUSE_ACTIONS )
{
2024-10-17 03:46:20 +00:00
//printf("^3sui warning: you have exceeded the amount of overlapping action elements with %.0f, MAX_MOUSE_ACTIONS = %.0f\n", num, MAX_MOUSE_ACTIONS);
2024-09-13 02:35:09 +00:00
return TRUE ;
}
return FALSE ;
} ;
// mouse move, mostly just update hovers
void ( vector pos ) _sui_mouse_move =
{
_cursor_position = pos ;
_action_element_count_sanity ( ) ;
// Reset hover, it'll be back to what it used to be before draw gets called if mouse is still on same element
_hover_action_count = 0 ;
2024-10-16 04:38:47 +00:00
_hover_actions [ 0 ] = " " ;
2024-09-13 02:35:09 +00:00
// Iterate front to back, so topmost element gets the click/hover
for ( int i = min ( MAX_ACTION_ELEMENTS , _action_elements_index ) - 1 ; i > = 0 ; i - - )
{
if ( is_2dpoint_in_bbox ( _cursor_position , _action_elements [ i ] . pos , _action_elements [ i ] . size ) )
{
if ( mouse_action_sanity ( _hover_action_count ) ) break ;
if ( _hover_action_count = = 0 ) _cursor_relative_hover = _cursor_position - _action_elements [ i ] . pos ;
_hover_actions [ _hover_action_count ] = _action_elements [ i ] . id ;
_hover_action_count + = 1 ;
}
}
// Cypress -- Reset last_hovered if nothing is hovered for menu
// navigation sounds
if ( ! _hover_action_count )
last_hovered_id = " " ;
} ;
2024-10-16 04:38:47 +00:00
void ( ) _sui_menukey_downarrow =
{
string current_hovered_option = _hover_actions [ 0 ] ;
switch ( current_menu ) {
2024-10-17 03:46:20 +00:00
# ifdef MENU
2024-10-16 04:38:47 +00:00
case MENU_MAIN :
_hover_actions [ 0 ] = Menu_Main_GetNextButton ( current_hovered_option ) ;
break ;
case MENU_SOLO :
case MENU_SOLOUSER :
_hover_actions [ 0 ] = Menu_Maps_GetNextButton ( current_hovered_option ) ;
break ;
2024-10-17 03:46:20 +00:00
# else
case MENU_PAUSE :
_hover_actions [ 0 ] = Menu_Pause_GetNextButton ( current_hovered_option ) ;
break ;
# endif // MENU
2024-10-16 04:38:47 +00:00
case MENU_OPTIONS :
_hover_actions [ 0 ] = Menu_Options_GetNextButton ( current_hovered_option ) ;
break ;
case MENU_VIDEO :
_hover_actions [ 0 ] = Menu_Video_GetNextButton ( current_hovered_option ) ;
break ;
case MENU_AUDIO :
_hover_actions [ 0 ] = Menu_Audio_GetNextButton ( current_hovered_option ) ;
break ;
case MENU_CONTROL :
_hover_actions [ 0 ] = Menu_Control_GetNextButton ( current_hovered_option ) ;
break ;
case MENU_GAMEPAD :
_hover_actions [ 0 ] = Menu_Gamepad_GetNextButton ( current_hovered_option ) ;
break ;
default :
break ;
}
_hover_action_count = 1 ;
} ;
void ( ) _sui_menukey_uparrow =
{
string current_hovered_option = _hover_actions [ 0 ] ;
switch ( current_menu ) {
2024-10-17 03:46:20 +00:00
# ifdef MENU
2024-10-16 04:38:47 +00:00
case MENU_MAIN :
_hover_actions [ 0 ] = Menu_Main_GetPreviousButton ( current_hovered_option ) ;
break ;
case MENU_SOLO :
case MENU_SOLOUSER :
_hover_actions [ 0 ] = Menu_Maps_GetPreviousButton ( current_hovered_option ) ;
break ;
2024-10-17 03:46:20 +00:00
# else
case MENU_PAUSE :
_hover_actions [ 0 ] = Menu_Pause_GetPreviousButton ( current_hovered_option ) ;
break ;
# endif // MENU
2024-10-16 04:38:47 +00:00
case MENU_OPTIONS :
_hover_actions [ 0 ] = Menu_Options_GetPreviousButton ( current_hovered_option ) ;
break ;
case MENU_VIDEO :
_hover_actions [ 0 ] = Menu_Video_GetPreviousButton ( current_hovered_option ) ;
break ;
case MENU_AUDIO :
_hover_actions [ 0 ] = Menu_Audio_GetPreviousButton ( current_hovered_option ) ;
break ;
case MENU_CONTROL :
_hover_actions [ 0 ] = Menu_Control_GetPreviousButton ( current_hovered_option ) ;
break ;
case MENU_GAMEPAD :
_hover_actions [ 0 ] = Menu_Gamepad_GetPreviousButton ( current_hovered_option ) ;
break ;
default :
break ;
}
_hover_action_count = 1 ;
} ;
void ( ) _sui_menukey_enter =
{
if ( _hover_actions [ 0 ] = = " " )
_sui_menukey_downarrow ( ) ;
_click_actions [ 0 ] = _hover_actions [ 0 ] ;
_click_action_count = 1 ;
_last_clicked_action_count = 1 ;
} ;
2024-09-13 02:35:09 +00:00
// JERK ALERT: hard to pass input params for it without them just being the globals
// ... so it just straight up uses the globals... optimization
void ( ) _sui_mouse1_down =
{
// Cheap but it should work...
_cursor_click = _cursor_position ;
_cursor_relative_click = _cursor_relative_hover ;
for ( int i = 0 ; i < _hover_action_count ; i + + ) _hold_actions [ i ] = _hover_actions [ i ] ;
_hold_action_count = _hover_action_count ;
_holding_click = TRUE ;
_last_clicked_action_count = 0 ;
} ;
void ( ) _sui_mouse1_up =
{
// Can't be cheap here, we have to get the action fn of the element anyway so..
_action_element_count_sanity ( ) ;
// Assume we won't hit anything
_click_action_count = 0 ;
_last_clicked_action_count = 0 ;
// Iterate front to back, so topmost element gets the click/hover
for ( int i = min ( MAX_ACTION_ELEMENTS , _action_elements_index ) - 1 ; i > = 0 ; i - - )
{
// If the thing wasn't the same thing we started pressing down on, ignore
for ( int j = 0 ; j < _hold_action_count ; j + + )
{
if ( _hold_actions [ j ] = = _action_elements [ i ] . id ) // yes this element was held
{
// Still in bounds?
if ( is_2dpoint_in_bbox ( _cursor_position , _action_elements [ i ] . pos , _action_elements [ i ] . size ) )
{
if ( mouse_action_sanity ( _click_action_count ) ) break ;
// Register click
_click_actions [ _click_action_count ] = _action_elements [ i ] . id ;
_last_clicked_actions [ _last_clicked_action_count ] = _action_elements [ i ] . id ;
_click_action_count + = 1 ;
_last_clicked_action_count + = 1 ;
}
}
}
}
// In case someone is keeping state on hold and wants to do stuff on release, even if cursor has moved
for ( int i = 0 ; i < _hold_action_count ; i + + ) _release_actions [ i ] = _hold_actions [ i ] ;
_release_action_count = _hold_action_count ;
_hold_action_count = 0 ;
_holding_click = FALSE ;
} ;
2024-10-16 04:38:47 +00:00
// HACK!
void ( ) sui_reset_hover =
{
_hover_actions [ 0 ] = " " ;
} ;
2024-09-13 02:35:09 +00:00
void ( vector pos , vector size , string id , void ( float index , vector click_ratios ) action ) sui_action_element =
{
if ( ! _sui_draw_initialized )
{
print ( " ^1sui error: adding sui elements before sui_pre_draw! \n ^1 Always do your sui menus between sui_pre_draw and sui_draw! \n " ) ;
}
if ( _action_elements_index > = MAX_ACTION_ELEMENTS )
{
// Silently fail here, sui will let us know another way, increase the count
// so that the error in click/mousemove handlers prints correct numbers.
_action_elements_index + = 1 ;
return ;
}
sui_transform_box ( pos , size ) ;
if ( _sui_is_clipping )
{
vector oldpos = pos ;
pos_x = max ( pos_x , _sui_clip_area_mins_x ) ;
pos_y = max ( pos_y , _sui_clip_area_mins_y ) ;
size - = pos - oldpos ;
size_x - = bound ( 0 , ( pos_x + size_x - _sui_clip_area_maxs_x ) , size_x ) ;
size_y - = bound ( 0 , ( pos_y + size_y - _sui_clip_area_maxs_y ) , size_y ) ;
}
_action_elements [ _action_elements_index ] . pos = pos ;
_action_elements [ _action_elements_index ] . size = size ;
_action_elements [ _action_elements_index ] . id = id ;
_action_elements [ _action_elements_index ] . action = action ;
_action_elements_index + = 1 ;
} ;
// Input related stuff
string _sui_binding_command ;
string _sui_binding_command_name ;
struct _input_t {
float char ;
float scan ;
} ;
const float MAX_INPUTS = 64 ;
_input_t _input_buffer [ MAX_INPUTS ] ;
float _input_index ;
float _input_length ;
// probably good to use it like while (sui_get_input(char, scan)) { ... };
float ( __inout float char , __inout float scan ) sui_get_input =
{
if ( _input_index > = _input_length ) return FALSE ;
char = _input_buffer [ _input_index ] . char ;
scan = _input_buffer [ _input_index ] . scan ;
_input_index + + ;
return TRUE ;
} ;
// if 2 controls want to read the same input for some reason..
void ( ) sui_reread_input =
{
_input_index = 0 ;
} ;
void ( ) sui_clear_input =
{
_input_length = 0 ;
_input_index = 0 ;
} ;
float ( float char , float scan ) _sui_add_input =
{
// TODO check if input was listened, return FALSE if not
if ( _input_length > = MAX_INPUTS )
{
2024-10-17 03:46:20 +00:00
// printf("^3sui warning: exceeded amount of per frame inputs count MAX_INPUTS = %.0f\n"
// "^3 - make sure sui_input_event isn't being called without sui_draw being called in update loop\n"
// "^3 - consider increasing MAX_INPUTS\n", MAX_INPUTS);
2024-09-13 02:35:09 +00:00
return TRUE ;
}
_input_buffer [ _input_length ] . char = char ;
_input_buffer [ _input_length ] . scan = scan ;
_input_length + = 1 ;
return TRUE ;
} ;
// Listen to a certain keycode if it was pressed, this way sui know it was requested
float ( float keycode ) sui_listen_keycode_down =
{
return FALSE ;
} ;
// all text that was input between last and current frame
string ( ) sui_listen_text_input =
{
return " " ;
} ;
void ( float char , float scan , __inout string text , __inout float cursor ) sui_handle_text_input =
{
float maxlen = 128 ;
string prev = text ;
string pre_cursor , post_cursor ;
float length = strlen ( prev ) ;
if ( char > 31 & & char < 128 ) //an actual input
{
if ( length > = maxlen ) return ;
pre_cursor = substring ( prev , 0 , cursor ) ;
post_cursor = substring ( prev , cursor , length ) ;
text = sprintf ( " %s%s%s " , pre_cursor , chr2str ( char ) , post_cursor ) ;
cursor + = 1 ;
}
else if ( char = = 8 ) // backspace
{
if ( cursor < = 0 ) return ;
pre_cursor = substring ( prev , 0 , cursor - 1 ) ;
post_cursor = substring ( prev , cursor , length ) ;
cursor - = 1 ;
cursor = max ( 0 , cursor ) ;
text = strcat ( pre_cursor , post_cursor ) ;
}
else if ( scan = = K_DEL )
{
if ( cursor > = length ) return ;
pre_cursor = substring ( prev , 0 , cursor ) ;
post_cursor = substring ( prev , cursor + 1 , length ) ;
text = strcat ( pre_cursor , post_cursor ) ;
}
else if ( char = = 13 | | char = = 27 ) // enter or escape
{
// Commit and deselect...
// Let's try a hack..
_last_clicked_action_count = 0 ;
}
else if ( scan = = K_LEFTARROW )
{
cursor - = 1 ;
cursor = max ( 0 , cursor ) ;
}
else if ( scan = = K_RIGHTARROW )
{
cursor + = 1 ;
cursor = min ( strlen ( prev ) , cursor ) ;
}
} ;
void ( float maxlen , __inout string text , __inout float cursor ) sui_cap_input_length =
{
if ( strlen ( text ) > maxlen )
{
text = substring ( text , 0 , strlen ( text ) ) ;
cursor = strlen ( text ) ;
}
} ;
void ( string command ) _sui_unbind =
{
tokenize ( findkeysforcommand ( command ) ) ;
string keyname = keynumtostring ( stof ( argv ( 0 ) ) ) ;
string altkeyname = keynumtostring ( stof ( argv ( 1 ) ) ) ;
localcmd ( sprintf ( " unbind %s \n " , keyname ) ) ;
localcmd ( sprintf ( " unbind %s \n " , altkeyname ) ) ;
} ;
void ( float scan , string command ) _sui_do_keybind =
{
if ( scan = = K_ESCAPE )
{
_sui_binding_command = " " ;
_sui_binding_command_name = " " ;
return ;
}
if ( scan = = K_BACKSPACE )
{
_sui_unbind ( command ) ;
_sui_binding_command = " " ;
_sui_binding_command_name = " " ;
return ;
}
string keyname = keynumtostring ( scan ) ;
_sui_unbind ( command ) ;
localcmd ( sprintf ( " bind %s %s \n " , keyname , command ) ) ;
_sui_binding_command = " " ;
_sui_binding_command_name = " " ;
} ;
void ( string command , string command_name ) sui_start_bind =
{
_sui_binding_command = command ;
_sui_binding_command_name = command_name ;
} ;
// void(float evtype, float scanx, float chary, float devid) sui_input_event
// same args is CSQC_InputEvent.
// return value tells you if sui used the event or not, in case you want to
// not let engine handle it if it was used.
// Sets all the internal sui action stuff, call it in CSQC_InputEvent
float ( float evtype , float scanx , float chary , float devid ) sui_input_event =
{
switch ( evtype )
{
case IE_MOUSEABS :
_sui_mouse_move ( [ scanx , chary ] ) ;
return TRUE ;
case IE_MOUSEDELTA :
// Big question mark...
// maybe make our own delta based sui_cursor here..
// maybe just ignore delta and let user fake mouseabs with their own
// delta cursor by passing different params to this func...?
// for MVP let's just use mouseabs only
return FALSE ;
case IE_KEYDOWN :
if ( _sui_binding_command ! = " " )
{
// Nothing
return TRUE ;
}
else if ( scanx = = K_MOUSE1 )
{
_sui_mouse1_down ( ) ;
return TRUE ;
}
2024-10-16 04:38:47 +00:00
else if ( scanx = = K_DOWNARROW | | scanx = = K_GP_DPAD_DOWN )
{
_sui_menukey_downarrow ( ) ;
return TRUE ;
}
else if ( scanx = = K_UPARROW | | scanx = = K_GP_DPAD_UP )
{
_sui_menukey_uparrow ( ) ;
return TRUE ;
}
else if ( scanx = = K_ENTER | | scanx = = K_GP_A )
{
_sui_menukey_enter ( ) ;
return TRUE ;
}
2024-09-13 02:35:09 +00:00
else
{
if ( ( scanx = = K_ESCAPE | | scanx = = K_BACKSPACE ) & & _sui_binding_command ! = " " )
return TRUE ;
else if ( scanx = = K_ESCAPE )
return FALSE ;
return _sui_add_input ( chary , scanx ) ;
}
case IE_KEYUP :
if ( _sui_binding_command ! = " " )
{
_sui_do_keybind ( scanx , _sui_binding_command ) ;
return TRUE ;
}
else if ( scanx = = K_MOUSE1 )
{
_sui_mouse1_up ( ) ;
return TRUE ;
}
default :
return FALSE ;
}
return FALSE ;
} ;
// void() sui_pre_draw
// Resets state for sui actions so that no trouble happens.
// Call it before your menu code per frame in your draw/updateview.
void ( float width , float height ) sui_begin =
{
_action_elements_index = 0 ;
_sui_draw_initialized = TRUE ;
sui_reset_frame ( ) ;
sui_push_frame ( [ 0 , 0 ] , [ width , height ] ) ;
}
void ( ) sui_draw_bind_overlay ;
// void() sui_end
// Call after your menu code per frame in your draw/updateview.
void ( ) sui_end =
{
// Todo: move overlay drawing elsewhere:
sui_draw_bind_overlay ( ) ;
// Dirty part:
_sui_draw_initialized = FALSE ;
// reset "once" type actions
_click_action_count = 0 ;
_release_action_count = 0 ;
// empty input buffer
sui_clear_input ( ) ;
} ;
// Different draw components:
void ( vector pos , vector size , vector color , float alpha , float flags ) sui_fill =
{
sui_transform_box ( pos , size ) ;
drawfill ( pos , size , color , alpha , flags ) ;
} ;
void ( vector pos , vector size , string pic , vector color , float alpha , float flags ) sui_pic =
{
sui_transform_box ( pos , size ) ;
drawpic ( pos , pic , size , color , alpha , flags ) ;
} ;
void ( vector pos , vector size , string pic , vector color , vector source_pos , vector source_size , float alpha , float flags ) sui_subpic =
{
sui_transform_box ( pos , size ) ;
drawsubpic ( pos , size , pic , source_pos , source_size , color , alpha , flags ) ;
} ;
// drawsubpic([g_width - 32, g_height - 32], [28, 28], "gfx/menu/social.tga", [0.5, 0], [0.5, 0.5], [1, 1, 1], 1); // YouTube
// drawsubpic([g_width - 32, g_height - 64], [28, 28], "gfx/menu/social.tga", [0, 0.5], [0.5, 0.5], [1, 1, 1], 1); // Twitter
// drawsubpic([g_width - 32, g_height - 96], [28, 28], "gfx/menu/social.tga", [0.5, 0.5], [0.5, 0.5], [1, 1, 1], 1); // Patreon
void ( vector pos , vector size , float width , vector color , float alpha , float flags ) sui_border_box =
{
sui_transform_box ( pos , size ) ;
// Top line
drawfill ( pos , [ size_x , width ] , color , alpha , flags ) ;
// Bottom line
drawfill ( [ pos_x , pos_y + size_y - width ] , [ size_x , width ] , color , alpha , flags ) ;
// Left line
drawfill ( [ pos_x , pos_y + width ] , [ width , size_y - width * 2 ] , color , alpha , flags ) ;
// Right line
drawfill ( [ pos_x + size_x - width , pos_y + width ] , [ width , size_y - width * 2 ] , color , alpha , flags ) ;
} ;
void ( vector pos , vector size , string text , vector color , float alpha , float flags ) sui_text =
{
sui_transform_box ( pos , [ getTextWidth ( text , size_x ) , size_y ] ) ;
Draw_String ( pos , text , size , color , alpha , flags ) ;
} ;
void ( float index , vector click_ratios ) sui_noop = { } ;
void ( float value ) sui_slider_noop = { } ;
float ( string id , vector pos , vector size , vector minmaxsteps , float value , void ( float value ) action ) sui_slidercontrol =
{
sui_action_element ( pos , size , id , sui_noop ) ;
float newvalue = value ;
sui_transform_box ( pos , size ) ;
// user is clicking and holding the slider
if ( sui_is_held ( id ) )
{
float min = minmaxsteps [ 0 ] ;
float max = minmaxsteps [ 1 ] ;
float steps = minmaxsteps [ 2 ] ;
float click_ratio = ( _cursor_position_x - pos_x ) / size_x ;
click_ratio = bound ( 0 , click_ratio , 1 ) ;
if ( steps > 0 ) click_ratio = rint ( click_ratio * steps ) / steps ;
newvalue = min + click_ratio * ( max - min ) ;
if ( newvalue ! = value ) action ( newvalue ) ;
}
return newvalue ;
} ;
void ( string id , vector pos , vector size , __inout string text , __inout float cursor ) sui_text_input =
{
sui_action_element ( pos , size , id , sui_noop ) ;
if ( sui_is_clicked ( id ) ) cursor = strlen ( text ) ;
if ( sui_is_last_clicked ( id ) )
{
float char = 0 ;
float scan = 0 ;
while ( sui_get_input ( char , scan ) ) sui_handle_text_input ( char , scan , text , cursor ) ;
}
} ;
void ( string id , vector size , vector contentsize , __inout vector offset , vector scrollbar_widths ) sui_scrollbar =
{
vector maxoffset = contentsize - size ;
maxoffset_x = max ( 0 , maxoffset_x ) ;
maxoffset_y = max ( 0 , maxoffset_y ) ;
sui_push_frame ( [ 0 , 0 ] , size ) ;
float ofs ;
float length ;
vector barpos , barsize ;
float scan = 0 ;
float char = 0 ;
string barname ;
if ( maxoffset_y > 0 & & contentsize_y > 0 )
{
sui_set_align ( [ SUI_ALIGN_END , SUI_ALIGN_START ] ) ;
sui_push_frame ( [ 0 , 0 ] , [ scrollbar_widths_y , size_y ] ) ;
ofs = ( offset_y / contentsize_y ) * size_y ;
length = ( size_y / contentsize_y ) * size_y ;
barpos = [ 0 , ofs ] ;
barsize = [ scrollbar_widths_y , length ] ;
barname = strcat ( id , " vbar " ) ;
if ( sui_is_held ( barname ) )
{
vector anchor = barpos + _cursor_relative_click ;
sui_transform_point ( anchor ) ;
float diff = _cursor_position_y - anchor_y ;
offset_y + = ( diff * contentsize_y ) / size_y ; // * contentsize_y; // (size_y / contentsize_y);
}
sui_fill ( barpos , barsize , ' 0.1 0.1 0.1 ' * ( 1 - sui_is_hovered ( barname ) ) , 0.66 , 0 ) ;
sui_action_element ( barpos , barsize , barname , sui_noop ) ;
sui_pop_frame ( ) ;
}
sui_pop_frame ( ) ;
} ;
void ( string id , vector pos , vector size , vector contentsize , __inout vector offset , vector scrollbar_widths ) sui_scroll_view_begin =
{
// make space for scrollbars
sui_push_frame ( pos , size - [ scrollbar_widths_y , scrollbar_widths_x ] ) ;
sui_action_element ( [ 0 , 0 ] , size , id , sui_noop ) ;
if ( sui_hover_index ( id ) > - 1 )
{
float scrollamount = 0 ;
float char = 0 ;
float scan = 0 ;
sui_reread_input ( ) ;
while ( sui_get_input ( char , scan ) )
{
if ( scan = = K_MWHEELUP ) scrollamount - = 20 ;
if ( scan = = K_MWHEELDOWN ) scrollamount + = 20 ;
}
offset_y + = scrollamount ;
}
vector maxoffset = contentsize - size ;
maxoffset_x = max ( 0 , maxoffset_x ) ;
maxoffset_y = max ( 0 , maxoffset_y ) ;
offset_x = bound ( 0 , offset_x , maxoffset_x ) ;
offset_y = bound ( 0 , offset_y , maxoffset_y ) ;
sui_scrollbar ( id , size , contentsize , offset , scrollbar_widths ) ;
offset_x = bound ( 0 , offset_x , maxoffset_x ) ;
offset_y = bound ( 0 , offset_y , maxoffset_y ) ;
sui_clip_to_frame ( ) ;
sui_push_frame ( - 1 * offset , contentsize ) ;
} ;
void ( ) sui_scroll_view_end =
{
sui_pop_frame ( ) ;
sui_reset_clip ( ) ;
sui_pop_frame ( ) ;
} ;
float _sui_list_item_height ;
float _sui_list_first ;
float _sui_list_last ;
float _sui_list_pos ;
int _sui_list_index ;
void ( string id , vector pos , vector size , vector itemsize , float numitems , __inout vector offset , vector scrollbar_widths ) sui_list_view_begin =
{
vector contentsize = [ itemsize_x , itemsize_y * numitems ] ;
sui_scroll_view_begin ( id , pos , size , contentsize , offset , scrollbar_widths ) ;
_sui_list_item_height = itemsize_y ;
float hidden_above = floor ( offset_y / itemsize_y ) ;
_sui_list_first = max ( 0 , hidden_above ) ; // Index of first elem
_sui_list_last = min ( _sui_list_first + rint ( size_y / itemsize_y ) + 1 , numitems ) ;
_sui_list_pos = hidden_above * itemsize_y ;
_sui_list_index = _sui_list_first ;
} ;
float ( __inout vector pos ) sui_list_item =
{
if ( _sui_list_index > = _sui_list_last ) return - 1 ;
pos = _sui_list_index * [ 0 , _sui_list_item_height ] ;
_sui_list_index + = 1 ;
return _sui_list_index - 1 ;
} ;
void ( ) sui_list_view_end =
{
sui_scroll_view_end ( ) ;
} ;
string ( string id , vector pos , vector size , string name , string command ) sui_binder =
{
sui_action_element ( pos , size , id , sui_noop ) ;
if ( sui_is_released ( id ) )
{
sui_start_bind ( command , name ) ;
}
tokenize ( findkeysforcommand ( command ) ) ;
string keyname = keynumtostring ( stof ( argv ( 0 ) ) ) ;
if ( keyname = = " 01 " ) keyname = " unbound " ;
return keyname ;
} ;
void ( ) sui_draw_bind_overlay =
{
if ( _sui_binding_command ! = " " )
{
vector size = sui_current_frame_size ( ) ;
sui_fill ( [ 0 , 0 ] , size , ' 0 0 0 ' , 0.5 , 0 ) ;
sui_set_align ( [ SUI_ALIGN_CENTER , SUI_ALIGN_CENTER ] ) ;
float textsize = 16 ;
sui_text ( [ 0 , - 16 ] , [ textsize , textsize ] , " Press a key for " , ' 1 1 1 ' , 1 , 0 ) ;
sui_text ( [ 0 , 0 ] , [ textsize , textsize ] , sprintf ( " '%s' " , _sui_binding_command_name ) , ' 1 1 1 ' , 1 , 0 ) ;
sui_text ( [ 0 , 16 ] , [ textsize - 4 , textsize - 4 ] , " ESC to cancel, BACKSPACE to remove " , ' 1 1 1 ' , 1 , 0 ) ;
}
} ;
// -------------------- END OF SUI SYSTEM STUFF --------------------