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 ![image](https://github.com/user-attachments/assets/55f56807-7c76-41a1-8c75-7ed293d581e9) ![image](https://github.com/user-attachments/assets/7742e741-a16b-4708-8e7b-7891f426de32) ![image](https://github.com/user-attachments/assets/6e22e138-af41-447d-95e6-fb6ca7d067af)
## 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 ( +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/components/index.ts b/tgui/packages/tgui/components/index.ts index fdeb475ed3b..7ec688666d9 100644 --- a/tgui/packages/tgui/components/index.ts +++ b/tgui/packages/tgui/components/index.ts @@ -27,6 +27,7 @@ export { Icon } from './Icon'; export { Image } from './Image'; export { InfinitePlane } from './InfinitePlane'; export { Input } from './Input'; +export { Interactive } from './Interactive'; // BUBBERSTATION EDIT: TGUI COLOR PICKER export { KeyListener } from './KeyListener'; export { Knob } from './Knob'; export { LabeledControls } from './LabeledControls'; @@ -35,6 +36,7 @@ export { MenuBar } from './MenuBar'; export { Modal } from './Modal'; export { NoticeBox } from './NoticeBox'; export { NumberInput } from './NumberInput'; +export { Pointer } from './Pointer'; // BUBBERSTATION EDIT: TGUI COLOR PICKER export { Popper } from './Popper'; export { ProgressBar } from './ProgressBar'; export { RestrictedInput } from './RestrictedInput'; diff --git a/tgui/packages/tgui/interfaces/ColorPickerModal.tsx b/tgui/packages/tgui/interfaces/ColorPickerModal.tsx new file mode 100644 index 00000000000..c9b2405d4b0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ColorPickerModal.tsx @@ -0,0 +1,767 @@ +/** + * @file + * @copyright 2023 itsmeow + * @license MIT + */ + +import { + colorList, + hexToHsva, + HsvaColor, + hsvaToHex, + hsvaToHslString, + hsvaToRgba, + rgbaToHsva, + validHex, +} from 'common/colorpicker'; +import { clamp } from 'common/math'; +import { classes } from 'common/react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Interaction, Interactive } from 'tgui/components/Interactive'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + Flex, + NumberInput, + Pointer, + Section, + Stack, + Tooltip, +} from '../components'; +import { Autofocus } from '../components/Autofocus'; +import { Window } from '../layouts'; +import { InputButtons } from './common/InputButtons'; +import { Loader } from './common/Loader'; + +interface ColorPickerData { + autofocus: boolean; + buttons: string[]; + message: string; + large_buttons: boolean; + swapped_buttons: boolean; + timeout: number; + title: string; + default_color: string; +} + +interface ColorPickerModalProps {} + +export const ColorPickerModal: React.FC = () => { + const { data } = useBackend(); + const { timeout, message, autofocus, default_color = '#000000' } = data; + let { title } = data; + + const [selectedColor, setSelectedColor] = useState( + hexToHsva(default_color), + ); + + useEffect(() => { + setSelectedColor(hexToHsva(default_color)); + }, [default_color]); + + if (!title) { + title = 'Color'; + } + + return ( + + {!!timeout && } + + + {!!autofocus && } + {message && ( + +
+ + {message} + +
+
+ )} + +
+ +
+
+ + + +
+
+
+ ); +}; + +interface ColorPresetsProps { + setColor: (color: HsvaColor) => void; + setShowPresets: (show: boolean) => void; +} + +const ColorPresets: React.FC = React.memo( + ({ setColor, setShowPresets }) => { + return ( + <> +