This commit is contained in:
Letter N
2021-02-12 18:36:12 +08:00
parent 5b0a1077f4
commit 51b4172509
31 changed files with 302 additions and 125 deletions

View File

@@ -1,4 +1,4 @@
/**
/*!
* External tgui definitions, such as src_object APIs.
*
* Copyright (c) 2020 Aleksej Komarov
@@ -71,12 +71,13 @@
* required action string The action/button that has been invoked by the user.
* required params list A list of parameters attached to the button.
*
* return bool If the UI should be updated or not.
* return bool If the user's input has been handled and the UI should update.
*/
/datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
// SHOULD_CALL_PARENT(TRUE)
// If UI is not interactive or usr calling Topic is not the UI user, bail.
if(!ui || ui.status != UI_INTERACTIVE)
return 1
return TRUE
/**
* public
@@ -157,7 +158,7 @@
// Name the verb, and hide it from the user panel.
set name = "uiclose"
set hidden = TRUE
var/mob/user = src && src.mob
var/mob/user = src?.mob
if(!user)
return
// Close all tgui datums based on window_id.

View File

@@ -1,4 +1,4 @@
/**
/*!
* Base state and helpers for states. Just does some sanity checks,
* implement a proper state for in-depth checks.
*
@@ -83,27 +83,12 @@
return ..()
/mob/living/silicon/robot/shared_ui_interaction(src_object)
// Disable UIs if the Borg is unpowered or locked.
if(!cell || cell.charge <= 0 || locked_down)
// Disable UIs if the object isn't installed in the borg AND the borg is either locked, has a dead cell, or no cell.
var/atom/device = src_object
if((istype(device) && device.loc != src) && (!cell || cell.charge <= 0 || locked_down))
return UI_DISABLED
return ..()
/**
* public
*
* Check the distance for a living mob.
* Really only used for checks outside the context of a mob.
* Otherwise, use shared_living_ui_distance().
*
* required src_object The object which owns the UI.
* required user mob The mob who opened/is using the UI.
*
* return UI_state The state of the UI.
*/
/atom/proc/contents_ui_distance(src_object, mob/living/user)
// Just call this mob's check.
return user.shared_living_ui_distance(src_object)
/**
* public
*

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: admin_state
*
* Checks that the user is an admin, end-of-story.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(admin_state, /datum/ui_state/admin_state, new)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: always_state
*
* Always grants the user UI_INTERACTIVE. Period.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(always_state, /datum/ui_state/always_state, new)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: conscious_state
*
* Only checks if the user is conscious.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(conscious_state, /datum/ui_state/conscious_state, new)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: contained_state
*
* Checks that the user is inside the src_object.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(contained_state, /datum/ui_state/contained_state, new)

View File

@@ -1,11 +1,13 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: deep_inventory_state
*
* Checks that the src_object is in the user's deep
* (backpack, box, toolbox, etc) inventory.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(deep_inventory_state, /datum/ui_state/deep_inventory_state, new)

View File

@@ -1,11 +1,13 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: default_state
*
* Checks a number of things -- mostly physical distance for humans
* and view for robots.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
@@ -18,15 +20,10 @@ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
/mob/living/default_can_use_topic(src_object)
. = shared_ui_interaction(src_object)
if(. > UI_CLOSE && loc)
. = min(., loc.contents_ui_distance(src_object, src)) // Check the distance...
if(. == UI_INTERACTIVE) // Non-human living mobs can only look, not touch.
return UI_UPDATE
/mob/living/carbon/human/default_can_use_topic(src_object)
. = shared_ui_interaction(src_object)
if(. > UI_CLOSE)
if(. > UI_CLOSE && loc) //must not be in nullspace.
. = min(., shared_living_ui_distance(src_object)) // Check the distance...
if(. == UI_INTERACTIVE && !IsAdvancedToolUser(src)) // unhandy living mobs can only look, not touch.
return UI_UPDATE
/mob/living/silicon/robot/default_can_use_topic(src_object)
. = shared_ui_interaction(src_object)
@@ -49,14 +46,9 @@ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
return UI_INTERACTIVE
return UI_CLOSE
/mob/living/simple_animal/default_can_use_topic(src_object)
. = shared_ui_interaction(src_object)
if(. > UI_CLOSE)
. = min(., shared_living_ui_distance(src_object)) //simple animals can only use things they're near.
/mob/living/silicon/pai/default_can_use_topic(src_object)
// pAIs can only use themselves and the owner's radio.
if((src_object == src || src_object == radio) && !stat)
return UI_INTERACTIVE
else
return ..()
return min(..(), UI_UPDATE)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: hands_state
*
* Checks that the src_object is in the user's hands.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(hands_state, /datum/ui_state/hands_state, new)

View File

@@ -1,11 +1,13 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: human_adjacent_state
*
* In addition to default checks, only allows interaction for a
* human adjacent user.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(human_adjacent_state, /datum/ui_state/human_adjacent_state, new)

View File

@@ -1,11 +1,13 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: inventory_state
*
* Checks that the src_object is in the user's top-level
* (hand, ear, pocket, belt, etc) inventory.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(inventory_state, /datum/ui_state/inventory_state, new)

View File

@@ -1,10 +1,12 @@
/**
* tgui state: language_menu_state
*
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: language_menu_state
*/
GLOBAL_DATUM_INIT(language_menu_state, /datum/ui_state/language_menu, new)
/datum/ui_state/language_menu/can_use_topic(src_object, mob/user)

View File

@@ -0,0 +1,13 @@
/**
* tgui state: new_player_state
*
* Checks that the user is a new_player, or if user is an admin
*/
GLOBAL_DATUM_INIT(new_player_state, /datum/ui_state/new_player_state, new)
/datum/ui_state/new_player_state/can_use_topic(src_object, mob/user)
if(isnewplayer(user) || check_rights_for(user.client, R_ADMIN))
return UI_INTERACTIVE
return UI_CLOSE

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: not_incapacitated_state
*
* Checks that the user isn't incapacitated
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(not_incapacitated_state, /datum/ui_state/not_incapacitated_state, new)
@@ -25,7 +27,7 @@ GLOBAL_DATUM_INIT(not_incapacitated_turf_state, /datum/ui_state/not_incapacitate
turf_check = no_turfs
/datum/ui_state/not_incapacitated_state/can_use_topic(src_object, mob/user)
if(user.stat)
if(user.stat != CONSCIOUS)
return UI_CLOSE
if(user.incapacitated() || (turf_check && !isturf(user.loc)))
return UI_DISABLED

View File

@@ -1,11 +1,13 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: notcontained_state
*
* Checks that the user is not inside src_object, and then makes the
* default checks.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(notcontained_state, /datum/ui_state/notcontained_state, new)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: observer_state
*
* Checks that the user is an observer/ghost.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(observer_state, /datum/ui_state/observer_state, new)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: physical_state
*
* Short-circuits the default state to only check physical distance.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: self_state
*
* Only checks that the user and src_object are the same.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(self_state, /datum/ui_state/self_state, new)

View File

@@ -1,10 +1,12 @@
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
/**
* tgui state: z_state
*
* Only checks that the Z-level of the user and src_object are the same.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(z_state, /datum/ui_state/z_state, new)

View File

@@ -1,4 +1,4 @@
/**
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
@@ -50,7 +50,7 @@
*/
/datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y)
log_tgui(user,
"new [interface] fancy [user.client.prefs.tgui_fancy]",
"new [interface] fancy [user?.client?.prefs.tgui_fancy]",
src_object = src_object)
src.user = user
src.src_object = src_object
@@ -67,18 +67,20 @@
* public
*
* Open this UI (and initialize it with data).
*
* return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists.
*/
/datum/tgui/proc/open()
if(!user.client)
return null
return FALSE
if(window)
return null
return FALSE
process_status()
if(status < UI_UPDATE)
return null
return FALSE
window = SStgui.request_pooled_window(user)
if(!window)
return null
return FALSE
opened_at = world.time
window.acquire_lock(src)
if(!window.is_ready())
@@ -101,6 +103,8 @@
with_static_data = TRUE))
SStgui.on_open(src)
return TRUE
/**
* public
*
@@ -156,7 +160,7 @@
*/
/datum/tgui/proc/send_asset(datum/asset/asset)
if(!window)
CRASH("send_asset() can only be called after open().")
CRASH("send_asset() was called either without calling open() first or when open() did not return TRUE.")
return window.send_asset(asset)
/**
@@ -237,7 +241,7 @@
* Run an update cycle for this UI. Called internally by SStgui
* every second or so.
*/
/datum/tgui/process(force = FALSE)
/datum/tgui/process(delta_time, force = FALSE)
if(closing)
return
var/datum/host = src_object.ui_host(user)

View File

@@ -0,0 +1,160 @@
/**
* 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, title, list/buttons, timeout = 60 SECONDS)
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_modal/alert = new(user, message, title, buttons, timeout)
alert.ui_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. Set to zero for no timeout.
*/
/proc/tgui_alert_async(mob/user, message, title, list/buttons, datum/callback/callback, timeout = 60 SECONDS)
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_modal/async/alert = new(user, message, title, buttons, callback, timeout)
alert.ui_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_modal
/// 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_modal/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_modal/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_modal/proc/wait()
while (!choice && !closed)
stoplag(1)
/datum/tgui_modal/ui_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_modal/ui_close(mob/user)
. = ..()
closed = TRUE
/datum/tgui_modal/ui_state(mob/user)
return GLOB.always_state
/datum/tgui_modal/ui_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_modal/ui_act(action, list/params)
. = ..()
if (.)
return
switch(action)
if("choose")
if (!(params["choice"] in buttons))
return
choice = params["choice"]
SStgui.close_uis(src)
return TRUE
/**
* # async tgui_modal
*
* An asynchronous version of tgui_modal to be used with callbacks instead of waiting on user responses.
*/
/datum/tgui_modal/async
/// The callback to be invoked by the tgui_modal upon having a choice made.
var/datum/callback/callback
/datum/tgui_modal/async/New(mob/user, message, title, list/buttons, callback, timeout)
..(user, title, message, buttons, timeout)
src.callback = callback
/datum/tgui_modal/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
/datum/tgui_modal/async/ui_close(mob/user)
. = ..()
qdel(src)
/datum/tgui_modal/async/ui_act(action, list/params)
. = ..()
if (!. || choice == null)
return
callback.InvokeAsync(choice)
qdel(src)
/datum/tgui_modal/async/wait()
return

View File

@@ -1,4 +1,4 @@
/**
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/

View File

@@ -1,4 +1,4 @@
/**
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/

View File

@@ -1,4 +1,4 @@
/**
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
@@ -12,19 +12,21 @@
set name = "Fix chat"
set category = "OOC"
var/action
log_tgui(src, "Started fixing.",
context = "verb/fix_tgui_panel")
// Not ready
if(!tgui_panel?.is_ready())
log_tgui(src, "Panel is not ready",
context = "verb/fix_tgui_panel")
tgui_panel.window.send_message("ping", force = TRUE)
action = alert(src, "Method: Pinging the panel.\nWait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if(action == "Fixed")
log_tgui(src, "Fixed by sending a ping",
context = "verb/fix_tgui_panel")
return
// Catch all solution
log_tgui(src, "Started fixing.", context = "verb/fix_tgui_panel")
nuke_chat()
// Failed to fix
action = alert(src, "Did that work?", "", "Yes", "No, switch to old ui")
if (action == "No, switch to old ui")
winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
winset(src, "browseroutput", "is-disabled=1;is-visible=0")
log_tgui(src, "Failed to fix.", context = "verb/fix_tgui_panel")
/client/proc/nuke_chat()
// Catch all solution (kick the whole thing in the pants)
winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
winset(src, "browseroutput", "is-disabled=1;is-visible=0")
if(!tgui_panel || !istype(tgui_panel))
log_tgui(src, "tgui_panel datum is missing",
context = "verb/fix_tgui_panel")
@@ -33,15 +35,3 @@
// Force show the panel to see if there are any errors
winset(src, "output", "is-disabled=1&is-visible=0")
winset(src, "browseroutput", "is-disabled=0;is-visible=1")
action = alert(src, "Method: Reinitializing the panel.\nWait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if(action == "Fixed")
log_tgui(src, "Fixed by calling 'initialize'",
context = "verb/fix_tgui_panel")
return
// Failed to fix
action = alert(src, "Welp, I'm all out of ideas. Try closing BYOND and reconnecting.\nWe could also disable tgui_panel and re-enable the old UI", "", "Thanks anyways", "Switch to old UI")
if (action == "Switch to old UI")
winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
winset(src, "browseroutput", "is-disabled=1;is-visible=0")
log_tgui(src, "Failed to fix.",
context = "verb/fix_tgui_panel")

View File

@@ -1,4 +1,4 @@
/**
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/

View File

@@ -1,4 +1,4 @@
/**
/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
@@ -55,7 +55,7 @@
*/
/datum/tgui_panel/proc/on_initialize_timed_out()
// Currently does nothing but sending a message to old chat.
SEND_TEXT(client, "<span class=\"userdanger\">Failed to load fancy chat, reverting to old chat. Certain features won't work.</span>")
SEND_TEXT(client, "<span class=\"userdanger\">Failed to load fancy chat, click <a href='?src=[REF(src)];reload_tguipanel=1'>HERE</a> to attempt to reload it.</span>")
/**
* private

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -3512,6 +3512,7 @@
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\states.dm"
#include "code\modules\tgui\tgui.dm"
#include "code\modules\tgui\tgui_alert.dm"
#include "code\modules\tgui\tgui_window.dm"
#include "code\modules\tgui\states\admin.dm"
#include "code\modules\tgui\states\always.dm"
@@ -3524,6 +3525,7 @@
#include "code\modules\tgui\states\human_adjacent.dm"
#include "code\modules\tgui\states\inventory.dm"
#include "code\modules\tgui\states\language_menu.dm"
#include "code\modules\tgui\states\new_player.dm"
#include "code\modules\tgui\states\not_incapacitated.dm"
#include "code\modules\tgui\states\notcontained.dm"
#include "code\modules\tgui\states\observer.dm"