From fc06554b2850464fc82edc0ef318eb8fba603b0a Mon Sep 17 00:00:00 2001 From: Aylong Date: Tue, 28 Nov 2023 10:14:21 +0200 Subject: [PATCH] Testing 1 --- code/__HELPERS/lists.dm | 14 +- code/modules/clothing/clothing.dm | 2 +- code/modules/tgui/modules/tgui_input_list.dm | 24 +- tgui/packages/tgui/interfaces/ListInput.js | 7 +- tgui/packages/tgui/interfaces/ListInput2.js | 237 +++++++++++++++++++ 5 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/ListInput2.js diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index c4f932574a6..3cc45ffca0f 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -848,7 +848,7 @@ for(var/thing in flat_list) .[thing] = TRUE -///compare two lists, returns TRUE if they are the same +/// compare two lists, returns TRUE if they are the same /proc/compare_list(list/l, list/d) if(!islist(l) || !islist(d)) return FALSE @@ -865,3 +865,15 @@ // Pick something else from a list than we last picked /proc/pick_excluding(list/l, exclude) return pick(l - exclude) + +/// takes an input_key, as text, and the list of keys already used, outputting a replacement key in the format of "[input_key] ([number_of_duplicates])" if it finds a duplicate +/// use this for lists of things that might have the same name, like mobs or objects, that you plan on giving to a player as input +/proc/avoid_assoc_duplicate_keys(input_key, list/used_key_list) + if(!input_key || !istype(used_key_list)) + return + if(used_key_list[input_key]) + used_key_list[input_key]++ + input_key = "[input_key] ([used_key_list[input_key]])" + else + used_key_list[input_key] = 1 + return input_key diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 01c12005572..fccb00d347b 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -311,7 +311,7 @@ return var/list/modes = list("Off", "Binary sensors", "Vitals tracker", "Tracking beacon") - var/switchMode = tgui_input_list(user, "Select a sensor mode.", "Suit Sensor Mode", modes) + var/switchMode = tgui_input_list(user, "Select a sensor mode", "Suit Sensor - [sensor_mode]", modes) if(!user.Adjacent(src)) to_chat(user, "You have moved too far away!") diff --git a/code/modules/tgui/modules/tgui_input_list.dm b/code/modules/tgui/modules/tgui_input_list.dm index c642ef6bc9d..dde10e9f808 100644 --- a/code/modules/tgui/modules/tgui_input_list.dm +++ b/code/modules/tgui/modules/tgui_input_list.dm @@ -7,9 +7,10 @@ * * message - The content of the input box, shown in the body of the TGUI window. * * title - The title of the input box, shown on the top of the TGUI window. * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. + * * default - If an option is already preselected on the UI. Current values, etc. * * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout. */ -/proc/tgui_input_list(mob/user, message, title, list/buttons, timeout = 0) +/proc/tgui_input_list(mob/user, message, title = "Select", list/buttons, default, timeout = 0) if(!user) user = usr if(!length(buttons)) @@ -19,7 +20,7 @@ return var/client/client = user user = client.mob - var/datum/tgui_list_input/input = new(user, message, title, buttons, timeout) + var/datum/tgui_list_input/input = new(user, message, title, buttons, default, timeout) input.ui_interact(user) input.wait() if(input) @@ -35,10 +36,11 @@ * * message - The content of the input box, shown in the body of the TGUI window. * * title - The title of the input box, shown on the top of the TGUI window. * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. + * * default - If an option is already preselected on the UI. Current values, etc. * * callback - The callback to be invoked when a choice is made. * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout. */ -/proc/tgui_input_list_async(mob/user, message, title, list/buttons, datum/callback/callback, timeout = 60 SECONDS) +/proc/tgui_input_list_async(mob/user, message, title = "Select", list/buttons, default, datum/callback/callback, timeout = 60 SECONDS) if(!user) user = usr if(!length(buttons)) @@ -48,7 +50,7 @@ return var/client/client = user user = client.mob - var/datum/tgui_list_input/async/input = new(user, message, title, buttons, timeout, callback) + var/datum/tgui_list_input/async/input = new(user, message, title, buttons, default, timeout, callback) input.ui_interact(user) /** @@ -68,6 +70,8 @@ var/list/buttons_map /// The button that the user has pressed, null if no selection has been made var/choice + /// The default button to be selected + var/default /// The time at which the tgui_list_input was created, for displaying timeout progress. var/start_time /// The lifespan of the tgui_list_input, after which the window will close and delete itself. @@ -75,18 +79,23 @@ /// Boolean field describing if the tgui_list_input was closed by the user. var/closed -/datum/tgui_list_input/New(mob/user, message, title, list/buttons, timeout) +/datum/tgui_list_input/New(mob/user, message, title, list/buttons, default, timeout) src.title = title src.message = message src.buttons = list() src.buttons_map = list() + src.default = default + var/list/repeat_items = list() // Gets rid of illegal characters var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"}) for(var/i in buttons) + if(!i) + continue var/string_key = whitelistedWords.Replace("[i]", "") - + // Avoids duplicated keys E.g: when areas have the same name + string_key = avoid_assoc_duplicate_keys(string_key, repeat_items) src.buttons += string_key src.buttons_map[string_key] = i @@ -124,6 +133,7 @@ var/list/data = list() data["title"] = title data["message"] = message + data["init_value"] = default || buttons[1] data["buttons"] = buttons return data @@ -157,7 +167,7 @@ /// The callback to be invoked by the tgui_list_input upon having a choice made. var/datum/callback/callback -/datum/tgui_list_input/async/New(mob/user, message, title, list/buttons, timeout, callback) +/datum/tgui_list_input/async/New(mob/user, message, title, list/buttons, default, timeout, callback) ..() src.callback = callback diff --git a/tgui/packages/tgui/interfaces/ListInput.js b/tgui/packages/tgui/interfaces/ListInput.js index 6256442fe63..0da9ad6b141 100644 --- a/tgui/packages/tgui/interfaces/ListInput.js +++ b/tgui/packages/tgui/interfaces/ListInput.js @@ -8,7 +8,7 @@ import { clamp01 } from 'common/math'; import { useBackend, useLocalState } from '../backend'; import { Box, Button, Flex, Section, Input } from '../components'; import { Window } from '../layouts'; -import { ARROW_KEY_UP, ARROW_KEY_DOWN } from '../hotkeys'; +import { ARROW_KEY_UP, ARROW_KEY_DOWN, KEY_ENTER, KEY_SPACE } from '../hotkeys'; let lastScrollTime = 0; @@ -102,6 +102,11 @@ export const ListInput = (props, context) => { return; } + if (e.keyCode === KEY_SPACE || e.keyCode === KEY_ENTER) { + act("choose", { choice: selectedButton }); + return; + } + const charCode = String.fromCharCode(e.keyCode).toLowerCase(); if (!charCode) { return; diff --git a/tgui/packages/tgui/interfaces/ListInput2.js b/tgui/packages/tgui/interfaces/ListInput2.js new file mode 100644 index 00000000000..0da9ad6b141 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ListInput2.js @@ -0,0 +1,237 @@ +/** + * @file + * @copyright 2020 watermelon914 (https://github.com/watermelon914) + * @license MIT + */ + +import { clamp01 } from 'common/math'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Flex, Section, Input } from '../components'; +import { Window } from '../layouts'; +import { ARROW_KEY_UP, ARROW_KEY_DOWN, KEY_ENTER, KEY_SPACE } from '../hotkeys'; + +let lastScrollTime = 0; + +export const ListInput = (props, context) => { + const { act, data } = useBackend(context); + const { title, message, buttons, timeout } = data; + + // Search + const [showSearchBar, setShowSearchBar] = useLocalState( + context, + 'search_bar', + false + ); + const [displayedArray, setDisplayedArray] = useLocalState( + context, + 'displayed_array', + buttons + ); + + // KeyPress + const [searchArray, setSearchArray] = useLocalState( + context, + 'search_array', + [] + ); + const [searchIndex, setSearchIndex] = useLocalState( + context, + 'search_index', + 0 + ); + const [lastCharCode, setLastCharCode] = useLocalState( + context, + 'last_char_code', + null + ); + + // Selected Button + const [selectedButton, setSelectedButton] = useLocalState( + context, + 'selected_button', + buttons[0] + ); + return ( + + {timeout !== undefined && } + + + +
{ + e.preventDefault(); + if (lastScrollTime > performance.now()) { + return; + } + lastScrollTime = performance.now() + 125; + + if ( + e.keyCode === ARROW_KEY_UP || + e.keyCode === ARROW_KEY_DOWN + ) { + let direction = 1; + if (e.keyCode === ARROW_KEY_UP) { + direction = -1; + } + + let index = 0; + for (index; index < buttons.length; index++) { + if (buttons[index] === selectedButton) { + break; + } + } + index += direction; + if (index < 0) { + index = buttons.length - 1; + } else if (index >= buttons.length) { + index = 0; + } + setSelectedButton(buttons[index]); + setLastCharCode(null); + document.getElementById(buttons[index]).focus(); + return; + } + + if (e.keyCode === KEY_SPACE || e.keyCode === KEY_ENTER) { + act("choose", { choice: selectedButton }); + return; + } + + const charCode = String.fromCharCode(e.keyCode).toLowerCase(); + if (!charCode) { + return; + } + + let foundValue; + if (charCode === lastCharCode && searchArray.length > 0) { + const nextIndex = searchIndex + 1; + + if (nextIndex < searchArray.length) { + foundValue = searchArray[nextIndex]; + setSearchIndex(nextIndex); + } else { + foundValue = searchArray[0]; + setSearchIndex(0); + } + } else { + const resultArray = displayedArray.filter( + (value) => value.substring(0, 1).toLowerCase() === charCode + ); + + if (resultArray.length > 0) { + setSearchArray(resultArray); + setSearchIndex(0); + foundValue = resultArray[0]; + } + } + + if (foundValue) { + setLastCharCode(charCode); + setSelectedButton(foundValue); + document.getElementById(foundValue).focus(); + } + }} + buttons={ +
+
+ {showSearchBar && ( + + + setDisplayedArray( + buttons.filter( + (val) => + val.toLowerCase().search(value.toLowerCase()) !== -1 + ) + ) + } + /> + + )} + + + +