diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index 2734f2ecdc..26e929f02f 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -189,6 +189,10 @@ What is the naming convention for planes or layers? #define PLANE_PLAYER_HUD_ABOVE 97 //Things above the player hud #define PLANE_PLAYER_SPLASH 98 //Splash screen //CHOMPEdit +#define RADIAL_BACKGROUND_LAYER 0 +///1000 is an unimportant number, it's just to normalize copied layers +#define RADIAL_CONTENT_LAYER 1000 + #define PLANE_ADMIN3 99 //Purely for shenanigans (above HUD) diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index 0d5056fab4..b803227325 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -3,33 +3,52 @@ GLOBAL_LIST_EMPTY(radial_menus) -// Ported from TG - /obj/screen/radial icon = 'icons/mob/radial.dmi' - layer = LAYER_HUD_ABOVE plane = PLANE_PLAYER_HUD_ABOVE + vis_flags = VIS_INHERIT_PLANE + var/click_on_hover = FALSE var/datum/radial_menu/parent +/obj/screen/radial/proc/set_parent(new_value) + if(parent) + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + parent = new_value + if(parent) + RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_parent_del)) + +/obj/screen/radial/proc/handle_parent_del() + SIGNAL_HANDLER + set_parent(null) + /obj/screen/radial/slice icon_state = "radial_slice" var/choice var/next_page = FALSE var/tooltips = FALSE -/obj/screen/radial/Destroy() - parent = null - return ..() +/obj/screen/radial/slice/set_parent(new_value) + . = ..() + if(parent) + icon_state = parent.radial_slice_icon /obj/screen/radial/slice/MouseEntered(location, control, params) . = ..() - icon_state = "radial_slice_focus" + if(next_page || !parent) + icon_state = "radial_slice_focus" + else + icon_state = "[parent.radial_slice_icon]_focus" if(tooltips) openToolTip(usr, src, params, title = name) + if (click_on_hover && !isnull(usr) && !isnull(parent)) + Click(location, control, params) /obj/screen/radial/slice/MouseExited(location, control, params) . = ..() - icon_state = "radial_slice" + if(next_page || !parent) + icon_state = "radial_slice" + else + icon_state = parent.radial_slice_icon if(tooltips) closeToolTip(usr) @@ -38,7 +57,7 @@ GLOBAL_LIST_EMPTY(radial_menus) if(next_page) parent.next_page() else - parent.element_chosen(choice,usr) + parent.element_chosen(choice, usr, params) /obj/screen/radial/center name = "Close Menu" @@ -57,9 +76,18 @@ GLOBAL_LIST_EMPTY(radial_menus) parent.finished = TRUE /datum/radial_menu - var/list/choices = list() //List of choice id's - var/list/choices_icons = list() //choice_id -> icon - var/list/choices_values = list() //choice_id -> choice + /// List of choice IDs + var/list/choices = list() + + /// choice_id -> icon + var/list/choices_icons = list() + + /// choice_id -> choice + var/list/choices_values = list() + + /// choice_id -> /datum/radial_menu_choice + var/list/choice_datums = list() + var/list/page_data = list() //list of choices per page @@ -87,6 +115,9 @@ GLOBAL_LIST_EMPTY(radial_menus) var/py_shift = 0 var/entry_animation = TRUE + ///A replacement icon state for the generic radial slice bg icon. Doesn't affect the next page nor the center buttons + var/radial_slice_icon + //If we swap to vis_contens inventory these will need a redo /datum/radial_menu/proc/check_screen_border(mob/user) var/atom/movable/AM = anchor @@ -98,6 +129,8 @@ GLOBAL_LIST_EMPTY(radial_menus) else py_shift = 32 restrict_to_dir(NORTH) //I was going to parse screen loc here but that's more effort than it's worth. + else if(hudfix_method && AM.loc) + anchor = get_atom_on_turf(anchor) //Sets defaults //These assume 45 deg min_angle @@ -116,7 +149,7 @@ GLOBAL_LIST_EMPTY(radial_menus) starting_angle = 180 ending_angle = 45 -/datum/radial_menu/proc/setup_menu(use_tooltips) +/datum/radial_menu/proc/setup_menu(use_tooltips, set_page = 1, click_on_hover = FALSE) if(ending_angle > starting_angle) zone = ending_angle - starting_angle else @@ -129,7 +162,7 @@ GLOBAL_LIST_EMPTY(radial_menus) for(var/i in 1 to elements_to_add) //Create all elements var/obj/screen/radial/slice/new_element = new /obj/screen/radial/slice new_element.tooltips = use_tooltips - new_element.parent = src + new_element.set_parent(src) elements += new_element var/page = 1 @@ -152,22 +185,31 @@ GLOBAL_LIST_EMPTY(radial_menus) page_data[page] = current pages = page - current_page = 1 - update_screen_objects(anim = entry_animation) + current_page = clamp(set_page, 1, pages) + update_screen_objects(entry_animation, click_on_hover) -/datum/radial_menu/proc/update_screen_objects(anim = FALSE) +/datum/radial_menu/proc/update_screen_objects(anim = FALSE, click_on_hover = FALSE) var/list/page_choices = page_data[current_page] var/angle_per_element = round(zone / page_choices.len) for(var/i in 1 to elements.len) - var/obj/screen/radial/E = elements[i] + var/obj/screen/radial/element = elements[i] var/angle = WRAP(starting_angle + (i - 1) * angle_per_element,0,360) if(i > page_choices.len) - HideElement(E) + HideElement(element) + element.click_on_hover = FALSE else - SetElement(E,page_choices[i],angle,anim = anim,anim_order = i) + SetElement(element,page_choices[i],angle,anim = anim,anim_order = i) + // Only activate click on hover after the animation plays + if (!click_on_hover) + continue + if (anim) + addtimer(VARSET_CALLBACK(element, click_on_hover, TRUE), i * 0.5) + else + element.click_on_hover = TRUE /datum/radial_menu/proc/HideElement(obj/screen/radial/slice/E) E.cut_overlays() + E.vis_contents.Cut() E.alpha = 0 E.name = "None" E.maptext = null @@ -194,13 +236,22 @@ GLOBAL_LIST_EMPTY(radial_menus) E.alpha = 255 E.mouse_opacity = MOUSE_OPACITY_ICON E.cut_overlays() + E.vis_contents.Cut() if(choice_id == NEXT_PAGE_ID) E.name = "Next Page" E.next_page = TRUE + E.icon_state = "radial_slice" // Resets the bg icon state to the default for next page buttons. E.add_overlay("radial_next") else - if(istext(choices_values[choice_id])) + //This isn't granted to exist, so use the ?. operator for conditionals that use it. + var/datum/radial_menu_choice/choice_datum = choice_datums[choice_id] + if(choice_datum?.name) + E.name = choice_datum.name + else if(istext(choices_values[choice_id])) E.name = choices_values[choice_id] + else if(ispath(choices_values[choice_id],/atom)) + var/atom/A = choices_values[choice_id] + E.name = initial(A.name) else var/atom/movable/AM = choices_values[choice_id] //Movables only E.name = AM.name @@ -209,15 +260,21 @@ GLOBAL_LIST_EMPTY(radial_menus) E.next_page = FALSE if(choices_icons[choice_id]) E.add_overlay(choices_icons[choice_id]) + if (choice_datum?.info) + var/obj/effect/abstract/info/info_button = new(E, choice_datum.info) + info_button.plane = PLANE_PLAYER_HUD_ABOVE + info_button.layer = RADIAL_CONTENT_LAYER + E.vis_contents += info_button /datum/radial_menu/New() close_button = new - close_button.parent = src + close_button.set_parent(src) /datum/radial_menu/proc/Reset() choices.Cut() choices_icons.Cut() choices_values.Cut() + choice_datums.Cut() current_page = 1 /datum/radial_menu/proc/element_chosen(choice_id,mob/user) @@ -226,7 +283,7 @@ GLOBAL_LIST_EMPTY(radial_menus) /datum/radial_menu/proc/get_next_id() return "c_[choices.len]" -/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips) +/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips, click_on_hover = FALSE, set_page = 1) if(choices.len) Reset() for(var/E in new_choices) @@ -237,13 +294,20 @@ GLOBAL_LIST_EMPTY(radial_menus) var/I = extract_image(new_choices[E]) if(I) choices_icons[id] = I - setup_menu(use_tooltips) + if (istype(new_choices[E], /datum/radial_menu_choice)) + choice_datums[id] = new_choices[E] + setup_menu(use_tooltips, set_page, click_on_hover) -/datum/radial_menu/proc/extract_image(E) - var/mutable_appearance/MA = new /mutable_appearance(E) +/datum/radial_menu/proc/extract_image(to_extract_from) + if (istype(to_extract_from, /datum/radial_menu_choice)) + var/datum/radial_menu_choice/choice = to_extract_from + to_extract_from = choice.image + + var/mutable_appearance/MA = new /mutable_appearance(to_extract_from) if(MA) - MA.layer = LAYER_HUD_ABOVE + MA.plane = PLANE_PLAYER_HUD_ABOVE + MA.layer = RADIAL_CONTENT_LAYER MA.appearance_flags |= RESET_TRANSFORM return MA @@ -253,17 +317,16 @@ GLOBAL_LIST_EMPTY(radial_menus) current_page = WRAP(current_page + 1,1,pages+1) update_screen_objects() -/datum/radial_menu/proc/show_to(mob/M) +/datum/radial_menu/proc/show_to(mob/M, offset_x = 0, offset_y = 0) if(current_user) hide() if(!M.client || !anchor) return current_user = M.client //Blank - menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing",layer = LAYER_HUD_ABOVE) - menu_holder.plane = PLANE_PLAYER_HUD_ABOVE //CHOMPEdit - fixed plane - menu_holder.appearance_flags |= RESET_TRANSFORM //CHOMPEdit - fix radial scaling improperly based on mob size - menu_holder.appearance_flags |= KEEP_APART + menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing", layer = RADIAL_BACKGROUND_LAYER, pixel_x = offset_x, pixel_y = offset_y) + menu_holder.plane = PLANE_PLAYER_HUD_ABOVE + menu_holder.appearance_flags |= KEEP_APART|RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM menu_holder.vis_contents += elements + close_button current_user.images += menu_holder @@ -285,9 +348,7 @@ GLOBAL_LIST_EMPTY(radial_menus) /datum/radial_menu/Destroy() Reset() hide() - QDEL_LIST_NULL(elements) - QDEL_NULL(close_button) - QDEL_NULL(custom_check_callback) + custom_check_callback = null . = ..() /* @@ -295,30 +356,66 @@ GLOBAL_LIST_EMPTY(radial_menus) Choices should be a list where list keys are movables or text used for element names and return value and list values are movables/icons/images used for element icons */ -/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE) +/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE, entry_animation = TRUE, click_on_hover = FALSE, user_space = FALSE) if(!user || !anchor || !length(choices)) return + + if(length(choices)==1 && autopick_single_option) + return choices[1] + if(!uniqueid) uniqueid = "defmenu_[REF(user)]_[REF(anchor)]" if(GLOB.radial_menus[uniqueid]) + if(!no_repeat_close) + var/datum/radial_menu/menu = GLOB.radial_menus[uniqueid] + menu.finished = TRUE return var/datum/radial_menu/menu = new + menu.entry_animation = entry_animation GLOB.radial_menus[uniqueid] = menu if(radius) menu.radius = radius if(istype(custom_check)) menu.custom_check_callback = custom_check - menu.anchor = anchor + menu.anchor = user_space ? user : anchor + menu.radial_slice_icon = radial_slice_icon menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud - menu.set_choices(choices, tooltips) - menu.show_to(user) + menu.set_choices(choices, tooltips, click_on_hover) + var/offset_x = 0 + var/offset_y = 0 + if (user_space) + var/turf/user_turf = get_turf(user) + var/turf/anchor_turf = get_turf(anchor) + offset_x = (anchor_turf.x - user_turf.x) * ICON_SIZE_X + anchor.pixel_x - user.pixel_x + offset_y = (anchor_turf.y - user_turf.y) * ICON_SIZE_Y + anchor.pixel_y - user.pixel_y + menu.show_to(user, offset_x, offset_y) menu.wait(user, anchor, require_near) var/answer = menu.selected_choice - QDEL_NULL(menu) + qdel(menu) GLOB.radial_menus -= uniqueid + if(require_near && !in_range(anchor, user)) + return + if(istype(custom_check)) + if(!custom_check.Invoke()) + return return answer +/// Can be provided to choices in radial menus if you want to provide more information +/datum/radial_menu_choice + /// Required -- what to display for this button + var/image + + /// If provided, this will be the name the radial slice hud button. This has priority over everything else. + var/name + + /// If provided, will display an info button that will put this text in your chat + var/info + +/datum/radial_menu_choice/Destroy(force) + . = ..() + QDEL_NULL(image) + #undef NEXT_PAGE_ID #undef DEFAULT_CHECK_DELAY diff --git a/code/game/objects/effects/info.dm b/code/game/objects/effects/info.dm new file mode 100644 index 0000000000..e98cf020a1 --- /dev/null +++ b/code/game/objects/effects/info.dm @@ -0,0 +1,28 @@ +/// An info button that, when clicked, puts some text in the user's chat +/obj/effect/abstract/info + name = "info" + icon = 'icons/effects/effects.dmi' + icon_state = "info" + + mouse_opacity = MOUSE_OPACITY_OPAQUE + + /// What should the info button display when clicked? + var/info_text + +/obj/effect/abstract/info/Initialize(mapload, info_text) + . = ..() + + if (!isnull(info_text)) + src.info_text = info_text + +/obj/effect/abstract/info/Click() + . = ..() + to_chat(usr, info_text) + +/obj/effect/abstract/info/MouseEntered(location, control, params) + . = ..() + icon_state = "info_hovered" + +/obj/effect/abstract/info/MouseExited() + . = ..() + icon_state = initial(icon_state) diff --git a/code/modules/food/kitchen/cooking_machines/_appliance.dm b/code/modules/food/kitchen/cooking_machines/_appliance.dm index 24c1b010d1..2a9e6a2090 100644 --- a/code/modules/food/kitchen/cooking_machines/_appliance.dm +++ b/code/modules/food/kitchen/cooking_machines/_appliance.dm @@ -43,6 +43,11 @@ var/combine_first = FALSE // If TRUE, this appliance will do combination cooking before checking recipes var/food_safety = FALSE //RS ADD - If true, the appliance automatically ejects food instead of burning it + var/static/radial_eject = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_eject") + var/static/radial_power = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_power") + var/static/radial_safety = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_safety") + var/static/radial_output = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_change_output") + /obj/machinery/appliance/Initialize() . = ..() @@ -611,8 +616,31 @@ if(..()) return - if(cooking_objs.len) - removal_menu(user) + interact(user) + +/obj/machinery/appliance/interact(mob/user) + var/list/options = list( + "power" = radial_power, + "safety" = radial_safety, + ) + + if(LAZYLEN(cooking_objs)) + options["remove"] = radial_eject + + if(LAZYLEN(output_options)) + options["select_output"] = radial_output + + var/choice = show_radial_menu(user, src, options, require_near = !issilicon(user)) + + switch(choice) + if("power") + toggle_power() + if("safety") + toggle_safety() + if("remove") + removal_menu(user) + if("select_output") + choose_output() /obj/machinery/appliance/proc/removal_menu(var/mob/user) if (can_remove_items(user)) diff --git a/code/modules/food/kitchen/cooking_machines/_cooker.dm b/code/modules/food/kitchen/cooking_machines/_cooker.dm index a8c1c25080..b557af04a8 100644 --- a/code/modules/food/kitchen/cooking_machines/_cooker.dm +++ b/code/modules/food/kitchen/cooking_machines/_cooker.dm @@ -26,11 +26,15 @@ /obj/machinery/appliance/cooker/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) var/list/data = ..() + data["on"] = !(stat & POWEROFF) + data["safety"] = food_safety data["temperature"] = round(temperature - T0C, 0.1) data["optimalTemp"] = round(optimal_temp - T0C, 0.1) data["temperatureEnough"] = temperature >= min_temp data["efficiency"] = round(get_efficiency(), 0.1) data["containersRemovable"] = can_remove_items(user, show_warning = FALSE) + data["selected_option"] = selected_option + data["show_selected_option"] = LAZYLEN(output_options) var/list/our_contents = list() for(var/i in 1 to max_contents) @@ -56,6 +60,15 @@ return TRUE switch(action) + if("toggle_power") + attempt_toggle_power(usr) + return TRUE + if("toggle_safety") + toggle_safety() + return TRUE + if("change_output") + choose_output() + return TRUE if("slot") var/slot = params["slot"] var/obj/item/I = usr.get_active_hand() diff --git a/code/modules/reagents/machinery/grinder.dm b/code/modules/reagents/machinery/grinder.dm index c9ee208066..f6497c3ea5 100644 --- a/code/modules/reagents/machinery/grinder.dm +++ b/code/modules/reagents/machinery/grinder.dm @@ -215,14 +215,7 @@ var/global/list/ore_reagents = list( //have a number of reageents divisible by R if(length(holdingitems)) options["grind"] = radial_grind - var/choice - if(length(options) < 1) - return - if(length(options) == 1) - for(var/key in options) - choice = key - else - choice = show_radial_menu(user, src, options, require_near = !issilicon(user)) + var/choice = show_radial_menu(user, src, options, require_near = !issilicon(user), autopick_single_option = FALSE) // post choice verification if(inuse || (isAI(user) && stat & NOPOWER) || user.incapacitated()) diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 4df86b72ff..97643ad7a8 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/radial.dmi b/icons/mob/radial.dmi index bf14a19734..d38f1e63be 100644 Binary files a/icons/mob/radial.dmi and b/icons/mob/radial.dmi differ diff --git a/tgui/packages/tgui/interfaces/CookingAppliance.tsx b/tgui/packages/tgui/interfaces/CookingAppliance.tsx index 7b951cd5db..2c721da681 100644 --- a/tgui/packages/tgui/interfaces/CookingAppliance.tsx +++ b/tgui/packages/tgui/interfaces/CookingAppliance.tsx @@ -12,6 +12,10 @@ import { import { Window } from '../layouts'; type Data = { + on: BooleanLike; + safety: BooleanLike; + selected_option: string | null; + show_selected_option: BooleanLike; temperature: number; optimalTemp: number; temperatureEnough: BooleanLike; @@ -29,6 +33,10 @@ export const CookingAppliance = (props) => { const { act, data } = useBackend(); const { + on, + safety, + selected_option, + show_selected_option, temperature, optimalTemp, temperatureEnough, @@ -40,8 +48,41 @@ export const CookingAppliance = (props) => { return ( -
+
act('toggle_power')} + > + {on ? 'On' : 'Off'} + + } + > + + + + {!!show_selected_option && ( + + + + )}