TGUI Input Framework

This commit is contained in:
Casey
2022-06-22 19:19:55 -04:00
committed by Darlantan
parent f03af38227
commit a3dd6b8ffa
30 changed files with 1519 additions and 927 deletions
@@ -118,7 +118,7 @@
if("add_player_info")
var/key = params["ckey"]
var/add = tgui_input_message(usr, "Write your comment below.", "Add Player Info")
var/add = tgui_input_text(usr, "Write your comment below.", "Add Player Info", multiline = TRUE)
if(!add) return
notes_add(key,add,usr)
+14
View File
@@ -32,8 +32,12 @@
var/closing = FALSE
/// The status/visibility of the UI.
var/status = STATUS_INTERACTIVE
/// Timed refreshing state
var/refreshing = FALSE
/// Topic state used to determine status/interactability.
var/datum/tgui_state/state = null
/// Rate limit client refreshes to prevent DoS.
COOLDOWN_DECLARE(refresh_cooldown)
/// The map z-level to display.
var/map_z_level = 1
/// The Parent UI
@@ -176,11 +180,17 @@
/datum/tgui/proc/send_full_update(custom_data, force)
if(!user.client || !initialized || closing)
return
if(!COOLDOWN_FINISHED(src, refresh_cooldown))
refreshing = TRUE
addtimer(CALLBACK(src, .proc/send_full_update), TGUI_REFRESH_FULL_UPDATE_COOLDOWN, TIMER_UNIQUE)
return
refreshing = FALSE
var/should_update_data = force || status >= STATUS_UPDATE
window.send_message("update", get_payload(
custom_data,
with_data = should_update_data,
with_static_data = TRUE))
COOLDOWN_START(src, refresh_cooldown, TGUI_REFRESH_FULL_UPDATE_COOLDOWN)
/**
* public
@@ -211,6 +221,7 @@
"title" = title,
"status" = status,
"interface" = interface,
"refreshing" = refreshing,
"map" = (using_map && using_map.path) ? using_map.path : "Unknown",
"mapZLevel" = map_z_level,
"window" = list(
@@ -312,6 +323,9 @@
return FALSE
switch(type)
if("ready")
// Send a full update when the user manually refreshes the UI
if(initialized)
send_full_update()
initialized = TRUE
if("pingReply")
initialized = TRUE
-169
View File
@@ -1,169 +0,0 @@
/**
* Creates a TGUI alert window and returns the user's response.
*
* This proc should be used to create alerts that the caller will wait for a response from.
* Arguments:
* * user - The user to show the alert to.
* * message - The content of the alert, shown in the body of the TGUI window.
* * title - The of the alert modal, shown on the top of the TGUI window.
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0)
if (istext(buttons))
stack_trace("tgui_alert() received text for buttons instead of list")
return
if (istext(user))
stack_trace("tgui_alert() received text for user instead of list")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_alert/alert = new(user, message, title, buttons, timeout)
alert.tgui_interact(user)
alert.wait()
if (alert)
. = alert.choice
qdel(alert)
/**
* Creates an asynchronous TGUI alert window with an associated callback.
*
* This proc should be used to create alerts that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the alert to.
* * message - The content of the alert, shown in the body of the TGUI window.
* * title - The of the alert modal, shown on the top of the TGUI window.
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Disabled by default, can be set to seconds otherwise.
*/
/proc/tgui_alert_async(mob/user, message = null, title = null, list/buttons = list("Ok"), datum/callback/callback, timeout = 0)
if (istext(buttons))
stack_trace("tgui_alert() received text for buttons instead of list")
return
if (istext(user))
stack_trace("tgui_alert() received text for user instead of list")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_alert/async/alert = new(user, message, title, buttons, callback, timeout)
alert.tgui_interact(user)
/**
* # tgui_modal
*
* Datum used for instantiating and using a TGUI-controlled modal that prompts the user with
* a message and has buttons for responses.
*/
/datum/tgui_alert
/// The title of the TGUI window
var/title
/// The textual body of the TGUI window
var/message
/// The list of buttons (responses) provided on the TGUI window
var/list/buttons
/// The button that the user has pressed, null if no selection has been made
var/choice
/// The time at which the tgui_modal was created, for displaying timeout progress.
var/start_time
/// The lifespan of the tgui_modal, after which the window will close and delete itself.
var/timeout
/// Boolean field describing if the tgui_modal was closed by the user.
var/closed
/datum/tgui_alert/New(mob/user, message, title, list/buttons, timeout)
src.title = title
src.message = message
src.buttons = buttons.Copy()
if (timeout)
src.timeout = timeout
start_time = world.time
QDEL_IN(src, timeout)
/datum/tgui_alert/Destroy(force, ...)
SStgui.close_uis(src)
QDEL_NULL(buttons)
. = ..()
/**
* Waits for a user's response to the tgui_modal's prompt before returning. Returns early if
* the window was closed by the user.
*/
/datum/tgui_alert/proc/wait()
while (!choice && !closed && !QDELETED(src))
stoplag(1)
/datum/tgui_alert/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AlertModal")
ui.open()
/datum/tgui_alert/tgui_close(mob/user)
. = ..()
closed = TRUE
/datum/tgui_alert/tgui_state(mob/user)
return GLOB.tgui_always_state
/datum/tgui_alert/tgui_data(mob/user)
. = list(
"title" = title,
"message" = message,
"buttons" = buttons
)
if(timeout)
.["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
/datum/tgui_alert/tgui_act(action, list/params)
. = ..()
if (.)
return
switch(action)
if("choose")
if (!(params["choice"] in buttons))
return
set_choice(params["choice"])
SStgui.close_uis(src)
return TRUE
/datum/tgui_alert/proc/set_choice(choice)
src.choice = choice
/**
* # async tgui_modal
*
* An asynchronous version of tgui_modal to be used with callbacks instead of waiting on user responses.
*/
/datum/tgui_alert/async
/// The callback to be invoked by the tgui_modal upon having a choice made.
var/datum/callback/callback
/datum/tgui_alert/async/New(mob/user, message, title, list/buttons, callback, timeout)
..(user, message, title, buttons, timeout)
src.callback = callback
/datum/tgui_alert/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
/datum/tgui_alert/async/set_choice(choice)
. = ..()
if(!isnull(src.choice))
callback?.InvokeAsync(src.choice)
/datum/tgui_alert/async/wait()
return
-205
View File
@@ -1,205 +0,0 @@
/**
* Creates a TGUI input list window and returns the user's response.
*
* This proc should be used to create alerts that the caller will wait for a response from.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * default - The option with this value will be selected on first paint of the TGUI window.
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_list(mob/user, message, title, list/buttons, default, timeout = 0)
if (istext(user))
stack_trace("tgui_alert() received text for user instead of mob")
return
if (!user)
user = usr
if(!length(buttons))
return
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_list_input/input = new(user, message, title, buttons, default, timeout)
input.tgui_interact(user)
input.wait()
if (input)
. = input.choice
qdel(input)
/**
* Creates an asynchronous TGUI input list window with an associated callback.
*
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * default - The option with this value will be selected on first paint of the TGUI window.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_list_async(mob/user, message, title, list/buttons, default, datum/callback/callback, timeout = 60 SECONDS)
if (istext(user))
stack_trace("tgui_alert() received text for user instead of mob")
return
if (!user)
user = usr
if(!length(buttons))
return
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_list_input/async/input = new(user, message, title, buttons, default, callback, timeout)
input.tgui_interact(user)
/**
* # tgui_list_input
*
* Datum used for instantiating and using a TGUI-controlled list input that prompts the user with
* a message and shows a list of selectable options
*/
/datum/tgui_list_input
/// The title of the TGUI window
var/title
/// The textual body of the TGUI window
var/message
/// The list of buttons (responses) provided on the TGUI window
var/list/buttons
/// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb)
var/list/buttons_map
/// Value of the button that should be pre-selected on first paint.
var/initial
/// The button that the user has pressed, null if no selection has been made
var/choice
/// The time at which the tgui_list_input was created, for displaying timeout progress.
var/start_time
/// The lifespan of the tgui_list_input, after which the window will close and delete itself.
var/timeout
/// Boolean field describing if the tgui_list_input was closed by the user.
var/closed
/datum/tgui_list_input/New(mob/user, message, title, list/buttons, default, timeout)
src.title = title
src.message = message
src.buttons = list()
src.buttons_map = list()
src.initial = default
var/list/repeat_buttons = list()
// Gets rid of illegal characters
var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
for(var/i in buttons)
if(isnull(i))
stack_trace("Null in a tgui_input_list() buttons")
continue
var/string_key = whitelistedWords.Replace("[i]", "")
//avoids duplicated keys E.g: when areas have the same name
string_key = avoid_assoc_duplicate_keys(string_key, repeat_buttons)
src.buttons += string_key
src.buttons_map[string_key] = i
if (timeout)
src.timeout = timeout
start_time = world.time
QDEL_IN(src, timeout)
/datum/tgui_list_input/Destroy(force, ...)
SStgui.close_uis(src)
QDEL_NULL(buttons)
. = ..()
/**
* Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if
* the window was closed by the user.
*/
/datum/tgui_list_input/proc/wait()
while (!choice && !closed)
stoplag(1)
/datum/tgui_list_input/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ListInput")
ui.open()
/datum/tgui_list_input/tgui_close(mob/user)
. = ..()
closed = TRUE
/datum/tgui_list_input/tgui_state(mob/user)
return GLOB.tgui_always_state
/datum/tgui_list_input/tgui_static_data(mob/user)
. = list(
"title" = title,
"message" = message,
"buttons" = buttons,
"initial" = initial
)
/datum/tgui_list_input/tgui_data(mob/user)
. = list()
if(timeout)
.["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
/datum/tgui_list_input/tgui_act(action, list/params)
. = ..()
if (.)
return
switch(action)
if("choose")
if (!(params["choice"] in buttons))
return
set_choice(buttons_map[params["choice"]])
SStgui.close_uis(src)
return TRUE
if("cancel")
SStgui.close_uis(src)
closed = TRUE
return TRUE
/datum/tgui_list_input/proc/set_choice(choice)
src.choice = choice
/**
* # async tgui_list_input
*
* An asynchronous version of tgui_list_input to be used with callbacks instead of waiting on user responses.
*/
/datum/tgui_list_input/async
/// The callback to be invoked by the tgui_list_input upon having a choice made.
var/datum/callback/callback
/datum/tgui_list_input/async/New(mob/user, message, title, list/buttons, default, callback, timeout)
..(user, title, message, buttons, default, timeout)
src.callback = callback
/datum/tgui_list_input/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
/datum/tgui_list_input/async/tgui_close(mob/user)
. = ..()
qdel(src)
/datum/tgui_list_input/async/set_choice(choice)
. = ..()
if(!isnull(src.choice))
callback?.InvokeAsync(src.choice)
/datum/tgui_list_input/async/wait()
return
-299
View File
@@ -1,299 +0,0 @@
/**
* Creates a TGUI input text window and returns the user's response.
*
* This proc should be used to create alerts that the caller will wait for a response from.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_text(mob/user, message, title, default, timeout = 0)
if (istext(user))
stack_trace("tgui_input_text() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_dialog/input = new(user, message, title, default, timeout)
input.input_type = "text"
input.tgui_interact(user)
input.wait()
if (input)
. = input.choice
qdel(input)
/**
* Creates a TGUI input message window and returns the user's response.
*
* This proc should be used to create alerts that the caller will wait for a response from.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_message(mob/user, message, title, default, timeout = 0)
if (istext(user))
stack_trace("tgui_input_message() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_dialog/input = new(user, message, title, default, timeout)
input.input_type = "message"
input.tgui_interact(user)
input.wait()
if (input)
. = input.choice
qdel(input)
/**
* Creates a TGUI input num window and returns the user's response.
*
* This proc should be used to create alerts that the caller will wait for a response from.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_num(mob/user, message, title, default, timeout = 0)
if (istext(user))
stack_trace("tgui_input_num() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_dialog/input = new(user, message, title, default, timeout)
input.input_type = "num"
input.tgui_interact(user)
input.wait()
if (input)
. = input.choice
qdel(input)
/**
* Creates an asynchronous TGUI input text window with an associated callback.
*
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_text_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS)
if (istext(user))
stack_trace("tgui_input_text_async() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_dialog/async/input = new(user, message, title, default, callback, timeout)
input.input_type = "text"
input.tgui_interact(user)
/**
* Creates an asynchronous TGUI input message window with an associated callback.
*
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_message_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS)
if (istext(user))
stack_trace("tgui_input_message_async() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_dialog/async/input = new(user, message, title, default, callback, timeout)
input.input_type = "message"
input.tgui_interact(user)
/**
* Creates an asynchronous TGUI input num window with an associated callback.
*
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_num_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS)
if (istext(user))
stack_trace("tgui_input_num_async() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_dialog/async/input = new(user, message, title, default, callback, timeout)
input.input_type = "num"
input.tgui_interact(user)
/**
* # tgui_input_dialog
*
* Datum used for instantiating and using a TGUI-controlled input that prompts the user with
* a message and a box for accepting text/message/num input.
*/
/datum/tgui_input_dialog
/// The title of the TGUI window
var/title
/// The textual body of the TGUI window
var/message
/// The default value to initially populate the input box.
var/initial
/// The value that the user input into the input box, null if cancelled.
var/choice
/// The time at which the tgui_text_input was created, for displaying timeout progress.
var/start_time
/// The lifespan of the tgui_text_input, after which the window will close and delete itself.
var/timeout
/// Boolean field describing if the tgui_text_input was closed by the user.
var/closed
/// Indicates the data type we want to collect ("text", "message", "num")
var/input_type = "text"
/datum/tgui_input_dialog/New(mob/user, message, title, default, timeout)
src.title = title
src.message = message
// TODO - Do we need to sanitize the initial value for illegal characters?
src.initial = default
if (timeout)
src.timeout = timeout
start_time = world.time
QDEL_IN(src, timeout)
/datum/tgui_input_dialog/Destroy(force, ...)
SStgui.close_uis(src)
. = ..()
/**
* Waits for a user's response to the tgui_text_input's prompt before returning. Returns early if
* the window was closed by the user.
*/
/datum/tgui_input_dialog/proc/wait()
while (!choice && !closed)
stoplag(1)
/datum/tgui_input_dialog/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "InputModal")
ui.open()
/datum/tgui_input_dialog/tgui_close(mob/user)
. = ..()
closed = TRUE
/datum/tgui_input_dialog/tgui_state(mob/user)
return GLOB.tgui_always_state
/datum/tgui_input_dialog/tgui_static_data(mob/user)
. = list(
"title" = title,
"message" = message,
"initial" = initial,
"input_type" = input_type
)
/datum/tgui_input_dialog/tgui_data(mob/user)
. = list()
if(timeout)
.["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
/datum/tgui_input_dialog/tgui_act(action, list/params)
. = ..()
if (.)
return
switch(action)
if("choose")
set_choice(params["choice"])
if(isnull(src.choice))
return
SStgui.close_uis(src)
return TRUE
if("cancel")
SStgui.close_uis(src)
closed = TRUE
return TRUE
/datum/tgui_input_dialog/proc/set_choice(choice)
if(input_type == "num")
src.choice = text2num(choice)
return
src.choice = choice
/**
* # async tgui_text_input
*
* An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses.
*/
/datum/tgui_input_dialog/async
/// The callback to be invoked by the tgui_text_input upon having a choice made.
var/datum/callback/callback
/datum/tgui_input_dialog/async/New(mob/user, message, title, default, callback, timeout)
..(user, title, message, default, timeout)
src.callback = callback
/datum/tgui_input_dialog/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
/datum/tgui_input_dialog/async/tgui_close(mob/user)
. = ..()
qdel(src)
/datum/tgui_input_dialog/async/set_choice(choice)
. = ..()
if(!isnull(src.choice))
callback?.InvokeAsync(src.choice)
/datum/tgui_input_dialog/async/wait()
return