Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port /tg/'s maptext/runechat improvements #1294

Merged
merged 5 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions code/__DEFINES/fonts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@

/// Emoji icon set
#define EMOJI_SET 'icons/ui_icons/emoji/emoji.dmi'

// Font metrics bitfield
/// Include leading A width and trailing C width in GetWidth() or in DrawText()
#define INCLUDE_AC (1<<0)

DEFINE_BITFIELD(font_flags, list(
"INCLUDE_AC" = INCLUDE_AC,
))
36 changes: 35 additions & 1 deletion code/__DEFINES/text.dm
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
/// Does 4 spaces. Used as a makeshift tabulator.
#define FOURSPACES "&nbsp;&nbsp;&nbsp;&nbsp;"

/// Standard maptext
/// Prepares a text to be used for maptext. Use this so it doesn't look hideous.
#define MAPTEXT(text) {"<span class='maptext'>[##text]</span>"}

/// Prepares a text to be used for maptext, using a font that can handle larger text better.
/**
* Pixel-perfect scaled fonts for use in the MAP element as defined in skin.dmf
*
* Four sizes to choose from, use the sizes as mentioned below.
* Between the variations and a step there should be an option that fits your use case.
* BYOND uses pt sizing, different than px used in TGUI. Using px will make it look blurry due to poor antialiasing.
*
* Default sizes are prefilled in the macro for ease of use and a consistent visual look.
* To use a step other than the default in the macro, specify it in a span style.
* For example: MAPTEXT_PIXELLARI("<span style='font-size: 24pt'>Some large maptext here</span>")
*/
/// Large size (ie: context tooltips) - Size options: 12pt 24pt.
#define MAPTEXT_PIXELLARI(text) {"<span style='font-family: \"Pixellari\"; font-size: 12pt; -dm-text-outline: 1px black'>[##text]</span>"}

/// Standard size (ie: normal runechat) - Size options: 6pt 12pt 18pt.
#define MAPTEXT_GRAND9K(text) {"<span style='font-family: \"Grand9K Pixel\"; font-size: 6pt; -dm-text-outline: 1px black'>[##text]</span>"}

/// Small size. (ie: context subtooltips, spell delays) - Size options: 12pt 24pt.
#define MAPTEXT_TINY_UNICODE(text) {"<span style='font-family: \"TinyUnicode\"; font-size: 12pt; line-height: 0.75; -dm-text-outline: 1px black'>[##text]</span>"}

/// Smallest size. (ie: whisper runechat) - Size options: 6pt 12pt 18pt.
#define MAPTEXT_SPESSFONT(text) {"<span style='font-family: \"Spess Font\"; font-size: 6pt; line-height: 1.4; -dm-text-outline: 1px black'>[##text]</span>"}

/**
* Prepares a text to be used for maptext, using a variable size font.
*
* More flexible but doesn't scale pixel perfect to BYOND icon resolutions.
* (May be blurry.) Can use any size in pt or px.
*
* You MUST Specify the size when using the macro
* For example: MAPTEXT_VCR_OSD_MONO("<span style='font-size: 24pt'>Some large maptext here</span>")
*/
/// Prepares a text to be used for maptext, using a variable size font.
/// Variable size font. More flexible but doesn't scale pixel perfect to BYOND icon resolutions. (May be blurry.) Can use any size in pt or px.
#define MAPTEXT_VCR_OSD_MONO(text) {"<span style='font-family: \"VCR OSD Mono\"'>[##text]</span>"}

/// Macro from Lummox used to get height from a MeasureText proc.
Expand Down
2 changes: 1 addition & 1 deletion code/__HELPERS/icons.dm
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ world
letter = lowertext(letter)

var/image/text_image = new(loc = A)
text_image.maptext = MAPTEXT("<font size = 4>[letter]</font>")
text_image.maptext = MAPTEXT("<span style='font-size: 24pt'>[letter]</span>")
text_image.pixel_x = 7
text_image.pixel_y = 5
qdel(atom_icon)
Expand Down
2 changes: 1 addition & 1 deletion code/_onclick/hud/credits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
icon = I
parent = P
icon_state = credited
maptext = MAPTEXT(credited)
maptext = MAPTEXT_PIXELLARI(credited)
maptext_x = world.icon_size + 8
maptext_y = (world.icon_size / 2) - 4
maptext_width = world.icon_size * 3
Expand Down
10 changes: 5 additions & 5 deletions code/datums/actions/cooldown_action.dm
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@
/datum/action/cooldown/create_button()
var/atom/movable/screen/movable/action_button/button = ..()
button.maptext = ""
button.maptext_x = 6
button.maptext_x = 4
button.maptext_y = 2
button.maptext_width = 24
button.maptext_height = 12
button.maptext_width = 32
button.maptext_height = 16
return button

/datum/action/cooldown/update_button_status(atom/movable/screen/movable/action_button/button, force = FALSE)
Expand All @@ -79,9 +79,9 @@
button.maptext = ""
else
if (cooldown_rounding > 0)
button.maptext = MAPTEXT("<b>[round(time_left/10, cooldown_rounding)]</b>")
button.maptext = MAPTEXT_TINY_UNICODE("[round(time_left/10, cooldown_rounding)]")
else
button.maptext = MAPTEXT("<b>[round(time_left/10)]</b>")
button.maptext = MAPTEXT_TINY_UNICODE("[round(time_left/10)]")

if(!IsAvailable() || !is_action_active(button))
return
Expand Down
10 changes: 7 additions & 3 deletions code/datums/chatmessage.dm
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/// Approximate height in pixels of an 'average' line, used for height decay
#define CHAT_MESSAGE_APPROX_LHEIGHT 11
/// Max width of chat message in pixels
#define CHAT_MESSAGE_WIDTH 96
#define CHAT_MESSAGE_WIDTH 112
/// The dimensions of the chat message icons
#define CHAT_MESSAGE_ICON_SIZE 9

Expand Down Expand Up @@ -145,6 +145,10 @@
if (!ismob(target))
extra_classes |= "small"

// Why are you yelling?
if(copytext_char(text, -2) == "!!")
extra_classes |= SPAN_YELL

var/list/prefixes

// Append radio icon if from a virtual speaker
Expand All @@ -171,7 +175,7 @@
var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color

// Approximate text height
var/complete_text = "<span class='center [extra_classes.Join(" ")]' style='color: [tgt_color]'>[owner.say_emphasis(text)]</span>"
var/complete_text = "<span style='color: [tgt_color]'><span class='center [extra_classes.Join(" ")]'>[owner.say_emphasis(text)]</span></span>"

var/mheight
WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight)
Expand Down Expand Up @@ -235,7 +239,7 @@
message.pixel_y = target.maptext_height
message.pixel_x = -target.base_pixel_x
message.maptext_width = CHAT_MESSAGE_WIDTH
message.maptext_height = mheight
message.maptext_height = mheight * 1.25 // We add extra because some characters are superscript, like actions
message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5
message.maptext = MAPTEXT(complete_text)

Expand Down
6 changes: 3 additions & 3 deletions code/datums/components/admin_popup.dm
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@

last_color_index = (last_color_index % colors.len) + 1

var/message = "<b style='color: [colors[last_color_index]]; text-align: center; font-size: 32px'>"
message += "HEY! An admin is trying to talk to you!<br>Check your chat window, and click their name to respond!"
message += "</b>"
var/message = "<span style='color: [colors[last_color_index]]; text-align: center; font-size: 24pt'>"
message += "HEY!<br>An admin is trying to talk to you!<br>Check your chat window,<br>and click their name to respond!"
message += "</span>"

maptext = MAPTEXT(message)
last_update_time = world.time
Expand Down
10 changes: 5 additions & 5 deletions code/game/atoms.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2046,7 +2046,7 @@
active_hud.screentip_text.maptext = ""
return

active_hud.screentip_text.maptext_y = 0
active_hud.screentip_text.maptext_y = 10 // 10px lines us up with the action buttons top left corner
var/lmb_rmb_line = ""
var/ctrl_lmb_ctrl_rmb_line = ""
var/alt_lmb_alt_rmb_line = ""
Expand Down Expand Up @@ -2113,15 +2113,15 @@
extra_lines++

if(extra_lines)
extra_context = "<br><span style='font-size: 7px'>[lmb_rmb_line][ctrl_lmb_ctrl_rmb_line][alt_lmb_alt_rmb_line][shift_lmb_ctrl_shift_lmb_line]</span>"
//first extra line pushes atom name line up 10px, subsequent lines push it up 9px, this offsets that and keeps the first line in the same place
active_hud.screentip_text.maptext_y = -10 + (extra_lines - 1) * -9
extra_context = "<br><span class='subcontext'>[lmb_rmb_line][ctrl_lmb_ctrl_rmb_line][alt_lmb_alt_rmb_line][shift_lmb_ctrl_shift_lmb_line]</span>"
//first extra line pushes atom name line up 11px, subsequent lines push it up 9px, this offsets that and keeps the first line in the same place
active_hud.screentip_text.maptext_y = -1 + (extra_lines - 1) * -9

if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "")
active_hud.screentip_text.maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
active_hud.screentip_text.maptext = "<span class='maptext' style='text-align: center; font-size: 32px; color: [active_hud.screentip_color]'>[name][extra_context]</span>"
active_hud.screentip_text.maptext = "<span class='context' style='text-align: center; color: [active_hud.screentip_color]'>[name][extra_context]</span>"

/// Gets a merger datum representing the connected blob of objects in the allowed_types argument
/atom/proc/GetMergeGroup(id, list/allowed_types)
Expand Down
104 changes: 41 additions & 63 deletions code/game/machinery/status_display.dm
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// Status display
// (formerly Countdown timer display)

#define MAX_STATIC_WIDTH 25
#define FONT_STYLE "5pt 'Small Fonts'"
#define MAX_STATIC_WIDTH 22
#define FONT_STYLE "12pt 'TinyUnicode'"
#define SCROLL_RATE (0.04 SECONDS) // time per pixel
#define LINE1_Y -8
#define LINE2_Y -15
#define SCROLL_PADDING 2 // how many pixels we chop to make a smooth loop
#define LINE1_X 1
#define LINE1_Y -4
#define LINE2_X 1
#define LINE2_Y -11
#define STATUS_DISPLAY_FONT_DATUM /datum/font/tiny_unicode/size_12pt

/// Status display which can show images and scrolling text.
/obj/machinery/status_display
name = "status display"
Expand Down Expand Up @@ -124,14 +128,14 @@
* * message - the new message text.
* Returns new /obj/effect/overlay/status_display_text or null if unchanged.
*/
/obj/machinery/status_display/proc/update_message(obj/effect/overlay/status_display_text/overlay, line_y, message, x_offset)
/obj/machinery/status_display/proc/update_message(obj/effect/overlay/status_display_text/overlay, line_y, message, x_offset, line_pair)
if(overlay && message == overlay.message)
return null

if(overlay)
qdel(overlay)

var/obj/effect/overlay/status_display_text/new_status_display_text = new(src, line_y, message, text_color, header_text_color, x_offset)
var/obj/effect/overlay/status_display_text/new_status_display_text = new(src, line_y, message, text_color, header_text_color, x_offset, line_pair)
// Draw our object visually "in front" of this display, taking advantage of sidemap
new_status_display_text.pixel_y = -32
new_status_display_text.pixel_z = 32
Expand All @@ -149,7 +153,7 @@
return
set_light(l_outer_range = 1.4, l_power = 0.7, l_color = LIGHT_COLOR_BLUE) // blue light

/obj/machinery/status_display/update_overlays()
/obj/machinery/status_display/update_overlays(updates)
. = ..()

if(machine_stat & (NOPOWER|BROKEN))
Expand All @@ -167,10 +171,18 @@
if(current_picture == AI_DISPLAY_DONT_GLOW) // If the thing's off, don't display the emissive yeah?
return .
else
var/overlay = update_message(message1_overlay, LINE1_Y, message1)
var/line1_metric
var/line2_metric
var/line_pair
var/datum/font/display_font = new STATUS_DISPLAY_FONT_DATUM()
line1_metric = display_font.get_metrics(message1)
line2_metric = display_font.get_metrics(message2)
line_pair = (line1_metric > line2_metric ? line1_metric : line2_metric)

var/overlay = update_message(message1_overlay, LINE1_Y, message1, LINE1_X, line_pair)
if(overlay)
message1_overlay = overlay
overlay = update_message(message2_overlay, LINE2_Y, message2)
overlay = update_message(message2_overlay, LINE2_Y, message2, LINE2_X, line_pair)
if(overlay)
message2_overlay = overlay

Expand Down Expand Up @@ -217,10 +229,10 @@
/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle)
if(!shuttle)
// the shuttle is missing - no processing
set_messages("shutl?","")
set_messages("shutl","not in service")
return PROCESS_KILL
else if(shuttle.timer)
var/line1 = "- [shuttle.getModeStr()] -"
var/line1 = "<<< [shuttle.getModeStr()]"
var/line2 = shuttle.getTimerStr()

set_messages(line1, line2)
Expand All @@ -245,39 +257,23 @@
// If the line is short enough to not marquee, and it matches this, it's a header.
var/static/regex/header_regex = regex("^-.*-$")

/// Width of each character, including kerning gap afterwards.
/// We don't use rich text or anything fancy, so we can bake these values.
var/static/list/char_widths = list(
// ! " # $ % & ' ( ) * + , - . /
1, 2, 3, 5, 4, 5, 5, 2, 3, 3, 3, 4, 2, 3, 2, 3,
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 3, 3, 3, 3,
// @ A B C D E F G H I J K L M N O
7, 5, 5, 5, 5, 4, 4, 5, 5, 2, 4, 5, 4, 6, 5, 5,
// P Q R S T U V W X Y Z [ \ ] ^ _
5, 5, 5, 5, 4, 5, 4, 6, 4, 4, 4, 3, 3, 3, 4, 4,
// ` a b c d e f g h i j k l m n o
3, 5, 5, 5, 5, 4, 4, 5, 5, 2, 4, 5, 4, 6, 5, 5,
// p q r s t u v w x y z { | } ~
5, 5, 5, 5, 4, 5, 4, 6, 4, 4, 4, 3, 2, 3, 4,
)

/obj/effect/overlay/status_display_text/Initialize(mapload, yoffset, line, text_color, header_text_color, xoffset = 0)
/obj/effect/overlay/status_display_text/Initialize(mapload, yoffset, line, text_color, header_text_color, xoffset = 0, line_pair)
. = ..()

maptext_y = yoffset
message = line

var/line_width = measure_width(line)
var/datum/font/display_font = new STATUS_DISPLAY_FONT_DATUM()
var/line_width = display_font.get_metrics(line)

if(line_width > MAX_STATIC_WIDTH)
// Marquee text
var/marquee_message = "[line] - [line] - [line]"
var/marquee_message = "[line] [line] [line]"

// Width of full content. Must of these is never revealed unless the user inputted a single character.
var/full_marquee_width = measure_width(marquee_message)
var/full_marquee_width = display_font.get_metrics("[marquee_message] ")
// We loop after only this much has passed.
var/looping_marquee_width = measure_width("[line] - ")
var/looping_marquee_width = (display_font.get_metrics("[line] ]") - SCROLL_PADDING)

maptext = generate_text(marquee_message, center = FALSE, text_color = text_color)
maptext_width = full_marquee_width
Expand All @@ -287,45 +283,24 @@
add_filter("mask", 1, alpha_mask_filter(icon = icon(icon, "outline")))

// Scroll.
var/time = looping_marquee_width * SCROLL_RATE
animate(src, maptext_x = -looping_marquee_width, time = time, loop = -1)
animate(maptext_x = 0, time = 0)
var/time = line_pair * SCROLL_RATE
animate(src, maptext_x = (-looping_marquee_width) + MAX_STATIC_WIDTH, time = time, loop = -1)
animate(maptext_x = MAX_STATIC_WIDTH, time = 0)
else
// Centered text
var/color = header_regex.Find(line) ? header_text_color : text_color
maptext = generate_text(line, center = TRUE, text_color = color)
maptext_x = xoffset //Defaults to 0, this would be centered unless overided

/**
* A hyper-streamlined version of MeasureText that doesn't support different fonts, rich formatting, or multiline.
* But it also doesn't require a client.
*
* Returns the width in pixels
*
* Arguments:
* * text - the text to measure
*/
/obj/effect/overlay/status_display_text/proc/measure_width(text)
var/width = 0
for(var/text_idx in 1 to length(text))
var/ascii = text2ascii(text, text_idx)
if(!(ascii in 0x20 to 0x7E))
// So we can't possibly runtime, even though the input should be in range already.
width += 3
continue
width += char_widths[ascii - 0x1F]

return width

/**
* Generate the actual maptext.
* Arguments:
* * text - the text to display
* * center - center the text if TRUE, otherwise left-align
* * center - center the text if TRUE, otherwise right-align (the direction the text is coming from)
* * text_color - the text color
*/
/obj/effect/overlay/status_display_text/proc/generate_text(text, center, text_color)
return {"<div style="color:[text_color];font:[FONT_STYLE][center ? ";text-align:center" : ""]" valign="top">[text]</div>"}
return {"<div style="color:[text_color];font:[FONT_STYLE][center ? ";text-align:center" : "text-align:right"]" valign="top">[text]</div>"}

/// Evac display which shows shuttle timer or message set by Command.
/obj/machinery/status_display/evac
Expand Down Expand Up @@ -412,8 +387,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
if(!SSshuttle.supply)
// Might be missing in our first update on initialize before shuttles
// have loaded. Cross our fingers that it will soon return.
line1 = "CARGO"
line2 = "shutl?"
line1 = "shutl"
line2 = "not in service"
else if(SSshuttle.supply.mode == SHUTTLE_IDLE)
if(is_station_level(SSshuttle.supply.z))
line1 = "CARGO"
Expand All @@ -422,7 +397,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
line1 = ""
line2 = ""
else
line1 = "- [SSshuttle.supply.getModeStr()] -"
line1 = "<<< [SSshuttle.supply.getModeStr()]"
line2 = SSshuttle.supply.getTimerStr()
set_messages(line1, line2)

Expand Down Expand Up @@ -602,5 +577,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/ai, 32)
#undef MAX_STATIC_WIDTH
#undef FONT_STYLE
#undef SCROLL_RATE
#undef LINE1_X
#undef LINE1_Y
#undef LINE2_X
#undef LINE2_Y
#undef STATUS_DISPLAY_FONT_DATUM
Loading
Loading