From 3118b7aad801017a72477a3cc2bbeb0595960076 Mon Sep 17 00:00:00 2001 From: CHOMPStation2StaffMirrorBot <94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com> Date: Sat, 31 May 2025 18:02:13 -0700 Subject: [PATCH] [MIRROR] View Variables Dark Mode (#10987) Co-authored-by: ShadowLarkens --- code/__defines/is_helpers.dm | 4 + code/_helpers/icons.dm | 22 ++++ .../admin/view_variables/debug_variables.dm | 4 +- .../admin/view_variables/view_variables.dm | 102 ++++++++++++------ code/modules/asset_cache/assets/vv.dm | 4 + .../client/preferences/types/game/admin.dm | 12 +++ html/admin/view_variables.css | 61 +++++++++++ .../features/game_preferences/admin.tsx | 15 +++ vorestation.dme | 1 + 9 files changed, 189 insertions(+), 36 deletions(-) create mode 100644 code/modules/asset_cache/assets/vv.dm create mode 100644 html/admin/view_variables.css diff --git a/code/__defines/is_helpers.dm b/code/__defines/is_helpers.dm index 31eebed78b..98564ed4b4 100644 --- a/code/__defines/is_helpers.dm +++ b/code/__defines/is_helpers.dm @@ -3,6 +3,10 @@ #define isweakref(A) istype(A, /datum/weakref) //#define islist(D) istype(D, /list) //Built in +#define isimage(thing) (istype(thing, /image)) + +GLOBAL_VAR_INIT(magic_appearance_detecting_image, new /image) // appearances are awful to detect safely, but this seems to be the best way ~ninjanomnom +#define isappearance(thing) (!isimage(thing) && !ispath(thing) && istype(GLOB.magic_appearance_detecting_image, thing)) //--------------- #define isatom(D) istype(D, /atom) diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm index 37ad20b0f9..3db67a1eb5 100644 --- a/code/_helpers/icons.dm +++ b/code/_helpers/icons.dm @@ -757,3 +757,25 @@ GLOBAL_LIST_EMPTY(cached_examine_icons) var/icon/I = getFlatIcon(thing, force_south = force_south) return icon2html(I, target, sourceonly = sourceonly) + +/// Checks whether a given icon state exists in a given icon file. If `file` and `state` both exist, +/// this will return `TRUE` - otherwise, it will return `FALSE`. +/// +/// If you want a stack trace to be output when the given state/file doesn't exist, use +/// `/proc/icon_exists_or_scream()`. +/proc/icon_exists(file, state) + var/static/list/icon_states_cache = list() + if(isnull(file) || isnull(state)) + return FALSE //This is common enough that it shouldn't panic, imo. + + if(isnull(icon_states_cache[file])) + icon_states_cache[file] = list() + var/file_string = "[file]" + if(isfile(file) && length(file_string)) // ensure that it's actually a file, and not a runtime icon + for(var/istate in json_decode(rustg_dmi_icon_states(file_string))) + icon_states_cache[file][istate] = TRUE + else // Otherwise, we have to use the slower BYOND proc + for(var/istate in icon_states(file)) + icon_states_cache[file][istate] = TRUE + + return !isnull(icon_states_cache[file][state]) diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm index 7be70af3f0..0a4a36ceb5 100644 --- a/code/modules/admin/view_variables/debug_variables.dm +++ b/code/modules/admin/view_variables/debug_variables.dm @@ -9,9 +9,9 @@ name = data_list[name] //name is really the index until this line else value = data_list[name] - header = "
  • (E) (C) (-) " + header = "
  • (E) (C) (-) " else - header = "
  • (E) (C) (M) " + header = "
  • (E) (C) (M) " else header = "
  • " diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index 770bd0b0a0..862f069326 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -1,3 +1,6 @@ +#define ICON_STATE_CHECKED 1 /// this dmi is checked. We don't check this one anymore. +#define ICON_STATE_NULL 2 /// this dmi has null-named icon_state, allowing it to show a sprite on vv editor. + /client/proc/debug_variables(datum/D in world) set category = "Debug.Investigate" set name = "View Variables" @@ -11,8 +14,14 @@ if(!D) return - var/islist = islist(D) - if (!islist && !istype(D)) + var/dark = usr.client.prefs ? usr.client.prefs.read_preference(/datum/preference/toggle/holder/vv_dark) : TRUE + var/use_gfi = usr.client.prefs ? usr.client.prefs.read_preference(/datum/preference/toggle/holder/vv_gfi) : TRUE + + var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/simple/vv) + asset_cache_datum.send(usr) + + var/islist = islist(D) || (!isdatum(D) && hascall(D, "Cut")) // Some special lists don't count as lists, but can be detected by if they have list procs + if(!islist && !isdatum(D)) return //VOREStation Edit Start - the rest of this proc in a spawn @@ -22,29 +31,57 @@ var/icon/sprite var/hash - var/type = /list - if (!islist) - type = D.type + var/type = islist ? /list : D.type + var/no_icon = FALSE - if(istype(D, /atom)) + if(isatom(D)) var/atom/AT = D - if(AT.icon && AT.icon_state) + if(use_gfi) + sprite = getFlatIcon(D) + if(!sprite) + no_icon = TRUE + else if(AT.icon && AT.icon_state) sprite = new /icon(AT.icon, AT.icon_state) - hash = md5(AT.icon) - hash = md5(hash + AT.icon_state) - src << browse_rsc(sprite, "vv[hash].png") + else + no_icon = TRUE + + else if(isimage(D)) + // icon_state=null shows first image even if dmi has no icon_state for null name. + // This list remembers which dmi has null icon_state, to determine if icon_state=null should display a sprite + // (NOTE: icon_state="" is correct, but saying null is obvious) + var/static/list/dmi_nullstate_checklist = list() + var/image/image_object = D + var/icon_filename_text = "[image_object.icon]" // "icon(null)" type can exist. textifying filters it. + if(icon_filename_text) + if(image_object.icon_state) + sprite = icon(image_object.icon, image_object.icon_state) + + else // it means: icon_state="" + if(!dmi_nullstate_checklist[icon_filename_text]) + dmi_nullstate_checklist[icon_filename_text] = ICON_STATE_CHECKED + if(icon_exists(image_object.icon, "")) + // this dmi has nullstate. We'll allow "icon_state=null" to show image. + dmi_nullstate_checklist[icon_filename_text] = ICON_STATE_NULL + + if(dmi_nullstate_checklist[icon_filename_text] == ICON_STATE_NULL) + sprite = icon(image_object.icon, image_object.icon_state) + + var/sprite_text + if(sprite) + hash = md5(sprite) + src << browse_rsc(sprite, "vv[hash].png") + sprite_text = no_icon ? "\[NO ICON\]" : "" title = "[D] (\ref[D]) = [type]" var/formatted_type = replacetext("[type]", "/", "/") - var/sprite_text - if(sprite) - sprite_text = "" var/list/header = islist(D)? list(span_bold("/list")) : D.vv_get_header() - var/marked + var/ref_line = "@[copytext(refid, 2, -1)]" // get rid of the brackets, add a @ prefix for copy pasting in asay + + var/marked_line if(holder && holder.marked_datum && holder.marked_datum == D) - marked = VV_MSG_MARKED + marked_line = VV_MSG_MARKED var/varedited_line = "" if(!islist && (D.datum_flags & DF_VAR_EDITED)) varedited_line = VV_MSG_EDITED @@ -86,35 +123,28 @@ var/ui_scale = prefs?.read_preference(/datum/preference/toggle/ui_scale) var/list/variable_html = list() - if (islist) + if(islist) var/list/L = D for (var/i in 1 to L.len) var/key = L[i] var/value - if (IS_NORMAL_LIST(L) && IS_VALID_ASSOC_KEY(key)) + if(IS_NORMAL_LIST(L) && IS_VALID_ASSOC_KEY(key)) value = L[key] variable_html += debug_variable(i, value, 0, D) else - names = sortList(names) for (var/V in names) if(D.can_vv_get(V)) variable_html += D.vv_get_var(V) var/html = {" - + + + + [title] - + [!ui_scale && window_scaling ? "" : ""] @@ -228,8 +258,9 @@
    - "} + span_bold(span_small("[formatted_type]")) + {" - [marked] + [formatted_type] +
    [ref_line] + [marked_line] [varedited_line] [deleted_line]
    @@ -252,9 +283,9 @@
    - "} + span_small(span_bold("E") + " - Edit, tries to determine the variable type by itself.
    ") + {" - "} + span_small(span_bold("C") + " - Change, asks you for the var type first.
    ") + {" - "} + span_small(span_bold("M") + " - Mass modify: changes this variable for all objects of this type.
    ") + {" + E - Edit, tries to determine the variable type by itself.
    + C - Change, asks you for the var type first.
    + M - Mass modify: changes this variable for all objects of this type.

    @@ -288,3 +319,6 @@ /client/proc/vv_update_display(datum/D, span, content) src << output("[span]:[content]", "variables\ref[D].browser:replace_span") + +#undef ICON_STATE_CHECKED +#undef ICON_STATE_NULL diff --git a/code/modules/asset_cache/assets/vv.dm b/code/modules/asset_cache/assets/vv.dm new file mode 100644 index 0000000000..0dd974c7c7 --- /dev/null +++ b/code/modules/asset_cache/assets/vv.dm @@ -0,0 +1,4 @@ +/datum/asset/simple/vv + assets = list( + "view_variables.css" = 'html/admin/view_variables.css' + ) diff --git a/code/modules/client/preferences/types/game/admin.dm b/code/modules/client/preferences/types/game/admin.dm index b90b2f7197..89ee5b6bd4 100644 --- a/code/modules/client/preferences/types/game/admin.dm +++ b/code/modules/client/preferences/types/game/admin.dm @@ -72,6 +72,18 @@ default_value = TRUE savefile_identifier = PREFERENCE_PLAYER +/datum/preference/toggle/holder/vv_dark + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "vvdark" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/toggle/holder/vv_gfi + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "vvgfi" + default_value = TRUE + savefile_identifier = PREFERENCE_PLAYER + /// The color admins will speak in for OOC. /datum/preference/color/ooc_color category = PREFERENCE_CATEGORY_GAME_PREFERENCES diff --git a/html/admin/view_variables.css b/html/admin/view_variables.css new file mode 100644 index 0000000000..21c6fcc6b3 --- /dev/null +++ b/html/admin/view_variables.css @@ -0,0 +1,61 @@ +body { + font-family: Verdana, sans-serif; + font-size: 9pt; +} +.value { + font-family: "Courier New", monospace; + font-size: 8pt; + display: inline-block; +} + +table.matrix { + border-collapse: collapse; border-spacing: 0; + font-size: 7pt; +} + +.matrix td{ + text-align: center; + padding: 0 1ex 0ex 1ex; +} + +table.matrixbrak { + border-collapse: collapse; border-spacing: 0; +} + +table.matrixbrak td.lbrak { + width: 0.8ex; + font-size: 50%; + border-top: solid 0.25ex black; + border-bottom: solid 0.25ex black; + border-left: solid 0.5ex black; + border-right: none; +} + +table.matrixbrak td.rbrak { + width: 0.8ex; + font-size: 50%; + border-top: solid 0.25ex black; + border-bottom: solid 0.25ex black; + border-right: solid 0.5ex black; + border-left: none; +} + +/* Dark Mode */ +.dark body { + background-color: #0d1117; + color: #f0f6fc; +} + +.dark a { + color: rgb(68, 147, 248); +} + +.dark select { + background-color: black; + color: white; +} + +.dark input { + background-color: black; + color: white; +} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx index df7b9e9c84..55e9cc3fe3 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx @@ -54,6 +54,21 @@ export const CHAT_ADSAY: FeatureToggle = { component: CheckboxInput, }; +export const vvdark: FeatureToggle = { + name: 'VV: Dark Theme', + category: 'ADMIN', + description: 'When enabled, View Variables will use a dark theme', + component: CheckboxInput, +}; + +export const vvgfi: FeatureToggle = { + name: 'VV: Use getFlatIcon', + category: 'ADMIN', + description: + 'When enabled, View Variables will use getFlatIcon for icon previews. Warning: Increases load time.', + component: CheckboxInput, +}; + export const ooccolor: Feature = { name: 'OOC Color', category: 'ADMIN', diff --git a/vorestation.dme b/vorestation.dme index 186bfce27e..0511f476eb 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -2166,6 +2166,7 @@ #include "code\modules\asset_cache\assets\preferences.dm" #include "code\modules\asset_cache\assets\tgfont.dm" #include "code\modules\asset_cache\assets\tgui.dm" +#include "code\modules\asset_cache\assets\vv.dm" #include "code\modules\asset_cache\assets\spritesheets\chat.dm" #include "code\modules\asset_cache\assets\spritesheets\chem_master.dm" #include "code\modules\asset_cache\assets\spritesheets\kitchen_recipes.dm"