From 28ed21319795bd4b0373dc553ebd236032574d1f Mon Sep 17 00:00:00 2001
From: Lex <41524861+Phoenix4O4@users.noreply.github.com>
Date: Sat, 11 Jan 2025 22:38:00 +0300
Subject: [PATCH] [PORT] [TGUI] Color picker (#2670)
## About The Pull Request
Port of the color selection TGUI interface from
BeeStation/Artea-Station.
https://github.com/BeeStation/BeeStation-Hornet/pull/9417
https://github.com/Artea-Station/Artea-Station-Server/pull/525
This implementation includes a port and adaptation to React, as TG has
discontinued the use of Inferno.
If the Input: Enable TGUI parameter is disabled in the UI Tab, the old
color selection mechanism will be used instead. The new interface
supports copying the color value in the #343231 format and allows
entering a 6-character hexadecimal color code.
Additionally, a color palette for selection is available.
## Why It's Good For The Game
The TGUI color picker is more adaptable and prettier.
## Proof Of Testing
Screenshots/Videos



## Changelog
:cl: itsmeow (original), RimiNosha, Phoenix404 (port)
add: Added TGUI-based color palette and replaced all usages of BYOND
color palettes with it (TGUI input preferences are still respected).
/:cl:
---
code/datums/browser.dm | 2 +-
code/datums/components/palette.dm | 2 +-
code/game/objects/items.dm | 2 +-
code/game/objects/items/frog_statue.dm | 2 +-
code/game/objects/items/rcd/RLD.dm | 2 +-
code/game/objects/items/stacks/wrap.dm | 4 +-
code/game/objects/structures/curtains.dm | 2 +-
code/game/objects/structures/dresser.dm | 8 +-
code/game/objects/structures/maintenance.dm | 2 +-
code/game/objects/structures/mirror.dm | 8 +-
code/modules/admin/greyscale_modify_menu.dm | 5 +-
code/modules/admin/verbs/light_debug.dm | 2 +-
.../admin/view_variables/filterrific.dm | 2 +-
.../admin/view_variables/get_variables.dm | 2 +-
code/modules/art/paintings.dm | 2 +-
code/modules/client/preferences.dm | 10 +-
code/modules/client/verbs/ooc.dm | 2 +-
code/modules/clothing/head/wig.dm | 2 +-
.../generic_skillchips/point.dm | 2 +-
.../mob/living/basic/guardian/guardian.dm | 2 +-
.../space_fauna/space_dragon/space_dragon.dm | 2 +-
code/modules/mob/living/living.dm | 2 +-
code/modules/mod/modules/modules_general.dm | 2 +-
.../computers/item/computer_ui.dm | 2 +-
.../guns/energy/kinetic_accelerator.dm | 2 +-
.../xenobiology/crossbreeding/_clothing.dm | 2 +-
.../xenobiology/crossbreeding/prismatic.dm | 2 +-
.../surgery/organs/internal/eyes/_eyes.dm | 5 +-
.../code/game/objects/items/dyekit.dm | 4 +-
.../code/game/objects/items/hhmirror.dm | 16 +-
.../middleware/limbs_and_markings.dm | 5 +-
.../mining/equipment/kinetic_crusher.dm | 2 +-
.../modules/carriers/code/carrier_tgui.dm | 4 +-
.../clothing/~donator/donator_clothing.dm | 2 +-
.../carbon/human/species/roundstartslime.dm | 9 +-
.../code/nifsofts/hivemind.dm | 2 +-
modular_skyrat/modules/salon/code/fur_dyer.dm | 12 +-
.../modules/salon/code/misc_items.dm | 6 +-
.../code/datums/components/vore/belly.dm | 2 +-
.../code/game/objects/items/holy_weapons.dm | 2 +-
.../code/modules/debug_tools/physgun.dm | 2 +-
.../debug_tools/toolgun_module/phys_color.dm | 2 +-
.../code/modules/tgui_input/color.dm | 174 ++++
tgstation.dme | 1 +
tgui/packages/common/colorpicker.ts | 502 ++++++++++++
tgui/packages/tgui/components/Interactive.tsx | 134 +++
tgui/packages/tgui/components/Pointer.tsx | 46 ++
tgui/packages/tgui/components/index.ts | 2 +
.../tgui/interfaces/ColorPickerModal.tsx | 767 ++++++++++++++++++
.../tgui/styles/interfaces/ColorPicker.scss | 153 ++++
tgui/packages/tgui/styles/main.scss | 3 +
51 files changed, 1865 insertions(+), 71 deletions(-)
create mode 100644 modular_zubbers/code/modules/tgui_input/color.dm
create mode 100644 tgui/packages/common/colorpicker.ts
create mode 100644 tgui/packages/tgui/components/Interactive.tsx
create mode 100644 tgui/packages/tgui/components/Pointer.tsx
create mode 100644 tgui/packages/tgui/interfaces/ColorPickerModal.tsx
create mode 100644 tgui/packages/tgui/styles/interfaces/ColorPicker.scss
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index b9d85955238..cf9a4c991c7 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -397,7 +397,7 @@
if ("number")
settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]") as num
if ("color")
- settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) as color
+ settings["mainsettings"][setting]["value"] = tgui_color_picker(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if ("boolean")
settings["mainsettings"][setting]["value"] = (settings["mainsettings"][setting]["value"] == "Yes") ? "No" : "Yes"
if ("ckey")
diff --git a/code/datums/components/palette.dm b/code/datums/components/palette.dm
index 2ae66f50b23..94014735d28 100644
--- a/code/datums/components/palette.dm
+++ b/code/datums/components/palette.dm
@@ -106,7 +106,7 @@
var/is_right_clicking = LAZYACCESS(params2list(params), RIGHT_CLICK)
var/index = text2num(choice)
if(is_right_clicking)
- var/chosen_color = input(user, "Pick new color", "[parent]", colors[index]) as color|null
+ var/chosen_color = tgui_color_picker(user, "Pick new color", "[parent]", colors[index]) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(chosen_color && !QDELETED(src) && !IS_DEAD_OR_INCAP(user) && user.is_holding(parent))
colors[index] = chosen_color
update_radial_list()
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index f7a1d8988f5..e5984c8cd39 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1687,7 +1687,7 @@
/// Common proc used by painting tools like spraycans and palettes that can access the entire 24 bits color space.
/obj/item/proc/pick_painting_tool_color(mob/user, default_color)
- var/chosen_color = input(user,"Pick new color", "[src]", default_color) as color|null
+ var/chosen_color = tgui_color_picker(user,"Pick new color", "[src]", default_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!chosen_color || QDELETED(src) || IS_DEAD_OR_INCAP(user) || !user.is_holding(src))
return
set_painting_tool_color(chosen_color)
diff --git a/code/game/objects/items/frog_statue.dm b/code/game/objects/items/frog_statue.dm
index d1f65dc4b2a..768ecf3602f 100644
--- a/code/game/objects/items/frog_statue.dm
+++ b/code/game/objects/items/frog_statue.dm
@@ -146,7 +146,7 @@
new_frog.name = frog_name
/obj/item/frog_contract/proc/select_frog_color(mob/user, mob/living/basic/leaper/new_frog)
- var/frog_color = input(user, "Select your frog's color!" , "Pet toad color", COLOR_GREEN) as color|null
+ var/frog_color = tgui_color_picker(user, "Select your frog's color!" , "Pet toad color", COLOR_GREEN) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(isnull(frog_color))
to_chat(user, span_warning("Please choose a valid color."))
select_frog_color(user, new_frog)
diff --git a/code/game/objects/items/rcd/RLD.dm b/code/game/objects/items/rcd/RLD.dm
index 2a99f535f42..068a1d10f7b 100644
--- a/code/game/objects/items/rcd/RLD.dm
+++ b/code/game/objects/items/rcd/RLD.dm
@@ -69,7 +69,7 @@
mode = GLOW_MODE
to_chat(user, span_notice("You change RLD's mode to 'Light Launcher'."))
if("Color Pick")
- var/new_choice = input(user,"","Choose Color",color_choice) as color
+ var/new_choice = tgui_color_picker(user, "", "Choose Color", color_choice) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_choice == null)
return
diff --git a/code/game/objects/items/stacks/wrap.dm b/code/game/objects/items/stacks/wrap.dm
index ec83b1b5efa..e55e968d855 100644
--- a/code/game/objects/items/stacks/wrap.dm
+++ b/code/game/objects/items/stacks/wrap.dm
@@ -53,8 +53,8 @@
set_greyscale(colors = list(generated_base_color, generated_ribbon_color))
/obj/item/stack/wrapping_paper/click_alt(mob/user)
- var/new_base = input(user, "", "Select a base color", color) as color
- var/new_ribbon = input(user, "", "Select a ribbon color", color) as color
+ var/new_base = tgui_color_picker(user, "", "Select a base color", color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
+ var/new_ribbon = tgui_color_picker(user, "", "Select a ribbon color", color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_base || !new_ribbon)
return CLICK_ACTION_BLOCKING
diff --git a/code/game/objects/structures/curtains.dm b/code/game/objects/structures/curtains.dm
index a571009d0a6..a39348046da 100644
--- a/code/game/objects/structures/curtains.dm
+++ b/code/game/objects/structures/curtains.dm
@@ -42,7 +42,7 @@
/obj/structure/curtain/attackby(obj/item/W, mob/user)
if (istype(W, /obj/item/toy/crayon))
- color = input(user,"","Choose Color",color) as color
+ color = tgui_color_picker(user, "", "Choose Color", color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
else
return ..()
diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm
index feb9cff2f00..c0452fd5a2a 100644
--- a/code/game/objects/structures/dresser.dm
+++ b/code/game/objects/structures/dresser.dm
@@ -46,7 +46,7 @@
if(new_undies)
dressing_human.underwear = new_undies
if("Underwear Color")
- var/new_underwear_color = input(dressing_human, "Choose your underwear color", "Underwear Color", dressing_human.underwear_color) as color|null
+ var/new_underwear_color = tgui_color_picker(dressing_human, "Choose your underwear color", "Underwear Color", dressing_human.underwear_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_underwear_color)
dressing_human.underwear_color = sanitize_hexcolor(new_underwear_color)
if("Undershirt")
@@ -59,11 +59,11 @@
dressing_human.socks = new_socks
//SKYRAT EDIT ADDITION BEGIN - Colorable Undershirt/Socks/Bras
if("Undershirt Color")
- var/new_undershirt_color = input(dressing_human, "Choose your undershirt color", "Undershirt Color", dressing_human.undershirt_color) as color|null
+ var/new_undershirt_color = tgui_color_picker(dressing_human, "Choose your undershirt color", "Undershirt Color", dressing_human.undershirt_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_undershirt_color)
dressing_human.undershirt_color = sanitize_hexcolor(new_undershirt_color)
if("Socks Color")
- var/new_socks_color = input(dressing_human, "Choose your socks color", "Socks Color", dressing_human.socks_color) as color|null
+ var/new_socks_color = tgui_color_picker(dressing_human, "Choose your socks color", "Socks Color", dressing_human.socks_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_socks_color)
dressing_human.socks_color = sanitize_hexcolor(new_socks_color)
@@ -73,7 +73,7 @@
dressing_human.bra = new_bra
if("Bra Color")
- var/new_bra_color = input(dressing_human, "Choose your Bra color", "Bra Color", dressing_human.bra_color) as color|null
+ var/new_bra_color = tgui_color_picker(dressing_human, "Choose your Bra color", "Bra Color", dressing_human.bra_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_bra_color)
dressing_human.bra_color = sanitize_hexcolor(new_bra_color)
diff --git a/code/game/objects/structures/maintenance.dm b/code/game/objects/structures/maintenance.dm
index d3eb552c46b..aca90467a9a 100644
--- a/code/game/objects/structures/maintenance.dm
+++ b/code/game/objects/structures/maintenance.dm
@@ -149,7 +149,7 @@ at the cost of risking a vicious bite.**/
var/altar_result = show_radial_menu(user, src, altar_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE)
switch(altar_result)
if("Change Color")
- var/chosen_color = input(user, "", "Choose Color", pants_color) as color|null
+ var/chosen_color = tgui_color_picker(usr, "", "Choose Color", pants_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!isnull(chosen_color) && user.can_perform_action(src))
pants_color = chosen_color
if("Create Artefact")
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 5fae9be3e88..90444805c93 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -178,7 +178,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
race_changer.skin_tone = new_s_tone
race_changer.dna.update_ui_block(DNA_SKIN_TONE_BLOCK)
else if(HAS_TRAIT(race_changer, TRAIT_MUTANT_COLORS) && !HAS_TRAIT(race_changer, TRAIT_FIXED_MUTANT_COLORS))
- var/new_mutantcolor = input(race_changer, "Choose your skin color:", "Race change", race_changer.dna.features["mcolor"]) as color|null
+ var/new_mutantcolor = tgui_color_picker(race_changer, "Choose your skin color:", "Race change", race_changer.dna.features["mcolor"]) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_mutantcolor)
var/list/mutant_hsv = rgb2hsv(new_mutantcolor)
@@ -224,7 +224,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
sexy.update_clothing(ITEM_SLOT_ICLOTHING) // update gender shaped clothing
/obj/structure/mirror/proc/change_eyes(mob/living/carbon/human/user)
- var/new_eye_color = input(user, "Choose your eye color", "Eye Color", user.eye_color_left) as color|null
+ var/new_eye_color = tgui_color_picker(user, "Choose your eye color", "Eye Color", user.eye_color_left) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(isnull(new_eye_color))
return TRUE
user.eye_color_left = sanitize_hexcolor(new_eye_color)
@@ -343,13 +343,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
if(hairchoice == "Style") //So you just want to use a mirror then?
return ..()
- var/new_hair_color = input(user, "Choose your hair color", "Hair Color", user.hair_color) as color|null
+ var/new_hair_color = tgui_color_picker(user, "Choose your hair color", "Hair Color", user.hair_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_hair_color)
user.set_haircolor(sanitize_hexcolor(new_hair_color), update = FALSE)
user.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK)
if(user.physique == MALE)
- var/new_face_color = input(user, "Choose your facial hair color", "Hair Color", user.facial_hair_color) as color|null
+ var/new_face_color = tgui_color_picker(user, "Choose your facial hair color", "Hair Color", user.facial_hair_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_face_color)
user.set_facial_haircolor(sanitize_hexcolor(new_face_color), update = FALSE)
user.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK)
diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm
index f84d3ea8aa8..d5ed2fb4937 100644
--- a/code/modules/admin/greyscale_modify_menu.dm
+++ b/code/modules/admin/greyscale_modify_menu.dm
@@ -166,12 +166,13 @@
if("pick_color")
var/group = params["color_index"]
- var/new_color = input(
+ //BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/new_color = tgui_color_picker(
usr,
"Choose color for greyscale color group [group]:",
"Greyscale Modification Menu",
split_colors[group]
- ) as color|null
+ ) //BUBBERSTATION EDIT END: TGUI COLOR PICKER
if(new_color)
split_colors[group] = new_color
queue_refresh()
diff --git a/code/modules/admin/verbs/light_debug.dm b/code/modules/admin/verbs/light_debug.dm
index 5738e06eeea..30caa79fa39 100644
--- a/code/modules/admin/verbs/light_debug.dm
+++ b/code/modules/admin/verbs/light_debug.dm
@@ -256,7 +256,7 @@ GLOBAL_LIST_EMPTY(light_debugged_atoms)
if("set_on")
parent.set_light(l_on = params["value"])
if("change_color")
- var/chosen_color = input(ui.user, "Pick new color", "[parent]", parent.light_color) as color|null
+ var/chosen_color = tgui_color_picker(ui.user, "Pick new color", "[parent]", parent.light_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(chosen_color)
parent.set_light(l_color = chosen_color)
if("set_power")
diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm
index a997d520477..3ec03fa6153 100644
--- a/code/modules/admin/view_variables/filterrific.dm
+++ b/code/modules/admin/view_variables/filterrific.dm
@@ -67,7 +67,7 @@
target.add_filter(params["name"], old_filter_data["priority"], new_filter_data)
. = TRUE
if("modify_color_value")
- var/new_color = input(usr, "Pick new filter color", "Filteriffic Colors!") as color|null
+ var/new_color = tgui_color_picker(usr, "Pick new filter color", "Filteriffic Colors!") // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_color)
target.transition_filter(params["name"], list("color" = new_color), 4)
. = TRUE
diff --git a/code/modules/admin/view_variables/get_variables.dm b/code/modules/admin/view_variables/get_variables.dm
index d493831da67..7794f4a722b 100644
--- a/code/modules/admin/view_variables/get_variables.dm
+++ b/code/modules/admin/view_variables/get_variables.dm
@@ -331,7 +331,7 @@
.["value"] = D
if(VV_COLOR)
- .["value"] = input("Enter new color:", "Color", current_value) as color|null
+ .["value"] = tgui_color_picker("Enter new color:", "Color", current_value) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(.["value"] == null)
.["class"] = null
return
diff --git a/code/modules/art/paintings.dm b/code/modules/art/paintings.dm
index 1c11e0f8086..f757b236684 100644
--- a/code/modules/art/paintings.dm
+++ b/code/modules/art/paintings.dm
@@ -207,7 +207,7 @@
return FALSE
//I'd have this done inside the signal, but that'd have to be asynced,
//while we want the UI to be updated after the color is chosen, not before.
- var/chosen_color = input(user, "Pick new color", painting_implement, params["old_color"]) as color|null
+ var/chosen_color = tgui_color_picker(user, "Pick new color", painting_implement, params["old_color"]) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!chosen_color || IS_DEAD_OR_INCAP(user) || !user.is_holding(painting_implement))
return FALSE
SEND_SIGNAL(painting_implement, COMSIG_PAINTING_TOOL_PALETTE_COLOR_CHANGED, chosen_color, params["color_index"])
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 0b910c18553..0d458f723af 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -295,12 +295,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/default_value = read_preference(requested_preference.type)
// Yielding
- var/new_color = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/new_color = tgui_color_picker(
usr,
"Select new color",
null,
default_value || COLOR_WHITE,
- ) as color | null
+ ) // BUBBERSTATION EDIT END: TGUI COLOR PICKER
if (!new_color)
return FALSE
@@ -336,12 +337,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/default_value = default_value_list[index_key]
// Yielding
- var/new_color = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/new_color = tgui_color_picker(
usr,
"Select new color",
null,
default_value || COLOR_WHITE,
- ) as color | null
+ ) // BUBBERSTATION EDIT END: TGUI COLOR PICKER
if (!new_color)
return FALSE
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index b10d47c2190..5e8f7f3acc9 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -145,7 +145,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
return
ADMIN_VERB(set_ooc_color, R_FUN, "Set Player OOC Color", "Modifies the global OOC color.", ADMIN_CATEGORY_SERVER)
- var/newColor = input(user, "Please select the new player OOC color.", "OOC color") as color|null
+ var/newColor = tgui_color_picker(user, "Please select the new player OOC color.", "OOC color") // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(isnull(newColor))
return
var/new_color = sanitize_color(newColor)
diff --git a/code/modules/clothing/head/wig.dm b/code/modules/clothing/head/wig.dm
index 10d69d2457c..45d654dea76 100644
--- a/code/modules/clothing/head/wig.dm
+++ b/code/modules/clothing/head/wig.dm
@@ -50,7 +50,7 @@
/obj/item/clothing/head/wig/attack_self(mob/user)
var/new_style = tgui_input_list(user, "Select a hairstyle", "Wig Styling", SSaccessories.hairstyles_list - "Bald")
- var/newcolor = adjustablecolor ? input(usr,"","Choose Color",color) as color|null : null
+ var/newcolor = adjustablecolor ? tgui_color_picker(usr, "", "Choose Color", color) : null // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!user.can_perform_action(src))
return
if(new_style && new_style != hairstyle)
diff --git a/code/modules/library/skill_learning/generic_skillchips/point.dm b/code/modules/library/skill_learning/generic_skillchips/point.dm
index ba6a2e3e236..f0ef3836382 100644
--- a/code/modules/library/skill_learning/generic_skillchips/point.dm
+++ b/code/modules/library/skill_learning/generic_skillchips/point.dm
@@ -72,7 +72,7 @@
build_all_button_icons(update_flags = UPDATE_BUTTON_ICON, force = TRUE)
/datum/action/change_pointer_color/proc/pick_color(mob/user)
- var/ncolor = input(owner, "Pick new color", "Pointer Color", arrow_color) as color|null
+ var/ncolor = tgui_color_picker(owner, "Pick new color", "Pointer Color", arrow_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(user != owner || !IsAvailable(feedback = TRUE))
return
arrow_color = ncolor
diff --git a/code/modules/mob/living/basic/guardian/guardian.dm b/code/modules/mob/living/basic/guardian/guardian.dm
index 9ddf2c1fc31..6ab024a6823 100644
--- a/code/modules/mob/living/basic/guardian/guardian.dm
+++ b/code/modules/mob/living/basic/guardian/guardian.dm
@@ -137,7 +137,7 @@
/mob/living/basic/guardian/proc/guardian_recolour()
if (isnull(client))
return
- var/chosen_guardian_colour = input(src, "What would you like your colour to be?", "Choose Your Colour", "#ffffff") as color|null
+ var/chosen_guardian_colour = tgui_color_picker(src, "What would you like your colour to be?", "Choose Your Colour", "#ffffff") // BUBBERSTATION EDIT: TGUI COLOR PICKER
if (isnull(chosen_guardian_colour)) //redo proc until we get a color
to_chat(src, span_warning("Invalid colour, please try again."))
return guardian_recolour()
diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
index dac119e843b..347d3cec389 100644
--- a/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
+++ b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
@@ -134,7 +134,7 @@
/// Select scale colour with the colour picker
/mob/living/basic/space_dragon/proc/select_colour()
- chosen_colour = input(src, "What colour would you like to be?" ,"Colour Selection", COLOR_WHITE) as color|null
+ chosen_colour = tgui_color_picker(src, "What colour would you like to be?" , "Colour Selection", COLOR_WHITE) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!chosen_colour) // Redo proc until we get a color
to_chat(src, span_warning("Not a valid colour, please try again."))
select_colour()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 71d29e44e55..d639fb91ea3 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2848,7 +2848,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
if(picked_theme == "Random")
picked_theme = null //holopara code handles not having a theme by giving a random one
var/picked_name = tgui_input_text(admin, "Name the guardian, leave empty to let player name it.", "Guardian Controller", max_length = MAX_NAME_LEN)
- var/picked_color = input(admin, "Set the guardian's color, cancel to let player set it.", "Guardian Controller", "#ffffff") as color|null
+ var/picked_color = tgui_color_picker(admin, "Set the guardian's color, cancel to let player set it.", "Guardian Controller", "#ffffff") // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(tgui_alert(admin, "Confirm creation.", "Guardian Controller", list("Yes", "No")) != "Yes")
return
var/mob/living/basic/guardian/summoned_guardian = new picked_type(src, picked_theme)
diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm
index 3faf44fe2f0..2f60a4604f9 100644
--- a/code/modules/mod/modules/modules_general.dm
+++ b/code/modules/mod/modules/modules_general.dm
@@ -455,7 +455,7 @@
/obj/item/mod/module/flashlight/configure_edit(key, value)
switch(key)
if("light_color")
- value = input(usr, "Pick new light color", "Flashlight Color") as color|null
+ value = tgui_color_picker(usr, "Pick new light color", "Flashlight Color") // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!value)
return
if(is_color_dark(value, 50))
diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm
index d8b97c52301..67ebe0a0095 100644
--- a/code/modules/modular_computers/computers/item/computer_ui.dm
+++ b/code/modules/modular_computers/computers/item/computer_ui.dm
@@ -172,7 +172,7 @@
var/mob/user = usr
var/new_color
while(!new_color)
- new_color = input(user, "Choose a new color for [src]'s flashlight.", "Light Color",light_color) as color|null
+ new_color = tgui_color_picker(user, "Choose a new color for [src]'s flashlight.", "Light Color",light_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_color)
return
if(is_color_dark(new_color, 50) ) //Colors too dark are rejected
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index d94e7c67d13..963d5b22e1d 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -659,5 +659,5 @@
/obj/item/borg/upgrade/modkit/tracer/adjustable/proc/choose_bolt_color(mob/user)
set waitfor = FALSE
- var/new_color = input(user,"","Choose Color",bolt_color) as color|null
+ var/new_color = tgui_color_picker(usr, "", "Choose Color", bolt_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
bolt_color = new_color || bolt_color
diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
index caaafd05642..5ddb6be46b8 100644
--- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
@@ -78,7 +78,7 @@ Slimecrossing Armor
if(!IsAvailable(feedback = TRUE))
return
var/obj/item/clothing/glasses/prism_glasses/glasses = target
- var/new_color = input(owner, "Choose the lens color:", "Color change",glasses.glasses_color) as color|null
+ var/new_color = tgui_color_picker(owner, "Choose the lens color:", "Color change",glasses.glasses_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_color)
return
RemoveElement(/datum/element/wearable_client_colour, /datum/client_colour/glass_colour, ITEM_SLOT_EYES, glasses.glasses_color, glasses.forced_glass_color)
diff --git a/code/modules/research/xenobiology/crossbreeding/prismatic.dm b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
index 947323b0e47..aa917552835 100644
--- a/code/modules/research/xenobiology/crossbreeding/prismatic.dm
+++ b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
@@ -115,7 +115,7 @@ Prismatic extracts:
colour = SLIME_TYPE_RAINBOW
/obj/item/slimecross/prismatic/rainbow/attack_self(mob/user)
- var/newcolor = input(user, "Choose the slime color:", "Color change",paintcolor) as color|null
+ var/newcolor = tgui_color_picker(user, "Choose the slime color:", "Color change",paintcolor) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(user.get_active_held_item() != src || user.stat != CONSCIOUS || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
if(!newcolor)
diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm
index 71932c649d6..f87647cc6c4 100644
--- a/code/modules/surgery/organs/internal/eyes/_eyes.dm
+++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm
@@ -661,12 +661,13 @@
set_beam_range(new_range)
return TRUE
if("pick_color")
- var/new_color = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/new_color = tgui_color_picker(
usr,
"Choose eye color color:",
"High Luminosity Eyes Menu",
light_color_string
- ) as color|null
+ ) // BUBBERSTATION EDIT END: TGUI COLOR PICKER
if(new_color)
var/to_update = params["to_update"]
set_beam_color(new_color, to_update)
diff --git a/modular_skyrat/master_files/code/game/objects/items/dyekit.dm b/modular_skyrat/master_files/code/game/objects/items/dyekit.dm
index 37645e35828..44271e61a2c 100644
--- a/modular_skyrat/master_files/code/game/objects/items/dyekit.dm
+++ b/modular_skyrat/master_files/code/game/objects/items/dyekit.dm
@@ -24,7 +24,7 @@
var/dying_themselves = target == user
if(gradient_or_hair == DYE_OPTION_HAIR_COLOR)
- var/new_color = input(usr, "Choose a hair color:", "Character Preference", "#" + human_target.hair_color) as color|null
+ var/new_color = tgui_color_picker(usr, "Choose a hair color:", "Character Preference", "#" + human_target.hair_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_color || !user.can_perform_action(src, NEED_DEXTERITY))
return
@@ -48,7 +48,7 @@
if(!new_grad_style || !user.can_perform_action(src, NEED_DEXTERITY))
return
- var/new_grad_color = input(usr, "Choose a secondary hair color:", "Dye Spray", human_target.grad_color) as color|null
+ var/new_grad_color = tgui_color_picker(usr, "Choose a secondary hair color:", "Dye Spray", human_target.grad_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_grad_color || !user.can_perform_action(src, NEED_DEXTERITY))
return
diff --git a/modular_skyrat/master_files/code/game/objects/items/hhmirror.dm b/modular_skyrat/master_files/code/game/objects/items/hhmirror.dm
index 598cace44c2..4fa692677db 100644
--- a/modular_skyrat/master_files/code/game/objects/items/hhmirror.dm
+++ b/modular_skyrat/master_files/code/game/objects/items/hhmirror.dm
@@ -71,7 +71,7 @@
#define MIN_MCOLOR_VALUE 50
if(HAS_TRAIT(human_user, TRAIT_MUTANT_COLORS) && !HAS_TRAIT(human_user, TRAIT_FIXED_MUTANT_COLORS))
- var/new_mutantcolor = input(user, "Choose your skin color:", "Race change", human_user.dna.features["mcolor"]) as color|null
+ var/new_mutantcolor = tgui_color_picker(user, "Choose your skin color:", "Race change", human_user.dna.features["mcolor"]) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_mutantcolor)
var/mutantcolor_hsv = rgb2hsv(new_mutantcolor)
@@ -80,7 +80,7 @@
else
to_chat(human_user, span_notice("Invalid color. Your color is not bright enough."))
-
+
#undef MIN_MCOLOR_VALUE
human_user.update_body()
@@ -115,19 +115,19 @@
if(hairchoice == "Style") // So you just want to use a mirror then?
..()
else
- var/new_hair_color = input(human_user, "Choose your hair color", "Hair Color", human_user.hair_color) as color|null
+ var/new_hair_color = tgui_color_picker(human_user, "Choose your hair color", "Hair Color", human_user.hair_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_hair_color)
human_user.hair_color = sanitize_hexcolor(new_hair_color)
human_user.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK)
if(human_user.gender == "male")
- var/new_face_color = input(human_user, "Choose your facial hair color", "Hair Color", human_user.facial_hair_color) as color|null
+ var/new_face_color = tgui_color_picker(human_user, "Choose your facial hair color", "Hair Color", human_user.facial_hair_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_face_color)
human_user.facial_hair_color = sanitize_hexcolor(new_face_color)
human_user.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK)
human_user.update_body_parts()
if(BODY_ZONE_PRECISE_EYES)
- var/new_eye_color = input(human_user, "Choose your eye color", "Eye Color", human_user.eye_color_left) as color|null
+ var/new_eye_color = tgui_color_picker(human_user, "Choose your eye color", "Eye Color", human_user.eye_color_left) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return TRUE
if(new_eye_color)
@@ -199,19 +199,19 @@
if(hairchoice == "Style") // So you just want to use a mirror then?
..()
else
- var/new_hair_color = input(human_user, "Choose your hair color", "Hair Color", human_user.hair_color) as color|null
+ var/new_hair_color = tgui_color_picker(human_user, "Choose your hair color", "Hair Color", human_user.hair_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_hair_color)
human_user.hair_color = sanitize_hexcolor(new_hair_color)
human_user.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK)
if(human_user.gender == "male")
- var/new_face_color = input(human_user, "Choose your facial hair color", "Hair Color", human_user.facial_hair_color) as color|null
+ var/new_face_color = tgui_color_picker(human_user, "Choose your facial hair color", "Hair Color", human_user.facial_hair_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_face_color)
human_user.facial_hair_color = sanitize_hexcolor(new_face_color)
human_user.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK)
human_user.update_body_parts()
if(BODY_ZONE_PRECISE_EYES)
- var/new_eye_color = input(human_user, "Choose your eye color", "Eye Color", human_user.eye_color_left) as color|null
+ var/new_eye_color = tgui_color_picker(human_user, "Choose your eye color", "Eye Color", human_user.eye_color_left) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return TRUE
if(new_eye_color)
diff --git a/modular_skyrat/master_files/code/modules/client/preferences/middleware/limbs_and_markings.dm b/modular_skyrat/master_files/code/modules/client/preferences/middleware/limbs_and_markings.dm
index 4a7d5693447..acb72b7dacf 100644
--- a/modular_skyrat/master_files/code/modules/client/preferences/middleware/limbs_and_markings.dm
+++ b/modular_skyrat/master_files/code/modules/client/preferences/middleware/limbs_and_markings.dm
@@ -159,12 +159,13 @@
if(marking_id == "[limb_slot]_[marking_count]")
marking_entry_name = marking_entry
new_markings[marking_entry] = markings[marking_entry]
- var/new_color = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/new_color = tgui_color_picker(
usr,
"Select new color",
null,
preferences.body_markings[limb_slot][marking_entry_name][1],
- ) as color | null
+ ) // BUBBERSTATION EDIT END: TGUI COLOR PICKER
if(!new_color)
return TRUE
new_markings[marking_entry_name][1] = sanitize_hexcolor(new_color) // gets the new color from the picker
diff --git a/modular_skyrat/master_files/code/modules/mining/equipment/kinetic_crusher.dm b/modular_skyrat/master_files/code/modules/mining/equipment/kinetic_crusher.dm
index 5ffd3c5b795..94dcc19e70f 100644
--- a/modular_skyrat/master_files/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/modular_skyrat/master_files/code/modules/mining/equipment/kinetic_crusher.dm
@@ -211,7 +211,7 @@
return "very pretty colors to imbue the destabilizer shots"
/obj/item/crusher_trophy/watcher_eye/attack_self(mob/user, modifiers)
- var/chosen_color = input(user, "Pick a new color", "[src]", used_color) as color|null
+ var/chosen_color = tgui_color_picker(user, "Pick a new color", "[src]", used_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(chosen_color)
used_color = chosen_color
to_chat(user, span_notice("You recolor [src]."))
diff --git a/modular_skyrat/modules/carriers/code/carrier_tgui.dm b/modular_skyrat/modules/carriers/code/carrier_tgui.dm
index 076a12dc3f1..c738b835c9a 100644
--- a/modular_skyrat/modules/carriers/code/carrier_tgui.dm
+++ b/modular_skyrat/modules/carriers/code/carrier_tgui.dm
@@ -200,14 +200,14 @@
return TRUE
if("change_room_color")
- var/new_room_color = input(usr, "", "Choose Color", SOULCATCHER_DEFAULT_COLOR) as color
+ var/new_room_color = tgui_color_picker(usr, "", "Choose Color", SOULCATCHER_DEFAULT_COLOR) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_room_color)
return FALSE
target_room.room_color = new_room_color
if("change_overlay_color")
- var/new_overlay_color = input(usr, "", "Choose Color", SOULCATCHER_DEFAULT_COLOR) as color
+ var/new_overlay_color = tgui_color_picker(usr, "", "Choose Color", SOULCATCHER_DEFAULT_COLOR) // BUBBERSTATION EDIT: TGUI COLOR PICKER
// It's okay for us not to have an overlay color
target_room.overlay_color = new_overlay_color
diff --git a/modular_skyrat/modules/customization/modules/clothing/~donator/donator_clothing.dm b/modular_skyrat/modules/customization/modules/clothing/~donator/donator_clothing.dm
index 0fc718feab5..9514972f951 100644
--- a/modular_skyrat/modules/customization/modules/clothing/~donator/donator_clothing.dm
+++ b/modular_skyrat/modules/customization/modules/clothing/~donator/donator_clothing.dm
@@ -119,7 +119,7 @@
/obj/item/canvas/drawingtablet/ui_action_click(mob/user, action)
if(istype(action, /datum/action/item_action/dtselectcolor))
- currentcolor = input(user, "", "Choose Color", currentcolor) as color|null
+ currentcolor = tgui_color_picker(usr, "", "Choose Color", currentcolor) // BUBBERSTATION EDIT: TGUI COLOR PICKER
else if(istype(action, /datum/action/item_action/dtcolormenu))
var/list/selects = colors.Copy()
selects["Save"] = "Save"
diff --git a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm
index 78627bf7ff6..fa23e6a40b5 100644
--- a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm
+++ b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm
@@ -159,12 +159,13 @@
if("Tertiary")
color_target = "mcolor3"
- var/new_mutant_colour = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/new_mutant_colour = tgui_color_picker(
alterer,
- "Choose your character's new [color_choice = "All" ? "" : lowertext(color_choice)] color:",
+ "Choose your character's new [color_choice = "All" ? "" : LOWER_TEXT(color_choice)] color:",
"Form Alteration",
alterer.dna.features[color_target]
- ) as color|null
+ ) // BUBBERSTATION EDIT END: TGUI COLOR PICKER
if(!new_mutant_colour)
return
@@ -264,7 +265,7 @@
var/hair_area = tgui_alert(alterer, "Select which color you would like to change", "Hair Color Alterations", list("Hairstyle", "Facial Hair", "Both"))
if(!hair_area)
return
- var/new_hair_color = input(alterer, "Select your new hair color", "Hair Color Alterations", alterer.dna.features["mcolor"]) as color|null
+ var/new_hair_color = tgui_color_picker(alterer, "Select your new hair color", "Hair Color Alterations", alterer.dna.features["mcolor"]) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_hair_color)
return
diff --git a/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm b/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm
index f7cefddb166..1397db05d44 100644
--- a/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm
+++ b/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm
@@ -246,7 +246,7 @@ GLOBAL_LIST_EMPTY(hivemind_users)
/datum/component/mind_linker/active_linking/nif/proc/change_chat_color()
var/mob/living/carbon/human/owner = parent
- var/new_chat_color = input(owner, "", "Choose Color", COLOR_ASSEMBLY_GREEN) as color
+ var/new_chat_color = tgui_color_picker(owner, "Pick new color", COLOR_ASSEMBLY_GREEN) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!new_chat_color)
return FALSE
diff --git a/modular_skyrat/modules/salon/code/fur_dyer.dm b/modular_skyrat/modules/salon/code/fur_dyer.dm
index 6a268d81878..de4e271f6a7 100644
--- a/modular_skyrat/modules/salon/code/fur_dyer.dm
+++ b/modular_skyrat/modules/salon/code/fur_dyer.dm
@@ -45,12 +45,14 @@
to_chat(user, span_danger("A red light blinks!"))
return
- var/selected_color = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/selected_color = tgui_color_picker(
user,
"Select marking color",
null,
COLOR_WHITE,
- ) as color | null
+ )
+ // BUBBERSTATION EDIT END: TGUI COLOR PICKER
if(!selected_color)
return
@@ -99,12 +101,14 @@
if(!selected_marking_id)
return
- var/selected_color = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/selected_color = tgui_color_picker(
user,
"Select marking color",
null,
COLOR_WHITE,
- ) as color | null
+ )
+ // BUBBERSTATION EDIT END: TGUI COLOR PICKER
if(!selected_color)
return
diff --git a/modular_skyrat/modules/salon/code/misc_items.dm b/modular_skyrat/modules/salon/code/misc_items.dm
index a65c4f92417..e944ae6b38a 100644
--- a/modular_skyrat/modules/salon/code/misc_items.dm
+++ b/modular_skyrat/modules/salon/code/misc_items.dm
@@ -23,12 +23,14 @@
INVOKE_ASYNC(src, PROC_REF(async_set_color), attacked_mob, user)
/obj/item/lipstick/quantum/proc/async_set_color(mob/attacked_mob, mob/user)
- var/new_color = input(
+ // BUBBERSTATION EDIT START: TGUI COLOR PICKER
+ var/new_color = tgui_color_picker(
user,
"Select lipstick color",
null,
COLOR_WHITE,
- ) as color | null
+ )
+ // BUBBERSTATION EDIT END: TGUI COLOR PICKER
var/mob/living/carbon/human/target = attacked_mob
if(target.is_mouth_covered())
diff --git a/modular_zubbers/code/datums/components/vore/belly.dm b/modular_zubbers/code/datums/components/vore/belly.dm
index 942c73a8a05..a875c4ba846 100644
--- a/modular_zubbers/code/datums/components/vore/belly.dm
+++ b/modular_zubbers/code/datums/components/vore/belly.dm
@@ -169,7 +169,7 @@
prey.clear_fullscreen("vore", FALSE)
show_fullscreen(prey)
if("overlay_color")
- var/new_color = input(usr, "Pick a belly color", "Belly Color", overlay_color) as color|null
+ var/new_color = tgui_color_picker(usr, "Pick a belly color", "Belly Color", overlay_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(new_color)
overlay_color = new_color
for(var/mob/living/prey in src)
diff --git a/modular_zubbers/code/game/objects/items/holy_weapons.dm b/modular_zubbers/code/game/objects/items/holy_weapons.dm
index 03939ff1e88..f81428caf48 100644
--- a/modular_zubbers/code/game/objects/items/holy_weapons.dm
+++ b/modular_zubbers/code/game/objects/items/holy_weapons.dm
@@ -141,7 +141,7 @@ means that you'll be forced to move carefully while it's on. Fits in pockets, an
to_chat(user, "You can't do that right now!")
return
if(alert("Are you sure you want to recolor your blade?", "Confirm Repaint", "Yes", "No") == "Yes")
- var/energy_color_input = input(usr,"","Choose Energy Color",light_color) as color|null
+ var/energy_color_input = tgui_color_picker(usr,"","Choose Energy Color",light_color) // BUBBERSTATION EDIT: TGUI COLOR PICKER
if(!energy_color_input || !user.can_perform_action(src, SILENT_ADJACENCY) || hacked)
return
set_light_color(energy_color_input)
diff --git a/modular_zubbers/code/modules/debug_tools/physgun.dm b/modular_zubbers/code/modules/debug_tools/physgun.dm
index 7fcd8140d21..3be4c9cfb7c 100644
--- a/modular_zubbers/code/modules/debug_tools/physgun.dm
+++ b/modular_zubbers/code/modules/debug_tools/physgun.dm
@@ -77,7 +77,7 @@
/obj/item/physic_manipulation_tool/click_alt(mob/user)
. = ..()
- var/choised_color = input(usr, "Pick new effect color", "Physgun color") as color|null
+ var/choised_color = tgui_color_picker(usr, "Pick new effect color", "Physgun color") // BUBBERSTATION EDIT: TGUI COLOR PICKER
effects_color = choised_color
color = choised_color
update_appearance()
diff --git a/modular_zubbers/code/modules/debug_tools/toolgun_module/phys_color.dm b/modular_zubbers/code/modules/debug_tools/toolgun_module/phys_color.dm
index d1c6c24c914..fdee5d5bb0d 100644
--- a/modular_zubbers/code/modules/debug_tools/toolgun_module/phys_color.dm
+++ b/modular_zubbers/code/modules/debug_tools/toolgun_module/phys_color.dm
@@ -6,7 +6,7 @@
/datum/phystool_mode/color_mode/use_act(mob/user)
. = ..()
- selected_color = input(user, "Pick new effects color", "Physgun color") as color|null
+ selected_color = tgui_color_picker(user, "Pick new effects color", "Physgun color") // BUBBERSTATION EDIT: TGUI COLOR PICKER
/datum/phystool_mode/color_mode/main_act(atom/target, mob/user)
. = ..()
diff --git a/modular_zubbers/code/modules/tgui_input/color.dm b/modular_zubbers/code/modules/tgui_input/color.dm
new file mode 100644
index 00000000000..f4713f40f3f
--- /dev/null
+++ b/modular_zubbers/code/modules/tgui_input/color.dm
@@ -0,0 +1,174 @@
+/**
+ * Creates a TGUI color picker window and returns the user's response.
+ *
+ * This proc should be used to create a color picker that the caller will wait for a response from.
+ * Arguments:
+ * * user - The user to show the picker to.
+ * * title - The of the picker modal, shown on the top of the TGUI window.
+ * * timeout - The timeout of the picker, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * autofocus - The bool that controls if this picker should grab window focus.
+ */
+/proc/tgui_color_picker(mob/user, message, title, default = "#000000", timeout = 0, autofocus = TRUE)
+ if (!user)
+ user = usr
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ // Client does NOT have tgui_input on: Returns regular input
+ if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ return input(user, message, title, default) as color|null
+ var/datum/tgui_color_picker/picker = new(user, message, title, default, timeout, autofocus)
+ picker.ui_interact(user)
+ picker.wait()
+ if (picker)
+ . = picker.choice
+ qdel(picker)
+
+/**
+ * Creates an asynchronous TGUI color picker window with an associated callback.
+ *
+ * This proc should be used to create a color picker that invokes a callback with the user's chosen option.
+ * Arguments:
+ * * user - The user to show the picker to.
+ * * title - The of the picker modal, shown on the top of the TGUI window.
+ * * callback - The callback to be invoked when a choice is made.
+ * * timeout - The timeout of the picker, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * autofocus - The bool that controls if this picker should grab window focus.
+ */
+/proc/tgui_color_picker_async(mob/user, message, title, default = "#000000", datum/callback/callback, timeout = 0, autofocus = TRUE)
+ if (!user)
+ user = usr
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ // Client does NOT have tgui_input on: Returns regular input
+ if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ return input(user, message, title, default) as color|null
+ var/datum/tgui_color_picker/async/picker = new(user, message, title, default, callback, timeout, autofocus)
+ picker.ui_interact(user)
+
+/**
+ * # tgui_color_picker
+ *
+ * Datum used for instantiating and using a TGUI-controlled color picker.
+ */
+/datum/tgui_color_picker
+ /// The title of the TGUI window
+ var/title
+ /// The message to show the user
+ var/message
+ /// The default choice, used if there is an existing value
+ var/default
+ /// The color the user selected, null if no selection has been made
+ var/choice
+ /// The time at which the tgui_color_picker was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the tgui_color_picker, after which the window will close and delete itself.
+ var/timeout
+ /// The bool that controls if this modal should grab window focus
+ var/autofocus
+ /// Boolean field describing if the tgui_color_picker was closed by the user.
+ var/closed
+
+/datum/tgui_color_picker/New(mob/user, message, title, default, timeout, autofocus)
+ src.autofocus = autofocus
+ src.title = title
+ src.default = default
+ src.message = message
+ if (timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+
+/datum/tgui_color_picker/Destroy(force, ...)
+ SStgui.close_uis(src)
+ . = ..()
+
+/**
+ * Waits for a user's response to the tgui_color_picker's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_color_picker/proc/wait()
+ while (!choice && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_color_picker/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ColorPickerModal")
+ ui.open()
+ ui.set_autoupdate(timeout > 0)
+
+/datum/tgui_color_picker/ui_close(mob/user)
+ . = ..()
+ closed = TRUE
+
+/datum/tgui_color_picker/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/tgui_color_picker/ui_static_data(mob/user)
+ . = list()
+ .["autofocus"] = autofocus
+ .["large_buttons"] = !user.client?.prefs || user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_large)
+ .["swapped_buttons"] = !user.client?.prefs || user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_swapped)
+ .["title"] = title
+ .["default_color"] = default
+ .["message"] = message
+
+/datum/tgui_color_picker/ui_data(mob/user)
+ . = list()
+ if(timeout)
+ .["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+
+/datum/tgui_color_picker/ui_act(action, list/params)
+ . = ..()
+ if (.)
+ return
+ switch(action)
+ if("submit")
+ var/raw_data = lowertext(params["entry"])
+ var/hex = sanitize_hexcolor(raw_data, desired_format = 6, include_crunch = TRUE)
+ if (!hex)
+ return
+ set_choice(hex)
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+
+/datum/tgui_color_picker/proc/set_choice(choice)
+ src.choice = choice
+
+/**
+ * # async tgui_color_picker
+ *
+ * An asynchronous version of tgui_color_picker to be used with callbacks instead of waiting on user responses.
+ */
+/datum/tgui_color_picker/async
+ /// The callback to be invoked by the tgui_color_picker upon having a choice made.
+ var/datum/callback/callback
+
+/datum/tgui_color_picker/async/New(mob/user, message, title, default, callback, timeout, autofocus)
+ ..(user, message, title, default, timeout, autofocus)
+ src.callback = callback
+
+/datum/tgui_color_picker/async/Destroy(force, ...)
+ QDEL_NULL(callback)
+ . = ..()
+
+/datum/tgui_color_picker/async/set_choice(choice)
+ . = ..()
+ if(!isnull(src.choice))
+ callback?.InvokeAsync(src.choice)
+
+/datum/tgui_color_picker/async/wait()
+ return
diff --git a/tgstation.dme b/tgstation.dme
index 3bc08569ba7..d0583beac32 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -9347,6 +9347,7 @@
#include "modular_zubbers\code\modules\syndicate_offstation\storage\safe.dm"
#include "modular_zubbers\code\modules\tarkon\code\misc-fluff\research.dm"
#include "modular_zubbers\code\modules\taur_mechanics\serpentine_taur.dm"
+#include "modular_zubbers\code\modules\tgui_input\color.dm"
#include "modular_zubbers\code\modules\title_screen\code\title_screen_subsystem.dm"
#include "modular_zubbers\code\modules\uplink\uplink_items\badass.dm"
#include "modular_zubbers\code\modules\vehicles\mech_fabricator.dm"
diff --git a/tgui/packages/common/colorpicker.ts b/tgui/packages/common/colorpicker.ts
new file mode 100644
index 00000000000..e0a8cf687fb
--- /dev/null
+++ b/tgui/packages/common/colorpicker.ts
@@ -0,0 +1,502 @@
+// BUBBERSTATION ADDITION START: TGUI COLOR PICKER
+/*
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+const round = (
+ number: number,
+ digits = 0,
+ base = Math.pow(10, digits),
+): number => {
+ return Math.round(base * number) / base;
+};
+
+export interface RgbColor {
+ r: number;
+ g: number;
+ b: number;
+}
+
+export interface RgbaColor extends RgbColor {
+ a: number;
+}
+
+export interface HslColor {
+ h: number;
+ s: number;
+ l: number;
+}
+
+export interface HslaColor extends HslColor {
+ a: number;
+}
+
+export interface HsvColor {
+ h: number;
+ s: number;
+ v: number;
+}
+
+export interface HsvaColor extends HsvColor {
+ a: number;
+}
+
+export type ObjectColor =
+ | RgbColor
+ | HslColor
+ | HsvColor
+ | RgbaColor
+ | HslaColor
+ | HsvaColor;
+
+export type AnyColor = string | ObjectColor;
+
+/**
+ * Valid CSS units.
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/angle
+ */
+const angleUnits: Record = {
+ grad: 360 / 400,
+ turn: 360,
+ rad: 360 / (Math.PI * 2),
+};
+
+export const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex));
+
+export const hexToRgba = (hex: string): RgbaColor => {
+ if (hex[0] === '#') hex = hex.substring(1);
+
+ if (hex.length < 6) {
+ return {
+ r: parseInt(hex[0] + hex[0], 16),
+ g: parseInt(hex[1] + hex[1], 16),
+ b: parseInt(hex[2] + hex[2], 16),
+ a: hex.length === 4 ? round(parseInt(hex[3] + hex[3], 16) / 255, 2) : 1,
+ };
+ }
+
+ return {
+ r: parseInt(hex.substring(0, 2), 16),
+ g: parseInt(hex.substring(2, 4), 16),
+ b: parseInt(hex.substring(4, 6), 16),
+ a: hex.length === 8 ? round(parseInt(hex.substring(6, 8), 16) / 255, 2) : 1,
+ };
+};
+
+export const parseHue = (value: string, unit = 'deg'): number => {
+ return Number(value) * (angleUnits[unit] || 1);
+};
+
+export const hslaStringToHsva = (hslString: string): HsvaColor => {
+ const matcher =
+ /hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(hslString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return hslaToHsva({
+ h: parseHue(match[1], match[2]),
+ s: Number(match[3]),
+ l: Number(match[4]),
+ a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+ });
+};
+
+export const hslStringToHsva = hslaStringToHsva;
+
+export const hslaToHsva = ({ h, s, l, a }: HslaColor): HsvaColor => {
+ s *= (l < 50 ? l : 100 - l) / 100;
+
+ return {
+ h: h,
+ s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0,
+ v: l + s,
+ a,
+ };
+};
+
+export const hsvaToHex = (hsva: HsvaColor): string =>
+ rgbaToHex(hsvaToRgba(hsva));
+
+export const hsvaToHsla = ({ h, s, v, a }: HsvaColor): HslaColor => {
+ const hh = ((200 - s) * v) / 100;
+
+ return {
+ h: round(h),
+ s: round(
+ hh > 0 && hh < 200
+ ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100
+ : 0,
+ ),
+ l: round(hh / 2),
+ a: round(a, 2),
+ };
+};
+
+export const hsvaToHslString = (hsva: HsvaColor): string => {
+ const { h, s, l } = hsvaToHsla(hsva);
+ return `hsl(${h}, ${s}%, ${l}%)`;
+};
+
+export const hsvaToHsvString = (hsva: HsvaColor): string => {
+ const { h, s, v } = roundHsva(hsva);
+ return `hsv(${h}, ${s}%, ${v}%)`;
+};
+
+export const hsvaToHsvaString = (hsva: HsvaColor): string => {
+ const { h, s, v, a } = roundHsva(hsva);
+ return `hsva(${h}, ${s}%, ${v}%, ${a})`;
+};
+
+export const hsvaToHslaString = (hsva: HsvaColor): string => {
+ const { h, s, l, a } = hsvaToHsla(hsva);
+ return `hsla(${h}, ${s}%, ${l}%, ${a})`;
+};
+
+export const hsvaToRgba = ({ h, s, v, a }: HsvaColor): RgbaColor => {
+ h = (h / 360) * 6;
+ s = s / 100;
+ v = v / 100;
+
+ const hh = Math.floor(h),
+ b = v * (1 - s),
+ c = v * (1 - (h - hh) * s),
+ d = v * (1 - (1 - h + hh) * s),
+ module = hh % 6;
+
+ return {
+ r: [v, c, b, b, d, v][module] * 255,
+ g: [d, v, v, c, b, b][module] * 255,
+ b: [b, b, d, v, v, c][module] * 255,
+ a: round(a, 2),
+ };
+};
+
+export const hsvaToRgbString = (hsva: HsvaColor): string => {
+ const { r, g, b } = hsvaToRgba(hsva);
+ return `rgb(${round(r)}, ${round(g)}, ${round(b)})`;
+};
+
+export const hsvaToRgbaString = (hsva: HsvaColor): string => {
+ const { r, g, b, a } = hsvaToRgba(hsva);
+ return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${round(a, 2)})`;
+};
+
+export const hsvaStringToHsva = (hsvString: string): HsvaColor => {
+ const matcher =
+ /hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(hsvString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return roundHsva({
+ h: parseHue(match[1], match[2]),
+ s: Number(match[3]),
+ v: Number(match[4]),
+ a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+ });
+};
+
+export const hsvStringToHsva = hsvaStringToHsva;
+
+export const rgbaStringToHsva = (rgbaString: string): HsvaColor => {
+ const matcher =
+ /rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(rgbaString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return rgbaToHsva({
+ r: Number(match[1]) / (match[2] ? 100 / 255 : 1),
+ g: Number(match[3]) / (match[4] ? 100 / 255 : 1),
+ b: Number(match[5]) / (match[6] ? 100 / 255 : 1),
+ a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),
+ });
+};
+
+export const rgbStringToHsva = rgbaStringToHsva;
+
+const format = (number: number) => {
+ const hex = number.toString(16);
+ return hex.length < 2 ? '0' + hex : hex;
+};
+
+export const rgbaToHex = ({ r, g, b, a }: RgbaColor): string => {
+ const alphaHex = a < 1 ? format(round(a * 255)) : '';
+ return (
+ '#' + format(round(r)) + format(round(g)) + format(round(b)) + alphaHex
+ );
+};
+
+export const rgbaToHsva = ({ r, g, b, a }: RgbaColor): HsvaColor => {
+ const max = Math.max(r, g, b);
+ const delta = max - Math.min(r, g, b);
+
+ let hhValue: number;
+
+ if (!delta) {
+ hhValue = 0;
+ } else {
+ switch (max) {
+ case r:
+ hhValue = (g - b) / delta;
+ break;
+ case g:
+ hhValue = 2 + (b - r) / delta;
+ break;
+ default:
+ hhValue = 4 + (r - g) / delta;
+ break;
+ }
+ }
+
+ const hh = hhValue;
+
+ return {
+ h: 60 * (hh < 0 ? hh + 6 : hh),
+ s: max ? (delta / max) * 100 : 0,
+ v: (max / 255) * 100,
+ a,
+ };
+};
+
+export const roundHsva = (hsva: HsvaColor): HsvaColor => ({
+ h: round(hsva.h),
+ s: round(hsva.s),
+ v: round(hsva.v),
+ a: round(hsva.a, 2),
+});
+
+export const rgbaToRgb = ({ r, g, b }: RgbaColor): RgbColor => ({ r, g, b });
+
+export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
+
+export const hsvaToHsv = (hsva: HsvaColor): HsvColor => {
+ const { h, s, v } = roundHsva(hsva);
+ return { h, s, v };
+};
+
+const hexMatcher = /^#?([0-9A-F]{3,8})$/i;
+
+export const validHex = (value: string, alpha?: boolean): boolean => {
+ const match = hexMatcher.exec(value);
+ const length = match ? match[1].length : 0;
+
+ return (
+ length === 3 || // '#rgb' format
+ length === 6 || // '#rrggbb' format
+ (!!alpha && length === 4) || // '#rgba' format
+ (!!alpha && length === 8) // '#rrggbbaa' format
+ );
+};
+
+// Source for the following luminance and contrast calculation code: https://blog.cristiana.tech/calculating-color-contrast-in-typescript-using-web-content-accessibility-guidelines-wcag
+export const luminance = (rgb: RgbColor): number => {
+ const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((v) => {
+ v /= 255;
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ });
+ return r * 0.2126 + g * 0.7152 + b * 0.0722;
+};
+
+export const contrast = (
+ foreground: RgbColor,
+ background: RgbColor,
+): number => {
+ const foreground_luminance = luminance(foreground);
+ const background_luminance = luminance(background);
+ return background_luminance < foreground_luminance
+ ? (background_luminance + 0.05) / (foreground_luminance + 0.05)
+ : (foreground_luminance + 0.05) / (background_luminance + 0.05);
+};
+
+// Used for the colour picker, and anything else that wants a list of preset colours.
+// prettier-ignore
+export const colorList = [
+ [
+ '003366',
+ '336699',
+ '3366CC',
+ '003399',
+ '000099',
+ '0000CC',
+ '000066',
+ ],
+ [
+ '006666',
+ '006699',
+ '0099CC',
+ '0066CC',
+ '0033CC',
+ '0000FF',
+ '3333FF',
+ '333399',
+ ],
+ [
+ '008080',
+ '009999',
+ '33CCCC',
+ '00CCFF',
+ '0099FF',
+ '0066FF',
+ '3366FF',
+ '3333CC',
+ '666699',
+ ],
+ [
+ '339966',
+ '00CC99',
+ '00FFCC',
+ '00FFFF',
+ '33CCFF',
+ '3399FF',
+ '6699FF',
+ '6666FF',
+ '6600FF',
+ '6600CC',
+ ],
+ [
+ '339933',
+ '00CC66',
+ '00FF99',
+ '66FFCC',
+ '66FFFF',
+ '66CCFF',
+ '99CCFF',
+ '9999FF',
+ '9966FF',
+ '9933FF',
+ '9900FF',
+ ],
+ [
+ '006600',
+ '00CC00',
+ '00FF00',
+ '66FF99',
+ '99FFCC',
+ 'CCFFFF',
+ 'CCECFF',
+ 'CCCCFF',
+ 'CC99FF',
+ 'CC66FF',
+ 'CC00FF',
+ '9900CC',
+ ],
+ [
+ '003300',
+ '008000',
+ '33CC33',
+ '66FF66',
+ '99FF99',
+ 'CCFFCC',
+ 'FFFFFF',
+ 'FFCCFF',
+ 'FF99FF',
+ 'FF66FF',
+ 'FF00FF',
+ 'CC00CC',
+ '660066',
+ ],
+ [
+ '336600',
+ '009900',
+ '66FF33',
+ '99FF66',
+ 'CCFF99',
+ 'FFFFCC',
+ 'FFCCCC',
+ 'FF99CC',
+ 'FF66CC',
+ 'FF33CC',
+ 'CC0099',
+ '800080',
+ ],
+ [
+ '333300',
+ '669900',
+ '99FF33',
+ 'CCFF66',
+ 'FFFF99',
+ 'FFCC99',
+ 'FF9999',
+ 'FF6699',
+ 'FF3399',
+ 'CC3399',
+ '990099',
+ ],
+ [
+ '666633',
+ '99CC00',
+ 'CCFF33',
+ 'FFFF66',
+ 'FFCC66',
+ 'FF9966',
+ 'FF7C80',
+ 'FF0066',
+ 'D60093',
+ '993366',
+ ],
+ [
+ '808000',
+ 'CCCC00',
+ 'FFFF00',
+ 'FFCC00',
+ 'FF9933',
+ 'FF6600',
+ 'FF5050',
+ 'CC0066',
+ '660033',
+ ],
+ [
+ '996633',
+ 'CC9900',
+ 'FF9900',
+ 'CC6600',
+ 'FF3300',
+ 'FF0000',
+ 'CC0000',
+ '990033',
+ ],
+ [
+ '663300',
+ '996600',
+ 'CC3300',
+ '993300',
+ '990000',
+ '800000',
+ 'A50021',
+ ],
+ [
+ 'F8F8F8',
+ 'DDDDDD',
+ 'B2B2B2',
+ '808080',
+ '5F5F5F',
+ '333333',
+ '1C1C1C',
+ '080808',
+ ],
+ [
+ 'FFFFFF',
+ 'EAEAEA',
+ 'C0C0C0',
+ '969696',
+ '777777',
+ '4D4D4D',
+ '292929',
+ '111111',
+ '000000',
+ ],
+];
+
+// BUBBERSTATION ADDITION END: TGUI COLOR PICKER
diff --git a/tgui/packages/tgui/components/Interactive.tsx b/tgui/packages/tgui/components/Interactive.tsx
new file mode 100644
index 00000000000..0d9738765f3
--- /dev/null
+++ b/tgui/packages/tgui/components/Interactive.tsx
@@ -0,0 +1,134 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { clamp } from 'common/math';
+import React, { Component, createRef, ReactNode, RefObject } from 'react';
+
+export interface Interaction {
+ left: number;
+ top: number;
+}
+
+// Finds the proper window object to fix iframe embedding issues
+const getParentWindow = (node?: HTMLDivElement | null): Window => {
+ return (node && node.ownerDocument.defaultView) || window;
+};
+
+// Returns a relative position of the pointer inside the node's bounding box
+const getRelativePosition = (
+ node: HTMLDivElement,
+ event: MouseEvent,
+): Interaction => {
+ const rect = node.getBoundingClientRect();
+ const parentWindow = getParentWindow(node);
+
+ const offsetX = event.pageX - (rect.left + parentWindow.pageXOffset);
+ const offsetY = event.pageY - (rect.top + parentWindow.pageYOffset);
+
+ return {
+ left: clamp(offsetX / rect.width, 0, 1),
+ top: clamp(offsetY / rect.height, 0, 1),
+ };
+};
+
+export interface InteractiveProps {
+ onMove: (interaction: Interaction) => void;
+ onKey: (offset: Interaction) => void;
+ children: ReactNode; // Change from ReactNode[] to ReactNode
+ style?: React.CSSProperties;
+}
+
+export class Interactive extends Component {
+ containerRef: RefObject;
+
+ constructor(props: InteractiveProps) {
+ super(props);
+ this.containerRef = createRef();
+ }
+
+ handleMoveStart = (event: React.MouseEvent) => {
+ const el = this.containerRef?.current;
+ if (!el) return;
+
+ // Prevent text selection
+ event.preventDefault();
+ el.focus();
+ this.props.onMove(getRelativePosition(el, event.nativeEvent));
+ this.toggleDocumentEvents(true);
+ };
+
+ handleMove = (event: MouseEvent) => {
+ event.preventDefault();
+
+ const isDown = event.buttons > 0;
+
+ if (isDown && this.containerRef?.current) {
+ this.props.onMove(getRelativePosition(this.containerRef.current, event));
+ } else {
+ this.toggleDocumentEvents(false);
+ }
+ };
+
+ handleMoveEnd = () => {
+ this.toggleDocumentEvents(false);
+ };
+
+ handleKeyDown = (event: React.KeyboardEvent) => {
+ const keyCode = event.which || event.keyCode;
+
+ if (keyCode < 37 || keyCode > 40) return;
+ event.preventDefault();
+ this.props.onKey({
+ left: keyCode === 39 ? 0.05 : keyCode === 37 ? -0.05 : 0,
+ top: keyCode === 40 ? 0.05 : keyCode === 38 ? -0.05 : 0,
+ });
+ };
+
+ toggleDocumentEvents(state?: boolean) {
+ const el = this.containerRef?.current;
+ const parentWindow = getParentWindow(el);
+
+ const toggleEvent = state
+ ? parentWindow.addEventListener
+ : parentWindow.removeEventListener;
+ toggleEvent('mousemove', this.handleMove);
+ toggleEvent('mouseup', this.handleMoveEnd);
+ }
+
+ componentDidMount() {
+ this.toggleDocumentEvents(true);
+ }
+
+ componentWillUnmount() {
+ this.toggleDocumentEvents(false);
+ }
+
+ render() {
+ const { style, children, ...rest } = this.props;
+ return (
+
+ {children}
+
+ );
+ }
+}
diff --git a/tgui/packages/tgui/components/Pointer.tsx b/tgui/packages/tgui/components/Pointer.tsx
new file mode 100644
index 00000000000..1e52cec4559
--- /dev/null
+++ b/tgui/packages/tgui/components/Pointer.tsx
@@ -0,0 +1,46 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { classes } from 'common/react';
+import React from 'react';
+
+interface PointerProps {
+ className?: string;
+ top?: number;
+ left: number;
+ color: string;
+}
+
+export const Pointer: React.FC = ({
+ className,
+ color,
+ left,
+ top = 0.5,
+}) => {
+ const nodeClassName = classes(['react-colorful__pointer', className]);
+
+ const style = {
+ top: `${top * 100}%`,
+ left: `${left * 100}%`,
+ };
+
+ return (
+