[MIRROR] View Variables Dark Mode (#10987)

Co-authored-by: ShadowLarkens <shadowlarkens@gmail.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-05-31 18:02:13 -07:00
committed by GitHub
parent 064422fbc3
commit 3118b7aad8
9 changed files with 189 additions and 36 deletions

View File

@@ -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)

View File

@@ -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])

View File

@@ -9,9 +9,9 @@
name = data_list[name] //name is really the index until this line
else
value = data_list[name]
header = "<li style='backgroundColor:white'>(<a href='byond://?_src_=vars;[HrefToken()];[VV_HK_LIST_EDIT]=1;target=\ref[DA];index=[index]'>E</a>) (<a href='byond://?_src_=vars;[HrefToken()];[VV_HK_LIST_CHANGE]=1;target=\ref[DA];index=[index]'>C</a>) (<a href='byond://?_src_=vars;[HrefToken()];[VV_HK_LIST_REMOVE]=1;target=\ref[DA];index=[index]'>-</a>) "
header = "<li>(<a href='byond://?_src_=vars;[HrefToken()];[VV_HK_LIST_EDIT]=1;target=\ref[DA];index=[index]'>E</a>) (<a href='byond://?_src_=vars;[HrefToken()];[VV_HK_LIST_CHANGE]=1;target=\ref[DA];index=[index]'>C</a>) (<a href='byond://?_src_=vars;[HrefToken()];[VV_HK_LIST_REMOVE]=1;target=\ref[DA];index=[index]'>-</a>) "
else
header = "<li style='backgroundColor:white'>(<a href='byond://?_src_=vars;[HrefToken()];datumedit=\ref[DA];varnameedit=[name]'>E</a>) (<a href='byond://?_src_=vars;[HrefToken()];datumchange=\ref[DA];varnamechange=[name]'>C</a>) (<a href='byond://?_src_=vars;[HrefToken()];datummass=\ref[DA];varnamemass=[name]'>M</a>) "
header = "<li>(<a href='byond://?_src_=vars;[HrefToken()];datumedit=\ref[DA];varnameedit=[name]'>E</a>) (<a href='byond://?_src_=vars;[HrefToken()];datumchange=\ref[DA];varnamechange=[name]'>C</a>) (<a href='byond://?_src_=vars;[HrefToken()];datummass=\ref[DA];varnamemass=[name]'>M</a>) "
else
header = "<li>"

View File

@@ -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\]" : "<img src='vv[hash].png'></td><td>"
title = "[D] (\ref[D]) = [type]"
var/formatted_type = replacetext("[type]", "/", "<wbr>/")
var/sprite_text
if(sprite)
sprite_text = "<img src='vv[hash].png'></td><td>"
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 = {"
<html>
<!DOCTYPE html>
<html class="[dark ? "dark" : ""]">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="utf-8" />
<title>[title]</title>
<style>
body {
font-family: Verdana, sans-serif;
font-size: 9pt;
}
.value {
font-family: "Courier New", monospace;
font-size: 8pt;
}
</style>
<link rel="stylesheet" type="text/css" href="[SSassets.transport.get_asset_url("view_variables.css")]">
[!ui_scale && window_scaling ? "<style>body {zoom: [100 / window_scaling]%;}</style>" : ""]
</head>
<body onload='selectTextField()' onkeydown='return handle_keydown()' onkeyup='handle_keyup()'>
@@ -228,8 +258,9 @@
</tr>
</table>
<div align='center'>
"} + span_bold(span_small("[formatted_type]")) + {"
<span id='marked'>[marked]</span>
<b><font size='1'>[formatted_type]</font></b>
<br><b><font size='1'>[ref_line]</font></b>
<span id='marked'>[marked_line]</span>
<span id='varedited'>[varedited_line]</span>
<span id='deleted'>[deleted_line]</span>
</div>
@@ -252,9 +283,9 @@
</table>
</div>
<hr>
"} + span_small(span_bold("E") + " - Edit, tries to determine the variable type by itself.<br>") + {"
"} + span_small(span_bold("C") + " - Change, asks you for the var type first.<br>") + {"
"} + span_small(span_bold("M") + " - Mass modify: changes this variable for all objects of this type.<br>") + {"
<b>E</b> - Edit, tries to determine the variable type by itself.<br>
<b>C</b> - Change, asks you for the var type first.<br>
<b>M</b> - Mass modify: changes this variable for all objects of this type.<br>
<hr>
<table width='100%'>
<tr>
@@ -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

View File

@@ -0,0 +1,4 @@
/datum/asset/simple/vv
assets = list(
"view_variables.css" = 'html/admin/view_variables.css'
)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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<string> = {
name: 'OOC Color',
category: 'ADMIN',

View File

@@ -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"