From 1499fcd2723962dc329bc643314ef2f66b659ef3 Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Thu, 13 Apr 2023 05:35:28 -0400 Subject: [PATCH] Tgui input checkboxes (#74544) ## About The Pull Request A request from @NamelessFairy: A tgui window that gives the user multiple items to select. The window returns the list of items selected. The ui design is open to critique, reviews, etc. Pretty simplistic. ![Screenshot 2023-04-07 174056](https://user-images.githubusercontent.com/42397676/230695947-bc6b6d94-c984-4a2e-b290-a21cb7f8961e.png) ## Why It's Good For The Game Sequentially asking yes/no is pretty ugly ## Changelog :cl: ui: Adds a new tgui input window for checkboxes. /:cl: --------- Co-authored-by: Jeremiah --- code/modules/tgui_input/checkboxes.dm | 127 ++++++++++++++++++ tgstation.dme | 1 + .../tgui/interfaces/CheckboxInput.tsx | 101 ++++++++++++++ .../tgui/interfaces/common/InputButtons.tsx | 5 +- 4 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 code/modules/tgui_input/checkboxes.dm create mode 100644 tgui/packages/tgui/interfaces/CheckboxInput.tsx diff --git a/code/modules/tgui_input/checkboxes.dm b/code/modules/tgui_input/checkboxes.dm new file mode 100644 index 00000000000..b83d9b3b333 --- /dev/null +++ b/code/modules/tgui_input/checkboxes.dm @@ -0,0 +1,127 @@ +/** + * ### tgui_input_checkbox + * Opens a window with a list of checkboxes and returns a list of selected choices. + * + * user - The mob to display the window to + * message - The message inside the window + * title - The title of the window + * list/items - The list of items to display + * max_checked - The maximum number of checkboxes that can be checked (optional) + * timeout - The timeout for the input (optional) + */ +/proc/tgui_input_checkboxes(mob/user, message, title = "Select", list/items, max_checked = 50, timeout = 0) + if (!user) + user = usr + if(!length(items)) + return + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) + return input(user, message, title) as null|anything in items + var/datum/tgui_checkbox_input/input = new(user, message, title, items, max_checked, timeout) + input.ui_interact(user) + input.wait() + if (input) + . = input.choices + qdel(input) + +/// Window for tgui_input_checkboxes +/datum/tgui_checkbox_input + /// Title of the window + var/title + /// Message to display + var/message + /// List of items to display + var/list/items + /// List of selected items + var/list/choices + /// Time when the input was created + var/start_time + /// Timeout for the input + var/timeout + /// Whether the input was closed + var/closed + /// Maximum number of checkboxes that can be checked + var/max_checked + +/datum/tgui_checkbox_input/New(mob/user, message, title, list/items, max_checked, timeout) + src.title = title + src.message = message + src.items = items.Copy() + src.max_checked = max_checked + + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + +/datum/tgui_checkbox_input/Destroy(force, ...) + SStgui.close_uis(src) + QDEL_NULL(items) + + return ..() + +/datum/tgui_checkbox_input/proc/wait() + while (!closed && !QDELETED(src)) + stoplag(1) + +/datum/tgui_checkbox_input/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CheckboxInput") + ui.open() + +/datum/tgui_checkbox_input/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_checkbox_input/ui_state(mob/user) + return GLOB.always_state + +/datum/tgui_checkbox_input/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_checkbox_input/ui_static_data(mob/user) + var/list/data = list() + + data["items"] = items + data["max_checked"] = max_checked + data["large_buttons"] = user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_large) + data["message"] = message + data["swapped_buttons"] = user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_swapped) + data["title"] = title + + return data + +/datum/tgui_checkbox_input/ui_act(action, list/params) + . = ..() + if (.) + return + + switch(action) + if("submit") + var/list/selections = params["entry"] + if(length(selections) > 0 && length(selections) <= max_checked) + set_choices(selections) + closed = TRUE + SStgui.close_uis(src) + return TRUE + + if("cancel") + closed = TRUE + SStgui.close_uis(src) + return TRUE + + return FALSE + +/datum/tgui_checkbox_input/proc/set_choices(list/selections) + src.choices = selections.Copy() diff --git a/tgstation.dme b/tgstation.dme index 7d05e070ead..034e4685e1b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4958,6 +4958,7 @@ #include "code\modules\tgui\states\self.dm" #include "code\modules\tgui\states\zlevel.dm" #include "code\modules\tgui_input\alert.dm" +#include "code\modules\tgui_input\checkboxes.dm" #include "code\modules\tgui_input\list.dm" #include "code\modules\tgui_input\number.dm" #include "code\modules\tgui_input\text.dm" diff --git a/tgui/packages/tgui/interfaces/CheckboxInput.tsx b/tgui/packages/tgui/interfaces/CheckboxInput.tsx new file mode 100644 index 00000000000..8a431156ca9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CheckboxInput.tsx @@ -0,0 +1,101 @@ +import { Button, Icon, Input, NoticeBox, Section, Stack, Table, Tooltip } from '../components'; +import { TableCell, TableRow } from '../components/Table'; +import { createSearch, decodeHtmlEntities } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; + +import { InputButtons } from './common/InputButtons'; +import { Loader } from './common/Loader'; +import { Window } from '../layouts'; + +type Data = { + items: string[]; + message: string; + title: string; + timeout: number; + max_checked: number; +}; + +/** Renders a list of checkboxes per items for input. */ +export const CheckboxInput = (props, context) => { + const { data } = useBackend(context); + const { items = [], max_checked, message, timeout, title } = data; + + const [selections, setSelections] = useLocalState( + context, + 'selections', + [] + ); + + const [searchQuery, setSearchQuery] = useLocalState( + context, + 'searchQuery', + '' + ); + const search = createSearch(searchQuery, (item: string) => item); + const toDisplay = items.filter(search); + + const selectItem = (name: string) => { + const newSelections = selections.includes(name) + ? selections.filter((item) => item !== name) + : [...selections, name]; + + setSelections(newSelections); + }; + + return ( + + {!!timeout && } + + + + + {decodeHtmlEntities(message)}{' '} + {max_checked < 50 && ` (Max: ${max_checked})`} + + + +
+ + {toDisplay.map((item, index) => ( + + + = max_checked && + !selections.includes(item) + } + fluid + onClick={() => selectItem(item)}> + {item} + + + + ))} +
+
+
+ + + + + + + + setSearchQuery(value)} + /> + + + +
+ +
+
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/common/InputButtons.tsx b/tgui/packages/tgui/interfaces/common/InputButtons.tsx index c5f79add6e9..d7cbc8cd7aa 100644 --- a/tgui/packages/tgui/interfaces/common/InputButtons.tsx +++ b/tgui/packages/tgui/interfaces/common/InputButtons.tsx @@ -1,13 +1,14 @@ -import { useBackend } from '../../backend'; import { Box, Button, Flex } from '../../components'; +import { useBackend } from '../../backend'; + type InputButtonsData = { large_buttons: boolean; swapped_buttons: boolean; }; type InputButtonsProps = { - input: string | number; + input: string | number | string[]; message?: string; };