From 45139cea5c500eb24aef6325a62b65c10a1e245d Mon Sep 17 00:00:00 2001 From: Cody Brittain <1779662+Generalcamo@users.noreply.github.com> Date: Wed, 14 May 2025 08:39:41 -0400 Subject: [PATCH] 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. --- SQL/migrate-2023/V014__ui_scale_option.sql | 2 + code/__DEFINES/tgui.dm | 6 + code/datums/browser.dm | 214 ++++++----- code/modules/admin/admin.dm | 8 +- code/modules/admin/verbs/access_control.dm | 2 +- code/modules/client/client_defines.dm | 5 + code/modules/client/client_procs.dm | 17 + .../client/preference_setup/global/01_ui.dm | 24 +- code/modules/client/preferences.dm | 2 + code/modules/clothing/under/miscellaneous.dm | 1 + code/modules/error_handler/error_viewer.dm | 2 +- code/modules/tgui/tgui.dm | 1 + code/modules/tgui_input/say_modal/modal.dm | 15 +- code/modules/tgui_panel/external.dm | 6 +- code/modules/tooltip/tooltip.html | 15 +- html/changelogs/GeneralCamo - Browser Fix.yml | 59 +++ html/changelogs/GeneralCamo - DPI Fix.yml | 61 ++++ .../GeneralCamo - TGUI Say Light Mode.yml | 59 +++ html/changelogs/GeneralCamo - Tooltip Fix.yml | 58 +++ interface/interface.dm | 57 ++- interface/skin.dmf | 338 +++++++++++------- tgui/packages/tgui-panel/index.js | 2 +- .../tgui-panel/settings/SettingsPanel.js | 28 ++ .../tgui-panel/settings/middleware.js | 59 --- .../tgui-panel/settings/middleware.ts | 124 +++++++ tgui/packages/tgui-panel/settings/scaling.ts | 59 +++ tgui/packages/tgui-panel/themes.js | 128 ------- tgui/packages/tgui-panel/themes.ts | 103 ++++++ tgui/packages/tgui-say/TguiSay.tsx | 81 +++-- tgui/packages/tgui-say/helpers.ts | 32 +- tgui/packages/tgui-say/styles/button.scss | 18 +- tgui/packages/tgui-say/styles/colors.scss | 7 + tgui/packages/tgui-say/styles/content.scss | 1 - tgui/packages/tgui-say/styles/dragzone.scss | 21 +- tgui/packages/tgui-say/styles/textarea.scss | 50 ++- tgui/packages/tgui-say/styles/window.scss | 17 +- tgui/packages/tgui/backend.ts | 1 + tgui/packages/tgui/drag.ts | 33 +- tgui/packages/tgui/events.ts | 4 +- .../tgui/styles/components/Input.scss | 9 +- .../tgui/styles/components/TextArea.scss | 6 + tgui/packages/tgui/styles/functions.scss | 6 + tgui/packages/tgui/styles/main.scss | 5 + 43 files changed, 1248 insertions(+), 498 deletions(-) create mode 100644 SQL/migrate-2023/V014__ui_scale_option.sql create mode 100644 html/changelogs/GeneralCamo - Browser Fix.yml create mode 100644 html/changelogs/GeneralCamo - DPI Fix.yml create mode 100644 html/changelogs/GeneralCamo - TGUI Say Light Mode.yml create mode 100644 html/changelogs/GeneralCamo - Tooltip Fix.yml delete mode 100644 tgui/packages/tgui-panel/settings/middleware.js create mode 100644 tgui/packages/tgui-panel/settings/middleware.ts create mode 100644 tgui/packages/tgui-panel/settings/scaling.ts delete mode 100644 tgui/packages/tgui-panel/themes.js create mode 100644 tgui/packages/tgui-panel/themes.ts diff --git a/SQL/migrate-2023/V014__ui_scale_option.sql b/SQL/migrate-2023/V014__ui_scale_option.sql new file mode 100644 index 00000000000..95a7aef1e29 --- /dev/null +++ b/SQL/migrate-2023/V014__ui_scale_option.sql @@ -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`; diff --git a/code/__DEFINES/tgui.dm b/code/__DEFINES/tgui.dm index 02d6a25a660..db68d7089a6 100644 --- a/code/__DEFINES/tgui.dm +++ b/code/__DEFINES/tgui.dm @@ -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" \ +) diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 5ca494be4b1..c9d277230eb 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -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 += "" + var/list/new_head_content = list() + for (var/file as anything in stylesheets) + new_head_content += "" - for (file in scripts) - head_content += "" + if(user.client?.window_scaling && user.client?.window_scaling != 1 && !user.client?.prefs.ui_scale && width && height) + new_head_content += {" + + "} + + for (var/file as anything in scripts) + new_head_content += "" 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 {" - - + + [head_content] @@ -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() diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index edc430bf06d..27994791a39 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -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 = "Options for [M.key]" body += "Options panel for [M]" if(M.client) @@ -219,7 +221,11 @@ var/global/enabled_spooking = 0 "} - 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! diff --git a/code/modules/admin/verbs/access_control.dm b/code/modules/admin/verbs/access_control.dm index 01d88f06bfe..5e8e82044d0 100644 --- a/code/modules/admin/verbs/access_control.dm +++ b/code/modules/admin/verbs/access_control.dm @@ -6,7 +6,7 @@ return var/datum/browser/config_window = new(usr, "access_control", "Access Control") - config_window.add_head_content("Access Control") + config_window.set_head_content("Access Control") var/data = "These settings control who can access the server during this round.
" data += "They must be reset every single time the server restarts.
" diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 1ddb7091c2a..08a24cd3a29 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -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 diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 50d220ba991..15957d821ca 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -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 here 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")) diff --git a/code/modules/client/preference_setup/global/01_ui.dm b/code/modules/client/preference_setup/global/01_ui.dm index e58421f1c6c..5de55749c4f 100644 --- a/code/modules/client/preference_setup/global/01_ui.dm +++ b/code/modules/client/preference_setup/global/01_ui.dm @@ -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 += "TGUI Inputs: [pref.tgui_inputs ? "ON" : "OFF"]
" dat += "TGUI Input Large Buttons: [pref.tgui_buttons_large ? "ON" : "OFF"]
" dat += "TGUI Input Swapped Buttons: [pref.tgui_inputs_swapped ? "ON" : "OFF"]
" + dat += "TGUI Say Light Mode: [pref.tgui_say_light_mode ? "ON" : "OFF"]
" + dat += "UI Scaling: [pref.ui_scale ? "ON" : "OFF"]
" dat += "FPS: [pref.clientfps] - reset
" if(can_select_ooc_color(user)) dat += "OOC Color: " @@ -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)) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 086ae994a3c..94bd9094529 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -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" diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 5ac1adc4be0..94053546048 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -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( diff --git a/code/modules/error_handler/error_viewer.dm b/code/modules/error_handler/error_viewer.dm index 1f10e79fbd1..b60390424e5 100644 --- a/code/modules/error_handler/error_viewer.dm +++ b/code/modules/error_handler/error_viewer.dm @@ -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({" @@ -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; diff --git a/html/changelogs/GeneralCamo - Browser Fix.yml b/html/changelogs/GeneralCamo - Browser Fix.yml new file mode 100644 index 00000000000..557ffa89a0f --- /dev/null +++ b/html/changelogs/GeneralCamo - Browser Fix.yml @@ -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." diff --git a/html/changelogs/GeneralCamo - DPI Fix.yml b/html/changelogs/GeneralCamo - DPI Fix.yml new file mode 100644 index 00000000000..61e54ad8876 --- /dev/null +++ b/html/changelogs/GeneralCamo - DPI Fix.yml @@ -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." diff --git a/html/changelogs/GeneralCamo - TGUI Say Light Mode.yml b/html/changelogs/GeneralCamo - TGUI Say Light Mode.yml new file mode 100644 index 00000000000..1426201c796 --- /dev/null +++ b/html/changelogs/GeneralCamo - TGUI Say Light Mode.yml @@ -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." diff --git a/html/changelogs/GeneralCamo - Tooltip Fix.yml b/html/changelogs/GeneralCamo - Tooltip Fix.yml new file mode 100644 index 00000000000..5c66877be3c --- /dev/null +++ b/html/changelogs/GeneralCamo - Tooltip Fix.yml @@ -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." diff --git a/interface/interface.dm b/interface/interface.dm index 9d133cae964..0c312cdc78b 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -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" diff --git a/interface/skin.dmf b/interface/skin.dmf index 9620c3e3447..92d1e7bd2b6 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -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" diff --git a/tgui/packages/tgui-panel/index.js b/tgui/packages/tgui-panel/index.js index 297ecd85852..78807f5b408 100644 --- a/tgui/packages/tgui-panel/index.js +++ b/tgui/packages/tgui-panel/index.js @@ -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', }); diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.js b/tgui/packages/tgui-panel/settings/SettingsPanel.js index d16b7cd2af6..354d0f741e2 100644 --- a/tgui/packages/tgui-panel/settings/SettingsPanel.js +++ b/tgui/packages/tgui-panel/settings/SettingsPanel.js @@ -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 (
@@ -73,6 +79,28 @@ export const SettingsGeneral = (props, context) => { } /> + + + + + + + + + + diff --git a/tgui/packages/tgui-panel/settings/middleware.js b/tgui/packages/tgui-panel/settings/middleware.js deleted file mode 100644 index 705d7a89f3b..00000000000 --- a/tgui/packages/tgui-panel/settings/middleware.js +++ /dev/null @@ -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); - }; -}; diff --git a/tgui/packages/tgui-panel/settings/middleware.ts b/tgui/packages/tgui-panel/settings/middleware.ts new file mode 100644 index 00000000000..8a32c5fe2c4 --- /dev/null +++ b/tgui/packages/tgui-panel/settings/middleware.ts @@ -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); + }; +}; diff --git a/tgui/packages/tgui-panel/settings/scaling.ts b/tgui/packages/tgui-panel/settings/scaling.ts new file mode 100644 index 00000000000..dd31cecc75c --- /dev/null +++ b/tgui/packages/tgui-panel/settings/scaling.ts @@ -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); +} diff --git a/tgui/packages/tgui-panel/themes.js b/tgui/packages/tgui-panel/themes.js deleted file mode 100644 index 85eeefea7ce..00000000000 --- a/tgui/packages/tgui-panel/themes.js +++ /dev/null @@ -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, - }); - } -}; diff --git a/tgui/packages/tgui-panel/themes.ts b/tgui/packages/tgui-panel/themes.ts new file mode 100644 index 00000000000..4e083e29ae1 --- /dev/null +++ b/tgui/packages/tgui-panel/themes.ts @@ -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, + }); +}; diff --git a/tgui/packages/tgui-say/TguiSay.tsx b/tgui/packages/tgui-say/TguiSay.tsx index cb7b5f75011..16c2300bae3 100644 --- a/tgui/packages/tgui-say/TguiSay.tsx +++ b/tgui/packages/tgui-say/TguiSay.tsx @@ -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 = { - 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; 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 ( -
+
-
+
-
+ {!!theme && ( -