TGUI Color Picker (#26326)

* Init

* Implements `tgui_input_color`

* Implements most color wheel conversions to TGUI

* Iteration two, went over titles and made sure most color inputs are functional.

* Implements preferences for large buttons, swapped buttons, and disabled TGUI

* Iteration three... the weird ones. (as null|color)

* Removes `as color|null` and `as null|color`

* Merge conflict resolution, again.

* Color input bundle and panel bundle

* CSLint being picky.

* Picky `while ()` instead of `while()`

* Apply suggestions from code review

Co-authored-by: Aylong <69762909+AyIong@users.noreply.github.com>
Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com>

* WIP

* de-bri'ish colour into color

Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com>

* Aylong review applied.
- Removed an extra space in machine.dm
- UI_STATE

+ Runechat color now uses TGUI_INPUT_COLOR

* Apply suggestions from code review

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com>

* Removes an unintentional space to `paradise.scss`

* Update code/modules/tgui/tgui_input/color_input.dm

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com>

* Adds "is_color" regex from TG

* Fixes a few conflicting files

* TGUI Merge conflict moment v3

* V4?

* V5....

* God please don't explode. Builds TGUI like the linter wants.

* Update code/modules/tgui/tgui_input/color_input.dm

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com>

* Rebuilds TGUI, again <3

* Lint be damned

---------

Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com>
Co-authored-by: Aylong <69762909+AyIong@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
This commit is contained in:
Spaghetti-bit
2024-11-07 21:24:45 +00:00
committed by GitHub
co-authored by Contrabang Aylong Burzah
parent d0dc432024
commit afd52f006a
27 changed files with 1711 additions and 301 deletions
+1
View File
@@ -1,2 +1,3 @@
GLOBAL_DATUM_INIT(filename_forbidden_chars, /regex, regex(@{""|[\\\n\t/?%*:|<>]|\.\."}, "g"))
GLOBAL_DATUM_INIT(is_color, /regex, regex("^#\[0-9a-fA-F]{6}$"))
GLOBAL_PROTECT(filename_forbidden_chars)
+27 -26
View File
@@ -818,9 +818,10 @@
M.change_gender(FEMALE)
if(eyes_organ)
var/new_eyes = input("Please select eye color.", "Character Generation", eyes_organ.eye_color) as null|color
if(new_eyes)
M.change_eye_color(new_eyes)
var/new_eyes = tgui_input_color(user, "Please select eye color.", "Character Generation", eyes_organ.eye_color)
if(isnull(new_eyes))
return
M.change_eye_color(new_eyes)
if(istype(head_organ))
//Alt heads.
@@ -838,14 +839,14 @@
if(new_style)
M.change_hair(new_style)
var/new_hair = input("Please select hair color.", "Character Generation", head_organ.hair_colour) as null|color
if(new_hair)
var/new_hair = tgui_input_color(user, "Please select hair color.", "Character Generation", head_organ.hair_colour)
if(!isnull(new_hair))
M.change_hair_color(new_hair)
var/datum/sprite_accessory/hair_style = GLOB.hair_styles_public_list[head_organ.h_style]
if(hair_style.secondary_theme && !hair_style.no_sec_colour)
new_hair = input("Please select secondary hair color.", "Character Generation", head_organ.sec_hair_colour) as null|color
if(new_hair)
new_hair = tgui_input_color(user, "Please select secondary hair color.", "Character Generation", head_organ.sec_hair_colour)
if(!isnull(new_hair))
M.change_hair_color(new_hair, TRUE)
// facial hair
@@ -855,25 +856,25 @@
if(new_style)
M.change_facial_hair(new_style)
var/new_facial = input("Please select facial hair color.", "Character Generation", head_organ.facial_colour) as null|color
if(new_facial)
var/new_facial = tgui_input_color(user, "Please select facial hair color.", "Character Generation", head_organ.facial_colour)
if(!isnull(new_facial))
M.change_facial_hair_color(new_facial)
var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style]
if(facial_hair_style.secondary_theme && !facial_hair_style.no_sec_colour)
new_facial = input("Please select secondary facial hair color.", "Character Generation", head_organ.sec_facial_colour) as null|color
if(new_facial)
new_facial = tgui_input_color(user, "Please select secondary facial hair color.", "Character Generation", head_organ.sec_facial_colour)
if(!isnull(new_facial))
M.change_facial_hair_color(new_facial, TRUE)
//Head accessory.
if(head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY)
var/list/valid_head_accessories = M.generate_valid_head_accessories()
var/new_head_accessory = tgui_input_list(user, "Please select head accessory style", "Character Generation", valid_head_accessories)
if(new_head_accessory)
if(!isnull(new_head_accessory))
M.change_head_accessory(new_head_accessory)
var/new_head_accessory_colour = input("Please select head accessory colour.", "Character Generation", head_organ.headacc_colour) as null|color
if(new_head_accessory_colour)
var/new_head_accessory_colour = tgui_input_color(user, "Please select head accessory color.", "Character Generation", head_organ.headacc_colour)
if(!isnull(new_head_accessory_colour))
M.change_head_accessory_color(new_head_accessory_colour)
@@ -882,7 +883,7 @@
var/list/valid_body_accessories = M.generate_valid_body_accessories()
if(length(valid_body_accessories) > 1) //By default valid_body_accessories will always have at the very least a 'none' entry populating the list, even if the user's species is not present in any of the list items.
var/new_body_accessory = tgui_input_list(user, "Please select body accessory style", "Character Generation", valid_body_accessories)
if(new_body_accessory)
if(!isnull(new_body_accessory))
M.change_body_accessory(new_body_accessory)
if(istype(head_organ))
@@ -890,32 +891,32 @@
if(M.dna.species.bodyflags & HAS_HEAD_MARKINGS)
var/list/valid_head_markings = M.generate_valid_markings("head")
var/new_marking = tgui_input_list(user, "Please select head marking style", "Character Generation", valid_head_markings)
if(new_marking)
if(!isnull(new_marking))
M.change_markings(new_marking, "head")
var/new_marking_colour = input("Please select head marking colour.", "Character Generation", M.m_colours["head"]) as null|color
if(new_marking_colour)
var/new_marking_colour = tgui_input_color(user, "Please select head marking color.", "Character Generation", M.m_colours["head"])
if(!isnull(new_marking_colour))
M.change_marking_color(new_marking_colour, "head")
//Body markings.
if(M.dna.species.bodyflags & HAS_BODY_MARKINGS)
var/list/valid_body_markings = M.generate_valid_markings("body")
var/new_marking = tgui_input_list(user, "Please select body marking style", "Character Generation", valid_body_markings)
if(new_marking)
if(!isnull(new_marking))
M.change_markings(new_marking, "body")
var/new_marking_colour = input("Please select body marking colour.", "Character Generation", M.m_colours["body"]) as null|color
if(new_marking_colour)
var/new_marking_colour = tgui_input_color(user, "Please select body marking color.", "Character Generation", M.m_colours["body"])
if(!isnull(new_marking_colour))
M.change_marking_color(new_marking_colour, "body")
//Tail markings.
if(M.dna.species.bodyflags & HAS_TAIL_MARKINGS)
var/list/valid_tail_markings = M.generate_valid_markings("tail")
var/new_marking = tgui_input_list("Please select tail marking style", "Character Generation", valid_tail_markings)
if(new_marking)
if(!isnull(new_marking))
M.change_markings(new_marking, "tail")
var/new_marking_colour = input("Please select tail marking colour.", "Character Generation", M.m_colours["tail"]) as null|color
if(new_marking_colour)
var/new_marking_colour = tgui_input_color(user, "Please select tail marking color.", "Character Generation", M.m_colours["tail"])
if(!isnull(new_marking_colour))
M.change_marking_color(new_marking_colour, "tail")
//Skin tone.
@@ -944,8 +945,8 @@
//Skin colour.
if(M.dna.species.bodyflags & HAS_SKIN_COLOR)
var/new_body_colour = input("Please select body colour.", "Character Generation", M.skin_colour) as null|color
if(new_body_colour)
var/new_body_colour = tgui_input_color(user, "Please select body color.", "Character Generation", M.skin_colour)
if(!isnull(new_body_colour))
M.change_skin_color(new_body_colour)
M.update_dna()
+3 -1
View File
@@ -44,7 +44,9 @@
src.add_fingerprint(user)
if(stat & (BROKEN|NOPOWER))
return
var/temp = input(usr, "Choose a dye color", "Dye Color") as color
var/temp = tgui_input_color(user, "Please select a dye color", "Dye Color")
if(isnull(temp))
return
dye_color = temp
set_light(2, l_color = temp)
+6 -2
View File
@@ -292,7 +292,9 @@
if(!Adjacent(usr) || usr.incapacitated())
return
if(href_list["color"])
var/temp = input(usr, "Please select colour.", "Crayon colour") as color
var/temp = tgui_input_color(usr, "Please select crayon color.", "Crayon color")
if(isnull(temp))
return
colour = temp
update_window(usr)
else
@@ -328,7 +330,9 @@
if("Change Drawing")
..()
if("Change Color")
colour = input(user,"Choose Color") as color
colour = tgui_input_color(user,"Please select a paint color.","Spray Can Color")
if(isnull(colour))
return
update_icon()
/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user as mob, proximity)
+1 -1
View File
@@ -41,7 +41,7 @@
/obj/structure/curtain/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/toy/crayon))
color = input(user, "Choose Color") as color
color = tgui_input_color(user,"Please choose a color.", "Curtain Color")
return
return ..()
@@ -69,8 +69,8 @@
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
if(!light_broken)
var/new_color = input(user, "Select a bulb color", "Select a bulb color", color) as color|null
if(!new_color)
var/new_color = tgui_input_color(user, "Select a bulb color", "Select a bulb color", color)
if(isnull(new_color))
return
// Cancel if they walked away
+2 -2
View File
@@ -1103,8 +1103,8 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
return
message = strip_html(message, 500)
var/message_color = input(src, "Input your message color:", "Color Selector") as color|null
if(!message_color)
var/message_color = tgui_input_color(src, "Input your message color:", "Admin Message - Color Selector")
if(isnull(message_color))
return
show_blurb(about_to_be_banned, 15, message, null, "center", "center", message_color, null, null, 1)
@@ -329,17 +329,16 @@
if("hair")
if(!(S.bodyflags & BALD))
var/input = "Choose your character's hair colour:"
var/new_hair = input(user, input, "Character Preference", active_character.h_colour) as color|null
if(new_hair)
var/new_hair = tgui_input_color(user, "Choose your character's hair color.", "Character Preference", active_character.h_colour)
if(!isnull(new_hair))
active_character.h_colour = new_hair
if("secondary_hair")
if(!(S.bodyflags & BALD))
var/datum/sprite_accessory/hair_style = GLOB.hair_styles_public_list[active_character.h_style]
if(hair_style.secondary_theme && !hair_style.no_sec_colour)
var/new_hair = input(user, "Choose your character's secondary hair colour:", "Character Preference", active_character.h_sec_colour) as color|null
if(new_hair)
var/new_hair = tgui_input_color(user, "Choose your character's secondary hair color.", "Character Preference", active_character.h_sec_colour)
if(!isnull(new_hair))
active_character.h_sec_colour = new_hair
if("h_style")
@@ -387,9 +386,10 @@
active_character.h_grad_offset_y = clamp(text2num(expl[2]) || 0, -16, 16)
if("h_grad_colour")
var/result = input(user, "Choose your character's hair gradient colour:", "Character Preference", active_character.h_grad_colour) as color|null
if(result)
active_character.h_grad_colour = result
var/result = tgui_input_color(user, "Choose your character's hair gradient color:", "Character Preference", active_character.h_grad_colour)
if(isnull(result))
return
active_character.h_grad_colour = result
if("h_grad_alpha")
var/result = tgui_input_number(user, "Choose your character's hair gradient alpha (0-255):", "Character Preference", active_character.h_grad_alpha, 255)
@@ -398,16 +398,17 @@
active_character.h_grad_alpha = clamp(result, 0, 255)
if("runechat_color")
var/result = input(user, "Choose your character's runechat color:", "Character Preference", active_character.runechat_color) as color|null
if(result)
active_character.runechat_color = result
var/result = tgui_input_color(user, "Choose your character's runechat color:", "Character Preference", active_character.runechat_color)
if(isnull(result))
return
active_character.runechat_color = result
if("headaccessory")
if(S.bodyflags & HAS_HEAD_ACCESSORY) //Species with head accessories.
var/input = "Choose the colour of your your character's head accessory:"
var/new_head_accessory = input(user, input, "Character Preference", active_character.hacc_colour) as color|null
if(new_head_accessory)
active_character.hacc_colour = new_head_accessory
var/new_head_accessory = tgui_input_color(user, "Choose the color of your your character's head accessory.", "Character Preference", active_character.hacc_colour)
if(isnull(new_head_accessory))
return
active_character.hacc_colour = new_head_accessory
if("ha_style")
if(S.bodyflags & HAS_HEAD_ACCESSORY) //Species with head accessories.
@@ -485,10 +486,10 @@
if("m_head_colour")
if(S.bodyflags & HAS_HEAD_MARKINGS) //Species with head markings.
var/input = "Choose the colour of your your character's head markings:"
var/new_markings = input(user, input, "Character Preference", active_character.m_colours["head"]) as color|null
if(new_markings)
active_character.m_colours["head"] = new_markings
var/new_markings = tgui_input_color(user, "Choose the color of your your character's head markings.", "Character Preference", active_character.m_colours["head"])
if(isnull(new_markings))
return
active_character.m_colours["head"] = new_markings
if("m_style_body")
if(S.bodyflags & HAS_BODY_MARKINGS) //Species with body markings/tattoos.
@@ -508,10 +509,10 @@
if("m_body_colour")
if(S.bodyflags & HAS_BODY_MARKINGS) //Species with body markings/tattoos.
var/input = "Choose the colour of your your character's body markings:"
var/new_markings = input(user, input, "Character Preference", active_character.m_colours["body"]) as color|null
if(new_markings)
active_character.m_colours["body"] = new_markings
var/new_markings = tgui_input_color(user, "Choose the color of your your character's body markings.", "Character Preference", active_character.m_colours["body"])
if(isnull(new_markings))
return
active_character.m_colours["body"] = new_markings
if("m_style_tail")
if(S.bodyflags & HAS_TAIL_MARKINGS) //Species with tail markings.
@@ -537,10 +538,10 @@
if("m_tail_colour")
if(S.bodyflags & HAS_TAIL_MARKINGS) //Species with tail markings.
var/input = "Choose the colour of your your character's tail markings:"
var/new_markings = input(user, input, "Character Preference", active_character.m_colours["tail"]) as color|null
if(new_markings)
active_character.m_colours["tail"] = new_markings
var/new_markings = tgui_input_color(user, "Choose the color of your your character's tail markings.", "Character Preference", active_character.m_colours["tail"])
if(isnull(new_markings))
return
active_character.m_colours["tail"] = new_markings
if("body_accessory")
var/list/possible_body_accessories = list()
@@ -565,17 +566,19 @@
if("facial")
if(!(S.bodyflags & SHAVED))
var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference", active_character.f_colour) as color|null
if(new_facial)
active_character.f_colour = new_facial
var/new_facial = tgui_input_color(user, "Choose your character's facial-hair color:", "Character Preference", active_character.f_colour)
if(isnull(new_facial))
return
active_character.f_colour = new_facial
if("secondary_facial")
if(!(S.bodyflags & SHAVED))
var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[active_character.f_style]
if(facial_hair_style.secondary_theme && !facial_hair_style.no_sec_colour)
var/new_facial = input(user, "Choose your character's secondary facial-hair colour:", "Character Preference", active_character.f_sec_colour) as color|null
if(new_facial)
active_character.f_sec_colour = new_facial
var/new_facial = tgui_input_color(user, "Choose your character's secondary facial-hair color:", "Character Preference", active_character.f_sec_colour)
if(isnull(new_facial))
return
active_character.f_sec_colour = new_facial
if("f_style")
var/list/valid_facial_hairstyles = list()
@@ -658,34 +661,37 @@
active_character.socks = new_socks
if("eyes")
var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference", active_character.e_colour) as color|null
if(new_eyes)
active_character.e_colour = new_eyes
var/new_eyes = tgui_input_color(user, "Choose your character's eye color:", "Character Preference", active_character.e_colour)
if(isnull(new_eyes))
return
active_character.e_colour = new_eyes
if("s_tone")
if(S.bodyflags & HAS_SKIN_TONE)
var/new_s_tone = input(user, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Character Preference") as num|null
if(!new_s_tone)
if(isnull(new_s_tone))
return
active_character.s_tone = 35 - max(min(round(new_s_tone), 220), 1)
else if(S.bodyflags & HAS_ICON_SKIN_TONE)
var/const/MAX_LINE_ENTRIES = 4
var/prompt = "Choose your character's skin tone: 1-[length(S.icon_skin_tones)]\n(Light to Dark)"
var/skin_c = tgui_input_number(user, prompt, "Character Preference", active_character.s_tone, length(S.icon_skin_tones), 1)
if(!skin_c)
if(isnull(skin_c))
return
active_character.s_tone = skin_c
if("skin")
if((S.bodyflags & HAS_SKIN_COLOR) || GLOB.body_accessory_by_species[active_character.species] || check_rights(R_ADMIN, 0, user))
var/new_skin = input(user, "Choose your character's skin colour: ", "Character Preference", active_character.s_colour) as color|null
if(new_skin)
active_character.s_colour = new_skin
var/new_skin = tgui_input_color(user, "Choose your character's skin color: ", "Character Preference", active_character.s_colour)
if(isnull(new_skin))
return
active_character.s_colour = new_skin
if("ooccolor")
var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference", ooccolor) as color|null
if(new_ooccolor)
ooccolor = new_ooccolor
var/new_ooccolor = tgui_input_color(user, "Choose your OOC color:", "Game Preference", ooccolor)
if(isnull(new_ooccolor))
return
ooccolor = new_ooccolor
if("bag")
var/new_backbag = tgui_input_list(user, "Choose your character's style of bag", "Character Preference", GLOB.backbaglist)
@@ -1015,8 +1021,9 @@
toggles2 ^= PREFTOGGLE_2_AFKWATCH
if("UIcolor")
var/UI_style_color_new = input(user, "Choose your UI color, dark colors are not recommended!", UI_style_color) as color|null
if(!UI_style_color_new) return
var/UI_style_color_new = tgui_input_color(user, "Choose your UI color, dark colors are not recommended!", "Game Preference", UI_style_color)
if(isnull(UI_style_color_new))
return
UI_style_color = UI_style_color_new
if(ishuman(usr)) //mid-round preference changes, for aesthetics
@@ -1151,9 +1158,10 @@
screentip_mode = desired_screentip_mode
if("screentip_color")
var/screentip_color_new = input(user, "Choose your screentip color", screentip_color) as color|null
if(screentip_color_new)
screentip_color = screentip_color_new
var/screentip_color_new = tgui_input_color(user, "Choose your screentip color", "Game Preference", screentip_color)
if(isnull(screentip_color_new))
return
screentip_color = screentip_color_new
if("edit_2fa")
// Do this async so we arent holding up a topic() call
@@ -33,7 +33,7 @@
/datum/gear_tweak/color/get_metadata(user, metadata)
if(valid_colors)
return input(user, "Choose an item color.", "Character Preference", metadata) as null|anything in valid_colors
return input(user, "Choose an item color.", "Global Preference", metadata) as color|null
return tgui_input_color(user, "Choose an item color.", "Global Preference", metadata)
/datum/gear_tweak/color/tweak_item(obj/item/I, metadata)
if(valid_colors && !(metadata in valid_colors))
@@ -510,8 +510,8 @@
blackbox_message = "Set Own OOC"
/datum/preference_toggle/special_toggle/set_ooc_color/set_toggles(client/user)
var/new_ooccolor = input(usr, "Please select your OOC color.", "OOC color", user.prefs.ooccolor) as color|null
if(new_ooccolor)
var/new_ooccolor = tgui_input_color(usr, "Please select your OOC color.", "OOC Color", user.prefs.ooccolor)
if(!isnull(new_ooccolor))
user.prefs.ooccolor = new_ooccolor
to_chat(usr, "Your OOC color has been set to [new_ooccolor].")
else
+2 -2
View File
@@ -100,8 +100,8 @@
/obj/item/fluff/tattoo_gun/elliot_cybernetic_tat/attack_self(mob/user as mob)
if(!used)
var/ink_color = input("Please select an ink color.", "Tattoo Ink Color", rgb(tattoo_r, tattoo_g, tattoo_b)) as color|null
if(ink_color && !(user.incapacitated() || used))
var/ink_color = tgui_input_color(user, "Please select an ink color.", "Tattoo Ink Color", rgb(tattoo_r, tattoo_g, tattoo_b))
if(!isnull(ink_color) && !(user.incapacitated() || used))
tattoo_r = color2R(ink_color)
tattoo_g = color2G(ink_color)
tattoo_b = color2B(ink_color)
@@ -152,11 +152,11 @@
if(!head_organ)
return
if(!robohead.is_monitor) //If they've got a prosthetic head and it isn't a monitor, they've no screen to adjust. Instead, let them change the colour of their optics!
var/optic_colour = input(H, "Select optic colour", H.m_colours["head"]) as color|null
var/optic_colour = tgui_input_color(H, "Please select an optic color", "Select Optic Color", H.m_colours["head"])
if(H.incapacitated(TRUE, TRUE))
to_chat(H, "<span class='warning'>You were interrupted while changing the colour of your optics.</span>")
to_chat(H, "<span class='warning'>You were interrupted while changing the color of your optics.</span>")
return
if(optic_colour)
if(!isnull(optic_colour))
H.change_markings(optic_colour, "head")
else if(robohead.is_monitor) //Means that the character's head is a monitor (has a screen). Time to customize.
@@ -175,7 +175,7 @@
var/new_style = tgui_input_list(H, "Select a monitor display", "Monitor Display", hair)
if(!new_style)
return
var/new_color = input("Please select hair color.", "Monitor Color", head_organ.hair_colour) as null|color
var/new_color = tgui_input_color(H, "Please select hair color.", "Monitor Color", head_organ.hair_colour)
if(H.incapacitated(TRUE, TRUE))
to_chat(H, "<span class='warning'>You were interrupted while changing your monitor display.</span>")
@@ -183,7 +183,7 @@
if(new_style)
H.change_hair(new_style, 1) // The 1 is to enable custom sprites
if(new_color)
if(!isnull(new_color))
H.change_hair_color(new_color)
/datum/species/machine/spec_electrocute_act(mob/living/carbon/human/H, shock_damage, source, siemens_coeff, flags)
+2 -2
View File
@@ -251,8 +251,8 @@
/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
if(!value)
value = tgui_input_color(usr, "Pick new light color", "Flashlight Color", light_color)
if(isnull(value))
return
if(is_color_dark(value, 50))
to_chat(mod.wearer, ("<span class='warning'>That is too dark</span>"))
@@ -657,4 +657,4 @@
desc = "Causes kinetic accelerator bolts to have an adjustable-colored tracer trail and explosion. Use in-hand to change color."
/obj/item/borg/upgrade/modkit/tracer/adjustable/attack_self(mob/user)
bolt_color = input(user,"","Choose Color",bolt_color) as color|null
bolt_color = tgui_input_color(user, "Please select a tracer color", "PKA Tracer Color", bolt_color)
+23 -23
View File
@@ -72,8 +72,8 @@
if("skin_color")
if(can_change_skin_color())
var/new_skin = input(usr, "Choose your character's skin colour: ", "Skin Color", owner.skin_colour) as color|null
if(new_skin && (!..()) && owner.change_skin_color(new_skin))
var/new_skin = tgui_input_color(usr, "Choose your character's skin colour: ", "Skin Color", owner.skin_colour)
if(!isnull(new_skin) && (!..()) && owner.change_skin_color(new_skin))
update_dna()
if("hair")
@@ -83,32 +83,32 @@
if("hair_color")
if(can_change(APPEARANCE_HAIR_COLOR))
var/new_hair = input("Please select hair color.", "Hair Color", head_organ.hair_colour) as color|null
if(new_hair && (!..()) && owner.change_hair_color(new_hair))
var/new_hair = tgui_input_color(usr, "Please select hair color.", "Hair Color", head_organ.hair_colour)
if(!isnull(new_hair) && (!..()) && owner.change_hair_color(new_hair))
update_dna()
if("secondary_hair_color")
if(can_change(APPEARANCE_SECONDARY_HAIR_COLOR))
var/new_hair = input("Please select secondary hair color.", "Secondary Hair Color", head_organ.sec_hair_colour) as color|null
if(new_hair && (!..()) && owner.change_hair_color(new_hair, 1))
var/new_hair = tgui_input_color(usr, "Please select secondary hair color.", "Secondary Hair Color", head_organ.sec_hair_colour)
if(!isnull(new_hair) && (!..()) && owner.change_hair_color(new_hair, 1))
update_dna()
if("hair_gradient")
if(can_change(APPEARANCE_HAIR) && length(valid_hairstyles))
var/new_style = tgui_input_list(usr, "Please select gradient style", "Hair Gradient", GLOB.hair_gradients_list)
if(new_style)
if(!isnull(new_style))
owner.change_hair_gradient(style = new_style)
if("hair_gradient_offset")
if(can_change(APPEARANCE_HAIR) && length(valid_hairstyles))
var/new_offset = input("Please enter gradient offset as a comma-separated value (x,y). Example:\n0,0 (no offset)\n5,0 (5 pixels to the right)", "Hair Gradient", "[head_organ.h_grad_offset_x],[head_organ.h_grad_offset_y]") as null|text
if(new_offset)
if(!isnull(new_offset))
owner.change_hair_gradient(offset_raw = new_offset)
if("hair_gradient_colour")
if(can_change(APPEARANCE_HAIR) && length(valid_hairstyles))
var/new_color = input("Please select gradient color.", "Hair Gradient", head_organ.h_grad_colour) as null|color
if(new_color)
var/new_color = tgui_input_color(usr, "Please select gradient color.", "Hair Gradient", head_organ.h_grad_colour)
if(!isnull(new_color))
owner.change_hair_gradient(color = new_color)
if("hair_gradient_alpha")
@@ -124,26 +124,26 @@
if("facial_hair_color")
if(can_change(APPEARANCE_FACIAL_HAIR_COLOR))
var/new_facial = input("Please select facial hair color.", "Facial Hair Color", head_organ.facial_colour) as color|null
if(new_facial && (!..()) && owner.change_facial_hair_color(new_facial))
var/new_facial = tgui_input_color(usr, "Please select facial hair color.", "Facial Hair Color", head_organ.facial_colour)
if(!isnull(new_facial) && (!..()) && owner.change_facial_hair_color(new_facial))
update_dna()
if("secondary_facial_hair_color")
if(can_change(APPEARANCE_SECONDARY_FACIAL_HAIR_COLOR))
var/new_facial = input("Please select secondary facial hair color.", "Secondary Facial Hair Color", head_organ.sec_facial_colour) as color|null
if(new_facial && (!..()) && owner.change_facial_hair_color(new_facial, 1))
var/new_facial = tgui_input_color(usr, "Please select secondary facial hair color.", "Secondary Facial Hair Color", head_organ.sec_facial_colour)
if(!isnull(new_facial) && (!..()) && owner.change_facial_hair_color(new_facial, 1))
update_dna()
if("eye_color")
if(can_change(APPEARANCE_EYE_COLOR))
var/obj/item/organ/internal/eyes/eyes_organ = owner.get_int_organ(/obj/item/organ/internal/eyes)
var/new_eyes = input("Please select eye color.", "Eye Color", eyes_organ.eye_color) as color|null
if(new_eyes && (!..()) && owner.change_eye_color(new_eyes))
var/new_eyes = tgui_input_color(usr, "Please select eye color.", "Eye Color", eyes_organ.eye_color)
if(!isnull(new_eyes) && (!..()) && owner.change_eye_color(new_eyes))
update_dna()
if("runechat_color")
var/new_runechat_color = input("Please select runechat color.", "Runechat Color", owner.dna.chat_color) as color|null
if(new_runechat_color && (!..()))
var/new_runechat_color = tgui_input_color("Please select runechat color.", "Runechat Color", owner.dna.chat_color)
if(!isnull(new_runechat_color) && (!..()))
owner.change_runechat_color(new_runechat_color)
if("head_accessory")
@@ -153,8 +153,8 @@
if("head_accessory_color")
if(can_change_head_accessory())
var/new_head_accessory = input("Please select head accessory color.", "Head Accessory Color", head_organ.headacc_colour) as color|null
if(new_head_accessory && (!..()) && owner.change_head_accessory_color(new_head_accessory))
var/new_head_accessory = tgui_input_color(usr, "Please select head accessory color.", "Head Accessory Color", head_organ.headacc_colour)
if(!isnull(new_head_accessory) && (!..()) && owner.change_head_accessory_color(new_head_accessory))
update_dna()
if("head_marking")
@@ -164,7 +164,7 @@
if("head_marking_color")
if(can_change_markings("head"))
var/new_markings = input("Please select head marking color.", "Marking Color", owner.m_colours["head"]) as color|null
var/new_markings = tgui_input_color(usr, "Please select head marking color.", "Marking Color", owner.m_colours["head"])
if(new_markings && (!..()) && owner.change_marking_color(new_markings, "head"))
update_dna()
@@ -175,7 +175,7 @@
if("body_marking_color")
if(can_change_markings("body"))
var/new_markings = input("Please select body marking color.", "Marking Color", owner.m_colours["body"]) as color|null
var/new_markings = tgui_input_color(usr, "Please select body marking color.", "Marking Color", owner.m_colours["body"])
if(new_markings && (!..()) && owner.change_marking_color(new_markings, "body"))
update_dna()
@@ -186,7 +186,7 @@
if("tail_marking_color")
if(can_change_markings("tail"))
var/new_markings = input("Please select tail marking color.", "Marking Color", owner.m_colours["tail"]) as color|null
var/new_markings = tgui_input_color(usr, "Please select tail marking color.", "Marking Color", owner.m_colours["tail"])
if(new_markings && (!..()) && owner.change_marking_color(new_markings, "tail"))
update_dna()
+132
View File
@@ -0,0 +1,132 @@
/**
* 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_input_color(mob/user, message, title, default = "#000000", timeout = 0, autofocus = TRUE, ui_state = GLOB.always_state)
if(!user)
user = usr
if(!istype(user))
if(!isclient(user))
CRASH("We passed something that wasn't a user/client in a TGUI Input Color! The passed thing was [user]!")
var/client/client = user
user = client.mob
if(isnull(user.client))
return
// Client does NOT have tgui_input on: Returns regular input
if(user.client?.prefs?.toggles2 & PREFTOGGLE_2_DISABLE_TGUI_INPUT)
return input(user, message, title, default) as color|null
var/datum/tgui_input_color/picker = new(user, message, title, default, timeout, autofocus, ui_state)
picker.ui_interact(user)
picker.wait()
if(picker)
. = picker.choice
qdel(picker)
/**
* tgui_input_color
*
* Datum used for instantiating and using a TGUI-controlled color picker.
*/
/datum/tgui_input_color
/// 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_input_color was created, for displaying timeout progress.
var/start_time
/// The lifespan of the tgui_input_color, 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_input_color was closed by the user.
var/closed
/// The attached timer that handles this objects timeout deletion
var/deletion_timer
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
var/datum/ui_state/state
/datum/tgui_input_color/New(mob/user, message, title, default, timeout, autofocus, ui_state)
src.autofocus = autofocus
src.title = title
src.default = default
src.message = message
src.state = ui_state
if(timeout)
src.timeout = timeout
start_time = world.time
deletion_timer = QDEL_IN(src, timeout)
/datum/tgui_input_color/Destroy(force, ...)
SStgui.close_uis(src)
state = null
deltimer(deletion_timer)
return ..()
/**
* Waits for a user's response to the tgui_input_color's prompt before returning. Returns early if
* the window was closed by the user.
*/
/datum/tgui_input_color/proc/wait()
while(!choice && !closed && !QDELETED(src))
stoplag(1)
/datum/tgui_input_color/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_input_color/ui_close(mob/user)
closed = TRUE
/datum/tgui_input_color/ui_state(mob/user)
return state
/datum/tgui_input_color/ui_static_data(mob/user)
var/list/data = list()
data["autofocus"] = autofocus
data["large_buttons"] = !user.client?.prefs || (user.client.prefs.toggles2 & PREFTOGGLE_2_LARGE_INPUT_BUTTONS)
data["swapped_buttons"] = !user.client?.prefs || (user.client.prefs.toggles2 & PREFTOGGLE_2_SWAP_INPUT_BUTTONS)
data["title"] = title
data["default_color"] = default
data["message"] = message
return data
/datum/tgui_input_color/ui_data(mob/user)
var/list/data = list()
if(timeout)
data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
return data
/datum/tgui_input_color/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("submit")
if(!findtext(params["entry"], GLOB.is_color))
return
choice = params["entry"]
closed = TRUE
SStgui.close_uis(src)
return TRUE
if("cancel")
closed = TRUE
SStgui.close_uis(src)
return TRUE
+1
View File
@@ -2953,6 +2953,7 @@
#include "code\modules\tgui\states\strippable_state.dm"
#include "code\modules\tgui\states\viewer_state.dm"
#include "code\modules\tgui\tgui_input\alert_input.dm"
#include "code\modules\tgui\tgui_input\color_input.dm"
#include "code\modules\tgui\tgui_input\keycombo_input.dm"
#include "code\modules\tgui\tgui_input\list_input.dm"
#include "code\modules\tgui\tgui_input\number_input.dm"
+338
View File
@@ -0,0 +1,338 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
const EPSILON = 0.0001;
export class Color {
r: number;
g: number;
b: number;
a: number;
constructor(r = 0, g = 0, b = 0, a = 1) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
toString() {
return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${this.a | 0})`;
}
/**
* Creates a color from the CSS hex color notation.
*/
static fromHex(hex: string): Color {
return new Color(parseInt(hex.substr(1, 2), 16), parseInt(hex.substr(3, 2), 16), parseInt(hex.substr(5, 2), 16));
}
/**
* Linear interpolation of two colors.
*/
static lerp(c1: Color, c2: Color, n: number): Color {
return new Color(
(c2.r - c1.r) * n + c1.r,
(c2.g - c1.g) * n + c1.g,
(c2.b - c1.b) * n + c1.b,
(c2.a - c1.a) * n + c1.a
);
}
/**
* Loops up the color in the provided list of colors
* with linear interpolation.
*/
static lookup(value: number, colors: Color[] = []): Color {
const len = colors.length;
if (len < 2) {
throw new Error('Needs at least two colors!');
}
const scaled = value * (len - 1);
if (value < EPSILON) {
return colors[0];
}
if (value >= 1 - EPSILON) {
return colors[len - 1];
}
const ratio = scaled % 1;
const index = scaled | 0;
return Color.lerp(colors[index], colors[index + 1], ratio);
}
}
/*
* MIT License
* https://github.com/omgovich/react-colorful/
*
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
* 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 <angle> units.
* https://developer.mozilla.org/en-US/docs/Web/CSS/angle
*/
const angleUnits: Record<string, number> = {
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);
// prettier-ignore
const hh = delta
? max === r
? (g - b) / delta
: max === g
? 2 + (b - r) / delta
: 4 + (r - g) / delta
: 0;
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
);
};
@@ -0,0 +1,138 @@
/**
* MIT License
* https://github.com/omgovich/react-colorful/
*
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
* 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 { Component, InfernoNode, createRef, RefObject } from 'inferno';
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) || self;
};
// 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 pointer = event as MouseEvent;
return {
left: clamp((pointer.pageX - (rect.left + getParentWindow(node).pageXOffset)) / rect.width, 0, 1),
top: clamp((pointer.pageY - (rect.top + getParentWindow(node).pageYOffset)) / rect.height, 0, 1),
};
};
export interface InteractiveProps {
onMove: (interaction: Interaction) => void;
onKey: (offset: Interaction) => void;
children: InfernoNode[];
style?: any;
}
export class Interactive extends Component {
containerRef: RefObject<HTMLDivElement>;
props: InteractiveProps;
constructor(props: InteractiveProps) {
super();
this.props = props;
this.containerRef = createRef();
}
handleMoveStart = (event: MouseEvent) => {
const el = this.containerRef?.current;
if (!el) return;
// Prevent text selection
event.preventDefault();
el.focus();
this.props.onMove(getRelativePosition(el, event));
this.toggleDocumentEvents(true);
};
handleMove = (event: MouseEvent) => {
// Prevent text selection
event.preventDefault();
// If user moves the pointer outside of the window or iframe bounds and release it there,
// `mouseup`/`touchend` won't be fired. In order to stop the picker from following the cursor
// after the user has moved the mouse/finger back to the document, we check `event.buttons`
// and `event.touches`. It allows us to detect that the user is just moving his pointer
// without pressing it down
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: KeyboardEvent) => {
const keyCode = event.which || event.keyCode;
// Ignore all keys except arrow ones
if (keyCode < 37 || keyCode > 40) return;
// Do not scroll page by arrow keys when document is focused on the element
event.preventDefault();
// Send relative offset to the parent component.
// We use codes (37←, 38↑, 39→, 40↓) instead of keys ('ArrowRight', 'ArrowDown', etc)
// to reduce the size of the library
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);
// Add or remove additional pointer event listeners
const toggleEvent = state ? parentWindow.addEventListener : parentWindow.removeEventListener;
toggleEvent('mousemove', this.handleMove);
toggleEvent('mouseup', this.handleMoveEnd);
}
componentDidMount() {
this.toggleDocumentEvents(true);
}
componentWillUnmount() {
this.toggleDocumentEvents(false);
}
render() {
return (
<div
{...this.props}
style={this.props.style}
ref={this.containerRef}
onMouseDown={this.handleMoveStart}
className="react-colorful__interactive"
onKeyDown={this.handleKeyDown}
tabIndex={0}
role="slider"
>
{this.props.children}
</div>
);
}
}
+38
View File
@@ -0,0 +1,38 @@
/**
* MIT License
* https://github.com/omgovich/react-colorful/
*
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
* 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 { InfernoNode } from 'inferno';
interface PointerProps {
className?: string;
top?: number;
left: number;
color: string;
}
export const Pointer = ({ className, color, left, top = 0.5 }: PointerProps): InfernoNode => {
const nodeClassName = classes(['react-colorful__pointer', className]);
const style = {
top: `${top * 100}%`,
left: `${left * 100}%`,
};
return (
<div className={nodeClassName} style={style}>
<div className="react-colorful__pointer-fill" style={{ 'background-color': color }} />
</div>
);
};
+2
View File
@@ -21,6 +21,7 @@ export { DraggableControl } from './DraggableControl';
export { Dropdown } from './Dropdown';
export { Flex } from './Flex';
export { Grid } from './Grid';
export { Interactive } from './Interactive';
export { Image } from './Image';
export { DmIcon } from './DmIcon';
export { Icon } from './Icon';
@@ -33,6 +34,7 @@ export { Modal } from './Modal';
export { NanoMap } from './NanoMap';
export { NoticeBox } from './NoticeBox';
export { NumberInput } from './NumberInput';
export { Pointer } from './Pointer';
export { Popper } from './Popper';
export { ProgressBar } from './ProgressBar';
export { RestrictedInput } from './RestrictedInput';
@@ -0,0 +1,596 @@
/* eslint-disable react/state-in-constructor */
/**
* @file
* @copyright 2023 itsmeow
* @license MIT
*/
import { Loader } from './common/Loader';
import { useBackend, useLocalState } from '../backend';
import { Autofocus, Box, Flex, Section, Stack, Pointer, NumberInput, Tooltip } from '../components';
import { Window } from '../layouts';
import { clamp } from 'common/math';
import { hexToHsva, HsvaColor, hsvaToHex, hsvaToHslString, hsvaToRgba, rgbaToHsva, validHex } from 'common/color';
import { Interaction, Interactive } from 'tgui/components/Interactive';
import { classes } from 'common/react';
import { Component, FocusEvent, FormEvent, InfernoNode } from 'inferno';
import { logger } from 'tgui/logging';
import { InputButtons } from './common/InputButtons';
type ColorPickerData = {
autofocus: boolean;
buttons: string[];
message: string;
large_buttons: boolean;
swapped_buttons: boolean;
timeout: number;
title: string;
default_color: string;
};
export const ColorPickerModal = (_, context) => {
const { data } = useBackend<ColorPickerData>(context);
const { timeout, message, title, autofocus, default_color = '#000000' } = data;
let [selectedColor, setSelectedColor] = useLocalState<HsvaColor>(
context,
'color_picker_choice',
hexToHsva(default_color)
);
return (
<Window height={400} title={title} width={600} theme="generic">
{!!timeout && <Loader value={timeout} />}
<Window.Content>
<Stack fill vertical>
{message && (
<Stack.Item m={1}>
<Section fill>
<Box color="label" overflow="hidden">
{message}
</Box>
</Section>
</Stack.Item>
)}
<Stack.Item grow>
<Section fill>
{!!autofocus && <Autofocus />}
<ColorSelector color={selectedColor} setColor={setSelectedColor} defaultColor={default_color} />
</Section>
</Stack.Item>
<Stack.Item>
<InputButtons input={hsvaToHex(selectedColor)} />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
export const ColorSelector = (
{ color, setColor, defaultColor }: { color: HsvaColor; setColor; defaultColor: string },
context
) => {
const handleChange = (params: Partial<HsvaColor>) => {
setColor((current: HsvaColor) => {
return Object.assign({}, current, params);
});
};
const rgb = hsvaToRgba(color);
const hexColor = hsvaToHex(color);
return (
<Flex direction="row">
<Flex.Item mr={2}>
<Stack vertical>
<Stack.Item>
<div className="react-colorful">
<SaturationValue hsva={color} onChange={handleChange} />
<Hue hue={color.h} onChange={handleChange} className="react-colorful__last-control" />
</div>
</Stack.Item>
<Stack.Item>
<Box inline width="100px" height="20px" textAlign="center">
Current
</Box>
<Box inline width="100px" height="20px" textAlign="center">
Previous
</Box>
<br />
<Tooltip content={hexColor} position="bottom">
<Box inline width="100px" height="30px" backgroundColor={hexColor} />
</Tooltip>
<Tooltip content={defaultColor} position="bottom">
<Box inline width="100px" height="30px" backgroundColor={defaultColor} />
</Tooltip>
</Stack.Item>
</Stack>
</Flex.Item>
<Flex.Item grow fontSize="15px" lineHeight="24px">
<Stack vertical>
<Stack.Item>
<Stack>
<Stack.Item>
<Box textColor="label">Hex:</Box>
</Stack.Item>
<Stack.Item grow height="24px">
<HexColorInput
fluid
color={hsvaToHex(color).substring(1)}
onChange={(value) => {
logger.info(value);
setColor(hexToHsva(value));
}}
prefixed
/>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<Stack>
<Stack.Item width="25px">
<Box textColor="label">H:</Box>
</Stack.Item>
<Stack.Item grow>
<Hue hue={color.h} onChange={handleChange} />
</Stack.Item>
<Stack.Item>
<TextSetter value={color.h} callback={(_, v) => handleChange({ h: v })} max={360} unit="°" />
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<Stack>
<Stack.Item width="25px">
<Box textColor="label">S:</Box>
</Stack.Item>
<Stack.Item grow>
<Saturation color={color} onChange={handleChange} />
</Stack.Item>
<Stack.Item>
<TextSetter value={color.s} callback={(_, v) => handleChange({ s: v })} unit="%" />
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<Stack>
<Stack.Item width="25px">
<Box textColor="label">V:</Box>
</Stack.Item>
<Stack.Item grow>
<Value color={color} onChange={handleChange} />
</Stack.Item>
<Stack.Item>
<TextSetter value={color.v} callback={(_, v) => handleChange({ v: v })} unit="%" />
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<Stack>
<Stack.Item width="25px">
<Box textColor="label">R:</Box>
</Stack.Item>
<Stack.Item grow>
<RGBSlider color={color} onChange={handleChange} target="r" />
</Stack.Item>
<Stack.Item>
<TextSetter
value={rgb.r}
callback={(_, v) => {
rgb.r = v;
handleChange(rgbaToHsva(rgb));
}}
max={255}
/>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<Stack>
<Stack.Item width="25px">
<Box textColor="label">G:</Box>
</Stack.Item>
<Stack.Item grow>
<RGBSlider color={color} onChange={handleChange} target="g" />
</Stack.Item>
<Stack.Item>
<TextSetter
value={rgb.g}
callback={(_, v) => {
rgb.g = v;
handleChange(rgbaToHsva(rgb));
}}
max={255}
/>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<Stack>
<Stack.Item width="25px">
<Box textColor="label">B:</Box>
</Stack.Item>
<Stack.Item grow>
<RGBSlider color={color} onChange={handleChange} target="b" />
</Stack.Item>
<Stack.Item>
<TextSetter
value={rgb.b}
callback={(_, v) => {
rgb.b = v;
handleChange(rgbaToHsva(rgb));
}}
max={255}
/>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Flex.Item>
</Flex>
);
};
const TextSetter = ({
value,
callback,
min = 0,
max = 100,
unit,
}: {
value: number;
callback: any;
min?: number;
max?: number;
unit?: string;
}) => {
return (
<NumberInput
width="70px"
value={Math.round(value)}
step={1}
minValue={min}
maxValue={max}
onChange={callback}
unit={unit}
/>
);
};
/**
* MIT License
* https://github.com/omgovich/react-colorful/
*
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
* 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.
*/
interface HexColorInputProps extends Omit<ColorInputBaseProps, 'escape' | 'validate'> {
/** Enables `#` prefix displaying */
prefixed?: boolean;
/** Allows `#rgba` and `#rrggbbaa` color formats */
alpha?: boolean;
}
/** Adds "#" symbol to the beginning of the string */
const prefix = (value: string) => '#' + value;
export const HexColorInput = (props: HexColorInputProps): InfernoNode => {
const { prefixed, alpha, color, fluid, onChange, ...rest } = props;
/** Escapes all non-hexadecimal characters including "#" */
const escape = (value: string) => value.replace(/([^0-9A-F]+)/gi, '').substring(0, alpha ? 8 : 6);
/** Validates hexadecimal strings */
const validate = (value: string) => validHex(value, alpha);
return (
<ColorInput
{...rest}
fluid={fluid}
color={color}
onChange={onChange}
escape={escape}
format={prefixed ? prefix : undefined}
validate={validate}
/>
);
};
interface ColorInputBaseProps {
fluid?: boolean;
color: string;
onChange: (newColor: string) => void;
/** Blocks typing invalid characters and limits string length */
escape: (value: string) => string;
/** Checks that value is valid color string */
validate: (value: string) => boolean;
/** Processes value before displaying it in the input */
format?: (value: string) => string;
}
export class ColorInput extends Component {
props: ColorInputBaseProps;
state: { localValue: string };
constructor(props: ColorInputBaseProps) {
super();
this.props = props;
this.state = { localValue: this.props.escape(this.props.color) };
}
// Trigger `onChange` handler only if the input value is a valid color
handleInput = (e: FormEvent<HTMLInputElement>) => {
const inputValue = this.props.escape(e.currentTarget.value);
this.setState({ localValue: inputValue });
};
// Take the color from props if the last typed color (in local state) is not valid
handleBlur = (e: FocusEvent<HTMLInputElement>) => {
if (e.currentTarget) {
if (!this.props.validate(e.currentTarget.value)) {
this.setState({ localValue: this.props.escape(this.props.color) }); // return to default;
} else {
this.props.onChange(this.props.escape ? this.props.escape(e.currentTarget.value) : e.currentTarget.value);
}
}
};
componentDidUpdate(prevProps, prevState): void {
if (prevProps.color !== this.props.color) {
// Update the local state when `color` property value is changed
this.setState({ localValue: this.props.escape(this.props.color) });
}
}
render() {
return (
<Box className={classes(['Input', this.props.fluid && 'Input--fluid'])}>
<div className="Input__baseline">.</div>
<input
className="Input__input"
value={this.props.format ? this.props.format(this.state.localValue) : this.state.localValue}
spellCheck="false" // the element should not be checked for spelling errors
onInput={this.handleInput}
onBlur={this.handleBlur}
/>
</Box>
);
}
}
const SaturationValue = ({ hsva, onChange }) => {
const handleMove = (interaction: Interaction) => {
onChange({
s: interaction.left * 100,
v: 100 - interaction.top * 100,
});
};
const handleKey = (offset: Interaction) => {
// Saturation and brightness always fit into [0, 100] range
onChange({
s: clamp(hsva.s + offset.left * 100, 0, 100),
v: clamp(hsva.v - offset.top * 100, 0, 100),
});
};
const containerStyle = {
'background-color': `${hsvaToHslString({ h: hsva.h, s: 100, v: 100, a: 1 })} !important`,
};
return (
<div className="react-colorful__saturation_value" style={containerStyle}>
<Interactive
onMove={handleMove}
onKey={handleKey}
aria-label="Color"
aria-valuetext={`Saturation ${Math.round(hsva.s)}%, Brightness ${Math.round(hsva.v)}%`}
>
<Pointer
className="react-colorful__saturation_value-pointer"
top={1 - hsva.v / 100}
left={hsva.s / 100}
color={hsvaToHslString(hsva)}
/>
</Interactive>
</div>
);
};
const Hue = ({
className,
hue,
onChange,
}: {
className?: string;
hue: number;
onChange: (newHue: { h: number }) => void;
}) => {
const handleMove = (interaction: Interaction) => {
onChange({ h: 360 * interaction.left });
};
const handleKey = (offset: Interaction) => {
// Hue measured in degrees of the color circle ranging from 0 to 360
onChange({
h: clamp(hue + offset.left * 360, 0, 360),
});
};
const nodeClassName = classes(['react-colorful__hue', className]);
return (
<div className={nodeClassName}>
<Interactive
onMove={handleMove}
onKey={handleKey}
aria-label="Hue"
aria-valuenow={Math.round(hue)}
aria-valuemax="360"
aria-valuemin="0"
>
<Pointer
className="react-colorful__hue-pointer"
left={hue / 360}
color={hsvaToHslString({ h: hue, s: 100, v: 100, a: 1 })}
/>
</Interactive>
</div>
);
};
const Saturation = ({
className,
color,
onChange,
}: {
className?: string;
color: HsvaColor;
onChange: (newSaturation: { s: number }) => void;
}) => {
const handleMove = (interaction: Interaction) => {
onChange({ s: 100 * interaction.left });
};
const handleKey = (offset: Interaction) => {
// Hue measured in degrees of the color circle ranging from 0 to 100
onChange({
s: clamp(color.s + offset.left * 100, 0, 100),
});
};
const nodeClassName = classes(['react-colorful__saturation', className]);
return (
<div className={nodeClassName}>
<Interactive
style={{
'background': `linear-gradient(to right, ${hsvaToHslString({
h: color.h,
s: 0,
v: color.v,
a: 1,
})}, ${hsvaToHslString({ h: color.h, s: 100, v: color.v, a: 1 })})`,
}}
onMove={handleMove}
onKey={handleKey}
aria-label="Saturation"
aria-valuenow={Math.round(color.s)}
aria-valuemax="100"
aria-valuemin="0"
>
<Pointer
className="react-colorful__saturation-pointer"
left={color.s / 100}
color={hsvaToHslString({ h: color.h, s: color.s, v: color.v, a: 1 })}
/>
</Interactive>
</div>
);
};
const Value = ({
className,
color,
onChange,
}: {
className?: string;
color: HsvaColor;
onChange: (newValue: { v: number }) => void;
}) => {
const handleMove = (interaction: Interaction) => {
onChange({ v: 100 * interaction.left });
};
const handleKey = (offset: Interaction) => {
onChange({
v: clamp(color.v + offset.left * 100, 0, 100),
});
};
const nodeClassName = classes(['react-colorful__value', className]);
return (
<div className={nodeClassName}>
<Interactive
style={{
'background': `linear-gradient(to right, ${hsvaToHslString({
h: color.h,
s: color.s,
v: 0,
a: 1,
})}, ${hsvaToHslString({ h: color.h, s: color.s, v: 100, a: 1 })})`,
}}
onMove={handleMove}
onKey={handleKey}
aria-label="Value"
aria-valuenow={Math.round(color.s)}
aria-valuemax="100"
aria-valuemin="0"
>
<Pointer
className="react-colorful__value-pointer"
left={color.v / 100}
color={hsvaToHslString({ h: color.h, s: color.s, v: color.v, a: 1 })}
/>
</Interactive>
</div>
);
};
const RGBSlider = ({
className,
color,
onChange,
target,
}: {
className?: string;
color: HsvaColor;
onChange: (newValue: HsvaColor) => void;
target: string;
}) => {
const rgb = hsvaToRgba(color);
const setNewTarget = (value: number) => {
rgb[target] = value;
onChange(rgbaToHsva(rgb));
};
const handleMove = (interaction: Interaction) => {
setNewTarget(255 * interaction.left);
};
const handleKey = (offset: Interaction) => {
setNewTarget(clamp(rgb[target] + offset.left * 255, 0, 255));
};
const nodeClassName = classes([`react-colorful__${target}`, className]);
let selected =
target === 'r'
? `rgb(${Math.round(rgb.r)},0,0)`
: target === 'g'
? `rgb(0,${Math.round(rgb.g)},0)`
: `rgb(0,0,${Math.round(rgb.b)})`;
return (
<div className={nodeClassName}>
<Interactive
onMove={handleMove}
onKey={handleKey}
aria-valuenow={rgb[target]}
aria-valuemax="100"
aria-valuemin="0"
>
<Pointer className={`react-colorful__${target}-pointer`} left={rgb[target] / 255} color={selected} />
</Interactive>
</div>
);
};
@@ -0,0 +1,140 @@
/**
* MIT License
* https://github.com/omgovich/react-colorful/
*
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
* 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.
*/
@use '../colors.scss';
@use '../base.scss';
.react-colorful {
position: relative;
display: flex;
flex-direction: column;
width: 200px;
height: 200px;
user-select: none;
cursor: default;
}
.react-colorful__saturation_value {
position: relative;
flex-grow: 1;
border-color: transparent; /* Fixes https://github.com/omgovich/react-colorful/issues/139 */
border-bottom: 12px solid #000;
border-radius: 8px 8px 0 0;
background-image: linear-gradient(to top, rgba(0, 0, 0, 255), rgba(0, 0, 0, 0)),
linear-gradient(to right, rgba(255, 255, 255, 255), rgba(255, 255, 255, 0));
}
.react-colorful__pointer-fill,
.react-colorful__alpha-gradient {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
pointer-events: none;
border-radius: inherit;
}
/* Improve elements rendering on light backgrounds */
.react-colorful__alpha-gradient,
.react-colorful__saturation_value {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.react-colorful__hue,
.react-colorful__r,
.react-colorful__g,
.react-colorful__b,
.react-colorful__alpha,
.react-colorful__saturation,
.react-colorful__value {
position: relative;
height: 24px;
}
.react-colorful__hue {
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}
.react-colorful__r {
background: linear-gradient(to right, #000, #f00);
}
.react-colorful__g {
background: linear-gradient(to right, #000, #0f0);
}
.react-colorful__b {
background: linear-gradient(to right, #000, #00f);
}
/* Round bottom corners of the last element: `Hue` for `ColorPicker` or `Alpha` for `AlphaColorPicker` */
.react-colorful__last-control {
border-radius: 0 0 8px 8px;
}
.react-colorful__interactive {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: inherit;
outline: none;
/* Don't trigger the default scrolling behavior when the event is originating from this element */
touch-action: none;
}
.react-colorful__pointer {
position: absolute;
z-index: 1;
box-sizing: border-box;
width: 28px;
height: 28px;
transform: translate(-50%, -50%);
background-color: #cfcfcf;
border: 2px solid #cfcfcf;
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
}
.react-colorful__interactive:focus .react-colorful__pointer {
transform: translate(-50%, -50%) scale(1.1);
background-color: #fff;
border-color: #fff;
}
/* Chessboard-like pattern for alpha related elements */
.react-colorful__alpha,
.react-colorful__alpha-pointer {
background-color: #fff;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><rect x="8" width="8" height="8"/><rect y="8" width="8" height="8"/></svg>');
}
.react-colorful__saturation-pointer,
.react-colorful__value-pointer,
.react-colorful__hue-pointer,
.react-colorful__r-pointer,
.react-colorful__g-pointer,
.react-colorful__b-pointer {
z-index: 1;
width: 20px;
height: 20px;
}
/* Display the saturation value pointer over the hue one */
.react-colorful__saturation_value-pointer {
z-index: 3;
}
+1
View File
@@ -49,6 +49,7 @@
@include meta.load-css('./interfaces/AlertModal.scss');
@include meta.load-css('./interfaces/BrigCells.scss');
@include meta.load-css('./interfaces/CameraConsole.scss');
@include meta.load-css('./interfaces/ColorPicker.scss');
@include meta.load-css('./interfaces/Contractor.scss');
@include meta.load-css('./interfaces/CrewMonitor.scss');
@include meta.load-css('./interfaces/ExosuitFabricator.scss');
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long