/datum/browser
var/mob/user
var/title = ""
/// window_id is used as the window name for browse and onclose
var/window_id
var/width = 0
var/height = 0
var/datum/weakref/source_ref = null
/// window option is set using window_id
var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;"
var/list/stylesheets = list()
var/list/scripts = list()
var/head_elements
var/body_elements
var/head_content = ""
var/content = ""
/datum/browser/New(mob/user, window_id, title = "", width = 0, height = 0, atom/source = null)
if(IS_CLIENT_OR_MOCK(user))
var/client/client_user = user
user = client_user.mob
src.user = user
RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(user_deleted))
src.window_id = window_id
if (title)
src.title = format_text(title)
if (width)
src.width = width
if (height)
src.height = height
if (source)
src.source_ref = WEAKREF(source)
/datum/browser/proc/user_deleted(datum/source)
SIGNAL_HANDLER
user = null
/datum/browser/proc/add_head_content(head_content)
src.head_content += head_content
/datum/browser/proc/set_head_content(head_content)
src.head_content = head_content
/datum/browser/proc/set_window_options(window_options)
src.window_options = window_options
/datum/browser/proc/add_stylesheet(name, file)
if (istype(name, /datum/asset/spritesheet))
var/datum/asset/spritesheet/sheet = name
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
else if (istype(name, /datum/asset/spritesheet_batched))
var/datum/asset/spritesheet_batched/sheet = name
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
else
var/asset_name = "[name].css"
stylesheets[asset_name] = file
if (!SSassets.cache[asset_name])
SSassets.transport.register_asset(asset_name, file)
/datum/browser/proc/add_script(name, file)
scripts["[ckey(name)].js"] = file
SSassets.transport.register_asset("[ckey(name)].js", file)
/datum/browser/proc/set_content(content)
src.content = content
/datum/browser/proc/add_content(content)
src.content += content
/datum/browser/proc/get_header()
var/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common)
var/list/new_head_content = list()
// If a client exists, but they have disabled fancy windowing, disable it!
if(user?.client?.prefs?.read_preference(/datum/preference/toggle/browser_style))
new_head_content += ""
for (var/file in stylesheets)
new_head_content += ""
if(user.client?.window_scaling && user.client?.window_scaling != 1 && !user.client?.prefs.read_preference(/datum/preference/toggle/ui_scale) && width && height)
new_head_content += {"
"}
for (var/file in scripts)
new_head_content += ""
head_content += new_head_content.Join()
return {"
[head_content]
[title ? "
[title]
" : ""]
"}
//" This is here because else the rest of the file looks like a string in notepad++.
/datum/browser/proc/get_footer()
return {"
"}
/datum/browser/proc/get_content()
return {"
[get_header()]
[content]
[get_footer()]
"}
/datum/browser/proc/open(use_on_close = TRUE)
if(isnull(window_id)) //null check because this can potentially nuke goonchat
WARNING("Browser [title] tried to open with a null ID")
to_chat(user, span_userdanger("The [title] browser you tried to open failed a sanity check! Please report this on GitHub!"))
return
var/window_size = ""
if(width && height)
if(user.client?.prefs?.read_preference(/datum/preference/toggle/ui_scale))
var/scaling = user.client.window_scaling
window_size = "size=[width * scaling]x[height * scaling];"
else
window_size = "size=[width]x[height];"
var/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common)
common_asset.send(user)
if (length(stylesheets))
SSassets.transport.send_assets(user, stylesheets)
if (length(scripts))
SSassets.transport.send_assets(user, scripts)
DIRECT_OUTPUT(user, browse(get_content(), "window=[window_id];[window_size][window_options]"))
if (use_on_close)
setup_onclose()
/datum/browser/proc/setup_onclose()
set waitfor = 0 //winexists sleeps, so we don't need to.
for (var/i in 1 to 10)
if (!user?.client || !winexists(user, window_id))
continue
var/atom/send_ref
if(source_ref)
send_ref = source_ref.resolve()
if(!send_ref)
source_ref = null
onclose(user, window_id, send_ref)
/datum/browser/proc/close()
if(!isnull(window_id))//null check because this can potentially nuke goonchat
user << browse(null, "window=[window_id]")
else
WARNING("Browser [title] tried to close with a null ID")
/datum/browser/modal/alert/New(user, message, title, button_1 = "Ok", button_2, button_3, steal_focus = TRUE, timeout = 600 SECONDS)
if (!user)
return
var/list/display_list = list()
display_list += {"
[message]
[button_1]"}
if (button_2)
display_list += {"[button_2]"}
if (button_3)
display_list += {"[button_3]"}
display_list += {"
"}
..(user, ckey("[user]-[message]-[title]-[world.time]-[rand(1,10000)]"), title, 350, 150, src, steal_focus, timeout)
set_content(display_list.Join())
/datum/browser/modal/alert/Topic(href,href_list)
if (href_list["close"] || !user || !user.client)
open_time = 0
return
if (href_list["button"])
var/button = text2num(href_list["button"])
if (button <= 3 && button >= 1)
selected_button = button
open_time = 0
close()
/**
* **DEPRECATED: USE tgui_alert(...) INSTEAD**
*
* Designed as a drop in replacement for alert(); functions the same. (outside of needing user specified)
* Arguments:
* * user - The user to show the alert to.
* * message - The textual body of the alert.
* * title - The title of the alert's window.
* * button_1 - The first button option.
* * button_2 - The second button option.
* * button_3 - The third button option.
* * steal_focus - Boolean operator controlling if the alert will steal the user's window focus.
* * timeout - The timeout of the window, after which no responses will be valid.
*/
/proc/tg_alert(mob/user, message, title, button_1 = "Ok", button_2, button_3, steal_focus = TRUE, timeout = 600 SECONDS)
if (!user)
user = usr
if (!ismob(user))
if (!istype(user, /client))
return
var/client/user_client = user
user = user_client.mob
// Get user's response using a modal
var/datum/browser/modal/alert/window = new(user, message, title, button_1, button_2, button_3, steal_focus, timeout)
window.open()
window.wait()
switch(window.selected_button)
if (1)
return button_1
if (2)
return button_2
if (3)
return button_3
/datum/browser/modal
var/open_time = 0
var/timeout
var/selected_button = 0
var/steal_focus
/datum/browser/modal/New(user, window_id, title = 0, width = 0, height = 0, atom/source = null, steal_focus = TRUE, timeout = 600 SECONDS)
..()
src.steal_focus = steal_focus
if (!src.steal_focus)
window_options += "focus=false;"
src.timeout = timeout
/datum/browser/modal/close()
. = ..()
open_time = 0
/datum/browser/modal/open(use_on_close)
set waitfor = FALSE
open_time = world.time
use_on_close = TRUE
if (steal_focus)
. = ..()
else
var/focused_window = winget(user, null, "focus")
. = ..()
//waits for the window to show up client side before attempting to un-focus it
//winexists sleeps until it gets a reply from the client, so we don't need to bother sleeping
for (var/i in 1 to 10)
if (user && winexists(user, window_id))
if (focused_window)
winset(user, focused_window, "focus=true")
else
winset(user, "mapwindow", "focus=true")
break
if (timeout)
addtimer(CALLBACK(src, PROC_REF(close)), timeout)
/datum/browser/modal/proc/wait()
while (open_time && selected_button <= 0 && (!timeout || open_time + timeout > world.time))
stoplag(1)
/datum/browser/modal/list_picker
var/values_list = list()
/datum/browser/modal/list_picker/New(user, message, title, button_1 = "Ok", button_2, button_3, steal_focus = TRUE, timeout = FALSE, list/values, input_type = "checkbox", width, height, slide_color)
if (!user)
return
var/list/display_list = list()
display_list += {"