Fix DPI scaling with TGUI, TGUI Say, and Tooltips (#20734)

Got tired of the issues here when working on something else.

Ported several PRs from /tg/station to fix DPI scaling issues. This was
not a problem before 516, however 516 now respects Window's DPI setting,
causing misalignment in several of our UI elements.

This PR implements these ~~four~~ ~~five~~ six PRs:
https://github.com/tgstation/tgstation/pull/65686
https://github.com/tgstation/tgstation/pull/89994
https://github.com/tgstation/tgstation/pull/90416
https://github.com/tgstation/tgstation/pull/90418
https://github.com/tgstation/tgstation/pull/90796
https://github.com/cmss13-devs/cmss13/pull/8734

~~Does not include fixes with TGUI-Say. /tg/station refactored TGUI-Say
after their port to React before they fixed DPI scaling, and this would
be pain to deconstruct to port over to Inferno. Since porting to React
is "inevitable", I considered it not worth my time to fix this.~~

Thanks to the assistance of harry, TGUI-Say fixes now included.
This commit is contained in:
Cody Brittain
2025-05-14 12:39:41 +00:00
committed by GitHub
parent 5b54dd2557
commit 45139cea5c
43 changed files with 1248 additions and 498 deletions
@@ -0,0 +1,2 @@
ALTER TABLE `ss13_player_preferences` ADD COLUMN `tgui_say_light_mode` TINYINT(1) NOT NULL DEFAULT 0 AFTER `tgui_lock`;
ALTER TABLE `ss13_player_preferences` ADD COLUMN `ui_scale` TINYINT(1) NOT NULL DEFAULT 1 AFTER `tgui_say_light_mode`;
+6
View File
@@ -36,3 +36,9 @@
#define TGUI_CREATE_MESSAGE(type, payload) ( \
"%7b%22type%22%3a%22[type]%22%2c%22payload%22%3a[url_encode(json_encode(payload))]%7d" \
)
/// Creates a message packet for sending via output() specifically for opening tgsay using an embedded winget
// This is {"type":"open","payload":{"channel":channel,"mapfocus":[[map.focus]]}}, but pre-encoded.
#define TGUI_CREATE_OPEN_MESSAGE(channel) ( \
"%7b%22type%22%3a%22open%22%2c%22payload%22%3a%7B%22channel%22%3a%22[channel]%22%2c%22mapfocus%22%3a\[\[map.focus\]\]%7d%7d" \
)
+128 -86
View File
@@ -1,13 +1,15 @@
/datum/browser
var/mob/user
var/title
var/window_id // window_id is used as the window name for browse and onclose
var/title = ""
/// window_id is used as the window name for browse and onclose
var/window_id
var/width = 0
var/height = 0
var/atom/ref = null
var/window_options = "focus=0;can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
var/stylesheets[0]
var/scripts[0]
var/datum/weakref/source_ref = null
/// window option is set using window_id
var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;"
var/stylesheets = list()
var/scripts = list()
var/title_image
var/head_elements
var/body_elements
@@ -16,70 +18,96 @@
var/title_buttons = ""
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null, var/skip_common_stylesheet = FALSE)
/datum/browser/New(mob/user, window_id, title = "", width = 0, height = 0, atom/source = null, skip_common_stylesheet = FALSE)
user = nuser
window_id = nwindow_id
if (ntitle)
title = format_text(ntitle)
if (nwidth)
width = nwidth
if (nheight)
height = nheight
if (nref)
ref = nref
src.user = user
RegisterSignal(user, COMSIG_QDELETING, PROC_REF(user_deleted))
src.window_id = window_id
if (title)
src.title = format_text(title)
if (width)
src.width = width
if (height)
src.height = height
if (source)
src.source_ref = WEAKREF(source)
if(!skip_common_stylesheet)
add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs
/datum/browser/proc/set_user(nuser)
user = nuser
/datum/browser/proc/user_deleted(datum/source)
SIGNAL_HANDLER
user = null
/datum/browser/proc/set_title(ntitle)
title = format_text(ntitle)
/datum/browser/proc/set_user(mob/user)
src.user = user
/datum/browser/proc/add_head_content(nhead_content)
head_content = nhead_content
/datum/browser/proc/set_title(title)
src.title = format_text(title)
/datum/browser/proc/set_title_buttons(ntitle_buttons)
title_buttons = ntitle_buttons
/datum/browser/proc/add_head_content(head_content)
src.head_content += head_content
/datum/browser/proc/set_window_options(nwindow_options)
window_options = nwindow_options
/datum/browser/proc/set_head_content(head_content)
src.head_content = head_content
/datum/browser/proc/set_title_buttons(title_buttons)
src.title_buttons = title_buttons
/datum/browser/proc/set_window_options(window_options)
src.window_options = window_options
/datum/browser/proc/set_title_image(ntitle_image)
//title_image = ntitle_image
/datum/browser/proc/add_stylesheet(name, file)
stylesheets["[ckey(name)].css"] = file
SSassets.transport.register_asset("[ckey(name)].css", file)
if (istype(name, /datum/asset/spritesheet))
var/datum/asset/spritesheet/sheet = name
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
else
var/asset_name = "[name].css"
stylesheets[asset_name] = file
if (!SSassets.cache[asset_name])
SSassets.transport.register_asset(asset_name, file)
/datum/browser/proc/add_script(name, file)
scripts["[ckey(name)].js"] = file
SSassets.transport.register_asset("[ckey(name)].js", file)
/datum/browser/proc/set_content(ncontent)
content = ncontent
/datum/browser/proc/set_content(content)
src.content = content
/datum/browser/proc/add_content(ncontent)
content += ncontent
/datum/browser/proc/add_content(content)
src.content += content
/datum/browser/proc/get_header()
var/file
for (file in stylesheets)
head_content += "<link rel='stylesheet' type='text/css' href='[SSassets.transport.get_asset_url(file)]'>"
var/list/new_head_content = list()
for (var/file as anything in stylesheets)
new_head_content += "<link rel='stylesheet' type='text/css' href='[SSassets.transport.get_asset_url(file)]'>"
for (file in scripts)
head_content += "<script type='text/javascript' src='[SSassets.transport.get_asset_url(file)]'></script>"
if(user.client?.window_scaling && user.client?.window_scaling != 1 && !user.client?.prefs.ui_scale && width && height)
new_head_content += {"
<style>
body {
zoom: [100 / user.client?.window_scaling]%;
}
</style>
"}
for (var/file as anything in scripts)
new_head_content += "<script type='text/javascript' src='[SSassets.transport.get_asset_url(file)]'></script>"
var/title_attributes = "class='uiTitle'"
if (title_image)
title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'"
head_content += new_head_content.Join()
return {"<!DOCTYPE html>
<html>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; utf-8">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; utf-8">
[head_content]
</head>
<body scroll=auto>
@@ -97,18 +125,27 @@
/datum/browser/proc/get_content()
return {"
[get_header()]
[content]
[get_footer()]
[get_header()]
[content]
[get_footer()]
"}
/datum/browser/proc/open(var/use_onclose = 1)
if(isnull(window_id)) //null check because this can potentially nuke goonchat
WARNING("Browser [title] tried to open with a null ID")
to_chat(user, SPAN_DANGER("The [title] browser you tried to open failed a sanity check! Please report this on GitHub!"))
return
var/window_size = ""
if (width && height)
window_size = "size=[width]x[height];"
if (stylesheets.len)
if(width && height)
if(user.client?.prefs.ui_scale)
var/scaling = user.client.window_scaling
window_size = "size=[width * scaling]x[height * scaling];"
else
window_size = "size=[width]x[height];"
if (length(stylesheets))
SSassets.transport.send_assets(user.client, stylesheets)
if (scripts.len)
if (length(scripts))
SSassets.transport.send_assets(user.client, scripts)
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
if (use_onclose)
@@ -118,9 +155,14 @@
/datum/browser/proc/setup_onclose()
set waitfor = 0
for (var/i in 1 to 10)
if (user && winexists(user, window_id))
onclose(user, window_id, ref)
break
if (!user?.client || !winexists(user, window_id))
continue
var/atom/send_ref
if(source_ref)
send_ref = source_ref.resolve()
if(!send_ref)
source_ref = null
onclose(user, window_id, send_ref)
/datum/browser/proc/update(var/force_open = 0, var/use_onclose = 1)
if(force_open)
@@ -130,6 +172,10 @@
/datum/browser/proc/close()
user << browse(null, "window=[window_id]")
if(!isnull(window_id))//null check because this can potentially nuke goonchat
user << browse(null, "window=[window_id]")
else
WARNING("Browser [title] tried to close with a null ID")
// This will allow you to show an icon in the browse window
// This is added to mob so that it can be used without a reference to the browser object
@@ -149,47 +195,43 @@
*/
// Registers the on-close verb for a browse window (client/verb/.windowclose)
// this will be called when the close-button of a window is pressed.
//
// This is usually only needed for devices that regularly update the browse window,
// e.g. canisters, timers, etc.
//
// windowid should be the specified window name
// e.g. code is : user << browse(text, "window=fred")
// then use : onclose(user, "fred")
//
// Optionally, specify the "ref" parameter as the controlled atom (usually src)
/// Registers the on-close verb for a browse window (client/verb/windowclose)
/// this will be called when the close-button of a window is pressed.
///
/// This is usually only needed for devices that regularly update the browse window,
/// e.g. canisters, timers, etc.
///
/// windowid should be the specified window name
/// e.g. code is : user << browse(text, "window=fred")
/// then use : onclose(user, "fred")
///
/// Optionally, specify the "source" parameter as the controlled atom (usually src)
// to pass a "close=1" parameter to the atom's Topic() proc for special handling.
// Otherwise, the user mob's machine var will be reset directly.
//
/proc/onclose(mob/user, windowid, var/atom/ref=null)
/// Otherwise, the user mob's machine var will be reset directly.
///
/proc/onclose(mob/user, windowid, atom/source = null)
if(!user || !user.client) return
var/param = "null"
if(ref)
param = "[REF(ref)]"
if(source)
param = "[REF(source)]"
winset(user, windowid, "on-close=\".windowclose [param]\"")
// the on-close client verb
// called when a browser popup window is closed after registering with proc/onclose()
// if a valid atom reference is supplied, call the atom's Topic() with "close=1"
// otherwise, just reset the client mob's machine var.
//
/client/verb/windowclose(var/atomref as text)
set hidden = 1 // hide this verb from the user's panel
set name = ".windowclose" // no autocomplete on cmd line
/// the on-close client verb
/// called when a browser popup window is closed after registering with proc/onclose()
/// if a valid atom reference is supplied, call the atom's Topic() with "close=1"
/// otherwise, just reset the client mob's machine var.
/client/verb/windowclose(atomref as text)
set hidden = TRUE // hide this verb from the user's panel
set name = ".windowclose" // no autocomplete on cmd line
if(atomref!="null") // if passed a real atomref
var/hsrc = locate(atomref) // find the reffed atom
if(hsrc)
usr = src.mob
src.Topic("close=1", list("close"="1"), hsrc) // this will direct to the atom's
return // Topic() proc via client.Topic()
// no atomref specified (or not found)
// so just reset the user mob's machine var
if(src && src.mob)
src.mob.unset_machine()
return
if(atomref == "null")
return
// if passed a real atomref
var/atom/hsrc = locate(atomref) // find the reffed atom
var/href = "close=1"
if(!hsrc)
return
usr = src.mob
src.Topic(href, params2list(href), hsrc) // this will direct to the atom's Topic() proc via client.Topic()
+7 -1
View File
@@ -48,6 +48,8 @@ var/global/enabled_spooking = 0
to_chat(usr, "Error: you are not an admin!")
return
var/ui_scale = owner.prefs?.ui_scale
var/body = "<html><head><title>Options for [M.key]</title></head>"
body += "<body>Options panel for <b>[M]</b>"
if(M.client)
@@ -219,7 +221,11 @@ var/global/enabled_spooking = 0
</body></html>
"}
usr << browse(body, "window=adminplayeropts;size=550x515")
var/window_size = "size=550x515"
if(owner.window_scaling && ui_scale)
window_size = "size=[550 * owner.window_scaling]x[515 * owner.window_scaling]"
usr << browse(body, "window=adminplayeropts;[window_size]")
feedback_add_details("admin_verb","SPP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+1 -1
View File
@@ -6,7 +6,7 @@
return
var/datum/browser/config_window = new(usr, "access_control", "Access Control")
config_window.add_head_content("<title>Access Control</title>")
config_window.set_head_content("<title>Access Control</title>")
var/data = "These settings control who can access the server during this round.<br>"
data += "They must be reset every single time the server restarts.<br>"
+5
View File
@@ -78,6 +78,8 @@
///Hide top bars
var/fullscreen = FALSE
///Hide status bar (bottom left)
var/show_status_bar = TRUE
/// our current tab
var/stat_tab
@@ -95,3 +97,6 @@
var/drag_start = 0
///The params we were passed at the start of the drag, in list form
var/list/drag_details
/// The DPI scale of the client. 1 is equivalent to 100% window scaling, 2 will be 200% window scaling
var/window_scaling
+17
View File
@@ -405,6 +405,8 @@ GLOBAL_LIST_INIT(localhost_addresses, list(
)
addtimer(CALLBACK(src, PROC_REF(check_panel_loaded)), 30 SECONDS)
INVOKE_ASYNC(src, PROC_REF(acquire_dpi))
// Initialize tgui panel
tgui_panel.initialize()
@@ -701,6 +703,17 @@ GLOBAL_LIST_INIT(localhost_addresses, list(
winset(src, "mainwindow", "menu=[fullscreen ? "" : "menu"];is-fullscreen=[fullscreen ? "true" : "false"];titlebar=[fullscreen ? "false" : "true"]")
attempt_auto_fit_viewport()
/client/verb/toggle_status_bar()
set name = "Toggle Status Bar"
set category = "Preferences.Menu"
show_status_bar = !show_status_bar
if (show_status_bar)
winset(src, "mapwindow.status_bar", "is-visible=true")
else
winset(src, "mapwindow.status_bar", "is-visible=false")
/client/verb/toggle_menu()
set name = "Toggle Menu"
set category = "Preferences.Menu"
@@ -1002,3 +1015,7 @@ GLOBAL_LIST_INIT(localhost_addresses, list(
if(stat_panel.is_ready())
return
to_chat(src, SPAN_DANGER("Statpanel failed to load, click <a href='byond://?src=[REF(src)];reload_statbrowser=1'>here</a> to reload the panel "))
/// This grabs the DPI of the user per their skin
/client/proc/acquire_dpi()
window_scaling = text2num(winget(src, null, "dpi"))
@@ -14,6 +14,8 @@
S["tgui_inputs"] >> pref.tgui_inputs
S["tgui_buttons_large"] >> pref.tgui_buttons_large
S["tgui_inputs_swapped"] >> pref.tgui_inputs_swapped
S["tgui_say_light_mode"] >> pref.tgui_say_light_mode
S["ui_scale"] >> pref.ui_scale
/datum/category_item/player_setup_item/player_global/ui/save_preferences(var/savefile/S)
S["UI_style"] << pref.UI_style
@@ -27,6 +29,8 @@
S["tgui_inputs"] << pref.tgui_inputs
S["tgui_buttons_large"] << pref.tgui_buttons_large
S["tgui_inputs_swapped"] << pref.tgui_inputs_swapped
S["tgui_say_light_mode"] << pref.tgui_say_light_mode
S["ui_scale"] << pref.ui_scale
/datum/category_item/player_setup_item/player_global/ui/gather_load_query()
return list(
@@ -42,7 +46,9 @@
"tooltip_style",
"tgui_inputs",
"tgui_buttons_large",
"tgui_inputs_swapped"
"tgui_inputs_swapped",
"tgui_say_light_mode",
"ui_scale",
),
"args" = list("ckey")
)
@@ -65,6 +71,7 @@
"tgui_inputs",
"tgui_buttons_large",
"tgui_inputs_swapped",
"ui_scale",
"ckey" = 1
)
)
@@ -82,7 +89,8 @@
"tooltip_style" = pref.tooltip_style,
"tgui_inputs" = pref.tgui_inputs,
"tgui_buttons_large" = pref.tgui_buttons_large,
"tgui_inputs_swapped" = pref.tgui_inputs_swapped
"tgui_inputs_swapped" = pref.tgui_inputs_swapped,
"ui_scale" = pref.ui_scale
)
/datum/category_item/player_setup_item/player_global/ui/sanitize_preferences()
@@ -110,6 +118,8 @@
dat += "<b>TGUI Inputs:</b> <a href='byond://?src=[REF(src)];tgui_inputs=1'><b>[pref.tgui_inputs ? "ON" : "OFF"]</b></a><br>"
dat += "<b>TGUI Input Large Buttons:</b> <a href='byond://?src=[REF(src)];tgui_inputs_large=1'><b>[pref.tgui_buttons_large ? "ON" : "OFF"]</b></a><br>"
dat += "<b>TGUI Input Swapped Buttons:</b> <a href='byond://?src=[REF(src)];tgui_inputs_swapped=1'><b>[pref.tgui_inputs_swapped ? "ON" : "OFF"]</b></a><br>"
dat += "<b>TGUI Say Light Mode:</b> <a href='byond://?src=[REF(src)];tgui_say_light_mode=1'><b>[pref.tgui_say_light_mode ? "ON" : "OFF"]</b></a><br>"
dat += "<b>UI Scaling:</b> <a href='byond://?src=[REF(src)];ui_scale=1'><b>[pref.ui_scale ? "ON" : "OFF"]</b></a><br>"
dat += "<b>FPS:</b> <a href='byond://?src=[REF(src)];select_fps=1'><b>[pref.clientfps]</b></a> - <a href='byond://?src=[REF(src)];reset=fps'>reset</a><br>"
if(can_select_ooc_color(user))
dat += "<b>OOC Color:</b> "
@@ -159,6 +169,16 @@
pref.tgui_inputs_swapped = !pref.tgui_inputs_swapped
return TOPIC_REFRESH
else if(href_list["tgui_say_light_mode"])
pref.tgui_say_light_mode = !pref.tgui_say_light_mode
user.client.tgui_say?.load()
return TOPIC_REFRESH
else if(href_list["ui_scale"])
pref.ui_scale = !pref.ui_scale
user.client.tgui_say?.load()
return TOPIC_REFRESH
else if(href_list["select_ooc_color"])
var/new_ooccolor = input(user, "Choose OOC color:", "Global Preference") as color|null
if(new_ooccolor && can_select_ooc_color(user) && CanUseTopic(user))
+2
View File
@@ -31,6 +31,8 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences)
var/tgui_inputs = TRUE
var/tgui_buttons_large = FALSE
var/tgui_inputs_swapped = FALSE
var/tgui_say_light_mode = FALSE
var/ui_scale = TRUE
//Style for popup tooltips
var/tooltip_style = "Midnight"
@@ -612,6 +612,7 @@
/obj/item/clothing/under/rank/elyran_fatigues
name = "elyran navy uniform"
desc = "An utility uniform worn by Elyran navy staff serving aboard ships and in the field."
icon = ""
icon_state = "elyran_fatigues"
item_state = "elyran_fatigues"
armor = list(
+1 -1
View File
@@ -27,7 +27,7 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache)
/datum/error_viewer/proc/browse_to(client/user, html)
var/datum/browser/browser = new(user.mob, "error_viewer", null, 600, 400)
browser.set_content(html)
browser.add_head_content({"
browser.set_head_content({"
<style>
.runtime
{
+1
View File
@@ -252,6 +252,7 @@
"size" = window_size,
"fancy" = user.client.prefs.tgui_fancy,
"locked" = user.client.prefs.tgui_lock,
"scale" = user.client.prefs.ui_scale,
),
"client" = list(
"ckey" = user.client.ckey,
+9 -6
View File
@@ -10,9 +10,7 @@
* string - A JSON encoded message to open the modal.
*/
/client/proc/tgui_say_create_open_command(channel)
var/message = TGUI_CREATE_MESSAGE("open", list(
channel = channel,
))
var/message = TGUI_CREATE_OPEN_MESSAGE(channel)
return "\".output tgui_say.browser:update [message]\""
/**
@@ -36,6 +34,7 @@
/datum/tgui_say/New(client/client, id)
src.client = client
window = new(client, id)
winset(client, "tgui_say", "size=1,1;is-visible=0;")
window.subscribe(src, PROC_REF(on_message))
window.is_browser = TRUE
@@ -62,11 +61,15 @@
*/
/datum/tgui_say/proc/load()
window_open = FALSE
winshow(client, "tgui_say", FALSE)
winset(client, "tgui_say", "pos=700,500;is-visible=0;")
window.send_message("props", list(
lightMode = FALSE,
maxLength = max_length,
"lightMode" = client?.prefs.tgui_say_light_mode,
"scale" = client?.prefs.ui_scale,
"maxLength" = max_length,
))
stop_thinking()
return TRUE
+3 -3
View File
@@ -19,16 +19,16 @@
// Failed to fix, using tgalert as fallback
action = input(src, "Did that work?", "", "Yes") in list("Yes", "No, switch to old ui")
if (action == "No, switch to old ui")
winset(src, "legacy_output_selector", "left=output_legacy")
winset(src, "output_selector.legacy_output_selector", "left=output_legacy")
log_tgui(src, "Failed to fix.", context = "verb/fix_tgui_panel")
/client/proc/nuke_chat()
// Catch all solution (kick the whole thing in the pants)
winset(src, "legacy_output_selector", "left=output_legacy")
winset(src, "output_selector.legacy_output_selector", "left=output_legacy")
if(!tgui_panel || !istype(tgui_panel))
log_tgui(src, "tgui_panel datum is missing",
context = "verb/fix_tgui_panel")
tgui_panel = new(src)
tgui_panel.initialize(force = TRUE)
// Force show the panel to see if there are any errors
winset(src, "legacy_output_selector", "left=output_browser")
winset(src, "output_selector.legacy_output_selector", "left=output_browser")
+11 -4
View File
@@ -65,12 +65,12 @@
.hisgrace .wrap {border-color: #7C1414;}
.hisgrace .content {color: #15D512; border-color: #9D1414; background-color: #861414;}
/* TG: Themes */
/* ScreenUI */
.midnight .wrap {border-color: #2B2B33;}
.midnight .content {color: #6087A0; border-color: #2B2B33; background-color: #36363C;}
.plasmafire .wrap {border-color: #21213D;}
.plasmafire .content {color: #FFA800 ; border-color: #21213D; background-color:#1D1D36;}
@@ -85,7 +85,7 @@
.clockwork .wrap {border-color: #170800;}
.clockwork .content {color: #B18B25; border-color: #000000; background-color: #5F380E;}
</style>
</head>
@@ -125,7 +125,7 @@
tilesShown = tooltip.client_view_w
realIconSize = mapWidth / tilesShown,
resizeRatio = realIconSize / tooltip.tileSize,
//Calculate letterboxing offsets
//Calculate letterboxing offsets
leftOffset = (map.size.x - mapWidth) / 2,
topOffset = (map.size.y - mapHeight) / 2;
@@ -219,6 +219,13 @@
var docWidth = $wrap.outerWidth(),
docHeight = $wrap.outerHeight();
var pixelRatio = 1;
if (window.devicePixelRatio) {
pixelRatio = window.devicePixelRatio;
}
var docWidth = Math.floor($wrap.outerWidth() * pixelRatio),
docHeight = Math.floor($wrap.outerHeight() * pixelRatio);
if (posY + docHeight > map.size.y) { //Is the bottom edge below the window? Snap it up if so
posY = (posY - docHeight) - realIconSize - tooltip.padding;
@@ -0,0 +1,59 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: GeneralCamo, LemonInTheDark
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- code_imp: "Brought browser code up to standard with the rest of the codebase."
- bugfix: "Repaired several hard dels that were likely occuring with browser datums."
+61
View File
@@ -0,0 +1,61 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: GeneralCamo, harry
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- qol: "Under the chat settings, you can now press \"Adjust UI Sizes\" to drag around some of the panel sizes."
- bugfix: "The input at the bottom right is no longer so teeny tiny on 516 if you use a window scaling that is greater than 100%."
- qol: "There's a new UI preference called UI scale which allows people that use windows scaling to have their UIs original size with the contents zoomed out, instead of the default, which is the UIs being larger with the contents 'normal' size."
- bugfix: "Various UIs did not respect windows scaling, they now do."
@@ -0,0 +1,59 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: GeneralCamo
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- rscadd: "Added a setting to set TGUI Say to light mode."
- bugfix: "Several radio channels that did not define a color in TGUI Say, now do so."
@@ -0,0 +1,58 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: willox, GeneralCamo
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- bugfix: "TGUI and Tooltip windows now work with high-DPI settings."
+40 -17
View File
@@ -3,12 +3,13 @@
set name = "wiki"
set desc = "Visit the wiki."
set hidden = 1
var/wikiurl = GLOB.config.wikiurl
if(GLOB.config.wikiurl)
if(tgui_alert(usr, "This will open the wiki in your browser. Are you sure?", "Wiki", list("Yes", "No")) == "No")
if(wikiurl)
if(tgui_alert(usr, "This will open the wiki in your browser. Are you sure?", "Wiki", list("Yes", "No")) != "Yes")
return
var/to_open = GLOB.config.wikiurl
var/to_open = wikiurl
if (sub_page)
to_open += sub_page
@@ -20,40 +21,62 @@
set name = "forum"
set desc = "Visit the forum."
set hidden = 1
if(GLOB.config.forumurl)
if(tgui_alert(usr, "This will open the forum in your browser. Are you sure?", "Forum", list("Yes", "No")) == "No")
var/forumurl = GLOB.config.forumurl
if(forumurl)
if(tgui_alert(usr, "This will open the forum in your browser. Are you sure?", "Forum", list("Yes", "No")) != "Yes")
return
send_link(src, GLOB.config.forumurl)
send_link(src, forumurl)
else
to_chat(src, SPAN_WARNING("The forum URL is not set in the server configuration."))
return
/client/verb/reportbug()
set name = "reportbug"
set desc = "Report a bug."
set hidden = 1
/client/verb/reportissue()
set name = "report-issue"
set desc = "Report an issue."
set hidden = TRUE
var/githuburl = GLOB.config.githuburl
if(GLOB.config.githuburl)
if(tgui_alert(usr, "This will open the issue tracker in your browser. Are you sure?", "Issue Tracker", list("Yes", "No")) == "No")
var/message = "This will open the GitHub issue reporter in your browser. Are you sure?"
if(githuburl)
// We still use alert here because some people were concerned that if someone wanted to report that tgui wasn't working
// then the report issue button being tgui-based would be problematic.
if(alert(src, message, "Report Issue", "Yes", "No") != "Yes")
return
send_link(src, GLOB.config.githuburl + "/issues")
send_link(src, githuburl + "/issues")
else
to_chat(src, SPAN_WARNING("The issue tracker URL is not set in the server configuration."))
return
/client/verb/rules()
set name = "Rules"
set name = "rules"
set desc = "Show Server Rules."
set hidden = 1
var/rulesurl = GLOB.config.rulesurl
if(GLOB.config.rulesurl)
if(tgui_alert(usr, "This will open the rules in your browser. Are you sure?", "Rules", list("Yes", "No")) == "No")
if(rulesurl)
if(tgui_alert(usr, "This will open the rules in your browser. Are you sure?", "Rules", list("Yes", "No")) != "Yes")
return
send_link(src, GLOB.config.rulesurl)
send_link(src, rulesurl)
else
to_chat(src, SPAN_WARNING("The rules URL is not set in the server configuration."))
return
/client/verb/github()
set name = "github"
set desc = "Visit Github"
set hidden = TRUE
var/githuburl = GLOB.config.githuburl
if(githuburl)
if(tgui_alert(src, "This will open the GitHub repository in your browser. Are you sure?", "GitHub", list("Yes","No")) !="Yes")
return
send_link(src, githuburl)
else
to_chat(src, SPAN_WARNING("The GitHub URL is not set in the server configuration."))
return
/client/verb/hotkeys_help()
set name = "hotkeys-help"
set category = "OOC"
+218 -120
View File
@@ -926,7 +926,7 @@ window "mainwindow"
anchor2 = 100,100
saved-params = "splitter"
left = "mapwindow"
right = "infowindow"
right = "info_and_buttons"
is-vert = true
elem "asset_cache_browser"
type = BROWSER
@@ -968,149 +968,233 @@ window "mapwindow"
style = ".center { text-align: center; } .maptext { font-family: 'Grand9K Pixel'; font-size: 6pt; -dm-text-outline: 1px black; color: white; line-height: 1.0; } .command_headset { font-weight: bold; } .context { font-family: 'Pixellari'; font-size: 12pt; -dm-text-outline: 1px black; } .subcontext { font-family: 'TinyUnicode'; font-size: 12pt; line-height: 0.75; } .small { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; } .big { font-family: 'Pixellari'; font-size: 12pt; } .reallybig { font-size: 12pt; } .extremelybig { font-size: 12pt; } .greentext { color: #00FF00; font-size: 6pt; } .redtext { color: #FF0000; font-size: 6pt; } .clown { color: #FF69BF; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; }"
elem "status_bar"
type = LABEL
pos = 0,464
size = 280x16
anchor1 = 0,100
is-visible = true
pos = 0,470
size = 128x10
anchor1 = 0,99
anchor2 = 20,100
text = ""
align = left
background-color = #222222
text-color = #ffffff
border = line
window "info_and_buttons"
elem "info_and_buttons"
type = MAIN
pos = 0,0
size = 640x480
anchor1 = 0,0
anchor2 = 100,100
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "info_button_child"
type = CHILD
pos = 0,0
size = 640x477
anchor1 = 0,0
anchor2 = 100,100
background-color = #ffc41f
saved-params = "splitter"
left = "infobuttons"
right = "infowindow"
is-vert = false
splitter = 2
show-splitter = false
window "infobuttons"
elem "infobuttons"
type = MAIN
pos = 291,0
size = 640x30
anchor1 = 0,0
anchor2 = 100,100
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "rules"
type = BUTTON
pos = 0,5
size = 87x25
anchor1 = 0,0
anchor2 = 15,100
saved-params = "is-checked"
text = "Rules"
command = "rules"
elem "wiki"
type = BUTTON
pos = 92,5
size = 88x25
anchor1 = 15,0
anchor2 = 29,100
saved-params = "is-checked"
text = "Wiki"
command = "wiki"
elem "forum"
type = BUTTON
pos = 185,5
size = 87x25
anchor1 = 29,0
anchor2 = 43,100
saved-params = "is-checked"
text = "Forum"
command = "forum"
elem "github"
type = BUTTON
pos = 277,5
size = 87x25
anchor1 = 43,0
anchor2 = 57,100
saved-params = "is-checked"
text = "GitHub"
command = "github"
elem "report-issue"
type = BUTTON
pos = 369,5
size = 87x25
anchor1 = 57,0
anchor2 = 71,100
saved-params = "is-checked"
text = "Report Issue"
command = "report-issue"
elem "interface"
type = BUTTON
pos = 461,5
size = 87x25
anchor1 = 71,0
anchor2 = 85,100
saved-params = "is-checked"
text = "Web Interface"
command = "open_webint"
elem "discord"
type = BUTTON
pos = 553,5
size = 89x25
anchor1 = 85,0
anchor2 = 99,100
saved-params = "is-checked"
text = "Discord"
command = "open_discord"
window "infowindow"
elem "infowindow"
type = MAIN
pos = 281,0
size = 640x480
size = 640x475
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
outer-size = 656x538
inner-size = 640x499
elem "info"
type = CHILD
pos = 0,30
size = 640x445
pos = 0,5
size = 640x475
anchor1 = 0,0
anchor2 = 100,100
background-color = none
saved-params = "splitter"
left = "statwindow"
right = "outputwindow"
is-vert = false
elem "wikib"
type = BUTTON
pos = 0,0
size = 60x16
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "is-checked"
text = "Wiki"
command = "wiki"
elem "forumb"
type = BUTTON
pos = 64,0
size = 60x16
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "is-checked"
text = "Forum"
command = "forum"
elem "rulesb"
type = BUTTON
pos = 128,0
size = 60x16
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "is-checked"
text = "Rules"
command = "rules"
elem "changelog"
type = BUTTON
pos = 192,0
size = 67x16
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "is-checked"
text = "Changelog"
command = "Changelog"
elem "reportbugb"
type = BUTTON
pos = 264,0
size = 67x16
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "is-checked"
text = "Report Bug"
command = "reportbug"
elem "interfaceb"
type = BUTTON
pos = 336,0
size = 60x16
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "is-checked"
text = "WI Link"
command = "open_webint"
elem "discordb"
type = BUTTON
pos = 400,0
size = 60x16
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "is-checked"
text = "Discord"
command = "open_discord"
window "outputwindow"
elem "outputwindow"
type = MAIN
pos = 0,0
size = 640x480
size = 640x475
anchor1 = -1,-1
anchor2 = -1,-1
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "output_input_child"
type = CHILD
pos = 0,0
size = 640x475
anchor1 = 0,0
anchor2 = 100,100
background-color = #ffc41f
saved-params = "splitter"
left = "output_selector"
right = "input_and_buttons"
is-vert = false
splitter = 96
show-splitter = false
window "output_selector"
elem "output_selector"
type = MAIN
pos = 0,0
size = 640x475
anchor1 = -1,-1
anchor2 = -1,-1
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "legacy_output_selector"
type = CHILD
pos = 0,0
size = 640x475
anchor1 = 0,0
anchor2 = 100,100
saved-params = "splitter"
left = "output_legacy"
is-vert = false
window "input_and_buttons"
elem "input_and_buttons"
type = MAIN
pos = 291,0
size = 640x20
anchor1 = -1,-1
anchor2 = -1,-1
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "input_buttons_child"
type = CHILD
pos = 0,0
size = 640x20
anchor1 = 0,0
anchor2 = 100,100
background-color = #ffc41f
saved-params = "splitter"
left = "inputwindow"
right = "inputbuttons"
is-vert = true
splitter = 80
show-splitter = false
window "inputwindow"
elem "inputwindow"
type = MAIN
pos = 575,0
size = 520x25
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
outer-size = 656x538
inner-size = 640x499
elem "input"
type = INPUT
pos = 2,460
size = 517x20
anchor1 = 0,100
pos = 0,0
size = 805x20
anchor1 = 0,0
anchor2 = 100,100
is-default = true
border = line
saved-params = "command"
elem "hotkey_toggle"
type = BUTTON
pos = 599,460
size = 80x20
anchor1 = 100,100
anchor2 = -1,-1
saved-params = ""
text = "Hotkey Toggle"
command = ".winset \"mainwindow.macro!=macro ? mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true : mainwindow.macro=hotkeymode hotkey_toggle.is-checked=true mapwindow.map.focus=true\""
is-flat = true
button-type = pushbox
window "inputbuttons"
elem "inputbuttons"
type = MAIN
pos = 291,0
size = 120x25
anchor1 = 0,0
anchor2 = 100,100
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "saybutton"
type = BUTTON
pos = 519,460
size = 40x20
anchor1 = 100,100
anchor2 = -1,-1
background-color = none
pos = 0,0
size = 30x20
anchor1 = 0,0
anchor2 = 25,100
font-size = 4
border = line
saved-params = "is-checked"
text = "Say"
@@ -1119,26 +1203,43 @@ window "outputwindow"
button-type = pushbox
elem "mebutton"
type = BUTTON
pos = 559,460
size = 40x20
anchor1 = 100,100
anchor2 = -1,-1
background-color = none
pos = 30,0
size = 30x20
anchor1 = 25,0
anchor2 = 50,100
font-size = 4
border = line
saved-params = "is-checked"
text = "Me"
command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\""
is-flat = true
button-type = pushbox
elem "legacy_output_selector"
type = CHILD
pos = 0,0
size = 640x456
anchor1 = 0,0
elem "oocbutton"
type = BUTTON
pos = 60,0
size = 30x20
anchor1 = 50,0
anchor2 = 75,100
font-size = 4
border = line
saved-params = "is-checked"
text = "OOC"
command = ".winset \"oocbutton.is-checked=true ? input.command=\"!ooc \\\"\" : input.command=\"\"oocbutton.is-checked=true ? mebutton.is-checked=false\"\"oocbutton.is-checked=true ? saybutton.is-checked=false\""
is-flat = true
button-type = pushbox
elem "hotkey_toggle"
type = BUTTON
pos = 90,0
size = 30x20
anchor1 = 75,0
anchor2 = 100,100
saved-params = "splitter"
left = "output_legacy"
is-vert = false
font-size = 4
border = line
saved-params = "is-checked"
text = "Hotkey Toggle"
command = ".winset \"mainwindow.macro!=macro ? mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true : mainwindow.macro=hotkeymode hotkey_toggle.is-checked=true mapwindow.map.focus=true\""
is-flat = true
button-type = pushbox
window "output_legacy"
elem "output_legacy"
@@ -1147,7 +1248,6 @@ window "output_legacy"
size = 640x456
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "output"
@@ -1165,7 +1265,6 @@ window "output_browser"
size = 640x456
anchor1 = -1,-1
anchor2 = -1,-1
background-color = none
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "browseroutput"
@@ -1174,7 +1273,6 @@ window "output_browser"
size = 640x456
anchor1 = 0,0
anchor2 = 100,100
background-color = none
saved-params = ""
window "preferences_window"
+1 -1
View File
@@ -75,7 +75,7 @@ const setupApp = () => {
Byond.subscribe((type, payload) => store.dispatch({ type, payload }));
// Unhide the panel
Byond.winset('legacy_output_selector', {
Byond.winset('output_selector.legacy_output_selector', {
left: 'output_browser',
});
@@ -13,6 +13,7 @@ import { rebuildChat, saveChatToDisk, clearChatMessages } from '../chat/actions'
import { THEMES } from '../themes';
import { changeSettingsTab, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions';
import { SETTINGS_TABS, FONTS, MAX_HIGHLIGHT_SETTINGS } from './constants';
import { resetPaneSplitters, setEditPaneSplitters } from './scaling';
import { selectActiveTab, selectSettings, selectHighlightSettings, selectHighlightSettingById } from './selectors';
import { IMPL_IFRAME_INDEXED_DB, storage } from 'common/storage';
@@ -57,6 +58,11 @@ export const SettingsGeneral = (props, context) => {
);
const dispatch = useDispatch(context);
const [freeFont, setFreeFont] = useLocalState(context, 'freeFont', false);
const [editingPanes, setEditingPanes] = useLocalState(
context,
'uiScaling',
false
);
return (
<Section>
<LabeledList>
@@ -73,6 +79,28 @@ export const SettingsGeneral = (props, context) => {
}
/>
</LabeledList.Item>
<LabeledList.Item label="UI sizes">
<Stack>
<Stack.Item>
<Button
onClick={() =>
setEditingPanes((val) => {
setEditPaneSplitters(!val);
return !val;
})
}
color={editingPanes ? 'red' : undefined}
icon={editingPanes ? 'save' : undefined}>
{editingPanes ? 'Save' : 'Adjust UI Sizes'}
</Button>
</Stack.Item>
<Stack.Item>
<Button onClick={resetPaneSplitters} icon="refresh" color="red">
Reset
</Button>
</Stack.Item>
</Stack>
</LabeledList.Item>
<LabeledList.Item label="Font style">
<Stack inline align="baseline">
<Stack.Item>
@@ -1,59 +0,0 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { storage } from 'common/storage';
import { setClientTheme } from '../themes';
import { loadSettings, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions';
import { selectSettings } from './selectors';
import { FONTS_DISABLED } from './constants';
const setGlobalFontSize = (fontSize) => {
document.documentElement.style.setProperty('font-size', fontSize + 'px');
document.body.style.setProperty('font-size', fontSize + 'px');
};
const setGlobalFontFamily = (fontFamily) => {
if (fontFamily === FONTS_DISABLED) fontFamily = null;
document.documentElement.style.setProperty('font-family', fontFamily);
document.body.style.setProperty('font-family', fontFamily);
};
export const settingsMiddleware = (store) => {
let initialized = false;
return (next) => (action) => {
const { type, payload } = action;
if (!initialized) {
initialized = true;
storage.get('panel-settings').then((settings) => {
store.dispatch(loadSettings(settings));
});
}
if (
type === updateSettings.type ||
type === loadSettings.type ||
type === addHighlightSetting.type ||
type === removeHighlightSetting.type ||
type === updateHighlightSetting.type
) {
// Set client theme
const theme = payload?.theme;
if (theme) {
setClientTheme(theme);
}
// Pass action to get an updated state
next(action);
const settings = selectSettings(store.getState());
// Update global UI font size
setGlobalFontSize(settings.fontSize);
setGlobalFontFamily(settings.fontFamily);
// Save settings to the web storage
storage.set('panel-settings', settings);
return;
}
return next(action);
};
};
@@ -0,0 +1,124 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { storage } from 'common/storage';
import { setClientTheme } from '../themes';
import { addHighlightSetting, loadSettings, removeHighlightSetting, updateHighlightSetting, updateSettings } from './actions';
import { selectSettings } from './selectors';
import { FONTS_DISABLED } from './constants';
import { setDisplayScaling } from './scaling';
let statFontTimer: NodeJS.Timeout;
let statTabsTimer: NodeJS.Timeout;
let overrideRule: HTMLStyleElement;
let overrideFontFamily: string | undefined;
let overrideFontSize: string;
/** Updates the global CSS rule to override the font family and size. */
function updateGlobalOverrideRule() {
let fontFamily = '';
if (overrideFontFamily !== undefined) {
fontFamily = `font-family: ${overrideFontFamily} !important;`;
}
const constructedRule = `body * :not(.Icon) {
${fontFamily}
}`;
if (overrideRule === undefined) {
overrideRule = document.createElement('style');
document.querySelector('head')?.append(overrideRule);
}
// no other way to force a CSS refresh other than to update its innerText
overrideRule.innerText = constructedRule;
document.body.style.setProperty('font-size', overrideFontSize);
}
function setGlobalFontSize(
fontSize: string,
statFontSize: string,
statLinked: boolean
) {
overrideFontSize = `${fontSize}px`;
// Used solution from theme.ts
clearInterval(statFontTimer);
Byond.command(
`.output statbrowser:set_font_size ${
statLinked ? fontSize : statFontSize
}px`
);
statFontTimer = setTimeout(() => {
Byond.command(
`.output statbrowser:set_font_size ${
statLinked ? fontSize : statFontSize
}px`
);
}, 1500);
}
function setGlobalFontFamily(fontFamily: string) {
overrideFontFamily = fontFamily === FONTS_DISABLED ? undefined : fontFamily;
}
function setStatTabsStyle(style: string) {
clearInterval(statTabsTimer);
Byond.command(`.output statbrowser:set_tabs_style ${style}`);
statTabsTimer = setTimeout(() => {
Byond.command(`.output statbrowser:set_tabs_style ${style}`);
}, 1500);
}
export const settingsMiddleware = (store) => {
let initialized = false;
return (next) => (action) => {
const { type, payload } = action;
if (!initialized) {
initialized = true;
setDisplayScaling();
storage.get('panel-settings').then((settings) => {
store.dispatch(loadSettings(settings));
});
}
if (
type === updateSettings.type ||
type === loadSettings.type ||
type === addHighlightSetting.type ||
type === removeHighlightSetting.type ||
type === updateHighlightSetting.type
) {
// Set client theme
const theme = payload?.theme;
if (theme) {
setClientTheme(theme);
}
// Pass action to get an updated state
next(action);
const settings = selectSettings(store.getState());
// Update stat panel settings
setStatTabsStyle(settings.statTabsStyle);
// Update global UI font size
setGlobalFontSize(
settings.fontSize,
settings.statFontSize,
settings.statLinked
);
setGlobalFontFamily(settings.fontFamily);
updateGlobalOverrideRule();
// Save settings to the web storage
storage.set('panel-settings', settings);
return;
}
return next(action);
};
};
@@ -0,0 +1,59 @@
// This is the elements from the skin.dmf that we need to adjust the fontsize of
const ELEMENTS_TO_ADJUST = [
'infobuttons.rules',
'infobuttons.wiki',
'infobuttons.forum',
'infobuttons.github',
'infobuttons.report-issue',
'infobuttons.interface',
'infobuttons.discord',
'inputwindow.input',
'inputbuttons.saybutton',
'inputbuttons.mebutton',
'inputbuttons.oocbutton',
'inputbuttons.hotkey_toggle',
'mapwindow.status_bar',
];
const DEFAULT_BUTTON_FONT_SIZE = 4;
export async function setDisplayScaling() {
if (window.devicePixelRatio === 1) {
return;
}
const newSizes: { [element: string]: number } = {};
for (const element of ELEMENTS_TO_ADJUST) {
newSizes[`${element}.font-size`] =
DEFAULT_BUTTON_FONT_SIZE * window.devicePixelRatio;
}
Byond.winset(null, newSizes);
}
const PANE_SPLITTERS = {
info_button_child: 2,
input_buttons_child: 80,
output_input_child: 96,
};
export function setEditPaneSplitters(editing: boolean) {
const toSet: { [element: string]: any } = {};
for (const pane of Object.keys(PANE_SPLITTERS)) {
toSet[`${pane}.show-splitter`] = editing;
}
Byond.winset(null, toSet);
}
export function resetPaneSplitters() {
const toSet: { [element: string]: any } = {};
for (const default_obj of Object.entries(PANE_SPLITTERS)) {
toSet[`${default_obj[0]}.splitter`] = default_obj[1];
}
Byond.winset(null, toSet);
}
-128
View File
@@ -1,128 +0,0 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
export const THEMES = ['light', 'dark'];
const COLOR_DARK_BG = '#202020';
const COLOR_DARK_BG_DARKER = '#171717';
const COLOR_DARK_TEXT = '#a4bad6';
let setClientThemeTimer = null;
/**
* Darkmode preference, originally by Kmc2000.
*
* This lets you switch client themes by using winset.
*
* If you change ANYTHING in interface/skin.dmf you need to change it here.
*
* There's no way round it. We're essentially changing the skin by hand.
* It's painful but it works, and is the way Lummox suggested.
*/
export const setClientTheme = (name) => {
// Transmit once for fast updates and again in a little while in case we won
// the race against statbrowser init.
clearInterval(setClientThemeTimer);
Byond.command(`.output statbrowser:set_theme ${name}`);
setClientThemeTimer = setTimeout(() => {
Byond.command(`.output statbrowser:set_theme ${name}`);
}, 1500);
if (name === 'light') {
return Byond.winset({
// Main windows
'mainwindow.background-color': 'none',
'mainwindow.text-color': '#000000',
'info.background-color': 'none',
'info.text-color': '#000000',
'infowindow.background-color': 'none',
'infowindow.text-color': '#000000',
'split.background-color': 'none',
'split.text-color': '#000000',
'browseroutput.background-color': 'none',
'browseroutput.text-color': '#000000',
'outputwindow.background-color': 'none',
'outputwindow.text-color': '#000000',
// Buttons
'changelog.background-color': 'none',
'changelog.text-color': '#000000',
'rulesb.background-color': 'none',
'rulesb.text-color': '#000000',
'wikib.background-color': 'none',
'wikib.text-color': '#000000',
'forumb.background-color': 'none',
'forumb.text-color': '#000000',
'interfaceb.background-color': 'none',
'interfaceb.text-color': '#000000',
'discordb.background-color': 'none',
'discordb.text-color': '#000000',
'reportbugb.background-color': 'none',
'reportbugb.text-color': '#000000',
'hotkey_toggle.background-color': '#494949',
'hotkey_toggle.text-color': '#000000',
// Status and verb tabs
'output.background-color': 'none',
'output.text-color': '#000000',
'statwindow.background-color': 'none',
'statwindow.text-color': '#000000',
// Say, OOC, me Buttons etc.
'asset_cache_browser.background-color': 'none',
'asset_cache_browser.text-color': '#000000',
'tooltip.background-color': 'none',
'tooltip.text-color': '#000000',
'input.background-color': '#FFFFFF',
'input.text-color': '#000000',
});
}
if (name === 'dark') {
Byond.winset({
// Main windows
'mainwindow.background-color': COLOR_DARK_BG,
'mainwindow.text-color': COLOR_DARK_TEXT,
'info.background-color': COLOR_DARK_BG,
'info.text-color': COLOR_DARK_TEXT,
'infowindow.background-color': COLOR_DARK_BG,
'infowindow.text-color': COLOR_DARK_TEXT,
'split.background-color': COLOR_DARK_BG,
'split.text-color': COLOR_DARK_TEXT,
'browseroutput.background-color': COLOR_DARK_BG,
'browseroutput.text-color': COLOR_DARK_TEXT,
'outputwindow.background-color': COLOR_DARK_BG,
'outputwindow.text-color': COLOR_DARK_TEXT,
// Buttons
'changelog.background-color': '#494949',
'changelog.text-color': COLOR_DARK_TEXT,
'rulesb.background-color': '#494949',
'rulesb.text-color': COLOR_DARK_TEXT,
'wikib.background-color': '#494949',
'wikib.text-color': COLOR_DARK_TEXT,
'forumb.background-color': '#494949',
'forumb.text-color': COLOR_DARK_TEXT,
'discordb.background-color': '#494949',
'discordb.text-color': COLOR_DARK_TEXT,
'interfaceb.background-color': '#494949',
'interfaceb.text-color': COLOR_DARK_TEXT,
'reportbugb.background-color': '#492020',
'reportbugb.text-color': COLOR_DARK_TEXT,
'hotkey_toggle.background-color': '#492020',
'hotkey_toggle.text-color': COLOR_DARK_TEXT,
// Status and verb tabs
'output.background-color': COLOR_DARK_BG_DARKER,
'output.text-color': COLOR_DARK_TEXT,
'statwindow.background-color': COLOR_DARK_BG_DARKER,
'statwindow.text-color': COLOR_DARK_TEXT,
// Say, OOC, me Buttons etc.
'saybutton.background-color': COLOR_DARK_BG,
'saybutton.text-color': COLOR_DARK_TEXT,
'asset_cache_browser.background-color': COLOR_DARK_BG,
'asset_cache_browser.text-color': COLOR_DARK_TEXT,
'tooltip.background-color': COLOR_DARK_BG,
'tooltip.text-color': COLOR_DARK_TEXT,
'input.background-color': COLOR_DARK_BG_DARKER,
'input.text-color': COLOR_DARK_TEXT,
});
}
};
+103
View File
@@ -0,0 +1,103 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
export const THEMES = ['light', 'dark'];
const COLORS = {
DARK: {
BG_BASE: '#212020',
BG_SECOND: '#161515',
BUTTON: '#414040',
TEXT: '#A6A6A6',
// AURORA SNOWFLAKE
HOTKEY_BACKGROUND: '#492020',
},
LIGHT: {
BG_BASE: '#EFEEEE',
BG_SECOND: '#FFFFFF',
BUTTON: '#FFFEFE',
TEXT: '#000000',
// AURORA SNOWFLAKE
HOTKEY_BACKGROUND: '#494949',
},
};
let setClientThemeTimer: NodeJS.Timeout;
/**
* Darkmode preference, originally by Kmc2000.
*
* This lets you switch client themes by using winset.
*
* If you change ANYTHING in interface/skin.dmf you need to change it here.
*
* There's no way round it. We're essentially changing the skin by hand.
* It's painful but it works, and is the way Lummox suggested.
*/
export const setClientTheme = (name) => {
// Transmit once for fast updates and again in a little while in case we won
// the race against statbrowser init.
clearInterval(setClientThemeTimer);
Byond.command(`.output statbrowser:set_theme ${name}`);
setClientThemeTimer = setTimeout(() => {
Byond.command(`.output statbrowser:set_theme ${name}`);
}, 1500);
const themeColor = COLORS[name.toUpperCase()];
if (!themeColor) {
return;
}
return Byond.winset({
// Main windows
'infobuttons.background-color': themeColor.BG_BASE,
'infobuttons.text-color': themeColor.TEXT,
'infowindow.background-color': themeColor.BG_BASE,
'infowindow.text-color': themeColor.TEXT,
'info_and_buttons.background-color': themeColor.BG_BASE,
'info.background-color': themeColor.BG_BASE,
'info.text-color': themeColor.TEXT,
'browseroutput.background-color': themeColor.BG_BASE,
'browseroutput.text-color': themeColor.TEXT,
'outputwindow.background-color': themeColor.BG_BASE,
'outputwindow.text-color': themeColor.TEXT,
'mainwindow.background-color': themeColor.BG_BASE,
'split.background-color': themeColor.BG_BASE,
// Buttons
'rules.background-color': themeColor.BUTTON,
'rules.text-color': themeColor.TEXT,
'wiki.background-color': themeColor.BUTTON,
'wiki.text-color': themeColor.TEXT,
'forum.background-color': themeColor.BUTTON,
'forum.text-color': themeColor.TEXT,
'discord.background-color': themeColor.BUTTON,
'discord.text-color': themeColor.TEXT,
'interface.background-color': themeColor.BUTTON,
'interface.text-color': themeColor.TEXT,
'github.background-color': themeColor.BUTTON,
'github.text-color': themeColor.TEXT,
'report-issue.background-color': themeColor.BUTTON,
'report-issue.text-color': themeColor.TEXT,
// Status and verb tabs
'output.background-color': themeColor.BG_BASE,
'output.text-color': themeColor.TEXT,
// Say, OOC, me Buttons etc.
'saybutton.background-color': themeColor.BG_BASE,
'saybutton.text-color': themeColor.TEXT,
'oocbutton.background-color': themeColor.BG_BASE,
'oocbutton.text-color': themeColor.TEXT,
'mebutton.background-color': themeColor.BG_BASE,
'mebutton.text-color': themeColor.TEXT,
'hotkey_toggle.background-color': themeColor.HOTKEY_BACKGROUND,
'hotkey_toggle.text-color': themeColor.TEXT,
'asset_cache_browser.background-color': themeColor.BG_BASE,
'asset_cache_browser.text-color': themeColor.TEXT,
'tooltip.background-color': themeColor.BG_BASE,
'tooltip.text-color': themeColor.TEXT,
'input.background-color': themeColor.BG_SECOND,
'input.text-color': themeColor.TEXT,
});
};
+52 -29
View File
@@ -10,11 +10,13 @@ import { isEscape, KEY } from 'common/keys';
type ByondOpen = {
channel: Channel;
mapfocus: BooleanLike;
};
type ByondProps = {
maxLength: number;
lightMode: BooleanLike;
scale: BooleanLike;
};
type State = {
@@ -24,19 +26,13 @@ type State = {
const CHANNEL_REGEX = /^[:.]\w\s/;
const ROWS: Record<keyof typeof WINDOW_SIZES, number> = {
small: 1,
medium: 2,
large: 3,
width: 1, // not used
} as const;
export class TguiSay extends Component<{}, State> {
private channelIterator: ChannelIterator;
private chatHistory: ChatHistory;
private currentPrefix: keyof typeof RADIO_PREFIXES | null;
private innerRef: RefObject<HTMLTextAreaElement>;
private lightMode: boolean;
private scale: boolean;
private maxLength: number;
private messages: typeof byondMessages;
state: State;
@@ -72,6 +68,8 @@ export class TguiSay extends Component<{}, State> {
}
componentDidMount() {
windowSet(WINDOW_SIZES.small, this.scale);
Byond.subscribeTo('props', this.handleProps);
Byond.subscribeTo('force', this.handleForceSay);
Byond.subscribeTo('open', this.handleOpen);
@@ -140,7 +138,7 @@ export class TguiSay extends Component<{}, State> {
this.chatHistory.reset();
this.channelIterator.reset();
this.currentPrefix = null;
windowClose();
windowClose(this.scale);
}
handleEnter() {
@@ -260,24 +258,38 @@ export class TguiSay extends Component<{}, State> {
}
handleOpen = (data: ByondOpen) => {
const { channel, mapfocus } = data;
if (!mapfocus) {
return;
}
setTimeout(() => {
this.innerRef.current?.focus();
}, 0);
const { channel } = data;
// Catches the case where the modal is already open
if (this.channelIterator.isSay()) {
this.channelIterator.set(channel);
}
this.setState({ buttonContent: this.channelIterator.current() });
windowOpen(this.channelIterator.current());
windowOpen(this.channelIterator.current(), this.scale);
};
handleProps = (data: ByondProps) => {
const { maxLength, lightMode } = data;
const { maxLength, lightMode, scale } = data;
this.maxLength = maxLength;
this.lightMode = !!lightMode;
this.scale = !!scale;
if (!this.scale) {
window.document.body.style.setProperty(
'zoom',
`${100 / window.devicePixelRatio}%`
);
} else {
window.document.body.style.setProperty('zoom', '');
}
};
reset() {
@@ -301,7 +313,7 @@ export class TguiSay extends Component<{}, State> {
if (this.state.size !== newSize) {
this.setState({ size: newSize });
windowSet(newSize);
windowSet(newSize, this.scale);
}
}
@@ -319,30 +331,35 @@ export class TguiSay extends Component<{}, State> {
this.channelIterator.current();
return (
<div
className={`window window-${theme} window-${this.state.size}`}
$HasKeyedChildren>
<div className={`window window-${theme} window-${this.state.size}`}>
<Dragzone position="top" theme={theme} />
<div className="center" $HasKeyedChildren>
<div className="center">
<Dragzone position="left" theme={theme} />
<div className="input" $HasKeyedChildren>
{!!theme && (
<button
className={`button button-${theme}`}
onClick={this.handleIncrementChannel}
type="button">
{this.state.buttonContent}
</button>
<textarea
autoCorrect="off"
className={`textarea textarea-${theme}`}
maxLength={this.maxLength}
onInput={this.handleInput}
onKeyDown={this.handleKeyDown}
ref={this.innerRef}
spellCheck={false}
rows={ROWS[this.state.size] || 1}
/>
</div>
)}
<textarea
className={`textarea textarea-${theme}`}
maxLength={this.maxLength}
onInput={this.handleInput}
onKeyDown={this.handleKeyDown}
ref={this.innerRef}
/>
{!!theme && (
<button
key="escape"
className={`button button-${theme}`}
onClick={this.handleClose}
type="submit"
style={{ width: '2rem', marginRight: '5px' }}>
X
</button>
)}
<Dragzone position="right" theme={theme} />
</div>
<Dragzone position="bottom" theme={theme} />
@@ -351,7 +368,13 @@ export class TguiSay extends Component<{}, State> {
}
}
const Dragzone = ({ theme, position }: { theme: string; position: string }) => {
const Dragzone = ({
theme,
position,
}: {
readonly theme: string;
readonly position: string;
}) => {
// Horizontal or vertical?
const location =
position === 'left' || position === 'right' ? 'vertical' : 'horizontal';
+22 -10
View File
@@ -5,8 +5,8 @@ import { WINDOW_SIZES } from './constants';
* Once byond signals this via keystroke, it
* ensures window size, visibility, and focus.
*/
export const windowOpen = (channel: Channel) => {
setWindowVisibility(true);
export const windowOpen = (channel: Channel, scale: boolean) => {
setWindowVisibility(true, scale);
Byond.sendMessage('open', { channel });
};
@@ -14,8 +14,8 @@ export const windowOpen = (channel: Channel) => {
* Resets the state of the window and hides it from user view.
* Sending "close" logs it server side.
*/
export const windowClose = () => {
setWindowVisibility(false);
export const windowClose = (scale: boolean) => {
setWindowVisibility(false, scale);
Byond.winset('map', {
focus: true,
});
@@ -25,22 +25,34 @@ export const windowClose = () => {
/**
* Modifies the window size.
*/
export const windowSet = (size = WINDOW_SIZES.small) => {
let sizeStr = `${WINDOW_SIZES.width}x${size}`;
export const windowSet = (size = WINDOW_SIZES.small, scale: boolean) => {
const pixelRatio = scale ? window.devicePixelRatio : 1;
Byond.winset('tgui_say.browser', {
const sizeStr = `${WINDOW_SIZES.width * pixelRatio}x${size * pixelRatio}`;
Byond.winset('tgui_say', {
size: sizeStr,
});
Byond.winset('tgui_say', {
Byond.winset('tgui_say.browser', {
size: sizeStr,
});
};
/** Helper function to set window size and visibility */
const setWindowVisibility = (visible: boolean) => {
const setWindowVisibility = (visible: boolean, scale: boolean) => {
const pixelRatio = scale ? window.devicePixelRatio : 1;
const sizeString = `${WINDOW_SIZES.width * pixelRatio}x${
WINDOW_SIZES.small * pixelRatio
}`;
Byond.winset('tgui_say', {
'is-visible': visible,
size: `${WINDOW_SIZES.width}x${WINDOW_SIZES.small}`,
size: sizeString,
});
Byond.winset('tgui_say.browser', {
size: sizeString,
});
};
+10 -8
View File
@@ -2,18 +2,19 @@
@use './colors.scss';
.button {
align-items: center;
background-color: colors.$button;
border-radius: 0.3rem;
border: thin solid;
display: flex;
flex-grow: 1;
font-family: inherit;
font-size: 0.9rem;
border-radius: 2px;
color: colors.$background;
font-family: 'Consolas', monospace;
font-weight: bold;
justify-content: center;
font-size: 0.9rem;
height: 100%;
padding: 0;
width: 2.6rem;
text-align: center;
vertical-align: middle;
width: 5rem;
&:hover {
background-color: lighten(colors.$button, 10%);
}
@@ -30,6 +31,7 @@
background-color: colors.$lightBorder;
border: none;
color: black;
&:hover {
background-color: colors.$lightHover;
}
@@ -13,6 +13,7 @@ $say: #a4bad6;
$radio: #1ecc43;
$me: #5975da;
$ooc: #cca300;
$looc: #e362b4;
////////////////////////////////////////////////
// Subchannel chat colors
@@ -31,12 +32,15 @@ $service: #7fc732;
$supply: #c09141;
$hail: #8b4cd8;
$ent: #cfcfcf;
$cling: #376340;
$_channel_map: (
'Say': $say,
'Comm': $radio,
'Radio': $radio,
'Me': $me,
'OOC': $ooc,
'LOOC': $looc,
'AI': $ai,
'io': $binary,
'Cmd': $command,
@@ -44,11 +48,14 @@ $_channel_map: (
'Med': $medical,
'Sci': $science,
'Sec': $security,
'Merc': $syndicate,
'Synd': $syndicate,
'Pen': $syndicate,
'Svc': $service,
'Supp': $supply,
'Hail': $hail,
'Ent': $ent,
'Cling': $cling,
);
$channel_keys: map.keys($_channel_map) !default;
@@ -10,5 +10,4 @@
flex: 1 1 0;
height: 100%;
width: 100%;
font-family: 'Consolas', monospace;
}
+18 -3
View File
@@ -1,27 +1,42 @@
@use 'sass:color';
@use './colors.scss';
$dragSize: 0.6rem;
$borderSize: 0.2rem;
// Remove conditionals with 516
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
$dragSize: 0.3rem;
}
// Remove with 516
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
$dragSize: 0.6rem;
}
$dragSize: 0.3rem;
$borderSize: 2px;
.dragzone-horizontal {
border-left: $borderSize solid;
border-right: $borderSize solid;
color: transparent;
width: 100%;
height: $dragSize;
width: 100%;
}
.dragzone-left {
border-left: $borderSize solid;
left: 0;
}
.dragzone-right {
border-right: $borderSize solid;
right: 0;
}
.dragzone-vertical {
color: transparent;
height: 100%;
position: absolute;
top: 0;
width: $dragSize;
}
+45 -5
View File
@@ -1,11 +1,51 @@
@use 'sass:color';
@use './colors.scss';
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
* {
&::-webkit-scrollbar {
width: 0.8rem;
}
&::-webkit-scrollbar-track {
background: color.scale(
colors.$button,
$lightness: -25% * colors.$scrollbar-color-multiplier
);
}
&::-webkit-scrollbar-thumb {
background: color.scale(
colors.$button,
$lightness: 10% * colors.$scrollbar-color-multiplier
);
}
}
}
.textarea {
align-items: center;
background: transparent;
border: none;
display: flex;
flex-grow: 4;
font-family: inherit;
font-size: 1.1rem;
overflow: hidden;
margin: 0.1rem 0 0 0.4rem;
}
// Remove conditionals with 516
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
.textarea {
flex-grow: 8;
outline: none;
resize: none;
}
}
// Remove with 516
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.textarea {
align-items: center;
display: flex;
flex-grow: 4;
overflow: hidden;
margin: 0.1rem 0 0 0.4rem;
}
}
+15 -2
View File
@@ -6,15 +6,28 @@
display: flex;
flex-direction: column;
max-width: 380px;
height: 100%;
width: 100%;
overflow: hidden;
}
// Remove with 516
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.window {
height: 100%;
width: 100%;
}
}
.window-lightMode {
background-color: colors.$lightMode;
}
.window__content {
display: flex;
flex-direction: row;
height: 100%;
padding: 1px 0 1px 5px;
}
/** Window sizes */
.window-30 {
height: 30px;
+1
View File
@@ -244,6 +244,7 @@ type BackendState<TData> = {
size: [number, number];
fancy: boolean;
locked: boolean;
scale: boolean;
};
client: {
ckey: string;
+25 -8
View File
@@ -42,14 +42,14 @@ export const getWindowSize = (): [number, number] => [
const setWindowPosition = (vec: [number, number]) => {
const byondPos = vecAdd(vec, screenOffset);
return Byond.winset(Byond.windowId, {
pos: byondPos[0] + ',' + byondPos[1],
pos: `${byondPos[0]},${byondPos[1]}`,
});
};
// Set window size
const setWindowSize = (vec: [number, number]) => {
return Byond.winset(Byond.windowId, {
size: vec[0] + 'x' + vec[1],
size: `${vec[0]}x${vec[1]}`,
});
};
@@ -120,6 +120,7 @@ export const recallWindowGeometry = async (
pos?: [number, number];
size?: [number, number];
locked?: boolean;
scale?: boolean;
} = {}
) => {
const geometry = options.fancy && (await storage.get(windowKey));
@@ -130,9 +131,22 @@ export const recallWindowGeometry = async (
let pos = geometry?.pos || options.pos;
let size = options.size;
// Convert size from css-pixels to display-pixels
if (size) {
if (options.scale && size) {
size = [size[0] * pixelRatio, size[1] * pixelRatio];
}
if (!options.scale) {
document.body.style.setProperty(
'zoom',
`${100 / window.devicePixelRatio}%`
);
document.documentElement.style.setProperty(
'--scaling-amount',
window.devicePixelRatio.toString()
);
} else {
document.body.style.setProperty('zoom', '');
document.documentElement.style.setProperty('--scaling-amount', null);
}
// Wait until screen offset gets resolved
await screenOffsetPromise;
const areaAvailable = getScreenSize();
@@ -166,7 +180,7 @@ export const recallWindowGeometry = async (
// Setup draggable window
export const setupDrag = async () => {
// Calculate screen offset caused by the windows taskbar
let windowPosition = getWindowPosition();
const windowPosition = getWindowPosition();
screenOffsetPromise = Byond.winget(Byond.windowId, 'pos').then((pos) => [
pos.x - windowPosition[0],
@@ -207,7 +221,7 @@ export const dragStartHandler = (event: MouseEvent) => {
logger.log('drag start');
dragging = true;
dragPointOffset = vecSubtract(
[event.screenX, event.screenY],
[event.screenX * pixelRatio, event.screenY * pixelRatio],
getWindowPosition()
);
// Focus click target
@@ -234,7 +248,10 @@ const dragMoveHandler = (event: MouseEvent) => {
}
event.preventDefault();
setWindowPosition(
vecSubtract([event.screenX, event.screenY], dragPointOffset)
vecSubtract(
[event.screenX * pixelRatio, event.screenY * pixelRatio],
dragPointOffset
) as [number, number]
);
};
@@ -245,7 +262,7 @@ export const resizeStartHandler =
logger.log('resize start', resizeMatrix);
resizing = true;
dragPointOffset = vecSubtract(
[event.screenX, event.screenY],
[event.screenX * pixelRatio, event.screenY * pixelRatio],
getWindowPosition()
);
initialSize = getWindowSize();
@@ -273,7 +290,7 @@ const resizeMoveHandler = (event: MouseEvent) => {
}
event.preventDefault();
const currentOffset = vecSubtract(
[event.screenX, event.screenY],
[event.screenX * pixelRatio, event.screenY * pixelRatio],
getWindowPosition()
);
const delta = vecSubtract(currentOffset, dragPointOffset);
+2 -2
View File
@@ -201,9 +201,9 @@ export class KeyEvent {
if (this.code >= 48 && this.code <= 90) {
this._str += String.fromCharCode(this.code);
} else if (this.code >= KEY_F1 && this.code <= KEY_F12) {
this._str += 'F' + (this.code - 111);
this._str += `F${this.code - 111}`;
} else {
this._str += '[' + this.code + ']';
this._str += `[${this.code}]`;
}
return this._str;
}
@@ -10,6 +10,7 @@ $text-color: base.$color-fg !default;
$background-color: #0a0a0a !default;
$border-color: #88bfff !default;
$border-radius: base.$border-radius !default;
$font-family: Verdana, sans-serif !default;
.Input {
position: relative;
@@ -51,11 +52,17 @@ $border-radius: base.$border-radius !default;
height: base.em(17px);
margin: 0;
padding: 0 0.5em;
font-family: Verdana, sans-serif;
font-family: $font-family;
background-color: transparent;
color: $text-color;
color: inherit;
&::placeholder {
font-style: italic;
color: #777;
color: rgba(255, 255, 255, 0.45);
}
&:-ms-input-placeholder {
font-style: italic;
color: #777;
@@ -65,6 +65,12 @@ $border-radius: Input.$border-radius !default;
word-wrap: break-word;
overflow: hidden;
&::placeholder {
font-style: italic;
color: #777;
color: rgba(255, 255, 255, 0.45);
}
&:-ms-input-placeholder {
font-style: italic;
color: #777;
+6
View File
@@ -76,3 +76,9 @@
color.alpha($color-rgba) * 100%
);
}
// Multiplies our viewport units by the DPI scaling amount
// to ensure that they display correctly when using differently-scaled windows
@function vp($viewportUnit) {
@return calc(var(--scaling-amount) * $viewportUnit);
}
+5
View File
@@ -69,3 +69,8 @@
background-position: center;
background-repeat: no-repeat;
}
// A default, in case someone uses the vp() function outside of a <Window>
:root {
--scaling-amount: 1;
}